当前位置:网站首页>[analysis notes] source code analysis of siliconlabs efr32bg22 Bluetooth mesh sensorclient
[analysis notes] source code analysis of siliconlabs efr32bg22 Bluetooth mesh sensorclient
2022-06-13 02:05:00 【lovemengx】
Hardware environment : SLTB010A(BRD4184A Rev A02 / EFR32BG22C224F512IM40)
Software environment : SimplicityStudio5/gecko_sdk_3.2.3
Analytical engineering : Bluetooth Mesh SensorClient
It's a bad remedy BluetoothMesh Related knowledge , For the first time contact SiliconLabs chip , Search the whole network , There are so few Chinese materials , Another person has been gnawing at many official documents in English , Understand the burning 、 compile 、 Debugging and other basic knowledge , But the way of software development is still in the clouds , Then start to analyze the source code to understand SiliconLabs Source code framework , Finally, I know how to play with this chip .
This chapter mainly analyzes the problems after the equipment has been connected to the network SensorClinet Power on operation process , It should be the only one in the whole network .
The code structure :
main There is one while(1), Keep querying and getting from the event queue mesh event , And then we can do different processing according to different events .
void sl_btmesh_step(void)
{
sl_btmesh_msg_t evt;
uint32_t event_len = sl_btmesh_event_pending_len();
// For preventing from data loss, the event will be kept in the stack's queue
// if application cannot process it at the moment.
if ((event_len == 0) || (!sl_btmesh_can_process_event(event_len))) {
return;
}
// Pop (non-blocking) a Bluetooth stack event from event queue.
sl_status_t status = sl_btmesh_pop_event(&evt);
if(status != SL_STATUS_OK){
return;
}
sl_btmesh_process_event(&evt);
}
void sl_btmesh_process_event(sl_btmesh_msg_t *evt)
{
sl_btmesh_handle_btmesh_logging_events(evt);
sl_btmesh_handle_provisioning_decorator_event(evt);
sl_btmesh_factory_reset_on_event(evt);
sl_btmesh_handle_sensor_client_on_event(evt);
sl_btmesh_on_event(evt);
}Source code overview :
It mainly analyzes three events , When the network has been accessed , And the device nodes respond , There are only three events :
1. sl_btmesh_evt_node_initialized_id
2. sl_btmesh_evt_sensor_client_descriptor_status_id
3. sl_btmesh_evt_sensor_client_status_id
One 、sl_btmesh_evt_node_initialized_id
The event sent after the Bluetooth protocol stack completes initialization after the chip is powered on ( guess )
Analysis and summary :
1. If you have access to the network , Then initialize the sensor client model : sl_btmesh_sensor_client_init();
[sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt); // important : initialization sensor client Model
case sl_btmesh_evt_node_initialized_id:
if (evt->data.evt_node_initialized.provisioned)
mesh_sensor_client_init();
--> sl_btmesh_sensor_client_init(); // Initialize the sensor client model .Sensor Client There is no internal configuration , It only activates the models in the Bluetooth grid stack .2. If you have access to the network , Then start the single timer , If you are not connected to the network, publish a broadcast to let the configurator know , Waiting to be configured for network access
[app.c]sl_btmesh_on_event(evt); // important : If already configured , Then start the single timer , If it is not configured, start broadcasting , Waiting for configuration .
--> case sl_btmesh_evt_node_initialized_id:
--> handle_node_initialized_event(&(evt->data.evt_node_initialized));
if (evt->provisioned) // If the device has been configured ( That is, it has been connected to the network )
sl_simple_timer_start(..., DEVICE_REGISTER_SHORT_TIMEOUT(100), app_update_registered_devices_timer_cb,..., true);
else // Turn on the broadcast and wait for the configuration to access the network
sl_btmesh_node_start_unprov_beaconing(PB_ADV | PB_GATT);3. The single timer will clear the list of registered devices first , And record the temperature sensor of interest ID, Then start the search for the specified Sensor ID, Restart cycle 2 Second timer requests data
[app.c]app_update_registered_devices_timer_cb()
--> [sl_btmesh_sensor_client.c]sl_btmesh_sensor_client_update_registered_devices(property:current_property(0x004f))
registered_devices.count = 0; // Clear the list of registered devices
memset(registered_devices.address_table, 0, sizeof(registered_devices.address_table));
registering_property = property; // Will default to the temperature sensor to be registered ID Put the global variable 0x004f
sl_btmesh_sensor_client_on_discovery_started(property_id:property); // Only the print information is output here
--> app_log("BT mesh Sensor Device discovery is started. (property_id: 0x%04x)\r\n", property_id);
// a key : Start exploring the specified Sensor 0x004f, If there is a node response , Will trigger sl_btmesh_evt_sensor_client_descriptor_status_id event
sc = sl_btmesh_sensor_client_get_descriptor(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc) {
log_info(SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES, property);
--> #define SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES "Registration of devices for property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED, property);
--> #define SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED "Registration of devices for property ID %4.4x failed\r\n"
// a key : The starting cycle is 2 The second timer sends a request for data at a fixed time
--> sl_simple_timer_start(..., SENSOR_DATA_TIMEOUT(2000), app_sensor_data_timer_cb, ..., true); 4. cycle 2 Second timer , Will 2 The frequency of seconds is sent to acquire the specified sensor data of all nodes ( Here is the temperature sensor 0x004f)
[app.c]app_sensor_data_timer_cb
--> sl_btmesh_sensor_client_get_sensor_data(property:current_property(0x004f));
sc = sl_btmesh_sensor_client_get(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc)
log_info(SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY "Get Sensor Data from property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL "Get Sensor Data from property ID %4.4x failed\r\n"The source code walkthrough :
[main.c]main() -> while(1) // This event will be triggered by the protocol stack after power on
--> [sl_system_process_action.c]sl_system_process_action()
--> [sl_event_handler.c]sl_stack_process_action()
--> [sl_btmesh.c]sl_btmesh_step()
sl_btmesh_pop_event(&evt); // Take out mesh Event type
--> sl_btmesh_process_event(&evt); // Handle mest Event type
--> [sl_btmesh_event_log.c]sl_btmesh_handle_btmesh_logging_events(evt); // This is only used to print the currently received event type
--> case sl_btmesh_evt_node_initialized_id:
app_log("evt:mesh_node_initialized\r\n");
--> [sl_btmesh_provisioning_decorator.c]sl_btmesh_handle_provisioning_decorator_event(evt); // Whether the printout has been configured
--> case sl_btmesh_evt_node_initialized_id:
--> sl_btmesh_on_provision_init_status(provisioned, address, iv_index);
--> if (provisioned) // If the device has been configured ( That is, it has been connected to the network )
app_show_btmesh_node_provisioned(address, iv_index);
--> app_log("BT mesh node is provisioned (address: 0x%04x, iv_index: 0x%lx)\r\n", address, iv_index);
else
app_log("BT mesh node is unprovisioned, started unprovisioned " "beaconing...\r\n");
--> [sl_btmesh_factory_reset.c]sl_btmesh_factory_reset_on_event(evt); // Ignore : Only reset events are processed sl_btmesh_evt_node_reset_id
--> [sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt); // important : initialization sensor client Model
case sl_btmesh_evt_node_initialized_id:
if (evt->data.evt_node_initialized.provisioned)
mesh_sensor_client_init();
--> sl_btmesh_sensor_client_init(); // Initialize the sensor client model .Sensor Client There is no internal configuration , It only activates the models in the Bluetooth grid stack .
--> [app.c]sl_btmesh_on_event(evt); // important : If already configured , Then start the single timer , If it is not configured, start broadcasting , Waiting for configuration .
case sl_btmesh_evt_node_initialized_id:
--> handle_node_initialized_event(&(evt->data.evt_node_initialized));
if (evt->provisioned) // If the device has been configured ( That is, it has been connected to the network )
sl_simple_timer_start(..., DEVICE_REGISTER_SHORT_TIMEOUT(100), app_update_registered_devices_timer_cb,..., true);
else // Turn on the broadcast and wait for the configuration to access the network
sl_btmesh_node_start_unprov_beaconing(PB_ADV | PB_GATT);It mainly starts to explore the specified sensor and starts to 2 Seconds is the cycle timer to request the acquisition of sensor data
[app.c]app_update_registered_devices_timer_cb()
--> [sl_btmesh_sensor_client.c]sl_btmesh_sensor_client_update_registered_devices(property:current_property(0x004f))
registered_devices.count = 0; // Clear the list of registered devices
memset(registered_devices.address_table, 0, sizeof(registered_devices.address_table));
registering_property = property; // Will default to the temperature sensor to be registered ID Put the global variable 0x004f
sl_btmesh_sensor_client_on_discovery_started(property_id:property); // Only the print information is output here
--> app_log("BT mesh Sensor Device discovery is started. (property_id: 0x%04x)\r\n", property_id);
// a key : Start exploring the specified Sensor 0x004f, If there is a node response , Will trigger sl_btmesh_evt_sensor_client_descriptor_status_id event
sc = sl_btmesh_sensor_client_get_descriptor(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc) {
log_info(SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES, property);
--> #define SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES "Registration of devices for property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED, property);
--> #define SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED "Registration of devices for property ID %4.4x failed\r\n"
// a key : The starting cycle is 2 The second timer sends a request for data at a fixed time
--> sl_simple_timer_start(..., SENSOR_DATA_TIMEOUT(2000), app_sensor_data_timer_cb, ..., true); The period is 2 Second timer timing acquisition Server All sensor data in the model
// The period is 2 Second timer timing acquisition Server All sensor data in the model
[app.c]app_sensor_data_timer_cb
--> sl_btmesh_sensor_client_get_sensor_data(property:current_property(0x004f));
sc = sl_btmesh_sensor_client_get(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc)
log_info(SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY "Get Sensor Data from property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL "Get Sensor Data from property ID %4.4x failed\r\n"Two 、sl_btmesh_evt_sensor_client_descriptor_status_id
The server node responds to events , Server if the node does not exist , There will be no such event
Analysis and summary :
1. When starting the search sensor , If there is a node response , This event will be triggered .
2. It will check whether the current node attribute is the temperature sensor of interest .
3. And check whether the node is in the device list , Add to the equipment list when you are not there .
4. This equipment list is very important , Only the data of nodes in this list will not be ignored .
if (descriptor.property_id == registering_property && number_of_devices < SENSOR_CLIENT_DISPLAYED_SENSORS(5)
&& !mesh_address_already_exists(®istered_devices, evt->server_address))
registered_devices.address_table[number_of_devices] = evt->server_address;
registered_devices.count = number_of_devices + 1;The source code walkthrough :
[main.c]main() -> while(1) // The incident was caused by sl_btmesh_sensor_client_get_descriptor() Trigger
--> [sl_system_process_action.c]sl_system_process_action()
--> [sl_event_handler.c]sl_stack_process_action()
--> [sl_btmesh.c]sl_btmesh_step()
sl_btmesh_pop_event(&evt); // Take out mesh Event type
--> sl_btmesh_process_event(&evt); // Handle mest Event type
--> [sl_btmesh_event_log.c]sl_btmesh_handle_btmesh_logging_events(evt); // This is only used to print the currently received event type
--> case sl_btmesh_evt_sensor_client_descriptor_status_id:
app_log("evt:mesh_sensor_client_descriptor_status\r\n");
--> [sl_btmesh_provisioning_decorator.c]sl_btmesh_handle_provisioning_decorator_event(evt); // Ignore : This function does not handle this event
--> [sl_btmesh_factory_reset.c]sl_btmesh_factory_reset_on_event(evt); // Ignore : Only reset events are processed sl_btmesh_evt_node_reset_id
--> [sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt);
case sl_btmesh_evt_sensor_client_descriptor_status_id:
--> handle_sensor_client_events(&(evt->data.evt_sensor_client_descriptor_status));
case sl_btmesh_evt_sensor_client_descriptor_status_id:
--> handle_sensor_client_descriptor_status(&(evt->data.evt_sensor_client_descriptor_status));
--> [sl_btmesh_sensor.c]mesh_lib_sensor_descriptors_from_buf(&descriptor, evt->descriptors.data, SIZE_OF_DESCRIPTOR); // Data is converted to a sensor descriptor
// If at present mesh The nature of the event ID That's what we want registering_property:0x004f, If it is not in the device registration list, add it to the list
uint8_t number_of_devices = registered_devices.count;
if (descriptor.property_id == registering_property && number_of_devices < SENSOR_CLIENT_DISPLAYED_SENSORS(5)
&& !mesh_address_already_exists(®istered_devices, evt->server_address))
registered_devices.address_table[number_of_devices] = evt->server_address;
registered_devices.count = number_of_devices + 1;
--> [app_out_log.c]sl_btmesh_sensor_client_on_new_device_found(descriptor.property_id, evt->server_address);
--> app_log("BT mesh Sensor Device (address: 0x%04x, property_id: 0x%04x) is found.\r\n", address, property_id);
3、 ... and 、sl_btmesh_evt_sensor_client_status_id
Server node status response event , Server if the node status is not responding or does not exist , There will be no such event
Analysis and summary :
1. The packet attached to this event may carry multiple node data , Therefore, it is necessary to split them one by one
2. Filter nodes that are not in the device list , Only the temperature sensor data in the equipment list and of interest will be processed .
The source code walkthrough :
[main.c]main() -> while(1) // The incident was caused by sl_btmesh_sensor_client_get_descriptor() Trigger
--> [sl_system_process_action.c]sl_system_process_action()
--> [sl_event_handler.c]sl_stack_process_action()
--> [sl_btmesh.c]sl_btmesh_step()
sl_btmesh_pop_event(&evt); // Take out mesh Event type
--> sl_btmesh_process_event(&evt); // Handle mest Event type
--> [sl_btmesh_event_log.c]sl_btmesh_handle_btmesh_logging_events(evt); // This is only used to print the currently received event type
--> case sl_btmesh_evt_sensor_client_status_id: // The address of the printout current response is the group address 、 Unicast address or virtual address
app_log("evt:mesh_sensor_client_status %s\r\n", (evt->data.evt_sensor_client_status.client_address & 0xC000) == 0xC000 ? "(group broadcast)" : (evt->data.evt_sensor_client_status.client_address & 0x1000) == 0 ? "(unicast)" : "(virtual)");
--> [sl_btmesh_provisioning_decorator.c]sl_btmesh_handle_provisioning_decorator_event(evt); // Ignore : This function does not handle this event
--> [sl_btmesh_factory_reset.c]sl_btmesh_factory_reset_on_event(evt); // Ignore : Only reset events are processed sl_btmesh_evt_node_reset_id
--> [sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt); // a key : This function will extract the match Server The sensor data
case sl_btmesh_evt_sensor_client_status_id:
--> handle_sensor_client_events(&(evt->data.evt_sensor_client_descriptor_status));
case sl_btmesh_evt_sensor_client_status_id:
--> static void handle_sensor_client_status(sl_btmesh_evt_sensor_client_status_t *evt)
{
uint8_t *sensor_data = evt->sensor_data.data;
uint8_t data_len = evt->sensor_data.len;
uint8_t pos = 0;
while (pos < data_len)
{ // Go through each Server Node messages
if (data_len - pos > PROPERTY_ID_SIZE) {
mesh_device_properties_t property_id = (mesh_device_properties_t)(sensor_data[pos] + (sensor_data[pos + 1] << 8));
uint8_t property_len = sensor_data[pos + PROPERTY_ID_SIZE];
uint8_t *property_data = NULL;
// Only those in the device registry are processed Service Node messages
if (mesh_address_already_exists(®istered_devices, evt->server_address)) {
sl_btmesh_sensor_client_data_status_t status;
uint16_t address;
uint8_t sensor_idx;
if (property_len && (data_len - pos > PROPERTY_HEADER_SIZE)) {
property_data = &sensor_data[pos + PROPERTY_HEADER_SIZE];
}
address = evt->server_address;
sensor_idx = mesh_get_sensor_index(®istered_devices, address);
status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
switch (property_id) {
......
case PRESENT_AMBIENT_TEMPERATURE:
temperature_8_t temperature = SL_BTMESH_SENSOR_CLIENT_TEMPERATURE_UNKNOWN;
if (property_len == 1) {
mesh_device_property_t new_property = mesh_sensor_data_from_buf(PRESENT_AMBIENT_TEMPERATURE, property_data);
temperature = new_property.temperature_8;
if (temperature == SL_BTMESH_SENSOR_CLIENT_TEMPERATURE_UNKNOWN) {
status = SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN;
} else {
status = SL_BTMESH_SENSOR_CLIENT_DATA_VALID;
}
} else {
status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
}
--> [app_out_log.c]sl_btmesh_sensor_client_on_new_temperature_data(sensor_idx, address, status, temperature);
if (PRESENT_AMBIENT_TEMPERATURE != app_get_current_property()) {
return;
}
if (SL_BTMESH_SENSOR_CLIENT_DATA_VALID == status) {
app_log("BT mesh Sensor Temperature (from 0x%04x): %3d.%1d Abduct C\r\n", address, INT_TEMP(temperature), FRAC_TEMP(temperature));
} else if (SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN == status) {
app_log("BT mesh Sensor Temperature (from 0x%04x): UNKNOWN\r\n", address);
} else {
app_log("BT mesh Sensor Temperature (from 0x%04x): NOT AVAILABLE\r\n", address);
}
break;
......
}
pos += PROPERTY_HEADER_SIZE + property_len;
}
} else {
pos = data_len;
}
}
}Four 、 Key handling logic
The above is the core logic processing , As for the logic of key triggering , With the above foundation , It's a lot easier .
app.c By redefining app_button_press.c The key processing callback of is used to intercept key events
[app_button_press.c ]SL_WEAK void app_button_press_cb(uint8_t button, uint8_t duration)
{
(void)button;
(void)duration;
}
[app.c]void app_button_press_cb(uint8_t button, uint8_t duration)
{
(void)duration;
if (duration == APP_BUTTON_PRESS_NONE) {
return;
}
// button pressed
if (button == BUTTON_PRESS_BUTTON_0) {
if (duration < APP_BUTTON_PRESS_DURATION_LONG) {
app_log("PB0 pressed\r\n");
sensor_client_change_current_property(); // Press the key briefly to adjust the sensor type of interest
} else {
app_log("PB0 long pressed\r\n");
update_registered_devices(); // Long press to restart the search for the specified Sensor ID
}
} else if (button == BUTTON_PRESS_BUTTON_1) {
app_log("PB1 pressed\r\n");
update_registered_devices();
}
}
[app.c]static void sensor_client_change_current_property(void)
{
switch (current_property) {
case PRESENT_AMBIENT_TEMPERATURE:
current_property = PEOPLE_COUNT;
break;
case PEOPLE_COUNT:
current_property = PRESENT_AMBIENT_LIGHT_LEVEL;
break;
case PRESENT_AMBIENT_LIGHT_LEVEL:
current_property = PRESENT_AMBIENT_TEMPERATURE;
break;
default:
app_log("Unsupported property ID change\r\n");
break;
}
}
// Reference resources sl_btmesh_evt_node_initialized_id The third and fourth step of
void update_registered_devices(void)
{
sl_status_t sc;
sl_btmesh_sensor_client_update_registered_devices(current_property);
sc = sl_simple_timer_start(&app_sensor_data_timer,SENSOR_DATA_TIMEOUT, app_sensor_data_timer_cb, NO_CALLBACK_DATA,true);
app_assert_status_f(sc, "Failed to start periodic timer\n");
}
5、 ... and 、 The way of chip development
1. It can be found that the final results are in the current project app_out_log.c Output
2. and app_out_log.c It's through sl_btmesh_sensor_client.c Inside SL_WEAK Decorated interface to intercept the results
3. When a source file in the project has a connection with SL_WEAK When the modified functions are the same , The compiler will only select the function implementation that compiles the source file .
4. So be familiar with sl_btmesh_sensor_client.c The logical implementation of , What results need to be achieved again SL_WEAK The modified function is OK .
边栏推荐
- Ruixing coffee moves towards "national consumption"
- 微服务开发环境搭建
- 记录:如何解决MultipartFile类的transferTo()上传图片报“系统找不到指定的路径“问题【亲测有效】
- How to learn C language and share super detailed experience (learning note 1 -- basic data types of C language)
- Read routing table
- Introduction to Google unit testing tools GTEST and gmoke
- js获取元素
- Detailed explanation of maxpooling corresponding to conv1d, conv2d and conv3d machines of tensorflow2
- Ten thousand words make it clear that synchronized and reentrantlock implement locks in concurrency
- Rsync transport exclusion directory
猜你喜欢

pringboot之restfull接口规范注解(二)

Mac下搭建MySQL环境

Devexpress implementation flow chart

Why is Huawei matebook x Pro 2022 leading a "laptop" revolution

华为设备配置CE双归属
![[the third day of actual combat of smart lock project based on stm32f401ret6 in 10 days] communication foundation and understanding serial port](/img/82/ed215078da0325b3adf95dcd6ffe30.jpg)
[the third day of actual combat of smart lock project based on stm32f401ret6 in 10 days] communication foundation and understanding serial port
![[learning notes] xr872 audio driver framework analysis](/img/1a/008a89f835dc1b350a1f1ff27bee00.jpg)
[learning notes] xr872 audio driver framework analysis

QT realizes mind mapping function (II)
![[the second day of actual combat of smart lock project based on stm32f401ret6 in 10 days] GPIO and register](/img/eb/9bd411be74937371de0bbf3f04267e.jpg)
[the second day of actual combat of smart lock project based on stm32f401ret6 in 10 days] GPIO and register
![[the 4th day of the 10 day smart lock project based on stm32f401ret6] what is interrupt, interrupt service function, system tick timer](/img/c4/0d97def5fb587b8301bcb907fc6fcf.jpg)
[the 4th day of the 10 day smart lock project based on stm32f401ret6] what is interrupt, interrupt service function, system tick timer
随机推荐
记录:如何解决MultipartFile类的transferTo()上传图片报“系统找不到指定的路径“问题【亲测有效】
Get started quickly cmake
Huawei equipment configures private IP routing FRR
SQLserver2008 拒绝了对对象 '****' (数据库 '****',架构 'dbo')的 SELECT 权限
Learning notes 51 single chip microcomputer keyboard (non coding keyboard and coding keyboard, scanning mode of non coding keyboard, independent keyboard, matrix keyboard)
Rsync transport exclusion directory
[the second day of the actual combat of the smart lock project based on stm32f401ret6 in 10 days] light up with the key ----- input and output of GPIO
SQL server deletes all tables and all stored procedures in the database
Application circuit and understanding of BAT54C as power supply protection
Ctrip reshapes new Ctrip
Day 1 of the 10 day smart lock project (understand the SCM stm32f401ret6 and C language foundation)
Vivo released originos ocean, and the domestic customized system is getting better and better
Uniapp preview function
传感器:SHT30温湿度传感器检测环境温湿度实验(底部附代码)
回顾ITIL各版本历程,找到企业运维发展的关键点
The new wild prospect of JD instant retailing from the perspective of "hour shopping"
Qt实现思维导图功能(二)
Leetcode daily question - 890 Find and replace mode
Detailed explanation of C language conditional compilation
What is Google plus large text ads? How to use it?