From fc3253163e2a6785621fd27e0458b9106c929ddf Mon Sep 17 00:00:00 2001 From: Island Date: Mon, 7 Jan 2019 15:16:47 +0800 Subject: [PATCH] component/ble_mesh: ESP BLE Mesh release 1. BLE Mesh Core * Provisioning: Node Role * Advertising and GATT bearer * Authentication OOB * Provisioning: Provisioner Role * Advertising and GATT bearer * Authentication OOB * Networking * Relay * Segmentation and Reassembly * Key Refresh * IV Update * Proxy Support * Multiple Client Models Run Simultaneously * Support multiple client models send packets to different nodes simultaneously * No blocking between client model and server * NVS Storage * Store Provisioning Data of BLE Mesh Nodes in Flash 2. BLE Mesh Applications * BLE Mesh Node & Provisioner * Node Example * Provisioner Example * Node + Generic OnOff Client Example * Fast Provisioning * Vendor Fast Prov Server Model * Vendor Fast Prov Client Model * Examples * Wi-Fi & BLE Mesh Coexistence * Example * BLE Mesh Console Commands * Example 3. BLE Mesh Models * Foundation Models * Configuration Server Model * Configuration Client Model * Health Server Model * Health Client Model * Generic Client Models * Generic OnOff Client * Generic Level Client * Generic Location Client * Generic Default Transition Timer Client * Generic Power OnOff Client * Generic Power Level Client * Generic Battery Client * Generic Property Client * Generic Server Models * Generic OnOff Server (Example) * Lighting Client Models * Light Lightness Client * Light CTL Client * Light HSL Client * Light xyL Client * Light LC Client * Sensor Client Model * Sensor Client * Time and Scenes Client Models * Time Client * Scene Client * Scheduler Client --- components/bt/CMakeLists.txt | 69 + components/bt/Kconfig | 811 ++++ .../api/core/esp_ble_mesh_common_api.c | 70 + .../esp_ble_mesh_local_data_operation_api.c | 80 + .../api/core/esp_ble_mesh_low_power_api.c | 26 + .../api/core/esp_ble_mesh_networking_api.c | 338 ++ .../api/core/esp_ble_mesh_provisioning_api.c | 423 ++ .../api/core/esp_ble_mesh_proxy_api.c | 65 + .../core/include/esp_ble_mesh_common_api.h | 37 + .../esp_ble_mesh_local_data_operation_api.h | 113 + .../core/include/esp_ble_mesh_low_power_api.h | 20 + .../include/esp_ble_mesh_networking_api.h | 267 ++ .../include/esp_ble_mesh_provisioning_api.h | 316 ++ .../api/core/include/esp_ble_mesh_proxy_api.h | 59 + .../bt/ble_mesh/api/esp_ble_mesh_defs.h | 1524 +++++++ .../models/esp_ble_mesh_config_model_api.c | 82 + .../models/esp_ble_mesh_generic_model_api.c | 75 + .../models/esp_ble_mesh_health_model_api.c | 98 + .../models/esp_ble_mesh_lighting_model_api.c | 76 + .../models/esp_ble_mesh_sensor_model_api.c | 76 + .../esp_ble_mesh_time_scene_model_api.c | 76 + .../include/esp_ble_mesh_config_model_api.h | 670 +++ .../include/esp_ble_mesh_generic_model_api.h | 478 +++ .../include/esp_ble_mesh_health_model_api.h | 261 ++ .../include/esp_ble_mesh_lighting_model_api.h | 526 +++ .../include/esp_ble_mesh_sensor_model_api.h | 230 ++ .../esp_ble_mesh_time_scene_model_api.h | 292 ++ .../ble_mesh/btc/btc_ble_mesh_config_model.c | 725 ++++ .../ble_mesh/btc/btc_ble_mesh_generic_model.c | 540 +++ .../ble_mesh/btc/btc_ble_mesh_health_model.c | 592 +++ .../btc/btc_ble_mesh_lighting_model.c | 381 ++ .../bt/ble_mesh/btc/btc_ble_mesh_prov.c | 1528 +++++++ .../ble_mesh/btc/btc_ble_mesh_sensor_model.c | 632 +++ .../btc/btc_ble_mesh_time_scene_model.c | 383 ++ .../btc/include/btc_ble_mesh_config_model.h | 64 + .../btc/include/btc_ble_mesh_generic_model.h | 52 + .../btc/include/btc_ble_mesh_health_model.h | 78 + .../btc/include/btc_ble_mesh_lighting_model.h | 53 + .../ble_mesh/btc/include/btc_ble_mesh_prov.h | 210 + .../btc/include/btc_ble_mesh_sensor_model.h | 53 + .../include/btc_ble_mesh_time_scene_model.h | 53 + components/bt/ble_mesh/mesh_core/access.c | 1043 +++++ components/bt/ble_mesh/mesh_core/access.h | 60 + components/bt/ble_mesh/mesh_core/adv.c | 411 ++ components/bt/ble_mesh/mesh_core/adv.h | 86 + components/bt/ble_mesh/mesh_core/beacon.c | 422 ++ components/bt/ble_mesh/mesh_core/beacon.h | 24 + components/bt/ble_mesh/mesh_core/cfg_cli.c | 1657 ++++++++ components/bt/ble_mesh/mesh_core/cfg_srv.c | 3593 +++++++++++++++++ components/bt/ble_mesh/mesh_core/crypto.c | 878 ++++ components/bt/ble_mesh/mesh_core/crypto.h | 166 + components/bt/ble_mesh/mesh_core/foundation.h | 166 + components/bt/ble_mesh/mesh_core/friend.c | 1326 ++++++ components/bt/ble_mesh/mesh_core/friend.h | 49 + components/bt/ble_mesh/mesh_core/health_cli.c | 462 +++ components/bt/ble_mesh/mesh_core/health_srv.c | 529 +++ .../bt/ble_mesh/mesh_core/include/cfg_cli.h | 297 ++ .../bt/ble_mesh/mesh_core/include/cfg_srv.h | 72 + .../ble_mesh/mesh_core/include/health_cli.h | 78 + .../ble_mesh/mesh_core/include/health_srv.h | 93 + .../ble_mesh/mesh_core/include/mesh_access.h | 444 ++ .../mesh_core/include/mesh_aes_encrypt.h | 171 + .../ble_mesh/mesh_core/include/mesh_atomic.h | 305 ++ .../mesh_core/include/mesh_bearer_adapt.h | 733 ++++ .../bt/ble_mesh/mesh_core/include/mesh_buf.h | 1064 +++++ .../ble_mesh/mesh_core/include/mesh_dlist.h | 496 +++ .../bt/ble_mesh/mesh_core/include/mesh_hci.h | 134 + .../ble_mesh/mesh_core/include/mesh_kernel.h | 275 ++ .../bt/ble_mesh/mesh_core/include/mesh_main.h | 584 +++ .../ble_mesh/mesh_core/include/mesh_proxy.h | 37 + .../ble_mesh/mesh_core/include/mesh_slist.h | 468 +++ .../ble_mesh/mesh_core/include/mesh_trace.h | 131 + .../ble_mesh/mesh_core/include/mesh_types.h | 46 + .../bt/ble_mesh/mesh_core/include/mesh_util.h | 440 ++ .../bt/ble_mesh/mesh_core/include/mesh_uuid.h | 530 +++ components/bt/ble_mesh/mesh_core/lpn.c | 1057 +++++ components/bt/ble_mesh/mesh_core/lpn.h | 67 + components/bt/ble_mesh/mesh_core/mesh.h | 22 + .../bt/ble_mesh/mesh_core/mesh_aes_encrypt.c | 409 ++ .../bt/ble_mesh/mesh_core/mesh_atomic.c | 179 + .../bt/ble_mesh/mesh_core/mesh_bearer_adapt.c | 1858 +++++++++ components/bt/ble_mesh/mesh_core/mesh_buf.c | 453 +++ components/bt/ble_mesh/mesh_core/mesh_hci.c | 45 + .../bt/ble_mesh/mesh_core/mesh_kernel.c | 205 + components/bt/ble_mesh/mesh_core/mesh_main.c | 509 +++ components/bt/ble_mesh/mesh_core/mesh_util.c | 86 + components/bt/ble_mesh/mesh_core/net.c | 1517 +++++++ components/bt/ble_mesh/mesh_core/net.h | 388 ++ components/bt/ble_mesh/mesh_core/prov.c | 1774 ++++++++ components/bt/ble_mesh/mesh_core/prov.h | 34 + .../ble_mesh/mesh_core/provisioner_beacon.c | 71 + .../ble_mesh/mesh_core/provisioner_beacon.h | 20 + .../bt/ble_mesh/mesh_core/provisioner_main.c | 1278 ++++++ .../bt/ble_mesh/mesh_core/provisioner_main.h | 122 + .../bt/ble_mesh/mesh_core/provisioner_prov.c | 3287 +++++++++++++++ .../bt/ble_mesh/mesh_core/provisioner_prov.h | 379 ++ .../bt/ble_mesh/mesh_core/provisioner_proxy.c | 608 +++ .../bt/ble_mesh/mesh_core/provisioner_proxy.h | 89 + components/bt/ble_mesh/mesh_core/proxy.c | 1393 +++++++ components/bt/ble_mesh/mesh_core/proxy.h | 51 + components/bt/ble_mesh/mesh_core/settings.c | 1578 ++++++++ components/bt/ble_mesh/mesh_core/settings.h | 39 + .../mesh_core/settings/settings_nvs.c | 374 ++ .../mesh_core/settings/settings_nvs.h | 44 + components/bt/ble_mesh/mesh_core/test.c | 131 + components/bt/ble_mesh/mesh_core/test.h | 43 + components/bt/ble_mesh/mesh_core/transport.c | 1681 ++++++++ components/bt/ble_mesh/mesh_core/transport.h | 102 + .../bt/ble_mesh/mesh_docs/BLE-Mesh_FAQs_EN.md | 9 + .../mesh_docs/BLE-Mesh_Feature_List_EN.md | 89 + .../mesh_docs/BLE-Mesh_Getting_Started_EN.md | 155 + .../mesh_docs/BLE-Mesh_Known_Issues_EN.md | 1 + components/bt/ble_mesh/mesh_docs/README.md | 50 + .../bt/ble_mesh/mesh_models/generic_client.c | 1225 ++++++ .../mesh_models/include/generic_client.h | 491 +++ .../mesh_models/include/lighting_client.h | 492 +++ .../mesh_models/include/mesh_common.h | 46 + .../mesh_models/include/model_common.h | 133 + .../mesh_models/include/model_opcode.h | 276 ++ .../mesh_models/include/sensor_client.h | 167 + .../mesh_models/include/time_scene_client.h | 257 ++ .../bt/ble_mesh/mesh_models/lighting_client.c | 1400 +++++++ .../bt/ble_mesh/mesh_models/mesh_common.c | 46 + .../bt/ble_mesh/mesh_models/model_common.c | 336 ++ .../bt/ble_mesh/mesh_models/sensor_client.c | 616 +++ .../ble_mesh/mesh_models/time_scene_client.c | 694 ++++ components/bt/bluedroid/btc/core/btc_task.c | 21 + .../bt/bluedroid/btc/include/btc/btc_task.h | 12 + components/bt/component.mk | 23 +- docs/en/COPYRIGHT.rst | 2 + .../ble_mesh_client_model/CMakeLists.txt | 6 + .../ble_mesh/ble_mesh_client_model/Makefile | 10 + .../ble_mesh/ble_mesh_client_model/README.md | 14 + .../ble_mesh_client_model/main/CMakeLists.txt | 6 + .../main/Kconfig.projbuild | 22 + .../main/ble_mesh_demo_main.c | 532 +++ .../ble_mesh_client_model/main/board.c | 115 + .../ble_mesh_client_model/main/board.h | 42 + .../ble_mesh_client_model/main/component.mk | 4 + .../ble_mesh_client_model/sdkconfig.defaults | 45 + .../tutorial/ble_mesh_client_model.md | 252 ++ .../tutorial/images/app.png | Bin 0 -> 443575 bytes .../tutorial/images/message.png | Bin 0 -> 67886 bytes .../tutorial/images/picture5.png | Bin 0 -> 26425 bytes .../ble_mesh_node/CMakeLists.txt | 6 + .../ble_mesh_console/ble_mesh_node/Makefile | 10 + .../ble_mesh_console/ble_mesh_node/README.md | 9 + .../ble_mesh_node/main/CMakeLists.txt | 12 + .../ble_mesh_node/main/ble_mesh_adapter.c | 164 + .../ble_mesh_node/main/ble_mesh_adapter.h | 97 + .../main/ble_mesh_cfg_srv_model.c | 208 + .../main/ble_mesh_cfg_srv_model.h | 107 + .../main/ble_mesh_console_decl.h | 28 + .../ble_mesh_node/main/ble_mesh_console_lib.c | 128 + .../ble_mesh_node/main/ble_mesh_console_lib.h | 31 + .../main/ble_mesh_console_main.c | 215 + .../main/ble_mesh_console_system.c | 183 + .../main/ble_mesh_register_node_cmd.c | 547 +++ .../main/ble_mesh_register_server_cmd.c | 83 + .../ble_mesh_node/main/component.mk | 5 + .../ble_mesh_node/main/register_bluetooth.c | 45 + .../ble_mesh_node/sdkconfig.defaults | 46 + .../ble_mesh_provisioner/CMakeLists.txt | 6 + .../ble_mesh_provisioner/Makefile | 10 + .../ble_mesh_provisioner/README.md | 10 + .../ble_mesh_provisioner/main/CMakeLists.txt | 15 + .../main/ble_mesh_adapter.c | 300 ++ .../main/ble_mesh_adapter.h | 123 + .../main/ble_mesh_cfg_srv_model.c | 205 + .../main/ble_mesh_cfg_srv_model.h | 107 + .../main/ble_mesh_console_decl.h | 38 + .../main/ble_mesh_console_lib.c | 124 + .../main/ble_mesh_console_lib.h | 29 + .../main/ble_mesh_console_main.c | 228 ++ .../main/ble_mesh_console_system.c | 179 + .../main/ble_mesh_reg_cfg_client_cmd.c | 390 ++ .../main/ble_mesh_reg_gen_onoff_client_cmd.c | 180 + .../main/ble_mesh_reg_test_perf_client_cmd.c | 219 + .../main/ble_mesh_register_node_cmd.c | 476 +++ .../main/ble_mesh_register_provisioner_cmd.c | 424 ++ .../ble_mesh_provisioner/main/component.mk | 5 + .../main/register_bluetooth.c | 45 + .../ble_mesh_provisioner/sdkconfig.defaults | 40 + .../ble_mesh_fast_prov_client/CMakeLists.txt | 8 + .../ble_mesh_fast_prov_client/Makefile | 12 + .../ble_mesh_fast_prov_client/README.md | 2 + .../main/CMakeLists.txt | 5 + .../main/ble_mesh_demo_main.c | 638 +++ .../main/component.mk | 4 + .../sdkconfig.defaults | 43 + .../ble_mesh_fast_provision_client.md | 218 + .../ble_mesh_fast_prov_server/CMakeLists.txt | 8 + .../ble_mesh_fast_prov_server/Makefile | 12 + .../ble_mesh_fast_prov_server/README.md | 2 + .../main/CMakeLists.txt | 6 + .../main/Kconfig.projbuild | 16 + .../main/ble_mesh_demo_main.c | 842 ++++ .../ble_mesh_fast_prov_server/main/board.c | 72 + .../ble_mesh_fast_prov_server/main/board.h | 47 + .../main/component.mk | 4 + .../sdkconfig.defaults | 51 + .../tutorial/EspBleMesh.md | 93 + .../ble_mesh_fast_provision_server.md | 409 ++ .../tutorial/images/app_ble.png | Bin 0 -> 182199 bytes .../tutorial/images/device.png | Bin 0 -> 356234 bytes .../tutorial/images/picture1.png | Bin 0 -> 73063 bytes .../tutorial/images/picture2.png | Bin 0 -> 69619 bytes .../tutorial/images/time.png | Bin 0 -> 56788 bytes .../ble_mesh/ble_mesh_node/CMakeLists.txt | 6 + .../bluetooth/ble_mesh/ble_mesh_node/Makefile | 10 + .../ble_mesh/ble_mesh_node/README.md | 16 + .../ble_mesh_node/main/CMakeLists.txt | 6 + .../ble_mesh_node/main/Kconfig.projbuild | 22 + .../ble_mesh_node/main/ble_mesh_demo_main.c | 395 ++ .../ble_mesh/ble_mesh_node/main/board.c | 130 + .../ble_mesh/ble_mesh_node/main/board.h | 42 + .../ble_mesh/ble_mesh_node/main/component.mk | 4 + .../ble_mesh/ble_mesh_node/sdkconfig.defaults | 43 + .../Ble_Mesh_Node_Example_Walkthrough.md | 471 +++ .../ble_mesh_provisioner/CMakeLists.txt | 6 + .../ble_mesh/ble_mesh_provisioner/Makefile | 10 + .../ble_mesh/ble_mesh_provisioner/README.md | 6 + .../ble_mesh_provisioner/main/CMakeLists.txt | 5 + .../main/ble_mesh_demo_main.c | 691 ++++ .../ble_mesh_provisioner/main/component.mk | 4 + .../ble_mesh_provisioner/sdkconfig.defaults | 40 + ...le_Mesh_Provisioner_Example_Walkthrough.md | 129 + .../fast_prov_vendor_model/CMakeLists.txt | 6 + .../fast_prov_vendor_model/Makefile | 10 + .../components/CMakeLists.txt | 9 + .../components/component.mk | 6 + .../components/esp_fast_prov_client_model.c | 407 ++ .../components/esp_fast_prov_client_model.h | 36 + .../components/esp_fast_prov_common.h | 121 + .../components/esp_fast_prov_operation.c | 577 +++ .../components/esp_fast_prov_operation.h | 69 + .../components/esp_fast_prov_server_model.c | 640 +++ .../components/esp_fast_prov_server_model.h | 102 + .../main/CMakeLists.txt | 3 + .../fast_prov_vendor_model/main/component.mk | 5 + .../fast_prov_vendor_model/main/main.c | 21 + .../fast_prov_vendor_model/sdkconfig.defaults | 33 + .../ble_mesh_wifi_coexist/CMakeLists.txt | 8 + .../ble_mesh/ble_mesh_wifi_coexist/Makefile | 12 + .../ble_mesh/ble_mesh_wifi_coexist/README.md | 20 + .../components/iperf/CMakeLists.txt | 8 + .../components/iperf/cmd_decl.h | 14 + .../components/iperf/cmd_wifi.c | 477 +++ .../components/iperf/component.mk | 11 + .../components/iperf/iperf.c | 461 +++ .../components/iperf/iperf.h | 61 + .../ble_mesh_wifi_coexist/main/CMakeLists.txt | 6 + .../main/Kconfig.projbuild | 16 + .../main/ble_mesh_demo_main.c | 977 +++++ .../ble_mesh_wifi_coexist/main/board.c | 72 + .../ble_mesh_wifi_coexist/main/board.h | 47 + .../ble_mesh_wifi_coexist/main/component.mk | 5 + .../ble_mesh_wifi_coexist/partitions.csv | 6 + .../ble_mesh_wifi_coexist/sdkconfig.defaults | 82 + .../tutorial/ble_mesh_wifi_coexist.md | 301 ++ 260 files changed, 71487 insertions(+), 1 deletion(-) create mode 100644 components/bt/ble_mesh/api/core/esp_ble_mesh_common_api.c create mode 100644 components/bt/ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c create mode 100644 components/bt/ble_mesh/api/core/esp_ble_mesh_low_power_api.c create mode 100644 components/bt/ble_mesh/api/core/esp_ble_mesh_networking_api.c create mode 100644 components/bt/ble_mesh/api/core/esp_ble_mesh_provisioning_api.c create mode 100644 components/bt/ble_mesh/api/core/esp_ble_mesh_proxy_api.c create mode 100644 components/bt/ble_mesh/api/core/include/esp_ble_mesh_common_api.h create mode 100644 components/bt/ble_mesh/api/core/include/esp_ble_mesh_local_data_operation_api.h create mode 100644 components/bt/ble_mesh/api/core/include/esp_ble_mesh_low_power_api.h create mode 100644 components/bt/ble_mesh/api/core/include/esp_ble_mesh_networking_api.h create mode 100644 components/bt/ble_mesh/api/core/include/esp_ble_mesh_provisioning_api.h create mode 100644 components/bt/ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h create mode 100644 components/bt/ble_mesh/api/esp_ble_mesh_defs.h create mode 100644 components/bt/ble_mesh/api/models/esp_ble_mesh_config_model_api.c create mode 100644 components/bt/ble_mesh/api/models/esp_ble_mesh_generic_model_api.c create mode 100644 components/bt/ble_mesh/api/models/esp_ble_mesh_health_model_api.c create mode 100644 components/bt/ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c create mode 100644 components/bt/ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c create mode 100644 components/bt/ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c create mode 100644 components/bt/ble_mesh/api/models/include/esp_ble_mesh_config_model_api.h create mode 100644 components/bt/ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h create mode 100644 components/bt/ble_mesh/api/models/include/esp_ble_mesh_health_model_api.h create mode 100644 components/bt/ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h create mode 100644 components/bt/ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h create mode 100644 components/bt/ble_mesh/api/models/include/esp_ble_mesh_time_scene_model_api.h create mode 100644 components/bt/ble_mesh/btc/btc_ble_mesh_config_model.c create mode 100644 components/bt/ble_mesh/btc/btc_ble_mesh_generic_model.c create mode 100644 components/bt/ble_mesh/btc/btc_ble_mesh_health_model.c create mode 100644 components/bt/ble_mesh/btc/btc_ble_mesh_lighting_model.c create mode 100644 components/bt/ble_mesh/btc/btc_ble_mesh_prov.c create mode 100644 components/bt/ble_mesh/btc/btc_ble_mesh_sensor_model.c create mode 100644 components/bt/ble_mesh/btc/btc_ble_mesh_time_scene_model.c create mode 100644 components/bt/ble_mesh/btc/include/btc_ble_mesh_config_model.h create mode 100644 components/bt/ble_mesh/btc/include/btc_ble_mesh_generic_model.h create mode 100644 components/bt/ble_mesh/btc/include/btc_ble_mesh_health_model.h create mode 100644 components/bt/ble_mesh/btc/include/btc_ble_mesh_lighting_model.h create mode 100644 components/bt/ble_mesh/btc/include/btc_ble_mesh_prov.h create mode 100644 components/bt/ble_mesh/btc/include/btc_ble_mesh_sensor_model.h create mode 100644 components/bt/ble_mesh/btc/include/btc_ble_mesh_time_scene_model.h create mode 100644 components/bt/ble_mesh/mesh_core/access.c create mode 100644 components/bt/ble_mesh/mesh_core/access.h create mode 100644 components/bt/ble_mesh/mesh_core/adv.c create mode 100644 components/bt/ble_mesh/mesh_core/adv.h create mode 100644 components/bt/ble_mesh/mesh_core/beacon.c create mode 100644 components/bt/ble_mesh/mesh_core/beacon.h create mode 100644 components/bt/ble_mesh/mesh_core/cfg_cli.c create mode 100644 components/bt/ble_mesh/mesh_core/cfg_srv.c create mode 100644 components/bt/ble_mesh/mesh_core/crypto.c create mode 100644 components/bt/ble_mesh/mesh_core/crypto.h create mode 100644 components/bt/ble_mesh/mesh_core/foundation.h create mode 100644 components/bt/ble_mesh/mesh_core/friend.c create mode 100644 components/bt/ble_mesh/mesh_core/friend.h create mode 100644 components/bt/ble_mesh/mesh_core/health_cli.c create mode 100644 components/bt/ble_mesh/mesh_core/health_srv.c create mode 100644 components/bt/ble_mesh/mesh_core/include/cfg_cli.h create mode 100644 components/bt/ble_mesh/mesh_core/include/cfg_srv.h create mode 100644 components/bt/ble_mesh/mesh_core/include/health_cli.h create mode 100644 components/bt/ble_mesh/mesh_core/include/health_srv.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_access.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_aes_encrypt.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_atomic.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_bearer_adapt.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_buf.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_dlist.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_hci.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_kernel.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_main.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_proxy.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_slist.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_trace.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_types.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_util.h create mode 100644 components/bt/ble_mesh/mesh_core/include/mesh_uuid.h create mode 100644 components/bt/ble_mesh/mesh_core/lpn.c create mode 100644 components/bt/ble_mesh/mesh_core/lpn.h create mode 100644 components/bt/ble_mesh/mesh_core/mesh.h create mode 100644 components/bt/ble_mesh/mesh_core/mesh_aes_encrypt.c create mode 100644 components/bt/ble_mesh/mesh_core/mesh_atomic.c create mode 100644 components/bt/ble_mesh/mesh_core/mesh_bearer_adapt.c create mode 100644 components/bt/ble_mesh/mesh_core/mesh_buf.c create mode 100644 components/bt/ble_mesh/mesh_core/mesh_hci.c create mode 100644 components/bt/ble_mesh/mesh_core/mesh_kernel.c create mode 100644 components/bt/ble_mesh/mesh_core/mesh_main.c create mode 100644 components/bt/ble_mesh/mesh_core/mesh_util.c create mode 100644 components/bt/ble_mesh/mesh_core/net.c create mode 100644 components/bt/ble_mesh/mesh_core/net.h create mode 100644 components/bt/ble_mesh/mesh_core/prov.c create mode 100644 components/bt/ble_mesh/mesh_core/prov.h create mode 100644 components/bt/ble_mesh/mesh_core/provisioner_beacon.c create mode 100644 components/bt/ble_mesh/mesh_core/provisioner_beacon.h create mode 100644 components/bt/ble_mesh/mesh_core/provisioner_main.c create mode 100644 components/bt/ble_mesh/mesh_core/provisioner_main.h create mode 100644 components/bt/ble_mesh/mesh_core/provisioner_prov.c create mode 100644 components/bt/ble_mesh/mesh_core/provisioner_prov.h create mode 100644 components/bt/ble_mesh/mesh_core/provisioner_proxy.c create mode 100644 components/bt/ble_mesh/mesh_core/provisioner_proxy.h create mode 100644 components/bt/ble_mesh/mesh_core/proxy.c create mode 100644 components/bt/ble_mesh/mesh_core/proxy.h create mode 100644 components/bt/ble_mesh/mesh_core/settings.c create mode 100644 components/bt/ble_mesh/mesh_core/settings.h create mode 100644 components/bt/ble_mesh/mesh_core/settings/settings_nvs.c create mode 100644 components/bt/ble_mesh/mesh_core/settings/settings_nvs.h create mode 100644 components/bt/ble_mesh/mesh_core/test.c create mode 100644 components/bt/ble_mesh/mesh_core/test.h create mode 100644 components/bt/ble_mesh/mesh_core/transport.c create mode 100644 components/bt/ble_mesh/mesh_core/transport.h create mode 100644 components/bt/ble_mesh/mesh_docs/BLE-Mesh_FAQs_EN.md create mode 100644 components/bt/ble_mesh/mesh_docs/BLE-Mesh_Feature_List_EN.md create mode 100644 components/bt/ble_mesh/mesh_docs/BLE-Mesh_Getting_Started_EN.md create mode 100644 components/bt/ble_mesh/mesh_docs/BLE-Mesh_Known_Issues_EN.md create mode 100644 components/bt/ble_mesh/mesh_docs/README.md create mode 100644 components/bt/ble_mesh/mesh_models/generic_client.c create mode 100644 components/bt/ble_mesh/mesh_models/include/generic_client.h create mode 100644 components/bt/ble_mesh/mesh_models/include/lighting_client.h create mode 100644 components/bt/ble_mesh/mesh_models/include/mesh_common.h create mode 100644 components/bt/ble_mesh/mesh_models/include/model_common.h create mode 100644 components/bt/ble_mesh/mesh_models/include/model_opcode.h create mode 100644 components/bt/ble_mesh/mesh_models/include/sensor_client.h create mode 100644 components/bt/ble_mesh/mesh_models/include/time_scene_client.h create mode 100644 components/bt/ble_mesh/mesh_models/lighting_client.c create mode 100644 components/bt/ble_mesh/mesh_models/mesh_common.c create mode 100644 components/bt/ble_mesh/mesh_models/model_common.c create mode 100644 components/bt/ble_mesh/mesh_models/sensor_client.c create mode 100644 components/bt/ble_mesh/mesh_models/time_scene_client.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/Makefile create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/README.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/main/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/main/Kconfig.projbuild create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/main/board.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/main/board.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/main/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/sdkconfig.defaults create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/ble_mesh_client_model.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/images/app.png create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/images/message.png create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/images/picture5.png create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/Makefile create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/README.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_decl.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_main.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_system.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_node_cmd.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_server_cmd.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/register_bluetooth.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/sdkconfig.defaults create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/Makefile create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/README.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_decl.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_main.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_system.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_cfg_client_cmd.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_gen_onoff_client_cmd.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_test_perf_client_cmd.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_node_cmd.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_provisioner_cmd.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/register_bluetooth.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/sdkconfig.defaults create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/Makefile create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/README.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/sdkconfig.defaults create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/tutorial/ble_mesh_fast_provision_client.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/Makefile create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/README.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/Kconfig.projbuild create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/sdkconfig.defaults create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/EspBleMesh.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/ble_mesh_fast_provision_server.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/app_ble.png create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/device.png create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/picture1.png create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/picture2.png create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/time.png create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/Makefile create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/README.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/main/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/main/Kconfig.projbuild create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/main/board.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/main/board.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/main/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/sdkconfig.defaults create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_node/tutorial/Ble_Mesh_Node_Example_Walkthrough.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_provisioner/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_provisioner/Makefile create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_provisioner/README.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_provisioner/sdkconfig.defaults create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_provisioner/tutorial/Ble_Mesh_Provisioner_Example_Walkthrough.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/Makefile create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_common.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/main.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/sdkconfig.defaults create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/Makefile create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/README.md create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_decl.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_wifi.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/iperf.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/iperf.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/main/CMakeLists.txt create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/main/Kconfig.projbuild create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/main/board.c create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/main/board.h create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/main/component.mk create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/partitions.csv create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/sdkconfig.defaults create mode 100644 examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/tutorial/ble_mesh_wifi_coexist.md diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index 73f6879039..58ccb27a82 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -287,6 +287,75 @@ if(CONFIG_BT_ENABLED) endif() endif() +if (CONFIG_BLE_MESH) + list(APPEND COMPONENT_ADD_INCLUDEDIRS + "bluedroid/osi/include" + "ble_mesh/mesh_core" + "ble_mesh/mesh_core/include" + "ble_mesh/mesh_core/settings" + "ble_mesh/btc/include" + "ble_mesh/mesh_models/include" + "ble_mesh/api/core/include" + "ble_mesh/api/models/include" + "ble_mesh/api") + + list(APPEND COMPONENT_SRCS + "ble_mesh/api/core/esp_ble_mesh_common_api.c" + "ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c" + "ble_mesh/api/core/esp_ble_mesh_low_power_api.c" + "ble_mesh/api/core/esp_ble_mesh_networking_api.c" + "ble_mesh/api/core/esp_ble_mesh_provisioning_api.c" + "ble_mesh/api/core/esp_ble_mesh_proxy_api.c" + "ble_mesh/api/models/esp_ble_mesh_config_model_api.c" + "ble_mesh/api/models/esp_ble_mesh_generic_model_api.c" + "ble_mesh/api/models/esp_ble_mesh_health_model_api.c" + "ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c" + "ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c" + "ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c" + "ble_mesh/btc/btc_ble_mesh_config_model.c" + "ble_mesh/btc/btc_ble_mesh_generic_model.c" + "ble_mesh/btc/btc_ble_mesh_health_model.c" + "ble_mesh/btc/btc_ble_mesh_lighting_model.c" + "ble_mesh/btc/btc_ble_mesh_prov.c" + "ble_mesh/btc/btc_ble_mesh_sensor_model.c" + "ble_mesh/btc/btc_ble_mesh_time_scene_model.c" + "ble_mesh/mesh_core/settings/settings_nvs.c" + "ble_mesh/mesh_core/access.c" + "ble_mesh/mesh_core/adv.c" + "ble_mesh/mesh_core/beacon.c" + "ble_mesh/mesh_core/cfg_cli.c" + "ble_mesh/mesh_core/cfg_srv.c" + "ble_mesh/mesh_core/crypto.c" + "ble_mesh/mesh_core/friend.c" + "ble_mesh/mesh_core/health_cli.c" + "ble_mesh/mesh_core/health_srv.c" + "ble_mesh/mesh_core/lpn.c" + "ble_mesh/mesh_core/mesh_aes_encrypt.c" + "ble_mesh/mesh_core/mesh_atomic.c" + "ble_mesh/mesh_core/mesh_bearer_adapt.c" + "ble_mesh/mesh_core/mesh_buf.c" + "ble_mesh/mesh_core/mesh_hci.c" + "ble_mesh/mesh_core/mesh_kernel.c" + "ble_mesh/mesh_core/mesh_main.c" + "ble_mesh/mesh_core/mesh_util.c" + "ble_mesh/mesh_core/net.c" + "ble_mesh/mesh_core/prov.c" + "ble_mesh/mesh_core/provisioner_beacon.c" + "ble_mesh/mesh_core/provisioner_main.c" + "ble_mesh/mesh_core/provisioner_prov.c" + "ble_mesh/mesh_core/provisioner_proxy.c" + "ble_mesh/mesh_core/proxy.c" + "ble_mesh/mesh_core/settings.c" + "ble_mesh/mesh_core/test.c" + "ble_mesh/mesh_core/transport.c" + "ble_mesh/mesh_models/generic_client.c" + "ble_mesh/mesh_models/lighting_client.c" + "ble_mesh/mesh_models/mesh_common.c" + "ble_mesh/mesh_models/model_common.c" + "ble_mesh/mesh_models/sensor_client.c" + "ble_mesh/mesh_models/time_scene_client.c") +endif() + # requirements can't depend on config set(COMPONENT_PRIV_REQUIRES nvs_flash soc) diff --git a/components/bt/Kconfig b/components/bt/Kconfig index d292e66c20..daf83b1be7 100644 --- a/components/bt/Kconfig +++ b/components/bt/Kconfig @@ -1352,3 +1352,814 @@ menu Bluetooth default 0 endmenu + +menuconfig BLE_MESH + bool "BLE Mesh Support" + help + This option enables BLE Mesh support. The specific features that are + available may depend on other features that have been enabled in the + stack, such as Bluetooth Support, Bluedroid Support & GATT support. + +if BLE_MESH + + config BLE_MESH_HCI_5_0 + bool "Support sending 20ms non-connectable adv packets" + default y + help + It is a temporary solution and needs further modifications. + + config BLE_MESH_USE_DUPLICATE_SCAN + bool "Support Duplicate Scan in BLE Mesh" + select BLE_SCAN_DUPLICATE + select BLE_MESH_SCAN_DUPLICATE_EN + default y + help + Enable this option to allow using specific duplicate scan filter + in BLE Mesh, and Scan Duplicate Type must be set to 0x02. + + config BLE_MESH_FAST_PROV + bool "Enable BLE Mesh Fast Provisioning" + select BLE_MESH_NODE + select BLE_MESH_PROVISIONER + select BLE_MESH_PB_ADV + default n + help + Enable this option to allow BLE Mesh fast provisioning solution to be used. + + config BLE_MESH_NODE + bool "Support for BLE Mesh Node" + help + Enable the device to be provisioned into a node. + + config BLE_MESH_PROVISIONER + bool "Support for BLE Mesh Provisioner" + help + Enable the device to be a provisioner. + + if BLE_MESH_PROVISIONER + + config BLE_MESH_WAIT_FOR_PROV_MAX_DEV_NUM + int "Maximum number of unprovisioned devices that can be added to device queue" + default 20 + range 1 100 + help + This option specifies how may unprovisioned devices can be added to device + queue for provisioning. + + config BLE_MESH_MAX_STORED_NODES + int "Maximum number of nodes whose information can be stored" + default 20 + range 1 1000 + help + This option specifies the maximum number of nodes whose information can be + stored by a provisioner in its upper layer. + + config BLE_MESH_MAX_PROV_NODES + int "Maximum number of devices that can be provisioned by provisioner" + default 20 + range 1 100 + help + This option specifies how many devices can be provisioned by provisioner. + + if BLE_MESH_PB_ADV + config BLE_MESH_PBA_SAME_TIME + int "Maximum number of PB-ADV running at the same time by provisioner" + default 2 + range 1 10 + help + This option specifies how many devices can be provisioned at the same + time using PB-ADV. + endif # BLE_MESH_PB_ADV + + if BLE_MESH_PB_GATT + config BLE_MESH_PBG_SAME_TIME + int "Maximum number of PB-GATT running at the same time by provisioner" + default 1 + range 1 5 + help + This option specifies how many devices can be provisioned at the same + time using PB-GATT. + endif # BLE_MESH_PB_GATT + + config BLE_MESH_PROVISIONER_SUBNET_COUNT + int "Maximum number of mesh subnets that can be created by provisioner" + default 3 + range 1 4096 + help + This option specifies how many subnets per network a provisioner can create. + + config BLE_MESH_PROVISIONER_APP_KEY_COUNT + int "Maximum number of application keys that can be owned by provisioner" + default 9 + range 1 4096 + help + This option specifies how many application keys the provisioner can have. + + endif # BLE_MESH_PROVISIONER + + # Virtual option enabled whenever Generic Provisioning layer is needed + config BLE_MESH_PROV + bool "BLE Mesh Provisioning support" + default y + help + Enable this option to support BLE Mesh Provisioning functionality. For + BLE Mesh, this option should be always enabled. + + config BLE_MESH_PB_ADV + bool "Provisioning support using the advertising bearer (PB-ADV)" + select BLE_MESH_PROV + default y + help + Enable this option to allow the device to be provisioned over the + advertising bearer. + + config BLE_MESH_PB_GATT + bool "Provisioning support using GATT (PB-GATT)" + select BLE_MESH_PROXY + select BLE_MESH_PROV + help + Enable this option to allow the device to be provisioned over GATT. + + # Virtual option enabled whenever any Proxy protocol is needed + config BLE_MESH_PROXY + bool "BLE Mesh Proxy protocol support" + default y + help + Enable this option to support BLE Mesh Proxy protocol used by PB-GATT + and other proxy pdu transmission. + + config BLE_MESH_GATT_PROXY + bool "BLE Mesh GATT Proxy Service" + select BLE_MESH_PROXY + help + This option enables support for Mesh GATT Proxy Service, i.e. the + ability to act as a proxy between a Mesh GATT Client and a Mesh network. + + config BLE_MESH_NODE_ID_TIMEOUT + int "Node Identity advertising timeout" + depends on BLE_MESH_GATT_PROXY + range 1 60 + default 60 + help + This option determines for how long the local node advertises using + Node Identity. The given value is in seconds. The specification limits + this to 60 seconds and lists it as the recommended value as well. + So leaving the default value is the safest option. + + if BLE_MESH_PROXY + + config BLE_MESH_PROXY_FILTER_SIZE + int "Maximum number of filter entries per Proxy Client" + default 1 + default 3 if BLE_MESH_GATT_PROXY + range 1 32767 + help + This option specifies how many Proxy Filter entries the local node supports. + + endif # BLE_MESH_PROXY + + config BLE_MESH_NET_BUF_POOL_USAGE + bool "BLE Mesh net buffer pool usage tracking" + default y + help + Enable BLE Mesh net buffer pool tracking. + + config BLE_MESH_SETTINGS + bool "Store BLE Mesh Node configuration persistently" + default n + help + When selected, the BLE Mesh stack will take care of storing/restoring the + BLE Mesh configuration persistently in flash. Currently this only supports + storing BLE Mesh node configuration. + + if BLE_MESH_SETTINGS + config BLE_MESH_STORE_TIMEOUT + int "Delay (in seconds) before storing anything persistently" + range 0 1000000 + default 0 + help + This value defines in seconds how soon any pending changes are actually + written into persistent storage (flash) after a change occurs. + + config BLE_MESH_SEQ_STORE_RATE + int "How often the sequence number gets updated in storage" + range 0 1000000 + default 128 + help + This value defines how often the local sequence number gets updated in + persistent storage (i.e. flash). e.g. a value of 100 means that the + sequence number will be stored to flash on every 100th increment. + If the node sends messages very frequently a higher value makes more + sense, whereas if the node sends infrequently a value as low as 0 + (update storage for every increment) can make sense. When the stack + gets initialized it will add sequence number to the last stored one, + so that it starts off with a value that's guaranteed to be larger than + the last one used before power off. + + config BLE_MESH_RPL_STORE_TIMEOUT + int "Minimum frequency that the RPL gets updated in storage" + range 0 1000000 + default 5 + help + This value defines in seconds how soon the RPL(Replay Protection List) + gets written to persistent storage after a change occurs. If the node + receives messages frequently, then a large value is recommended. If the + node receives messages rarely, then the value can be as low as 0 (which + means the PRL is written into the storage immediately). + Note that if the node operates in a security-sensitive case, and there is + a risk of sudden power-off, then a value of 0 is strongly recommended. + Otherwise, a power loss before RPL being written into the storage may + introduce message replay attacks and system security will be in a + vulnerable state. + endif # if BLE_MESH_SETTINGS + + config BLE_MESH_SUBNET_COUNT + int "Maximum number of mesh subnets per network" + default 3 + range 1 4096 + help + This option specifies how many subnets a Mesh network can have at the same time. + + config BLE_MESH_APP_KEY_COUNT + int "Maximum number of application keys per network" + default 3 + range 1 4096 + help + This option specifies how many application keys the device can store per network. + + config BLE_MESH_MODEL_KEY_COUNT + int "Maximum number of application keys per model" + default 3 + range 1 4096 + help + This option specifies the maximum number of application keys to which each model + can be bound. + + config BLE_MESH_MODEL_GROUP_COUNT + int "Maximum number of group address subscriptions per model" + default 3 + range 1 4096 + help + This option specifies the maximum number of addresses to which each model can + be subscribed. + + config BLE_MESH_LABEL_COUNT + int "Maximum number of Label UUIDs used for Virtual Addresses" + default 3 + range 0 4096 + help + This option specifies how many Label UUIDs can be stored. + + config BLE_MESH_CRPL + int "Maximum capacity of the replay protection list" + default 10 + range 2 65535 + help + This option specifies the maximum capacity of the replay protection list. + It is similar to Network message cache size, but has a different purpose. + + config BLE_MESH_MSG_CACHE_SIZE + int "Network message cache size" + default 10 + range 2 65535 + help + Number of messages that are cached for the network. This helps prevent + unnecessary decryption operations and unnecessary relays. This option + is similar to Replay protection list, but has a different purpose. + + config BLE_MESH_ADV_BUF_COUNT + int "Number of advertising buffers" + default 60 + range 6 256 + help + Number of advertising buffers available. The transport layer reserves + ADV_BUF_COUNT - 3 buffers for outgoing segments. The maximum outgoing + SDU size is 12 times this value (out of which 4 or 8 bytes are used + for the Transport Layer MIC). For example, 5 segments means the maximum + SDU size is 60 bytes, which leaves 56 bytes for application layer data + using a 4-byte MIC, or 52 bytes using an 8-byte MIC. + + config BLE_MESH_IVU_DIVIDER + int "Divider for IV Update state refresh timer" + default 4 + range 2 96 + help + When the IV Update state enters Normal operation or IV Update + in Progress, we need to keep track of how many hours has passed + in the state, since the specification requires us to remain in + the state at least for 96 hours (Update in Progress has an + additional upper limit of 144 hours). + + In order to fulfill the above requirement, even if the node might + be powered off once in a while, we need to store persistently + how many hours the node has been in the state. This doesn't + necessarily need to happen every hour (thanks to the flexible + duration range). The exact cadence will depend a lot on the + ways that the node will be used and what kind of power source it + has. + + Since there is no single optimal answer, this configuration + option allows specifying a divider, i.e. how many intervals + the 96 hour minimum gets split into. After each interval the + duration that the node has been in the current state gets + stored to flash. E.g. the default value of 4 means that the + state is saved every 24 hours (96 / 4). + + config BLE_MESH_TX_SEG_MSG_COUNT + int "Maximum number of simultaneous outgoing segmented messages" + default 1 + range 1 BLE_MESH_ADV_BUF_COUNT + help + Maximum number of simultaneous outgoing multi-segment and/or reliable messages. + + config BLE_MESH_RX_SEG_MSG_COUNT + int "Maximum number of simultaneous incoming segmented messages" + default 1 + range 1 255 + help + Maximum number of simultaneous incoming multi-segment and/or reliable messages. + + config BLE_MESH_RX_SDU_MAX + int "Maximum incoming Upper Transport Access PDU length" + default 384 + range 36 384 + help + Maximum incoming Upper Transport Access PDU length. Leave this to the default + value, unless you really need to optimize memory usage. + + config BLE_MESH_TX_SEG_MAX + int "Maximum number of segments in outgoing messages" + default 20 + range 2 32 + help + Maximum number of segments supported for outgoing messages. + This value should typically be fine-tuned based on what + models the local node supports, i.e. what's the largest + message payload that the node needs to be able to send. + This value affects memory and call stack consumption, which + is why the default is lower than the maximum that the + specification would allow (32 segments). + + The maximum outgoing SDU size is 12 times this number (out of + which 4 or 8 bytes is used for the Transport Layer MIC). For + example, 5 segments means the maximum SDU size is 60 bytes, + which leaves 56 bytes for application layer data using a + 4-byte MIC and 52 bytes using an 8-byte MIC. + + Be sure to specify a sufficient number of advertising buffers + when setting this option to a higher value. There must be at + least three more advertising buffers (BLE_MESH_ADV_BUF_COUNT) + as there are outgoing segments. + + config BLE_MESH_RELAY + bool "Relay support" + help + Support for acting as a Mesh Relay Node. + + config BLE_MESH_LOW_POWER + bool "Support for Low Power features" + help + Enable this option to operate as a Low Power Node. + + if BLE_MESH_LOW_POWER + + config BLE_MESH_LPN_ESTABLISHMENT + bool "Perform Friendship establishment using low power" + default y + help + Perform the Friendship establishment using low power with the help of a + reduced scan duty cycle. The downside of this is that the node may miss + out on messages intended for it until it has successfully set up Friendship + with a Friend node. + + config BLE_MESH_LPN_AUTO + bool "Automatically start looking for Friend nodes once provisioned" + default y + help + Once provisioned, automatically enable LPN functionality and start looking + for Friend nodes. If this option is disabled LPN mode needs to be manually + enabled by calling bt_mesh_lpn_set(true). + + config BLE_MESH_LPN_AUTO_TIMEOUT + int "Time from last received message before going to LPN mode" + default 15 + range 0 3600 + depends on BLE_MESH_LPN_AUTO + help + Time in seconds from the last received message, that the node waits out + before starting to look for Friend nodes. + + config BLE_MESH_LPN_RETRY_TIMEOUT + int "Retry timeout for Friend requests" + default 8 + range 1 3600 + help + Time in seconds between Friend Requests, if a previous Friend Request did + not yield any acceptable Friend Offers. + + config BLE_MESH_LPN_RSSI_FACTOR + int "RSSIFactor, used in Friend Offer Delay calculation" + range 0 3 + default 0 + help + The contribution of the RSSI, measured by the Friend node, used in Friend + Offer Delay calculations. 0 = 1, 1 = 1.5, 2 = 2, 3 = 2.5. + + config BLE_MESH_LPN_RECV_WIN_FACTOR + int "ReceiveWindowFactor, used in Friend Offer Delay calculation" + range 0 3 + default 0 + help + The contribution of the supported Receive Window used in Friend Offer + Delay calculations. 0 = 1, 1 = 1.5, 2 = 2, 3 = 2.5. + + config BLE_MESH_LPN_MIN_QUEUE_SIZE + int "Minimum size of the acceptable friend queue (MinQueueSizeLog)" + range 1 7 + default 1 + help + The MinQueueSizeLog field is defined as log_2(N), where N is the minimum + number of maximum size Lower Transport PDUs that the Friend node can store + in its Friend Queue. As an example, MinQueueSizeLog value 1 gives N = 2, + and value 7 gives N = 128. + + config BLE_MESH_LPN_RECV_DELAY + int "Receive delay requested by the local node" + range 10 255 + default 100 + help + The ReceiveDelay is the time between the Low Power node sending a + request and listening for a response. This delay allows the Friend + node time to prepare the response. The value is in units of milliseconds. + + config BLE_MESH_LPN_POLL_TIMEOUT + int "The value of the PollTimeout timer" + range 10 244735 + default 300 + help + PollTimeout timer is used to measure time between two consecutive + requests sent by a Low Power node. If no requests are received + the Friend node before the PollTimeout timer expires, then the + friendship is considered terminated. The value is in units of 100 + milliseconds, so e.g. a value of 300 means 30 seconds. + + config BLE_MESH_LPN_INIT_POLL_TIMEOUT + int "The starting value of the PollTimeout timer" + range 10 BLE_MESH_LPN_POLL_TIMEOUT + default BLE_MESH_LPN_POLL_TIMEOUT + help + The initial value of the PollTimeout timer when Friendship is to be + established for the first time. After this, the timeout gradually + grows toward the actual PollTimeout, doubling in value for each iteration. + The value is in units of 100 milliseconds, so e.g. a value of 300 means + 30 seconds. + + config BLE_MESH_LPN_SCAN_LATENCY + int "Latency for enabling scanning" + range 0 50 + default 10 + help + Latency (in milliseconds) is the time it takes to enable scanning. In + practice, it means how much time in advance of the Receive Window, the + request to enable scanning is made. + + config BLE_MESH_LPN_GROUPS + int "Number of groups the LPN can subscribe to" + range 0 16384 + default 8 + help + Maximum number of groups to which the LPN can subscribe. + endif # BLE_MESH_LOW_POWER + + config BLE_MESH_FRIEND + bool "Support for acting as a Friend Node" + help + Enable this option to be able to act as a Friend Node. + + if BLE_MESH_FRIEND + + config BLE_MESH_FRIEND_RECV_WIN + int "Friend Receive Window" + range 1 255 + default 255 + help + Receive Window in milliseconds supported by the Friend node. + + config BLE_MESH_FRIEND_QUEUE_SIZE + int "Minimum number of buffers supported per Friend Queue" + range 2 65536 + default 16 + help + Minimum number of buffers available to be stored for each local Friend Queue. + + config BLE_MESH_FRIEND_SUB_LIST_SIZE + int "Friend Subscription List Size" + range 0 1023 + default 3 + help + Size of the Subscription List that can be supported by a Friend node for a + Low Power node. + + config BLE_MESH_FRIEND_LPN_COUNT + int "Number of supported LPN nodes" + range 1 1000 + default 2 + help + Number of Low Power Nodes with which a Friend can have Friendship simultaneously. + + config BLE_MESH_FRIEND_SEG_RX + int "Number of incomplete segment lists per LPN" + range 1 1000 + default 1 + help + Number of incomplete segment lists tracked for each Friends' LPN. + In other words, this determines from how many elements can segmented + messages destined for the Friend queue be received simultaneously. + + endif # BLE_MESH_FRIEND + + config BLE_MESH_NO_LOG + bool "Disable BLE Mesh debug logs (minimize bin size)" + depends on BLE_MESH + default n + help + Select this to save the BLE Mesh related rodata code size. + + menu "BLE Mesh STACK DEBUG LOG LEVEL" + depends on BLE_MESH && !BLE_MESH_NO_LOG + + choice BLE_MESH_STACK_TRACE_LEVEL + prompt "BLE_MESH_STACK" + default BLE_MESH_TRACE_LEVEL_WARNING + depends on BLE_MESH && !BLE_MESH_NO_LOG + help + Define BLE Mesh trace level for BLE Mesh stack. + + config BLE_MESH_TRACE_LEVEL_NONE + bool "NONE" + config BLE_MESH_TRACE_LEVEL_ERROR + bool "ERROR" + config BLE_MESH_TRACE_LEVEL_WARNING + bool "WARNING" + config BLE_MESH_TRACE_LEVEL_INFO + bool "INFO" + config BLE_MESH_TRACE_LEVEL_DEBUG + bool "DEBUG" + config BLE_MESH_TRACE_LEVEL_VERBOSE + bool "VERBOSE" + endchoice + + config BLE_MESH_STACK_TRACE_LEVEL + int + depends on BLE_MESH + default 0 if BLE_MESH_TRACE_LEVEL_NONE + default 1 if BLE_MESH_TRACE_LEVEL_ERROR + default 2 if BLE_MESH_TRACE_LEVEL_WARNING + default 3 if BLE_MESH_TRACE_LEVEL_INFO + default 4 if BLE_MESH_TRACE_LEVEL_DEBUG + default 5 if BLE_MESH_TRACE_LEVEL_VERBOSE + default 2 + + endmenu #BLE Mesh DEBUG LOG LEVEL + + menu "BLE Mesh NET BUF DEBUG LOG LEVEL" + depends on BLE_MESH && !BLE_MESH_NO_LOG + + choice BLE_MESH_NET_BUF_TRACE_LEVEL + prompt "BLE_MESH_NET_BUF" + default BLE_MESH_NET_BUF_TRACE_LEVEL_WARNING + depends on BLE_MESH && !BLE_MESH_NO_LOG + help + Define BLE Mesh trace level for BLE Mesh net buffer. + + config BLE_MESH_NET_BUF_TRACE_LEVEL_NONE + bool "NONE" + config BLE_MESH_NET_BUF_TRACE_LEVEL_ERROR + bool "ERROR" + config BLE_MESH_NET_BUF_TRACE_LEVEL_WARNING + bool "WARNING" + config BLE_MESH_NET_BUF_TRACE_LEVEL_INFO + bool "INFO" + config BLE_MESH_NET_BUF_TRACE_LEVEL_DEBUG + bool "DEBUG" + config BLE_MESH_NET_BUF_TRACE_LEVEL_VERBOSE + bool "VERBOSE" + endchoice + + config BLE_MESH_NET_BUF_TRACE_LEVEL + int + depends on BLE_MESH + default 0 if BLE_MESH_NET_BUF_TRACE_LEVEL_NONE + default 1 if BLE_MESH_NET_BUF_TRACE_LEVEL_ERROR + default 2 if BLE_MESH_NET_BUF_TRACE_LEVEL_WARNING + default 3 if BLE_MESH_NET_BUF_TRACE_LEVEL_INFO + default 4 if BLE_MESH_NET_BUF_TRACE_LEVEL_DEBUG + default 5 if BLE_MESH_NET_BUF_TRACE_LEVEL_VERBOSE + default 2 + + endmenu #BLE Mesh NET BUF DEBUG LOG LEVEL + + config BLE_MESH_IRQ_LOCK + bool "Used the IRQ lock instead of task lock" + help + To improve the real-time requirements of bt controller in BLE Mesh, + task lock is used to replace IRQ lock. + + config BLE_MESH_CLIENT_MSG_TIMEOUT + int "Timeout(ms) for client message response" + range 100 1200000 + default 4000 + help + Timeout value used by the node to get response of the acknowledged + message which is sent by the client model. + + menu "Support for BLE Mesh Client Models" + + config BLE_MESH_CFG_CLI + bool "Configuration Client Model" + help + Enable support for Configuration client model. + + config BLE_MESH_HEALTH_CLI + bool "Health Client Model" + help + Enable support for Health client model. + + config BLE_MESH_GENERIC_ONOFF_CLI + bool "Generic OnOff Client Model" + help + Enable support for Generic OnOff client model. + + config BLE_MESH_GENERIC_LEVEL_CLI + bool "Generic Level Client Model" + help + Enable support for Generic Level client model. + + config BLE_MESH_GENERIC_DEF_TRANS_TIME_CLI + bool "Generic Default Transition Time Client Model" + help + Enable support for Generic Default Transition Time client model. + + config BLE_MESH_GENERIC_POWER_ONOFF_CLI + bool "Generic Power Onoff Client Model" + help + Enable support for Generic Power Onoff client model. + + config BLE_MESH_GENERIC_POWER_LEVEL_CLI + bool "Generic Power Level Client Model" + help + Enable support for Generic Power Level client model. + + config BLE_MESH_GENERIC_BATTERY_CLI + bool "Generic Battery Client Model" + help + Enable support for Generic Battery client model. + + config BLE_MESH_GENERIC_LOCATION_CLI + bool "Generic Location Client Model" + help + Enable support for Generic Location client model. + + config BLE_MESH_GENERIC_PROPERTY_CLI + bool "Generic Property Client Model" + help + Enable support for Generic Property client model. + + config BLE_MESH_SENSOR_CLI + bool "Sensor Client Model" + help + Enable support for Sensor client model. + + config BLE_MESH_TIME_CLI + bool "Time Client Model" + help + Enable support for Time client model. + + config BLE_MESH_SCENE_CLI + bool "Scene Client Model" + help + Enable support for Scene client model. + + config BLE_MESH_SCHEDULER_CLI + bool "Scheduler Client Model" + help + Enable support for Scheduler client model. + + config BLE_MESH_LIGHT_LIGHTNESS_CLI + bool "Light Lightness Client Model" + help + Enable support for Light Lightness client model. + + config BLE_MESH_LIGHT_CTL_CLI + bool "Light CTL Client Model" + help + Enable support for Light CTL client model. + + config BLE_MESH_LIGHT_HSL_CLI + bool "Light HSL Client Model" + help + Enable support for Light HSL client model. + + config BLE_MESH_LIGHT_XYL_CLI + bool "Light XYL Client Model" + help + Enable support for Light XYL client model. + + config BLE_MESH_LIGHT_LC_CLI + bool "Light LC Client Model" + help + Enable support for Light LC client model. + + endmenu + + config BLE_MESH_IV_UPDATE_TEST + bool "Test the IV Update Procedure" + default n + help + This option removes the 96 hour limit of the IV Update Procedure and + lets the state to be changed at any time. + + menu "BLE Mesh specific test option" + + config BLE_MESH_SELF_TEST + bool "Perform BLE Mesh self-tests" + default n + help + This option adds extra self-tests which are run every time BLE Mesh + networking is initialized. + + config BLE_MESH_SHELL + bool "Enable BLE Mesh shell" + default n + help + Activate shell module that provides BLE Mesh commands to the console. + + config BLE_MESH_DEBUG + bool "Enable BLE Mesh debug logs" + default n + help + Enable debug logs for the BLE Mesh functionality. + + if BLE_MESH_DEBUG + + config BLE_MESH_DEBUG_NET + bool "Network layer debug" + help + Enable Network layer debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_TRANS + bool "Transport layer debug" + help + Enable Transport layer debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_BEACON + bool "Beacon debug" + help + Enable Beacon-related debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_CRYPTO + bool "Crypto debug" + help + Enable cryptographic debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_PROV + bool "Provisioning debug" + help + Enable Provisioning debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_ACCESS + bool "Access layer debug" + help + Enable Access layer debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_MODEL + bool "Foundation model debug" + help + Enable Foundation Models debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_ADV + bool "Advertising debug" + help + Enable advertising debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_LOW_POWER + bool "Low Power debug" + help + Enable Low Power debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_FRIEND + bool "Friend debug" + help + Enable Friend debug logs for the BLE Mesh functionality. + + config BLE_MESH_DEBUG_PROXY + bool "Proxy debug" + depends on BLE_MESH_PROXY + help + Enable Proxy protocol debug logs for the BLE Mesh functionality. + + endif # BLE_MESH_DEBUG + + endmenu + +endif # BLE_MESH diff --git a/components/bt/ble_mesh/api/core/esp_ble_mesh_common_api.c b/components/bt/ble_mesh/api/core/esp_ble_mesh_common_api.c new file mode 100644 index 0000000000..4172ef0c7d --- /dev/null +++ b/components/bt/ble_mesh/api/core/esp_ble_mesh_common_api.c @@ -0,0 +1,70 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_err.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_prov.h" +#include "esp_ble_mesh_defs.h" + +esp_err_t esp_ble_mesh_init(esp_ble_mesh_prov_t *prov, esp_ble_mesh_comp_t *comp) +{ + btc_ble_mesh_prov_args_t arg = {0}; + SemaphoreHandle_t semaphore = NULL; + btc_msg_t msg = {0}; + + if (prov == NULL || comp == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + // Create a semaphore + if ((semaphore = xSemaphoreCreateCounting(1, 0)) == NULL) { + LOG_ERROR("%s, Failed to allocate memory for the semaphore", __func__); + return ESP_ERR_NO_MEM; + } + + arg.mesh_init.prov = prov; + arg.mesh_init.comp = comp; + /* Transport semaphore pointer to BTC layer, and will give the semaphore in the BTC task */ + arg.mesh_init.semaphore = semaphore; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_MESH_INIT; + + if (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) != BT_STATUS_SUCCESS) { + vSemaphoreDelete(semaphore); + LOG_ERROR("%s, BLE Mesh initialise failed", __func__); + return ESP_FAIL; + } + + /* Take the Semaphore, wait BLE Mesh initialization to finish. */ + xSemaphoreTake(semaphore, portMAX_DELAY); + /* Don't forget to delete the semaphore at the end. */ + vSemaphoreDelete(semaphore); + + return ESP_OK; +} + diff --git a/components/bt/ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c b/components/bt/ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c new file mode 100644 index 0000000000..6efeaf1765 --- /dev/null +++ b/components/bt/ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c @@ -0,0 +1,80 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_err.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_prov.h" +#include "esp_ble_mesh_defs.h" + +int32_t esp_ble_mesh_get_model_publish_period(esp_ble_mesh_model_t *model) +{ + if (model == NULL) { + return 0; + } + return btc_ble_mesh_model_pub_period_get(model); +} + +uint16_t esp_ble_mesh_get_primary_element_address(void) +{ + return btc_ble_mesh_get_primary_addr(); +} + +uint16_t *esp_ble_mesh_is_model_subscribed_to_group(esp_ble_mesh_model_t *model, uint16_t group_addr) +{ + if (model == NULL) { + return NULL; + } + return btc_ble_mesh_model_find_group(model, group_addr); +} + +esp_ble_mesh_elem_t *esp_ble_mesh_find_element(uint16_t element_addr) +{ + return btc_ble_mesh_elem_find(element_addr); +} + +uint8_t esp_ble_mesh_get_element_count(void) +{ + return btc_ble_mesh_elem_count(); +} + +esp_ble_mesh_model_t *esp_ble_mesh_find_vendor_model(const esp_ble_mesh_elem_t *element, + uint16_t company_id, uint16_t model_id) +{ + if (element == NULL) { + return NULL; + } + return btc_ble_mesh_model_find_vnd(element, company_id, model_id); +} + +esp_ble_mesh_model_t *esp_ble_mesh_find_sig_model(const esp_ble_mesh_elem_t *element, uint16_t model_id) +{ + if (element == NULL) { + return NULL; + } + return btc_ble_mesh_model_find(element, model_id); +} + +const esp_ble_mesh_comp_t *esp_ble_mesh_get_composition_data(void) +{ + return btc_ble_mesh_comp_get(); +} + diff --git a/components/bt/ble_mesh/api/core/esp_ble_mesh_low_power_api.c b/components/bt/ble_mesh/api/core/esp_ble_mesh_low_power_api.c new file mode 100644 index 0000000000..6d3745ca6e --- /dev/null +++ b/components/bt/ble_mesh/api/core/esp_ble_mesh_low_power_api.c @@ -0,0 +1,26 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_err.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_prov.h" +#include "esp_ble_mesh_defs.h" + diff --git a/components/bt/ble_mesh/api/core/esp_ble_mesh_networking_api.c b/components/bt/ble_mesh/api/core/esp_ble_mesh_networking_api.c new file mode 100644 index 0000000000..9e920dd113 --- /dev/null +++ b/components/bt/ble_mesh/api/core/esp_ble_mesh_networking_api.c @@ -0,0 +1,338 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_err.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_prov.h" +#include "esp_ble_mesh_networking_api.h" + +#define ESP_BLE_MESH_TX_SDU_MAX ((CONFIG_BLE_MESH_ADV_BUF_COUNT - 3) * 12) + +static esp_err_t ble_mesh_send_msg(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint32_t opcode, + btc_ble_mesh_model_act_t act, + uint16_t length, uint8_t *data, + int32_t msg_timeout, bool need_rsp, + esp_ble_mesh_dev_role_t device_role) +{ + btc_ble_mesh_model_args_t arg = {0}; + uint8_t op_len = 0, mic_len = 0; + uint8_t *msg_data = NULL; + btc_msg_t msg = {0}; + esp_err_t status; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + if (device_role > ROLE_FAST_PROV) { + return ESP_ERR_INVALID_ARG; + } + + /* When data is NULL, it is mandatory to set length to 0 to prevent users from misinterpreting parameters. */ + if (data == NULL) { + length = 0; + } + + if (opcode < 0x100) { + op_len = 1; + } else if (opcode < 0x10000) { + op_len = 2; + } else { + op_len = 3; + } + + if (act == BTC_BLE_MESH_ACT_MODEL_PUBLISH) { + if (op_len + length > model->pub->msg->size) { + LOG_ERROR("%s, Model publication msg size %d is too small", __func__, model->pub->msg->size); + return ESP_ERR_INVALID_ARG; + } + } + + if (act == BTC_BLE_MESH_ACT_MODEL_PUBLISH) { + mic_len = 4; + } else { + mic_len = ctx->send_rel ? 8 : 4; + } + + if (op_len + length + mic_len > MIN(ESP_BLE_MESH_SDU_MAX_LEN, ESP_BLE_MESH_TX_SDU_MAX)) { + LOG_ERROR("%s, Data length %d is too large", __func__, length); + return ESP_ERR_INVALID_ARG; + } + + if (act == BTC_BLE_MESH_ACT_MODEL_PUBLISH) { + bt_mesh_model_msg_init(model->pub->msg, opcode); + net_buf_simple_add_mem(model->pub->msg, data, length); + } else { + msg_data = (uint8_t *)osi_malloc(op_len + length); + if (msg_data == NULL) { + return ESP_ERR_NO_MEM; + } + esp_ble_mesh_model_msg_opcode_init(msg_data, opcode); + memcpy(msg_data + op_len, data, length); + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_MODEL; + msg.act = act; + + if (act == BTC_BLE_MESH_ACT_MODEL_PUBLISH) { + arg.model_publish.model = model; + arg.model_publish.device_role = device_role; + } else { + arg.model_send.model = model; + arg.model_send.ctx = ctx; + arg.model_send.need_rsp = need_rsp; + arg.model_send.opcode = opcode; + arg.model_send.length = op_len + length; + arg.model_send.data = msg_data; + arg.model_send.device_role = device_role; + arg.model_send.msg_timeout = msg_timeout; + } + + status = (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_model_args_t), btc_ble_mesh_prov_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); + + osi_free(msg_data); + + return status; +} + +esp_err_t esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_MODEL, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_model_msg_opcode_init(uint8_t *data, uint32_t opcode) +{ + uint16_t val; + + if (data == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (opcode < 0x100) { + /* 1-byte OpCode */ + data[0] = opcode & 0xff; + return ESP_OK; + } + + if (opcode < 0x10000) { + /* 2-byte OpCode, big endian */ + val = sys_cpu_to_be16 (opcode); + memcpy(data, &val, 2); + return ESP_OK; + } + + /* 3-byte OpCode, note that little endian for the least 2 bytes(Company ID) of opcode */ + data[0] = (opcode >> 16) & 0xff; + val = sys_cpu_to_le16(opcode & 0xffff); + memcpy(&data[1], &val, 2); + + return ESP_OK; +} + +esp_err_t esp_ble_mesh_client_model_init(esp_ble_mesh_model_t *model) +{ + if (model == NULL) { + return ESP_ERR_INVALID_ARG; + } + return btc_ble_mesh_client_init(model); +} + +esp_err_t esp_ble_mesh_server_model_send_msg(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, uint32_t opcode, + uint16_t length, uint8_t *data) +{ + if (!model || !ctx) { + return ESP_ERR_INVALID_ARG; + } + return ble_mesh_send_msg(model, ctx, opcode, BTC_BLE_MESH_ACT_SERVER_MODEL_SEND, + length, data, 0, false, ROLE_NODE); +} + +esp_err_t esp_ble_mesh_client_model_send_msg(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, uint32_t opcode, + uint16_t length, uint8_t *data, int32_t msg_timeout, + bool need_rsp, esp_ble_mesh_dev_role_t device_role) +{ + if (!model || !ctx) { + return ESP_ERR_INVALID_ARG; + } + return ble_mesh_send_msg(model, ctx, opcode, BTC_BLE_MESH_ACT_CLIENT_MODEL_SEND, + length, data, msg_timeout, need_rsp, device_role); +} + +esp_err_t esp_ble_mesh_model_publish(esp_ble_mesh_model_t *model, uint32_t opcode, + uint16_t length, uint8_t *data, + esp_ble_mesh_dev_role_t device_role) +{ + if (!model || !model->pub || !model->pub->msg) { + return ESP_ERR_INVALID_ARG; + } + return ble_mesh_send_msg(model, NULL, opcode, BTC_BLE_MESH_ACT_MODEL_PUBLISH, + length, data, 0, false, device_role); +} + +esp_err_t esp_ble_mesh_node_local_reset(void) +{ + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_NODE_RESET; + + return (btc_transfer_context(&msg, NULL, 0, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +#if (CONFIG_BLE_MESH_PROVISIONER) + +esp_err_t esp_ble_mesh_provisioner_set_node_name(int index, const char *name) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!name || (strlen(name) > ESP_BLE_MESH_NODE_NAME_MAX_LEN)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_SET_NODE_NAME; + + arg.set_node_name.index = index; + memset(arg.set_node_name.name, 0, sizeof(arg.set_node_name.name)); + memcpy(arg.set_node_name.name, name, strlen(name)); + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +const char *esp_ble_mesh_provisioner_get_node_name(int index) +{ + return bt_mesh_provisioner_get_node_name(index); +} + +int esp_ble_mesh_provisioner_get_node_index(const char *name) +{ + if (!name || (strlen(name) > ESP_BLE_MESH_NODE_NAME_MAX_LEN)) { + return -EINVAL; + } + + return bt_mesh_provisioner_get_node_index(name); +} + +esp_err_t esp_ble_mesh_provisioner_add_local_app_key(const uint8_t app_key[16], + uint16_t net_idx, uint16_t app_idx) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_SET_LOCAL_APP_KEY; + + arg.add_local_app_key.net_idx = net_idx; + arg.add_local_app_key.app_idx = app_idx; + if (app_key) { + memcpy(arg.add_local_app_key.app_key, app_key, 16); + } else { + bzero(arg.add_local_app_key.app_key, 16); + } + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +const uint8_t *esp_ble_mesh_provisioner_get_local_app_key(uint16_t net_idx, uint16_t app_idx) +{ + return bt_mesh_provisioner_local_app_key_get(net_idx, app_idx); +} + +esp_err_t esp_ble_mesh_provisioner_bind_app_key_to_local_model(uint16_t element_addr, uint16_t app_idx, + uint16_t model_id, uint16_t company_id) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(element_addr)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_BIND_LOCAL_MOD_APP; + + arg.local_mod_app_bind.elem_addr = element_addr; + arg.local_mod_app_bind.app_idx = app_idx; + arg.local_mod_app_bind.model_id = model_id; + arg.local_mod_app_bind.cid = company_id; + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_provisioner_add_local_net_key(const uint8_t net_key[16], uint16_t net_idx) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (net_idx == ESP_BLE_MESH_KEY_PRIMARY) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_ADD_LOCAL_NET_KEY; + + arg.add_local_net_key.net_idx = net_idx; + if (net_key) { + memcpy(arg.add_local_net_key.net_key, net_key, 16); + } else { + bzero(arg.add_local_net_key.net_key, 16); + } + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +const uint8_t *esp_ble_mesh_provisioner_get_local_net_key(uint16_t net_idx) +{ + return bt_mesh_provisioner_local_net_key_get(net_idx); +} + +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +#if (CONFIG_BLE_MESH_FAST_PROV) +const uint8_t *esp_ble_mesh_get_fast_prov_app_key(uint16_t net_idx, uint16_t app_idx) +{ + return bt_mesh_get_fast_prov_app_key(net_idx, app_idx); +} +#endif /* CONFIG_BLE_MESH_FAST_PROV */ + diff --git a/components/bt/ble_mesh/api/core/esp_ble_mesh_provisioning_api.c b/components/bt/ble_mesh/api/core/esp_ble_mesh_provisioning_api.c new file mode 100644 index 0000000000..855bcf188f --- /dev/null +++ b/components/bt/ble_mesh/api/core/esp_ble_mesh_provisioning_api.c @@ -0,0 +1,423 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_err.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_prov.h" +#include "esp_ble_mesh_provisioning_api.h" + +#define MAX_PROV_LINK_IDX (CONFIG_BLE_MESH_PBA_SAME_TIME + CONFIG_BLE_MESH_PBG_SAME_TIME) +#define MAX_OOB_INPUT_NUM 0x5F5E0FF /* Decimal: 99999999 */ + +esp_err_t esp_ble_mesh_register_prov_callback(esp_ble_mesh_prov_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_PROV, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +bool esp_ble_mesh_node_is_provisioned(void) +{ + return bt_mesh_is_provisioned(); +} + +esp_err_t esp_ble_mesh_node_prov_enable(esp_ble_mesh_prov_bearer_t bearers) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROV_ENABLE; + arg.node_prov_enable.bearers = bearers; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_node_prov_disable(esp_ble_mesh_prov_bearer_t bearers) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROV_DISABLE; + arg.node_prov_disable.bearers = bearers; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_node_set_oob_pub_key(uint8_t pub_key_x[32], uint8_t pub_key_y[32], + uint8_t private_key[32]) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!pub_key_x || !pub_key_y || !private_key) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_SET_OOB_PUB_KEY; + + memcpy(arg.set_oob_pub_key.pub_key_x, pub_key_x, 32); + memcpy(arg.set_oob_pub_key.pub_key_y, pub_key_y, 32); + memcpy(arg.set_oob_pub_key.private_key, private_key, 32); + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_node_input_number(uint32_t number) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (number > MAX_OOB_INPUT_NUM) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_INPUT_NUMBER; + arg.input_number.number = number; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_node_input_string(const char *string) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!string) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_INPUT_STRING; + memset(arg.input_string.string, 0, sizeof(arg.input_string.string)); + strncpy(arg.input_string.string, string, strlen(string)); + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_set_unprovisioned_device_name(const char *name) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!name || strlen(name) > ESP_BLE_MESH_DEVICE_NAME_MAX_LEN) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_SET_DEVICE_NAME; + + memset(arg.set_device_name.name, 0, sizeof(arg.set_device_name.name)); + memcpy(arg.set_device_name.name, name, strlen(name)); + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +#if (CONFIG_BLE_MESH_PROVISIONER) +esp_err_t esp_ble_mesh_provisioner_read_oob_pub_key(uint8_t link_idx, uint8_t pub_key_x[32], + uint8_t pub_key_y[32]) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!pub_key_x || !pub_key_y || link_idx >= MAX_PROV_LINK_IDX) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_READ_OOB_PUB_KEY; + + arg.provisioner_read_oob_pub_key.link_idx = link_idx; + memcpy(arg.provisioner_read_oob_pub_key.pub_key_x, pub_key_x, 32); + memcpy(arg.provisioner_read_oob_pub_key.pub_key_y, pub_key_y, 32); + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_provisioner_input_string(const char *string, uint8_t link_idx) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!string || link_idx >= MAX_PROV_LINK_IDX) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_INPUT_STR; + + memset(arg.provisioner_input_str.string, 0, sizeof(arg.provisioner_input_str.string)); + strncpy(arg.provisioner_input_str.string, string, strlen(string)); + arg.provisioner_input_str.link_idx = link_idx; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_provisioner_input_number(uint32_t number, uint8_t link_idx) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (number > MAX_OOB_INPUT_NUM || link_idx >= MAX_PROV_LINK_IDX) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_INPUT_NUM; + + arg.provisioner_input_num.number = number; + arg.provisioner_input_num.link_idx = link_idx; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_provisioner_prov_enable(esp_ble_mesh_prov_bearer_t bearers) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_ENABLE; + + arg.provisioner_enable.bearers = bearers; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_provisioner_prov_disable(esp_ble_mesh_prov_bearer_t bearers) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_DISABLE; + + arg.provisioner_disable.bearers = bearers; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_provisioner_add_unprov_dev(esp_ble_mesh_unprov_dev_add_t *add_dev, + esp_ble_mesh_dev_add_flag_t flags) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (add_dev == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_DEV_ADD; + + arg.provisioner_dev_add.add_dev.addr_type = add_dev->addr_type; + arg.provisioner_dev_add.add_dev.oob_info = add_dev->oob_info; + arg.provisioner_dev_add.add_dev.bearer = add_dev->bearer; + memcpy(arg.provisioner_dev_add.add_dev.addr, add_dev->addr, sizeof(esp_bd_addr_t)); + memcpy(arg.provisioner_dev_add.add_dev.uuid, add_dev->uuid, 16); + arg.provisioner_dev_add.flags = flags; + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_provisioner_delete_dev(esp_ble_mesh_device_delete_t *del_dev) +{ + uint8_t val = DEL_DEV_ADDR_FLAG | DEL_DEV_UUID_FLAG; + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (del_dev == NULL || (__builtin_popcount(del_dev->flag & val) != 1)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_DEV_DEL; + + arg.provisioner_dev_del.del_dev.flag = del_dev->flag; + if (del_dev->flag & DEL_DEV_ADDR_FLAG) { + arg.provisioner_dev_del.del_dev.addr_type = del_dev->addr_type; + memcpy(arg.provisioner_dev_del.del_dev.addr, del_dev->addr, sizeof(esp_bd_addr_t)); + } else if (del_dev->flag & DEL_DEV_UUID_FLAG) { + memcpy(arg.provisioner_dev_del.del_dev.uuid, del_dev->uuid, 16); + } + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_provisioner_set_dev_uuid_match(const uint8_t *match_val, uint8_t match_len, + uint8_t offset, bool prov_after_match) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_SET_DEV_UUID_MATCH; + + if (match_len && match_val) { + memcpy(arg.set_dev_uuid_match.match_val, match_val, match_len); + } + arg.set_dev_uuid_match.match_len = match_len; + arg.set_dev_uuid_match.offset = offset; + arg.set_dev_uuid_match.prov_after_match = prov_after_match; + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_provisioner_set_prov_data_info(esp_ble_mesh_prov_data_info_t *prov_data_info) +{ + uint8_t val = PROV_DATA_NET_IDX_FLAG | PROV_DATA_FLAGS_FLAG | PROV_DATA_IV_INDEX_FLAG; + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (prov_data_info == NULL || (__builtin_popcount(prov_data_info->flag & val) != 1)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_SET_PROV_DATA_INFO; + + arg.set_prov_data_info.prov_data.flag = prov_data_info->flag; + if (prov_data_info->flag & PROV_DATA_NET_IDX_FLAG) { + arg.set_prov_data_info.prov_data.net_idx = prov_data_info->net_idx; + } else if (prov_data_info->flag & PROV_DATA_FLAGS_FLAG) { + arg.set_prov_data_info.prov_data.flags = prov_data_info->flags; + } else if (prov_data_info->flag & PROV_DATA_IV_INDEX_FLAG) { + arg.set_prov_data_info.prov_data.iv_index = prov_data_info->iv_index; + } + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +/* The following APIs are for fast provisioning */ + +#if (CONFIG_BLE_MESH_FAST_PROV) + +esp_err_t esp_ble_mesh_set_fast_prov_info(esp_ble_mesh_fast_prov_info_t *fast_prov_info) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (fast_prov_info == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_SET_FAST_PROV_INFO; + + arg.set_fast_prov_info.unicast_min = fast_prov_info->unicast_min; + arg.set_fast_prov_info.unicast_max = fast_prov_info->unicast_max; + arg.set_fast_prov_info.net_idx = fast_prov_info->net_idx; + arg.set_fast_prov_info.flags = fast_prov_info->flags; + arg.set_fast_prov_info.iv_index = fast_prov_info->iv_index; + arg.set_fast_prov_info.offset = fast_prov_info->offset; + arg.set_fast_prov_info.match_len = fast_prov_info->match_len; + if (fast_prov_info->match_len && fast_prov_info->match_val) { + memcpy(arg.set_fast_prov_info.match_val, fast_prov_info->match_val, fast_prov_info->match_len); + } + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_set_fast_prov_action(esp_ble_mesh_fast_prov_action_t action) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (action >= FAST_PROV_ACT_MAX) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_SET_FAST_PROV_ACTION; + + arg.set_fast_prov_action.action = action; + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +#endif /* CONFIG_BLE_MESH_FAST_PROV */ + diff --git a/components/bt/ble_mesh/api/core/esp_ble_mesh_proxy_api.c b/components/bt/ble_mesh/api/core/esp_ble_mesh_proxy_api.c new file mode 100644 index 0000000000..0605e97a6b --- /dev/null +++ b/components/bt/ble_mesh/api/core/esp_ble_mesh_proxy_api.c @@ -0,0 +1,65 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_err.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_prov.h" +#include "esp_ble_mesh_defs.h" + +esp_err_t esp_ble_mesh_proxy_identity_enable(void) +{ + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROXY_IDENTITY_ENABLE; + + return (btc_transfer_context(&msg, NULL, 0, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_proxy_gatt_enable(void) +{ + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROXY_GATT_ENABLE; + + return (btc_transfer_context(&msg, NULL, 0, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_proxy_gatt_disable(void) +{ + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROXY_GATT_DISABLE; + + return (btc_transfer_context(&msg, NULL, 0, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + diff --git a/components/bt/ble_mesh/api/core/include/esp_ble_mesh_common_api.h b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_common_api.h new file mode 100644 index 0000000000..a550ad381f --- /dev/null +++ b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_common_api.h @@ -0,0 +1,37 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_BLE_MESH_COMMON_API_H_ +#define _ESP_BLE_MESH_COMMON_API_H_ + +#include "esp_ble_mesh_defs.h" + +/** + * @brief Initialize BLE Mesh module. + * This API initializes provisioning capabilities and composition data information. + * + * @note After calling this API, the device needs to call esp_ble_mesh_prov_enable() + * to enable provisioning functionality again. + * + * @param[in] prov: Pointer to the device provisioning capabilities. This pointer must + * remain valid during the lifetime of the BLE Mesh device. + * @param[in] comp: Pointer to the device composition data information. This pointer + * must remain valid during the lifetime of the BLE Mesh device. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_init(esp_ble_mesh_prov_t *prov, esp_ble_mesh_comp_t *comp); + +#endif /* _ESP_BLE_MESH_COMMON_API_H_ */ diff --git a/components/bt/ble_mesh/api/core/include/esp_ble_mesh_local_data_operation_api.h b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_local_data_operation_api.h new file mode 100644 index 0000000000..0acb6d73f0 --- /dev/null +++ b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_local_data_operation_api.h @@ -0,0 +1,113 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_BLE_MESH_LOCAL_DATA_OPERATION_API_H_ +#define _ESP_BLE_MESH_LOCAL_DATA_OPERATION_API_H_ + +#include "esp_ble_mesh_defs.h" + +/** + * @brief Get the model publish period, the unit is ms. + * + * @param[in] model: Model instance pointer. + * + * @return Publish period value on success, 0 or (negative) error code from errno.h on failure. + * + */ +int32_t esp_ble_mesh_get_model_publish_period(esp_ble_mesh_model_t *model); + +/** + * @brief Get the address of the primary element. + * + * @param None. + * + * @return Address of the primary element on success, or + * ESP_BLE_MESH_ADDR_UNASSIGNED on failure which means the device has not been provisioned. + * + */ +uint16_t esp_ble_mesh_get_primary_element_address(void); + +/** + * @brief Check if the model has subscribed to the given group address. + * Note: E.g., once a status message is received and the destination address + * is a group address, the model uses this API to check if it is successfully subscribed + * to the given group address. + * + * @param[in] model: Pointer to the model. + * @param[in] group_addr: Group address. + * + * @return Pointer to the group address within the Subscription List of the model on success, or + * NULL on failure which means the model has not subscribed to the given group address. + * Note: With the pointer to the group address returned, you can reset the group address + * to 0x0000 in order to unsubscribe the model from the group. + * + */ +uint16_t *esp_ble_mesh_is_model_subscribed_to_group(esp_ble_mesh_model_t *model, uint16_t group_addr); + +/** + * @brief Find the BLE Mesh element pointer via the element address. + * + * @param[in] element_addr: Element address. + * + * @return Pointer to the element on success, or NULL on failure. + * + */ +esp_ble_mesh_elem_t *esp_ble_mesh_find_element(uint16_t element_addr); + +/** + * @brief Get the number of elements that have been registered. + * + * @param None. + * + * @return Number of elements. + * + */ +uint8_t esp_ble_mesh_get_element_count(void); + +/** + * @brief Find the Vendor specific model with the given element, + * the company ID and the Vendor Model ID. + * + * @param[in] element: Element to which the model belongs. + * @param[in] company_id: A 16-bit company identifier assigned by the Bluetooth SIG. + * @param[in] model_id: A 16-bit vendor-assigned model identifier. + * + * @return Pointer to the Vendor Model on success, or NULL on failure which means the Vendor Model is not found. + * + */ +esp_ble_mesh_model_t *esp_ble_mesh_find_vendor_model(const esp_ble_mesh_elem_t *element, + uint16_t company_id, uint16_t model_id); + +/** + * @brief Find the SIG model with the given element and Model id. + * + * @param[in] element: Element to which the model belongs. + * @param[in] model_id: SIG model identifier. + * + * @return Pointer to the SIG Model on success, or NULL on failure which means the SIG Model is not found. + * + */ +esp_ble_mesh_model_t *esp_ble_mesh_find_sig_model(const esp_ble_mesh_elem_t *element, uint16_t model_id); + +/** + * @brief Get the Composition data which has been registered. + * + * @param None. + * + * @return Pointer to the Composition data on success, or NULL on failure which means the Composition data is not initialized. + * + */ +const esp_ble_mesh_comp_t *esp_ble_mesh_get_composition_data(void); + +#endif /* _ESP_BLE_MESH_LOCAL_DATA_OPERATION_API_H_ */ diff --git a/components/bt/ble_mesh/api/core/include/esp_ble_mesh_low_power_api.h b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_low_power_api.h new file mode 100644 index 0000000000..7a203d50cc --- /dev/null +++ b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_low_power_api.h @@ -0,0 +1,20 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_BLE_MESH_LOW_POWER_API_H_ +#define _ESP_BLE_MESH_LOW_POWER_API_H_ + +#include "esp_ble_mesh_defs.h" + +#endif /* _ESP_BLE_MESH_LOW_POWER_API_H_ */ diff --git a/components/bt/ble_mesh/api/core/include/esp_ble_mesh_networking_api.h b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_networking_api.h new file mode 100644 index 0000000000..d3df66879d --- /dev/null +++ b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_networking_api.h @@ -0,0 +1,267 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_BLE_MESH_NETWORKING_API_H_ +#define _ESP_BLE_MESH_NETWORKING_API_H_ + +#include "esp_ble_mesh_defs.h" + +/** @brief: event, event code of user-defined model events; param, parameters of user-defined model events */ +typedef void (* esp_ble_mesh_model_cb_t)(esp_ble_mesh_model_cb_event_t event, + esp_ble_mesh_model_cb_param_t *param); + +/** + * @brief Register BLE Mesh callback for user-defined models' operations. + * This callback can report the following events generated for the user-defined models: + * - Call back the messages received by user-defined client and server models to the + * application layer; + * - If users call esp_ble_mesh_server/client_model_send, this callback notifies the + * application layer of the send_complete event; + * - If user-defined client model sends a message that requires response, and the response + * message is received after the timer expires, the response message will be reported + * to the application layer as published by a peer device; + * - If the user-defined client model fails to receive the response message during a specified + * period of time, a timeout event will be reported to the application layer. + * + * @note The client models (i.e. Config Client model, Health Client model, Generic + * Client models, Sensor Client model, Scene Client model and Lighting Client models) + * that have been realized internally have their specific register functions. + * For example, esp_ble_mesh_register_config_client_callback is the register + * function for Config Client Model. + * + * @param[in] callback: Pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb_t callback); + +/** + * @brief Add the message opcode to the beginning of the model message + * before sending or publishing the model message. + * + * @note This API is only used to set the opcode of the message. + * + * @param[in] data: Pointer to the message data. + * @param[in] opcode: The message opcode. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_model_msg_opcode_init(uint8_t *data, uint32_t opcode); + +/** + * @brief Initialize the user-defined client model. All user-defined client models + * shall call this function to initialize the client model internal data. + * Node: Before calling this API, the op_pair_size and op_pair variabled within + * the user_data(defined using esp_ble_mesh_client_t_) of the client model + * need to be initialized. + * + * @param[in] model: BLE Mesh Client model to which the message belongs. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_client_model_init(esp_ble_mesh_model_t *model); + +/** + * @brief Send server model messages(such as server model status messages). + * + * @param[in] model: BLE Mesh Server Model to which the message belongs. + * @param[in] ctx: Message context, includes keys, TTL, etc. + * @param[in] opcode: Message opcode. + * @param[in] length: Message length (exclude the message opcode). + * @param[in] data: Parameters of Access Payload (exclude the message opcode) to be sent. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_server_model_send_msg(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, uint32_t opcode, + uint16_t length, uint8_t *data); + +/** + * @brief Send client model message (such as model get, set, etc). + * + * @param[in] model: BLE Mesh Client Model to which the message belongs. + * @param[in] ctx: Message context, includes keys, TTL, etc. + * @param[in] opcode: Message opcode. + * @param[in] length: Message length (exclude the message opcode). + * @param[in] data: Parameters of the Access Payload (exclude the message opcode) to be sent. + * @param[in] msg_timeout: Time to get response to the message (in milliseconds). + * @param[in] need_rsp: TRUE if the opcode requires the peer device to reply, FALSE otherwise. + * @param[in] device_role: Role of the device (Node/Provisioner) that sends the message. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_client_model_send_msg(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, uint32_t opcode, + uint16_t length, uint8_t *data, int32_t msg_timeout, + bool need_rsp, esp_ble_mesh_dev_role_t device_role); + +/** + * @brief Send a model publication message. + * + * @note Before calling this function, the user needs to ensure that the model + * publication message (@ref esp_ble_mesh_model_pub_t.msg) contains a valid + * message to be sent. And if users want to update the publishing message, + * this API should be called in ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT + * with the message updated. + * + * + * @param[in] model: Mesh (client) Model publishing the message. + * @param[in] opcode: Message opcode. + * @param[in] length: Message length (exclude the message opcode). + * @param[in] data: Parameters of the Access Payload (exclude the message opcode) to be sent. + * @param[in] device_role: Role of the device (node/provisioner) publishing the message of the type esp_ble_mesh_dev_role_t. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_model_publish(esp_ble_mesh_model_t *model, uint32_t opcode, + uint16_t length, uint8_t *data, + esp_ble_mesh_dev_role_t device_role); + +/** + * @brief Reset the provisioning procedure of the local BLE Mesh node. + * + * @note All provisioning information in this node will be deleted and the node + * needs to be reprovisioned. The API function esp_ble_mesh_node_prov_enable() + * needs to be called to start a new provisioning procedure. + * + * @param None. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_node_local_reset(void); + +/** + * @brief This function is called to set the node (provisioned device) name. + * + * @param[in] index: Index of the node in the node queue. + * @param[in] name: Name (end by '\0') to be set for the node. + * + * @note index is obtained from the parameters of ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_set_node_name(int index, const char *name); + +/** + * @brief This function is called to get the node (provisioned device) name. + * + * @param[in] index: Index of the node in the node queue. + * + * @note index is obtained from the parameters of ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT. + * + * @return Node name on success, or NULL on failure. + * + */ +const char *esp_ble_mesh_provisioner_get_node_name(int index); + +/** + * @brief This function is called to get the node (provisioned device) index. + * + * @param[in] name: Name of the node (end by '\0'). + * + * @return Node index on success, or (negative) error code from errno.h on failure. + * + */ +int esp_ble_mesh_provisioner_get_node_index(const char *name); + +/** + * @brief This function is called to set the app key for the local BLE Mesh stack. + * + * @param[in] app_key: The app key to be set for the local BLE Mesh stack. + * @param[in] net_idx: The network key index. + * @param[in] app_idx: The app key index. + * + * @note app_key: If set to NULL, app_key will be generated internally. + * net_idx: Should be an existing one. + * app_idx: If it is going to be generated internally, it should be set to + * 0xFFFF, and the new app_idx will be reported via an event. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_add_local_app_key(const uint8_t app_key[16], uint16_t net_idx, uint16_t app_idx); + +/** + * @brief This function is called by Provisioner to get the local app key value. + * + * @param[in] net_idx: Network key index. + * @param[in] app_idx: Application key index. + * + * @return App key on success, or NULL on failure. + * + */ +const uint8_t *esp_ble_mesh_provisioner_get_local_app_key(uint16_t net_idx, uint16_t app_idx); + +/** + * @brief This function is called by Provisioner to bind own model with proper app key. + * + * @param[in] element_addr: Provisioner local element address + * @param[in] app_idx: Provisioner local appkey index + * @param[in] model_id: Provisioner local model id + * @param[in] company_id: Provisioner local company id + * + * @note company_id: If going to bind app_key with local vendor model, company_id + * should be set to 0xFFFF. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_bind_app_key_to_local_model(uint16_t element_addr, uint16_t app_idx, + uint16_t model_id, uint16_t company_id); + +/** + * @brief This function is called by Provisioner to add local network key. + * + * @param[in] net_key: The network key to be added to the Provisioner local BLE Mesh stack. + * @param[in] net_idx: The network key index. + * + * @note net_key: If set to NULL, net_key will be generated internally. + * net_idx: If it is going to be generated internally, it should be set to + * 0xFFFF, and the new net_idx will be reported via an event. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_add_local_net_key(const uint8_t net_key[16], uint16_t net_idx); + +/** + * @brief This function is called by Provisioner to get the local network key value. + * + * @param[in] net_idx: Network key index. + * + * @return Network key on success, or NULL on failure. + * + */ +const uint8_t *esp_ble_mesh_provisioner_get_local_net_key(uint16_t net_idx); + +/** + * @brief This function is called to get fast provisioning application key. + * + * @param[in] net_idx: Network key index. + * @param[in] app_idx: Application key index. + * + * @return Application key on success, or NULL on failure. + * + */ +const uint8_t *esp_ble_mesh_get_fast_prov_app_key(uint16_t net_idx, uint16_t app_idx); + +#endif /* _ESP_BLE_MESH_NETWORKING_API_H_ */ diff --git a/components/bt/ble_mesh/api/core/include/esp_ble_mesh_provisioning_api.h b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_provisioning_api.h new file mode 100644 index 0000000000..e03c4d4ef1 --- /dev/null +++ b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_provisioning_api.h @@ -0,0 +1,316 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_BLE_MESH_PROVISIONING_API_H_ +#define _ESP_BLE_MESH_PROVISIONING_API_H_ + +#include "esp_ble_mesh_defs.h" + +/** @brief: event, event code of provisioning events; param, parameters of provisioning events */ +typedef void (* esp_ble_mesh_prov_cb_t)(esp_ble_mesh_prov_cb_event_t event, + esp_ble_mesh_prov_cb_param_t *param); + +/** + * @brief Register BLE Mesh provisioning callback. + * + * @param[in] callback: Pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_prov_callback(esp_ble_mesh_prov_cb_t callback); + +/** + * @brief Check if a device has been provisioned. + * + * @param None. + * + * @return TRUE if the device is provisioned, FALSE if the device is unprovisioned. + * + */ +bool esp_ble_mesh_node_is_provisioned(void); + +/** + * @brief Enable specific provisioning bearers to get the device ready for provisioning. + * + * @note PB-ADV: send unprovisioned device beacon. + * PB-GATT: send connectable advertising packets. + * + * @param bearers: Bit-wise OR of provisioning bearers. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_node_prov_enable(esp_ble_mesh_prov_bearer_t bearers); + +/** + * @brief Disable specific provisioning bearers to make a device inaccessible for provisioning. + * + * @param bearers: Bit-wise OR of provisioning bearers. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_node_prov_disable(esp_ble_mesh_prov_bearer_t bearers); + +/** + * @brief Unprovisioned device set own oob public key & private key pair. + * + * @param[in] pub_key_x: Unprovisioned device's Public Key X + * @param[in] pub_key_y: Unprovisioned device's Public Key Y + * @param[in] private_key: Unprovisioned device's Private Key + * + * @return ESP_OK on success or error code otherwise. + */ +esp_err_t esp_ble_mesh_node_set_oob_pub_key(uint8_t pub_key_x[32], uint8_t pub_key_y[32], + uint8_t private_key[32]); + +/** + * @brief Provide provisioning input OOB number. + * + * @note This is intended to be called if the user has received ESP_BLE_MESH_NODE_PROV_INPUT_EVT + * with ESP_BLE_MESH_ENTER_NUMBER as the action. + * + * @param[in] number: Number input by device. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_node_input_number(uint32_t number); + +/** + * @brief Provide provisioning input OOB string. + * + * @note This is intended to be called if the user has received ESP_BLE_MESH_NODE_PROV_INPUT_EVT + * with ESP_BLE_MESH_ENTER_STRING as the action. + * + * @param[in] string: String input by device. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_node_input_string(const char *string); + +/** + * @brief Using this function, an unprovisioned device can set its own device name, + * which will be broadcasted in its advertising data. + * + * @param[in] name: Unprovisioned device name + * + * @note This API applicable to PB-GATT mode only by setting the name to the scan response data, + * it doesn't apply to PB-ADV mode. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_set_unprovisioned_device_name(const char *name); + +/** + * @brief Provisioner inputs unprovisioned device's oob public key. + * + * @param[in] link_idx: The provisioning link index + * @param[in] pub_key_x: Unprovisioned device's Public Key X + * @param[in] pub_key_y: Unprovisioned device's Public Key Y + * + * @return ESP_OK on success or error code otherwise. + */ +esp_err_t esp_ble_mesh_provisioner_read_oob_pub_key(uint8_t link_idx, uint8_t pub_key_x[32], + uint8_t pub_key_y[32]); + +/** + * @brief Provide provisioning input OOB string. + * + * This is intended to be called after the esp_ble_mesh_prov_t prov_input_num + * callback has been called with ESP_BLE_MESH_ENTER_STRING as the action. + * + * @param[in] string: String input by Provisioner. + * @param[in] link_idx: The provisioning link index. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_input_string(const char *string, uint8_t link_idx); + +/** + * @brief Provide provisioning input OOB number. + * + * This is intended to be called after the esp_ble_mesh_prov_t prov_input_num + * callback has been called with ESP_BLE_MESH_ENTER_NUMBER as the action. + * + * @param[in] number: Number input by Provisioner. + * @param[in] link_idx: The provisioning link index. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_input_number(uint32_t number, uint8_t link_idx); + +/** + * @brief Enable one or more provisioning bearers. + * + * @param[in] bearers: Bit-wise OR of provisioning bearers. + * + * @note PB-ADV: Enable BLE scan. + * PB-GATT: Initialize corresponding BLE Mesh Proxy info. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_prov_enable(esp_ble_mesh_prov_bearer_t bearers); + +/** + * @brief Disable one or more provisioning bearers. + * + * @param[in] bearers: Bit-wise OR of provisioning bearers. + * + * @note PB-ADV: Disable BLE scan. + * PB-GATT: Break any existing BLE Mesh Provisioning connections. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_prov_disable(esp_ble_mesh_prov_bearer_t bearers); + +/** + * @brief Add unprovisioned device info to the unprov_dev queue. + * + * @param[in] add_dev: Pointer to a struct containing the device information + * @param[in] flags: Flags indicate several operations on the device information + * - Remove device information from queue after device has been provisioned (BIT0) + * - Start provisioning immediately after device is added to queue (BIT1) + * - Device can be removed if device queue is full (BIT2) + * + * @return ESP_OK on success or error code otherwise. + * + * @note: 1. Currently address type only supports public address and static random address. + * 2. If device UUID and/or device address as well as address type already exist in the + * device queue, but the bearer is different from the existing one, add operation + * will also be successful and it will update the provision bearer supported by + * the device. + * 3. For example, if the Provisioner wants to add an unprovisioned device info before + * receiving its unprovisioned device beacon or Mesh Provisioning advertising packets, + * the Provisioner can use this API to add the device info with each one or both of + * device UUID and device address added. When the Provisioner gets the device's + * advertising packets, it will start provisioning the device internally. + * - In this situation, the Provisioner can set bearers with each one or both of + * ESP_BLE_MESH_PROV_ADV and ESP_BLE_MESH_PROV_GATT enabled, and cannot set flags + * with ADD_DEV_START_PROV_NOW_FLAG enabled. + * 4. Another example is when the Provisioner receives the unprovisioned device's beacon or + * Mesh Provisioning advertising packets, the advertising packets will be reported on to + * the application layer using the callback registered by the function + * esp_ble_mesh_register_prov_callback. And in the callback, the Provisioner + * can call this API to start provisioning the device. + * - If the Provisioner uses PB-ADV to provision, either one or both of device UUID and + * device address can be added, bearers shall be set with ESP_BLE_MESH_PROV_ADV + * enabled and the flags shall be set with ADD_DEV_START_PROV_NOW_FLAG enabled. + * - If the Provisioner uses PB-GATT to provision, both the device UUID and device + * address need to be added, bearers shall be set with ESP_BLE_MESH_PROV_GATT enabled, + * and the flags shall be set with ADD_DEV_START_PROV_NOW_FLAG enabled. + * - If the Provisioner just wants to store the unprovisioned device info when receiving + * its advertising packets and start to provision it the next time (e.g. after receiving + * its advertising packets again), then it can add the device info with either one or both + * of device UUID and device address included. Bearers can be set with either one or both + * of ESP_BLE_MESH_PROV_ADV and ESP_BLE_MESH_PROV_GATT enabled (recommend to enable the + * bearer which will receive its advertising packets, because if the other bearer is + * enabled, the Provisioner is not aware if the device supports the bearer), and flags + * cannot be set with ADD_DEV_START_PROV_NOW_FLAG enabled. + * - Note: ESP_BLE_MESH_PROV_ADV, ESP_BLE_MESH_PROV_GATT and ADD_DEV_START_PROV_NOW_FLAG + * can not be enabled at the same time. + * + */ +esp_err_t esp_ble_mesh_provisioner_add_unprov_dev(esp_ble_mesh_unprov_dev_add_t *add_dev, + esp_ble_mesh_dev_add_flag_t flags); + +/** + * @brief Delete device from queue, reset current provisioning link and reset the node. + * + * @note If the device is in the queue, remove it from the queue; if the device is being + * provisioned, terminate the provisioning procedure; if the device has already + * been provisioned, reset the device. And either one of the addr or device UUID + * can be input. + * + * @param[in] del_dev: Pointer to a struct containing the device information. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_delete_dev(esp_ble_mesh_device_delete_t *del_dev); + +/** + * @brief Callback for Provisioner that received advertising packets from unprovisioned devices which are + * not in the unprovisioned device queue. + * + * Report on the unprovisioned device beacon and mesh provisioning service adv data to application. + * + * @param[in] addr: Pointer to the unprovisioned device address. + * @param[in] addr_type: Unprovisioned device address type. + * @param[in] adv_type: Adv packet type(ADV_IND or ADV_NONCONN_IND). + * @param[in] dev_uuid: Unprovisioned device UUID pointer. + * @param[in] oob_info: OOB information of the unprovisioned device. + * @param[in] bearer: Adv packet received from PB-GATT or PB-ADV bearer. + * + */ +typedef void (*esp_ble_mesh_prov_adv_cb_t)(const esp_bd_addr_t addr, const esp_ble_addr_type_t addr_type, + const uint8_t adv_type, const uint8_t *dev_uuid, + uint16_t oob_info, esp_ble_mesh_prov_bearer_t bearer); + +/** + * @brief This function is called by Provisioner to set the part of the device UUID + * to be compared before starting to provision. + * + * @param[in] match_val: Value to be compared with the part of the device UUID. + * @param[in] match_len: Length of the compared match value. + * @param[in] offset: Offset of the device UUID to be compared (based on zero). + * @param[in] prov_after_match: Flag used to indicate whether provisioner should start to provision + * the device immediately if the part of the UUID matches. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_set_dev_uuid_match(const uint8_t *match_val, uint8_t match_len, + uint8_t offset, bool prov_after_match); + +/** + * @brief This function is called by Provisioner to set provisioning data information + * before starting to provision. + * + * @param[in] prov_data_info: Pointer to a struct containing net_idx or flags or iv_index. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_set_prov_data_info(esp_ble_mesh_prov_data_info_t *prov_data_info); + +/** + * @brief This function is called to set provisioning data information before starting + * fast provisioning. + * + * @param[in] fast_prov_info: Pointer to a struct containing unicast address range, net_idx, etc. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_set_fast_prov_info(esp_ble_mesh_fast_prov_info_t *fast_prov_info); + +/** + * @brief This function is called to start/suspend/exit fast provisioning. + * + * @param[in] action: fast provisioning action (i.e. enter, suspend, exit). + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_set_fast_prov_action(esp_ble_mesh_fast_prov_action_t action); + +#endif /* _ESP_BLE_MESH_PROVISIONING_API_H_ */ diff --git a/components/bt/ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h new file mode 100644 index 0000000000..67a785dda6 --- /dev/null +++ b/components/bt/ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h @@ -0,0 +1,59 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_BLE_MESH_PROXY_API_H_ +#define _ESP_BLE_MESH_PROXY_API_H_ + +#include "esp_ble_mesh_defs.h" + +/** + * @brief Enable advertising with Node Identity. + * + * @note This API requires that GATT Proxy support be enabled. Once called, + * each subnet starts advertising using Node Identity for the next 60 + * seconds, and after 60s Network ID will be advertised. + * Under normal conditions, the BLE Mesh Proxy Node Identity and + * Network ID advertising will be enabled automatically by BLE Mesh + * stack after the device is provisioned. + * + * @param None. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_proxy_identity_enable(void); + +/** + * @brief Enable BLE Mesh GATT Proxy Service. + * + * @param None. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_proxy_gatt_enable(void); + +/** + * @brief Disconnect the BLE Mesh GATT Proxy connection if there is any, and + * disable the BLE Mesh GATT Proxy Service. + * + * @param None. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_proxy_gatt_disable(void); + +#endif /* _ESP_BLE_MESH_PROXY_API_H_ */ + diff --git a/components/bt/ble_mesh/api/esp_ble_mesh_defs.h b/components/bt/ble_mesh/api/esp_ble_mesh_defs.h new file mode 100644 index 0000000000..6e751bd315 --- /dev/null +++ b/components/bt/ble_mesh/api/esp_ble_mesh_defs.h @@ -0,0 +1,1524 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_BLE_MESH_DEFS_H_ +#define _ESP_BLE_MESH_DEFS_H_ + +#include + +#include "esp_bt_defs.h" + +#include "mesh_proxy.h" +#include "mesh_access.h" +#include "mesh_main.h" + +#include "mesh.h" +#include "proxy.h" +#include "foundation.h" +#include "provisioner_main.h" + +#include "model_opcode.h" +#include "mesh_common.h" + +#define ESP_BLE_MESH_SDU_MAX_LEN 384 + +/*!< The maximum length of a BLE Mesh provisioned node name */ +#define ESP_BLE_MESH_NODE_NAME_MAX_LEN 31 + +/*!< The maximum length of a BLE Mesh unprovisioned device name */ +#define ESP_BLE_MESH_DEVICE_NAME_MAX_LEN DEVICE_NAME_SIZE + +/*!< Define the BLE Mesh octet 16 bytes size */ +#define ESP_BLE_MESH_OCTET16_LEN 16 +typedef uint8_t esp_ble_mesh_octet16_t[ESP_BLE_MESH_OCTET16_LEN]; + +/*!< Define the BLE Mesh octet 8 bytes size */ +#define ESP_BLE_MESH_OCTET8_LEN 8 +typedef uint8_t esp_ble_mesh_octet8_t[ESP_BLE_MESH_OCTET8_LEN]; + +#define ESP_BLE_MESH_ADDR_UNASSIGNED BLE_MESH_ADDR_UNASSIGNED +#define ESP_BLE_MESH_ADDR_ALL_NODES BLE_MESH_ADDR_ALL_NODES +#define ESP_BLE_MESH_ADDR_PROXIES BLE_MESH_ADDR_PROXIES +#define ESP_BLE_MESH_ADDR_FRIENDS BLE_MESH_ADDR_FRIENDS +#define ESP_BLE_MESH_ADDR_RELAYS BLE_MESH_ADDR_RELAYS + +#define ESP_BLE_MESH_KEY_UNUSED BLE_MESH_KEY_UNUSED +#define ESP_BLE_MESH_KEY_DEV BLE_MESH_KEY_DEV + +#define ESP_BLE_MESH_KEY_PRIMARY BLE_MESH_KEY_PRIMARY +#define ESP_BLE_MESH_KEY_ANY BLE_MESH_KEY_ANY + +/*!< Primary Network Key index */ +#define ESP_BLE_MESH_NET_PRIMARY BLE_MESH_NET_PRIMARY + +/*!< Relay state value */ +#define ESP_BLE_MESH_RELAY_DISABLED BLE_MESH_RELAY_DISABLED +#define ESP_BLE_MESH_RELAY_ENABLED BLE_MESH_RELAY_ENABLED +#define ESP_BLE_MESH_RELAY_NOT_SUPPORTED BLE_MESH_RELAY_NOT_SUPPORTED + +/*!< Beacon state value */ +#define ESP_BLE_MESH_BEACON_DISABLED BLE_MESH_BEACON_DISABLED +#define ESP_BLE_MESH_BEACON_ENABLED BLE_MESH_BEACON_ENABLED + +/*!< GATT Proxy state value */ +#define ESP_BLE_MESH_GATT_PROXY_DISABLED BLE_MESH_GATT_PROXY_DISABLED +#define ESP_BLE_MESH_GATT_PROXY_ENABLED BLE_MESH_GATT_PROXY_ENABLED +#define ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED BLE_MESH_GATT_PROXY_NOT_SUPPORTED + +/*!< Friend state value */ +#define ESP_BLE_MESH_FRIEND_DISABLED BLE_MESH_FRIEND_DISABLED +#define ESP_BLE_MESH_FRIEND_ENABLED BLE_MESH_FRIEND_ENABLED +#define ESP_BLE_MESH_FRIEND_NOT_SUPPORTED BLE_MESH_FRIEND_NOT_SUPPORTED + +/*!< Node identity state value */ +#define ESP_BLE_MESH_NODE_IDENTITY_STOPPED BLE_MESH_NODE_IDENTITY_STOPPED +#define ESP_BLE_MESH_NODE_IDENTITY_RUNNING BLE_MESH_NODE_IDENTITY_RUNNING +#define ESP_BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED + +/*!< Supported features */ +#define ESP_BLE_MESH_FEATURE_RELAY BLE_MESH_FEAT_RELAY +#define ESP_BLE_MESH_FEATURE_PROXY BLE_MESH_FEAT_PROXY +#define ESP_BLE_MESH_FEATURE_FRIEND BLE_MESH_FEAT_FRIEND +#define ESP_BLE_MESH_FEATURE_LOW_POWER BLE_MESH_FEAT_LOW_POWER +#define ESP_BLE_MESH_FEATURE_ALL_SUPPORTED BLE_MESH_FEAT_SUPPORTED + +#define ESP_BLE_MESH_ADDR_IS_UNICAST(addr) BLE_MESH_ADDR_IS_UNICAST(addr) +#define ESP_BLE_MESH_ADDR_IS_GROUP(addr) BLE_MESH_ADDR_IS_GROUP(addr) +#define ESP_BLE_MESH_ADDR_IS_VIRTUAL(addr) BLE_MESH_ADDR_IS_VIRTUAL(addr) +#define ESP_BLE_MESH_ADDR_IS_RFU(addr) BLE_MESH_ADDR_IS_RFU(addr) + +#define ESP_BLE_MESH_INVALID_NODE_INDEX (-1) + +/*!< Foundation Models */ +#define ESP_BLE_MESH_MODEL_ID_CONFIG_SRV BLE_MESH_MODEL_ID_CFG_SRV +#define ESP_BLE_MESH_MODEL_ID_CONFIG_CLI BLE_MESH_MODEL_ID_CFG_CLI +#define ESP_BLE_MESH_MODEL_ID_HEALTH_SRV BLE_MESH_MODEL_ID_HEALTH_SRV +#define ESP_BLE_MESH_MODEL_ID_HEALTH_CLI BLE_MESH_MODEL_ID_HEALTH_CLI + +/*!< Models from the Mesh Model Specification */ +#define ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV BLE_MESH_MODEL_ID_GEN_ONOFF_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI BLE_MESH_MODEL_ID_GEN_ONOFF_CLI +#define ESP_BLE_MESH_MODEL_ID_GEN_LEVEL_SRV BLE_MESH_MODEL_ID_GEN_LEVEL_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_LEVEL_CLI BLE_MESH_MODEL_ID_GEN_LEVEL_CLI +#define ESP_BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI +#define ESP_BLE_MESH_MODEL_ID_GEN_BATTERY_SRV BLE_MESH_MODEL_ID_GEN_BATTERY_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_BATTERY_CLI BLE_MESH_MODEL_ID_GEN_BATTERY_CLI +#define ESP_BLE_MESH_MODEL_ID_GEN_LOCATION_SRV BLE_MESH_MODEL_ID_GEN_LOCATION_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV BLE_MESH_MODEL_ID_GEN_LOCATION_SETUPSRV +#define ESP_BLE_MESH_MODEL_ID_GEN_LOCATION_CLI BLE_MESH_MODEL_ID_GEN_LOCATION_CLI +#define ESP_BLE_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV BLE_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV BLE_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_USER_PROP_SRV BLE_MESH_MODEL_ID_GEN_USER_PROP_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV BLE_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV +#define ESP_BLE_MESH_MODEL_ID_GEN_PROP_CLI BLE_MESH_MODEL_ID_GEN_PROP_CLI +#define ESP_BLE_MESH_MODEL_ID_SENSOR_SRV BLE_MESH_MODEL_ID_SENSOR_SRV +#define ESP_BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_SENSOR_CLI BLE_MESH_MODEL_ID_SENSOR_CLI +#define ESP_BLE_MESH_MODEL_ID_TIME_SRV BLE_MESH_MODEL_ID_TIME_SRV +#define ESP_BLE_MESH_MODEL_ID_TIME_SETUP_SRV BLE_MESH_MODEL_ID_TIME_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_TIME_CLI BLE_MESH_MODEL_ID_TIME_CLI +#define ESP_BLE_MESH_MODEL_ID_SCENE_SRV BLE_MESH_MODEL_ID_SCENE_SRV +#define ESP_BLE_MESH_MODEL_ID_SCENE_SETUP_SRV BLE_MESH_MODEL_ID_SCENE_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_SCENE_CLI BLE_MESH_MODEL_ID_SCENE_CLI +#define ESP_BLE_MESH_MODEL_ID_SCHEDULER_SRV BLE_MESH_MODEL_ID_SCHEDULER_SRV +#define ESP_BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_SCHEDULER_CLI BLE_MESH_MODEL_ID_SCHEDULER_CLI +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI +#define ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SRV BLE_MESH_MODEL_ID_LIGHT_CTL_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_CLI BLE_MESH_MODEL_ID_LIGHT_CTL_CLI +#define ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SRV BLE_MESH_MODEL_ID_LIGHT_HSL_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_CLI BLE_MESH_MODEL_ID_LIGHT_HSL_CLI +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_XYL_SRV BLE_MESH_MODEL_ID_LIGHT_XYL_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_XYL_CLI BLE_MESH_MODEL_ID_LIGHT_XYL_CLI +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LC_SRV BLE_MESH_MODEL_ID_LIGHT_LC_SRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LC_SETUP_SRV BLE_MESH_MODEL_ID_LIGHT_LC_SETUPSRV +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LC_CLI BLE_MESH_MODEL_ID_LIGHT_LC_CLI + +/*!< The following opcodes will only be used in the esp_ble_mesh_config_client_get_state function. */ +typedef uint32_t esp_ble_mesh_opcode_config_client_get_t; /*!< esp_ble_mesh_opcode_config_client_get_t belongs to esp_ble_mesh_opcode_t, + this typedef is only used to locate the opcodes used by esp_ble_mesh_config_client_get_state */ +#define ESP_BLE_MESH_MODEL_OP_BEACON_GET OP_BEACON_GET /*!< To determine the Secure Network Beacon state of a Configuration Server */ +#define ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET OP_DEV_COMP_DATA_GET /*!< To determine the Composition Data state of a Configuration Server, a Configuration + Client shall send a Config Composition Data Get message with the Page field value set + to 0xFF. The response is a Config Composition Data Status message that contains the last + page of the Composition Data state. If the Page field of the Config Composition Data Status + message contains a non-zero value, then the Configuration Client shall send another Composition + Data Get message with the Page field value set to one less than the Page field value of the + Config Composition Data Status message. */ +#define ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_GET OP_DEFAULT_TTL_GET /*!< To determine the Default TTL state of a Configuration Server */ +#define ESP_BLE_MESH_MODEL_OP_GATT_PROXY_GET OP_GATT_PROXY_GET /*!< To determine the GATT Proxy state of a Configuration Server */ +#define ESP_BLE_MESH_MODEL_OP_RELAY_GET OP_RELAY_GET /*!< To determine the Relay and Relay Retransmit states of a Configuration Server */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_GET OP_MOD_PUB_GET /*!< To determine the Publish Address, Publish AppKey Index, CredentialFlag, + Publish Period, Publish Retransmit Count, Publish Retransmit Interval Steps, + and Publish TTL states of a particular Model within the element */ +#define ESP_BLE_MESH_MODEL_OP_FRIEND_GET OP_FRIEND_GET /*!< To determine the Friend state of a Configuration Server */ +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_GET OP_HEARTBEAT_PUB_GET /*!< To determine the Heartbeat Subscription Source, Heartbeat Subscription Destination, + Heartbeat Subscription Count Log, Heartbeat Subscription Period Log, Heartbeat + Subscription Min Hops, and Heartbeat Subscription Max Hops states of a node */ +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_GET OP_HEARTBEAT_SUB_GET /*!< To determine the Heartbeat Subscription Source, Heartbeat Subscription Destination, + Heartbeat Subscription Count Log, Heartbeat Subscription Period Log, Heartbeat + Subscription Min Hops, and Heartbeat Subscription Max Hops states of a node */ +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_GET OP_NET_KEY_GET /*!< To determine all NetKeys known to the node */ +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_GET OP_APP_KEY_GET /*!< To determine all AppKeys bound to the NetKey */ +#define ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_GET OP_NODE_IDENTITY_GET /*!< To get the current Node Identity state for a subnet */ +#define ESP_BLE_MESH_MODEL_OP_SIG_MODEL_SUB_GET OP_MOD_SUB_GET /*!< To get the list of subscription addresses of a model within the element */ +#define ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_SUB_GET OP_MOD_SUB_GET_VND /*!< To get the list of subscription addresses of a model within the element */ +#define ESP_BLE_MESH_MODEL_OP_SIG_MODEL_APP_GET OP_SIG_MOD_APP_GET /*!< To request report of all AppKeys bound to the SIG Model */ +#define ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_APP_GET OP_VND_MOD_APP_GET /*!< To request report of all AppKeys bound to the Vendor Model */ +#define ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_GET OP_KRP_GET /*!< To get the current Key Refresh Phase state of the identified network key */ +#define ESP_BLE_MESH_MODEL_OP_LPN_POLLTIMEOUT_GET OP_LPN_TIMEOUT_GET /*!< To get the current value of PollTimeout timer of the Low Power node within a Friend node */ +#define ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_GET OP_NET_TRANSMIT_GET /*!< To get the current Network Transmit state of a node */ + +/*!< The following opcodes will only be used in the esp_ble_mesh_config_client_set_state function. */ +typedef uint32_t esp_ble_mesh_opcode_config_client_set_t; /*!< esp_ble_mesh_opcode_config_client_set_t belongs to esp_ble_mesh_opcode_t, + this typedef is only used to locate the opcodes used by esp_ble_mesh_config_client_set_state */ +#define ESP_BLE_MESH_MODEL_OP_BEACON_SET OP_BEACON_SET /*!< Set the Secure Network Beacon state of a Configuration Server with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_SET OP_DEFAULT_TTL_SET /*!< Set the Default TTL state of a Configuration Server with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_GATT_PROXY_SET OP_GATT_PROXY_SET /*!< Determine the GATT Proxy state of a Configuration Server */ +#define ESP_BLE_MESH_MODEL_OP_RELAY_SET OP_RELAY_SET /*!< Set the Relay and Relay Retransmit states of a Configuration Server with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_SET OP_MOD_PUB_SET /*!< Set the Publish Address, Publish AppKey Index, CredentialFlag, Publish + Period, Publish Retransmit Count, Publish Retransmit Interval Steps, and + Publish TTL states of a particular model within the element with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD OP_MOD_SUB_ADD /*!< Add the address to the Subscription List state of a particular model + within the element with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_ADD OP_MOD_SUB_VA_ADD /*!< Add the Label UUID to the Subscription List state of a particular model + within the element with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE OP_MOD_SUB_DEL /*!< Delete the address from the Subscription List state of a particular + model within the element with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_DELETE OP_MOD_SUB_VA_DEL /*!< Delete the Label UUID from the Subscription List state of a particular + model within the element with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_OVERWRITE OP_MOD_SUB_OVERWRITE /*!< Clear the Subscription List and add the address to the Subscription List + state of a particular Model within the element with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_OVERWRITE OP_MOD_SUB_VA_OVERWRITE /*!< Clear the Subscription List and add the Label UUID to the Subscription + List state of a particular model within the element with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD OP_NET_KEY_ADD /*!< Add the NetKey identified by NetKeyIndex to the NetKey List state with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD OP_APP_KEY_ADD /*!< Add the AppKey to the AppKey List and bind it to the NetKey identified + by the NetKeyIndex of a Configuration Server with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND OP_MOD_APP_BIND /*!< Bind the AppKey to a model of a particular element of a Configuration Server with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_NODE_RESET OP_NODE_RESET /*!< Reset a node (other than a Provisioner) and remove it from the network */ +#define ESP_BLE_MESH_MODEL_OP_FRIEND_SET OP_FRIEND_SET /*!< Set the Friend state of a Configuration Server with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET OP_HEARTBEAT_PUB_SET /*!< Set the Heartbeat Publication Destination, Heartbeat Publication Count, + Heartbeat Publication Period, Heartbeat Publication TTL, Publication Features, + and Publication NetKey Index of a node with acknowledgment */ +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET OP_HEARTBEAT_SUB_SET /*!< Determine the Heartbeat Subscription Source, Heartbeat Subscription Destination, + Heartbeat Subscription Count Log, Heartbeat Subscription Period Log, Heartbeat + Subscription Min Hops, and Heartbeat Subscription Max Hops states of a node */ +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_UPDATE OP_NET_KEY_UPDATE /*!< To update a NetKey on a node */ +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_DELETE OP_NET_KEY_DEL /*!< To delete a NetKey on a NetKey List from a node */ +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_UPDATE OP_APP_KEY_UPDATE /*!< To update an AppKey value on the AppKey List on a node */ +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_DELETE OP_APP_KEY_DEL /*!< To delete an AppKey from the AppKey List on a node */ +#define ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_SET OP_NODE_IDENTITY_SET /*!< To set the current Node Identity state for a subnet */ +#define ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_SET OP_KRP_SET /*!< To set the Key Refresh Phase state of the identified network key */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_VIRTUAL_ADDR_SET OP_MOD_PUB_VA_SET /*!< To set the model Publication state of an outgoing message that originates from a model */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE_ALL OP_MOD_SUB_DEL_ALL /*!< To discard the Subscription List of a model */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_APP_UNBIND OP_MOD_APP_UNBIND /*!< To remove the binding between an AppKey and a model */ +#define ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_SET OP_NET_TRANSMIT_SET /*!< To set the Network Transmit state of a node */ + +/*!< The following opcodes are used by the BLE Mesh Config Server Model internally to respond to the Config Client Model's request messages */ +typedef uint32_t esp_ble_mesh_config_model_status_t; /*!< esp_ble_mesh_config_model_status_t belongs to esp_ble_mesh_opcode_t, this typedef + is only used to locate the opcodes used by the Config Model messages */ +#define ESP_BLE_MESH_MODEL_OP_BEACON_STATUS OP_BEACON_STATUS +#define ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_STATUS OP_DEV_COMP_DATA_STATUS +#define ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_STATUS OP_DEFAULT_TTL_STATUS +#define ESP_BLE_MESH_MODEL_OP_GATT_PROXY_STATUS OP_GATT_PROXY_STATUS +#define ESP_BLE_MESH_MODEL_OP_RELAY_STATUS OP_RELAY_STATUS +#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_STATUS OP_MOD_PUB_STATUS +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_STATUS OP_MOD_SUB_STATUS +#define ESP_BLE_MESH_MODEL_OP_SIG_MODEL_SUB_LIST OP_MOD_SUB_LIST +#define ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_SUB_LIST OP_MOD_SUB_LIST_VND +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_STATUS OP_NET_KEY_STATUS +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_LIST OP_NET_KEY_LIST +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_STATUS OP_APP_KEY_STATUS +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_LIST OP_APP_KEY_LIST +#define ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_STATUS OP_NODE_IDENTITY_STATUS +#define ESP_BLE_MESH_MODEL_OP_MODEL_APP_STATUS OP_MOD_APP_STATUS +#define ESP_BLE_MESH_MODEL_OP_SIG_MODEL_APP_LIST OP_SIG_MOD_APP_LIST +#define ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_APP_LIST OP_VND_MOD_APP_LIST +#define ESP_BLE_MESH_MODEL_OP_NODE_RESET_STATUS OP_NODE_RESET_STATUS +#define ESP_BLE_MESH_MODEL_OP_FRIEND_STATUS OP_FRIEND_STATUS +#define ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_STATUS OP_KRP_STATUS +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_STATUS OP_HEARTBEAT_PUB_STATUS +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_STATUS OP_HEARTBEAT_SUB_STATUS +#define ESP_BLE_MESH_MODEL_OP_LPN_POLLTIMEOUT_STATUS OP_LPN_TIMEOUT_STATUS +#define ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_STATUS OP_NET_TRANSMIT_STATUS + +/*!< The following opcodes will only be used in the esp_ble_mesh_health_client_get_state function. */ +typedef uint32_t esp_ble_mesh_opcode_health_client_get_t; /*!< esp_ble_mesh_opcode_health_client_get_t belongs to esp_ble_mesh_opcode_t, + this typedef is only used to locate the opcodes used by esp_ble_mesh_health_client_get_state */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET OP_HEALTH_FAULT_GET /*!< Get the current Registered Fault state */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_GET OP_HEALTH_PERIOD_GET /*!< Get the current Health Period state */ +#define ESP_BLE_MESH_MODEL_OP_ATTENTION_GET OP_ATTENTION_GET /*!< Get the current Attention Timer state */ + +/*!< The following opcodes will only be used in the esp_ble_mesh_health_client_set_state function. */ +typedef uint32_t esp_ble_mesh_opcode_health_client_set_t; /*!< esp_ble_mesh_opcode_health_client_set_t belongs to esp_ble_mesh_opcode_t, + this typedef is only used to locate the opcodes used by esp_ble_mesh_health_client_set_state */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR OP_HEALTH_FAULT_CLEAR /*!< Clear Health Fault acknowledged */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR_UNACK OP_HEALTH_FAULT_CLEAR_UNREL /*!< Clear Health Fault Unacknowledged */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST OP_HEALTH_FAULT_TEST /*!< Invoke Health Fault Test acknowledged */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST_UNACK OP_HEALTH_FAULT_TEST_UNREL /*!< Invoke Health Fault Test unacknowledged */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET OP_HEALTH_PERIOD_SET /*!< Set Health Period acknowledged */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET_UNACK OP_HEALTH_PERIOD_SET_UNREL /*!< Set Health Period unacknowledged */ +#define ESP_BLE_MESH_MODEL_OP_ATTENTION_SET OP_ATTENTION_SET /*!< Set Health Attention acknowledged of the Health Server */ +#define ESP_BLE_MESH_MODEL_OP_ATTENTION_SET_UNACK OP_ATTENTION_SET_UNREL /*!< Set Health Attention Unacknowledged of the Health Server */ + +/*!< The following opcodes are used by the BLE Mesh Health Server Model internally to respond to the Health Client Model's request messages */ +typedef uint32_t esp_ble_mesh_health_model_status_t; /*!< esp_ble_mesh_health_model_status_t belongs to esp_ble_mesh_opcode_t, this typedef + is only used to locate the opcodes used by the Health Model messages */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_CURRENT_STATUS OP_HEALTH_CURRENT_STATUS +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_STATUS OP_HEALTH_FAULT_STATUS +#define ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_STATUS OP_HEALTH_PERIOD_STATUS +#define ESP_BLE_MESH_MODEL_OP_ATTENTION_STATUS OP_ATTENTION_STATUS + +typedef uint32_t esp_ble_mesh_generic_message_opcode_t; /*!< esp_ble_mesh_generic_message_opcode_t belongs to esp_ble_mesh_opcode_t, + this typedef is only used to locate the opcodes used by functions + esp_ble_mesh_generic_client_get_state & esp_ble_mesh_generic_client_set_state */ +/*!< Generic OnOff Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET BLE_MESH_MODEL_OP_GEN_ONOFF_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET BLE_MESH_MODEL_OP_GEN_ONOFF_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS + +/*!< Generic Level Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_GET BLE_MESH_MODEL_OP_GEN_LEVEL_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_SET BLE_MESH_MODEL_OP_GEN_LEVEL_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS +#define ESP_BLE_MESH_MODEL_OP_GEN_DELTA_SET BLE_MESH_MODEL_OP_GEN_DELTA_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_MOVE_SET BLE_MESH_MODEL_OP_GEN_MOVE_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK + +/*!< Generic Default Transition Time Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_GET BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET_UNACK BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS + +/*!< Generic Power OnOff Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_GET BLE_MESH_MODEL_OP_GEN_ONPOWERUP_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS + +/*!< Generic Power OnOff Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET_UNACK BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET_UNACK + +/*!< Generic Power Level Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_GET BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET_UNACK BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LAST_GET BLE_MESH_MODEL_OP_GEN_POWER_LAST_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_GET BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_GET BLE_MESH_MODEL_OP_GEN_POWER_RANGE_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS + +/*!< Generic Power Level Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET_UNACK BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET_UNACK + +/*!< Generic Battery Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_BATTERY_GET BLE_MESH_MODEL_OP_GEN_BATTERY_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_BATTERY_STATUS BLE_MESH_MODEL_OP_GEN_BATTERY_STATUS + +/*!< Generic Location Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_GET BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_GET BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS + +/*!< Generic Location Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET_UNACK BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET_UNACK BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET_UNACK + +/*!< Generic Manufacturer Property Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTIES_GET BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTIES_STATUS BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_STATUS +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_GET BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_SET BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_SET_UNACK BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_STATUS BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_STATUS + +/*!< Generic Admin Property Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS + +/*!< Generic User Property Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS + +/*!< Generic Client Property Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET +#define ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS + +typedef uint32_t esp_ble_mesh_sensor_message_opcode_t; /*!< esp_ble_mesh_sensor_message_opcode_t belongs to esp_ble_mesh_opcode_t, + this typedef is only used to locate the opcodes used by functions + esp_ble_mesh_sensor_client_get_state & esp_ble_mesh_sensor_client_set_state */ +/*!< Sensor Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET +#define ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS +#define ESP_BLE_MESH_MODEL_OP_SENSOR_GET BLE_MESH_MODEL_OP_SENSOR_GET +#define ESP_BLE_MESH_MODEL_OP_SENSOR_STATUS BLE_MESH_MODEL_OP_SENSOR_STATUS +#define ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET +#define ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_GET BLE_MESH_MODEL_OP_SENSOR_SERIES_GET +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS + +/*!< Sensor Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET +#define ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET +#define ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_GET BLE_MESH_MODEL_OP_SENSOR_SETTING_GET +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET BLE_MESH_MODEL_OP_SENSOR_SETTING_SET +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS + +typedef uint32_t esp_ble_mesh_time_scene_message_opcode_t; /*!< esp_ble_mesh_time_scene_message_opcode_t belongs to esp_ble_mesh_opcode_t, + this typedef is only used to locate the opcodes used by functions + esp_ble_mesh_time_scene_client_get_state & esp_ble_mesh_time_scene_client_set_state */ +/*!< Time Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_TIME_GET BLE_MESH_MODEL_OP_TIME_GET +#define ESP_BLE_MESH_MODEL_OP_TIME_SET BLE_MESH_MODEL_OP_TIME_SET +#define ESP_BLE_MESH_MODEL_OP_TIME_STATUS BLE_MESH_MODEL_OP_TIME_STATUS +#define ESP_BLE_MESH_MODEL_OP_TIME_ROLE_GET BLE_MESH_MODEL_OP_TIME_ROLE_GET +#define ESP_BLE_MESH_MODEL_OP_TIME_ROLE_SET BLE_MESH_MODEL_OP_TIME_ROLE_SET +#define ESP_BLE_MESH_MODEL_OP_TIME_ROLE_STATUS BLE_MESH_MODEL_OP_TIME_ROLE_STATUS +#define ESP_BLE_MESH_MODEL_OP_TIME_ZONE_GET BLE_MESH_MODEL_OP_TIME_ZONE_GET +#define ESP_BLE_MESH_MODEL_OP_TIME_ZONE_SET BLE_MESH_MODEL_OP_TIME_ZONE_SET +#define ESP_BLE_MESH_MODEL_OP_TIME_ZONE_STATUS BLE_MESH_MODEL_OP_TIME_ZONE_STATUS +#define ESP_BLE_MESH_MODEL_OP_TAI_UTC_DELTA_GET BLE_MESH_MODEL_OP_TAI_UTC_DELTA_GET +#define ESP_BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET +#define ESP_BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS + +/*!< Scene Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SCENE_GET BLE_MESH_MODEL_OP_SCENE_GET +#define ESP_BLE_MESH_MODEL_OP_SCENE_RECALL BLE_MESH_MODEL_OP_SCENE_RECALL +#define ESP_BLE_MESH_MODEL_OP_SCENE_RECALL_UNACK BLE_MESH_MODEL_OP_SCENE_RECALL_UNACK +#define ESP_BLE_MESH_MODEL_OP_SCENE_STATUS BLE_MESH_MODEL_OP_SCENE_STATUS +#define ESP_BLE_MESH_MODEL_OP_SCENE_REGISTER_GET BLE_MESH_MODEL_OP_SCENE_REGISTER_GET +#define ESP_BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS + +/*!< Scene Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SCENE_STORE BLE_MESH_MODEL_OP_SCENE_STORE +#define ESP_BLE_MESH_MODEL_OP_SCENE_STORE_UNACK BLE_MESH_MODEL_OP_SCENE_STORE_UNACK +#define ESP_BLE_MESH_MODEL_OP_SCENE_DELETE BLE_MESH_MODEL_OP_SCENE_DELETE +#define ESP_BLE_MESH_MODEL_OP_SCENE_DELETE_UNACK BLE_MESH_MODEL_OP_SCENE_DELETE_UNACK + +/*!< Scheduler Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_GET BLE_MESH_MODEL_OP_SCHEDULER_GET +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_STATUS BLE_MESH_MODEL_OP_SCHEDULER_STATUS + +/*!< Scheduler Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET_UNACK BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET_UNACK + +typedef uint32_t esp_ble_mesh_light_message_opcode_t; /*!< esp_ble_mesh_light_message_opcode_t belongs to esp_ble_mesh_opcode_t, + this typedef is only used to locate the opcodes used by functions + esp_ble_mesh_light_client_get_state & esp_ble_mesh_light_client_set_state */ +/*!< Light Lightness Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_GET BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_GET BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_GET BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_GET BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_GET BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS + +/*!< Light Lightness Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET_UNACK + +/*!< Light CTL Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_GET BLE_MESH_MODEL_OP_LIGHT_CTL_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_SET BLE_MESH_MODEL_OP_LIGHT_CTL_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_CTL_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_GET BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_GET BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_GET BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS + +/*!< Light CTL Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET_UNACK + +/*!< Light HSL Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_GET BLE_MESH_MODEL_OP_LIGHT_HSL_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_GET BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_GET BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SET BLE_MESH_MODEL_OP_LIGHT_HSL_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_HSL_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_GET BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_GET BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_GET BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS + +/*!< Light HSL Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET_UNACK /* Model spec is wrong */ + +/*!< Light xyL Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_GET BLE_MESH_MODEL_OP_LIGHT_XYL_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_SET BLE_MESH_MODEL_OP_LIGHT_XYL_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_XYL_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_GET BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_GET BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_GET BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS + +/*!< Light xyL Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET_UNACK + +/*!< Light Control Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_GET BLE_MESH_MODEL_OP_LIGHT_LC_MODE_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_GET BLE_MESH_MODEL_OP_LIGHT_LC_OM_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_GET BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS + +typedef uint32_t esp_ble_mesh_opcode_t; +/*!< End of defines of esp_ble_mesh_opcode_t */ + +#define ESP_BLE_MESH_CFG_STATUS_SUCCESS STATUS_SUCCESS +#define ESP_BLE_MESH_CFG_STATUS_INVALID_ADDRESS STATUS_INVALID_ADDRESS +#define ESP_BLE_MESH_CFG_STATUS_INVALID_MODEL STATUS_INVALID_MODEL +#define ESP_BLE_MESH_CFG_STATUS_INVALID_APPKEY STATUS_INVALID_APPKEY +#define ESP_BLE_MESH_CFG_STATUS_INVALID_NETKEY STATUS_INVALID_NETKEY +#define ESP_BLE_MESH_CFG_STATUS_INSUFFICIENT_RESOURCES STATUS_INSUFF_RESOURCES +#define ESP_BLE_MESH_CFG_STATUS_KEY_INDEX_ALREADY_STORED STATUS_IDX_ALREADY_STORED +#define ESP_BLE_MESH_CFG_STATUS_INVALID_PUBLISH_PARAMETERS STATUS_NVAL_PUB_PARAM +#define ESP_BLE_MESH_CFG_STATUS_NOT_A_SUBSCRIBE_MODEL STATUS_NOT_SUB_MOD +#define ESP_BLE_MESH_CFG_STATUS_STORAGE_FAILURE STATUS_STORAGE_FAIL +#define ESP_BLE_MESH_CFG_STATUS_FEATURE_NOT_SUPPORTED STATUS_FEAT_NOT_SUPP +#define ESP_BLE_MESH_CFG_STATUS_CANNOT_UPDATE STATUS_CANNOT_UPDATE +#define ESP_BLE_MESH_CFG_STATUS_CANNOT_REMOVE STATUS_CANNOT_REMOVE +#define ESP_BLE_MESH_CFG_STATUS_CANNOT_BIND STATUS_CANNOT_BIND +#define ESP_BLE_MESH_CFG_STATUS_TEMP_UNABLE_TO_CHANGE_STATE STATUS_TEMP_STATE_CHG_FAIL +#define ESP_BLE_MESH_CFG_STATUS_CANNOT_SET STATUS_CANNOT_SET +#define ESP_BLE_MESH_CFG_STATUS_UNSPECIFIED_ERROR STATUS_UNSPECIFIED +#define ESP_BLE_MESH_CFG_STATUS_INVALID_BINDING STATUS_INVALID_BINDING +typedef uint8_t esp_ble_mesh_cfg_status_t; /*!< This typedef is only used to indicate the status code + contained in some of the Config Server Model status message */ + +#define ESP_BLE_MESH_MODEL_STATUS_SUCCESS 0x00 +#define ESP_BLE_MESH_MODEL_STATUS_CANNOT_SET_RANGE_MIN 0x01 +#define ESP_BLE_MESH_MODEL_STATUS_CANNOT_SET_RANGE_MAX 0x02 +typedef uint8_t esp_ble_mesh_model_status_t; /*!< This typedef is only used to indicate the status code contained in + some of the server model (e.g. Generic Server Model) status message */ + +/** @def ESP_BLE_MESH_TRANSMIT + * + * @brief Encode transmission count & interval steps. + * + * @note For example, ESP_BLE_MESH_TRANSMIT(2, 20) means that the message + * will be sent about 90ms(count is 3, step is 1, interval is 30 ms + * which includes 10ms of advertising interval random delay). + * + * @param count Number of retransmissions (first transmission is excluded). + * @param int_ms Interval steps in milliseconds. Must be greater than 0 + * and a multiple of 10. + * + * @return BLE Mesh transmit value that can be used e.g. for the default + * values of the Configuration Model data. + */ +#define ESP_BLE_MESH_TRANSMIT(count, int_ms) BLE_MESH_TRANSMIT(count, int_ms) + +/** @def ESP_BLE_MESH_GET_TRANSMIT_COUNT + * + * @brief Decode transmit count from a transmit value. + * + * @param transmit Encoded transmit count & interval value. + * + * @return Transmission count (actual transmissions equal to N + 1). + */ +#define ESP_BLE_MESH_GET_TRANSMIT_COUNT(transmit) BLE_MESH_TRANSMIT_COUNT(transmit) + +/** @def ESP_BLE_MESH_GET_TRANSMIT_INTERVAL + * + * @brief Decode transmit interval from a transmit value. + * + * @param transmit Encoded transmit count & interval value. + * + * @return Transmission interval in milliseconds. + */ +#define ESP_BLE_MESH_GET_TRANSMIT_INTERVAL(transmit) BLE_MESH_TRANSMIT_INT(transmit) + +/** @def ESP_BLE_MESH_PUBLISH_TRANSMIT + * + * @brief Encode Publish Retransmit count & interval steps. + * + * @param count Number of retransmissions (first transmission is excluded). + * @param int_ms Interval steps in milliseconds. Must be greater than 0 + * and a multiple of 50. + * + * @return BLE Mesh transmit value that can be used e.g. for the default + * values of the Configuration Model data. + */ +#define ESP_BLE_MESH_PUBLISH_TRANSMIT(count, int_ms) BLE_MESH_PUB_TRANSMIT(count, int_ms) + +/** @def ESP_BLE_MESH_GET_PUBLISH_TRANSMIT_COUNT + * + * @brief Decode Publish Retransmit count from a given value. + * + * @param transmit Encoded Publish Retransmit count & interval value. + * + * @return Retransmission count (actual transmissions equal to N + 1). + */ +#define ESP_BLE_MESH_GET_PUBLISH_TRANSMIT_COUNT(transmit) BLE_MESH_PUB_TRANSMIT_COUNT(transmit) + +/** @def ESP_BLE_MESH_GET_PUBLISH_TRANSMIT_INTERVAL + * + * @brief Decode Publish Retransmit interval from a given value. + * + * @param transmit Encoded Publish Retransmit count & interval value. + * + * @return Transmission interval in milliseconds. + */ +#define ESP_BLE_MESH_GET_PUBLISH_TRANSMIT_INTERVAL(transmit) BLE_MESH_PUB_TRANSMIT_INT(transmit) + +/* esp_ble_mesh_cb_t is not needed to be initialized by users (set with 0 and will be initialized internally) */ +typedef uint32_t esp_ble_mesh_cb_t; + +typedef enum { + ESP_BLE_MESH_TYPE_PROV_CB, + ESP_BLE_MESH_TYPE_OUTPUT_NUM_CB, + ESP_BLE_MESH_TYPE_OUTPUT_STR_CB, + ESP_BLE_MESH_TYPE_INTPUT_CB, + ESP_BLE_MESH_TYPE_LINK_OPEN_CB, + ESP_BLE_MESH_TYPE_LINK_CLOSE_CB, + ESP_BLE_MESH_TYPE_COMPLETE_CB, + ESP_BLE_MESH_TYPE_RESET_CB, +} esp_ble_mesh_cb_type_t; + +/*!< This enum value is provisioning authentication oob method */ +typedef enum { + ESP_BLE_MESH_NO_OOB, + ESP_BLE_MESH_STATIC_OOB, + ESP_BLE_MESH_OUTPUT_OOB, + ESP_BLE_MESH_INPUT_OOB, +} esp_ble_mesh_oob_method_t; + +/*!< This enum value is associated with bt_mesh_output_action_t in mesh_main.h */ +typedef enum { + ESP_BLE_MESH_NO_OUTPUT = 0, + ESP_BLE_MESH_BLINK = BIT(0), + ESP_BLE_MESH_BEEP = BIT(1), + ESP_BLE_MESH_VIBRATE = BIT(2), + ESP_BLE_MESH_DISPLAY_NUMBER = BIT(3), + ESP_BLE_MESH_DISPLAY_STRING = BIT(4), +} esp_ble_mesh_output_action_t; + +/*!< This enum value is associated with bt_mesh_input_action_t in mesh_main.h */ +typedef enum { + ESP_BLE_MESH_NO_INPUT = 0, + ESP_BLE_MESH_PUSH = BIT(0), + ESP_BLE_MESH_TWIST = BIT(1), + ESP_BLE_MESH_ENTER_NUMBER = BIT(2), + ESP_BLE_MESH_ENTER_STRING = BIT(3), +} esp_ble_mesh_input_action_t; + +/*!< This enum value is associated with bt_mesh_prov_bearer_t in mesh_main.h */ +typedef enum { + ESP_BLE_MESH_PROV_ADV = BIT(0), + ESP_BLE_MESH_PROV_GATT = BIT(1), +} esp_ble_mesh_prov_bearer_t; + +/*!< This enum value is associated with bt_mesh_prov_oob_info_t in mesh_main.h */ +typedef enum { + ESP_BLE_MESH_PROV_OOB_OTHER = BIT(0), + ESP_BLE_MESH_PROV_OOB_URI = BIT(1), + ESP_BLE_MESH_PROV_OOB_2D_CODE = BIT(2), + ESP_BLE_MESH_PROV_OOB_BAR_CODE = BIT(3), + ESP_BLE_MESH_PROV_OOB_NFC = BIT(4), + ESP_BLE_MESH_PROV_OOB_NUMBER = BIT(5), + ESP_BLE_MESH_PROV_OOB_STRING = BIT(6), + /* 7 - 10 are reserved */ + ESP_BLE_MESH_PROV_OOB_ON_BOX = BIT(11), + ESP_BLE_MESH_PROV_OOB_IN_BOX = BIT(12), + ESP_BLE_MESH_PROV_OOB_ON_PAPER = BIT(13), + ESP_BLE_MESH_PROV_OOB_IN_MANUAL = BIT(14), + ESP_BLE_MESH_PROV_OOB_ON_DEV = BIT(15), +} esp_ble_mesh_prov_oob_info_t; + +#define ESP_BLE_MESH_MODEL_OP_1(b0) BLE_MESH_MODEL_OP_1(b0) +#define ESP_BLE_MESH_MODEL_OP_2(b0, b1) BLE_MESH_MODEL_OP_2(b0, b1) +#define ESP_BLE_MESH_MODEL_OP_3(b0, cid) BLE_MESH_MODEL_OP_3(b0, cid) + +/*!< This macro is associated with BLE_MESH_MODEL in mesh_access.h */ +#define ESP_BLE_MESH_SIG_MODEL(_id, _op, _pub, _user_data) \ +{ \ + .model_id = (_id), \ + .op = _op, \ + .keys = { [0 ... (CONFIG_BLE_MESH_MODEL_KEY_COUNT - 1)] = \ + ESP_BLE_MESH_KEY_UNUSED }, \ + .pub = _pub, \ + .groups = { [0 ... (CONFIG_BLE_MESH_MODEL_GROUP_COUNT - 1)] = \ + ESP_BLE_MESH_ADDR_UNASSIGNED }, \ + .user_data = _user_data, \ +} + +/*!< This macro is associated with BLE_MESH_MODEL_VND in mesh_access.h */ +#define ESP_BLE_MESH_VENDOR_MODEL(_company, _id, _op, _pub, _user_data) \ +{ \ + .vnd.company_id = (_company), \ + .vnd.model_id = (_id), \ + .op = _op, \ + .pub = _pub, \ + .keys = { [0 ... (CONFIG_BLE_MESH_MODEL_KEY_COUNT - 1)] = \ + ESP_BLE_MESH_KEY_UNUSED }, \ + .groups = { [0 ... (CONFIG_BLE_MESH_MODEL_GROUP_COUNT - 1)] = \ + ESP_BLE_MESH_ADDR_UNASSIGNED }, \ + .user_data = _user_data, \ +} + +/** @brief Helper to define a BLE Mesh element within an array. + * + * In case the element has no SIG or Vendor models, the helper + * macro ESP_BLE_MESH_MODEL_NONE can be given instead. + * + * @note This macro is associated with BLE_MESH_ELEM in mesh_access.h + * + * @param _loc Location Descriptor. + * @param _mods Array of SIG models. + * @param _vnd_mods Array of vendor models. + */ +#define ESP_BLE_MESH_ELEMENT(_loc, _mods, _vnd_mods) \ +{ \ + .location = (_loc), \ + .sig_model_count = ARRAY_SIZE(_mods), \ + .sig_models = (_mods), \ + .vnd_model_count = ARRAY_SIZE(_vnd_mods), \ + .vnd_models = (_vnd_mods), \ +} + +#define ESP_BLE_MESH_PROV(uuid, sta_val, sta_val_len, out_size, out_act, in_size, in_act) { \ + .uuid = uuid, \ + .static_val = sta_val, \ + .static_val_len = sta_val_len, \ + .output_size = out_size, \ + .output_action = out_act, \ + .input_size = in_size, \ + .input_action = in_act, \ +} + +typedef struct esp_ble_mesh_model esp_ble_mesh_model_t; + +/*!< Abstraction that describes a BLE Mesh Element. + This structure is associated with bt_mesh_elem in mesh_access.h */ +typedef struct { + /* Element Address, assigned during provisioning. */ + uint16_t element_addr; + + /* Location Descriptor (GATT Bluetooth Namespace Descriptors) */ + const uint16_t location; + + /* Model count */ + const uint8_t sig_model_count; + const uint8_t vnd_model_count; + + /* Models */ + esp_ble_mesh_model_t *sig_models; + esp_ble_mesh_model_t *vnd_models; +} esp_ble_mesh_elem_t; + +/*!< Model publication context. + This structure is associated with bt_mesh_model_pub in mesh_access.h */ +typedef struct { + /** The model to which the context belongs. Initialized by the stack. */ + esp_ble_mesh_model_t *model; + + uint16_t publish_addr; /**< Publish Address. */ + uint16_t app_idx; /**< Publish AppKey Index. */ + + uint8_t ttl; /**< Publish Time to Live. */ + uint8_t retransmit; /**< Retransmit Count & Interval Steps. */ + + uint8_t period; /*!< Publish Period. */ + uint16_t period_div: 4, /*!< Divisor for the Period. */ + cred: 1, /*!< Friendship Credentials Flag. */ + fast_period: 1, /**< Use FastPeriodDivisor */ + count: 3; /*!< Retransmissions left. */ + + uint32_t period_start; /**< Start of the current period. */ + + /** @brief Publication buffer, containing the publication message. + * + * This will get correctly created when the publication context + * has been defined using the ESP_BLE_MESH_MODEL_PUB_DEFINE macro. + * + * ESP_BLE_MESH_MODEL_PUB_DEFINE(name, size); + */ + struct net_buf_simple *msg; + + /* The callback is only used for the BLE Mesh stack, not for the app layer. */ + esp_ble_mesh_cb_t update; + + /* Role of the device that is going to publish messages */ + uint8_t dev_role; + + /** Publish Period Timer. Only for stack-internal use. */ + struct k_delayed_work timer; +} esp_ble_mesh_model_pub_t; + +/** @def ESP_BLE_MESH_MODEL_PUB_DEFINE + * + * Define a model publication context. + * + * @param _name Variable name given to the context. + * @param _msg_len Length of the publication message. + * @param _role Role of the device which contains the model. + */ +#define ESP_BLE_MESH_MODEL_PUB_DEFINE(_name, _msg_len, _role) \ + NET_BUF_SIMPLE_DEFINE_STATIC(bt_mesh_pub_msg_##_name, _msg_len); \ + static esp_ble_mesh_model_pub_t _name = { \ + .update = (uint32_t)NULL, \ + .msg = &bt_mesh_pub_msg_##_name, \ + .dev_role = _role, \ + } + +/*!< Model operation context. + This structure is associated with bt_mesh_model_op in mesh_access.h */ +#define ESP_BLE_MESH_MODEL_OP(_opcode, _min_len) \ +{ \ + .opcode = _opcode, \ + .min_len = _min_len, \ + .param_cb = (uint32_t)NULL, \ +} + +typedef struct { + const uint32_t opcode; /* Opcode encoded with the ESP_BLE_MESH_MODEL_OP_* macro */ + const size_t min_len; /* Minimum required message length */ + esp_ble_mesh_cb_t param_cb; /* The callback is only used for BLE Mesh stack, not for the app layer. */ +} esp_ble_mesh_model_op_t; + +/** Define the terminator for the model operation table, each + * model operation struct array must use this terminator as + * the end tag of the operation unit. + */ +#define ESP_BLE_MESH_MODEL_OP_END {0, 0, 0} + +/** Abstraction that describes a Mesh Model instance. + * This structure is associated with bt_mesh_model in mesh_access.h + */ +struct esp_ble_mesh_model { + /* Model ID */ + union { + const uint16_t model_id; + struct { + uint16_t company_id; + uint16_t model_id; + } vnd; + }; + + /* Internal information, mainly for persistent storage */ + uint8_t element_idx; /* Belongs to Nth element */ + uint8_t model_idx; /* Is the Nth model in the element */ + uint16_t flags; /* Information about what has changed */ + + /* The Element to which this Model belongs */ + esp_ble_mesh_elem_t *element; + + /* Model Publication */ + esp_ble_mesh_model_pub_t *const pub; + + /* AppKey List */ + uint16_t keys[CONFIG_BLE_MESH_MODEL_KEY_COUNT]; + + /* Subscription List (group or virtual addresses) */ + uint16_t groups[CONFIG_BLE_MESH_MODEL_GROUP_COUNT]; + + /* Model operation context */ + esp_ble_mesh_model_op_t *op; + + /* Model-specific user data */ + void *user_data; +}; + +/** Helper to define an empty model array. + * This structure is associated with BLE_MESH_MODEL_NONE in mesh_access.h + */ +#define ESP_BLE_MESH_MODEL_NONE ((esp_ble_mesh_model_t []){}) + +/** Message sending context. + * This structure is associated with bt_mesh_msg_ctx in mesh_access.h + */ +typedef struct { + /** NetKey Index of the subnet through which to send the message. */ + uint16_t net_idx; + + /** AppKey Index for message encryption. */ + uint16_t app_idx; + + /** Remote address. */ + uint16_t addr; + + /** Destination address of a received message. Not used for sending. */ + uint16_t recv_dst; + + /** Received TTL value. Not used for sending. */ + uint8_t recv_ttl: 7; + + /** Force sending reliably by using segment acknowledgement */ + uint8_t send_rel: 1; + + /** TTL, or BLE_MESH_TTL_DEFAULT for default TTL. */ + uint8_t send_ttl; + + /** Opcode of a received message. Not used for sending message. */ + uint32_t recv_op; + + /** Model corresponding to the message, no need to be initialized before sending message */ + esp_ble_mesh_model_t *model; + + /** Indicate if the message is sent by a node server model, no need to be initialized before sending message */ + bool srv_send; +} esp_ble_mesh_msg_ctx_t; + +/** Provisioning properties & capabilities. + * This structure is associated with bt_mesh_prov in mesh_access.h + */ +typedef struct { +#if CONFIG_BLE_MESH_NODE + /** The UUID that is used when advertising as an unprovisioned device */ + const uint8_t *uuid; + + /** Optional URI. This will be advertised separately from the + * unprovisioned beacon, however the unprovisioned beacon will + * contain a hash of it so the two can be associated by the + * provisioner. + */ + const char *uri; + + /** Out of Band information field. */ + esp_ble_mesh_prov_oob_info_t oob_info; + + /** Flag indicates whether unprovisioned devices support OOB public key */ + bool oob_pub_key; + + /* This callback is only used for the BLE Mesh stack, not for the app layer */ + esp_ble_mesh_cb_t oob_pub_key_cb; + + /** Static OOB value */ + const uint8_t *static_val; + /** Static OOB value length */ + uint8_t static_val_len; + + /** Maximum size of Output OOB supported */ + uint8_t output_size; + /** Supported Output OOB Actions */ + uint16_t output_actions; + + /** Maximum size of Input OOB supported */ + uint8_t input_size; + /** Supported Input OOB Actions */ + uint16_t input_actions; + + /* These callbacks are only used for the BLE Mesh stack, not for the app layer */ + esp_ble_mesh_cb_t output_num_cb; + esp_ble_mesh_cb_t output_str_cb; + esp_ble_mesh_cb_t input_cb; + esp_ble_mesh_cb_t link_open_cb; + esp_ble_mesh_cb_t link_close_cb; + esp_ble_mesh_cb_t complete_cb; + esp_ble_mesh_cb_t reset_cb; +#endif /* CONFIG_BLE_MESH_NODE */ + +#ifdef CONFIG_BLE_MESH_PROVISIONER + /* Provisioner device UUID */ + const uint8_t *prov_uuid; + + /* Primary element address of the provisioner */ + const uint16_t prov_unicast_addr; + + /* Pre-incremental unicast address value to be assigned to the first device */ + uint16_t prov_start_address; + + /* Attention timer contained in Provisioning Invite PDU */ + uint8_t prov_attention; + + /* Provisioning Algorithm for the Provisioner */ + uint8_t prov_algorithm; + + /* Provisioner public key oob */ + uint8_t prov_pub_key_oob; + + /* The callback is only used for BLE Mesh stack, not for the app layer */ + esp_ble_mesh_cb_t provisioner_prov_read_oob_pub_key; + + /* Provisioner static oob value */ + uint8_t *prov_static_oob_val; + /* Provisioner static oob value length */ + uint8_t prov_static_oob_len; + + /* These callbacks are only used for BLE Mesh stack, not for the app layer */ + esp_ble_mesh_cb_t provisioner_prov_input; + esp_ble_mesh_cb_t provisioner_prov_output; + + /* Key refresh and IV update flag */ + uint8_t flags; + + /* IV index */ + uint32_t iv_index; + + /* These callbacks are only used for BLE Mesh stack, not for the app layer */ + esp_ble_mesh_cb_t provisioner_link_open; + esp_ble_mesh_cb_t provisioner_link_close; + esp_ble_mesh_cb_t provisioner_prov_comp; +#endif /* CONFIG_BLE_MESH_PROVISIONER */ +} esp_ble_mesh_prov_t; + +/** Node Composition + * This structure is associated with bt_mesh_comp in mesh_access.h + */ +typedef struct { + uint16_t cid; + uint16_t pid; + uint16_t vid; + + size_t element_count; + esp_ble_mesh_elem_t *elements; +} esp_ble_mesh_comp_t; + +typedef enum { + ROLE_NODE = 0, + ROLE_PROVISIONER, + ROLE_FAST_PROV, +} esp_ble_mesh_dev_role_t; + +typedef struct { + esp_ble_mesh_opcode_t opcode; /*!< Message opcode */ + esp_ble_mesh_model_t *model; /*!< Pointer to the client model structure */ + esp_ble_mesh_msg_ctx_t ctx; /*!< The context used to send message */ + int32_t msg_timeout; /*!< Timeout value (ms) to get response to the sent message */ + /*!< Note: if using default timeout value in menuconfig, make sure to set this value to 0 */ + uint8_t msg_role; /*!< Role of the device - Node/Provisioner, only used for tx */ +} esp_ble_mesh_client_common_param_t; + +typedef uint8_t esp_ble_mesh_dev_add_flag_t; +#define ADD_DEV_RM_AFTER_PROV_FLAG BIT(0) +#define ADD_DEV_START_PROV_NOW_FLAG BIT(1) +#define ADD_DEV_FLUSHABLE_DEV_FLAG BIT(2) +typedef struct { + esp_bd_addr_t addr; + esp_ble_addr_type_t addr_type; + uint8_t uuid[16]; + uint16_t oob_info; + /*!< ADD_DEV_START_PROV_NOW_FLAG shall not be set if the bearer has both PB-ADV and PB-GATT enabled */ + esp_ble_mesh_prov_bearer_t bearer; +} esp_ble_mesh_unprov_dev_add_t; + +#define DEL_DEV_ADDR_FLAG BIT(0) +#define DEL_DEV_UUID_FLAG BIT(1) +typedef struct { + union { + struct { + esp_bd_addr_t addr; + esp_ble_addr_type_t addr_type; + }; + uint8_t uuid[16]; + }; + uint8_t flag; /*!< BIT0: device address; BIT1: device UUID */ +} esp_ble_mesh_device_delete_t; + +#define PROV_DATA_NET_IDX_FLAG BIT(0) +#define PROV_DATA_FLAGS_FLAG BIT(1) +#define PROV_DATA_IV_INDEX_FLAG BIT(2) +typedef struct { + union { + uint16_t net_idx; + uint8_t flags; + uint32_t iv_index; + }; + uint8_t flag; /*!< BIT0: net_idx; BIT1: flags; BIT2: iv_index */ +} esp_ble_mesh_prov_data_info_t; + +typedef struct { + uint16_t unicast_min; /* Minimum unicast address used for fast provisioning */ + uint16_t unicast_max; /* Maximum unicast address used for fast provisioning */ + uint16_t net_idx; /* Netkey index used for fast provisioning */ + uint8_t flags; /* Flags used for fast provisioning */ + uint32_t iv_index; /* IV Index used for fast provisioning */ + uint8_t offset; /* Offset of the UUID to be compared */ + uint8_t match_len; /* Length of the UUID to be compared */ + uint8_t match_val[16]; /* Value of UUID to be compared */ +} esp_ble_mesh_fast_prov_info_t; + +typedef enum { + FAST_PROV_ACT_NONE, + FAST_PROV_ACT_ENTER, + FAST_PROV_ACT_SUSPEND, + FAST_PROV_ACT_EXIT, + FAST_PROV_ACT_MAX, +} esp_ble_mesh_fast_prov_action_t; + +typedef enum { + ESP_BLE_MESH_PROV_REGISTER_COMP_EVT, /*!< Initialize BLE Mesh provisioning capabilities and internal data information completion event */ + ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT, /*!< Set the unprovisioned device name completion event */ + ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT, /*!< Enable node provisioning functionality completion event */ + ESP_BLE_MESH_NODE_PROV_DISABLE_COMP_EVT, /*!< Disable node provisioning functionality completion event */ + ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT, /*!< Establish a BLE Mesh link event */ + ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT, /*!< Close a BLE Mesh link event */ + ESP_BLE_MESH_NODE_PROV_OOB_PUB_KEY_EVT, /*!< Generate Node input OOB public key event */ + ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT, /*!< Generate Node Output Number event */ + ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT, /*!< Generate Node Output String event */ + ESP_BLE_MESH_NODE_PROV_INPUT_EVT, /*!< Event requiring the user to input a number or string */ + ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT, /*!< Provisioning done event */ + ESP_BLE_MESH_NODE_PROV_RESET_EVT, /*!< Provisioning reset event */ + ESP_BLE_MESH_NODE_PROV_SET_OOB_PUB_KEY_COMP_EVT, /*!< Node set oob public key completion event */ + ESP_BLE_MESH_NODE_PROV_INPUT_NUMBER_COMP_EVT, /*!< Node input number completion event */ + ESP_BLE_MESH_NODE_PROV_INPUT_STRING_COMP_EVT, /*!< Node input string completion event */ + ESP_BLE_MESH_NODE_PROXY_IDENTITY_ENABLE_COMP_EVT, /*!< Enable BLE Mesh Proxy Identity advertising completion event */ + ESP_BLE_MESH_NODE_PROXY_GATT_ENABLE_COMP_EVT, /*!< Enable BLE Mesh GATT Proxy Service completion event */ + ESP_BLE_MESH_NODE_PROXY_GATT_DISABLE_COMP_EVT, /*!< Disable BLE Mesh GATT Proxy Service completion event */ + ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT, /*!< Provisioner enable provisioning functionality completion event */ + ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT, /*!< Provisioner disable provisioning functionality completion event */ + ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT, /*!< Provisioner receives unprovisioned device beacon event */ + ESP_BLE_MESH_PROVISIONER_PROV_READ_OOB_PUB_KEY_EVT, /*!< Provisioner read unprovisioned device OOB public key event */ + ESP_BLE_MESH_PROVISIONER_PROV_INPUT_EVT, /*!< Provisioner input value for provisioning procedure event */ + ESP_BLE_MESH_PROVISIONER_PROV_OUTPUT_EVT, /*!< Provisioner output value for provisioning procedure event */ + ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT, /*!< Provisioner establish a BLE Mesh link event */ + ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT, /*!< Provisioner close a BLE Mesh link event */ + ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT, /*!< Provisioner provisioning done event */ + ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT, /*!< Provisioner add a device to the list which contains devices that are waiting/going to be provisioned completion event */ + ESP_BLE_MESH_PROVISIONER_DELETE_DEV_COMP_EVT, /*!< Provisioner delete a device from the list, close provisioning link with the device if it exists and remove the device from network completion event */ + ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT, /*!< Provisioner set the value to be compared with part of the unprovisioned device UUID completion event */ + ESP_BLE_MESH_PROVISIONER_SET_PROV_DATA_INFO_COMP_EVT, /*!< Provisioner set net_idx/flags/iv_index used for provisioning completion event */ + ESP_BLE_MESH_PROVISIONER_PROV_READ_OOB_PUB_KEY_COMP_EVT, /*!< Provisioner read unprovisioned device OOB public key completion event */ + ESP_BLE_MESH_PROVISIONER_PROV_INPUT_NUMBER_COMP_EVT, /*!< Provisioner input number completion event */ + ESP_BLE_MESH_PROVISIONER_PROV_INPUT_STRING_COMP_EVT, /*!< Provisioner input string completion event */ + ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT, /*!< Provisioner set node name completion event */ + ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT, /*!< Provisioner add local app key completion event */ + ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT, /*!< Provisioner bind local model with local app key completion event */ + ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_NET_KEY_COMP_EVT, /*!< Provisioner add local network key completion event */ + ESP_BLE_MESH_SET_FAST_PROV_INFO_COMP_EVT, /* !< Set fast provisioning information (e.g. unicast address range, net_idx, etc.) completion event */ + ESP_BLE_MESH_SET_FAST_PROV_ACTION_COMP_EVT, /* !< Set fast provisioning action completion event */ + ESP_BLE_MESH_PROV_EVT_MAX, +} esp_ble_mesh_prov_cb_event_t; + +typedef enum { + ESP_BLE_MESH_MODEL_OPERATION_EVT, /*!< User-defined models receive messages from peer devices (e.g. get, set, status, etc) event */ + ESP_BLE_MESH_MODEL_SEND_COMP_EVT, /*!< User-defined models send messages completion event */ + ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT, /*!< User-defined models publish messages completion event */ + ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT, /*!< User-defined client models receive publish messages event */ + ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT, /*!< Timeout event for the user-defined client models that failed to receive response from peer server models */ + ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT, /*!< When a model is configured to publish messages periodically, this event will occur during every publish period */ + ESP_BLE_MESH_MODEL_EVT_MAX, +} esp_ble_mesh_model_cb_event_t; + +typedef union { + /** + * @brief ESP_BLE_MESH_PROV_REGISTER_COMP_EVT + */ + struct ble_mesh_prov_register_comp_param { + int err_code; + } prov_register_comp; + /** + * @brief ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT + */ + struct ble_mesh_set_unprov_dev_name_comp_param { + int err_code; + } node_set_unprov_dev_name_comp; + /** + * @brief ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT + */ + struct ble_mesh_prov_enable_comp_param { + int err_code; + } node_prov_enable_comp; + /** + * @brief ESP_BLE_MESH_NODE_PROV_DISABLE_COMP_EVT + */ + struct ble_mesh_prov_disable_comp_param { + int err_code; + } node_prov_disable_comp; + /** + * @brief ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT + */ + struct ble_mesh_link_open_evt_param { + esp_ble_mesh_prov_bearer_t bearer; + } node_prov_link_open; + /** + * @brief ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT + */ + struct ble_mesh_link_close_evt_param { + esp_ble_mesh_prov_bearer_t bearer; + } node_prov_link_close; + /** + * @brief ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT + */ + struct ble_mesh_output_num_evt_param { + esp_ble_mesh_output_action_t action; + uint32_t number; + } node_prov_output_num; + /** + * @brief ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT + */ + struct ble_mesh_output_str_evt_param { + char string[8]; + } node_prov_output_str; + /** + * @brief ESP_BLE_MESH_NODE_PROV_INPUT_EVT + */ + struct ble_mesh_input_evt_param { + esp_ble_mesh_input_action_t action; + uint8_t size; + } node_prov_input; + /** + * @brief ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT + */ + struct ble_mesh_provision_complete_evt_param { + uint16_t net_idx; + uint16_t addr; + uint8_t flags; + uint32_t iv_index; + } node_prov_complete; + /** + * @brief ESP_BLE_MESH_NODE_PROV_RESET_EVT + */ + struct ble_mesh_provision_reset_param { + + } node_prov_reset; + /** + * @brief ESP_BLE_MESH_NODE_PROV_SET_OOB_PUB_KEY_COMP_EVT + */ + struct ble_mesh_set_oob_pub_key_comp_param { + int err_code; + } node_prov_set_oob_pub_key_comp; + /** + * @brief ESP_BLE_MESH_NODE_PROV_INPUT_NUM_COMP_EVT + */ + struct ble_mesh_input_number_comp_param { + int err_code; + } node_prov_input_num_comp; + /** + * @brief ESP_BLE_MESH_NODE_PROV_INPUT_STR_COMP_EVT + */ + struct ble_mesh_input_string_comp_param { + int err_code; + } node_prov_input_str_comp; + /** + * @brief ESP_BLE_MESH_NODE_PROXY_IDENTITY_ENABLE_COMP_EVT + */ + struct ble_mesh_proxy_identity_enable_comp_param { + int err_code; + } node_proxy_identity_enable_comp; + /** + * @brief ESP_BLE_MESH_NODE_PROXY_GATT_ENABLE_COMP_EVT + */ + struct ble_mesh_proxy_gatt_enable_comp_param { + int err_code; + } node_proxy_gatt_enable_comp; + /** + * @brief ESP_BLE_MESH_NODE_PROXY_GATT_DISABLE_COMP_EVT + */ + struct ble_mesh_proxy_gatt_disable_comp_param { + int err_code; + } node_proxy_gatt_disable_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT + */ + struct ble_mesh_provisioner_recv_unprov_adv_pkt_param { + uint8_t dev_uuid[16]; + uint8_t addr[6]; + esp_ble_addr_type_t addr_type; + uint16_t oob_info; + uint8_t adv_type; + esp_ble_mesh_prov_bearer_t bearer; + } provisioner_recv_unprov_adv_pkt; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT + */ + struct ble_mesh_provisioner_prov_enable_comp_param { + int err_code; + } provisioner_prov_enable_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT + */ + struct ble_mesh_provisioner_prov_disable_comp_param { + int err_code; + } provisioner_prov_disable_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT + */ + struct ble_mesh_provisioner_link_open_evt_param { + esp_ble_mesh_prov_bearer_t bearer; + } provisioner_prov_link_open; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_READ_OOB_PUB_KEY_EVT + */ + struct ble_mesh_provisioner_prov_read_oob_pub_key_evt_param { + uint8_t link_idx; + } provisioner_prov_read_oob_pub_key; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_INPUT_EVT + */ + struct ble_mesh_provisioner_prov_input_evt_param { + esp_ble_mesh_oob_method_t method; + esp_ble_mesh_output_action_t action; + uint8_t size; + uint8_t link_idx; + } provisioner_prov_input; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_OUTPUT_EVT + */ + struct ble_mesh_provisioner_prov_output_evt_param { + esp_ble_mesh_oob_method_t method; + esp_ble_mesh_input_action_t action; + uint8_t size; + uint8_t link_idx; + union { + char string[8]; + uint32_t number; + }; + } provisioner_prov_output; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT + */ + struct ble_mesh_provisioner_link_close_evt_param { + esp_ble_mesh_prov_bearer_t bearer; + uint8_t reason; + } provisioner_prov_link_close; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT + */ + struct ble_mesh_provisioner_prov_comp_param { + int node_idx; + esp_ble_mesh_octet16_t device_uuid; + uint16_t unicast_addr; + uint8_t element_num; + uint16_t netkey_idx; + } provisioner_prov_complete; + /** + * @brief ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT + */ + struct ble_mesh_provisioner_add_unprov_dev_comp_param { + int err_code; + } provisioner_add_unprov_dev_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_DELETE_DEV_COMP_EVT + */ + struct ble_mesh_provisioner_delete_dev_comp_param { + int err_code; + } provisioner_delete_dev_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT + */ + struct ble_mesh_provisioner_set_dev_uuid_match_comp_param { + int err_code; + } provisioner_set_dev_uuid_match_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_SET_PROV_DATA_INFO_COMP_EVT + */ + struct ble_mesh_provisioner_set_prov_data_info_comp_param { + int err_code; + } provisioner_set_prov_data_info_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_READ_OOB_PUB_KEY_COMP_EVT + */ + struct ble_mesh_provisioner_prov_read_oob_pub_key_comp_param { + int err_code; + } provisioner_prov_read_oob_pub_key_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_INPUT_NUMBER_COMP_EVT + */ + struct ble_mesh_provisioner_prov_input_num_comp_param { + int err_code; + } provisioner_prov_input_num_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_INPUT_STRING_COMP_EVT + */ + struct ble_mesh_provisioner_prov_input_str_comp_param { + int err_code; + } provisioner_prov_input_str_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT + */ + struct ble_mesh_provisioner_set_node_name_comp_param { + int err_code; + int node_index; + } provisioner_set_node_name_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT + */ + struct ble_mesh_provisioner_add_local_app_key_comp_param { + int err_code; + uint16_t app_idx; + } provisioner_add_app_key_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT + */ + struct ble_mesh_provisioner_bind_local_mod_app_comp_param { + int err_code; + } provisioner_bind_app_key_to_model_comp; + /** + * @brief ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_NET_KEY_COMP_EVT + */ + struct ble_mesh_provisioner_add_local_net_key_comp_param { + int err_code; + uint16_t net_idx; + } provisioner_add_net_key_comp; + struct ble_mesh_set_fast_prov_info_comp_param { + uint8_t status_unicast; + uint8_t status_net_idx; + uint8_t status_match; + } set_fast_prov_info_comp; + struct ble_mesh_set_fast_prov_action_comp_param { + uint8_t status_action; + } set_fast_prov_action_comp; +} esp_ble_mesh_prov_cb_param_t; + +typedef union { + /** + * @brief ESP_BLE_MESH_MODEL_OPERATION_EVT + */ + struct ble_mesh_model_operation_evt_param { + uint32_t opcode; + esp_ble_mesh_model_t *model; + esp_ble_mesh_msg_ctx_t *ctx; + uint16_t length; + uint8_t *msg; + } model_operation; + /** + * @brief ESP_BLE_MESH_MODEL_SEND_COMP_EVT + */ + struct ble_mesh_model_send_comp_param { + int err_code; + uint32_t opcode; + esp_ble_mesh_model_t *model; + esp_ble_mesh_msg_ctx_t *ctx; + } model_send_comp; + /** + * @brief ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT + */ + struct ble_mesh_model_publish_comp_param { + int err_code; + esp_ble_mesh_model_t *model; + } model_publish_comp; + /** + * @brief ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT + */ + struct ble_mesh_mod_recv_publish_msg_param { + uint32_t opcode; + esp_ble_mesh_model_t *model; + esp_ble_mesh_msg_ctx_t *ctx; + uint16_t length; + uint8_t *msg; + } client_recv_publish_msg; + /** + * @brief ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT + */ + struct ble_mesh_client_model_send_timeout_param { + uint32_t opcode; + esp_ble_mesh_model_t *model; + esp_ble_mesh_msg_ctx_t *ctx; + } client_send_timeout; + /** + * @brief ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT + */ + struct ble_mesh_model_publish_update_evt_param { + esp_ble_mesh_model_t *model; + } model_publish_update; +} esp_ble_mesh_model_cb_param_t; + +typedef struct { + uint32_t cli_op; /*!< The client message opcode */ + uint32_t status_op; /*!< The server status opcode corresponding to the client message opcode */ +} esp_ble_mesh_client_op_pair_t; + +/*!< Mesh Client Model Context */ +typedef struct { + esp_ble_mesh_model_t *model; + int op_pair_size; /*!< Size of the op_pair */ + const esp_ble_mesh_client_op_pair_t *op_pair; /*!< Table containing get/set message opcode and corresponding status message opcode */ + uint32_t publish_status; /*!< This variable is reserved for BLE Mesh Stack, does not require initializing on the application layer */ + void *internal_data; /*!< Pointer to the structure of the client model internal data */ + uint8_t msg_role; /*!< Role of the device (Node/Provisioner) that is going to send messages */ +} esp_ble_mesh_client_t; + +#endif /* _ESP_BLE_MESH_DEFS_H_ */ diff --git a/components/bt/ble_mesh/api/models/esp_ble_mesh_config_model_api.c b/components/bt/ble_mesh/api/models/esp_ble_mesh_config_model_api.c new file mode 100644 index 0000000000..67694eef4e --- /dev/null +++ b/components/bt/ble_mesh/api/models/esp_ble_mesh_config_model_api.c @@ -0,0 +1,82 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_config_model.h" +#include "esp_ble_mesh_config_model_api.h" + +esp_err_t esp_ble_mesh_register_config_client_callback(esp_ble_mesh_cfg_client_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_CFG_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_register_config_server_callback(esp_ble_mesh_cfg_server_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_CFG_SERVER, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_config_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_cfg_client_get_state_t *get_state) +{ + btc_ble_mesh_cfg_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !get_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_CFG_CLIENT; + msg.act = BTC_BLE_MESH_ACT_CONFIG_CLIENT_GET_STATE; + arg.cfg_client_get_state.params = params; + arg.cfg_client_get_state.get_state = get_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_cfg_client_args_t), btc_ble_mesh_cfg_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_config_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_cfg_client_set_state_t *set_state) +{ + btc_ble_mesh_cfg_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !set_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_CFG_CLIENT; + msg.act = BTC_BLE_MESH_ACT_CONFIG_CLIENT_SET_STATE; + arg.cfg_client_set_state.params = params; + arg.cfg_client_set_state.set_state = set_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_cfg_client_args_t), btc_ble_mesh_cfg_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} diff --git a/components/bt/ble_mesh/api/models/esp_ble_mesh_generic_model_api.c b/components/bt/ble_mesh/api/models/esp_ble_mesh_generic_model_api.c new file mode 100644 index 0000000000..82f6ff5850 --- /dev/null +++ b/components/bt/ble_mesh/api/models/esp_ble_mesh_generic_model_api.c @@ -0,0 +1,75 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_generic_model.h" +#include "esp_ble_mesh_generic_model_api.h" + +esp_err_t esp_ble_mesh_register_generic_client_callback(esp_ble_mesh_generic_client_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_GENERIC_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_generic_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_generic_client_get_state_t *get_state) +{ + btc_ble_mesh_generic_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !get_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GENERIC_CLIENT; + msg.act = BTC_BLE_MESH_ACT_GENERIC_CLIENT_GET_STATE; + arg.generic_client_get_state.params = params; + arg.generic_client_get_state.get_state = get_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_generic_client_args_t), btc_ble_mesh_generic_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_generic_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_generic_client_set_state_t *set_state) +{ + btc_ble_mesh_generic_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !set_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GENERIC_CLIENT; + msg.act = BTC_BLE_MESH_ACT_GENERIC_CLIENT_SET_STATE; + arg.generic_client_set_state.params = params; + arg.generic_client_set_state.set_state = set_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_generic_client_args_t), btc_ble_mesh_generic_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} diff --git a/components/bt/ble_mesh/api/models/esp_ble_mesh_health_model_api.c b/components/bt/ble_mesh/api/models/esp_ble_mesh_health_model_api.c new file mode 100644 index 0000000000..e7df777dc0 --- /dev/null +++ b/components/bt/ble_mesh/api/models/esp_ble_mesh_health_model_api.c @@ -0,0 +1,98 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_health_model.h" +#include "esp_ble_mesh_health_model_api.h" + +esp_err_t esp_ble_mesh_register_health_client_callback(esp_ble_mesh_health_client_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_HEALTH_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_register_health_server_callback(esp_ble_mesh_health_server_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_HEALTH_SERVER, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_health_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_health_client_get_state_t *get_state) +{ + btc_ble_mesh_health_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !get_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_HEALTH_CLIENT; + msg.act = BTC_BLE_MESH_ACT_HEALTH_CLIENT_GET_STATE; + arg.health_client_get_state.params = params; + arg.health_client_get_state.get_state = get_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_health_client_args_t), btc_ble_mesh_health_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_health_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_health_client_set_state_t *set_state) +{ + btc_ble_mesh_health_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !set_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_HEALTH_CLIENT; + msg.act = BTC_BLE_MESH_ACT_HEALTH_CLIENT_SET_STATE; + arg.health_client_set_state.params = params; + arg.health_client_set_state.set_state = set_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_health_client_args_t), btc_ble_mesh_health_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_health_server_fault_update(esp_ble_mesh_elem_t *element) +{ + btc_ble_mesh_health_server_args_t arg = {0}; + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_HEALTH_SERVER; + msg.act = BTC_BLE_MESH_ACT_HEALTH_SERVER_FAULT_UPDATE; + arg.fault_update.element = element; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_health_server_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} diff --git a/components/bt/ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c b/components/bt/ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c new file mode 100644 index 0000000000..eea415d618 --- /dev/null +++ b/components/bt/ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c @@ -0,0 +1,76 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_lighting_model.h" +#include "esp_ble_mesh_lighting_model_api.h" + +esp_err_t esp_ble_mesh_register_light_client_callback(esp_ble_mesh_light_client_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_LIGHT_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_light_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_light_client_get_state_t *get_state) +{ + btc_ble_mesh_light_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !get_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_LIGHT_CLIENT; + msg.act = BTC_BLE_MESH_ACT_LIGHT_CLIENT_GET_STATE; + arg.light_client_get_state.params = params; + arg.light_client_get_state.get_state = get_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_light_client_args_t), btc_ble_mesh_light_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_light_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_light_client_set_state_t *set_state) +{ + btc_ble_mesh_light_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !set_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_LIGHT_CLIENT; + msg.act = BTC_BLE_MESH_ACT_LIGHT_CLIENT_SET_STATE; + arg.light_client_set_state.params = params; + arg.light_client_set_state.set_state = set_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_light_client_args_t), btc_ble_mesh_light_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + diff --git a/components/bt/ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c b/components/bt/ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c new file mode 100644 index 0000000000..41072d227f --- /dev/null +++ b/components/bt/ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c @@ -0,0 +1,76 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_sensor_model.h" +#include "esp_ble_mesh_sensor_model_api.h" + +esp_err_t esp_ble_mesh_register_sensor_client_callback(esp_ble_mesh_sensor_client_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_SENSOR_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_sensor_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_sensor_client_get_state_t *get_state) +{ + btc_ble_mesh_sensor_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !get_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_SENSOR_CLIENT; + msg.act = BTC_BLE_MESH_ACT_SENSOR_CLIENT_GET_STATE; + arg.sensor_client_get_state.params = params; + arg.sensor_client_get_state.get_state = get_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_sensor_client_args_t), btc_ble_mesh_sensor_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_sensor_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_sensor_client_set_state_t *set_state) +{ + btc_ble_mesh_sensor_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !set_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_SENSOR_CLIENT; + msg.act = BTC_BLE_MESH_ACT_SENSOR_CLIENT_SET_STATE; + arg.sensor_client_set_state.params = params; + arg.sensor_client_set_state.set_state = set_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_sensor_client_args_t), btc_ble_mesh_sensor_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + diff --git a/components/bt/ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c b/components/bt/ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c new file mode 100644 index 0000000000..13823b9787 --- /dev/null +++ b/components/bt/ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c @@ -0,0 +1,76 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "btc/btc_task.h" +#include "btc/btc_manage.h" + +#include "esp_bt_defs.h" +#include "esp_bt_main.h" + +#include "btc_ble_mesh_time_scene_model.h" +#include "esp_ble_mesh_time_scene_model_api.h" + +esp_err_t esp_ble_mesh_register_time_scene_client_callback(esp_ble_mesh_time_scene_client_cb_t callback) +{ + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_TIME_SCENE_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_time_scene_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_time_scene_client_get_state_t *get_state) +{ + btc_ble_mesh_time_scene_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !get_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_TIME_SCENE_CLIENT; + msg.act = BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_GET_STATE; + arg.time_scene_client_get_state.params = params; + arg.time_scene_client_get_state.get_state = get_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_time_scene_client_args_t), btc_ble_mesh_time_scene_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_time_scene_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_time_scene_client_set_state_t *set_state) +{ + btc_ble_mesh_time_scene_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || !set_state) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_TIME_SCENE_CLIENT; + msg.act = BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_SET_STATE; + arg.time_scene_client_set_state.params = params; + arg.time_scene_client_set_state.set_state = set_state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_time_scene_client_args_t), btc_ble_mesh_time_scene_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + diff --git a/components/bt/ble_mesh/api/models/include/esp_ble_mesh_config_model_api.h b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_config_model_api.h new file mode 100644 index 0000000000..755e0671b2 --- /dev/null +++ b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_config_model_api.h @@ -0,0 +1,670 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_BLE_MESH_CONFIG_MODEL_API_H_ +#define _ESP_BLE_MESH_CONFIG_MODEL_API_H_ + +#include "esp_ble_mesh_defs.h" + +/** @def ESP_BLE_MESH_MODEL_CFG_SRV + * + * @brief Define a new Config Server Model. + * + * @note The Config Server Model can only be included by a Primary Element. + * + * @param srv_data Pointer to a unique Config Server Model user_data. + * + * @return New Config Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_CFG_SRV(srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_CONFIG_SRV, \ + NULL, NULL, srv_data) + +/** @def ESP_BLE_MESH_MODEL_CFG_CLI + * + * @brief Define a new Config Client Model. + * + * @note The Config Client Model can only be included by a Primary Element. + * + * @param cli_data Pointer to a unique struct esp_ble_mesh_client_t. + * + * @return New Config Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_CFG_CLI(cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_CONFIG_CLI, \ + NULL, NULL, cli_data) + +typedef struct esp_ble_mesh_cfg_srv { + esp_ble_mesh_model_t *model; + + uint8_t net_transmit; /*!< Network Transmit state */ + uint8_t relay; /*!< Relay Mode state */ + uint8_t relay_retransmit; /*!< Relay Retransmit state */ + uint8_t beacon; /*!< Secure Network Beacon state */ + uint8_t gatt_proxy; /*!< GATT Proxy state */ + uint8_t friend_state; /*!< Friend state */ + uint8_t default_ttl; /*!< Default TTL */ + + /** Heartbeat Publication */ + struct { + struct k_delayed_work timer; + + uint16_t dst; + uint16_t count; + uint8_t period; + uint8_t ttl; + uint16_t feature; + uint16_t net_idx; + } heartbeat_pub; + + /** Heartbeat Subscription */ + struct { + int64_t expiry; + + uint16_t src; + uint16_t dst; + uint16_t count; + uint8_t min_hops; + uint8_t max_hops; + + /** Optional subscription tracking function */ + void (*func)(uint8_t hops, uint16_t feature); + } heartbeat_sub; +} esp_ble_mesh_cfg_srv_t; + +/** Parameters of Composition Data Get. */ +typedef struct { + uint8_t page; /*!< Page number of the Composition Data. */ +} esp_ble_mesh_cfg_composition_data_get_t; + +/** Parameters of Model Publication Get. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_pub_get_t; + +/** Parameters of SIG Model Subscription Get. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t model_id; /*!< The model id */ +} esp_ble_mesh_cfg_sig_model_sub_get_t; + +/** Parameters of Vendor Model Subscription Get. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_vnd_model_sub_get_t; + +/** Parameters of Application Key Get. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ +} esp_ble_mesh_cfg_app_key_get_t; + +/** Parameters of Node Identity Get. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ +} esp_ble_mesh_cfg_node_identity_get_t; + +/** Parameters of SIG Model App Get. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t model_id; /*!< The model id */ +} esp_ble_mesh_cfg_sig_model_app_get_t; + +/** Parameters of Vendor Model App Get. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_vnd_model_app_get_t; + +/** Parameters of Key Refresh Phase Get. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ +} esp_ble_mesh_cfg_kr_phase_get_t; + +/** Parameters of Low Power Node PollTimeout Get. */ +typedef struct { + uint16_t lpn_addr; /*!< The unicast address of the Low Power node */ +} esp_ble_mesh_cfg_lpn_polltimeout_get_t; + +/** Parameters of Beacon Set. */ +typedef struct { + uint8_t beacon; +} esp_ble_mesh_cfg_beacon_set_t; + +/** Parameters of Default TTL Set. */ +typedef struct { + uint8_t ttl; /*!< The default TTL state value */ +} esp_ble_mesh_cfg_default_ttl_set_t; + +/** Parameters of Friend Set. */ +typedef struct { + uint8_t friend_state; /*!< The friend state value */ +} esp_ble_mesh_cfg_friend_set_t; + +/** Parameters of GATT Proxy Set. */ +typedef struct { + uint8_t gatt_proxy; /*!< The GATT Proxy state value */ +} esp_ble_mesh_cfg_gatt_proxy_set_t; + +/** Parameters of Relay Set. */ +typedef struct { + uint8_t relay; /*!< The relay value */ + uint8_t relay_retransmit; /*!< The relay retransmit value */ +} esp_ble_mesh_cfg_relay_set_t; + +/** Parameters of Network Key Add. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ + uint8_t net_key[16]; /*!< The network key value */ +} esp_ble_mesh_cfg_net_key_add_t; + +/** Parameters of Application Key Add. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ + uint16_t app_idx; /*!< The app key index */ + uint8_t app_key[16]; /*!< The app key value */ +} esp_ble_mesh_cfg_app_key_add_t; + +/** Parameters of Model Application Key Bind. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t model_app_idx; /*!< Index of the app key to bind with the model */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_app_bind_t; + +/** Parameters of Model Publication Set. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t publish_addr; /*!< Value of the publish address */ + uint16_t publish_app_idx; /*!< Index of the application key */ + bool cred_flag; /*!< Value of the Friendship Credential Flag */ + uint8_t publish_ttl; /*!< Default TTL value for the publishing messages */ + uint8_t publish_period; /*!< Period for periodic status publishing */ + uint8_t publish_retransmit; /*!< Number of retransmissions and number of 50-millisecond steps between retransmissions */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_pub_set_t; + +/** Parameters of Model Subscription Add. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t sub_addr; /*!< The address to be added to the Subscription List */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_sub_add_t; + +/** Parameters of Model Subscription Delete. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t sub_addr; /*!< The address to be removed from the Subscription List */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_sub_delete_t; + +/** Parameters of Model Subscription Overwrite. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t sub_addr; /*!< The address to be added to the Subscription List */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_sub_overwrite_t; + +/** Parameters of Model Subscription Virtual Address Add. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint8_t label_uuid[16]; /*!< The Label UUID of the virtual address to be added to the Subscription List */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_sub_va_add_t; + +/** Parameters of Model Subscription Virtual Address Delete. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint8_t label_uuid[16]; /*!< The Label UUID of the virtual address to be removed from the Subscription List */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_sub_va_delete_t; + +/** Parameters of Model Subscription Virtual Address Overwrite. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint8_t label_uuid[16]; /*!< The Label UUID of the virtual address to be added to the Subscription List */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_sub_va_overwrite_t; + +/** Parameters of Model Publication Virtual Address Set. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint8_t label_uuid[16]; /*!< Value of the Label UUID publish address */ + uint16_t publish_app_idx; /*!< Index of the application key */ + bool cred_flag; /*!< Value of the Friendship Credential Flag */ + uint8_t publish_ttl; /*!< Default TTL value for the publishing messages */ + uint8_t publish_period; /*!< Period for periodic status publishing */ + uint8_t publish_retransmit; /*!< Number of retransmissions and number of 50-millisecond steps between retransmissions */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_pub_va_set_t; + +/** Parameters of Model Subscription Delete All. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_sub_delete_all_t; + +/** Parameters of Network Key Update. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ + uint8_t net_key[16]; /*!< The network key value */ +} esp_ble_mesh_cfg_net_key_update_t; + +/** Parameters of Network Key Delete. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ +} esp_ble_mesh_cfg_net_key_delete_t; + +/** Parameters of Application Key Update. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ + uint16_t app_idx; /*!< The app key index */ + uint8_t app_key[16]; /*!< The app key value */ +} esp_ble_mesh_cfg_app_key_update_t; + +/** Parameters of Application Key Delete. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ + uint16_t app_idx; /*!< The app key index */ +} esp_ble_mesh_cfg_app_key_delete_t; + +/** Parameters of Node Identity Set. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ + uint8_t identity; /*!< New Node Identity state */ +} esp_ble_mesh_cfg_node_identity_set_t; + +/** Parameters of Model Application Key Unbind. */ +typedef struct { + uint16_t element_addr; /*!< The element address */ + uint16_t model_app_idx; /*!< Index of the app key to bind with the model */ + uint16_t model_id; /*!< The model id */ + uint16_t company_id; /*!< The company id, if not a vendor model, shall set to 0xFFFF */ +} esp_ble_mesh_cfg_model_app_unbind_t; + +/** Parameters of Key Refresh Phase Set. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ + uint8_t transition; /*!< New Key Refresh Phase Transition */ +} esp_ble_mesh_cfg_kr_phase_set_t; + +/** Parameters of Network Transmit Set. */ +typedef struct { + uint8_t net_transmit; /*!< Network Transmit State */ +} esp_ble_mesh_cfg_net_transmit_set_t; + +/** Parameters of Model Heartbeat Publication Set. */ +typedef struct { + uint16_t dst; + uint8_t count; + uint8_t period; + uint8_t ttl; + uint16_t feature; + uint16_t net_idx; +} esp_ble_mesh_cfg_heartbeat_pub_set_t; + +/** Parameters of Model Heartbeat Subscription Set. */ +typedef struct { + uint16_t src; + uint16_t dst; + uint8_t period; +} esp_ble_mesh_cfg_heartbeat_sub_set_t; + +/** + * @brief For ESP_BLE_MESH_MODEL_OP_BEACON_GET + * ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET + * ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_GET + * ESP_BLE_MESH_MODEL_OP_GATT_PROXY_GET + * ESP_BLE_MESH_MODEL_OP_RELAY_GET + * ESP_BLE_MESH_MODEL_OP_MODEL_PUB_GET + * ESP_BLE_MESH_MODEL_OP_FRIEND_GET + * ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_GET + * ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_GET + * the get_state parameter in the esp_ble_mesh_config_client_get_state function should not be set to NULL. + */ +typedef union { + esp_ble_mesh_cfg_model_pub_get_t model_pub_get; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_PUB_GET. */ + esp_ble_mesh_cfg_composition_data_get_t comp_data_get; /*!< For ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET. */ + esp_ble_mesh_cfg_sig_model_sub_get_t sig_model_sub_get; /*!< For ESP_BLE_MESH_MODEL_OP_SIG_MODEL_SUB_GET */ + esp_ble_mesh_cfg_vnd_model_sub_get_t vnd_model_sub_get; /*!< For ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_SUB_GET */ + esp_ble_mesh_cfg_app_key_get_t app_key_get; /*!< For ESP_BLE_MESH_MODEL_OP_APP_KEY_GET. */ + esp_ble_mesh_cfg_node_identity_get_t node_identity_get; /*!< For ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_GET. */ + esp_ble_mesh_cfg_sig_model_app_get_t sig_model_app_get; /*!< For ESP_BLE_MESH_MODEL_OP_SIG_MODEL_APP_GET */ + esp_ble_mesh_cfg_vnd_model_app_get_t vnd_model_app_get; /*!< For ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_APP_GET */ + esp_ble_mesh_cfg_kr_phase_get_t kr_phase_get; /*!< For ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_GET */ + esp_ble_mesh_cfg_lpn_polltimeout_get_t lpn_pollto_get; /*!< For ESP_BLE_MESH_MODEL_OP_LPN_POLLTIMEOUT_GET */ +} esp_ble_mesh_cfg_client_get_state_t; + +/** + * @brief For ESP_BLE_MESH_MODEL_OP_BEACON_SET + * ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_SET + * ESP_BLE_MESH_MODEL_OP_GATT_PROXY_SET + * ESP_BLE_MESH_MODEL_OP_RELAY_SET + * ESP_BLE_MESH_MODEL_OP_MODEL_PUB_SET + * ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD + * ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_ADD + * ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE + * ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_DELETE + * ESP_BLE_MESH_MODEL_OP_MODEL_SUB_OVERWRITE + * ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_OVERWRITE + * ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD + * ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD + * ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND + * ESP_BLE_MESH_MODEL_OP_NODE_RESET + * ESP_BLE_MESH_MODEL_OP_FRIEND_SET + * ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET + * ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET + * the set_state parameter in the esp_ble_mesh_config_client_set_state function should not be set to NULL. + */ +typedef union { + esp_ble_mesh_cfg_beacon_set_t beacon_set; /*!< For ESP_BLE_MESH_MODEL_OP_BEACON_SET */ + esp_ble_mesh_cfg_default_ttl_set_t default_ttl_set; /*!< For ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_SET */ + esp_ble_mesh_cfg_friend_set_t friend_set; /*!< For ESP_BLE_MESH_MODEL_OP_FRIEND_SET */ + esp_ble_mesh_cfg_gatt_proxy_set_t gatt_proxy_set; /*!< For ESP_BLE_MESH_MODEL_OP_GATT_PROXY_SET */ + esp_ble_mesh_cfg_relay_set_t relay_set; /*!< For ESP_BLE_MESH_MODEL_OP_RELAY_SET */ + esp_ble_mesh_cfg_net_key_add_t net_key_add; /*!< For ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD */ + esp_ble_mesh_cfg_app_key_add_t app_key_add; /*!< For ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD */ + esp_ble_mesh_cfg_model_app_bind_t model_app_bind; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND */ + esp_ble_mesh_cfg_model_pub_set_t model_pub_set; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_PUB_SET */ + esp_ble_mesh_cfg_model_sub_add_t model_sub_add; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD */ + esp_ble_mesh_cfg_model_sub_delete_t model_sub_delete; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE */ + esp_ble_mesh_cfg_model_sub_overwrite_t model_sub_overwrite; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_SUB_OVERWRITE */ + esp_ble_mesh_cfg_model_sub_va_add_t model_sub_va_add; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_ADD */ + esp_ble_mesh_cfg_model_sub_va_delete_t model_sub_va_delete; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_DELETE */ + esp_ble_mesh_cfg_model_sub_va_overwrite_t model_sub_va_overwrite; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_OVERWRITE */ + esp_ble_mesh_cfg_heartbeat_pub_set_t heartbeat_pub_set; /*!< For ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET */ + esp_ble_mesh_cfg_heartbeat_sub_set_t heartbeat_sub_set; /*!< For ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET */ + esp_ble_mesh_cfg_model_pub_va_set_t model_pub_va_set; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_PUB_VIRTUAL_ADDR_SET */ + esp_ble_mesh_cfg_model_sub_delete_all_t model_sub_delete_all; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE_ALL */ + esp_ble_mesh_cfg_net_key_update_t net_key_update; /*!< For ESP_BLE_MESH_MODEL_OP_NET_KEY_UPDATE */ + esp_ble_mesh_cfg_net_key_delete_t net_key_delete; /*!< For ESP_BLE_MESH_MODEL_OP_NET_KEY_DELETE */ + esp_ble_mesh_cfg_app_key_update_t app_key_update; /*!< For ESP_BLE_MESH_MODEL_OP_APP_KEY_UPDATE */ + esp_ble_mesh_cfg_app_key_delete_t app_key_delete; /*!< For ESP_BLE_MESH_MODEL_OP_APP_KEY_DELETE */ + esp_ble_mesh_cfg_node_identity_set_t node_identity_set; /*!< For ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_SET */ + esp_ble_mesh_cfg_model_app_unbind_t model_app_unbind; /*!< For ESP_BLE_MESH_MODEL_OP_MODEL_APP_UNBIND */ + esp_ble_mesh_cfg_kr_phase_set_t kr_phase_set; /*!< For ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_SET */ + esp_ble_mesh_cfg_net_transmit_set_t net_transmit_set; /*!< For ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_SET */ +} esp_ble_mesh_cfg_client_set_state_t; + +typedef struct { + uint8_t beacon; /*!< Secure Network Beacon state value */ +} esp_ble_mesh_cfg_beacon_status_cb_t; + +typedef struct { + uint8_t page; /*!< Page number of the Composition Data */ + struct net_buf_simple *composition_data; /*!< Pointer to Composition Data for the identified page */ +} esp_ble_mesh_cfg_comp_data_status_cb_t; + +typedef struct { + uint8_t default_ttl; /*!< Default TTL state value */ +} esp_ble_mesh_cfg_default_ttl_status_cb_t; + +typedef struct { + uint8_t gatt_proxy; /*!< GATT Proxy state value */ +} esp_ble_mesh_cfg_gatt_proxy_status_cb_t; + +typedef struct { + uint8_t relay; /*!< Relay state value */ + uint8_t retransmit; /*!< Relay retransmit value(number of retransmissions and number of 10-millisecond steps between retransmissions) */ +} esp_ble_mesh_cfg_relay_status_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t element_addr; /*!< Address of the element */ + uint16_t publish_addr; /*!< Value of the publish address */ + uint16_t app_idx; /*!< Index of the application key */ + bool cred_flag; /*!< Value of the Friendship Credential Flag */ + uint8_t ttl; /*!< Default TTL value for the outgoing messages */ + uint8_t period; /*!< Period for periodic status publishing */ + uint8_t transmit; /*!< Number of retransmissions and number of 50-millisecond steps between retransmissions */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ +} esp_ble_mesh_cfg_model_pub_status_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t element_addr; /*!< Address of the element */ + uint16_t sub_addr; /*!< Value of the address */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ +} esp_ble_mesh_cfg_model_sub_status_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t net_idx; /*!< Index of the NetKey */ +} esp_ble_mesh_cfg_net_key_status_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t net_idx; /*!< Index of the NetKey */ + uint16_t app_idx; /*!< Index of the application key */ +} esp_ble_mesh_cfg_app_key_status_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t element_addr; /*!< Address of the element */ + uint16_t app_idx; /*!< Index of the application key */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ +} esp_ble_mesh_cfg_mod_app_status_cb_t; + +typedef struct { + uint8_t friend_state; /*!< Friend state value */ +} esp_ble_mesh_cfg_friend_status_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t dst; /*!< Destination address for Heartbeat messages */ + uint8_t count; /*!< Number of Heartbeat messages remaining to be sent */ + uint8_t period; /*!< Period for sending Heartbeat messages */ + uint8_t ttl; /*!< TTL to be used when sending Heartbeat messages */ + uint16_t features; /*!< Features that trigger Heartbeat messages when changed */ + uint16_t net_idx; /*!< Index of the NetKey */ +} esp_ble_mesh_cfg_hb_pub_status_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t src; /*!< Source address for Heartbeat messages */ + uint16_t dst; /*!< Destination address for Heartbeat messages */ + uint8_t period; /*!< Remaining Period for processing Heartbeat messages */ + uint8_t count; /*!< Number of Heartbeat messages received */ + uint8_t min_hops; /*!< Minimum hops when receiving Heartbeat messages */ + uint8_t max_hops; /*!< Maximum hops when receiving Heartbeat messages */ +} esp_ble_mesh_cfg_hb_sub_status_cb_t; + +typedef struct { + uint8_t net_trans_count:3; /*!< Number of transmissions for each Network PDU originating from the node */ + uint8_t net_trans_step :5; /*!< Maximum hops when receiving Heartbeat messages */ +} esp_ble_mesh_cfg_net_trans_status_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t element_addr; /*!< Address of the element */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ + struct net_buf_simple *sub_addr; /*!< A block of all addresses from the Subscription List */ +} esp_ble_mesh_cfg_model_sub_list_cb_t; + +typedef struct { + struct net_buf_simple *net_idx; /*!< A list of NetKey Indexes known to the node */ +} esp_ble_mesh_cfg_net_key_list_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t net_idx; /*!< NetKey Index of the NetKey that the AppKeys are bound to */ + struct net_buf_simple *app_idx; /*!< A list of AppKey indexes that are bound to the NetKey identified by NetKeyIndex */ +} esp_ble_mesh_cfg_app_key_list_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t net_idx; /*!< Index of the NetKey */ + uint8_t identity; /*!< Node Identity state */ +} esp_ble_mesh_cfg_node_id_status_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t element_addr; /*!< Address of the element */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ + struct net_buf_simple *app_idx; /*!< All AppKey indexes bound to the Model */ +} esp_ble_mesh_cfg_model_app_list_cb_t; + +typedef struct { + uint8_t status; /*!< Status Code for the request message */ + uint16_t net_idx; /*!< Index of the NetKey */ + uint8_t phase; /*!< Key Refresh Phase state */ +} esp_ble_mesh_cfg_kr_phase_status_cb_t; + +typedef struct { + uint16_t lpn_addr; /*!< The unicast address of the Low Power node */ + int32_t poll_timeout; /*!< The current value of the PollTimeout timer of the Low Power node */ +} esp_ble_mesh_cfg_lpn_pollto_status_cb_t; + +typedef union { + esp_ble_mesh_cfg_beacon_status_cb_t beacon_status; /*!< The beacon status value */ + esp_ble_mesh_cfg_comp_data_status_cb_t comp_data_status; /*!< The composition data status value */ + esp_ble_mesh_cfg_default_ttl_status_cb_t default_ttl_status; /*!< The default_ttl status value */ + esp_ble_mesh_cfg_gatt_proxy_status_cb_t gatt_proxy_status; /*!< The gatt_proxy status value */ + esp_ble_mesh_cfg_relay_status_cb_t relay_status; /*!< The relay status value */ + esp_ble_mesh_cfg_model_pub_status_cb_t model_pub_status; /*!< The model publication status value */ + esp_ble_mesh_cfg_model_sub_status_cb_t model_sub_status; /*!< The model subscription status value */ + esp_ble_mesh_cfg_net_key_status_cb_t netkey_status; /*!< The netkey status value */ + esp_ble_mesh_cfg_app_key_status_cb_t appkey_status; /*!< The appkey status value */ + esp_ble_mesh_cfg_mod_app_status_cb_t model_app_status; /*!< The model app status value */ + esp_ble_mesh_cfg_friend_status_cb_t friend_status; /*!< The friend status value */ + esp_ble_mesh_cfg_hb_pub_status_cb_t heartbeat_pub_status; /*!< The heartbeat publication status value */ + esp_ble_mesh_cfg_hb_sub_status_cb_t heartbeat_sub_status; /*!< The heartbeat subscription status value */ + esp_ble_mesh_cfg_net_trans_status_cb_t net_transmit_status; /*!< The network transmit status value */ + esp_ble_mesh_cfg_model_sub_list_cb_t model_sub_list; /*!< The model subscription list value */ + esp_ble_mesh_cfg_net_key_list_cb_t netkey_list; /*!< The network key index list value */ + esp_ble_mesh_cfg_app_key_list_cb_t appkey_list; /*!< The application key index list value */ + esp_ble_mesh_cfg_node_id_status_cb_t node_identity_status; /*!< The node identity status value */ + esp_ble_mesh_cfg_model_app_list_cb_t model_app_list; /*!< The model application key index list value */ + esp_ble_mesh_cfg_kr_phase_status_cb_t kr_phase_status; /*!< The key refresh phase status value */ + esp_ble_mesh_cfg_lpn_pollto_status_cb_t lpn_timeout_status; /*!< The low power node poll timeout status value */ +} esp_ble_mesh_cfg_client_common_cb_param_t; + +typedef struct { + int error_code; /*!< Appropriate error code */ + esp_ble_mesh_client_common_param_t *params; /*!< The client common parameters */ + esp_ble_mesh_cfg_client_common_cb_param_t status_cb; /*!< The config status message callback values */ +} esp_ble_mesh_cfg_client_cb_param_t; + +typedef enum { + ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT, + ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT, + ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT, + ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT, + ESP_BLE_MESH_CFG_CLIENT_EVT_MAX, +} esp_ble_mesh_cfg_client_cb_event_t; + +typedef struct { + uint16_t app_idx; /* AppKey Index of the Config AppKey Add */ +} esp_ble_mesh_cfg_srv_app_key_add_cb_t; + +typedef union { + esp_ble_mesh_cfg_srv_app_key_add_cb_t app_key_add; /* !< The Config AppKey Add event value */ +} esp_ble_mesh_cfg_server_common_cb_param_t; + +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the server model structure */ + esp_ble_mesh_msg_ctx_t ctx; /*!< The context of the received message */ + esp_ble_mesh_cfg_server_common_cb_param_t status_cb; /*!< The received configuration message callback values */ +} esp_ble_mesh_cfg_server_cb_param_t; + +typedef enum { + ESP_BLE_MESH_CFG_SERVER_RECV_MSG_EVT, + ESP_BLE_MESH_CFG_SERVER_EVT_MAX, +} esp_ble_mesh_cfg_server_cb_event_t; + +/** + * @brief Bluetooth Mesh Config Client and Server Model functions. + */ + +/** @brief: event, event code of Config Client Model events; param, parameters of Config Client Model events */ +typedef void (* esp_ble_mesh_cfg_client_cb_t)(esp_ble_mesh_cfg_client_cb_event_t event, + esp_ble_mesh_cfg_client_cb_param_t *param); + +/** @brief: event, event code of Config Client Model events; param, parameters of Config Client Model events */ +typedef void (* esp_ble_mesh_cfg_server_cb_t)(esp_ble_mesh_cfg_server_cb_event_t event, + esp_ble_mesh_cfg_server_cb_param_t *param); + +/** + * @brief Register BLE Mesh Config Client Model callback. + * + * @param[in] callback: Pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_config_client_callback(esp_ble_mesh_cfg_client_cb_t callback); + +/** + * @brief Register BLE Mesh Config Server Model callback. + * + * @param[in] callback: Pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_config_server_callback(esp_ble_mesh_cfg_server_cb_t callback); + +/** + * @brief Get the value of Config Server Model states using the Config Client Model get messages. + * + * @note If you want to find the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_opcode_config_client_get_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] get_state: Pointer to a union, each kind of opcode corresponds to one structure inside. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_config_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_cfg_client_get_state_t *get_state); + +/** + * @brief Set the value of the Configuration Server Model states using the Config Client Model set messages. + * + * @note If you want to find the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_opcode_config_client_set_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] set_state: Pointer to a union, each kind of opcode corresponds to one structure inside. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_config_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_cfg_client_set_state_t *set_state); + +#endif /** _ESP_BLE_MESH_CONFIG_MODEL_API_H_ */ + diff --git a/components/bt/ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h new file mode 100644 index 0000000000..a8db2852f7 --- /dev/null +++ b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h @@ -0,0 +1,478 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @file + * @brief Bluetooth Mesh Generic Client Model APIs. + */ + +#ifndef _ESP_BLE_MESH_GENERIC_MODEL_API_H_ +#define _ESP_BLE_MESH_GENERIC_MODEL_API_H_ + +#include "generic_client.h" +#include "esp_ble_mesh_defs.h" + +/** @def ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI + * + * @brief Define a new Generic OnOff Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Generic OnOff Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Generic OnOff Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_LEVEL_CLI + * + * @brief Define a new Generic Level Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Generic Level Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Generic Level Client Model instance. + */ + +#define ESP_BLE_MESH_MODEL_GEN_LEVEL_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_LEVEL_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_DEF_TRANS_TIME_CLI + * + * @brief Define a new Generic Default Transition Time Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Generic Default Transition + * Time Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Generic Default Transition Time Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_DEF_TRANS_TIME_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_POWER_ONOFF_CLI + * + * @brief Define a new Generic Power OnOff Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Generic Power OnOff Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Generic Power OnOff Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_POWER_ONOFF_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_POWER_LEVEL_CLI + * + * @brief Define a new Generic Power Level Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Generic Power Level Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Generic Power Level Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_POWER_LEVEL_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_BATTERY_CLI + * + * @brief Define a new Generic Battery Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Generic Battery Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Generic Battery Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_BATTERY_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_BATTERY_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_LOCATION_CLI + * + * @brief Define a new Generic Location Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Generic Location Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Generic Location Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_LOCATION_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_LOCATION_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_PROPERTY_CLI + * + * @brief Define a new Generic Property Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Generic Property Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Generic Location Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_PROPERTY_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_PROP_CLI, \ + NULL, cli_pub, cli_data) + +/** + * @brief Bluetooth Mesh Generic Client Model Get and Set parameters structure. + */ + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + uint8_t onoff; /* Target value of Generic OnOff state */ + uint8_t tid; /* Transaction ID */ + uint8_t trans_time; /* Time to complete state transition (optional) */ + uint8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_gen_onoff_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + int16_t level; /* Target value of Generic Level state */ + uint8_t tid; /* Transaction ID */ + uint8_t trans_time; /* Time to complete state transition (optional) */ + uint8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_gen_level_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + int32_t level; /* Delta change of Generic Level state */ + uint8_t tid; /* Transaction ID */ + uint8_t trans_time; /* Time to complete state transition (optional) */ + uint8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_gen_delta_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + int16_t delta_level;/* Delta Level step to calculate Move speed for Generic Level state */ + uint8_t tid; /* Transaction ID */ + uint8_t trans_time; /* Time to complete state transition (optional) */ + uint8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_gen_move_set_t; + +typedef struct { + uint8_t trans_time; /* The value of the Generic Default Transition Time state */ +} esp_ble_mesh_gen_def_trans_time_set_t; + +typedef struct { + uint8_t onpowerup; /* The value of the Generic OnPowerUp state */ +} esp_ble_mesh_gen_onpowerup_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + uint16_t power; /* Target value of Generic Power Actual state */ + uint8_t tid; /* Transaction ID */ + uint8_t trans_time; /* Time to complete state transition (optional) */ + uint8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_gen_power_level_set_t; + +typedef struct { + uint16_t power; /* The value of the Generic Power Default state */ +} esp_ble_mesh_gen_power_default_set_t; + +typedef struct { + uint16_t range_min; /* Value of Range Min field of Generic Power Range state */ + uint16_t range_max; /* Value of Range Max field of Generic Power Range state */ +} esp_ble_mesh_gen_power_range_set_t; + +typedef struct { + int32_t global_latitude; /* Global Coordinates (Latitude) */ + int32_t global_longitude; /* Global Coordinates (Longitude) */ + int16_t global_altitude; /* Global Altitude */ +} esp_ble_mesh_gen_loc_global_set_t; + +typedef struct { + int16_t local_north; /* Local Coordinates (North) */ + int16_t local_east; /* Local Coordinates (East) */ + int16_t local_altitude; /* Local Altitude */ + uint8_t floor_number; /* Floor Number */ + uint16_t uncertainty; /* Uncertainty */ +} esp_ble_mesh_gen_loc_local_set_t; + +typedef struct { + uint16_t property_id; /* Property ID identifying a Generic User Property */ +} esp_ble_mesh_gen_user_property_get_t; + +typedef struct { + uint16_t property_id; /* Property ID identifying a Generic User Property */ + struct net_buf_simple *property_value; /* Raw value for the User Property */ +} esp_ble_mesh_gen_user_property_set_t; + +typedef struct { + uint16_t property_id; /* Property ID identifying a Generic Admin Property */ +} esp_ble_mesh_gen_admin_property_get_t; + +typedef struct { + uint16_t property_id; /* Property ID identifying a Generic Admin Property */ + uint8_t user_access; /* Enumeration indicating user access */ + struct net_buf_simple *property_value; /* Raw value for the Admin Property */ +} esp_ble_mesh_gen_admin_property_set_t; + +typedef struct { + uint16_t property_id; /* Property ID identifying a Generic Manufacturer Property */ +} esp_ble_mesh_gen_manufacturer_property_get_t; + +typedef struct { + uint16_t property_id; /* Property ID identifying a Generic Manufacturer Property */ + uint8_t user_access; /* Enumeration indicating user access */ +} esp_ble_mesh_gen_manufacturer_property_set_t; + +typedef struct { + uint16_t property_id; /* A starting Client Property ID present within an element */ +} esp_ble_mesh_gen_client_properties_get_t; + +typedef union { + esp_ble_mesh_gen_user_property_get_t user_property_get; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET */ + esp_ble_mesh_gen_admin_property_get_t admin_property_get; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET*/ + esp_ble_mesh_gen_manufacturer_property_get_t manufacturer_property_get; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_SET */ + esp_ble_mesh_gen_client_properties_get_t client_properties_get; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET */ +} esp_ble_mesh_generic_client_get_state_t; + +typedef union { + esp_ble_mesh_gen_onoff_set_t onoff_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET & ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK */ + esp_ble_mesh_gen_level_set_t level_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_SET & ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK */ + esp_ble_mesh_gen_delta_set_t delta_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_DELTA_SET & ESP_BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK */ + esp_ble_mesh_gen_move_set_t move_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_MOVE_SET & ESP_BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK */ + esp_ble_mesh_gen_def_trans_time_set_t def_trans_time_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET & ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET_UNACK */ + esp_ble_mesh_gen_onpowerup_set_t power_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET & ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET_UNACK */ + esp_ble_mesh_gen_power_level_set_t power_level_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET & ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET_UNACK */ + esp_ble_mesh_gen_power_default_set_t power_default_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET & ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET_UNACK */ + esp_ble_mesh_gen_power_range_set_t power_range_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET & ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET_UNACK */ + esp_ble_mesh_gen_loc_global_set_t loc_global_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET & ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET_UNACK */ + esp_ble_mesh_gen_loc_local_set_t loc_local_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET & ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET_UNACK */ + esp_ble_mesh_gen_user_property_set_t user_property_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET & ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK */ + esp_ble_mesh_gen_admin_property_set_t admin_property_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET & ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK */ + esp_ble_mesh_gen_manufacturer_property_set_t manufacturer_property_set; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_SET & ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_SET_UNACK */ +} esp_ble_mesh_generic_client_set_state_t; + +/** + * @brief Bluetooth Mesh Generic Client Model Get and Set callback parameters structure. + */ + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + uint8_t present_onoff; /* Current value of Generic OnOff state */ + uint8_t target_onoff; /* Target value of Generic OnOff state (optional) */ + uint8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_gen_onoff_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + int16_t present_level; /* Current value of Generic Level state */ + int16_t target_level; /* Target value of the Generic Level state (optional) */ + uint8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_gen_level_status_cb_t; + +typedef struct { + uint8_t trans_time; /* The value of the Generic Default Transition Time state */ +} esp_ble_mesh_gen_def_trans_time_status_cb_t; + +typedef struct { + uint8_t onpowerup; /* The value of the Generic OnPowerUp state */ +} esp_ble_mesh_gen_onpowerup_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + uint16_t present_power; /* Current value of Generic Power Actual state */ + uint16_t target_power; /* Target value of Generic Power Actual state (optional) */ + uint8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_gen_power_level_status_cb_t; + +typedef struct { + uint16_t power; /* The value of the Generic Power Last state */ +} esp_ble_mesh_gen_power_last_status_cb_t; + +typedef struct { + uint16_t power; /* The value of the Generic Default Last state */ +} esp_ble_mesh_gen_power_default_status_cb_t; + +typedef struct { + uint8_t status_code; /* Status Code for the request message */ + uint16_t range_min; /* Value of Range Min field of Generic Power Range state */ + uint16_t range_max; /* Value of Range Max field of Generic Power Range state */ +} esp_ble_mesh_gen_power_range_status_cb_t; + +typedef struct { + u32_t battery_level : 8; /* Value of Generic Battery Level state */ + u32_t time_to_discharge : 24; /* Value of Generic Battery Time to Discharge state */ + u32_t time_to_charge : 24; /* Value of Generic Battery Time to Charge state */ + u32_t flags : 8; /* Value of Generic Battery Flags state */ +} esp_ble_mesh_gen_battery_status_cb_t; + +typedef struct { + int32_t global_latitude; /* Global Coordinates (Latitude) */ + int32_t global_longitude; /* Global Coordinates (Longitude) */ + int16_t global_altitude; /* Global Altitude */ +} esp_ble_mesh_gen_loc_global_status_cb_t; + +typedef struct { + int16_t local_north; /* Local Coordinates (North) */ + int16_t local_east; /* Local Coordinates (East) */ + int16_t local_altitude; /* Local Altitude */ + uint8_t floor_number; /* Floor Number */ + uint16_t uncertainty; /* Uncertainty */ +} esp_ble_mesh_gen_loc_local_status_cb_t; + +typedef struct { + struct net_buf_simple *property_ids; /* Buffer contains a sequence of N User Property IDs */ +} esp_ble_mesh_gen_user_properties_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + uint16_t property_id; /* Property ID identifying a Generic User Property */ + uint8_t user_access; /* Enumeration indicating user access (optional) */ + struct net_buf_simple *property_value; /* Raw value for the User Property (C.1) */ +} esp_ble_mesh_gen_user_property_status_cb_t; + +typedef struct { + struct net_buf_simple *property_ids; /* Buffer contains a sequence of N Admin Property IDs */ +} esp_ble_mesh_gen_admin_properties_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + uint16_t property_id; /* Property ID identifying a Generic Admin Property */ + uint8_t user_access; /* Enumeration indicating user access (optional) */ + struct net_buf_simple *property_value; /* Raw value for the Admin Property (C.1) */ +} esp_ble_mesh_gen_admin_property_status_cb_t; + +typedef struct { + struct net_buf_simple *property_ids; /* Buffer contains a sequence of N Manufacturer Property IDs */ +} esp_ble_mesh_gen_manufacturer_properties_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + uint16_t property_id; /* Property ID identifying a Generic Manufacturer Property */ + uint8_t user_access; /* Enumeration indicating user access (optional) */ + struct net_buf_simple *property_value; /* Raw value for the Manufacturer Property (C.1) */ +} esp_ble_mesh_gen_manufacturer_property_status_cb_t; + +typedef struct { + struct net_buf_simple *property_ids; /* Buffer contains a sequence of N Client Property IDs */ +} esp_ble_mesh_gen_client_properties_status_cb_t; + +typedef union { + esp_ble_mesh_gen_onoff_status_cb_t onoff_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS */ + esp_ble_mesh_gen_level_status_cb_t level_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS */ + esp_ble_mesh_gen_def_trans_time_status_cb_t def_trans_time_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS */ + esp_ble_mesh_gen_onpowerup_status_cb_t onpowerup_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS */ + esp_ble_mesh_gen_power_level_status_cb_t power_level_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS */ + esp_ble_mesh_gen_power_last_status_cb_t power_last_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS */ + esp_ble_mesh_gen_power_default_status_cb_t power_default_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS */ + esp_ble_mesh_gen_power_range_status_cb_t power_range_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS */ + esp_ble_mesh_gen_battery_status_cb_t battery_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_BATTERY_STATUS */ + esp_ble_mesh_gen_loc_global_status_cb_t location_global_status; /*!< For ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS */ + esp_ble_mesh_gen_loc_local_status_cb_t location_local_status; /*!< ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS */ + esp_ble_mesh_gen_user_properties_status_cb_t user_properties_status; /*!< ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS */ + esp_ble_mesh_gen_user_property_status_cb_t user_property_status; /*!< ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS */ + esp_ble_mesh_gen_admin_properties_status_cb_t admin_properties_status; /*!< ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS */ + esp_ble_mesh_gen_admin_property_status_cb_t admin_property_status; /*!< ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS */ + esp_ble_mesh_gen_manufacturer_properties_status_cb_t manufacturer_properties_status; /*!< ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTIES_STATUS */ + esp_ble_mesh_gen_manufacturer_property_status_cb_t manufacturer_property_status; /*!< ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_STATUS */ + esp_ble_mesh_gen_client_properties_status_cb_t client_properties_status; /*!< ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS */ +} esp_ble_mesh_gen_client_status_cb_t; + +typedef struct { + int error_code; /*!< Appropriate error code */ + esp_ble_mesh_client_common_param_t *params; /*!< The client common parameters. */ + esp_ble_mesh_gen_client_status_cb_t status_cb; /*!< The generic status message callback values */ +} esp_ble_mesh_generic_client_cb_param_t; + +typedef enum { + ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT, + ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT, + ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT, + ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT, + ESP_BLE_MESH_GENERIC_CLIENT_EVT_MAX, +} esp_ble_mesh_generic_client_cb_event_t; + +/** + * @brief Bluetooth Mesh Generic Client Model function. + */ + +/** @brief: event, event code of Generic Client Model events; param, parameters of Generic Client Model events */ +typedef void (* esp_ble_mesh_generic_client_cb_t)(esp_ble_mesh_generic_client_cb_event_t event, + esp_ble_mesh_generic_client_cb_param_t *param); + +/** + * @brief Register BLE Mesh Generic Client Model callback. + * + * @param[in] callback: Pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_generic_client_callback(esp_ble_mesh_generic_client_cb_t callback); + +/** + * @brief Get the value of Generic Server Model states using the Generic Client Model get messages. + * + * @note If you want to find the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_generic_message_opcode_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] get_state: Pointer to generic get message value. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_generic_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_generic_client_get_state_t *get_state); + +/** + * @brief Set the value of Generic Server Model states using the Generic Client Model set messages. + * + * @note If you want to find the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_generic_message_opcode_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] set_state: Pointer to generic set message value. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_generic_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_generic_client_set_state_t *set_state); + + +#endif /* _ESP_BLE_MESH_GENERIC_MODEL_API_H_ */ + diff --git a/components/bt/ble_mesh/api/models/include/esp_ble_mesh_health_model_api.h b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_health_model_api.h new file mode 100644 index 0000000000..687bdc604e --- /dev/null +++ b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_health_model_api.h @@ -0,0 +1,261 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_BLE_MESH_HEALTH_MODEL_API_H_ +#define _ESP_BLE_MESH_HEALTH_MODEL_API_H_ + +#include "esp_ble_mesh_defs.h" + +/** @def ESP_BLE_MESH_MODEL_HEALTH_SRV + * + * @brief Define a new Health Server Model. + * + * @note The Health Server Model can only be included by a Primary Element. + * + * @param srv Pointer to the unique struct esp_ble_mesh_health_srv_t. + * @param pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * + * @return New Health Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_HEALTH_SRV(srv, pub) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_HEALTH_SRV, \ + NULL, pub, srv) + +/** @def ESP_BLE_MESH_MODEL_HEALTH_CLI + * + * @brief Define a new Health Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Health Client Model. + * + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Health Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_HEALTH_CLI(cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_HEALTH_CLI, \ + NULL, NULL, cli_data) + +typedef struct { + /* Fetch current faults */ + int (*fault_get_cur)(esp_ble_mesh_model_t *model, uint8_t *test_id, + uint16_t *company_id, uint8_t *faults, uint8_t *fault_count); + + /* Fetch registered faults */ + int (*fault_get_reg)(esp_ble_mesh_model_t *model, uint16_t company_id, + uint8_t *test_id, uint8_t *faults, uint8_t *fault_count); + + /* Clear registered faults */ + int (*fault_clear)(esp_ble_mesh_model_t *model, uint16_t company_id); + + /* Run a specific test */ + int (*fault_test)(esp_ble_mesh_model_t *model, uint8_t test_id, uint16_t company_id); + + /* Attention on */ + void (*attn_on)(esp_ble_mesh_model_t *model); + + /* Attention off */ + void (*attn_off)(esp_ble_mesh_model_t *model); +} esp_ble_mesh_health_srv_cb_t; + +/** ESP BLE Mesh Health Server Model Context */ +typedef struct { + esp_ble_mesh_model_t *model; + + /* Optional callback struct */ + const esp_ble_mesh_health_srv_cb_t *cb; + + /* Attention Timer state */ + struct k_delayed_work attn_timer; +} esp_ble_mesh_health_srv_t; + +/** BLE Mesh Health Client Model fault get Context */ +typedef struct { + uint16_t company_id; /*!< Bluetooth assigned 16-bit Company ID */ +} esp_ble_mesh_health_fault_get_t; + +/** Mesh Health Client Model attention set Context */ +typedef struct { + uint8_t attention; /*!< Value of the Attention Timer state */ +} esp_ble_mesh_health_attention_set_t; + +/** Mesh Health client Model period set Context */ +typedef struct { + uint8_t fast_period_divisor; /*!< Divider for the Publish Period */ +} esp_ble_mesh_health_period_set_t; + +/** BLE Mesh Health Client Model fault test Context */ +typedef struct { + uint16_t company_id; /*!< Bluetooth assigned 16-bit Company ID */ + uint8_t test_id; /*!< ID of a specific test to be performed */ +} esp_ble_mesh_health_fault_test_t; + +/** BLE Mesh Health Client Model fault clear Context */ +typedef struct { + uint16_t company_id; /*!< Bluetooth assigned 16-bit Company ID */ +} esp_ble_mesh_health_fault_clear_t; + +/** + * @brief For ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET + * ESP_BLE_MESH_MODEL_OP_ATTENTION_GET + * ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_GET + * the get_state parameter in the esp_ble_mesh_health_client_get_state function should not be set to NULL. + */ +typedef union { + esp_ble_mesh_health_fault_get_t fault_get; /*!< For ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET. */ +} esp_ble_mesh_health_client_get_state_t; + +/** + * @brief For ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR + * ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR_UNACK + * ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST + * ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST_UNACK + * ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET + * ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET_UNACK + * ESP_BLE_MESH_MODEL_OP_ATTENTION_SET + * ESP_BLE_MESH_MODEL_OP_ATTENTION_SET_UNACK + * the set_state parameter in the esp_ble_mesh_health_client_set_state function should not be set to NULL. + */ +typedef union { + esp_ble_mesh_health_attention_set_t attention_set; /*!< For ESP_BLE_MESH_MODEL_OP_ATTENTION_SET or ESP_BLE_MESH_MODEL_OP_ATTENTION_SET_UNACK. */ + esp_ble_mesh_health_period_set_t period_set; /*!< For ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET or ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET_UNACK. */ + esp_ble_mesh_health_fault_test_t fault_test; /*!< For ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST or ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST_UNACK. */ + esp_ble_mesh_health_fault_clear_t fault_clear; /*!< For ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR or ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR_UNACK. */ +} esp_ble_mesh_health_client_set_state_t; + +typedef struct { + uint8_t test_id; /*!< ID of a most recently performed test */ + uint16_t company_id; /*!< Bluetooth assigned 16-bit Company ID */ + struct net_buf_simple *fault_array; /*!< FaultArray field contains a sequence of 1-octet fault values */ +} esp_ble_mesh_health_current_status_cb_t; + +typedef struct { + uint8_t test_id; /*!< ID of a most recently performed test */ + uint16_t company_id; /*!< Bluetooth assigned 16-bit Company ID */ + struct net_buf_simple *fault_array; /*!< FaultArray field contains a sequence of 1-octet fault values */ +} esp_ble_mesh_health_fault_status_cb_t; + +typedef struct { + uint8_t fast_period_divisor; /*!< Divider for the Publish Period */ +} esp_ble_mesh_health_period_status_cb_t; + +typedef struct { + uint8_t attention; /*!< Value of the Attention Timer state */ +} esp_ble_mesh_health_attention_status_cb_t; + +typedef union { + esp_ble_mesh_health_current_status_cb_t current_status; /*!< The health current status value */ + esp_ble_mesh_health_fault_status_cb_t fault_status; /*!< The health fault status value */ + esp_ble_mesh_health_period_status_cb_t period_status; /*!< The health period status value */ + esp_ble_mesh_health_attention_status_cb_t attention_status; /*!< The health attention status value */ +} esp_ble_mesh_health_client_common_cb_param_t; + +typedef struct { + int error_code; /*!< Appropriate error code */ + esp_ble_mesh_client_common_param_t *params; /*!< The client common parameters. */ + esp_ble_mesh_health_client_common_cb_param_t status_cb; /*!< The health message status callback values */ +} esp_ble_mesh_health_client_cb_param_t; + +typedef enum { + ESP_BLE_MESH_HEALTH_CLIENT_GET_STATE_EVT, + ESP_BLE_MESH_HEALTH_CLIENT_SET_STATE_EVT, + ESP_BLE_MESH_HEALTH_CLIENT_PUBLISH_EVT, + ESP_BLE_MESH_HEALTH_CLIENT_TIMEOUT_EVT, + ESP_BLE_MESH_HEALTH_CLIENT_EVT_MAX, +} esp_ble_mesh_health_client_cb_event_t; + +typedef struct { + int error_code; /*!< Appropriate error code */ +} esp_ble_mesh_health_server_cb_param_t; + +typedef enum { + ESP_BLE_MESH_HEALTH_SERVER_FAULT_UPDATE_COMPLETE_EVT, + ESP_BLE_MESH_HEALTH_SERVER_EVT_MAX, +} esp_ble_mesh_health_server_cb_event_t; + +/** + * @brief Bluetooth Mesh Health Client and Server Model function. + */ + +/** @brief: event, event code of Health Client Model event; param, parameters of Health Client Model event) */ +typedef void (* esp_ble_mesh_health_client_cb_t)(esp_ble_mesh_health_client_cb_event_t event, + esp_ble_mesh_health_client_cb_param_t *param); + +/** @brief: event, event code of Health Server Model event; param, parameters of Health Server Model event) */ +typedef void (* esp_ble_mesh_health_server_cb_t)(esp_ble_mesh_health_server_cb_event_t event, + esp_ble_mesh_health_server_cb_param_t *param); + +/** + * @brief Register BLE Mesh Health Model callback, the callback will report Health Client & Server Model events. + * + * @param[in] callback: Pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_health_client_callback(esp_ble_mesh_health_client_cb_t callback); + +/** + * @brief Register BLE Mesh Health Server Model callback. + * + * @param[in] callback: Pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_health_server_callback(esp_ble_mesh_health_server_cb_t callback); + +/** + * @brief This function is called to get the Health Server states using the Health Client Model get messages. + * + * @note If you want to find the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_opcode_health_client_get_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] get_state: Pointer to a union, each kind of opcode corresponds to one structure inside. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_health_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_health_client_get_state_t *get_state); + +/** + * @brief This function is called to set the Health Server states using the Health Client Model set messages. + * + * @note If you want to find the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_opcode_health_client_set_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] set_state: Pointer to a union, each kind of opcode corresponds to one structure inside. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_health_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_health_client_set_state_t *set_state); + +/** + * @brief This function is called by the Health Server Model to start to publish its Current Health Fault. + * + * @param[in] element: The element to which the Health Server Model belongs. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_health_server_fault_update(esp_ble_mesh_elem_t *element); + +#endif /** _ESP_BLE_MESH_HEALTH_MODEL_API_H_ */ diff --git a/components/bt/ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h new file mode 100644 index 0000000000..ab119a48bc --- /dev/null +++ b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h @@ -0,0 +1,526 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @file + * @brief Bluetooth Mesh Light Client Model APIs. + */ + +#ifndef _ESP_BLE_MESH_LIGHTING_MODEL_API_H_ +#define _ESP_BLE_MESH_LIGHTING_MODEL_API_H_ + +#include "lighting_client.h" +#include "esp_ble_mesh_defs.h" + +/** @def ESP_BLE_MESH_MODEL_LIGHT_LIGHTNESS_CLI + * + * @brief Define a new Light Lightness Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Light Lightness Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Light Lightness Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_LIGHTNESS_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_CTL_CLI + * + * @brief Define a new Light CTL Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Light CTL Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Light CTL Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_CTL_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_HSL_CLI + * + * @brief Define a new Light HSL Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Light HSL Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Light HSL Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_HSL_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_XYL_CLI + * + * @brief Define a new Light xyL Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Light xyL Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Light xyL Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_XYL_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_XYL_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_LC_CLI + * + * @brief Define a new Light LC Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Light LC Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Light LC Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_LC_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_LC_CLI, \ + NULL, cli_pub, cli_data) + +/** + * @brief Bluetooth Mesh Light Lightness Client Model Get and Set parameters structure. + */ + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t lightness; /* Target value of light lightness actual state */ + u8_t tid; /* Transaction ID */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_lightness_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t lightness; /* Target value of light lightness linear state */ + u8_t tid; /* Transaction ID */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_lightness_linear_set_t; + +typedef struct { + u16_t lightness; /* The value of the Light Lightness Default state */ +} esp_ble_mesh_light_lightness_default_set_t; + +typedef struct { + u16_t range_min; /* Value of range min field of light lightness range state */ + u16_t range_max; /* Value of range max field of light lightness range state */ +} esp_ble_mesh_light_lightness_range_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t ctl_lightness; /* Target value of light ctl lightness state */ + u16_t ctl_temperatrue; /* Target value of light ctl temperature state */ + s16_t ctl_delta_uv; /* Target value of light ctl delta UV state */ + u8_t tid; /* Transaction ID */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_ctl_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t ctl_temperatrue; /* Target value of light ctl temperature state */ + s16_t ctl_delta_uv; /* Target value of light ctl delta UV state */ + u8_t tid; /* Transaction ID */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_ctl_temperature_set_t; + +typedef struct { + u16_t range_min; /* Value of temperature range min field of light ctl temperature range state */ + u16_t range_max; /* Value of temperature range max field of light ctl temperature range state */ +} esp_ble_mesh_light_ctl_temperature_range_set_t; + +typedef struct { + u16_t lightness; /* Value of light lightness default state */ + u16_t temperature; /* Value of light temperature default state */ + s16_t delta_uv; /* Value of light delta UV default state */ +} esp_ble_mesh_light_ctl_default_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t hsl_lightness; /* Target value of light hsl lightness state */ + u16_t hsl_hue; /* Target value of light hsl hue state */ + u16_t hsl_saturation; /* Target value of light hsl saturation state */ + u8_t tid; /* Transaction ID */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_hsl_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t hue; /* Target value of light hsl hue state */ + u8_t tid; /* Transaction ID */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_hsl_hue_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t saturation; /* Target value of light hsl hue state */ + u8_t tid; /* Transaction ID */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_hsl_saturation_set_t; + +typedef struct { + u16_t lightness; /* Value of light lightness default state */ + u16_t hue; /* Value of light hue default state */ + u16_t saturation; /* Value of light saturation default state */ +} esp_ble_mesh_light_hsl_default_set_t; + +typedef struct { + u16_t hue_range_min; /* Value of hue range min field of light hsl hue range state */ + u16_t hue_range_max; /* Value of hue range max field of light hsl hue range state */ + u16_t saturation_range_min; /* Value of saturation range min field of light hsl saturation range state */ + u16_t saturation_range_max; /* Value of saturation range max field of light hsl saturation range state */ +} esp_ble_mesh_light_hsl_range_set_t; + +typedef struct { + bool op_en; /* Indicate whether optional parameters included */ + u16_t xyl_lightness; /* The target value of the Light xyL Lightness state */ + u16_t xyl_x; /* The target value of the Light xyL x state */ + u16_t xyl_y; /* The target value of the Light xyL y state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_xyl_set_t; + +typedef struct { + u16_t lightness; /* The value of the Light Lightness Default state */ + u16_t xyl_x; /* The value of the Light xyL x Default state */ + u16_t xyl_y; /* The value of the Light xyL y Default state */ +} esp_ble_mesh_light_xyl_default_set_t; + +typedef struct { + u16_t xyl_x_range_min; /* The value of the xyL x Range Min field of the Light xyL x Range state */ + u16_t xyl_x_range_max; /* The value of the xyL x Range Max field of the Light xyL x Range state */ + u16_t xyl_y_range_min; /* The value of the xyL y Range Min field of the Light xyL y Range state */ + u16_t xyl_y_range_max; /* The value of the xyL y Range Max field of the Light xyL y Range state */ +} esp_ble_mesh_light_xyl_range_set_t; + +typedef struct { + u8_t mode; /* The target value of the Light LC Mode state */ +} esp_ble_mesh_light_lc_mode_set_t; + +typedef struct { + u8_t mode; /* The target value of the Light LC Occupancy Mode state */ +} esp_ble_mesh_light_lc_om_set_t; + +typedef struct { + bool op_en; /* Indicate whether optional parameters included */ + u8_t light_onoff; /* The target value of the Light LC Light OnOff state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_lc_light_onoff_set_t; + +typedef struct { + u16_t property_id; /* Property ID identifying a Light LC Property */ +} esp_ble_mesh_light_lc_property_get_t; + +typedef struct { + u16_t property_id; /* Property ID identifying a Light LC Property */ + struct net_buf_simple *property_value; /* Raw value for the Light LC Property */ +} esp_ble_mesh_light_lc_property_set_t; + +typedef union { + esp_ble_mesh_light_lc_property_get_t lc_property_get; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET */ +} esp_ble_mesh_light_client_get_state_t; + +typedef union { + esp_ble_mesh_light_lightness_set_t lightness_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET_UNACK */ + esp_ble_mesh_light_lightness_linear_set_t lightness_linear_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET_UNACK */ + esp_ble_mesh_light_lightness_default_set_t lightness_default_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET_UNACK */ + esp_ble_mesh_light_lightness_range_set_t lightness_range_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET_UNACK */ + esp_ble_mesh_light_ctl_set_t ctl_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_SET_UNACK */ + esp_ble_mesh_light_ctl_temperature_set_t ctl_temperature_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET_UNACK */ + esp_ble_mesh_light_ctl_temperature_range_set_t ctl_temperature_range_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET_UNACK */ + esp_ble_mesh_light_ctl_default_set_t ctl_default_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET_UNACK */ + esp_ble_mesh_light_hsl_set_t hsl_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SET_UNACK */ + esp_ble_mesh_light_hsl_hue_set_t hsl_hue_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET_UNACK */ + esp_ble_mesh_light_hsl_saturation_set_t hsl_saturation_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET_UNACK */ + esp_ble_mesh_light_hsl_default_set_t hsl_default_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET_UNACK */ + esp_ble_mesh_light_hsl_range_set_t hsl_range_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET_UNACK */ + esp_ble_mesh_light_xyl_set_t xyl_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_SET_UNACK */ + esp_ble_mesh_light_xyl_default_set_t xyl_default_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET_UNACK */ + esp_ble_mesh_light_xyl_range_set_t xyl_range_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET_UNACK */ + esp_ble_mesh_light_lc_mode_set_t lc_mode_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET_UNACK */ + esp_ble_mesh_light_lc_om_set_t lc_om_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET_UNACK */ + esp_ble_mesh_light_lc_light_onoff_set_t lc_light_onoff_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET_UNACK */ + esp_ble_mesh_light_lc_property_set_t lc_property_set; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET & ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK */ +} esp_ble_mesh_light_client_set_state_t; + +/** + * @brief Bluetooth Mesh Light Lightness Client Model Get and Set callback parameters structure. + */ + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t present_lightness; /* Current value of light lightness actual state */ + u16_t target_lightness; /* Target value of light lightness actual state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_light_lightness_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t present_lightness; /* Current value of light lightness linear state */ + u16_t target_lightness; /* Target value of light lightness linear state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_light_lightness_linear_status_cb_t; + +typedef struct { + u16_t lightness; /* The value of the Light Lightness Last state */ +} esp_ble_mesh_light_lightness_last_status_cb_t; + +typedef struct { + u16_t lightness; /* The value of the Light Lightness default State */ +} esp_ble_mesh_light_lightness_default_status_cb_t; + +typedef struct { + u8_t status_code; /* Status Code for the request message */ + u16_t range_min; /* Value of range min field of light lightness range state */ + u16_t range_max; /* Value of range max field of light lightness range state */ +} esp_ble_mesh_light_lightness_range_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t present_ctl_lightness; /* Current value of light ctl lightness state */ + u16_t present_ctl_temperature; /* Current value of light ctl temperature state */ + u16_t target_ctl_lightness; /* Target value of light ctl lightness state (optional) */ + u16_t target_ctl_temperature; /* Target value of light ctl temperature state (C.1) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_light_ctl_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t present_ctl_temperature; /* Current value of light ctl temperature state */ + u16_t present_ctl_delta_uv; /* Current value of light ctl delta UV state */ + u16_t target_ctl_temperature; /* Target value of light ctl temperature state (optional) */ + u16_t target_ctl_delta_uv; /* Target value of light ctl delta UV state (C.1) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_light_ctl_temperature_status_cb_t; + +typedef struct { + u8_t status_code; /* Status code for the request message */ + u16_t range_min; /* Value of temperature range min field of light ctl temperature range state */ + u16_t range_max; /* Value of temperature range max field of light ctl temperature range state */ +} esp_ble_mesh_light_ctl_temperature_range_status_cb_t; + +typedef struct { + u16_t lightness; /* Value of light lightness default state */ + u16_t temperature; /* Value of light temperature default state */ + s16_t delta_uv; /* Value of light delta UV default state */ +} esp_ble_mesh_light_ctl_default_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t hsl_lightness; /* Current value of light hsl lightness state */ + u16_t hsl_hue; /* Current value of light hsl hue state */ + u16_t hsl_saturation; /* Current value of light hsl saturation state */ + u8_t remain_time; /* Time to complete state transition (optional) */ +} esp_ble_mesh_light_hsl_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t hsl_lightness_target; /* Target value of light hsl lightness state */ + u16_t hsl_hue_target; /* Target value of light hsl hue state */ + u16_t hsl_saturation_target; /* Target value of light hsl saturation state */ + u8_t remain_time; /* Time to complete state transition (optional) */ +} esp_ble_mesh_light_hsl_target_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t present_hue; /* Current value of light hsl hue state */ + u16_t target_hue; /* Target value of light hsl hue state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_light_hsl_hue_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t present_saturation; /* Current value of light hsl saturation state */ + u16_t target_saturation; /* Target value of light hsl saturation state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_light_hsl_saturation_status_cb_t; + +typedef struct { + u16_t lightness; /* Value of light lightness default state */ + u16_t hue; /* Value of light hue default state */ + u16_t saturation; /* Value of light saturation default state */ +} esp_ble_mesh_light_hsl_default_status_cb_t; + +typedef struct { + u8_t status_code; /* Status code for the request message */ + u16_t hue_range_min; /* Value of hue range min field of light hsl hue range state */ + u16_t hue_range_max; /* Value of hue range max field of light hsl hue range state */ + u16_t saturation_range_min; /* Value of saturation range min field of light hsl saturation range state */ + u16_t saturation_range_max; /* Value of saturation range max field of light hsl saturation range state */ +} esp_ble_mesh_light_hsl_range_status_cb_t; + +typedef struct { + bool op_en; /* Indicate whether optional parameters included */ + u16_t xyl_lightness; /* The present value of the Light xyL Lightness state */ + u16_t xyl_x; /* The present value of the Light xyL x state */ + u16_t xyl_y; /* The present value of the Light xyL y state */ + u8_t remain_time; /* Time to complete state transition (optional) */ +} esp_ble_mesh_light_xyl_status_cb_t; + +typedef struct { + bool op_en; /* Indicate whether optional parameters included */ + u16_t target_xyl_lightness; /* The target value of the Light xyL Lightness state */ + u16_t target_xyl_x; /* The target value of the Light xyL x state */ + u16_t target_xyl_y; /* The target value of the Light xyL y state */ + u8_t remain_time; /* Time to complete state transition (optional) */ +} esp_ble_mesh_light_xyl_target_status_cb_t; + +typedef struct { + u16_t lightness; /* The value of the Light Lightness Default state */ + u16_t xyl_x; /* The value of the Light xyL x Default state */ + u16_t xyl_y; /* The value of the Light xyL y Default state */ +} esp_ble_mesh_light_xyl_default_status_cb_t; + +typedef struct { + u8_t status_code; /* Status Code for the requesting message */ + u16_t xyl_x_range_min; /* The value of the xyL x Range Min field of the Light xyL x Range state */ + u16_t xyl_x_range_max; /* The value of the xyL x Range Max field of the Light xyL x Range state */ + u16_t xyl_y_range_min; /* The value of the xyL y Range Min field of the Light xyL y Range state */ + u16_t xyl_y_range_max; /* The value of the xyL y Range Max field of the Light xyL y Range state */ +} esp_ble_mesh_light_xyl_range_status_cb_t; + +typedef struct { + u8_t mode; /* The present value of the Light LC Mode state */ +} esp_ble_mesh_light_lc_mode_status_cb_t; + +typedef struct { + u8_t mode; /* The present value of the Light LC Occupancy Mode state */ +} esp_ble_mesh_light_lc_om_status_cb_t; + +typedef struct { + bool op_en; /* Indicate whether optional parameters included */ + u8_t present_light_onoff; /* The present value of the Light LC Light OnOff state */ + u8_t target_light_onoff; /* The target value of the Light LC Light OnOff state (Optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_light_lc_light_onoff_status_cb_t; + +typedef struct { + u16_t property_id; /* Property ID identifying a Light LC Property */ + struct net_buf_simple *property_value; /* Raw value for the Light LC Property */ +} esp_ble_mesh_light_lc_property_status_cb_t; + +typedef union { + esp_ble_mesh_light_lightness_status_cb_t lightness_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS */ + esp_ble_mesh_light_lightness_linear_status_cb_t lightness_linear_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS */ + esp_ble_mesh_light_lightness_last_status_cb_t lightness_last_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS */ + esp_ble_mesh_light_lightness_default_status_cb_t lightness_default_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS */ + esp_ble_mesh_light_lightness_range_status_cb_t lightness_range_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS */ + esp_ble_mesh_light_ctl_status_cb_t ctl_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS */ + esp_ble_mesh_light_ctl_temperature_status_cb_t ctl_temperature_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS */ + esp_ble_mesh_light_ctl_temperature_range_status_cb_t ctl_temperature_range_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS */ + esp_ble_mesh_light_ctl_default_status_cb_t ctl_default_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS */ + esp_ble_mesh_light_hsl_status_cb_t hsl_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS */ + esp_ble_mesh_light_hsl_target_status_cb_t hsl_target_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS */ + esp_ble_mesh_light_hsl_hue_status_cb_t hsl_hue_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS */ + esp_ble_mesh_light_hsl_saturation_status_cb_t hsl_saturation_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS */ + esp_ble_mesh_light_hsl_default_status_cb_t hsl_default_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS */ + esp_ble_mesh_light_hsl_range_status_cb_t hsl_range_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS */ + esp_ble_mesh_light_xyl_status_cb_t xyl_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS */ + esp_ble_mesh_light_xyl_target_status_cb_t xyl_target_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS */ + esp_ble_mesh_light_xyl_default_status_cb_t xyl_default_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS */ + esp_ble_mesh_light_xyl_range_status_cb_t xyl_range_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS */ + esp_ble_mesh_light_lc_mode_status_cb_t lc_mode_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS */ + esp_ble_mesh_light_lc_om_status_cb_t lc_om_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS */ + esp_ble_mesh_light_lc_light_onoff_status_cb_t lc_light_onoff_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS */ + esp_ble_mesh_light_lc_property_status_cb_t lc_property_status; /*!< For ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS */ +} esp_ble_mesh_light_client_status_cb_t; + +typedef struct { + int error_code; /*!< Appropriate error code */ + esp_ble_mesh_client_common_param_t *params; /*!< The client common parameters. */ + esp_ble_mesh_light_client_status_cb_t status_cb; /*!< The light status message callback values */ +} esp_ble_mesh_light_client_cb_param_t; + +typedef enum { + ESP_BLE_MESH_LIGHT_CLIENT_GET_STATE_EVT, + ESP_BLE_MESH_LIGHT_CLIENT_SET_STATE_EVT, + ESP_BLE_MESH_LIGHT_CLIENT_PUBLISH_EVT, + ESP_BLE_MESH_LIGHT_CLIENT_TIMEOUT_EVT, + ESP_BLE_MESH_LIGHT_CLIENT_EVT_MAX, +} esp_ble_mesh_light_client_cb_event_t; + +/** + * @brief Bluetooth Mesh Light Client Model function. + */ + +/** @brief: event, event code of Light Client Model events; param, parameters of Light Client Model events */ +typedef void (* esp_ble_mesh_light_client_cb_t)(esp_ble_mesh_light_client_cb_event_t event, + esp_ble_mesh_light_client_cb_param_t *param); + +/** + * @brief Register BLE Mesh Light Client Model callback. + * + * @param[in] callback: pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_light_client_callback(esp_ble_mesh_light_client_cb_t callback); + +/** + * @brief Get the value of Light Server Model states using the Light Client Model get messages. + * + * @note If you want to know the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_light_message_opcode_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] get_state: Pointer of light get message value. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_light_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_light_client_get_state_t *get_state); + +/** + * @brief Set the value of Light Server Model states using the Light Client Model set messages. + * + * @note If you want to know the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_light_message_opcode_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] set_state: Pointer of generic set message value. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_light_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_light_client_set_state_t *set_state); + + +#endif /* _ESP_BLE_MESH_LIGHTING_MODEL_API_H_ */ + diff --git a/components/bt/ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h new file mode 100644 index 0000000000..553845cad8 --- /dev/null +++ b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h @@ -0,0 +1,230 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @file + * @brief Bluetooth Mesh Sensor Client Model APIs. + */ + +#ifndef _ESP_BLE_MESH_SENSOR_MODEL_API_H_ +#define _ESP_BLE_MESH_SENSOR_MODEL_API_H_ + +#include "sensor_client.h" +#include "esp_ble_mesh_defs.h" + +/** @def ESP_BLE_MESH_MODEL_SENSOR_CLI + * + * @brief Define a new Sensor Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Sensor Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Sensor Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_SENSOR_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_SENSOR_CLI, \ + NULL, cli_pub, cli_data) + +/** + * @brief Bluetooth Mesh Sensor Client Model Get and Set parameters structure. + */ +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t property_id; /* Property ID of a sensor (optional) */ +} esp_ble_mesh_sensor_descriptor_get_t; + +typedef struct { + u16_t property_id; /* Property ID of a sensor */ +} esp_ble_mesh_sensor_cadence_get_t; + +typedef struct { + u16_t property_id; /* Property ID for the sensor */ + u8_t fast_cadence_period_divisor : 7, /* Divisor for the publish period */ + status_trigger_type : 1; /* The unit and format of the Status Trigger Delta fields */ + struct net_buf_simple *status_trigger_delta_down; /* Delta down value that triggers a status message */ + struct net_buf_simple *status_trigger_delta_up; /* Delta up value that triggers a status message */ + u8_t status_min_interval; /* Minimum interval between two consecutive Status messages */ + struct net_buf_simple *fast_cadence_low; /* Low value for the fast cadence range */ + struct net_buf_simple *fast_cadence_high; /* Fast value for the fast cadence range */ +} esp_ble_mesh_sensor_cadence_set_t; + +typedef struct { + u16_t sensor_property_id; /* Property ID of a sensor */ +} esp_ble_mesh_sensor_settings_get_t; + +typedef struct { + u16_t sensor_property_id; /* Property ID of a sensor */ + u16_t sensor_setting_property_id; /* Setting ID identifying a setting within a sensor */ +} esp_ble_mesh_sensor_setting_get_t; + +typedef struct { + u16_t sensor_property_id; /* Property ID identifying a sensor */ + u16_t sensor_setting_property_id; /* Setting ID identifying a setting within a sensor */ + struct net_buf_simple *sensor_setting_raw; /* Raw value for the setting */ +} esp_ble_mesh_sensor_setting_set_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t property_id; /* Property ID for the sensor (optional) */ +} esp_ble_mesh_sensor_get_t; + +typedef struct { + u16_t property_id; /* Property identifying a sensor */ + struct net_buf_simple *raw_value_x; /* Raw value identifying a column */ +} esp_ble_mesh_sensor_column_get_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t property_id; /* Property identifying a sensor */ + struct net_buf_simple *raw_value_x1; /* Raw value identifying a starting column (optional) */ + struct net_buf_simple *raw_value_x2; /* Raw value identifying an ending column (C.1) */ +} esp_ble_mesh_sensor_series_get_t; + +typedef union { + esp_ble_mesh_sensor_descriptor_get_t descriptor_get; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET */ + esp_ble_mesh_sensor_cadence_get_t cadence_get; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET */ + esp_ble_mesh_sensor_settings_get_t settings_get; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET */ + esp_ble_mesh_sensor_setting_get_t setting_get; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_GET */ + esp_ble_mesh_sensor_get_t sensor_get; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_GET */ + esp_ble_mesh_sensor_column_get_t column_get; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET */ + esp_ble_mesh_sensor_series_get_t series_get; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_GET */ +} esp_ble_mesh_sensor_client_get_state_t; + +typedef union { + esp_ble_mesh_sensor_cadence_set_t cadence_set; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET & ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK */ + esp_ble_mesh_sensor_setting_set_t setting_set; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET & ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK */ +} esp_ble_mesh_sensor_client_set_state_t; + +/** + * @brief Bluetooth Mesh Sensor Client Model Get and Set callback parameters structure. + */ + +typedef struct { + struct net_buf_simple *descriptor; /* Sequence of 8-octet sensor descriptors (optional) */ +} esp_ble_mesh_sensor_descriptor_status_cb_t; + +typedef struct { + u16_t property_id; /* Property for the sensor */ + struct net_buf_simple *sensor_cadence_value; /* Value of sensor cadence state */ +} esp_ble_mesh_sensor_cadence_status_cb_t; + +typedef struct { + u16_t sensor_property_id; /* Property ID identifying a sensor */ + struct net_buf_simple *sensor_setting_property_ids; /* A sequence of N sensor setting property IDs (optional) */ +} esp_ble_mesh_sensor_settings_status_cb_t; + +typedef struct { + bool op_en; /* Indicate id optional parameters are included */ + u16_t sensor_property_id; /* Property ID identifying a sensor */ + u16_t sensor_setting_property_id; /* Setting ID identifying a setting within a sensor */ + u8_t sensor_setting_access; /* Read/Write access rights for the setting (optional) */ + struct net_buf_simple *sensor_setting_raw; /* Raw value for the setting */ +} esp_ble_mesh_sensor_setting_status_cb_t; + +typedef struct { + struct net_buf_simple *marshalled_sensor_data; /* Value of sensor data state (optional) */ +} esp_ble_mesh_sensor_status_cb_t; + +typedef struct { + u16_t property_id; /* Property identifying a sensor and the Y axis */ + struct net_buf_simple *sensor_column_value; /* Left values of sensor column status */ +} esp_ble_mesh_sensor_column_status_cb_t; + +typedef struct { + u16_t property_id; /* Property identifying a sensor and the Y axis */ + struct net_buf_simple *sensor_series_value; /* Left values of sensor series status */ +} esp_ble_mesh_sensor_series_status_cb_t; + + +typedef union { + esp_ble_mesh_sensor_descriptor_status_cb_t descriptor_status; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS */ + esp_ble_mesh_sensor_cadence_status_cb_t cadence_status; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS */ + esp_ble_mesh_sensor_settings_status_cb_t settings_status; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS */ + esp_ble_mesh_sensor_setting_status_cb_t setting_status; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS */ + esp_ble_mesh_sensor_status_cb_t sensor_status; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_STATUS */ + esp_ble_mesh_sensor_column_status_cb_t column_status; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS */ + esp_ble_mesh_sensor_series_status_cb_t series_status; /*!< For ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS */ +} esp_ble_mesh_sensor_client_status_cb_t; + +typedef struct { + int error_code; /*!< 0: success, + * otherwise failure. For the error code values please refer to errno.h file. + * A negative sign is added to the standard error codes in errno.h. */ + esp_ble_mesh_client_common_param_t *params; /*!< The client common parameters. */ + esp_ble_mesh_sensor_client_status_cb_t status_cb; /*!< The sensor status message callback values */ +} esp_ble_mesh_sensor_client_cb_param_t; + +typedef enum { + ESP_BLE_MESH_SENSOR_CLIENT_GET_STATE_EVT, + ESP_BLE_MESH_SENSOR_CLIENT_SET_STATE_EVT, + ESP_BLE_MESH_SENSOR_CLIENT_PUBLISH_EVT, + ESP_BLE_MESH_SENSOR_CLIENT_TIMEOUT_EVT, + ESP_BLE_MESH_SENSOR_CLIENT_EVT_MAX, +} esp_ble_mesh_sensor_client_cb_event_t; + +/** + * @brief Bluetooth Mesh Sensor Client Model function. + */ + +/** @brief: event, event code of Sensor Client Model events; param, parameters of Sensor Client Model events */ +typedef void (* esp_ble_mesh_sensor_client_cb_t)(esp_ble_mesh_sensor_client_cb_event_t event, + esp_ble_mesh_sensor_client_cb_param_t *param); + +/** + * @brief Register BLE Mesh Sensor Client Model callback. + * + * @param[in] callback: Pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_sensor_client_callback(esp_ble_mesh_sensor_client_cb_t callback); + +/** + * @brief Get the value of Sensor Server Model states using the Sensor Client Model get messages. + * + * @note If you want to know the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_sensor_message_opcode_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] get_state: Pointer to sensor get message value. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_sensor_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_sensor_client_get_state_t *get_state); + +/** + * @brief Set the value of Sensor Server Model states using the Sensor Client Model set messages. + * + * @note If you want to know the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_sensor_message_opcode_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] set_state: Pointer to sensor set message value. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_sensor_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_sensor_client_set_state_t *set_state); + +#endif /* _ESP_BLE_MESH_SENSOR_MODEL_API_H_ */ + + diff --git a/components/bt/ble_mesh/api/models/include/esp_ble_mesh_time_scene_model_api.h b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_time_scene_model_api.h new file mode 100644 index 0000000000..cdf55ef170 --- /dev/null +++ b/components/bt/ble_mesh/api/models/include/esp_ble_mesh_time_scene_model_api.h @@ -0,0 +1,292 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @file + * @brief Bluetooth Mesh Time and Scene Client Model APIs. + */ + +#ifndef _ESP_BLE_MESH_TIME_SCENE_MODEL_API_H_ +#define _ESP_BLE_MESH_TIME_SCENE_MODEL_API_H_ + +#include "time_scene_client.h" +#include "esp_ble_mesh_defs.h" + +/** @def ESP_BLE_MESH_MODEL_TIME_CLI + * + * @brief Define a new Time Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Time Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Time Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_TIME_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_TIME_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_SCENE_CLI + * + * @brief Define a new Scene Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Scene Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Scene Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_SCENE_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_SCENE_CLI, \ + NULL, cli_pub, cli_data) + +/** @def ESP_BLE_MESH_MODEL_SCHEDULER_CLI + * + * @brief Define a new Scheduler Client Model. + * + * @note This API needs to be called for each element on which + * the application needs to have a Scheduler Client Model. + * + * @param cli_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param cli_data Pointer to the unique struct esp_ble_mesh_client_t. + * + * @return New Scheduler Client Model instance. + */ +#define ESP_BLE_MESH_MODEL_SCHEDULER_CLI(cli_pub, cli_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_SCHEDULER_CLI, \ + NULL, cli_pub, cli_data) + +/** + * @brief Bluetooth Mesh Time Scene Client Model Get and Set parameters structure. + */ + +typedef struct { + u8_t tai_seconds[5]; /* The current TAI time in seconds */ + u8_t sub_second; /* The sub-second time in units of 1/256 second */ + u8_t uncertainty; /* The estimated uncertainty in 10-millisecond steps */ + u16_t time_authority : 1; /* 0 = No Time Authority, 1 = Time Authority */ + u16_t tai_utc_delta : 15; /* Current difference between TAI and UTC in seconds */ + u8_t time_zone_offset; /* The local time zone offset in 15-minute increments */ +} esp_ble_mesh_time_set_t; + +typedef struct { + u8_t time_zone_offset_new; /* Upcoming local time zone offset */ + u8_t tai_zone_change[5]; /* TAI Seconds time of the upcoming Time Zone Offset change */ +} esp_ble_mesh_time_zone_set_t; + +typedef struct { + u16_t tai_utc_delta_new : 15; /* Upcoming difference between TAI and UTC in seconds */ + u16_t padding : 1; /* Always 0b0. Other values are Prohibited. */ + u8_t tai_delta_change[5]; /* TAI Seconds time of the upcoming TAI-UTC Delta change */ +} esp_ble_mesh_tai_utc_delta_set_t; + +typedef struct { + u8_t time_role; /* The Time Role for the element */ +} esp_ble_mesh_time_role_set_t; + +typedef struct { + u16_t scene_number; /* The number of scenes to be stored */ +} esp_ble_mesh_scene_store_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u16_t scene_number; /* The number of scenes to be recalled */ + u8_t tid; /* Transaction ID */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +} esp_ble_mesh_scene_recall_t; + +typedef struct { + u16_t scene_number; /* The number of scenes to be deleted */ +} esp_ble_mesh_scene_delete_t; + +typedef struct { + u8_t index; /* Index of the Schedule Register entry to get */ +} esp_ble_mesh_scheduler_act_get_t; + +typedef struct { + u64_t index : 4; /* Index of the Schedule Register entry to set */ + u64_t year : 7; /* Scheduled year for the action */ + u64_t month : 12; /* Scheduled month for the action */ + u64_t day : 5; /* Scheduled day of the month for the action */ + u64_t hour : 5; /* Scheduled hour for the action */ + u64_t minute : 6; /* Scheduled minute for the action */ + u64_t second : 6; /* Scheduled second for the action */ + u64_t day_of_week : 7; /* Schedule days of the week for the action */ + u64_t action : 4; /* Action to be performed at the scheduled time */ + u64_t trans_time : 8; /* Transition time for this action */ + u16_t scene_number; /* Transition time for this action */ +} esp_ble_mesh_scheduler_act_set_t; + +/** + * @brief For + * + * the get_state parameter in the esp_ble_mesh_time_scene_client_get_state function should be set to NULL. + */ +typedef union { + esp_ble_mesh_scheduler_act_get_t scheduler_act_get; /*!< For ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET */ +} esp_ble_mesh_time_scene_client_get_state_t; + +typedef union { + esp_ble_mesh_time_set_t time_set; /*!< For ESP_BLE_MESH_MODEL_OP_TIME_SET */ + esp_ble_mesh_time_zone_set_t time_zone_set; /*!< For ESP_BLE_MESH_MODEL_OP_TIME_ZONE_SET */ + esp_ble_mesh_tai_utc_delta_set_t tai_utc_delta_set; /*!< For ESP_BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET */ + esp_ble_mesh_time_role_set_t time_role_set; /*!< For ESP_BLE_MESH_MODEL_OP_TIME_ROLE_SET */ + esp_ble_mesh_scene_store_t scene_store; /*!< For ESP_BLE_MESH_MODEL_OP_SCENE_STORE & ESP_BLE_MESH_MODEL_OP_SCENE_STORE_UNACK */ + esp_ble_mesh_scene_recall_t scene_recall; /*!< For ESP_BLE_MESH_MODEL_OP_SCENE_RECALL & ESP_BLE_MESH_MODEL_OP_SCENE_RECALL_UNACK */ + esp_ble_mesh_scene_delete_t scene_delete; /*!< For ESP_BLE_MESH_MODEL_OP_SCENE_DELETE & ESP_BLE_MESH_MODEL_OP_SCENE_DELETE_UNACK */ + esp_ble_mesh_scheduler_act_set_t scheduler_act_set; /*!< For ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET & ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET_UNACK */ +} esp_ble_mesh_time_scene_client_set_state_t; + +/** + * @brief Bluetooth Mesh Time Scene Client Model Get and Set callback parameters structure. + */ + +typedef struct { + u8_t tai_seconds[5]; /* The current TAI time in seconds */ + u8_t sub_second; /* The sub-second time in units of 1/256 second */ + u8_t uncertainty; /* The estimated uncertainty in 10-millisecond steps */ + u16_t time_authority : 1; /* 0 = No Time Authority, 1 = Time Authority */ + u16_t tai_utc_delta : 15; /* Current difference between TAI and UTC in seconds */ + u8_t time_zone_offset; /* The local time zone offset in 15-minute increments */ +} esp_ble_mesh_time_status_cb_t; + +typedef struct { + u8_t time_zone_offset_curr; /* Current local time zone offset */ + u8_t time_zone_offset_new; /* Upcoming local time zone offset */ + u8_t tai_zone_change[5]; /* TAI Seconds time of the upcoming Time Zone Offset change */ +} esp_ble_mesh_time_zone_status_cb_t; + +typedef struct { + u16_t tai_utc_delta_curr : 15; /* Current difference between TAI and UTC in seconds */ + u16_t padding_1 : 1; /* Always 0b0. Other values are Prohibited. */ + u16_t tai_utc_delta_new : 15; /* Upcoming difference between TAI and UTC in seconds */ + u16_t padding_2 : 1; /* Always 0b0. Other values are Prohibited. */ + u8_t tai_delta_change[5]; /* TAI Seconds time of the upcoming TAI-UTC Delta change */ +} esp_ble_mesh_tai_utc_delta_status_cb_t; + +typedef struct { + u8_t time_role; /* The Time Role for the element */ +} esp_ble_mesh_time_role_status_cb_t; + +typedef struct { + bool op_en; /* Indicate if optional parameters are included */ + u8_t status_code; /* Status code of the last operation */ + u16_t current_scene; /* Scene Number of the current scene */ + u16_t target_scene; /* Scene Number of the target scene (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +} esp_ble_mesh_scene_status_cb_t; + +typedef struct { + u8_t status_code; /* Status code for the previous operation */ + u16_t current_scene; /* Scene Number of the current scene */ + struct net_buf_simple *scenes; /* A list of scenes stored within an element */ +} esp_ble_mesh_scene_register_status_cb_t; + +typedef struct { + u16_t schedules; /* Bit field indicating defined Actions in the Schedule Register */ +} esp_ble_mesh_scheduler_status_cb_t; + +typedef struct { + u64_t index : 4; /* Enumerates (selects) a Schedule Register entry */ + u64_t year : 7; /* Scheduled year for the action */ + u64_t month : 12; /* Scheduled month for the action */ + u64_t day : 5; /* Scheduled day of the month for the action */ + u64_t hour : 5; /* Scheduled hour for the action */ + u64_t minute : 6; /* Scheduled minute for the action */ + u64_t second : 6; /* Scheduled second for the action */ + u64_t day_of_week : 7; /* Schedule days of the week for the action */ + u64_t action : 4; /* Action to be performed at the scheduled time */ + u64_t trans_time : 8; /* Transition time for this action */ + u16_t scene_number; /* Transition time for this action */ +} esp_ble_mesh_scheduler_act_status_cb_t; + +typedef union { + esp_ble_mesh_time_status_cb_t time_status; /*!< For ESP_BLE_MESH_MODEL_OP_TIME_STATUS */ + esp_ble_mesh_time_zone_status_cb_t time_zone_status; /*!< For ESP_BLE_MESH_MODEL_OP_TIME_ZONE_STATUS */ + esp_ble_mesh_tai_utc_delta_status_cb_t tai_utc_delta_status; /*!< For ESP_BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS */ + esp_ble_mesh_time_role_status_cb_t time_role_status; /*!< For ESP_BLE_MESH_MODEL_OP_TIME_ROLE_STATUS */ + esp_ble_mesh_scene_status_cb_t scene_status; /*!< For ESP_BLE_MESH_MODEL_OP_SCENE_STATUS */ + esp_ble_mesh_scene_register_status_cb_t scene_register_status; /*!< For ESP_BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS */ + esp_ble_mesh_scheduler_status_cb_t scheduler_status; /*!< For ESP_BLE_MESH_MODEL_OP_SCHEDULER_STATUS */ + esp_ble_mesh_scheduler_act_status_cb_t scheduler_act_status; /*!< For ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS */ +} esp_ble_mesh_time_scene_client_status_cb_t; + +typedef struct { + int error_code; /*!< Appropriate error code */ + esp_ble_mesh_client_common_param_t *params; /*!< The client common parameters. */ + esp_ble_mesh_time_scene_client_status_cb_t status_cb; /*!< The scene status message callback values */ +} esp_ble_mesh_time_scene_client_cb_param_t; + +typedef enum { + ESP_BLE_MESH_TIME_SCENE_CLIENT_GET_STATE_EVT, + ESP_BLE_MESH_TIME_SCENE_CLIENT_SET_STATE_EVT, + ESP_BLE_MESH_TIME_SCENE_CLIENT_PUBLISH_EVT, + ESP_BLE_MESH_TIME_SCENE_CLIENT_TIMEOUT_EVT, + ESP_BLE_MESH_TIME_SCENE_CLIENT_EVT_MAX, +} esp_ble_mesh_time_scene_client_cb_event_t; + +/** + * @brief Bluetooth Mesh Time Scene Client Model function. + */ + +/** @brief: event, event code of Time Scene Client Model events; param, parameters of Time Scene Client Model events */ +typedef void (* esp_ble_mesh_time_scene_client_cb_t)(esp_ble_mesh_time_scene_client_cb_event_t event, + esp_ble_mesh_time_scene_client_cb_param_t *param); + +/** + * @brief Register BLE Mesh Time Scene Client Model callback. + * + * @param[in] callback: Pointer to the callback function. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_register_time_scene_client_callback(esp_ble_mesh_time_scene_client_cb_t callback); + +/** + * @brief Get the value of Time Scene Server Model states using the Time Scene Client Model get messages. + * + * @note If you want to know the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_time_scene_message_opcode_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] get_state: Pointer to time scene get message value. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + */ +esp_err_t esp_ble_mesh_time_scene_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_time_scene_client_get_state_t *get_state); + +/** + * @brief Set the value of Time Scene Server Model states using the Time Scene Client Model set messages. + * + * @note If you want to know the opcodes and corresponding meanings accepted by this API, + * please refer to (@ref esp_ble_mesh_time_scene_message_opcode_t). + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] set_state: Pointer to time scene set message value. + * Shall not be set to NULL. + * + * @return ESP_OK on success or error code otherwise. + */ +esp_err_t esp_ble_mesh_time_scene_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_time_scene_client_set_state_t *set_state); + +#endif /* _ESP_BLE_MESH_TIME_SCENE_MODEL_API_H_ */ + diff --git a/components/bt/ble_mesh/btc/btc_ble_mesh_config_model.c b/components/bt/ble_mesh/btc/btc_ble_mesh_config_model.c new file mode 100644 index 0000000000..7fe0e0595d --- /dev/null +++ b/components/bt/ble_mesh/btc/btc_ble_mesh_config_model.c @@ -0,0 +1,725 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "btc/btc_manage.h" +#include "osi/allocator.h" + +#include "cfg_cli.h" +#include "mesh_common.h" +#include "btc_ble_mesh_config_model.h" +#include "esp_ble_mesh_config_model_api.h" + +#define CID_NVAL 0xffff + +extern s32_t config_msg_timeout; + +static inline void btc_ble_mesh_cfg_client_cb_to_app(esp_ble_mesh_cfg_client_cb_event_t event, + esp_ble_mesh_cfg_client_cb_param_t *param) +{ + esp_ble_mesh_cfg_client_cb_t btc_mesh_cb = (esp_ble_mesh_cfg_client_cb_t)btc_profile_cb_get(BTC_PID_CFG_CLIENT); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +static inline void btc_ble_mesh_cfg_server_cb_to_app(esp_ble_mesh_cfg_server_cb_event_t event, + esp_ble_mesh_cfg_server_cb_param_t *param) +{ + esp_ble_mesh_cfg_server_cb_t btc_mesh_cb = (esp_ble_mesh_cfg_server_cb_t)btc_profile_cb_get(BTC_PID_CFG_SERVER); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +void btc_ble_mesh_cfg_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_cfg_client_args_t *dst = (btc_ble_mesh_cfg_client_args_t *)p_dest; + btc_ble_mesh_cfg_client_args_t *src = (btc_ble_mesh_cfg_client_args_t *)p_src; + + if (!msg || !dst || !src) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_CONFIG_CLIENT_GET_STATE: { + dst->cfg_client_get_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->cfg_client_get_state.get_state = (esp_ble_mesh_cfg_client_get_state_t *)osi_malloc(sizeof(esp_ble_mesh_cfg_client_get_state_t)); + if (dst->cfg_client_get_state.params && dst->cfg_client_get_state.get_state) { + memcpy(dst->cfg_client_get_state.params, src->cfg_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->cfg_client_get_state.get_state, src->cfg_client_get_state.get_state, + sizeof(esp_ble_mesh_cfg_client_get_state_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + case BTC_BLE_MESH_ACT_CONFIG_CLIENT_SET_STATE: { + dst->cfg_client_set_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->cfg_client_set_state.set_state = (esp_ble_mesh_cfg_client_set_state_t *)osi_malloc(sizeof(esp_ble_mesh_cfg_client_set_state_t)); + if (dst->cfg_client_set_state.params && dst->cfg_client_set_state.set_state) { + memcpy(dst->cfg_client_set_state.params, src->cfg_client_set_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->cfg_client_set_state.set_state, src->cfg_client_set_state.set_state, + sizeof(esp_ble_mesh_cfg_client_set_state_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + LOG_DEBUG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_cfg_client_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_cfg_client_cb_param_t *p_dest_data = (esp_ble_mesh_cfg_client_cb_param_t *)p_dest; + esp_ble_mesh_cfg_client_cb_param_t *p_src_data = (esp_ble_mesh_cfg_client_cb_param_t *)p_src; + u32_t opcode; + u16_t length; + + if (!msg || !p_src_data || !p_dest_data) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT: + if (p_src_data->params) { + opcode = p_src_data->params->opcode; + switch (opcode) { + case OP_DEV_COMP_DATA_GET: + case OP_DEV_COMP_DATA_STATUS: + if (p_src_data->status_cb.comp_data_status.composition_data) { + length = p_src_data->status_cb.comp_data_status.composition_data->len; + p_dest_data->status_cb.comp_data_status.composition_data = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.comp_data_status.composition_data) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.comp_data_status.composition_data, + p_src_data->status_cb.comp_data_status.composition_data->data, + p_src_data->status_cb.comp_data_status.composition_data->len); + } + break; + case OP_MOD_SUB_GET: + case OP_MOD_SUB_GET_VND: + case OP_MOD_SUB_LIST: + case OP_MOD_SUB_LIST_VND: + if (p_src_data->status_cb.model_sub_list.sub_addr) { + length = p_src_data->status_cb.model_sub_list.sub_addr->len; + p_dest_data->status_cb.model_sub_list.sub_addr = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.model_sub_list.sub_addr) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.model_sub_list.sub_addr, + p_src_data->status_cb.model_sub_list.sub_addr->data, + p_src_data->status_cb.model_sub_list.sub_addr->len); + } + break; + case OP_NET_KEY_GET: + case OP_NET_KEY_LIST: + if (p_src_data->status_cb.netkey_list.net_idx) { + length = p_src_data->status_cb.netkey_list.net_idx->len; + p_dest_data->status_cb.netkey_list.net_idx = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.netkey_list.net_idx) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.netkey_list.net_idx, + p_src_data->status_cb.netkey_list.net_idx->data, + p_src_data->status_cb.netkey_list.net_idx->len); + } + break; + case OP_APP_KEY_GET: + case OP_APP_KEY_LIST: + if (p_src_data->status_cb.appkey_list.app_idx) { + length = p_src_data->status_cb.appkey_list.app_idx->len; + p_dest_data->status_cb.appkey_list.app_idx = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.appkey_list.app_idx) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.appkey_list.app_idx, + p_src_data->status_cb.appkey_list.app_idx->data, + p_src_data->status_cb.appkey_list.app_idx->len); + } + break; + case OP_SIG_MOD_APP_GET: + case OP_VND_MOD_APP_GET: + case OP_SIG_MOD_APP_LIST: + case OP_VND_MOD_APP_LIST: + if (p_src_data->status_cb.model_app_list.app_idx) { + length = p_src_data->status_cb.model_app_list.app_idx->len; + p_dest_data->status_cb.model_app_list.app_idx = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.model_app_list.app_idx) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.model_app_list.app_idx, + p_src_data->status_cb.model_app_list.app_idx->data, + p_src_data->status_cb.model_app_list.app_idx->len); + } + break; + default: + break; + } + } + case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT: + if (p_src_data->params) { + p_dest_data->params = osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (p_dest_data->params) { + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + default: + break; + } +} + +static void btc_ble_mesh_cfg_client_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_cfg_client_cb_param_t *arg = NULL; + u32_t opcode; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_cfg_client_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT: + if (arg->params) { + opcode = arg->params->opcode; + switch (opcode) { + case OP_DEV_COMP_DATA_GET: + case OP_DEV_COMP_DATA_STATUS: + bt_mesh_free_buf(arg->status_cb.comp_data_status.composition_data); + break; + case OP_MOD_SUB_GET: + case OP_MOD_SUB_GET_VND: + case OP_MOD_SUB_LIST: + case OP_MOD_SUB_LIST_VND: + bt_mesh_free_buf(arg->status_cb.model_sub_list.sub_addr); + break; + case OP_NET_KEY_GET: + case OP_NET_KEY_LIST: + bt_mesh_free_buf(arg->status_cb.netkey_list.net_idx); + break; + case OP_APP_KEY_GET: + case OP_APP_KEY_LIST: + bt_mesh_free_buf(arg->status_cb.appkey_list.app_idx); + break; + case OP_SIG_MOD_APP_GET: + case OP_VND_MOD_APP_GET: + case OP_SIG_MOD_APP_LIST: + case OP_VND_MOD_APP_LIST: + bt_mesh_free_buf(arg->status_cb.model_app_list.app_idx); + break; + default: + break; + } + } + case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT: + if (arg->params) { + osi_free(arg->params); + } + break; + default: + break; + } +} + +void btc_ble_mesh_cfg_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_cfg_client_args_t *arg = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_cfg_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_CONFIG_CLIENT_GET_STATE: + if (arg->cfg_client_get_state.params) { + osi_free(arg->cfg_client_get_state.params); + } + if (arg->cfg_client_get_state.get_state) { + osi_free(arg->cfg_client_get_state.get_state); + } + break; + case BTC_BLE_MESH_ACT_CONFIG_CLIENT_SET_STATE: + if (arg->cfg_client_set_state.params) { + osi_free(arg->cfg_client_set_state.params); + } + if (arg->cfg_client_set_state.set_state) { + osi_free(arg->cfg_client_set_state.set_state); + } + break; + default: + break; + } + + return; +} + +static void btc_mesh_cfg_client_callback(esp_ble_mesh_cfg_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_CFG_CLIENT; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_cfg_client_cb_param_t), btc_ble_mesh_cfg_client_copy_req_data); +} + +void bt_mesh_callback_config_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len) +{ + esp_ble_mesh_cfg_client_cb_param_t cb_params = {0}; + esp_ble_mesh_client_common_param_t params = {0}; + size_t length; + uint8_t act; + + if (!model || !ctx) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case 0x00: + act = ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT; + break; + case 0x01: + act = ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT; + break; + case 0x02: + act = ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT; + break; + case 0x03: + act = ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT; + break; + default: + LOG_ERROR("%s, Unknown config client event type %d", __func__, evt_type); + return; + } + + params.opcode = opcode; + params.model = (esp_ble_mesh_model_t *)model; + params.ctx.net_idx = ctx->net_idx; + params.ctx.app_idx = ctx->app_idx; + params.ctx.addr = ctx->addr; + params.ctx.recv_ttl = ctx->recv_ttl; + params.ctx.recv_op = ctx->recv_op; + params.ctx.recv_dst = ctx->recv_dst; + + cb_params.error_code = 0; + cb_params.params = ¶ms; + + if (val && len) { + length = (len <= sizeof(cb_params.status_cb)) ? len : sizeof(cb_params.status_cb); + memcpy(&cb_params.status_cb, val, length); + } + + btc_mesh_cfg_client_callback(&cb_params, act); +} + + +void btc_mesh_cfg_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) +{ + if (!model || !ctx || !buf) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_callback_config_status_to_btc(opcode, 0x02, model, ctx, buf->data, buf->len); +} + +void btc_mesh_cfg_client_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_cfg_client_cb_param_t cfg_client_cb = {0}; + btc_ble_mesh_cfg_client_args_t *arg = NULL; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_cfg_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_CONFIG_CLIENT_GET_STATE: { + cfg_client_cb.params = arg->cfg_client_get_state.params; + role_param.model = (struct bt_mesh_model *)cfg_client_cb.params->model; + role_param.role = cfg_client_cb.params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + btc_ble_mesh_config_client_get_state(arg->cfg_client_get_state.params, + arg->cfg_client_get_state.get_state, + &cfg_client_cb); + if (cfg_client_cb.error_code) { + btc_mesh_cfg_client_callback(&cfg_client_cb, ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT); + } + break; + } + case BTC_BLE_MESH_ACT_CONFIG_CLIENT_SET_STATE: { + cfg_client_cb.params = arg->cfg_client_set_state.params; + role_param.model = (struct bt_mesh_model *)cfg_client_cb.params->model; + role_param.role = cfg_client_cb.params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + btc_ble_mesh_config_client_set_state(arg->cfg_client_set_state.params, + arg->cfg_client_set_state.set_state, + &cfg_client_cb); + if (cfg_client_cb.error_code) { + btc_mesh_cfg_client_callback(&cfg_client_cb, ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_cfg_client_arg_deep_free(msg); +} + +void btc_mesh_cfg_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_cfg_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_cfg_client_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_CFG_CLIENT_EVT_MAX) { + btc_ble_mesh_cfg_client_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_cfg_client_free_req_data(msg); +} + +int btc_ble_mesh_config_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_cfg_client_get_state_t *get_state, + esp_ble_mesh_cfg_client_cb_param_t *cfg_client_cb) +{ + struct bt_mesh_msg_ctx ctx = {0}; + + if (!params || !cfg_client_cb) { + LOG_ERROR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + ctx.net_idx = params->ctx.net_idx; + ctx.app_idx = BLE_MESH_KEY_DEV; + ctx.addr = params->ctx.addr; + ctx.send_rel = params->ctx.send_rel; + ctx.send_ttl = params->ctx.send_ttl; + + config_msg_timeout = params->msg_timeout; + + switch (params->opcode) { + case ESP_BLE_MESH_MODEL_OP_BEACON_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_beacon_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_ttl_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_FRIEND_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_friend_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_GATT_PROXY_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_gatt_proxy_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_RELAY_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_relay_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_MODEL_PUB_GET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_pub_get(&ctx, get_state->model_pub_get.element_addr, get_state->model_pub_get.model_id, + get_state->model_pub_get.company_id)); + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_hb_pub_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_hb_sub_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_comp_data_get(&ctx, get_state->comp_data_get.page)); + case ESP_BLE_MESH_MODEL_OP_SIG_MODEL_SUB_GET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_sub_get(&ctx, get_state->sig_model_sub_get.element_addr, get_state->sig_model_sub_get.model_id)); + case ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_SUB_GET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_sub_get_vnd(&ctx, get_state->vnd_model_sub_get.element_addr, + get_state->vnd_model_sub_get.model_id, get_state->vnd_model_sub_get.company_id)); + case ESP_BLE_MESH_MODEL_OP_NET_KEY_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_net_key_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_APP_KEY_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_app_key_get(&ctx, get_state->app_key_get.net_idx)); + case ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_node_identity_get(&ctx, get_state->node_identity_get.net_idx)); + case ESP_BLE_MESH_MODEL_OP_SIG_MODEL_APP_GET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_app_get(&ctx, get_state->sig_model_app_get.element_addr, get_state->sig_model_app_get.model_id)); + case ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_APP_GET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_app_get_vnd(&ctx, get_state->vnd_model_app_get.element_addr, + get_state->vnd_model_app_get.model_id, get_state->vnd_model_app_get.company_id)); + case ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_kr_phase_get(&ctx, get_state->kr_phase_get.net_idx)); + case ESP_BLE_MESH_MODEL_OP_LPN_POLLTIMEOUT_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_lpn_timeout_get(&ctx, get_state->lpn_pollto_get.lpn_addr)); + case ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_GET: + return (cfg_client_cb->error_code = bt_mesh_cfg_net_transmit_get(&ctx)); + default: + BT_WARN("%s, Invalid opcode 0x%x", __func__, params->opcode); + return (cfg_client_cb->error_code = -EINVAL); + } + + return 0; +} + +int btc_ble_mesh_config_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_cfg_client_set_state_t *set_state, + esp_ble_mesh_cfg_client_cb_param_t *cfg_client_cb) +{ + struct bt_mesh_msg_ctx ctx = {0}; + + if (!params || !set_state || !cfg_client_cb) { + LOG_ERROR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + ctx.net_idx = params->ctx.net_idx; + ctx.app_idx = BLE_MESH_KEY_DEV; + ctx.addr = params->ctx.addr; + ctx.send_rel = params->ctx.send_rel; + ctx.send_ttl = params->ctx.send_ttl; + + config_msg_timeout = params->msg_timeout; + + switch (params->opcode) { + case ESP_BLE_MESH_MODEL_OP_BEACON_SET: + return (cfg_client_cb->error_code = bt_mesh_cfg_beacon_set(&ctx, set_state->beacon_set.beacon)); + case ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_SET: + return (cfg_client_cb->error_code = bt_mesh_cfg_ttl_set(&ctx, set_state->default_ttl_set.ttl)); + case ESP_BLE_MESH_MODEL_OP_FRIEND_SET: + return (cfg_client_cb->error_code = bt_mesh_cfg_friend_set(&ctx, set_state->friend_set.friend_state)); + case ESP_BLE_MESH_MODEL_OP_GATT_PROXY_SET: + return (cfg_client_cb->error_code = bt_mesh_cfg_gatt_proxy_set(&ctx, set_state->gatt_proxy_set.gatt_proxy)); + case ESP_BLE_MESH_MODEL_OP_RELAY_SET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_relay_set(&ctx, set_state->relay_set.relay, set_state->relay_set.relay_retransmit)); + case ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD: + return (cfg_client_cb->error_code = + bt_mesh_cfg_net_key_add(&ctx, set_state->net_key_add.net_idx, &set_state->net_key_add.net_key[0])); + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: + return (cfg_client_cb->error_code = + bt_mesh_cfg_app_key_add(&ctx, set_state->app_key_add.net_idx, + set_state->app_key_add.app_idx, &set_state->app_key_add.app_key[0])); + case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_app_bind(&ctx, set_state->model_app_bind.element_addr, set_state->model_app_bind.model_app_idx, + set_state->model_app_bind.model_id, set_state->model_app_bind.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_PUB_SET: { + struct bt_mesh_cfg_mod_pub model_pub = { + .addr = set_state->model_pub_set.publish_addr, + .app_idx = set_state->model_pub_set.publish_app_idx, + .cred_flag = set_state->model_pub_set.cred_flag, + .ttl = set_state->model_pub_set.publish_ttl, + .period = set_state->model_pub_set.publish_period, + .transmit = set_state->model_pub_set.publish_retransmit, + }; + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_pub_set(&ctx, set_state->model_pub_set.element_addr, set_state->model_pub_set.model_id, + set_state->model_pub_set.company_id, &model_pub)); + } + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_sub_add(&ctx, set_state->model_sub_add.element_addr, set_state->model_sub_add.sub_addr, + set_state->model_sub_add.model_id, set_state->model_sub_add.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_sub_del(&ctx, set_state->model_sub_delete.element_addr, set_state->model_sub_delete.sub_addr, + set_state->model_sub_delete.model_id, set_state->model_sub_delete.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_OVERWRITE: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_sub_overwrite(&ctx, set_state->model_sub_overwrite.element_addr, set_state->model_sub_overwrite.sub_addr, + set_state->model_sub_overwrite.model_id, set_state->model_sub_overwrite.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_ADD: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_sub_va_add(&ctx, set_state->model_sub_va_add.element_addr, &set_state->model_sub_va_add.label_uuid[0], + set_state->model_sub_va_add.model_id, set_state->model_sub_va_add.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_OVERWRITE: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_sub_va_overwrite(&ctx, set_state->model_sub_va_overwrite.element_addr, &set_state->model_sub_va_overwrite.label_uuid[0], + set_state->model_sub_va_overwrite.model_id, set_state->model_sub_va_overwrite.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_DELETE: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_sub_va_del(&ctx, set_state->model_sub_va_delete.element_addr, &set_state->model_sub_va_delete.label_uuid[0], + set_state->model_sub_va_delete.model_id, set_state->model_sub_va_delete.company_id)); + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_hb_sub_set(&ctx, (struct bt_mesh_cfg_hb_sub *)&set_state->heartbeat_sub_set)); + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_hb_pub_set(&ctx, (const struct bt_mesh_cfg_hb_pub *)&set_state->heartbeat_pub_set)); + case ESP_BLE_MESH_MODEL_OP_NODE_RESET: + return (cfg_client_cb->error_code = bt_mesh_cfg_node_reset(&ctx)); + case ESP_BLE_MESH_MODEL_OP_MODEL_PUB_VIRTUAL_ADDR_SET: { + struct bt_mesh_cfg_mod_pub model_pub = { + .app_idx = set_state->model_pub_va_set.publish_app_idx, + .cred_flag = set_state->model_pub_va_set.cred_flag, + .ttl = set_state->model_pub_va_set.publish_ttl, + .period = set_state->model_pub_va_set.publish_period, + .transmit = set_state->model_pub_va_set.publish_retransmit, + }; + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_pub_va_set(&ctx, set_state->model_pub_va_set.element_addr, set_state->model_pub_va_set.model_id, + set_state->model_pub_va_set.company_id, set_state->model_pub_va_set.label_uuid, &model_pub)); + } + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE_ALL: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_sub_del_all(&ctx, set_state->model_sub_delete_all.element_addr, + set_state->model_sub_delete_all.model_id, set_state->model_sub_delete_all.company_id)); + case ESP_BLE_MESH_MODEL_OP_NET_KEY_UPDATE: + return (cfg_client_cb->error_code = + bt_mesh_cfg_net_key_update(&ctx, set_state->net_key_update.net_idx, set_state->net_key_update.net_key)); + case ESP_BLE_MESH_MODEL_OP_NET_KEY_DELETE: + return (cfg_client_cb->error_code = + bt_mesh_cfg_net_key_delete(&ctx, set_state->net_key_delete.net_idx)); + case ESP_BLE_MESH_MODEL_OP_APP_KEY_UPDATE: + return (cfg_client_cb->error_code = + bt_mesh_cfg_app_key_update(&ctx, set_state->app_key_update.net_idx, set_state->app_key_update.app_idx, + set_state->app_key_update.app_key)); + case ESP_BLE_MESH_MODEL_OP_APP_KEY_DELETE: + return (cfg_client_cb->error_code = + bt_mesh_cfg_app_key_delete(&ctx, set_state->app_key_delete.net_idx, set_state->app_key_delete.app_idx)); + case ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_SET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_node_identity_set(&ctx, set_state->node_identity_set.net_idx, set_state->node_identity_set.identity)); + case ESP_BLE_MESH_MODEL_OP_MODEL_APP_UNBIND: + return (cfg_client_cb->error_code = + bt_mesh_cfg_mod_app_unbind(&ctx, set_state->model_app_unbind.element_addr, set_state->model_app_unbind.model_app_idx, + set_state->model_app_unbind.model_id, set_state->model_app_unbind.company_id)); + case ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_SET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_kr_phase_set(&ctx, set_state->kr_phase_set.net_idx, set_state->kr_phase_set.transition)); + case ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_SET: + return (cfg_client_cb->error_code = + bt_mesh_cfg_net_transmit_set(&ctx, set_state->net_transmit_set.net_transmit)); + default: + BT_WARN("%s, Invalid opcode 0x%x", __func__, params->opcode); + return (cfg_client_cb->error_code = -EINVAL); + } + + return 0; +} + +static void btc_mesh_cfg_server_callback(esp_ble_mesh_cfg_server_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_CFG_SERVER; + msg.act = act; + + btc_transfer_context(&msg, cb_params, sizeof(esp_ble_mesh_cfg_server_cb_param_t), NULL); +} + +void bt_mesh_callback_cfg_server_event_to_btc(u8_t evt_type, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len) +{ + esp_ble_mesh_cfg_server_cb_param_t cb_params = {0}; + size_t length; + uint8_t act; + + if (!model || !ctx) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case 0x00: + act = ESP_BLE_MESH_CFG_SERVER_RECV_MSG_EVT; + break; + default: + LOG_ERROR("%s, Unknown config server event type %d", __func__, evt_type); + return; + } + + cb_params.model = (esp_ble_mesh_model_t *)model; + cb_params.ctx.net_idx = ctx->net_idx; + cb_params.ctx.app_idx = ctx->app_idx; + cb_params.ctx.addr = ctx->addr; + cb_params.ctx.recv_ttl = ctx->recv_ttl; + cb_params.ctx.recv_op = ctx->recv_op; + cb_params.ctx.recv_dst = ctx->recv_dst; + + if (val && len) { + length = (len <= sizeof(cb_params.status_cb)) ? len : sizeof(cb_params.status_cb); + memcpy(&cb_params.status_cb, val, length); + } + + btc_mesh_cfg_server_callback(&cb_params, act); +} + +void btc_mesh_cfg_server_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_cfg_server_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_cfg_server_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_CFG_SERVER_EVT_MAX) { + btc_ble_mesh_cfg_server_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } +} diff --git a/components/bt/ble_mesh/btc/btc_ble_mesh_generic_model.c b/components/bt/ble_mesh/btc/btc_ble_mesh_generic_model.c new file mode 100644 index 0000000000..b8fc338169 --- /dev/null +++ b/components/bt/ble_mesh/btc/btc_ble_mesh_generic_model.c @@ -0,0 +1,540 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "btc/btc_manage.h" +#include "osi/allocator.h" + +#include "cfg_cli.h" +#include "btc_ble_mesh_generic_model.h" +#include "esp_ble_mesh_generic_model_api.h" + +static inline void btc_ble_mesh_cb_to_app(esp_ble_mesh_generic_client_cb_event_t event, + esp_ble_mesh_generic_client_cb_param_t *param) +{ + esp_ble_mesh_generic_client_cb_t btc_mesh_cb = (esp_ble_mesh_generic_client_cb_t)btc_profile_cb_get(BTC_PID_GENERIC_CLIENT); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +void btc_ble_mesh_generic_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_generic_client_args_t *dst = (btc_ble_mesh_generic_client_args_t *)p_dest; + btc_ble_mesh_generic_client_args_t *src = (btc_ble_mesh_generic_client_args_t *)p_src; + u32_t opcode; + u16_t length; + + if (!msg || !dst || !src) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_GENERIC_CLIENT_GET_STATE: { + dst->generic_client_get_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->generic_client_get_state.get_state = (esp_ble_mesh_generic_client_get_state_t *)osi_malloc(sizeof(esp_ble_mesh_generic_client_get_state_t)); + if (dst->generic_client_get_state.params && dst->generic_client_get_state.get_state) { + memcpy(dst->generic_client_get_state.params, src->generic_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->generic_client_get_state.get_state, src->generic_client_get_state.get_state, + sizeof(esp_ble_mesh_generic_client_get_state_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + case BTC_BLE_MESH_ACT_GENERIC_CLIENT_SET_STATE: { + dst->generic_client_set_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->generic_client_set_state.set_state = (esp_ble_mesh_generic_client_set_state_t *)osi_malloc(sizeof(esp_ble_mesh_generic_client_set_state_t)); + if (dst->generic_client_set_state.params && dst->generic_client_set_state.set_state) { + memcpy(dst->generic_client_set_state.params, src->generic_client_set_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->generic_client_set_state.set_state, src->generic_client_set_state.set_state, + sizeof(esp_ble_mesh_generic_client_set_state_t)); + + opcode = src->generic_client_set_state.params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + if (src->generic_client_set_state.set_state->user_property_set.property_value) { + length = src->generic_client_set_state.set_state->user_property_set.property_value->len; + dst->generic_client_set_state.set_state->user_property_set.property_value = bt_mesh_alloc_buf(length); + if (!dst->generic_client_set_state.set_state->user_property_set.property_value) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->generic_client_set_state.set_state->user_property_set.property_value, + src->generic_client_set_state.set_state->user_property_set.property_value->data, + src->generic_client_set_state.set_state->user_property_set.property_value->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + if (src->generic_client_set_state.set_state->admin_property_set.property_value) { + length = src->generic_client_set_state.set_state->admin_property_set.property_value->len; + dst->generic_client_set_state.set_state->admin_property_set.property_value = bt_mesh_alloc_buf(length); + if (!dst->generic_client_set_state.set_state->admin_property_set.property_value) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->generic_client_set_state.set_state->admin_property_set.property_value, + src->generic_client_set_state.set_state->admin_property_set.property_value->data, + src->generic_client_set_state.set_state->admin_property_set.property_value->len); + } + break; + default: + break; + } + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + LOG_DEBUG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_generic_client_cb_param_t *p_dest_data = (esp_ble_mesh_generic_client_cb_param_t *)p_dest; + esp_ble_mesh_generic_client_cb_param_t *p_src_data = (esp_ble_mesh_generic_client_cb_param_t *)p_src; + u32_t opcode; + u16_t length; + + if (!msg || !p_src_data || !p_dest_data) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT: + if (p_src_data->params) { + opcode = p_src_data->params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS: + if (p_src_data->status_cb.user_properties_status.property_ids) { + length = p_src_data->status_cb.user_properties_status.property_ids->len; + p_dest_data->status_cb.user_properties_status.property_ids = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.user_properties_status.property_ids) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.user_properties_status.property_ids, + p_src_data->status_cb.user_properties_status.property_ids->data, + p_src_data->status_cb.user_properties_status.property_ids->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS: + if (p_src_data->status_cb.user_property_status.property_value) { + length = p_src_data->status_cb.user_property_status.property_value->len; + p_dest_data->status_cb.user_property_status.property_value = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.user_property_status.property_value) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.user_property_status.property_value, + p_src_data->status_cb.user_property_status.property_value->data, + p_src_data->status_cb.user_property_status.property_value->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS: + if (p_src_data->status_cb.admin_properties_status.property_ids) { + length = p_src_data->status_cb.admin_properties_status.property_ids->len; + p_dest_data->status_cb.admin_properties_status.property_ids = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.admin_properties_status.property_ids) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.admin_properties_status.property_ids, + p_src_data->status_cb.admin_properties_status.property_ids->data, + p_src_data->status_cb.admin_properties_status.property_ids->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS: + if (p_src_data->status_cb.admin_property_status.property_value) { + length = p_src_data->status_cb.admin_property_status.property_value->len; + p_dest_data->status_cb.admin_property_status.property_value = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.admin_property_status.property_value) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.admin_property_status.property_value, + p_src_data->status_cb.admin_property_status.property_value->data, + p_src_data->status_cb.admin_property_status.property_value->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTIES_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTIES_STATUS: + if (p_src_data->status_cb.manufacturer_properties_status.property_ids) { + length = p_src_data->status_cb.manufacturer_properties_status.property_ids->len; + p_dest_data->status_cb.manufacturer_properties_status.property_ids = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.manufacturer_properties_status.property_ids) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.manufacturer_properties_status.property_ids, + p_src_data->status_cb.manufacturer_properties_status.property_ids->data, + p_src_data->status_cb.manufacturer_properties_status.property_ids->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_STATUS: + if (p_src_data->status_cb.manufacturer_property_status.property_value) { + length = p_src_data->status_cb.manufacturer_property_status.property_value->len; + p_dest_data->status_cb.manufacturer_property_status.property_value = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.manufacturer_property_status.property_value) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.manufacturer_property_status.property_value, + p_src_data->status_cb.manufacturer_property_status.property_value->data, + p_src_data->status_cb.manufacturer_property_status.property_value->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS: + if (p_src_data->status_cb.client_properties_status.property_ids) { + length = p_src_data->status_cb.client_properties_status.property_ids->len; + p_dest_data->status_cb.client_properties_status.property_ids = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.client_properties_status.property_ids) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.client_properties_status.property_ids, + p_src_data->status_cb.client_properties_status.property_ids->data, + p_src_data->status_cb.client_properties_status.property_ids->len); + } + break; + default: + break; + } + } + case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT: + if (p_src_data->params) { + p_dest_data->params = osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (p_dest_data->params) { + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + default: + break; + } +} + +static void btc_ble_mesh_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_generic_client_cb_param_t *arg = NULL; + u32_t opcode; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_generic_client_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT: + if (arg->params) { + opcode = arg->params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS: + bt_mesh_free_buf(arg->status_cb.user_properties_status.property_ids); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS: + bt_mesh_free_buf(arg->status_cb.user_property_status.property_value); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS: + bt_mesh_free_buf(arg->status_cb.admin_properties_status.property_ids); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS: + bt_mesh_free_buf(arg->status_cb.admin_property_status.property_value); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTIES_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTIES_STATUS: + bt_mesh_free_buf(arg->status_cb.manufacturer_properties_status.property_ids); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_STATUS: + bt_mesh_free_buf(arg->status_cb.manufacturer_property_status.property_value); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS: + bt_mesh_free_buf(arg->status_cb.client_properties_status.property_ids); + break; + default: + break; + } + } + case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT: + if (arg->params) { + osi_free(arg->params); + } + break; + default: + break; + } +} + +void btc_ble_mesh_generic_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_generic_client_args_t *arg = NULL; + u32_t opcode = 0; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_generic_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_GENERIC_CLIENT_GET_STATE: + if (arg->generic_client_get_state.params) { + osi_free(arg->generic_client_get_state.params); + } + if (arg->generic_client_get_state.get_state) { + osi_free(arg->generic_client_get_state.get_state); + } + break; + case BTC_BLE_MESH_ACT_GENERIC_CLIENT_SET_STATE: + if (arg->generic_client_set_state.params) { + opcode = arg->generic_client_set_state.params->opcode; + osi_free(arg->generic_client_set_state.params); + } + if (arg->generic_client_set_state.set_state) { + if (opcode) { + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + bt_mesh_free_buf(arg->generic_client_set_state.set_state->user_property_set.property_value); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + bt_mesh_free_buf(arg->generic_client_set_state.set_state->admin_property_set.property_value); + break; + default: + break; + } + } + osi_free(arg->generic_client_set_state.set_state); + } + break; + default: + break; + } + + return; +} + +static void btc_mesh_generic_client_callback(esp_ble_mesh_generic_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_GENERIC_CLIENT; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_generic_client_cb_param_t), btc_ble_mesh_copy_req_data); +} + +void bt_mesh_callback_generic_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len) +{ + esp_ble_mesh_generic_client_cb_param_t cb_params = {0}; + esp_ble_mesh_client_common_param_t params = {0}; + size_t length; + uint8_t act; + + if (!model || !ctx) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case 0x00: + act = ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT; + break; + case 0x01: + act = ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT; + break; + case 0x02: + act = ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT; + break; + case 0x03: + act = ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT; + break; + default: + LOG_ERROR("%s, Unknown generic client event type %d", __func__, evt_type); + return; + } + + params.opcode = opcode; + params.model = (esp_ble_mesh_model_t *)model; + params.ctx.net_idx = ctx->net_idx; + params.ctx.app_idx = ctx->app_idx; + params.ctx.addr = ctx->addr; + params.ctx.recv_ttl = ctx->recv_ttl; + params.ctx.recv_op = ctx->recv_op; + params.ctx.recv_dst = ctx->recv_dst; + + cb_params.error_code = 0; + cb_params.params = ¶ms; + + if (val && len) { + length = (len <= sizeof(cb_params.status_cb)) ? len : sizeof(cb_params.status_cb); + memcpy(&cb_params.status_cb, val, length); + } + + btc_mesh_generic_client_callback(&cb_params, act); +} + +void btc_mesh_generic_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) +{ + if (!model || !ctx || !buf) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_callback_generic_status_to_btc(opcode, 0x02, model, ctx, buf->data, buf->len); +} + +void btc_mesh_generic_client_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_generic_client_cb_param_t generic_client_cb = {0}; + esp_ble_mesh_client_common_param_t *params = NULL; + btc_ble_mesh_generic_client_args_t *arg = NULL; + struct bt_mesh_common_param common = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_generic_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_GENERIC_CLIENT_GET_STATE: { + params = arg->generic_client_get_state.params; + role_param.model = (struct bt_mesh_model *)params->model; + role_param.role = params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + common.opcode = params->opcode; + common.model = (struct bt_mesh_model *)params->model; + common.ctx.net_idx = params->ctx.net_idx; + common.ctx.app_idx = params->ctx.app_idx; + common.ctx.addr = params->ctx.addr; + common.ctx.send_rel = params->ctx.send_rel; + common.ctx.send_ttl = params->ctx.send_ttl; + common.msg_timeout = params->msg_timeout; + + generic_client_cb.params = arg->generic_client_get_state.params; + generic_client_cb.error_code = + bt_mesh_generic_client_get_state(&common, + (void *)arg->generic_client_get_state.get_state, + (void *)&generic_client_cb.status_cb); + if (generic_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_generic_client_callback(&generic_client_cb, + ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT); + } + break; + } + case BTC_BLE_MESH_ACT_GENERIC_CLIENT_SET_STATE: { + params = arg->generic_client_set_state.params; + role_param.model = (struct bt_mesh_model *)params->model; + role_param.role = params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + common.opcode = params->opcode; + common.model = (struct bt_mesh_model *)params->model; + common.ctx.net_idx = params->ctx.net_idx; + common.ctx.app_idx = params->ctx.app_idx; + common.ctx.addr = params->ctx.addr; + common.ctx.send_rel = params->ctx.send_rel; + common.ctx.send_ttl = params->ctx.send_ttl; + common.msg_timeout = params->msg_timeout; + + generic_client_cb.params = arg->generic_client_set_state.params; + generic_client_cb.error_code = + bt_mesh_generic_client_set_state(&common, + (void *)arg->generic_client_set_state.set_state, + (void *)&generic_client_cb.status_cb); + if (generic_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_generic_client_callback(&generic_client_cb, + ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_generic_client_arg_deep_free(msg); +} + +void btc_mesh_generic_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_generic_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_generic_client_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_GENERIC_CLIENT_EVT_MAX) { + btc_ble_mesh_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_free_req_data(msg); +} diff --git a/components/bt/ble_mesh/btc/btc_ble_mesh_health_model.c b/components/bt/ble_mesh/btc/btc_ble_mesh_health_model.c new file mode 100644 index 0000000000..6651f59936 --- /dev/null +++ b/components/bt/ble_mesh/btc/btc_ble_mesh_health_model.c @@ -0,0 +1,592 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "btc/btc_manage.h" +#include "btc/btc_task.h" +#include "osi/allocator.h" + +#include "health_srv.h" +#include "health_cli.h" +#include "mesh_common.h" + +#include "btc_ble_mesh_health_model.h" +#include "esp_ble_mesh_defs.h" + +extern s32_t health_msg_timeout; + +static inline void btc_ble_mesh_health_client_cb_to_app(esp_ble_mesh_health_client_cb_event_t event, + esp_ble_mesh_health_client_cb_param_t *param) +{ + esp_ble_mesh_health_client_cb_t btc_mesh_cb = (esp_ble_mesh_health_client_cb_t)btc_profile_cb_get(BTC_PID_HEALTH_CLIENT); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +static inline void btc_ble_mesh_health_server_cb_to_app(esp_ble_mesh_health_server_cb_event_t event, + esp_ble_mesh_health_server_cb_param_t *param) +{ + esp_ble_mesh_health_server_cb_t btc_mesh_cb = (esp_ble_mesh_health_server_cb_t)btc_profile_cb_get(BTC_PID_HEALTH_SERVER); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +void btc_ble_mesh_health_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_health_client_args_t *dst = (btc_ble_mesh_health_client_args_t *)p_dest; + btc_ble_mesh_health_client_args_t *src = (btc_ble_mesh_health_client_args_t *)p_src; + + if (!msg || !dst || !src) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_HEALTH_CLIENT_GET_STATE: { + dst->health_client_get_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->health_client_get_state.get_state = (esp_ble_mesh_health_client_get_state_t *)osi_malloc(sizeof(esp_ble_mesh_health_client_get_state_t)); + if (dst->health_client_get_state.params && dst->health_client_get_state.get_state) { + memcpy(dst->health_client_get_state.params, src->health_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->health_client_get_state.get_state, src->health_client_get_state.get_state, + sizeof(esp_ble_mesh_health_client_get_state_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + case BTC_BLE_MESH_ACT_HEALTH_CLIENT_SET_STATE: { + dst->health_client_set_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->health_client_set_state.set_state = (esp_ble_mesh_health_client_set_state_t *)osi_malloc(sizeof(esp_ble_mesh_health_client_set_state_t)); + if (dst->health_client_set_state.params && dst->health_client_set_state.set_state) { + memcpy(dst->health_client_set_state.params, src->health_client_set_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->health_client_set_state.set_state, src->health_client_set_state.set_state, + sizeof(esp_ble_mesh_health_client_set_state_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + LOG_DEBUG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_health_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_health_client_args_t *arg = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_health_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_HEALTH_CLIENT_GET_STATE: + if (arg->health_client_get_state.params) { + osi_free(arg->health_client_get_state.params); + } + if (arg->health_client_get_state.get_state) { + osi_free(arg->health_client_get_state.get_state); + } + break; + case BTC_BLE_MESH_ACT_HEALTH_CLIENT_SET_STATE: + if (arg->health_client_set_state.params) { + osi_free(arg->health_client_set_state.params); + } + if (arg->health_client_set_state.set_state) { + osi_free(arg->health_client_set_state.set_state); + } + break; + default: + break; + } + + return; +} + +void btc_ble_mesh_health_server_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + if (!msg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_HEALTH_SERVER_FAULT_UPDATE: + break; + default: + break; + } +} + +static void btc_ble_mesh_health_server_arg_deep_free(btc_msg_t *msg) +{ + if (!msg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_HEALTH_SERVER_FAULT_UPDATE: + break; + default: + break; + } +} + +static void btc_ble_mesh_health_client_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_health_client_cb_param_t *p_dest_data = (esp_ble_mesh_health_client_cb_param_t *)p_dest; + esp_ble_mesh_health_client_cb_param_t *p_src_data = (esp_ble_mesh_health_client_cb_param_t *)p_src; + u32_t opcode; + u16_t length; + + if (!msg || !p_src_data || !p_dest_data) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_HEALTH_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_HEALTH_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_HEALTH_CLIENT_PUBLISH_EVT: + if (p_src_data->params) { + opcode = p_src_data->params->opcode; + switch (opcode) { + case OP_HEALTH_CURRENT_STATUS: + if (p_src_data->status_cb.current_status.fault_array) { + length = p_src_data->status_cb.current_status.fault_array->len; + p_dest_data->status_cb.current_status.fault_array = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.current_status.fault_array) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.current_status.fault_array, + p_src_data->status_cb.current_status.fault_array->data, + p_src_data->status_cb.current_status.fault_array->len); + } + break; + case OP_HEALTH_FAULT_GET: + case OP_HEALTH_FAULT_CLEAR: + case OP_HEALTH_FAULT_TEST: + case OP_HEALTH_FAULT_STATUS: + if (p_src_data->status_cb.fault_status.fault_array) { + length = p_src_data->status_cb.fault_status.fault_array->len; + p_dest_data->status_cb.fault_status.fault_array = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.fault_status.fault_array) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.fault_status.fault_array, + p_src_data->status_cb.fault_status.fault_array->data, + p_src_data->status_cb.fault_status.fault_array->len); + } + break; + default: + break; + } + } + case ESP_BLE_MESH_HEALTH_CLIENT_TIMEOUT_EVT: + if (p_src_data->params) { + p_dest_data->params = osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (p_dest_data->params) { + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + default: + break; + } +} + +static void btc_ble_mesh_health_client_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_health_client_cb_param_t *arg = NULL; + u32_t opcode; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_health_client_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_HEALTH_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_HEALTH_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_HEALTH_CLIENT_PUBLISH_EVT: + if (arg->params) { + opcode = arg->params->opcode; + switch (opcode) { + case OP_HEALTH_CURRENT_STATUS: + bt_mesh_free_buf(arg->status_cb.current_status.fault_array); + break; + case OP_HEALTH_FAULT_GET: + case OP_HEALTH_FAULT_CLEAR: + case OP_HEALTH_FAULT_TEST: + case OP_HEALTH_FAULT_STATUS: + bt_mesh_free_buf(arg->status_cb.fault_status.fault_array); + break; + default: + break; + } + } + case ESP_BLE_MESH_HEALTH_CLIENT_TIMEOUT_EVT: + if (arg->params) { + osi_free(arg->params); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_health_server_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + if (!msg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_HEALTH_SERVER_FAULT_UPDATE_COMPLETE_EVT: + break; + default: + break; + } +} + +static void btc_ble_mesh_health_server_free_req_data(btc_msg_t *msg) +{ + if (!msg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_HEALTH_SERVER_FAULT_UPDATE_COMPLETE_EVT: + break; + default: + break; + } +} + +static void btc_mesh_health_client_callback(esp_ble_mesh_health_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_HEALTH_CLIENT; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_health_client_cb_param_t), btc_ble_mesh_health_client_copy_req_data); +} + +static void btc_mesh_health_server_callback(esp_ble_mesh_health_server_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_HEALTH_SERVER; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_health_server_cb_param_t), btc_ble_mesh_health_server_copy_req_data); +} + +int btc_ble_mesh_health_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_health_client_get_state_t *get_state, + esp_ble_mesh_health_client_cb_param_t *client_cb) +{ + struct bt_mesh_msg_ctx ctx = {0}; + + if (!params || !client_cb) { + LOG_ERROR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + ctx.net_idx = params->ctx.net_idx; + ctx.app_idx = params->ctx.app_idx; + ctx.addr = params->ctx.addr; + ctx.send_rel = params->ctx.send_rel; + ctx.send_ttl = params->ctx.send_ttl; + + health_msg_timeout = params->msg_timeout; + + switch (params->opcode) { + case ESP_BLE_MESH_MODEL_OP_ATTENTION_GET: + return (client_cb->error_code = bt_mesh_health_attention_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_GET: + return (client_cb->error_code = bt_mesh_health_period_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET: + return (client_cb->error_code = bt_mesh_health_fault_get(&ctx, get_state->fault_get.company_id)); + default: + BT_WARN("%s, invalid opcode 0x%x", __func__, params->opcode); + return (client_cb->error_code = -EINVAL); + } + + return 0; +} + +int btc_ble_mesh_health_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_health_client_set_state_t *set_state, + esp_ble_mesh_health_client_cb_param_t *client_cb) +{ + struct bt_mesh_msg_ctx ctx = {0}; + + if (!params || !set_state || !client_cb) { + LOG_ERROR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + ctx.net_idx = params->ctx.net_idx; + ctx.app_idx = params->ctx.app_idx; + ctx.addr = params->ctx.addr; + ctx.send_rel = params->ctx.send_rel; + ctx.send_ttl = params->ctx.send_ttl; + + health_msg_timeout = params->msg_timeout; + + switch (params->opcode) { + case ESP_BLE_MESH_MODEL_OP_ATTENTION_SET: + return (client_cb->error_code = + bt_mesh_health_attention_set(&ctx, set_state->attention_set.attention, true)); + case ESP_BLE_MESH_MODEL_OP_ATTENTION_SET_UNACK: + return (client_cb->error_code = + bt_mesh_health_attention_set(&ctx, set_state->attention_set.attention, false)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET: + return (client_cb->error_code = + bt_mesh_health_period_set(&ctx, set_state->period_set.fast_period_divisor, true)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET_UNACK: + return (client_cb->error_code = + bt_mesh_health_period_set(&ctx, set_state->period_set.fast_period_divisor, false)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST: + return (client_cb->error_code = + bt_mesh_health_fault_test(&ctx, set_state->fault_test.company_id, set_state->fault_test.test_id, true)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST_UNACK: + return (client_cb->error_code = + bt_mesh_health_fault_test(&ctx, set_state->fault_test.company_id, set_state->fault_test.test_id, false)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR: + return (client_cb->error_code = + bt_mesh_health_fault_clear(&ctx, set_state->fault_clear.company_id, true)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR_UNACK: + return (client_cb->error_code = + bt_mesh_health_fault_clear(&ctx, set_state->fault_clear.company_id, false)); + default: + BT_WARN("%s, Invalid opcode 0x%x", __func__, params->opcode); + return (client_cb->error_code = -EINVAL); + } + + return 0; +} + +void bt_mesh_callback_health_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, u16_t len) +{ + esp_ble_mesh_health_client_cb_param_t cb_params = {0}; + esp_ble_mesh_client_common_param_t params = {0}; + size_t length; + uint8_t act; + + if (!model || !ctx) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case 0x00: + act = ESP_BLE_MESH_HEALTH_CLIENT_GET_STATE_EVT; + break; + case 0x01: + act = ESP_BLE_MESH_HEALTH_CLIENT_SET_STATE_EVT; + break; + case 0x02: + act = ESP_BLE_MESH_HEALTH_CLIENT_PUBLISH_EVT; + break; + case 0x03: + act = ESP_BLE_MESH_HEALTH_CLIENT_TIMEOUT_EVT; + break; + default: + LOG_ERROR("%s, Unknown health client event type %d", __func__, evt_type); + return; + } + + params.opcode = opcode; + params.model = (esp_ble_mesh_model_t *)model; + params.ctx.net_idx = ctx->net_idx; + params.ctx.app_idx = ctx->app_idx; + params.ctx.addr = ctx->addr; + params.ctx.recv_ttl = ctx->recv_ttl; + params.ctx.recv_op = ctx->recv_op; + params.ctx.recv_dst = ctx->recv_dst; + + cb_params.error_code = 0; + cb_params.params = ¶ms; + + if (val && len) { + length = (len <= sizeof(cb_params.status_cb)) ? len : sizeof(cb_params.status_cb); + memcpy(&cb_params.status_cb, val, length); + } + + btc_mesh_health_client_callback(&cb_params, act); +} + +void btc_mesh_health_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) +{ + if (!model || !ctx || !buf) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_callback_health_status_to_btc(opcode, 0x02, model, ctx, buf->data, buf->len); +} + +void btc_mesh_health_client_call_handler(btc_msg_t *msg) +{ + btc_ble_mesh_health_client_args_t *arg = NULL; + esp_ble_mesh_health_client_cb_param_t health_client_cb = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_health_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_HEALTH_CLIENT_GET_STATE: { + health_client_cb.params = arg->health_client_get_state.params; + role_param.model = (struct bt_mesh_model *)health_client_cb.params->model; + role_param.role = health_client_cb.params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + btc_ble_mesh_health_client_get_state(arg->health_client_get_state.params, + arg->health_client_get_state.get_state, + &health_client_cb); + if (health_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_health_client_callback(&health_client_cb, ESP_BLE_MESH_HEALTH_CLIENT_GET_STATE_EVT); + } + break; + } + case BTC_BLE_MESH_ACT_HEALTH_CLIENT_SET_STATE: { + health_client_cb.params = arg->health_client_set_state.params; + role_param.model = (struct bt_mesh_model *)health_client_cb.params->model; + role_param.role = health_client_cb.params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + btc_ble_mesh_health_client_set_state(arg->health_client_set_state.params, + arg->health_client_set_state.set_state, + &health_client_cb); + if (health_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_health_client_callback(&health_client_cb, ESP_BLE_MESH_HEALTH_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_health_client_arg_deep_free(msg); + return; +} + +void btc_mesh_health_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_health_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_health_client_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_HEALTH_CLIENT_EVT_MAX) { + btc_ble_mesh_health_client_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_health_client_free_req_data(msg); +} + +void btc_mesh_health_server_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_health_server_cb_param_t health_server_cb = {0}; + btc_ble_mesh_health_server_args_t *arg = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_health_server_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_HEALTH_SERVER_FAULT_UPDATE: { + health_server_cb.error_code = bt_mesh_fault_update((struct bt_mesh_elem *)arg->fault_update.element); + btc_mesh_health_server_callback(&health_server_cb, ESP_BLE_MESH_HEALTH_SERVER_FAULT_UPDATE_COMPLETE_EVT); + } + default: + break; + } + + btc_ble_mesh_health_server_arg_deep_free(msg); +} + +void btc_mesh_health_server_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_health_server_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_health_server_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_HEALTH_SERVER_EVT_MAX) { + btc_ble_mesh_health_server_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_health_server_free_req_data(msg); +} diff --git a/components/bt/ble_mesh/btc/btc_ble_mesh_lighting_model.c b/components/bt/ble_mesh/btc/btc_ble_mesh_lighting_model.c new file mode 100644 index 0000000000..1b89c51713 --- /dev/null +++ b/components/bt/ble_mesh/btc/btc_ble_mesh_lighting_model.c @@ -0,0 +1,381 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "btc/btc_manage.h" +#include "osi/allocator.h" + +#include "lighting_client.h" +#include "btc_ble_mesh_lighting_model.h" +#include "esp_ble_mesh_lighting_model_api.h" + +static inline void btc_ble_mesh_cb_to_app(esp_ble_mesh_light_client_cb_event_t event, + esp_ble_mesh_light_client_cb_param_t *param) +{ + esp_ble_mesh_light_client_cb_t btc_mesh_cb = (esp_ble_mesh_light_client_cb_t)btc_profile_cb_get(BTC_PID_LIGHT_CLIENT); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +void btc_ble_mesh_light_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_light_client_args_t *dst = (btc_ble_mesh_light_client_args_t *)p_dest; + btc_ble_mesh_light_client_args_t *src = (btc_ble_mesh_light_client_args_t *)p_src; + + if (!msg || !dst || !src) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_LIGHT_CLIENT_GET_STATE: { + dst->light_client_get_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->light_client_get_state.get_state = (esp_ble_mesh_light_client_get_state_t *)osi_malloc(sizeof(esp_ble_mesh_light_client_get_state_t)); + if (dst->light_client_get_state.params && dst->light_client_get_state.get_state) { + memcpy(dst->light_client_get_state.params, src->light_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->light_client_get_state.get_state, src->light_client_get_state.get_state, + sizeof(esp_ble_mesh_light_client_get_state_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + case BTC_BLE_MESH_ACT_LIGHT_CLIENT_SET_STATE: { + dst->light_client_set_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->light_client_set_state.set_state = (esp_ble_mesh_light_client_set_state_t *)osi_malloc(sizeof(esp_ble_mesh_light_client_set_state_t)); + if (dst->light_client_set_state.params && dst->light_client_set_state.set_state) { + memcpy(dst->light_client_set_state.params, src->light_client_set_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->light_client_set_state.set_state, src->light_client_set_state.set_state, + sizeof(esp_ble_mesh_light_client_set_state_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + LOG_DEBUG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_light_client_cb_param_t *p_dest_data = (esp_ble_mesh_light_client_cb_param_t *)p_dest; + esp_ble_mesh_light_client_cb_param_t *p_src_data = (esp_ble_mesh_light_client_cb_param_t *)p_src; + u32_t opcode; + u16_t length; + + if (!msg || !p_src_data || !p_dest_data) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_LIGHT_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_LIGHT_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_LIGHT_CLIENT_PUBLISH_EVT: + if (p_src_data->params) { + opcode = p_src_data->params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS: + if (p_src_data->status_cb.lc_property_status.property_value) { + length = p_src_data->status_cb.lc_property_status.property_value->len; + p_dest_data->status_cb.lc_property_status.property_value = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.lc_property_status.property_value) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.lc_property_status.property_value, + p_src_data->status_cb.lc_property_status.property_value->data, + p_src_data->status_cb.lc_property_status.property_value->len); + } + break; + default: + break; + } + } + case ESP_BLE_MESH_LIGHT_CLIENT_TIMEOUT_EVT: + if (p_src_data->params) { + p_dest_data->params = osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (p_dest_data->params) { + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + default: + break; + } +} + +static void btc_ble_mesh_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_light_client_cb_param_t *arg = NULL; + u32_t opcode; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_light_client_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_LIGHT_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_LIGHT_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_LIGHT_CLIENT_PUBLISH_EVT: + if (arg->params) { + opcode = arg->params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS: + bt_mesh_free_buf(arg->status_cb.lc_property_status.property_value); + break; + default: + break; + } + } + case ESP_BLE_MESH_LIGHT_CLIENT_TIMEOUT_EVT: + if (arg->params) { + osi_free(arg->params); + } + break; + default: + break; + } +} + +void btc_ble_mesh_light_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_light_client_args_t *arg = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_light_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_LIGHT_CLIENT_GET_STATE: + if (arg->light_client_get_state.params) { + osi_free(arg->light_client_get_state.params); + } + if (arg->light_client_get_state.get_state) { + osi_free(arg->light_client_get_state.get_state); + } + break; + case BTC_BLE_MESH_ACT_LIGHT_CLIENT_SET_STATE: + if (arg->light_client_set_state.params) { + osi_free(arg->light_client_set_state.params); + } + if (arg->light_client_set_state.set_state) { + osi_free(arg->light_client_set_state.set_state); + } + break; + default: + break; + } + + return; +} + +static void btc_mesh_light_client_callback(esp_ble_mesh_light_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_LIGHT_CLIENT; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_light_client_cb_param_t), btc_ble_mesh_copy_req_data); +} + +void bt_mesh_callback_light_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len) +{ + esp_ble_mesh_light_client_cb_param_t cb_params = {0}; + esp_ble_mesh_client_common_param_t params = {0}; + size_t length; + uint8_t act; + + if (!model || !ctx) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case 0x00: + act = ESP_BLE_MESH_LIGHT_CLIENT_GET_STATE_EVT; + break; + case 0x01: + act = ESP_BLE_MESH_LIGHT_CLIENT_SET_STATE_EVT; + break; + case 0x02: + act = ESP_BLE_MESH_LIGHT_CLIENT_PUBLISH_EVT; + break; + case 0x03: + act = ESP_BLE_MESH_LIGHT_CLIENT_TIMEOUT_EVT; + break; + default: + LOG_ERROR("%s, Unknown lighting client event type", __func__); + return; + } + + params.opcode = opcode; + params.model = (esp_ble_mesh_model_t *)model; + params.ctx.net_idx = ctx->net_idx; + params.ctx.app_idx = ctx->app_idx; + params.ctx.addr = ctx->addr; + params.ctx.recv_ttl = ctx->recv_ttl; + params.ctx.recv_op = ctx->recv_op; + params.ctx.recv_dst = ctx->recv_dst; + + cb_params.error_code = 0; + cb_params.params = ¶ms; + + if (val && len) { + length = (len <= sizeof(cb_params.status_cb)) ? len : sizeof(cb_params.status_cb); + memcpy(&cb_params.status_cb, val, length); + } + + btc_mesh_light_client_callback(&cb_params, act); +} + +void btc_mesh_light_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) +{ + if (!model || !ctx || !buf) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_callback_light_status_to_btc(opcode, 0x02, model, ctx, buf->data, buf->len); +} + +void btc_mesh_light_client_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_light_client_cb_param_t light_client_cb = {0}; + esp_ble_mesh_client_common_param_t *params = NULL; + btc_ble_mesh_light_client_args_t *arg = NULL; + struct bt_mesh_common_param common = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_light_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_LIGHT_CLIENT_GET_STATE: { + params = arg->light_client_get_state.params; + role_param.model = (struct bt_mesh_model *)params->model; + role_param.role = params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + common.opcode = params->opcode; + common.model = (struct bt_mesh_model *)params->model; + common.ctx.net_idx = params->ctx.net_idx; + common.ctx.app_idx = params->ctx.app_idx; + common.ctx.addr = params->ctx.addr; + common.ctx.send_rel = params->ctx.send_rel; + common.ctx.send_ttl = params->ctx.send_ttl; + common.msg_timeout = params->msg_timeout; + + light_client_cb.params = arg->light_client_get_state.params; + light_client_cb.error_code = + bt_mesh_light_client_get_state(&common, + (void *)arg->light_client_get_state.get_state, + (void *)&light_client_cb.status_cb); + if (light_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_light_client_callback(&light_client_cb, + ESP_BLE_MESH_LIGHT_CLIENT_GET_STATE_EVT); + } + break; + } + case BTC_BLE_MESH_ACT_LIGHT_CLIENT_SET_STATE: { + params = arg->light_client_set_state.params; + role_param.model = (struct bt_mesh_model *)params->model; + role_param.role = params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + common.opcode = params->opcode; + common.model = (struct bt_mesh_model *)params->model; + common.ctx.net_idx = params->ctx.net_idx; + common.ctx.app_idx = params->ctx.app_idx; + common.ctx.addr = params->ctx.addr; + common.ctx.send_rel = params->ctx.send_rel; + common.ctx.send_ttl = params->ctx.send_ttl; + common.msg_timeout = params->msg_timeout; + + light_client_cb.params = arg->light_client_set_state.params; + light_client_cb.error_code = + bt_mesh_light_client_set_state(&common, + (void *)arg->light_client_set_state.set_state, + (void *)&light_client_cb.status_cb); + if (light_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_light_client_callback(&light_client_cb, + ESP_BLE_MESH_LIGHT_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_light_client_arg_deep_free(msg); +} + +void btc_mesh_light_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_light_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_light_client_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_LIGHT_CLIENT_EVT_MAX) { + btc_ble_mesh_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_free_req_data(msg); +} + diff --git a/components/bt/ble_mesh/btc/btc_ble_mesh_prov.c b/components/bt/ble_mesh/btc/btc_ble_mesh_prov.c new file mode 100644 index 0000000000..d53b0d8cb4 --- /dev/null +++ b/components/bt/ble_mesh/btc/btc_ble_mesh_prov.c @@ -0,0 +1,1528 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" + +#include "btc/btc_manage.h" +#include "osi/allocator.h" + +#include "sdkconfig.h" + +#include "mesh_util.h" +#include "mesh_main.h" +#include "mesh_access.h" +#include "mesh_proxy.h" +#include "cfg_cli.h" +#include "health_cli.h" + +#include "mesh.h" +#include "access.h" +#include "transport.h" +#include "proxy.h" +#include "prov.h" +#include "provisioner_prov.h" +#include "provisioner_main.h" + +#include "generic_client.h" +#include "lighting_client.h" +#include "sensor_client.h" +#include "time_scene_client.h" +#include "model_common.h" + +#include "btc_ble_mesh_prov.h" +#include "btc_ble_mesh_config_model.h" +#include "btc_ble_mesh_health_model.h" +#include "btc_ble_mesh_generic_model.h" +#include "btc_ble_mesh_time_scene_model.h" +#include "btc_ble_mesh_sensor_model.h" +#include "btc_ble_mesh_lighting_model.h" + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_common_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_networking_api.h" + +#define BLE_MESH_MAX_DATA_SIZE 379 + +void btc_ble_mesh_prov_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_model_args_t *dst = (btc_ble_mesh_model_args_t *)p_dest; + btc_ble_mesh_model_args_t *src = (btc_ble_mesh_model_args_t *)p_src; + + if (!msg || !dst || !src) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_SERVER_MODEL_SEND: + case BTC_BLE_MESH_ACT_CLIENT_MODEL_SEND: { + LOG_DEBUG("%s, BTC_BLE_MESH_ACT_MODEL_SEND, src->model_send.length = %d", __func__, src->model_send.length); + dst->model_send.data = src->model_send.length ? (uint8_t *)osi_malloc(src->model_send.length) : NULL; + dst->model_send.ctx = osi_malloc(sizeof(esp_ble_mesh_msg_ctx_t)); + if (src->model_send.length) { + if (dst->model_send.data) { + memcpy(dst->model_send.data, src->model_send.data, src->model_send.length); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + if (dst->model_send.ctx) { + memcpy(dst->model_send.ctx, src->model_send.ctx, sizeof(esp_ble_mesh_msg_ctx_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + LOG_DEBUG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +void btc_ble_mesh_prov_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_model_args_t *arg = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_model_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_SERVER_MODEL_SEND: + case BTC_BLE_MESH_ACT_CLIENT_MODEL_SEND: + if (arg->model_send.data) { + osi_free(arg->model_send.data); + } + if (arg->model_send.ctx) { + osi_free(arg->model_send.ctx); + } + break; + default: + break; + } + + return; +} + +static void btc_ble_mesh_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_model_cb_param_t *p_dest_data = (esp_ble_mesh_model_cb_param_t *)p_dest; + esp_ble_mesh_model_cb_param_t *p_src_data = (esp_ble_mesh_model_cb_param_t *)p_src; + + if (!msg || !p_src_data || !p_dest_data) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_MODEL_OPERATION_EVT: { + if (p_src_data->model_operation.ctx && p_src_data->model_operation.msg) { + p_dest_data->model_operation.ctx = osi_malloc(sizeof(esp_ble_mesh_msg_ctx_t)); + p_dest_data->model_operation.msg = p_src_data->model_operation.length ? (uint8_t *)osi_malloc(p_src_data->model_operation.length) : NULL; + if (p_dest_data->model_operation.ctx) { + memcpy(p_dest_data->model_operation.ctx, p_src_data->model_operation.ctx, sizeof(esp_ble_mesh_msg_ctx_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + if (p_src_data->model_operation.length) { + if (p_dest_data->model_operation.msg) { + memcpy(p_dest_data->model_operation.msg, p_src_data->model_operation.msg, p_src_data->model_operation.length); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + } + break; + } + case ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT: { + if (p_src_data->client_recv_publish_msg.ctx && p_src_data->client_recv_publish_msg.msg) { + p_dest_data->client_recv_publish_msg.ctx = osi_malloc(sizeof(esp_ble_mesh_msg_ctx_t)); + p_dest_data->client_recv_publish_msg.msg = p_src_data->client_recv_publish_msg.length ? (uint8_t *)osi_malloc(p_src_data->client_recv_publish_msg.length) : NULL; + if (p_dest_data->client_recv_publish_msg.ctx) { + memcpy(p_dest_data->client_recv_publish_msg.ctx, p_src_data->client_recv_publish_msg.ctx, sizeof(esp_ble_mesh_msg_ctx_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + if (p_src_data->client_recv_publish_msg.length) { + if (p_dest_data->client_recv_publish_msg.msg) { + memcpy(p_dest_data->client_recv_publish_msg.msg, p_src_data->client_recv_publish_msg.msg, p_src_data->client_recv_publish_msg.length); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + } + break; + } + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: { + if (p_src_data->model_send_comp.ctx) { + p_dest_data->model_send_comp.ctx = osi_malloc(sizeof(esp_ble_mesh_msg_ctx_t)); + if (p_dest_data->model_send_comp.ctx) { + memcpy(p_dest_data->model_send_comp.ctx, p_src_data->model_send_comp.ctx, sizeof(esp_ble_mesh_msg_ctx_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + } + case ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT: { + if (p_src_data->client_send_timeout.ctx) { + p_dest_data->client_send_timeout.ctx = osi_malloc(sizeof(esp_ble_mesh_msg_ctx_t)); + if (p_dest_data->client_send_timeout.ctx) { + memcpy(p_dest_data->client_send_timeout.ctx, p_src_data->client_send_timeout.ctx, sizeof(esp_ble_mesh_msg_ctx_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + } + default: + break; + } +} + +static void btc_ble_mesh_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_model_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_model_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_MODEL_OPERATION_EVT: { + if (arg->model_operation.msg) { + osi_free(arg->model_operation.msg); + } + if (arg->model_operation.ctx) { + osi_free(arg->model_operation.ctx); + } + break; + } + case ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT: { + if (arg->client_recv_publish_msg.msg) { + osi_free(arg->client_recv_publish_msg.msg); + } + if (arg->client_recv_publish_msg.ctx) { + osi_free(arg->client_recv_publish_msg.ctx); + } + break; + } + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: { + if (arg->model_send_comp.ctx) { + osi_free(arg->model_send_comp.ctx); + } + break; + } + case ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT: { + if (arg->client_send_timeout.ctx) { + osi_free(arg->client_send_timeout.ctx); + } + break; + } + default: + break; + } +} + +static inline void btc_ble_mesh_cb_to_app(esp_ble_mesh_prov_cb_event_t event, + esp_ble_mesh_prov_cb_param_t *param) +{ + esp_ble_mesh_prov_cb_t btc_mesh_cb = (esp_ble_mesh_prov_cb_t)btc_profile_cb_get(BTC_PID_PROV); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +static inline void btc_ble_mesh_model_cb_to_app(esp_ble_mesh_model_cb_event_t event, + esp_ble_mesh_model_cb_param_t *param) +{ + esp_ble_mesh_model_cb_t btc_mesh_cb = (esp_ble_mesh_model_cb_t)btc_profile_cb_get(BTC_PID_MODEL); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +extern u32_t mesh_opcode; + +static void btc_ble_mesh_server_model_op_cb(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + esp_ble_mesh_model_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + mesh_param.model_operation.opcode = mesh_opcode; + mesh_param.model_operation.model = (esp_ble_mesh_model_t *)model; + mesh_param.model_operation.ctx = (esp_ble_mesh_msg_ctx_t *)ctx; + mesh_param.model_operation.length = buf->len; + mesh_param.model_operation.msg = buf->data; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_MODEL; + msg.act = ESP_BLE_MESH_MODEL_OPERATION_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_model_cb_param_t), btc_ble_mesh_copy_req_data); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static void btc_ble_mesh_client_model_op_cb(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + esp_ble_mesh_model_cb_param_t mesh_param = {0}; + bt_mesh_client_common_t *client_param = NULL; + bt_mesh_internal_data_t *data = NULL; + bt_mesh_client_node_t *node = NULL; + btc_msg_t msg = {0}; + bt_status_t ret; + + if (!model || !model->user_data || !ctx || !buf) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + client_param = (bt_mesh_client_common_t *)model->user_data; + data = (bt_mesh_internal_data_t *)client_param->internal_data; + if (!data) { + LOG_ERROR("%s, Client internal_data is NULL", __func__); + return; + } + + node = bt_mesh_is_model_message_publish(model, ctx, buf, false); + if (node == NULL) { + msg.act = ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT; + mesh_param.client_recv_publish_msg.opcode = mesh_opcode; + mesh_param.client_recv_publish_msg.model = (esp_ble_mesh_model_t *)model; + mesh_param.client_recv_publish_msg.ctx = (esp_ble_mesh_msg_ctx_t *)ctx; + mesh_param.client_recv_publish_msg.length = buf->len; + mesh_param.client_recv_publish_msg.msg = buf->data; + } else { + msg.act = ESP_BLE_MESH_MODEL_OPERATION_EVT; + mesh_param.model_operation.opcode = mesh_opcode; + mesh_param.model_operation.model = (esp_ble_mesh_model_t *)model; + mesh_param.model_operation.ctx = (esp_ble_mesh_msg_ctx_t *)ctx; + mesh_param.model_operation.length = buf->len; + mesh_param.model_operation.msg = buf->data; + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_MODEL; + if (msg.act != ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT) { + // Don't forget to release the node at the end. + bt_mesh_client_free_node(&data->queue, node); + } + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_model_cb_param_t), btc_ble_mesh_copy_req_data); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s, btc_transfer_context failed", __func__); + } + return; +} + +static void btc_ble_mesh_model_send_comp_cb(esp_ble_mesh_model_t *model, esp_ble_mesh_msg_ctx_t *ctx, u32_t opcode, int err) +{ + esp_ble_mesh_model_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + mesh_param.model_send_comp.err_code = err; + mesh_param.model_send_comp.opcode = opcode; + mesh_param.model_send_comp.model = model; + mesh_param.model_send_comp.ctx = ctx; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_MODEL; + msg.act = ESP_BLE_MESH_MODEL_SEND_COMP_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_model_cb_param_t), btc_ble_mesh_copy_req_data); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static void btc_ble_mesh_model_publish_comp_cb(esp_ble_mesh_model_t *model, int err) +{ + esp_ble_mesh_model_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + mesh_param.model_publish_comp.err_code = err; + mesh_param.model_publish_comp.model = model; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_MODEL; + msg.act = ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_model_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +#if defined(CONFIG_BLE_MESH_NODE) +static void btc_oob_pub_key_cb(void) +{ + btc_msg_t msg = {0}; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_NODE_PROV_OOB_PUB_KEY_EVT; + + if (btc_transfer_context(&msg, NULL, 0, NULL) != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static int btc_output_number_cb(bt_mesh_output_action_t act, u32_t num) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.node_prov_output_num.action = (esp_ble_mesh_output_action_t)act; + mesh_param.node_prov_output_num.number = num; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + return -1; + } + return 0; +} + +static int btc_output_string_cb(const char *str) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + strncpy(mesh_param.node_prov_output_str.string, str, strlen(str)); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + return -1; + } + return 0; +} + +static int btc_input_cb(bt_mesh_input_action_t act, u8_t size) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.node_prov_input.action = (esp_ble_mesh_input_action_t)act; + mesh_param.node_prov_input.size = size; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_NODE_PROV_INPUT_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + return -1; + } + return 0; +} + +static void btc_link_open_cb(bt_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.node_prov_link_open.bearer = (esp_ble_mesh_prov_bearer_t)bearer; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static void btc_link_close_cb(bt_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.node_prov_link_close.bearer = (esp_ble_mesh_prov_bearer_t)bearer; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static void btc_complete_cb(u16_t net_idx, u16_t addr, u8_t flags, u32_t iv_index) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.node_prov_complete.net_idx = net_idx; + mesh_param.node_prov_complete.addr = addr; + mesh_param.node_prov_complete.flags = flags; + mesh_param.node_prov_complete.iv_index = iv_index; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static void btc_reset_cb(void) +{ + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_NODE_PROV_RESET_EVT; + ret = btc_transfer_context(&msg, NULL, 0, NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} +#endif /* defined(CONFIG_BLE_MESH_NODE) */ + +static void btc_prov_register_complete_cb(int err_code) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.prov_register_comp.err_code = err_code; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_PROV_REGISTER_COMP_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static void btc_client_model_timeout_cb(struct k_work *work) +{ + esp_ble_mesh_model_cb_param_t mesh_param = {0}; + bt_mesh_client_common_t *client_param = NULL; + bt_mesh_internal_data_t *data = NULL; + bt_mesh_client_node_t *node = NULL; + btc_msg_t msg = {0}; + bt_status_t ret; + + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (!node || !node->ctx.model || !node->ctx.model->user_data) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + client_param = (bt_mesh_client_common_t *)node->ctx.model->user_data; + data = (bt_mesh_internal_data_t *)client_param->internal_data; + if (!data) { + LOG_ERROR("%s, Client internal_data is NULL", __func__); + return; + } + + mesh_param.client_send_timeout.opcode = node->opcode; + mesh_param.client_send_timeout.model = (esp_ble_mesh_model_t *)node->ctx.model; + mesh_param.client_send_timeout.ctx = (esp_ble_mesh_msg_ctx_t *)&node->ctx; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_MODEL; + msg.act = ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_model_cb_param_t), btc_ble_mesh_copy_req_data); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + // Don't forget to release the node at the end. + bt_mesh_client_free_node(&data->queue, node); + return; +} + +static int btc_model_publish_update(struct bt_mesh_model *mod) +{ + esp_ble_mesh_model_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.model_publish_update.model = (esp_ble_mesh_model_t *)mod; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_MODEL; + msg.act = ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_model_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + return -1; + } + return 0; +} + +static void btc_prov_set_complete_cb(esp_ble_mesh_prov_cb_param_t *param, uint8_t act) +{ + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = act; + ret = btc_transfer_context(&msg, param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +#if (CONFIG_BLE_MESH_PROVISIONER) +static void btc_provisioner_recv_unprov_adv_pkt_cb(const u8_t addr[6], const u8_t addr_type, + const u8_t adv_type, const u8_t dev_uuid[16], + u16_t oob_info, bt_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + if (addr == NULL || dev_uuid == NULL || + (bearer != BLE_MESH_PROV_ADV && bearer != BLE_MESH_PROV_GATT)) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + memcpy(mesh_param.provisioner_recv_unprov_adv_pkt.dev_uuid, dev_uuid, 16); + memcpy(mesh_param.provisioner_recv_unprov_adv_pkt.addr, addr, ESP_BD_ADDR_LEN); + mesh_param.provisioner_recv_unprov_adv_pkt.addr_type = addr_type; + mesh_param.provisioner_recv_unprov_adv_pkt.oob_info = oob_info; + mesh_param.provisioner_recv_unprov_adv_pkt.adv_type = adv_type; + mesh_param.provisioner_recv_unprov_adv_pkt.bearer = bearer; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static int btc_provisioner_prov_read_oob_pub_key_cb(u8_t link_idx) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.provisioner_prov_read_oob_pub_key.link_idx = link_idx; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_PROVISIONER_PROV_READ_OOB_PUB_KEY_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + return -1; + } + return 0; +} + +static int btc_provisioner_prov_input_cb(u8_t method, bt_mesh_output_action_t act, u8_t size, u8_t link_idx) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.provisioner_prov_input.method = (esp_ble_mesh_oob_method_t)method; + mesh_param.provisioner_prov_input.action = (esp_ble_mesh_output_action_t)act; + mesh_param.provisioner_prov_input.size = size; + mesh_param.provisioner_prov_input.link_idx = link_idx; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_PROVISIONER_PROV_INPUT_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + return -1; + } + return 0; +} + +static int btc_provisioner_prov_output_cb(u8_t method, bt_mesh_input_action_t act, void *data, u8_t size, u8_t link_idx) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.provisioner_prov_output.method = (esp_ble_mesh_oob_method_t)method; + mesh_param.provisioner_prov_output.action = (esp_ble_mesh_input_action_t)act; + mesh_param.provisioner_prov_output.size = size; + mesh_param.provisioner_prov_output.link_idx = link_idx; + if (act == BLE_MESH_ENTER_STRING) { + strncpy(mesh_param.provisioner_prov_output.string, (char *)data, size); + } else { + mesh_param.provisioner_prov_output.number = sys_get_le32((u8_t *)data); + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_PROVISIONER_PROV_OUTPUT_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + return -1; + } + return 0; +} + +static void btc_provisioner_link_open_cb(bt_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.provisioner_prov_link_open.bearer = (esp_ble_mesh_prov_bearer_t)bearer; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static void btc_provisioner_link_close_cb(bt_mesh_prov_bearer_t bearer, u8_t reason) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.provisioner_prov_link_close.bearer = (esp_ble_mesh_prov_bearer_t)bearer; + mesh_param.provisioner_prov_link_close.reason = reason; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} + +static void btc_provisioner_prov_complete_cb(int node_idx, const u8_t device_uuid[16], + u16_t unicast_addr, u8_t element_num, + u16_t netkey_idx) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + btc_msg_t msg = {0}; + bt_status_t ret; + + LOG_DEBUG("%s", __func__); + + mesh_param.provisioner_prov_complete.node_idx = node_idx; + mesh_param.provisioner_prov_complete.unicast_addr = unicast_addr; + mesh_param.provisioner_prov_complete.element_num = element_num; + mesh_param.provisioner_prov_complete.netkey_idx = netkey_idx; + memcpy(mesh_param.provisioner_prov_complete.device_uuid, device_uuid, sizeof(esp_ble_mesh_octet16_t)); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_PROV; + msg.act = ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT; + ret = btc_transfer_context(&msg, &mesh_param, + sizeof(esp_ble_mesh_prov_cb_param_t), NULL); + + if (ret != BT_STATUS_SUCCESS) { + LOG_ERROR("%s btc_transfer_context failed", __func__); + } + return; +} +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +int btc_ble_mesh_client_init(esp_ble_mesh_model_t *model) +{ + __ASSERT(model && model->op, "%s, Invalid parameter", __func__); + esp_ble_mesh_model_op_t *op = model->op; + while (op != NULL && op->opcode != 0) { + op->param_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_client_model_op_cb; + op++; + } + return bt_mesh_client_init((struct bt_mesh_model *)model); +} + +int32_t btc_ble_mesh_model_pub_period_get(esp_ble_mesh_model_t *mod) +{ + return bt_mesh_model_pub_period_get((struct bt_mesh_model *)mod); +} + +uint16_t btc_ble_mesh_get_primary_addr(void) +{ + return bt_mesh_primary_addr(); +} + +uint16_t *btc_ble_mesh_model_find_group(esp_ble_mesh_model_t *mod, uint16_t addr) +{ + return bt_mesh_model_find_group((struct bt_mesh_model *)mod, addr); +} + +esp_ble_mesh_elem_t *btc_ble_mesh_elem_find(u16_t addr) +{ + return (esp_ble_mesh_elem_t *)bt_mesh_elem_find(addr); +} + +uint8_t btc_ble_mesh_elem_count(void) +{ + return bt_mesh_elem_count(); +} + +esp_ble_mesh_model_t *btc_ble_mesh_model_find_vnd(const esp_ble_mesh_elem_t *elem, + uint16_t company, uint16_t id) +{ + return (esp_ble_mesh_model_t *)bt_mesh_model_find_vnd((struct bt_mesh_elem *)elem, company, id); +} + +esp_ble_mesh_model_t *btc_ble_mesh_model_find(const esp_ble_mesh_elem_t *elem, uint16_t id) +{ + return (esp_ble_mesh_model_t *)bt_mesh_model_find((struct bt_mesh_elem *)elem, id); +} + +const esp_ble_mesh_comp_t *btc_ble_mesh_comp_get(void) +{ + return (const esp_ble_mesh_comp_t *)bt_mesh_comp_get(); +} + +/* Configuration Models */ +extern const struct bt_mesh_model_op bt_mesh_cfg_srv_op[]; +extern const struct bt_mesh_model_op bt_mesh_cfg_cli_op[]; +/* Health Models */ +extern const struct bt_mesh_model_op bt_mesh_health_srv_op[]; +extern const struct bt_mesh_model_op bt_mesh_health_cli_op[]; +/* Generic Client Models */ +extern const struct bt_mesh_model_op gen_onoff_cli_op[]; +extern const struct bt_mesh_model_op gen_level_cli_op[]; +extern const struct bt_mesh_model_op gen_def_trans_time_cli_op[]; +extern const struct bt_mesh_model_op gen_power_onoff_cli_op[]; +extern const struct bt_mesh_model_op gen_power_level_cli_op[]; +extern const struct bt_mesh_model_op gen_battery_cli_op[]; +extern const struct bt_mesh_model_op gen_location_cli_op[]; +extern const struct bt_mesh_model_op gen_property_cli_op[]; +/* Lighting Client Models */ +extern const struct bt_mesh_model_op light_lightness_cli_op[]; +extern const struct bt_mesh_model_op light_ctl_cli_op[]; +extern const struct bt_mesh_model_op light_hsl_cli_op[]; +extern const struct bt_mesh_model_op light_xyl_cli_op[]; +extern const struct bt_mesh_model_op light_lc_cli_op[]; +/* Sensor Client Models */ +extern const struct bt_mesh_model_op sensor_cli_op[]; +/* Time and Scenes Client Models */ +extern const struct bt_mesh_model_op time_cli_op[]; +extern const struct bt_mesh_model_op scene_cli_op[]; +extern const struct bt_mesh_model_op scheduler_cli_op[]; + +static void btc_mesh_model_op_add(esp_ble_mesh_model_t *model) +{ + esp_ble_mesh_model_op_t *op = NULL; + + if (!model) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + /* 1. For SIG client models, model->op will be NULL and initialized here. + * 2. The vendor model opcode is 3 bytes. + */ + if ((model->op != NULL) && (model->op->opcode >= 0x10000)) { + goto add_model_op; + } + + switch (model->model_id) { + case BLE_MESH_MODEL_ID_CFG_SRV: { + model->op = (esp_ble_mesh_model_op_t *)bt_mesh_cfg_srv_op; + break; + } + case BLE_MESH_MODEL_ID_CFG_CLI: { + model->op = (esp_ble_mesh_model_op_t *)bt_mesh_cfg_cli_op; + bt_mesh_config_client_t *cli = (bt_mesh_config_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_cfg_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_HEALTH_SRV: { + model->op = (esp_ble_mesh_model_op_t *)bt_mesh_health_srv_op; + break; + } + case BLE_MESH_MODEL_ID_HEALTH_CLI: { + model->op = (esp_ble_mesh_model_op_t *)bt_mesh_health_cli_op; + bt_mesh_health_client_t *cli = (bt_mesh_health_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_health_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_ONOFF_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)gen_onoff_cli_op); + bt_mesh_gen_onoff_cli_t *cli = (bt_mesh_gen_onoff_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_generic_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_LEVEL_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)gen_level_cli_op); + bt_mesh_gen_level_cli_t *cli = (bt_mesh_gen_level_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_generic_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)gen_def_trans_time_cli_op); + bt_mesh_gen_def_trans_time_cli_t *cli = (bt_mesh_gen_def_trans_time_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_generic_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)gen_power_onoff_cli_op); + bt_mesh_gen_power_onoff_cli_t *cli = (bt_mesh_gen_power_onoff_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_generic_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)gen_power_level_cli_op); + bt_mesh_gen_power_level_cli_t *cli = (bt_mesh_gen_power_level_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_generic_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_BATTERY_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)gen_battery_cli_op); + bt_mesh_gen_battery_cli_t *cli = (bt_mesh_gen_battery_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_generic_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_LOCATION_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)gen_location_cli_op); + bt_mesh_gen_location_cli_t *cli = (bt_mesh_gen_location_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_generic_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_PROP_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)gen_property_cli_op); + bt_mesh_gen_property_cli_t *cli = (bt_mesh_gen_property_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_generic_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)light_lightness_cli_op); + bt_mesh_light_lightness_cli_t *cli = (bt_mesh_light_lightness_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_light_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_CTL_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)light_ctl_cli_op); + bt_mesh_light_ctl_cli_t *cli = (bt_mesh_light_ctl_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_light_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)light_hsl_cli_op); + bt_mesh_light_hsl_cli_t *cli = (bt_mesh_light_hsl_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_light_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_XYL_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)light_xyl_cli_op); + bt_mesh_light_xyl_cli_t *cli = (bt_mesh_light_xyl_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_light_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_LC_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)light_lc_cli_op); + bt_mesh_light_lc_cli_t *cli = (bt_mesh_light_lc_cli_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_light_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_SENSOR_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)sensor_cli_op); + bt_mesh_sensor_client_t *cli = (bt_mesh_sensor_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_sensor_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_TIME_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)time_cli_op); + bt_mesh_time_scene_client_t *cli = (bt_mesh_time_scene_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_time_scene_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_SCENE_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)scene_cli_op); + bt_mesh_time_scene_client_t *cli = (bt_mesh_time_scene_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_time_scene_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_SCHEDULER_CLI: { + model->op = ((esp_ble_mesh_model_op_t *)scheduler_cli_op); + bt_mesh_time_scene_client_t *cli = (bt_mesh_time_scene_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_mesh_time_scene_client_publish_callback; + } + break; + } + default: { + goto add_model_op; + } + } + return; + +add_model_op: + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_model_publish_update; + } + op = model->op; + while (op != NULL && op->opcode != 0) { + op->param_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_server_model_op_cb; + op++; + } + return; +} + +void btc_mesh_prov_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_prov_cb_param_t param = {0}; + btc_ble_mesh_prov_args_t *arg = NULL; + uint8_t act; + + if (!msg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + arg = (btc_ble_mesh_prov_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_MESH_INIT: { + int err_code; + for (int i = 0; i < arg->mesh_init.comp->element_count; i++) { + esp_ble_mesh_elem_t *elem = &arg->mesh_init.comp->elements[i]; + /* For SIG models */ + for (int j = 0; j < elem->sig_model_count; j++) { + esp_ble_mesh_model_t *sig_model = &elem->sig_models[j]; + /* The opcode of sig model should be 1 or 2 bytes. */ + if (sig_model && sig_model->op && (sig_model->op->opcode >= 0x10000)) { + err_code = -EINVAL; + btc_prov_register_complete_cb(err_code); + return; + } + btc_mesh_model_op_add(sig_model); + } + /* For vendor models */ + for (int k = 0; k < elem->vnd_model_count; k++) { + esp_ble_mesh_model_t *vnd_model = &elem->vnd_models[k]; + /* The opcode of vendor model should be 3 bytes. */ + if (vnd_model && vnd_model->op && vnd_model->op->opcode < 0x10000) { + err_code = -EINVAL; + btc_prov_register_complete_cb(err_code); + return; + } + btc_mesh_model_op_add(vnd_model); + } + } +#if CONFIG_BLE_MESH_NODE + arg->mesh_init.prov->oob_pub_key_cb = (esp_ble_mesh_cb_t)btc_oob_pub_key_cb; + arg->mesh_init.prov->output_num_cb = (esp_ble_mesh_cb_t)btc_output_number_cb; + arg->mesh_init.prov->output_str_cb = (esp_ble_mesh_cb_t)btc_output_string_cb; + arg->mesh_init.prov->input_cb = (esp_ble_mesh_cb_t)btc_input_cb; + arg->mesh_init.prov->link_open_cb = (esp_ble_mesh_cb_t)btc_link_open_cb; + arg->mesh_init.prov->link_close_cb = (esp_ble_mesh_cb_t)btc_link_close_cb; + arg->mesh_init.prov->complete_cb = (esp_ble_mesh_cb_t)btc_complete_cb; + arg->mesh_init.prov->reset_cb = (esp_ble_mesh_cb_t)btc_reset_cb; +#endif /* CONFIG_BLE_MESH_NODE */ +#if (CONFIG_BLE_MESH_PROVISIONER) + arg->mesh_init.prov->provisioner_prov_read_oob_pub_key = (esp_ble_mesh_cb_t)btc_provisioner_prov_read_oob_pub_key_cb; + arg->mesh_init.prov->provisioner_prov_input = (esp_ble_mesh_cb_t)btc_provisioner_prov_input_cb; + arg->mesh_init.prov->provisioner_prov_output = (esp_ble_mesh_cb_t)btc_provisioner_prov_output_cb; + arg->mesh_init.prov->provisioner_link_open = (esp_ble_mesh_cb_t)btc_provisioner_link_open_cb; + arg->mesh_init.prov->provisioner_link_close = (esp_ble_mesh_cb_t)btc_provisioner_link_close_cb; + arg->mesh_init.prov->provisioner_prov_comp = (esp_ble_mesh_cb_t)btc_provisioner_prov_complete_cb; + bt_mesh_prov_adv_pkt_cb_register(btc_provisioner_recv_unprov_adv_pkt_cb); +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + err_code = bt_mesh_init((struct bt_mesh_prov *)arg->mesh_init.prov, + (struct bt_mesh_comp *)arg->mesh_init.comp); + /* Give the semaphore when BLE Mesh initialization is finished. */ + xSemaphoreGive(arg->mesh_init.semaphore); + btc_prov_register_complete_cb(err_code); + return; + } +#if CONFIG_BLE_MESH_NODE + case BTC_BLE_MESH_ACT_PROV_ENABLE: + LOG_DEBUG("%s, BTC_BLE_MESH_ACT_PROV_ENABLE, bearers = %d", __func__, arg->node_prov_enable.bearers); + act = ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT; + param.node_prov_enable_comp.err_code = bt_mesh_prov_enable(arg->node_prov_enable.bearers); + break; + case BTC_BLE_MESH_ACT_PROV_DISABLE: + LOG_DEBUG("%s, BTC_BLE_MESH_ACT_PROV_DISABLE, bearers = %d", __func__, arg->node_prov_disable.bearers); + act = ESP_BLE_MESH_NODE_PROV_DISABLE_COMP_EVT; + param.node_prov_disable_comp.err_code = bt_mesh_prov_disable(arg->node_prov_disable.bearers); + break; + case BTC_BLE_MESH_ACT_NODE_RESET: + LOG_DEBUG("%s, BTC_BLE_MESH_ACT_NODE_RESET", __func__); + bt_mesh_reset(); + return; + case BTC_BLE_MESH_ACT_SET_OOB_PUB_KEY: + act = ESP_BLE_MESH_NODE_PROV_SET_OOB_PUB_KEY_COMP_EVT; + param.node_prov_set_oob_pub_key_comp.err_code = + bt_mesh_set_oob_pub_key(arg->set_oob_pub_key.pub_key_x, + arg->set_oob_pub_key.pub_key_y, + arg->set_oob_pub_key.private_key); + break; + case BTC_BLE_MESH_ACT_INPUT_NUMBER: + act = ESP_BLE_MESH_NODE_PROV_INPUT_NUMBER_COMP_EVT; + param.node_prov_input_num_comp.err_code = bt_mesh_input_number(arg->input_number.number); + break; + case BTC_BLE_MESH_ACT_INPUT_STRING: + act = ESP_BLE_MESH_NODE_PROV_INPUT_STRING_COMP_EVT; + param.node_prov_input_str_comp.err_code = bt_mesh_input_string(arg->input_string.string); + break; + case BTC_BLE_MESH_ACT_SET_DEVICE_NAME: + act = ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT; + param.node_set_unprov_dev_name_comp.err_code = bt_mesh_set_device_name(arg->set_device_name.name); + break; +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + case BTC_BLE_MESH_ACT_PROXY_IDENTITY_ENABLE: + act = ESP_BLE_MESH_NODE_PROXY_IDENTITY_ENABLE_COMP_EVT; + param.node_proxy_identity_enable_comp.err_code = bt_mesh_proxy_identity_enable(); + break; + case BTC_BLE_MESH_ACT_PROXY_GATT_ENABLE: + act = ESP_BLE_MESH_NODE_PROXY_GATT_ENABLE_COMP_EVT; + param.node_proxy_gatt_enable_comp.err_code = bt_mesh_proxy_gatt_enable(); + break; + case BTC_BLE_MESH_ACT_PROXY_GATT_DISABLE: + act = ESP_BLE_MESH_NODE_PROXY_GATT_DISABLE_COMP_EVT; + param.node_proxy_gatt_disable_comp.err_code = bt_mesh_proxy_gatt_disable(); + break; +#endif /* CONFIG_BLE_MESH_GATT_PROXY */ +#endif /* CONFIG_BLE_MESH_NODE */ +#if (CONFIG_BLE_MESH_PROVISIONER) + case BTC_BLE_MESH_ACT_PROVISIONER_READ_OOB_PUB_KEY: + act = ESP_BLE_MESH_PROVISIONER_PROV_READ_OOB_PUB_KEY_COMP_EVT; + param.provisioner_prov_read_oob_pub_key_comp.err_code = + bt_mesh_prov_read_oob_pub_key(arg->provisioner_read_oob_pub_key.link_idx, + arg->provisioner_read_oob_pub_key.pub_key_x, + arg->provisioner_read_oob_pub_key.pub_key_y); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_INPUT_STR: + act = ESP_BLE_MESH_PROVISIONER_PROV_INPUT_STRING_COMP_EVT; + param.provisioner_prov_input_str_comp.err_code = + bt_mesh_prov_set_oob_input_data(arg->provisioner_input_str.link_idx, + (const u8_t *)&arg->provisioner_input_str.string, false); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_INPUT_NUM: + act = ESP_BLE_MESH_PROVISIONER_PROV_INPUT_NUMBER_COMP_EVT; + param.provisioner_prov_input_num_comp.err_code = + bt_mesh_prov_set_oob_input_data(arg->provisioner_input_num.link_idx, + (const u8_t *)&arg->provisioner_input_num.number, true); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_ENABLE: + act = ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT; + param.provisioner_prov_enable_comp.err_code = + bt_mesh_provisioner_enable(arg->provisioner_enable.bearers); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_DISABLE: + act = ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT; + param.provisioner_prov_disable_comp.err_code = + bt_mesh_provisioner_disable(arg->provisioner_disable.bearers); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_DEV_ADD: { + struct bt_mesh_unprov_dev_add add_dev = {0}; + add_dev.addr_type = arg->provisioner_dev_add.add_dev.addr_type; + add_dev.oob_info = arg->provisioner_dev_add.add_dev.oob_info; + add_dev.bearer = arg->provisioner_dev_add.add_dev.bearer; + memcpy(add_dev.addr, arg->provisioner_dev_add.add_dev.addr, 6); + memcpy(add_dev.uuid, arg->provisioner_dev_add.add_dev.uuid, 16); + act = ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT; + param.provisioner_add_unprov_dev_comp.err_code = + bt_mesh_provisioner_add_unprov_dev(&add_dev, arg->provisioner_dev_add.flags); + break; + } + case BTC_BLE_MESH_ACT_PROVISIONER_DEV_DEL: { + struct bt_mesh_device_delete del_dev = {0}; + if (arg->provisioner_dev_del.del_dev.flag & DEL_DEV_ADDR_FLAG) { + del_dev.addr_type = arg->provisioner_dev_del.del_dev.addr_type; + memcpy(del_dev.addr, arg->provisioner_dev_del.del_dev.addr, 6); + } else if (arg->provisioner_dev_del.del_dev.flag & DEL_DEV_UUID_FLAG) { + memcpy(del_dev.uuid, arg->provisioner_dev_del.del_dev.uuid, 16); + } + act = ESP_BLE_MESH_PROVISIONER_DELETE_DEV_COMP_EVT; + param.provisioner_delete_dev_comp.err_code = bt_mesh_provisioner_delete_device(&del_dev); + break; + } + case BTC_BLE_MESH_ACT_PROVISIONER_SET_DEV_UUID_MATCH: + act = ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT; + param.provisioner_set_dev_uuid_match_comp.err_code = + bt_mesh_provisioner_set_dev_uuid_match(arg->set_dev_uuid_match.offset, + arg->set_dev_uuid_match.match_len, + arg->set_dev_uuid_match.match_val, + arg->set_dev_uuid_match.prov_after_match); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_SET_PROV_DATA_INFO: { + struct bt_mesh_prov_data_info info = {0}; + info.flag = arg->set_prov_data_info.prov_data.flag; + if (arg->set_prov_data_info.prov_data.flag & PROV_DATA_NET_IDX_FLAG) { + info.net_idx = arg->set_prov_data_info.prov_data.net_idx; + } else if (arg->set_prov_data_info.prov_data.flag & PROV_DATA_FLAGS_FLAG) { + info.flags = arg->set_prov_data_info.prov_data.flags; + } else if (arg->set_prov_data_info.prov_data.flag & PROV_DATA_IV_INDEX_FLAG) { + info.iv_index = arg->set_prov_data_info.prov_data.iv_index; + } + act = ESP_BLE_MESH_PROVISIONER_SET_PROV_DATA_INFO_COMP_EVT; + param.provisioner_set_prov_data_info_comp.err_code = + bt_mesh_provisioner_set_prov_data_info(&info); + break; + } + case BTC_BLE_MESH_ACT_PROVISIONER_SET_NODE_NAME: + act = ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT; + param.provisioner_set_node_name_comp.err_code = + bt_mesh_provisioner_set_node_name(arg->set_node_name.index, arg->set_node_name.name); + if (param.provisioner_set_node_name_comp.err_code) { + param.provisioner_set_node_name_comp.node_index = ESP_BLE_MESH_INVALID_NODE_INDEX; + } else { + param.provisioner_set_node_name_comp.node_index = arg->set_node_name.index; + } + break; + case BTC_BLE_MESH_ACT_PROVISIONER_SET_LOCAL_APP_KEY: { + const u8_t *app_key = NULL; + const u8_t zero[16] = {0}; + if (memcmp(arg->add_local_app_key.app_key, zero, 16)) { + app_key = arg->add_local_app_key.app_key; + } + act = ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT; + param.provisioner_add_app_key_comp.err_code = + bt_mesh_provisioner_local_app_key_add(app_key, arg->add_local_app_key.net_idx, + &arg->add_local_app_key.app_idx); + if (param.provisioner_add_app_key_comp.err_code) { + param.provisioner_add_app_key_comp.app_idx = ESP_BLE_MESH_KEY_UNUSED; + } else { + param.provisioner_add_app_key_comp.app_idx = arg->add_local_app_key.app_idx; + } + break; + } + case BTC_BLE_MESH_ACT_PROVISIONER_BIND_LOCAL_MOD_APP: + act = ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT; + param.provisioner_bind_app_key_to_model_comp.err_code = + bt_mesh_provisioner_bind_local_model_app_idx(arg->local_mod_app_bind.elem_addr, + arg->local_mod_app_bind.model_id, + arg->local_mod_app_bind.cid, + arg->local_mod_app_bind.app_idx); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_ADD_LOCAL_NET_KEY: { + const u8_t *net_key = NULL; + const u8_t zero[16] = {0}; + if (memcmp(arg->add_local_net_key.net_key, zero, 16)) { + net_key = arg->add_local_net_key.net_key; + } + act = ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_NET_KEY_COMP_EVT; + param.provisioner_add_net_key_comp.err_code = + bt_mesh_provisioner_local_net_key_add(net_key, &arg->add_local_net_key.net_idx); + if (param.provisioner_add_net_key_comp.err_code) { + param.provisioner_add_net_key_comp.net_idx = ESP_BLE_MESH_KEY_UNUSED; + } else { + param.provisioner_add_net_key_comp.net_idx = arg->add_local_net_key.net_idx; + } + break; + } +#endif /* CONFIG_BLE_MESH_PROVISIONER */ +#if (CONFIG_BLE_MESH_FAST_PROV) + case BTC_BLE_MESH_ACT_SET_FAST_PROV_INFO: + act = ESP_BLE_MESH_SET_FAST_PROV_INFO_COMP_EVT; + param.set_fast_prov_info_comp.status_unicast = + bt_mesh_set_fast_prov_unicast_addr_range(arg->set_fast_prov_info.unicast_min, + arg->set_fast_prov_info.unicast_max); + param.set_fast_prov_info_comp.status_net_idx = + bt_mesh_set_fast_prov_net_idx(arg->set_fast_prov_info.net_idx); + bt_mesh_set_fast_prov_flags_iv_index(arg->set_fast_prov_info.flags, + arg->set_fast_prov_info.iv_index); + param.set_fast_prov_info_comp.status_match = + bt_mesh_provisioner_set_dev_uuid_match(arg->set_fast_prov_info.offset, + arg->set_fast_prov_info.match_len, + arg->set_fast_prov_info.match_val, false); + break; + case BTC_BLE_MESH_ACT_SET_FAST_PROV_ACTION: + act = ESP_BLE_MESH_SET_FAST_PROV_ACTION_COMP_EVT; + param.set_fast_prov_action_comp.status_action = + bt_mesh_set_fast_prov_action(arg->set_fast_prov_action.action); + break; +#endif /* (CONFIG_BLE_MESH_FAST_PROV) */ + default: + LOG_WARN("%s, Invalid msg->act %d", __func__, msg->act); + return; + } + + btc_prov_set_complete_cb(¶m, act); + return; +} + +void btc_mesh_model_call_handler(btc_msg_t *msg) +{ + btc_ble_mesh_model_args_t *arg = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_model_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_MODEL_PUBLISH: { + if (arg->model_publish.device_role == PROVISIONER) { + bt_mesh_role_param_t common = {0}; + common.model = (struct bt_mesh_model *)(arg->model_publish.model); + common.role = arg->model_publish.device_role; + if (bt_mesh_set_model_role(&common)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + } + int err = bt_mesh_model_publish((struct bt_mesh_model *)arg->model_publish.model); + btc_ble_mesh_model_publish_comp_cb(arg->model_publish.model, err); + break; + } + case BTC_BLE_MESH_ACT_SERVER_MODEL_SEND: { + /* arg->model_send.length contains opcode & message, 8 is used for TransMIC */ + struct net_buf_simple *buf = bt_mesh_alloc_buf(arg->model_send.length + 8); + if (!buf) { + LOG_ERROR("%s, Failed to allocate memory", __func__); + return; + } + net_buf_simple_add_mem(buf, arg->model_send.data, arg->model_send.length); + arg->model_send.ctx->srv_send = true; + int err = bt_mesh_model_send((struct bt_mesh_model *)arg->model_send.model, + (struct bt_mesh_msg_ctx *)arg->model_send.ctx, + buf, NULL, NULL); + bt_mesh_free_buf(buf); + btc_ble_mesh_model_send_comp_cb(arg->model_send.model, arg->model_send.ctx, + arg->model_send.opcode, err); + break; + } + case BTC_BLE_MESH_ACT_CLIENT_MODEL_SEND: { + bt_mesh_role_param_t common = {0}; + /* arg->model_send.length contains opcode & message, 8 is used for TransMIC */ + struct net_buf_simple *buf = bt_mesh_alloc_buf(arg->model_send.length + 8); + if (!buf) { + LOG_ERROR("%s, Failed to allocate memory", __func__); + return; + } + net_buf_simple_add_mem(buf, arg->model_send.data, arg->model_send.length); + arg->model_send.ctx->srv_send = false; + common.model = (struct bt_mesh_model *)(arg->model_send.model); + common.role = arg->model_send.device_role; + if (bt_mesh_set_model_role(&common)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + int err = bt_mesh_client_send_msg((struct bt_mesh_model *)arg->model_send.model, + arg->model_send.opcode, + (struct bt_mesh_msg_ctx *)arg->model_send.ctx, buf, + btc_client_model_timeout_cb, arg->model_send.msg_timeout, + arg->model_send.need_rsp, NULL, NULL); + bt_mesh_free_buf(buf); + btc_ble_mesh_model_send_comp_cb(arg->model_send.model, arg->model_send.ctx, + arg->model_send.opcode, err); + break; + } + default: + LOG_WARN("%s, Unknown msg->act %d", __func__, msg->act); + break; + } + + btc_ble_mesh_prov_arg_deep_free(msg); +} + +void btc_mesh_model_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_model_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_model_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_MODEL_EVT_MAX) { + btc_ble_mesh_model_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_free_req_data(msg); +} + +void btc_mesh_prov_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_prov_cb_param_t *param = NULL; + + if (!msg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_prov_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_PROV_EVT_MAX) { + btc_ble_mesh_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } +} diff --git a/components/bt/ble_mesh/btc/btc_ble_mesh_sensor_model.c b/components/bt/ble_mesh/btc/btc_ble_mesh_sensor_model.c new file mode 100644 index 0000000000..53d773842f --- /dev/null +++ b/components/bt/ble_mesh/btc/btc_ble_mesh_sensor_model.c @@ -0,0 +1,632 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "btc/btc_manage.h" +#include "osi/allocator.h" + +#include "sensor_client.h" +#include "btc_ble_mesh_sensor_model.h" +#include "esp_ble_mesh_sensor_model_api.h" + +static inline void btc_ble_mesh_cb_to_app(esp_ble_mesh_sensor_client_cb_event_t event, + esp_ble_mesh_sensor_client_cb_param_t *param) +{ + esp_ble_mesh_sensor_client_cb_t btc_mesh_cb = (esp_ble_mesh_sensor_client_cb_t)btc_profile_cb_get(BTC_PID_SENSOR_CLIENT); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +void btc_ble_mesh_sensor_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_sensor_client_args_t *dst = (btc_ble_mesh_sensor_client_args_t *)p_dest; + btc_ble_mesh_sensor_client_args_t *src = (btc_ble_mesh_sensor_client_args_t *)p_src; + u32_t opcode; + u16_t length; + + if (!msg || !dst || !src) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_SENSOR_CLIENT_GET_STATE: { + dst->sensor_client_get_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->sensor_client_get_state.get_state = (esp_ble_mesh_sensor_client_get_state_t *)osi_malloc(sizeof(esp_ble_mesh_sensor_client_get_state_t)); + if (dst->sensor_client_get_state.params && dst->sensor_client_get_state.get_state) { + memcpy(dst->sensor_client_get_state.params, src->sensor_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->sensor_client_get_state.get_state, src->sensor_client_get_state.get_state, + sizeof(esp_ble_mesh_sensor_client_get_state_t)); + + opcode = src->sensor_client_get_state.params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET: + if (src->sensor_client_get_state.get_state->column_get.raw_value_x) { + length = src->sensor_client_get_state.get_state->column_get.raw_value_x->len; + dst->sensor_client_get_state.get_state->column_get.raw_value_x = bt_mesh_alloc_buf(length); + if (!dst->sensor_client_get_state.get_state->column_get.raw_value_x) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->sensor_client_get_state.get_state->column_get.raw_value_x, + src->sensor_client_get_state.get_state->column_get.raw_value_x->data, + src->sensor_client_get_state.get_state->column_get.raw_value_x->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_GET: + if (src->sensor_client_get_state.get_state->series_get.raw_value_x1) { + length = src->sensor_client_get_state.get_state->series_get.raw_value_x1->len; + dst->sensor_client_get_state.get_state->series_get.raw_value_x1 = bt_mesh_alloc_buf(length); + if (!dst->sensor_client_get_state.get_state->series_get.raw_value_x1) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->sensor_client_get_state.get_state->series_get.raw_value_x1, + src->sensor_client_get_state.get_state->series_get.raw_value_x1->data, + src->sensor_client_get_state.get_state->series_get.raw_value_x1->len); + } + if (src->sensor_client_get_state.get_state->series_get.raw_value_x2) { + length = src->sensor_client_get_state.get_state->series_get.raw_value_x2->len; + dst->sensor_client_get_state.get_state->series_get.raw_value_x2 = bt_mesh_alloc_buf(length); + if (!dst->sensor_client_get_state.get_state->series_get.raw_value_x2) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->sensor_client_get_state.get_state->series_get.raw_value_x2, + src->sensor_client_get_state.get_state->series_get.raw_value_x2->data, + src->sensor_client_get_state.get_state->series_get.raw_value_x2->len); + } + break; + default: + break; + } + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + case BTC_BLE_MESH_ACT_SENSOR_CLIENT_SET_STATE: { + dst->sensor_client_set_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->sensor_client_set_state.set_state = (esp_ble_mesh_sensor_client_set_state_t *)osi_malloc(sizeof(esp_ble_mesh_sensor_client_set_state_t)); + if (dst->sensor_client_set_state.params && dst->sensor_client_set_state.set_state) { + memcpy(dst->sensor_client_set_state.params, src->sensor_client_set_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->sensor_client_set_state.set_state, src->sensor_client_set_state.set_state, + sizeof(esp_ble_mesh_sensor_client_set_state_t)); + + opcode = src->sensor_client_set_state.params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET: + if (src->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_down) { + length = src->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_down->len; + dst->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_down = bt_mesh_alloc_buf(length); + if (!dst->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_down) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_down, + src->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_down->data, + src->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_down->len); + } + if (src->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_up) { + length = src->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_up->len; + dst->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_up = bt_mesh_alloc_buf(length); + if (!dst->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_up) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_up, + src->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_up->data, + src->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_up->len); + } + if (src->sensor_client_set_state.set_state->cadence_set.fast_cadence_low) { + length = src->sensor_client_set_state.set_state->cadence_set.fast_cadence_low->len; + dst->sensor_client_set_state.set_state->cadence_set.fast_cadence_low = bt_mesh_alloc_buf(length); + if (!dst->sensor_client_set_state.set_state->cadence_set.fast_cadence_low) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->sensor_client_set_state.set_state->cadence_set.fast_cadence_low, + src->sensor_client_set_state.set_state->cadence_set.fast_cadence_low->data, + src->sensor_client_set_state.set_state->cadence_set.fast_cadence_low->len); + } + if (src->sensor_client_set_state.set_state->cadence_set.fast_cadence_high) { + length = src->sensor_client_set_state.set_state->cadence_set.fast_cadence_high->len; + dst->sensor_client_set_state.set_state->cadence_set.fast_cadence_high = bt_mesh_alloc_buf(length); + if (!dst->sensor_client_set_state.set_state->cadence_set.fast_cadence_high) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->sensor_client_set_state.set_state->cadence_set.fast_cadence_high, + src->sensor_client_set_state.set_state->cadence_set.fast_cadence_high->data, + src->sensor_client_set_state.set_state->cadence_set.fast_cadence_high->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET: + if (src->sensor_client_set_state.set_state->setting_set.sensor_setting_raw) { + length = src->sensor_client_set_state.set_state->setting_set.sensor_setting_raw->len; + dst->sensor_client_set_state.set_state->setting_set.sensor_setting_raw = bt_mesh_alloc_buf(length); + if (!dst->sensor_client_set_state.set_state->setting_set.sensor_setting_raw) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(dst->sensor_client_set_state.set_state->setting_set.sensor_setting_raw, + src->sensor_client_set_state.set_state->setting_set.sensor_setting_raw->data, + src->sensor_client_set_state.set_state->setting_set.sensor_setting_raw->len); + } + break; + default: + break; + } + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + LOG_DEBUG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_sensor_client_cb_param_t *p_dest_data = (esp_ble_mesh_sensor_client_cb_param_t *)p_dest; + esp_ble_mesh_sensor_client_cb_param_t *p_src_data = (esp_ble_mesh_sensor_client_cb_param_t *)p_src; + u32_t opcode; + u16_t length; + + if (!msg || !p_src_data || !p_dest_data) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_SENSOR_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_SENSOR_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_SENSOR_CLIENT_PUBLISH_EVT: + if (p_src_data->params) { + opcode = p_src_data->params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS: + if (p_src_data->status_cb.descriptor_status.descriptor) { + length = p_src_data->status_cb.descriptor_status.descriptor->len; + p_dest_data->status_cb.descriptor_status.descriptor = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.descriptor_status.descriptor) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.descriptor_status.descriptor, + p_src_data->status_cb.descriptor_status.descriptor->data, + p_src_data->status_cb.descriptor_status.descriptor->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS: + if (p_src_data->status_cb.cadence_status.sensor_cadence_value) { + length = p_src_data->status_cb.cadence_status.sensor_cadence_value->len; + p_dest_data->status_cb.cadence_status.sensor_cadence_value = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.cadence_status.sensor_cadence_value) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.cadence_status.sensor_cadence_value, + p_src_data->status_cb.cadence_status.sensor_cadence_value->data, + p_src_data->status_cb.cadence_status.sensor_cadence_value->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS: + if (p_src_data->status_cb.settings_status.sensor_setting_property_ids) { + length = p_src_data->status_cb.settings_status.sensor_setting_property_ids->len; + p_dest_data->status_cb.settings_status.sensor_setting_property_ids = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.settings_status.sensor_setting_property_ids) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.settings_status.sensor_setting_property_ids, + p_src_data->status_cb.settings_status.sensor_setting_property_ids->data, + p_src_data->status_cb.settings_status.sensor_setting_property_ids->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS: + if (p_src_data->status_cb.setting_status.sensor_setting_raw) { + length = p_src_data->status_cb.setting_status.sensor_setting_raw->len; + p_dest_data->status_cb.setting_status.sensor_setting_raw = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.setting_status.sensor_setting_raw) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.setting_status.sensor_setting_raw, + p_src_data->status_cb.setting_status.sensor_setting_raw->data, + p_src_data->status_cb.setting_status.sensor_setting_raw->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_STATUS: + if (p_src_data->status_cb.sensor_status.marshalled_sensor_data) { + length = p_src_data->status_cb.sensor_status.marshalled_sensor_data->len; + p_dest_data->status_cb.sensor_status.marshalled_sensor_data = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.sensor_status.marshalled_sensor_data) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.sensor_status.marshalled_sensor_data, + p_src_data->status_cb.sensor_status.marshalled_sensor_data->data, + p_src_data->status_cb.sensor_status.marshalled_sensor_data->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS: + if (p_src_data->status_cb.column_status.sensor_column_value) { + length = p_src_data->status_cb.column_status.sensor_column_value->len; + p_dest_data->status_cb.column_status.sensor_column_value = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.column_status.sensor_column_value) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.column_status.sensor_column_value, + p_src_data->status_cb.column_status.sensor_column_value->data, + p_src_data->status_cb.column_status.sensor_column_value->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS: + if (p_src_data->status_cb.series_status.sensor_series_value) { + length = p_src_data->status_cb.series_status.sensor_series_value->len; + p_dest_data->status_cb.series_status.sensor_series_value = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.series_status.sensor_series_value) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.series_status.sensor_series_value, + p_src_data->status_cb.series_status.sensor_series_value->data, + p_src_data->status_cb.series_status.sensor_series_value->len); + } + break; + default: + break; + } + } + case ESP_BLE_MESH_SENSOR_CLIENT_TIMEOUT_EVT: + if (p_src_data->params) { + p_dest_data->params = osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (p_dest_data->params) { + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + default: + break; + } +} + +static void btc_ble_mesh_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_sensor_client_cb_param_t *arg = NULL; + u32_t opcode; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_sensor_client_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_SENSOR_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_SENSOR_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_SENSOR_CLIENT_PUBLISH_EVT: + if (arg->params) { + opcode = arg->params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS: + bt_mesh_free_buf(arg->status_cb.descriptor_status.descriptor); + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS: + bt_mesh_free_buf(arg->status_cb.cadence_status.sensor_cadence_value); + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS: + bt_mesh_free_buf(arg->status_cb.settings_status.sensor_setting_property_ids); + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS: + bt_mesh_free_buf(arg->status_cb.setting_status.sensor_setting_raw); + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_STATUS: + bt_mesh_free_buf(arg->status_cb.sensor_status.marshalled_sensor_data); + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS: + bt_mesh_free_buf(arg->status_cb.column_status.sensor_column_value); + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_GET: + case ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS: + bt_mesh_free_buf(arg->status_cb.series_status.sensor_series_value); + break; + default: + break; + } + } + case ESP_BLE_MESH_SENSOR_CLIENT_TIMEOUT_EVT: + if (arg->params) { + osi_free(arg->params); + } + break; + default: + break; + } +} + +void btc_ble_mesh_sensor_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_sensor_client_args_t *arg = NULL; + u32_t opcode = 0; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_sensor_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_SENSOR_CLIENT_GET_STATE: + if (arg->sensor_client_get_state.params) { + opcode = arg->sensor_client_get_state.params->opcode; + osi_free(arg->sensor_client_get_state.params); + } + if (arg->sensor_client_get_state.get_state) { + if (opcode) { + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET: + bt_mesh_free_buf(arg->sensor_client_get_state.get_state->column_get.raw_value_x); + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_GET: + bt_mesh_free_buf(arg->sensor_client_get_state.get_state->series_get.raw_value_x1); + bt_mesh_free_buf(arg->sensor_client_get_state.get_state->series_get.raw_value_x2); + break; + default: + break; + } + } + osi_free(arg->sensor_client_get_state.get_state); + } + break; + case BTC_BLE_MESH_ACT_SENSOR_CLIENT_SET_STATE: + if (arg->sensor_client_set_state.params) { + opcode = arg->sensor_client_set_state.params->opcode; + osi_free(arg->sensor_client_set_state.params); + } + if (arg->sensor_client_set_state.set_state) { + if (opcode) { + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET: + bt_mesh_free_buf(arg->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_down); + bt_mesh_free_buf(arg->sensor_client_set_state.set_state->cadence_set.status_trigger_delta_up); + bt_mesh_free_buf(arg->sensor_client_set_state.set_state->cadence_set.fast_cadence_low); + bt_mesh_free_buf(arg->sensor_client_set_state.set_state->cadence_set.fast_cadence_high); + break; + case ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET: + bt_mesh_free_buf(arg->sensor_client_set_state.set_state->setting_set.sensor_setting_raw); + break; + default: + break; + } + } + osi_free(arg->sensor_client_set_state.set_state); + } + break; + default: + break; + } + + return; +} + +static void btc_mesh_sensor_client_callback(esp_ble_mesh_sensor_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_SENSOR_CLIENT; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_sensor_client_cb_param_t), btc_ble_mesh_copy_req_data); +} + +void bt_mesh_callback_sensor_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len) +{ + esp_ble_mesh_sensor_client_cb_param_t cb_params = {0}; + esp_ble_mesh_client_common_param_t params = {0}; + size_t length; + uint8_t act; + + if (!model || !ctx) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case 0x00: + act = ESP_BLE_MESH_SENSOR_CLIENT_GET_STATE_EVT; + break; + case 0x01: + act = ESP_BLE_MESH_SENSOR_CLIENT_SET_STATE_EVT; + break; + case 0x02: + act = ESP_BLE_MESH_SENSOR_CLIENT_PUBLISH_EVT; + break; + case 0x03: + act = ESP_BLE_MESH_SENSOR_CLIENT_TIMEOUT_EVT; + break; + default: + LOG_ERROR("%s, Unknown sensor client event type %d", __func__, evt_type); + return; + } + + params.opcode = opcode; + params.model = (esp_ble_mesh_model_t *)model; + params.ctx.net_idx = ctx->net_idx; + params.ctx.app_idx = ctx->app_idx; + params.ctx.addr = ctx->addr; + params.ctx.recv_ttl = ctx->recv_ttl; + params.ctx.recv_op = ctx->recv_op; + params.ctx.recv_dst = ctx->recv_dst; + + cb_params.error_code = 0; + cb_params.params = ¶ms; + + if (val && len) { + length = (len <= sizeof(cb_params.status_cb)) ? len : sizeof(cb_params.status_cb); + memcpy(&cb_params.status_cb, val, length); + } + + btc_mesh_sensor_client_callback(&cb_params, act); +} + +void btc_mesh_sensor_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) +{ + if (!model || !ctx || !buf) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_callback_sensor_status_to_btc(opcode, 0x02, model, ctx, buf->data, buf->len); +} + +void btc_mesh_sensor_client_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_sensor_client_cb_param_t sensor_client_cb = {0}; + esp_ble_mesh_client_common_param_t *params = NULL; + btc_ble_mesh_sensor_client_args_t *arg = NULL; + struct bt_mesh_common_param common = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_sensor_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_SENSOR_CLIENT_GET_STATE: { + params = arg->sensor_client_get_state.params; + role_param.model = (struct bt_mesh_model *)params->model; + role_param.role = params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + common.opcode = params->opcode; + common.model = (struct bt_mesh_model *)params->model; + common.ctx.net_idx = params->ctx.net_idx; + common.ctx.app_idx = params->ctx.app_idx; + common.ctx.addr = params->ctx.addr; + common.ctx.send_rel = params->ctx.send_rel; + common.ctx.send_ttl = params->ctx.send_ttl; + common.msg_timeout = params->msg_timeout; + + sensor_client_cb.params = arg->sensor_client_get_state.params; + sensor_client_cb.error_code = + bt_mesh_sensor_client_get_state(&common, + (void *)arg->sensor_client_get_state.get_state, + (void *)&sensor_client_cb.status_cb); + if (sensor_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_sensor_client_callback(&sensor_client_cb, + ESP_BLE_MESH_SENSOR_CLIENT_GET_STATE_EVT); + } + break; + } + case BTC_BLE_MESH_ACT_SENSOR_CLIENT_SET_STATE: { + params = arg->sensor_client_set_state.params; + role_param.model = (struct bt_mesh_model *)params->model; + role_param.role = params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + common.opcode = params->opcode; + common.model = (struct bt_mesh_model *)params->model; + common.ctx.net_idx = params->ctx.net_idx; + common.ctx.app_idx = params->ctx.app_idx; + common.ctx.addr = params->ctx.addr; + common.ctx.send_rel = params->ctx.send_rel; + common.ctx.send_ttl = params->ctx.send_ttl; + common.msg_timeout = params->msg_timeout; + + sensor_client_cb.params = arg->sensor_client_set_state.params; + sensor_client_cb.error_code = + bt_mesh_sensor_client_set_state(&common, + (void *)arg->sensor_client_set_state.set_state, + (void *)&sensor_client_cb.status_cb); + if (sensor_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_sensor_client_callback(&sensor_client_cb, + ESP_BLE_MESH_SENSOR_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_sensor_client_arg_deep_free(msg); +} + +void btc_mesh_sensor_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_sensor_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_sensor_client_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_SENSOR_CLIENT_EVT_MAX) { + btc_ble_mesh_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_free_req_data(msg); +} + diff --git a/components/bt/ble_mesh/btc/btc_ble_mesh_time_scene_model.c b/components/bt/ble_mesh/btc/btc_ble_mesh_time_scene_model.c new file mode 100644 index 0000000000..4c1a36905e --- /dev/null +++ b/components/bt/ble_mesh/btc/btc_ble_mesh_time_scene_model.c @@ -0,0 +1,383 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "btc/btc_manage.h" +#include "osi/allocator.h" + +#include "time_scene_client.h" +#include "btc_ble_mesh_time_scene_model.h" +#include "esp_ble_mesh_time_scene_model_api.h" + +static inline void btc_ble_mesh_cb_to_app(esp_ble_mesh_time_scene_client_cb_event_t event, + esp_ble_mesh_time_scene_client_cb_param_t *param) +{ + esp_ble_mesh_time_scene_client_cb_t btc_mesh_cb = (esp_ble_mesh_time_scene_client_cb_t)btc_profile_cb_get(BTC_PID_TIME_SCENE_CLIENT); + if (btc_mesh_cb) { + btc_mesh_cb(event, param); + } +} + +void btc_ble_mesh_time_scene_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_time_scene_client_args_t *dst = (btc_ble_mesh_time_scene_client_args_t *)p_dest; + btc_ble_mesh_time_scene_client_args_t *src = (btc_ble_mesh_time_scene_client_args_t *)p_src; + + if (!msg || !dst || !src) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_GET_STATE: { + dst->time_scene_client_get_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->time_scene_client_get_state.get_state = (esp_ble_mesh_time_scene_client_get_state_t *)osi_malloc(sizeof(esp_ble_mesh_time_scene_client_get_state_t)); + if (dst->time_scene_client_get_state.params && dst->time_scene_client_get_state.get_state) { + memcpy(dst->time_scene_client_get_state.params, src->time_scene_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->time_scene_client_get_state.get_state, src->time_scene_client_get_state.get_state, + sizeof(esp_ble_mesh_time_scene_client_get_state_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + case BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_SET_STATE: { + dst->time_scene_client_set_state.params = (esp_ble_mesh_client_common_param_t *)osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->time_scene_client_set_state.set_state = (esp_ble_mesh_time_scene_client_set_state_t *)osi_malloc(sizeof(esp_ble_mesh_time_scene_client_set_state_t)); + if (dst->time_scene_client_set_state.params && dst->time_scene_client_set_state.set_state) { + memcpy(dst->time_scene_client_set_state.params, src->time_scene_client_set_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + memcpy(dst->time_scene_client_set_state.set_state, src->time_scene_client_set_state.set_state, + sizeof(esp_ble_mesh_time_scene_client_set_state_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + LOG_DEBUG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_time_scene_client_cb_param_t *p_dest_data = (esp_ble_mesh_time_scene_client_cb_param_t *)p_dest; + esp_ble_mesh_time_scene_client_cb_param_t *p_src_data = (esp_ble_mesh_time_scene_client_cb_param_t *)p_src; + u32_t opcode; + u16_t length; + + if (!msg || !p_src_data || !p_dest_data) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_TIME_SCENE_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_TIME_SCENE_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_TIME_SCENE_CLIENT_PUBLISH_EVT: + if (p_src_data->params) { + opcode = p_src_data->params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_SCENE_STORE: + case ESP_BLE_MESH_MODEL_OP_SCENE_REGISTER_GET: + case ESP_BLE_MESH_MODEL_OP_SCENE_DELETE: + case ESP_BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS: + if (p_src_data->status_cb.scene_register_status.scenes) { + length = p_src_data->status_cb.scene_register_status.scenes->len; + p_dest_data->status_cb.scene_register_status.scenes = bt_mesh_alloc_buf(length); + if (!p_dest_data->status_cb.scene_register_status.scenes) { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->status_cb.scene_register_status.scenes, + p_src_data->status_cb.scene_register_status.scenes->data, + p_src_data->status_cb.scene_register_status.scenes->len); + } + break; + default: + break; + } + } + case ESP_BLE_MESH_TIME_SCENE_CLIENT_TIMEOUT_EVT: + if (p_src_data->params) { + p_dest_data->params = osi_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (p_dest_data->params) { + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } else { + LOG_ERROR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + default: + break; + } +} + +static void btc_ble_mesh_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_time_scene_client_cb_param_t *arg = NULL; + u32_t opcode; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_time_scene_client_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_TIME_SCENE_CLIENT_GET_STATE_EVT: + case ESP_BLE_MESH_TIME_SCENE_CLIENT_SET_STATE_EVT: + case ESP_BLE_MESH_TIME_SCENE_CLIENT_PUBLISH_EVT: + if (arg->params) { + opcode = arg->params->opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_SCENE_STORE: + case ESP_BLE_MESH_MODEL_OP_SCENE_REGISTER_GET: + case ESP_BLE_MESH_MODEL_OP_SCENE_DELETE: + case ESP_BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS: + bt_mesh_free_buf(arg->status_cb.scene_register_status.scenes); + break; + default: + break; + } + } + case ESP_BLE_MESH_TIME_SCENE_CLIENT_TIMEOUT_EVT: + if (arg->params) { + osi_free(arg->params); + } + break; + default: + break; + } +} + +void btc_ble_mesh_time_scene_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_time_scene_client_args_t *arg = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_time_scene_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_GET_STATE: + if (arg->time_scene_client_get_state.params) { + osi_free(arg->time_scene_client_get_state.params); + } + if (arg->time_scene_client_get_state.get_state) { + osi_free(arg->time_scene_client_get_state.get_state); + } + break; + case BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_SET_STATE: + if (arg->time_scene_client_set_state.params) { + osi_free(arg->time_scene_client_set_state.params); + } + if (arg->time_scene_client_set_state.set_state) { + osi_free(arg->time_scene_client_set_state.set_state); + } + break; + default: + break; + } + + return; +} + +static void btc_mesh_time_scene_client_callback(esp_ble_mesh_time_scene_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + LOG_DEBUG("%s", __func__); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_TIME_SCENE_CLIENT; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_time_scene_client_cb_param_t), btc_ble_mesh_copy_req_data); +} + +void bt_mesh_callback_time_scene_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len) +{ + esp_ble_mesh_time_scene_client_cb_param_t cb_params = {0}; + esp_ble_mesh_client_common_param_t params = {0}; + size_t length; + uint8_t act; + + if (!model || !ctx) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case 0x00: + act = ESP_BLE_MESH_TIME_SCENE_CLIENT_GET_STATE_EVT; + break; + case 0x01: + act = ESP_BLE_MESH_TIME_SCENE_CLIENT_SET_STATE_EVT; + break; + case 0x02: + act = ESP_BLE_MESH_TIME_SCENE_CLIENT_PUBLISH_EVT; + break; + case 0x03: + act = ESP_BLE_MESH_TIME_SCENE_CLIENT_TIMEOUT_EVT; + break; + default: + LOG_ERROR("%s, Unknown time scene client event type %d", __func__, evt_type); + return; + } + + params.opcode = opcode; + params.model = (esp_ble_mesh_model_t *)model; + params.ctx.net_idx = ctx->net_idx; + params.ctx.app_idx = ctx->app_idx; + params.ctx.addr = ctx->addr; + params.ctx.recv_ttl = ctx->recv_ttl; + params.ctx.recv_op = ctx->recv_op; + params.ctx.recv_dst = ctx->recv_dst; + + cb_params.error_code = 0; + cb_params.params = ¶ms; + + if (val && len) { + length = (len <= sizeof(cb_params.status_cb)) ? len : sizeof(cb_params.status_cb); + memcpy(&cb_params.status_cb, val, length); + } + + btc_mesh_time_scene_client_callback(&cb_params, act); +} + +void btc_mesh_time_scene_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) +{ + if (!model || !ctx || !buf) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_callback_time_scene_status_to_btc(opcode, 0x02, model, ctx, buf->data, buf->len); +} + +void btc_mesh_time_scene_client_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_time_scene_client_cb_param_t time_scene_client_cb = {0}; + btc_ble_mesh_time_scene_client_args_t *arg = NULL; + esp_ble_mesh_client_common_param_t *params = NULL; + struct bt_mesh_common_param common = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_time_scene_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_GET_STATE: { + params = arg->time_scene_client_get_state.params; + role_param.model = (struct bt_mesh_model *)params->model; + role_param.role = params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + common.opcode = params->opcode; + common.model = (struct bt_mesh_model *)params->model; + common.ctx.net_idx = params->ctx.net_idx; + common.ctx.app_idx = params->ctx.app_idx; + common.ctx.addr = params->ctx.addr; + common.ctx.send_rel = params->ctx.send_rel; + common.ctx.send_ttl = params->ctx.send_ttl; + common.msg_timeout = params->msg_timeout; + + time_scene_client_cb.params = arg->time_scene_client_get_state.params; + time_scene_client_cb.error_code = + bt_mesh_time_scene_client_get_state(&common, + (void *)arg->time_scene_client_get_state.get_state, + (void *)&time_scene_client_cb.status_cb); + if (time_scene_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_time_scene_client_callback(&time_scene_client_cb, + ESP_BLE_MESH_TIME_SCENE_CLIENT_GET_STATE_EVT); + } + break; + } + case BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_SET_STATE: { + params = arg->time_scene_client_set_state.params; + role_param.model = (struct bt_mesh_model *)params->model; + role_param.role = params->msg_role; + if (bt_mesh_set_model_role(&role_param)) { + LOG_ERROR("%s, Failed to set model role", __func__); + return; + } + common.opcode = params->opcode; + common.model = (struct bt_mesh_model *)params->model; + common.ctx.net_idx = params->ctx.net_idx; + common.ctx.app_idx = params->ctx.app_idx; + common.ctx.addr = params->ctx.addr; + common.ctx.send_rel = params->ctx.send_rel; + common.ctx.send_ttl = params->ctx.send_ttl; + common.msg_timeout = params->msg_timeout; + + time_scene_client_cb.params = arg->time_scene_client_set_state.params; + time_scene_client_cb.error_code = + bt_mesh_time_scene_client_set_state(&common, + (void *)arg->time_scene_client_set_state.set_state, + (void *)&time_scene_client_cb.status_cb); + if (time_scene_client_cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_mesh_time_scene_client_callback(&time_scene_client_cb, + ESP_BLE_MESH_TIME_SCENE_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_time_scene_client_arg_deep_free(msg); +} + +void btc_mesh_time_scene_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_time_scene_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + LOG_ERROR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_time_scene_client_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_TIME_SCENE_CLIENT_EVT_MAX) { + btc_ble_mesh_cb_to_app(msg->act, param); + } else { + LOG_ERROR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_free_req_data(msg); +} + diff --git a/components/bt/ble_mesh/btc/include/btc_ble_mesh_config_model.h b/components/bt/ble_mesh/btc/include/btc_ble_mesh_config_model.h new file mode 100644 index 0000000000..6744b96120 --- /dev/null +++ b/components/bt/ble_mesh/btc/include/btc_ble_mesh_config_model.h @@ -0,0 +1,64 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BTC_BLE_MESH_CONFIG_MODEL_H_ +#define _BTC_BLE_MESH_CONFIG_MODEL_H_ + +#include +#include "btc/btc_task.h" +#include "esp_ble_mesh_config_model_api.h" + +typedef enum { + BTC_BLE_MESH_ACT_CONFIG_CLIENT_GET_STATE, + BTC_BLE_MESH_ACT_CONFIG_CLIENT_SET_STATE, +} btc_ble_mesh_cfg_client_act_t; + +typedef union { + struct ble_mesh_clg_client_get_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_cfg_client_get_state_t *get_state; + } cfg_client_get_state; + struct ble_mesh_clg_client_set_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_cfg_client_set_state_t *set_state; + } cfg_client_set_state; +} btc_ble_mesh_cfg_client_args_t; + +void btc_mesh_cfg_client_call_handler(btc_msg_t *msg); + +void btc_mesh_cfg_client_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_cfg_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +int btc_ble_mesh_config_client_get_state(esp_ble_mesh_client_common_param_t *params, esp_ble_mesh_cfg_client_get_state_t *get_state, + esp_ble_mesh_cfg_client_cb_param_t *cfg_client_cb); + +int btc_ble_mesh_config_client_set_state(esp_ble_mesh_client_common_param_t *params, esp_ble_mesh_cfg_client_set_state_t *set_state, + esp_ble_mesh_cfg_client_cb_param_t *cfg_client_cb); + +void btc_mesh_cfg_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf); + +void bt_mesh_callback_config_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +void btc_mesh_cfg_server_cb_handler(btc_msg_t *msg); + +void bt_mesh_callback_cfg_server_event_to_btc(u8_t evt_type, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +#endif /* _BTC_BLE_MESH_CONFIG_MODEL_H_ */ diff --git a/components/bt/ble_mesh/btc/include/btc_ble_mesh_generic_model.h b/components/bt/ble_mesh/btc/include/btc_ble_mesh_generic_model.h new file mode 100644 index 0000000000..480003141d --- /dev/null +++ b/components/bt/ble_mesh/btc/include/btc_ble_mesh_generic_model.h @@ -0,0 +1,52 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BTC_BLE_MESH_GENERIC_MODEL_H_ +#define _BTC_BLE_MESH_GENERIC_MODEL_H_ + +#include +#include "btc/btc_task.h" +#include "esp_ble_mesh_generic_model_api.h" + +typedef enum { + BTC_BLE_MESH_ACT_GENERIC_CLIENT_GET_STATE, + BTC_BLE_MESH_ACT_GENERIC_CLIENT_SET_STATE, +} btc_ble_mesh_generic_client_act_t; + +typedef union { + struct ble_mesh_generic_client_get_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_generic_client_get_state_t *get_state; + } generic_client_get_state; + struct ble_mesh_generic_client_set_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_generic_client_set_state_t *set_state; + } generic_client_set_state; +} btc_ble_mesh_generic_client_args_t; + +void btc_mesh_generic_client_call_handler(btc_msg_t *msg); + +void btc_mesh_generic_client_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_generic_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_mesh_generic_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf); + +void bt_mesh_callback_generic_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +#endif /* _BTC_BLE_MESH_GENERIC_MODEL_H_ */ diff --git a/components/bt/ble_mesh/btc/include/btc_ble_mesh_health_model.h b/components/bt/ble_mesh/btc/include/btc_ble_mesh_health_model.h new file mode 100644 index 0000000000..859624dba2 --- /dev/null +++ b/components/bt/ble_mesh/btc/include/btc_ble_mesh_health_model.h @@ -0,0 +1,78 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BTC_BLE_MESH_HEALTH_MODEL_H_ +#define _BTC_BLE_MESH_HEALTH_MODEL_H_ + +#include +#include "btc/btc_task.h" +#include "esp_ble_mesh_health_model_api.h" + +typedef enum { + BTC_BLE_MESH_ACT_HEALTH_CLIENT_GET_STATE, + BTC_BLE_MESH_ACT_HEALTH_CLIENT_SET_STATE, + BTC_BLE_MESH_ACT_HEALTH_CLIENT_MAX, +} btc_ble_mesh_health_client_act_t; + +typedef enum { + BTC_BLE_MESH_ACT_HEALTH_SERVER_FAULT_UPDATE, + BTC_BLE_MESH_ACT_HEALTH_SERVER_MAX, +} btc_ble_mesh_health_server_act_t; + +typedef union { + struct ble_mesh_health_client_get_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_health_client_get_state_t *get_state; + } health_client_get_state; + struct ble_mesh_health_client_set_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_health_client_set_state_t *set_state; + } health_client_set_state; +} btc_ble_mesh_health_client_args_t; + +typedef union { + struct ble_mesh_health_server_fault_update_args { + esp_ble_mesh_elem_t *element; + } fault_update; +} btc_ble_mesh_health_server_args_t; + +void btc_mesh_health_client_call_handler(btc_msg_t *msg); + +void btc_mesh_health_client_cb_handler(btc_msg_t *msg); + +void btc_mesh_health_server_call_handler(btc_msg_t *msg); + +void btc_mesh_health_server_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_health_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_ble_mesh_health_server_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +int btc_ble_mesh_health_client_get_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_health_client_get_state_t *get_state, + esp_ble_mesh_health_client_cb_param_t *client_cb); + +int btc_ble_mesh_health_client_set_state(esp_ble_mesh_client_common_param_t *params, + esp_ble_mesh_health_client_set_state_t *set_state, + esp_ble_mesh_health_client_cb_param_t *client_cb); + +void btc_mesh_health_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf); + +void bt_mesh_callback_health_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, u16_t len); + +#endif /* _BTC_BLE_MESH_HEALTH_MODEL_H_ */ diff --git a/components/bt/ble_mesh/btc/include/btc_ble_mesh_lighting_model.h b/components/bt/ble_mesh/btc/include/btc_ble_mesh_lighting_model.h new file mode 100644 index 0000000000..8a3088f32d --- /dev/null +++ b/components/bt/ble_mesh/btc/include/btc_ble_mesh_lighting_model.h @@ -0,0 +1,53 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BTC_BLE_MESH_LIGHTING_MODEL_H_ +#define _BTC_BLE_MESH_LIGHTING_MODEL_H_ + +#include +#include "btc/btc_task.h" +#include "esp_ble_mesh_lighting_model_api.h" + +typedef enum { + BTC_BLE_MESH_ACT_LIGHT_CLIENT_GET_STATE, + BTC_BLE_MESH_ACT_LIGHT_CLIENT_SET_STATE, +} btc_ble_mesh_light_client_act_t; + +typedef union { + struct ble_mesh_light_client_get_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_light_client_get_state_t *get_state; + } light_client_get_state; + struct ble_mesh_light_client_set_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_light_client_set_state_t *set_state; + } light_client_set_state; +} btc_ble_mesh_light_client_args_t; + +void btc_mesh_light_client_call_handler(btc_msg_t *msg); + +void btc_mesh_light_client_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_light_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_mesh_light_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf); + +void bt_mesh_callback_light_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +#endif /* _BTC_BLE_MESH_LIGHTING_MODEL_H_ */ + diff --git a/components/bt/ble_mesh/btc/include/btc_ble_mesh_prov.h b/components/bt/ble_mesh/btc/include/btc_ble_mesh_prov.h new file mode 100644 index 0000000000..9ce21e4865 --- /dev/null +++ b/components/bt/ble_mesh/btc/include/btc_ble_mesh_prov.h @@ -0,0 +1,210 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BTC_BLE_MESH_PROV_H_ +#define _BTC_BLE_MESH_PROV_H_ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" + +#include "btc/btc_task.h" +#include "esp_bt_defs.h" + +#include "mesh_access.h" +#include "mesh_buf.h" +#include "mesh_main.h" +#include "provisioner_prov.h" +#include "esp_ble_mesh_defs.h" + +typedef enum { + BTC_BLE_MESH_ACT_MESH_INIT = 0, + BTC_BLE_MESH_ACT_PROV_ENABLE, + BTC_BLE_MESH_ACT_PROV_DISABLE, + BTC_BLE_MESH_ACT_NODE_RESET, + BTC_BLE_MESH_ACT_SET_OOB_PUB_KEY, + BTC_BLE_MESH_ACT_INPUT_NUMBER, + BTC_BLE_MESH_ACT_INPUT_STRING, + BTC_BLE_MESH_ACT_SET_DEVICE_NAME, + BTC_BLE_MESH_ACT_PROXY_IDENTITY_ENABLE, + BTC_BLE_MESH_ACT_PROXY_GATT_ENABLE, + BTC_BLE_MESH_ACT_PROXY_GATT_DISABLE, + BTC_BLE_MESH_ACT_PROVISIONER_READ_OOB_PUB_KEY, + BTC_BLE_MESH_ACT_PROVISIONER_INPUT_STR, + BTC_BLE_MESH_ACT_PROVISIONER_INPUT_NUM, + BTC_BLE_MESH_ACT_PROVISIONER_ENABLE, + BTC_BLE_MESH_ACT_PROVISIONER_DISABLE, + BTC_BLE_MESH_ACT_PROVISIONER_DEV_ADD, + BTC_BLE_MESH_ACT_PROVISIONER_DEV_DEL, + BTC_BLE_MESH_ACT_PROVISIONER_SET_DEV_UUID_MATCH, + BTC_BLE_MESH_ACT_PROVISIONER_SET_PROV_DATA_INFO, + BTC_BLE_MESH_ACT_PROVISIONER_SET_NODE_NAME, + BTC_BLE_MESH_ACT_PROVISIONER_SET_LOCAL_APP_KEY, + BTC_BLE_MESH_ACT_PROVISIONER_BIND_LOCAL_MOD_APP, + BTC_BLE_MESH_ACT_PROVISIONER_ADD_LOCAL_NET_KEY, + BTC_BLE_MESH_ACT_SET_FAST_PROV_INFO, + BTC_BLE_MESH_ACT_SET_FAST_PROV_ACTION, +} btc_ble_mesh_prov_act_t; + +typedef enum { + BTC_BLE_MESH_ACT_MODEL_PUBLISH, + BTC_BLE_MESH_ACT_SERVER_MODEL_SEND, + BTC_BLE_MESH_ACT_CLIENT_MODEL_SEND +} btc_ble_mesh_model_act_t; + +typedef union { + struct ble_mesh_init_args { + esp_ble_mesh_prov_t *prov; + esp_ble_mesh_comp_t *comp; + SemaphoreHandle_t semaphore; + } mesh_init; + struct ble_mesh_node_prov_enable_args { + esp_ble_mesh_prov_bearer_t bearers; + } node_prov_enable; + struct ble_mesh_node_prov_disable_args { + esp_ble_mesh_prov_bearer_t bearers; + } node_prov_disable; + struct ble_mesh_set_oob_pub_key_args { + uint8_t pub_key_x[32]; + uint8_t pub_key_y[32]; + uint8_t private_key[32]; + } set_oob_pub_key; + struct ble_mesh_node_input_num_args { + uint32_t number; + } input_number; + struct ble_mesh_node_input_str_args { + char string[8]; + } input_string; + struct ble_mesh_set_device_name_args { + char name[ESP_BLE_MESH_DEVICE_NAME_MAX_LEN]; + } set_device_name; + struct ble_mesh_provisioner_read_oob_pub_key_args { + uint8_t link_idx; + uint8_t pub_key_x[32]; + uint8_t pub_key_y[32]; + } provisioner_read_oob_pub_key; + struct ble_mesh_provisioner_input_str_args { + char string[8]; + uint8_t link_idx; + } provisioner_input_str; + struct ble_mesh_provisioner_input_num_args { + uint32_t number; + uint8_t link_idx; + } provisioner_input_num; + struct ble_mesh_provisioner_enable_args { + esp_ble_mesh_prov_bearer_t bearers; + } provisioner_enable; + struct ble_mesh_provisioner_disable_args { + esp_ble_mesh_prov_bearer_t bearers; + } provisioner_disable; + struct ble_mesh_provisioner_dev_add_args { + esp_ble_mesh_unprov_dev_add_t add_dev; + esp_ble_mesh_dev_add_flag_t flags; + } provisioner_dev_add; + struct ble_mesh_provisioner_dev_del_args { + esp_ble_mesh_device_delete_t del_dev; + } provisioner_dev_del; + struct ble_mesh_provisioner_set_dev_uuid_match_args { + uint8_t offset; + uint8_t match_len; + uint8_t match_val[16]; + bool prov_after_match; + } set_dev_uuid_match; + struct ble_mesh_provisioner_set_prov_net_idx_args { + esp_ble_mesh_prov_data_info_t prov_data; + } set_prov_data_info; + struct ble_mesh_provisioner_set_node_name_args { + int index; + char name[ESP_BLE_MESH_NODE_NAME_MAX_LEN]; + } set_node_name; + struct ble_mesh_provisioner_add_local_app_key_args { + uint8_t app_key[16]; + uint16_t net_idx; + uint16_t app_idx; + } add_local_app_key; + struct ble_mesh_provisioner_bind_local_mod_app_args { + uint16_t elem_addr; + uint16_t model_id; + uint16_t cid; + uint16_t app_idx; + } local_mod_app_bind; + struct ble_mesh_provisioner_add_local_net_key_args { + uint8_t net_key[16]; + uint16_t net_idx; + } add_local_net_key; + struct ble_mesh_set_fast_prov_info_args { + uint16_t unicast_min; + uint16_t unicast_max; + uint16_t net_idx; + uint8_t flags; + uint32_t iv_index; + uint8_t offset; + uint8_t match_len; + uint8_t match_val[16]; + } set_fast_prov_info; + struct ble_mesh_set_fast_prov_action_args { + uint8_t action; + } set_fast_prov_action; +} btc_ble_mesh_prov_args_t; + +typedef union { + struct ble_mesh_model_publish_args { + esp_ble_mesh_model_t *model; + uint8_t device_role; + } model_publish; + struct ble_mesh_model_send_args { + esp_ble_mesh_model_t *model; + esp_ble_mesh_msg_ctx_t *ctx; + uint32_t opcode; + bool need_rsp; + uint16_t length; + uint8_t *data; + uint8_t device_role; + int32_t msg_timeout; + } model_send; +} btc_ble_mesh_model_args_t; + +void btc_ble_mesh_prov_arg_deep_free(btc_msg_t *msg); + +void btc_ble_mesh_prov_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +int btc_ble_mesh_client_init(esp_ble_mesh_model_t *model); + +int32_t btc_ble_mesh_model_pub_period_get(esp_ble_mesh_model_t *mod); + +uint16_t btc_ble_mesh_get_primary_addr(void); + +uint16_t *btc_ble_mesh_model_find_group(esp_ble_mesh_model_t *mod, uint16_t addr); + +esp_ble_mesh_elem_t *btc_ble_mesh_elem_find(u16_t addr); + +uint8_t btc_ble_mesh_elem_count(void); + +esp_ble_mesh_model_t *btc_ble_mesh_model_find_vnd(const esp_ble_mesh_elem_t *elem, + uint16_t company, uint16_t id); + +esp_ble_mesh_model_t *btc_ble_mesh_model_find(const esp_ble_mesh_elem_t *elem, + uint16_t id); + +const esp_ble_mesh_comp_t *btc_ble_mesh_comp_get(void); + +void btc_mesh_model_call_handler(btc_msg_t *msg); +void btc_mesh_model_cb_handler(btc_msg_t *msg); + +void btc_mesh_prov_call_handler(btc_msg_t *msg); + +void btc_mesh_prov_cb_handler(btc_msg_t *msg); + +#endif /* _BTC_BLE_MESH_PROV_H_ */ diff --git a/components/bt/ble_mesh/btc/include/btc_ble_mesh_sensor_model.h b/components/bt/ble_mesh/btc/include/btc_ble_mesh_sensor_model.h new file mode 100644 index 0000000000..6bd97355e8 --- /dev/null +++ b/components/bt/ble_mesh/btc/include/btc_ble_mesh_sensor_model.h @@ -0,0 +1,53 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BTC_BLE_MESH_SENSOR_MODEL_H_ +#define _BTC_BLE_MESH_SENSOR_MODEL_H_ + +#include +#include "btc/btc_task.h" +#include "esp_ble_mesh_sensor_model_api.h" + +typedef enum { + BTC_BLE_MESH_ACT_SENSOR_CLIENT_GET_STATE, + BTC_BLE_MESH_ACT_SENSOR_CLIENT_SET_STATE, +} btc_ble_mesh_sensor_client_act_t; + +typedef union { + struct ble_mesh_sensor_client_get_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_sensor_client_get_state_t *get_state; + } sensor_client_get_state; + struct ble_mesh_sensor_client_set_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_sensor_client_set_state_t *set_state; + } sensor_client_set_state; +} btc_ble_mesh_sensor_client_args_t; + +void btc_mesh_sensor_client_call_handler(btc_msg_t *msg); + +void btc_mesh_sensor_client_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_sensor_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_mesh_sensor_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf); + +void bt_mesh_callback_sensor_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +#endif /* _BTC_BLE_MESH_SENSOR_MODEL_H_ */ + diff --git a/components/bt/ble_mesh/btc/include/btc_ble_mesh_time_scene_model.h b/components/bt/ble_mesh/btc/include/btc_ble_mesh_time_scene_model.h new file mode 100644 index 0000000000..1778fa2347 --- /dev/null +++ b/components/bt/ble_mesh/btc/include/btc_ble_mesh_time_scene_model.h @@ -0,0 +1,53 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BTC_BLE_MESH_TIME_SCENE_MODEL_H_ +#define _BTC_BLE_MESH_TIME_SCENE_MODEL_H_ + +#include +#include "btc/btc_task.h" +#include "esp_ble_mesh_time_scene_model_api.h" + +typedef enum { + BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_GET_STATE, + BTC_BLE_MESH_ACT_TIME_SCENE_CLIENT_SET_STATE, +} btc_ble_mesh_time_scene_client_act_t; + +typedef union { + struct ble_mesh_time_scene_client_get_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_time_scene_client_get_state_t *get_state; + } time_scene_client_get_state; + struct ble_mesh_time_scene_client_set_state_reg_args { + esp_ble_mesh_client_common_param_t *params; + esp_ble_mesh_time_scene_client_set_state_t *set_state; + } time_scene_client_set_state; +} btc_ble_mesh_time_scene_client_args_t; + +void btc_mesh_time_scene_client_call_handler(btc_msg_t *msg); + +void btc_mesh_time_scene_client_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_time_scene_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_mesh_time_scene_client_publish_callback(u32_t opcode, struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf); + +void bt_mesh_callback_time_scene_status_to_btc(u32_t opcode, u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +#endif /* _BTC_BLE_MESH_TIME_SCENE_MODEL_H_ */ + diff --git a/components/bt/ble_mesh/mesh_core/access.c b/components/bt/ble_mesh/mesh_core/access.c new file mode 100644 index 0000000000..6184d30091 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/access.c @@ -0,0 +1,1043 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_ACCESS) + +#include "mesh_util.h" +#include "mesh_buf.h" +#include "mesh_trace.h" +#include "mesh_kernel.h" +#include "mesh_access.h" +#include "mesh_main.h" + +#include "mesh.h" +#include "adv.h" +#include "net.h" +#include "lpn.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" + +#include "mesh_common.h" +#include "generic_client.h" +#include "sensor_client.h" +#include "time_scene_client.h" +#include "lighting_client.h" +#include "provisioner_main.h" + +#define BLE_MESH_SDU_MAX_LEN 384 + +static const struct bt_mesh_comp *dev_comp; +static u16_t dev_primary_addr; + +static const struct { + const u16_t id; + int (*const init)(struct bt_mesh_model *model, bool primary); +} model_init[] = { + { BLE_MESH_MODEL_ID_CFG_SRV, bt_mesh_cfg_srv_init }, + { BLE_MESH_MODEL_ID_HEALTH_SRV, bt_mesh_health_srv_init }, +#if defined(CONFIG_BLE_MESH_CFG_CLI) + { BLE_MESH_MODEL_ID_CFG_CLI, bt_mesh_cfg_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_HEALTH_CLI) + { BLE_MESH_MODEL_ID_HEALTH_CLI, bt_mesh_health_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) + { BLE_MESH_MODEL_ID_GEN_ONOFF_CLI, bt_mesh_gen_onoff_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_LEVEL_CLI) + { BLE_MESH_MODEL_ID_GEN_LEVEL_CLI, bt_mesh_gen_level_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_DEF_TRANS_TIME_CLI) + { BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI, bt_mesh_gen_def_trans_time_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_POWER_ONOFF_CLI) + { BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI, bt_mesh_gen_pwr_onoff_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_POWER_LEVEL_CLI) + { BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI, bt_mesh_gen_pwr_level_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_BATTERY_CLI) + { BLE_MESH_MODEL_ID_GEN_BATTERY_CLI, bt_mesh_gen_battery_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_LOCATION_CLI) + { BLE_MESH_MODEL_ID_GEN_LOCATION_CLI, bt_mesh_gen_location_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_PROPERTY_CLI) + { BLE_MESH_MODEL_ID_GEN_PROP_CLI, bt_mesh_gen_property_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_SENSOR_CLI) + { BLE_MESH_MODEL_ID_SENSOR_CLI, bt_mesh_sensor_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_TIME_CLI) + { BLE_MESH_MODEL_ID_TIME_CLI, bt_mesh_time_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_SCENE_CLI) + { BLE_MESH_MODEL_ID_SCENE_CLI, bt_mesh_scene_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_SCHEDULER_CLI) + { BLE_MESH_MODEL_ID_SCHEDULER_CLI, bt_mesh_scheduler_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_LIGHTNESS_CLI) + { BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI, bt_mesh_light_lightness_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_CTL_CLI) + { BLE_MESH_MODEL_ID_LIGHT_CTL_CLI, bt_mesh_light_ctl_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_HSL_CLI) + { BLE_MESH_MODEL_ID_LIGHT_HSL_CLI, bt_mesh_light_hsl_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_XYL_CLI) + { BLE_MESH_MODEL_ID_LIGHT_XYL_CLI, bt_mesh_light_xyl_cli_init }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_LC_CLI) + { BLE_MESH_MODEL_ID_LIGHT_LC_CLI, bt_mesh_light_lc_cli_init }, +#endif +}; + +void bt_mesh_model_foreach(void (*func)(struct bt_mesh_model *mod, + struct bt_mesh_elem *elem, + bool vnd, bool primary, + void *user_data), + void *user_data) +{ + int i, j; + + for (i = 0; i < dev_comp->elem_count; i++) { + struct bt_mesh_elem *elem = &dev_comp->elem[i]; + + for (j = 0; j < elem->model_count; j++) { + struct bt_mesh_model *model = &elem->models[j]; + + func(model, elem, false, i == 0, user_data); + } + + for (j = 0; j < elem->vnd_model_count; j++) { + struct bt_mesh_model *model = &elem->vnd_models[j]; + + func(model, elem, true, i == 0, user_data); + } + } +} + +s32_t bt_mesh_model_pub_period_get(struct bt_mesh_model *mod) +{ + int period = 0; + + if (!mod->pub) { + BT_ERR("%s, Model has no publication support", __func__); + return 0; + } + + switch (mod->pub->period >> 6) { + case 0x00: + /* 1 step is 100 ms */ + period = K_MSEC((mod->pub->period & BIT_MASK(6)) * 100U); + break; + case 0x01: + /* 1 step is 1 second */ + period = K_SECONDS(mod->pub->period & BIT_MASK(6)); + break; + case 0x02: + /* 1 step is 10 seconds */ + period = K_SECONDS((mod->pub->period & BIT_MASK(6)) * 10U); + break; + case 0x03: + /* 1 step is 10 minutes */ + period = K_MINUTES((mod->pub->period & BIT_MASK(6)) * 10U); + break; + default: + BT_ERR("%s, Unknown model publication period", __func__); + return 0; + } + + if (mod->pub->fast_period) { + return period >> mod->pub->period_div; + } else { + return period; + } +} + +static s32_t next_period(struct bt_mesh_model *mod) +{ + struct bt_mesh_model_pub *pub = mod->pub; + u32_t elapsed, period; + + if (!pub) { + BT_ERR("%s, Model has no publication support", __func__); + return -ENOTSUP; + } + + period = bt_mesh_model_pub_period_get(mod); + if (!period) { + return 0; + } + + elapsed = k_uptime_get_32() - pub->period_start; + + BT_DBG("Publishing took %ums", elapsed); + + if (elapsed > period) { + BT_WARN("Publication sending took longer than the period"); + /* Return smallest positive number since 0 means disabled */ + return K_MSEC(1); + } + + return period - elapsed; +} + +static void publish_sent(int err, void *user_data) +{ + struct bt_mesh_model *mod = user_data; + s32_t delay; + + BT_DBG("err %d", err); + + if (!mod->pub) { + BT_ERR("%s, Model has no publication support", __func__); + return; + } + + if (mod->pub->count) { + delay = BLE_MESH_PUB_TRANSMIT_INT(mod->pub->retransmit); + } else { + delay = next_period(mod); + } + + if (delay) { + BT_DBG("Publishing next time in %dms", delay); + k_delayed_work_submit(&mod->pub->timer, delay); + } +} + +static const struct bt_mesh_send_cb pub_sent_cb = { + .end = publish_sent, +}; + +static int publish_retransmit(struct bt_mesh_model *mod) +{ + struct bt_mesh_model_pub *pub = mod->pub; + if (!pub) { + BT_ERR("%s, Model has no publication support", __func__); + return -ENOTSUP; + } + + struct bt_mesh_app_key *key = NULL; + struct net_buf_simple *sdu = NULL; + struct bt_mesh_msg_ctx ctx = { + .addr = pub->addr, + .send_ttl = pub->ttl, + .model = mod, + .srv_send = (pub->dev_role == NODE ? true : false), + }; + struct bt_mesh_net_tx tx = { + .ctx = &ctx, + .src = bt_mesh_model_elem(mod)->addr, + .xmit = bt_mesh_net_transmit_get(), + .friend_cred = pub->cred, + }; + int err; + + key = bt_mesh_app_key_find(pub->key); + if (!key) { + BT_ERR("%s, Failed to find AppKey", __func__); + return -EADDRNOTAVAIL; + } + + tx.sub = bt_mesh_subnet_get(key->net_idx); + + ctx.net_idx = key->net_idx; + ctx.app_idx = key->app_idx; + + sdu = bt_mesh_alloc_buf(pub->msg->len + 4); + if (!sdu) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + net_buf_simple_add_mem(sdu, pub->msg->data, pub->msg->len); + + pub->count--; + + err = bt_mesh_trans_send(&tx, sdu, &pub_sent_cb, mod); + + bt_mesh_free_buf(sdu); + return err; +} + +static void mod_publish(struct k_work *work) +{ + struct bt_mesh_model_pub *pub = CONTAINER_OF(work, + struct bt_mesh_model_pub, + timer.work); + s32_t period_ms; + int err; + + BT_DBG("%s", __func__); + + period_ms = bt_mesh_model_pub_period_get(pub->mod); + BT_DBG("period %u ms", period_ms); + + if (pub->count) { + err = publish_retransmit(pub->mod); + if (err) { + BT_ERR("%s, Failed to retransmit (err %d)", __func__, err); + + pub->count = 0U; + + /* Continue with normal publication */ + if (period_ms) { + k_delayed_work_submit(&pub->timer, period_ms); + } + } + + return; + } + + if (!period_ms) { + return; + } + + __ASSERT_NO_MSG(pub->update != NULL); + + pub->period_start = k_uptime_get_32(); + + /* Callback the model publish update event to the application layer. + * In the event, users can update the context of the publish message + * which will be published in the next period. + */ + err = pub->update(pub->mod); + if (err) { + BT_ERR("%s, Failed to update publication message", __func__); + return; + } + + err = bt_mesh_model_publish(pub->mod); + if (err) { + BT_ERR("%s, Publishing failed (err %d)", __func__, err); + } + + if (pub->count) { + /* Retransmissions also control the timer */ + k_delayed_work_cancel(&pub->timer); + } +} + +struct bt_mesh_elem *bt_mesh_model_elem(struct bt_mesh_model *mod) +{ + return &dev_comp->elem[mod->elem_idx]; +} + +struct bt_mesh_model *bt_mesh_model_get(bool vnd, u8_t elem_idx, u8_t mod_idx) +{ + struct bt_mesh_elem *elem; + + if (!dev_comp) { + BT_ERR("%s, dev_comp is not initialized", __func__); + return NULL; + } + + if (elem_idx >= dev_comp->elem_count) { + BT_ERR("%s, Invalid element index %u", __func__, elem_idx); + return NULL; + } + + elem = &dev_comp->elem[elem_idx]; + + if (vnd) { + if (mod_idx >= elem->vnd_model_count) { + BT_ERR("%s, Invalid vendor model index %u", __func__, mod_idx); + return NULL; + } + + return &elem->vnd_models[mod_idx]; + } else { + if (mod_idx >= elem->model_count) { + BT_ERR("%s, Invalid SIG model index %u", __func__, mod_idx); + return NULL; + } + + return &elem->models[mod_idx]; + } +} + +static void mod_init(struct bt_mesh_model *mod, struct bt_mesh_elem *elem, + bool vnd, bool primary, void *user_data) +{ + int i; + + mod->elem = elem; + + if (mod->pub) { + mod->pub->mod = mod; + k_delayed_work_init(&mod->pub->timer, mod_publish); + } + + for (i = 0; i < ARRAY_SIZE(mod->keys); i++) { + mod->keys[i] = BLE_MESH_KEY_UNUSED; + } + + mod->flags = 0; + mod->elem_idx = elem - dev_comp->elem; + if (vnd) { + mod->model_idx = mod - elem->vnd_models; + } else { + mod->model_idx = mod - elem->models; + } + + if (vnd) { + return; + } + + for (i = 0; i < ARRAY_SIZE(model_init); i++) { + if (model_init[i].id == mod->id) { + model_init[i].init(mod, primary); + } + } +} + +int bt_mesh_comp_register(const struct bt_mesh_comp *comp) +{ + /* There must be at least one element */ + if (!comp->elem_count) { + return -EINVAL; + } + + dev_comp = comp; + + bt_mesh_model_foreach(mod_init, NULL); + + return 0; +} + +void bt_mesh_comp_provision(u16_t addr) +{ + int i; + + dev_primary_addr = addr; + + BT_DBG("addr 0x%04x elem_count %u", addr, dev_comp->elem_count); + + for (i = 0; i < dev_comp->elem_count; i++) { + struct bt_mesh_elem *elem = &dev_comp->elem[i]; + + elem->addr = addr++; + + BT_DBG("addr 0x%04x mod_count %u vnd_mod_count %u", + elem->addr, elem->model_count, elem->vnd_model_count); + } +} + +void bt_mesh_comp_unprovision(void) +{ + BT_DBG("%s", __func__); + + dev_primary_addr = BLE_MESH_ADDR_UNASSIGNED; + + bt_mesh_model_foreach(mod_init, NULL); +} + +u16_t bt_mesh_primary_addr(void) +{ + return dev_primary_addr; +} + +u16_t *bt_mesh_model_find_group(struct bt_mesh_model *mod, u16_t addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mod->groups); i++) { + if (mod->groups[i] == addr) { + return &mod->groups[i]; + } + } + + return NULL; +} + +static struct bt_mesh_model *bt_mesh_elem_find_group(struct bt_mesh_elem *elem, + u16_t group_addr) +{ + struct bt_mesh_model *model; + u16_t *match; + int i; + + for (i = 0; i < elem->model_count; i++) { + model = &elem->models[i]; + + match = bt_mesh_model_find_group(model, group_addr); + if (match) { + return model; + } + } + + for (i = 0; i < elem->vnd_model_count; i++) { + model = &elem->vnd_models[i]; + + match = bt_mesh_model_find_group(model, group_addr); + if (match) { + return model; + } + } + + return NULL; +} + +struct bt_mesh_elem *bt_mesh_elem_find(u16_t addr) +{ + int i; + + for (i = 0; i < dev_comp->elem_count; i++) { + struct bt_mesh_elem *elem = &dev_comp->elem[i]; + + if (BLE_MESH_ADDR_IS_GROUP(addr) || + BLE_MESH_ADDR_IS_VIRTUAL(addr)) { + if (bt_mesh_elem_find_group(elem, addr)) { + return elem; + } + } else if (elem->addr == addr) { + return elem; + } + } + + return NULL; +} + +u8_t bt_mesh_elem_count(void) +{ + return dev_comp->elem_count; +} + +static bool model_has_key(struct bt_mesh_model *mod, u16_t key) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mod->keys); i++) { + if (mod->keys[i] == key) { + return true; + } + } + + return false; +} + +static const struct bt_mesh_model_op *find_op(struct bt_mesh_model *models, + u8_t model_count, u16_t dst, + u16_t app_idx, u32_t opcode, + struct bt_mesh_model **model) +{ + u8_t i; + + for (i = 0U; i < model_count; i++) { + const struct bt_mesh_model_op *op; + + *model = &models[i]; + + if (BLE_MESH_ADDR_IS_GROUP(dst) || + BLE_MESH_ADDR_IS_VIRTUAL(dst)) { + if (!bt_mesh_model_find_group(*model, dst)) { + continue; + } + } + + if (!model_has_key(*model, app_idx)) { + continue; + } + + for (op = (*model)->op; op->func; op++) { + if (op->opcode == opcode) { + return op; + } + } + } + + *model = NULL; + return NULL; +} + +static int get_opcode(struct net_buf_simple *buf, u32_t *opcode) +{ + switch (buf->data[0] >> 6) { + case 0x00: + case 0x01: + if (buf->data[0] == 0x7f) { + BT_ERR("%s, Ignoring RFU OpCode", __func__); + return -EINVAL; + } + + *opcode = net_buf_simple_pull_u8(buf); + return 0; + case 0x02: + if (buf->len < 2) { + BT_ERR("%s, Too short payload for 2-octet OpCode", __func__); + return -EINVAL; + } + + *opcode = net_buf_simple_pull_be16(buf); + return 0; + case 0x03: + if (buf->len < 3) { + BT_ERR("%s, Too short payload for 3-octet OpCode", __func__); + return -EINVAL; + } + + *opcode = net_buf_simple_pull_u8(buf) << 16; + *opcode |= net_buf_simple_pull_le16(buf); + return 0; + } + + return -EINVAL; +} + +bool bt_mesh_fixed_group_match(u16_t addr) +{ + /* Check for fixed group addresses */ + switch (addr) { + case BLE_MESH_ADDR_ALL_NODES: + return true; + case BLE_MESH_ADDR_PROXIES: + return (bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_ENABLED); + case BLE_MESH_ADDR_FRIENDS: + return (bt_mesh_friend_get() == BLE_MESH_FRIEND_ENABLED); + case BLE_MESH_ADDR_RELAYS: + return (bt_mesh_relay_get() == BLE_MESH_RELAY_ENABLED); + default: + return false; + } +} + +u32_t mesh_opcode; + +void bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf) +{ + struct bt_mesh_model *models, *model; + const struct bt_mesh_model_op *op; + u32_t opcode; + u8_t count; + int i; + + BT_DBG("app_idx 0x%04x src 0x%04x dst 0x%04x", rx->ctx.app_idx, + rx->ctx.addr, rx->ctx.recv_dst); + BT_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (get_opcode(buf, &opcode) < 0) { + BT_WARN("%s, Unable to decode OpCode", __func__); + return; + } + + BT_DBG("OpCode 0x%08x", opcode); + + mesh_opcode = opcode; + + for (i = 0; i < dev_comp->elem_count; i++) { + struct bt_mesh_elem *elem = &dev_comp->elem[i]; + + if (BLE_MESH_ADDR_IS_UNICAST(rx->ctx.recv_dst)) { + if (elem->addr != rx->ctx.recv_dst) { + continue; + } + } else if (BLE_MESH_ADDR_IS_GROUP(rx->ctx.recv_dst) || + BLE_MESH_ADDR_IS_VIRTUAL(rx->ctx.recv_dst)) { + /* find_op() will do proper model/group matching */ + } else if (i != 0 || + !bt_mesh_fixed_group_match(rx->ctx.recv_dst)) { + continue; + } + + /* SIG models cannot contain 3-byte (vendor) OpCodes, and + * vendor models cannot contain SIG (1- or 2-byte) OpCodes, so + * we only need to do the lookup in one of the model lists. + */ + if (opcode < 0x10000) { + models = elem->models; + count = elem->model_count; + } else { + models = elem->vnd_models; + count = elem->vnd_model_count; + } + + op = find_op(models, count, rx->ctx.recv_dst, rx->ctx.app_idx, + opcode, &model); + if (op) { + struct net_buf_simple_state state; + + if (buf->len < op->min_len) { + BT_ERR("%s, Too short message for OpCode 0x%08x", + __func__, opcode); + continue; + } + + /* The callback will likely parse the buffer, so + * store the parsing state in case multiple models + * receive the message. + */ + net_buf_simple_save(buf, &state); + + /** Changed by Espressif, here we update recv_op with the + * value opcode got from the buf. + */ + rx->ctx.recv_op = opcode; + /** Changed by Espressif, we update the model pointer to the + * found model when we received a message. + */ + rx->ctx.model = model; + /** Changed by Espressif, we update the srv_send flag to be + * true when we received a message. This flag will be used + * when a server model sends a status message and will + * have no impact on the client sent messages. + */ + rx->ctx.srv_send = true; + + op->func(model, &rx->ctx, buf); + net_buf_simple_restore(buf, &state); + + } else { + BT_DBG("No OpCode 0x%08x for elem %d", opcode, i); + } + } +} + +void bt_mesh_model_msg_init(struct net_buf_simple *msg, u32_t opcode) +{ + net_buf_simple_init(msg, 0); + + if (opcode < 0x100) { + /* 1-byte OpCode */ + net_buf_simple_add_u8(msg, opcode); + return; + } + + if (opcode < 0x10000) { + /* 2-byte OpCode */ + net_buf_simple_add_be16(msg, opcode); + return; + } + + /* 3-byte OpCode */ + net_buf_simple_add_u8(msg, ((opcode >> 16) & 0xff)); + net_buf_simple_add_le16(msg, opcode & 0xffff); +} + +static int model_send(struct bt_mesh_model *model, + struct bt_mesh_net_tx *tx, bool implicit_bind, + struct net_buf_simple *msg, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + bool check = false; + u8_t role; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", tx->ctx->net_idx, + tx->ctx->app_idx, tx->ctx->addr); + BT_DBG("len %u: %s", msg->len, bt_hex(msg->data, msg->len)); + + role = bt_mesh_get_model_role(model, tx->ctx->srv_send); + if (role == ROLE_NVAL) { + BT_ERR("%s, Failed to get model role", __func__); + return -EINVAL; + } + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (role == NODE) { + if (!bt_mesh_is_provisioned()) { + BT_ERR("%s, Local node is not yet provisioned", __func__); + return -EAGAIN; + } + if (!bt_mesh_is_provisioner_en()) { + check = true; + } + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (role == PROVISIONER) { + if (!provisioner_check_msg_dst_addr(tx->ctx->addr)) { + BT_ERR("%s, Failed to check DST", __func__); + return -EINVAL; + } + if (bt_mesh_is_provisioner_en()) { + check = true; + } + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (role == PROVISIONER) { + if (!provisioner_check_msg_dst_addr(tx->ctx->addr)) { + BT_ERR("%s, Failed to check DST", __func__); + return -EINVAL; + } + if (bt_mesh_is_provisioner_en()) { + check = true; + } + } else { + if (!bt_mesh_is_provisioned()) { + BT_ERR("%s, Local node is not yet provisioned", __func__); + return -EAGAIN; + } + check = true; + } +#endif + + if (!check) { + BT_ERR("%s, fail", __func__); + return -EINVAL; + } + + if (net_buf_simple_tailroom(msg) < 4) { + BT_ERR("%s, Not enough tailroom for TransMIC", __func__); + return -EINVAL; + } + + if (msg->len > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SDU_MAX_LEN) - 4) { + BT_ERR("%s, Too big message", __func__); + return -EMSGSIZE; + } + + if (!implicit_bind && !model_has_key(model, tx->ctx->app_idx)) { + BT_ERR("%s, Model not bound to AppKey 0x%04x", __func__, tx->ctx->app_idx); + return -EINVAL; + } + + return bt_mesh_trans_send(tx, msg, cb, cb_data); +} + +int bt_mesh_model_send(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *msg, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + struct bt_mesh_subnet *sub = NULL; + u8_t role; + + role = bt_mesh_get_model_role(model, ctx->srv_send); + if (role == ROLE_NVAL) { + BT_ERR("%s, Failed to get model role", __func__); + return -EINVAL; + } + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (role == NODE) { + if (!bt_mesh_is_provisioner_en()) { + sub = bt_mesh_subnet_get(ctx->net_idx); + } + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + sub = provisioner_subnet_get(ctx->net_idx); + } + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (role == NODE) { + sub = bt_mesh_subnet_get(ctx->net_idx); + } else if (role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + sub = provisioner_subnet_get(ctx->net_idx); + } + } else if (role == FAST_PROV) { +#if CONFIG_BLE_MESH_FAST_PROV + sub = get_fast_prov_subnet(ctx->net_idx); +#endif + } +#endif + + if (!sub) { + BT_ERR("%s, Failed to get subnet", __func__); + return -EINVAL; + } + + ctx->model = model; + + struct bt_mesh_net_tx tx = { + .sub = sub, + .ctx = ctx, + .src = bt_mesh_model_elem(model)->addr, + .xmit = bt_mesh_net_transmit_get(), + .friend_cred = 0, + }; + + return model_send(model, &tx, false, msg, cb, cb_data); +} + +int bt_mesh_model_publish(struct bt_mesh_model *model) +{ + struct bt_mesh_model_pub *pub = model->pub; + struct bt_mesh_app_key *key = NULL; + struct net_buf_simple *sdu = NULL; + struct bt_mesh_msg_ctx ctx = {0}; + struct bt_mesh_net_tx tx = { + .sub = NULL, + .ctx = &ctx, + .src = bt_mesh_model_elem(model)->addr, + .xmit = bt_mesh_net_transmit_get(), + }; + int err; + + BT_DBG("%s", __func__); + + if (!pub) { + BT_ERR("%s, Model has no publication support", __func__); + return -ENOTSUP; + } + + if (pub->addr == BLE_MESH_ADDR_UNASSIGNED) { + BT_WARN("%s, Unassigned model publish address", __func__); + return -EADDRNOTAVAIL; + } + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (pub->dev_role == NODE) { + if (bt_mesh_is_provisioned()) { + key = bt_mesh_app_key_find(pub->key); + } + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (pub->dev_role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + key = provisioner_app_key_find(pub->key); + } + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (pub->dev_role == NODE) { + if (bt_mesh_is_provisioned()) { + key = bt_mesh_app_key_find(pub->key); + } + } else if (pub->dev_role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + key = provisioner_app_key_find(pub->key); + } + } +#endif + + if (!key) { + BT_ERR("%s, Failed to get AppKey", __func__); + return -EADDRNOTAVAIL; + } + + if (pub->msg->len + 4 > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SDU_MAX_LEN)) { + BT_ERR("%s, Message does not fit maximum SDU size", __func__); + return -EMSGSIZE; + } + + if (pub->count) { + BT_WARN("%s, Clearing publish retransmit timer", __func__); + k_delayed_work_cancel(&pub->timer); + } + + ctx.addr = pub->addr; + ctx.send_ttl = pub->ttl; + ctx.net_idx = key->net_idx; + ctx.app_idx = key->app_idx; + ctx.srv_send = pub->dev_role == NODE ? true : false; + + tx.friend_cred = pub->cred; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (pub->dev_role == NODE) { + if (bt_mesh_is_provisioned()) { + tx.sub = bt_mesh_subnet_get(ctx.net_idx); + } + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (pub->dev_role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + tx.sub = provisioner_subnet_get(ctx.net_idx); + } + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (pub->dev_role == NODE) { + if (bt_mesh_is_provisioned()) { + tx.sub = bt_mesh_subnet_get(ctx.net_idx); + } + } else if (pub->dev_role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + tx.sub = provisioner_subnet_get(ctx.net_idx); + } + } +#endif + + if (!tx.sub) { + BT_ERR("%s, Failed to get subnet", __func__); + return -EADDRNOTAVAIL; + } + + pub->count = BLE_MESH_PUB_TRANSMIT_COUNT(pub->retransmit); + + BT_DBG("Publish Retransmit Count %u Interval %ums", pub->count, + BLE_MESH_PUB_TRANSMIT_INT(pub->retransmit)); + + sdu = bt_mesh_alloc_buf(pub->msg->len + 4); + if (!sdu) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + net_buf_simple_add_mem(sdu, pub->msg->data, pub->msg->len); + + err = model_send(model, &tx, true, sdu, &pub_sent_cb, model); + if (err) { + /* Don't try retransmissions for this publish attempt */ + pub->count = 0U; + /* Make sure the publish timer gets reset */ + publish_sent(err, model); + } + + bt_mesh_free_buf(sdu); + return err; +} + +struct bt_mesh_model *bt_mesh_model_find_vnd(struct bt_mesh_elem *elem, + u16_t company, u16_t id) +{ + u8_t i; + + for (i = 0U; i < elem->vnd_model_count; i++) { + if (elem->vnd_models[i].vnd.company == company && + elem->vnd_models[i].vnd.id == id) { + return &elem->vnd_models[i]; + } + } + + return NULL; +} + +struct bt_mesh_model *bt_mesh_model_find(struct bt_mesh_elem *elem, + u16_t id) +{ + u8_t i; + + for (i = 0U; i < elem->model_count; i++) { + if (elem->models[i].id == id) { + return &elem->models[i]; + } + } + + return NULL; +} + +const struct bt_mesh_comp *bt_mesh_comp_get(void) +{ + return dev_comp; +} diff --git a/components/bt/ble_mesh/mesh_core/access.h b/components/bt/ble_mesh/mesh_core/access.h new file mode 100644 index 0000000000..114c1662be --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/access.h @@ -0,0 +1,60 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ACCESS_H_ +#define _ACCESS_H_ + +#include "mesh_access.h" +#include "mesh_buf.h" +#include "net.h" + +/* bt_mesh_model.flags */ +enum { + BLE_MESH_MOD_BIND_PENDING = BIT(0), + BLE_MESH_MOD_SUB_PENDING = BIT(1), + BLE_MESH_MOD_PUB_PENDING = BIT(2), +}; + +void bt_mesh_elem_register(struct bt_mesh_elem *elem, u8_t count); + +u8_t bt_mesh_elem_count(void); + +/* Find local element based on unicast or group address */ +struct bt_mesh_elem *bt_mesh_elem_find(u16_t addr); + +struct bt_mesh_model *bt_mesh_model_find_vnd(struct bt_mesh_elem *elem, + u16_t company, u16_t id); +struct bt_mesh_model *bt_mesh_model_find(struct bt_mesh_elem *elem, + u16_t id); + +u16_t *bt_mesh_model_find_group(struct bt_mesh_model *mod, u16_t addr); + +bool bt_mesh_fixed_group_match(u16_t addr); + +void bt_mesh_model_foreach(void (*func)(struct bt_mesh_model *mod, + struct bt_mesh_elem *elem, + bool vnd, bool primary, + void *user_data), + void *user_data); + +s32_t bt_mesh_model_pub_period_get(struct bt_mesh_model *mod); + +void bt_mesh_comp_provision(u16_t addr); +void bt_mesh_comp_unprovision(void); + +u16_t bt_mesh_primary_addr(void); + +const struct bt_mesh_comp *bt_mesh_comp_get(void); + +struct bt_mesh_model *bt_mesh_model_get(bool vnd, u8_t elem_idx, u8_t mod_idx); + +void bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf); + +int bt_mesh_comp_register(const struct bt_mesh_comp *comp); + +#endif /* _ACCESS_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/adv.c b/components/bt/ble_mesh/mesh_core/adv.c new file mode 100644 index 0000000000..87ef363061 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/adv.c @@ -0,0 +1,411 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" + +#include "osi/thread.h" +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_ADV) + +#include "mesh_util.h" +#include "mesh_buf.h" +#include "mesh_bearer_adapt.h" +#include "mesh_trace.h" +#include "mesh_hci.h" + +#include "mesh.h" +#include "adv.h" +#include "net.h" +#include "foundation.h" +#include "beacon.h" +#include "prov.h" +#include "proxy.h" + +#include "provisioner_prov.h" +#include "provisioner_proxy.h" +#include "provisioner_beacon.h" + +/* Convert from ms to 0.625ms units */ +#define ADV_SCAN_UNIT(_ms) ((_ms) * 8 / 5) + +/* Window and Interval are equal for continuous scanning */ +#define MESH_SCAN_INTERVAL 0x20 +#define MESH_SCAN_WINDOW 0x20 + +/* Pre-5.0 controllers enforce a minimum interval of 100ms + * whereas 5.0+ controllers can go down to 20ms. + */ +#define ADV_INT_DEFAULT_MS 100 +#define ADV_INT_FAST_MS 20 + +#if defined(CONFIG_BT_HOST_CRYPTO) +#define ADV_STACK_SIZE 1024 +#else +#define ADV_STACK_SIZE 768 +#endif + +static xQueueHandle xBleMeshQueue; +static const bt_mesh_addr_t *dev_addr; + +static const u8_t adv_type[] = { + [BLE_MESH_ADV_PROV] = BLE_MESH_DATA_MESH_PROV, + [BLE_MESH_ADV_DATA] = BLE_MESH_DATA_MESH_MESSAGE, + [BLE_MESH_ADV_BEACON] = BLE_MESH_DATA_MESH_BEACON, + [BLE_MESH_ADV_URI] = BLE_MESH_DATA_URI, +}; + +NET_BUF_POOL_DEFINE(adv_buf_pool, CONFIG_BLE_MESH_ADV_BUF_COUNT + 3 * CONFIG_BLE_MESH_PBA_SAME_TIME, + BLE_MESH_ADV_DATA_SIZE, BLE_MESH_ADV_USER_DATA_SIZE, NULL); + +static struct bt_mesh_adv adv_pool[CONFIG_BLE_MESH_ADV_BUF_COUNT + 3 * CONFIG_BLE_MESH_PBA_SAME_TIME]; + +static struct bt_mesh_adv *adv_alloc(int id) +{ + return &adv_pool[id]; +} + +static inline void adv_send_start(u16_t duration, int err, + const struct bt_mesh_send_cb *cb, + void *cb_data) +{ + if (cb && cb->start) { + cb->start(duration, err, cb_data); + } +} + +static inline void adv_send_end(int err, const struct bt_mesh_send_cb *cb, + void *cb_data) +{ + if (cb && cb->end) { + cb->end(err, cb_data); + } +} + +static inline int adv_send(struct net_buf *buf) +{ + const s32_t adv_int_min = ((bt_mesh_dev.hci_version >= BLE_MESH_HCI_VERSION_5_0) ? + ADV_INT_FAST_MS : ADV_INT_DEFAULT_MS); + const struct bt_mesh_send_cb *cb = BLE_MESH_ADV(buf)->cb; + void *cb_data = BLE_MESH_ADV(buf)->cb_data; + struct bt_mesh_adv_param param = {0}; + u16_t duration, adv_int; + struct bt_mesh_adv_data ad = {0}; + int err; + + adv_int = MAX(adv_int_min, + BLE_MESH_TRANSMIT_INT(BLE_MESH_ADV(buf)->xmit)); + duration = (BLE_MESH_TRANSMIT_COUNT(BLE_MESH_ADV(buf)->xmit) + 1) * + (adv_int + 10); + + BT_DBG("type %u len %u: %s", BLE_MESH_ADV(buf)->type, + buf->len, bt_hex(buf->data, buf->len)); + BT_DBG("count %u interval %ums duration %ums", + BLE_MESH_TRANSMIT_COUNT(BLE_MESH_ADV(buf)->xmit) + 1, adv_int, + duration); + + ad.type = adv_type[BLE_MESH_ADV(buf)->type]; + ad.data_len = buf->len; + ad.data = buf->data; + + param.options = 0U; + param.interval_min = ADV_SCAN_UNIT(adv_int); + param.interval_max = param.interval_min; + + err = bt_le_adv_start(¶m, &ad, 1, NULL, 0); + net_buf_unref(buf); + adv_send_start(duration, err, cb, cb_data); + if (err) { + BT_ERR("%s, Advertising failed: err %d", __func__, err); + return err; + } + + BT_DBG("Advertising started. Sleeping %u ms", duration); + + k_sleep(K_MSEC(duration)); + + err = bt_le_adv_stop(); + adv_send_end(err, cb, cb_data); + if (err) { + BT_ERR("%s, Stop advertising failed: err %d", __func__, err); + return 0; + } + + BT_DBG("Advertising stopped"); + return 0; +} + +static void adv_thread(void *p) +{ + struct net_buf **buf = NULL; + bt_mesh_msg_t msg = {0}; + int status; + + BT_DBG("started"); + + buf = (struct net_buf **)(&msg.arg); + + while (1) { + *buf = NULL; +#if CONFIG_BLE_MESH_NODE + if (IS_ENABLED(CONFIG_BLE_MESH_PROXY)) { + xQueueReceive(xBleMeshQueue, &msg, K_NO_WAIT); + while (!(*buf)) { + s32_t timeout; + BT_DBG("Proxy advertising start"); + timeout = bt_mesh_proxy_adv_start(); + BT_DBG("Proxy Advertising up to %d ms", timeout); + xQueueReceive(xBleMeshQueue, &msg, timeout); + BT_DBG("Proxy advertising stop"); + bt_mesh_proxy_adv_stop(); + } + } else { + xQueueReceive(xBleMeshQueue, &msg, (portTickType)portMAX_DELAY); + } +#else + xQueueReceive(xBleMeshQueue, &msg, (portTickType)portMAX_DELAY); +#endif + + if (!(*buf)) { + continue; + } + + /* busy == 0 means this was canceled */ + if (BLE_MESH_ADV(*buf)->busy) { + BLE_MESH_ADV(*buf)->busy = 0U; + status = adv_send(*buf); + if (status) { + if (xQueueSendToFront(xBleMeshQueue, &msg, K_NO_WAIT) != pdTRUE) { + BT_ERR("%s, xQueueSendToFront failed", __func__); + } + } + } + + /* Give other threads a chance to run */ + taskYIELD(); + } +} + +void bt_mesh_adv_update(void) +{ + BT_DBG("%s", __func__); + bt_mesh_msg_t msg = {0}; + bt_mesh_task_post(&msg, 0); +} + +struct net_buf *bt_mesh_adv_create_from_pool(struct net_buf_pool *pool, + bt_mesh_adv_alloc_t get_id, + enum bt_mesh_adv_type type, + u8_t xmit, s32_t timeout) +{ + struct bt_mesh_adv *adv; + struct net_buf *buf; + + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_SUSPENDED)) { + BT_WARN("Refusing to allocate buffer while suspended"); + return NULL; + } + + buf = net_buf_alloc(pool, timeout); + if (!buf) { + return NULL; + } + + BT_DBG("%s, pool = %p, buf_count = %d, uinit_count = %d", __func__, + buf->pool, pool->buf_count, pool->uninit_count); + + adv = get_id(net_buf_id(buf)); + BLE_MESH_ADV(buf) = adv; + + (void)memset(adv, 0, sizeof(*adv)); + + adv->type = type; + adv->xmit = xmit; + + return buf; +} + +struct net_buf *bt_mesh_adv_create(enum bt_mesh_adv_type type, u8_t xmit, + s32_t timeout) +{ + return bt_mesh_adv_create_from_pool(&adv_buf_pool, adv_alloc, type, + xmit, timeout); +} + +void bt_mesh_task_post(bt_mesh_msg_t *msg, uint32_t timeout) +{ + BT_DBG("%s", __func__); + if (xQueueSend(xBleMeshQueue, msg, timeout) != pdTRUE) { + BT_ERR("%s, Failed to post msg to queue", __func__); + } +} + +void bt_mesh_adv_send(struct net_buf *buf, const struct bt_mesh_send_cb *cb, + void *cb_data) +{ + BT_DBG("type 0x%02x len %u: %s", BLE_MESH_ADV(buf)->type, buf->len, + bt_hex(buf->data, buf->len)); + + BLE_MESH_ADV(buf)->cb = cb; + BLE_MESH_ADV(buf)->cb_data = cb_data; + BLE_MESH_ADV(buf)->busy = 1U; + + bt_mesh_msg_t msg = {0}; + msg.arg = (void *)net_buf_ref(buf); + bt_mesh_task_post(&msg, portMAX_DELAY); +} + +const bt_mesh_addr_t *bt_mesh_pba_get_addr(void) +{ + return dev_addr; +} + +static void bt_mesh_scan_cb(const bt_mesh_addr_t *addr, s8_t rssi, + u8_t adv_type, struct net_buf_simple *buf) +{ +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + u16_t uuid = 0; +#endif + + if (adv_type != BLE_MESH_ADV_NONCONN_IND && adv_type != BLE_MESH_ADV_IND) { + return; + } + + BT_DBG("%s, len %u: %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + dev_addr = addr; + + while (buf->len > 1) { + struct net_buf_simple_state state; + u8_t len, type; + + len = net_buf_simple_pull_u8(buf); + /* Check for early termination */ + if (len == 0U) { + return; + } + + if (len > buf->len) { + BT_WARN("AD malformed"); + return; + } + + net_buf_simple_save(buf, &state); + + type = net_buf_simple_pull_u8(buf); + + buf->len = len - 1; + +#if 0 + /* TODO: Check with BLE Mesh BQB test cases */ + if ((type == BLE_MESH_DATA_MESH_PROV || type == BLE_MESH_DATA_MESH_MESSAGE || + type == BLE_MESH_DATA_MESH_BEACON) && (adv_type != BLE_MESH_ADV_NONCONN_IND)) { + BT_DBG("%s, ignore BLE Mesh packet (type 0x%02x) with adv_type 0x%02x", + __func__, type, adv_type); + return; + } +#endif + + switch (type) { + case BLE_MESH_DATA_MESH_MESSAGE: + bt_mesh_net_recv(buf, rssi, BLE_MESH_NET_IF_ADV); + break; +#if CONFIG_BLE_MESH_PB_ADV + case BLE_MESH_DATA_MESH_PROV: +#if CONFIG_BLE_MESH_NODE + if (!bt_mesh_is_provisioner_en()) { + bt_mesh_pb_adv_recv(buf); + } +#endif +#if CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + provisioner_pb_adv_recv(buf); + } +#endif + break; +#endif /* CONFIG_BLE_MESH_PB_ADV */ + case BLE_MESH_DATA_MESH_BEACON: +#if CONFIG_BLE_MESH_NODE + if (!bt_mesh_is_provisioner_en()) { + bt_mesh_beacon_recv(buf); + } +#endif +#if CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + provisioner_beacon_recv(buf); + } +#endif + break; +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + case BLE_MESH_DATA_FLAGS: + if (bt_mesh_is_provisioner_en()) { + if (!provisioner_flags_match(buf)) { + BT_DBG("Flags mismatch, ignore this adv pkt"); + return; + } + } + break; + case BLE_MESH_DATA_UUID16_ALL: + if (bt_mesh_is_provisioner_en()) { + uuid = provisioner_srv_uuid_recv(buf); + if (!uuid) { + BT_DBG("Service UUID mismatch, ignore this adv pkt"); + return; + } + } + break; + case BLE_MESH_DATA_SVC_DATA16: + if (bt_mesh_is_provisioner_en()) { + provisioner_srv_data_recv(buf, addr, uuid); + } + break; +#endif /* CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT */ + default: + break; + } + + net_buf_simple_restore(buf, &state); + net_buf_simple_pull(buf, len); + } + + return; +} + +void bt_mesh_adv_init(void) +{ + xBleMeshQueue = xQueueCreate(150, sizeof(bt_mesh_msg_t)); + xTaskCreatePinnedToCore(adv_thread, "BLE_Mesh_ADV_Task", 3072, NULL, + configMAX_PRIORITIES - 7, NULL, TASK_PINNED_TO_CORE); +} + +int bt_mesh_scan_enable(void) +{ + struct bt_mesh_scan_param scan_param = { + .type = BLE_MESH_SCAN_PASSIVE, +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) + .filter_dup = BLE_MESH_SCAN_FILTER_DUP_ENABLE, +#else + .filter_dup = BLE_MESH_SCAN_FILTER_DUP_DISABLE, +#endif + .interval = MESH_SCAN_INTERVAL, + .window = MESH_SCAN_WINDOW + }; + + BT_DBG("%s", __func__); + + return bt_le_scan_start(&scan_param, bt_mesh_scan_cb); +} + +int bt_mesh_scan_disable(void) +{ + BT_DBG("%s", __func__); + + return bt_le_scan_stop(); +} diff --git a/components/bt/ble_mesh/mesh_core/adv.h b/components/bt/ble_mesh/mesh_core/adv.h new file mode 100644 index 0000000000..b827af59ea --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/adv.h @@ -0,0 +1,86 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ADV_H_ +#define _ADV_H_ + +#include "mesh_bearer_adapt.h" + +/* Maximum advertising data payload for a single data type */ +#define BLE_MESH_ADV_DATA_SIZE 29 + +/* The user data is a pointer (4 bytes) to struct bt_mesh_adv */ +#define BLE_MESH_ADV_USER_DATA_SIZE 4 + +#define BLE_MESH_ADV(buf) (*(struct bt_mesh_adv **)net_buf_user_data(buf)) + +typedef struct bt_mesh_msg { + uint8_t sig; //event signal + uint8_t aid; //application id + uint8_t pid; //profile id + uint8_t act; //profile action, defined in seprerate header files + void *arg; //param for btc function or function param +} bt_mesh_msg_t; + +enum bt_mesh_adv_type { + BLE_MESH_ADV_PROV, + BLE_MESH_ADV_DATA, + BLE_MESH_ADV_BEACON, + BLE_MESH_ADV_URI, +}; + +typedef void (*bt_mesh_adv_func_t)(struct net_buf *buf, u16_t duration, + int err, void *user_data); + +struct bt_mesh_adv { + const struct bt_mesh_send_cb *cb; + void *cb_data; + + u8_t type: 2, + busy: 1; + u8_t xmit; + + union { + /* Address, used e.g. for Friend Queue messages */ + u16_t addr; + + /* For transport layer segment sending */ + struct { + u8_t attempts; + } seg; + }; +}; + +typedef struct bt_mesh_adv *(*bt_mesh_adv_alloc_t)(int id); + +/* xmit_count: Number of retransmissions, i.e. 0 == 1 transmission */ +struct net_buf *bt_mesh_adv_create(enum bt_mesh_adv_type type, u8_t xmit, + s32_t timeout); + +struct net_buf *bt_mesh_adv_create_from_pool(struct net_buf_pool *pool, + bt_mesh_adv_alloc_t get_id, + enum bt_mesh_adv_type type, + u8_t xmit, s32_t timeout); + +void bt_mesh_adv_send(struct net_buf *buf, const struct bt_mesh_send_cb *cb, + void *cb_data); + +const bt_mesh_addr_t *bt_mesh_pba_get_addr(void); + +void bt_mesh_adv_update(void); + +void bt_mesh_adv_init(void); + +int bt_mesh_scan_enable(void); + +int bt_mesh_scan_disable(void); + +void bt_mesh_task_post(bt_mesh_msg_t *msg, uint32_t timeout); + +#endif /* _ADV_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/beacon.c b/components/bt/ble_mesh/mesh_core/beacon.c new file mode 100644 index 0000000000..a62cf073ad --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/beacon.c @@ -0,0 +1,422 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_BEACON) + +#include "mesh_util.h" +#include "mesh_buf.h" +#include "mesh_main.h" +#include "mesh_trace.h" + +#include "adv.h" +#include "mesh.h" +#include "net.h" +#include "prov.h" +#include "crypto.h" +#include "beacon.h" +#include "foundation.h" + +#if CONFIG_BLE_MESH_NODE + +#if defined(CONFIG_BLE_MESH_FAST_PROV) +#define UNPROVISIONED_INTERVAL K_SECONDS(3) +#else +#define UNPROVISIONED_INTERVAL K_SECONDS(5) +#endif /* CONFIG_BLE_MESH_FAST_PROV */ +#define PROVISIONED_INTERVAL K_SECONDS(10) + +#define BEACON_TYPE_UNPROVISIONED 0x00 +#define BEACON_TYPE_SECURE 0x01 + +/* 3 transmissions, 20ms interval */ +#define UNPROV_XMIT BLE_MESH_TRANSMIT(2, 20) + +/* 1 transmission, 20ms interval */ +#define PROV_XMIT BLE_MESH_TRANSMIT(0, 20) + +static struct k_delayed_work beacon_timer; + +static struct bt_mesh_subnet *cache_check(u8_t data[21]) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + if (!memcmp(sub->beacon_cache, data, 21)) { + return sub; + } + } + + return NULL; +} + +static void cache_add(u8_t data[21], struct bt_mesh_subnet *sub) +{ + memcpy(sub->beacon_cache, data, 21); +} + +static void beacon_complete(int err, void *user_data) +{ + struct bt_mesh_subnet *sub = user_data; + + BT_DBG("err %d", err); + + sub->beacon_sent = k_uptime_get_32(); +} + +void bt_mesh_beacon_create(struct bt_mesh_subnet *sub, + struct net_buf_simple *buf) +{ + u8_t flags = bt_mesh_net_flags(sub); + struct bt_mesh_subnet_keys *keys; + + net_buf_simple_add_u8(buf, BEACON_TYPE_SECURE); + + if (sub->kr_flag) { + keys = &sub->keys[1]; + } else { + keys = &sub->keys[0]; + } + + net_buf_simple_add_u8(buf, flags); + + /* Network ID */ + net_buf_simple_add_mem(buf, keys->net_id, 8); + + /* IV Index */ + net_buf_simple_add_be32(buf, bt_mesh.iv_index); + + net_buf_simple_add_mem(buf, sub->auth, 8); + + BT_DBG("net_idx 0x%04x flags 0x%02x NetID %s", sub->net_idx, + flags, bt_hex(keys->net_id, 8)); + BT_DBG("IV Index 0x%08x Auth %s", bt_mesh.iv_index, + bt_hex(sub->auth, 8)); +} + +/* If the interval has passed or is within 5 seconds from now send a beacon */ +#define BEACON_THRESHOLD(sub) (K_SECONDS(10 * ((sub)->beacons_last + 1)) - \ + K_SECONDS(5)) + +static int secure_beacon_send(void) +{ + static const struct bt_mesh_send_cb send_cb = { + .end = beacon_complete, + }; + u32_t now = k_uptime_get_32(); + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + struct net_buf *buf; + u32_t time_diff; + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + time_diff = now - sub->beacon_sent; + if (time_diff < K_SECONDS(600) && + time_diff < BEACON_THRESHOLD(sub)) { + continue; + } + + buf = bt_mesh_adv_create(BLE_MESH_ADV_BEACON, PROV_XMIT, + K_NO_WAIT); + if (!buf) { + BT_ERR("%s, Unable to allocate beacon buffer", __func__); + return -ENOBUFS; + } + + bt_mesh_beacon_create(sub, &buf->b); + + bt_mesh_adv_send(buf, &send_cb, sub); + net_buf_unref(buf); + } + + return 0; +} + +static int unprovisioned_beacon_send(void) +{ +#if defined(CONFIG_BLE_MESH_PB_ADV) + const struct bt_mesh_prov *prov; + u8_t uri_hash[16] = { 0 }; + struct net_buf *buf; + u16_t oob_info; + + BT_DBG("%s", __func__); + + buf = bt_mesh_adv_create(BLE_MESH_ADV_BEACON, UNPROV_XMIT, K_NO_WAIT); + if (!buf) { + BT_ERR("%s, Unable to allocate beacon buffer", __func__); + return -ENOBUFS; + } + + prov = bt_mesh_prov_get(); + + net_buf_add_u8(buf, BEACON_TYPE_UNPROVISIONED); + net_buf_add_mem(buf, prov->uuid, 16); + + if (prov->uri && bt_mesh_s1(prov->uri, uri_hash) == 0) { + oob_info = prov->oob_info | BLE_MESH_PROV_OOB_URI; + } else { + oob_info = prov->oob_info; + } + + net_buf_add_be16(buf, oob_info); + net_buf_add_mem(buf, uri_hash, 4); + + bt_mesh_adv_send(buf, NULL, NULL); + net_buf_unref(buf); + + if (prov->uri) { + size_t len; + + buf = bt_mesh_adv_create(BLE_MESH_ADV_URI, UNPROV_XMIT, + K_NO_WAIT); + if (!buf) { + BT_ERR("Unable to allocate URI buffer"); + return -ENOBUFS; + } + + len = strlen(prov->uri); + if (net_buf_tailroom(buf) < len) { + BT_WARN("Too long URI to fit advertising data"); + } else { + net_buf_add_mem(buf, prov->uri, len); + bt_mesh_adv_send(buf, NULL, NULL); + } + + net_buf_unref(buf); + } + +#endif /* CONFIG_BLE_MESH_PB_ADV */ + return 0; +} + +static void update_beacon_observation(void) +{ + static bool first_half; + int i; + + /* Observation period is 20 seconds, whereas the beacon timer + * runs every 10 seconds. We process what's happened during the + * window only after the second half. + */ + first_half = !first_half; + if (first_half) { + return; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + sub->beacons_last = sub->beacons_cur; + sub->beacons_cur = 0U; + } +} + +static void beacon_send(struct k_work *work) +{ + /* Don't send anything if we have an active provisioning link */ + if (IS_ENABLED(CONFIG_BLE_MESH_PROV) && bt_prov_active()) { + k_delayed_work_submit(&beacon_timer, UNPROVISIONED_INTERVAL); + return; + } + + BT_DBG("%s", __func__); + + if (bt_mesh_is_provisioned()) { + update_beacon_observation(); + secure_beacon_send(); + + /* Only resubmit if beaconing is still enabled */ + if (bt_mesh_beacon_get() == BLE_MESH_BEACON_ENABLED || + bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_INITIATOR)) { + k_delayed_work_submit(&beacon_timer, + PROVISIONED_INTERVAL); + } + } else { + unprovisioned_beacon_send(); + k_delayed_work_submit(&beacon_timer, UNPROVISIONED_INTERVAL); + } + +} + +static void secure_beacon_recv(struct net_buf_simple *buf) +{ + u8_t *data, *net_id, *auth; + struct bt_mesh_subnet *sub; + u32_t iv_index; + bool new_key, kr_change, iv_change; + u8_t flags; + + if (buf->len < 21) { + BT_ERR("%s, Too short secure beacon (len %u)", __func__, buf->len); + return; + } + + sub = cache_check(buf->data); + if (sub) { + /* We've seen this beacon before - just update the stats */ + goto update_stats; + } + + /* So we can add to the cache if auth matches */ + data = buf->data; + + flags = net_buf_simple_pull_u8(buf); + net_id = net_buf_simple_pull_mem(buf, 8); + iv_index = net_buf_simple_pull_be32(buf); + auth = buf->data; + + BT_DBG("flags 0x%02x id %s iv_index 0x%08x", + flags, bt_hex(net_id, 8), iv_index); + + sub = bt_mesh_subnet_find(net_id, flags, iv_index, auth, &new_key); + if (!sub) { + BT_DBG("No subnet that matched beacon"); + return; + } + + if (sub->kr_phase == BLE_MESH_KR_PHASE_2 && !new_key) { + BT_WARN("Ignoring Phase 2 KR Update secured using old key"); + return; + } + + cache_add(data, sub); + + /* If we have NetKey0 accept initiation only from it */ + if (bt_mesh_subnet_get(BLE_MESH_KEY_PRIMARY) && + sub->net_idx != BLE_MESH_KEY_PRIMARY) { + BT_WARN("Ignoring secure beacon on non-primary subnet"); + goto update_stats; + } + + BT_DBG("net_idx 0x%04x iv_index 0x%08x, current iv_index 0x%08x", + sub->net_idx, iv_index, bt_mesh.iv_index); + + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_INITIATOR) && + (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS) == + BLE_MESH_IV_UPDATE(flags))) { + bt_mesh_beacon_ivu_initiator(false); + } + + iv_change = bt_mesh_net_iv_update(iv_index, BLE_MESH_IV_UPDATE(flags)); + + kr_change = bt_mesh_kr_update(sub, BLE_MESH_KEY_REFRESH(flags), new_key); + if (kr_change) { + bt_mesh_net_beacon_update(sub); + } + + if (iv_change) { + /* Update all subnets */ + bt_mesh_net_sec_update(NULL); + } else if (kr_change) { + /* Key Refresh without IV Update only impacts one subnet */ + bt_mesh_net_sec_update(sub); + } + +update_stats: + if (bt_mesh_beacon_get() == BLE_MESH_BEACON_ENABLED && + sub->beacons_cur < 0xff) { + sub->beacons_cur++; + } +} + +void bt_mesh_beacon_recv(struct net_buf_simple *buf) +{ + u8_t type; + + BT_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (buf->len < 1) { + BT_ERR("%s, Too short beacon", __func__); + return; + } + + type = net_buf_simple_pull_u8(buf); + switch (type) { + case BEACON_TYPE_UNPROVISIONED: + BT_DBG("Ignoring unprovisioned device beacon"); + break; + case BEACON_TYPE_SECURE: + secure_beacon_recv(buf); + break; + default: + BT_DBG("Unknown beacon type 0x%02x", type); + break; + } +} + +void bt_mesh_beacon_init(void) +{ + k_delayed_work_init(&beacon_timer, beacon_send); +} + +void bt_mesh_beacon_ivu_initiator(bool enable) +{ + bt_mesh_atomic_set_bit_to(bt_mesh.flags, BLE_MESH_IVU_INITIATOR, enable); + + if (enable) { + k_work_submit(&beacon_timer.work); + } else if (bt_mesh_beacon_get() == BLE_MESH_BEACON_DISABLED) { + k_delayed_work_cancel(&beacon_timer); + } +} + +void bt_mesh_beacon_enable(void) +{ + int i; + + if (!bt_mesh_is_provisioned()) { + k_work_submit(&beacon_timer.work); + return; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + sub->beacons_last = 0U; + sub->beacons_cur = 0U; + + bt_mesh_net_beacon_update(sub); + } + + k_work_submit(&beacon_timer.work); +} + +void bt_mesh_beacon_disable(void) +{ + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_INITIATOR)) { + k_delayed_work_cancel(&beacon_timer); + } +} + +#endif /* CONFIG_BLE_MESH_NODE */ diff --git a/components/bt/ble_mesh/mesh_core/beacon.h b/components/bt/ble_mesh/mesh_core/beacon.h new file mode 100644 index 0000000000..f410fa5d59 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/beacon.h @@ -0,0 +1,24 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BEACON_H_ +#define _BEACON_H_ + +void bt_mesh_beacon_enable(void); +void bt_mesh_beacon_disable(void); + +void bt_mesh_beacon_ivu_initiator(bool enable); + +void bt_mesh_beacon_recv(struct net_buf_simple *buf); + +void bt_mesh_beacon_create(struct bt_mesh_subnet *sub, + struct net_buf_simple *buf); + +void bt_mesh_beacon_init(void); + +#endif /* _BEACON_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/cfg_cli.c b/components/bt/ble_mesh/mesh_core/cfg_cli.c new file mode 100644 index 0000000000..aa2daf0da8 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/cfg_cli.c @@ -0,0 +1,1657 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "osi/allocator.h" +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_MODEL) + +#include "mesh_types.h" +#include "mesh_util.h" +#include "mesh_kernel.h" +#include "mesh_trace.h" +#include "cfg_cli.h" + +#include "mesh.h" +#include "foundation.h" +#include "mesh_common.h" +#include "btc_ble_mesh_config_model.h" + +#define CID_NVAL 0xffff + +s32_t config_msg_timeout; + +static bt_mesh_config_client_t *cli; + +static const bt_mesh_client_op_pair_t cfg_op_pair[] = { + { OP_BEACON_GET, OP_BEACON_STATUS }, + { OP_BEACON_SET, OP_BEACON_STATUS }, + { OP_DEV_COMP_DATA_GET, OP_DEV_COMP_DATA_STATUS }, + { OP_DEFAULT_TTL_GET, OP_DEFAULT_TTL_STATUS }, + { OP_DEFAULT_TTL_SET, OP_DEFAULT_TTL_STATUS }, + { OP_GATT_PROXY_GET, OP_GATT_PROXY_STATUS }, + { OP_GATT_PROXY_SET, OP_GATT_PROXY_STATUS }, + { OP_RELAY_GET, OP_RELAY_STATUS }, + { OP_RELAY_SET, OP_RELAY_STATUS }, + { OP_MOD_PUB_GET, OP_MOD_PUB_STATUS }, + { OP_MOD_PUB_SET, OP_MOD_PUB_STATUS }, + { OP_MOD_PUB_VA_SET, OP_MOD_PUB_STATUS }, + { OP_MOD_SUB_ADD, OP_MOD_SUB_STATUS }, + { OP_MOD_SUB_VA_ADD, OP_MOD_SUB_STATUS }, + { OP_MOD_SUB_DEL, OP_MOD_SUB_STATUS }, + { OP_MOD_SUB_VA_DEL, OP_MOD_SUB_STATUS }, + { OP_MOD_SUB_OVERWRITE, OP_MOD_SUB_STATUS }, + { OP_MOD_SUB_VA_OVERWRITE, OP_MOD_SUB_STATUS }, + { OP_MOD_SUB_DEL_ALL, OP_MOD_SUB_STATUS }, + { OP_MOD_SUB_GET, OP_MOD_SUB_LIST }, + { OP_MOD_SUB_GET_VND, OP_MOD_SUB_LIST_VND }, + { OP_NET_KEY_ADD, OP_NET_KEY_STATUS }, + { OP_NET_KEY_UPDATE, OP_NET_KEY_STATUS }, + { OP_NET_KEY_DEL, OP_NET_KEY_STATUS }, + { OP_NET_KEY_GET, OP_NET_KEY_LIST }, + { OP_APP_KEY_ADD, OP_APP_KEY_STATUS }, + { OP_APP_KEY_UPDATE, OP_APP_KEY_STATUS }, + { OP_APP_KEY_DEL, OP_APP_KEY_STATUS }, + { OP_APP_KEY_GET, OP_APP_KEY_LIST }, + { OP_NODE_IDENTITY_GET, OP_NODE_IDENTITY_STATUS }, + { OP_NODE_IDENTITY_SET, OP_NODE_IDENTITY_STATUS }, + { OP_MOD_APP_BIND, OP_MOD_APP_STATUS }, + { OP_MOD_APP_UNBIND, OP_MOD_APP_STATUS }, + { OP_SIG_MOD_APP_GET, OP_SIG_MOD_APP_LIST }, + { OP_VND_MOD_APP_GET, OP_VND_MOD_APP_LIST }, + { OP_NODE_RESET, OP_NODE_RESET_STATUS }, + { OP_FRIEND_GET, OP_FRIEND_STATUS }, + { OP_FRIEND_SET, OP_FRIEND_STATUS }, + { OP_KRP_GET, OP_KRP_STATUS }, + { OP_KRP_SET, OP_KRP_STATUS }, + { OP_HEARTBEAT_PUB_GET, OP_HEARTBEAT_PUB_STATUS }, + { OP_HEARTBEAT_PUB_SET, OP_HEARTBEAT_PUB_STATUS }, + { OP_HEARTBEAT_SUB_GET, OP_HEARTBEAT_SUB_STATUS }, + { OP_HEARTBEAT_SUB_SET, OP_HEARTBEAT_SUB_STATUS }, + { OP_LPN_TIMEOUT_GET, OP_LPN_TIMEOUT_STATUS }, + { OP_NET_TRANSMIT_GET, OP_NET_TRANSMIT_STATUS }, + { OP_NET_TRANSMIT_SET, OP_NET_TRANSMIT_STATUS }, +}; + +static void timeout_handler(struct k_work *work) +{ + config_internal_data_t *internal = NULL; + bt_mesh_config_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + + BT_WARN("Receive configuration status message timeout"); + + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (!node || !node->ctx.model) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + client = (bt_mesh_config_client_t *)node->ctx.model->user_data; + if (!client) { + BT_ERR("%s, Config Client user_data is NULL", __func__); + return; + } + + internal = (config_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Config Client internal_data is NULL", __func__); + return; + } + + bt_mesh_callback_config_status_to_btc(node->opcode, 0x03, node->ctx.model, + &node->ctx, NULL, 0); + + bt_mesh_client_free_node(&internal->queue, node); + + return; +} + +static void cfg_client_cancel(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + void *status, size_t len) +{ + config_internal_data_t *data = NULL; + bt_mesh_client_node_t *node = NULL; + struct net_buf_simple buf = {0}; + u8_t evt_type = 0xFF; + + if (!model || !ctx) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + data = (config_internal_data_t *)cli->internal_data; + if (!data) { + BT_ERR("%s, Config Client internal_data is NULL", __func__); + return; + } + + /* If it is a publish message, sent to the user directly. */ + buf.data = (u8_t *)status; + buf.len = (u16_t)len; + node = bt_mesh_is_model_message_publish(model, ctx, &buf, true); + if (!node) { + BT_DBG("Unexpected config status message 0x%x", ctx->recv_op); + } else { + switch (node->opcode) { + case OP_BEACON_GET: + case OP_DEV_COMP_DATA_GET: + case OP_DEFAULT_TTL_GET: + case OP_GATT_PROXY_GET: + case OP_RELAY_GET: + case OP_MOD_PUB_GET: + case OP_MOD_SUB_GET: + case OP_MOD_SUB_GET_VND: + case OP_NET_KEY_GET: + case OP_APP_KEY_GET: + case OP_NODE_IDENTITY_GET: + case OP_SIG_MOD_APP_GET: + case OP_VND_MOD_APP_GET: + case OP_FRIEND_GET: + case OP_KRP_GET: + case OP_HEARTBEAT_PUB_GET: + case OP_HEARTBEAT_SUB_GET: + case OP_LPN_TIMEOUT_GET: + case OP_NET_TRANSMIT_GET: + evt_type = 0x00; + break; + case OP_BEACON_SET: + case OP_DEFAULT_TTL_SET: + case OP_GATT_PROXY_SET: + case OP_RELAY_SET: + case OP_MOD_PUB_SET: + case OP_MOD_PUB_VA_SET: + case OP_MOD_SUB_ADD: + case OP_MOD_SUB_VA_ADD: + case OP_MOD_SUB_DEL: + case OP_MOD_SUB_VA_DEL: + case OP_MOD_SUB_OVERWRITE: + case OP_MOD_SUB_VA_OVERWRITE: + case OP_MOD_SUB_DEL_ALL: + case OP_NET_KEY_ADD: + case OP_NET_KEY_UPDATE: + case OP_NET_KEY_DEL: + case OP_APP_KEY_ADD: + case OP_APP_KEY_UPDATE: + case OP_APP_KEY_DEL: + case OP_NODE_IDENTITY_SET: + case OP_MOD_APP_BIND: + case OP_MOD_APP_UNBIND: + case OP_NODE_RESET: + case OP_FRIEND_SET: + case OP_KRP_SET: + case OP_HEARTBEAT_PUB_SET: + case OP_HEARTBEAT_SUB_SET: + case OP_NET_TRANSMIT_SET: + evt_type = 0x01; + break; + default: + break; + } + + bt_mesh_callback_config_status_to_btc(node->opcode, evt_type, model, + ctx, (const u8_t *)status, len); + // Don't forget to release the node at the end. + bt_mesh_client_free_node(&data->queue, node); + } + + switch (ctx->recv_op) { + case OP_DEV_COMP_DATA_STATUS: { + struct bt_mesh_cfg_comp_data_status *val; + val = (struct bt_mesh_cfg_comp_data_status *)status; + bt_mesh_free_buf(val->comp_data); + break; + } + case OP_MOD_SUB_LIST: + case OP_MOD_SUB_LIST_VND: { + struct bt_mesh_cfg_mod_sub_list *val = status; + bt_mesh_free_buf(val->addr); + break; + } + case OP_NET_KEY_LIST: { + struct bt_mesh_cfg_net_key_list *val = status; + bt_mesh_free_buf(val->net_idx); + break; + } + case OP_APP_KEY_LIST: { + struct bt_mesh_cfg_app_key_list *val = status; + bt_mesh_free_buf(val->app_idx); + break; + } + case OP_SIG_MOD_APP_LIST: + case OP_VND_MOD_APP_LIST: { + struct bt_mesh_cfg_mod_app_list *val = status; + bt_mesh_free_buf(val->app_idx); + break; + } + default: + break; + } +} + +static void comp_data_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_comp_data_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.page = net_buf_simple_pull_u8(buf); + status.comp_data = bt_mesh_alloc_buf(buf->len); + if (!status.comp_data) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + net_buf_simple_add_mem(status.comp_data, buf->data, buf->len); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_comp_data_status)); +} + +static void state_status_u8(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t status = 0; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + + cfg_client_cancel(model, ctx, &status, sizeof(u8_t)); +} + +static void beacon_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + state_status_u8(model, ctx, buf); +} + +static void ttl_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + state_status_u8(model, ctx, buf); +} + +static void friend_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + state_status_u8(model, ctx, buf); +} + +static void gatt_proxy_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + state_status_u8(model, ctx, buf); +} + +static void relay_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_relay_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.relay = net_buf_simple_pull_u8(buf); + status.retransmit = net_buf_simple_pull_u8(buf); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_relay_status)); +} + +static void net_key_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_netkey_status status = {0}; + u16_t app_idx; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.status = net_buf_simple_pull_u8(buf); + key_idx_unpack(buf, &status.net_idx, &app_idx); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_netkey_status)); +} + +static void app_key_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_appkey_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.status = net_buf_simple_pull_u8(buf); + key_idx_unpack(buf, &status.net_idx, &status.app_idx); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_appkey_status)); +} + +static void mod_app_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_mod_app_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.status = net_buf_simple_pull_u8(buf); + status.elem_addr = net_buf_simple_pull_le16(buf); + status.app_idx = net_buf_simple_pull_le16(buf); + if (buf->len >= 4) { + status.cid = net_buf_simple_pull_le16(buf); + } else { + status.cid = CID_NVAL; + } + status.mod_id = net_buf_simple_pull_le16(buf); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_mod_app_status)); +} + +static void mod_pub_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_mod_pub_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.status = net_buf_simple_pull_u8(buf); + status.elem_addr = net_buf_simple_pull_le16(buf); + status.addr = net_buf_simple_pull_le16(buf); + status.app_idx = net_buf_simple_pull_le16(buf); + status.cred_flag = (status.app_idx & BIT(12)); + status.app_idx &= BIT_MASK(12); + status.ttl = net_buf_simple_pull_u8(buf); + status.period = net_buf_simple_pull_u8(buf); + status.transmit = net_buf_simple_pull_u8(buf); + if (buf->len >= 4) { + status.cid = net_buf_simple_pull_le16(buf); + } else { + status.cid = CID_NVAL; + } + status.mod_id = net_buf_simple_pull_le16(buf); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_mod_pub_status)); +} + +static void mod_sub_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_mod_sub_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.status = net_buf_simple_pull_u8(buf); + status.elem_addr = net_buf_simple_pull_le16(buf); + status.sub_addr = net_buf_simple_pull_le16(buf); + if (buf->len >= 4) { + status.cid = net_buf_simple_pull_le16(buf); + } else { + status.cid = CID_NVAL; + } + status.mod_id = net_buf_simple_pull_le16(buf); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_mod_sub_status)); +} + +static void hb_sub_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_hb_sub_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.status = net_buf_simple_pull_u8(buf); + status.src = net_buf_simple_pull_le16(buf); + status.dst = net_buf_simple_pull_le16(buf); + status.period = net_buf_simple_pull_u8(buf); + status.count = net_buf_simple_pull_u8(buf); + status.min = net_buf_simple_pull_u8(buf); + status.max = net_buf_simple_pull_u8(buf); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_hb_sub_status)); +} + +static void hb_pub_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_hb_pub_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.status = net_buf_simple_pull_u8(buf); + status.dst = net_buf_simple_pull_le16(buf); + status.count = net_buf_simple_pull_u8(buf); + status.period = net_buf_simple_pull_u8(buf); + status.ttl = net_buf_simple_pull_u8(buf); + status.feat = net_buf_simple_pull_u8(buf); + status.net_idx = net_buf_simple_pull_u8(buf); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_hb_sub_status)); +} + +static void node_reset_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + cfg_client_cancel(model, ctx, NULL, 0); +} + +static void mod_sub_list(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_mod_sub_list list = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + list.status = net_buf_simple_pull_u8(buf); + list.elem_addr = net_buf_simple_pull_le16(buf); + if (ctx->recv_op == OP_MOD_SUB_LIST_VND) { + list.cid = net_buf_simple_pull_le16(buf); + } else { + list.cid = CID_NVAL; + } + list.mod_id = net_buf_simple_pull_le16(buf); + + list.addr = bt_mesh_alloc_buf(buf->len); + if (!list.addr) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + net_buf_simple_add_mem(list.addr, buf->data, buf->len); + + cfg_client_cancel(model, ctx, &list, sizeof(struct bt_mesh_cfg_mod_sub_list)); +} + +static void net_key_list(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_net_key_list list = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + list.net_idx = bt_mesh_alloc_buf(buf->len); + if (!list.net_idx) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + net_buf_simple_add_mem(list.net_idx, buf->data, buf->len); + + cfg_client_cancel(model, ctx, &list, sizeof(struct bt_mesh_cfg_net_key_list)); +} + +static void app_key_list(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_app_key_list list = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + list.status = net_buf_simple_pull_u8(buf); + list.net_idx = net_buf_simple_pull_le16(buf); + list.app_idx = bt_mesh_alloc_buf(buf->len); + if (!list.app_idx) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + net_buf_simple_add_mem(list.app_idx, buf->data, buf->len); + + cfg_client_cancel(model, ctx, &list, sizeof(struct bt_mesh_cfg_app_key_list)); +} + +static void node_id_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_node_id_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.status = net_buf_simple_pull_u8(buf); + status.net_idx = net_buf_simple_pull_le16(buf); + status.identity = net_buf_simple_pull_u8(buf); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_node_id_status)); +} + +static void mod_app_list(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_mod_app_list list = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + list.status = net_buf_simple_pull_u8(buf); + list.elem_addr = net_buf_simple_pull_le16(buf); + if (ctx->recv_op == OP_VND_MOD_APP_LIST) { + list.cid = net_buf_simple_pull_le16(buf); + } else { + list.cid = CID_NVAL; + } + list.mod_id = net_buf_simple_pull_le16(buf); + + list.app_idx = bt_mesh_alloc_buf(buf->len); + if (!list.app_idx) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + net_buf_simple_add_mem(list.app_idx, buf->data, buf->len); + + cfg_client_cancel(model, ctx, &list, sizeof(struct bt_mesh_cfg_mod_app_list)); +} + +static void kr_phase_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_key_refresh_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.status = net_buf_simple_pull_u8(buf); + status.net_idx = net_buf_simple_pull_le16(buf); + status.phase = net_buf_simple_pull_u8(buf); + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_key_refresh_status)); +} + +static void lpn_pollto_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_lpn_pollto_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.lpn_addr = net_buf_simple_pull_le16(buf); + status.timeout = net_buf_simple_pull_u8(buf); + status.timeout |= net_buf_simple_pull_u8(buf) << 8; + status.timeout |= net_buf_simple_pull_u8(buf) << 16; + + cfg_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_cfg_lpn_pollto_status)); +} + +static void net_trans_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + state_status_u8(model, ctx, buf); +} + +const struct bt_mesh_model_op bt_mesh_cfg_cli_op[] = { + { OP_DEV_COMP_DATA_STATUS, 15, comp_data_status }, + { OP_BEACON_STATUS, 1, beacon_status }, + { OP_DEFAULT_TTL_STATUS, 1, ttl_status }, + { OP_FRIEND_STATUS, 1, friend_status }, + { OP_GATT_PROXY_STATUS, 1, gatt_proxy_status }, + { OP_RELAY_STATUS, 2, relay_status }, + { OP_NET_KEY_STATUS, 3, net_key_status }, + { OP_APP_KEY_STATUS, 4, app_key_status }, + { OP_MOD_APP_STATUS, 7, mod_app_status }, + { OP_MOD_PUB_STATUS, 12, mod_pub_status }, + { OP_MOD_SUB_STATUS, 7, mod_sub_status }, + { OP_HEARTBEAT_SUB_STATUS, 9, hb_sub_status }, + { OP_HEARTBEAT_PUB_STATUS, 10, hb_pub_status }, + { OP_NODE_RESET_STATUS, 0, node_reset_status }, + { OP_MOD_SUB_LIST, 5, mod_sub_list }, + { OP_MOD_SUB_LIST_VND, 7, mod_sub_list }, + { OP_NET_KEY_LIST, 2, net_key_list }, + { OP_APP_KEY_LIST, 3, app_key_list }, + { OP_NODE_IDENTITY_STATUS, 4, node_id_status }, + { OP_SIG_MOD_APP_LIST, 5, mod_app_list }, + { OP_VND_MOD_APP_LIST, 7, mod_app_list }, + { OP_KRP_STATUS, 4, kr_phase_status }, + { OP_LPN_TIMEOUT_STATUS, 5, lpn_pollto_status }, + { OP_NET_TRANSMIT_STATUS, 1, net_trans_status }, + BLE_MESH_MODEL_OP_END, +}; + +int bt_mesh_cfg_comp_data_get(struct bt_mesh_msg_ctx *ctx, u8_t page) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_DEV_COMP_DATA_GET); + net_buf_simple_add_u8(&msg, page); + + err = bt_mesh_client_send_msg(cli->model, OP_DEV_COMP_DATA_GET, ctx, + &msg, timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +static int get_state_u8(struct bt_mesh_msg_ctx *ctx, u32_t op) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 0 + 4); + int err; + + bt_mesh_model_msg_init(&msg, op); + + err = bt_mesh_client_send_msg(cli->model, op, ctx, &msg, timeout_handler, + config_msg_timeout, true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +static int set_state_u8(struct bt_mesh_msg_ctx *ctx, u32_t op, u8_t new_val) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + int err; + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_u8(&msg, new_val); + + err = bt_mesh_client_send_msg(cli->model, op, ctx, &msg, timeout_handler, + config_msg_timeout, true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_beacon_get(struct bt_mesh_msg_ctx *ctx) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return get_state_u8(ctx, OP_BEACON_GET); +} + +int bt_mesh_cfg_beacon_set(struct bt_mesh_msg_ctx *ctx, u8_t val) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return set_state_u8(ctx, OP_BEACON_SET, val); +} + +int bt_mesh_cfg_ttl_get(struct bt_mesh_msg_ctx *ctx) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return get_state_u8(ctx, OP_DEFAULT_TTL_GET); +} + +int bt_mesh_cfg_ttl_set(struct bt_mesh_msg_ctx *ctx, u8_t val) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return set_state_u8(ctx, OP_DEFAULT_TTL_SET, val); +} + +int bt_mesh_cfg_friend_get(struct bt_mesh_msg_ctx *ctx) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return get_state_u8(ctx, OP_FRIEND_GET); +} + +int bt_mesh_cfg_friend_set(struct bt_mesh_msg_ctx *ctx, u8_t val) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return set_state_u8(ctx, OP_FRIEND_SET, val); +} + +int bt_mesh_cfg_gatt_proxy_get(struct bt_mesh_msg_ctx *ctx) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return get_state_u8(ctx, OP_GATT_PROXY_GET); +} + +int bt_mesh_cfg_gatt_proxy_set(struct bt_mesh_msg_ctx *ctx, u8_t val) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return set_state_u8(ctx, OP_GATT_PROXY_SET, val); +} + +int bt_mesh_cfg_relay_get(struct bt_mesh_msg_ctx *ctx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 0 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_RELAY_GET); + + err = bt_mesh_client_send_msg(cli->model, OP_RELAY_GET, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_relay_set(struct bt_mesh_msg_ctx *ctx, u8_t new_relay, + u8_t new_transmit) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 2 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_RELAY_SET); + net_buf_simple_add_u8(&msg, new_relay); + net_buf_simple_add_u8(&msg, new_transmit); + + err = bt_mesh_client_send_msg(cli->model, OP_RELAY_SET, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_net_key_add(struct bt_mesh_msg_ctx *ctx, u16_t key_net_idx, + const u8_t net_key[16]) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 18 + 4); + int err; + + if (!ctx || !ctx->addr || !net_key) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_ADD); + net_buf_simple_add_le16(&msg, key_net_idx); + net_buf_simple_add_mem(&msg, net_key, 16); + + err = bt_mesh_client_send_msg(cli->model, OP_NET_KEY_ADD, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_app_key_add(struct bt_mesh_msg_ctx *ctx, u16_t key_net_idx, + u16_t key_app_idx, const u8_t app_key[16]) +{ + NET_BUF_SIMPLE_DEFINE(msg, 1 + 19 + 4); + int err; + + if (!ctx || !ctx->addr || !app_key) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_ADD); + key_idx_pack(&msg, key_net_idx, key_app_idx); + net_buf_simple_add_mem(&msg, app_key, 16); + + err = bt_mesh_client_send_msg(cli->model, OP_APP_KEY_ADD, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_mod_app_bind(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_app_idx, u16_t mod_id, u16_t cid) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 8 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_MOD_APP_BIND); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_le16(&msg, mod_app_idx); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, OP_MOD_APP_BIND, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +static int mod_sub(u32_t op, struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t sub_addr, u16_t mod_id, u16_t cid) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 8 + 4); + int err; + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_le16(&msg, sub_addr); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, op, ctx, &msg, timeout_handler, + config_msg_timeout, true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_mod_sub_add(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t sub_addr, u16_t mod_id, u16_t cid) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return mod_sub(OP_MOD_SUB_ADD, ctx, elem_addr, sub_addr, mod_id, cid); +} + +int bt_mesh_cfg_mod_sub_del(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t sub_addr, u16_t mod_id, u16_t cid) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return mod_sub(OP_MOD_SUB_DEL, ctx, elem_addr, sub_addr, mod_id, cid); +} + +int bt_mesh_cfg_mod_sub_overwrite(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t sub_addr, u16_t mod_id, u16_t cid) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return mod_sub(OP_MOD_SUB_OVERWRITE, ctx, elem_addr, sub_addr, mod_id, cid); +} + +static int mod_sub_va(u32_t op, struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + const u8_t label[16], u16_t mod_id, u16_t cid) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 22 + 4); + int err; + + BT_DBG("net_idx 0x%04x addr 0x%04x elem_addr 0x%04x label %s", + ctx->net_idx, ctx->addr, elem_addr, label); + BT_DBG("mod_id 0x%04x cid 0x%04x", mod_id, cid); + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_mem(&msg, label, 16); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, op, ctx, &msg, timeout_handler, + config_msg_timeout, true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_mod_sub_va_add(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + const u8_t label[16], u16_t mod_id, u16_t cid) +{ + if (!ctx || !ctx->addr || !label) { + return -EINVAL; + } + return mod_sub_va(OP_MOD_SUB_VA_ADD, ctx, elem_addr, label, mod_id, cid); +} + +int bt_mesh_cfg_mod_sub_va_del(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + const u8_t label[16], u16_t mod_id, u16_t cid) +{ + if (!ctx || !ctx->addr || !label) { + return -EINVAL; + } + return mod_sub_va(OP_MOD_SUB_VA_DEL, ctx, elem_addr, label, mod_id, cid); +} + +int bt_mesh_cfg_mod_sub_va_overwrite(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + const u8_t label[16], u16_t mod_id, u16_t cid) +{ + if (!ctx || !ctx->addr || !label) { + return -EINVAL; + } + return mod_sub_va(OP_MOD_SUB_VA_OVERWRITE, ctx, elem_addr, label, mod_id, cid); +} + +int bt_mesh_cfg_mod_pub_get(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 6 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_MOD_PUB_GET); + net_buf_simple_add_le16(&msg, elem_addr); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, OP_MOD_PUB_GET, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_mod_pub_set(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid, + struct bt_mesh_cfg_mod_pub *pub) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 13 + 4); + int err; + + if (!ctx || !ctx->addr || !pub) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_MOD_PUB_SET); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_le16(&msg, pub->addr); + net_buf_simple_add_le16(&msg, (pub->app_idx | (pub->cred_flag << 12))); + net_buf_simple_add_u8(&msg, pub->ttl); + net_buf_simple_add_u8(&msg, pub->period); + net_buf_simple_add_u8(&msg, pub->transmit); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, OP_MOD_PUB_SET, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_hb_sub_set(struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_cfg_hb_sub *sub) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 5 + 4); + int err; + + if (!ctx || !ctx->addr || !sub) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_SUB_SET); + net_buf_simple_add_le16(&msg, sub->src); + net_buf_simple_add_le16(&msg, sub->dst); + net_buf_simple_add_u8(&msg, sub->period); + + err = bt_mesh_client_send_msg(cli->model, OP_HEARTBEAT_SUB_SET, ctx, + &msg, timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_hb_sub_get(struct bt_mesh_msg_ctx *ctx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 0 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_SUB_GET); + + err = bt_mesh_client_send_msg(cli->model, OP_HEARTBEAT_SUB_GET, ctx, + &msg, timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_hb_pub_set(struct bt_mesh_msg_ctx *ctx, + const struct bt_mesh_cfg_hb_pub *pub) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 9 + 4); + int err; + + if (!ctx || !ctx->addr || !pub) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_PUB_SET); + net_buf_simple_add_le16(&msg, pub->dst); + net_buf_simple_add_u8(&msg, pub->count); + net_buf_simple_add_u8(&msg, pub->period); + net_buf_simple_add_u8(&msg, pub->ttl); + net_buf_simple_add_le16(&msg, pub->feat); + net_buf_simple_add_le16(&msg, pub->net_idx); + + err = bt_mesh_client_send_msg(cli->model, OP_HEARTBEAT_PUB_SET, ctx, + &msg, timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_hb_pub_get(struct bt_mesh_msg_ctx *ctx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 0 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_PUB_GET); + + err = bt_mesh_client_send_msg(cli->model, OP_HEARTBEAT_PUB_GET, ctx, + &msg, timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_node_reset(struct bt_mesh_msg_ctx *ctx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 0 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_NODE_RESET); + + err = bt_mesh_client_send_msg(cli->model, OP_NODE_RESET, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_mod_pub_va_set(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid, const u8_t label[16], + struct bt_mesh_cfg_mod_pub *pub) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 27 + 4); + int err; + + if (!ctx || !ctx->addr || !label || !pub) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_MOD_PUB_VA_SET); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_mem(&msg, label, 16); + net_buf_simple_add_le16(&msg, (pub->app_idx | (pub->cred_flag << 12))); + net_buf_simple_add_u8(&msg, pub->ttl); + net_buf_simple_add_u8(&msg, pub->period); + net_buf_simple_add_u8(&msg, pub->transmit); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, OP_MOD_PUB_VA_SET, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_mod_sub_del_all(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 6 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_MOD_SUB_DEL_ALL); + net_buf_simple_add_le16(&msg, elem_addr); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, OP_MOD_SUB_DEL_ALL, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +static int mod_sub_get(u32_t op, struct bt_mesh_msg_ctx *ctx, + u16_t elem_addr, u16_t mod_id, u16_t cid) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 6 + 4); + int err; + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_le16(&msg, elem_addr); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, op, ctx, &msg, timeout_handler, + config_msg_timeout, true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_mod_sub_get(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, u16_t mod_id) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return mod_sub_get(OP_MOD_SUB_GET, ctx, elem_addr, mod_id, CID_NVAL); +} + +int bt_mesh_cfg_mod_sub_get_vnd(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid) +{ + if (!ctx || !ctx->addr || cid == CID_NVAL) { + return -EINVAL; + } + return mod_sub_get(OP_MOD_SUB_GET_VND, ctx, elem_addr, mod_id, cid); +} + +int bt_mesh_cfg_net_key_update(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, + const u8_t net_key[16]) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 18 + 4); + int err; + + if (!ctx || !ctx->addr || !net_key) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_UPDATE); + net_buf_simple_add_le16(&msg, net_idx); + net_buf_simple_add_mem(&msg, net_key, 16); + + err = bt_mesh_client_send_msg(cli->model, OP_NET_KEY_UPDATE, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_net_key_delete(struct bt_mesh_msg_ctx *ctx, u16_t net_idx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 2 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_DEL); + net_buf_simple_add_le16(&msg, net_idx); + + err = bt_mesh_client_send_msg(cli->model, OP_NET_KEY_DEL, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_net_key_get(struct bt_mesh_msg_ctx *ctx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 0 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_GET); + + err = bt_mesh_client_send_msg(cli->model, OP_NET_KEY_GET, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_app_key_update(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, + u16_t app_idx, const u8_t app_key[16]) +{ + NET_BUF_SIMPLE_DEFINE(msg, 1 + 19 + 4); + int err; + + if (!ctx || !ctx->addr || !app_key) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_UPDATE); + key_idx_pack(&msg, net_idx, app_idx); + net_buf_simple_add_mem(&msg, app_key, 16); + + err = bt_mesh_client_send_msg(cli->model, OP_APP_KEY_UPDATE, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_app_key_delete(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, u16_t app_idx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 3 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_DEL); + key_idx_pack(&msg, net_idx, app_idx); + + err = bt_mesh_client_send_msg(cli->model, OP_APP_KEY_DEL, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_app_key_get(struct bt_mesh_msg_ctx *ctx, u16_t net_idx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 2 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_GET); + net_buf_simple_add_le16(&msg, net_idx); + + err = bt_mesh_client_send_msg(cli->model, OP_APP_KEY_GET, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +static int node_identity_op(u32_t op, struct bt_mesh_msg_ctx *ctx, + u16_t net_idx, u8_t identity) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 3 + 4); + int err; + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_le16(&msg, net_idx); + if (op == OP_NODE_IDENTITY_SET) { + net_buf_simple_add_u8(&msg, identity); + } + + err = bt_mesh_client_send_msg(cli->model, op, ctx, &msg, timeout_handler, + config_msg_timeout, true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_node_identity_get(struct bt_mesh_msg_ctx *ctx, u16_t net_idx) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return node_identity_op(OP_NODE_IDENTITY_GET, ctx, net_idx, 0xFF); +} + +int bt_mesh_cfg_node_identity_set(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, u8_t identity) +{ + if (!ctx || !ctx->addr || identity > 0x01) { + return -EINVAL; + } + return node_identity_op(OP_NODE_IDENTITY_SET, ctx, net_idx, identity); +} + +int bt_mesh_cfg_mod_app_unbind(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t app_idx, u16_t mod_id, u16_t cid) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 8 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_MOD_APP_UNBIND); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_le16(&msg, app_idx); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, OP_MOD_APP_UNBIND, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +static int mod_app_get(u32_t op, struct bt_mesh_msg_ctx *ctx, + u16_t elem_addr, u16_t mod_id, u16_t cid) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 6 + 4); + int err; + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_le16(&msg, elem_addr); + if (cid != CID_NVAL) { + net_buf_simple_add_le16(&msg, cid); + } + net_buf_simple_add_le16(&msg, mod_id); + + err = bt_mesh_client_send_msg(cli->model, op, ctx, &msg, timeout_handler, + config_msg_timeout, true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_mod_app_get(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, u16_t mod_id) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return mod_app_get(OP_SIG_MOD_APP_GET, ctx, elem_addr, mod_id, CID_NVAL); +} + +int bt_mesh_cfg_mod_app_get_vnd(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid) +{ + if (!ctx || !ctx->addr || cid == CID_NVAL) { + return -EINVAL; + } + return mod_app_get(OP_VND_MOD_APP_GET, ctx, elem_addr, mod_id, cid); +} + +static int kr_phase_op(u32_t op, struct bt_mesh_msg_ctx *ctx, + u16_t net_idx, u8_t transition) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 3 + 4); + int err; + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_le16(&msg, net_idx); + if (op == OP_KRP_SET) { + net_buf_simple_add_u8(&msg, transition); + } + + err = bt_mesh_client_send_msg(cli->model, op, ctx, &msg, timeout_handler, + config_msg_timeout, true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_kr_phase_get(struct bt_mesh_msg_ctx *ctx, u16_t net_idx) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return kr_phase_op(OP_KRP_GET, ctx, net_idx, 0xFF); +} + +int bt_mesh_cfg_kr_phase_set(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, u8_t transition) +{ + if (!ctx || !ctx->addr || transition > 0x03) { + return -EINVAL; + } + return kr_phase_op(OP_KRP_SET, ctx, net_idx, transition);; +} + +int bt_mesh_cfg_lpn_timeout_get(struct bt_mesh_msg_ctx *ctx, u16_t lpn_addr) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 2 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_LPN_TIMEOUT_GET); + net_buf_simple_add_le16(&msg, lpn_addr); + + err = bt_mesh_client_send_msg(cli->model, OP_LPN_TIMEOUT_GET, ctx, &msg, + timeout_handler, config_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_cfg_net_transmit_get(struct bt_mesh_msg_ctx *ctx) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return get_state_u8(ctx, OP_NET_TRANSMIT_GET); +} + +int bt_mesh_cfg_net_transmit_set(struct bt_mesh_msg_ctx *ctx, u8_t transmit) +{ + if (!ctx || !ctx->addr) { + return -EINVAL; + } + return set_state_u8(ctx, OP_NET_TRANSMIT_SET, transmit); +} + +s32_t bt_mesh_cfg_cli_timeout_get(void) +{ + return config_msg_timeout; +} + +void bt_mesh_cfg_cli_timeout_set(s32_t timeout) +{ + config_msg_timeout = timeout; +} + +int bt_mesh_cfg_cli_init(struct bt_mesh_model *model, bool primary) +{ + config_internal_data_t *internal = NULL; + bt_mesh_config_client_t *client = NULL; + + BT_DBG("primary %u", primary); + + if (!primary) { + BT_ERR("Configuration Client only allowed in primary element"); + return -EINVAL; + } + + if (!model) { + BT_ERR("Configuration Client model is NULL"); + return -EINVAL; + } + + client = (bt_mesh_config_client_t *)model->user_data; + if (!client) { + BT_ERR("No Configuration Client context provided"); + return -EINVAL; + } + + /* TODO: call osi_free() when deinit function is invoked*/ + internal = osi_calloc(sizeof(config_internal_data_t)); + if (!internal) { + BT_ERR("Allocate memory for Configuration Client internal data fail"); + return -ENOMEM; + } + + sys_slist_init(&internal->queue); + + client->model = model; + client->op_pair_size = ARRAY_SIZE(cfg_op_pair); + client->op_pair = cfg_op_pair; + client->internal_data = internal; + + cli = client; + + /* Configuration Model security is device-key based */ + model->keys[0] = BLE_MESH_KEY_DEV; + + return 0; +} diff --git a/components/bt/ble_mesh/mesh_core/cfg_srv.c b/components/bt/ble_mesh/mesh_core/cfg_srv.c new file mode 100644 index 0000000000..4e330e61a0 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/cfg_srv.c @@ -0,0 +1,3593 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_MODEL) + +#include "mesh_types.h" +#include "mesh_util.h" +#include "mesh_main.h" +#include "mesh_trace.h" +#include "cfg_srv.h" +#include "settings.h" + +#include "mesh.h" +#include "adv.h" +#include "net.h" +#include "lpn.h" +#include "transport.h" +#include "crypto.h" +#include "access.h" +#include "beacon.h" +#include "proxy.h" +#include "foundation.h" +#include "friend.h" +#include "settings.h" + +#include "mesh_common.h" +#include "btc_ble_mesh_config_model.h" + +#define DEFAULT_TTL 7 + +/* Maximum message length is 384 in BLE Mesh. Here for composition data, + * due to 1 octet opcode and 4 octets TransMIC, 379 octets can be used to + * store device composition data. + */ +#define COMP_DATA_MAX_LEN 379 + +static struct bt_mesh_cfg_srv *conf; + +static struct label { + u16_t ref; + u16_t addr; + u8_t uuid[16]; +} labels[CONFIG_BLE_MESH_LABEL_COUNT]; + +static void hb_send(struct bt_mesh_model *model) +{ + struct bt_mesh_cfg_srv *cfg = model->user_data; + u16_t feat = 0U; + struct __packed { + u8_t init_ttl; + u16_t feat; + } hb; + struct bt_mesh_msg_ctx ctx = { + .net_idx = cfg->hb_pub.net_idx, + .app_idx = BLE_MESH_KEY_UNUSED, + .addr = cfg->hb_pub.dst, + .send_ttl = cfg->hb_pub.ttl, + }; + struct bt_mesh_net_tx tx = { + .sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx), + .ctx = &ctx, + .src = bt_mesh_model_elem(model)->addr, + .xmit = bt_mesh_net_transmit_get(), + }; + + hb.init_ttl = cfg->hb_pub.ttl; + + if (bt_mesh_relay_get() == BLE_MESH_RELAY_ENABLED) { + feat |= BLE_MESH_FEAT_RELAY; + } + + if (bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_ENABLED) { + feat |= BLE_MESH_FEAT_PROXY; + } + + if (bt_mesh_friend_get() == BLE_MESH_FRIEND_ENABLED) { + feat |= BLE_MESH_FEAT_FRIEND; + } + +#if defined(CONFIG_BLE_MESH_LOW_POWER) + if (bt_mesh.lpn.state != BLE_MESH_LPN_DISABLED) { + feat |= BLE_MESH_FEAT_LOW_POWER; + } +#endif + + hb.feat = sys_cpu_to_be16(feat); + + BT_DBG("InitTTL %u feat 0x%04x", cfg->hb_pub.ttl, feat); + + bt_mesh_ctl_send(&tx, TRANS_CTL_OP_HEARTBEAT, &hb, sizeof(hb), + NULL, NULL, NULL); +} + +static int comp_add_elem(struct net_buf_simple *buf, struct bt_mesh_elem *elem, + bool primary) +{ + struct bt_mesh_model *mod; + int i; + + if (net_buf_simple_tailroom(buf) < + 4 + (elem->model_count * 2U) + (elem->vnd_model_count * 2U)) { + BT_ERR("%s, Too large device composition", __func__); + return -E2BIG; + } + + net_buf_simple_add_le16(buf, elem->loc); + + net_buf_simple_add_u8(buf, elem->model_count); + net_buf_simple_add_u8(buf, elem->vnd_model_count); + + for (i = 0; i < elem->model_count; i++) { + mod = &elem->models[i]; + net_buf_simple_add_le16(buf, mod->id); + } + + for (i = 0; i < elem->vnd_model_count; i++) { + mod = &elem->vnd_models[i]; + net_buf_simple_add_le16(buf, mod->vnd.company); + net_buf_simple_add_le16(buf, mod->vnd.id); + } + + return 0; +} + +static int comp_get_page_0(struct net_buf_simple *buf) +{ + u16_t feat = 0U; + const struct bt_mesh_comp *comp; + int i; + + comp = bt_mesh_comp_get(); + + if (IS_ENABLED(CONFIG_BLE_MESH_RELAY)) { + feat |= BLE_MESH_FEAT_RELAY; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY)) { + feat |= BLE_MESH_FEAT_PROXY; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + feat |= BLE_MESH_FEAT_FRIEND; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + feat |= BLE_MESH_FEAT_LOW_POWER; + } + + net_buf_simple_add_le16(buf, comp->cid); + net_buf_simple_add_le16(buf, comp->pid); + net_buf_simple_add_le16(buf, comp->vid); + net_buf_simple_add_le16(buf, CONFIG_BLE_MESH_CRPL); + net_buf_simple_add_le16(buf, feat); + + for (i = 0; i < comp->elem_count; i++) { + int err; + + err = comp_add_elem(buf, &comp->elem[i], i == 0); + if (err) { + return err; + } + } + + return 0; +} + +static void dev_comp_data_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct net_buf_simple *sdu = NULL; + u8_t page; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + page = net_buf_simple_pull_u8(buf); + if (page != 0U) { + BT_WARN("Composition page %u not available", page); + page = 0U; + } + + sdu = bt_mesh_alloc_buf(MIN(BLE_MESH_TX_SDU_MAX, COMP_DATA_MAX_LEN)); + if (!sdu) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + bt_mesh_model_msg_init(sdu, OP_DEV_COMP_DATA_STATUS); + + net_buf_simple_add_u8(sdu, page); + if (comp_get_page_0(sdu) < 0) { + BT_ERR("%s, Unable to get composition page 0", __func__); + bt_mesh_free_buf(sdu); + return; + } + + if (bt_mesh_model_send(model, ctx, sdu, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Composition Data Status", __func__); + } + + bt_mesh_free_buf(sdu); + return; +} + +static struct bt_mesh_model *get_model(struct bt_mesh_elem *elem, + struct net_buf_simple *buf, bool *vnd) +{ + if (buf->len < 4) { + u16_t id; + + id = net_buf_simple_pull_le16(buf); + + BT_DBG("ID 0x%04x addr 0x%04x", id, elem->addr); + + *vnd = false; + + return bt_mesh_model_find(elem, id); + } else { + u16_t company, id; + + company = net_buf_simple_pull_le16(buf); + id = net_buf_simple_pull_le16(buf); + + BT_DBG("Company 0x%04x ID 0x%04x addr 0x%04x", company, id, + elem->addr); + + *vnd = true; + + return bt_mesh_model_find_vnd(elem, company, id); + } +} + +static bool app_key_is_valid(u16_t app_idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) { + struct bt_mesh_app_key *key = &bt_mesh.app_keys[i]; + + if (key->net_idx != BLE_MESH_KEY_UNUSED && + key->app_idx == app_idx) { + return true; + } + } + + return false; +} + +static u8_t _mod_pub_set(struct bt_mesh_model *model, u16_t pub_addr, + u16_t app_idx, u8_t cred_flag, u8_t ttl, u8_t period, + u8_t retransmit, bool store) +{ + if (!model->pub) { + return STATUS_NVAL_PUB_PARAM; + } + + if (!IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER) && cred_flag) { + return STATUS_FEAT_NOT_SUPP; + } + + if (!model->pub->update && period) { + return STATUS_NVAL_PUB_PARAM; + } + + if (pub_addr == BLE_MESH_ADDR_UNASSIGNED) { + if (model->pub->addr == BLE_MESH_ADDR_UNASSIGNED) { + return STATUS_SUCCESS; + } + + model->pub->addr = BLE_MESH_ADDR_UNASSIGNED; + model->pub->key = 0U; + model->pub->cred = 0U; + model->pub->ttl = 0U; + model->pub->period = 0U; + model->pub->retransmit = 0U; + model->pub->count = 0U; + + if (model->pub->update) { + k_delayed_work_cancel(&model->pub->timer); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS) && store) { + bt_mesh_store_mod_pub(model); + } + + return STATUS_SUCCESS; + } + + if (!bt_mesh_app_key_find(app_idx)) { + return STATUS_INVALID_APPKEY; + } + + model->pub->addr = pub_addr; + model->pub->key = app_idx; + model->pub->cred = cred_flag; + model->pub->ttl = ttl; + model->pub->period = period; + model->pub->retransmit = retransmit; + + if (model->pub->update) { + s32_t period_ms; + + period_ms = bt_mesh_model_pub_period_get(model); + BT_DBG("period %u ms", period_ms); + + if (period_ms) { + k_delayed_work_submit(&model->pub->timer, period_ms); + } else { + k_delayed_work_cancel(&model->pub->timer); + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS) && store) { + bt_mesh_store_mod_pub(model); + } + + return STATUS_SUCCESS; +} + +static u8_t mod_bind(struct bt_mesh_model *model, u16_t key_idx) +{ + int i; + + BT_DBG("model %p key_idx 0x%03x", model, key_idx); + + if (!app_key_is_valid(key_idx)) { + return STATUS_INVALID_APPKEY; + } + + for (i = 0; i < ARRAY_SIZE(model->keys); i++) { + /* Treat existing binding as success */ + if (model->keys[i] == key_idx) { + return STATUS_SUCCESS; + } + } + + for (i = 0; i < ARRAY_SIZE(model->keys); i++) { + if (model->keys[i] == BLE_MESH_KEY_UNUSED) { + model->keys[i] = key_idx; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_bind(model); + } + + return STATUS_SUCCESS; + } + } + + return STATUS_INSUFF_RESOURCES; +} + +static u8_t mod_unbind(struct bt_mesh_model *model, u16_t key_idx, bool store) +{ + int i; + + BT_DBG("model %p key_idx 0x%03x store %u", model, key_idx, store); + + if (!app_key_is_valid(key_idx)) { + return STATUS_INVALID_APPKEY; + } + + for (i = 0; i < ARRAY_SIZE(model->keys); i++) { + if (model->keys[i] != key_idx) { + continue; + } + + model->keys[i] = BLE_MESH_KEY_UNUSED; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS) && store) { + bt_mesh_store_mod_bind(model); + } + + if (model->pub && model->pub->key == key_idx) { + _mod_pub_set(model, BLE_MESH_ADDR_UNASSIGNED, + 0, 0, 0, 0, 0, store); + } + } + + return STATUS_SUCCESS; +} + +struct bt_mesh_app_key *bt_mesh_app_key_alloc(u16_t app_idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) { + struct bt_mesh_app_key *key = &bt_mesh.app_keys[i]; + + if (key->net_idx == BLE_MESH_KEY_UNUSED) { + return key; + } + } + + return NULL; +} + +static u8_t app_key_set(u16_t net_idx, u16_t app_idx, const u8_t val[16], + bool update) +{ + struct bt_mesh_app_keys *keys; + struct bt_mesh_app_key *key; + struct bt_mesh_subnet *sub; + + BT_DBG("net_idx 0x%04x app_idx %04x update %u val %s", + net_idx, app_idx, update, bt_hex(val, 16)); + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) { + return STATUS_INVALID_NETKEY; + } + + key = bt_mesh_app_key_find(app_idx); + if (update) { + if (!key) { + return STATUS_INVALID_APPKEY; + } + + if (key->net_idx != net_idx) { + return STATUS_INVALID_BINDING; + } + + keys = &key->keys[1]; + + /* The AppKey Update message shall generate an error when node + * is in normal operation, Phase 2, or Phase 3 or in Phase 1 + * when the AppKey Update message on a valid AppKeyIndex when + * the AppKey value is different. + */ + if (sub->kr_phase != BLE_MESH_KR_PHASE_1) { + return STATUS_CANNOT_UPDATE; + } + + if (key->updated) { + if (memcmp(keys->val, val, 16)) { + return STATUS_CANNOT_UPDATE; + } else { + return STATUS_SUCCESS; + } + } + + key->updated = true; + } else { + if (key) { + if (key->net_idx == net_idx && + !memcmp(key->keys[0].val, val, 16)) { + return STATUS_SUCCESS; + } + + if (key->net_idx == net_idx) { + return STATUS_IDX_ALREADY_STORED; + } else { + return STATUS_INVALID_NETKEY; + } + } + + key = bt_mesh_app_key_alloc(app_idx); + if (!key) { + return STATUS_INSUFF_RESOURCES; + } + + keys = &key->keys[0]; + } + + if (bt_mesh_app_id(val, &keys->id)) { + if (update) { + key->updated = false; + } + + return STATUS_STORAGE_FAIL; + } + + BT_DBG("app_idx 0x%04x AID 0x%02x", app_idx, keys->id); + + key->net_idx = net_idx; + key->app_idx = app_idx; + memcpy(keys->val, val, 16); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + BT_DBG("Storing AppKey persistently"); + bt_mesh_store_app_key(key); + } + + return STATUS_SUCCESS; +} + +static void app_key_add(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 4 + 4); + u16_t key_net_idx, key_app_idx; + u8_t status; + + key_idx_unpack(buf, &key_net_idx, &key_app_idx); + + BT_DBG("AppIdx 0x%04x NetIdx 0x%04x", key_app_idx, key_net_idx); + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_STATUS); + + status = app_key_set(key_net_idx, key_app_idx, buf->data, false); + BT_DBG("status 0x%02x", status); + net_buf_simple_add_u8(&msg, status); + + key_idx_pack(&msg, key_net_idx, key_app_idx); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config AppKey Status", __func__); + return; + } + +#if defined(CONFIG_BLE_MESH_FAST_PROV) + bt_mesh_callback_cfg_server_event_to_btc(0x0, model, ctx, + (u8_t *)&key_app_idx, sizeof(u16_t)); +#endif +} + +static void app_key_update(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 4 + 4); + u16_t key_net_idx, key_app_idx; + u8_t status; + + key_idx_unpack(buf, &key_net_idx, &key_app_idx); + + BT_DBG("AppIdx 0x%04x NetIdx 0x%04x", key_app_idx, key_net_idx); + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_STATUS); + + status = app_key_set(key_net_idx, key_app_idx, buf->data, true); + BT_DBG("status 0x%02x", status); + net_buf_simple_add_u8(&msg, status); + + key_idx_pack(&msg, key_net_idx, key_app_idx); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config AppKey Status", __func__); + } +} + +struct unbind_data { + u16_t app_idx; + bool store; +}; + +static void _mod_unbind(struct bt_mesh_model *mod, struct bt_mesh_elem *elem, + bool vnd, bool primary, void *user_data) +{ + struct unbind_data *data = user_data; + + mod_unbind(mod, data->app_idx, data->store); +} + +void bt_mesh_app_key_del(struct bt_mesh_app_key *key, bool store) +{ + struct unbind_data data = { .app_idx = key->app_idx, .store = store }; + + BT_DBG("AppIdx 0x%03x store %u", key->app_idx, store); + + bt_mesh_model_foreach(_mod_unbind, &data); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS) && store) { + bt_mesh_clear_app_key(key); + } + + key->net_idx = BLE_MESH_KEY_UNUSED; + (void)memset(key->keys, 0, sizeof(key->keys)); +} + +static void app_key_del(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 4 + 4); + u16_t key_net_idx, key_app_idx; + struct bt_mesh_app_key *key; + u8_t status; + + key_idx_unpack(buf, &key_net_idx, &key_app_idx); + + BT_DBG("AppIdx 0x%04x NetIdx 0x%04x", key_app_idx, key_net_idx); + + if (!bt_mesh_subnet_get(key_net_idx)) { + status = STATUS_INVALID_NETKEY; + goto send_status; + } + + key = bt_mesh_app_key_find(key_app_idx); + if (!key) { + /* Treat as success since the client might have missed a + * previous response and is resending the request. + */ + status = STATUS_SUCCESS; + goto send_status; + } + + if (key->net_idx != key_net_idx) { + status = STATUS_INVALID_BINDING; + goto send_status; + } + + bt_mesh_app_key_del(key, true); + status = STATUS_SUCCESS; + +send_status: + bt_mesh_model_msg_init(&msg, OP_APP_KEY_STATUS); + + net_buf_simple_add_u8(&msg, status); + + key_idx_pack(&msg, key_net_idx, key_app_idx); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config AppKey Status", __func__); + } +} + +/* Index list length: 3 bytes for every pair and 2 bytes for an odd idx */ +#define IDX_LEN(num) (((num) / 2) * 3 + ((num) % 2) * 2) + +static void app_key_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 3 + 4 + + IDX_LEN(CONFIG_BLE_MESH_APP_KEY_COUNT)); + u16_t get_idx, i, prev; + u8_t status; + + get_idx = net_buf_simple_pull_le16(buf); + if (get_idx > 0xfff) { + BT_ERR("%s, Invalid NetKeyIndex 0x%04x", __func__, get_idx); + return; + } + + BT_DBG("idx 0x%04x", get_idx); + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_LIST); + + if (!bt_mesh_subnet_get(get_idx)) { + status = STATUS_INVALID_NETKEY; + } else { + status = STATUS_SUCCESS; + } + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, get_idx); + + if (status != STATUS_SUCCESS) { + goto send_status; + } + + prev = BLE_MESH_KEY_UNUSED; + for (i = 0U; i < ARRAY_SIZE(bt_mesh.app_keys); i++) { + struct bt_mesh_app_key *key = &bt_mesh.app_keys[i]; + + if (key->net_idx != get_idx) { + continue; + } + + if (prev == BLE_MESH_KEY_UNUSED) { + prev = key->app_idx; + continue; + } + + key_idx_pack(&msg, prev, key->app_idx); + prev = BLE_MESH_KEY_UNUSED; + } + + if (prev != BLE_MESH_KEY_UNUSED) { + net_buf_simple_add_le16(&msg, prev); + } + +send_status: + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config AppKey List", __func__); + } +} + +static void beacon_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + bt_mesh_model_msg_init(&msg, OP_BEACON_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_beacon_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Beacon Status", __func__); + } +} + +static void beacon_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + struct bt_mesh_cfg_srv *cfg = model->user_data; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + if (!cfg) { + BT_WARN("No Configuration Server context available"); + } else if (buf->data[0] == 0x00 || buf->data[0] == 0x01) { + if (buf->data[0] != cfg->beacon) { + cfg->beacon = buf->data[0]; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_cfg(); + } + +#if CONFIG_BLE_MESH_NODE + if (cfg->beacon) { + bt_mesh_beacon_enable(); + } else { + bt_mesh_beacon_disable(); + } +#endif + } + } else { + BT_WARN("Invalid Config Beacon value 0x%02x", buf->data[0]); + return; + } + + bt_mesh_model_msg_init(&msg, OP_BEACON_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_beacon_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Beacon Status", __func__); + } +} + +static void default_ttl_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + bt_mesh_model_msg_init(&msg, OP_DEFAULT_TTL_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_default_ttl_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Default TTL Status", __func__); + } +} + +static void default_ttl_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + struct bt_mesh_cfg_srv *cfg = model->user_data; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + if (!cfg) { + BT_WARN("No Configuration Server context available"); + } else if (buf->data[0] <= BLE_MESH_TTL_MAX && buf->data[0] != 0x01) { + if (cfg->default_ttl != buf->data[0]) { + cfg->default_ttl = buf->data[0]; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_cfg(); + } + } + } else { + BT_WARN("Prohibited Default TTL value 0x%02x", buf->data[0]); + return; + } + + bt_mesh_model_msg_init(&msg, OP_DEFAULT_TTL_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_default_ttl_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Default TTL Status", __func__); + } +} + +static void send_gatt_proxy_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + + bt_mesh_model_msg_init(&msg, OP_GATT_PROXY_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_gatt_proxy_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config GATT Proxy Status", __func__); + } +} + +static void gatt_proxy_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + send_gatt_proxy_status(model, ctx); +} + +static void gatt_proxy_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_srv *cfg = model->user_data; + struct bt_mesh_subnet *sub; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + if (buf->data[0] != 0x00 && buf->data[0] != 0x01) { + BT_WARN("Invalid GATT Proxy value 0x%02x", buf->data[0]); + return; + } + + if (!IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY) || + bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_NOT_SUPPORTED) { + goto send_status; + } + + if (!cfg) { + BT_WARN("No Configuration Server context available"); + goto send_status; + } + + BT_DBG("GATT Proxy 0x%02x -> 0x%02x", cfg->gatt_proxy, buf->data[0]); + + if (cfg->gatt_proxy == buf->data[0]) { + goto send_status; + } + + cfg->gatt_proxy = buf->data[0]; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_cfg(); + } + +#if CONFIG_BLE_MESH_NODE + if (cfg->gatt_proxy == BLE_MESH_GATT_PROXY_DISABLED) { + int i; + + /* Section 4.2.11.1: "When the GATT Proxy state is set to + * 0x00, the Node Identity state for all subnets shall be set + * to 0x00 and shall not be changed." + */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (sub->net_idx != BLE_MESH_KEY_UNUSED) { + bt_mesh_proxy_identity_stop(sub); + } + } + + /* Section 4.2.11: "Upon transition from GATT Proxy state 0x01 + * to GATT Proxy state 0x00 the GATT Bearer Server shall + * disconnect all GATT Bearer Clients. + */ + bt_mesh_proxy_gatt_disconnect(); + } + + bt_mesh_adv_update(); +#endif + + sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx); + if ((cfg->hb_pub.feat & BLE_MESH_FEAT_PROXY) && sub) { + hb_send(model); + } + +send_status: + send_gatt_proxy_status(model, ctx); +} + +static void net_transmit_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + bt_mesh_model_msg_init(&msg, OP_NET_TRANSMIT_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_net_transmit_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Network Transmit Status", __func__); + } +} + +static void net_transmit_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + struct bt_mesh_cfg_srv *cfg = model->user_data; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + BT_DBG("Transmit 0x%02x (count %u interval %ums)", buf->data[0], + BLE_MESH_TRANSMIT_COUNT(buf->data[0]), + BLE_MESH_TRANSMIT_INT(buf->data[0])); + + if (!cfg) { + BT_WARN("No Configuration Server context available"); + } else { + cfg->net_transmit = buf->data[0]; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_cfg(); + } + } + + bt_mesh_model_msg_init(&msg, OP_NET_TRANSMIT_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_net_transmit_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Network Transmit Status", __func__); + } +} + +static void relay_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 2 + 4); + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + bt_mesh_model_msg_init(&msg, OP_RELAY_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_relay_get()); + net_buf_simple_add_u8(&msg, bt_mesh_relay_retransmit_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Relay Status", __func__); + } +} + +static void relay_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 2 + 4); + struct bt_mesh_cfg_srv *cfg = model->user_data; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + if (!cfg) { + BT_WARN("No Configuration Server context available"); + } else if (buf->data[0] == 0x00 || buf->data[0] == 0x01) { + struct bt_mesh_subnet *sub; + bool change; + + if (cfg->relay == BLE_MESH_RELAY_NOT_SUPPORTED) { + change = false; + } else { + change = (cfg->relay != buf->data[0]); + cfg->relay = buf->data[0]; + cfg->relay_retransmit = buf->data[1]; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_cfg(); + } + } + + BT_DBG("Relay 0x%02x (%s) xmit 0x%02x (count %u interval %u)", + cfg->relay, change ? "changed" : "not changed", + cfg->relay_retransmit, + BLE_MESH_TRANSMIT_COUNT(cfg->relay_retransmit), + BLE_MESH_TRANSMIT_INT(cfg->relay_retransmit)); + + sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx); + if ((cfg->hb_pub.feat & BLE_MESH_FEAT_RELAY) && sub && change) { + hb_send(model); + } + } else { + BT_WARN("Invalid Relay value 0x%02x", buf->data[0]); + return; + } + + bt_mesh_model_msg_init(&msg, OP_RELAY_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_relay_get()); + net_buf_simple_add_u8(&msg, bt_mesh_relay_retransmit_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Relay Status", __func__); + } +} + +static void send_mod_pub_status(struct bt_mesh_model *cfg_mod, + struct bt_mesh_msg_ctx *ctx, + u16_t elem_addr, u16_t pub_addr, + bool vnd, struct bt_mesh_model *mod, + u8_t status, u8_t *mod_id) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 14 + 4); + + bt_mesh_model_msg_init(&msg, OP_MOD_PUB_STATUS); + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, elem_addr); + + if (status != STATUS_SUCCESS) { + (void)memset(net_buf_simple_add(&msg, 7), 0, 7); + } else { + u16_t idx_cred; + + net_buf_simple_add_le16(&msg, pub_addr); + + idx_cred = mod->pub->key | (u16_t)mod->pub->cred << 12; + net_buf_simple_add_le16(&msg, idx_cred); + net_buf_simple_add_u8(&msg, mod->pub->ttl); + net_buf_simple_add_u8(&msg, mod->pub->period); + net_buf_simple_add_u8(&msg, mod->pub->retransmit); + } + + if (vnd) { + memcpy(net_buf_simple_add(&msg, 4), mod_id, 4); + } else { + memcpy(net_buf_simple_add(&msg, 2), mod_id, 2); + } + + if (bt_mesh_model_send(cfg_mod, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Model Publication Status", __func__); + } +} + +static void mod_pub_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u16_t elem_addr, pub_addr = 0U; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *mod_id, status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + mod_id = buf->data; + + BT_DBG("elem_addr 0x%04x", elem_addr); + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (!mod->pub) { + status = STATUS_NVAL_PUB_PARAM; + goto send_status; + } + + pub_addr = mod->pub->addr; + status = STATUS_SUCCESS; + +send_status: + send_mod_pub_status(model, ctx, elem_addr, pub_addr, vnd, mod, + status, mod_id); +} + +static void mod_pub_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t retransmit, status, pub_ttl, pub_period, cred_flag; + u16_t elem_addr, pub_addr, pub_app_idx; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *mod_id; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + pub_addr = net_buf_simple_pull_le16(buf); + pub_app_idx = net_buf_simple_pull_le16(buf); + cred_flag = ((pub_app_idx >> 12) & BIT_MASK(1)); + pub_app_idx &= BIT_MASK(12); + + pub_ttl = net_buf_simple_pull_u8(buf); + if (pub_ttl > BLE_MESH_TTL_MAX && pub_ttl != BLE_MESH_TTL_DEFAULT) { + BT_ERR("%s, Invalid TTL value 0x%02x", __func__, pub_ttl); + return; + } + + pub_period = net_buf_simple_pull_u8(buf); + retransmit = net_buf_simple_pull_u8(buf); + mod_id = buf->data; + + BT_DBG("elem_addr 0x%04x pub_addr 0x%04x cred_flag %u", + elem_addr, pub_addr, cred_flag); + BT_DBG("pub_app_idx 0x%03x, pub_ttl %u pub_period 0x%02x", + pub_app_idx, pub_ttl, pub_period); + BT_DBG("retransmit 0x%02x (count %u interval %ums)", retransmit, + BLE_MESH_PUB_TRANSMIT_COUNT(retransmit), + BLE_MESH_PUB_TRANSMIT_INT(retransmit)); + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = _mod_pub_set(mod, pub_addr, pub_app_idx, cred_flag, pub_ttl, + pub_period, retransmit, true); + +send_status: + send_mod_pub_status(model, ctx, elem_addr, pub_addr, vnd, mod, + status, mod_id); +} + +#if CONFIG_BLE_MESH_LABEL_COUNT > 0 +static u8_t va_add(u8_t *label_uuid, u16_t *addr) +{ + struct label *free_slot = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(labels); i++) { + if (!labels[i].ref) { + free_slot = &labels[i]; + continue; + } + + if (!memcmp(labels[i].uuid, label_uuid, 16)) { + *addr = labels[i].addr; + labels[i].ref++; + return STATUS_SUCCESS; + } + } + + if (!free_slot) { + return STATUS_INSUFF_RESOURCES; + } + + if (bt_mesh_virtual_addr(label_uuid, addr) < 0) { + return STATUS_UNSPECIFIED; + } + + free_slot->ref = 1U; + free_slot->addr = *addr; + memcpy(free_slot->uuid, label_uuid, 16); + + return STATUS_SUCCESS; +} + +static u8_t va_del(u8_t *label_uuid, u16_t *addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(labels); i++) { + if (!memcmp(labels[i].uuid, label_uuid, 16)) { + if (addr) { + *addr = labels[i].addr; + } + + labels[i].ref--; + return STATUS_SUCCESS; + } + } + + if (addr) { + *addr = BLE_MESH_ADDR_UNASSIGNED; + } + + return STATUS_CANNOT_REMOVE; +} + +static size_t mod_sub_list_clear(struct bt_mesh_model *mod) +{ + u8_t *label_uuid; + size_t clear_count; + int i; + + /* Unref stored labels related to this model */ + for (i = 0, clear_count = 0; i < ARRAY_SIZE(mod->groups); i++) { + if (!BLE_MESH_ADDR_IS_VIRTUAL(mod->groups[i])) { + if (mod->groups[i] != BLE_MESH_ADDR_UNASSIGNED) { + mod->groups[i] = BLE_MESH_ADDR_UNASSIGNED; + clear_count++; + } + + continue; + } + + label_uuid = bt_mesh_label_uuid_get(mod->groups[i]); + + mod->groups[i] = BLE_MESH_ADDR_UNASSIGNED; + clear_count++; + + if (label_uuid) { + va_del(label_uuid, NULL); + } else { + BT_ERR("%s, Label UUID not found", __func__); + } + } + + return clear_count; +} + +static void mod_pub_va_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t retransmit, status, pub_ttl, pub_period, cred_flag; + u16_t elem_addr, pub_addr, pub_app_idx; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *label_uuid; + u8_t *mod_id; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + label_uuid = net_buf_simple_pull_mem(buf, 16); + pub_app_idx = net_buf_simple_pull_le16(buf); + cred_flag = ((pub_app_idx >> 12) & BIT_MASK(1)); + pub_app_idx &= BIT_MASK(12); + pub_ttl = net_buf_simple_pull_u8(buf); + if (pub_ttl > BLE_MESH_TTL_MAX && pub_ttl != BLE_MESH_TTL_DEFAULT) { + BT_ERR("%s, Invalid TTL value 0x%02x", __func__, pub_ttl); + return; + } + + pub_period = net_buf_simple_pull_u8(buf); + retransmit = net_buf_simple_pull_u8(buf); + mod_id = buf->data; + + BT_DBG("elem_addr 0x%04x cred_flag %u", elem_addr, cred_flag); + BT_DBG("pub_app_idx 0x%03x, pub_ttl %u pub_period 0x%02x", + pub_app_idx, pub_ttl, pub_period); + BT_DBG("retransmit 0x%02x (count %u interval %ums)", retransmit, + BLE_MESH_PUB_TRANSMIT_COUNT(retransmit), + BLE_MESH_PUB_TRANSMIT_INT(retransmit)); + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + pub_addr = 0U; + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + pub_addr = 0U; + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = va_add(label_uuid, &pub_addr); + if (status == STATUS_SUCCESS) { + status = _mod_pub_set(mod, pub_addr, pub_app_idx, cred_flag, + pub_ttl, pub_period, retransmit, true); + } + +send_status: + send_mod_pub_status(model, ctx, elem_addr, pub_addr, vnd, mod, + status, mod_id); +} +#else +static size_t mod_sub_list_clear(struct bt_mesh_model *mod) +{ + size_t clear_count; + int i; + + /* Unref stored labels related to this model */ + for (i = 0, clear_count = 0; i < ARRAY_SIZE(mod->groups); i++) { + if (mod->groups[i] != BLE_MESH_ADDR_UNASSIGNED) { + mod->groups[i] = BLE_MESH_ADDR_UNASSIGNED; + clear_count++; + } + } + + return clear_count; +} + +static void mod_pub_va_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t *mod_id, status; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u16_t elem_addr, pub_addr = 0U; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + net_buf_simple_pull(buf, 16); + mod_id = net_buf_simple_pull(buf, 4); + + BT_DBG("elem_addr 0x%04x", elem_addr); + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (!mod->pub) { + status = STATUS_NVAL_PUB_PARAM; + goto send_status; + } + + pub_addr = mod->pub->addr; + status = STATUS_INSUFF_RESOURCES; + +send_status: + send_mod_pub_status(model, ctx, elem_addr, pub_addr, vnd, mod, + status, mod_id); +} +#endif /* CONFIG_BLE_MESH_LABEL_COUNT > 0 */ + +static void send_mod_sub_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, u8_t status, + u16_t elem_addr, u16_t sub_addr, u8_t *mod_id, + bool vnd) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 9 + 4); + + BT_DBG("status 0x%02x elem_addr 0x%04x sub_addr 0x%04x", status, + elem_addr, sub_addr); + + bt_mesh_model_msg_init(&msg, OP_MOD_SUB_STATUS); + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_le16(&msg, sub_addr); + + if (vnd) { + memcpy(net_buf_simple_add(&msg, 4), mod_id, 4); + } else { + memcpy(net_buf_simple_add(&msg, 2), mod_id, 2); + } + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Model Subscription Status", __func__); + } +} + +static void mod_sub_add(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u16_t elem_addr, sub_addr; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *mod_id; + u8_t status; + bool vnd; + int i; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + sub_addr = net_buf_simple_pull_le16(buf); + + BT_DBG("elem_addr 0x%04x, sub_addr 0x%04x", elem_addr, sub_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (!BLE_MESH_ADDR_IS_GROUP(sub_addr)) { + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + if (bt_mesh_model_find_group(mod, sub_addr)) { + /* Tried to add existing subscription */ + BT_DBG("found existing subscription"); + status = STATUS_SUCCESS; + goto send_status; + } + + for (i = 0; i < ARRAY_SIZE(mod->groups); i++) { + if (mod->groups[i] == BLE_MESH_ADDR_UNASSIGNED) { + mod->groups[i] = sub_addr; + break; + } + } + + if (i == ARRAY_SIZE(mod->groups)) { + status = STATUS_INSUFF_RESOURCES; + } else { + status = STATUS_SUCCESS; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_sub(mod); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_group_add(sub_addr); + } + } + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, + mod_id, vnd); +} + +static void mod_sub_del(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u16_t elem_addr, sub_addr; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *mod_id; + u16_t *match; + u8_t status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + sub_addr = net_buf_simple_pull_le16(buf); + + BT_DBG("elem_addr 0x%04x sub_addr 0x%04x", elem_addr, sub_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (!BLE_MESH_ADDR_IS_GROUP(sub_addr)) { + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + /* An attempt to remove a non-existing address shall be treated + * as a success. + */ + status = STATUS_SUCCESS; + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_group_del(&sub_addr, 1); + } + + match = bt_mesh_model_find_group(mod, sub_addr); + if (match) { + *match = BLE_MESH_ADDR_UNASSIGNED; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_sub(mod); + } + } + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, + mod_id, vnd); +} + +static void mod_sub_overwrite(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u16_t elem_addr, sub_addr; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *mod_id; + u8_t status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + sub_addr = net_buf_simple_pull_le16(buf); + + BT_DBG("elem_addr 0x%04x sub_addr 0x%04x", elem_addr, sub_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (!BLE_MESH_ADDR_IS_GROUP(sub_addr)) { + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_group_del(mod->groups, ARRAY_SIZE(mod->groups)); + } + + mod_sub_list_clear(mod); + + if (ARRAY_SIZE(mod->groups) > 0) { + mod->groups[0] = sub_addr; + status = STATUS_SUCCESS; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_sub(mod); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_group_add(sub_addr); + } + } else { + status = STATUS_INSUFF_RESOURCES; + } + + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, + mod_id, vnd); +} + +static void mod_sub_del_all(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u16_t elem_addr; + u8_t *mod_id; + u8_t status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + BT_DBG("elem_addr 0x%04x", elem_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_group_del(mod->groups, ARRAY_SIZE(mod->groups)); + } + + mod_sub_list_clear(mod); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_sub(mod); + } + + status = STATUS_SUCCESS; + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, + BLE_MESH_ADDR_UNASSIGNED, mod_id, vnd); +} + +static void mod_sub_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 5 + 4 + + CONFIG_BLE_MESH_MODEL_GROUP_COUNT * 2); + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u16_t addr, id; + int i; + + addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, addr); + return; + } + + id = net_buf_simple_pull_le16(buf); + + BT_DBG("addr 0x%04x id 0x%04x", addr, id); + + bt_mesh_model_msg_init(&msg, OP_MOD_SUB_LIST); + + elem = bt_mesh_elem_find(addr); + if (!elem) { + net_buf_simple_add_u8(&msg, STATUS_INVALID_ADDRESS); + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, id); + goto send_list; + } + + mod = bt_mesh_model_find(elem, id); + if (!mod) { + net_buf_simple_add_u8(&msg, STATUS_INVALID_MODEL); + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, id); + goto send_list; + } + + net_buf_simple_add_u8(&msg, STATUS_SUCCESS); + + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, id); + + for (i = 0; i < ARRAY_SIZE(mod->groups); i++) { + if (mod->groups[i] != BLE_MESH_ADDR_UNASSIGNED) { + net_buf_simple_add_le16(&msg, mod->groups[i]); + } + } + +send_list: + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Model Subscription List", __func__); + } +} + +static void mod_sub_get_vnd(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 7 + 4 + + CONFIG_BLE_MESH_MODEL_GROUP_COUNT * 2); + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u16_t company, addr, id; + int i; + + addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, addr); + return; + } + + company = net_buf_simple_pull_le16(buf); + id = net_buf_simple_pull_le16(buf); + + BT_DBG("addr 0x%04x company 0x%04x id 0x%04x", addr, company, id); + + bt_mesh_model_msg_init(&msg, OP_MOD_SUB_LIST_VND); + + elem = bt_mesh_elem_find(addr); + if (!elem) { + net_buf_simple_add_u8(&msg, STATUS_INVALID_ADDRESS); + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, company); + net_buf_simple_add_le16(&msg, id); + goto send_list; + } + + mod = bt_mesh_model_find_vnd(elem, company, id); + if (!mod) { + net_buf_simple_add_u8(&msg, STATUS_INVALID_MODEL); + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, company); + net_buf_simple_add_le16(&msg, id); + goto send_list; + } + + net_buf_simple_add_u8(&msg, STATUS_SUCCESS); + + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, company); + net_buf_simple_add_le16(&msg, id); + + for (i = 0; i < ARRAY_SIZE(mod->groups); i++) { + if (mod->groups[i] != BLE_MESH_ADDR_UNASSIGNED) { + net_buf_simple_add_le16(&msg, mod->groups[i]); + } + } + +send_list: + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Vendor Model Subscription List", __func__); + } +} + +#if CONFIG_BLE_MESH_LABEL_COUNT > 0 +static void mod_sub_va_add(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u16_t elem_addr, sub_addr; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *label_uuid; + u8_t *mod_id; + u8_t status; + bool vnd; + int i; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + label_uuid = net_buf_simple_pull_mem(buf, 16); + + BT_DBG("elem_addr 0x%04x", elem_addr); + + mod_id = buf->data; + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + sub_addr = BLE_MESH_ADDR_UNASSIGNED; + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + sub_addr = BLE_MESH_ADDR_UNASSIGNED; + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = va_add(label_uuid, &sub_addr); + if (status != STATUS_SUCCESS) { + goto send_status; + } + + if (bt_mesh_model_find_group(mod, sub_addr)) { + /* Tried to add existing subscription */ + status = STATUS_SUCCESS; + goto send_status; + } + + for (i = 0; i < ARRAY_SIZE(mod->groups); i++) { + if (mod->groups[i] == BLE_MESH_ADDR_UNASSIGNED) { + mod->groups[i] = sub_addr; + break; + } + } + + if (i == ARRAY_SIZE(mod->groups)) { + status = STATUS_INSUFF_RESOURCES; + } else { + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_group_add(sub_addr); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_sub(mod); + } + + status = STATUS_SUCCESS; + } + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, + mod_id, vnd); +} + +static void mod_sub_va_del(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u16_t elem_addr, sub_addr; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *label_uuid; + u8_t *mod_id; + u16_t *match; + u8_t status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + label_uuid = net_buf_simple_pull_mem(buf, 16); + + BT_DBG("elem_addr 0x%04x", elem_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + sub_addr = BLE_MESH_ADDR_UNASSIGNED; + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + sub_addr = BLE_MESH_ADDR_UNASSIGNED; + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = va_del(label_uuid, &sub_addr); + if (sub_addr == BLE_MESH_ADDR_UNASSIGNED) { + goto send_status; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_group_del(&sub_addr, 1); + } + + match = bt_mesh_model_find_group(mod, sub_addr); + if (match) { + *match = BLE_MESH_ADDR_UNASSIGNED; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_sub(mod); + } + + status = STATUS_SUCCESS; + } else { + status = STATUS_CANNOT_REMOVE; + } + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, + mod_id, vnd); +} + +static void mod_sub_va_overwrite(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u16_t elem_addr, sub_addr = BLE_MESH_ADDR_UNASSIGNED; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *label_uuid; + u8_t *mod_id; + u8_t status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + label_uuid = net_buf_simple_pull_mem(buf, 16); + + BT_DBG("elem_addr 0x%04x", elem_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_group_del(mod->groups, ARRAY_SIZE(mod->groups)); + } + + mod_sub_list_clear(mod); + + if (ARRAY_SIZE(mod->groups) > 0) { + status = va_add(label_uuid, &sub_addr); + if (status == STATUS_SUCCESS) { + mod->groups[0] = sub_addr; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_sub(mod); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_group_add(sub_addr); + } + } + } else { + status = STATUS_INSUFF_RESOURCES; + } + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, + mod_id, vnd); +} +#else +static void mod_sub_va_add(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u16_t elem_addr; + u8_t *mod_id; + u8_t status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + net_buf_simple_pull(buf, 16); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = STATUS_INSUFF_RESOURCES; + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, + BLE_MESH_ADDR_UNASSIGNED, mod_id, vnd); +} + +static void mod_sub_va_del(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_elem *elem; + u16_t elem_addr; + u8_t *mod_id; + u8_t status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + net_buf_simple_pull(buf, 16); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + if (!get_model(elem, buf, &vnd)) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = STATUS_INSUFF_RESOURCES; + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, + BLE_MESH_ADDR_UNASSIGNED, mod_id, vnd); +} + +static void mod_sub_va_overwrite(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_elem *elem; + u16_t elem_addr; + u8_t *mod_id; + u8_t status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + net_buf_simple_pull(buf, 18); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + if (!get_model(elem, buf, &vnd)) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = STATUS_INSUFF_RESOURCES; + +send_status: + send_mod_sub_status(model, ctx, status, elem_addr, + BLE_MESH_ADDR_UNASSIGNED, mod_id, vnd); +} +#endif /* CONFIG_BLE_MESH_LABEL_COUNT > 0 */ + +static void send_net_key_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t idx, u8_t status) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 3 + 4); + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_STATUS); + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, idx); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config NetKey Status", __func__); + } +} + +static void net_key_add(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_subnet *sub; + u16_t idx; + int err; + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) { + BT_ERR("%s, Invalid NetKeyIndex 0x%04x", __func__, idx); + return; + } + + BT_DBG("idx 0x%04x", idx); + + sub = bt_mesh_subnet_get(idx); + if (!sub) { + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + if (bt_mesh.sub[i].net_idx == BLE_MESH_KEY_UNUSED) { + sub = &bt_mesh.sub[i]; + break; + } + } + + if (!sub) { + send_net_key_status(model, ctx, idx, + STATUS_INSUFF_RESOURCES); + return; + } + } + + /* Check for already existing subnet */ + if (sub->net_idx == idx) { + u8_t status; + + if (memcmp(buf->data, sub->keys[0].net, 16)) { + status = STATUS_IDX_ALREADY_STORED; + } else { + status = STATUS_SUCCESS; + } + + send_net_key_status(model, ctx, idx, status); + return; + } + + err = bt_mesh_net_keys_create(&sub->keys[0], buf->data); + if (err) { + send_net_key_status(model, ctx, idx, STATUS_UNSPECIFIED); + return; + } + + sub->net_idx = idx; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + BT_DBG("Storing NetKey persistently"); + bt_mesh_store_subnet(sub); + } + + /* Make sure we have valid beacon data to be sent */ + bt_mesh_net_beacon_update(sub); + + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY)) { + sub->node_id = BLE_MESH_NODE_IDENTITY_STOPPED; +#if CONFIG_BLE_MESH_NODE + bt_mesh_proxy_beacon_send(sub); + bt_mesh_adv_update(); +#endif + } else { + sub->node_id = BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED; + } + + send_net_key_status(model, ctx, idx, STATUS_SUCCESS); +} + +static void net_key_update(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_subnet *sub; + u16_t idx; + int err; + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) { + BT_ERR("%s, Invalid NetKeyIndex 0x%04x", __func__, idx); + return; + } + + BT_DBG("idx 0x%04x", idx); + + sub = bt_mesh_subnet_get(idx); + if (!sub) { + send_net_key_status(model, ctx, idx, STATUS_INVALID_NETKEY); + return; + } + + /* The node shall successfully process a NetKey Update message on a + * valid NetKeyIndex when the NetKey value is different and the Key + * Refresh procedure has not been started, or when the NetKey value is + * the same in Phase 1. The NetKey Update message shall generate an + * error when the node is in Phase 2, or Phase 3. + */ + switch (sub->kr_phase) { + case BLE_MESH_KR_NORMAL: + if (!memcmp(buf->data, sub->keys[0].net, 16)) { + return; + } + break; + case BLE_MESH_KR_PHASE_1: + if (!memcmp(buf->data, sub->keys[1].net, 16)) { + send_net_key_status(model, ctx, idx, STATUS_SUCCESS); + return; + } + /* fall through */ + case BLE_MESH_KR_PHASE_2: + case BLE_MESH_KR_PHASE_3: + send_net_key_status(model, ctx, idx, STATUS_CANNOT_UPDATE); + return; + } + + err = bt_mesh_net_keys_create(&sub->keys[1], buf->data); + if (!err && (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER) || + IS_ENABLED(CONFIG_BLE_MESH_FRIEND))) { + err = friend_cred_update(sub); + } + + if (err) { + send_net_key_status(model, ctx, idx, STATUS_UNSPECIFIED); + return; + } + + sub->kr_phase = BLE_MESH_KR_PHASE_1; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + BT_DBG("Storing NetKey persistently"); + bt_mesh_store_subnet(sub); + } + + bt_mesh_net_beacon_update(sub); + + send_net_key_status(model, ctx, idx, STATUS_SUCCESS); +} + +static void hb_pub_disable(struct bt_mesh_cfg_srv *cfg) +{ + BT_DBG("%s", __func__); + + cfg->hb_pub.dst = BLE_MESH_ADDR_UNASSIGNED; + cfg->hb_pub.count = 0U; + cfg->hb_pub.ttl = 0U; + cfg->hb_pub.period = 0U; + + k_delayed_work_cancel(&cfg->hb_pub.timer); +} + +static void net_key_del(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_subnet *sub; + u16_t del_idx; + u8_t status; + + del_idx = net_buf_simple_pull_le16(buf); + if (del_idx > 0xfff) { + BT_ERR("%s, Invalid NetKeyIndex 0x%04x", __func__, del_idx); + return; + } + + BT_DBG("idx 0x%04x", del_idx); + + sub = bt_mesh_subnet_get(del_idx); + if (!sub) { + /* This could be a retry of a previous attempt that had its + * response lost, so pretend that it was a success. + */ + status = STATUS_SUCCESS; + goto send_status; + } + + /* The key that the message was encrypted with cannot be removed. + * The NetKey List must contain a minimum of one NetKey. + */ + if (ctx->net_idx == del_idx) { + status = STATUS_CANNOT_REMOVE; + goto send_status; + } + + bt_mesh_subnet_del(sub, true); + status = STATUS_SUCCESS; + +send_status: + send_net_key_status(model, ctx, del_idx, status); +} + +static void net_key_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, + 2 + 4 + IDX_LEN(CONFIG_BLE_MESH_SUBNET_COUNT)); + u16_t prev, i; + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_LIST); + + prev = BLE_MESH_KEY_UNUSED; + for (i = 0U; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + if (prev == BLE_MESH_KEY_UNUSED) { + prev = sub->net_idx; + continue; + } + + key_idx_pack(&msg, prev, sub->net_idx); + prev = BLE_MESH_KEY_UNUSED; + } + + if (prev != BLE_MESH_KEY_UNUSED) { + net_buf_simple_add_le16(&msg, prev); + } + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config NetKey List", __func__); + } +} + +static void node_identity_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 4 + 4); + struct bt_mesh_subnet *sub; + u8_t node_id; + u16_t idx; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) { + BT_ERR("%s, Invalid NetKeyIndex 0x%04x", __func__, idx); + return; + } + + bt_mesh_model_msg_init(&msg, OP_NODE_IDENTITY_STATUS); + + sub = bt_mesh_subnet_get(idx); + if (!sub) { + net_buf_simple_add_u8(&msg, STATUS_INVALID_NETKEY); + node_id = 0x00; + } else { + net_buf_simple_add_u8(&msg, STATUS_SUCCESS); + node_id = sub->node_id; + } + + net_buf_simple_add_le16(&msg, idx); + net_buf_simple_add_u8(&msg, node_id); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Node Identity Status", __func__); + } +} + +static void node_identity_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 4 + 4); + struct bt_mesh_subnet *sub; + u8_t node_id; + u16_t idx; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) { + BT_WARN("%s, Invalid NetKeyIndex 0x%04x", __func__, idx); + return; + } + + node_id = net_buf_simple_pull_u8(buf); + if (node_id != 0x00 && node_id != 0x01) { + BT_WARN("%s, Invalid Node ID value 0x%02x", __func__, node_id); + return; + } + + bt_mesh_model_msg_init(&msg, OP_NODE_IDENTITY_STATUS); + + sub = bt_mesh_subnet_get(idx); + if (!sub) { + net_buf_simple_add_u8(&msg, STATUS_INVALID_NETKEY); + net_buf_simple_add_le16(&msg, idx); + net_buf_simple_add_u8(&msg, node_id); + } else { + net_buf_simple_add_u8(&msg, STATUS_SUCCESS); + net_buf_simple_add_le16(&msg, idx); +#if CONFIG_BLE_MESH_NODE + /* Section 4.2.11.1: "When the GATT Proxy state is set to + * 0x00, the Node Identity state for all subnets shall be set + * to 0x00 and shall not be changed." + */ + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY) && + bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_ENABLED) { + if (node_id) { + bt_mesh_proxy_identity_start(sub); + } else { + bt_mesh_proxy_identity_stop(sub); + } + bt_mesh_adv_update(); + } +#endif + net_buf_simple_add_u8(&msg, sub->node_id); + } + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Node Identity Status", __func__); + } +} + +static void create_mod_app_status(struct net_buf_simple *msg, + struct bt_mesh_model *mod, bool vnd, + u16_t elem_addr, u16_t app_idx, + u8_t status, u8_t *mod_id) +{ + bt_mesh_model_msg_init(msg, OP_MOD_APP_STATUS); + + net_buf_simple_add_u8(msg, status); + net_buf_simple_add_le16(msg, elem_addr); + net_buf_simple_add_le16(msg, app_idx); + + if (vnd) { + memcpy(net_buf_simple_add(msg, 4), mod_id, 4); + } else { + memcpy(net_buf_simple_add(msg, 2), mod_id, 2); + } +} + +static void mod_app_bind(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 9 + 4); + u16_t elem_addr, key_app_idx; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *mod_id, status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + key_app_idx = net_buf_simple_pull_le16(buf); + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + /* Configuration Server only allows device key based access */ + if (model == mod) { + BT_ERR("%s, Client tried to bind AppKey to Configuration Model", __func__); + status = STATUS_CANNOT_BIND; + goto send_status; + } + + status = mod_bind(mod, key_app_idx); + +send_status: + BT_DBG("status 0x%02x", status); + create_mod_app_status(&msg, mod, vnd, elem_addr, key_app_idx, status, + mod_id); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Model App Bind Status", __func__); + } +} + +static void mod_app_unbind(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 9 + 4); + u16_t elem_addr, key_app_idx; + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *mod_id, status; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + key_app_idx = net_buf_simple_pull_le16(buf); + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = mod_unbind(mod, key_app_idx, true); + +send_status: + BT_DBG("status 0x%02x", status); + create_mod_app_status(&msg, mod, vnd, elem_addr, key_app_idx, status, + mod_id); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Model App Unbind Status", __func__); + } +} + +#define KEY_LIST_LEN (CONFIG_BLE_MESH_MODEL_KEY_COUNT * 2) + +static void mod_app_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 9 + KEY_LIST_LEN + 4); + struct bt_mesh_model *mod; + struct bt_mesh_elem *elem; + u8_t *mod_id, status; + u16_t elem_addr; + bool vnd; + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(elem_addr)) { + BT_ERR("%s, Prohibited element address 0x%04x", __func__, elem_addr); + return; + } + + mod_id = buf->data; + + BT_DBG("elem_addr 0x%04x", elem_addr); + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_list; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) { + status = STATUS_INVALID_MODEL; + goto send_list; + } + + status = STATUS_SUCCESS; + +send_list: + if (vnd) { + bt_mesh_model_msg_init(&msg, OP_VND_MOD_APP_LIST); + } else { + bt_mesh_model_msg_init(&msg, OP_SIG_MOD_APP_LIST); + } + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, elem_addr); + + if (vnd) { + net_buf_simple_add_mem(&msg, mod_id, 4); + } else { + net_buf_simple_add_mem(&msg, mod_id, 2); + } + + if (mod) { + int i; + + for (i = 0; i < ARRAY_SIZE(mod->keys); i++) { + if (mod->keys[i] != BLE_MESH_KEY_UNUSED) { + net_buf_simple_add_le16(&msg, mod->keys[i]); + } + } + } + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Model Application List", __func__); + } +} + +static void node_reset(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 0 + 4); + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + + bt_mesh_model_msg_init(&msg, OP_NODE_RESET_STATUS); + + /* Send the response first since we wont have any keys left to + * send it later. + */ + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Node Reset Status", __func__); + } + +#if CONFIG_BLE_MESH_NODE + bt_mesh_reset(); +#endif +} + +static void send_friend_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + struct bt_mesh_cfg_srv *cfg = model->user_data; + + bt_mesh_model_msg_init(&msg, OP_FRIEND_STATUS); + net_buf_simple_add_u8(&msg, cfg->frnd); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Friend Status", __func__); + } +} + +static void friend_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + send_friend_status(model, ctx); +} + +static void friend_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_srv *cfg = model->user_data; + struct bt_mesh_subnet *sub; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + if (buf->data[0] != 0x00 && buf->data[0] != 0x01) { + BT_WARN("Invalid Friend value 0x%02x", buf->data[0]); + return; + } + + if (!cfg) { + BT_WARN("No Configuration Server context available"); + goto send_status; + } + + BT_DBG("Friend 0x%02x -> 0x%02x", cfg->frnd, buf->data[0]); + + if (cfg->frnd == buf->data[0]) { + goto send_status; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + cfg->frnd = buf->data[0]; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_cfg(); + } + + if (cfg->frnd == BLE_MESH_FRIEND_DISABLED) { + bt_mesh_friend_clear_net_idx(BLE_MESH_KEY_ANY); + } + } + + sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx); + if ((cfg->hb_pub.feat & BLE_MESH_FEAT_FRIEND) && sub) { + hb_send(model); + } + +send_status: + send_friend_status(model, ctx); +} + +static void lpn_timeout_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 5 + 4); + struct bt_mesh_friend *frnd; + u16_t lpn_addr; + s32_t timeout; + + lpn_addr = net_buf_simple_pull_le16(buf); + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x lpn_addr 0x%02x", + ctx->net_idx, ctx->app_idx, ctx->addr, lpn_addr); + + if (!BLE_MESH_ADDR_IS_UNICAST(lpn_addr)) { + BT_WARN("Invalid LPNAddress; ignoring msg"); + return; + } + + bt_mesh_model_msg_init(&msg, OP_LPN_TIMEOUT_STATUS); + net_buf_simple_add_le16(&msg, lpn_addr); + + if (!IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + timeout = 0; + goto send_rsp; + } + + frnd = bt_mesh_friend_find(BLE_MESH_KEY_ANY, lpn_addr, true, true); + if (!frnd) { + timeout = 0; + goto send_rsp; + } + + timeout = k_delayed_work_remaining_get(&frnd->timer) / 100; + +send_rsp: + net_buf_simple_add_u8(&msg, timeout); + net_buf_simple_add_u8(&msg, timeout >> 8); + net_buf_simple_add_u8(&msg, timeout >> 16); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config LPN PollTimeout Status", __func__); + } +} + +static void send_krp_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t idx, u8_t phase, u8_t status) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 4 + 4); + + bt_mesh_model_msg_init(&msg, OP_KRP_STATUS); + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, idx); + net_buf_simple_add_u8(&msg, phase); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Key Refresh Phase Status", __func__); + } +} + +static void krp_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_subnet *sub; + u16_t idx; + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) { + BT_ERR("%s, Invalid NetKeyIndex 0x%04x", __func__, idx); + return; + } + + BT_DBG("idx 0x%04x", idx); + + sub = bt_mesh_subnet_get(idx); + if (!sub) { + send_krp_status(model, ctx, idx, 0x00, STATUS_INVALID_NETKEY); + } else { + send_krp_status(model, ctx, idx, sub->kr_phase, + STATUS_SUCCESS); + } +} + +static void krp_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_subnet *sub; + u8_t phase; + u16_t idx; + + idx = net_buf_simple_pull_le16(buf); + phase = net_buf_simple_pull_u8(buf); + + if (idx > 0xfff) { + BT_ERR("%s, Invalid NetKeyIndex 0x%04x", __func__, idx); + return; + } + + BT_DBG("idx 0x%04x transition 0x%02x", idx, phase); + + sub = bt_mesh_subnet_get(idx); + if (!sub) { + send_krp_status(model, ctx, idx, 0x00, STATUS_INVALID_NETKEY); + return; + } + + BT_DBG("%u -> %u", sub->kr_phase, phase); + + if (phase < BLE_MESH_KR_PHASE_2 || phase > BLE_MESH_KR_PHASE_3 || + (sub->kr_phase == BLE_MESH_KR_NORMAL && + phase == BLE_MESH_KR_PHASE_2)) { + BT_WARN("%s, Prohibited transition %u -> %u", __func__, sub->kr_phase, phase); + return; + } + + if (sub->kr_phase == BLE_MESH_KR_PHASE_1 && + phase == BLE_MESH_KR_PHASE_2) { + sub->kr_phase = BLE_MESH_KR_PHASE_2; + sub->kr_flag = 1; + bt_mesh_net_beacon_update(sub); + } else if ((sub->kr_phase == BLE_MESH_KR_PHASE_1 || + sub->kr_phase == BLE_MESH_KR_PHASE_2) && + phase == BLE_MESH_KR_PHASE_3) { + bt_mesh_net_revoke_keys(sub); + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER) || + IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + friend_cred_refresh(ctx->net_idx); + } + sub->kr_phase = BLE_MESH_KR_NORMAL; + sub->kr_flag = 0; + bt_mesh_net_beacon_update(sub); + } + + send_krp_status(model, ctx, idx, sub->kr_phase, STATUS_SUCCESS); +} + +static u8_t hb_log(u16_t val) +{ + if (!val) { + return 0x00; + } else if (val == 0xffff) { + return 0xff; + } else { + return 32 - __builtin_clz(val); + } +} + +static u8_t hb_pub_count_log(u16_t val) +{ + if (!val) { + return 0x00; + } else if (val == 0x01) { + return 0x01; + } else if (val == 0xffff) { + return 0xff; + } else { + return 32 - __builtin_clz(val - 1) + 1; + } +} + +static u16_t hb_pwr2(u8_t val, u8_t sub) +{ + if (!val) { + return 0x0000; + } else if (val == 0xff || val == 0x11) { + return 0xffff; + } else { + return (1 << (val - sub)); + } +} + +struct hb_pub_param { + u16_t dst; + u8_t count_log; + u8_t period_log; + u8_t ttl; + u16_t feat; + u16_t net_idx; +} __packed; + +static void hb_pub_send_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, u8_t status, + struct hb_pub_param *orig_msg) +{ + /* Needed size: opcode (1 byte) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 1 + 10 + 4); + struct bt_mesh_cfg_srv *cfg = model->user_data; + + BT_DBG("src 0x%04x status 0x%02x", ctx->addr, status); + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_PUB_STATUS); + + net_buf_simple_add_u8(&msg, status); + + if (orig_msg) { + memcpy(net_buf_simple_add(&msg, sizeof(*orig_msg)), orig_msg, + sizeof(*orig_msg)); + goto send; + } + + net_buf_simple_add_le16(&msg, cfg->hb_pub.dst); + net_buf_simple_add_u8(&msg, hb_pub_count_log(cfg->hb_pub.count)); + net_buf_simple_add_u8(&msg, cfg->hb_pub.period); + net_buf_simple_add_u8(&msg, cfg->hb_pub.ttl); + net_buf_simple_add_le16(&msg, cfg->hb_pub.feat); + net_buf_simple_add_le16(&msg, cfg->hb_pub.net_idx); + +send: + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Heartbeat Publication Status", __func__); + } +} + +static void heartbeat_pub_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_DBG("src 0x%04x", ctx->addr); + + hb_pub_send_status(model, ctx, STATUS_SUCCESS, NULL); +} + +static void heartbeat_pub_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct hb_pub_param *param = (void *)buf->data; + struct bt_mesh_cfg_srv *cfg = model->user_data; + u16_t dst, feat, idx; + u8_t status; + + BT_DBG("src 0x%04x", ctx->addr); + + dst = sys_le16_to_cpu(param->dst); + /* All other address types but virtual are valid */ + if (BLE_MESH_ADDR_IS_VIRTUAL(dst)) { + status = STATUS_INVALID_ADDRESS; + goto failed; + } + + if (param->count_log > 0x11 && param->count_log != 0xff) { + status = STATUS_CANNOT_SET; + goto failed; + } + + if (param->period_log > 0x10) { + status = STATUS_CANNOT_SET; + goto failed; + } + + if (param->ttl > BLE_MESH_TTL_MAX && param->ttl != BLE_MESH_TTL_DEFAULT) { + BT_ERR("%s, Invalid TTL value 0x%02x", __func__, param->ttl); + return; + } + + feat = sys_le16_to_cpu(param->feat); + + idx = sys_le16_to_cpu(param->net_idx); + if (idx > 0xfff) { + BT_ERR("%s, Invalid NetKeyIndex 0x%04x", __func__, idx); + return; + } + + if (!bt_mesh_subnet_get(idx)) { + status = STATUS_INVALID_NETKEY; + goto failed; + } + + cfg->hb_pub.dst = dst; + cfg->hb_pub.period = param->period_log; + cfg->hb_pub.feat = feat & BLE_MESH_FEAT_SUPPORTED; + cfg->hb_pub.net_idx = idx; + + if (dst == BLE_MESH_ADDR_UNASSIGNED) { + hb_pub_disable(cfg); + } else { + /* 2^(n-1) */ + cfg->hb_pub.count = hb_pwr2(param->count_log, 1); + cfg->hb_pub.ttl = param->ttl; + + BT_DBG("period %u ms", hb_pwr2(param->period_log, 1) * 1000U); + + /* Note: Send heartbeat message here will cause wrong heartbeat status message */ +#if 0 + /* The first Heartbeat message shall be published as soon + * as possible after the Heartbeat Publication Period state + * has been configured for periodic publishing. + */ + if (param->period_log && param->count_log) { + k_work_submit(&cfg->hb_pub.timer.work); + } else { + k_delayed_work_cancel(&cfg->hb_pub.timer); + } +#endif + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_hb_pub(); + } + + hb_pub_send_status(model, ctx, STATUS_SUCCESS, NULL); + + /* The first Heartbeat message shall be published as soon + * as possible after the Heartbeat Publication Period state + * has been configured for periodic publishing. + */ + if (dst != BLE_MESH_ADDR_UNASSIGNED) { + if (param->period_log && param->count_log) { + k_work_submit(&cfg->hb_pub.timer.work); + } else { + k_delayed_work_cancel(&cfg->hb_pub.timer); + } + } + + return; + +failed: + hb_pub_send_status(model, ctx, status, param); +} + +static void hb_sub_send_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, u8_t status) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 9 + 4); + struct bt_mesh_cfg_srv *cfg = model->user_data; + u16_t period; + s64_t uptime; + + BT_DBG("src 0x%04x status 0x%02x", ctx->addr, status); + + uptime = k_uptime_get(); + if (uptime > cfg->hb_sub.expiry) { + period = 0U; + } else { + period = (cfg->hb_sub.expiry - uptime) / 1000; + } + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_SUB_STATUS); + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, cfg->hb_sub.src); + net_buf_simple_add_le16(&msg, cfg->hb_sub.dst); + if (cfg->hb_sub.src == BLE_MESH_ADDR_UNASSIGNED || + cfg->hb_sub.dst == BLE_MESH_ADDR_UNASSIGNED) { + memset(net_buf_simple_add(&msg, 4), 0, 4); + } else { + net_buf_simple_add_u8(&msg, hb_log(period)); + net_buf_simple_add_u8(&msg, hb_log(cfg->hb_sub.count)); + net_buf_simple_add_u8(&msg, cfg->hb_sub.min_hops); + net_buf_simple_add_u8(&msg, cfg->hb_sub.max_hops); + } + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Config Heartbeat Subscription Status", __func__); + } +} + +static void heartbeat_sub_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_DBG("src 0x%04x", ctx->addr); + + hb_sub_send_status(model, ctx, STATUS_SUCCESS); +} + +static void heartbeat_sub_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_cfg_srv *cfg = model->user_data; + u16_t sub_src, sub_dst; + u8_t sub_period; + s32_t period_ms; + + BT_DBG("src 0x%04x", ctx->addr); + + sub_src = net_buf_simple_pull_le16(buf); + sub_dst = net_buf_simple_pull_le16(buf); + sub_period = net_buf_simple_pull_u8(buf); + + BT_DBG("sub_src 0x%04x sub_dst 0x%04x period 0x%02x", + sub_src, sub_dst, sub_period); + + if (sub_src != BLE_MESH_ADDR_UNASSIGNED && + !BLE_MESH_ADDR_IS_UNICAST(sub_src)) { + BT_WARN("Prohibited source address"); + return; + } + + if (BLE_MESH_ADDR_IS_VIRTUAL(sub_dst) || BLE_MESH_ADDR_IS_RFU(sub_dst) || + (BLE_MESH_ADDR_IS_UNICAST(sub_dst) && + sub_dst != bt_mesh_primary_addr())) { + BT_WARN("Prohibited destination address"); + return; + } + + if (sub_period > 0x11) { + BT_WARN("Prohibited subscription period 0x%02x", sub_period); + return; + } + + if (sub_src == BLE_MESH_ADDR_UNASSIGNED || + sub_dst == BLE_MESH_ADDR_UNASSIGNED || + sub_period == 0x00) { + /* Only an explicit address change to unassigned should + * trigger clearing of the values according to + * MESH/NODE/CFG/HBS/BV-02-C. + */ + if (cfg->hb_sub.src != sub_src || cfg->hb_sub.dst != sub_dst) { + cfg->hb_sub.src = BLE_MESH_ADDR_UNASSIGNED; + cfg->hb_sub.dst = BLE_MESH_ADDR_UNASSIGNED; + } + + period_ms = 0; + } else { + cfg->hb_sub.src = sub_src; + cfg->hb_sub.dst = sub_dst; + cfg->hb_sub.min_hops = BLE_MESH_TTL_MAX; + cfg->hb_sub.max_hops = 0U; + cfg->hb_sub.count = 0U; + period_ms = hb_pwr2(sub_period, 1) * 1000U; + } + + /* Let the transport layer know it needs to handle this address */ + bt_mesh_set_hb_sub_dst(cfg->hb_sub.dst); + + BT_DBG("period_ms %u", period_ms); + + if (period_ms) { + cfg->hb_sub.expiry = k_uptime_get() + period_ms; + } else { + cfg->hb_sub.expiry = 0; + } + + hb_sub_send_status(model, ctx, STATUS_SUCCESS); + + /* MESH/NODE/CFG/HBS/BV-01-C expects the MinHops to be 0x7f after + * disabling subscription, but 0x00 for subsequent Get requests. + */ + if (!period_ms) { + cfg->hb_sub.min_hops = 0U; + } +} + +const struct bt_mesh_model_op bt_mesh_cfg_srv_op[] = { + { OP_DEV_COMP_DATA_GET, 1, dev_comp_data_get }, + { OP_APP_KEY_ADD, 19, app_key_add }, + { OP_APP_KEY_UPDATE, 19, app_key_update }, + { OP_APP_KEY_DEL, 3, app_key_del }, + { OP_APP_KEY_GET, 2, app_key_get }, + { OP_BEACON_GET, 0, beacon_get }, + { OP_BEACON_SET, 1, beacon_set }, + { OP_DEFAULT_TTL_GET, 0, default_ttl_get }, + { OP_DEFAULT_TTL_SET, 1, default_ttl_set }, + { OP_GATT_PROXY_GET, 0, gatt_proxy_get }, + { OP_GATT_PROXY_SET, 1, gatt_proxy_set }, + { OP_NET_TRANSMIT_GET, 0, net_transmit_get }, + { OP_NET_TRANSMIT_SET, 1, net_transmit_set }, + { OP_RELAY_GET, 0, relay_get }, + { OP_RELAY_SET, 2, relay_set }, + { OP_MOD_PUB_GET, 4, mod_pub_get }, + { OP_MOD_PUB_SET, 11, mod_pub_set }, + { OP_MOD_PUB_VA_SET, 24, mod_pub_va_set }, + { OP_MOD_SUB_ADD, 6, mod_sub_add }, + { OP_MOD_SUB_VA_ADD, 20, mod_sub_va_add }, + { OP_MOD_SUB_DEL, 6, mod_sub_del }, + { OP_MOD_SUB_VA_DEL, 20, mod_sub_va_del }, + { OP_MOD_SUB_OVERWRITE, 6, mod_sub_overwrite }, + { OP_MOD_SUB_VA_OVERWRITE, 20, mod_sub_va_overwrite }, + { OP_MOD_SUB_DEL_ALL, 4, mod_sub_del_all }, + { OP_MOD_SUB_GET, 4, mod_sub_get }, + { OP_MOD_SUB_GET_VND, 6, mod_sub_get_vnd }, + { OP_NET_KEY_ADD, 18, net_key_add }, + { OP_NET_KEY_UPDATE, 18, net_key_update }, + { OP_NET_KEY_DEL, 2, net_key_del }, + { OP_NET_KEY_GET, 0, net_key_get }, + { OP_NODE_IDENTITY_GET, 2, node_identity_get }, + { OP_NODE_IDENTITY_SET, 3, node_identity_set }, + { OP_MOD_APP_BIND, 6, mod_app_bind }, + { OP_MOD_APP_UNBIND, 6, mod_app_unbind }, + { OP_SIG_MOD_APP_GET, 4, mod_app_get }, + { OP_VND_MOD_APP_GET, 6, mod_app_get }, + { OP_NODE_RESET, 0, node_reset }, + { OP_FRIEND_GET, 0, friend_get }, + { OP_FRIEND_SET, 1, friend_set }, + { OP_LPN_TIMEOUT_GET, 2, lpn_timeout_get }, + { OP_KRP_GET, 2, krp_get }, + { OP_KRP_SET, 3, krp_set }, + { OP_HEARTBEAT_PUB_GET, 0, heartbeat_pub_get }, + { OP_HEARTBEAT_PUB_SET, 9, heartbeat_pub_set }, + { OP_HEARTBEAT_SUB_GET, 0, heartbeat_sub_get }, + { OP_HEARTBEAT_SUB_SET, 5, heartbeat_sub_set }, + BLE_MESH_MODEL_OP_END, +}; + +static void hb_publish(struct k_work *work) +{ + struct bt_mesh_cfg_srv *cfg = CONTAINER_OF(work, + struct bt_mesh_cfg_srv, + hb_pub.timer.work); + struct bt_mesh_model *model = cfg->model; + struct bt_mesh_subnet *sub; + u16_t period_ms; + + BT_DBG("hb_pub.count: %u", cfg->hb_pub.count); + + sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx); + if (!sub) { + BT_ERR("%s, No matching subnet for idx 0x%02x", + __func__, cfg->hb_pub.net_idx); + cfg->hb_pub.dst = BLE_MESH_ADDR_UNASSIGNED; + return; + } + + if (cfg->hb_pub.count == 0U) { + return; + } + + period_ms = hb_pwr2(cfg->hb_pub.period, 1) * 1000U; + if (period_ms && cfg->hb_pub.count > 1) { + k_delayed_work_submit(&cfg->hb_pub.timer, period_ms); + } + + hb_send(model); + + if (cfg->hb_pub.count != 0xffff) { + cfg->hb_pub.count--; + } +} + +static bool conf_is_valid(struct bt_mesh_cfg_srv *cfg) +{ + if (cfg->relay > 0x02) { + return false; + } + + if (cfg->beacon > 0x01) { + return false; + } + + if (cfg->default_ttl > BLE_MESH_TTL_MAX) { + return false; + } + + return true; +} + +int bt_mesh_cfg_srv_init(struct bt_mesh_model *model, bool primary) +{ + struct bt_mesh_cfg_srv *cfg = model->user_data; + + if (!cfg) { + BT_ERR("%s, No Configuration Server context provided", __func__); + return -EINVAL; + } + + if (!conf_is_valid(cfg)) { + BT_ERR("%s, Invalid values in configuration", __func__); + return -EINVAL; + } + + /* Configuration Model security is device-key based */ + model->keys[0] = BLE_MESH_KEY_DEV; + + if (!IS_ENABLED(CONFIG_BLE_MESH_RELAY)) { + cfg->relay = BLE_MESH_RELAY_NOT_SUPPORTED; + } + + if (!IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + cfg->frnd = BLE_MESH_FRIEND_NOT_SUPPORTED; + } + + if (!IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY)) { + cfg->gatt_proxy = BLE_MESH_GATT_PROXY_NOT_SUPPORTED; + } + + k_delayed_work_init(&cfg->hb_pub.timer, hb_publish); + cfg->hb_pub.net_idx = BLE_MESH_KEY_UNUSED; + cfg->hb_sub.expiry = 0; + + cfg->model = model; + + conf = cfg; + + return 0; +} + +static void mod_reset(struct bt_mesh_model *mod, struct bt_mesh_elem *elem, + bool vnd, bool primary, void *user_data) +{ + size_t clear_count; + + /* Clear model state that isn't otherwise cleared. E.g. AppKey + * binding and model publication is cleared as a consequence + * of removing all app keys, however model subscription clearing + * must be taken care of here. + */ + + clear_count = mod_sub_list_clear(mod); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS) && clear_count) { + bt_mesh_store_mod_sub(mod); + } +} + +void bt_mesh_cfg_reset(void) +{ + struct bt_mesh_cfg_srv *cfg = conf; + int i; + + BT_DBG("%s", __func__); + + if (!cfg) { + return; + } + + bt_mesh_set_hb_sub_dst(BLE_MESH_ADDR_UNASSIGNED); + + cfg->hb_sub.src = BLE_MESH_ADDR_UNASSIGNED; + cfg->hb_sub.dst = BLE_MESH_ADDR_UNASSIGNED; + cfg->hb_sub.expiry = 0; + + /* Delete all net keys, which also takes care of all app keys which + * are associated with each net key. + */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (sub->net_idx != BLE_MESH_KEY_UNUSED) { + bt_mesh_subnet_del(sub, true); + } + } + + bt_mesh_model_foreach(mod_reset, NULL); + + (void)memset(labels, 0, sizeof(labels)); +} + +void bt_mesh_heartbeat(u16_t src, u16_t dst, u8_t hops, u16_t feat) +{ + struct bt_mesh_cfg_srv *cfg = conf; + + if (!cfg) { + BT_WARN("No configuaration server context available"); + return; + } + + if (src != cfg->hb_sub.src || dst != cfg->hb_sub.dst) { + BT_WARN("No subscription for received heartbeat"); + return; + } + + if (k_uptime_get() > cfg->hb_sub.expiry) { + BT_WARN("Heartbeat subscription period expired"); + return; + } + + cfg->hb_sub.min_hops = MIN(cfg->hb_sub.min_hops, hops); + cfg->hb_sub.max_hops = MAX(cfg->hb_sub.max_hops, hops); + + if (cfg->hb_sub.count < 0xffff) { + cfg->hb_sub.count++; + } + + BT_DBG("src 0x%04x dst 0x%04x hops %u min %u max %u count %u", src, + dst, hops, cfg->hb_sub.min_hops, cfg->hb_sub.max_hops, + cfg->hb_sub.count); + + if (cfg->hb_sub.func) { + cfg->hb_sub.func(hops, feat); + } +} + +u8_t bt_mesh_net_transmit_get(void) +{ + if (conf) { + return conf->net_transmit; + } + + return 0; +} + +u8_t bt_mesh_relay_get(void) +{ + if (conf) { + return conf->relay; + } + + return BLE_MESH_RELAY_NOT_SUPPORTED; +} + +u8_t bt_mesh_friend_get(void) +{ + if (conf) { + BT_DBG("conf %p conf->frnd 0x%02x", conf, conf->frnd); + return conf->frnd; + } + + return BLE_MESH_FRIEND_NOT_SUPPORTED; +} + +u8_t bt_mesh_relay_retransmit_get(void) +{ + if (conf) { + return conf->relay_retransmit; + } + + return 0; +} + +u8_t bt_mesh_beacon_get(void) +{ + if (conf) { + return conf->beacon; + } + + return BLE_MESH_BEACON_DISABLED; +} + +u8_t bt_mesh_gatt_proxy_get(void) +{ + if (conf) { + return conf->gatt_proxy; + } + + return BLE_MESH_GATT_PROXY_NOT_SUPPORTED; +} + +u8_t bt_mesh_default_ttl_get(void) +{ + if (conf) { + return conf->default_ttl; + } + + return DEFAULT_TTL; +} + +u8_t *bt_mesh_label_uuid_get(u16_t addr) +{ + int i; + + BT_DBG("addr 0x%04x", addr); + + for (i = 0; i < ARRAY_SIZE(labels); i++) { + if (labels[i].addr == addr) { + BT_DBG("Found Label UUID for 0x%04x: %s", addr, + bt_hex(labels[i].uuid, 16)); + return labels[i].uuid; + } + } + + BT_WARN("No matching Label UUID for 0x%04x", addr); + + return NULL; +} + +struct bt_mesh_hb_pub *bt_mesh_hb_pub_get(void) +{ + if (!conf) { + return NULL; + } + + return &conf->hb_pub; +} + +void bt_mesh_hb_pub_disable(void) +{ + if (conf) { + hb_pub_disable(conf); + } +} + +struct bt_mesh_cfg_srv *bt_mesh_cfg_get(void) +{ + return conf; +} + +void bt_mesh_subnet_del(struct bt_mesh_subnet *sub, bool store) +{ + int i; + + BT_DBG("NetIdx 0x%03x store %u", sub->net_idx, store); + + if (conf && conf->hb_pub.net_idx == sub->net_idx) { + hb_pub_disable(conf); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS) && store) { + bt_mesh_store_hb_pub(); + } + } + + /* Delete any app keys bound to this NetKey index */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) { + struct bt_mesh_app_key *key = &bt_mesh.app_keys[i]; + + if (key->net_idx == sub->net_idx) { + bt_mesh_app_key_del(key, store); + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + bt_mesh_friend_clear_net_idx(sub->net_idx); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS) && store) { + bt_mesh_clear_subnet(sub); + } + + (void)memset(sub, 0, sizeof(*sub)); + sub->net_idx = BLE_MESH_KEY_UNUSED; +} diff --git a/components/bt/ble_mesh/mesh_core/crypto.c b/components/bt/ble_mesh/mesh_core/crypto.c new file mode 100644 index 0000000000..d2e6507882 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/crypto.c @@ -0,0 +1,878 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_CRYPTO) + +#include "mesh_types.h" +#include "mesh_util.h" +#include "mesh_buf.h" +#include "mesh_trace.h" +#include "mesh_bearer_adapt.h" +#include "mesh_aes_encrypt.h" + +#include "mesh.h" +#include "crypto.h" + +#define NET_MIC_LEN(pdu) (((pdu)[1] & 0x80) ? 8 : 4) +#define APP_MIC_LEN(aszmic) ((aszmic) ? 8 : 4) + +int bt_mesh_aes_cmac(const u8_t key[16], struct bt_mesh_sg *sg, + size_t sg_len, u8_t mac[16]) +{ + struct tc_aes_key_sched_struct sched; + struct tc_cmac_struct state; + + if (tc_cmac_setup(&state, key, &sched) == TC_CRYPTO_FAIL) { + return -EIO; + } + + for (; sg_len; sg_len--, sg++) { + if (tc_cmac_update(&state, sg->data, + sg->len) == TC_CRYPTO_FAIL) { + return -EIO; + } + } + + if (tc_cmac_final(mac, &state) == TC_CRYPTO_FAIL) { + return -EIO; + } + + return 0; +} + +int bt_mesh_k1(const u8_t *ikm, size_t ikm_len, const u8_t salt[16], + const char *info, u8_t okm[16]) +{ + int err; + + err = bt_mesh_aes_cmac_one(salt, ikm, ikm_len, okm); + if (err < 0) { + return err; + } + + return bt_mesh_aes_cmac_one(okm, info, strlen(info), okm); +} + +int bt_mesh_k2(const u8_t n[16], const u8_t *p, size_t p_len, + u8_t net_id[1], u8_t enc_key[16], u8_t priv_key[16]) +{ + struct bt_mesh_sg sg[3]; + u8_t salt[16]; + u8_t out[16]; + u8_t t[16]; + u8_t pad; + int err; + + BT_DBG("n %s", bt_hex(n, 16)); + BT_DBG("p %s", bt_hex(p, p_len)); + + err = bt_mesh_s1("smk2", salt); + if (err) { + return err; + } + + err = bt_mesh_aes_cmac_one(salt, n, 16, t); + if (err) { + return err; + } + + pad = 0x01; + + sg[0].data = NULL; + sg[0].len = 0; + sg[1].data = p; + sg[1].len = p_len; + sg[2].data = &pad; + sg[2].len = sizeof(pad); + + err = bt_mesh_aes_cmac(t, sg, ARRAY_SIZE(sg), out); + if (err) { + return err; + } + + net_id[0] = out[15] & 0x7f; + + sg[0].data = out; + sg[0].len = sizeof(out); + pad = 0x02; + + err = bt_mesh_aes_cmac(t, sg, ARRAY_SIZE(sg), out); + if (err) { + return err; + } + + memcpy(enc_key, out, 16); + + pad = 0x03; + + err = bt_mesh_aes_cmac(t, sg, ARRAY_SIZE(sg), out); + if (err) { + return err; + } + + memcpy(priv_key, out, 16); + + BT_DBG("NID 0x%02x enc_key %s", net_id[0], bt_hex(enc_key, 16)); + BT_DBG("priv_key %s", bt_hex(priv_key, 16)); + + return 0; +} + +int bt_mesh_k3(const u8_t n[16], u8_t out[8]) +{ + u8_t id64[] = { 'i', 'd', '6', '4', 0x01 }; + u8_t tmp[16]; + u8_t t[16]; + int err; + + err = bt_mesh_s1("smk3", tmp); + if (err) { + return err; + } + + err = bt_mesh_aes_cmac_one(tmp, n, 16, t); + if (err) { + return err; + } + + err = bt_mesh_aes_cmac_one(t, id64, sizeof(id64), tmp); + if (err) { + return err; + } + + memcpy(out, tmp + 8, 8); + + return 0; +} + +int bt_mesh_k4(const u8_t n[16], u8_t out[1]) +{ + u8_t id6[] = { 'i', 'd', '6', 0x01 }; + u8_t tmp[16]; + u8_t t[16]; + int err; + + err = bt_mesh_s1("smk4", tmp); + if (err) { + return err; + } + + err = bt_mesh_aes_cmac_one(tmp, n, 16, t); + if (err) { + return err; + } + + err = bt_mesh_aes_cmac_one(t, id6, sizeof(id6), tmp); + if (err) { + return err; + } + + out[0] = tmp[15] & BIT_MASK(6); + + return 0; +} + +int bt_mesh_id128(const u8_t n[16], const char *s, u8_t out[16]) +{ + const char *id128 = "id128\x01"; + u8_t salt[16]; + int err; + + err = bt_mesh_s1(s, salt); + if (err) { + return err; + } + + return bt_mesh_k1(n, 16, salt, id128, out); +} + +static int bt_mesh_ccm_decrypt(const u8_t key[16], u8_t nonce[13], + const u8_t *enc_msg, size_t msg_len, + const u8_t *aad, size_t aad_len, + u8_t *out_msg, size_t mic_size) +{ + u8_t msg[16], pmsg[16], cmic[16], cmsg[16], Xn[16], mic[16]; + u16_t last_blk, blk_cnt; + size_t i, j; + int err; + + if (msg_len < 1 || aad_len >= 0xff00) { + return -EINVAL; + } + + /* C_mic = e(AppKey, 0x01 || nonce || 0x0000) */ + pmsg[0] = 0x01; + memcpy(pmsg + 1, nonce, 13); + sys_put_be16(0x0000, pmsg + 14); + + err = bt_mesh_encrypt_be(key, pmsg, cmic); + if (err) { + return err; + } + + /* X_0 = e(AppKey, 0x09 || nonce || length) */ + if (mic_size == sizeof(u64_t)) { + pmsg[0] = 0x19 | (aad_len ? 0x40 : 0x00); + } else { + pmsg[0] = 0x09 | (aad_len ? 0x40 : 0x00); + } + + memcpy(pmsg + 1, nonce, 13); + sys_put_be16(msg_len, pmsg + 14); + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + + /* If AAD is being used to authenticate, include it here */ + if (aad_len) { + sys_put_be16(aad_len, pmsg); + + for (i = 0; i < sizeof(u16_t); i++) { + pmsg[i] = Xn[i] ^ pmsg[i]; + } + + j = 0; + aad_len += sizeof(u16_t); + while (aad_len > 16) { + do { + pmsg[i] = Xn[i] ^ aad[j]; + i++, j++; + } while (i < 16); + + aad_len -= 16; + i = 0; + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + } + + for (i = 0; i < aad_len; i++, j++) { + pmsg[i] = Xn[i] ^ aad[j]; + } + + for (i = aad_len; i < 16; i++) { + pmsg[i] = Xn[i]; + } + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + } + + last_blk = msg_len % 16; + blk_cnt = (msg_len + 15) / 16; + if (!last_blk) { + last_blk = 16U; + } + + for (j = 0; j < blk_cnt; j++) { + if (j + 1 == blk_cnt) { + /* C_1 = e(AppKey, 0x01 || nonce || 0x0001) */ + pmsg[0] = 0x01; + memcpy(pmsg + 1, nonce, 13); + sys_put_be16(j + 1, pmsg + 14); + + err = bt_mesh_encrypt_be(key, pmsg, cmsg); + if (err) { + return err; + } + + /* Encrypted = Payload[0-15] ^ C_1 */ + for (i = 0; i < last_blk; i++) { + msg[i] = enc_msg[(j * 16) + i] ^ cmsg[i]; + } + + memcpy(out_msg + (j * 16), msg, last_blk); + + /* X_1 = e(AppKey, X_0 ^ Payload[0-15]) */ + for (i = 0; i < last_blk; i++) { + pmsg[i] = Xn[i] ^ msg[i]; + } + + for (i = last_blk; i < 16; i++) { + pmsg[i] = Xn[i] ^ 0x00; + } + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + + /* MIC = C_mic ^ X_1 */ + for (i = 0; i < sizeof(mic); i++) { + mic[i] = cmic[i] ^ Xn[i]; + } + } else { + /* C_1 = e(AppKey, 0x01 || nonce || 0x0001) */ + pmsg[0] = 0x01; + memcpy(pmsg + 1, nonce, 13); + sys_put_be16(j + 1, pmsg + 14); + + err = bt_mesh_encrypt_be(key, pmsg, cmsg); + if (err) { + return err; + } + + /* Encrypted = Payload[0-15] ^ C_1 */ + for (i = 0; i < 16; i++) { + msg[i] = enc_msg[(j * 16) + i] ^ cmsg[i]; + } + + memcpy(out_msg + (j * 16), msg, 16); + + /* X_1 = e(AppKey, X_0 ^ Payload[0-15]) */ + for (i = 0; i < 16; i++) { + pmsg[i] = Xn[i] ^ msg[i]; + } + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + } + } + + if (memcmp(mic, enc_msg + msg_len, mic_size)) { + return -EBADMSG; + } + + return 0; +} + +static int bt_mesh_ccm_encrypt(const u8_t key[16], u8_t nonce[13], + const u8_t *msg, size_t msg_len, + const u8_t *aad, size_t aad_len, + u8_t *out_msg, size_t mic_size) +{ + u8_t pmsg[16], cmic[16], cmsg[16], mic[16], Xn[16]; + u16_t blk_cnt, last_blk; + size_t i, j; + int err; + + BT_DBG("key %s", bt_hex(key, 16)); + BT_DBG("nonce %s", bt_hex(nonce, 13)); + BT_DBG("msg (len %u) %s", msg_len, bt_hex(msg, msg_len)); + BT_DBG("aad_len %u mic_size %u", aad_len, mic_size); + + /* Unsupported AAD size */ + if (aad_len >= 0xff00) { + return -EINVAL; + } + + /* C_mic = e(AppKey, 0x01 || nonce || 0x0000) */ + pmsg[0] = 0x01; + memcpy(pmsg + 1, nonce, 13); + sys_put_be16(0x0000, pmsg + 14); + + err = bt_mesh_encrypt_be(key, pmsg, cmic); + if (err) { + return err; + } + + /* X_0 = e(AppKey, 0x09 || nonce || length) */ + if (mic_size == sizeof(u64_t)) { + pmsg[0] = 0x19 | (aad_len ? 0x40 : 0x00); + } else { + pmsg[0] = 0x09 | (aad_len ? 0x40 : 0x00); + } + + memcpy(pmsg + 1, nonce, 13); + sys_put_be16(msg_len, pmsg + 14); + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + + /* If AAD is being used to authenticate, include it here */ + if (aad_len) { + sys_put_be16(aad_len, pmsg); + + for (i = 0; i < sizeof(u16_t); i++) { + pmsg[i] = Xn[i] ^ pmsg[i]; + } + + j = 0; + aad_len += sizeof(u16_t); + while (aad_len > 16) { + do { + pmsg[i] = Xn[i] ^ aad[j]; + i++, j++; + } while (i < 16); + + aad_len -= 16; + i = 0; + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + } + + for (i = 0; i < aad_len; i++, j++) { + pmsg[i] = Xn[i] ^ aad[j]; + } + + for (i = aad_len; i < 16; i++) { + pmsg[i] = Xn[i]; + } + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + } + + last_blk = msg_len % 16; + blk_cnt = (msg_len + 15) / 16; + if (!last_blk) { + last_blk = 16U; + } + + for (j = 0; j < blk_cnt; j++) { + if (j + 1 == blk_cnt) { + /* X_1 = e(AppKey, X_0 ^ Payload[0-15]) */ + for (i = 0; i < last_blk; i++) { + pmsg[i] = Xn[i] ^ msg[(j * 16) + i]; + } + for (i = last_blk; i < 16; i++) { + pmsg[i] = Xn[i] ^ 0x00; + } + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + + /* MIC = C_mic ^ X_1 */ + for (i = 0; i < sizeof(mic); i++) { + mic[i] = cmic[i] ^ Xn[i]; + } + + /* C_1 = e(AppKey, 0x01 || nonce || 0x0001) */ + pmsg[0] = 0x01; + memcpy(pmsg + 1, nonce, 13); + sys_put_be16(j + 1, pmsg + 14); + + err = bt_mesh_encrypt_be(key, pmsg, cmsg); + if (err) { + return err; + } + + /* Encrypted = Payload[0-15] ^ C_1 */ + for (i = 0; i < last_blk; i++) { + out_msg[(j * 16) + i] = + msg[(j * 16) + i] ^ cmsg[i]; + } + } else { + /* X_1 = e(AppKey, X_0 ^ Payload[0-15]) */ + for (i = 0; i < 16; i++) { + pmsg[i] = Xn[i] ^ msg[(j * 16) + i]; + } + + err = bt_mesh_encrypt_be(key, pmsg, Xn); + if (err) { + return err; + } + + /* C_1 = e(AppKey, 0x01 || nonce || 0x0001) */ + pmsg[0] = 0x01; + memcpy(pmsg + 1, nonce, 13); + sys_put_be16(j + 1, pmsg + 14); + + err = bt_mesh_encrypt_be(key, pmsg, cmsg); + if (err) { + return err; + } + + /* Encrypted = Payload[0-15] ^ C_N */ + for (i = 0; i < 16; i++) { + out_msg[(j * 16) + i] = + msg[(j * 16) + i] ^ cmsg[i]; + } + + } + } + + memcpy(out_msg + msg_len, mic, mic_size); + + return 0; +} + +#if defined(CONFIG_BLE_MESH_PROXY) +static void create_proxy_nonce(u8_t nonce[13], const u8_t *pdu, + u32_t iv_index) +{ + /* Nonce Type */ + nonce[0] = 0x03; + + /* Pad */ + nonce[1] = 0x00; + + /* Sequence Number */ + nonce[2] = pdu[2]; + nonce[3] = pdu[3]; + nonce[4] = pdu[4]; + + /* Source Address */ + nonce[5] = pdu[5]; + nonce[6] = pdu[6]; + + /* Pad */ + nonce[7] = 0U; + nonce[8] = 0U; + + /* IV Index */ + sys_put_be32(iv_index, &nonce[9]); +} +#endif /* PROXY */ + +static void create_net_nonce(u8_t nonce[13], const u8_t *pdu, + u32_t iv_index) +{ + /* Nonce Type */ + nonce[0] = 0x00; + + /* FRND + TTL */ + nonce[1] = pdu[1]; + + /* Sequence Number */ + nonce[2] = pdu[2]; + nonce[3] = pdu[3]; + nonce[4] = pdu[4]; + + /* Source Address */ + nonce[5] = pdu[5]; + nonce[6] = pdu[6]; + + /* Pad */ + nonce[7] = 0U; + nonce[8] = 0U; + + /* IV Index */ + sys_put_be32(iv_index, &nonce[9]); +} + +int bt_mesh_net_obfuscate(u8_t *pdu, u32_t iv_index, + const u8_t privacy_key[16]) +{ + u8_t priv_rand[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, }; + u8_t tmp[16]; + int err, i; + + BT_DBG("IVIndex %u, PrivacyKey %s", iv_index, bt_hex(privacy_key, 16)); + + sys_put_be32(iv_index, &priv_rand[5]); + memcpy(&priv_rand[9], &pdu[7], 7); + + BT_DBG("PrivacyRandom %s", bt_hex(priv_rand, 16)); + + err = bt_mesh_encrypt_be(privacy_key, priv_rand, tmp); + if (err) { + return err; + } + + for (i = 0; i < 6; i++) { + pdu[1 + i] ^= tmp[i]; + } + + return 0; +} + +int bt_mesh_net_encrypt(const u8_t key[16], struct net_buf_simple *buf, + u32_t iv_index, bool proxy) +{ + u8_t mic_len = NET_MIC_LEN(buf->data); + u8_t nonce[13]; + int err; + + BT_DBG("IVIndex %u EncKey %s mic_len %u", iv_index, bt_hex(key, 16), + mic_len); + BT_DBG("PDU (len %u) %s", buf->len, bt_hex(buf->data, buf->len)); + +#if defined(CONFIG_BLE_MESH_PROXY) + if (proxy) { + create_proxy_nonce(nonce, buf->data, iv_index); + } else { + create_net_nonce(nonce, buf->data, iv_index); + } +#else + create_net_nonce(nonce, buf->data, iv_index); +#endif + + BT_DBG("Nonce %s", bt_hex(nonce, 13)); + + err = bt_mesh_ccm_encrypt(key, nonce, &buf->data[7], buf->len - 7, + NULL, 0, &buf->data[7], mic_len); + if (!err) { + net_buf_simple_add(buf, mic_len); + } + + return err; +} + +int bt_mesh_net_decrypt(const u8_t key[16], struct net_buf_simple *buf, + u32_t iv_index, bool proxy) +{ + u8_t mic_len = NET_MIC_LEN(buf->data); + u8_t nonce[13]; + + BT_DBG("PDU (%u bytes) %s", buf->len, bt_hex(buf->data, buf->len)); + BT_DBG("iv_index %u, key %s mic_len %u", iv_index, bt_hex(key, 16), + mic_len); + +#if defined(CONFIG_BLE_MESH_PROXY) + if (proxy) { + create_proxy_nonce(nonce, buf->data, iv_index); + } else { + create_net_nonce(nonce, buf->data, iv_index); + } +#else + create_net_nonce(nonce, buf->data, iv_index); +#endif + + BT_DBG("Nonce %s", bt_hex(nonce, 13)); + + buf->len -= mic_len; + + return bt_mesh_ccm_decrypt(key, nonce, &buf->data[7], buf->len - 7, + NULL, 0, &buf->data[7], mic_len); +} + +static void create_app_nonce(u8_t nonce[13], bool dev_key, u8_t aszmic, + u16_t src, u16_t dst, u32_t seq_num, + u32_t iv_index) +{ + if (dev_key) { + nonce[0] = 0x02; + } else { + nonce[0] = 0x01; + } + + sys_put_be32((seq_num | ((u32_t)aszmic << 31)), &nonce[1]); + + sys_put_be16(src, &nonce[5]); + sys_put_be16(dst, &nonce[7]); + + sys_put_be32(iv_index, &nonce[9]); +} + +int bt_mesh_app_encrypt(const u8_t key[16], bool dev_key, u8_t aszmic, + struct net_buf_simple *buf, const u8_t *ad, + u16_t src, u16_t dst, u32_t seq_num, u32_t iv_index) +{ + u8_t nonce[13]; + int err; + + BT_DBG("AppKey %s", bt_hex(key, 16)); + BT_DBG("dev_key %u src 0x%04x dst 0x%04x", dev_key, src, dst); + BT_DBG("seq_num 0x%08x iv_index 0x%08x", seq_num, iv_index); + BT_DBG("Clear: %s", bt_hex(buf->data, buf->len)); + + create_app_nonce(nonce, dev_key, aszmic, src, dst, seq_num, iv_index); + + BT_DBG("Nonce %s", bt_hex(nonce, 13)); + + err = bt_mesh_ccm_encrypt(key, nonce, buf->data, buf->len, ad, + ad ? 16 : 0, buf->data, APP_MIC_LEN(aszmic)); + if (!err) { + net_buf_simple_add(buf, APP_MIC_LEN(aszmic)); + BT_DBG("Encr: %s", bt_hex(buf->data, buf->len)); + } + + return err; +} + +int bt_mesh_app_decrypt(const u8_t key[16], bool dev_key, u8_t aszmic, + struct net_buf_simple *buf, struct net_buf_simple *out, + const u8_t *ad, u16_t src, u16_t dst, u32_t seq_num, + u32_t iv_index) +{ + u8_t nonce[13]; + int err; + + BT_DBG("EncData (len %u) %s", buf->len, bt_hex(buf->data, buf->len)); + + create_app_nonce(nonce, dev_key, aszmic, src, dst, seq_num, iv_index); + + BT_DBG("AppKey %s", bt_hex(key, 16)); + BT_DBG("Nonce %s", bt_hex(nonce, 13)); + + err = bt_mesh_ccm_decrypt(key, nonce, buf->data, buf->len, ad, + ad ? 16 : 0, out->data, APP_MIC_LEN(aszmic)); + if (!err) { + net_buf_simple_add(out, buf->len); + } + + return err; +} + +/* reversed, 8-bit, poly=0x07 */ +static const u8_t crc_table[256] = { + 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, + 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, + 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, + 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, + + 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, + 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, + 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, + 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, + + 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, + 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, + 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, + 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, + + 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, + 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, + 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, + 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, + + 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, + 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, + 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, + 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, + + 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, + 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, + 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, + 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, + + 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, + 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, + 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, + 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, + + 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, + 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, + 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, + 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf +}; + +u8_t bt_mesh_fcs_calc(const u8_t *data, u8_t data_len) +{ + u8_t fcs = 0xff; + + while (data_len--) { + fcs = crc_table[fcs ^ *data++]; + } + + BT_DBG("fcs 0x%02x", 0xff - fcs); + + return 0xff - fcs; +} + +bool bt_mesh_fcs_check(struct net_buf_simple *buf, u8_t received_fcs) +{ + const u8_t *data = buf->data; + u16_t data_len = buf->len; + u8_t fcs = 0xff; + + while (data_len--) { + fcs = crc_table[fcs ^ *data++]; + } + + return crc_table[fcs ^ received_fcs] == 0xcf; +} + +int bt_mesh_virtual_addr(const u8_t virtual_label[16], u16_t *addr) +{ + u8_t salt[16]; + u8_t tmp[16]; + int err; + + err = bt_mesh_s1("vtad", salt); + if (err) { + return err; + } + + err = bt_mesh_aes_cmac_one(salt, virtual_label, 16, tmp); + if (err) { + return err; + } + + *addr = (sys_get_be16(&tmp[14]) & 0x3fff) | 0x8000; + + return 0; +} + +int bt_mesh_prov_conf_salt(const u8_t conf_inputs[145], u8_t salt[16]) +{ + const u8_t conf_salt_key[16] = { 0 }; + + return bt_mesh_aes_cmac_one(conf_salt_key, conf_inputs, 145, salt); +} + +int bt_mesh_prov_conf_key(const u8_t dhkey[32], const u8_t conf_salt[16], + u8_t conf_key[16]) +{ + return bt_mesh_k1(dhkey, 32, conf_salt, "prck", conf_key); +} + +int bt_mesh_prov_conf(const u8_t conf_key[16], const u8_t rand[16], + const u8_t auth[16], u8_t conf[16]) +{ + struct bt_mesh_sg sg[] = { { rand, 16 }, { auth, 16 } }; + + BT_DBG("ConfirmationKey %s", bt_hex(conf_key, 16)); + BT_DBG("RandomDevice %s", bt_hex(rand, 16)); + BT_DBG("AuthValue %s", bt_hex(auth, 16)); + + return bt_mesh_aes_cmac(conf_key, sg, ARRAY_SIZE(sg), conf); +} + +int bt_mesh_prov_decrypt(const u8_t key[16], u8_t nonce[13], + const u8_t data[25 + 8], u8_t out[25]) +{ + return bt_mesh_ccm_decrypt(key, nonce, data, 25, NULL, 0, out, 8); +} + +#if CONFIG_BLE_MESH_PROVISIONER +int bt_mesh_prov_encrypt(const u8_t key[16], u8_t nonce[13], + const u8_t data[25], u8_t out[33]) +{ + return bt_mesh_ccm_encrypt(key, nonce, data, 25, NULL, 0, out, 8); +} +#endif + +int bt_mesh_beacon_auth(const u8_t beacon_key[16], u8_t flags, + const u8_t net_id[8], u32_t iv_index, + u8_t auth[8]) +{ + u8_t msg[13], tmp[16]; + int err; + + BT_DBG("BeaconKey %s", bt_hex(beacon_key, 16)); + BT_DBG("NetId %s", bt_hex(net_id, 8)); + BT_DBG("IV Index 0x%08x", iv_index); + + msg[0] = flags; + memcpy(&msg[1], net_id, 8); + sys_put_be32(iv_index, &msg[9]); + + BT_DBG("BeaconMsg %s", bt_hex(msg, sizeof(msg))); + + err = bt_mesh_aes_cmac_one(beacon_key, msg, sizeof(msg), tmp); + if (!err) { + memcpy(auth, tmp, 8); + } + + return err; +} diff --git a/components/bt/ble_mesh/mesh_core/crypto.h b/components/bt/ble_mesh/mesh_core/crypto.h new file mode 100644 index 0000000000..d8e9b1452e --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/crypto.h @@ -0,0 +1,166 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _CRYPTO_H_ +#define _CRYPTO_H_ + +#include "mesh_types.h" +#include + +struct bt_mesh_sg { + const void *data; + size_t len; +}; + +int bt_mesh_aes_cmac(const u8_t key[16], struct bt_mesh_sg *sg, + size_t sg_len, u8_t mac[16]); + +static inline int bt_mesh_aes_cmac_one(const u8_t key[16], const void *m, + size_t len, u8_t mac[16]) +{ + struct bt_mesh_sg sg = { m, len }; + + return bt_mesh_aes_cmac(key, &sg, 1, mac); +} + +static inline bool bt_mesh_s1(const char *m, u8_t salt[16]) +{ + const u8_t zero[16] = { 0 }; + + return bt_mesh_aes_cmac_one(zero, m, strlen(m), salt); +} + +int bt_mesh_k1(const u8_t *ikm, size_t ikm_len, const u8_t salt[16], + const char *info, u8_t okm[16]); + +#define bt_mesh_k1_str(ikm, ikm_len, salt_str, info, okm) \ +({ \ + const u8_t salt[16] = salt_str; \ + bt_mesh_k1(ikm, ikm_len, salt, info, okm); \ +}) + +int bt_mesh_k2(const u8_t n[16], const u8_t *p, size_t p_len, + u8_t net_id[1], u8_t enc_key[16], u8_t priv_key[16]); + +int bt_mesh_k3(const u8_t n[16], u8_t out[8]); + +int bt_mesh_k4(const u8_t n[16], u8_t out[1]); + +int bt_mesh_id128(const u8_t n[16], const char *s, u8_t out[16]); + +static inline int bt_mesh_id_resolving_key(const u8_t net_key[16], + u8_t resolving_key[16]) +{ + return bt_mesh_k1_str(net_key, 16, "smbt", "smbi", resolving_key); +} + +static inline int bt_mesh_identity_key(const u8_t net_key[16], + u8_t identity_key[16]) +{ + return bt_mesh_id128(net_key, "nkik", identity_key); +} + +static inline int bt_mesh_beacon_key(const u8_t net_key[16], + u8_t beacon_key[16]) +{ + return bt_mesh_id128(net_key, "nkbk", beacon_key); +} + +int bt_mesh_beacon_auth(const u8_t beacon_key[16], u8_t flags, + const u8_t net_id[16], u32_t iv_index, + u8_t auth[8]); + +static inline int bt_mesh_app_id(const u8_t app_key[16], u8_t app_id[1]) +{ + return bt_mesh_k4(app_key, app_id); +} + +static inline int bt_mesh_session_key(const u8_t dhkey[32], + const u8_t prov_salt[16], + u8_t session_key[16]) +{ + return bt_mesh_k1(dhkey, 32, prov_salt, "prsk", session_key); +} + +static inline int bt_mesh_prov_nonce(const u8_t dhkey[32], + const u8_t prov_salt[16], + u8_t nonce[13]) +{ + u8_t tmp[16]; + int err; + + err = bt_mesh_k1(dhkey, 32, prov_salt, "prsn", tmp); + if (!err) { + memcpy(nonce, tmp + 3, 13); + } + + return err; +} + +static inline int bt_mesh_dev_key(const u8_t dhkey[32], + const u8_t prov_salt[16], + u8_t dev_key[16]) +{ + return bt_mesh_k1(dhkey, 32, prov_salt, "prdk", dev_key); +} + +static inline int bt_mesh_prov_salt(const u8_t conf_salt[16], + const u8_t prov_rand[16], + const u8_t dev_rand[16], + u8_t prov_salt[16]) +{ + const u8_t prov_salt_key[16] = { 0 }; + struct bt_mesh_sg sg[] = { + { conf_salt, 16 }, + { prov_rand, 16 }, + { dev_rand, 16 }, + }; + + return bt_mesh_aes_cmac(prov_salt_key, sg, ARRAY_SIZE(sg), prov_salt); +} + +int bt_mesh_net_obfuscate(u8_t *pdu, u32_t iv_index, + const u8_t privacy_key[16]); + +int bt_mesh_net_encrypt(const u8_t key[16], struct net_buf_simple *buf, + u32_t iv_index, bool proxy); + +int bt_mesh_net_decrypt(const u8_t key[16], struct net_buf_simple *buf, + u32_t iv_index, bool proxy); + +int bt_mesh_app_encrypt(const u8_t key[16], bool dev_key, u8_t aszmic, + struct net_buf_simple *buf, const u8_t *ad, + u16_t src, u16_t dst, u32_t seq_num, u32_t iv_index); + +int bt_mesh_app_decrypt(const u8_t key[16], bool dev_key, u8_t aszmic, + struct net_buf_simple *buf, struct net_buf_simple *out, + const u8_t *ad, u16_t src, u16_t dst, u32_t seq_num, + u32_t iv_index); + +u8_t bt_mesh_fcs_calc(const u8_t *data, u8_t data_len); + +bool bt_mesh_fcs_check(struct net_buf_simple *buf, u8_t received_fcs); + +int bt_mesh_virtual_addr(const u8_t virtual_label[16], u16_t *addr); + +int bt_mesh_prov_conf_salt(const u8_t conf_inputs[145], u8_t salt[16]); + +int bt_mesh_prov_conf_key(const u8_t dhkey[32], const u8_t conf_salt[16], + u8_t conf_key[16]); + +int bt_mesh_prov_conf(const u8_t conf_key[16], const u8_t rand[16], + const u8_t auth[16], u8_t conf[16]); + +int bt_mesh_prov_decrypt(const u8_t key[16], u8_t nonce[13], + const u8_t data[25 + 8], u8_t out[25]); + +int bt_mesh_prov_encrypt(const u8_t key[16], u8_t nonce[13], + const u8_t data[25], u8_t out[33]); + +#endif /* _CRYPTO_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/foundation.h b/components/bt/ble_mesh/mesh_core/foundation.h new file mode 100644 index 0000000000..d1ccd31ae4 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/foundation.h @@ -0,0 +1,166 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _FOUNDATION_H_ +#define _FOUNDATION_H_ + +#include "mesh_access.h" +#include "net.h" + +#define OP_APP_KEY_ADD BLE_MESH_MODEL_OP_1(0x00) +#define OP_APP_KEY_UPDATE BLE_MESH_MODEL_OP_1(0x01) +#define OP_DEV_COMP_DATA_STATUS BLE_MESH_MODEL_OP_1(0x02) +#define OP_MOD_PUB_SET BLE_MESH_MODEL_OP_1(0x03) +#define OP_HEALTH_CURRENT_STATUS BLE_MESH_MODEL_OP_1(0x04) +#define OP_HEALTH_FAULT_STATUS BLE_MESH_MODEL_OP_1(0x05) +#define OP_HEARTBEAT_PUB_STATUS BLE_MESH_MODEL_OP_1(0x06) +#define OP_APP_KEY_DEL BLE_MESH_MODEL_OP_2(0x80, 0x00) +#define OP_APP_KEY_GET BLE_MESH_MODEL_OP_2(0x80, 0x01) +#define OP_APP_KEY_LIST BLE_MESH_MODEL_OP_2(0x80, 0x02) +#define OP_APP_KEY_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x03) +#define OP_ATTENTION_GET BLE_MESH_MODEL_OP_2(0x80, 0x04) +#define OP_ATTENTION_SET BLE_MESH_MODEL_OP_2(0x80, 0x05) +#define OP_ATTENTION_SET_UNREL BLE_MESH_MODEL_OP_2(0x80, 0x06) +#define OP_ATTENTION_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x07) +#define OP_DEV_COMP_DATA_GET BLE_MESH_MODEL_OP_2(0x80, 0x08) +#define OP_BEACON_GET BLE_MESH_MODEL_OP_2(0x80, 0x09) +#define OP_BEACON_SET BLE_MESH_MODEL_OP_2(0x80, 0x0a) +#define OP_BEACON_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x0b) +#define OP_DEFAULT_TTL_GET BLE_MESH_MODEL_OP_2(0x80, 0x0c) +#define OP_DEFAULT_TTL_SET BLE_MESH_MODEL_OP_2(0x80, 0x0d) +#define OP_DEFAULT_TTL_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x0e) +#define OP_FRIEND_GET BLE_MESH_MODEL_OP_2(0x80, 0x0f) +#define OP_FRIEND_SET BLE_MESH_MODEL_OP_2(0x80, 0x10) +#define OP_FRIEND_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x11) +#define OP_GATT_PROXY_GET BLE_MESH_MODEL_OP_2(0x80, 0x12) +#define OP_GATT_PROXY_SET BLE_MESH_MODEL_OP_2(0x80, 0x13) +#define OP_GATT_PROXY_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x14) +#define OP_KRP_GET BLE_MESH_MODEL_OP_2(0x80, 0x15) +#define OP_KRP_SET BLE_MESH_MODEL_OP_2(0x80, 0x16) +#define OP_KRP_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x17) +#define OP_MOD_PUB_GET BLE_MESH_MODEL_OP_2(0x80, 0x18) +#define OP_MOD_PUB_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x19) +#define OP_MOD_PUB_VA_SET BLE_MESH_MODEL_OP_2(0x80, 0x1a) +#define OP_MOD_SUB_ADD BLE_MESH_MODEL_OP_2(0x80, 0x1b) +#define OP_MOD_SUB_DEL BLE_MESH_MODEL_OP_2(0x80, 0x1c) +#define OP_MOD_SUB_DEL_ALL BLE_MESH_MODEL_OP_2(0x80, 0x1d) +#define OP_MOD_SUB_OVERWRITE BLE_MESH_MODEL_OP_2(0x80, 0x1e) +#define OP_MOD_SUB_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x1f) +#define OP_MOD_SUB_VA_ADD BLE_MESH_MODEL_OP_2(0x80, 0x20) +#define OP_MOD_SUB_VA_DEL BLE_MESH_MODEL_OP_2(0x80, 0x21) +#define OP_MOD_SUB_VA_OVERWRITE BLE_MESH_MODEL_OP_2(0x80, 0x22) +#define OP_NET_TRANSMIT_GET BLE_MESH_MODEL_OP_2(0x80, 0x23) +#define OP_NET_TRANSMIT_SET BLE_MESH_MODEL_OP_2(0x80, 0x24) +#define OP_NET_TRANSMIT_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x25) +#define OP_RELAY_GET BLE_MESH_MODEL_OP_2(0x80, 0x26) +#define OP_RELAY_SET BLE_MESH_MODEL_OP_2(0x80, 0x27) +#define OP_RELAY_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x28) +#define OP_MOD_SUB_GET BLE_MESH_MODEL_OP_2(0x80, 0x29) +#define OP_MOD_SUB_LIST BLE_MESH_MODEL_OP_2(0x80, 0x2a) +#define OP_MOD_SUB_GET_VND BLE_MESH_MODEL_OP_2(0x80, 0x2b) +#define OP_MOD_SUB_LIST_VND BLE_MESH_MODEL_OP_2(0x80, 0x2c) +#define OP_LPN_TIMEOUT_GET BLE_MESH_MODEL_OP_2(0x80, 0x2d) +#define OP_LPN_TIMEOUT_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x2e) +#define OP_HEALTH_FAULT_CLEAR BLE_MESH_MODEL_OP_2(0x80, 0x2f) +#define OP_HEALTH_FAULT_CLEAR_UNREL BLE_MESH_MODEL_OP_2(0x80, 0x30) +#define OP_HEALTH_FAULT_GET BLE_MESH_MODEL_OP_2(0x80, 0x31) +#define OP_HEALTH_FAULT_TEST BLE_MESH_MODEL_OP_2(0x80, 0x32) +#define OP_HEALTH_FAULT_TEST_UNREL BLE_MESH_MODEL_OP_2(0x80, 0x33) +#define OP_HEALTH_PERIOD_GET BLE_MESH_MODEL_OP_2(0x80, 0x34) +#define OP_HEALTH_PERIOD_SET BLE_MESH_MODEL_OP_2(0x80, 0x35) +#define OP_HEALTH_PERIOD_SET_UNREL BLE_MESH_MODEL_OP_2(0x80, 0x36) +#define OP_HEALTH_PERIOD_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x37) +#define OP_HEARTBEAT_PUB_GET BLE_MESH_MODEL_OP_2(0x80, 0x38) +#define OP_HEARTBEAT_PUB_SET BLE_MESH_MODEL_OP_2(0x80, 0x39) +#define OP_HEARTBEAT_SUB_GET BLE_MESH_MODEL_OP_2(0x80, 0x3a) +#define OP_HEARTBEAT_SUB_SET BLE_MESH_MODEL_OP_2(0x80, 0x3b) +#define OP_HEARTBEAT_SUB_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x3c) +#define OP_MOD_APP_BIND BLE_MESH_MODEL_OP_2(0x80, 0x3d) +#define OP_MOD_APP_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x3e) +#define OP_MOD_APP_UNBIND BLE_MESH_MODEL_OP_2(0x80, 0x3f) +#define OP_NET_KEY_ADD BLE_MESH_MODEL_OP_2(0x80, 0x40) +#define OP_NET_KEY_DEL BLE_MESH_MODEL_OP_2(0x80, 0x41) +#define OP_NET_KEY_GET BLE_MESH_MODEL_OP_2(0x80, 0x42) +#define OP_NET_KEY_LIST BLE_MESH_MODEL_OP_2(0x80, 0x43) +#define OP_NET_KEY_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x44) +#define OP_NET_KEY_UPDATE BLE_MESH_MODEL_OP_2(0x80, 0x45) +#define OP_NODE_IDENTITY_GET BLE_MESH_MODEL_OP_2(0x80, 0x46) +#define OP_NODE_IDENTITY_SET BLE_MESH_MODEL_OP_2(0x80, 0x47) +#define OP_NODE_IDENTITY_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x48) +#define OP_NODE_RESET BLE_MESH_MODEL_OP_2(0x80, 0x49) +#define OP_NODE_RESET_STATUS BLE_MESH_MODEL_OP_2(0x80, 0x4a) +#define OP_SIG_MOD_APP_GET BLE_MESH_MODEL_OP_2(0x80, 0x4b) +#define OP_SIG_MOD_APP_LIST BLE_MESH_MODEL_OP_2(0x80, 0x4c) +#define OP_VND_MOD_APP_GET BLE_MESH_MODEL_OP_2(0x80, 0x4d) +#define OP_VND_MOD_APP_LIST BLE_MESH_MODEL_OP_2(0x80, 0x4e) + +#define STATUS_SUCCESS 0x00 +#define STATUS_INVALID_ADDRESS 0x01 +#define STATUS_INVALID_MODEL 0x02 +#define STATUS_INVALID_APPKEY 0x03 +#define STATUS_INVALID_NETKEY 0x04 +#define STATUS_INSUFF_RESOURCES 0x05 +#define STATUS_IDX_ALREADY_STORED 0x06 +#define STATUS_NVAL_PUB_PARAM 0x07 +#define STATUS_NOT_SUB_MOD 0x08 +#define STATUS_STORAGE_FAIL 0x09 +#define STATUS_FEAT_NOT_SUPP 0x0a +#define STATUS_CANNOT_UPDATE 0x0b +#define STATUS_CANNOT_REMOVE 0x0c +#define STATUS_CANNOT_BIND 0x0d +#define STATUS_TEMP_STATE_CHG_FAIL 0x0e +#define STATUS_CANNOT_SET 0x0f +#define STATUS_UNSPECIFIED 0x10 +#define STATUS_INVALID_BINDING 0x11 + +int bt_mesh_cfg_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_health_srv_init(struct bt_mesh_model *model, bool primary); + +int bt_mesh_cfg_cli_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_health_cli_init(struct bt_mesh_model *model, bool primary); + +void bt_mesh_cfg_reset(void); + +void bt_mesh_heartbeat(u16_t src, u16_t dst, u8_t hops, u16_t feat); + +void bt_mesh_attention(struct bt_mesh_model *model, u8_t time); + +u8_t *bt_mesh_label_uuid_get(u16_t addr); + +struct bt_mesh_hb_pub *bt_mesh_hb_pub_get(void); +void bt_mesh_hb_pub_disable(void); +struct bt_mesh_cfg_srv *bt_mesh_cfg_get(void); + +u8_t bt_mesh_net_transmit_get(void); +u8_t bt_mesh_relay_get(void); +u8_t bt_mesh_friend_get(void); +u8_t bt_mesh_relay_retransmit_get(void); +u8_t bt_mesh_beacon_get(void); +u8_t bt_mesh_gatt_proxy_get(void); +u8_t bt_mesh_default_ttl_get(void); + +void bt_mesh_subnet_del(struct bt_mesh_subnet *sub, bool store); + +struct bt_mesh_app_key *bt_mesh_app_key_alloc(u16_t app_idx); +void bt_mesh_app_key_del(struct bt_mesh_app_key *key, bool store); + +static inline void key_idx_pack(struct net_buf_simple *buf, + u16_t idx1, u16_t idx2) +{ + net_buf_simple_add_le16(buf, idx1 | ((idx2 & 0x00f) << 12)); + net_buf_simple_add_u8(buf, idx2 >> 4); +} + +static inline void key_idx_unpack(struct net_buf_simple *buf, + u16_t *idx1, u16_t *idx2) +{ + *idx1 = sys_get_le16(&buf->data[0]) & 0xfff; + *idx2 = sys_get_le16(&buf->data[1]) >> 4; + net_buf_simple_pull(buf, 3); +} + +#endif /* _FOUNDATION_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/friend.c b/components/bt/ble_mesh/mesh_core/friend.c new file mode 100644 index 0000000000..b47e86fe81 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/friend.c @@ -0,0 +1,1326 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_FRIEND) + +#include "mesh_buf.h" +#include "mesh_util.h" +#include "mesh_main.h" +#include "mesh_trace.h" + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "net.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "friend.h" + +#ifdef CONFIG_BLE_MESH_FRIEND + +#define FRIEND_BUF_SIZE (BLE_MESH_ADV_DATA_SIZE - BLE_MESH_NET_HDR_LEN) + +/* We reserve one extra buffer for each friendship, since we need to be able + * to resend the last sent PDU, which sits separately outside of the queue. + */ +#define FRIEND_BUF_COUNT ((CONFIG_BLE_MESH_FRIEND_QUEUE_SIZE + 1) * \ + CONFIG_BLE_MESH_FRIEND_LPN_COUNT) + +#define FRIEND_ADV(buf) CONTAINER_OF(BLE_MESH_ADV(buf), \ + struct friend_adv, adv) + +/* PDUs from Friend to the LPN should only be transmitted once with the + * smallest possible interval (20ms). + */ +#define FRIEND_XMIT BLE_MESH_TRANSMIT(0, 20) + +struct friend_pdu_info { + u16_t src; + u16_t dst; + + u8_t seq[3]; + + u8_t ttl: 7, + ctl: 1; + + u32_t iv_index; +}; + +NET_BUF_POOL_FIXED_DEFINE(friend_buf_pool, FRIEND_BUF_COUNT, + BLE_MESH_ADV_DATA_SIZE, NULL); + +static struct friend_adv { + struct bt_mesh_adv adv; + u64_t seq_auth; +} adv_pool[FRIEND_BUF_COUNT]; + +static struct bt_mesh_adv *adv_alloc(int id) +{ + return &adv_pool[id].adv; +} + +static void discard_buffer(void) +{ + struct bt_mesh_friend *frnd = &bt_mesh.frnd[0]; + struct net_buf *buf; + int i; + + /* Find the Friend context with the most queued buffers */ + for (i = 1; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + if (bt_mesh.frnd[i].queue_size > frnd->queue_size) { + frnd = &bt_mesh.frnd[i]; + } + } + + buf = net_buf_slist_get(&frnd->queue); + __ASSERT_NO_MSG(buf != NULL); + BT_WARN("Discarding buffer %p for LPN 0x%04x", buf, frnd->lpn); + net_buf_unref(buf); +} + +static struct net_buf *friend_buf_alloc(u16_t src) +{ + struct net_buf *buf; + + BT_DBG("src 0x%04x", src); + + do { + buf = bt_mesh_adv_create_from_pool(&friend_buf_pool, adv_alloc, + BLE_MESH_ADV_DATA, + FRIEND_XMIT, K_NO_WAIT); + if (!buf) { + discard_buffer(); + } + } while (!buf); + + BLE_MESH_ADV(buf)->addr = src; + FRIEND_ADV(buf)->seq_auth = TRANS_SEQ_AUTH_NVAL; + + BT_DBG("allocated buf %p", buf); + + return buf; +} + +static bool is_lpn_unicast(struct bt_mesh_friend *frnd, u16_t addr) +{ + if (frnd->lpn == BLE_MESH_ADDR_UNASSIGNED) { + return false; + } + + return (addr >= frnd->lpn && addr < (frnd->lpn + frnd->num_elem)); +} + +struct bt_mesh_friend *bt_mesh_friend_find(u16_t net_idx, u16_t lpn_addr, + bool valid, bool established) +{ + int i; + + BT_DBG("net_idx 0x%04x lpn_addr 0x%04x", net_idx, lpn_addr); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (valid && !frnd->valid) { + continue; + } + + if (established && !frnd->established) { + continue; + } + + if (net_idx != BLE_MESH_KEY_ANY && frnd->net_idx != net_idx) { + continue; + } + + if (is_lpn_unicast(frnd, lpn_addr)) { + return frnd; + } + } + + return NULL; +} + +/* Intentionally start a little bit late into the ReceiveWindow when + * it's large enough. This may improve reliability with some platforms, + * like the PTS, where the receiver might not have sufficiently compensated + * for internal latencies required to start scanning. + */ +static s32_t recv_delay(struct bt_mesh_friend *frnd) +{ +#if CONFIG_BLE_MESH_FRIEND_RECV_WIN > 50 + return (s32_t)frnd->recv_delay + (CONFIG_BLE_MESH_FRIEND_RECV_WIN / 5); +#else + return frnd->recv_delay; +#endif +} + +static void friend_clear(struct bt_mesh_friend *frnd) +{ + int i; + + BT_DBG("LPN 0x%04x", frnd->lpn); + + k_delayed_work_cancel(&frnd->timer); + + friend_cred_del(frnd->net_idx, frnd->lpn); + + if (frnd->last) { + /* Cancel the sending if necessary */ + if (frnd->pending_buf) { + BLE_MESH_ADV(frnd->last)->busy = 0U; + } + + net_buf_unref(frnd->last); + frnd->last = NULL; + } + + while (!sys_slist_is_empty(&frnd->queue)) { + net_buf_unref(net_buf_slist_get(&frnd->queue)); + } + + for (i = 0; i < ARRAY_SIZE(frnd->seg); i++) { + struct bt_mesh_friend_seg *seg = &frnd->seg[i]; + + while (!sys_slist_is_empty(&seg->queue)) { + net_buf_unref(net_buf_slist_get(&seg->queue)); + } + } + + frnd->valid = 0U; + frnd->established = 0U; + frnd->pending_buf = 0U; + frnd->fsn = 0U; + frnd->queue_size = 0U; + frnd->pending_req = 0U; + (void)memset(frnd->sub_list, 0, sizeof(frnd->sub_list)); +} + +void bt_mesh_friend_clear_net_idx(u16_t net_idx) +{ + int i; + + BT_DBG("net_idx 0x%04x", net_idx); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (frnd->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + if (net_idx == BLE_MESH_KEY_ANY || frnd->net_idx == net_idx) { + friend_clear(frnd); + } + } +} + +void bt_mesh_friend_sec_update(u16_t net_idx) +{ + int i; + + BT_DBG("net_idx 0x%04x", net_idx); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (frnd->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + if (net_idx == BLE_MESH_KEY_ANY || frnd->net_idx == net_idx) { + frnd->sec_update = 1U; + } + } +} + +int bt_mesh_friend_clear(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_clear *msg = (void *)buf->data; + struct bt_mesh_friend *frnd; + u16_t lpn_addr, lpn_counter; + struct bt_mesh_net_tx tx = { + .sub = rx->sub, + .ctx = &rx->ctx, + .src = bt_mesh_primary_addr(), + .xmit = bt_mesh_net_transmit_get(), + }; + struct bt_mesh_ctl_friend_clear_confirm cfm; + + if (buf->len < sizeof(*msg)) { + BT_WARN("%s, Too short Friend Clear", __func__); + return -EINVAL; + } + + lpn_addr = sys_be16_to_cpu(msg->lpn_addr); + lpn_counter = sys_be16_to_cpu(msg->lpn_counter); + + BT_DBG("LPN addr 0x%04x counter 0x%04x", lpn_addr, lpn_counter); + + frnd = bt_mesh_friend_find(rx->sub->net_idx, lpn_addr, false, false); + if (!frnd) { + BT_WARN("%s, No matching LPN addr 0x%04x", __func__, lpn_addr); + return 0; + } + + /* A Friend Clear message is considered valid if the result of the + * subtraction of the value of the LPNCounter field of the Friend + * Request message (the one that initiated the friendship) from the + * value of the LPNCounter field of the Friend Clear message, modulo + * 65536, is in the range 0 to 255 inclusive. + */ + if (lpn_counter - frnd->lpn_counter > 255) { + BT_WARN("%s, LPN Counter out of range (old %u new %u)", + __func__, frnd->lpn_counter, lpn_counter); + return 0; + } + + tx.ctx->send_ttl = BLE_MESH_TTL_MAX; + + cfm.lpn_addr = msg->lpn_addr; + cfm.lpn_counter = msg->lpn_counter; + + bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR_CFM, &cfm, + sizeof(cfm), NULL, NULL, NULL); + + friend_clear(frnd); + + return 0; +} + +static void friend_sub_add(struct bt_mesh_friend *frnd, u16_t addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(frnd->sub_list); i++) { + if (frnd->sub_list[i] == BLE_MESH_ADDR_UNASSIGNED) { + frnd->sub_list[i] = addr; + return; + } + } + + BT_WARN("%s, No space in friend subscription list", __func__); +} + +static void friend_sub_rem(struct bt_mesh_friend *frnd, u16_t addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(frnd->sub_list); i++) { + if (frnd->sub_list[i] == addr) { + frnd->sub_list[i] = BLE_MESH_ADDR_UNASSIGNED; + return; + } + } +} + +static struct net_buf *create_friend_pdu(struct bt_mesh_friend *frnd, + struct friend_pdu_info *info, + struct net_buf_simple *sdu) +{ + struct bt_mesh_subnet *sub; + const u8_t *enc, *priv; + struct net_buf *buf; + u8_t nid; + + sub = bt_mesh_subnet_get(frnd->net_idx); + __ASSERT_NO_MSG(sub != NULL); + + buf = friend_buf_alloc(info->src); + + /* Friend Offer needs master security credentials */ + if (info->ctl && TRANS_CTL_OP(sdu->data) == TRANS_CTL_OP_FRIEND_OFFER) { + enc = sub->keys[sub->kr_flag].enc; + priv = sub->keys[sub->kr_flag].privacy; + nid = sub->keys[sub->kr_flag].nid; + } else { + if (friend_cred_get(sub, frnd->lpn, &nid, &enc, &priv)) { + BT_ERR("%s, friend_cred_get failed", __func__); + goto failed; + } + } + + net_buf_add_u8(buf, (nid | (info->iv_index & 1) << 7)); + + if (info->ctl) { + net_buf_add_u8(buf, info->ttl | 0x80); + } else { + net_buf_add_u8(buf, info->ttl); + } + + net_buf_add_mem(buf, info->seq, sizeof(info->seq)); + + net_buf_add_be16(buf, info->src); + net_buf_add_be16(buf, info->dst); + + net_buf_add_mem(buf, sdu->data, sdu->len); + + /* We re-encrypt and obfuscate using the received IVI rather than + * the normal TX IVI (which may be different) since the transport + * layer nonce includes the IVI. + */ + if (bt_mesh_net_encrypt(enc, &buf->b, info->iv_index, false)) { + BT_ERR("%s, Re-encrypting failed", __func__); + goto failed; + } + + if (bt_mesh_net_obfuscate(buf->data, info->iv_index, priv)) { + BT_ERR("%s, Re-obfuscating failed", __func__); + goto failed; + } + + return buf; + +failed: + net_buf_unref(buf); + return NULL; +} + +static struct net_buf *encode_friend_ctl(struct bt_mesh_friend *frnd, + u8_t ctl_op, + struct net_buf_simple *sdu) +{ + struct friend_pdu_info info; + u32_t seq; + + BT_DBG("LPN 0x%04x", frnd->lpn); + + net_buf_simple_push_u8(sdu, TRANS_CTL_HDR(ctl_op, 0)); + + info.src = bt_mesh_primary_addr(); + info.dst = frnd->lpn; + + info.ctl = 1U; + info.ttl = 0U; + + seq = bt_mesh_next_seq(); + info.seq[0] = seq >> 16; + info.seq[1] = seq >> 8; + info.seq[2] = seq; + + info.iv_index = BLE_MESH_NET_IVI_TX; + + return create_friend_pdu(frnd, &info, sdu); +} + +static struct net_buf *encode_update(struct bt_mesh_friend *frnd, u8_t md) +{ + struct bt_mesh_ctl_friend_update *upd; + NET_BUF_SIMPLE_DEFINE(sdu, 1 + sizeof(*upd)); + struct bt_mesh_subnet *sub = bt_mesh_subnet_get(frnd->net_idx); + + __ASSERT_NO_MSG(sub != NULL); + + BT_DBG("lpn 0x%04x md 0x%02x", frnd->lpn, md); + + net_buf_simple_reserve(&sdu, 1); + + upd = net_buf_simple_add(&sdu, sizeof(*upd)); + upd->flags = bt_mesh_net_flags(sub); + upd->iv_index = sys_cpu_to_be32(bt_mesh.iv_index); + upd->md = md; + + return encode_friend_ctl(frnd, TRANS_CTL_OP_FRIEND_UPDATE, &sdu); +} + +static void enqueue_sub_cfm(struct bt_mesh_friend *frnd, u8_t xact) +{ + struct bt_mesh_ctl_friend_sub_confirm *cfm; + NET_BUF_SIMPLE_DEFINE(sdu, 1 + sizeof(*cfm)); + struct net_buf *buf; + + BT_DBG("lpn 0x%04x xact 0x%02x", frnd->lpn, xact); + + net_buf_simple_reserve(&sdu, 1); + + cfm = net_buf_simple_add(&sdu, sizeof(*cfm)); + cfm->xact = xact; + + buf = encode_friend_ctl(frnd, TRANS_CTL_OP_FRIEND_SUB_CFM, &sdu); + if (!buf) { + BT_ERR("%s, Unable to encode Subscription List Confirmation", __func__); + return; + } + + if (frnd->last) { + BT_DBG("Discarding last PDU"); + net_buf_unref(frnd->last); + } + + frnd->last = buf; + frnd->send_last = 1U; +} + +static void friend_recv_delay(struct bt_mesh_friend *frnd) +{ + frnd->pending_req = 1U; + k_delayed_work_submit(&frnd->timer, recv_delay(frnd)); + BT_DBG("Waiting RecvDelay of %d ms", recv_delay(frnd)); +} + +int bt_mesh_friend_sub_add(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_friend *frnd; + u8_t xact; + + if (buf->len < BLE_MESH_FRIEND_SUB_MIN_LEN) { + BT_WARN("%s, Too short Friend Subscription Add", __func__); + return -EINVAL; + } + + frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, true); + if (!frnd) { + BT_WARN("%s, No matching LPN addr 0x%04x", __func__, rx->ctx.addr); + return 0; + } + + if (frnd->pending_buf) { + BT_WARN("%s, Previous buffer not yet sent!", __func__); + return 0; + } + + friend_recv_delay(frnd); + + xact = net_buf_simple_pull_u8(buf); + + while (buf->len >= 2U) { + friend_sub_add(frnd, net_buf_simple_pull_be16(buf)); + } + + enqueue_sub_cfm(frnd, xact); + + return 0; +} + +int bt_mesh_friend_sub_rem(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_friend *frnd; + u8_t xact; + + if (buf->len < BLE_MESH_FRIEND_SUB_MIN_LEN) { + BT_WARN("%s, Too short Friend Subscription Remove", __func__); + return -EINVAL; + } + + frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, true); + if (!frnd) { + BT_WARN("%s, No matching LPN addr 0x%04x", __func__, rx->ctx.addr); + return 0; + } + + if (frnd->pending_buf) { + BT_WARN("%s, Previous buffer not yet sent!", __func__); + return 0; + } + + friend_recv_delay(frnd); + + xact = net_buf_simple_pull_u8(buf); + + while (buf->len >= 2U) { + friend_sub_rem(frnd, net_buf_simple_pull_be16(buf)); + } + + enqueue_sub_cfm(frnd, xact); + + return 0; +} + +static void enqueue_buf(struct bt_mesh_friend *frnd, struct net_buf *buf) +{ + net_buf_slist_put(&frnd->queue, buf); + frnd->queue_size++; +} + +static void enqueue_update(struct bt_mesh_friend *frnd, u8_t md) +{ + struct net_buf *buf; + + buf = encode_update(frnd, md); + if (!buf) { + BT_ERR("%s, Unable to encode Friend Update", __func__); + return; + } + + frnd->sec_update = 0U; + enqueue_buf(frnd, buf); +} + +int bt_mesh_friend_poll(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_poll *msg = (void *)buf->data; + struct bt_mesh_friend *frnd; + + if (buf->len < sizeof(*msg)) { + BT_WARN("%s, Too short Friend Poll", __func__); + return -EINVAL; + } + + frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, false); + if (!frnd) { + BT_WARN("%s, No matching LPN addr 0x%04x", __func__, rx->ctx.addr); + return 0; + } + + if (msg->fsn & ~1) { + BT_WARN("%s, Prohibited (non-zero) padding bits", __func__); + return -EINVAL; + } + + if (frnd->pending_buf) { + BT_WARN("%s, Previous buffer not yet sent!", __func__); + return 0; + } + + BT_DBG("msg->fsn %u frnd->fsn %u", (msg->fsn & 1), frnd->fsn); + + friend_recv_delay(frnd); + + if (!frnd->established) { + BT_DBG("Friendship established with 0x%04x", frnd->lpn); + frnd->established = 1U; + } + + if (msg->fsn == frnd->fsn && frnd->last) { + BT_DBG("Re-sending last PDU"); + frnd->send_last = 1U; + } else { + if (frnd->last) { + net_buf_unref(frnd->last); + frnd->last = NULL; + } + + frnd->fsn = msg->fsn; + + if (sys_slist_is_empty(&frnd->queue)) { + enqueue_update(frnd, 0); + BT_DBG("Enqueued Friend Update to empty queue"); + } + } + + return 0; +} + +static struct bt_mesh_friend *find_clear(u16_t prev_friend) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (frnd->clear.frnd == prev_friend) { + return frnd; + } + } + + return NULL; +} + +static void friend_clear_sent(int err, void *user_data) +{ + struct bt_mesh_friend *frnd = user_data; + + k_delayed_work_submit(&frnd->clear.timer, + K_SECONDS(frnd->clear.repeat_sec)); + frnd->clear.repeat_sec *= 2U; +} + +static const struct bt_mesh_send_cb clear_sent_cb = { + .end = friend_clear_sent, +}; + +static void send_friend_clear(struct bt_mesh_friend *frnd) +{ + struct bt_mesh_msg_ctx ctx = { + .net_idx = frnd->net_idx, + .app_idx = BLE_MESH_KEY_UNUSED, + .addr = frnd->clear.frnd, + .send_ttl = BLE_MESH_TTL_MAX, + }; + struct bt_mesh_net_tx tx = { + .sub = &bt_mesh.sub[0], + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = bt_mesh_net_transmit_get(), + }; + struct bt_mesh_ctl_friend_clear req = { + .lpn_addr = sys_cpu_to_be16(frnd->lpn), + .lpn_counter = sys_cpu_to_be16(frnd->lpn_counter), + }; + + BT_DBG("%s", __func__); + + bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR, &req, + sizeof(req), NULL, &clear_sent_cb, frnd); +} + +static void clear_timeout(struct k_work *work) +{ + struct bt_mesh_friend *frnd = CONTAINER_OF(work, struct bt_mesh_friend, + clear.timer.work); + u32_t duration; + + BT_DBG("LPN 0x%04x (old) Friend 0x%04x", frnd->lpn, frnd->clear.frnd); + + duration = k_uptime_get_32() - frnd->clear.start; + if (duration > 2 * frnd->poll_to) { + BT_DBG("Clear Procedure timer expired"); + frnd->clear.frnd = BLE_MESH_ADDR_UNASSIGNED; + return; + } + + send_friend_clear(frnd); +} + +static void clear_procedure_start(struct bt_mesh_friend *frnd) +{ + BT_DBG("LPN 0x%04x (old) Friend 0x%04x", frnd->lpn, frnd->clear.frnd); + + frnd->clear.start = k_uptime_get_32() + (2 * frnd->poll_to); + frnd->clear.repeat_sec = 1U; + + send_friend_clear(frnd); +} + +int bt_mesh_friend_clear_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_clear_confirm *msg = (void *)buf->data; + struct bt_mesh_friend *frnd; + u16_t lpn_addr, lpn_counter; + + BT_DBG("%s", __func__); + + if (buf->len < sizeof(*msg)) { + BT_WARN("%s, Too short Friend Clear Confirm", __func__); + return -EINVAL; + } + + frnd = find_clear(rx->ctx.addr); + if (!frnd) { + BT_WARN("%s, No pending clear procedure for 0x%02x", __func__, rx->ctx.addr); + return 0; + } + + lpn_addr = sys_be16_to_cpu(msg->lpn_addr); + if (lpn_addr != frnd->lpn) { + BT_WARN("%s, LPN address mismatch (0x%04x != 0x%04x)", + __func__, lpn_addr, frnd->lpn); + return 0; + } + + lpn_counter = sys_be16_to_cpu(msg->lpn_counter); + if (lpn_counter != frnd->lpn_counter) { + BT_WARN("%s, LPN counter mismatch (0x%04x != 0x%04x)", + __func__, lpn_counter, frnd->lpn_counter); + return 0; + } + + k_delayed_work_cancel(&frnd->clear.timer); + frnd->clear.frnd = BLE_MESH_ADDR_UNASSIGNED; + + return 0; +} + +static void enqueue_offer(struct bt_mesh_friend *frnd, s8_t rssi) +{ + struct bt_mesh_ctl_friend_offer *off; + NET_BUF_SIMPLE_DEFINE(sdu, 1 + sizeof(*off)); + struct net_buf *buf; + + BT_DBG("%s", __func__); + + net_buf_simple_reserve(&sdu, 1); + + off = net_buf_simple_add(&sdu, sizeof(*off)); + + off->recv_win = CONFIG_BLE_MESH_FRIEND_RECV_WIN, + off->queue_size = CONFIG_BLE_MESH_FRIEND_QUEUE_SIZE, + off->sub_list_size = ARRAY_SIZE(frnd->sub_list), + off->rssi = rssi, + off->frnd_counter = sys_cpu_to_be16(frnd->counter); + + buf = encode_friend_ctl(frnd, TRANS_CTL_OP_FRIEND_OFFER, &sdu); + if (!buf) { + BT_ERR("%s, Unable to encode Friend Offer", __func__); + return; + } + + frnd->counter++; + + if (frnd->last) { + net_buf_unref(frnd->last); + } + + frnd->last = buf; + frnd->send_last = 1U; +} + +#define RECV_WIN CONFIG_BLE_MESH_FRIEND_RECV_WIN +#define RSSI_FACT(crit) (((crit) >> 5) & (u8_t)BIT_MASK(2)) +#define RECV_WIN_FACT(crit) (((crit) >> 3) & (u8_t)BIT_MASK(2)) +#define MIN_QUEUE_SIZE_LOG(crit) ((crit) & (u8_t)BIT_MASK(3)) +#define MIN_QUEUE_SIZE(crit) ((u32_t)BIT(MIN_QUEUE_SIZE_LOG(crit))) + +static s32_t offer_delay(struct bt_mesh_friend *frnd, s8_t rssi, u8_t crit) +{ + /* Scaling factors. The actual values are 1, 1.5, 2 & 2.5, but we + * want to avoid floating-point arithmetic. + */ + static const u8_t fact[] = { 10, 15, 20, 25 }; + s32_t delay; + + BT_DBG("ReceiveWindowFactor %u ReceiveWindow %u RSSIFactor %u RSSI %d", + fact[RECV_WIN_FACT(crit)], RECV_WIN, + fact[RSSI_FACT(crit)], rssi); + + /* Delay = ReceiveWindowFactor * ReceiveWindow - RSSIFactor * RSSI */ + delay = (s32_t)fact[RECV_WIN_FACT(crit)] * RECV_WIN; + delay -= (s32_t)fact[RSSI_FACT(crit)] * rssi; + delay /= 10; + + BT_DBG("Local Delay calculated as %d ms", delay); + + if (delay < 100) { + return K_MSEC(100); + } + + return K_MSEC(delay); +} + +int bt_mesh_friend_req(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_req *msg = (void *)buf->data; + struct bt_mesh_friend *frnd = NULL; + u32_t poll_to; + int i; + + if (buf->len < sizeof(*msg)) { + BT_WARN("%s, Too short Friend Request", __func__); + return -EINVAL; + } + + if (msg->recv_delay <= 0x09) { + BT_WARN("%s, Prohibited ReceiveDelay (0x%02x)", __func__, msg->recv_delay); + return -EINVAL; + } + + poll_to = (((u32_t)msg->poll_to[0] << 16) | + ((u32_t)msg->poll_to[1] << 8) | + ((u32_t)msg->poll_to[2])); + + if (poll_to <= 0x000009 || poll_to >= 0x34bc00) { + BT_WARN("%s, Prohibited PollTimeout (0x%06x)", __func__, poll_to); + return -EINVAL; + } + + if (msg->num_elem == 0x00) { + BT_WARN("%s, Prohibited NumElements value (0x00)", __func__); + return -EINVAL; + } + + if (!BLE_MESH_ADDR_IS_UNICAST(rx->ctx.addr + msg->num_elem - 1)) { + BT_WARN("%s, LPN elements stretch outside of unicast range", __func__); + return -EINVAL; + } + + if (!MIN_QUEUE_SIZE_LOG(msg->criteria)) { + BT_WARN("%s, Prohibited Minimum Queue Size in Friend Request", __func__); + return -EINVAL; + } + + if (CONFIG_BLE_MESH_FRIEND_QUEUE_SIZE < MIN_QUEUE_SIZE(msg->criteria)) { + BT_WARN("%s, We have a too small Friend Queue size (%u < %u)", + __func__, CONFIG_BLE_MESH_FRIEND_QUEUE_SIZE, + MIN_QUEUE_SIZE(msg->criteria)); + return 0; + } + + frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, false); + if (frnd) { + BT_WARN("%s, Existing LPN re-requesting Friendship", __func__); + friend_clear(frnd); + goto init_friend; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + if (!bt_mesh.frnd[i].valid) { + frnd = &bt_mesh.frnd[i]; + frnd->valid = 1U; + break; + } + } + + if (!frnd) { + BT_WARN("%s, No free Friend contexts for new LPN", __func__); + return -ENOMEM; + } + +init_friend: + frnd->lpn = rx->ctx.addr; + frnd->num_elem = msg->num_elem; + frnd->net_idx = rx->sub->net_idx; + frnd->recv_delay = msg->recv_delay; + frnd->poll_to = poll_to * 100U; + frnd->lpn_counter = sys_be16_to_cpu(msg->lpn_counter); + frnd->clear.frnd = sys_be16_to_cpu(msg->prev_addr); + + BT_DBG("LPN 0x%04x rssi %d recv_delay %u poll_to %ums", + frnd->lpn, rx->rssi, frnd->recv_delay, frnd->poll_to); + + if (BLE_MESH_ADDR_IS_UNICAST(frnd->clear.frnd) && + !bt_mesh_elem_find(frnd->clear.frnd)) { + clear_procedure_start(frnd); + } + + k_delayed_work_submit(&frnd->timer, + offer_delay(frnd, rx->rssi, msg->criteria)); + + friend_cred_create(rx->sub, frnd->lpn, frnd->lpn_counter, + frnd->counter); + + enqueue_offer(frnd, rx->rssi); + + return 0; +} + +static struct bt_mesh_friend_seg *get_seg(struct bt_mesh_friend *frnd, + u16_t src, u64_t *seq_auth) +{ + struct bt_mesh_friend_seg *unassigned = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(frnd->seg); i++) { + struct bt_mesh_friend_seg *seg = &frnd->seg[i]; + struct net_buf *buf = (void *)sys_slist_peek_head(&seg->queue); + + if (buf && BLE_MESH_ADV(buf)->addr == src && + FRIEND_ADV(buf)->seq_auth == *seq_auth) { + return seg; + } + + if (!unassigned && !buf) { + unassigned = seg; + } + } + + return unassigned; +} + +static void enqueue_friend_pdu(struct bt_mesh_friend *frnd, + enum bt_mesh_friend_pdu_type type, + struct net_buf *buf) +{ + struct bt_mesh_friend_seg *seg; + struct friend_adv *adv; + + BT_DBG("type %u", type); + + if (type == BLE_MESH_FRIEND_PDU_SINGLE) { + if (frnd->sec_update) { + enqueue_update(frnd, 1); + } + + enqueue_buf(frnd, buf); + return; + } + + adv = FRIEND_ADV(buf); + seg = get_seg(frnd, BLE_MESH_ADV(buf)->addr, &adv->seq_auth); + if (!seg) { + BT_ERR("%s, No free friend segment RX contexts for 0x%04x", + __func__, BLE_MESH_ADV(buf)->addr); + net_buf_unref(buf); + return; + } + + net_buf_slist_put(&seg->queue, buf); + + if (type == BLE_MESH_FRIEND_PDU_COMPLETE) { + if (frnd->sec_update) { + enqueue_update(frnd, 1); + } + + /* Only acks should have a valid SeqAuth in the Friend queue + * (otherwise we can't easily detect them there), so clear + * the SeqAuth information from the segments before merging. + */ + SYS_SLIST_FOR_EACH_CONTAINER(&seg->queue, buf, node) { + FRIEND_ADV(buf)->seq_auth = TRANS_SEQ_AUTH_NVAL; + frnd->queue_size++; + } + + sys_slist_merge_slist(&frnd->queue, &seg->queue); + } +} + +static void buf_send_start(u16_t duration, int err, void *user_data) +{ + struct bt_mesh_friend *frnd = user_data; + + BT_DBG("err %d", err); + + frnd->pending_buf = 0U; + + /* Friend Offer doesn't follow the re-sending semantics */ + if (!frnd->established) { + net_buf_unref(frnd->last); + frnd->last = NULL; + } +} + +static void buf_send_end(int err, void *user_data) +{ + struct bt_mesh_friend *frnd = user_data; + + BT_DBG("err %d", err); + + if (frnd->pending_req) { + BT_WARN("Another request before previous completed sending"); + return; + } + + if (frnd->established) { + k_delayed_work_submit(&frnd->timer, frnd->poll_to); + BT_DBG("Waiting %u ms for next poll", frnd->poll_to); + } else { + /* Friend offer timeout is 1 second */ + k_delayed_work_submit(&frnd->timer, K_SECONDS(1)); + BT_DBG("Waiting for first poll"); + } +} + +static void friend_timeout(struct k_work *work) +{ + struct bt_mesh_friend *frnd = CONTAINER_OF(work, struct bt_mesh_friend, + timer.work); + static const struct bt_mesh_send_cb buf_sent_cb = { + .start = buf_send_start, + .end = buf_send_end, + }; + + __ASSERT_NO_MSG(frnd->pending_buf == 0U); + + BT_DBG("lpn 0x%04x send_last %u last %p", frnd->lpn, + frnd->send_last, frnd->last); + + if (frnd->send_last && frnd->last) { + BT_DBG("Sending frnd->last %p", frnd->last); + frnd->send_last = 0U; + goto send_last; + } + + if (frnd->established && !frnd->pending_req) { + BT_WARN("%s, Friendship lost with 0x%04x", __func__, frnd->lpn); + friend_clear(frnd); + return; + } + + frnd->last = net_buf_slist_get(&frnd->queue); + if (!frnd->last) { + BT_WARN("%s, Friendship not established with 0x%04x", __func__, frnd->lpn); + friend_clear(frnd); + return; + } + + BT_DBG("Sending buf %p from Friend Queue of LPN 0x%04x", + frnd->last, frnd->lpn); + frnd->queue_size--; + +send_last: + frnd->pending_req = 0U; + frnd->pending_buf = 1U; + bt_mesh_adv_send(frnd->last, &buf_sent_cb, frnd); +} + +int bt_mesh_friend_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + int j; + + frnd->net_idx = BLE_MESH_KEY_UNUSED; + + sys_slist_init(&frnd->queue); + + k_delayed_work_init(&frnd->timer, friend_timeout); + k_delayed_work_init(&frnd->clear.timer, clear_timeout); + + for (j = 0; j < ARRAY_SIZE(frnd->seg); j++) { + sys_slist_init(&frnd->seg[j].queue); + } + } + + return 0; +} + +static void friend_purge_old_ack(struct bt_mesh_friend *frnd, u64_t *seq_auth, + u16_t src) +{ + sys_snode_t *cur, *prev = NULL; + + BT_DBG("SeqAuth %llx src 0x%04x", *seq_auth, src); + + for (cur = sys_slist_peek_head(&frnd->queue); + cur != NULL; prev = cur, cur = sys_slist_peek_next(cur)) { + struct net_buf *buf = (void *)cur; + + if (BLE_MESH_ADV(buf)->addr == src && + FRIEND_ADV(buf)->seq_auth == *seq_auth) { + BT_DBG("Removing old ack from Friend Queue"); + + sys_slist_remove(&frnd->queue, prev, cur); + frnd->queue_size--; + /* Make sure old slist entry state doesn't remain */ + buf->frags = NULL; + + net_buf_unref(buf); + break; + } + } +} + +static void friend_lpn_enqueue_rx(struct bt_mesh_friend *frnd, + struct bt_mesh_net_rx *rx, + enum bt_mesh_friend_pdu_type type, + u64_t *seq_auth, struct net_buf_simple *sbuf) +{ + struct friend_pdu_info info; + struct net_buf *buf; + + BT_DBG("LPN 0x%04x queue_size %u", frnd->lpn, frnd->queue_size); + + if (type == BLE_MESH_FRIEND_PDU_SINGLE && seq_auth) { + friend_purge_old_ack(frnd, seq_auth, rx->ctx.addr); + } + + info.src = rx->ctx.addr; + info.dst = rx->ctx.recv_dst; + + if (rx->net_if == BLE_MESH_NET_IF_LOCAL) { + info.ttl = rx->ctx.recv_ttl; + } else { + info.ttl = rx->ctx.recv_ttl - 1U; + } + + info.ctl = rx->ctl; + + info.seq[0] = (rx->seq >> 16); + info.seq[1] = (rx->seq >> 8); + info.seq[2] = rx->seq; + + info.iv_index = BLE_MESH_NET_IVI_RX(rx); + + buf = create_friend_pdu(frnd, &info, sbuf); + if (!buf) { + BT_ERR("%s, Failed to encode Friend buffer", __func__); + return; + } + + if (seq_auth) { + FRIEND_ADV(buf)->seq_auth = *seq_auth; + } + + enqueue_friend_pdu(frnd, type, buf); + + BT_DBG("Queued message for LPN 0x%04x, queue_size %u", + frnd->lpn, frnd->queue_size); +} + +static void friend_lpn_enqueue_tx(struct bt_mesh_friend *frnd, + struct bt_mesh_net_tx *tx, + enum bt_mesh_friend_pdu_type type, + u64_t *seq_auth, struct net_buf_simple *sbuf) +{ + struct friend_pdu_info info; + struct net_buf *buf; + u32_t seq; + + BT_DBG("LPN 0x%04x", frnd->lpn); + + if (type == BLE_MESH_FRIEND_PDU_SINGLE && seq_auth) { + friend_purge_old_ack(frnd, seq_auth, tx->src); + } + + info.src = tx->src; + info.dst = tx->ctx->addr; + + info.ttl = tx->ctx->send_ttl; + info.ctl = (tx->ctx->app_idx == BLE_MESH_KEY_UNUSED); + + seq = bt_mesh_next_seq(); + info.seq[0] = seq >> 16; + info.seq[1] = seq >> 8; + info.seq[2] = seq; + + info.iv_index = BLE_MESH_NET_IVI_TX; + + buf = create_friend_pdu(frnd, &info, sbuf); + if (!buf) { + BT_ERR("%s, Failed to encode Friend buffer", __func__); + return; + } + + if (seq_auth) { + FRIEND_ADV(buf)->seq_auth = *seq_auth; + } + + enqueue_friend_pdu(frnd, type, buf); + + BT_DBG("Queued message for LPN 0x%04x", frnd->lpn); +} + +static bool friend_lpn_matches(struct bt_mesh_friend *frnd, u16_t net_idx, + u16_t addr) +{ + int i; + + if (!frnd->established) { + return false; + } + + if (net_idx != frnd->net_idx) { + return false; + } + + if (BLE_MESH_ADDR_IS_UNICAST(addr)) { + return is_lpn_unicast(frnd, addr); + } + + for (i = 0; i < ARRAY_SIZE(frnd->sub_list); i++) { + if (frnd->sub_list[i] == addr) { + return true; + } + } + + return false; +} + +bool bt_mesh_friend_match(u16_t net_idx, u16_t addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (friend_lpn_matches(frnd, net_idx, addr)) { + BT_DBG("LPN 0x%04x matched address 0x%04x", + frnd->lpn, addr); + return true; + } + } + + BT_DBG("No matching LPN for address 0x%04x", addr); + + return false; +} + +void bt_mesh_friend_enqueue_rx(struct bt_mesh_net_rx *rx, + enum bt_mesh_friend_pdu_type type, + u64_t *seq_auth, struct net_buf_simple *sbuf) +{ + int i; + + if (!rx->friend_match || + (rx->ctx.recv_ttl <= 1U && rx->net_if != BLE_MESH_NET_IF_LOCAL) || + bt_mesh_friend_get() != BLE_MESH_FRIEND_ENABLED) { + return; + } + + BT_DBG("recv_ttl %u net_idx 0x%04x src 0x%04x dst 0x%04x", + rx->ctx.recv_ttl, rx->sub->net_idx, rx->ctx.addr, + rx->ctx.recv_dst); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (friend_lpn_matches(frnd, rx->sub->net_idx, + rx->ctx.recv_dst)) { + friend_lpn_enqueue_rx(frnd, rx, type, seq_auth, sbuf); + } + } +} + +bool bt_mesh_friend_enqueue_tx(struct bt_mesh_net_tx *tx, + enum bt_mesh_friend_pdu_type type, + u64_t *seq_auth, struct net_buf_simple *sbuf) +{ + bool matched = false; + int i; + + if (!bt_mesh_friend_match(tx->sub->net_idx, tx->ctx->addr) || + bt_mesh_friend_get() != BLE_MESH_FRIEND_ENABLED) { + return matched; + } + + BT_DBG("net_idx 0x%04x dst 0x%04x src 0x%04x", tx->sub->net_idx, + tx->ctx->addr, tx->src); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (friend_lpn_matches(frnd, tx->sub->net_idx, tx->ctx->addr)) { + friend_lpn_enqueue_tx(frnd, tx, type, seq_auth, sbuf); + matched = true; + } + } + + return matched; +} + +void bt_mesh_friend_clear_incomplete(struct bt_mesh_subnet *sub, u16_t src, + u16_t dst, u64_t *seq_auth) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + int j; + + if (!friend_lpn_matches(frnd, sub->net_idx, dst)) { + continue; + } + + for (j = 0; j < ARRAY_SIZE(frnd->seg); j++) { + struct bt_mesh_friend_seg *seg = &frnd->seg[j]; + struct net_buf *buf; + + buf = (void *)sys_slist_peek_head(&seg->queue); + if (!buf) { + continue; + } + + if (BLE_MESH_ADV(buf)->addr != src) { + continue; + } + + if (FRIEND_ADV(buf)->seq_auth != *seq_auth) { + continue; + } + + BT_WARN("%s, Clearing incomplete segments for 0x%04x", __func__, src); + + while (!sys_slist_is_empty(&seg->queue)) { + net_buf_unref(net_buf_slist_get(&seg->queue)); + } + } + } +} + +#endif /* CONFIG_BLE_MESH_FRIEND */ diff --git a/components/bt/ble_mesh/mesh_core/friend.h b/components/bt/ble_mesh/mesh_core/friend.h new file mode 100644 index 0000000000..008a342c9b --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/friend.h @@ -0,0 +1,49 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _FRIEND_H_ +#define _FRIEND_H_ + +enum bt_mesh_friend_pdu_type { + BLE_MESH_FRIEND_PDU_SINGLE, + BLE_MESH_FRIEND_PDU_PARTIAL, + BLE_MESH_FRIEND_PDU_COMPLETE, +}; + +bool bt_mesh_friend_match(u16_t net_idx, u16_t addr); + +struct bt_mesh_friend *bt_mesh_friend_find(u16_t net_idx, u16_t lpn_addr, + bool valid, bool established); + +void bt_mesh_friend_enqueue_rx(struct bt_mesh_net_rx *rx, + enum bt_mesh_friend_pdu_type type, + u64_t *seq_auth, struct net_buf_simple *sbuf); +bool bt_mesh_friend_enqueue_tx(struct bt_mesh_net_tx *tx, + enum bt_mesh_friend_pdu_type type, + u64_t *seq_auth, struct net_buf_simple *sbuf); + +void bt_mesh_friend_clear_incomplete(struct bt_mesh_subnet *sub, u16_t src, + u16_t dst, u64_t *seq_auth); + +void bt_mesh_friend_sec_update(u16_t net_idx); + +void bt_mesh_friend_clear_net_idx(u16_t net_idx); + +int bt_mesh_friend_poll(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf); +int bt_mesh_friend_req(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf); +int bt_mesh_friend_clear(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf); +int bt_mesh_friend_clear_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_friend_sub_add(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_friend_sub_rem(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); + +int bt_mesh_friend_init(void); + +#endif /* _FRIEND_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/health_cli.c b/components/bt/ble_mesh/mesh_core/health_cli.c new file mode 100644 index 0000000000..c66e42afcd --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/health_cli.c @@ -0,0 +1,462 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "osi/allocator.h" +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_MODEL) + +#include "mesh_types.h" +#include "mesh_util.h" +#include "mesh_trace.h" +#include "health_cli.h" + +#include "foundation.h" +#include "mesh_common.h" +#include "btc_ble_mesh_health_model.h" + +s32_t health_msg_timeout; + +static bt_mesh_health_client_t *health_cli; + +static const bt_mesh_client_op_pair_t health_op_pair[] = { + { OP_HEALTH_FAULT_GET, OP_HEALTH_FAULT_STATUS }, + { OP_HEALTH_FAULT_CLEAR, OP_HEALTH_FAULT_STATUS }, + { OP_HEALTH_FAULT_TEST, OP_HEALTH_FAULT_STATUS }, + { OP_HEALTH_PERIOD_GET, OP_HEALTH_PERIOD_STATUS }, + { OP_HEALTH_PERIOD_SET, OP_HEALTH_PERIOD_STATUS }, + { OP_ATTENTION_GET, OP_ATTENTION_STATUS }, + { OP_ATTENTION_SET, OP_ATTENTION_STATUS }, +}; + +static void timeout_handler(struct k_work *work) +{ + health_internal_data_t *internal = NULL; + bt_mesh_health_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + + BT_WARN("Receive health status message timeout"); + + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (!node || !node->ctx.model) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + client = (bt_mesh_health_client_t *)node->ctx.model->user_data; + if (!client) { + BT_ERR("%s, Health Client user_data is NULL", __func__); + return; + } + + internal = (health_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Health Client internal_data is NULL", __func__); + return; + } + + bt_mesh_callback_health_status_to_btc(node->opcode, 0x03, node->ctx.model, + &node->ctx, NULL, 0); + + bt_mesh_client_free_node(&internal->queue, node); + + return; +} + +static void health_client_cancel(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + void *status, size_t len) +{ + health_internal_data_t *data = NULL; + bt_mesh_client_node_t *node = NULL; + struct net_buf_simple buf = {0}; + u8_t evt_type = 0xFF; + + if (!model || !ctx || !status || !len) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + data = (health_internal_data_t *)health_cli->internal_data; + if (!data) { + BT_ERR("%s, Health Client internal_data is NULL", __func__); + return; + } + + /* If it is a publish message, sent to the user directly. */ + buf.data = (u8_t *)status; + buf.len = (u16_t)len; + node = bt_mesh_is_model_message_publish(model, ctx, &buf, true); + if (!node) { + BT_DBG("Unexpected health status message 0x%x", ctx->recv_op); + } else { + switch (node->opcode) { + case OP_HEALTH_FAULT_GET: + case OP_HEALTH_PERIOD_GET: + case OP_ATTENTION_GET: + evt_type = 0x00; + break; + case OP_HEALTH_FAULT_CLEAR: + case OP_HEALTH_FAULT_TEST: + case OP_HEALTH_PERIOD_SET: + case OP_ATTENTION_SET: + evt_type = 0x01; + break; + default: + break; + } + + bt_mesh_callback_health_status_to_btc(node->opcode, evt_type, model, + ctx, (const u8_t *)status, len); + // Don't forget to release the node at the end. + bt_mesh_client_free_node(&data->queue, node); + } + + switch (ctx->recv_op) { + case OP_HEALTH_FAULT_STATUS: { + struct bt_mesh_health_fault_status *val; + val = (struct bt_mesh_health_fault_status *)status; + bt_mesh_free_buf(val->fault_array); + break; + } + default: + break; + } +} + +static void health_fault_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_fault_status status = {0}; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status.test_id = net_buf_simple_pull_u8(buf); + status.cid = net_buf_simple_pull_le16(buf); + status.fault_array = bt_mesh_alloc_buf(buf->len); + if (!status.fault_array) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + net_buf_simple_add_mem(status.fault_array, buf->data, buf->len); + + health_client_cancel(model, ctx, &status, sizeof(struct bt_mesh_health_fault_status)); +} + +static void health_current_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + bt_mesh_client_node_t *node = NULL; + u8_t test_id; + u16_t cid; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + /* Health current status is a publish message, sent to the user directly. */ + if (!(node = bt_mesh_is_model_message_publish(model, ctx, buf, true))) { + return; + } + + test_id = net_buf_simple_pull_u8(buf); + cid = net_buf_simple_pull_le16(buf); + + BT_DBG("Test ID 0x%02x Company ID 0x%04x Fault Count %u", + test_id, cid, buf->len); +} + +static void health_period_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t status = 0; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + + health_client_cancel(model, ctx, &status, sizeof(u8_t)); +} + +static void health_attention_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t status = 0; + + BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s", + ctx->net_idx, ctx->app_idx, ctx->addr, buf->len, + bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + + health_client_cancel(model, ctx, &status, sizeof(u8_t)); +} + +const struct bt_mesh_model_op bt_mesh_health_cli_op[] = { + { OP_HEALTH_FAULT_STATUS, 3, health_fault_status }, + { OP_HEALTH_CURRENT_STATUS, 3, health_current_status }, + { OP_HEALTH_PERIOD_STATUS, 1, health_period_status }, + { OP_ATTENTION_STATUS, 1, health_attention_status }, + BLE_MESH_MODEL_OP_END, +}; + +int bt_mesh_health_attention_get(struct bt_mesh_msg_ctx *ctx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 0 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_ATTENTION_GET); + + err = bt_mesh_client_send_msg(health_cli->model, OP_ATTENTION_GET, ctx, + &msg, timeout_handler, health_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_health_attention_set(struct bt_mesh_msg_ctx *ctx, + u8_t attention, bool need_ack) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + u32_t opcode; + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + if (need_ack) { + opcode = OP_ATTENTION_SET; + } else { + opcode = OP_ATTENTION_SET_UNREL; + } + bt_mesh_model_msg_init(&msg, opcode); + net_buf_simple_add_u8(&msg, attention); + + err = bt_mesh_client_send_msg(health_cli->model, opcode, ctx, &msg, + timeout_handler, health_msg_timeout, + need_ack, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_health_period_get(struct bt_mesh_msg_ctx *ctx) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 0 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_HEALTH_PERIOD_GET); + + err = bt_mesh_client_send_msg(health_cli->model, OP_HEALTH_PERIOD_GET, + ctx, &msg, timeout_handler, health_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_health_period_set(struct bt_mesh_msg_ctx *ctx, + u8_t divisor, bool need_ack) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + u32_t opcode; + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + if (need_ack) { + opcode = OP_HEALTH_PERIOD_SET; + } else { + opcode = OP_HEALTH_PERIOD_SET_UNREL; + } + bt_mesh_model_msg_init(&msg, opcode); + net_buf_simple_add_u8(&msg, divisor); + + err = bt_mesh_client_send_msg(health_cli->model, opcode, ctx, &msg, + timeout_handler, health_msg_timeout, + need_ack, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_health_fault_test(struct bt_mesh_msg_ctx *ctx, + u16_t cid, u8_t test_id, bool need_ack) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 3 + 4); + u32_t opcode; + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + if (need_ack) { + opcode = OP_HEALTH_FAULT_TEST; + } else { + opcode = OP_HEALTH_FAULT_TEST_UNREL; + } + bt_mesh_model_msg_init(&msg, opcode); + net_buf_simple_add_u8(&msg, test_id); + net_buf_simple_add_le16(&msg, cid); + + err = bt_mesh_client_send_msg(health_cli->model, opcode, ctx, &msg, + timeout_handler, health_msg_timeout, + need_ack, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_health_fault_clear(struct bt_mesh_msg_ctx *ctx, + u16_t cid, bool need_ack) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 2 + 4); + u32_t opcode; + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + if (need_ack) { + opcode = OP_HEALTH_FAULT_CLEAR; + } else { + opcode = OP_HEALTH_FAULT_CLEAR_UNREL; + } + bt_mesh_model_msg_init(&msg, opcode); + net_buf_simple_add_le16(&msg, cid); + + err = bt_mesh_client_send_msg(health_cli->model, opcode, ctx, &msg, + timeout_handler, health_msg_timeout, + need_ack, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +int bt_mesh_health_fault_get(struct bt_mesh_msg_ctx *ctx, u16_t cid) +{ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 2 + 4); + int err; + + if (!ctx || !ctx->addr) { + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_HEALTH_FAULT_GET); + net_buf_simple_add_le16(&msg, cid); + + err = bt_mesh_client_send_msg(health_cli->model, OP_HEALTH_FAULT_GET, ctx, + &msg, timeout_handler, health_msg_timeout, + true, NULL, NULL); + if (err) { + BT_ERR("%s, send failed (err %d)", __func__, err); + } + + return err; +} + +s32_t bt_mesh_health_cli_timeout_get(void) +{ + return health_msg_timeout; +} + +void bt_mesh_health_cli_timeout_set(s32_t timeout) +{ + health_msg_timeout = timeout; +} + +int bt_mesh_health_cli_set(struct bt_mesh_model *model) +{ + if (!model || !model->user_data) { + BT_ERR("%s, No Health Client context for given model", __func__); + return -EINVAL; + } + + health_cli = model->user_data; + + return 0; +} + +int bt_mesh_health_cli_init(struct bt_mesh_model *model, bool primary) +{ + health_internal_data_t *internal = NULL; + bt_mesh_health_client_t *client = NULL; + + BT_DBG("primary %u", primary); + + if (!model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_health_client_t *)model->user_data; + if (!client) { + BT_ERR("%s, No Health Client context provided", __func__); + return -EINVAL; + } + + /* TODO: call osi_free() when deinit function is invoked*/ + internal = osi_calloc(sizeof(health_internal_data_t)); + if (!internal) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + sys_slist_init(&internal->queue); + + client->model = model; + client->op_pair_size = ARRAY_SIZE(health_op_pair); + client->op_pair = health_op_pair; + client->internal_data = internal; + + /* Set the default health client pointer */ + if (!health_cli) { + health_cli = client; + } + + return 0; +} diff --git a/components/bt/ble_mesh/mesh_core/health_srv.c b/components/bt/ble_mesh/mesh_core/health_srv.c new file mode 100644 index 0000000000..96e16692dd --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/health_srv.c @@ -0,0 +1,529 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_MODEL) + +#include "mesh_types.h" +#include "mesh_util.h" +#include "mesh_trace.h" +#include "health_srv.h" + +#include "mesh.h" +#include "adv.h" +#include "net.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "mesh_common.h" + +#define HEALTH_TEST_STANDARD 0x00 + +/* Maximum message length is 384 in BLE Mesh. Here for health fault status, + * due to 1 octet opcode and 4 octets TransMIC, 379 octets can be used to + * store health fault status. + */ +#define HEALTH_FAULT_MAX_LEN 379 + +/* Health Server context of the primary element */ +struct bt_mesh_health_srv *health_srv; + +static void health_get_registered(struct bt_mesh_model *mod, + u16_t company_id, + struct net_buf_simple *msg) +{ + struct bt_mesh_health_srv *srv = mod->user_data; + u8_t *test_id; + + BT_DBG("Company ID 0x%04x", company_id); + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_STATUS); + + test_id = net_buf_simple_add(msg, 1); + net_buf_simple_add_le16(msg, company_id); + + if (srv->cb && srv->cb->fault_get_reg) { + u8_t fault_count = net_buf_simple_tailroom(msg) - 4; + int err; + + err = srv->cb->fault_get_reg(mod, company_id, test_id, + net_buf_simple_tail(msg), + &fault_count); + if (err) { + BT_ERR("%s, Failed to get faults (err %d)", __func__, err); + *test_id = HEALTH_TEST_STANDARD; + } else { + net_buf_simple_add(msg, fault_count); + } + } else { + BT_WARN("No callback for getting faults"); + *test_id = HEALTH_TEST_STANDARD; + } +} + +static size_t health_get_current(struct bt_mesh_model *mod, + struct net_buf_simple *msg) +{ + struct bt_mesh_health_srv *srv = mod->user_data; + const struct bt_mesh_comp *comp; + u8_t *test_id, *company_ptr; + u16_t company_id; + u8_t fault_count; + int err; + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return 0; + } + + bt_mesh_model_msg_init(msg, OP_HEALTH_CURRENT_STATUS); + + test_id = net_buf_simple_add(msg, 1); + company_ptr = net_buf_simple_add(msg, sizeof(company_id)); + comp = bt_mesh_comp_get(); + + if (srv->cb && srv->cb->fault_get_cur) { + fault_count = net_buf_simple_tailroom(msg); + err = srv->cb->fault_get_cur(mod, test_id, &company_id, + net_buf_simple_tail(msg), + &fault_count); + if (err) { + BT_ERR("%s, Failed to get faults (err %d)", __func__, err); + sys_put_le16(comp->cid, company_ptr); + *test_id = HEALTH_TEST_STANDARD; + fault_count = 0U; + } else { + sys_put_le16(company_id, company_ptr); + net_buf_simple_add(msg, fault_count); + } + } else { + BT_WARN("No callback for getting faults"); + sys_put_le16(comp->cid, company_ptr); + *test_id = HEALTH_TEST_STANDARD; + fault_count = 0U; + } + + return fault_count; +} + +static void health_fault_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct net_buf_simple *sdu = NULL; + u16_t company_id; + + company_id = net_buf_simple_pull_le16(buf); + + BT_DBG("company_id 0x%04x", company_id); + + sdu = bt_mesh_alloc_buf(MIN(BLE_MESH_TX_SDU_MAX, HEALTH_FAULT_MAX_LEN)); + if (!sdu) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + health_get_registered(model, company_id, sdu); + + if (bt_mesh_model_send(model, ctx, sdu, NULL, NULL)) { + BT_ERR("%s, Unable to send Health Current Status", __func__); + } + + bt_mesh_free_buf(sdu); + return; +} + +static void health_fault_clear_unrel(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_srv *srv = model->user_data; + u16_t company_id; + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + company_id = net_buf_simple_pull_le16(buf); + + BT_DBG("company_id 0x%04x", company_id); + + if (srv->cb && srv->cb->fault_clear) { + srv->cb->fault_clear(model, company_id); + } +} + +static void health_fault_clear(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_srv *srv = model->user_data; + struct net_buf_simple *sdu = NULL; + u16_t company_id; + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + company_id = net_buf_simple_pull_le16(buf); + + BT_DBG("company_id 0x%04x", company_id); + + if (srv->cb && srv->cb->fault_clear) { + srv->cb->fault_clear(model, company_id); + } + + sdu = bt_mesh_alloc_buf(MIN(BLE_MESH_TX_SDU_MAX, HEALTH_FAULT_MAX_LEN)); + if (!sdu) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + health_get_registered(model, company_id, sdu); + + if (bt_mesh_model_send(model, ctx, sdu, NULL, NULL)) { + BT_ERR("%s, Unable to send Health Current Status", __func__); + } + + bt_mesh_free_buf(sdu); + return; +} + +static void health_fault_test_unrel(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_srv *srv = model->user_data; + u16_t company_id; + u8_t test_id; + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + test_id = net_buf_simple_pull_u8(buf); + company_id = net_buf_simple_pull_le16(buf); + + BT_DBG("test 0x%02x company 0x%04x", test_id, company_id); + + if (srv->cb && srv->cb->fault_test) { + srv->cb->fault_test(model, test_id, company_id); + } +} + +static void health_fault_test(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_srv *srv = model->user_data; + struct net_buf_simple *sdu = NULL; + u16_t company_id; + u8_t test_id; + + BT_DBG("%s", __func__); + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + test_id = net_buf_simple_pull_u8(buf); + company_id = net_buf_simple_pull_le16(buf); + + BT_DBG("test 0x%02x company 0x%04x", test_id, company_id); + + if (srv->cb && srv->cb->fault_test) { + int err; + + err = srv->cb->fault_test(model, test_id, company_id); + if (err) { + BT_WARN("Running fault test failed with err %d", err); + return; + } + } + + sdu = bt_mesh_alloc_buf(MIN(BLE_MESH_TX_SDU_MAX, HEALTH_FAULT_MAX_LEN)); + if (!sdu) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + health_get_registered(model, company_id, sdu); + + if (bt_mesh_model_send(model, ctx, sdu, NULL, NULL)) { + BT_ERR("%s, Unable to send Health Current Status", __func__); + } + + bt_mesh_free_buf(sdu); + return; +} + +static void send_attention_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + struct bt_mesh_health_srv *srv = model->user_data; + u8_t time; + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + time = k_delayed_work_remaining_get(&srv->attn_timer) / 1000; + BT_DBG("%u second%s", time, (time == 1U) ? "" : "s"); + + bt_mesh_model_msg_init(&msg, OP_ATTENTION_STATUS); + + net_buf_simple_add_u8(&msg, time); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Health Attention Status", __func__); + } +} + +static void attention_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_DBG("%s", __func__); + + send_attention_status(model, ctx); +} + +static void attention_set_unrel(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t time; + + time = net_buf_simple_pull_u8(buf); + + BT_DBG("%u second%s", time, (time == 1U) ? "" : "s"); + + bt_mesh_attention(model, time); +} + +static void attention_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_DBG("%s", __func__); + + attention_set_unrel(model, ctx, buf); + + send_attention_status(model, ctx); +} + +static void send_health_period_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); + + bt_mesh_model_msg_init(&msg, OP_HEALTH_PERIOD_STATUS); + + net_buf_simple_add_u8(&msg, model->pub->period_div); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { + BT_ERR("%s, Unable to send Health Period Status", __func__); + } +} + +static void health_period_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_DBG("%s", __func__); + + send_health_period_status(model, ctx); +} + +static void health_period_set_unrel(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t period; + + period = net_buf_simple_pull_u8(buf); + if (period > 15) { + BT_WARN("%s, Prohibited period value %u", __func__, period); + return; + } + + BT_DBG("period %u", period); + + model->pub->period_div = period; +} + +static void health_period_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_DBG("%s", __func__); + + health_period_set_unrel(model, ctx, buf); + + send_health_period_status(model, ctx); +} + +const struct bt_mesh_model_op bt_mesh_health_srv_op[] = { + { OP_HEALTH_FAULT_GET, 2, health_fault_get }, + { OP_HEALTH_FAULT_CLEAR, 2, health_fault_clear }, + { OP_HEALTH_FAULT_CLEAR_UNREL, 2, health_fault_clear_unrel }, + { OP_HEALTH_FAULT_TEST, 3, health_fault_test }, + { OP_HEALTH_FAULT_TEST_UNREL, 3, health_fault_test_unrel }, + { OP_HEALTH_PERIOD_GET, 0, health_period_get }, + { OP_HEALTH_PERIOD_SET, 1, health_period_set }, + { OP_HEALTH_PERIOD_SET_UNREL, 1, health_period_set_unrel }, + { OP_ATTENTION_GET, 0, attention_get }, + { OP_ATTENTION_SET, 1, attention_set }, + { OP_ATTENTION_SET_UNREL, 1, attention_set_unrel }, + BLE_MESH_MODEL_OP_END, +}; + +static int health_pub_update(struct bt_mesh_model *mod) +{ + struct bt_mesh_model_pub *pub = mod->pub; + size_t count; + + BT_DBG("%s", __func__); + + count = health_get_current(mod, pub->msg); + if (count) { + pub->fast_period = 1U; + } else { + pub->fast_period = 0U; + } + + return 0; +} + +int bt_mesh_fault_update(struct bt_mesh_elem *elem) +{ + struct bt_mesh_model *mod; + + mod = bt_mesh_model_find(elem, BLE_MESH_MODEL_ID_HEALTH_SRV); + if (!mod) { + BT_ERR("%s, Health Server does not exist", __func__); + return -EINVAL; + } + + if (!mod->pub) { + BT_ERR("%s, Health Server has no publication support", __func__); + return -EIO; + } + + /* Let periodic publishing, if enabled, take care of sending the + * Health Current Status. + */ + if (bt_mesh_model_pub_period_get(mod)) { + return 0; + } + + health_pub_update(mod); + + return bt_mesh_model_publish(mod); +} + +static void attention_off(struct k_work *work) +{ + struct bt_mesh_health_srv *srv = CONTAINER_OF(work, + struct bt_mesh_health_srv, + attn_timer.work); + BT_DBG("%s", __func__); + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + if (srv->cb && srv->cb->attn_off) { + srv->cb->attn_off(srv->model); + } +} + +int bt_mesh_health_srv_init(struct bt_mesh_model *model, bool primary) +{ + struct bt_mesh_health_srv *srv = model->user_data; + + if (!srv) { + if (!primary) { + return 0; + } + + BT_ERR("%s, No Health Server context provided", __func__); + return -EINVAL; + } + + if (!model->pub) { + BT_ERR("%s, Health Server has no publication support", __func__); + return -EINVAL; + } + + model->pub->update = health_pub_update; + + k_delayed_work_init(&srv->attn_timer, attention_off); + + srv->model = model; + + if (primary) { + health_srv = srv; + } + + return 0; +} + +void bt_mesh_attention(struct bt_mesh_model *model, u8_t time) +{ + struct bt_mesh_health_srv *srv; + + if (!model) { + srv = health_srv; + if (!srv) { + BT_WARN("%s, No Health Server context provided", __func__); + return; + } + + model = srv->model; + } else { + srv = model->user_data; + if (!srv) { + BT_WARN("%s, No Health Server context provided", __func__); + return; + } + } + + if (time) { + if (srv->cb && srv->cb->attn_on) { + srv->cb->attn_on(model); + } + + k_delayed_work_submit(&srv->attn_timer, time * 1000U); + } else { + k_delayed_work_cancel(&srv->attn_timer); + + if (srv->cb && srv->cb->attn_off) { + srv->cb->attn_off(model); + } + } +} diff --git a/components/bt/ble_mesh/mesh_core/include/cfg_cli.h b/components/bt/ble_mesh/mesh_core/include/cfg_cli.h new file mode 100644 index 0000000000..b001d84b8a --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/cfg_cli.h @@ -0,0 +1,297 @@ +/** @file + * @brief Bluetooth Mesh Configuration Client Model APIs. + */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BLE_MESH_CFG_CLI_H_ +#define _BLE_MESH_CFG_CLI_H_ + +#include "mesh_access.h" +#include "mesh_kernel.h" +#include "model_common.h" + +/** + * @brief Bluetooth Mesh + * @defgroup bt_mesh_cfg_cli Bluetooth Mesh Configuration Client Model + * @ingroup bt_mesh + * @{ + */ + +/* Config client model common structure */ +typedef bt_mesh_client_common_t bt_mesh_config_client_t; +typedef bt_mesh_internal_data_t config_internal_data_t; + +extern const struct bt_mesh_model_op bt_mesh_cfg_cli_op[]; + +#define BLE_MESH_MODEL_CFG_CLI(cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_CFG_CLI, \ + bt_mesh_cfg_cli_op, NULL, cli_data) + +int bt_mesh_cfg_comp_data_get(struct bt_mesh_msg_ctx *ctx, u8_t page); + +int bt_mesh_cfg_beacon_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_cfg_beacon_set(struct bt_mesh_msg_ctx *ctx, u8_t val); + +int bt_mesh_cfg_ttl_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_cfg_ttl_set(struct bt_mesh_msg_ctx *ctx, u8_t val); + +int bt_mesh_cfg_friend_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_cfg_friend_set(struct bt_mesh_msg_ctx *ctx, u8_t val); + +int bt_mesh_cfg_gatt_proxy_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_cfg_gatt_proxy_set(struct bt_mesh_msg_ctx *ctx, u8_t val); + +int bt_mesh_cfg_relay_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_cfg_relay_set(struct bt_mesh_msg_ctx *ctx, u8_t new_relay, u8_t new_transmit); + +int bt_mesh_cfg_net_key_add(struct bt_mesh_msg_ctx *ctx, u16_t key_net_idx, + const u8_t net_key[16]); + +int bt_mesh_cfg_app_key_add(struct bt_mesh_msg_ctx *ctx, u16_t key_net_idx, + u16_t key_app_idx, const u8_t app_key[16]); + +int bt_mesh_cfg_mod_app_bind(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_app_idx, u16_t mod_id, u16_t cid); + +struct bt_mesh_cfg_mod_pub { + u16_t addr; + u16_t app_idx; + bool cred_flag; + u8_t ttl; + u8_t period; + u8_t transmit; +}; + +int bt_mesh_cfg_mod_pub_get(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_mod_pub_set(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid, + struct bt_mesh_cfg_mod_pub *pub); + +int bt_mesh_cfg_mod_sub_add(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t sub_addr, u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_mod_sub_del(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t sub_addr, u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_mod_sub_overwrite(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t sub_addr, u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_mod_sub_va_add(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + const u8_t label[16], u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_mod_sub_va_del(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + const u8_t label[16], u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_mod_sub_va_overwrite(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + const u8_t label[16], u16_t mod_id, u16_t cid); + +struct bt_mesh_cfg_hb_sub { + u16_t src; + u16_t dst; + u8_t period; +}; + +int bt_mesh_cfg_hb_sub_set(struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_cfg_hb_sub *sub); + +int bt_mesh_cfg_hb_sub_get(struct bt_mesh_msg_ctx *ctx); + +struct bt_mesh_cfg_hb_pub { + u16_t dst; + u8_t count; + u8_t period; + u8_t ttl; + u16_t feat; + u16_t net_idx; +}; + +int bt_mesh_cfg_hb_pub_set(struct bt_mesh_msg_ctx *ctx, + const struct bt_mesh_cfg_hb_pub *pub); + +int bt_mesh_cfg_hb_pub_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_cfg_node_reset(struct bt_mesh_msg_ctx *ctx); + +s32_t bt_mesh_cfg_cli_timeout_get(void); +void bt_mesh_cfg_cli_timeout_set(s32_t timeout); + +/* Configuration Client Status Message Context */ + +struct bt_mesh_cfg_comp_data_status { + u8_t page; + struct net_buf_simple *comp_data; +}; + +struct bt_mesh_cfg_relay_status { + u8_t relay; + u8_t retransmit; +}; + +struct bt_mesh_cfg_netkey_status { + u8_t status; + u16_t net_idx; +}; + +struct bt_mesh_cfg_appkey_status { + u8_t status; + u16_t net_idx; + u16_t app_idx; +}; + +struct bt_mesh_cfg_mod_app_status { + u8_t status; + u16_t elem_addr; + u16_t app_idx; + u16_t cid; + u16_t mod_id; +}; + +struct bt_mesh_cfg_mod_pub_status { + u8_t status; + u16_t elem_addr; + u16_t addr; + u16_t app_idx; + bool cred_flag; + u8_t ttl; + u8_t period; + u8_t transmit; + u16_t cid; + u16_t mod_id; +}; + +struct bt_mesh_cfg_mod_sub_status { + u8_t status; + u16_t elem_addr; + u16_t sub_addr; + u16_t cid; + u16_t mod_id; +}; + +struct bt_mesh_cfg_hb_sub_status { + u8_t status; + u16_t src; + u16_t dst; + u8_t period; + u8_t count; + u8_t min; + u8_t max; +}; + +struct bt_mesh_cfg_hb_pub_status { + u8_t status; + u16_t dst; + u8_t count; + u8_t period; + u8_t ttl; + u16_t feat; + u16_t net_idx; +}; + +struct bt_mesh_cfg_mod_sub_list { + u8_t status; + u16_t elem_addr; + u16_t cid; + u16_t mod_id; + struct net_buf_simple *addr; +}; + +struct bt_mesh_cfg_net_key_list { + struct net_buf_simple *net_idx; +}; + +struct bt_mesh_cfg_app_key_list { + u8_t status; + u16_t net_idx; + struct net_buf_simple *app_idx; +}; + +struct bt_mesh_cfg_node_id_status { + u8_t status; + u16_t net_idx; + u8_t identity; +}; + +struct bt_mesh_cfg_mod_app_list { + u8_t status; + u16_t elem_addr; + u16_t cid; + u16_t mod_id; + struct net_buf_simple *app_idx; +}; + +struct bt_mesh_cfg_key_refresh_status { + u8_t status; + u16_t net_idx; + u8_t phase; +}; + +struct bt_mesh_cfg_lpn_pollto_status { + u16_t lpn_addr; + s32_t timeout; +}; + +int bt_mesh_cfg_mod_pub_va_set(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid, const u8_t label[16], + struct bt_mesh_cfg_mod_pub *pub); + +int bt_mesh_cfg_mod_sub_del_all(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_mod_sub_get(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, u16_t mod_id); + +int bt_mesh_cfg_mod_sub_get_vnd(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_net_key_update(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, + const u8_t net_key[16]); + +int bt_mesh_cfg_net_key_delete(struct bt_mesh_msg_ctx *ctx, u16_t net_idx); + +int bt_mesh_cfg_net_key_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_cfg_app_key_update(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, + u16_t app_idx, const u8_t app_key[16]); + +int bt_mesh_cfg_app_key_delete(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, u16_t app_idx); + +int bt_mesh_cfg_app_key_get(struct bt_mesh_msg_ctx *ctx, u16_t net_idx); + +int bt_mesh_cfg_node_identity_get(struct bt_mesh_msg_ctx *ctx, u16_t net_idx); + +int bt_mesh_cfg_node_identity_set(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, u8_t identity); + +int bt_mesh_cfg_mod_app_unbind(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t app_idx, u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_mod_app_get(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, u16_t mod_id); + +int bt_mesh_cfg_mod_app_get_vnd(struct bt_mesh_msg_ctx *ctx, u16_t elem_addr, + u16_t mod_id, u16_t cid); + +int bt_mesh_cfg_kr_phase_get(struct bt_mesh_msg_ctx *ctx, u16_t net_idx); + +int bt_mesh_cfg_kr_phase_set(struct bt_mesh_msg_ctx *ctx, u16_t net_idx, u8_t transition); + +int bt_mesh_cfg_lpn_timeout_get(struct bt_mesh_msg_ctx *ctx, u16_t lpn_addr); + +int bt_mesh_cfg_net_transmit_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_cfg_net_transmit_set(struct bt_mesh_msg_ctx *ctx, u8_t transmit); + +/** + * @} + */ + +#endif /* __BLE_MESH_CFG_CLI_H */ diff --git a/components/bt/ble_mesh/mesh_core/include/cfg_srv.h b/components/bt/ble_mesh/mesh_core/include/cfg_srv.h new file mode 100644 index 0000000000..d5f77e7b01 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/cfg_srv.h @@ -0,0 +1,72 @@ +/** @file + * @brief Bluetooth Mesh Configuration Server Model APIs. + */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BLE_MESH_CFG_SRV_H_ +#define _BLE_MESH_CFG_SRV_H_ + +#include "mesh_access.h" +#include "mesh_kernel.h" + +/** + * @brief Bluetooth Mesh + * @defgroup bt_mesh_cfg_srv Bluetooth Mesh Configuration Server Model + * @ingroup bt_mesh + * @{ + */ + +/** Mesh Configuration Server Model Context */ +struct bt_mesh_cfg_srv { + struct bt_mesh_model *model; + + u8_t net_transmit; /* Network Transmit state */ + u8_t relay; /* Relay Mode state */ + u8_t relay_retransmit; /* Relay Retransmit state */ + u8_t beacon; /* Secure Network Beacon state */ + u8_t gatt_proxy; /* GATT Proxy state */ + u8_t frnd; /* Friend state */ + u8_t default_ttl; /* Default TTL */ + + /* Heartbeat Publication */ + struct bt_mesh_hb_pub { + struct k_delayed_work timer; + + u16_t dst; + u16_t count; + u8_t period; + u8_t ttl; + u16_t feat; + u16_t net_idx; + } hb_pub; + + /* Heartbeat Subscription */ + struct bt_mesh_hb_sub { + s64_t expiry; + + u16_t src; + u16_t dst; + u16_t count; + u8_t min_hops; + u8_t max_hops; + + /* Optional subscription tracking function */ + void (*func)(u8_t hops, u16_t feat); + } hb_sub; +}; + +extern const struct bt_mesh_model_op bt_mesh_cfg_srv_op[]; + +#define BLE_MESH_MODEL_CFG_SRV(srv_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_CFG_SRV, \ + bt_mesh_cfg_srv_op, NULL, srv_data) + +/** + * @} + */ + +#endif /* __BLE_MESH_CFG_SRV_H */ diff --git a/components/bt/ble_mesh/mesh_core/include/health_cli.h b/components/bt/ble_mesh/mesh_core/include/health_cli.h new file mode 100644 index 0000000000..9d7230ebac --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/health_cli.h @@ -0,0 +1,78 @@ +/** @file + * @brief Bluetooth Mesh Health Client Model APIs. + */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BLE_MESH_HEALTH_CLI_H_ +#define _BLE_MESH_HEALTH_CLI_H_ + +#include "mesh_access.h" +#include "mesh_kernel.h" +#include "model_common.h" + +/** + * @brief Bluetooth Mesh + * @defgroup bt_mesh_health_cli Bluetooth Mesh Health Client Model + * @ingroup bt_mesh + * @{ + */ + +/* Health client model common structure */ +typedef bt_mesh_client_common_t bt_mesh_health_client_t; +typedef bt_mesh_internal_data_t health_internal_data_t; + +typedef bt_mesh_internal_data_t health_client_internal_data_t; + +extern const struct bt_mesh_model_op bt_mesh_health_cli_op[]; + +#define BLE_MESH_MODEL_HEALTH_CLI(cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_HEALTH_CLI, \ + bt_mesh_health_cli_op, NULL, cli_data) + +int bt_mesh_health_cli_set(struct bt_mesh_model *model); + +int bt_mesh_health_fault_get(struct bt_mesh_msg_ctx *ctx, u16_t cid); + +int bt_mesh_health_fault_clear(struct bt_mesh_msg_ctx *ctx, u16_t cid, + bool need_ack); + +int bt_mesh_health_fault_test(struct bt_mesh_msg_ctx *ctx, + u16_t cid, u8_t test_id, bool need_ack); + +int bt_mesh_health_period_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_health_period_set(struct bt_mesh_msg_ctx *ctx, + u8_t divisor, bool need_ack); + +int bt_mesh_health_attention_get(struct bt_mesh_msg_ctx *ctx); + +int bt_mesh_health_attention_set(struct bt_mesh_msg_ctx *ctx, + u8_t attention, bool need_ack); + +s32_t bt_mesh_health_cli_timeout_get(void); +void bt_mesh_health_cli_timeout_set(s32_t timeout); + +/* Health Client Status Message Context */ + +struct bt_mesh_health_current_status { + u8_t test_id; + u16_t cid; + struct net_buf_simple *fault_array; +}; + +struct bt_mesh_health_fault_status { + u8_t test_id; + u16_t cid; + struct net_buf_simple *fault_array; +}; + +/** + * @} + */ + +#endif /* __BLE_MESH_HEALTH_CLI_H */ diff --git a/components/bt/ble_mesh/mesh_core/include/health_srv.h b/components/bt/ble_mesh/mesh_core/include/health_srv.h new file mode 100644 index 0000000000..4e9b840776 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/health_srv.h @@ -0,0 +1,93 @@ +/** @file + * @brief Bluetooth Mesh Health Server Model APIs. + */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BLE_MESH_HEALTH_SRV_H_ +#define _BLE_MESH_HEALTH_SRV_H_ + +#include "mesh_access.h" +#include "mesh_kernel.h" + +/** + * @brief Bluetooth Mesh Health Server Model + * @defgroup bt_mesh_health_srv Bluetooth Mesh Health Server Model + * @ingroup bt_mesh + * @{ + */ + +struct bt_mesh_health_srv_cb { + /* Fetch current faults */ + int (*fault_get_cur)(struct bt_mesh_model *model, u8_t *test_id, + u16_t *company_id, u8_t *faults, + u8_t *fault_count); + + /* Fetch registered faults */ + int (*fault_get_reg)(struct bt_mesh_model *model, u16_t company_id, + u8_t *test_id, u8_t *faults, + u8_t *fault_count); + + /* Clear registered faults */ + int (*fault_clear)(struct bt_mesh_model *model, u16_t company_id); + + /* Run a specific test */ + int (*fault_test)(struct bt_mesh_model *model, u8_t test_id, + u16_t company_id); + + /* Attention on */ + void (*attn_on)(struct bt_mesh_model *model); + + /* Attention off */ + void (*attn_off)(struct bt_mesh_model *model); +}; + +/** @def BLE_MESH_HEALTH_PUB_DEFINE + * + * A helper to define a health publication context + * + * @param _name Name given to the publication context variable. + * @param _max_faults Maximum number of faults the element can have. + */ +#define BLE_MESH_HEALTH_PUB_DEFINE(_name, _max_faults) \ + BLE_MESH_MODEL_PUB_DEFINE(_name, NULL, (1 + 3 + (_max_faults))) + +/** Mesh Health Server Model Context */ +struct bt_mesh_health_srv { + struct bt_mesh_model *model; + + /* Optional callback struct */ + const struct bt_mesh_health_srv_cb *cb; + + /* Attention Timer state */ + struct k_delayed_work attn_timer; +}; + +extern const struct bt_mesh_model_op bt_mesh_health_srv_op[]; + +/** @def BLE_MESH_MODEL_HEALTH_SRV + * + * Define a new health server model. Note that this API needs to be + * repeated for each element which the application wants to have a + * health server model on. Each instance also needs a unique + * bt_mesh_health_srv and bt_mesh_model_pub context. + * + * @param srv Pointer to a unique struct bt_mesh_health_srv. + * @param pub Pointer to a unique struct bt_mesh_model_pub. + * + * @return New mesh model instance. + */ +#define BLE_MESH_MODEL_HEALTH_SRV(srv, pub) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_HEALTH_SRV, \ + bt_mesh_health_srv_op, pub, srv) + +int bt_mesh_fault_update(struct bt_mesh_elem *elem); + +/** + * @} + */ + +#endif /* __BLE_MESH_HEALTH_SRV_H */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_access.h b/components/bt/ble_mesh/mesh_core/include/mesh_access.h new file mode 100644 index 0000000000..bdabab15ca --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_access.h @@ -0,0 +1,444 @@ +/** @file + * @brief Bluetooth Mesh Access Layer APIs. + */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BLE_MESH_ACCESS_H_ +#define _BLE_MESH_ACCESS_H_ + +#include +#include "mesh_types.h" +#include "mesh_util.h" +#include "mesh_buf.h" +#include "sdkconfig.h" + +/** + * @brief Bluetooth Mesh Access Layer + * @defgroup bt_mesh_access Bluetooth Mesh Access Layer + * @ingroup bt_mesh + * @{ + */ + +#define BLE_MESH_ADDR_UNASSIGNED 0x0000 +#define BLE_MESH_ADDR_ALL_NODES 0xffff +#define BLE_MESH_ADDR_PROXIES 0xfffc +#define BLE_MESH_ADDR_FRIENDS 0xfffd +#define BLE_MESH_ADDR_RELAYS 0xfffe + +#define BLE_MESH_KEY_UNUSED 0xffff +#define BLE_MESH_KEY_DEV 0xfffe + +/** Helper to define a mesh element within an array. + * + * In case the element has no SIG or Vendor models the helper + * macro BLE_MESH_MODEL_NONE can be given instead. + * + * @param _loc Location Descriptor. + * @param _mods Array of models. + * @param _vnd_mods Array of vendor models. + */ +#define BLE_MESH_ELEM(_loc, _mods, _vnd_mods) \ +{ \ + .loc = (_loc), \ + .model_count = ARRAY_SIZE(_mods), \ + .models = (_mods), \ + .vnd_model_count = ARRAY_SIZE(_vnd_mods), \ + .vnd_models = (_vnd_mods), \ +} + +/** Abstraction that describes a Mesh Element */ +struct bt_mesh_elem { + /* Unicast Address. Set at runtime during provisioning. */ + u16_t addr; + + /* Location Descriptor (GATT Bluetooth Namespace Descriptors) */ + const u16_t loc; + + const u8_t model_count; + const u8_t vnd_model_count; + + struct bt_mesh_model *const models; + struct bt_mesh_model *const vnd_models; +}; + +/* Foundation Models */ +#define BLE_MESH_MODEL_ID_CFG_SRV 0x0000 +#define BLE_MESH_MODEL_ID_CFG_CLI 0x0001 +#define BLE_MESH_MODEL_ID_HEALTH_SRV 0x0002 +#define BLE_MESH_MODEL_ID_HEALTH_CLI 0x0003 + +/* Models from the Mesh Model Specification */ +#define BLE_MESH_MODEL_ID_GEN_ONOFF_SRV 0x1000 +#define BLE_MESH_MODEL_ID_GEN_ONOFF_CLI 0x1001 +#define BLE_MESH_MODEL_ID_GEN_LEVEL_SRV 0x1002 +#define BLE_MESH_MODEL_ID_GEN_LEVEL_CLI 0x1003 +#define BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV 0x1004 +#define BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI 0x1005 +#define BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV 0x1006 +#define BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV 0x1007 +#define BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI 0x1008 +#define BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV 0x1009 +#define BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV 0x100a +#define BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI 0x100b +#define BLE_MESH_MODEL_ID_GEN_BATTERY_SRV 0x100c +#define BLE_MESH_MODEL_ID_GEN_BATTERY_CLI 0x100d +#define BLE_MESH_MODEL_ID_GEN_LOCATION_SRV 0x100e +#define BLE_MESH_MODEL_ID_GEN_LOCATION_SETUPSRV 0x100f +#define BLE_MESH_MODEL_ID_GEN_LOCATION_CLI 0x1010 +#define BLE_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV 0x1011 +#define BLE_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV 0x1012 +#define BLE_MESH_MODEL_ID_GEN_USER_PROP_SRV 0x1013 +#define BLE_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV 0x1014 +#define BLE_MESH_MODEL_ID_GEN_PROP_CLI 0x1015 +#define BLE_MESH_MODEL_ID_SENSOR_SRV 0x1100 +#define BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV 0x1101 +#define BLE_MESH_MODEL_ID_SENSOR_CLI 0x1102 +#define BLE_MESH_MODEL_ID_TIME_SRV 0x1200 +#define BLE_MESH_MODEL_ID_TIME_SETUP_SRV 0x1201 +#define BLE_MESH_MODEL_ID_TIME_CLI 0x1202 +#define BLE_MESH_MODEL_ID_SCENE_SRV 0x1203 +#define BLE_MESH_MODEL_ID_SCENE_SETUP_SRV 0x1204 +#define BLE_MESH_MODEL_ID_SCENE_CLI 0x1205 +#define BLE_MESH_MODEL_ID_SCHEDULER_SRV 0x1206 +#define BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV 0x1207 +#define BLE_MESH_MODEL_ID_SCHEDULER_CLI 0x1208 +#define BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV 0x1300 +#define BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV 0x1301 +#define BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI 0x1302 +#define BLE_MESH_MODEL_ID_LIGHT_CTL_SRV 0x1303 +#define BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV 0x1304 +#define BLE_MESH_MODEL_ID_LIGHT_CTL_CLI 0x1305 +#define BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV 0x1306 +#define BLE_MESH_MODEL_ID_LIGHT_HSL_SRV 0x1307 +#define BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV 0x1308 +#define BLE_MESH_MODEL_ID_LIGHT_HSL_CLI 0x1309 +#define BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV 0x130a +#define BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV 0x130b +#define BLE_MESH_MODEL_ID_LIGHT_XYL_SRV 0x130c +#define BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV 0x130d +#define BLE_MESH_MODEL_ID_LIGHT_XYL_CLI 0x130e +#define BLE_MESH_MODEL_ID_LIGHT_LC_SRV 0x130f +#define BLE_MESH_MODEL_ID_LIGHT_LC_SETUPSRV 0x1310 +#define BLE_MESH_MODEL_ID_LIGHT_LC_CLI 0x1311 + +/** Message sending context. */ +struct bt_mesh_msg_ctx { + /** NetKey Index of the subnet to send the message on. */ + u16_t net_idx; + + /** AppKey Index to encrypt the message with. */ + u16_t app_idx; + + /** Remote address. */ + u16_t addr; + + /** Destination address of a received message. Not used for sending. */ + u16_t recv_dst; + + /** Received TTL value. Not used for sending. */ + u8_t recv_ttl: 7; + + /** Force sending reliably by using segment acknowledgement */ + u8_t send_rel: 1; + + /** TTL, or BLE_MESH_TTL_DEFAULT for default TTL. */ + u8_t send_ttl; + + /** Change by Espressif, opcode of a received message. + * Not used for sending message. */ + u32_t recv_op; + + /** Change by Espressif, model corresponds to the message */ + struct bt_mesh_model *model; + + /** Change by Espressif, if the message is sent by a server + * model. Not used for receiving message. */ + bool srv_send; +}; + +struct bt_mesh_model_op { + /* OpCode encoded using the BLE_MESH_MODEL_OP_* macros */ + const u32_t opcode; + + /* Minimum required message length */ + const size_t min_len; + + /* Message handler for the opcode */ + void (*const func)(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf); +}; + +#define BLE_MESH_MODEL_OP_1(b0) (b0) +#define BLE_MESH_MODEL_OP_2(b0, b1) (((b0) << 8) | (b1)) +#define BLE_MESH_MODEL_OP_3(b0, cid) ((((b0) << 16) | 0xc00000) | (cid)) + +#define BLE_MESH_MODEL_OP_END { 0, 0, NULL } +#define BLE_MESH_MODEL_NO_OPS ((struct bt_mesh_model_op []) \ + { BLE_MESH_MODEL_OP_END }) + +/** Helper to define an empty model array */ +#define BLE_MESH_MODEL_NONE ((struct bt_mesh_model []){}) + +#define BLE_MESH_MODEL(_id, _op, _pub, _user_data) \ +{ \ + .id = (_id), \ + .op = _op, \ + .keys = { [0 ... (CONFIG_BLE_MESH_MODEL_KEY_COUNT - 1)] = \ + BLE_MESH_KEY_UNUSED }, \ + .pub = _pub, \ + .groups = { [0 ... (CONFIG_BLE_MESH_MODEL_GROUP_COUNT - 1)] = \ + BLE_MESH_ADDR_UNASSIGNED }, \ + .user_data = _user_data, \ +} + +#define BLE_MESH_MODEL_VND(_company, _id, _op, _pub, _user_data) \ +{ \ + .vnd.company = (_company), \ + .vnd.id = (_id), \ + .op = _op, \ + .pub = _pub, \ + .keys = { [0 ... (CONFIG_BLE_MESH_MODEL_KEY_COUNT - 1)] = \ + BLE_MESH_KEY_UNUSED }, \ + .groups = { [0 ... (CONFIG_BLE_MESH_MODEL_GROUP_COUNT - 1)] = \ + BLE_MESH_ADDR_UNASSIGNED }, \ + .user_data = _user_data, \ +} + +/** @def BLE_MESH_TRANSMIT + * + * @brief Encode transmission count & interval steps. + * + * @param count Number of retransmissions (first transmission is excluded). + * @param int_ms Interval steps in milliseconds. Must be greater than 0 + * and a multiple of 10. + * + * @return Mesh transmit value that can be used e.g. for the default + * values of the configuration model data. + */ +#define BLE_MESH_TRANSMIT(count, int_ms) ((count) | (((int_ms / 10) - 1) << 3)) + +/** @def BLE_MESH_TRANSMIT_COUNT + * + * @brief Decode transmit count from a transmit value. + * + * @param transmit Encoded transmit count & interval value. + * + * @return Transmission count (actual transmissions is N + 1). + */ +#define BLE_MESH_TRANSMIT_COUNT(transmit) (((transmit) & (u8_t)BIT_MASK(3))) + +/** @def BLE_MESH_TRANSMIT_INT + * + * @brief Decode transmit interval from a transmit value. + * + * @param transmit Encoded transmit count & interval value. + * + * @return Transmission interval in milliseconds. + */ +#define BLE_MESH_TRANSMIT_INT(transmit) ((((transmit) >> 3) + 1) * 10) + +/** @def BLE_MESH_PUB_TRANSMIT + * + * @brief Encode Publish Retransmit count & interval steps. + * + * @param count Number of retransmissions (first transmission is excluded). + * @param int_ms Interval steps in milliseconds. Must be greater than 0 + * and a multiple of 50. + * + * @return Mesh transmit value that can be used e.g. for the default + * values of the configuration model data. + */ +#define BLE_MESH_PUB_TRANSMIT(count, int_ms) BLE_MESH_TRANSMIT(count, (int_ms) / 5) + +/** @def BLE_MESH_PUB_TRANSMIT_COUNT + * + * @brief Decode Pubhlish Retransmit count from a given value. + * + * @param transmit Encoded Publish Retransmit count & interval value. + * + * @return Retransmission count (actual transmissions is N + 1). + */ +#define BLE_MESH_PUB_TRANSMIT_COUNT(transmit) BLE_MESH_TRANSMIT_COUNT(transmit) + +/** @def BLE_MESH_PUB_TRANSMIT_INT + * + * @brief Decode Publish Retransmit interval from a given value. + * + * @param transmit Encoded Publish Retransmit count & interval value. + * + * @return Transmission interval in milliseconds. + */ +#define BLE_MESH_PUB_TRANSMIT_INT(transmit) ((((transmit) >> 3) + 1) * 50) + +/** Model publication context. */ +struct bt_mesh_model_pub { + /** The model the context belongs to. Initialized by the stack. */ + struct bt_mesh_model *mod; + + u16_t addr; /**< Publish Address. */ + u16_t key; /**< Publish AppKey Index. */ + + u8_t ttl; /**< Publish Time to Live. */ + u8_t retransmit; /**< Retransmit Count & Interval Steps. */ + u8_t period; /**< Publish Period. */ + u16_t period_div: 4, /**< Divisor for the Period. */ + cred: 1, /**< Friendship Credentials Flag. */ + fast_period: 1, /**< Use FastPeriodDivisor */ + count: 3; /**< Retransmissions left. */ + + u32_t period_start; /**< Start of the current period. */ + + /** @brief Publication buffer, containing the publication message. + * + * This will get correctly created when the publication context + * has been defined using the BLE_MESH_MODEL_PUB_DEFINE macro. + * + * BLE_MESH_MODEL_PUB_DEFINE(name, update, size); + */ + struct net_buf_simple *msg; + + /** @brief Callback for updating the publication buffer. + * + * When set to NULL, the model is assumed not to support + * periodic publishing. When set to non-NULL the callback + * will be called periodically and is expected to update + * @ref bt_mesh_model_pub.msg with a valid publication + * message. + * + * @param mod The Model the Publication Context belogs to. + * + * @return Zero on success or (negative) error code otherwise. + */ + int (*update)(struct bt_mesh_model *mod); + + /* Change by Espressif, role of the device going to publish messages */ + u8_t dev_role; + + /** Publish Period Timer. Only for stack-internal use. */ + struct k_delayed_work timer; +}; + +/** @def BLE_MESH_MODEL_PUB_DEFINE + * + * Define a model publication context. + * + * @param _name Variable name given to the context. + * @param _update Optional message update callback (may be NULL). + * @param _msg_len Length of the publication message. + */ +#define BLE_MESH_MODEL_PUB_DEFINE(_name, _update, _msg_len) \ + NET_BUF_SIMPLE_DEFINE_STATIC(bt_mesh_pub_msg_##_name, _msg_len); \ + static struct bt_mesh_model_pub _name = { \ + .update = _update, \ + .msg = &bt_mesh_pub_msg_##_name, \ + } + +/** Abstraction that describes a Mesh Model instance */ +struct bt_mesh_model { + union { + const u16_t id; + struct { + u16_t company; + u16_t id; + } vnd; + }; + + /* Internal information, mainly for persistent storage */ + u8_t elem_idx; /* Belongs to Nth element */ + u8_t model_idx; /* Is the Nth model in the element */ + u16_t flags; /* Information about what has changed */ + + /* The Element this Model belongs to */ + struct bt_mesh_elem *elem; + + /* Model Publication */ + struct bt_mesh_model_pub *const pub; + + /* AppKey List */ + u16_t keys[CONFIG_BLE_MESH_MODEL_KEY_COUNT]; + + /* Subscription List (group or virtual addresses) */ + u16_t groups[CONFIG_BLE_MESH_MODEL_GROUP_COUNT]; + + const struct bt_mesh_model_op *const op; + + /* Model-specific user data */ + void *user_data; +}; + +struct bt_mesh_send_cb { + void (*start)(u16_t duration, int err, void *cb_data); + void (*end)(int err, void *cb_data); +}; + +void bt_mesh_model_msg_init(struct net_buf_simple *msg, u32_t opcode); + +/** Special TTL value to request using configured default TTL */ +#define BLE_MESH_TTL_DEFAULT 0xff + +/** Maximum allowed TTL value */ +#define BLE_MESH_TTL_MAX 0x7f + +/** + * @brief Send an Access Layer message. + * + * @param model Mesh (client) Model that the message belongs to. + * @param ctx Message context, includes keys, TTL, etc. + * @param msg Access Layer payload (the actual message to be sent). + * @param cb Optional "message sent" callback. + * @param cb_data User data to be passed to the callback. + * + * @return 0 on success, or (negative) error code on failure. + */ +int bt_mesh_model_send(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *msg, + const struct bt_mesh_send_cb *cb, + void *cb_data); + +/** + * @brief Send a model publication message. + * + * Before calling this function, the user needs to ensure that the model + * publication message (@ref bt_mesh_model_pub.msg) contains a valid + * message to be sent. Note that this API is only to be used for + * non-period publishing. For periodic publishing the app only needs + * to make sure that @ref bt_mesh_model_pub.msg contains a valid message + * whenever the @ref bt_mesh_model_pub.update callback is called. + * + * @param model Mesh (client) Model that's publishing the message. + * + * @return 0 on success, or (negative) error code on failure. + */ +int bt_mesh_model_publish(struct bt_mesh_model *model); + +/** + * @brief Get the element that a model belongs to. + * + * @param mod Mesh model. + * + * @return Pointer to the element that the given model belongs to. + */ +struct bt_mesh_elem *bt_mesh_model_elem(struct bt_mesh_model *mod); + +/** Node Composition */ +struct bt_mesh_comp { + u16_t cid; + u16_t pid; + u16_t vid; + + size_t elem_count; + struct bt_mesh_elem *elem; +}; + +/** + * @} + */ + +#endif /* __BLE_MESH_ACCESS_H */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_aes_encrypt.h b/components/bt/ble_mesh/mesh_core/include/mesh_aes_encrypt.h new file mode 100644 index 0000000000..afaa6b27dd --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_aes_encrypt.h @@ -0,0 +1,171 @@ +/* aes.h - TinyCrypt interface to an AES-128 implementation */ + +/* + * Copyright (C) 2017 by Intel Corporation, All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief -- Interface to an AES-128 implementation. + * + * Overview: AES-128 is a NIST approved block cipher specified in + * FIPS 197. Block ciphers are deterministic algorithms that + * perform a transformation specified by a symmetric key in fixed- + * length data sets, also called blocks. + * + * Security: AES-128 provides approximately 128 bits of security. + * + * Usage: 1) call tc_aes128_set_encrypt/decrypt_key to set the key. + * + * 2) call tc_aes_encrypt/decrypt to process the data. + */ + +#ifndef _BLE_MESH_AES_ENCRYPT_H_ +#define _BLE_MESH_AES_ENCRYPT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define Nb (4) /* number of columns (32-bit words) comprising the state */ +#define Nk (4) /* number of 32-bit words comprising the key */ +#define Nr (10) /* number of rounds */ +#define TC_AES_BLOCK_SIZE (Nb*Nk) +#define TC_AES_KEY_SIZE (Nb*Nk) + +#define TC_CRYPTO_SUCCESS 1 +#define TC_CRYPTO_FAIL 0 + +#define TC_ZERO_BYTE 0x00 + +/* padding for last message block */ +#define TC_CMAC_PADDING 0x80 + +typedef struct tc_aes_key_sched_struct { + unsigned int words[Nb * (Nr + 1)]; +} *TCAesKeySched_t; + +/* struct tc_cmac_struct represents the state of a CMAC computation */ +typedef struct tc_cmac_struct { + /* initialization vector */ + uint8_t iv[TC_AES_BLOCK_SIZE]; + /* used if message length is a multiple of block_size bytes */ + uint8_t K1[TC_AES_BLOCK_SIZE]; + /* used if message length isn't a multiple block_size bytes */ + uint8_t K2[TC_AES_BLOCK_SIZE]; + /* where to put bytes that didn't fill a block */ + uint8_t leftover[TC_AES_BLOCK_SIZE]; + /* identifies the encryption key */ + unsigned int keyid; + /* next available leftover location */ + unsigned int leftover_offset; + /* AES key schedule */ + TCAesKeySched_t sched; + /* calls to tc_cmac_update left before re-key */ + uint64_t countdown; +} *TCCmacState_t; + +/** + * @brief Set AES-128 encryption key + * Uses key k to initialize s + * @return returns TC_CRYPTO_SUCCESS (1) + * returns TC_CRYPTO_FAIL (0) if: s == NULL or k == NULL + * @note This implementation skips the additional steps required for keys + * larger than 128 bits, and must not be used for AES-192 or + * AES-256 key schedule -- see FIPS 197 for details + * @param s IN/OUT -- initialized struct tc_aes_key_sched_struct + * @param k IN -- points to the AES key + */ +int tc_aes128_set_encrypt_key(TCAesKeySched_t s, const uint8_t *k); + +/** + * @brief AES-128 Encryption procedure + * Encrypts contents of in buffer into out buffer under key; + * schedule s + * @note Assumes s was initialized by aes_set_encrypt_key; + * out and in point to 16 byte buffers + * @return returns TC_CRYPTO_SUCCESS (1) + * returns TC_CRYPTO_FAIL (0) if: out == NULL or in == NULL or s == NULL + * @param out IN/OUT -- buffer to receive ciphertext block + * @param in IN -- a plaintext block to encrypt + * @param s IN -- initialized AES key schedule + */ +int tc_aes_encrypt(uint8_t *out, const uint8_t *in, + const TCAesKeySched_t s); + +/** + * @brief Set the AES-128 decryption key + * Uses key k to initialize s + * @return returns TC_CRYPTO_SUCCESS (1) + * returns TC_CRYPTO_FAIL (0) if: s == NULL or k == NULL + * @note This is the implementation of the straightforward inverse cipher + * using the cipher documented in FIPS-197 figure 12, not the + * equivalent inverse cipher presented in Figure 15 + * @warning This routine skips the additional steps required for keys larger + * than 128, and must not be used for AES-192 or AES-256 key + * schedule -- see FIPS 197 for details + * @param s IN/OUT -- initialized struct tc_aes_key_sched_struct + * @param k IN -- points to the AES key + */ +int tc_aes128_set_decrypt_key(TCAesKeySched_t s, const uint8_t *k); + +/** + * @brief AES-128 Encryption procedure + * Decrypts in buffer into out buffer under key schedule s + * @return returns TC_CRYPTO_SUCCESS (1) + * returns TC_CRYPTO_FAIL (0) if: out is NULL or in is NULL or s is NULL + * @note Assumes s was initialized by aes_set_encrypt_key + * out and in point to 16 byte buffers + * @param out IN/OUT -- buffer to receive ciphertext block + * @param in IN -- a plaintext block to encrypt + * @param s IN -- initialized AES key schedule + */ +int tc_aes_decrypt(uint8_t *out, const uint8_t *in, + const TCAesKeySched_t s); + +int tc_cmac_setup(TCCmacState_t s, const uint8_t *key, TCAesKeySched_t sched); + +void gf_double(uint8_t *out, uint8_t *in); + +int tc_cmac_init(TCCmacState_t s); + +int tc_cmac_update(TCCmacState_t s, const uint8_t *data, size_t data_length); + +int tc_cmac_final(uint8_t *tag, TCCmacState_t s); + +int tc_cmac_erase(TCCmacState_t s); + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_AES_ENCRYPT_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_atomic.h b/components/bt/ble_mesh/mesh_core/include/mesh_atomic.h new file mode 100644 index 0000000000..5c8bf17b89 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_atomic.h @@ -0,0 +1,305 @@ +/* atomic operations */ + +/* + * Copyright (c) 1997-2015, Wind River Systems, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BLE_MESH_ATOMIC_H_ +#define _BLE_MESH_ATOMIC_H_ + +#include "mesh_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef bt_mesh_atomic_t bt_mesh_atomic_val_t; + +/** + * @defgroup atomic_apis Atomic Services APIs + * @ingroup kernel_apis + * @{ + */ + +/** + * + * @brief Atomic increment. + * + * This routine performs an atomic increment by 1 on @a target. + * + * @param target Address of atomic variable. + * + * @return Previous value of @a target. + */ +#ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN +static inline bt_mesh_atomic_val_t bt_mesh_atomic_inc(bt_mesh_atomic_t *target) +{ + return bt_mesh_atomic_add(target, 1); +} +#else +extern bt_mesh_atomic_val_t bt_mesh_atomic_inc(bt_mesh_atomic_t *target); +#endif + +/** + * + * @brief Atomic decrement. + * + * This routine performs an atomic decrement by 1 on @a target. + * + * @param target Address of atomic variable. + * + * @return Previous value of @a target. + */ +#ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN +static inline bt_mesh_atomic_val_t bt_mesh_atomic_dec(bt_mesh_atomic_t *target) +{ + return bt_mesh_atomic_sub(target, 1); +} +#else +extern bt_mesh_atomic_val_t bt_mesh_atomic_dec(bt_mesh_atomic_t *target); +#endif + +/** + * + * @brief Atomic get. + * + * This routine performs an atomic read on @a target. + * + * @param target Address of atomic variable. + * + * @return Value of @a target. + */ +#ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN +static inline bt_mesh_atomic_val_t bt_mesh_atomic_get(const bt_mesh_atomic_t *target) +{ + return __atomic_load_n(target, __ATOMIC_SEQ_CST); +} +#else +extern bt_mesh_atomic_val_t bt_mesh_atomic_get(const bt_mesh_atomic_t *target); +#endif + +/** + * + * @brief Atomic get-and-set. + * + * This routine atomically sets @a target to @a value and returns + * the previous value of @a target. + * + * @param target Address of atomic variable. + * @param value Value to write to @a target. + * + * @return Previous value of @a target. + */ +#ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN +static inline bt_mesh_atomic_val_t bt_mesh_atomic_set(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t value) +{ + /* This builtin, as described by Intel, is not a traditional + * test-and-set operation, but rather an atomic exchange operation. It + * writes value into *ptr, and returns the previous contents of *ptr. + */ + return __atomic_exchange_n(target, value, __ATOMIC_SEQ_CST); +} +#else +extern bt_mesh_atomic_val_t bt_mesh_atomic_set(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t value); +#endif + +/** + * + * @brief Atomic bitwise inclusive OR. + * + * This routine atomically sets @a target to the bitwise inclusive OR of + * @a target and @a value. + * + * @param target Address of atomic variable. + * @param value Value to OR. + * + * @return Previous value of @a target. + */ +#ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN +static inline bt_mesh_atomic_val_t bt_mesh_atomic_or(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t value) +{ + return __atomic_fetch_or(target, value, __ATOMIC_SEQ_CST); +} +#else +extern bt_mesh_atomic_val_t bt_mesh_atomic_or(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t value); +#endif + +/** + * + * @brief Atomic bitwise AND. + * + * This routine atomically sets @a target to the bitwise AND of @a target + * and @a value. + * + * @param target Address of atomic variable. + * @param value Value to AND. + * + * @return Previous value of @a target. + */ +#ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN +static inline bt_mesh_atomic_val_t bt_mesh_atomic_and(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t value) +{ + return __atomic_fetch_and(target, value, __ATOMIC_SEQ_CST); +} +#else +extern bt_mesh_atomic_val_t bt_mesh_atomic_and(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t value); +#endif + +/** + * @cond INTERNAL_HIDDEN + */ + +#define BLE_MESH_ATOMIC_BITS (sizeof(bt_mesh_atomic_val_t) * 8) +#define BLE_MESH_ATOMIC_MASK(bit) (1 << ((bit) & (BLE_MESH_ATOMIC_BITS - 1))) +#define BLE_MESH_ATOMIC_ELEM(addr, bit) ((addr) + ((bit) / BLE_MESH_ATOMIC_BITS)) + +/** + * INTERNAL_HIDDEN @endcond + */ + +/** + * @brief Define an array of atomic variables. + * + * This macro defines an array of atomic variables containing at least + * @a num_bits bits. + * + * @note + * If used from file scope, the bits of the array are initialized to zero; + * if used from within a function, the bits are left uninitialized. + * + * @param name Name of array of atomic variables. + * @param num_bits Number of bits needed. + */ +#define BLE_MESH_ATOMIC_DEFINE(name, num_bits) \ + bt_mesh_atomic_t name[1 + ((num_bits) - 1) / BLE_MESH_ATOMIC_BITS] + +/** + * @brief Atomically test a bit. + * + * This routine tests whether bit number @a bit of @a target is set or not. + * The target may be a single atomic variable or an array of them. + * + * @param target Address of atomic variable or array. + * @param bit Bit number (starting from 0). + * + * @return 1 if the bit was set, 0 if it wasn't. + */ +static inline int bt_mesh_atomic_test_bit(const bt_mesh_atomic_t *target, int bit) +{ + bt_mesh_atomic_val_t val = bt_mesh_atomic_get(BLE_MESH_ATOMIC_ELEM(target, bit)); + + return (1 & (val >> (bit & (BLE_MESH_ATOMIC_BITS - 1)))); +} + +/** + * @brief Atomically test and clear a bit. + * + * Atomically clear bit number @a bit of @a target and return its old value. + * The target may be a single atomic variable or an array of them. + * + * @param target Address of atomic variable or array. + * @param bit Bit number (starting from 0). + * + * @return 1 if the bit was set, 0 if it wasn't. + */ +static inline int bt_mesh_atomic_test_and_clear_bit(bt_mesh_atomic_t *target, int bit) +{ + bt_mesh_atomic_val_t mask = BLE_MESH_ATOMIC_MASK(bit); + bt_mesh_atomic_val_t old; + + old = bt_mesh_atomic_and(BLE_MESH_ATOMIC_ELEM(target, bit), ~mask); + + return (old & mask) != 0; +} + +/** + * @brief Atomically set a bit. + * + * Atomically set bit number @a bit of @a target and return its old value. + * The target may be a single atomic variable or an array of them. + * + * @param target Address of atomic variable or array. + * @param bit Bit number (starting from 0). + * + * @return 1 if the bit was set, 0 if it wasn't. + */ +static inline int bt_mesh_atomic_test_and_set_bit(bt_mesh_atomic_t *target, int bit) +{ + bt_mesh_atomic_val_t mask = BLE_MESH_ATOMIC_MASK(bit); + bt_mesh_atomic_val_t old; + + old = bt_mesh_atomic_or(BLE_MESH_ATOMIC_ELEM(target, bit), mask); + + return (old & mask) != 0; +} + +/** + * @brief Atomically clear a bit. + * + * Atomically clear bit number @a bit of @a target. + * The target may be a single atomic variable or an array of them. + * + * @param target Address of atomic variable or array. + * @param bit Bit number (starting from 0). + * + * @return N/A + */ +static inline void bt_mesh_atomic_clear_bit(bt_mesh_atomic_t *target, int bit) +{ + bt_mesh_atomic_val_t mask = BLE_MESH_ATOMIC_MASK(bit); + + (void)bt_mesh_atomic_and(BLE_MESH_ATOMIC_ELEM(target, bit), ~mask); +} + +/** + * @brief Atomically set a bit. + * + * Atomically set bit number @a bit of @a target. + * The target may be a single atomic variable or an array of them. + * + * @param target Address of atomic variable or array. + * @param bit Bit number (starting from 0). + * + * @return N/A + */ +static inline void bt_mesh_atomic_set_bit(bt_mesh_atomic_t *target, int bit) +{ + bt_mesh_atomic_val_t mask = BLE_MESH_ATOMIC_MASK(bit); + + (void)bt_mesh_atomic_or(BLE_MESH_ATOMIC_ELEM(target, bit), mask); +} + +/** + * @brief Atomically set a bit to a given value. + * + * Atomically set bit number @a bit of @a target to value @a val. + * The target may be a single atomic variable or an array of them. + * + * @param target Address of atomic variable or array. + * @param bit Bit number (starting from 0). + * @param val true for 1, false for 0. + * + * @return N/A + */ +static inline void bt_mesh_atomic_set_bit_to(bt_mesh_atomic_t *target, int bit, bool val) +{ + bt_mesh_atomic_val_t mask = BLE_MESH_ATOMIC_MASK(bit); + + if (val) { + (void)bt_mesh_atomic_or(BLE_MESH_ATOMIC_ELEM(target, bit), mask); + } else { + (void)bt_mesh_atomic_and(BLE_MESH_ATOMIC_ELEM(target, bit), ~mask); + } +} + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_ATOMIC_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_bearer_adapt.h b/components/bt/ble_mesh/mesh_core/include/mesh_bearer_adapt.h new file mode 100644 index 0000000000..1382031878 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_bearer_adapt.h @@ -0,0 +1,733 @@ +/* + * Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015-2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BLE_MESH_BEARER_ADRPT_H_ +#define _BLE_MESH_BEARER_ADRPT_H_ + +#include +#include "mesh_types.h" +#include "mesh_util.h" +#include "mesh_buf.h" +#include "mesh_uuid.h" + +/* BLE Mesh Max Connection Count */ +#define BLE_MESH_MAX_CONN CONFIG_BT_ACL_CONNECTIONS + +/* BD ADDR types */ +#define BLE_MESH_ADDR_PUBLIC 0x00 +#define BLE_MESH_ADDR_RANDOM 0x01 +#define BLE_MESH_ADDR_PUBLIC_ID 0x02 +#define BLE_MESH_ADDR_RANDOM_ID 0x03 + +/* BD ADDR length */ +#define BLE_MESH_ADDR_LEN 0x06 + +/* Advertising types */ +#define BLE_MESH_ADV_IND 0x00 +#define BLE_MESH_ADV_DIRECT_IND 0x01 +#define BLE_MESH_ADV_SCAN_IND 0x02 +#define BLE_MESH_ADV_NONCONN_IND 0x03 +#define BLE_MESH_ADV_DIRECT_IND_LOW_DUTY 0x04 + +/* advertising channel map */ +#define BLE_MESH_ADV_CHNL_37 BIT(0) +#define BLE_MESH_ADV_CHNL_38 BIT(1) +#define BLE_MESH_ADV_CHNL_39 BIT(2) + +/* Advertising filter policy */ +#define BLE_MESH_AP_SCAN_CONN_ALL 0x00 +#define BLE_MESH_AP_SCAN_WL_CONN_ALL 0x01 +#define BLE_MESH_AP_SCAN_ALL_CONN_WL 0x02 +#define BLE_MESH_AP_SCAN_CONN_WL 0x03 + +/* Scan types */ +#define BLE_MESH_SCAN_PASSIVE 0x00 +#define BLE_MESH_SCAN_ACTIVE 0x01 + +/* Scan operation */ +#define BLE_MESH_SCAN_DISABLE 0x00 +#define BLE_MESH_SCAN_ENABLE 0x01 + +/* Scan duplicate operation */ +#define BLE_MESH_SCAN_FILTER_DUP_DISABLE 0x00 +#define BLE_MESH_SCAN_FILTER_DUP_ENABLE 0x01 + +/* Scan filter policy */ +#define BLE_MESH_SP_ADV_ALL 0x00 +#define BLE_MESH_SP_ADV_WL 0x01 +#define BLE_MESH_SP_ADV_ALL_RPA_DIR_ADV 0x02 +#define BLE_MESH_SP_ADV_WL_RPA_DIR_ADV 0x03 + +/* Error codes for Error response PDU */ +#define BLE_MESH_ATT_ERR_INVALID_HANDLE 0x01 +#define BLE_MESH_ATT_ERR_READ_NOT_PERMITTED 0x02 +#define BLE_MESH_ATT_ERR_WRITE_NOT_PERMITTED 0x03 +#define BLE_MESH_ATT_ERR_INVALID_PDU 0x04 +#define BLE_MESH_ATT_ERR_AUTHENTICATION 0x05 +#define BLE_MESH_ATT_ERR_NOT_SUPPORTED 0x06 +#define BLE_MESH_ATT_ERR_INVALID_OFFSET 0x07 +#define BLE_MESH_ATT_ERR_AUTHORIZATION 0x08 +#define BLE_MESH_ATT_ERR_PREPARE_QUEUE_FULL 0x09 +#define BLE_MESH_ATT_ERR_ATTRIBUTE_NOT_FOUND 0x0a +#define BLE_MESH_ATT_ERR_ATTRIBUTE_NOT_LONG 0x0b +#define BLE_MESH_ATT_ERR_ENCRYPTION_KEY_SIZE 0x0c +#define BLE_MESH_ATT_ERR_INVALID_ATTRIBUTE_LEN 0x0d +#define BLE_MESH_ATT_ERR_UNLIKELY 0x0e +#define BLE_MESH_ATT_ERR_INSUFFICIENT_ENCRYPTION 0x0f +#define BLE_MESH_ATT_ERR_UNSUPPORTED_GROUP_TYPE 0x10 +#define BLE_MESH_ATT_ERR_INSUFFICIENT_RESOURCES 0x11 + +/* Common Profile Error Codes (from CSS) */ +#define BLE_MESH_ATT_ERR_WRITE_REQ_REJECTED 0xfc +#define BLE_MESH_ATT_ERR_CCC_IMPROPER_CONF 0xfd +#define BLE_MESH_ATT_ERR_PROCEDURE_IN_PROGRESS 0xfe +#define BLE_MESH_ATT_ERR_OUT_OF_RANGE 0xff + +/* EIR/AD data type definitions */ +#define BLE_MESH_DATA_FLAGS 0x01 /* AD flags */ +#define BLE_MESH_DATA_UUID16_SOME 0x02 /* 16-bit UUID, more available */ +#define BLE_MESH_DATA_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ +#define BLE_MESH_DATA_UUID32_SOME 0x04 /* 32-bit UUID, more available */ +#define BLE_MESH_DATA_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ +#define BLE_MESH_DATA_UUID128_SOME 0x06 /* 128-bit UUID, more available */ +#define BLE_MESH_DATA_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ +#define BLE_MESH_DATA_NAME_SHORTENED 0x08 /* Shortened name */ +#define BLE_MESH_DATA_NAME_COMPLETE 0x09 /* Complete name */ +#define BLE_MESH_DATA_TX_POWER 0x0a /* Tx Power */ +#define BLE_MESH_DATA_SOLICIT16 0x14 /* Solicit UUIDs, 16-bit */ +#define BLE_MESH_DATA_SOLICIT128 0x15 /* Solicit UUIDs, 128-bit */ +#define BLE_MESH_DATA_SVC_DATA16 0x16 /* Service data, 16-bit UUID */ +#define BLE_MESH_DATA_GAP_APPEARANCE 0x19 /* GAP appearance */ +#define BLE_MESH_DATA_SOLICIT32 0x1f /* Solicit UUIDs, 32-bit */ +#define BLE_MESH_DATA_SVC_DATA32 0x20 /* Service data, 32-bit UUID */ +#define BLE_MESH_DATA_SVC_DATA128 0x21 /* Service data, 128-bit UUID */ +#define BLE_MESH_DATA_URI 0x24 /* URI */ +#define BLE_MESH_DATA_MESH_PROV 0x29 /* Mesh Provisioning PDU */ +#define BLE_MESH_DATA_MESH_MESSAGE 0x2a /* Mesh Networking PDU */ +#define BLE_MESH_DATA_MESH_BEACON 0x2b /* Mesh Beacon */ + +#define BLE_MESH_DATA_MANUFACTURER_DATA 0xff /* Manufacturer Specific Data */ + +#define BLE_MESH_AD_LIMITED 0x01 /* Limited Discoverable */ +#define BLE_MESH_AD_GENERAL 0x02 /* General Discoverable */ +#define BLE_MESH_AD_NO_BREDR 0x04 /* BR/EDR not supported */ + +/* Client Characteristic Configuration Values */ + +/** @def BLE_MESH_GATT_CCC_NOTIFY + * @brief Client Characteristic Configuration Notification. + * + * If set, changes to Characteristic Value shall be notified. + */ +#define BLE_MESH_GATT_CCC_NOTIFY 0x0001 + +/** @def BLE_MESH_GATT_CCC_INDICATE + * @brief Client Characteristic Configuration Indication. + * + * If set, changes to Characteristic Value shall be indicated. + */ +#define BLE_MESH_GATT_CCC_INDICATE 0x0002 + +/** @def BLE_MESH_GATT_ERR + * @brief Construct error return value for attribute read and write callbacks. + * + * @param _att_err ATT error code + * + * @return Appropriate error code for the attribute callbacks. + * + */ +#define BLE_MESH_GATT_ERR(_att_err) (-(_att_err)) + +enum { + BLE_MESH_GATT_ITER_STOP = 0, + BLE_MESH_GATT_ITER_CONTINUE, +}; + +/* GATT attribute permission bit field values */ +enum { + /** No operations supported, e.g. for notify-only */ + BLE_MESH_GATT_PERM_NONE = 0, + + /** Attribute read permission. */ + BLE_MESH_GATT_PERM_READ = BIT(0), + + /** Attribute write permission. */ + BLE_MESH_GATT_PERM_WRITE = BIT(1), + + /** Attribute read permission with encryption. + * + * If set, requires encryption for read access. + */ + BLE_MESH_GATT_PERM_READ_ENCRYPT = BIT(2), + + /** Attribute write permission with encryption. + * + * If set, requires encryption for write access. + */ + BLE_MESH_GATT_PERM_WRITE_ENCRYPT = BIT(3), + + /** Attribute read permission with authentication. + * + * If set, requires encryption using authenticated link-key for read + * access. + */ + BLE_MESH_GATT_PERM_READ_AUTHEN = BIT(4), + + /** Attribute write permission with authentication. + * + * If set, requires encryption using authenticated link-key for write + * access. + */ + BLE_MESH_GATT_PERM_WRITE_AUTHEN = BIT(5), + + /** Attribute prepare write permission. + * + * If set, allows prepare writes with use of BT_GATT_WRITE_FLAG_PREPARE + * passed to write callback. + */ + BLE_MESH_GATT_PERM_PREPARE_WRITE = BIT(6), +}; + +/** Advertising options */ +enum { + /** Convenience value when no options are specified. */ + BLE_MESH_ADV_OPT_NONE = 0, + + /** Advertise as connectable. Type of advertising is determined by + * providing SCAN_RSP data and/or enabling local privacy support. + */ + BLE_MESH_ADV_OPT_CONNECTABLE = BIT(0), + + /** Don't try to resume connectable advertising after a connection. + * This option is only meaningful when used together with + * BLE_MESH_ADV_OPT_CONNECTABLE. If set the advertising will be stopped + * when bt_le_adv_stop() is called or when an incoming (slave) + * connection happens. If this option is not set the stack will + * take care of keeping advertising enabled even as connections + * occur. + */ + BLE_MESH_ADV_OPT_ONE_TIME = BIT(1), +}; + +/* Defined GAP timers */ +#define BLE_MESH_GAP_SCAN_FAST_INTERVAL 0x0060 /* 60 ms */ +#define BLE_MESH_GAP_SCAN_FAST_WINDOW 0x0030 /* 30 ms */ +#define BLE_MESH_GAP_SCAN_SLOW_INTERVAL_1 0x0800 /* 1.28 s */ +#define BLE_MESH_GAP_SCAN_SLOW_WINDOW_1 0x0012 /* 11.25 ms */ +#define BLE_MESH_GAP_SCAN_SLOW_INTERVAL_2 0x1000 /* 2.56 s */ +#define BLE_MESH_GAP_SCAN_SLOW_WINDOW_2 0x0012 /* 11.25 ms */ +#define BLE_MESH_GAP_ADV_FAST_INT_MIN_0 0x0020 /* 20 ms */ +#define BLE_MESH_GAP_ADV_FAST_INT_MAX_0 0x0020 /* 20 ms */ +#define BLE_MESH_GAP_ADV_FAST_INT_MIN_1 0x0030 /* 30 ms */ +#define BLE_MESH_GAP_ADV_FAST_INT_MAX_1 0x0060 /* 60 ms */ +#define BLE_MESH_GAP_ADV_FAST_INT_MIN_2 0x00a0 /* 100 ms */ +#define BLE_MESH_GAP_ADV_FAST_INT_MAX_2 0x00f0 /* 150 ms */ +#define BLE_MESH_GAP_ADV_SLOW_INT_MIN 0x0320 /* 500 ms */ +#define BLE_MESH_GAP_ADV_SLOW_INT_MAX 0x0320 /* 500 ms */ +#define BLE_MESH_GAP_INIT_CONN_INT_MIN 0x0018 /* 30 ms */ +#define BLE_MESH_GAP_INIT_CONN_INT_MAX 0x0028 /* 50 ms */ + +/* Characteristic Properties Bit field values */ + +/** @def BLE_MESH_GATT_CHRC_BROADCAST + * @brief Characteristic broadcast property. + * + * If set, permits broadcasts of the Characteristic Value using Server + * Characteristic Configuration Descriptor. + */ +#define BLE_MESH_GATT_CHRC_BROADCAST 0x01 + +/** @def BLE_MESH_GATT_CHRC_READ + * @brief Characteristic read property. + * + * If set, permits reads of the Characteristic Value. + */ +#define BLE_MESH_GATT_CHRC_READ 0x02 + +/** @def BLE_MESH_GATT_CHRC_WRITE_WITHOUT_RESP + * @brief Characteristic write without response property. + * + * If set, permits write of the Characteristic Value without response. + */ +#define BLE_MESH_GATT_CHRC_WRITE_WITHOUT_RESP 0x04 + +/** @def BLE_MESH_GATT_CHRC_WRITE + * @brief Characteristic write with response property. + * + * If set, permits write of the Characteristic Value with response. + */ +#define BLE_MESH_GATT_CHRC_WRITE 0x08 + +/** @def BLE_MESH_GATT_CHRC_NOTIFY + * @brief Characteristic notify property. + * + * If set, permits notifications of a Characteristic Value without + * acknowledgment. + */ +#define BLE_MESH_GATT_CHRC_NOTIFY 0x10 + +/** @def BLE_MESH_GATT_CHRC_INDICATE + * @brief Characteristic indicate property. + * + * If set, permits indications of a Characteristic Value with acknowledgment. + */ +#define BLE_MESH_GATT_CHRC_INDICATE 0x20 + +/** @def BLE_MESH_GATT_CHRC_AUTH + * @brief Characteristic Authenticated Signed Writes property. + * + * If set, permits signed writes to the Characteristic Value. + */ +#define BLE_MESH_GATT_CHRC_AUTH 0x40 + +/** @def BLE_MESH_GATT_CHRC_EXT_PROP + * @brief Characteristic Extended Properties property. + * + * If set, additional characteristic properties are defined in the + * Characteristic Extended Properties Descriptor. + */ +#define BLE_MESH_GATT_CHRC_EXT_PROP 0x80 + +/** @brief Characteristic Attribute Value. */ +struct bt_mesh_gatt_char { + /** Characteristic UUID. */ + const struct bt_mesh_uuid *uuid; + /** Characteristic properties. */ + u8_t properties; +}; + +/** @brief GATT Service structure */ +struct bt_mesh_gatt_service { + /** Service Attributes */ + struct bt_mesh_gatt_attr *attrs; + /** Service Attribute count */ + u16_t attr_count; + sys_snode_t node; +}; + +struct bt_mesh_ecb_param { + u8_t key[16]; + u8_t clear_text[16]; + u8_t cipher_text[16]; +} __packed; + +typedef struct { + u8_t type; + u8_t val[6]; +} bt_mesh_addr_t; + +/** Description of different data types that can be encoded into + * advertising data. Used to form arrays that are passed to the + * bt_le_adv_start() function. + */ +struct bt_mesh_adv_data { + u8_t type; + u8_t data_len; + const u8_t *data; +}; + +/** @brief Helper to declare elements of bt_data arrays + * + * This macro is mainly for creating an array of struct + * bt_mesh_adv_data elements which is then passed to + * bt_le_adv_start(). + * + * @param _type Type of advertising data field + * @param _data Pointer to the data field payload + * @param _data_len Number of bytes behind the _data pointer + */ +#define BLE_MESH_ADV_DATA(_type, _data, _data_len) \ + { \ + .type = (_type), \ + .data_len = (_data_len), \ + .data = (const u8_t *)(_data), \ + } + +/** @brief Helper to declare elements of bt_data arrays + * + * This macro is mainly for creating an array of struct bt_mesh_adv_data + * elements which is then passed to bt_le_adv_start(). + * + * @param _type Type of advertising data field + * @param _bytes Variable number of single-byte parameters + */ +#define BLE_MESH_ADV_DATA_BYTES(_type, _bytes...) \ + BLE_MESH_ADV_DATA(_type, ((u8_t []) { _bytes }), \ + sizeof((u8_t []) { _bytes })) + +/* BLE Mesh Advertising Parameters */ +struct bt_mesh_adv_param { + /** Bit-field of advertising options */ + u8_t options; + + /** Minimum Advertising Interval (N * 0.625) */ + u16_t interval_min; + + /** Maximum Advertising Interval (N * 0.625) */ + u16_t interval_max; +}; + +/* BLE Mesh scan parameters */ +struct bt_mesh_scan_param { + /** Scan type (BLE_MESH_SCAN_ACTIVE or BLE_MESH_SCAN_PASSIVE) */ + u8_t type; + + /** Duplicate filtering (BLE_MESH_SCAN_FILTER_DUP_ENABLE or + * BLE_MESH_SCAN_FILTER_DUP_DISABLE) + */ + u8_t filter_dup; + + /** Scan interval (N * 0.625 ms) */ + u16_t interval; + + /** Scan window (N * 0.625 ms) */ + u16_t window; +}; + +struct bt_mesh_conn { + u16_t handle; + bt_mesh_atomic_t ref; +}; + +/** @typedef bt_mesh_scan_cb_t + * @brief Callback type for reporting LE scan results. + * + * A function of this type is given to the bt_le_scan_start() function + * and will be called for any discovered LE device. + * + * @param addr Advertiser LE address and type. + * @param rssi Strength of advertiser signal. + * @param adv_type Type of advertising response from advertiser. + * @param data Buffer containing advertiser data. + */ +typedef void bt_mesh_scan_cb_t(const bt_mesh_addr_t *addr, s8_t rssi, + u8_t adv_type, struct net_buf_simple *buf); + +/* @typedef bt_mesh_dh_key_cb_t + * @brief Callback type for DH Key calculation. + * + * Used to notify of the calculated DH Key. + * + * @param key Public key. + * @param idx Provisioning link index, only used by Provisioner. + * + * @return The DH Key, or NULL in case of failure. + */ +typedef void (*bt_mesh_dh_key_cb_t)(const u8_t key[32], const u8_t idx); + +/** @typedef bt_mesh_gatt_attr_func_t + * @brief Attribute iterator callback. + * + * @param attr Attribute found. + * @param user_data Data given. + * + * @return BLE_MESH_GATT_ITER_CONTINUE if should continue to the next attribute + * or BLE_MESH_GATT_ITER_STOP to stop. + */ +typedef u8_t (*bt_mesh_gatt_attr_func_t)(const struct bt_mesh_gatt_attr *attr, + void *user_data); + +/** @brief Connection callback structure. + * + * This structure is used for tracking the state of a connection. + * It is registered with the help of the bt_mesh_gatts_conn_cb_register() API. + * It's permissible to register multiple instances of this @ref bt_conn_cb + * type, in case different modules of an application are interested in + * tracking the connection state. If a callback is not of interest for + * an instance, it may be set to NULL and will as a consequence not be + * used for that instance. + */ +struct bt_mesh_conn_cb { + /** @brief A new connection has been established. + * + * This callback notifies the application of a new connection. + * In case the err parameter is non-zero it means that the + * connection establishment failed. + * + * @param conn New connection object. + * @param err HCI error. Zero for success, non-zero otherwise. + */ + void (*connected)(struct bt_mesh_conn *conn, u8_t err); + + /** @brief A connection has been disconnected. + * + * This callback notifies the application that a connection + * has been disconnected. + * + * @param conn Connection object. + * @param reason HCI reason for the disconnection. + */ + void (*disconnected)(struct bt_mesh_conn *conn, u8_t reason); +}; + +struct bt_mesh_prov_conn_cb { + void (*connected)(const u8_t addr[6], struct bt_mesh_conn *conn, int id); + + void (*disconnected)(struct bt_mesh_conn *conn, u8_t reason); + + ssize_t (*prov_write_descr)(struct bt_mesh_conn *conn, u8_t *addr); + + ssize_t (*prov_notify)(struct bt_mesh_conn *conn, u8_t *data, u16_t len); + + ssize_t (*proxy_write_descr)(struct bt_mesh_conn *conn); + + ssize_t (*proxy_notify)(struct bt_mesh_conn *conn, u8_t *data, u16_t len); +}; + +/** @brief GATT Attribute structure. */ +struct bt_mesh_gatt_attr { + /** Attribute UUID */ + const struct bt_mesh_uuid *uuid; + + /** Attribute read callback + * + * @param conn The connection that is requesting to read + * @param attr The attribute that's being read + * @param buf Buffer to place the read result in + * @param len Length of data to read + * @param offset Offset to start reading from + * + * @return Number fo bytes read, or in case of an error + * BLE_MESH_GATT_ERR() with a specific ATT error code. + */ + ssize_t (*read)(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + void *buf, u16_t len, + u16_t offset); + + /** Attribute write callback + * + * @param conn The connection that is requesting to write + * @param attr The attribute that's being written + * @param buf Buffer with the data to write + * @param len Number of bytes in the buffer + * @param offset Offset to start writing from + * @param flags Flags (BT_GATT_WRITE_*) + * + * @return Number of bytes written, or in case of an error + * BLE_MESH_GATT_ERR() with a specific ATT error code. + */ + ssize_t (*write)(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + const void *buf, u16_t len, + u16_t offset, u8_t flags); + + /** Attribute user data */ + void *user_data; + /** Attribute handle */ + u16_t handle; + /** Attribute permissions */ + u8_t perm; +}; + +/** @def BLE_MESH_GATT_PRIMARY_SERVICE + * @brief Primary Service Declaration Macro. + * + * Helper macro to declare a primary service attribute. + * + * @param _service Service attribute value. + */ +#define BLE_MESH_GATT_PRIMARY_SERVICE(_service) \ +{ \ + .uuid = BLE_MESH_UUID_GATT_PRIMARY, \ + .perm = BLE_MESH_GATT_PERM_READ, \ + .read = bt_mesh_gatts_attr_read_service, \ + .user_data = _service, \ +} + +/** @def BLE_MESH_GATT_SECONDARY_SERVICE + * @brief Secondary Service Declaration Macro. + * + * Helper macro to declare a secondary service attribute. + * + * @param _service Service attribute value. + */ +#define BLE_MESH_GATT_SECONDARY_SERVICE(_service) \ +{ \ + .uuid = BLE_MESH_UUID_GATT_SECONDARY, \ + .perm = BLE_MESH_GATT_PERM_READ, \ + .read = bt_mesh_gatts_attr_read_service, \ + .user_data = _service, \ +} + +/** @def BLE_MESH_GATT_INCLUDE_SERVICE + * @brief Include Service Declaration Macro. + * + * Helper macro to declare database internal include service attribute. + * + * @param _service_incl the first service attribute of service to include + */ +#define BLE_MESH_GATT_INCLUDE_SERVICE(_service_incl) \ +{ \ + .uuid = BLE_MESH_UUID_GATT_INCLUDE, \ + .perm = BLE_MESH_GATT_PERM_READ, \ + .read = bt_mesh_gatts_attr_read_included, \ + .user_data = _service_incl, \ +} + +/** @def BLE_MESH_GATT_CHARACTERISTIC + * @brief Characteristic Declaration Macro. + * + * Helper macro to declare a characteristic attribute. + * + * @param _uuid Characteristic attribute uuid. + * @param _props Characteristic attribute properties. + */ +#define BLE_MESH_GATT_CHARACTERISTIC(_uuid, _props) \ +{ \ + .uuid = BLE_MESH_UUID_GATT_CHRC, \ + .perm = BLE_MESH_GATT_PERM_READ, \ + .read = bt_mesh_gatts_attr_read_chrc, \ + .user_data = (&(struct bt_mesh_gatt_char) { .uuid = _uuid, \ + .properties = _props, }), \ +} + +/** @def BLE_MESH_GATT_DESCRIPTOR + * @brief Descriptor Declaration Macro. + * + * Helper macro to declare a descriptor attribute. + * + * @param _uuid Descriptor attribute uuid. + * @param _perm Descriptor attribute access permissions. + * @param _read Descriptor attribute read callback. + * @param _write Descriptor attribute write callback. + * @param _value Descriptor attribute value. + */ +#define BLE_MESH_GATT_DESCRIPTOR(_uuid, _perm, _read, _write, _value) \ +{ \ + .uuid = _uuid, \ + .perm = _perm, \ + .read = _read, \ + .write = _write, \ + .user_data = _value, \ +} + +/** @def BLE_MESH_GATT_SERVICE + * @brief Service Structure Declaration Macro. + * + * Helper macro to declare a service structure. + * + * @param _attrs Service attributes. + */ +#define BLE_MESH_GATT_SERVICE(_attrs) \ +{ \ + .attrs = _attrs, \ + .attr_count = ARRAY_SIZE(_attrs), \ +} + +int bt_le_adv_start(const struct bt_mesh_adv_param *param, + const struct bt_mesh_adv_data *ad, size_t ad_len, + const struct bt_mesh_adv_data *sd, size_t sd_len); + +int bt_le_adv_stop(void); + +int bt_le_scan_start(const struct bt_mesh_scan_param *param, bt_mesh_scan_cb_t cb); + +int bt_le_scan_stop(void); + +void bt_mesh_gatts_conn_cb_register(struct bt_mesh_conn_cb *cb); + +int bt_mesh_gatts_disconnect(struct bt_mesh_conn *conn, u8_t reason); + +int bt_mesh_gatts_service_register(struct bt_mesh_gatt_service *svc); + +int bt_mesh_gatts_service_unregister(struct bt_mesh_gatt_service *svc); + +ssize_t bt_mesh_gatts_attr_read_included(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + void *buf, u16_t len, u16_t offset); + +ssize_t bt_mesh_gatts_attr_read(struct bt_mesh_conn *conn, const struct bt_mesh_gatt_attr *attr, + void *buf, u16_t buf_len, u16_t offset, + const void *value, u16_t value_len); + +ssize_t bt_mesh_gatts_attr_read_service(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + void *buf, u16_t len, u16_t offset); + +ssize_t bt_mesh_gatts_attr_read_chrc(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, void *buf, + u16_t len, u16_t offset); + +int bt_mesh_gatts_notify(struct bt_mesh_conn *conn, const struct bt_mesh_gatt_attr *attr, + const void *data, u16_t len); + +u16_t bt_mesh_gatt_get_mtu(struct bt_mesh_conn *conn); + +/** APIs added by Espressif */ +int bt_mesh_gatts_service_stop(struct bt_mesh_gatt_service *svc); +int bt_mesh_gatts_service_start(struct bt_mesh_gatt_service *svc); + +void bt_mesh_gattc_conn_cb_register(struct bt_mesh_prov_conn_cb *cb); + +u16_t bt_mesh_gattc_get_service_uuid(struct bt_mesh_conn *conn); + +int bt_mesh_gattc_conn_create(const bt_mesh_addr_t *addr, u16_t service_uuid); + +void bt_gattc_conn_close(struct bt_mesh_conn *conn); + +void bt_mesh_gattc_exchange_mtu(u8_t index); + +u16_t bt_mesh_gattc_get_mtu_info(struct bt_mesh_conn *conn); + +int bt_mesh_gattc_write_no_rsp(struct bt_mesh_conn *conn, const struct bt_mesh_gatt_attr *attr, + const void *data, u16_t len); + +void bt_mesh_gattc_disconnect(struct bt_mesh_conn *conn); + +struct bt_mesh_conn *bt_mesh_conn_ref(struct bt_mesh_conn *conn); + +void bt_mesh_conn_unref(struct bt_mesh_conn *conn); + +void bt_mesh_gatt_init(void); + +void bt_mesh_adapt_init(void); + +int bt_mesh_rand(void *buf, size_t len); + +void bt_mesh_set_private_key(const u8_t pri_key[32]); + +const u8_t *bt_mesh_pub_key_get(void); + +bool bt_mesh_check_public_key(const uint8_t key[64]); + +int bt_mesh_dh_key_gen(const u8_t remote_pk[64], bt_mesh_dh_key_cb_t cb, const u8_t idx); + +int bt_mesh_encrypt_le(const u8_t key[16], const u8_t plaintext[16], + u8_t enc_data[16]); + +int bt_mesh_encrypt_be(const u8_t key[16], const u8_t plaintext[16], + u8_t enc_data[16]); + +enum { + BLE_MESH_EXCEP_LIST_ADD = 0, + BLE_MESH_EXCEP_LIST_REMOVE, + BLE_MESH_EXCEP_LIST_CLEAN, +}; + +enum { + BLE_MESH_EXCEP_INFO_ADV_ADDR = 0, + BLE_MESH_EXCEP_INFO_MESH_LINK_ID, + BLE_MESH_EXCEP_INFO_MESH_BEACON, + BLE_MESH_EXCEP_INFO_MESH_PROV_ADV, + BLE_MESH_EXCEP_INFO_MESH_PROXY_ADV, +}; + +enum { + BLE_MESH_EXCEP_CLEAN_ADDR_LIST = BIT(0), + BLE_MESH_EXCEP_CLEAN_MESH_LINK_ID_LIST = BIT(1), + BLE_MESH_EXCEP_CLEAN_MESH_BEACON_LIST = BIT(2), + BLE_MESH_EXCEP_CLEAN_MESH_PROV_ADV_LIST = BIT(3), + BLE_MESH_EXCEP_CLEAN_MESH_PROXY_ADV_LIST = BIT(4), + BLE_MESH_EXCEP_CLEAN_ALL_LIST = 0xFFFF, +}; + +int bt_mesh_update_exceptional_list(u8_t sub_code, u8_t type, void *info); + +#endif /* _BLE_MESH_BEARER_ADRPT_H_ */ + diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_buf.h b/components/bt/ble_mesh/mesh_core/include/mesh_buf.h new file mode 100644 index 0000000000..e6c62877ea --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_buf.h @@ -0,0 +1,1064 @@ +/** @file + * @brief Buffer management. + */ + +/* + * Copyright (c) 2015 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BLE_MESH_BUF_H_ +#define _BLE_MESH_BUF_H_ + +#include +#include "sys/cdefs.h" +#include "mesh_types.h" +#include "mesh_slist.h" +#include "mesh_kernel.h" +#include "mesh_util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Unaligned access */ +#define UNALIGNED_GET(p) \ +__extension__ ({ \ + struct __attribute__((__packed__)) { \ + __typeof__(*(p)) __v; \ + } *__p = (__typeof__(__p)) (p); \ + __p->__v; \ +}) + +#define BLE_MESH_NET_BUF_USER_DATA_SIZE 4 + +/** + * @brief Network buffer library + * @defgroup net_buf Network Buffer Library + * @ingroup networking + * @{ + */ + +/* Alignment needed for various parts of the buffer definition */ +#define __net_buf_align __aligned(sizeof(int)) + +/** + * @def NET_BUF_SIMPLE_DEFINE + * @brief Define a net_buf_simple stack variable. + * + * This is a helper macro which is used to define a net_buf_simple object + * on the stack. + * + * @param _name Name of the net_buf_simple object. + * @param _size Maximum data storage for the buffer. + */ +#define NET_BUF_SIMPLE_DEFINE(_name, _size) \ + u8_t net_buf_data_##_name[_size]; \ + struct net_buf_simple _name = { \ + .data = net_buf_data_##_name, \ + .len = 0, \ + .size = _size, \ + .__buf = net_buf_data_##_name, \ + } + +/** + * @def NET_BUF_SIMPLE_DEFINE_STATIC + * @brief Define a static net_buf_simple variable. + * + * This is a helper macro which is used to define a static net_buf_simple + * object. + * + * @param _name Name of the net_buf_simple object. + * @param _size Maximum data storage for the buffer. + */ +#define NET_BUF_SIMPLE_DEFINE_STATIC(_name, _size) \ + static u8_t net_buf_data_##_name[_size]; \ + static struct net_buf_simple _name = { \ + .data = net_buf_data_##_name, \ + .len = 0, \ + .size = _size, \ + .__buf = net_buf_data_##_name, \ + } + +/** + * @brief Simple network buffer representation. + * + * This is a simpler variant of the net_buf object (in fact net_buf uses + * net_buf_simple internally). It doesn't provide any kind of reference + * counting, user data, dynamic allocation, or in general the ability to + * pass through kernel objects such as FIFOs. + * + * The main use of this is for scenarios where the meta-data of the normal + * net_buf isn't needed and causes too much overhead. This could be e.g. + * when the buffer only needs to be allocated on the stack or when the + * access to and lifetime of the buffer is well controlled and constrained. + */ +struct net_buf_simple { + /** Pointer to the start of data in the buffer. */ + u8_t *data; + + /** Length of the data behind the data pointer. */ + u16_t len; + + /** Amount of data that this buffer can store. */ + u16_t size; + + /** Start of the data storage. Not to be accessed directly + * (the data pointer should be used instead). + */ + u8_t *__buf; +}; + +/** + * @def NET_BUF_SIMPLE + * @brief Define a net_buf_simple stack variable and get a pointer to it. + * + * This is a helper macro which is used to define a net_buf_simple object on + * the stack and the get a pointer to it as follows: + * + * struct net_buf_simple *my_buf = NET_BUF_SIMPLE(10); + * + * After creating the object it needs to be initialized by calling + * net_buf_simple_init(). + * + * @param _size Maximum data storage for the buffer. + * + * @return Pointer to stack-allocated net_buf_simple object. + */ +#define NET_BUF_SIMPLE(_size) \ + ((struct net_buf_simple *)(&(struct { \ + struct net_buf_simple buf; \ + u8_t data[_size] __net_buf_align; \ + }) { \ + .buf.size = _size, \ + .buf.__buf = NULL, \ + })) + +/** + * @brief Initialize a net_buf_simple object. + * + * This needs to be called after creating a net_buf_simple object using + * the NET_BUF_SIMPLE macro. + * + * @param buf Buffer to initialize. + * @param reserve_head Headroom to reserve. + */ +static inline void net_buf_simple_init(struct net_buf_simple *buf, + size_t reserve_head) +{ + if (!buf->__buf) { + buf->__buf = (u8_t *)buf + sizeof(*buf); + } + + buf->data = buf->__buf + reserve_head; + buf->len = 0; +} + +/** + * @brief Reset buffer + * + * Reset buffer data so it can be reused for other purposes. + * + * @param buf Buffer to reset. + */ +static inline void net_buf_simple_reset(struct net_buf_simple *buf) +{ + buf->len = 0; + buf->data = buf->__buf; +} + +/** + * @brief Prepare data to be added at the end of the buffer + * + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param len Number of bytes to increment the length with. + * + * @return The original tail of the buffer. + */ +void *net_buf_simple_add(struct net_buf_simple *buf, size_t len); + +/** + * @brief Copy given number of bytes from memory to the end of the buffer + * + * Increments the data length of the buffer to account for more data at the + * end. + * + * @param buf Buffer to update. + * @param mem Location of data to be added. + * @param len Length of data to be added + * + * @return The original tail of the buffer. + */ +void *net_buf_simple_add_mem(struct net_buf_simple *buf, const void *mem, + size_t len); + +/** + * @brief Add (8-bit) byte at the end of the buffer + * + * Increments the data length of the buffer to account for more data at the + * end. + * + * @param buf Buffer to update. + * @param val byte value to be added. + * + * @return Pointer to the value added + */ +u8_t *net_buf_simple_add_u8(struct net_buf_simple *buf, u8_t val); + +/** + * @brief Add 16-bit value at the end of the buffer + * + * Adds 16-bit value in little endian format at the end of buffer. + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param val 16-bit value to be added. + */ +void net_buf_simple_add_le16(struct net_buf_simple *buf, u16_t val); + +/** + * @brief Add 16-bit value at the end of the buffer + * + * Adds 16-bit value in big endian format at the end of buffer. + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param val 16-bit value to be added. + */ +void net_buf_simple_add_be16(struct net_buf_simple *buf, u16_t val); + +/** + * @brief Add 32-bit value at the end of the buffer + * + * Adds 32-bit value in little endian format at the end of buffer. + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param val 32-bit value to be added. + */ +void net_buf_simple_add_le32(struct net_buf_simple *buf, u32_t val); + +/** + * @brief Add 32-bit value at the end of the buffer + * + * Adds 32-bit value in big endian format at the end of buffer. + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param val 32-bit value to be added. + */ +void net_buf_simple_add_be32(struct net_buf_simple *buf, u32_t val); + +/** + * @brief Push data to the beginning of the buffer. + * + * Modifies the data pointer and buffer length to account for more data + * in the beginning of the buffer. + * + * @param buf Buffer to update. + * @param len Number of bytes to add to the beginning. + * + * @return The new beginning of the buffer data. + */ +void *net_buf_simple_push(struct net_buf_simple *buf, size_t len); + +/** + * @brief Push 16-bit value to the beginning of the buffer + * + * Adds 16-bit value in little endian format to the beginning of the + * buffer. + * + * @param buf Buffer to update. + * @param val 16-bit value to be pushed to the buffer. + */ +void net_buf_simple_push_le16(struct net_buf_simple *buf, u16_t val); + +/** + * @brief Push 16-bit value to the beginning of the buffer + * + * Adds 16-bit value in big endian format to the beginning of the + * buffer. + * + * @param buf Buffer to update. + * @param val 16-bit value to be pushed to the buffer. + */ +void net_buf_simple_push_be16(struct net_buf_simple *buf, u16_t val); + +/** + * @brief Push 8-bit value to the beginning of the buffer + * + * Adds 8-bit value the beginning of the buffer. + * + * @param buf Buffer to update. + * @param val 8-bit value to be pushed to the buffer. + */ +void net_buf_simple_push_u8(struct net_buf_simple *buf, u8_t val); + +/** + * @brief Remove data from the beginning of the buffer. + * + * Removes data from the beginning of the buffer by modifying the data + * pointer and buffer length. + * + * @param buf Buffer to update. + * @param len Number of bytes to remove. + * + * @return New beginning of the buffer data. + */ +void *net_buf_simple_pull(struct net_buf_simple *buf, size_t len); + +/** + * @brief Remove data from the beginning of the buffer. + * + * Removes data from the beginning of the buffer by modifying the data + * pointer and buffer length. + * + * @param buf Buffer to update. + * @param len Number of bytes to remove. + * + * @return Pointer to the old location of the buffer data. + */ +void *net_buf_simple_pull_mem(struct net_buf_simple *buf, size_t len); + +/** + * @brief Remove a 8-bit value from the beginning of the buffer + * + * Same idea as with net_buf_simple_pull(), but a helper for operating + * on 8-bit values. + * + * @param buf A valid pointer on a buffer. + * + * @return The 8-bit removed value + */ +u8_t net_buf_simple_pull_u8(struct net_buf_simple *buf); + +/** + * @brief Remove and convert 16 bits from the beginning of the buffer. + * + * Same idea as with net_buf_simple_pull(), but a helper for operating + * on 16-bit little endian data. + * + * @param buf A valid pointer on a buffer. + * + * @return 16-bit value converted from little endian to host endian. + */ +u16_t net_buf_simple_pull_le16(struct net_buf_simple *buf); + +/** + * @brief Remove and convert 16 bits from the beginning of the buffer. + * + * Same idea as with net_buf_simple_pull(), but a helper for operating + * on 16-bit big endian data. + * + * @param buf A valid pointer on a buffer. + * + * @return 16-bit value converted from big endian to host endian. + */ +u16_t net_buf_simple_pull_be16(struct net_buf_simple *buf); + +/** + * @brief Remove and convert 32 bits from the beginning of the buffer. + * + * Same idea as with net_buf_simple_pull(), but a helper for operating + * on 32-bit little endian data. + * + * @param buf A valid pointer on a buffer. + * + * @return 32-bit value converted from little endian to host endian. + */ +u32_t net_buf_simple_pull_le32(struct net_buf_simple *buf); + +/** + * @brief Remove and convert 32 bits from the beginning of the buffer. + * + * Same idea as with net_buf_simple_pull(), but a helper for operating + * on 32-bit big endian data. + * + * @param buf A valid pointer on a buffer. + * + * @return 32-bit value converted from big endian to host endian. + */ +u32_t net_buf_simple_pull_be32(struct net_buf_simple *buf); + +/** + * @brief Get the tail pointer for a buffer. + * + * Get a pointer to the end of the data in a buffer. + * + * @param buf Buffer. + * + * @return Tail pointer for the buffer. + */ +static inline u8_t *net_buf_simple_tail(struct net_buf_simple *buf) +{ + return buf->data + buf->len; +} + +/** + * @brief Check buffer headroom. + * + * Check how much free space there is in the beginning of the buffer. + * + * buf A valid pointer on a buffer + * + * @return Number of bytes available in the beginning of the buffer. + */ +size_t net_buf_simple_headroom(struct net_buf_simple *buf); + +/** + * @brief Check buffer tailroom. + * + * Check how much free space there is at the end of the buffer. + * + * @param buf A valid pointer on a buffer + * + * @return Number of bytes available at the end of the buffer. + */ +size_t net_buf_simple_tailroom(struct net_buf_simple *buf); + +/** + * @brief Parsing state of a buffer. + * + * This is used for temporarily storing the parsing state of a buffer + * while giving control of the parsing to a routine which we don't + * control. + */ +struct net_buf_simple_state { + /** Offset of the data pointer from the beginning of the storage */ + u16_t offset; + /** Length of data */ + u16_t len; +}; + +/** + * @brief Save the parsing state of a buffer. + * + * Saves the parsing state of a buffer so it can be restored later. + * + * @param buf Buffer from which the state should be saved. + * @param state Storage for the state. + */ +static inline void net_buf_simple_save(struct net_buf_simple *buf, + struct net_buf_simple_state *state) +{ + state->offset = net_buf_simple_headroom(buf); + state->len = buf->len; +} + +/** + * @brief Restore the parsing state of a buffer. + * + * Restores the parsing state of a buffer from a state previously stored + * by net_buf_simple_save(). + * + * @param buf Buffer to which the state should be restored. + * @param state Stored state. + */ +static inline void net_buf_simple_restore(struct net_buf_simple *buf, + struct net_buf_simple_state *state) +{ + buf->data = buf->__buf + state->offset; + buf->len = state->len; +} + +/** + * @brief Initialize buffer with the given headroom. + * + * The buffer is not expected to contain any data when this API is called. + * + * @param buf Buffer to initialize. + * @param reserve How much headroom to reserve. + */ +void net_buf_simple_reserve(struct net_buf_simple *buf, size_t reserve); + +/** + * Flag indicating that the buffer has associated fragments. Only used + * internally by the buffer handling code while the buffer is inside a + * FIFO, meaning this never needs to be explicitly set or unset by the + * net_buf API user. As long as the buffer is outside of a FIFO, i.e. + * in practice always for the user for this API, the buf->frags pointer + * should be used instead. + */ +#define NET_BUF_FRAGS BIT(0) + +/** + * @brief Network buffer representation. + * + * This struct is used to represent network buffers. Such buffers are + * normally defined through the NET_BUF_POOL_*_DEFINE() APIs and allocated + * using the net_buf_alloc() API. + */ +struct net_buf { + union { + /** Allow placing the buffer into sys_slist_t */ + sys_snode_t node; + + /** Fragments associated with this buffer. */ + struct net_buf *frags; + }; + + /** Reference count. */ + u8_t ref; + + /** Bit-field of buffer flags. */ + u8_t flags; + + /** Where the buffer should go when freed up. */ + struct net_buf_pool *pool; + + /* Union for convenience access to the net_buf_simple members, also + * preserving the old API. + */ + union { + /* The ABI of this struct must match net_buf_simple */ + struct { + /** Pointer to the start of data in the buffer. */ + u8_t *data; + + /** Length of the data behind the data pointer. */ + u16_t len; + + /** Amount of data that this buffer can store. */ + u16_t size; + + /** Start of the data storage. Not to be accessed + * directly (the data pointer should be used + * instead). + */ + u8_t *__buf; + }; + + struct net_buf_simple b; + }; + + /** System metadata for this buffer. */ + u8_t user_data[BLE_MESH_NET_BUF_USER_DATA_SIZE] __net_buf_align; +}; + +struct net_buf_data_cb { + u8_t * (*alloc)(struct net_buf *buf, size_t *size, s32_t timeout); + u8_t * (*ref)(struct net_buf *buf, u8_t *data); + void (*unref)(struct net_buf *buf, u8_t *data); +}; + +struct net_buf_data_alloc { + const struct net_buf_data_cb *cb; + void *alloc_data; +}; + +struct net_buf_pool { + /** Number of buffers in pool */ + const u16_t buf_count; + + /** Number of uninitialized buffers */ + u16_t uninit_count; + +#if defined(CONFIG_BLE_MESH_NET_BUF_POOL_USAGE) + /** Amount of available buffers in the pool. */ + s16_t avail_count; + + /** Total size of the pool. */ + const u16_t pool_size; + + /** Name of the pool. Used when printing pool information. */ + const char *name; +#endif /* CONFIG_BLE_MESH_NET_BUF_POOL_USAGE */ + + /** Optional destroy callback when buffer is freed. */ + void (*const destroy)(struct net_buf *buf); + + /** Data allocation handlers. */ + const struct net_buf_data_alloc *alloc; + + /** Helper to access the start of storage (for net_buf_pool_init) */ + struct net_buf *const __bufs; +}; + +#if defined(CONFIG_BLE_MESH_NET_BUF_POOL_USAGE) +#define NET_BUF_POOL_INITIALIZER(_pool, _alloc, _bufs, _count, _destroy) \ + { \ + .alloc = _alloc, \ + .__bufs = (struct net_buf *)_bufs, \ + .buf_count = _count, \ + .uninit_count = _count, \ + .avail_count = _count, \ + .destroy = _destroy, \ + .name = STRINGIFY(_pool), \ + } +#else +#define NET_BUF_POOL_INITIALIZER(_pool, _alloc, _bufs, _count, _destroy) \ + { \ + .alloc = _alloc, \ + .__bufs = (struct net_buf *)_bufs, \ + .buf_count = _count, \ + .uninit_count = _count, \ + .destroy = _destroy, \ + } +#endif /* CONFIG_BLE_MESH_NET_BUF_POOL_USAGE */ + +struct net_buf_pool_fixed { + size_t data_size; + u8_t *data_pool; +}; + +/** @cond INTERNAL_HIDDEN */ +extern const struct net_buf_data_cb net_buf_fixed_cb; + +/** + * @def NET_BUF_POOL_FIXED_DEFINE + * @brief Define a new pool for buffers based on fixed-size data + * + * Defines a net_buf_pool struct and the necessary memory storage (array of + * structs) for the needed amount of buffers. After this, the buffers can be + * accessed from the pool through net_buf_alloc. The pool is defined as a + * static variable, so if it needs to be exported outside the current module + * this needs to happen with the help of a separate pointer rather than an + * extern declaration. + * + * The data payload of the buffers will be allocated from a byte array + * of fixed sized chunks. This kind of pool does not support blocking on + * the data allocation, so the timeout passed to net_buf_alloc will be + * always treated as K_NO_WAIT when trying to allocate the data. This means + * that allocation failures, i.e. NULL returns, must always be handled + * cleanly. + * + * If provided with a custom destroy callback, this callback is + * responsible for eventually calling net_buf_destroy() to complete the + * process of returning the buffer to the pool. + * + * @param _name Name of the pool variable. + * @param _count Number of buffers in the pool. + * @param _data_size Maximum data payload per buffer. + * @param _destroy Optional destroy callback when buffer is freed. + */ +#define NET_BUF_POOL_FIXED_DEFINE(_name, _count, _data_size, _destroy) \ + static struct net_buf net_buf_##_name[_count]; \ + static u8_t net_buf_data_##_name[_count][_data_size]; \ + static const struct net_buf_pool_fixed net_buf_fixed_##_name = { \ + .data_size = _data_size, \ + .data_pool = (u8_t *)net_buf_data_##_name, \ + }; \ + static const struct net_buf_data_alloc net_buf_fixed_alloc_##_name = { \ + .cb = &net_buf_fixed_cb, \ + .alloc_data = (void *)&net_buf_fixed_##_name, \ + }; \ + struct net_buf_pool _name __net_buf_align \ + __in_section(_net_buf_pool, static, _name) = \ + NET_BUF_POOL_INITIALIZER(_name, &net_buf_fixed_alloc_##_name, \ + net_buf_##_name, _count, _destroy) + +/** + * @def NET_BUF_POOL_DEFINE + * @brief Define a new pool for buffers + * + * Defines a net_buf_pool struct and the necessary memory storage (array of + * structs) for the needed amount of buffers. After this,the buffers can be + * accessed from the pool through net_buf_alloc. The pool is defined as a + * static variable, so if it needs to be exported outside the current module + * this needs to happen with the help of a separate pointer rather than an + * extern declaration. + * + * If provided with a custom destroy callback this callback is + * responsible for eventually calling net_buf_destroy() to complete the + * process of returning the buffer to the pool. + * + * @param _name Name of the pool variable. + * @param _count Number of buffers in the pool. + * @param _size Maximum data size for each buffer. + * @param _ud_size Amount of user data space to reserve. + * @param _destroy Optional destroy callback when buffer is freed. + */ +#define NET_BUF_POOL_DEFINE(_name, _count, _size, _ud_size, _destroy) \ + NET_BUF_POOL_FIXED_DEFINE(_name, _count, _size, _destroy) + +/** + * @brief Get a zero-based index for a buffer. + * + * This function will translate a buffer into a zero-based index, + * based on its placement in its buffer pool. This can be useful if you + * want to associate an external array of meta-data contexts with the + * buffers of a pool. + * + * @param buf Network buffer. + * + * @return Zero-based index for the buffer. + */ +int net_buf_id(struct net_buf *buf); + +/** + * @brief Allocate a new fixed buffer from a pool. + * + * @param pool Which pool to allocate the buffer from. + * @param timeout Affects the action taken should the pool be empty. + * If K_NO_WAIT, then return immediately. If K_FOREVER, then + * wait as long as necessary. Otherwise, wait up to the specified + * number of milliseconds before timing out. Note that some types + * of data allocators do not support blocking (such as the HEAP + * type). In this case it's still possible for net_buf_alloc() to + * fail (return NULL) even if it was given K_FOREVER. + * + * @return New buffer or NULL if out of buffers. + */ +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) +struct net_buf *net_buf_alloc_fixed_debug(struct net_buf_pool *pool, s32_t timeout, + const char *func, int line); +#define net_buf_alloc_fixed(_pool, _timeout) \ + net_buf_alloc_fixed_debug(_pool, _timeout, __func__, __LINE__) +#else +struct net_buf *net_buf_alloc_fixed(struct net_buf_pool *pool, s32_t timeout); +#endif + +/** + * @def net_buf_alloc + * + * @copydetails net_buf_alloc_fixed + */ +#define net_buf_alloc(pool, timeout) net_buf_alloc_fixed(pool, timeout) + +/** + * @brief Reset buffer + * + * Reset buffer data and flags so it can be reused for other purposes. + * + * @param buf Buffer to reset. + */ +void net_buf_reset(struct net_buf *buf); + +/** + * @def net_buf_reserve + * @brief Initialize buffer with the given headroom. + * + * The buffer is not expected to contain any data when this API is called. + * + * @param buf Buffer to initialize. + * @param reserve How much headroom to reserve. + */ +#define net_buf_reserve(buf, reserve) net_buf_simple_reserve(&(buf)->b, reserve) + +/** + * @brief Put a buffer into a list + * + * Put a buffer to the end of a list. If the buffer contains follow-up + * fragments this function will take care of inserting them as well + * into the list. + * + * @param list Which list to append the buffer to. + * @param buf Buffer. + */ +void net_buf_slist_put(sys_slist_t *list, struct net_buf *buf); + +/** + * @brief Get a buffer from a list. + * + * Get buffer from a list. If the buffer had any fragments, these will + * automatically be recovered from the list as well and be placed to + * the buffer's fragment list. + * + * @param list Which list to take the buffer from. + * + * @return New buffer or NULL if the FIFO is empty. + */ +struct net_buf *net_buf_slist_get(sys_slist_t *list); + +/** + * @brief Decrements the reference count of a buffer. + * + * Decrements the reference count of a buffer and puts it back into the + * pool if the count reaches zero. + * + * @param buf A valid pointer on a buffer + */ +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) +void net_buf_unref_debug(struct net_buf *buf, const char *func, int line); +#define net_buf_unref(_buf) \ + net_buf_unref_debug(_buf, __func__, __LINE__) +#else +void net_buf_unref(struct net_buf *buf); +#endif + +/** + * @brief Increment the reference count of a buffer. + * + * @param buf A valid pointer on a buffer + * + * @return the buffer newly referenced + */ +struct net_buf *net_buf_ref(struct net_buf *buf); + +/** + * @brief Get a pointer to the user data of a buffer. + * + * @param buf A valid pointer on a buffer + * + * @return Pointer to the user data of the buffer. + */ +static inline void *net_buf_user_data(struct net_buf *buf) +{ + return (void *)buf->user_data; +} + +/** + * @def net_buf_add + * @brief Prepare data to be added at the end of the buffer + * + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param len Number of bytes to increment the length with. + * + * @return The original tail of the buffer. + */ +#define net_buf_add(buf, len) net_buf_simple_add(&(buf)->b, len) + +/** + * @def net_buf_add_mem + * @brief Copy bytes from memory to the end of the buffer + * + * Copies the given number of bytes to the end of the buffer. Increments the + * data length of the buffer to account for more data at the end. + * + * @param buf Buffer to update. + * @param mem Location of data to be added. + * @param len Length of data to be added + * + * @return The original tail of the buffer. + */ +#define net_buf_add_mem(buf, mem, len) net_buf_simple_add_mem(&(buf)->b, mem, len) + +/** + * @def net_buf_add_u8 + * @brief Add (8-bit) byte at the end of the buffer + * + * Adds a byte at the end of the buffer. Increments the data length of + * the buffer to account for more data at the end. + * + * @param buf Buffer to update. + * @param val byte value to be added. + * + * @return Pointer to the value added + */ +#define net_buf_add_u8(buf, val) net_buf_simple_add_u8(&(buf)->b, val) + +/** + * @def net_buf_add_le16 + * @brief Add 16-bit value at the end of the buffer + * + * Adds 16-bit value in little endian format at the end of buffer. + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param val 16-bit value to be added. + */ +#define net_buf_add_le16(buf, val) net_buf_simple_add_le16(&(buf)->b, val) + +/** + * @def net_buf_add_be16 + * @brief Add 16-bit value at the end of the buffer + * + * Adds 16-bit value in big endian format at the end of buffer. + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param val 16-bit value to be added. + */ +#define net_buf_add_be16(buf, val) net_buf_simple_add_be16(&(buf)->b, val) + +/** + * @def net_buf_add_le32 + * @brief Add 32-bit value at the end of the buffer + * + * Adds 32-bit value in little endian format at the end of buffer. + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param val 32-bit value to be added. + */ +#define net_buf_add_le32(buf, val) net_buf_simple_add_le32(&(buf)->b, val) + +/** + * @def net_buf_add_be32 + * @brief Add 32-bit value at the end of the buffer + * + * Adds 32-bit value in big endian format at the end of buffer. + * Increments the data length of a buffer to account for more data + * at the end. + * + * @param buf Buffer to update. + * @param val 32-bit value to be added. + */ +#define net_buf_add_be32(buf, val) net_buf_simple_add_be32(&(buf)->b, val) + +/** + * @def net_buf_push + * @brief Push data to the beginning of the buffer. + * + * Modifies the data pointer and buffer length to account for more data + * in the beginning of the buffer. + * + * @param buf Buffer to update. + * @param len Number of bytes to add to the beginning. + * + * @return The new beginning of the buffer data. + */ +#define net_buf_push(buf, len) net_buf_simple_push(&(buf)->b, len) + +/** + * @def net_buf_push_le16 + * @brief Push 16-bit value to the beginning of the buffer + * + * Adds 16-bit value in little endian format to the beginning of the + * buffer. + * + * @param buf Buffer to update. + * @param val 16-bit value to be pushed to the buffer. + */ +#define net_buf_push_le16(buf, val) net_buf_simple_push_le16(&(buf)->b, val) + +/** + * @def net_buf_push_be16 + * @brief Push 16-bit value to the beginning of the buffer + * + * Adds 16-bit value in little endian format to the beginning of the + * buffer. + * + * @param buf Buffer to update. + * @param val 16-bit value to be pushed to the buffer. + */ +#define net_buf_push_be16(buf, val) net_buf_simple_push_be16(&(buf)->b, val) + +/** + * @def net_buf_push_u8 + * @brief Push 8-bit value to the beginning of the buffer + * + * Adds 8-bit value the beginning of the buffer. + * + * @param buf Buffer to update. + * @param val 8-bit value to be pushed to the buffer. + */ +#define net_buf_push_u8(buf, val) net_buf_simple_push_u8(&(buf)->b, val) + +/** + * @def net_buf_pull + * @brief Remove data from the beginning of the buffer. + * + * Removes data from the beginning of the buffer by modifying the data + * pointer and buffer length. + * + * @param buf Buffer to update. + * @param len Number of bytes to remove. + * + * @return New beginning of the buffer data. + */ +#define net_buf_pull(buf, len) net_buf_simple_pull(&(buf)->b, len) + +/** + * @def net_buf_pull_u8 + * @brief Remove a 8-bit value from the beginning of the buffer + * + * Same idea as with net_buf_pull(), but a helper for operating on + * 8-bit values. + * + * @param buf A valid pointer on a buffer. + * + * @return The 8-bit removed value + */ +#define net_buf_pull_u8(buf) net_buf_simple_pull_u8(&(buf)->b) + +/** + * @def net_buf_pull_le16 + * @brief Remove and convert 16 bits from the beginning of the buffer. + * + * Same idea as with net_buf_pull(), but a helper for operating on + * 16-bit little endian data. + * + * @param buf A valid pointer on a buffer. + * + * @return 16-bit value converted from little endian to host endian. + */ +#define net_buf_pull_le16(buf) net_buf_simple_pull_le16(&(buf)->b) + +/** + * @def net_buf_pull_be16 + * @brief Remove and convert 16 bits from the beginning of the buffer. + * + * Same idea as with net_buf_pull(), but a helper for operating on + * 16-bit big endian data. + * + * @param buf A valid pointer on a buffer. + * + * @return 16-bit value converted from big endian to host endian. + */ +#define net_buf_pull_be16(buf) net_buf_simple_pull_be16(&(buf)->b) + +/** + * @def net_buf_pull_le32 + * @brief Remove and convert 32 bits from the beginning of the buffer. + * + * Same idea as with net_buf_pull(), but a helper for operating on + * 32-bit little endian data. + * + * @param buf A valid pointer on a buffer. + * + * @return 32-bit value converted from little endian to host endian. + */ +#define net_buf_pull_le32(buf) net_buf_simple_pull_le32(&(buf)->b) + +/** + * @def net_buf_pull_be32 + * @brief Remove and convert 32 bits from the beginning of the buffer. + * + * Same idea as with net_buf_pull(), but a helper for operating on + * 32-bit big endian data. + * + * @param buf A valid pointer on a buffer + * + * @return 32-bit value converted from big endian to host endian. + */ +#define net_buf_pull_be32(buf) net_buf_simple_pull_be32(&(buf)->b) + +/** + * @def net_buf_tailroom + * @brief Check buffer tailroom. + * + * Check how much free space there is at the end of the buffer. + * + * @param buf A valid pointer on a buffer + * + * @return Number of bytes available at the end of the buffer. + */ +#define net_buf_tailroom(buf) net_buf_simple_tailroom(&(buf)->b) + +/** + * @def net_buf_headroom + * @brief Check buffer headroom. + * + * Check how much free space there is in the beginning of the buffer. + * + * buf A valid pointer on a buffer + * + * @return Number of bytes available in the beginning of the buffer. + */ +#define net_buf_headroom(buf) net_buf_simple_headroom(&(buf)->b) + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_BUF_H_ */ + diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_dlist.h b/components/bt/ble_mesh/mesh_core/include/mesh_dlist.h new file mode 100644 index 0000000000..31eef746ec --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_dlist.h @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2013-2015 Wind River Systems, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Doubly-linked list implementation + * + * Doubly-linked list implementation using inline macros/functions. + * This API is not thread safe, and thus if a list is used across threads, + * calls to functions must be protected with synchronization primitives. + * + * The lists are expected to be initialized such that both the head and tail + * pointers point to the list itself. Initializing the lists in such a fashion + * simplifies the adding and removing of nodes to/from the list. + */ + +#ifndef _BLE_MESH_DLIST_H_ +#define _BLE_MESH_DLIST_H_ + +#include +#include "mesh_util.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +struct _dnode { + union { + struct _dnode *head; /* ptr to head of list (sys_dlist_t) */ + struct _dnode *next; /* ptr to next node (sys_dnode_t) */ + }; + union { + struct _dnode *tail; /* ptr to tail of list (sys_dlist_t) */ + struct _dnode *prev; /* ptr to previous node (sys_dnode_t) */ + }; +}; + +typedef struct _dnode sys_dlist_t; +typedef struct _dnode sys_dnode_t; + +/** + * @brief Provide the primitive to iterate on a list + * Note: the loop is unsafe and thus __dn should not be removed + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_DLIST_FOR_EACH_NODE(l, n) { + * + * } + * + * This and other SYS_DLIST_*() macros are not thread safe. + * + * @param __dl A pointer on a sys_dlist_t to iterate on + * @param __dn A sys_dnode_t pointer to peek each node of the list + */ +#define SYS_DLIST_FOR_EACH_NODE(__dl, __dn) \ + for (__dn = sys_dlist_peek_head(__dl); __dn; \ + __dn = sys_dlist_peek_next(__dl, __dn)) + +/** + * @brief Provide the primitive to iterate on a list, from a node in the list + * Note: the loop is unsafe and thus __dn should not be removed + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_DLIST_ITERATE_FROM_NODE(l, n) { + * + * } + * + * Like SYS_DLIST_FOR_EACH_NODE(), but __dn already contains a node in the list + * where to start searching for the next entry from. If NULL, it starts from + * the head. + * + * This and other SYS_DLIST_*() macros are not thread safe. + * + * @param __dl A pointer on a sys_dlist_t to iterate on + * @param __dn A sys_dnode_t pointer to peek each node of the list; + * it contains the starting node, or NULL to start from the head + */ +#define SYS_DLIST_ITERATE_FROM_NODE(__dl, __dn) \ + for (__dn = __dn ? sys_dlist_peek_next_no_check(__dl, __dn) \ + : sys_dlist_peek_head(__dl); \ + __dn; \ + __dn = sys_dlist_peek_next(__dl, __dn)) + +/** + * @brief Provide the primitive to safely iterate on a list + * Note: __dn can be removed, it will not break the loop. + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_DLIST_FOR_EACH_NODE_SAFE(l, n, s) { + * + * } + * + * This and other SYS_DLIST_*() macros are not thread safe. + * + * @param __dl A pointer on a sys_dlist_t to iterate on + * @param __dn A sys_dnode_t pointer to peek each node of the list + * @param __dns A sys_dnode_t pointer for the loop to run safely + */ +#define SYS_DLIST_FOR_EACH_NODE_SAFE(__dl, __dn, __dns) \ + for (__dn = sys_dlist_peek_head(__dl), \ + __dns = sys_dlist_peek_next(__dl, __dn); \ + __dn; __dn = __dns, \ + __dns = sys_dlist_peek_next(__dl, __dn)) + +/* + * @brief Provide the primitive to resolve the container of a list node + * Note: it is safe to use with NULL pointer nodes + * + * @param __dn A pointer on a sys_dnode_t to get its container + * @param __cn Container struct type pointer + * @param __n The field name of sys_dnode_t within the container struct + */ +#define SYS_DLIST_CONTAINER(__dn, __cn, __n) \ + (__dn ? CONTAINER_OF(__dn, __typeof__(*__cn), __n) : NULL) +/* + * @brief Provide the primitive to peek container of the list head + * + * @param __dl A pointer on a sys_dlist_t to peek + * @param __cn Container struct type pointer + * @param __n The field name of sys_dnode_t within the container struct + */ +#define SYS_DLIST_PEEK_HEAD_CONTAINER(__dl, __cn, __n) \ + SYS_DLIST_CONTAINER(sys_dlist_peek_head(__dl), __cn, __n) + +/* + * @brief Provide the primitive to peek the next container + * + * @param __dl A pointer on a sys_dlist_t to peek + * @param __cn Container struct type pointer + * @param __n The field name of sys_dnode_t within the container struct + */ +#define SYS_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n) \ + ((__cn) ? SYS_DLIST_CONTAINER(sys_dlist_peek_next(__dl, &(__cn->__n)), \ + __cn, __n) : NULL) + +/** + * @brief Provide the primitive to iterate on a list under a container + * Note: the loop is unsafe and thus __cn should not be detached + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_DLIST_FOR_EACH_CONTAINER(l, c, n) { + * + * } + * + * @param __dl A pointer on a sys_dlist_t to iterate on + * @param __cn A pointer to peek each entry of the list + * @param __n The field name of sys_dnode_t within the container struct + */ +#define SYS_DLIST_FOR_EACH_CONTAINER(__dl, __cn, __n) \ + for (__cn = SYS_DLIST_PEEK_HEAD_CONTAINER(__dl, __cn, __n); __cn; \ + __cn = SYS_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n)) + +/** + * @brief Provide the primitive to safely iterate on a list under a container + * Note: __cn can be detached, it will not break the loop. + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_DLIST_FOR_EACH_CONTAINER_SAFE(l, c, cn, n) { + * + * } + * + * @param __dl A pointer on a sys_dlist_t to iterate on + * @param __cn A pointer to peek each entry of the list + * @param __cns A pointer for the loop to run safely + * @param __n The field name of sys_dnode_t within the container struct + */ +#define SYS_DLIST_FOR_EACH_CONTAINER_SAFE(__dl, __cn, __cns, __n) \ + for (__cn = SYS_DLIST_PEEK_HEAD_CONTAINER(__dl, __cn, __n), \ + __cns = SYS_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n); __cn; \ + __cn = __cns, \ + __cns = SYS_DLIST_PEEK_NEXT_CONTAINER(__dl, __cn, __n)) + +/** + * @brief initialize list + * + * @param list the doubly-linked list + * + * @return N/A + */ + +static inline void sys_dlist_init(sys_dlist_t *list) +{ + list->head = (sys_dnode_t *)list; + list->tail = (sys_dnode_t *)list; +} + +#define SYS_DLIST_STATIC_INIT(ptr_to_list) {{(ptr_to_list)}, {(ptr_to_list)}} + +/** + * @brief check if a node is the list's head + * + * @param list the doubly-linked list to operate on + * @param node the node to check + * + * @return 1 if node is the head, 0 otherwise + */ + +static inline int sys_dlist_is_head(sys_dlist_t *list, sys_dnode_t *node) +{ + return list->head == node; +} + +/** + * @brief check if a node is the list's tail + * + * @param list the doubly-linked list to operate on + * @param node the node to check + * + * @return 1 if node is the tail, 0 otherwise + */ + +static inline int sys_dlist_is_tail(sys_dlist_t *list, sys_dnode_t *node) +{ + return list->tail == node; +} + +/** + * @brief check if the list is empty + * + * @param list the doubly-linked list to operate on + * + * @return 1 if empty, 0 otherwise + */ + +static inline int sys_dlist_is_empty(sys_dlist_t *list) +{ + return list->head == list; +} + +/** + * @brief check if more than one node present + * + * This and other sys_dlist_*() functions are not thread safe. + * + * @param list the doubly-linked list to operate on + * + * @return 1 if multiple nodes, 0 otherwise + */ + +static inline int sys_dlist_has_multiple_nodes(sys_dlist_t *list) +{ + return list->head != list->tail; +} + +/** + * @brief get a reference to the head item in the list + * + * @param list the doubly-linked list to operate on + * + * @return a pointer to the head element, NULL if list is empty + */ + +static inline sys_dnode_t *sys_dlist_peek_head(sys_dlist_t *list) +{ + return sys_dlist_is_empty(list) ? NULL : list->head; +} + +/** + * @brief get a reference to the head item in the list + * + * The list must be known to be non-empty. + * + * @param list the doubly-linked list to operate on + * + * @return a pointer to the head element + */ + +static inline sys_dnode_t *sys_dlist_peek_head_not_empty(sys_dlist_t *list) +{ + return list->head; +} + +/** + * @brief get a reference to the next item in the list, node is not NULL + * + * Faster than sys_dlist_peek_next() if node is known not to be NULL. + * + * @param list the doubly-linked list to operate on + * @param node the node from which to get the next element in the list + * + * @return a pointer to the next element from a node, NULL if node is the tail + */ + +static inline sys_dnode_t *sys_dlist_peek_next_no_check(sys_dlist_t *list, + sys_dnode_t *node) +{ + return (node == list->tail) ? NULL : node->next; +} + +/** + * @brief get a reference to the next item in the list + * + * @param list the doubly-linked list to operate on + * @param node the node from which to get the next element in the list + * + * @return a pointer to the next element from a node, NULL if node is the tail + * or NULL (when node comes from reading the head of an empty list). + */ + +static inline sys_dnode_t *sys_dlist_peek_next(sys_dlist_t *list, + sys_dnode_t *node) +{ + return node ? sys_dlist_peek_next_no_check(list, node) : NULL; +} + +/** + * @brief get a reference to the tail item in the list + * + * @param list the doubly-linked list to operate on + * + * @return a pointer to the tail element, NULL if list is empty + */ + +static inline sys_dnode_t *sys_dlist_peek_tail(sys_dlist_t *list) +{ + return sys_dlist_is_empty(list) ? NULL : list->tail; +} + +/** + * @brief add node to tail of list + * + * This and other sys_dlist_*() functions are not thread safe. + * + * @param list the doubly-linked list to operate on + * @param node the element to append + * + * @return N/A + */ + +static inline void sys_dlist_append(sys_dlist_t *list, sys_dnode_t *node) +{ + node->next = list; + node->prev = list->tail; + + list->tail->next = node; + list->tail = node; +} + +/** + * @brief add node to head of list + * + * This and other sys_dlist_*() functions are not thread safe. + * + * @param list the doubly-linked list to operate on + * @param node the element to append + * + * @return N/A + */ + +static inline void sys_dlist_prepend(sys_dlist_t *list, sys_dnode_t *node) +{ + node->next = list->head; + node->prev = list; + + list->head->prev = node; + list->head = node; +} + +/** + * @brief insert node after a node + * + * Insert a node after a specified node in a list. + * This and other sys_dlist_*() functions are not thread safe. + * + * @param list the doubly-linked list to operate on + * @param insert_point the insert point in the list: if NULL, insert at head + * @param node the element to append + * + * @return N/A + */ + +static inline void sys_dlist_insert_after(sys_dlist_t *list, + sys_dnode_t *insert_point, sys_dnode_t *node) +{ + if (!insert_point) { + sys_dlist_prepend(list, node); + } else { + node->next = insert_point->next; + node->prev = insert_point; + insert_point->next->prev = node; + insert_point->next = node; + } +} + +/** + * @brief insert node before a node + * + * Insert a node before a specified node in a list. + * This and other sys_dlist_*() functions are not thread safe. + * + * @param list the doubly-linked list to operate on + * @param insert_point the insert point in the list: if NULL, insert at tail + * @param node the element to insert + * + * @return N/A + */ + +static inline void sys_dlist_insert_before(sys_dlist_t *list, + sys_dnode_t *insert_point, sys_dnode_t *node) +{ + if (!insert_point) { + sys_dlist_append(list, node); + } else { + node->prev = insert_point->prev; + node->next = insert_point; + insert_point->prev->next = node; + insert_point->prev = node; + } +} + +/** + * @brief insert node at position + * + * Insert a node in a location depending on a external condition. The cond() + * function checks if the node is to be inserted _before_ the current node + * against which it is checked. + * This and other sys_dlist_*() functions are not thread safe. + * + * @param list the doubly-linked list to operate on + * @param node the element to insert + * @param cond a function that determines if the current node is the correct + * insert point + * @param data parameter to cond() + * + * @return N/A + */ + +static inline void sys_dlist_insert_at(sys_dlist_t *list, sys_dnode_t *node, + int (*cond)(sys_dnode_t *, void *), void *data) +{ + if (sys_dlist_is_empty(list)) { + sys_dlist_append(list, node); + } else { + sys_dnode_t *pos = sys_dlist_peek_head(list); + + while (pos && !cond(pos, data)) { + pos = sys_dlist_peek_next(list, pos); + } + sys_dlist_insert_before(list, pos, node); + } +} + +/** + * @brief remove a specific node from a list + * + * The list is implicit from the node. The node must be part of a list. + * This and other sys_dlist_*() functions are not thread safe. + * + * @param node the node to remove + * + * @return N/A + */ + +static inline void sys_dlist_remove(sys_dnode_t *node) +{ + node->prev->next = node->next; + node->next->prev = node->prev; +} + +/** + * @brief get the first node in a list + * + * This and other sys_dlist_*() functions are not thread safe. + * + * @param list the doubly-linked list to operate on + * + * @return the first node in the list, NULL if list is empty + */ + +static inline sys_dnode_t *sys_dlist_get(sys_dlist_t *list) +{ + sys_dnode_t *node; + + if (sys_dlist_is_empty(list)) { + return NULL; + } + + node = list->head; + sys_dlist_remove(node); + return node; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_DLIST_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_hci.h b/components/bt/ble_mesh/mesh_core/include/mesh_hci.h new file mode 100644 index 0000000000..1c3e093e0b --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_hci.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2015-2016 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BLE_MESH_HCI_H_ +#define _BLE_MESH_HCI_H_ + +#include "mesh_kernel.h" +#include "mesh_bearer_adapt.h" +#include "mesh_atomic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Porting form zephyr/subsys/bluetooth/host/hci_core.h */ + +#define BLE_MESH_LMP_FEAT_PAGES_COUNT 1 + +/* bt_mesh_dev flags: the flags defined here represent BT controller state */ +enum { + BLE_MESH_DEV_ENABLE, + BLE_MESH_DEV_READY, + BLE_MESH_DEV_ID_STATIC_RANDOM, + BLE_MESH_DEV_HAS_PUB_KEY, + BLE_MESH_DEV_PUB_KEY_BUSY, + + BLE_MESH_DEV_ADVERTISING, + BLE_MESH_DEV_KEEP_ADVERTISING, + BLE_MESH_DEV_SCANNING, + BLE_MESH_DEV_EXPLICIT_SCAN, + BLE_MESH_DEV_ACTIVE_SCAN, + BLE_MESH_DEV_SCAN_FILTER_DUP, + + BLE_MESH_DEV_RPA_VALID, + + BLE_MESH_DEV_ID_PENDING, + + /* Total number of flags - must be at the end of the enum */ + BLE_MESH_DEV_NUM_FLAGS, +}; + +struct bt_mesh_dev_le { + /* LE features */ + u8_t features[8]; + + /* LE states */ + u64_t states; +}; + +/* State tracking for the local Bluetooth controller */ +struct bt_mesh_dev { + /* Flags indicate which functionality is enabled */ + BLE_MESH_ATOMIC_DEFINE(flags, BLE_MESH_DEV_NUM_FLAGS); + + /* Controller version & manufacturer information */ + u8_t hci_version; + u8_t lmp_version; + u16_t hci_revision; + u16_t lmp_subversion; + u16_t manufacturer; + + /* LMP features (pages 0, 1, 2) */ + u8_t features[BLE_MESH_LMP_FEAT_PAGES_COUNT][8]; + + /* LE controller specific features */ + struct bt_mesh_dev_le le; +}; + +/*Porting from zephyr/subsys/bluetooth/host/hci_core.h */ +/* HCI version from Assigned Numbers */ +#define BLE_MESH_HCI_VERSION_1_0B 0 +#define BLE_MESH_HCI_VERSION_1_1 1 +#define BLE_MESH_HCI_VERSION_1_2 2 +#define BLE_MESH_HCI_VERSION_2_0 3 +#define BLE_MESH_HCI_VERSION_2_1 4 +#define BLE_MESH_HCI_VERSION_3_0 5 +#define BLE_MESH_HCI_VERSION_4_0 6 +#define BLE_MESH_HCI_VERSION_4_1 7 +#define BLE_MESH_HCI_VERSION_4_2 8 +#define BLE_MESH_HCI_VERSION_5_0 9 + +/* OpCode Group Fields */ +#define BLE_MESH_OGF_LINK_CTRL 0x01 +#define BLE_MESH_OGF_BASEBAND 0x03 +#define BLE_MESH_OGF_INFO 0x04 +#define BLE_MESH_OGF_STATUS 0x05 +#define BLE_MESH_OGF_LE 0x08 +#define BLE_MESH_OGF_VS 0x3f + +/* Construct OpCode from OGF and OCF */ +#define BLE_MESH_OP(ogf, ocf) ((ocf) | ((ogf) << 10)) + +/* Obtain OGF from OpCode */ +#define BLE_MESH_OGF(opcode) (((opcode) >> 10) & BIT_MASK(6)) + +/* Obtain OCF from OpCode */ +#define BLE_MESH_OCF(opcode) ((opcode) & BIT_MASK(10)) + +#define BLE_MESH_HCI_OP_SET_ADV_PARAM BLE_MESH_OP(BLE_MESH_OGF_LE, 0x0006) +struct bt_mesh_hci_cp_set_adv_param { + u16_t min_interval; + u16_t max_interval; + u8_t type; + u8_t own_addr_type; + bt_mesh_addr_t direct_addr; + u8_t channel_map; + u8_t filter_policy; +} __packed; + +#define BLE_MESH_HCI_OP_SET_ADV_DATA BLE_MESH_OP(BLE_MESH_OGF_LE, 0x0008) +struct bt_mesh_hci_cp_set_adv_data { + u8_t len; + u8_t data[31]; +} __packed; + +#define BLE_MESH_HCI_OP_SET_SCAN_RSP_DATA BLE_MESH_OP(BLE_MESH_OGF_LE, 0x0009) +struct bt_mesh_hci_cp_set_scan_rsp_data { + u8_t len; + u8_t data[31]; +} __packed; + +/* Added by Espressif */ +extern struct bt_mesh_dev bt_mesh_dev; + +void bt_mesh_hci_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_HCI_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_kernel.h b/components/bt/ble_mesh/mesh_core/include/mesh_kernel.h new file mode 100644 index 0000000000..785b8b8dfb --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_kernel.h @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2016, Wind River Systems, Inc. + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BLE_MESH_KERNEL_H_ +#define _BLE_MESH_KERNEL_H_ + +#include "osi/mutex.h" +#include "mesh_types.h" +#include "mesh_slist.h" +#include "mesh_atomic.h" +#include "mesh_dlist.h" + +/* number of nsec per usec */ +#define NSEC_PER_USEC 1000 + +/* number of microseconds per millisecond */ +#define USEC_PER_MSEC 1000 + +/* number of milliseconds per second */ +#define MSEC_PER_SEC 1000 + +/* number of microseconds per second */ +#define USEC_PER_SEC ((USEC_PER_MSEC) * (MSEC_PER_SEC)) + +/* number of nanoseconds per second */ +#define NSEC_PER_SEC ((NSEC_PER_USEC) * (USEC_PER_MSEC) * (MSEC_PER_SEC)) + +/* timeout is not in use */ +#define _INACTIVE (-1) + +struct k_work; + +/** + * @typedef k_work_handler_t + * @brief Work item handler function type. + * + * A work item's handler function is executed by a workqueue's thread + * when the work item is processed by the workqueue. + * + * @param work Address of the work item. + * + * @return N/A + */ +typedef void (*k_work_handler_t)(struct k_work *work); + +typedef sys_dlist_t _wait_q_t; + +struct k_work { + void *_reserved; + k_work_handler_t handler; + int index; +}; + +#define _K_WORK_INITIALIZER(work_handler) \ +{ \ + ._reserved = NULL, \ + .handler = work_handler, \ +} + +/** + * @brief Generate null timeout delay. + * + * This macro generates a timeout delay that that instructs a kernel API + * not to wait if the requested operation cannot be performed immediately. + * + * @return Timeout delay value. + */ +#define K_NO_WAIT 0 + +/** + * @brief Generate timeout delay from milliseconds. + * + * This macro generates a timeout delay that that instructs a kernel API + * to wait up to @a ms milliseconds to perform the requested operation. + * + * @param ms Duration in milliseconds. + * + * @return Timeout delay value. + */ +#define K_MSEC(ms) (ms) + +/** + * @brief Generate timeout delay from seconds. + * + * This macro generates a timeout delay that that instructs a kernel API + * to wait up to @a s seconds to perform the requested operation. + * + * @param s Duration in seconds. + * + * @return Timeout delay value. + */ +#define K_SECONDS(s) K_MSEC((s) * MSEC_PER_SEC) + +/** + * @brief Generate timeout delay from minutes. + * + * This macro generates a timeout delay that that instructs a kernel API + * to wait up to @a m minutes to perform the requested operation. + * + * @param m Duration in minutes. + * + * @return Timeout delay value. + */ +#define K_MINUTES(m) K_SECONDS((m) * 60) + +/** + * @brief Generate timeout delay from hours. + * + * This macro generates a timeout delay that that instructs a kernel API + * to wait up to @a h hours to perform the requested operation. + * + * @param h Duration in hours. + * + * @return Timeout delay value. + */ +#define K_HOURS(h) K_MINUTES((h) * 60) + +/** + * @brief Generate infinite timeout delay. + * + * This macro generates a timeout delay that that instructs a kernel API + * to wait as long as necessary to perform the requested operation. + * + * @return Timeout delay value. + */ +#define K_FOREVER (-1) + +/** + * @brief Get system uptime (32-bit version). + * + * This routine returns the lower 32-bits of the elapsed time since the system + * booted, in milliseconds. + * + * This routine can be more efficient than k_uptime_get(), as it reduces the + * need for interrupt locking and 64-bit math. However, the 32-bit result + * cannot hold a system uptime time larger than approximately 50 days, so the + * caller must handle possible rollovers. + * + * @return Current uptime. + */ +u32_t k_uptime_get_32(void); + +struct k_delayed_work { + struct k_work work; +}; + +/** + * @brief Submit a delayed work item to the system workqueue. + * + * This routine schedules work item @a work to be processed by the system + * workqueue after a delay of @a delay milliseconds. The routine initiates + * an asynchronous countdown for the work item and then returns to the caller. + * Only when the countdown completes is the work item actually submitted to + * the workqueue and becomes pending. + * + * Submitting a previously submitted delayed work item that is still + * counting down cancels the existing submission and restarts the countdown + * using the new delay. If the work item is currently pending on the + * workqueue's queue because the countdown has completed it is too late to + * resubmit the item, and resubmission fails without impacting the work item. + * If the work item has already been processed, or is currently being processed, + * its work is considered complete and the work item can be resubmitted. + * + * @warning + * Work items submitted to the system workqueue should avoid using handlers + * that block or yield since this may prevent the system workqueue from + * processing other work items in a timely manner. + * + * @note Can be called by ISRs. + * + * @param work Address of delayed work item. + * @param delay Delay before submitting the work item (in milliseconds). + * + * @retval 0 Work item countdown started. + * @retval -EINPROGRESS Work item is already pending. + * @retval -EINVAL Work item is being processed or has completed its work. + * @retval -EADDRINUSE Work item is pending on a different workqueue. + */ +int k_delayed_work_submit(struct k_delayed_work *work, s32_t delay); + +/** + * @brief Get time remaining before a delayed work gets scheduled. + * + * This routine computes the (approximate) time remaining before a + * delayed work gets executed. If the delayed work is not waiting to be + * scheduled, it returns zero. + * + * @param work Delayed work item. + * + * @return Remaining time (in milliseconds). + */ +s32_t k_delayed_work_remaining_get(struct k_delayed_work *work); + +/** + * @brief Submit a work item to the system workqueue. + * + * This routine submits work item @a work to be processed by the system + * workqueue. If the work item is already pending in the workqueue's queue + * as a result of an earlier submission, this routine has no effect on the + * work item. If the work item has already been processed, or is currently + * being processed, its work is considered complete and the work item can be + * resubmitted. + * + * @warning + * Work items submitted to the system workqueue should avoid using handlers + * that block or yield since this may prevent the system workqueue from + * processing other work items in a timely manner. + * + * @note Can be called by ISRs. + * + * @param work Address of work item. + * + * @return N/A + */ +static inline void k_work_submit(struct k_work *work) +{ + if (work && work->handler) { + work->handler(work); + } +} + +/** + * @brief Initialize a work item. + * + * This routine initializes a workqueue work item, prior to its first use. + * + * @param work Address of work item. + * @param handler Function to invoke each time work item is processed. + * + * @return N/A + */ +static inline void k_work_init(struct k_work *work, k_work_handler_t handler) +{ + work->handler = handler; +} + +int k_delayed_work_cancel(struct k_delayed_work *work); + +int k_delayed_work_free(struct k_delayed_work *work); + +void k_delayed_work_init(struct k_delayed_work *work, k_work_handler_t handler); + +/** + * @brief Get system uptime. + * + * This routine returns the elapsed time since the system booted, + * in milliseconds. + * + * @return Current uptime. + */ +s64_t k_uptime_get(void); + +/** + * @brief Put the current thread to sleep. + * + * This routine puts the current thread to sleep for @a duration + * milliseconds. + * + * @param duration Number of milliseconds to sleep. + * + * @return N/A + */ +void k_sleep(s32_t duration); + +unsigned int bt_mesh_irq_lock(void); +void bt_mesh_irq_unlock(unsigned int key); + +void bt_mesh_k_init(void); + +#endif /* _BLE_MESH_KERNEL_H_ */ + diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_main.h b/components/bt/ble_mesh/mesh_core/include/mesh_main.h new file mode 100644 index 0000000000..374c6a0770 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_main.h @@ -0,0 +1,584 @@ +/** @file + * @brief Bluetooth Mesh Profile APIs. + */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BLE_MESH_MAIN_H_ +#define _BLE_MESH_MAIN_H_ + +#include "mesh_util.h" +#include "mesh_access.h" + +/** + * @brief Bluetooth Mesh Provisioning + * @defgroup bt_mesh_prov Bluetooth Mesh Provisioning + * @ingroup bt_mesh + * @{ + */ + +typedef enum { + BLE_MESH_NO_OUTPUT = 0, + BLE_MESH_BLINK = BIT(0), + BLE_MESH_BEEP = BIT(1), + BLE_MESH_VIBRATE = BIT(2), + BLE_MESH_DISPLAY_NUMBER = BIT(3), + BLE_MESH_DISPLAY_STRING = BIT(4), +} bt_mesh_output_action_t; + +typedef enum { + BLE_MESH_NO_INPUT = 0, + BLE_MESH_PUSH = BIT(0), + BLE_MESH_TWIST = BIT(1), + BLE_MESH_ENTER_NUMBER = BIT(2), + BLE_MESH_ENTER_STRING = BIT(3), +} bt_mesh_input_action_t; + +typedef enum { + BLE_MESH_PROV_ADV = BIT(0), + BLE_MESH_PROV_GATT = BIT(1), +} bt_mesh_prov_bearer_t; + +typedef enum { + BLE_MESH_PROV_OOB_OTHER = BIT(0), + BLE_MESH_PROV_OOB_URI = BIT(1), + BLE_MESH_PROV_OOB_2D_CODE = BIT(2), + BLE_MESH_PROV_OOB_BAR_CODE = BIT(3), + BLE_MESH_PROV_OOB_NFC = BIT(4), + BLE_MESH_PROV_OOB_NUMBER = BIT(5), + BLE_MESH_PROV_OOB_STRING = BIT(6), + /* 7 - 10 are reserved */ + BLE_MESH_PROV_OOB_ON_BOX = BIT(11), + BLE_MESH_PROV_OOB_IN_BOX = BIT(12), + BLE_MESH_PROV_OOB_ON_PAPER = BIT(13), + BLE_MESH_PROV_OOB_IN_MANUAL = BIT(14), + BLE_MESH_PROV_OOB_ON_DEV = BIT(15), +} bt_mesh_prov_oob_info_t; + +/** Provisioning properties & capabilities. */ +struct bt_mesh_prov { +#if CONFIG_BLE_MESH_NODE + /** The UUID that's used when advertising as unprovisioned */ + const u8_t *uuid; + + /** Optional URI. This will be advertised separately from the + * unprovisioned beacon, however the unprovisioned beacon will + * contain a hash of it so the two can be associated by the + * provisioner. + */ + const char *uri; + + /** Out of Band information field. */ + bt_mesh_prov_oob_info_t oob_info; + + /** Flag indicates whether unprovisioned devices support OOB public key */ + bool oob_pub_key; + + /** @brief Set device OOB public key. + * + * This callback notifies the application to + * set OOB public key & private key pair. + */ + void (*oob_pub_key_cb)(void); + + /** Static OOB value */ + const u8_t *static_val; + /** Static OOB value length */ + u8_t static_val_len; + + /** Maximum size of Output OOB supported */ + u8_t output_size; + /** Supported Output OOB Actions */ + u16_t output_actions; + + /* Maximum size of Input OOB supported */ + u8_t input_size; + /** Supported Input OOB Actions */ + u16_t input_actions; + + /** @brief Output of a number is requested. + * + * This callback notifies the application to + * output the given number using the given action. + * + * @param act Action for outputting the number. + * @param num Number to be out-put. + * + * @return Zero on success or negative error code otherwise + */ + int (*output_number)(bt_mesh_output_action_t act, u32_t num); + + /** @brief Output of a string is requested. + * + * This callback notifies the application to + * display the given string to the user. + * + * @param str String to be displayed. + * + * @return Zero on success or negative error code otherwise + */ + int (*output_string)(const char *str); + + /** @brief Input is requested. + * + * This callback notifies the application to request + * input from the user using the given action. The + * requested input will either be a string or a number, and + * the application needs to consequently call the + * bt_mesh_input_string() or bt_mesh_input_number() functions + * once the data has been acquired from the user. + * + * @param act Action for inputting data. + * @param num Maximum size of the in-put data. + * + * @return Zero on success or negative error code otherwise + */ + int (*input)(bt_mesh_input_action_t act, u8_t size); + + /** @brief Provisioning link has been opened. + * + * This callback notifies the application that a provisioning + * link has been opened on the given provisioning bearer. + * + * @param bearer Provisioning bearer. + */ + void (*link_open)(bt_mesh_prov_bearer_t bearer); + + /** @brief Provisioning link has been closed. + * + * This callback notifies the application that a provisioning + * link has been closed on the given provisioning bearer. + * + * @param bearer Provisioning bearer. + */ + void (*link_close)(bt_mesh_prov_bearer_t bearer); + + /** @brief Provisioning is complete. + * + * This callback notifies the application that provisioning has + * been successfully completed, and that the local node has been + * assigned the specified NetKeyIndex and primary element address. + * + * @param net_idx NetKeyIndex given during provisioning. + * @param addr Primary element address. + * @param flags Key Refresh & IV Update flags + * @param iv_index IV Index. + */ + void (*complete)(u16_t net_idx, u16_t addr, u8_t flags, u32_t iv_index); + + /** @brief Node has been reset. + * + * This callback notifies the application that the local node + * has been reset and needs to be reprovisioned. The node will + * not automatically advertise as unprovisioned, rather the + * bt_mesh_prov_enable() API needs to be called to enable + * unprovisioned advertising on one or more provisioning bearers. + */ + void (*reset)(void); +#endif /* CONFIG_BLE_MESH_NODE */ + +#if CONFIG_BLE_MESH_PROVISIONER + /* Provisioner device uuid */ + const u8_t *prov_uuid; + + /* + * Primary element address of the provisioner. + * No need to initialize it for fast provisioning. + */ + const u16_t prov_unicast_addr; + + /* + * Starting unicast address going to assigned. + * No need to initialize it for fast provisioning. + */ + u16_t prov_start_address; + + /* Attention timer contained in Provisioning Invite */ + u8_t prov_attention; + + /* Provisioner provisioning Algorithm */ + u8_t prov_algorithm; + + /* Provisioner public key oob */ + u8_t prov_pub_key_oob; + + /** @brief Input is requested. + * + * This callback notifies the application that it should + * read device's public key with OOB + * + * @param link_idx: The provisioning link index + * + * @return Zero on success or negative error code otherwise + */ + int (*prov_pub_key_oob_cb)(u8_t link_idx); + + /* Provisioner static oob value */ + u8_t *prov_static_oob_val; + + /* Provisioner static oob value length */ + u8_t prov_static_oob_len; + + /** @brief Provisioner input a number read from device output + * + * This callback notifies the application that it should + * input the number given by the device. + * + * @param method: The OOB authentication method + * @param act: The output action of the device + * @param size: The output size of the device + * @param link_idx: The provisioning link index + * + * @return Zero on success or negative error code otherwise + */ + int (*prov_input_num)(u8_t method, bt_mesh_output_action_t act, u8_t size, u8_t link_idx); + + /** @brief Provisioner output a number to the device + * + * This callback notifies the application that it should + * output the number to the device. + * + * @param method: The OOB authentication method + * @param act: The input action of the device + * @param data: The input number/string of the device + * @param size: The input size of the device + * @param link_idx: The provisioning link index + * + * @return Zero on success or negative error code otherwise + */ + int (*prov_output_num)(u8_t method, bt_mesh_input_action_t act, void *data, u8_t size, u8_t link_idx); + + /* + * Key refresh and IV update flag. + * No need to initialize it for fast provisioning. + */ + u8_t flags; + + /* + * IV index. No need to initialize it for fast provisioning. + */ + u32_t iv_index; + + /** @brief Provisioner has opened a provisioning link. + * + * This callback notifies the application that a provisioning + * link has been opened on the given provisioning bearer. + * + * @param bearer Provisioning bearer. + */ + void (*prov_link_open)(bt_mesh_prov_bearer_t bearer); + + /** @brief Provisioner has closed a provisioning link. + * + * This callback notifies the application that a provisioning + * link has been closed on the given provisioning bearer. + * + * @param bearer Provisioning bearer. + * @param reason Provisioning link close reason(disconnect reason) + * 0xFF: disconnect due to provisioner_pb_gatt_disable() + */ + void (*prov_link_close)(bt_mesh_prov_bearer_t bearer, u8_t reason); + + /** @brief Provision one device is complete. + * + * This callback notifies the application that provisioner has + * successfully provisioned a device, and the node has been assigned + * the specified NetKeyIndex and primary element address. + * + * @param node_idx Node index within the node(provisioned device) queue. + * @param device_uuid Provisioned device uuid pointer. + * @param unicast_addr Provisioned device assigned unicast address. + * @param element_num Provisioned device element number. + * @param netkey_idx Provisioned device assigned netkey index. + */ + void (*prov_complete)(int node_idx, const u8_t device_uuid[16], + u16_t unicast_addr, u8_t element_num, + u16_t netkey_idx); +#endif /* CONFIG_BLE_MESH_PROVISIONER */ +}; + +/* The following APIs are for BLE Mesh Node */ + +/** @brief Provide provisioning input OOB string. + * + * This is intended to be called after the bt_mesh_prov input callback + * has been called with BLE_MESH_ENTER_STRING as the action. + * + * @param str String. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_input_string(const char *str); + +/** @brief Provide provisioning input OOB number. + * + * This is intended to be called after the bt_mesh_prov input callback + * has been called with BLE_MESH_ENTER_NUMBER as the action. + * + * @param num Number. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_input_number(u32_t num); + +/** @brief Enable specific provisioning bearers + * + * Enable one or more provisioning bearers. + * + * @param Bit-wise OR of provisioning bearers. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_prov_enable(bt_mesh_prov_bearer_t bearers); + +/** @brief Disable specific provisioning bearers + * + * Disable one or more provisioning bearers. + * + * @param Bit-wise OR of provisioning bearers. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_prov_disable(bt_mesh_prov_bearer_t bearers); + +/** @brief Indicate whether provisioner is enabled + * + * @return true - enabled, false - disabled. + */ +bool bt_mesh_is_provisioner_en(void); + +/* The following API is for BLE Mesh Fast Provisioning */ + +/** @brief Change the device action + * + * @param[IN] action: role of device to be set + * 0x01 - enter, 0x02 - suspend, 0x03 - exit + * + * @return status + */ +u8_t bt_mesh_set_fast_prov_action(u8_t action); + +/* The following APIs are for BLE Mesh Provisioner */ + +/** @brief Provide provisioning input OOB string. + * + * This is intended to be called after the bt_mesh_prov input callback + * has been called with BLE_MESH_ENTER_STRING as the action. + * + * @param str String. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_prov_input_string(const char *str); + +/** @brief Provide provisioning input OOB number. + * + * This is intended to be called after the bt_mesh_prov input callback + * has been called with BLE_MESH_ENTER_NUMBER as the action. + * + * @param num Number. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_prov_input_number(u32_t num); + +/** @brief Enable specific provisioning bearers + * + * Enable one or more provisioning bearers. + * + * @param bearers Bit-wise OR of provisioning bearers. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_provisioner_enable(bt_mesh_prov_bearer_t bearers); + +/** @brief Disable specific provisioning bearers + * + * Disable one or more provisioning bearers. + * + * @param bearers Bit-wise OR of provisioning bearers. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_provisioner_disable(bt_mesh_prov_bearer_t bearers); + +/** + * @} + */ + +/** + * @brief Bluetooth Mesh + * @defgroup bt_mesh Bluetooth Mesh + * @ingroup bluetooth + * @{ + */ + +/* Primary Network Key index */ +#define BLE_MESH_NET_PRIMARY 0x000 + +#define BLE_MESH_RELAY_DISABLED 0x00 +#define BLE_MESH_RELAY_ENABLED 0x01 +#define BLE_MESH_RELAY_NOT_SUPPORTED 0x02 + +#define BLE_MESH_BEACON_DISABLED 0x00 +#define BLE_MESH_BEACON_ENABLED 0x01 + +#define BLE_MESH_GATT_PROXY_DISABLED 0x00 +#define BLE_MESH_GATT_PROXY_ENABLED 0x01 +#define BLE_MESH_GATT_PROXY_NOT_SUPPORTED 0x02 + +#define BLE_MESH_FRIEND_DISABLED 0x00 +#define BLE_MESH_FRIEND_ENABLED 0x01 +#define BLE_MESH_FRIEND_NOT_SUPPORTED 0x02 + +#define BLE_MESH_NODE_IDENTITY_STOPPED 0x00 +#define BLE_MESH_NODE_IDENTITY_RUNNING 0x01 +#define BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED 0x02 + +/* Features */ +#define BLE_MESH_FEAT_RELAY BIT(0) +#define BLE_MESH_FEAT_PROXY BIT(1) +#define BLE_MESH_FEAT_FRIEND BIT(2) +#define BLE_MESH_FEAT_LOW_POWER BIT(3) +#define BLE_MESH_FEAT_SUPPORTED (BLE_MESH_FEAT_RELAY | \ + BLE_MESH_FEAT_PROXY | \ + BLE_MESH_FEAT_FRIEND | \ + BLE_MESH_FEAT_LOW_POWER) + +/** @brief Initialize Mesh support + * + * After calling this API, the node will not automatically advertise as + * unprovisioned, rather the bt_mesh_prov_enable() API needs to be called + * to enable unprovisioned advertising on one or more provisioning bearers. + * + * @param prov Node provisioning information. + * @param comp Node Composition. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_init(const struct bt_mesh_prov *prov, + const struct bt_mesh_comp *comp); + +/** @brief Reset the state of the local Mesh node. + * + * Resets the state of the node, which means that it needs to be + * reprovisioned to become an active node in a Mesh network again. + * + * After calling this API, the node will not automatically advertise as + * unprovisioned, rather the bt_mesh_prov_enable() API needs to be called + * to enable unprovisioned advertising on one or more provisioning bearers. + * + */ +void bt_mesh_reset(void); + +/** @brief Suspend the Mesh network temporarily. + * + * This API can be used for power saving purposes, but the user should be + * aware that leaving the local node suspended for a long period of time + * may cause it to become permanently disconnected from the Mesh network. + * If at all possible, the Friendship feature should be used instead, to + * make the node into a Low Power Node. + * + * @return 0 on success, or (negative) error code on failure. + */ +int bt_mesh_suspend(void); + +/** @brief Resume a suspended Mesh network. + * + * This API resumes the local node, after it has been suspended using the + * bt_mesh_suspend() API. + * + * @return 0 on success, or (negative) error code on failure. + */ +int bt_mesh_resume(void); + +/** @brief Provision the local Mesh Node. + * + * This API should normally not be used directly by the application. The + * only exception is for testing purposes where manual provisioning is + * desired without an actual external provisioner. + * + * @param net_key Network Key + * @param net_idx Network Key Index + * @param flags Provisioning Flags + * @param iv_index IV Index + * @param addr Primary element address + * @param dev_key Device Key + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_provision(const u8_t net_key[16], u16_t net_idx, + u8_t flags, u32_t iv_index, u16_t addr, + const u8_t dev_key[16]); + +/** @brief Check if the local node has been provisioned. + * + * This API can be used to check if the local node has been provisioned + * or not. It can e.g. be helpful to determine if there was a stored + * network in flash, i.e. if the network was restored after calling + * settings_load(). + * + * @return True if the node is provisioned. False otherwise. + */ +bool bt_mesh_is_provisioned(void); + +/** @brief Toggle the IV Update test mode + * + * This API is only available if the IV Update test mode has been enabled + * in Kconfig. It is needed for passing most of the IV Update qualification + * test cases. + * + * @param enable true to enable IV Update test mode, false to disable it. + */ +void bt_mesh_iv_update_test(bool enable); + +/** @brief Toggle the IV Update state + * + * This API is only available if the IV Update test mode has been enabled + * in Kconfig. It is needed for passing most of the IV Update qualification + * test cases. + * + * @return true if IV Update In Progress state was entered, false otherwise. + */ +bool bt_mesh_iv_update(void); + +/** @brief Toggle the Low Power feature of the local device + * + * Enables or disables the Low Power feature of the local device. This is + * exposed as a run-time feature, since the device might want to change + * this e.g. based on being plugged into a stable power source or running + * from a battery power source. + * + * @param enable true to enable LPN functionality, false to disable it. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_lpn_set(bool enable); + +/** @brief Send out a Friend Poll message. + * + * Send a Friend Poll message to the Friend of this node. If there is no + * established Friendship the function will return an error. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_lpn_poll(void); + +/** @brief Register a callback for Friendship changes. + * + * Registers a callback that will be called whenever Friendship gets + * established or is lost. + * + * @param cb Function to call when the Friendship status changes. + */ +void bt_mesh_lpn_set_cb(void (*cb)(u16_t friend_addr, bool established)); + +/** + * @} + */ + +#endif /* _BLE_MESH_MAIN_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_proxy.h b/components/bt/ble_mesh/mesh_core/include/mesh_proxy.h new file mode 100644 index 0000000000..0b14d9e184 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_proxy.h @@ -0,0 +1,37 @@ +/** @file + * @brief Bluetooth Mesh Proxy APIs. + */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BLE_MESH_PROXY_H_ +#define _BLE_MESH_PROXY_H_ + +#include +/** + * @brief Bluetooth Mesh Proxy + * @defgroup bt_mesh_proxy Bluetooth Mesh Proxy + * @ingroup bt_mesh + * @{ + */ + +/** + * @brief Enable advertising with Node Identity. + * + * This API requires that GATT Proxy support has been enabled. Once called + * each subnet will start advertising using Node Identity for the next + * 60 seconds. + * + * @return 0 on success, or (negative) error code on failure. + */ +int bt_mesh_proxy_identity_enable(void); + +/** + * @} + */ + +#endif /* _BLE_MESH_PROXY_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_slist.h b/components/bt/ble_mesh/mesh_core/include/mesh_slist.h new file mode 100644 index 0000000000..4ddf427e6a --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_slist.h @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * + * @brief Single-linked list implementation + * + * Single-linked list implementation using inline macros/functions. + * This API is not thread safe, and thus if a list is used across threads, + * calls to functions must be protected with synchronization primitives. + */ + +#ifndef _BLE_MESH_SLIST_H_ +#define _BLE_MESH_SLIST_H_ + +#include +#include +#include "mesh_util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct _snode { + struct _snode *next; +}; + +typedef struct _snode sys_snode_t; + +struct _slist { + sys_snode_t *head; + sys_snode_t *tail; +}; + +typedef struct _slist sys_slist_t; + +/** + * @brief Provide the primitive to iterate on a list + * Note: the loop is unsafe and thus __sn should not be removed + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_SLIST_FOR_EACH_NODE(l, n) { + * + * } + * + * This and other SYS_SLIST_*() macros are not thread safe. + * + * @param __sl A pointer on a sys_slist_t to iterate on + * @param __sn A sys_snode_t pointer to peek each node of the list + */ +#define SYS_SLIST_FOR_EACH_NODE(__sl, __sn) \ + for (__sn = sys_slist_peek_head(__sl); __sn; \ + __sn = sys_slist_peek_next(__sn)) + +/** + * @brief Provide the primitive to iterate on a list, from a node in the list + * Note: the loop is unsafe and thus __sn should not be removed + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_SLIST_ITERATE_FROM_NODE(l, n) { + * + * } + * + * Like SYS_SLIST_FOR_EACH_NODE(), but __dn already contains a node in the list + * where to start searching for the next entry from. If NULL, it starts from + * the head. + * + * This and other SYS_SLIST_*() macros are not thread safe. + * + * @param __sl A pointer on a sys_slist_t to iterate on + * @param __sn A sys_snode_t pointer to peek each node of the list + * it contains the starting node, or NULL to start from the head + */ +#define SYS_SLIST_ITERATE_FROM_NODE(__sl, __sn) \ + for (__sn = __sn ? sys_slist_peek_next_no_check(__sn) \ + : sys_slist_peek_head(__sl); \ + __sn; \ + __sn = sys_slist_peek_next(__sn)) + +/** + * @brief Provide the primitive to safely iterate on a list + * Note: __sn can be removed, it will not break the loop. + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_SLIST_FOR_EACH_NODE_SAFE(l, n, s) { + * + * } + * + * This and other SYS_SLIST_*() macros are not thread safe. + * + * @param __sl A pointer on a sys_slist_t to iterate on + * @param __sn A sys_snode_t pointer to peek each node of the list + * @param __sns A sys_snode_t pointer for the loop to run safely + */ +#define SYS_SLIST_FOR_EACH_NODE_SAFE(__sl, __sn, __sns) \ + for (__sn = sys_slist_peek_head(__sl), \ + __sns = sys_slist_peek_next(__sn); \ + __sn; __sn = __sns, \ + __sns = sys_slist_peek_next(__sn)) + +/* + * @brief Provide the primitive to resolve the container of a list node + * Note: it is safe to use with NULL pointer nodes + * + * @param __ln A pointer on a sys_node_t to get its container + * @param __cn Container struct type pointer + * @param __n The field name of sys_node_t within the container struct + */ +#define SYS_SLIST_CONTAINER(__ln, __cn, __n) \ + ((__ln) ? CONTAINER_OF((__ln), __typeof__(*(__cn)), __n) : NULL) +/* + * @brief Provide the primitive to peek container of the list head + * + * @param __sl A pointer on a sys_slist_t to peek + * @param __cn Container struct type pointer + * @param __n The field name of sys_node_t within the container struct + */ +#define SYS_SLIST_PEEK_HEAD_CONTAINER(__sl, __cn, __n) \ + SYS_SLIST_CONTAINER(sys_slist_peek_head(__sl), __cn, __n) + +/* + * @brief Provide the primitive to peek container of the list tail + * + * @param __sl A pointer on a sys_slist_t to peek + * @param __cn Container struct type pointer + * @param __n The field name of sys_node_t within the container struct + */ +#define SYS_SLIST_PEEK_TAIL_CONTAINER(__sl, __cn, __n) \ + SYS_SLIST_CONTAINER(sys_slist_peek_tail(__sl), __cn, __n) + +/* + * @brief Provide the primitive to peek the next container + * + * @param __cn Container struct type pointer + * @param __n The field name of sys_node_t within the container struct + */ + +#define SYS_SLIST_PEEK_NEXT_CONTAINER(__cn, __n) \ + ((__cn) ? SYS_SLIST_CONTAINER(sys_slist_peek_next(&((__cn)->__n)), \ + __cn, __n) : NULL) + +/** + * @brief Provide the primitive to iterate on a list under a container + * Note: the loop is unsafe and thus __cn should not be detached + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_SLIST_FOR_EACH_CONTAINER(l, c, n) { + * + * } + * + * @param __sl A pointer on a sys_slist_t to iterate on + * @param __cn A pointer to peek each entry of the list + * @param __n The field name of sys_node_t within the container struct + */ +#define SYS_SLIST_FOR_EACH_CONTAINER(__sl, __cn, __n) \ + for (__cn = SYS_SLIST_PEEK_HEAD_CONTAINER(__sl, __cn, __n); __cn; \ + __cn = SYS_SLIST_PEEK_NEXT_CONTAINER(__cn, __n)) + +/** + * @brief Provide the primitive to safely iterate on a list under a container + * Note: __cn can be detached, it will not break the loop. + * + * User _MUST_ add the loop statement curly braces enclosing its own code: + * + * SYS_SLIST_FOR_EACH_NODE_SAFE(l, c, cn, n) { + * + * } + * + * @param __sl A pointer on a sys_slist_t to iterate on + * @param __cn A pointer to peek each entry of the list + * @param __cns A pointer for the loop to run safely + * @param __n The field name of sys_node_t within the container struct + */ +#define SYS_SLIST_FOR_EACH_CONTAINER_SAFE(__sl, __cn, __cns, __n) \ + for (__cn = SYS_SLIST_PEEK_HEAD_CONTAINER(__sl, __cn, __n), \ + __cns = SYS_SLIST_PEEK_NEXT_CONTAINER(__cn, __n); __cn; \ + __cn = __cns, __cns = SYS_SLIST_PEEK_NEXT_CONTAINER(__cn, __n)) + +/** + * @brief Initialize a list + * + * @param list A pointer on the list to initialize + */ +static inline void sys_slist_init(sys_slist_t *list) +{ + list->head = NULL; + list->tail = NULL; +} + +#define SYS_SLIST_STATIC_INIT(ptr_to_list) {NULL, NULL} + +/** + * @brief Test if the given list is empty + * + * @param list A pointer on the list to test + * + * @return a boolean, true if it's empty, false otherwise + */ +static inline bool sys_slist_is_empty(sys_slist_t *list) +{ + return (!list->head); +} + +/** + * @brief Peek the first node from the list + * + * @param list A point on the list to peek the first node from + * + * @return A pointer on the first node of the list (or NULL if none) + */ +static inline sys_snode_t *sys_slist_peek_head(sys_slist_t *list) +{ + return list->head; +} + +/** + * @brief Peek the last node from the list + * + * @param list A point on the list to peek the last node from + * + * @return A pointer on the last node of the list (or NULL if none) + */ +static inline sys_snode_t *sys_slist_peek_tail(sys_slist_t *list) +{ + return list->tail; +} + +/** + * @brief Peek the next node from current node, node is not NULL + * + * Faster then sys_slist_peek_next() if node is known not to be NULL. + * + * @param node A pointer on the node where to peek the next node + * + * @return a pointer on the next node (or NULL if none) + */ +static inline sys_snode_t *sys_slist_peek_next_no_check(sys_snode_t *node) +{ + return node->next; +} + +/** + * @brief Peek the next node from current node + * + * @param node A pointer on the node where to peek the next node + * + * @return a pointer on the next node (or NULL if none) + */ +static inline sys_snode_t *sys_slist_peek_next(sys_snode_t *node) +{ + return node ? sys_slist_peek_next_no_check(node) : NULL; +} + +/** + * @brief Prepend a node to the given list + * + * This and other sys_slist_*() functions are not thread safe. + * + * @param list A pointer on the list to affect + * @param node A pointer on the node to prepend + */ +static inline void sys_slist_prepend(sys_slist_t *list, + sys_snode_t *node) +{ + node->next = list->head; + list->head = node; + + if (!list->tail) { + list->tail = list->head; + } +} + +/** + * @brief Append a node to the given list + * + * This and other sys_slist_*() functions are not thread safe. + * + * @param list A pointer on the list to affect + * @param node A pointer on the node to append + */ +static inline void sys_slist_append(sys_slist_t *list, + sys_snode_t *node) +{ + node->next = NULL; + + if (!list->tail) { + list->tail = node; + list->head = node; + } else { + list->tail->next = node; + list->tail = node; + } +} + +/** + * @brief Append a list to the given list + * + * Append a singly-linked, NULL-terminated list consisting of nodes containing + * the pointer to the next node as the first element of a node, to @a list. + * This and other sys_slist_*() functions are not thread safe. + * + * @param list A pointer on the list to affect + * @param head A pointer to the first element of the list to append + * @param tail A pointer to the last element of the list to append + */ +static inline void sys_slist_append_list(sys_slist_t *list, + void *head, void *tail) +{ + if (!list->tail) { + list->head = (sys_snode_t *)head; + list->tail = (sys_snode_t *)tail; + } else { + list->tail->next = (sys_snode_t *)head; + list->tail = (sys_snode_t *)tail; + } +} + +/** + * @brief merge two slists, appending the second one to the first + * + * When the operation is completed, the appending list is empty. + * This and other sys_slist_*() functions are not thread safe. + * + * @param list A pointer on the list to affect + * @param list_to_append A pointer to the list to append. + */ +static inline void sys_slist_merge_slist(sys_slist_t *list, + sys_slist_t *list_to_append) +{ + sys_slist_append_list(list, list_to_append->head, + list_to_append->tail); + sys_slist_init(list_to_append); +} + +/** + * @brief Insert a node to the given list + * + * This and other sys_slist_*() functions are not thread safe. + * + * @param list A pointer on the list to affect + * @param prev A pointer on the previous node + * @param node A pointer on the node to insert + */ +static inline void sys_slist_insert(sys_slist_t *list, + sys_snode_t *prev, + sys_snode_t *node) +{ + if (!prev) { + sys_slist_prepend(list, node); + } else if (!prev->next) { + sys_slist_append(list, node); + } else { + node->next = prev->next; + prev->next = node; + } +} + +/** + * @brief Fetch and remove the first node of the given list + * + * List must be known to be non-empty. + * This and other sys_slist_*() functions are not thread safe. + * + * @param list A pointer on the list to affect + * + * @return A pointer to the first node of the list + */ +static inline sys_snode_t *sys_slist_get_not_empty(sys_slist_t *list) +{ + sys_snode_t *node = list->head; + + list->head = node->next; + if (list->tail == node) { + list->tail = list->head; + } + + return node; +} + +/** + * @brief Fetch and remove the first node of the given list + * + * This and other sys_slist_*() functions are not thread safe. + * + * @param list A pointer on the list to affect + * + * @return A pointer to the first node of the list (or NULL if empty) + */ +static inline sys_snode_t *sys_slist_get(sys_slist_t *list) +{ + return sys_slist_is_empty(list) ? NULL : sys_slist_get_not_empty(list); +} + +/** + * @brief Remove a node + * + * This and other sys_slist_*() functions are not thread safe. + * + * @param list A pointer on the list to affect + * @param prev_node A pointer on the previous node + * (can be NULL, which means the node is the list's head) + * @param node A pointer on the node to remove + */ +static inline void sys_slist_remove(sys_slist_t *list, + sys_snode_t *prev_node, + sys_snode_t *node) +{ + if (!prev_node) { + list->head = node->next; + + /* Was node also the tail? */ + if (list->tail == node) { + list->tail = list->head; + } + } else { + prev_node->next = node->next; + + /* Was node the tail? */ + if (list->tail == node) { + list->tail = prev_node; + } + } + + node->next = NULL; +} + +/** + * @brief Find and remove a node from a list + * + * This and other sys_slist_*() functions are not thread safe. + * + * @param list A pointer on the list to affect + * @param node A pointer on the node to remove from the list + * + * @return true if node was removed + */ +static inline bool sys_slist_find_and_remove(sys_slist_t *list, + sys_snode_t *node) +{ + sys_snode_t *prev = NULL; + sys_snode_t *test; + + SYS_SLIST_FOR_EACH_NODE(list, test) { + if (test == node) { + sys_slist_remove(list, prev, node); + return true; + } + + prev = test; + } + + return false; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_SLIST_H_ */ + diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_trace.h b/components/bt/ble_mesh/mesh_core/include/mesh_trace.h new file mode 100644 index 0000000000..325c807f93 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_trace.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BLE_MESH_TRACE_H_ +#define _BLE_MESH_TRACE_H_ + +#include "esp_log.h" +#include "sdkconfig.h" + +/* Define common tracing for all */ +#ifndef LOG_LEVEL_ERROR +#define LOG_LEVEL_ERROR 1 +#endif /* LOG_LEVEL_ERROR */ + +#ifndef LOG_LEVEL_WARN +#define LOG_LEVEL_WARN 2 +#endif /* LOG_LEVEL_WARN */ + +#ifndef LOG_LEVEL_INFO +#define LOG_LEVEL_INFO 3 +#endif /* LOG_LEVEL_INFO */ + +#ifndef LOG_LEVEL_DEBUG +#define LOG_LEVEL_DEBUG 4 +#endif /* LOG_LEVEL_DEBUG */ + +#ifndef LOG_LEVEL_VERBOSE +#define LOG_LEVEL_VERBOSE 5 +#endif /*LOG_LEVEL_VERBOSE */ + +#ifdef CONFIG_BLE_MESH_STACK_TRACE_LEVEL +#define MESH_LOG_LEVEL CONFIG_BLE_MESH_STACK_TRACE_LEVEL +#else +#define MESH_LOG_LEVEL LOG_LEVEL_WARN +#endif + +#ifdef CONFIG_BLE_MESH_NET_BUF_TRACE_LEVEL +#define NET_BUF_LOG_LEVEL CONFIG_BLE_MESH_NET_BUF_TRACE_LEVEL +#else +#define NET_BUF_LOG_LEVEL LOG_LEVEL_WARN +#endif + +#define MESH_TRACE_TAG "BLE_MESH" + +#if (LOG_LOCAL_LEVEL >= 4) +#define BLE_MESH_LOG_LOCAL_LEVEL_MAPPING (LOG_LOCAL_LEVEL + 1) +#else +#define BLE_MESH_LOG_LOCAL_LEVEL_MAPPING LOG_LOCAL_LEVEL +#endif + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif /* MAX(a, b) */ + +#define BLE_MESH_LOG_LEVEL_CHECK(LAYER, LEVEL) (MAX(LAYER##_LOG_LEVEL, BLE_MESH_LOG_LOCAL_LEVEL_MAPPING) >= LOG_LEVEL_##LEVEL) + +#define BLE_MESH_PRINT_E(tag, format, ...) {esp_log_write(ESP_LOG_ERROR, tag, LOG_FORMAT(E, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } +#define BLE_MESH_PRINT_W(tag, format, ...) {esp_log_write(ESP_LOG_WARN, tag, LOG_FORMAT(W, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } +#define BLE_MESH_PRINT_I(tag, format, ...) {esp_log_write(ESP_LOG_INFO, tag, LOG_FORMAT(I, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } +#define BLE_MESH_PRINT_D(tag, format, ...) {esp_log_write(ESP_LOG_DEBUG, tag, LOG_FORMAT(D, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } +#define BLE_MESH_PRINT_V(tag, format, ...) {esp_log_write(ESP_LOG_VERBOSE, tag, LOG_FORMAT(V, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } + +#define printk ets_printf + +#define _STRINGIFY(x) #x +#define STRINGIFY(s) _STRINGIFY(s) + +#ifndef __ASSERT +#define __ASSERT(test, fmt, ...) \ + do { \ + if (!(test)) { \ + printk("ASSERTION FAIL [%s] @ %s:%d:\n\t", \ + _STRINGIFY(test), \ + __FILE__, \ + __LINE__); \ + printk(fmt, ##__VA_ARGS__); \ + for (;;); \ + } \ + } while ((0)) +#endif + +#ifndef __ASSERT_NO_MSG +#define __ASSERT_NO_MSG(x) do { if (!(x)) BLE_MESH_PRINT_E(MESH_TRACE_TAG, "error %s %u", __FILE__, __LINE__); } while (0) +#endif + +#if !CONFIG_BLE_MESH_NO_LOG +#define BT_ERR(fmt, args...) do {if ((MESH_LOG_LEVEL >= LOG_LEVEL_ERROR) && BLE_MESH_LOG_LEVEL_CHECK(MESH, ERROR)) BLE_MESH_PRINT_E(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define BT_WARN(fmt, args...) do {if ((MESH_LOG_LEVEL >= LOG_LEVEL_WARN) && BLE_MESH_LOG_LEVEL_CHECK(MESH, WARN)) BLE_MESH_PRINT_W(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define BT_INFO(fmt, args...) do {if ((MESH_LOG_LEVEL >= LOG_LEVEL_INFO) && BLE_MESH_LOG_LEVEL_CHECK(MESH, INFO)) BLE_MESH_PRINT_I(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define BT_DBG(fmt, args...) do {if ((MESH_LOG_LEVEL >= LOG_LEVEL_DEBUG) && BLE_MESH_LOG_LEVEL_CHECK(MESH, DEBUG)) BLE_MESH_PRINT_D(MESH_TRACE_TAG, fmt, ## args);} while(0) +#else +#define BT_ERR(fmt, args...) +#define BT_WARN(fmt, args...) +#define BT_INFO(fmt, args...) +#define BT_DBG(fmt, args...) +#endif + +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) && (!CONFIG_BLE_MESH_NO_LOG) +#define NET_BUF_ERR(fmt, args...) do {if ((NET_BUF_LOG_LEVEL >= LOG_LEVEL_ERROR) && BLE_MESH_LOG_LEVEL_CHECK(NET_BUF, ERROR)) BLE_MESH_PRINT_E(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define NET_BUF_WARN(fmt, args...) do {if ((NET_BUF_LOG_LEVEL >= LOG_LEVEL_WARN) && BLE_MESH_LOG_LEVEL_CHECK(NET_BUF, WARN)) BLE_MESH_PRINT_W(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define NET_BUF_INFO(fmt, args...) do {if ((NET_BUF_LOG_LEVEL >= LOG_LEVEL_INFO) && BLE_MESH_LOG_LEVEL_CHECK(NET_BUF, INFO)) BLE_MESH_PRINT_I(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define NET_BUF_DBG(fmt, args...) do {if ((NET_BUF_LOG_LEVEL >= LOG_LEVEL_DEBUG) && BLE_MESH_LOG_LEVEL_CHECK(NET_BUF, DEBUG)) BLE_MESH_PRINT_D(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define NET_BUF_ASSERT(cond) __ASSERT_NO_MSG(cond) +#else +#define NET_BUF_ERR(fmt, args...) +#define NET_BUF_WARN(fmt, args...) +#define NET_BUF_INFO(fmt, args...) +#define NET_BUF_DBG(fmt, args...) +#define NET_BUF_ASSERT(cond) +#endif + +#if defined(CONFIG_BLE_MESH_NET_BUF_SIMPLE_LOG) && (!CONFIG_BLE_MESH_NO_LOG) +#define NET_BUF_SIMPLE_ERR(fmt, args...) do {if ((NET_BUF_LOG_LEVEL >= LOG_LEVEL_ERROR) && BLE_MESH_LOG_LEVEL_CHECK(NET_BUF, ERROR)) BLE_MESH_PRINT_E(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define NET_BUF_SIMPLE_WARN(fmt, args...) do {if ((NET_BUF_LOG_LEVEL >= LOG_LEVEL_WARN) && BLE_MESH_LOG_LEVEL_CHECK(NET_BUF, WARN)) BLE_MESH_PRINT_W(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define NET_BUF_SIMPLE_INFO(fmt, args...) do {if ((NET_BUF_LOG_LEVEL >= LOG_LEVEL_INFO) && BLE_MESH_LOG_LEVEL_CHECK(NET_BUF, INFO)) BLE_MESH_PRINT_I(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define NET_BUF_SIMPLE_DBG(fmt, args...) do {if ((NET_BUF_LOG_LEVEL >= LOG_LEVEL_DEBUG) && BLE_MESH_LOG_LEVEL_CHECK(NET_BUF, DEBUG)) BLE_MESH_PRINT_D(MESH_TRACE_TAG, fmt, ## args);} while(0) +#define NET_BUF_SIMPLE_ASSERT(cond) __ASSERT_NO_MSG(cond) +#else +#define NET_BUF_SIMPLE_ERR(fmt, args...) +#define NET_BUF_SIMPLE_WARN(fmt, args...) +#define NET_BUF_SIMPLE_INFO(fmt, args...) +#define NET_BUF_SIMPLE_DBG(fmt, args...) +#define NET_BUF_SIMPLE_ASSERT(cond) +#endif + +#endif /* _BLE_MESH_TRACE_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_types.h b/components/bt/ble_mesh/mesh_core/include/mesh_types.h new file mode 100644 index 0000000000..66df1ec75e --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_types.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Linaro Limited + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BLE_MESH_TYPES_H_ +#define _BLE_MESH_TYPES_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef signed char s8_t; +typedef signed short s16_t; +typedef signed int s32_t; +typedef signed long long s64_t; + +typedef unsigned char u8_t; +typedef unsigned short u16_t; +typedef unsigned int u32_t; +typedef unsigned long long u64_t; + +typedef int bt_mesh_atomic_t; + +#ifndef bool +#define bool int8_t +#endif + +#ifndef false +#define false 0 +#endif + +#ifndef true +#define true 1 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_TYPES_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_util.h b/components/bt/ble_mesh/mesh_core/include/mesh_util.h new file mode 100644 index 0000000000..8258e2c691 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_util.h @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2011-2014, Wind River Systems, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Misc utilities + * + * Misc utilities usable by the kernel and application code. + */ + +#ifndef _BLE_MESH_UTIL_H_ +#define _BLE_MESH_UTIL_H_ + +#include +#include "mesh_types.h" +#include "mesh_trace.h" +#include "soc/soc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper to pass a int as a pointer or vice-versa. + * Those are available for 32 bits architectures: + */ +#define POINTER_TO_UINT(x) ((u32_t) (x)) +#define UINT_TO_POINTER(x) ((void *) (x)) +#define POINTER_TO_INT(x) ((s32_t) (x)) +#define INT_TO_POINTER(x) ((void *) (x)) + +/* Evaluates to 0 if cond is true-ish; compile error otherwise */ +#define ZERO_OR_COMPILE_ERROR(cond) ((int) sizeof(char[1 - 2 * !(cond)]) - 1) + +/* Evaluates to 0 if array is an array; compile error if not array (e.g. + * pointer) + */ +#define IS_ARRAY(array) \ + ZERO_OR_COMPILE_ERROR( \ + !__builtin_types_compatible_p(__typeof__(array), \ + __typeof__(&(array)[0]))) + +/* Evaluates to number of elements in an array; compile error if not + * an array (e.g. pointer) + */ +#define ARRAY_SIZE(array) \ + ((unsigned long) (IS_ARRAY(array) + \ + (sizeof(array) / sizeof((array)[0])))) + +/* Evaluates to 1 if ptr is part of array, 0 otherwise; compile error if + * "array" argument is not an array (e.g. "ptr" and "array" mixed up) + */ +#define PART_OF_ARRAY(array, ptr) \ + ((ptr) && ((ptr) >= &array[0] && (ptr) < &array[ARRAY_SIZE(array)])) + +#define CONTAINER_OF(ptr, type, field) \ + ((type *)(((char *)(ptr)) - offsetof(type, field))) + +/* round "x" up/down to next multiple of "align" (which must be a power of 2) */ +#define ROUND_UP(x, align) \ + (((unsigned long)(x) + ((unsigned long)align - 1)) & \ + ~((unsigned long)align - 1)) +#define ROUND_DOWN(x, align) ((unsigned long)(x) & ~((unsigned long)align - 1)) + +#define ceiling_fraction(numerator, divider) \ + (((numerator) + ((divider) - 1)) / (divider)) + +/* Internal helpers only used by the sys_* APIs further below */ +#ifndef __bswap_16 +#define __bswap_16(x) ((u16_t) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))) +#endif + +#ifndef __bswap_32 +#define __bswap_32(x) ((u32_t) ((((x) >> 24) & 0xff) | \ + (((x) >> 8) & 0xff00) | \ + (((x) & 0xff00) << 8) | \ + (((x) & 0xff) << 24))) +#endif + +#ifndef __bswap_64 +#define __bswap_64(x) ((u64_t) ((((x) >> 56) & 0xff) | \ + (((x) >> 40) & 0xff00) | \ + (((x) >> 24) & 0xff0000) | \ + (((x) >> 8) & 0xff000000) | \ + (((x) & 0xff000000) << 8) | \ + (((x) & 0xff0000) << 24) | \ + (((x) & 0xff00) << 40) | \ + (((x) & 0xff) << 56))) +#endif + +#define sys_le16_to_cpu(val) (val) +#define sys_cpu_to_le16(val) (val) +#define sys_be16_to_cpu(val) __bswap_16(val) +#define sys_cpu_to_be16(val) __bswap_16(val) +#define sys_le32_to_cpu(val) (val) +#define sys_cpu_to_le32(val) (val) +#define sys_le64_to_cpu(val) (val) +#define sys_cpu_to_le64(val) (val) +#define sys_be32_to_cpu(val) __bswap_32(val) +#define sys_cpu_to_be32(val) __bswap_32(val) +#define sys_be64_to_cpu(val) __bswap_64(val) +#define sys_cpu_to_be64(val) __bswap_64(val) + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef BIT +#define BIT(n) (1UL << (n)) +#endif + +#ifndef BIT_MASK +#define BIT_MASK(n) (BIT(n) - 1) +#endif + +/** + * @brief Check for macro definition in compiler-visible expressions + * + * This trick was pioneered in Linux as the config_enabled() macro. + * The madness has the effect of taking a macro value that may be + * defined to "1" (e.g. CONFIG_MYFEATURE), or may not be defined at + * all and turning it into a literal expression that can be used at + * "runtime". That is, it works similarly to + * "defined(CONFIG_MYFEATURE)" does except that it is an expansion + * that can exist in a standard expression and be seen by the compiler + * and optimizer. Thus much ifdef usage can be replaced with cleaner + * expressions like: + * + * if (IS_ENABLED(CONFIG_MYFEATURE)) + * myfeature_enable(); + * + * INTERNAL + * First pass just to expand any existing macros, we need the macro + * value to be e.g. a literal "1" at expansion time in the next macro, + * not "(1)", etc... Standard recursive expansion does not work. + */ +#define IS_ENABLED(config_macro) _IS_ENABLED1(config_macro) + +/* Now stick on a "_XXXX" prefix, it will now be "_XXXX1" if config_macro + * is "1", or just "_XXXX" if it's undefined. + * ENABLED: _IS_ENABLED2(_XXXX1) + * DISABLED _IS_ENABLED2(_XXXX) + */ +#define _IS_ENABLED1(config_macro) _IS_ENABLED2(_XXXX##config_macro) + +/* Here's the core trick, we map "_XXXX1" to "_YYYY," (i.e. a string + * with a trailing comma), so it has the effect of making this a + * two-argument tuple to the preprocessor only in the case where the + * value is defined to "1" + * ENABLED: _YYYY, <--- note comma! + * DISABLED: _XXXX + */ +#define _XXXX1 _YYYY, + +/* Then we append an extra argument to fool the gcc preprocessor into + * accepting it as a varargs macro. + * arg1 arg2 arg3 + * ENABLED: _IS_ENABLED3(_YYYY, 1, 0) + * DISABLED _IS_ENABLED3(_XXXX 1, 0) + */ +#define _IS_ENABLED2(one_or_two_args) _IS_ENABLED3(one_or_two_args 1, 0) + +/* And our second argument is thus now cooked to be 1 in the case + * where the value is defined to 1, and 0 if not: + */ +#define _IS_ENABLED3(ignore_this, val, ...) val + +/* ESP Toolchain doesn't support section */ +#define ___in_section(a, b, c) +#define __in_section(a, b, c) ___in_section(a, b, c) + +#define __in_section_unique(seg) ___in_section(seg, __FILE__, __COUNTER__) + +#define popcount(x) __builtin_popcount(x) + +/** + * + * @brief find most significant bit set in a 32-bit word + * + * This routine finds the first bit set starting from the most significant bit + * in the argument passed in and returns the index of that bit. Bits are + * numbered starting at 1 from the least significant bit. A return value of + * zero indicates that the value passed is zero. + * + * @return most significant bit set, 0 if @a op is 0 + */ + +#if defined(__GNUC__) +static inline unsigned int find_msb_set(u32_t op) +{ + if (!op) { + return 0; + } + return 32 - __builtin_clz(op); +} +#endif + +/** + * + * @brief find least significant bit set in a 32-bit word + * + * This routine finds the first bit set starting from the least significant bit + * in the argument passed in and returns the index of that bit. Bits are + * numbered starting at 1 from the least significant bit. A return value of + * zero indicates that the value passed is zero. + * + * @return least significant bit set, 0 if @a op is 0 + */ + +#if defined(__GNUC__) +static inline unsigned int find_lsb_set(u32_t op) +{ + return __builtin_ffs(op); +} +#endif + +/** + * @brief Put a 16-bit integer as big-endian to arbitrary location. + * + * Put a 16-bit integer, originally in host endianness, to a + * potentially unaligned memory location in big-endian format. + * + * @param val 16-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ +static inline void sys_put_be16(u16_t val, u8_t dst[2]) +{ + dst[0] = val >> 8; + dst[1] = val; +} + +/** + * @brief Put a 32-bit integer as big-endian to arbitrary location. + * + * Put a 32-bit integer, originally in host endianness, to a + * potentially unaligned memory location in big-endian format. + * + * @param val 32-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ +static inline void sys_put_be32(u32_t val, u8_t dst[4]) +{ + sys_put_be16(val >> 16, dst); + sys_put_be16(val, &dst[2]); +} + +/** + * @brief Put a 16-bit integer as little-endian to arbitrary location. + * + * Put a 16-bit integer, originally in host endianness, to a + * potentially unaligned memory location in little-endian format. + * + * @param val 16-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ +static inline void sys_put_le16(u16_t val, u8_t dst[2]) +{ + dst[0] = val; + dst[1] = val >> 8; +} + +/** + * @brief Put a 32-bit integer as little-endian to arbitrary location. + * + * Put a 32-bit integer, originally in host endianness, to a + * potentially unaligned memory location in little-endian format. + * + * @param val 32-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ +static inline void sys_put_le32(u32_t val, u8_t dst[4]) +{ + sys_put_le16(val, dst); + sys_put_le16(val >> 16, &dst[2]); +} + +/** + * @brief Put a 64-bit integer as little-endian to arbitrary location. + * + * Put a 64-bit integer, originally in host endianness, to a + * potentially unaligned memory location in little-endian format. + * + * @param val 64-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ +static inline void sys_put_le64(u64_t val, u8_t dst[8]) +{ + sys_put_le32(val, dst); + sys_put_le32(val >> 32, &dst[4]); +} + +/** + * @brief Get a 16-bit integer stored in big-endian format. + * + * Get a 16-bit integer, stored in big-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the big-endian 16-bit integer to get. + * + * @return 16-bit integer in host endianness. + */ +static inline u16_t sys_get_be16(const u8_t src[2]) +{ + return ((u16_t)src[0] << 8) | src[1]; +} + +/** + * @brief Get a 32-bit integer stored in big-endian format. + * + * Get a 32-bit integer, stored in big-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the big-endian 32-bit integer to get. + * + * @return 32-bit integer in host endianness. + */ +static inline u32_t sys_get_be32(const u8_t src[4]) +{ + return ((u32_t)sys_get_be16(&src[0]) << 16) | sys_get_be16(&src[2]); +} + +/** + * @brief Get a 16-bit integer stored in little-endian format. + * + * Get a 16-bit integer, stored in little-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the little-endian 16-bit integer to get. + * + * @return 16-bit integer in host endianness. + */ +static inline u16_t sys_get_le16(const u8_t src[2]) +{ + return ((u16_t)src[1] << 8) | src[0]; +} + +/** + * @brief Get a 32-bit integer stored in little-endian format. + * + * Get a 32-bit integer, stored in little-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the little-endian 32-bit integer to get. + * + * @return 32-bit integer in host endianness. + */ +static inline u32_t sys_get_le32(const u8_t src[4]) +{ + return ((u32_t)sys_get_le16(&src[2]) << 16) | sys_get_le16(&src[0]); +} + +/** + * @brief Get a 64-bit integer stored in little-endian format. + * + * Get a 64-bit integer, stored in little-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the little-endian 64-bit integer to get. + * + * @return 64-bit integer in host endianness. + */ +static inline u64_t sys_get_le64(const u8_t src[8]) +{ + return ((u64_t)sys_get_le32(&src[4]) << 32) | sys_get_le32(&src[0]); +} + +const char *bt_hex(const void *buf, size_t len); + +void mem_rcopy(u8_t *dst, u8_t const *src, u16_t len); + +void _set(void *to, uint8_t val, unsigned int len); + +unsigned int _copy(uint8_t *to, unsigned int to_len, + const uint8_t *from, unsigned int from_len); + +void _set(void *to, uint8_t val, unsigned int len); + +uint8_t _double_byte(uint8_t a); + +int _compare(const uint8_t *a, const uint8_t *b, size_t size); + +/** + * @brief Swap one buffer content into another + * + * Copy the content of src buffer into dst buffer in reversed order, + * i.e.: src[n] will be put in dst[end-n] + * Where n is an index and 'end' the last index in both arrays. + * The 2 memory pointers must be pointing to different areas, and have + * a minimum size of given length. + * + * @param dst A valid pointer on a memory area where to copy the data in + * @param src A valid pointer on a memory area where to copy the data from + * @param length Size of both dst and src memory areas + */ +static inline void sys_memcpy_swap(void *dst, const void *src, size_t length) +{ + __ASSERT(((src < dst && (src + length) <= dst) || + (src > dst && (dst + length) <= src)), + "Source and destination buffers must not overlap"); + + src += length - 1; + + for (; length > 0; length--) { + *((u8_t *)dst++) = *((u8_t *)src--); + } +} + +/** + * @brief Swap buffer content + * + * In-place memory swap, where final content will be reversed. + * I.e.: buf[n] will be put in buf[end-n] + * Where n is an index and 'end' the last index of buf. + * + * @param buf A valid pointer on a memory area to swap + * @param length Size of buf memory area + */ +static inline void sys_mem_swap(void *buf, size_t length) +{ + size_t i; + + for (i = 0; i < (length / 2); i++) { + u8_t tmp = ((u8_t *)buf)[i]; + + ((u8_t *)buf)[i] = ((u8_t *)buf)[length - 1 - i]; + ((u8_t *)buf)[length - 1 - i] = tmp; + } +} + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_UTIL_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/include/mesh_uuid.h b/components/bt/ble_mesh/mesh_core/include/mesh_uuid.h new file mode 100644 index 0000000000..de03df5c32 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/include/mesh_uuid.h @@ -0,0 +1,530 @@ +/** @file + * @brief Bluetooth UUID handling + */ + +/* + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BLE_MESH_UUID_H_ +#define _BLE_MESH_UUID_H_ + +/** + * @brief UUIDs + * @defgroup bt_uuid UUIDs + * @ingroup bluetooth + * @{ + */ + +#include "mesh_util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Bluetooth UUID types */ +enum { + BLE_MESH_UUID_TYPE_16, + BLE_MESH_UUID_TYPE_32, + BLE_MESH_UUID_TYPE_128, +}; + +/** @brief This is a 'tentative' type and should be used as a pointer only */ +struct bt_mesh_uuid { + u8_t type; +}; + +struct bt_mesh_uuid_16 { + struct bt_mesh_uuid uuid; + u16_t val; +}; + +struct bt_mesh_uuid_32 { + struct bt_mesh_uuid uuid; + u32_t val; +}; + +struct bt_mesh_uuid_128 { + struct bt_mesh_uuid uuid; + u8_t val[16]; +}; + +#define BLE_MESH_UUID_INIT_16(value) \ +{ \ + .uuid.type = BLE_MESH_UUID_TYPE_16, \ + .val = (value), \ +} + +#define BLE_MESH_UUID_INIT_32(value) \ +{ \ + .uuid.type = BLE_MESH_UUID_TYPE_32, \ + .val = (value), \ +} + +#define BLE_MESH_UUID_INIT_128(value...) \ +{ \ + .uuid.type = BLE_MESH_UUID_TYPE_128, \ + .val = { value }, \ +} + +#define BLE_MESH_UUID_DECLARE_16(value) \ + ((struct bt_mesh_uuid *) (&(struct bt_mesh_uuid_16) BLE_MESH_UUID_INIT_16(value))) + +#define BLE_MESH_UUID_DECLARE_32(value) \ + ((struct bt_mesh_uuid *) (&(struct bt_mesh_uuid_32) BLE_MESH_UUID_INIT_32(value))) + +#define BLE_MESH_UUID_DECLARE_128(value...) \ + ((struct bt_mesh_uuid *) (&(struct bt_mesh_uuid_128) BLE_MESH_UUID_INIT_128(value))) + +#define BLE_MESH_UUID_16(__u) CONTAINER_OF(__u, struct bt_mesh_uuid_16, uuid) +#define BLE_MESH_UUID_32(__u) CONTAINER_OF(__u, struct bt_mesh_uuid_32, uuid) +#define BLE_MESH_UUID_128(__u) CONTAINER_OF(__u, struct bt_mesh_uuid_128, uuid) + +/** @def BLE_MESH_UUID_GAP + * @brief Generic Access + */ +#define BLE_MESH_UUID_GAP BLE_MESH_UUID_DECLARE_16(0x1800) +#define BLE_MESH_UUID_GAP_VAL 0x1800 +/** @def BLE_MESH_UUID_GATT + * @brief Generic Attribute + */ +#define BLE_MESH_UUID_GATT BLE_MESH_UUID_DECLARE_16(0x1801) +#define BLE_MESH_UUID_GATT_VAL 0x1801 +/** @def BLE_MESH_UUID_CTS + * @brief Current Time Service + */ +#define BLE_MESH_UUID_CTS BLE_MESH_UUID_DECLARE_16(0x1805) +#define BLE_MESH_UUID_CTS_VAL 0x1805 +/** @def BLE_MESH_UUID_DIS + * @brief Device Information Service + */ +#define BLE_MESH_UUID_DIS BLE_MESH_UUID_DECLARE_16(0x180a) +#define BLE_MESH_UUID_DIS_VAL 0x180a +/** @def BLE_MESH_UUID_HRS + * @brief Heart Rate Service + */ +#define BLE_MESH_UUID_HRS BLE_MESH_UUID_DECLARE_16(0x180d) +#define BLE_MESH_UUID_HRS_VAL 0x180d +/** @def BLE_MESH_UUID_BAS + * @brief Battery Service + */ +#define BLE_MESH_UUID_BAS BLE_MESH_UUID_DECLARE_16(0x180f) +#define BLE_MESH_UUID_BAS_VAL 0x180f +/** @def BLE_MESH_UUID_HIDS + * @brief HID Service + */ +#define BLE_MESH_UUID_HIDS BLE_MESH_UUID_DECLARE_16(0x1812) +#define BLE_MESH_UUID_HIDS_VAL 0x1812 +/** @def BLE_MESH_UUID_CSC + * @brief Cycling Speed and Cadence Service + */ +#define BLE_MESH_UUID_CSC BLE_MESH_UUID_DECLARE_16(0x1816) +#define BLE_MESH_UUID_CSC_VAL 0x1816 +/** @def BLE_MESH_UUID_ESS + * @brief Environmental Sensing Service + */ +#define BLE_MESH_UUID_ESS BLE_MESH_UUID_DECLARE_16(0x181a) +#define BLE_MESH_UUID_ESS_VAL 0x181a +/** @def BLE_MESH_UUID_IPSS + * @brief IP Support Service + */ +#define BLE_MESH_UUID_IPSS BLE_MESH_UUID_DECLARE_16(0x1820) +#define BLE_MESH_UUID_IPSS_VAL 0x1820 +/** @def BLE_MESH_UUID_MESH_PROV + * @brief Mesh Provisioning Service + */ +#define BLE_MESH_UUID_MESH_PROV BLE_MESH_UUID_DECLARE_16(0x1827) +#define BLE_MESH_UUID_MESH_PROV_VAL 0x1827 +/** @def BLE_MESH_UUID_MESH_PROXY + * @brief Mesh Proxy Service + */ +#define BLE_MESH_UUID_MESH_PROXY BLE_MESH_UUID_DECLARE_16(0x1828) +#define BLE_MESH_UUID_MESH_PROXY_VAL 0x1828 +/** @def BLE_MESH_UUID_GATT_PRIMARY + * @brief GATT Primary Service + */ +#define BLE_MESH_UUID_GATT_PRIMARY BLE_MESH_UUID_DECLARE_16(0x2800) +#define BLE_MESH_UUID_GATT_PRIMARY_VAL 0x2800 +/** @def BLE_MESH_UUID_GATT_SECONDARY + * @brief GATT Secondary Service + */ +#define BLE_MESH_UUID_GATT_SECONDARY BLE_MESH_UUID_DECLARE_16(0x2801) +#define BLE_MESH_UUID_GATT_SECONDARY_VAL 0x2801 +/** @def BLE_MESH_UUID_GATT_INCLUDE + * @brief GATT Include Service + */ +#define BLE_MESH_UUID_GATT_INCLUDE BLE_MESH_UUID_DECLARE_16(0x2802) +#define BLE_MESH_UUID_GATT_INCLUDE_VAL 0x2802 +/** @def BLE_MESH_UUID_GATT_CHRC + * @brief GATT Characteristic + */ +#define BLE_MESH_UUID_GATT_CHRC BLE_MESH_UUID_DECLARE_16(0x2803) +#define BLE_MESH_UUID_GATT_CHRC_VAL 0x2803 +/** @def BLE_MESH_UUID_GATT_CEP + * @brief GATT Characteristic Extended Properties + */ +#define BLE_MESH_UUID_GATT_CEP BLE_MESH_UUID_DECLARE_16(0x2900) +#define BLE_MESH_UUID_GATT_CEP_VAL 0x2900 +/** @def BLE_MESH_UUID_GATT_CUD + * @brief GATT Characteristic User Description + */ +#define BLE_MESH_UUID_GATT_CUD BLE_MESH_UUID_DECLARE_16(0x2901) +#define BLE_MESH_UUID_GATT_CUD_VAL 0x2901 +/** @def BLE_MESH_UUID_GATT_CCC + * @brief GATT Client Characteristic Configuration + */ +#define BLE_MESH_UUID_GATT_CCC BLE_MESH_UUID_DECLARE_16(0x2902) +#define BLE_MESH_UUID_GATT_CCC_VAL 0x2902 +/** @def BLE_MESH_UUID_GATT_SCC + * @brief GATT Server Characteristic Configuration + */ +#define BLE_MESH_UUID_GATT_SCC BLE_MESH_UUID_DECLARE_16(0x2903) +#define BLE_MESH_UUID_GATT_SCC_VAL 0x2903 +/** @def BLE_MESH_UUID_GATT_CPF + * @brief GATT Characteristic Presentation Format + */ +#define BLE_MESH_UUID_GATT_CPF BLE_MESH_UUID_DECLARE_16(0x2904) +#define BLE_MESH_UUID_GATT_CPF_VAL 0x2904 +/** @def BLE_MESH_UUID_VALID_RANGE + * @brief Valid Range Descriptor + */ +#define BLE_MESH_UUID_VALID_RANGE BLE_MESH_UUID_DECLARE_16(0x2906) +#define BLE_MESH_UUID_VALID_RANGE_VAL 0x2906 +/** @def BLE_MESH_UUID_HIDS_EXT_REPORT + * @brief HID External Report Descriptor + */ +#define BLE_MESH_UUID_HIDS_EXT_REPORT BLE_MESH_UUID_DECLARE_16(0x2907) +#define BLE_MESH_UUID_HIDS_EXT_REPORT_VAL 0x2907 +/** @def BLE_MESH_UUID_HIDS_REPORT_REF + * @brief HID Report Reference Descriptor + */ +#define BLE_MESH_UUID_HIDS_REPORT_REF BLE_MESH_UUID_DECLARE_16(0x2908) +#define BLE_MESH_UUID_HIDS_REPORT_REF_VAL 0x2908 +/** @def BLE_MESH_UUID_ES_CONFIGURATION + * @brief Environmental Sensing Configuration Descriptor + */ +#define BLE_MESH_UUID_ES_CONFIGURATION BLE_MESH_UUID_DECLARE_16(0x290b) +#define BLE_MESH_UUID_ES_CONFIGURATION_VAL 0x290b +/** @def BLE_MESH_UUID_ES_MEASUREMENT + * @brief Environmental Sensing Measurement Descriptor + */ +#define BLE_MESH_UUID_ES_MEASUREMENT BLE_MESH_UUID_DECLARE_16(0x290c) +#define BLE_MESH_UUID_ES_MEASUREMENT_VAL 0x290c +/** @def BLE_MESH_UUID_ES_TRIGGER_SETTING + * @brief Environmental Sensing Trigger Setting Descriptor + */ +#define BLE_MESH_UUID_ES_TRIGGER_SETTING BLE_MESH_UUID_DECLARE_16(0x290d) +#define BLE_MESH_UUID_ES_TRIGGER_SETTING_VAL 0x290d +/** @def BLE_MESH_UUID_GAP_DEVICE_NAME + * @brief GAP Characteristic Device Name + */ +#define BLE_MESH_UUID_GAP_DEVICE_NAME BLE_MESH_UUID_DECLARE_16(0x2a00) +#define BLE_MESH_UUID_GAP_DEVICE_NAME_VAL 0x2a00 +/** @def BLE_MESH_UUID_GAP_APPEARANCE + * @brief GAP Characteristic Appearance + */ +#define BLE_MESH_UUID_GAP_APPEARANCE BLE_MESH_UUID_DECLARE_16(0x2a01) +#define BLE_MESH_UUID_GAP_APPEARANCE_VAL 0x2a01 +/** @def BLE_MESH_UUID_GAP_PPCP + * @brief GAP Characteristic Peripheral Preferred Connection Parameters + */ +#define BLE_MESH_UUID_GAP_PPCP BLE_MESH_UUID_DECLARE_16(0x2a04) +#define BLE_MESH_UUID_GAP_PPCP_VAL 0x2a04 +/** @def BLE_MESH_UUID_GATT_SC + * @brief GATT Characteristic Service Changed + */ +#define BLE_MESH_UUID_GATT_SC BLE_MESH_UUID_DECLARE_16(0x2a05) +#define BLE_MESH_UUID_GATT_SC_VAL 0x2a05 +/** @def BLE_MESH_UUID_BAS_BATTERY_LEVEL + * @brief BAS Characteristic Battery Level + */ +#define BLE_MESH_UUID_BAS_BATTERY_LEVEL BLE_MESH_UUID_DECLARE_16(0x2a19) +#define BLE_MESH_UUID_BAS_BATTERY_LEVEL_VAL 0x2a19 +/** @def BLE_MESH_UUID_DIS_SYSTEM_ID + * @brief DIS Characteristic System ID + */ +#define BLE_MESH_UUID_DIS_SYSTEM_ID BLE_MESH_UUID_DECLARE_16(0x2a23) +#define BLE_MESH_UUID_DIS_SYSTEM_ID_VAL 0x2a23 +/** @def BLE_MESH_UUID_DIS_MODEL_NUMBER + * @brief DIS Characteristic Model Number String + */ +#define BLE_MESH_UUID_DIS_MODEL_NUMBER BLE_MESH_UUID_DECLARE_16(0x2a24) +#define BLE_MESH_UUID_DIS_MODEL_NUMBER_VAL 0x2a24 +/** @def BLE_MESH_UUID_DIS_SERIAL_NUMBER + * @brief DIS Characteristic Serial Number String + */ +#define BLE_MESH_UUID_DIS_SERIAL_NUMBER BLE_MESH_UUID_DECLARE_16(0x2a25) +#define BLE_MESH_UUID_DIS_SERIAL_NUMBER_VAL 0x2a25 +/** @def BLE_MESH_UUID_DIS_FIRMWARE_REVISION + * @brief DIS Characteristic Firmware Revision String + */ +#define BLE_MESH_UUID_DIS_FIRMWARE_REVISION BLE_MESH_UUID_DECLARE_16(0x2a26) +#define BLE_MESH_UUID_DIS_FIRMWARE_REVISION_VAL 0x2a26 +/** @def BLE_MESH_UUID_DIS_HARDWARE_REVISION + * @brief DIS Characteristic Hardware Revision String + */ +#define BLE_MESH_UUID_DIS_HARDWARE_REVISION BLE_MESH_UUID_DECLARE_16(0x2a27) +#define BLE_MESH_UUID_DIS_HARDWARE_REVISION_VAL 0x2a27 +/** @def BLE_MESH_UUID_DIS_SOFTWARE_REVISION + * @brief DIS Characteristic Software Revision String + */ +#define BLE_MESH_UUID_DIS_SOFTWARE_REVISION BLE_MESH_UUID_DECLARE_16(0x2a28) +#define BLE_MESH_UUID_DIS_SOFTWARE_REVISION_VAL 0x2a28 +/** @def BLE_MESH_UUID_DIS_MANUFACTURER_NAME + * @brief DIS Characteristic Manufacturer Name String + */ +#define BLE_MESH_UUID_DIS_MANUFACTURER_NAME BLE_MESH_UUID_DECLARE_16(0x2a29) +#define BLE_MESH_UUID_DIS_MANUFACTURER_NAME_VAL 0x2a29 +/** @def BLE_MESH_UUID_DIS_PNP_ID + * @brief DIS Characteristic PnP ID + */ +#define BLE_MESH_UUID_DIS_PNP_ID BLE_MESH_UUID_DECLARE_16(0x2a50) +#define BLE_MESH_UUID_DIS_PNP_ID_VAL 0x2a50 +/** @def BLE_MESH_UUID_CTS_CURRENT_TIME + * @brief CTS Characteristic Current Time + */ +#define BLE_MESH_UUID_CTS_CURRENT_TIME BLE_MESH_UUID_DECLARE_16(0x2a2b) +#define BLE_MESH_UUID_CTS_CURRENT_TIME_VAL 0x2a2b +/** @def BLE_MESH_UUID_MAGN_DECLINATION + * @brief Magnetic Declination Characteristic + */ +#define BLE_MESH_UUID_MAGN_DECLINATION BLE_MESH_UUID_DECLARE_16(0x2a2c) +#define BLE_MESH_UUID_MAGN_DECLINATION_VAL 0x2a2c +/** @def BLE_MESH_UUID_HRS_MEASUREMENT + * @brief HRS Characteristic Measurement Interval + */ +#define BLE_MESH_UUID_HRS_MEASUREMENT BLE_MESH_UUID_DECLARE_16(0x2a37) +#define BLE_MESH_UUID_HRS_MEASUREMENT_VAL 0x2a37 +/** @def BLE_MESH_UUID_HRS_BODY_SENSOR + * @brief HRS Characteristic Body Sensor Location + */ +#define BLE_MESH_UUID_HRS_BODY_SENSOR BLE_MESH_UUID_DECLARE_16(0x2a38) +#define BLE_MESH_UUID_HRS_BODY_SENSOR_VAL 0x2a38 +/** @def BLE_MESH_UUID_HRS_CONTROL_POINT + * @brief HRS Characteristic Control Point + */ +#define BLE_MESH_UUID_HRS_CONTROL_POINT BLE_MESH_UUID_DECLARE_16(0x2a39) +#define BLE_MESH_UUID_HRS_CONTROL_POINT_VAL 0x2a39 +/** @def BLE_MESH_UUID_HIDS_INFO + * @brief HID Information Characteristic + */ +#define BLE_MESH_UUID_HIDS_INFO BLE_MESH_UUID_DECLARE_16(0x2a4a) +#define BLE_MESH_UUID_HIDS_INFO_VAL 0x2a4a +/** @def BLE_MESH_UUID_HIDS_REPORT_MAP + * @brief HID Report Map Characteristic + */ +#define BLE_MESH_UUID_HIDS_REPORT_MAP BLE_MESH_UUID_DECLARE_16(0x2a4b) +#define BLE_MESH_UUID_HIDS_REPORT_MAP_VAL 0x2a4b +/** @def BLE_MESH_UUID_HIDS_CTRL_POINT + * @brief HID Control Point Characteristic + */ +#define BLE_MESH_UUID_HIDS_CTRL_POINT BLE_MESH_UUID_DECLARE_16(0x2a4c) +#define BLE_MESH_UUID_HIDS_CTRL_POINT_VAL 0x2a4c +/** @def BLE_MESH_UUID_HIDS_REPORT + * @brief HID Report Characteristic + */ +#define BLE_MESH_UUID_HIDS_REPORT BLE_MESH_UUID_DECLARE_16(0x2a4d) +#define BLE_MESH_UUID_HIDS_REPORT_VAL 0x2a4d +/** @def BLE_MESH_UUID_CSC_MEASUREMENT + * @brief CSC Measurement Characteristic + */ +#define BLE_MESH_UUID_CSC_MEASUREMENT BLE_MESH_UUID_DECLARE_16(0x2a5b) +#define BLE_MESH_UUID_CSC_MEASUREMENT_VAL 0x2a5b +/** @def BLE_MESH_UUID_CSC_FEATURE + * @brief CSC Feature Characteristic + */ +#define BLE_MESH_UUID_CSC_FEATURE BLE_MESH_UUID_DECLARE_16(0x2a5c) +#define BLE_MESH_UUID_CSC_FEATURE_VAL 0x2a5c +/** @def BLE_MESH_UUID_SENSOR_LOCATION + * @brief Sensor Location Characteristic + */ +#define BLE_MESH_UUID_SENSOR_LOCATION BLE_MESH_UUID_DECLARE_16(0x2a5d) +#define BLE_MESH_UUID_SENSOR_LOCATION_VAL 0x2a5d +/** @def BLE_MESH_UUID_SC_CONTROL_POINT + * @brief SC Control Point Characteristic + */ +#define BLE_MESH_UUID_SC_CONTROL_POINT BLE_MESH_UUID_DECLARE_16(0x2a55) +#define BLE_MESH_UUID_SC_CONTROL_POINT_VAl 0x2a55 +/** @def BLE_MESH_UUID_ELEVATION + * @brief Elevation Characteristic + */ +#define BLE_MESH_UUID_ELEVATION BLE_MESH_UUID_DECLARE_16(0x2a6c) +#define BLE_MESH_UUID_ELEVATION_VAL 0x2a6c +/** @def BLE_MESH_UUID_PRESSURE + * @brief Pressure Characteristic + */ +#define BLE_MESH_UUID_PRESSURE BLE_MESH_UUID_DECLARE_16(0x2a6d) +#define BLE_MESH_UUID_PRESSURE_VAL 0x2a6d +/** @def BLE_MESH_UUID_TEMPERATURE + * @brief Temperature Characteristic + */ +#define BLE_MESH_UUID_TEMPERATURE BLE_MESH_UUID_DECLARE_16(0x2a6e) +#define BLE_MESH_UUID_TEMPERATURE_VAL 0x2a6e +/** @def BLE_MESH_UUID_HUMIDITY + * @brief Humidity Characteristic + */ +#define BLE_MESH_UUID_HUMIDITY BLE_MESH_UUID_DECLARE_16(0x2a6f) +#define BLE_MESH_UUID_HUMIDITY_VAL 0x2a6f +/** @def BLE_MESH_UUID_TRUE_WIND_SPEED + * @brief True Wind Speed Characteristic + */ +#define BLE_MESH_UUID_TRUE_WIND_SPEED BLE_MESH_UUID_DECLARE_16(0x2a70) +#define BLE_MESH_UUID_TRUE_WIND_SPEED_VAL 0x2a70 +/** @def BLE_MESH_UUID_TRUE_WIND_DIR + * @brief True Wind Direction Characteristic + */ +#define BLE_MESH_UUID_TRUE_WIND_DIR BLE_MESH_UUID_DECLARE_16(0x2a71) +#define BLE_MESH_UUID_TRUE_WIND_DIR_VAL 0x2a71 +/** @def BLE_MESH_UUID_APPARENT_WIND_SPEED + * @brief Apparent Wind Speed Characteristic + */ +#define BLE_MESH_UUID_APPARENT_WIND_SPEED BLE_MESH_UUID_DECLARE_16(0x2a72) +#define BLE_MESH_UUID_APPARENT_WIND_SPEED_VAL 0x2a72 +/** @def BLE_MESH_UUID_APPARENT_WIND_DIR + * @brief Apparent Wind Direction Characteristic + */ +#define BLE_MESH_UUID_APPARENT_WIND_DIR BLE_MESH_UUID_DECLARE_16(0x2a73) +#define BLE_MESH_UUID_APPARENT_WIND_DIR_VAL 0x2a73 +/** @def BLE_MESH_UUID_GUST_FACTOR + * @brief Gust Factor Characteristic + */ +#define BLE_MESH_UUID_GUST_FACTOR BLE_MESH_UUID_DECLARE_16(0x2a74) +#define BLE_MESH_UUID_GUST_FACTOR_VAL 0x2a74 +/** @def BLE_MESH_UUID_POLLEN_CONCENTRATION + * @brief Pollen Concentration Characteristic + */ +#define BLE_MESH_UUID_POLLEN_CONCENTRATION BLE_MESH_UUID_DECLARE_16(0x2a75) +#define BLE_MESH_UUID_POLLEN_CONCENTRATION_VAL 0x2a75 +/** @def BLE_MESH_UUID_UV_INDEX + * @brief UV Index Characteristic + */ +#define BLE_MESH_UUID_UV_INDEX BLE_MESH_UUID_DECLARE_16(0x2a76) +#define BLE_MESH_UUID_UV_INDEX_VAL 0x2a76 +/** @def BLE_MESH_UUID_IRRADIANCE + * @brief Irradiance Characteristic + */ +#define BLE_MESH_UUID_IRRADIANCE BLE_MESH_UUID_DECLARE_16(0x2a77) +#define BLE_MESH_UUID_IRRADIANCE_VAL 0x2a77 +/** @def BLE_MESH_UUID_RAINFALL + * @brief Rainfall Characteristic + */ +#define BLE_MESH_UUID_RAINFALL BLE_MESH_UUID_DECLARE_16(0x2a78) +#define BLE_MESH_UUID_RAINFALL_VAL 0x2a78 +/** @def BLE_MESH_UUID_WIND_CHILL + * @brief Wind Chill Characteristic + */ +#define BLE_MESH_UUID_WIND_CHILL BLE_MESH_UUID_DECLARE_16(0x2a79) +#define BLE_MESH_UUID_WIND_CHILL_VAL 0x2a79 +/** @def BLE_MESH_UUID_HEAT_INDEX + * @brief Heat Index Characteristic + */ +#define BLE_MESH_UUID_HEAT_INDEX BLE_MESH_UUID_DECLARE_16(0x2a7a) +#define BLE_MESH_UUID_HEAT_INDEX_VAL 0x2a7a +/** @def BLE_MESH_UUID_DEW_POINT + * @brief Dew Point Characteristic + */ +#define BLE_MESH_UUID_DEW_POINT BLE_MESH_UUID_DECLARE_16(0x2a7b) +#define BLE_MESH_UUID_DEW_POINT_VAL 0x2a7b +/** @def BLE_MESH_UUID_DESC_VALUE_CHANGED + * @brief Descriptor Value Changed Characteristic + */ +#define BLE_MESH_UUID_DESC_VALUE_CHANGED BLE_MESH_UUID_DECLARE_16(0x2a7d) +#define BLE_MESH_UUID_DESC_VALUE_CHANGED_VAL 0x2a7d +/** @def BLE_MESH_UUID_MAGN_FLUX_DENSITY_2D + * @brief Magnetic Flux Density - 2D Characteristic + */ +#define BLE_MESH_UUID_MAGN_FLUX_DENSITY_2D BLE_MESH_UUID_DECLARE_16(0x2aa0) +#define BLE_MESH_UUID_MAGN_FLUX_DENSITY_2D_VAL 0x2aa0 +/** @def BLE_MESH_UUID_MAGN_FLUX_DENSITY_3D + * @brief Magnetic Flux Density - 3D Characteristic + */ +#define BLE_MESH_UUID_MAGN_FLUX_DENSITY_3D BLE_MESH_UUID_DECLARE_16(0x2aa1) +#define BLE_MESH_UUID_MAGN_FLUX_DENSITY_3D_VAL 0x2aa1 +/** @def BLE_MESH_UUID_BAR_PRESSURE_TREND + * @brief Barometric Pressure Trend Characteristic + */ +#define BLE_MESH_UUID_BAR_PRESSURE_TREND BLE_MESH_UUID_DECLARE_16(0x2aa3) +#define BLE_MESH_UUID_BAR_PRESSURE_TREND_VAL 0x2aa3 +/** @def BLE_MESH_UUID_MESH_PROV_DATA_IN + * @brief Mesh Provisioning Data In + */ +#define BLE_MESH_UUID_MESH_PROV_DATA_IN BLE_MESH_UUID_DECLARE_16(0x2adb) +#define BLE_MESH_UUID_MESH_PROV_DATA_IN_VAL 0x2adb +/** @def BLE_MESH_UUID_MESH_PROV_DATA_OUT + * @brief Mesh Provisioning Data Out + */ +#define BLE_MESH_UUID_MESH_PROV_DATA_OUT BLE_MESH_UUID_DECLARE_16(0x2adc) +#define BLE_MESH_UUID_MESH_PROV_DATA_OUT_VAL 0x2adc +/** @def BLE_MESH_UUID_MESH_PROXY_DATA_IN + * @brief Mesh Proxy Data In + */ +#define BLE_MESH_UUID_MESH_PROXY_DATA_IN BLE_MESH_UUID_DECLARE_16(0x2add) +#define BLE_MESH_UUID_MESH_PROXY_DATA_IN_VAL 0x2add +/** @def BLE_MESH_UUID_MESH_PROXY_DATA_OUT + * @brief Mesh Proxy Data Out + */ +#define BLE_MESH_UUID_MESH_PROXY_DATA_OUT BLE_MESH_UUID_DECLARE_16(0x2ade) +#define BLE_MESH_UUID_MESH_PROXY_DATA_OUT_VAL 0x2ade + +/* + * Protocol UUIDs + */ +#define BLE_MESH_UUID_SDP BLE_MESH_UUID_DECLARE_16(0x0001) +#define BLE_MESH_UUID_SDP_VAL 0x0001 +#define BLE_MESH_UUID_UDP BLE_MESH_UUID_DECLARE_16(0x0002) +#define BLE_MESH_UUID_UDP_VAL 0x0002 +#define BLE_MESH_UUID_RFCOMM BLE_MESH_UUID_DECLARE_16(0x0003) +#define BLE_MESH_UUID_RFCOMM_VAL 0x0003 +#define BLE_MESH_UUID_TCP BLE_MESH_UUID_DECLARE_16(0x0004) +#define BLE_MESH_UUID_TCP_VAL 0x0004 +#define BLE_MESH_UUID_TCS_BIN BLE_MESH_UUID_DECLARE_16(0x0005) +#define BLE_MESH_UUID_TCS_BIN_VAL 0x0005 +#define BLE_MESH_UUID_TCS_AT BLE_MESH_UUID_DECLARE_16(0x0006) +#define BLE_MESH_UUID_TCS_AT_VAL 0x0006 +#define BLE_MESH_UUID_ATT BLE_MESH_UUID_DECLARE_16(0x0007) +#define BLE_MESH_UUID_ATT_VAL 0x0007 +#define BLE_MESH_UUID_OBEX BLE_MESH_UUID_DECLARE_16(0x0008) +#define BLE_MESH_UUID_OBEX_VAL 0x0008 +#define BLE_MESH_UUID_IP BLE_MESH_UUID_DECLARE_16(0x0009) +#define BLE_MESH_UUID_IP_VAL 0x0009 +#define BLE_MESH_UUID_FTP BLE_MESH_UUID_DECLARE_16(0x000a) +#define BLE_MESH_UUID_FTP_VAL 0x000a +#define BLE_MESH_UUID_HTTP BLE_MESH_UUID_DECLARE_16(0x000c) +#define BLE_MESH_UUID_HTTP_VAL 0x000c +#define BLE_MESH_UUID_BNEP BLE_MESH_UUID_DECLARE_16(0x000f) +#define BLE_MESH_UUID_BNEP_VAL 0x000f +#define BLE_MESH_UUID_UPNP BLE_MESH_UUID_DECLARE_16(0x0010) +#define BLE_MESH_UUID_UPNP_VAL 0x0010 +#define BLE_MESH_UUID_HIDP BLE_MESH_UUID_DECLARE_16(0x0011) +#define BLE_MESH_UUID_HIDP_VAL 0x0011 +#define BLE_MESH_UUID_HCRP_CTRL BLE_MESH_UUID_DECLARE_16(0x0012) +#define BLE_MESH_UUID_HCRP_CTRL_VAL 0x0012 +#define BLE_MESH_UUID_HCRP_DATA BLE_MESH_UUID_DECLARE_16(0x0014) +#define BLE_MESH_UUID_HCRP_DATA_VAL 0x0014 +#define BLE_MESH_UUID_HCRP_NOTE BLE_MESH_UUID_DECLARE_16(0x0016) +#define BLE_MESH_UUID_HCRP_NOTE_VAL 0x0016 +#define BLE_MESH_UUID_AVCTP BLE_MESH_UUID_DECLARE_16(0x0017) +#define BLE_MESH_UUID_AVCTP_VAL 0x0017 +#define BLE_MESH_UUID_AVDTP BLE_MESH_UUID_DECLARE_16(0x0019) +#define BLE_MESH_UUID_AVDTP_VAL 0x0019 +#define BLE_MESH_UUID_CMTP BLE_MESH_UUID_DECLARE_16(0x001b) +#define BLE_MESH_UUID_CMTP_VAL 0x001b +#define BLE_MESH_UUID_UDI BLE_MESH_UUID_DECLARE_16(0x001d) +#define BLE_MESH_UUID_UDI_VAL 0x001d +#define BLE_MESH_UUID_MCAP_CTRL BLE_MESH_UUID_DECLARE_16(0x001e) +#define BLE_MESH_UUID_MCAP_CTRL_VAL 0x001e +#define BLE_MESH_UUID_MCAP_DATA BLE_MESH_UUID_DECLARE_16(0x001f) +#define BLE_MESH_UUID_MCAP_DATA_VAL 0x001f +#define BLE_MESH_UUID_L2CAP BLE_MESH_UUID_DECLARE_16(0x0100) +#define BLE_MESH_UUID_L2CAP_VAL 0x0100 + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _BLE_MESH_UUID_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/lpn.c b/components/bt/ble_mesh/mesh_core/lpn.c new file mode 100644 index 0000000000..8adf2a24bb --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/lpn.c @@ -0,0 +1,1057 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_LOW_POWER) + +#include "mesh_buf.h" +#include "mesh_kernel.h" +#include "mesh_trace.h" +#include "mesh_main.h" + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "net.h" +#include "transport.h" +#include "access.h" +#include "beacon.h" +#include "foundation.h" +#include "lpn.h" + +#ifdef CONFIG_BLE_MESH_LOW_POWER + +#if defined(CONFIG_BLE_MESH_LPN_AUTO) +#define LPN_AUTO_TIMEOUT K_SECONDS(CONFIG_BLE_MESH_LPN_AUTO_TIMEOUT) +#else +#define LPN_AUTO_TIMEOUT 0 +#endif + +#define LPN_RECV_DELAY CONFIG_BLE_MESH_LPN_RECV_DELAY +#define SCAN_LATENCY MIN(CONFIG_BLE_MESH_LPN_SCAN_LATENCY, \ + LPN_RECV_DELAY) + +#define FRIEND_REQ_RETRY_TIMEOUT K_SECONDS(CONFIG_BLE_MESH_LPN_RETRY_TIMEOUT) + +#define FRIEND_REQ_WAIT K_MSEC(100) +#define FRIEND_REQ_SCAN K_SECONDS(1) +#define FRIEND_REQ_TIMEOUT (FRIEND_REQ_WAIT + FRIEND_REQ_SCAN) + +#define POLL_RETRY_TIMEOUT K_MSEC(100) + +#define REQ_RETRY_DURATION(lpn) (4 * (LPN_RECV_DELAY + (lpn)->adv_duration + \ + (lpn)->recv_win + POLL_RETRY_TIMEOUT)) + +#define POLL_TIMEOUT_INIT (CONFIG_BLE_MESH_LPN_INIT_POLL_TIMEOUT * 100) +#define POLL_TIMEOUT_MAX(lpn) ((CONFIG_BLE_MESH_LPN_POLL_TIMEOUT * 100) - \ + REQ_RETRY_DURATION(lpn)) + +/* Update 4 to 20 for BQB test case MESH/NODE/FRND/LPM/BI-02-C */ +#define REQ_ATTEMPTS(lpn) (POLL_TIMEOUT_MAX(lpn) < K_SECONDS(3) ? 2 : 4) + +#define CLEAR_ATTEMPTS 2 + +#define LPN_CRITERIA ((CONFIG_BLE_MESH_LPN_MIN_QUEUE_SIZE) | \ + (CONFIG_BLE_MESH_LPN_RSSI_FACTOR << 3) | \ + (CONFIG_BLE_MESH_LPN_RECV_WIN_FACTOR << 5)) + +#define POLL_TO(to) { (u8_t)((to) >> 16), (u8_t)((to) >> 8), (u8_t)(to) } +#define LPN_POLL_TO POLL_TO(CONFIG_BLE_MESH_LPN_POLL_TIMEOUT) + +/* 2 transmissions, 20ms interval */ +#define POLL_XMIT BLE_MESH_TRANSMIT(1, 20) + +static void (*lpn_cb)(u16_t friend_addr, bool established); + +#if defined(CONFIG_BLE_MESH_DEBUG_LOW_POWER) +static const char *state2str(int state) +{ + switch (state) { + case BLE_MESH_LPN_DISABLED: + return "disabled"; + case BLE_MESH_LPN_CLEAR: + return "clear"; + case BLE_MESH_LPN_TIMER: + return "timer"; + case BLE_MESH_LPN_ENABLED: + return "enabled"; + case BLE_MESH_LPN_REQ_WAIT: + return "req wait"; + case BLE_MESH_LPN_WAIT_OFFER: + return "wait offer"; + case BLE_MESH_LPN_ESTABLISHED: + return "established"; + case BLE_MESH_LPN_RECV_DELAY: + return "recv delay"; + case BLE_MESH_LPN_WAIT_UPDATE: + return "wait update"; + default: + return "(unknown)"; + } +} +#endif /* CONFIG_BLE_MESH_DEBUG_LOW_POWER */ + +static inline void lpn_set_state(int state) +{ +#if defined(CONFIG_BLE_MESH_DEBUG_LOW_POWER) + BT_DBG("%s -> %s", state2str(bt_mesh.lpn.state), state2str(state)); +#endif + bt_mesh.lpn.state = state; +} + +static inline void group_zero(bt_mesh_atomic_t *target) +{ +#if CONFIG_BLE_MESH_LPN_GROUPS > 32 + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) { + bt_mesh_atomic_set(&target[i], 0); + } +#else + bt_mesh_atomic_set(target, 0); +#endif +} + +static inline void group_set(bt_mesh_atomic_t *target, bt_mesh_atomic_t *source) +{ +#if CONFIG_BLE_MESH_LPN_GROUPS > 32 + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) { + (void)bt_mesh_atomic_or(&target[i], bt_mesh_atomic_get(&source[i])); + } +#else + (void)bt_mesh_atomic_or(target, bt_mesh_atomic_get(source)); +#endif +} + +static inline void group_clear(bt_mesh_atomic_t *target, bt_mesh_atomic_t *source) +{ +#if CONFIG_BLE_MESH_LPN_GROUPS > 32 + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) { + (void)bt_mesh_atomic_and(&target[i], ~bt_mesh_atomic_get(&source[i])); + } +#else + (void)bt_mesh_atomic_and(target, ~bt_mesh_atomic_get(source)); +#endif +} + +static void clear_friendship(bool force, bool disable); + +static void friend_clear_sent(int err, void *user_data) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + /* We're switching away from Low Power behavior, so permanently + * enable scanning. + */ + bt_mesh_scan_enable(); + + lpn->req_attempts++; + + if (err) { + BT_ERR("%s, Sending Friend Request failed (err %d)", __func__, err); + lpn_set_state(BLE_MESH_LPN_ENABLED); + clear_friendship(false, lpn->disable); + return; + } + + lpn_set_state(BLE_MESH_LPN_CLEAR); + k_delayed_work_submit(&lpn->timer, FRIEND_REQ_TIMEOUT); +} + +static const struct bt_mesh_send_cb clear_sent_cb = { + .end = friend_clear_sent, +}; + +static int send_friend_clear(void) +{ + struct bt_mesh_msg_ctx ctx = { + .net_idx = bt_mesh.sub[0].net_idx, + .app_idx = BLE_MESH_KEY_UNUSED, + .addr = bt_mesh.lpn.frnd, + .send_ttl = 0, + }; + struct bt_mesh_net_tx tx = { + .sub = &bt_mesh.sub[0], + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = bt_mesh_net_transmit_get(), + }; + struct bt_mesh_ctl_friend_clear req = { + .lpn_addr = sys_cpu_to_be16(tx.src), + .lpn_counter = sys_cpu_to_be16(bt_mesh.lpn.counter), + }; + + BT_DBG("%s", __func__); + + return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR, &req, + sizeof(req), NULL, &clear_sent_cb, NULL); +} + +static void clear_friendship(bool force, bool disable) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + BT_DBG("force %u disable %u", force, disable); + + if (!force && lpn->established && !lpn->clear_success && + lpn->req_attempts < CLEAR_ATTEMPTS) { + send_friend_clear(); + lpn->disable = disable; + return; + } + + bt_mesh_rx_reset(); + + k_delayed_work_cancel(&lpn->timer); + + friend_cred_del(bt_mesh.sub[0].net_idx, lpn->frnd); + + if (lpn->clear_success) { + lpn->old_friend = BLE_MESH_ADDR_UNASSIGNED; + } else { + lpn->old_friend = lpn->frnd; + } + + if (lpn_cb && lpn->frnd != BLE_MESH_ADDR_UNASSIGNED) { + lpn_cb(lpn->frnd, false); + } + + lpn->frnd = BLE_MESH_ADDR_UNASSIGNED; + lpn->fsn = 0U; + lpn->req_attempts = 0U; + lpn->recv_win = 0U; + lpn->queue_size = 0U; + lpn->disable = 0U; + lpn->sent_req = 0U; + lpn->established = 0U; + lpn->clear_success = 0U; + + group_zero(lpn->added); + group_zero(lpn->pending); + group_zero(lpn->to_remove); + + /* Set this to 1 to force group subscription when the next + * Friendship is created, in case lpn->groups doesn't get + * modified meanwhile. + */ + lpn->groups_changed = 1U; + + if (disable) { + lpn_set_state(BLE_MESH_LPN_DISABLED); + return; + } + + lpn_set_state(BLE_MESH_LPN_ENABLED); + k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT); +} + +static void friend_req_sent(u16_t duration, int err, void *user_data) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (err) { + BT_ERR("%s, Sending Friend Request failed (err %d)", __func__, err); + return; + } + + lpn->adv_duration = duration; + + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + k_delayed_work_submit(&lpn->timer, FRIEND_REQ_WAIT); + lpn_set_state(BLE_MESH_LPN_REQ_WAIT); + } else { + k_delayed_work_submit(&lpn->timer, + duration + FRIEND_REQ_TIMEOUT); + lpn_set_state(BLE_MESH_LPN_WAIT_OFFER); + } +} + +static const struct bt_mesh_send_cb friend_req_sent_cb = { + .start = friend_req_sent, +}; + +static int send_friend_req(struct bt_mesh_lpn *lpn) +{ + const struct bt_mesh_comp *comp = bt_mesh_comp_get(); + struct bt_mesh_msg_ctx ctx = { + .net_idx = bt_mesh.sub[0].net_idx, + .app_idx = BLE_MESH_KEY_UNUSED, + .addr = BLE_MESH_ADDR_FRIENDS, + .send_ttl = 0, + }; + struct bt_mesh_net_tx tx = { + .sub = &bt_mesh.sub[0], + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = POLL_XMIT, + }; + struct bt_mesh_ctl_friend_req req = { + .criteria = LPN_CRITERIA, + .recv_delay = LPN_RECV_DELAY, + .poll_to = LPN_POLL_TO, + .prev_addr = lpn->old_friend, + .num_elem = comp->elem_count, + .lpn_counter = sys_cpu_to_be16(lpn->counter), + }; + + BT_DBG("%s", __func__); + + return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_REQ, &req, + sizeof(req), NULL, &friend_req_sent_cb, NULL); +} + +static void req_sent(u16_t duration, int err, void *user_data) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + +#if defined(CONFIG_BLE_MESH_DEBUG_LOW_POWER) + BT_DBG("req 0x%02x duration %u err %d state %s", + lpn->sent_req, duration, err, state2str(lpn->state)); +#endif + + if (err) { + BT_ERR("%s, Sending request failed (err %d)", __func__, err); + lpn->sent_req = 0U; + group_zero(lpn->pending); + return; + } + + lpn->req_attempts++; + lpn->adv_duration = duration; + + if (lpn->established || IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + lpn_set_state(BLE_MESH_LPN_RECV_DELAY); + /* We start scanning a bit early to elimitate risk of missing + * response data due to HCI and other latencies. + */ + k_delayed_work_submit(&lpn->timer, + LPN_RECV_DELAY - SCAN_LATENCY); + } else { + lpn_set_state(BLE_MESH_LPN_OFFER_RECV); + k_delayed_work_submit(&lpn->timer, + LPN_RECV_DELAY + duration + + lpn->recv_win); + } +} + +static const struct bt_mesh_send_cb req_sent_cb = { + .start = req_sent, +}; + +static int send_friend_poll(void) +{ + struct bt_mesh_msg_ctx ctx = { + .net_idx = bt_mesh.sub[0].net_idx, + .app_idx = BLE_MESH_KEY_UNUSED, + .addr = bt_mesh.lpn.frnd, + .send_ttl = 0, + }; + struct bt_mesh_net_tx tx = { + .sub = &bt_mesh.sub[0], + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = POLL_XMIT, + .friend_cred = true, + }; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + u8_t fsn = lpn->fsn; + int err; + + BT_DBG("lpn->sent_req 0x%02x", lpn->sent_req); + + if (lpn->sent_req) { + if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) { + lpn->pending_poll = 1U; + } + + return 0; + } + + err = bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_POLL, &fsn, 1, + NULL, &req_sent_cb, NULL); + if (err == 0) { + lpn->pending_poll = 0U; + lpn->sent_req = TRANS_CTL_OP_FRIEND_POLL; + } + + return err; +} + +void bt_mesh_lpn_disable(bool force) +{ + if (bt_mesh.lpn.state == BLE_MESH_LPN_DISABLED) { + return; + } + + clear_friendship(force, true); +} + +int bt_mesh_lpn_set(bool enable) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (enable) { + if (lpn->state != BLE_MESH_LPN_DISABLED) { + return 0; + } + } else { + if (lpn->state == BLE_MESH_LPN_DISABLED) { + return 0; + } + } + + if (!bt_mesh_is_provisioned()) { + if (enable) { + lpn_set_state(BLE_MESH_LPN_ENABLED); + } else { + lpn_set_state(BLE_MESH_LPN_DISABLED); + } + + return 0; + } + + if (enable) { + lpn_set_state(BLE_MESH_LPN_ENABLED); + + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } + + send_friend_req(lpn); + } else { + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_AUTO) && + lpn->state == BLE_MESH_LPN_TIMER) { + k_delayed_work_cancel(&lpn->timer); + lpn_set_state(BLE_MESH_LPN_DISABLED); + } else { + bt_mesh_lpn_disable(false); + } + } + + return 0; +} + +static void friend_response_received(struct bt_mesh_lpn *lpn) +{ + BT_DBG("lpn->sent_req 0x%02x", lpn->sent_req); + + if (lpn->sent_req == TRANS_CTL_OP_FRIEND_POLL) { + lpn->fsn++; + } + + k_delayed_work_cancel(&lpn->timer); + bt_mesh_scan_disable(); + lpn_set_state(BLE_MESH_LPN_ESTABLISHED); + lpn->req_attempts = 0U; + lpn->sent_req = 0U; +} + +void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (lpn->state == BLE_MESH_LPN_TIMER) { + BT_DBG("Restarting establishment timer"); + k_delayed_work_submit(&lpn->timer, LPN_AUTO_TIMEOUT); + return; + } + + if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) { + BT_WARN("Unexpected message withouth a preceding Poll"); + return; + } + + friend_response_received(lpn); + + BT_DBG("Requesting more messages from Friend"); + + send_friend_poll(); +} + +int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_offer *msg = (void *)buf->data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + struct bt_mesh_subnet *sub = rx->sub; + struct friend_cred *cred; + u16_t frnd_counter; + int err; + + if (buf->len < sizeof(*msg)) { + BT_WARN("Too short Friend Offer"); + return -EINVAL; + } + + if (lpn->state != BLE_MESH_LPN_WAIT_OFFER) { + BT_WARN("Ignoring unexpected Friend Offer"); + return 0; + } + + if (!msg->recv_win) { + BT_WARN("Prohibited ReceiveWindow value"); + return -EINVAL; + } + + frnd_counter = sys_be16_to_cpu(msg->frnd_counter); + + BT_DBG("recv_win %u queue_size %u sub_list_size %u rssi %d counter %u", + msg->recv_win, msg->queue_size, msg->sub_list_size, msg->rssi, + frnd_counter); + + lpn->frnd = rx->ctx.addr; + + cred = friend_cred_create(sub, lpn->frnd, lpn->counter, frnd_counter); + if (!cred) { + lpn->frnd = BLE_MESH_ADDR_UNASSIGNED; + return -ENOMEM; + } + + /* TODO: Add offer acceptance criteria check */ + + k_delayed_work_cancel(&lpn->timer); + + lpn->recv_win = msg->recv_win; + lpn->queue_size = msg->queue_size; + + err = send_friend_poll(); + if (err) { + friend_cred_clear(cred); + lpn->frnd = BLE_MESH_ADDR_UNASSIGNED; + lpn->recv_win = 0U; + lpn->queue_size = 0U; + return err; + } + + lpn->counter++; + + return 0; +} + +int bt_mesh_lpn_friend_clear_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_clear_confirm *msg = (void *)buf->data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + u16_t addr, counter; + + if (buf->len < sizeof(*msg)) { + BT_WARN("Too short Friend Clear Confirm"); + return -EINVAL; + } + + if (lpn->state != BLE_MESH_LPN_CLEAR) { + BT_WARN("Ignoring unexpected Friend Clear Confirm"); + return 0; + } + + addr = sys_be16_to_cpu(msg->lpn_addr); + counter = sys_be16_to_cpu(msg->lpn_counter); + + BT_DBG("LPNAddress 0x%04x LPNCounter 0x%04x", addr, counter); + + if (addr != bt_mesh_primary_addr() || counter != lpn->counter) { + BT_WARN("Invalid parameters in Friend Clear Confirm"); + return 0; + } + + lpn->clear_success = 1U; + clear_friendship(false, lpn->disable); + + return 0; +} + +static void lpn_group_add(u16_t group) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + u16_t *free_slot = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(lpn->groups); i++) { + if (lpn->groups[i] == group) { + bt_mesh_atomic_clear_bit(lpn->to_remove, i); + return; + } + + if (!free_slot && lpn->groups[i] == BLE_MESH_ADDR_UNASSIGNED) { + free_slot = &lpn->groups[i]; + } + } + + if (!free_slot) { + BT_WARN("Friend Subscription List exceeded!"); + return; + } + + *free_slot = group; + lpn->groups_changed = 1U; +} + +static void lpn_group_del(u16_t group) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + int i; + + for (i = 0; i < ARRAY_SIZE(lpn->groups); i++) { + if (lpn->groups[i] == group) { + if (bt_mesh_atomic_test_bit(lpn->added, i) || + bt_mesh_atomic_test_bit(lpn->pending, i)) { + bt_mesh_atomic_set_bit(lpn->to_remove, i); + lpn->groups_changed = 1U; + } else { + lpn->groups[i] = BLE_MESH_ADDR_UNASSIGNED; + } + } + } +} + +static inline int group_popcount(bt_mesh_atomic_t *target) +{ +#if CONFIG_BLE_MESH_LPN_GROUPS > 32 + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) { + count += popcount(bt_mesh_atomic_get(&target[i])); + } +#else + return popcount(bt_mesh_atomic_get(target)); +#endif +} + +static bool sub_update(u8_t op) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + int added_count = group_popcount(lpn->added); + struct bt_mesh_msg_ctx ctx = { + .net_idx = bt_mesh.sub[0].net_idx, + .app_idx = BLE_MESH_KEY_UNUSED, + .addr = lpn->frnd, + .send_ttl = 0, + }; + struct bt_mesh_net_tx tx = { + .sub = &bt_mesh.sub[0], + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = POLL_XMIT, + .friend_cred = true, + }; + struct bt_mesh_ctl_friend_sub req; + size_t i, g; + + BT_DBG("op 0x%02x sent_req 0x%02x", op, lpn->sent_req); + + if (lpn->sent_req) { + return false; + } + + for (i = 0, g = 0; i < ARRAY_SIZE(lpn->groups); i++) { + if (lpn->groups[i] == BLE_MESH_ADDR_UNASSIGNED) { + continue; + } + + if (op == TRANS_CTL_OP_FRIEND_SUB_ADD) { + if (bt_mesh_atomic_test_bit(lpn->added, i)) { + continue; + } + } else { + if (!bt_mesh_atomic_test_bit(lpn->to_remove, i)) { + continue; + } + } + + if (added_count + g >= lpn->queue_size) { + BT_WARN("%s, Friend Queue Size exceeded", __func__); + break; + } + + req.addr_list[g++] = sys_cpu_to_be16(lpn->groups[i]); + bt_mesh_atomic_set_bit(lpn->pending, i); + + if (g == ARRAY_SIZE(req.addr_list)) { + break; + } + } + + if (g == 0) { + group_zero(lpn->pending); + return false; + } + + req.xact = lpn->xact_next++; + + if (bt_mesh_ctl_send(&tx, op, &req, 1 + g * 2, NULL, + &req_sent_cb, NULL) < 0) { + group_zero(lpn->pending); + return false; + } + + lpn->xact_pending = req.xact; + lpn->sent_req = op; + return true; +} + +static void update_timeout(struct bt_mesh_lpn *lpn) +{ + if (lpn->established) { + BT_WARN("No response from Friend during ReceiveWindow"); + bt_mesh_scan_disable(); + lpn_set_state(BLE_MESH_LPN_ESTABLISHED); + k_delayed_work_submit(&lpn->timer, POLL_RETRY_TIMEOUT); + } else { + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } + + if (lpn->req_attempts < 6) { + BT_WARN("Retrying first Friend Poll"); + lpn->sent_req = 0U; + if (send_friend_poll() == 0) { + return; + } + } + + BT_ERR("Timed out waiting for first Friend Update"); + clear_friendship(false, false); + } +} + +static void lpn_timeout(struct k_work *work) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + +#if defined(CONFIG_BLE_MESH_DEBUG_LOW_POWER) + BT_DBG("state: %s", state2str(lpn->state)); +#endif + + switch (lpn->state) { + case BLE_MESH_LPN_DISABLED: + break; + case BLE_MESH_LPN_CLEAR: + clear_friendship(false, bt_mesh.lpn.disable); + break; + case BLE_MESH_LPN_TIMER: + BT_DBG("Starting to look for Friend nodes"); + lpn_set_state(BLE_MESH_LPN_ENABLED); + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } + /* fall through */ + case BLE_MESH_LPN_ENABLED: + send_friend_req(lpn); + break; + case BLE_MESH_LPN_REQ_WAIT: + bt_mesh_scan_enable(); + k_delayed_work_submit(&lpn->timer, + lpn->adv_duration + FRIEND_REQ_SCAN); + lpn_set_state(BLE_MESH_LPN_WAIT_OFFER); + break; + case BLE_MESH_LPN_WAIT_OFFER: + BT_WARN("No acceptable Friend Offers received"); + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } + lpn->counter++; + lpn_set_state(BLE_MESH_LPN_ENABLED); + k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT); + break; + case BLE_MESH_LPN_OFFER_RECV: + BT_WARN("No Friend Update received after the first Friend Poll"); + lpn->sent_req = 0U; + send_friend_poll(); + break; + case BLE_MESH_LPN_ESTABLISHED: + if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) { + u8_t req = lpn->sent_req; + + lpn->sent_req = 0U; + + if (!req || req == TRANS_CTL_OP_FRIEND_POLL) { + send_friend_poll(); + } else { + sub_update(req); + } + + break; + } + + BT_ERR("No response from Friend after %u retries", + lpn->req_attempts); + lpn->req_attempts = 0U; + clear_friendship(false, false); + break; + case BLE_MESH_LPN_RECV_DELAY: + k_delayed_work_submit(&lpn->timer, + lpn->adv_duration + SCAN_LATENCY + + lpn->recv_win); + bt_mesh_scan_enable(); + lpn_set_state(BLE_MESH_LPN_WAIT_UPDATE); + break; + case BLE_MESH_LPN_WAIT_UPDATE: + update_timeout(lpn); + break; + default: + __ASSERT(0, "Unhandled LPN state"); + break; + } +} + +void bt_mesh_lpn_group_add(u16_t group) +{ + BT_DBG("group 0x%04x", group); + + lpn_group_add(group); + + if (!bt_mesh_lpn_established() || bt_mesh.lpn.sent_req) { + return; + } + + sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD); +} + +void bt_mesh_lpn_group_del(u16_t *groups, size_t group_count) +{ + int i; + + for (i = 0; i < group_count; i++) { + if (groups[i] != BLE_MESH_ADDR_UNASSIGNED) { + BT_DBG("group 0x%04x", groups[i]); + lpn_group_del(groups[i]); + } + } + + if (!bt_mesh_lpn_established() || bt_mesh.lpn.sent_req) { + return; + } + + sub_update(TRANS_CTL_OP_FRIEND_SUB_REM); +} + +static s32_t poll_timeout(struct bt_mesh_lpn *lpn) +{ + /* If we're waiting for segment acks keep polling at high freq */ + if (bt_mesh_tx_in_progress()) { + return MIN(POLL_TIMEOUT_MAX(lpn), K_SECONDS(1)); + } + + if (lpn->poll_timeout < POLL_TIMEOUT_MAX(lpn)) { + lpn->poll_timeout *= 2; + lpn->poll_timeout = MIN(lpn->poll_timeout, + POLL_TIMEOUT_MAX(lpn)); + } + + BT_DBG("Poll Timeout is %ums", lpn->poll_timeout); + + return lpn->poll_timeout; +} + +int bt_mesh_lpn_friend_sub_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_sub_confirm *msg = (void *)buf->data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (buf->len < sizeof(*msg)) { + BT_WARN("Too short Friend Subscription Confirm"); + return -EINVAL; + } + + BT_DBG("xact 0x%02x", msg->xact); + + if (!lpn->sent_req) { + BT_WARN("No pending subscription list message"); + return 0; + } + + if (msg->xact != lpn->xact_pending) { + BT_WARN("Transaction mismatch (0x%02x != 0x%02x)", + msg->xact, lpn->xact_pending); + return 0; + } + + if (lpn->sent_req == TRANS_CTL_OP_FRIEND_SUB_ADD) { + group_set(lpn->added, lpn->pending); + group_zero(lpn->pending); + } else if (lpn->sent_req == TRANS_CTL_OP_FRIEND_SUB_REM) { + int i; + + group_clear(lpn->added, lpn->pending); + + for (i = 0; i < ARRAY_SIZE(lpn->groups); i++) { + if (bt_mesh_atomic_test_and_clear_bit(lpn->pending, i) && + bt_mesh_atomic_test_and_clear_bit(lpn->to_remove, i)) { + lpn->groups[i] = BLE_MESH_ADDR_UNASSIGNED; + } + } + } else { + BT_WARN("Unexpected Friend Subscription Confirm"); + return 0; + } + + friend_response_received(lpn); + + if (lpn->groups_changed) { + sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD); + sub_update(TRANS_CTL_OP_FRIEND_SUB_REM); + + if (!lpn->sent_req) { + lpn->groups_changed = 0U; + } + } + + if (lpn->pending_poll) { + send_friend_poll(); + } + + if (!lpn->sent_req) { + k_delayed_work_submit(&lpn->timer, poll_timeout(lpn)); + } + + return 0; +} + +int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_update *msg = (void *)buf->data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + struct bt_mesh_subnet *sub = rx->sub; + u32_t iv_index; + + if (buf->len < sizeof(*msg)) { + BT_WARN("Too short Friend Update"); + return -EINVAL; + } + + if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) { + BT_WARN("Unexpected friend update"); + return 0; + } + + if (sub->kr_phase == BLE_MESH_KR_PHASE_2 && !rx->new_key) { + BT_WARN("Ignoring Phase 2 KR Update secured using old key"); + return 0; + } + + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_INITIATOR) && + (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS) == + BLE_MESH_IV_UPDATE(msg->flags))) { + bt_mesh_beacon_ivu_initiator(false); + } + + if (!lpn->established) { + /* This is normally checked on the transport layer, however + * in this state we're also still accepting master + * credentials so we need to ensure the right ones (Friend + * Credentials) were used for this message. + */ + if (!rx->friend_cred) { + BT_WARN("Friend Update with wrong credentials"); + return -EINVAL; + } + + lpn->established = 1U; + + BT_INFO("Friendship established with 0x%04x", lpn->frnd); + + if (lpn_cb) { + lpn_cb(lpn->frnd, true); + } + + /* Set initial poll timeout */ + lpn->poll_timeout = MIN(POLL_TIMEOUT_MAX(lpn), + POLL_TIMEOUT_INIT); + } + + friend_response_received(lpn); + + iv_index = sys_be32_to_cpu(msg->iv_index); + + BT_DBG("flags 0x%02x iv_index 0x%08x md %u", msg->flags, iv_index, + msg->md); + + if (bt_mesh_kr_update(sub, BLE_MESH_KEY_REFRESH(msg->flags), + rx->new_key)) { + bt_mesh_net_beacon_update(sub); + } + + bt_mesh_net_iv_update(iv_index, BLE_MESH_IV_UPDATE(msg->flags)); + + if (lpn->groups_changed) { + sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD); + sub_update(TRANS_CTL_OP_FRIEND_SUB_REM); + + if (!lpn->sent_req) { + lpn->groups_changed = 0U; + } + } + + if (msg->md) { + BT_DBG("Requesting for more messages"); + send_friend_poll(); + } + + if (!lpn->sent_req) { + k_delayed_work_submit(&lpn->timer, poll_timeout(lpn)); + } + + return 0; +} + +int bt_mesh_lpn_poll(void) +{ + if (!bt_mesh.lpn.established) { + return -EAGAIN; + } + + BT_DBG("Requesting more messages"); + + return send_friend_poll(); +} + +void bt_mesh_lpn_set_cb(void (*cb)(u16_t friend_addr, bool established)) +{ + lpn_cb = cb; +} + +int bt_mesh_lpn_init(void) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + BT_DBG("%s", __func__); + + k_delayed_work_init(&lpn->timer, lpn_timeout); + + if (lpn->state == BLE_MESH_LPN_ENABLED) { + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } else { + bt_mesh_scan_enable(); + } + + send_friend_req(lpn); + } else { + bt_mesh_scan_enable(); + + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_AUTO)) { + BT_DBG("Waiting %u ms for messages", LPN_AUTO_TIMEOUT); + lpn_set_state(BLE_MESH_LPN_TIMER); + k_delayed_work_submit(&lpn->timer, LPN_AUTO_TIMEOUT); + } + } + + return 0; +} + +#endif /* CONFIG_BLE_MESH_LOW_POWER */ diff --git a/components/bt/ble_mesh/mesh_core/lpn.h b/components/bt/ble_mesh/mesh_core/lpn.h new file mode 100644 index 0000000000..ad870e99e1 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/lpn.h @@ -0,0 +1,67 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _LPN_H_ +#define _LPN_H_ + +int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_lpn_friend_clear_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_lpn_friend_sub_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); + +static inline bool bt_mesh_lpn_established(void) +{ +#if defined(CONFIG_BLE_MESH_LOW_POWER) + return bt_mesh.lpn.established; +#else + return false; +#endif +} + +static inline bool bt_mesh_lpn_match(u16_t addr) +{ +#if defined(CONFIG_BLE_MESH_LOW_POWER) + if (bt_mesh_lpn_established()) { + return (addr == bt_mesh.lpn.frnd); + } +#endif + return false; +} + +static inline bool bt_mesh_lpn_waiting_update(void) +{ +#if defined(CONFIG_BLE_MESH_LOW_POWER) + return (bt_mesh.lpn.state == BLE_MESH_LPN_WAIT_UPDATE); +#else + return false; +#endif +} + +static inline bool bt_mesh_lpn_timer(void) +{ +#if defined(CONFIG_BLE_MESH_LPN_AUTO) + return (bt_mesh.lpn.state == BLE_MESH_LPN_TIMER); +#else + return false; +#endif +} + +void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx); + +void bt_mesh_lpn_group_add(u16_t group); +void bt_mesh_lpn_group_del(u16_t *groups, size_t group_count); + +void bt_mesh_lpn_disable(bool force); + +int bt_mesh_lpn_init(void); + +#endif /* _LPN_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/mesh.h b/components/bt/ble_mesh/mesh_core/mesh.h new file mode 100644 index 0000000000..c536184aa9 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/mesh.h @@ -0,0 +1,22 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MESH_H_ +#define _MESH_H_ + +#define BLE_MESH_KEY_PRIMARY 0x0000 +#define BLE_MESH_KEY_ANY 0xffff + +#define BLE_MESH_ADDR_IS_UNICAST(addr) ((addr) && (addr) < 0x8000) +#define BLE_MESH_ADDR_IS_GROUP(addr) ((addr) >= 0xc000 && (addr) <= 0xff00) +#define BLE_MESH_ADDR_IS_VIRTUAL(addr) ((addr) >= 0x8000 && (addr) < 0xc000) +#define BLE_MESH_ADDR_IS_RFU(addr) ((addr) >= 0xff00 && (addr) <= 0xfffb) + +struct bt_mesh_net; + +#endif /* _MESH_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/mesh_aes_encrypt.c b/components/bt/ble_mesh/mesh_core/mesh_aes_encrypt.c new file mode 100644 index 0000000000..2d1842cc4b --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/mesh_aes_encrypt.c @@ -0,0 +1,409 @@ +/* aes_encrypt.c - TinyCrypt implementation of AES encryption procedure */ + +/* + * Copyright (C) 2017 by Intel Corporation, All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "mesh_aes_encrypt.h" +#include "mesh_util.h" +#include "sdkconfig.h" + +/* max number of calls until change the key (2^48).*/ +const static uint64_t MAX_CALLS = ((uint64_t)1 << 48); + +/* + * gf_wrap -- In our implementation, GF(2^128) is represented as a 16 byte + * array with byte 0 the most significant and byte 15 the least significant. + * High bit carry reduction is based on the primitive polynomial + * + * X^128 + X^7 + X^2 + X + 1, + * + * which leads to the reduction formula X^128 = X^7 + X^2 + X + 1. Indeed, + * since 0 = (X^128 + X^7 + X^2 + 1) mod (X^128 + X^7 + X^2 + X + 1) and since + * addition of polynomials with coefficients in Z/Z(2) is just XOR, we can + * add X^128 to both sides to get + * + * X^128 = (X^7 + X^2 + X + 1) mod (X^128 + X^7 + X^2 + X + 1) + * + * and the coefficients of the polynomial on the right hand side form the + * string 1000 0111 = 0x87, which is the value of gf_wrap. + * + * This gets used in the following way. Doubling in GF(2^128) is just a left + * shift by 1 bit, except when the most significant bit is 1. In the latter + * case, the relation X^128 = X^7 + X^2 + X + 1 says that the high order bit + * that overflows beyond 128 bits can be replaced by addition of + * X^7 + X^2 + X + 1 <--> 0x87 to the low order 128 bits. Since addition + * in GF(2^128) is represented by XOR, we therefore only have to XOR 0x87 + * into the low order byte after a left shift when the starting high order + * bit is 1. + */ +const unsigned char gf_wrap = 0x87; + +static const uint8_t sbox[256] = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, + 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, + 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, + 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, + 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, + 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, + 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, + 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, + 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, + 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, + 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, + 0xb0, 0x54, 0xbb, 0x16 +}; + +static inline unsigned int rotword(unsigned int a) +{ + return (((a) >> 24) | ((a) << 8)); +} + +#define subbyte(a, o) (sbox[((a) >> (o))&0xff] << (o)) +#define subword(a) (subbyte(a, 24)|subbyte(a, 16)|subbyte(a, 8)|subbyte(a, 0)) + +int tc_aes128_set_encrypt_key(TCAesKeySched_t s, const uint8_t *k) +{ + const unsigned int rconst[11] = { + 0x00000000, 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1b000000, 0x36000000 + }; + unsigned int i; + unsigned int t; + + if (s == (TCAesKeySched_t) 0) { + return TC_CRYPTO_FAIL; + } else if (k == (const uint8_t *) 0) { + return TC_CRYPTO_FAIL; + } + + for (i = 0; i < Nk; ++i) { + s->words[i] = (k[Nb * i] << 24) | (k[Nb * i + 1] << 16) | + (k[Nb * i + 2] << 8) | (k[Nb * i + 3]); + } + + for (; i < (Nb * (Nr + 1)); ++i) { + t = s->words[i - 1]; + if ((i % Nk) == 0) { + t = subword(rotword(t)) ^ rconst[i / Nk]; + } + s->words[i] = s->words[i - Nk] ^ t; + } + + return TC_CRYPTO_SUCCESS; +} + +static inline void add_round_key(uint8_t *s, const unsigned int *k) +{ + s[0] ^= (uint8_t)(k[0] >> 24); s[1] ^= (uint8_t)(k[0] >> 16); + s[2] ^= (uint8_t)(k[0] >> 8); s[3] ^= (uint8_t)(k[0]); + s[4] ^= (uint8_t)(k[1] >> 24); s[5] ^= (uint8_t)(k[1] >> 16); + s[6] ^= (uint8_t)(k[1] >> 8); s[7] ^= (uint8_t)(k[1]); + s[8] ^= (uint8_t)(k[2] >> 24); s[9] ^= (uint8_t)(k[2] >> 16); + s[10] ^= (uint8_t)(k[2] >> 8); s[11] ^= (uint8_t)(k[2]); + s[12] ^= (uint8_t)(k[3] >> 24); s[13] ^= (uint8_t)(k[3] >> 16); + s[14] ^= (uint8_t)(k[3] >> 8); s[15] ^= (uint8_t)(k[3]); +} + +static inline void sub_bytes(uint8_t *s) +{ + unsigned int i; + + for (i = 0; i < (Nb * Nk); ++i) { + s[i] = sbox[s[i]]; + } +} + +#define triple(a)(_double_byte(a)^(a)) + +static inline void mult_row_column(uint8_t *out, const uint8_t *in) +{ + out[0] = _double_byte(in[0]) ^ triple(in[1]) ^ in[2] ^ in[3]; + out[1] = in[0] ^ _double_byte(in[1]) ^ triple(in[2]) ^ in[3]; + out[2] = in[0] ^ in[1] ^ _double_byte(in[2]) ^ triple(in[3]); + out[3] = triple(in[0]) ^ in[1] ^ in[2] ^ _double_byte(in[3]); +} + +static inline void mix_columns(uint8_t *s) +{ + uint8_t t[Nb * Nk]; + + mult_row_column(t, s); + mult_row_column(&t[Nb], s + Nb); + mult_row_column(&t[2 * Nb], s + (2 * Nb)); + mult_row_column(&t[3 * Nb], s + (3 * Nb)); + (void) _copy(s, sizeof(t), t, sizeof(t)); +} + +/* + * This shift_rows also implements the matrix flip required for mix_columns, but + * performs it here to reduce the number of memory operations. + */ +static inline void shift_rows(uint8_t *s) +{ + uint8_t t[Nb * Nk]; + + t[0] = s[0]; t[1] = s[5]; t[2] = s[10]; t[3] = s[15]; + t[4] = s[4]; t[5] = s[9]; t[6] = s[14]; t[7] = s[3]; + t[8] = s[8]; t[9] = s[13]; t[10] = s[2]; t[11] = s[7]; + t[12] = s[12]; t[13] = s[1]; t[14] = s[6]; t[15] = s[11]; + (void) _copy(s, sizeof(t), t, sizeof(t)); +} + +int tc_aes_encrypt(uint8_t *out, const uint8_t *in, const TCAesKeySched_t s) +{ + uint8_t state[Nk * Nb]; + unsigned int i; + + if (out == (uint8_t *) 0) { + return TC_CRYPTO_FAIL; + } else if (in == (const uint8_t *) 0) { + return TC_CRYPTO_FAIL; + } else if (s == (TCAesKeySched_t) 0) { + return TC_CRYPTO_FAIL; + } + + (void)_copy(state, sizeof(state), in, sizeof(state)); + add_round_key(state, s->words); + + for (i = 0; i < (Nr - 1); ++i) { + sub_bytes(state); + shift_rows(state); + mix_columns(state); + add_round_key(state, s->words + Nb * (i + 1)); + } + + sub_bytes(state); + shift_rows(state); + add_round_key(state, s->words + Nb * (i + 1)); + + (void)_copy(out, sizeof(state), state, sizeof(state)); + + /* zeroing out the state buffer */ + _set(state, TC_ZERO_BYTE, sizeof(state)); + + return TC_CRYPTO_SUCCESS; +} + +int tc_cmac_setup(TCCmacState_t s, const uint8_t *key, TCAesKeySched_t sched) +{ + + /* input sanity check: */ + if (s == (TCCmacState_t) 0 || + key == (const uint8_t *) 0) { + return TC_CRYPTO_FAIL; + } + + /* put s into a known state */ + _set(s, 0, sizeof(*s)); + s->sched = sched; + + /* configure the encryption key used by the underlying block cipher */ + tc_aes128_set_encrypt_key(s->sched, key); + + /* compute s->K1 and s->K2 from s->iv using s->keyid */ + _set(s->iv, 0, TC_AES_BLOCK_SIZE); + tc_aes_encrypt(s->iv, s->iv, s->sched); + gf_double (s->K1, s->iv); + gf_double (s->K2, s->K1); + + /* reset s->iv to 0 in case someone wants to compute now */ + tc_cmac_init(s); + + return TC_CRYPTO_SUCCESS; +} + +/* + * assumes: out != NULL and points to a GF(2^n) value to receive the + * doubled value; + * in != NULL and points to a 16 byte GF(2^n) value + * to double; + * the in and out buffers do not overlap. + * effects: doubles the GF(2^n) value pointed to by "in" and places + * the result in the GF(2^n) value pointed to by "out." + */ +void gf_double(uint8_t *out, uint8_t *in) +{ + + /* start with low order byte */ + uint8_t *x = in + (TC_AES_BLOCK_SIZE - 1); + + /* if msb == 1, we need to add the gf_wrap value, otherwise add 0 */ + uint8_t carry = (in[0] >> 7) ? gf_wrap : 0; + + out += (TC_AES_BLOCK_SIZE - 1); + for (;;) { + *out-- = (*x << 1) ^ carry; + if (x == in) { + break; + } + carry = *x-- >> 7; + } +} + +int tc_cmac_init(TCCmacState_t s) +{ + /* input sanity check: */ + if (s == (TCCmacState_t) 0) { + return TC_CRYPTO_FAIL; + } + + /* CMAC starts with an all zero initialization vector */ + _set(s->iv, 0, TC_AES_BLOCK_SIZE); + + /* and the leftover buffer is empty */ + _set(s->leftover, 0, TC_AES_BLOCK_SIZE); + s->leftover_offset = 0; + + /* Set countdown to max number of calls allowed before re-keying: */ + s->countdown = MAX_CALLS; + + return TC_CRYPTO_SUCCESS; +} + +int tc_cmac_update(TCCmacState_t s, const uint8_t *data, size_t data_length) +{ + unsigned int i; + + /* input sanity check: */ + if (s == (TCCmacState_t) 0) { + return TC_CRYPTO_FAIL; + } + if (data_length == 0) { + return TC_CRYPTO_SUCCESS; + } + if (data == (const uint8_t *) 0) { + return TC_CRYPTO_FAIL; + } + + if (s->countdown == 0) { + return TC_CRYPTO_FAIL; + } + + s->countdown--; + + if (s->leftover_offset > 0) { + /* last data added to s didn't end on a TC_AES_BLOCK_SIZE byte boundary */ + size_t remaining_space = TC_AES_BLOCK_SIZE - s->leftover_offset; + + if (data_length < remaining_space) { + /* still not enough data to encrypt this time either */ + _copy(&s->leftover[s->leftover_offset], data_length, data, data_length); + s->leftover_offset += data_length; + return TC_CRYPTO_SUCCESS; + } + /* leftover block is now full; encrypt it first */ + _copy(&s->leftover[s->leftover_offset], + remaining_space, + data, + remaining_space); + data_length -= remaining_space; + data += remaining_space; + s->leftover_offset = 0; + + for (i = 0; i < TC_AES_BLOCK_SIZE; ++i) { + s->iv[i] ^= s->leftover[i]; + } + tc_aes_encrypt(s->iv, s->iv, s->sched); + } + + /* CBC encrypt each (except the last) of the data blocks */ + while (data_length > TC_AES_BLOCK_SIZE) { + for (i = 0; i < TC_AES_BLOCK_SIZE; ++i) { + s->iv[i] ^= data[i]; + } + tc_aes_encrypt(s->iv, s->iv, s->sched); + data += TC_AES_BLOCK_SIZE; + data_length -= TC_AES_BLOCK_SIZE; + } + + if (data_length > 0) { + /* save leftover data for next time */ + _copy(s->leftover, data_length, data, data_length); + s->leftover_offset = data_length; + } + + return TC_CRYPTO_SUCCESS; +} + +int tc_cmac_final(uint8_t *tag, TCCmacState_t s) +{ + uint8_t *k; + unsigned int i; + + /* input sanity check: */ + if (tag == (uint8_t *) 0 || + s == (TCCmacState_t) 0) { + return TC_CRYPTO_FAIL; + } + + if (s->leftover_offset == TC_AES_BLOCK_SIZE) { + /* the last message block is a full-sized block */ + k = (uint8_t *) s->K1; + } else { + /* the final message block is not a full-sized block */ + size_t remaining = TC_AES_BLOCK_SIZE - s->leftover_offset; + + _set(&s->leftover[s->leftover_offset], 0, remaining); + s->leftover[s->leftover_offset] = TC_CMAC_PADDING; + k = (uint8_t *) s->K2; + } + for (i = 0; i < TC_AES_BLOCK_SIZE; ++i) { + s->iv[i] ^= s->leftover[i] ^ k[i]; + } + + tc_aes_encrypt(tag, s->iv, s->sched); + + /* erasing state: */ + tc_cmac_erase(s); + + return TC_CRYPTO_SUCCESS; +} + +int tc_cmac_erase(TCCmacState_t s) +{ + if (s == (TCCmacState_t) 0) { + return TC_CRYPTO_FAIL; + } + + /* destroy the current state */ + _set(s, 0, sizeof(*s)); + + return TC_CRYPTO_SUCCESS; +} diff --git a/components/bt/ble_mesh/mesh_core/mesh_atomic.c b/components/bt/ble_mesh/mesh_core/mesh_atomic.c new file mode 100644 index 0000000000..ce73638053 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/mesh_atomic.c @@ -0,0 +1,179 @@ +/** + * @brief Atomically set a bit. + * + * Atomically set bit number @a bit of @a target. + * The target may be a single atomic variable or an array of them. + * + * @param target Address of atomic variable or array. + * @param bit Bit number (starting from 0). + * + * @return N/A + */ + +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2011-2014 Wind River Systems, Inc. + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mesh_atomic.h" +#include "mesh_kernel.h" +#include "sdkconfig.h" + +#ifndef CONFIG_ATOMIC_OPERATIONS_BUILTIN + +/** +* +* @brief Atomic get primitive +* +* @param target memory location to read from +* +* This routine provides the atomic get primitive to atomically read +* a value from . It simply does an ordinary load. Note that +* is expected to be aligned to a 4-byte boundary. +* +* @return The value read from +*/ +bt_mesh_atomic_val_t bt_mesh_atomic_get(const bt_mesh_atomic_t *target) +{ + return *target; +} + +/** + * + * @brief Atomic get-and-set primitive + * + * This routine provides the atomic set operator. The is atomically + * written at and the previous value at is returned. + * + * @param target the memory location to write to + * @param value the value to write + * + * @return The previous value from + */ +bt_mesh_atomic_val_t bt_mesh_atomic_set(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t value) +{ + unsigned int key; + bt_mesh_atomic_val_t ret; + + key = bt_mesh_irq_lock(); + + ret = *target; + *target = value; + + bt_mesh_irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic bitwise inclusive OR primitive + * + * This routine provides the atomic bitwise inclusive OR operator. The + * is atomically bitwise OR'ed with the value at , placing the result + * at , and the previous value at is returned. + * + * @param target the memory location to be modified + * @param value the value to OR + * + * @return The previous value from + */ +bt_mesh_atomic_val_t bt_mesh_atomic_or(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t value) +{ + unsigned int key; + bt_mesh_atomic_val_t ret; + + key = bt_mesh_irq_lock(); + + ret = *target; + *target |= value; + + bt_mesh_irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic bitwise AND primitive + * + * This routine provides the atomic bitwise AND operator. The is + * atomically bitwise AND'ed with the value at , placing the result + * at , and the previous value at is returned. + * + * @param target the memory location to be modified + * @param value the value to AND + * + * @return The previous value from + */ +bt_mesh_atomic_val_t bt_mesh_atomic_and(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t value) +{ + unsigned int key; + bt_mesh_atomic_val_t ret; + + key = bt_mesh_irq_lock(); + + ret = *target; + *target &= value; + + bt_mesh_irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic decrement primitive + * + * @param target memory location to decrement + * + * This routine provides the atomic decrement operator. The value at + * is atomically decremented by 1, and the old value from is returned. + * + * @return The value from prior to the decrement + */ +bt_mesh_atomic_val_t bt_mesh_atomic_dec(bt_mesh_atomic_t *target) +{ + unsigned int key; + bt_mesh_atomic_val_t ret; + + key = bt_mesh_irq_lock(); + + ret = *target; + (*target)--; + + bt_mesh_irq_unlock(key); + + return ret; +} + +/** + * + * @brief Atomic increment primitive + * + * @param target memory location to increment + * + * This routine provides the atomic increment operator. The value at + * is atomically incremented by 1, and the old value from is returned. + * + * @return The value from before the increment + */ +bt_mesh_atomic_val_t bt_mesh_atomic_inc(bt_mesh_atomic_t *target) +{ + unsigned int key; + bt_mesh_atomic_val_t ret; + + key = bt_mesh_irq_lock(); + + ret = *target; + (*target)++; + + bt_mesh_irq_unlock(key); + + return ret; +} + +#endif /* #ifndef CONFIG_ATOMIC_OPERATIONS_BUILTIN */ diff --git a/components/bt/ble_mesh/mesh_core/mesh_bearer_adapt.c b/components/bt/ble_mesh/mesh_core/mesh_bearer_adapt.c new file mode 100644 index 0000000000..67bea67e03 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/mesh_bearer_adapt.c @@ -0,0 +1,1858 @@ +/* + * Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "sdkconfig.h" + +#include "bta/bta_api.h" +#include "bta/bta_gatt_api.h" +#include "bta/bta_gatt_common.h" +#include "bta_gattc_int.h" +#include "stack/btm_ble_api.h" +#include "p_256_ecc_pp.h" +#include "stack/hcimsgs.h" +#include "osi/future.h" +#include "osi/allocator.h" + +#include "mbedtls/aes.h" + +#include "mesh_hci.h" +#include "mesh_aes_encrypt.h" +#include "mesh_bearer_adapt.h" +#include "mesh_trace.h" +#include "mesh_buf.h" +#include "mesh_atomic.h" + +#include "provisioner_prov.h" +#include "mesh_common.h" + +#define BLE_MESH_BTM_CHECK_STATUS(func) do { \ + tBTM_STATUS __status = (func); \ + if ((__status != BTM_SUCCESS) && (__status != BTM_CMD_STARTED)) { \ + BT_ERR("%s, Invalid status %d", __func__, __status); \ + return -1; \ + } \ + } while(0); + +#define BLE_MESH_GATT_GET_CONN_ID(conn_id) (((u16_t)(conn_id)) >> 8) +#define BLE_MESH_GATT_CREATE_CONN_ID(gatt_if, conn_id) ((u16_t)((((u8_t)(conn_id)) << 8) | ((u8_t)(gatt_if)))) + +/* We don't need to manage the BLE_MESH_DEV_ADVERTISING flags in the version of bluedriod, + * it will manage it in the BTM layer. + */ +#define BLE_MESH_DEV 0 + +/* P-256 Variables */ +static u8_t bt_mesh_public_key[64]; +static BT_OCTET32 bt_mesh_private_key = { + 0x3f, 0x49, 0xf6, 0xd4, 0xa3, 0xc5, 0x5f, 0x38, + 0x74, 0xc9, 0xb3, 0xe3, 0xd2, 0x10, 0x3f, 0x50, + 0x4a, 0xff, 0x60, 0x7b, 0xeb, 0x40, 0xb7, 0x99, + 0x58, 0x99, 0xb8, 0xa6, 0xcd, 0x3c, 0x1a, 0xbd +}; + +/* Scan related functions */ +static bt_mesh_scan_cb_t *bt_mesh_scan_dev_found_cb; +static void bt_mesh_scan_result_callback(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data); + +#if defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE +/* the gatt database list to save the attribute table */ +static sys_slist_t bt_mesh_gatts_db; + +/* Static Variables */ +static struct bt_mesh_conn bt_mesh_gatts_conn[BLE_MESH_MAX_CONN]; +static struct bt_mesh_conn_cb *bt_mesh_gatts_conn_cb; +static tBTA_GATTS_IF bt_mesh_gatts_if; +static BD_ADDR bt_mesh_gatts_addr; +static u16_t svc_handle, char_handle; +static future_t *future_mesh; + +/* Static Functions */ +static struct bt_mesh_gatt_attr *bt_mesh_gatts_find_attr_by_handle(u16_t handle); +#endif /* defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE */ + +#if defined(CONFIG_BLE_MESH_PROVISIONER) && CONFIG_BLE_MESH_PROVISIONER +#define BLE_MESH_GATTC_APP_UUID_BYTE 0x97 +static struct gattc_prov_info { + /* Service to be found depends on the type of adv pkt received */ + struct bt_mesh_conn conn; + BD_ADDR addr; + u8_t addr_type; + u16_t service_uuid; + u16_t mtu; + bool wr_desc_done; /* Indicate if write char descriptor event is received */ + u16_t start_handle; /* Service attribute start handle */ + u16_t end_handle; /* Service attribute end handle */ + u16_t data_in_handle; /* Data In Characteristic attribute handle */ + u16_t data_out_handle; /* Data Out Characteristic attribute handle */ + u16_t ccc_handle; /* Data Out Characteristic CCC attribute handle */ +} bt_mesh_gattc_info[BLE_MESH_MAX_CONN]; +static struct bt_mesh_prov_conn_cb *bt_mesh_gattc_conn_cb; +static tBTA_GATTC_IF bt_mesh_gattc_if; +#endif /* defined(CONFIG_BLE_MESH_PROVISIONER) && CONFIG_BLE_MESH_PROVISIONER */ + +static void bt_mesh_scan_results_change_2_bta(tBTM_INQ_RESULTS *p_inq, u8_t *p_eir, + tBTA_DM_SEARCH_CBACK *p_scan_cback) +{ + tBTM_INQ_INFO *p_inq_info; + tBTA_DM_SEARCH result; + + bdcpy(result.inq_res.bd_addr, p_inq->remote_bd_addr); + result.inq_res.rssi = p_inq->rssi; + result.inq_res.ble_addr_type = p_inq->ble_addr_type; + result.inq_res.inq_result_type = p_inq->inq_result_type; + result.inq_res.device_type = p_inq->device_type; + result.inq_res.flag = p_inq->flag; + result.inq_res.adv_data_len = p_inq->adv_data_len; + result.inq_res.scan_rsp_len = p_inq->scan_rsp_len; + memcpy(result.inq_res.dev_class, p_inq->dev_class, sizeof(DEV_CLASS)); + result.inq_res.ble_evt_type = p_inq->ble_evt_type; + + /* application will parse EIR to find out remote device name */ + result.inq_res.p_eir = p_eir; + + if ((p_inq_info = BTM_InqDbRead(p_inq->remote_bd_addr)) != NULL) { + /* initialize remt_name_not_required to FALSE so that we get the name by default */ + result.inq_res.remt_name_not_required = FALSE; + } + + if (p_scan_cback) { + p_scan_cback(BTA_DM_INQ_RES_EVT, &result); + } + + if (p_inq_info) { + /* application indicates if it knows the remote name, inside the callback + copy that to the inquiry data base*/ + if (result.inq_res.remt_name_not_required) { + p_inq_info->appl_knows_rem_name = TRUE; + } + } +} + +static void bt_mesh_scan_results_cb(tBTM_INQ_RESULTS *p_inq, u8_t *p_eir) +{ + bt_mesh_scan_results_change_2_bta(p_inq, p_eir, bt_mesh_scan_result_callback); +} + +static bool valid_adv_param(const struct bt_mesh_adv_param *param) +{ + if (!(param->options & BLE_MESH_ADV_OPT_CONNECTABLE)) { +#if BLE_MESH_DEV + if (bt_mesh_dev.hci_version < BLE_MESH_HCI_VERSION_5_0 && + param->interval_min < 0x00a0) { + return false; + } +#endif + } + + if (param->interval_min > param->interval_max || + param->interval_min < 0x0020 || param->interval_max > 0x4000) { + return false; + } + + return true; +} + +static int set_adv_data(u16_t hci_op, const struct bt_mesh_adv_data *ad, size_t ad_len) +{ + struct bt_mesh_hci_cp_set_adv_data param = {0}; + int i; + + if (ad == NULL || ad_len == 0) { + return 0; + } + + for (i = 0; i < ad_len; i++) { + /* Check if ad fit in the remaining buffer */ + if (param.len + ad[i].data_len + 2 > 31) { + return -EINVAL; + } + + param.data[param.len++] = ad[i].data_len + 1; + param.data[param.len++] = ad[i].type; + + memcpy(¶m.data[param.len], ad[i].data, ad[i].data_len); + param.len += ad[i].data_len; + } + + /* Set adv data and scan rsp data. */ + if (hci_op == BLE_MESH_HCI_OP_SET_ADV_DATA) { + BLE_MESH_BTM_CHECK_STATUS(BTM_BleWriteAdvDataRaw(param.data, param.len)); + } else if (hci_op == BLE_MESH_HCI_OP_SET_SCAN_RSP_DATA) { + BLE_MESH_BTM_CHECK_STATUS(BTM_BleWriteScanRspRaw(param.data, param.len)); + } + + return 0; +} + +static void start_adv_completed_cb(u8_t status) +{ +#if BLE_MESH_DEV + if (!status) { + bt_mesh_atomic_set_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ADVERTISING); + } +#endif +} + +static bool valid_scan_param(const struct bt_mesh_scan_param *param) +{ + if (param->type != BLE_MESH_SCAN_PASSIVE && + param->type != BLE_MESH_SCAN_ACTIVE) { + return false; + } + + if (param->filter_dup != BLE_MESH_SCAN_FILTER_DUP_DISABLE && + param->filter_dup != BLE_MESH_SCAN_FILTER_DUP_ENABLE) { + return false; + } + + if (param->interval < 0x0004 || param->interval > 0x4000) { + return false; + } + + if (param->window < 0x0004 || param->window > 0x4000) { + return false; + } + + if (param->window > param->interval) { + return false; + } + + return true; +} + +static int start_le_scan(u8_t scan_type, u16_t interval, u16_t window, u8_t filter_dup) +{ + UINT8 scan_fil_policy = BLE_MESH_SP_ADV_ALL; /* No whitelist for BLE Mesh */ + UINT8 addr_type_own = BLE_MESH_ADDR_PUBLIC; /* Currently only support Public Address */ + tGATT_IF client_if = 0xFF; /* Default GATT interface id */ + + BLE_MESH_BTM_CHECK_STATUS( + BTM_BleSetScanFilterParams(client_if, interval, window, scan_type, addr_type_own, + filter_dup, scan_fil_policy, NULL)); + + /* BLE Mesh scan permanently, so no duration of scan here */ + BLE_MESH_BTM_CHECK_STATUS(BTM_BleScan(true, 0, bt_mesh_scan_results_cb, NULL, NULL)); + +#if BLE_MESH_DEV + if (scan_type == BLE_MESH_SCAN_ACTIVE) { + bt_mesh_atomic_set_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ACTIVE_SCAN); + } else { + bt_mesh_atomic_clear_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ACTIVE_SCAN); + } +#endif + + return 0; +} + +static void bt_mesh_scan_result_callback(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data) +{ + bt_mesh_addr_t addr = {0}; + UINT8 adv_type; + UINT8 rssi; + + BT_DBG("%s, event = %d", __func__, event); + + if (event == BTA_DM_INQ_RES_EVT) { + /* TODO: How to process scan response here? */ + addr.type = p_data->inq_res.ble_addr_type; + memcpy(addr.val, p_data->inq_res.bd_addr, BLE_MESH_ADDR_LEN); + rssi = p_data->inq_res.rssi; + adv_type = p_data->inq_res.ble_evt_type; + + /* scan rsp len: p_data->inq_res.scan_rsp_len */ + struct net_buf_simple *buf = bt_mesh_alloc_buf(p_data->inq_res.adv_data_len); + if (!buf) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + net_buf_simple_add_mem(buf, p_data->inq_res.p_eir, p_data->inq_res.adv_data_len); + + if (bt_mesh_scan_dev_found_cb != NULL) { + bt_mesh_scan_dev_found_cb(&addr, rssi, adv_type, buf); + } + osi_free(buf); + } else if (event == BTA_DM_INQ_CMPL_EVT) { + BT_INFO("%s, Scan completed, number of scan response %d", __func__, p_data->inq_cmpl.num_resps); + } else { + BT_WARN("%s, Unexpected event 0x%x", __func__, event); + } +} + +/* APIs functions */ +int bt_le_adv_start(const struct bt_mesh_adv_param *param, + const struct bt_mesh_adv_data *ad, size_t ad_len, + const struct bt_mesh_adv_data *sd, size_t sd_len) +{ + tBTA_START_ADV_CMPL_CBACK *p_start_adv_cb; + tBTM_BLE_ADV_CHNL_MAP channel_map; + tBLE_ADDR_TYPE addr_type_own; + tBLE_BD_ADDR p_dir_bda = {0}; + tBTM_BLE_AFP adv_fil_pol; + UINT8 adv_type; + int err; + +#if BLE_MESH_DEV + if (bt_mesh_atomic_test_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ADVERTISING)) { + return -EALREADY; + } +#endif + + if (!valid_adv_param(param)) { + BT_ERR("%s, Invalid adv parameters", __func__); + return -EINVAL; + } + + err = set_adv_data(BLE_MESH_HCI_OP_SET_ADV_DATA, ad, ad_len); + if (err) { + BT_ERR("%s, Failed to set adv data", __func__); + return err; + } + + /* + * We need to set SCAN_RSP when enabling advertising type that allows + * for Scan Requests. + * + * If sd was not provided but we enable connectable undirected + * advertising sd needs to be cleared from values set by previous calls. + * Clearing sd is done by calling set_adv_data() with NULL data and zero len. + * So following condition check is unusual but correct. + */ + if (sd && (param->options & BLE_MESH_ADV_OPT_CONNECTABLE)) { + err = set_adv_data(BLE_MESH_HCI_OP_SET_SCAN_RSP_DATA, sd, sd_len); + if (err) { + BT_ERR("%s, Failed to set scan rsp data", __func__); + return err; + } + } + + if (param->options & BLE_MESH_ADV_OPT_CONNECTABLE) { + adv_type = BLE_MESH_ADV_IND; + } else if (sd != NULL) { + adv_type = BLE_MESH_ADV_SCAN_IND; + } else { + adv_type = BLE_MESH_ADV_NONCONN_IND; + } + addr_type_own = BLE_MESH_ADDR_PUBLIC; /* Currently only support Public Address */ + channel_map = BLE_MESH_ADV_CHNL_37 | BLE_MESH_ADV_CHNL_38 | BLE_MESH_ADV_CHNL_39; + adv_fil_pol = BLE_MESH_AP_SCAN_CONN_ALL; + p_start_adv_cb = start_adv_completed_cb; + + /* Check if we can start adv using BTM_BleSetAdvParamsStartAdvCheck */ + BLE_MESH_BTM_CHECK_STATUS( + BTM_BleSetAdvParamsAll(param->interval_min, param->interval_max, adv_type, + addr_type_own, &p_dir_bda, + channel_map, adv_fil_pol, p_start_adv_cb)); + BLE_MESH_BTM_CHECK_STATUS(BTM_BleStartAdv()); + +#if BLE_MESH_DEV + bt_mesh_atomic_set_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ADVERTISING); + + if (!(param->options & BLE_MESH_ADV_OPT_ONE_TIME)) { + bt_mesh_atomic_set_bit(bt_mesh_dev.flags, BLE_MESH_DEV_KEEP_ADVERTISING); + } +#endif + + return 0; +} + +int bt_le_adv_stop(void) +{ +#if BLE_MESH_DEV + bt_mesh_atomic_clear_bit(bt_mesh_dev.flags, BLE_MESH_DEV_KEEP_ADVERTISING); + if (!bt_mesh_atomic_test_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ADVERTISING)) { + return 0; + } +#endif + + BLE_MESH_BTM_CHECK_STATUS(BTM_BleBroadcast(false, NULL)); + +#if BLE_MESH_DEV + bt_mesh_atomic_clear_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ADVERTISING); +#endif + + return 0; +} + +int bt_le_scan_start(const struct bt_mesh_scan_param *param, bt_mesh_scan_cb_t cb) +{ + int err; + +#if BLE_MESH_DEV + if (bt_mesh_atomic_test_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCANNING)) { + return -EALREADY; + } +#endif + + if (!valid_scan_param(param)) { + return -EINVAL; + } + +#if BLE_MESH_DEV + if (param->filter_dup) { + bt_mesh_atomic_set_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCAN_FILTER_DUP); + } else { + bt_mesh_atomic_clear_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCAN_FILTER_DUP); + } +#endif + + err = start_le_scan(param->type, param->interval, param->window, param->filter_dup); + if (err) { + return err; + } + +#if BLE_MESH_DEV + bt_mesh_atomic_set_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCANNING); +#endif + + bt_mesh_scan_dev_found_cb = cb; + return err; +} + +int bt_le_scan_stop(void) +{ +#if BLE_MESH_DEV + if (bt_mesh_atomic_test_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCANNING)) { + bt_mesh_atomic_clear_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCANNING); + BLE_MESH_BTM_CHECK_STATUS(BTM_BleScan(false, 0, NULL, NULL, NULL)); + } +#else + BLE_MESH_BTM_CHECK_STATUS(BTM_BleScan(false, 0, NULL, NULL, NULL)); +#endif + + bt_mesh_scan_dev_found_cb = NULL; + return 0; +} + +#if defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE +static void bt_mesh_bta_gatts_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) +{ + switch (event) { + case BTA_GATTS_REG_EVT: + if (p_data->reg_oper.status == BTA_GATT_OK) { + bt_mesh_gatts_if = p_data->reg_oper.server_if; + } + break; + case BTA_GATTS_READ_EVT: { + struct bt_mesh_gatt_attr *attr = bt_mesh_gatts_find_attr_by_handle(p_data->req_data.p_data->read_req.handle); + u8_t index = BLE_MESH_GATT_GET_CONN_ID(p_data->req_data.conn_id); + tBTA_GATTS_RSP rsp; + u8_t buf[100] = {0}; + u16_t len = 0; + + BT_DBG("%s, read: handle = %d", __func__, p_data->req_data.p_data->read_req.handle); + + if (attr != NULL && attr->read != NULL) { + if ((len = attr->read(&bt_mesh_gatts_conn[index], attr, buf, 100, + p_data->req_data.p_data->read_req.offset)) > 0) { + rsp.attr_value.handle = p_data->req_data.p_data->read_req.handle; + rsp.attr_value.len = len; + memcpy(&rsp.attr_value.value[0], buf, len); + BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, + p_data->req_data.status, &rsp); + BT_DBG("%s, Send gatts read response, handle = %x", __func__, attr->handle); + } else { + BT_WARN("%s, BLE Mesh gatts read failed", __func__); + } + } + break; + } + case BTA_GATTS_WRITE_EVT: { + struct bt_mesh_gatt_attr *attr = bt_mesh_gatts_find_attr_by_handle(p_data->req_data.p_data->write_req.handle); + u8_t index = BLE_MESH_GATT_GET_CONN_ID(p_data->req_data.conn_id); + u16_t len = 0; + + BT_DBG("%s, write: handle = %d, len = %d, data = %s", __func__, p_data->req_data.p_data->write_req.handle, + p_data->req_data.p_data->write_req.len, + bt_hex(p_data->req_data.p_data->write_req.value, p_data->req_data.p_data->write_req.len)); + + if (attr != NULL && attr->write != NULL) { + if ((len = attr->write(&bt_mesh_gatts_conn[index], attr, + p_data->req_data.p_data->write_req.value, + p_data->req_data.p_data->write_req.len, + p_data->req_data.p_data->write_req.offset, 0)) > 0) { + if (p_data->req_data.p_data->write_req.need_rsp) { + BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, + p_data->req_data.status, NULL); + BT_DBG("%s, send mesh write rsp, handle = %x", __func__, attr->handle); + } + } + } + break; + } + case BTA_GATTS_EXEC_WRITE_EVT: + break; + case BTA_GATTS_MTU_EVT: + break; + case BTA_GATTS_CONF_EVT: + break; + case BTA_GATTS_CREATE_EVT: + svc_handle = p_data->create.service_id; + BT_DBG("%s, svc_handle = %d, future_mesh = %p", __func__, svc_handle, future_mesh); + if (future_mesh != NULL) { + future_ready(future_mesh, FUTURE_SUCCESS); + } + break; + case BTA_GATTS_ADD_INCL_SRVC_EVT: + svc_handle = p_data->add_result.attr_id; + if (future_mesh != NULL) { + future_ready(future_mesh, FUTURE_SUCCESS); + } + break; + case BTA_GATTS_ADD_CHAR_EVT: + char_handle = p_data->add_result.attr_id; + if (future_mesh != NULL) { + future_ready(future_mesh, FUTURE_SUCCESS); + } + break; + case BTA_GATTS_ADD_CHAR_DESCR_EVT: + char_handle = p_data->add_result.attr_id; + if (future_mesh != NULL) { + future_ready(future_mesh, FUTURE_SUCCESS); + } + break; + case BTA_GATTS_DELELTE_EVT: + break; + case BTA_GATTS_START_EVT: + break; + case BTA_GATTS_STOP_EVT: + break; + case BTA_GATTS_CONNECT_EVT: +#if BLE_MESH_DEV + /* When connection is created, advertising will be stopped automatically. */ + bt_mesh_atomic_test_and_clear_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ADVERTISING); +#endif + if (bt_mesh_gatts_conn_cb != NULL && bt_mesh_gatts_conn_cb->connected != NULL) { + u8_t index = BLE_MESH_GATT_GET_CONN_ID(p_data->conn.conn_id); + if (index < BLE_MESH_MAX_CONN) { + bt_mesh_gatts_conn[index].handle = BLE_MESH_GATT_GET_CONN_ID(p_data->conn.conn_id); + (bt_mesh_gatts_conn_cb->connected)(&bt_mesh_gatts_conn[index], 0); + } + memcpy(bt_mesh_gatts_addr, p_data->conn.remote_bda, BLE_MESH_ADDR_LEN); + /* This is for EspBleMesh Android app. When it tries to connect with the + * device at the first time and it fails due to some reason. And after + * the second connection, the device needs to send GATT service change + * indication to the phone manually to notify it dicovering service again. + */ + BTA_GATTS_SendServiceChangeIndication(bt_mesh_gatts_if, bt_mesh_gatts_addr); + } + break; + case BTA_GATTS_DISCONNECT_EVT: +#if BLE_MESH_DEV + bt_mesh_atomic_test_and_clear_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ADVERTISING); +#endif + if (bt_mesh_gatts_conn_cb != NULL && bt_mesh_gatts_conn_cb->disconnected != NULL) { + u8_t index = BLE_MESH_GATT_GET_CONN_ID(p_data->conn.conn_id); + if (index < BLE_MESH_MAX_CONN) { + bt_mesh_gatts_conn[index].handle = BLE_MESH_GATT_GET_CONN_ID(p_data->conn.conn_id); + (bt_mesh_gatts_conn_cb->disconnected)(&bt_mesh_gatts_conn[index], p_data->conn.reason); + } + memset(bt_mesh_gatts_addr, 0x0, BLE_MESH_ADDR_LEN); + } + break; + case BTA_GATTS_CLOSE_EVT: + break; + default: + break; + } +} + +void bt_mesh_gatts_conn_cb_register(struct bt_mesh_conn_cb *cb) +{ + bt_mesh_gatts_conn_cb = cb; +} + +static struct bt_mesh_gatt_attr *bt_mesh_gatts_find_attr_by_handle(u16_t handle) +{ + struct bt_mesh_gatt_service *svc = NULL; + struct bt_mesh_gatt_attr *attr = NULL; + + SYS_SLIST_FOR_EACH_CONTAINER(&bt_mesh_gatts_db, svc, node) { + int i; + + for (i = 0; i < svc->attr_count; i++) { + attr = &svc->attrs[i]; + /* Check the attrs handle is equal to the handle or not */ + if (attr->handle == handle) { + return attr; + } + } + } + + return NULL; +} + +static void bt_mesh_gatts_foreach_attr(u16_t start_handle, u16_t end_handle, + bt_mesh_gatt_attr_func_t func, void *user_data) +{ + struct bt_mesh_gatt_service *svc = NULL; + + SYS_SLIST_FOR_EACH_CONTAINER(&bt_mesh_gatts_db, svc, node) { + int i; + + for (i = 0; i < svc->attr_count; i++) { + struct bt_mesh_gatt_attr *attr = &svc->attrs[i]; + + /* Check if attribute handle is within range */ + if (attr->handle < start_handle || + attr->handle > end_handle) { + continue; + } + + if (func(attr, user_data) == BLE_MESH_GATT_ITER_STOP) { + return; + } + } + } +} + +static u8_t find_next(const struct bt_mesh_gatt_attr *attr, void *user_data) +{ + struct bt_mesh_gatt_attr **next = user_data; + + *next = (struct bt_mesh_gatt_attr *)attr; + + return BLE_MESH_GATT_ITER_STOP; +} + +static struct bt_mesh_gatt_attr *bt_mesh_gatts_attr_next(const struct bt_mesh_gatt_attr *attr) +{ + struct bt_mesh_gatt_attr *next = NULL; + + bt_mesh_gatts_foreach_attr(attr->handle + 1, attr->handle + 1, find_next, &next); + + return next; +} + +ssize_t bt_mesh_gatts_attr_read(struct bt_mesh_conn *conn, const struct bt_mesh_gatt_attr *attr, + void *buf, u16_t buf_len, u16_t offset, + const void *value, u16_t value_len) +{ + u16_t len; + + if (offset > value_len) { + return BLE_MESH_GATT_ERR(BLE_MESH_ATT_ERR_INVALID_OFFSET); + } + + len = MIN(buf_len, value_len - offset); + + BT_DBG("handle 0x%04x offset %u length %u", attr->handle, offset, len); + + memcpy(buf, value + offset, len); + + return len; +} + +struct gatts_incl { + u16_t start_handle; + u16_t end_handle; + u16_t uuid16; +} __packed; + +ssize_t bt_mesh_gatts_attr_read_included(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + void *buf, u16_t len, u16_t offset) +{ + struct bt_mesh_gatt_attr *incl = attr->user_data; + struct bt_mesh_uuid *uuid = incl->user_data; + struct gatts_incl pdu = {0}; + u8_t value_len; + + /* First attr points to the start handle */ + pdu.start_handle = sys_cpu_to_le16(incl->handle); + value_len = sizeof(pdu.start_handle) + sizeof(pdu.end_handle); + + /* + * Core 4.2, Vol 3, Part G, 3.2, + * The Service UUID shall only be present when the UUID is a 16-bit Bluetooth UUID. + */ + if (uuid->type == BLE_MESH_UUID_TYPE_16) { + pdu.uuid16 = sys_cpu_to_le16(BLE_MESH_UUID_16(uuid)->val); + value_len += sizeof(pdu.uuid16); + } + + return bt_mesh_gatts_attr_read(conn, attr, buf, len, offset, &pdu, value_len); +} + +ssize_t bt_mesh_gatts_attr_read_service(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + void *buf, u16_t len, u16_t offset) +{ + struct bt_mesh_uuid *uuid = attr->user_data; + + if (uuid->type == BLE_MESH_UUID_TYPE_16) { + u16_t uuid16 = sys_cpu_to_le16(BLE_MESH_UUID_16(uuid)->val); + + return bt_mesh_gatts_attr_read(conn, attr, buf, len, offset, &uuid16, 2); + } + + return bt_mesh_gatts_attr_read(conn, attr, buf, len, offset, + BLE_MESH_UUID_128(uuid)->val, 16); +} + +struct gatts_chrc { + u8_t properties; + u16_t value_handle; + union { + u16_t uuid16; + u8_t uuid[16]; + }; +} __packed; + +ssize_t bt_mesh_gatts_attr_read_chrc(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, void *buf, + u16_t len, u16_t offset) +{ + struct bt_mesh_gatt_char *chrc = attr->user_data; + const struct bt_mesh_gatt_attr *next = NULL; + struct gatts_chrc pdu = {0}; + u8_t value_len; + + pdu.properties = chrc->properties; + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 534: + * 3.3.2 Characteristic Value Declaration + * The Characteristic Value declaration contains the value of the + * characteristic. It is the first Attribute after the characteristic + * declaration. All characteristic definitions shall have a + * Characteristic Value declaration. + */ + next = bt_mesh_gatts_attr_next(attr); + if (!next) { + BT_WARN("%s, No value for characteristic at 0x%04x", __func__, attr->handle); + pdu.value_handle = 0x0000; + } else { + pdu.value_handle = sys_cpu_to_le16(next->handle); + } + value_len = sizeof(pdu.properties) + sizeof(pdu.value_handle); + + if (chrc->uuid->type == BLE_MESH_UUID_TYPE_16) { + pdu.uuid16 = sys_cpu_to_le16(BLE_MESH_UUID_16(chrc->uuid)->val); + value_len += 2; + } else { + memcpy(pdu.uuid, BLE_MESH_UUID_128(chrc->uuid)->val, 16); + value_len += 16; + } + + return bt_mesh_gatts_attr_read(conn, attr, buf, len, offset, &pdu, value_len); +} + +static void bta_uuid_to_bt_mesh_uuid(tBT_UUID *bta_uuid, const struct bt_mesh_uuid *uuid) +{ + assert(uuid != NULL && bta_uuid != NULL); + + if (uuid->type == BLE_MESH_UUID_TYPE_16) { + bta_uuid->len = LEN_UUID_16; + bta_uuid->uu.uuid16 = BLE_MESH_UUID_16(uuid)->val; + } else if (uuid->type == BLE_MESH_UUID_TYPE_32) { + bta_uuid->len = LEN_UUID_32; + bta_uuid->uu.uuid32 = BLE_MESH_UUID_32(uuid)->val; + } else if (uuid->type == BLE_MESH_UUID_TYPE_128) { + bta_uuid->len = LEN_UUID_128; + memcpy(bta_uuid->uu.uuid128, BLE_MESH_UUID_128(uuid)->val, LEN_UUID_128); + } else { + BT_ERR("%s, Invalid mesh uuid type = %d", __func__, uuid->type); + } + + return; +} + +static int gatts_register(struct bt_mesh_gatt_service *svc) +{ + struct bt_mesh_gatt_service *last; + u16_t handle; + + if (sys_slist_is_empty(&bt_mesh_gatts_db)) { + handle = 0; + goto populate; + } + + last = SYS_SLIST_PEEK_TAIL_CONTAINER(&bt_mesh_gatts_db, last, node); + handle = last->attrs[last->attr_count - 1].handle; + BT_DBG("%s, handle = %d", __func__, handle); + +populate: + sys_slist_append(&bt_mesh_gatts_db, &svc->node); + return 0; +} + +static tBTA_GATT_PERM bt_mesh_perm_to_bta_perm(u8_t perm) +{ + tBTA_GATT_PERM bta_perm = 0; + + if ((perm & BLE_MESH_GATT_PERM_READ) == BLE_MESH_GATT_PERM_READ) { + bta_perm |= BTA_GATT_PERM_READ; + } + + if ((perm & BLE_MESH_GATT_PERM_WRITE) == BLE_MESH_GATT_PERM_WRITE) { + bta_perm |= BTA_GATT_PERM_WRITE; + } + + if ((perm & BLE_MESH_GATT_PERM_READ_ENCRYPT) == BLE_MESH_GATT_PERM_READ_ENCRYPT) { + bta_perm |= BTA_GATT_PERM_READ_ENCRYPTED; + } + + if ((perm & BLE_MESH_GATT_PERM_WRITE_ENCRYPT) == BLE_MESH_GATT_PERM_WRITE_ENCRYPT) { + bta_perm |= BTA_GATT_PERM_WRITE_ENCRYPTED; + } + + if ((perm & BLE_MESH_GATT_PERM_READ_AUTHEN) == BLE_MESH_GATT_PERM_READ_AUTHEN) { + bta_perm |= BTA_GATT_PERM_READ_ENC_MITM; + } + + if ((perm & BLE_MESH_GATT_PERM_WRITE_AUTHEN) == BLE_MESH_GATT_PERM_WRITE_AUTHEN) { + bta_perm |= BTA_GATT_PERM_WRITE_ENC_MITM; + } + + return bta_perm; +} + +int bt_mesh_gatts_service_register(struct bt_mesh_gatt_service *svc) +{ + tBT_UUID bta_uuid = {0}; + + assert(svc != NULL); + + for (int i = 0; i < svc->attr_count; i++) { + if (svc->attrs[i].uuid->type == BLE_MESH_UUID_TYPE_16) { + switch (BLE_MESH_UUID_16(svc->attrs[i].uuid)->val) { + case BLE_MESH_UUID_GATT_PRIMARY_VAL: { + future_mesh = future_new(); + bta_uuid_to_bt_mesh_uuid(&bta_uuid, (struct bt_mesh_uuid *)svc->attrs[i].user_data); + BTA_GATTS_CreateService(bt_mesh_gatts_if, + &bta_uuid, 0, svc->attr_count, true); + if (future_await(future_mesh) == FUTURE_FAIL) { + BT_ERR("%s, Failed to add primary service", __func__); + return ESP_FAIL; + } + svc->attrs[i].handle = svc_handle; + BT_DBG("Add primary service: svc_uuid = %x, perm = %d, svc_handle = %d", bta_uuid.uu.uuid16, svc->attrs[i].perm, svc_handle); + break; + } + case BLE_MESH_UUID_GATT_SECONDARY_VAL: { + future_mesh = future_new(); + bta_uuid_to_bt_mesh_uuid(&bta_uuid, (struct bt_mesh_uuid *)svc->attrs[i].user_data); + BTA_GATTS_CreateService(bt_mesh_gatts_if, + &bta_uuid, 0, svc->attr_count, false); + if (future_await(future_mesh) == FUTURE_FAIL) { + BT_ERR("%s, Failed to add secondary service", __func__); + return ESP_FAIL; + } + svc->attrs[i].handle = svc_handle; + BT_DBG("Add secondary service: svc_uuid = %x, perm = %d, svc_handle = %d", bta_uuid.uu.uuid16, svc->attrs[i].perm, svc_handle); + break; + } + case BLE_MESH_UUID_GATT_INCLUDE_VAL: { + break; + } + case BLE_MESH_UUID_GATT_CHRC_VAL: { + future_mesh = future_new(); + struct bt_mesh_gatt_char *gatts_chrc = (struct bt_mesh_gatt_char *)svc->attrs[i].user_data; + bta_uuid_to_bt_mesh_uuid(&bta_uuid, gatts_chrc->uuid); + BTA_GATTS_AddCharacteristic(svc_handle, &bta_uuid, bt_mesh_perm_to_bta_perm(svc->attrs[i + 1].perm), gatts_chrc->properties, NULL, NULL); + if (future_await(future_mesh) == FUTURE_FAIL) { + BT_ERR("%s, Failed to add characristic", __func__); + return ESP_FAIL; + } + /* All the characristic should have two handle: the declaration handle and the value handle */ + svc->attrs[i].handle = char_handle - 1; + svc->attrs[i + 1].handle = char_handle; + BT_DBG("Add characteristic: char_uuid = %x, char_handle = %d, perm = %d, char_pro = %d", BLE_MESH_UUID_16(gatts_chrc->uuid)->val, char_handle, svc->attrs[i + 1].perm, gatts_chrc->properties); + break; + } + case BLE_MESH_UUID_GATT_CEP_VAL: + case BLE_MESH_UUID_GATT_CUD_VAL: + case BLE_MESH_UUID_GATT_CCC_VAL: + case BLE_MESH_UUID_GATT_SCC_VAL: + case BLE_MESH_UUID_GATT_CPF_VAL: + case BLE_MESH_UUID_VALID_RANGE_VAL: + case BLE_MESH_UUID_HIDS_EXT_REPORT_VAL: + case BLE_MESH_UUID_HIDS_REPORT_REF_VAL: + case BLE_MESH_UUID_ES_CONFIGURATION_VAL: + case BLE_MESH_UUID_ES_MEASUREMENT_VAL: + case BLE_MESH_UUID_ES_TRIGGER_SETTING_VAL: { + future_mesh = future_new(); + bta_uuid_to_bt_mesh_uuid(&bta_uuid, svc->attrs[i].uuid); + BTA_GATTS_AddCharDescriptor(svc_handle, bt_mesh_perm_to_bta_perm(svc->attrs[i].perm), &bta_uuid, NULL, NULL); + if (future_await(future_mesh) == FUTURE_FAIL) { + BT_ERR("%s, Failed to add descriptor", __func__); + return ESP_FAIL; + } + svc->attrs[i].handle = char_handle; + BT_DBG("Add descriptor: descr_uuid = %x, perm= %d, descr_handle = %d", BLE_MESH_UUID_16(svc->attrs[i].uuid)->val, svc->attrs[i].perm, char_handle); + break; + } + default: + break; + } + } + } + + if (svc_handle != 0) { + svc_handle = 0; + } + + gatts_register(svc); + return 0; +} + +int bt_mesh_gatts_disconnect(struct bt_mesh_conn *conn, u8_t reason) +{ + UNUSED(reason); + u16_t conn_id = BLE_MESH_GATT_CREATE_CONN_ID(bt_mesh_gatts_if, conn->handle); + BTA_GATTS_Close(conn_id); + return 0; +} + +int bt_mesh_gatts_service_unregister(struct bt_mesh_gatt_service *svc) +{ + assert(svc != NULL); + + BTA_GATTS_DeleteService(svc->attrs[0].handle); + return 0; +} + +int bt_mesh_gatts_notify(struct bt_mesh_conn *conn, const struct bt_mesh_gatt_attr *attr, + const void *data, u16_t len) +{ + u16_t conn_id = BLE_MESH_GATT_CREATE_CONN_ID(bt_mesh_gatts_if, conn->handle); + BTA_GATTS_HandleValueIndication(conn_id, attr->handle, len, (u8_t *)data, false); + return 0; +} + +u16_t bt_mesh_gatt_get_mtu(struct bt_mesh_conn *conn) +{ + return BTA_GATT_GetLocalMTU(); +} + +/* APIs added by Espressif */ +int bt_mesh_gatts_service_stop(struct bt_mesh_gatt_service *svc) +{ + if (!svc) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + BT_DBG("Stop service:%d", svc->attrs[0].handle); + + BTA_GATTS_StopService(svc->attrs[0].handle); + return 0; +} + +int bt_mesh_gatts_service_start(struct bt_mesh_gatt_service *svc) +{ + struct bt_mesh_uuid_16 *uuid_16 = NULL; + struct bt_mesh_uuid *uuid = NULL; + + if (!svc) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + BT_DBG("Start service:%d", svc->attrs[0].handle); + + BTA_GATTS_StartService(svc->attrs[0].handle, BTA_GATT_TRANSPORT_LE); + + /* For EspBleMesh Android app, it does not disconnect after provisioning + * is done, and hence we send GATT service change indication manually + * when Mesh Proxy Service is started after provisioning. + */ + uuid = (struct bt_mesh_uuid *)svc->attrs[0].user_data; + if (uuid && uuid->type == BLE_MESH_UUID_TYPE_16) { + uuid_16 = (struct bt_mesh_uuid_16 *)uuid; + BT_DBG("%s, type 0x%02x, val 0x%04x", __func__, uuid_16->uuid.type, uuid_16->val); + if (uuid_16->val == BLE_MESH_UUID_MESH_PROXY_VAL) { + BTA_GATTS_SendServiceChangeIndication(bt_mesh_gatts_if, bt_mesh_gatts_addr); + } + } + + return 0; +} +#endif /* defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE */ + +#if defined(CONFIG_BLE_MESH_PROVISIONER) && CONFIG_BLE_MESH_PROVISIONER +void bt_mesh_gattc_conn_cb_register(struct bt_mesh_prov_conn_cb *cb) +{ + bt_mesh_gattc_conn_cb = cb; +} + +u16_t bt_mesh_gattc_get_service_uuid(struct bt_mesh_conn *conn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (conn == &bt_mesh_gattc_info[i].conn) { + break; + } + } + + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + return 0; + } + + return bt_mesh_gattc_info[i].service_uuid; +} + +/** For provisioner acting as a GATT client, it may follow the procedures + * listed below. + * 1. Create connection with the unprovisioned device + * 2. Exchange MTU size + * 3. Find Mesh Prov Service in the device's service database + * 4. Find Mesh Prov Data In/Out characteristic within the service + * 5. Get CCC of Mesh Prov Data Out Characteristic + * 6. Set the Notification bit of CCC + */ + +int bt_mesh_gattc_conn_create(const bt_mesh_addr_t *addr, u16_t service_uuid) +{ + u8_t zero[6] = {0}; + int i; + + if (!addr || !memcmp(addr->val, zero, BLE_MESH_ADDR_LEN) || + (addr->type > BLE_ADDR_RANDOM)) { + BT_ERR("%s, Invalid remote address", __func__); + return -EINVAL; + } + + if (service_uuid != BLE_MESH_UUID_MESH_PROV_VAL && + service_uuid != BLE_MESH_UUID_MESH_PROXY_VAL) { + BT_ERR("%s, Invalid service uuid 0x%04x", __func__, service_uuid); + return -EINVAL; + } + + /* Check if already creating connection with the device */ + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (!memcmp(bt_mesh_gattc_info[i].addr, addr->val, BLE_MESH_ADDR_LEN)) { + BT_WARN("%s, Already create connection with %s", + __func__, bt_hex(addr->val, BLE_MESH_ADDR_LEN)); + return -EALREADY; + } + } + + /* Find empty element in queue to store device info */ + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if ((bt_mesh_gattc_info[i].conn.handle == 0xFFFF) && + (bt_mesh_gattc_info[i].service_uuid == 0x0000)) { + memcpy(bt_mesh_gattc_info[i].addr, addr->val, BLE_MESH_ADDR_LEN); + bt_mesh_gattc_info[i].addr_type = addr->type; + /* Service to be found after exhanging mtu size */ + bt_mesh_gattc_info[i].service_uuid = service_uuid; + break; + } + } + + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + BT_WARN("%s, gattc info is full", __func__); + return -ENOMEM; + } + +#if BLE_MESH_DEV + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCANNING)) { + BLE_MESH_BTM_CHECK_STATUS(BTM_BleScan(false, 0, NULL, NULL, NULL)); + } +#else + BLE_MESH_BTM_CHECK_STATUS(BTM_BleScan(false, 0, NULL, NULL, NULL)); +#endif /* BLE_MESH_DEV */ + + BT_DBG("%s, create conn with %s", __func__, bt_hex(addr->val, BLE_MESH_ADDR_LEN)); + + /* Min_interval: 250ms + * Max_interval: 250ms + * Slave_latency: 0x0 + * Supervision_timeout: 32 sec + */ + BTA_DmSetBlePrefConnParams(bt_mesh_gattc_info[i].addr, 0xC8, 0xC8, 0x00, 0xC80); + + BTA_GATTC_Open(bt_mesh_gattc_if, bt_mesh_gattc_info[i].addr, + bt_mesh_gattc_info[i].addr_type, true, BTA_GATT_TRANSPORT_LE); + + /* Increment pbg_count */ + provisioner_pbg_count_inc(); + + return 0; +} + +void bt_mesh_gattc_exchange_mtu(u8_t index) +{ + /** Set local MTU and exchange with GATT server. + * ATT_MTU >= 69 for Mesh GATT Prov Service + * ATT_NTU >= 33 for Mesh GATT Proxy Service + */ + u16_t conn_id; + + conn_id = BLE_MESH_GATT_CREATE_CONN_ID(bt_mesh_gattc_if, bt_mesh_gattc_info[index].conn.handle); + + BTA_GATTC_ConfigureMTU(conn_id); +} + +u16_t bt_mesh_gattc_get_mtu_info(struct bt_mesh_conn *conn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (conn == &bt_mesh_gattc_info[i].conn) { + return bt_mesh_gattc_info[i].mtu; + } + } + + return 0; +} + +int bt_mesh_gattc_write_no_rsp(struct bt_mesh_conn *conn, const struct bt_mesh_gatt_attr *attr, + const void *data, u16_t len) +{ + u16_t conn_id; + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (conn == &bt_mesh_gattc_info[i].conn) { + break; + } + } + + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + BT_ERR("%s, Conn is not found", __func__); + /** Here we return 0 for prov_send() return value check in provisioner.c + */ + return 0; + } + + conn_id = BLE_MESH_GATT_CREATE_CONN_ID(bt_mesh_gattc_if, bt_mesh_gattc_info[i].conn.handle); + + BTA_GATTC_WriteCharValue(conn_id, bt_mesh_gattc_info[i].data_in_handle, + BTA_GATTC_TYPE_WRITE_NO_RSP, len, + (u8_t *)data, BTA_GATT_AUTH_REQ_NONE); + + return 0; +} + +void bt_mesh_gattc_disconnect(struct bt_mesh_conn *conn) +{ + /** Disconnect + * Clear proper proxy server information + * Clear proper prov_link information + * Clear proper bt_mesh_gattc_info information + * Here in adapter, we just clear proper bt_mesh_gattc_info, and + * when proxy_disconnected callback comes, the proxy server + * information and prov_link information should be cleared. + */ + u16_t conn_id; + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (conn == &bt_mesh_gattc_info[i].conn) { + break; + } + } + + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + BT_ERR("%s, Conn is not found", __func__); + return; + } + + conn_id = BLE_MESH_GATT_CREATE_CONN_ID(bt_mesh_gattc_if, bt_mesh_gattc_info[i].conn.handle); + + BTA_GATTC_Close(conn_id); +} + +/** Mesh Provisioning Service: 0x1827 + * Mesh Provisioning Data In: 0x2ADB + * Mesh Provisioning Data Out: 0x2ADC + * Mesh Proxy Service: 0x1828 + * Mesh Proxy Data In: 0x2ADD + * Mesh PROXY Data Out: 0x2ADE + */ +static void bt_mesh_bta_gattc_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data) +{ + struct bt_mesh_conn *conn = NULL; + u16_t handle = 0; + ssize_t len = 0; + int i = 0; + + switch (event) { + case BTA_GATTC_REG_EVT: + if (p_data->reg_oper.status == BTA_GATT_OK) { + u8_t uuid[16] = { [0 ... 15] = BLE_MESH_GATTC_APP_UUID_BYTE }; + + BT_DBG("BTA_GATTC_REG_EVT"); + + if (p_data->reg_oper.app_uuid.len == LEN_UUID_128 && + !memcmp(p_data->reg_oper.app_uuid.uu.uuid128, uuid, 16)) { + bt_mesh_gattc_if = p_data->reg_oper.client_if; + BT_DBG("bt_mesh_gattc_if is %d", bt_mesh_gattc_if); + } + } + break; + case BTA_GATTC_CFG_MTU_EVT: { + if (p_data->cfg_mtu.status == BTA_GATT_OK) { + BT_DBG("BTA_GATTC_CFG_MTU_EVT, cfg_mtu is %d", p_data->cfg_mtu.mtu); + + handle = BLE_MESH_GATT_GET_CONN_ID(p_data->cfg_mtu.conn_id); + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].conn.handle == handle) { + bt_mesh_gattc_info[i].mtu = p_data->cfg_mtu.mtu; + break; + } + } + + /** Once mtu exchanged accomplished, start to find services, and here + * need a flag to indicate which service to find(Mesh Prov Service or + * Mesh Proxy Service) + */ + if (i != ARRAY_SIZE(bt_mesh_gattc_info)) { + tBT_UUID service_uuid; + u16_t conn_id; + + conn_id = BLE_MESH_GATT_CREATE_CONN_ID(bt_mesh_gattc_if, bt_mesh_gattc_info[i].conn.handle); + service_uuid.len = sizeof(bt_mesh_gattc_info[i].service_uuid); + service_uuid.uu.uuid16 = bt_mesh_gattc_info[i].service_uuid; + + /* Search Mesh Provisioning Service or Mesh Proxy Service */ + BTA_GATTC_ServiceSearchRequest(conn_id, &service_uuid); + } + } + break; + } + case BTA_GATTC_SEARCH_RES_EVT: { + BT_DBG("BTA_GATTC_SEARCH_RES_EVT"); + + handle = BLE_MESH_GATT_GET_CONN_ID(p_data->srvc_res.conn_id); + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].conn.handle == handle) { + break; + } + } + + if (i != ARRAY_SIZE(bt_mesh_gattc_info)) { + if (p_data->srvc_res.service_uuid.uuid.len == 2 && + p_data->srvc_res.service_uuid.uuid.uu.uuid16 == bt_mesh_gattc_info[i].service_uuid) { + bt_mesh_gattc_info[i].start_handle = p_data->srvc_res.start_handle; + bt_mesh_gattc_info[i].end_handle = p_data->srvc_res.end_handle; + } + } + break; + } + case BTA_GATTC_SEARCH_CMPL_EVT: { + if (p_data->search_cmpl.status == BTA_GATT_OK) { + BT_DBG("BTA_GATTC_SEARCH_CMPL_EVT"); + + handle = BLE_MESH_GATT_GET_CONN_ID(p_data->search_cmpl.conn_id); + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].conn.handle == handle) { + break; + } + } + + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + BT_ERR("%s, Conn handle is not found", __func__); + return; + } + + conn = &bt_mesh_gattc_info[i].conn; + + if (bt_mesh_gattc_info[i].start_handle == 0x00 || + bt_mesh_gattc_info[i].end_handle == 0x00 || + (bt_mesh_gattc_info[i].start_handle > bt_mesh_gattc_info[i].end_handle)) { + bt_mesh_gattc_disconnect(conn); + return; + } + + int count = 0; + int num = 0; + u16_t conn_id; + tBT_UUID char_uuid; + btgatt_db_element_t *result = NULL; + tBTA_GATT_STATUS status; + u16_t notify_en = BLE_MESH_GATT_CCC_NOTIFY; + tBTA_GATT_UNFMT write; + + /* Get the characteristic num within Mesh Provisioning/Proxy Service */ + conn_id = BLE_MESH_GATT_CREATE_CONN_ID(bt_mesh_gattc_if, bt_mesh_gattc_info[i].conn.handle); + BTA_GATTC_GetDBSizeByType(conn_id, BTGATT_DB_CHARACTERISTIC, bt_mesh_gattc_info[i].start_handle, + bt_mesh_gattc_info[i].end_handle, BTA_GATTC_INVALID_HANDLE, &count); + if (count != 2) { + bt_mesh_gattc_disconnect(conn); + return; + } + + /* Get Mesh Provisioning/Proxy Data In/Out Characteristic */ + for (int j = 0; j != 2; j++) { + /** First: find Mesh Provisioning/Proxy Data In Characteristic + * Second: find Mesh Provisioning/Proxy Data Out Characteristic + */ + char_uuid.len = 2; + if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROV_VAL) { + char_uuid.uu.uuid16 = BLE_MESH_UUID_MESH_PROV_DATA_IN_VAL + j; + } else if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROXY_VAL) { + char_uuid.uu.uuid16 = BLE_MESH_UUID_MESH_PROXY_DATA_IN_VAL + j; + } + + BTA_GATTC_GetCharByUUID(conn_id, bt_mesh_gattc_info[i].start_handle, + bt_mesh_gattc_info[i].end_handle, char_uuid, &result, &num); + + if (!result) { + bt_mesh_gattc_disconnect(conn); + return; + } + + if (num != 1) { + osi_free(result); + bt_mesh_gattc_disconnect(conn); + return; + } + + if (!j) { + if (!(result[0].properties & BLE_MESH_GATT_CHRC_WRITE_WITHOUT_RESP)) { + osi_free(result); + bt_mesh_gattc_disconnect(conn); + return; + } + bt_mesh_gattc_info[i].data_in_handle = result[0].attribute_handle; + } else { + if (!(result[0].properties & BLE_MESH_GATT_CHRC_NOTIFY)) { + osi_free(result); + bt_mesh_gattc_disconnect(conn); + return; + } + bt_mesh_gattc_info[i].data_out_handle = result[0].attribute_handle; + } + osi_free(result); + result = NULL; + } + + /* Register Notification fot Mesh Provisioning/Proxy Data Out Characteristic */ + status = BTA_GATTC_RegisterForNotifications(bt_mesh_gattc_if, bt_mesh_gattc_info[i].addr, + bt_mesh_gattc_info[i].data_out_handle); + if (status != BTA_GATT_OK) { + bt_mesh_gattc_disconnect(conn); + return; + } + + /** After notification is registered, get descriptor number of the + * Mesh Provisioning/Proxy Data Out Characteristic + */ + BTA_GATTC_GetDBSizeByType(conn_id, BTGATT_DB_DESCRIPTOR, bt_mesh_gattc_info[i].start_handle, + bt_mesh_gattc_info[i].end_handle, bt_mesh_gattc_info[i].data_out_handle, &num); + if (!num) { + bt_mesh_gattc_disconnect(conn); + return; + } + + /* Get CCC of Mesh Provisioning/Proxy Data Out Characteristic */ + char_uuid.len = 2; + char_uuid.uu.uuid16 = BLE_MESH_UUID_GATT_CCC_VAL; + BTA_GATTC_GetDescrByCharHandle(conn_id, bt_mesh_gattc_info[i].data_out_handle, + char_uuid, &result, &num); + + if (!result) { + bt_mesh_gattc_disconnect(conn); + return; + } + + if (num != 1) { + osi_free(result); + bt_mesh_gattc_disconnect(conn); + return; + } + + bt_mesh_gattc_info[i].ccc_handle = result[0].attribute_handle; + + /** Enable Notification of Mesh Provisioning/Proxy Data Out + * Characteristic Descriptor. + */ + write.len = sizeof(notify_en); + write.p_value = (u8_t *)¬ify_en; + BTA_GATTC_WriteCharDescr(conn_id, result[0].attribute_handle, + BTA_GATTC_TYPE_WRITE, &write, BTA_GATT_AUTH_REQ_NONE); + + osi_free(result); + result = NULL; + } + break; + } + case BTA_GATTC_READ_DESCR_EVT: + break; + case BTA_GATTC_WRITE_DESCR_EVT: { + if (p_data->write.status == BTA_GATT_OK) { + BT_DBG("BTA_GATTC_WRITE_DESCR_EVT"); + + handle = BLE_MESH_GATT_GET_CONN_ID(p_data->write.conn_id); + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].conn.handle == handle) { + break; + } + } + + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + BT_ERR("%s, Conn handle is not found", __func__); + return; + } + + conn = &bt_mesh_gattc_info[i].conn; + + if (bt_mesh_gattc_info[i].ccc_handle != p_data->write.handle) { + BT_WARN("%s, gattc ccc_handle is not matched", __func__); + bt_mesh_gattc_disconnect(conn); + return; + } + + if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROV_VAL) { + if (bt_mesh_gattc_conn_cb != NULL && bt_mesh_gattc_conn_cb->prov_write_descr != NULL) { + len = bt_mesh_gattc_conn_cb->prov_write_descr(&bt_mesh_gattc_info[i].conn, bt_mesh_gattc_info[i].addr); + if (len < 0) { + BT_ERR("%s, prov_write_descr failed", __func__); + bt_mesh_gattc_disconnect(conn); + return; + } + bt_mesh_gattc_info[i].wr_desc_done = true; + } + } else if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROXY_VAL) { + if (bt_mesh_gattc_conn_cb != NULL && bt_mesh_gattc_conn_cb->proxy_write_descr != NULL) { + len = bt_mesh_gattc_conn_cb->proxy_write_descr(&bt_mesh_gattc_info[i].conn); + if (len < 0) { + BT_ERR("%s, proxy_write_descr failed", __func__); + bt_mesh_gattc_disconnect(conn); + return; + } + } + } + } + break; + } + case BTA_GATTC_NOTIF_EVT: { + BT_DBG("BTA_GATTC_NOTIF_EVT"); + + handle = BLE_MESH_GATT_GET_CONN_ID(p_data->notify.conn_id); + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].conn.handle == handle) { + break; + } + } + + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + BT_ERR("%s, Conn handle is not found", __func__); + return; + } + + conn = &bt_mesh_gattc_info[i].conn; + + if (memcmp(bt_mesh_gattc_info[i].addr, p_data->notify.bda, BLE_MESH_ADDR_LEN) || + bt_mesh_gattc_info[i].data_out_handle != p_data->notify.handle || + p_data->notify.is_notify == false) { + BT_ERR("%s, Notification error", __func__); + bt_mesh_gattc_disconnect(conn); + return; + } + + if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROV_VAL) { + if (bt_mesh_gattc_conn_cb != NULL && bt_mesh_gattc_conn_cb->prov_notify != NULL) { + len = bt_mesh_gattc_conn_cb->prov_notify(&bt_mesh_gattc_info[i].conn, + p_data->notify.value, p_data->notify.len); + if (len < 0) { + BT_ERR("%s, prov_notify failed", __func__); + bt_mesh_gattc_disconnect(conn); + return; + } + } + } else if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROXY_VAL) { + if (bt_mesh_gattc_conn_cb != NULL && bt_mesh_gattc_conn_cb->proxy_notify != NULL) { + len = bt_mesh_gattc_conn_cb->proxy_notify(&bt_mesh_gattc_info[i].conn, + p_data->notify.value, p_data->notify.len); + if (len < 0) { + BT_ERR("%s, proxy_notify failed", __func__); + bt_mesh_gattc_disconnect(conn); + return; + } + } + } + break; + } + case BTA_GATTC_READ_CHAR_EVT: + break; + case BTA_GATTC_WRITE_CHAR_EVT: + break; + case BTA_GATTC_PREP_WRITE_EVT: + break; + case BTA_GATTC_EXEC_EVT: + break; + case BTA_GATTC_OPEN_EVT: { + BT_DBG("BTA_GATTC_OPEN_EVT"); + /** After current connection is established, provisioner can + * use BTA_DmBleScan() to re-enable scan. + */ + tBTM_STATUS status; +#if BLE_MESH_DEV + if (!bt_mesh_atomic_test_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCANNING)) { + status = BTM_BleScan(true, 0, bt_mesh_scan_results_cb, NULL, NULL); + if (status != BTM_SUCCESS && status != BTM_CMD_STARTED) { + BT_ERR("%s, Invalid status %d", __func__, status); + break; + } + bt_mesh_atomic_set_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCANNING); + } +#else + status = BTM_BleScan(true, 0, bt_mesh_scan_results_cb, NULL, NULL); + if (status != BTM_SUCCESS && status != BTM_CMD_STARTED) { + BT_ERR("%s, Invalid status %d", __func__, status); + break; + } +#endif /* BLE_MESH_DEV */ + break; + } + case BTA_GATTC_CLOSE_EVT: + BT_DBG("BTA_GATTC_CLOSE_EVT"); + break; + case BTA_GATTC_CONNECT_EVT: { + BT_DBG("BTA_GATTC_CONNECT_EVT"); + + if (bt_mesh_gattc_if != p_data->connect.client_if) { + BT_ERR("%s, gattc_if & connect_if don't match", __func__); + return; + } + + if (bt_mesh_gattc_conn_cb != NULL && bt_mesh_gattc_conn_cb->connected != NULL) { + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (!memcmp(bt_mesh_gattc_info[i].addr, p_data->connect.remote_bda, BLE_MESH_ADDR_LEN)) { + bt_mesh_gattc_info[i].conn.handle = BLE_MESH_GATT_GET_CONN_ID(p_data->connect.conn_id); + (bt_mesh_gattc_conn_cb->connected)(bt_mesh_gattc_info[i].addr, &bt_mesh_gattc_info[i].conn, i); + break; + } + } + } + break; + } + case BTA_GATTC_DISCONNECT_EVT: { + BT_DBG("BTA_GATTC_DISCONNECT_EVT"); + + if (bt_mesh_gattc_if != p_data->disconnect.client_if) { + BT_ERR("%s, gattc_if & disconnect_if don't match", __func__); + return; + } + + handle = BLE_MESH_GATT_GET_CONN_ID(p_data->disconnect.conn_id); + + if (bt_mesh_gattc_conn_cb != NULL && bt_mesh_gattc_conn_cb->disconnected != NULL) { + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (!memcmp(bt_mesh_gattc_info[i].addr, p_data->disconnect.remote_bda, BLE_MESH_ADDR_LEN)) { + if (bt_mesh_gattc_info[i].conn.handle == handle) { + (bt_mesh_gattc_conn_cb->disconnected)(&bt_mesh_gattc_info[i].conn, p_data->disconnect.reason); + if (!bt_mesh_gattc_info[i].wr_desc_done) { + /* Add this in case connection is established, connected event comes, but + * connection is terminated before server->filter_type is set to PROV. + */ + provisioner_clear_link_conn_info(bt_mesh_gattc_info[i].addr); + } + } else { + /* Add this in case connection is failed to be established, and here we + * need to clear some provision link info, like connecting flag, device + * uuid, address info, etc. + */ + provisioner_clear_link_conn_info(bt_mesh_gattc_info[i].addr); + } + /* Decrease prov pbg_count */ + provisioner_pbg_count_dec(); + /* Reset corresponding gattc info */ + memset(&bt_mesh_gattc_info[i], 0, sizeof(bt_mesh_gattc_info[i])); + bt_mesh_gattc_info[i].conn.handle = 0xFFFF; + bt_mesh_gattc_info[i].mtu = GATT_DEF_BLE_MTU_SIZE; + bt_mesh_gattc_info[i].wr_desc_done = false; + break; + } + } + } + break; + } + case BTA_GATTC_CONGEST_EVT: + break; + case BTA_GATTC_SRVC_CHG_EVT: + break; + default: + break; + } +} +#endif /* defined(CONFIG_BLE_MESH_PROVISIONER) && CONFIG_BLE_MESH_PROVISIONER */ + +struct bt_mesh_conn *bt_mesh_conn_ref(struct bt_mesh_conn *conn) +{ + bt_mesh_atomic_inc(&conn->ref); + + BT_DBG("handle %u ref %u", conn->handle, bt_mesh_atomic_get(&conn->ref)); + + return conn; +} + +void bt_mesh_conn_unref(struct bt_mesh_conn *conn) +{ + bt_mesh_atomic_dec(&conn->ref); + + BT_DBG("handle %u ref %u", conn->handle, bt_mesh_atomic_get(&conn->ref)); +} + +void bt_mesh_gatt_init(void) +{ + tBT_UUID app_uuid = {LEN_UUID_128, {0}}; + + BTA_GATT_SetLocalMTU(GATT_DEF_BLE_MTU_SIZE); + +#if CONFIG_BLE_MESH_NODE + /* Fill our internal UUID with a fixed pattern 0x96 for the ble mesh */ + memset(&app_uuid.uu.uuid128, 0x96, LEN_UUID_128); + BTA_GATTS_AppRegister(&app_uuid, bt_mesh_bta_gatts_cb); +#endif + +#if CONFIG_BLE_MESH_PROVISIONER + for (int i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + bt_mesh_gattc_info[i].conn.handle = 0xFFFF; + bt_mesh_gattc_info[i].mtu = GATT_DEF_BLE_MTU_SIZE; /* Default MTU_SIZE 23 */ + bt_mesh_gattc_info[i].wr_desc_done = false; + } + memset(&app_uuid.uu.uuid128, BLE_MESH_GATTC_APP_UUID_BYTE, LEN_UUID_128); + BTA_GATTC_AppRegister(&app_uuid, bt_mesh_bta_gattc_cb); +#endif +} + +void bt_mesh_adapt_init(void) +{ + BT_DBG("%s", __func__); + /* initialization of P-256 parameters */ + p_256_init_curve(KEY_LENGTH_DWORDS_P256); +} + +int bt_mesh_rand(void *buf, size_t len) +{ + int i; + + if (buf == NULL || len == 0) { + BT_ERR("%s, Invalid parameter", __func__); + return -EAGAIN; + } + + for (i = 0; i < (int)(len / sizeof(u32_t)); i++) { + u32_t rand = esp_random(); + memcpy(buf + i * sizeof(u32_t), &rand, sizeof(u32_t)); + } + + BT_DBG("%s, rand: %s", __func__, bt_hex(buf, len)); + return 0; +} + +void bt_mesh_set_private_key(const u8_t pri_key[32]) +{ + memcpy(bt_mesh_private_key, pri_key, 32); +} + +const u8_t *bt_mesh_pub_key_get(void) +{ + Point public_key; + BT_OCTET32 pri_key; +#if 1 + if (bt_mesh_atomic_test_bit(bt_mesh_dev.flags, BLE_MESH_DEV_HAS_PUB_KEY)) { + return bt_mesh_public_key; + } +#else + /* BLE Mesh BQB test case MESH/NODE/PROV/UPD/BV-12-C requires + * different public key for each provisioning procedure. + * Note: if enabled, when Provisioner provision multiple devices + * at the same time, this may cause invalid confirmation value. + */ + if (bt_mesh_rand(bt_mesh_private_key, 32)) { + BT_ERR("%s, Unable to generate bt_mesh_private_key", __func__); + return NULL; + } +#endif + mem_rcopy(pri_key, bt_mesh_private_key, 32); + ECC_PointMult(&public_key, &(curve_p256.G), (DWORD *)pri_key, KEY_LENGTH_DWORDS_P256); + + memcpy(bt_mesh_public_key, public_key.x, BT_OCTET32_LEN); + memcpy(bt_mesh_public_key + BT_OCTET32_LEN, public_key.y, BT_OCTET32_LEN); + + bt_mesh_atomic_set_bit(bt_mesh_dev.flags, BLE_MESH_DEV_HAS_PUB_KEY); + BT_DBG("gen the bt_mesh_public_key:%s", bt_hex(bt_mesh_public_key, sizeof(bt_mesh_public_key))); + + return bt_mesh_public_key; +} + +bool bt_mesh_check_public_key(const u8_t key[64]) +{ + struct p256_pub_key { + u8_t x[32]; + u8_t y[32]; + } check = {0}; + + sys_memcpy_swap(check.x, key, 32); + sys_memcpy_swap(check.y, key + 32, 32); + + return ECC_CheckPointIsInElliCur_P256((Point *)&check); +} + +int bt_mesh_dh_key_gen(const u8_t remote_pk[64], bt_mesh_dh_key_cb_t cb, const u8_t idx) +{ + BT_OCTET32 private_key; + Point peer_publ_key; + Point new_publ_key; + BT_OCTET32 dhkey; + + BT_DBG("private key = %s", bt_hex(bt_mesh_private_key, BT_OCTET32_LEN)); + + mem_rcopy(private_key, bt_mesh_private_key, BT_OCTET32_LEN); + memcpy(peer_publ_key.x, remote_pk, BT_OCTET32_LEN); + memcpy(peer_publ_key.y, &remote_pk[BT_OCTET32_LEN], BT_OCTET32_LEN); + + BT_DBG("remote public key x = %s", bt_hex(peer_publ_key.x, BT_OCTET32_LEN)); + BT_DBG("remote public key y = %s", bt_hex(peer_publ_key.y, BT_OCTET32_LEN)); + + ECC_PointMult(&new_publ_key, &peer_publ_key, (DWORD *) private_key, KEY_LENGTH_DWORDS_P256); + + memcpy(dhkey, new_publ_key.x, BT_OCTET32_LEN); + + BT_DBG("new public key x = %s", bt_hex(new_publ_key.x, 32)); + BT_DBG("new public key y = %s", bt_hex(new_publ_key.y, 32)); + + if (cb != NULL) { + cb((const u8_t *)dhkey, idx); + } + + return 0; +} + +#if CONFIG_MBEDTLS_HARDWARE_AES +static void ecb_encrypt(u8_t const *const key_le, u8_t const *const clear_text_le, + u8_t *const cipher_text_le, u8_t *const cipher_text_be) +{ + struct bt_mesh_ecb_param ecb; + mbedtls_aes_context aes_ctx = {0}; + + aes_ctx.key_bytes = 16; + mem_rcopy(&aes_ctx.key[0], key_le, 16); + mem_rcopy(&ecb.clear_text[0], clear_text_le, sizeof(ecb.clear_text)); + mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT, &ecb.clear_text[0], &ecb.cipher_text[0]); + + if (cipher_text_le) { + mem_rcopy(cipher_text_le, &ecb.cipher_text[0], + sizeof(ecb.cipher_text)); + } + + if (cipher_text_be) { + memcpy(cipher_text_be, &ecb.cipher_text[0], + sizeof(ecb.cipher_text)); + } +} + +static void ecb_encrypt_be(u8_t const *const key_be, u8_t const *const clear_text_be, + u8_t *const cipher_text_be) +{ + struct bt_mesh_ecb_param ecb; + mbedtls_aes_context aes_ctx = {0}; + + aes_ctx.key_bytes = 16; + memcpy(&aes_ctx.key[0], key_be, 16); + memcpy(&ecb.clear_text[0], clear_text_be, sizeof(ecb.clear_text)); + mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT, &ecb.clear_text[0], &ecb.cipher_text[0]); + + memcpy(cipher_text_be, &ecb.cipher_text[0], sizeof(ecb.cipher_text)); +} +#endif /* CONFIG_MBEDTLS_HARDWARE_AES */ + +int bt_mesh_encrypt_le(const u8_t key[16], const u8_t plaintext[16], + u8_t enc_data[16]) +{ +#if CONFIG_MBEDTLS_HARDWARE_AES + BT_DBG("key %s plaintext %s", bt_hex(key, 16), bt_hex(plaintext, 16)); + + ecb_encrypt(key, plaintext, enc_data, NULL); + + BT_DBG("enc_data %s", bt_hex(enc_data, 16)); + return 0; +#else /* CONFIG_MBEDTLS_HARDWARE_AES */ + struct tc_aes_key_sched_struct s; + u8_t tmp[16]; + + BT_DBG("key %s plaintext %s", bt_hex(key, 16), bt_hex(plaintext, 16)); + + sys_memcpy_swap(tmp, key, 16); + + if (tc_aes128_set_encrypt_key(&s, tmp) == TC_CRYPTO_FAIL) { + return -EINVAL; + } + + sys_memcpy_swap(tmp, plaintext, 16); + + if (tc_aes_encrypt(enc_data, tmp, &s) == TC_CRYPTO_FAIL) { + return -EINVAL; + } + + sys_mem_swap(enc_data, 16); + + BT_DBG("enc_data %s", bt_hex(enc_data, 16)); + + return 0; +#endif /* CONFIG_MBEDTLS_HARDWARE_AES */ +} + +int bt_mesh_encrypt_be(const u8_t key[16], const u8_t plaintext[16], + u8_t enc_data[16]) +{ +#if CONFIG_MBEDTLS_HARDWARE_AES + BT_DBG("key %s plaintext %s", bt_hex(key, 16), bt_hex(plaintext, 16)); + + ecb_encrypt_be(key, plaintext, enc_data); + + BT_DBG("enc_data %s", bt_hex(enc_data, 16)); + + return 0; +#else /* CONFIG_MBEDTLS_HARDWARE_AES */ + struct tc_aes_key_sched_struct s; + + BT_DBG("key %s plaintext %s", bt_hex(key, 16), bt_hex(plaintext, 16)); + + if (tc_aes128_set_encrypt_key(&s, key) == TC_CRYPTO_FAIL) { + return -EINVAL; + } + + if (tc_aes_encrypt(enc_data, plaintext, &s) == TC_CRYPTO_FAIL) { + return -EINVAL; + } + + BT_DBG("enc_data %s", bt_hex(enc_data, 16)); + + return 0; +#endif /* CONFIG_MBEDTLS_HARDWARE_AES */ +} + +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) +int bt_mesh_update_exceptional_list(u8_t sub_code, u8_t type, void *info) +{ + BD_ADDR value = {0}; + + if ((sub_code > BLE_MESH_EXCEP_LIST_CLEAN) || + (type > BLE_MESH_EXCEP_INFO_MESH_PROXY_ADV)) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (type == BLE_MESH_EXCEP_INFO_MESH_LINK_ID) { + if (!info) { + BT_ERR("%s, NULL Provisioning Link ID", __func__); + return -EINVAL; + } + memcpy(value, info, sizeof(u32_t)); + } + + BT_DBG("%s, %s type 0x%x", __func__, sub_code ? "Remove" : "Add", type); + + /* The parameter "device_info" can't be NULL in the API */ + BLE_MESH_BTM_CHECK_STATUS(BTM_UpdateBleDuplicateExceptionalList(sub_code, type, value, NULL)); + + return 0; +} +#endif diff --git a/components/bt/ble_mesh/mesh_core/mesh_buf.c b/components/bt/ble_mesh/mesh_core/mesh_buf.c new file mode 100644 index 0000000000..d6e0cd710e --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/mesh_buf.c @@ -0,0 +1,453 @@ +/* + * Copyright (c) 2015 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "sdkconfig.h" + +#include "mesh_buf.h" +#include "mesh_trace.h" + +int net_buf_id(struct net_buf *buf) +{ + struct net_buf_pool *pool = buf->pool; + + return buf - pool->__bufs; +} + +static inline struct net_buf *pool_get_uninit(struct net_buf_pool *pool, + u16_t uninit_count) +{ + struct net_buf *buf; + + buf = &pool->__bufs[pool->buf_count - uninit_count]; + + buf->pool = pool; + + return buf; +} + +void *net_buf_simple_add(struct net_buf_simple *buf, size_t len) +{ + u8_t *tail = net_buf_simple_tail(buf); + + NET_BUF_SIMPLE_DBG("buf %p len %u", buf, len); + + NET_BUF_SIMPLE_ASSERT(net_buf_simple_tailroom(buf) >= len); + + buf->len += len; + return tail; +} + +void *net_buf_simple_add_mem(struct net_buf_simple *buf, const void *mem, + size_t len) +{ + NET_BUF_SIMPLE_DBG("buf %p len %u", buf, len); + + return memcpy(net_buf_simple_add(buf, len), mem, len); +} + +u8_t *net_buf_simple_add_u8(struct net_buf_simple *buf, u8_t val) +{ + u8_t *u8; + + NET_BUF_SIMPLE_DBG("buf %p val 0x%02x", buf, val); + + u8 = net_buf_simple_add(buf, 1); + *u8 = val; + + return u8; +} + +void net_buf_simple_add_le16(struct net_buf_simple *buf, u16_t val) +{ + NET_BUF_SIMPLE_DBG("buf %p val %u", buf, val); + + val = sys_cpu_to_le16(val); + memcpy(net_buf_simple_add(buf, sizeof(val)), &val, sizeof(val)); +} + +void net_buf_simple_add_be16(struct net_buf_simple *buf, u16_t val) +{ + NET_BUF_SIMPLE_DBG("buf %p val %u", buf, val); + + val = sys_cpu_to_be16(val); + memcpy(net_buf_simple_add(buf, sizeof(val)), &val, sizeof(val)); +} + +void net_buf_simple_add_le32(struct net_buf_simple *buf, u32_t val) +{ + NET_BUF_SIMPLE_DBG("buf %p val %u", buf, val); + + val = sys_cpu_to_le32(val); + memcpy(net_buf_simple_add(buf, sizeof(val)), &val, sizeof(val)); +} + +void net_buf_simple_add_be32(struct net_buf_simple *buf, u32_t val) +{ + NET_BUF_SIMPLE_DBG("buf %p val %u", buf, val); + + val = sys_cpu_to_be32(val); + memcpy(net_buf_simple_add(buf, sizeof(val)), &val, sizeof(val)); +} + +void *net_buf_simple_push(struct net_buf_simple *buf, size_t len) +{ + NET_BUF_SIMPLE_DBG("buf %p len %u", buf, len); + + NET_BUF_SIMPLE_ASSERT(net_buf_simple_headroom(buf) >= len); + + buf->data -= len; + buf->len += len; + return buf->data; +} + +void net_buf_simple_push_le16(struct net_buf_simple *buf, u16_t val) +{ + NET_BUF_SIMPLE_DBG("buf %p val %u", buf, val); + + val = sys_cpu_to_le16(val); + memcpy(net_buf_simple_push(buf, sizeof(val)), &val, sizeof(val)); +} + +void net_buf_simple_push_be16(struct net_buf_simple *buf, u16_t val) +{ + NET_BUF_SIMPLE_DBG("buf %p val %u", buf, val); + + val = sys_cpu_to_be16(val); + memcpy(net_buf_simple_push(buf, sizeof(val)), &val, sizeof(val)); +} + +void net_buf_simple_push_u8(struct net_buf_simple *buf, u8_t val) +{ + u8_t *data = net_buf_simple_push(buf, 1); + + *data = val; +} + +void *net_buf_simple_pull(struct net_buf_simple *buf, size_t len) +{ + NET_BUF_SIMPLE_DBG("buf %p len %u", buf, len); + + NET_BUF_SIMPLE_ASSERT(buf->len >= len); + + buf->len -= len; + return buf->data += len; +} + +void *net_buf_simple_pull_mem(struct net_buf_simple *buf, size_t len) +{ + void *data = buf->data; + + NET_BUF_SIMPLE_DBG("buf %p len %zu", buf, len); + + NET_BUF_SIMPLE_ASSERT(buf->len >= len); + + buf->len -= len; + buf->data += len; + + return data; +} + +u8_t net_buf_simple_pull_u8(struct net_buf_simple *buf) +{ + u8_t val; + + val = buf->data[0]; + net_buf_simple_pull(buf, 1); + + return val; +} + +u16_t net_buf_simple_pull_le16(struct net_buf_simple *buf) +{ + u16_t val; + + val = UNALIGNED_GET((u16_t *)buf->data); + net_buf_simple_pull(buf, sizeof(val)); + + return sys_le16_to_cpu(val); +} + +u16_t net_buf_simple_pull_be16(struct net_buf_simple *buf) +{ + u16_t val; + + val = UNALIGNED_GET((u16_t *)buf->data); + net_buf_simple_pull(buf, sizeof(val)); + + return sys_be16_to_cpu(val); +} + +u32_t net_buf_simple_pull_le32(struct net_buf_simple *buf) +{ + u32_t val; + + val = UNALIGNED_GET((u32_t *)buf->data); + net_buf_simple_pull(buf, sizeof(val)); + + return sys_le32_to_cpu(val); +} + +u32_t net_buf_simple_pull_be32(struct net_buf_simple *buf) +{ + u32_t val; + + val = UNALIGNED_GET((u32_t *)buf->data); + net_buf_simple_pull(buf, sizeof(val)); + + return sys_be32_to_cpu(val); +} + +size_t net_buf_simple_headroom(struct net_buf_simple *buf) +{ + return buf->data - buf->__buf; +} + +size_t net_buf_simple_tailroom(struct net_buf_simple *buf) +{ + return buf->size - net_buf_simple_headroom(buf) - buf->len; +} + +void net_buf_reset(struct net_buf *buf) +{ + NET_BUF_ASSERT(buf->flags == 0); + NET_BUF_ASSERT(buf->frags == NULL); + + net_buf_simple_reset(&buf->b); +} + +void net_buf_simple_reserve(struct net_buf_simple *buf, size_t reserve) +{ + NET_BUF_ASSERT(buf); + NET_BUF_ASSERT(buf->len == 0U); + NET_BUF_DBG("buf %p reserve %zu", buf, reserve); + + buf->data = buf->__buf + reserve; +} + +void net_buf_slist_put(sys_slist_t *list, struct net_buf *buf) +{ + struct net_buf *tail; + unsigned int key; + + NET_BUF_ASSERT(list); + NET_BUF_ASSERT(buf); + + for (tail = buf; tail->frags; tail = tail->frags) { + tail->flags |= NET_BUF_FRAGS; + } + + key = bt_mesh_irq_lock(); + sys_slist_append_list(list, &buf->node, &tail->node); + bt_mesh_irq_unlock(key); +} + +struct net_buf *net_buf_slist_get(sys_slist_t *list) +{ + struct net_buf *buf, *frag; + unsigned int key; + + NET_BUF_ASSERT(list); + + key = bt_mesh_irq_lock(); + buf = (void *)sys_slist_get(list); + bt_mesh_irq_unlock(key); + + if (!buf) { + return NULL; + } + + /* Get any fragments belonging to this buffer */ + for (frag = buf; (frag->flags & NET_BUF_FRAGS); frag = frag->frags) { + key = bt_mesh_irq_lock(); + frag->frags = (void *)sys_slist_get(list); + bt_mesh_irq_unlock(key); + + NET_BUF_ASSERT(frag->frags); + + /* The fragments flag is only for list-internal usage */ + frag->flags &= ~NET_BUF_FRAGS; + } + + /* Mark the end of the fragment list */ + frag->frags = NULL; + + return buf; +} + +struct net_buf *net_buf_ref(struct net_buf *buf) +{ + NET_BUF_ASSERT(buf); + + NET_BUF_DBG("buf %p (old) ref %u pool %p", buf, buf->ref, buf->pool); + + buf->ref++; + return buf; +} + +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) +void net_buf_unref_debug(struct net_buf *buf, const char *func, int line) +#else +void net_buf_unref(struct net_buf *buf) +#endif +{ + NET_BUF_ASSERT(buf); + + while (buf) { + struct net_buf *frags = buf->frags; + struct net_buf_pool *pool; + +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) + if (!buf->ref) { + NET_BUF_ERR("%s():%d: buf %p double free", func, line, + buf); + return; + } +#endif + NET_BUF_DBG("buf %p ref %u pool %p frags %p", buf, buf->ref, + buf->pool, buf->frags); + + /* Changed by Espressif. Add !buf->ref to avoid minus 0 */ + if (!buf->ref || --buf->ref > 0) { + return; + } + + buf->frags = NULL; + + pool = buf->pool; + + pool->uninit_count++; +#if defined(CONFIG_BLE_MESH_NET_BUF_POOL_USAGE) + pool->avail_count++; + NET_BUF_DBG("%s, pool %p, avail_count %d, uninit_count %d", __func__, + pool, pool->avail_count, pool->uninit_count); + NET_BUF_ASSERT(pool->avail_count <= pool->buf_count); +#endif + + if (pool->destroy) { + pool->destroy(buf); + } + + buf = frags; + } +} + +static u8_t *fixed_data_alloc(struct net_buf *buf, size_t *size, s32_t timeout) +{ + struct net_buf_pool *pool = buf->pool; + const struct net_buf_pool_fixed *fixed = pool->alloc->alloc_data; + + *size = MIN(fixed->data_size, *size); + + return fixed->data_pool + fixed->data_size * net_buf_id(buf); +} + +static void fixed_data_unref(struct net_buf *buf, u8_t *data) +{ + /* Nothing needed for fixed-size data pools */ +} + +const struct net_buf_data_cb net_buf_fixed_cb = { + .alloc = fixed_data_alloc, + .unref = fixed_data_unref, +}; + +static u8_t *data_alloc(struct net_buf *buf, size_t *size, s32_t timeout) +{ + struct net_buf_pool *pool = buf->pool; + + return pool->alloc->cb->alloc(buf, size, timeout); +} + +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) +struct net_buf *net_buf_alloc_len_debug(struct net_buf_pool *pool, size_t size, + s32_t timeout, const char *func, int line) +#else +struct net_buf *net_buf_alloc_len(struct net_buf_pool *pool, size_t size, + s32_t timeout) +#endif +{ + struct net_buf *buf = NULL; + unsigned int key; + int i; + + NET_BUF_ASSERT(pool); + + NET_BUF_DBG("%s, pool %p, uninit_count %d, buf_count %d", __func__, + pool, pool->uninit_count, pool->buf_count); + + /* We need to lock interrupts temporarily to prevent race conditions + * when accessing pool->uninit_count. + */ + key = bt_mesh_irq_lock(); + + /* If there are uninitialized buffers we're guaranteed to succeed + * with the allocation one way or another. + */ + if (pool->uninit_count) { + /* Changed by Espressif. Use buf when buf->ref is 0 */ + for (i = pool->buf_count; i > 0; i--) { + buf = pool_get_uninit(pool, i); + if (!buf->ref) { + bt_mesh_irq_unlock(key); + goto success; + } + } + } + + bt_mesh_irq_unlock(key); + + NET_BUF_ERR("%s, Failed to get free buffer", __func__); + return NULL; + +success: + NET_BUF_DBG("allocated buf %p", buf); + + if (size) { + buf->__buf = data_alloc(buf, &size, timeout); + if (!buf->__buf) { + NET_BUF_ERR("%s, Failed to allocate data", __func__); + return NULL; + } + } else { + NET_BUF_WARN("%s, Zero data size", __func__); + buf->__buf = NULL; + } + + buf->ref = 1; + buf->flags = 0; + buf->frags = NULL; + buf->size = size; + net_buf_reset(buf); + + pool->uninit_count--; +#if defined(CONFIG_BLE_MESH_NET_BUF_POOL_USAGE) + pool->avail_count--; + NET_BUF_ASSERT(pool->avail_count >= 0); +#endif + + return buf; +} + +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) +struct net_buf *net_buf_alloc_fixed_debug(struct net_buf_pool *pool, + s32_t timeout, const char *func, + int line) +{ + const struct net_buf_pool_fixed *fixed = pool->alloc->alloc_data; + + return net_buf_alloc_len_debug(pool, fixed->data_size, timeout, func, line); +} +#else +struct net_buf *net_buf_alloc_fixed(struct net_buf_pool *pool, s32_t timeout) +{ + const struct net_buf_pool_fixed *fixed = pool->alloc->alloc_data; + + return net_buf_alloc_len(pool, fixed->data_size, timeout); +} +#endif \ No newline at end of file diff --git a/components/bt/ble_mesh/mesh_core/mesh_hci.c b/components/bt/ble_mesh/mesh_core/mesh_hci.c new file mode 100644 index 0000000000..72da73a389 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/mesh_hci.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" + +#include "stack/bt_types.h" +#include "device/controller.h" + +#include "mesh_hci.h" + +struct bt_mesh_dev bt_mesh_dev; + +void bt_mesh_hci_init(void) +{ + const uint8_t *features = controller_get_interface()->get_features_ble()->as_array; + if (features != NULL) { + memcpy(bt_mesh_dev.features[0], features, 8); + memcpy(bt_mesh_dev.le.features, features, 8); + } + + /** + * Currently 20ms non-connectable adv interval is supported, and we need to add + * a flag to indicate this support. + */ +#ifdef CONFIG_BLE_MESH_HCI_5_0 + bt_mesh_dev.hci_version = BLE_MESH_HCI_VERSION_5_0; +#else + bt_mesh_dev.hci_version = controller_get_interface()->get_bt_version()->hci_version; +#endif + bt_mesh_dev.lmp_version = controller_get_interface()->get_bt_version()->lmp_version; + bt_mesh_dev.hci_revision = controller_get_interface()->get_bt_version()->hci_revision; + bt_mesh_dev.lmp_subversion = controller_get_interface()->get_bt_version()->lmp_subversion; + bt_mesh_dev.manufacturer = controller_get_interface()->get_bt_version()->manufacturer; + + const uint8_t *p = controller_get_interface()->get_ble_supported_states(); + uint64_t states_fh = 0, states_sh = 0; + STREAM_TO_UINT32(states_fh, p); + STREAM_TO_UINT32(states_sh, p); + bt_mesh_dev.le.states = (states_sh << 32) | states_fh; +} diff --git a/components/bt/ble_mesh/mesh_core/mesh_kernel.c b/components/bt/ble_mesh/mesh_core/mesh_kernel.c new file mode 100644 index 0000000000..d1d1ca6670 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/mesh_kernel.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2016 Wind River Systems, Inc. + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "sdkconfig.h" + +#include "osi/hash_map.h" +#include "osi/alarm.h" +#include "osi/hash_functions.h" + +#include "common/bt_trace.h" +#include "common/bt_defs.h" + +#include "esp_timer.h" + +#include "mesh_kernel.h" +#include "mesh_trace.h" + +#include "provisioner_prov.h" + +static osi_mutex_t bm_alarm_lock; +static osi_mutex_t bm_irq_lock; +static hash_map_t *bm_alarm_hash_map; +static const size_t BLE_MESH_GENERAL_ALARM_HASH_MAP_SIZE = 20 + CONFIG_BLE_MESH_PBA_SAME_TIME + \ + CONFIG_BLE_MESH_PBG_SAME_TIME; + +typedef struct alarm_t { + /* timer id point to here */ + esp_timer_handle_t alarm_hdl; + osi_alarm_callback_t cb; + void *cb_data; + int64_t deadline_us; +} osi_alarm_t; + +static void bt_mesh_alarm_cb(void *data) +{ + assert(data != NULL); + struct k_delayed_work *work = (struct k_delayed_work *)data; + work->work.handler(&work->work); + return; +} + +unsigned int bt_mesh_irq_lock(void) +{ +#if defined(CONFIG_BLE_MESH_IRQ_LOCK) && CONFIG_BLE_MESH_IRQ_LOCK + unsigned int key = XTOS_SET_INTLEVEL(XCHAL_EXCM_LEVEL); + return key; +#else + /* Change by Espressif. In BLE Mesh, in order to improve the real-time + * requirements of bt controller, we use task lock to replace IRQ lock. + */ + osi_mutex_lock(&bm_irq_lock, OSI_MUTEX_MAX_TIMEOUT); + return 0; +#endif +} + +void bt_mesh_irq_unlock(unsigned int key) +{ +#if defined(CONFIG_BLE_MESH_IRQ_LOCK) && CONFIG_BLE_MESH_IRQ_LOCK + XTOS_RESTORE_INTLEVEL(key); +#else + osi_mutex_unlock(&bm_irq_lock); +#endif +} + +s64_t k_uptime_get(void) +{ + /** k_uptime_get_32 is in in milliseconds, + * but esp_timer_get_time is in microseconds + */ + return (esp_timer_get_time() / 1000); +} + +u32_t k_uptime_get_32(void) +{ + /** k_uptime_get_32 is in in milliseconds, + * but esp_timer_get_time is in microseconds + */ + return (u32_t)(esp_timer_get_time() / 1000); +} + +void k_sleep(s32_t duration) +{ + vTaskDelay(duration / portTICK_PERIOD_MS); + return; +} + +void bt_mesh_k_init(void) +{ + osi_mutex_new(&bm_alarm_lock); + osi_mutex_new(&bm_irq_lock); + bm_alarm_hash_map = hash_map_new(BLE_MESH_GENERAL_ALARM_HASH_MAP_SIZE, + hash_function_pointer, NULL, + (data_free_fn)osi_alarm_free, NULL); + assert(bm_alarm_hash_map != NULL); +} + +void k_delayed_work_init(struct k_delayed_work *work, k_work_handler_t handler) +{ + osi_alarm_t *alarm = NULL; + + assert(work != NULL && bm_alarm_hash_map != NULL); + + k_work_init(&work->work, handler); + + osi_mutex_lock(&bm_alarm_lock, OSI_MUTEX_MAX_TIMEOUT); + if (!hash_map_has_key(bm_alarm_hash_map, (void *)work)) { + alarm = osi_alarm_new("bt_mesh", bt_mesh_alarm_cb, (void *)work, 0); + if (alarm == NULL) { + BT_ERR("%s, Unable to create alarm", __func__); + return; + } + if (!hash_map_set(bm_alarm_hash_map, work, (void *)alarm)) { + BT_ERR("%s Unable to add the timer to hash map.", __func__); + } + } + osi_mutex_unlock(&bm_alarm_lock); + + alarm = hash_map_get(bm_alarm_hash_map, work); + if (alarm == NULL) { + BT_WARN("%s, Unable to find expected alarm in hash map", __func__); + return; + } + + // Just init the work timer only, don't start it. + osi_alarm_cancel(alarm); + return; +} + +int k_delayed_work_submit(struct k_delayed_work *work, + s32_t delay) +{ + assert(work != NULL && bm_alarm_hash_map != NULL); + + osi_alarm_t *alarm = hash_map_get(bm_alarm_hash_map, (void *)work); + if (alarm == NULL) { + BT_WARN("%s, Unable to find expected alarm in hash map", __func__); + return -EINVAL; + } + + // Cancel the alarm first, before start the alarm. + osi_alarm_cancel(alarm); + osi_alarm_set(alarm, delay); + return 0; +} + +int k_delayed_work_cancel(struct k_delayed_work *work) +{ + assert(work != NULL && bm_alarm_hash_map != NULL); + + osi_alarm_t *alarm = hash_map_get(bm_alarm_hash_map, (void *)work); + if (alarm == NULL) { + BT_WARN("%s, Unable to find expected alarm in hash map", __func__); + return -EINVAL; + } + + osi_alarm_cancel(alarm); + alarm->deadline_us = 0; + return 0; +} + +int k_delayed_work_free(struct k_delayed_work *work) +{ + assert(work != NULL && bm_alarm_hash_map != NULL); + + osi_alarm_t *alarm = hash_map_get(bm_alarm_hash_map, work); + if (alarm == NULL) { + BT_WARN("%s Unable to find expected alarm in hash map", __func__); + return -EINVAL; + } + + hash_map_erase(bm_alarm_hash_map, work); + return 0; +} + +s32_t k_delayed_work_remaining_get(struct k_delayed_work *work) +{ + assert(work != NULL && bm_alarm_hash_map != NULL); + + osi_alarm_t *alarm = hash_map_get(bm_alarm_hash_map, (void *)work); + if (alarm == NULL) { + BT_WARN("%s Unable to find expected alarm in hash map", __func__); + return 0; + } + + if (!alarm->deadline_us) { + return 0; + } + + s32_t remain_time = 0; + int64_t now = esp_timer_get_time(); + if ((alarm->deadline_us - now) < 0x1FFFFFFFFFF) { + remain_time = (alarm->deadline_us - now) / 1000; + } else { + return 0; + } + + return remain_time; +} diff --git a/components/bt/ble_mesh/mesh_core/mesh_main.c b/components/bt/ble_mesh/mesh_core/mesh_main.c new file mode 100644 index 0000000000..03760d957c --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/mesh_main.c @@ -0,0 +1,509 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG) + +#include "mesh_buf.h" +#include "mesh_trace.h" +#include "mesh_main.h" +#include "mesh_hci.h" + +#include "adv.h" +#include "prov.h" +#include "net.h" +#include "beacon.h" +#include "lpn.h" +#include "friend.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "proxy.h" +#include "settings.h" +#include "mesh.h" +#include "provisioner_prov.h" +#include "provisioner_proxy.h" +#include "provisioner_main.h" + +static volatile bool provisioner_en = false; + +#define ACTION_ENTER 0x01 +#define ACTION_SUSPEND 0x02 +#define ACTION_EXIT 0x03 + +#if CONFIG_BLE_MESH_NODE + +int bt_mesh_provision(const u8_t net_key[16], u16_t net_idx, + u8_t flags, u32_t iv_index, u16_t addr, + const u8_t dev_key[16]) +{ + bool pb_gatt_enabled; + int err; + + BT_INFO("Primary Element: 0x%04x", addr); + BT_DBG("net_idx 0x%04x flags 0x%02x iv_index 0x%04x", + net_idx, flags, iv_index); + + if (bt_mesh_atomic_test_and_set_bit(bt_mesh.flags, BLE_MESH_VALID)) { + return -EALREADY; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) { + if (bt_mesh_proxy_prov_disable(false) == 0) { + pb_gatt_enabled = true; + } else { + pb_gatt_enabled = false; + } + } else { + pb_gatt_enabled = false; + } + + err = bt_mesh_net_create(net_idx, flags, net_key, iv_index); + if (err) { + bt_mesh_atomic_clear_bit(bt_mesh.flags, BLE_MESH_VALID); + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && pb_gatt_enabled) { + bt_mesh_proxy_prov_enable(); + } + + return err; + } + + bt_mesh.seq = 0U; + + bt_mesh_comp_provision(addr); + + memcpy(bt_mesh.dev_key, dev_key, 16); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + BT_DBG("Storing network information persistently"); + bt_mesh_store_net(); + bt_mesh_store_subnet(&bt_mesh.sub[0]); + bt_mesh_store_iv(false); + } + + /* Add this to avoid "already active status" for bt_mesh_scan_enable() */ + bt_mesh_scan_disable(); + + bt_mesh_net_start(); + + return 0; +} + +void bt_mesh_reset(void) +{ + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + BT_WARN("%s, Not provisioned", __func__); + return; + } + + bt_mesh.iv_index = 0U; + bt_mesh.seq = 0U; + + memset(bt_mesh.flags, 0, sizeof(bt_mesh.flags)); + + k_delayed_work_cancel(&bt_mesh.ivu_timer); + + bt_mesh_cfg_reset(); + + bt_mesh_rx_reset(); + bt_mesh_tx_reset(); + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_disable(true); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + bt_mesh_friend_clear_net_idx(BLE_MESH_KEY_ANY); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY)) { + bt_mesh_proxy_gatt_disable(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_net(); + } + + (void)memset(bt_mesh.dev_key, 0, sizeof(bt_mesh.dev_key)); + + bt_mesh_scan_disable(); + bt_mesh_beacon_disable(); + + bt_mesh_comp_unprovision(); + + if (IS_ENABLED(CONFIG_BLE_MESH_PROV)) { + bt_mesh_prov_reset(); + } +} + +bool bt_mesh_is_provisioned(void) +{ + return bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID); +} + +int bt_mesh_prov_enable(bt_mesh_prov_bearer_t bearers) +{ + if (bt_mesh_is_provisioned()) { + return -EALREADY; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_ADV) && + (bearers & BLE_MESH_PROV_ADV)) { + /* Make sure we're scanning for provisioning inviations */ + bt_mesh_scan_enable(); + /* Enable unprovisioned beacon sending */ + bt_mesh_beacon_enable(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + (bearers & BLE_MESH_PROV_GATT)) { + bt_mesh_proxy_prov_enable(); + bt_mesh_adv_update(); + } + + return 0; +} + +int bt_mesh_prov_disable(bt_mesh_prov_bearer_t bearers) +{ + if (bt_mesh_is_provisioned()) { + return -EALREADY; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_ADV) && + (bearers & BLE_MESH_PROV_ADV)) { + bt_mesh_beacon_disable(); + bt_mesh_scan_disable(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + (bearers & BLE_MESH_PROV_GATT)) { + bt_mesh_proxy_prov_disable(true); + } + + return 0; +} + +#endif /* CONFIG_BLE_MESH_NODE */ + +static void model_suspend(struct bt_mesh_model *mod, struct bt_mesh_elem *elem, + bool vnd, bool primary, void *user_data) +{ + if (mod->pub && mod->pub->update) { + mod->pub->count = 0U; + k_delayed_work_cancel(&mod->pub->timer); + } +} + +int bt_mesh_suspend(void) +{ + int err; + + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + return -EINVAL; + } + + if (bt_mesh_atomic_test_and_set_bit(bt_mesh.flags, BLE_MESH_SUSPENDED)) { + return -EALREADY; + } + + err = bt_mesh_scan_disable(); + if (err) { + bt_mesh_atomic_clear_bit(bt_mesh.flags, BLE_MESH_SUSPENDED); + BT_WARN("%s, Disabling scanning failed (err %d)", __func__, err); + return err; + } + + bt_mesh_hb_pub_disable(); + + if (bt_mesh_beacon_get() == BLE_MESH_BEACON_ENABLED) { + bt_mesh_beacon_disable(); + } + + bt_mesh_model_foreach(model_suspend, NULL); + + return 0; +} + +static void model_resume(struct bt_mesh_model *mod, struct bt_mesh_elem *elem, + bool vnd, bool primary, void *user_data) +{ + if (mod->pub && mod->pub->update) { + s32_t period_ms = bt_mesh_model_pub_period_get(mod); + + if (period_ms) { + k_delayed_work_submit(&mod->pub->timer, period_ms); + } + } +} + +int bt_mesh_resume(void) +{ + int err; + + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + return -EINVAL; + } + + if (!bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_SUSPENDED)) { + return -EALREADY; + } + + err = bt_mesh_scan_enable(); + if (err) { + BT_WARN("%s, Re-enabling scanning failed (err %d)", __func__, err); + bt_mesh_atomic_set_bit(bt_mesh.flags, BLE_MESH_SUSPENDED); + return err; + } + + if (bt_mesh_beacon_get() == BLE_MESH_BEACON_ENABLED) { + bt_mesh_beacon_enable(); + } + + bt_mesh_model_foreach(model_resume, NULL); + + return err; +} + +int bt_mesh_init(const struct bt_mesh_prov *prov, + const struct bt_mesh_comp *comp) +{ + int err; + + bt_mesh_k_init(); + + bt_mesh_hci_init(); + + bt_mesh_adapt_init(); + + err = bt_mesh_comp_register(comp); + if (err) { + return err; + } + + bt_mesh_gatt_init(); + +#if CONFIG_BLE_MESH_NODE + extern struct bt_mesh_gatt_service proxy_svc; + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY)) { + bt_mesh_gatts_service_register(&proxy_svc); + } + + extern struct bt_mesh_gatt_service prov_svc; + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) { + bt_mesh_gatts_service_register(&prov_svc); + } +#endif + + if (IS_ENABLED(CONFIG_BLE_MESH_PROV)) { +#if CONFIG_BLE_MESH_NODE + err = bt_mesh_prov_init(prov); + if (err) { + return err; + } +#endif +#if CONFIG_BLE_MESH_PROVISIONER + err = provisioner_prov_init(prov); + if (err) { + return err; + } +#endif + } + + bt_mesh_net_init(); + bt_mesh_trans_init(); + +#if CONFIG_BLE_MESH_NODE + /* Changed by Espressif, add random delay (0 ~ 3s) */ +#if defined(CONFIG_BLE_MESH_FAST_PROV) + u32_t delay = 0; + bt_mesh_rand(&delay, sizeof(u32_t)); + vTaskDelay((delay % 3000) / portTICK_PERIOD_MS); +#endif + bt_mesh_beacon_init(); +#endif + + bt_mesh_adv_init(); + + if (IS_ENABLED(CONFIG_BLE_MESH_PROXY)) { +#if CONFIG_BLE_MESH_NODE + bt_mesh_proxy_init(); +#endif +#if CONFIG_BLE_MESH_PROVISIONER + provisioner_proxy_init(); +#endif + } + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + /* If node & provisioner are both enabled and the + * device starts as a node, it must finish provisioning */ + err = provisioner_upper_init(); + if (err) { + return err; + } +#endif + +#if defined(CONFIG_BLE_MESH_SETTINGS) + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_settings_init(); + } +#endif + + return 0; +} + +bool bt_mesh_is_provisioner_en(void) +{ + return provisioner_en; +} + +/* The following APIs are for fast provisioning */ + +#if CONFIG_BLE_MESH_PROVISIONER +int bt_mesh_provisioner_enable(bt_mesh_prov_bearer_t bearers) +{ + int err; + + if (bt_mesh_is_provisioner_en()) { + BT_WARN("%s, Provisioner is already enabled", __func__); + return -EALREADY; + } + + err = provisioner_upper_init(); + if (err) { + BT_ERR("%s, provisioner_upper_init fail", __func__); + return err; + } + +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) + if (IS_ENABLED(CONFIG_BLE_MESH_PB_ADV) && + (bearers & BLE_MESH_PROV_ADV)) { + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_ADD, + BLE_MESH_EXCEP_INFO_MESH_BEACON, NULL); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + (bearers & BLE_MESH_PROV_GATT)) { + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_ADD, + BLE_MESH_EXCEP_INFO_MESH_PROV_ADV, NULL); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROXY)) { + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_ADD, + BLE_MESH_EXCEP_INFO_MESH_PROXY_ADV, NULL); + } +#endif + + if ((IS_ENABLED(CONFIG_BLE_MESH_PB_ADV) && + (bearers & BLE_MESH_PROV_ADV)) || + (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + (bearers & BLE_MESH_PROV_GATT))) { + bt_mesh_scan_enable(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + (bearers & BLE_MESH_PROV_GATT)) { + provisioner_pb_gatt_enable(); + } + + provisioner_en = true; + + return 0; +} + +int bt_mesh_provisioner_disable(bt_mesh_prov_bearer_t bearers) +{ + if (!bt_mesh_is_provisioner_en()) { + BT_WARN("%s, Provisioner is already disabled", __func__); + return -EALREADY; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + (bearers & BLE_MESH_PROV_GATT)) { + provisioner_pb_gatt_disable(); + } + + if ((IS_ENABLED(CONFIG_BLE_MESH_PB_ADV) && + (bearers & BLE_MESH_PROV_ADV)) && + (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + (bearers & BLE_MESH_PROV_GATT))) { + bt_mesh_scan_disable(); + } + + provisioner_en = false; + + return 0; +} +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +/* The following API is for fast provisioning */ + +#if CONFIG_BLE_MESH_FAST_PROV +u8_t bt_mesh_set_fast_prov_action(u8_t action) +{ + if (!action || action > ACTION_EXIT) { + return 0x01; + } + + if ((!provisioner_en && (action == ACTION_SUSPEND || action == ACTION_EXIT)) || + (provisioner_en && (action == ACTION_ENTER))) { + BT_WARN("%s, Action is already done", __func__); + return 0x0; + } + + if (action == ACTION_ENTER) { +#if 0 + /* If the device is provisioned using PB-GATT and connected to + * the phone with proxy service, proxy_gatt shall not be disabled + * here. The node needs to send some status messages to the phone + * while it is connected. + */ + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY)) { + bt_mesh_proxy_gatt_disable(); + } +#endif + if (bt_mesh_beacon_get() == BLE_MESH_BEACON_ENABLED) { + bt_mesh_beacon_disable(); + } + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) { + provisioner_pb_gatt_enable(); + } + provisioner_set_fast_prov_flag(true); + provisioner_en = true; + } else { + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) { + provisioner_pb_gatt_disable(); + } + if (bt_mesh_beacon_get() == BLE_MESH_BEACON_ENABLED) { + bt_mesh_beacon_enable(); + } +#if 0 + /* Mesh Proxy GATT will be re-enabled on application layer */ + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY) && + bt_mesh_gatt_proxy_get() != BLE_MESH_GATT_PROXY_NOT_SUPPORTED) { + bt_mesh_proxy_gatt_enable(); + bt_mesh_adv_update(); + } +#endif + provisioner_set_fast_prov_flag(false); + provisioner_en = false; + if (action == ACTION_EXIT) { + provisioner_upper_reset_all_nodes(); + provisioner_prov_reset_all_nodes(); + } + } + + return 0x0; +} +#endif /* CONFIG_BLE_MESH_FAST_PROV */ diff --git a/components/bt/ble_mesh/mesh_core/mesh_util.c b/components/bt/ble_mesh/mesh_core/mesh_util.c new file mode 100644 index 0000000000..4650bb2603 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/mesh_util.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2016 Vinayak Kariappa Chettimada + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#include "mesh_util.h" +#include "mesh_kernel.h" +#include "mesh_aes_encrypt.h" + +#define MASK_TWENTY_SEVEN 0x1b + +const char *bt_hex(const void *buf, size_t len) +{ + static const char hex[] = "0123456789abcdef"; + static char hexbufs[4][129]; + static u8_t curbuf; + const u8_t *b = buf; + unsigned int mask; + char *str; + int i; + + mask = bt_mesh_irq_lock(); + str = hexbufs[curbuf++]; + curbuf %= ARRAY_SIZE(hexbufs); + bt_mesh_irq_unlock(mask); + + len = MIN(len, (sizeof(hexbufs[0]) - 1) / 2); + + for (i = 0; i < len; i++) { + str[i * 2] = hex[b[i] >> 4]; + str[i * 2 + 1] = hex[b[i] & 0xf]; + } + + str[i * 2] = '\0'; + + return str; +} + +void mem_rcopy(u8_t *dst, u8_t const *src, u16_t len) +{ + src += len; + while (len--) { + *dst++ = *--src; + } +} + +unsigned int _copy(uint8_t *to, unsigned int to_len, + const uint8_t *from, unsigned int from_len) +{ + if (from_len <= to_len) { + (void)memcpy(to, from, from_len); + return from_len; + } else { + return TC_CRYPTO_FAIL; + } +} + +void _set(void *to, uint8_t val, unsigned int len) +{ + (void)memset(to, val, len); +} + +/* + * Doubles the value of a byte for values up to 127. + */ +uint8_t _double_byte(uint8_t a) +{ + return ((a << 1) ^ ((a >> 7) * MASK_TWENTY_SEVEN)); +} + +int _compare(const uint8_t *a, const uint8_t *b, size_t size) +{ + const uint8_t *tempa = a; + const uint8_t *tempb = b; + uint8_t result = 0; + + for (unsigned int i = 0; i < size; i++) { + result |= tempa[i] ^ tempb[i]; + } + return result; +} diff --git a/components/bt/ble_mesh/mesh_core/net.c b/components/bt/ble_mesh/mesh_core/net.c new file mode 100644 index 0000000000..020ea02707 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/net.c @@ -0,0 +1,1517 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_NET) + +#include "mesh_util.h" +#include "mesh_buf.h" +#include "mesh_main.h" +#include "mesh_trace.h" +#include "mesh.h" + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "net.h" +#include "lpn.h" +#include "friend.h" +#include "proxy.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "beacon.h" +#include "settings.h" +#include "prov.h" +#include "provisioner_main.h" + +/* Minimum valid Mesh Network PDU length. The Network headers + * themselves take up 9 bytes. After that there is a minumum of 1 byte + * payload for both CTL=1 and CTL=0 PDUs (smallest OpCode is 1 byte). CTL=1 + * PDUs must use a 64-bit (8 byte) NetMIC, whereas CTL=0 PDUs have at least + * a 32-bit (4 byte) NetMIC and AppMIC giving again a total of 8 bytes. + */ +#define BLE_MESH_NET_MIN_PDU_LEN (BLE_MESH_NET_HDR_LEN + 1 + 8) + +/* Seq limit after IV Update is triggered */ +#define IV_UPDATE_SEQ_LIMIT 8000000 + +#define IVI(pdu) ((pdu)[0] >> 7) +#define NID(pdu) ((pdu)[0] & 0x7f) +#define CTL(pdu) ((pdu)[1] >> 7) +#define TTL(pdu) ((pdu)[1] & 0x7f) +#define SEQ(pdu) (((u32_t)(pdu)[2] << 16) | \ + ((u32_t)(pdu)[3] << 8) | (u32_t)(pdu)[4]); +#define SRC(pdu) (sys_get_be16(&(pdu)[5])) +#define DST(pdu) (sys_get_be16(&(pdu)[7])) + +/* Determine how many friendship credentials we need */ +#if defined(CONFIG_BLE_MESH_FRIEND) +#define FRIEND_CRED_COUNT CONFIG_BLE_MESH_FRIEND_LPN_COUNT +#elif defined(CONFIG_BLE_MESH_LOW_POWER) +#define FRIEND_CRED_COUNT CONFIG_BLE_MESH_SUBNET_COUNT +#else +#define FRIEND_CRED_COUNT 0 +#endif + +#if FRIEND_CRED_COUNT > 0 +static struct friend_cred friend_cred[FRIEND_CRED_COUNT]; +#endif + +static u64_t msg_cache[CONFIG_BLE_MESH_MSG_CACHE_SIZE]; +static u16_t msg_cache_next; + +/* Singleton network context (the implementation only supports one) */ +struct bt_mesh_net bt_mesh = { + .local_queue = SYS_SLIST_STATIC_INIT(&bt_mesh.local_queue), + .sub = { + [0 ... (CONFIG_BLE_MESH_SUBNET_COUNT - 1)] = { + .net_idx = BLE_MESH_KEY_UNUSED, + } + }, + .app_keys = { + [0 ... (CONFIG_BLE_MESH_APP_KEY_COUNT - 1)] = { + .net_idx = BLE_MESH_KEY_UNUSED, + } + }, +}; + +static u32_t dup_cache[4]; +static int dup_cache_next; + +static bool check_dup(struct net_buf_simple *data) +{ + const u8_t *tail = net_buf_simple_tail(data); + u32_t val; + int i; + + val = sys_get_be32(tail - 4) ^ sys_get_be32(tail - 8); + + for (i = 0; i < ARRAY_SIZE(dup_cache); i++) { + if (dup_cache[i] == val) { + return true; + } + } + + dup_cache[dup_cache_next++] = val; + dup_cache_next %= ARRAY_SIZE(dup_cache); + + return false; +} + +static u64_t msg_hash(struct bt_mesh_net_rx *rx, struct net_buf_simple *pdu) +{ + u32_t hash1, hash2; + + /* Three least significant bytes of IVI + first byte of SEQ */ + hash1 = (BLE_MESH_NET_IVI_RX(rx) << 8) | pdu->data[2]; + + /* Two last bytes of SEQ + SRC */ + memcpy(&hash2, &pdu->data[3], 4); + + return (u64_t)hash1 << 32 | (u64_t)hash2; +} + +static bool msg_cache_match(struct bt_mesh_net_rx *rx, + struct net_buf_simple *pdu) +{ + u64_t hash = msg_hash(rx, pdu); + u16_t i; + + for (i = 0U; i < ARRAY_SIZE(msg_cache); i++) { + if (msg_cache[i] == hash) { + return true; + } + } + + /* Add to the cache */ + msg_cache[msg_cache_next++] = hash; + msg_cache_next %= ARRAY_SIZE(msg_cache); + + return false; +} + +struct bt_mesh_subnet *bt_mesh_subnet_get(u16_t net_idx) +{ + int i; + + if (net_idx == BLE_MESH_KEY_ANY) { + return &bt_mesh.sub[0]; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + if (bt_mesh.sub[i].net_idx == net_idx) { + return &bt_mesh.sub[i]; + } + } + + return NULL; +} + +int bt_mesh_net_keys_create(struct bt_mesh_subnet_keys *keys, + const u8_t key[16]) +{ + u8_t p[] = { 0 }; + u8_t nid; + int err; + + err = bt_mesh_k2(key, p, sizeof(p), &nid, keys->enc, keys->privacy); + if (err) { + BT_ERR("%s, Unable to generate NID, EncKey & PrivacyKey", __func__); + return err; + } + + memcpy(keys->net, key, 16); + + keys->nid = nid; + + BT_DBG("NID 0x%02x EncKey %s", keys->nid, bt_hex(keys->enc, 16)); + BT_DBG("PrivacyKey %s", bt_hex(keys->privacy, 16)); + + err = bt_mesh_k3(key, keys->net_id); + if (err) { + BT_ERR("%s, Unable to generate Net ID", __func__); + return err; + } + + BT_DBG("NetID %s", bt_hex(keys->net_id, 8)); + +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + err = bt_mesh_identity_key(key, keys->identity); + if (err) { + BT_ERR("%s, Unable to generate IdentityKey", __func__); + return err; + } + + BT_DBG("IdentityKey %s", bt_hex(keys->identity, 16)); +#endif /* GATT_PROXY */ + + err = bt_mesh_beacon_key(key, keys->beacon); + if (err) { + BT_ERR("%s, Unable to generate beacon key", __func__); + return err; + } + + BT_DBG("BeaconKey %s", bt_hex(keys->beacon, 16)); + + return 0; +} + +#if (defined(CONFIG_BLE_MESH_LOW_POWER) || \ + defined(CONFIG_BLE_MESH_FRIEND)) +int friend_cred_set(struct friend_cred *cred, u8_t idx, const u8_t net_key[16]) +{ + u16_t lpn_addr, frnd_addr; + int err; + u8_t p[9]; + +#if defined(CONFIG_BLE_MESH_LOW_POWER) + if (cred->addr == bt_mesh.lpn.frnd) { + lpn_addr = bt_mesh_primary_addr(); + frnd_addr = cred->addr; + } else { + lpn_addr = cred->addr; + frnd_addr = bt_mesh_primary_addr(); + } +#else + lpn_addr = cred->addr; + frnd_addr = bt_mesh_primary_addr(); +#endif + + BT_DBG("LPNAddress 0x%04x FriendAddress 0x%04x", lpn_addr, frnd_addr); + BT_DBG("LPNCounter 0x%04x FriendCounter 0x%04x", cred->lpn_counter, + cred->frnd_counter); + + p[0] = 0x01; + sys_put_be16(lpn_addr, p + 1); + sys_put_be16(frnd_addr, p + 3); + sys_put_be16(cred->lpn_counter, p + 5); + sys_put_be16(cred->frnd_counter, p + 7); + + err = bt_mesh_k2(net_key, p, sizeof(p), &cred->cred[idx].nid, + cred->cred[idx].enc, cred->cred[idx].privacy); + if (err) { + BT_ERR("%s, Unable to generate NID, EncKey & PrivacyKey", __func__); + return err; + } + + BT_DBG("Friend NID 0x%02x EncKey %s", cred->cred[idx].nid, + bt_hex(cred->cred[idx].enc, 16)); + BT_DBG("Friend PrivacyKey %s", bt_hex(cred->cred[idx].privacy, 16)); + + return 0; +} + +void friend_cred_refresh(u16_t net_idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(friend_cred); i++) { + struct friend_cred *cred = &friend_cred[i]; + + if (cred->addr != BLE_MESH_ADDR_UNASSIGNED && + cred->net_idx == net_idx) { + memcpy(&cred->cred[0], &cred->cred[1], + sizeof(cred->cred[0])); + } + } +} + +int friend_cred_update(struct bt_mesh_subnet *sub) +{ + int err, i; + + BT_DBG("net_idx 0x%04x", sub->net_idx); + + for (i = 0; i < ARRAY_SIZE(friend_cred); i++) { + struct friend_cred *cred = &friend_cred[i]; + + if (cred->addr == BLE_MESH_ADDR_UNASSIGNED || + cred->net_idx != sub->net_idx) { + continue; + } + + err = friend_cred_set(cred, 1, sub->keys[1].net); + if (err) { + return err; + } + } + + return 0; +} + +struct friend_cred *friend_cred_create(struct bt_mesh_subnet *sub, u16_t addr, + u16_t lpn_counter, u16_t frnd_counter) +{ + struct friend_cred *cred; + int i, err; + + BT_DBG("net_idx 0x%04x addr 0x%04x", sub->net_idx, addr); + + for (cred = NULL, i = 0; i < ARRAY_SIZE(friend_cred); i++) { + if ((friend_cred[i].addr == BLE_MESH_ADDR_UNASSIGNED) || + (friend_cred[i].addr == addr && + friend_cred[i].net_idx == sub->net_idx)) { + cred = &friend_cred[i]; + break; + } + } + + if (!cred) { + BT_WARN("No free friend credential slots"); + return NULL; + } + + cred->net_idx = sub->net_idx; + cred->addr = addr; + cred->lpn_counter = lpn_counter; + cred->frnd_counter = frnd_counter; + + err = friend_cred_set(cred, 0, sub->keys[0].net); + if (err) { + friend_cred_clear(cred); + return NULL; + } + + if (sub->kr_flag) { + err = friend_cred_set(cred, 1, sub->keys[1].net); + if (err) { + friend_cred_clear(cred); + return NULL; + } + } + + return cred; +} + +void friend_cred_clear(struct friend_cred *cred) +{ + cred->net_idx = BLE_MESH_KEY_UNUSED; + cred->addr = BLE_MESH_ADDR_UNASSIGNED; + cred->lpn_counter = 0U; + cred->frnd_counter = 0U; + (void)memset(cred->cred, 0, sizeof(cred->cred)); +} + +int friend_cred_del(u16_t net_idx, u16_t addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(friend_cred); i++) { + struct friend_cred *cred = &friend_cred[i]; + + if (cred->addr == addr && cred->net_idx == net_idx) { + friend_cred_clear(cred); + return 0; + } + } + + return -ENOENT; +} + +int friend_cred_get(struct bt_mesh_subnet *sub, u16_t addr, u8_t *nid, + const u8_t **enc, const u8_t **priv) +{ + int i; + + BT_DBG("net_idx 0x%04x addr 0x%04x", sub->net_idx, addr); + + for (i = 0; i < ARRAY_SIZE(friend_cred); i++) { + struct friend_cred *cred = &friend_cred[i]; + + if (cred->net_idx != sub->net_idx) { + continue; + } + + if (addr != BLE_MESH_ADDR_UNASSIGNED && cred->addr != addr) { + continue; + } + + if (nid) { + *nid = cred->cred[sub->kr_flag].nid; + } + + if (enc) { + *enc = cred->cred[sub->kr_flag].enc; + } + + if (priv) { + *priv = cred->cred[sub->kr_flag].privacy; + } + + return 0; + } + + return -ENOENT; +} +#else +int friend_cred_get(struct bt_mesh_subnet *sub, u16_t addr, u8_t *nid, + const u8_t **enc, const u8_t **priv) +{ + return -ENOENT; +} +#endif /* FRIEND || LOW_POWER */ + +u8_t bt_mesh_net_flags(struct bt_mesh_subnet *sub) +{ + u8_t flags = 0x00; + + if (sub && sub->kr_flag) { + flags |= BLE_MESH_NET_FLAG_KR; + } + + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS)) { + flags |= BLE_MESH_NET_FLAG_IVU; + } + + return flags; +} + +int bt_mesh_net_beacon_update(struct bt_mesh_subnet *sub) +{ + u8_t flags = bt_mesh_net_flags(sub); + struct bt_mesh_subnet_keys *keys; + + if (sub->kr_flag) { + BT_DBG("NetIndex %u Using new key", sub->net_idx); + keys = &sub->keys[1]; + } else { + BT_DBG("NetIndex %u Using current key", sub->net_idx); + keys = &sub->keys[0]; + } + + BT_DBG("flags 0x%02x, IVI 0x%08x", flags, bt_mesh.iv_index); + + return bt_mesh_beacon_auth(keys->beacon, flags, keys->net_id, + bt_mesh.iv_index, sub->auth); +} + +int bt_mesh_net_create(u16_t idx, u8_t flags, const u8_t key[16], + u32_t iv_index) +{ + struct bt_mesh_subnet *sub; + int err; + + BT_DBG("idx %u flags 0x%02x iv_index %u", idx, flags, iv_index); + + BT_DBG("NetKey %s", bt_hex(key, 16)); + + (void)memset(msg_cache, 0, sizeof(msg_cache)); + msg_cache_next = 0U; + + sub = &bt_mesh.sub[0]; + + sub->kr_flag = BLE_MESH_KEY_REFRESH(flags); + if (sub->kr_flag) { + err = bt_mesh_net_keys_create(&sub->keys[1], key); + if (err) { + return -EIO; + } + + sub->kr_phase = BLE_MESH_KR_PHASE_2; + } else { + err = bt_mesh_net_keys_create(&sub->keys[0], key); + if (err) { + return -EIO; + } + } + + sub->net_idx = idx; + + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY)) { + sub->node_id = BLE_MESH_NODE_IDENTITY_STOPPED; + } else { + sub->node_id = BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED; + } + + bt_mesh.iv_index = iv_index; + bt_mesh_atomic_set_bit_to(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS, + BLE_MESH_IV_UPDATE(flags)); + + /* Set minimum required hours, since the 96-hour minimum requirement + * doesn't apply straight after provisioning (since we can't know how + * long has actually passed since the network changed its state). + */ + bt_mesh.ivu_duration = BLE_MESH_IVU_MIN_HOURS; + + /* Make sure we have valid beacon data to be sent */ + bt_mesh_net_beacon_update(sub); + + return 0; +} + +void bt_mesh_net_revoke_keys(struct bt_mesh_subnet *sub) +{ + int i; + + BT_DBG("idx 0x%04x", sub->net_idx); + + memcpy(&sub->keys[0], &sub->keys[1], sizeof(sub->keys[0])); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) { + struct bt_mesh_app_key *key = &bt_mesh.app_keys[i]; + + if (key->net_idx != sub->net_idx || !key->updated) { + continue; + } + + memcpy(&key->keys[0], &key->keys[1], sizeof(key->keys[0])); + key->updated = false; + } +} + +bool bt_mesh_kr_update(struct bt_mesh_subnet *sub, u8_t new_kr, bool new_key) +{ + if (new_kr != sub->kr_flag && sub->kr_phase == BLE_MESH_KR_NORMAL) { + BT_WARN("KR change in normal operation. Are we blacklisted?"); + return false; + } + + sub->kr_flag = new_kr; + + if (sub->kr_flag) { + if (sub->kr_phase == BLE_MESH_KR_PHASE_1) { + BT_DBG("Phase 1 -> Phase 2"); + sub->kr_phase = BLE_MESH_KR_PHASE_2; + return true; + } + } else { + switch (sub->kr_phase) { + case BLE_MESH_KR_PHASE_1: + if (!new_key) { + /* Ignore */ + break; + } + /* Upon receiving a Secure Network beacon with the KR flag set + * to 0 using the new NetKey in Phase 1, the node shall + * immediately transition to Phase 3, which effectively skips + * Phase 2. + * + * Intentional fall-through. + */ + case BLE_MESH_KR_PHASE_2: + BT_DBG("KR Phase 0x%02x -> Normal", sub->kr_phase); + bt_mesh_net_revoke_keys(sub); + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER) || + IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + friend_cred_refresh(sub->net_idx); + } + sub->kr_phase = BLE_MESH_KR_NORMAL; + return true; + } + } + + return false; +} + +void bt_mesh_rpl_reset(void) +{ + int i; + + /* Discard "old old" IV Index entries from RPL and flag + * any other ones (which are valid) as old. + */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) { + struct bt_mesh_rpl *rpl = &bt_mesh.rpl[i]; + + if (rpl->src) { + if (rpl->old_iv) { + (void)memset(rpl, 0, sizeof(*rpl)); + } else { + rpl->old_iv = true; + } + } + } +} + +#if defined(CONFIG_BLE_MESH_IV_UPDATE_TEST) +void bt_mesh_iv_update_test(bool enable) +{ + bt_mesh_atomic_set_bit_to(bt_mesh.flags, BLE_MESH_IVU_TEST, enable); + /* Reset the duration variable - needed for some PTS tests */ + bt_mesh.ivu_duration = 0U; +} + +bool bt_mesh_iv_update(void) +{ + if (!bt_mesh_is_provisioned()) { + BT_ERR("%s, Not yet provisioned", __func__); + return false; + } + + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS)) { + bt_mesh_net_iv_update(bt_mesh.iv_index, false); + } else { + bt_mesh_net_iv_update(bt_mesh.iv_index + 1, true); + } + + bt_mesh_net_sec_update(NULL); + + return bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS); +} +#endif /* CONFIG_BLE_MESH_IV_UPDATE_TEST */ + +/* Used for sending immediate beacons to Friend queues and GATT clients */ +void bt_mesh_net_sec_update(struct bt_mesh_subnet *sub) +{ + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + bt_mesh_friend_sec_update(sub ? sub->net_idx : BLE_MESH_KEY_ANY); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY) && + bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_ENABLED) { +#if CONFIG_BLE_MESH_NODE + bt_mesh_proxy_beacon_send(sub); +#endif + } +} + +bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update) +{ + int i; + + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS)) { + /* We're currently in IV Update mode */ + + if (iv_index != bt_mesh.iv_index) { + BT_WARN("IV Index mismatch: 0x%08x != 0x%08x", + iv_index, bt_mesh.iv_index); + return false; + } + + if (iv_update) { + /* Nothing to do */ + BT_DBG("Already in IV Update in Progress state"); + return false; + } + } else { + /* We're currently in Normal mode */ + + if (iv_index == bt_mesh.iv_index) { + BT_DBG("Same IV Index in normal mode"); + return false; + } + + if (iv_index < bt_mesh.iv_index || + iv_index > bt_mesh.iv_index + 42) { + BT_ERR("IV Index out of sync: 0x%08x != 0x%08x", + iv_index, bt_mesh.iv_index); + return false; + } + + if (iv_index > bt_mesh.iv_index + 1) { + BT_WARN("Performing IV Index Recovery"); + (void)memset(bt_mesh.rpl, 0, sizeof(bt_mesh.rpl)); + bt_mesh.iv_index = iv_index; + bt_mesh.seq = 0U; + goto do_update; + } + + if (iv_index == bt_mesh.iv_index + 1 && !iv_update) { + BT_WARN("Ignoring new index in normal mode"); + return false; + } + + if (!iv_update) { + /* Nothing to do */ + BT_DBG("Already in Normal state"); + return false; + } + } + + if (!(IS_ENABLED(CONFIG_BLE_MESH_IV_UPDATE_TEST) && + bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_TEST))) { + if (bt_mesh.ivu_duration < BLE_MESH_IVU_MIN_HOURS) { + BT_WARN("IV Update before minimum duration"); + return false; + } + } + + /* Defer change to Normal Operation if there are pending acks */ + if (!iv_update && bt_mesh_tx_in_progress()) { + BT_WARN("IV Update deferred because of pending transfer"); + bt_mesh_atomic_set_bit(bt_mesh.flags, BLE_MESH_IVU_PENDING); + return false; + } + +do_update: + bt_mesh_atomic_set_bit_to(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS, iv_update); + bt_mesh.ivu_duration = 0U; + + if (iv_update) { + bt_mesh.iv_index = iv_index; + BT_DBG("IV Update state entered. New index 0x%08x", + bt_mesh.iv_index); + + bt_mesh_rpl_reset(); + } else { + BT_DBG("Normal mode entered"); + bt_mesh.seq = 0U; + } + + k_delayed_work_submit(&bt_mesh.ivu_timer, BLE_MESH_IVU_TIMEOUT); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + if (bt_mesh.sub[i].net_idx != BLE_MESH_KEY_UNUSED) { + bt_mesh_net_beacon_update(&bt_mesh.sub[i]); + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_iv(false); + } + + return true; +} + +u32_t bt_mesh_next_seq(void) +{ + u32_t seq = bt_mesh.seq++; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_seq(); + } + + return seq; +} + +int bt_mesh_net_resend(struct bt_mesh_subnet *sub, struct net_buf *buf, + bool new_key, const struct bt_mesh_send_cb *cb, + void *cb_data) +{ + const u8_t *enc, *priv; + u32_t seq; + int err; + + BT_DBG("net_idx 0x%04x new_key %u len %u", sub->net_idx, new_key, + buf->len); + + enc = sub->keys[new_key].enc; + priv = sub->keys[new_key].privacy; + + err = bt_mesh_net_obfuscate(buf->data, BLE_MESH_NET_IVI_TX, priv); + if (err) { + BT_ERR("%s, Deobfuscate failed (err %d)", __func__, err); + return err; + } + + err = bt_mesh_net_decrypt(enc, &buf->b, BLE_MESH_NET_IVI_TX, false); + if (err) { + BT_ERR("%s, Decrypt failed (err %d)", __func__, err); + return err; + } + + /* Update with a new sequence number */ + seq = bt_mesh_next_seq(); + buf->data[2] = seq >> 16; + buf->data[3] = seq >> 8; + buf->data[4] = seq; + + err = bt_mesh_net_encrypt(enc, &buf->b, BLE_MESH_NET_IVI_TX, false); + if (err) { + BT_ERR("%s, Encrypt failed (err %d)", __func__, err); + return err; + } + + err = bt_mesh_net_obfuscate(buf->data, BLE_MESH_NET_IVI_TX, priv); + if (err) { + BT_ERR("%s, Obfuscate failed (err %d)", __func__, err); + return err; + } + + bt_mesh_adv_send(buf, cb, cb_data); + + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS) && + bt_mesh.seq > IV_UPDATE_SEQ_LIMIT) { +#if CONFIG_BLE_MESH_NODE + bt_mesh_beacon_ivu_initiator(true); +#endif + bt_mesh_net_iv_update(bt_mesh.iv_index + 1, true); + bt_mesh_net_sec_update(NULL); + } + + return 0; +} + +static void bt_mesh_net_local(struct k_work *work) +{ + struct net_buf *buf; + + while ((buf = net_buf_slist_get(&bt_mesh.local_queue))) { + BT_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len)); + bt_mesh_net_recv(&buf->b, 0, BLE_MESH_NET_IF_LOCAL); + net_buf_unref(buf); + } +} + +int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct net_buf_simple *buf, + bool proxy) +{ + const bool ctl = (tx->ctx->app_idx == BLE_MESH_KEY_UNUSED); + u32_t seq_val; + u8_t nid; + const u8_t *enc, *priv; + u8_t *seq; + int err; + + if (ctl && net_buf_simple_tailroom(buf) < 8) { + BT_ERR("%s, Insufficient MIC space for CTL PDU", __func__); + return -EINVAL; + } else if (net_buf_simple_tailroom(buf) < 4) { + BT_ERR("%s, Insufficient MIC space for PDU", __func__); + return -EINVAL; + } + + BT_DBG("src 0x%04x dst 0x%04x ctl %u seq 0x%06x", + tx->src, tx->ctx->addr, ctl, bt_mesh.seq); + + net_buf_simple_push_be16(buf, tx->ctx->addr); + net_buf_simple_push_be16(buf, tx->src); + + seq = net_buf_simple_push(buf, 3); + seq_val = bt_mesh_next_seq(); + seq[0] = seq_val >> 16; + seq[1] = seq_val >> 8; + seq[2] = seq_val; + + if (ctl) { + net_buf_simple_push_u8(buf, tx->ctx->send_ttl | 0x80); + } else { + net_buf_simple_push_u8(buf, tx->ctx->send_ttl); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER) && tx->friend_cred) { + if (friend_cred_get(tx->sub, BLE_MESH_ADDR_UNASSIGNED, + &nid, &enc, &priv)) { + BT_WARN("Falling back to master credentials"); + + tx->friend_cred = 0U; + + nid = tx->sub->keys[tx->sub->kr_flag].nid; + enc = tx->sub->keys[tx->sub->kr_flag].enc; + priv = tx->sub->keys[tx->sub->kr_flag].privacy; + } + } else { + tx->friend_cred = 0U; + nid = tx->sub->keys[tx->sub->kr_flag].nid; + enc = tx->sub->keys[tx->sub->kr_flag].enc; + priv = tx->sub->keys[tx->sub->kr_flag].privacy; + } + + net_buf_simple_push_u8(buf, (nid | (BLE_MESH_NET_IVI_TX & 1) << 7)); + + err = bt_mesh_net_encrypt(enc, buf, BLE_MESH_NET_IVI_TX, proxy); + if (err) { + return err; + } + + return bt_mesh_net_obfuscate(buf->data, BLE_MESH_NET_IVI_TX, priv); +} + +int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct net_buf *buf, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + int err; + + BT_DBG("src 0x%04x dst 0x%04x len %u headroom %u tailroom %u", + tx->src, tx->ctx->addr, buf->len, net_buf_headroom(buf), + net_buf_tailroom(buf)); + BT_DBG("Payload len %u: %s", buf->len, bt_hex(buf->data, buf->len)); + BT_DBG("Seq 0x%06x", bt_mesh.seq); + + if (tx->ctx->send_ttl == BLE_MESH_TTL_DEFAULT) { + tx->ctx->send_ttl = bt_mesh_default_ttl_get(); + } + + err = bt_mesh_net_encode(tx, &buf->b, false); + if (err) { + goto done; + } + + /* Deliver to GATT Proxy Clients if necessary. Mesh spec 3.4.5.2: + * "The output filter of the interface connected to advertising or + * GATT bearers shall drop all messages with TTL value set to 1." + */ +#if CONFIG_BLE_MESH_NODE + if (bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY) && + tx->ctx->send_ttl != 1U) { + if (bt_mesh_proxy_relay(&buf->b, tx->ctx->addr) && + BLE_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) { + /* Notify completion if this only went + * through the Mesh Proxy. + */ + if (cb) { + if (cb->start) { + cb->start(0, 0, cb_data); + } + + if (cb->end) { + cb->end(0, cb_data); + } + } + + err = 0; + goto done; + } + } + } +#endif + + /* Deliver to local network interface if necessary */ + if (bt_mesh_fixed_group_match(tx->ctx->addr) || + bt_mesh_elem_find(tx->ctx->addr)) { + if (cb && cb->start) { + cb->start(0, 0, cb_data); + } + net_buf_slist_put(&bt_mesh.local_queue, net_buf_ref(buf)); + if (cb && cb->end) { + cb->end(0, cb_data); + } + k_work_submit(&bt_mesh.local_work); + } else if (tx->ctx->send_ttl != 1U) { + /* Deliver to the advertising network interface. Mesh spec + * 3.4.5.2: "The output filter of the interface connected to + * advertising or GATT bearers shall drop all messages with + * TTL value set to 1." + */ + bt_mesh_adv_send(buf, cb, cb_data); + } + +done: + net_buf_unref(buf); + return err; +} + +static bool auth_match(struct bt_mesh_subnet_keys *keys, + const u8_t net_id[8], u8_t flags, + u32_t iv_index, const u8_t auth[8]) +{ + u8_t net_auth[8]; + + if (memcmp(net_id, keys->net_id, 8)) { + return false; + } + + bt_mesh_beacon_auth(keys->beacon, flags, keys->net_id, iv_index, + net_auth); + + if (memcmp(auth, net_auth, 8)) { + BT_WARN("Authentication Value %s != %s", + bt_hex(auth, 8), bt_hex(net_auth, 8)); + return false; + } + + return true; +} + +struct bt_mesh_subnet *bt_mesh_subnet_find(const u8_t net_id[8], u8_t flags, + u32_t iv_index, const u8_t auth[8], + bool *new_key) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + if (auth_match(&sub->keys[0], net_id, flags, iv_index, auth)) { + *new_key = false; + return sub; + } + + if (sub->kr_phase == BLE_MESH_KR_NORMAL) { + continue; + } + + if (auth_match(&sub->keys[1], net_id, flags, iv_index, auth)) { + *new_key = true; + return sub; + } + } + + return NULL; +} + +static int net_decrypt(struct bt_mesh_subnet *sub, const u8_t *enc, + const u8_t *priv, const u8_t *data, + size_t data_len, struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + BT_DBG("NID 0x%02x net_idx 0x%04x", NID(data), sub->net_idx); + BT_DBG("IVI %u net->iv_index 0x%08x", IVI(data), bt_mesh.iv_index); + + rx->old_iv = (IVI(data) != (bt_mesh.iv_index & 0x01)); + + net_buf_simple_reset(buf); + memcpy(net_buf_simple_add(buf, data_len), data, data_len); + + if (bt_mesh_net_obfuscate(buf->data, BLE_MESH_NET_IVI_RX(rx), priv)) { + return -ENOENT; + } + + /* TODO: For provisioner, when a device is re-provisioned and start to + * send the same message(e.g. cfg_appkey_add), the status message is easy + * to be filtered here. So when a device is re-provisioned, the related + * msg_cache should be cleared. Will do it later. + */ + if (rx->net_if == BLE_MESH_NET_IF_ADV && msg_cache_match(rx, buf)) { + BT_WARN("Duplicate found in Network Message Cache"); + return -EALREADY; + } + + rx->ctx.addr = SRC(buf->data); + if (!BLE_MESH_ADDR_IS_UNICAST(rx->ctx.addr)) { + BT_WARN("Ignoring non-unicast src addr 0x%04x", rx->ctx.addr); + return -EINVAL; + } + + BT_DBG("src 0x%04x", rx->ctx.addr); + +#if CONFIG_BLE_MESH_NODE + if (bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_PROXY) && + rx->net_if == BLE_MESH_NET_IF_PROXY_CFG) { + return bt_mesh_net_decrypt(enc, buf, BLE_MESH_NET_IVI_RX(rx), + true); + } + } +#endif + + return bt_mesh_net_decrypt(enc, buf, BLE_MESH_NET_IVI_RX(rx), false); +} + +#if (defined(CONFIG_BLE_MESH_LOW_POWER) || \ + defined(CONFIG_BLE_MESH_FRIEND)) +static int friend_decrypt(struct bt_mesh_subnet *sub, const u8_t *data, + size_t data_len, struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + int i; + + BT_DBG("NID 0x%02x net_idx 0x%04x", NID(data), sub->net_idx); + + for (i = 0; i < ARRAY_SIZE(friend_cred); i++) { + struct friend_cred *cred = &friend_cred[i]; + + if (cred->net_idx != sub->net_idx) { + continue; + } + + if (NID(data) == cred->cred[0].nid && + !net_decrypt(sub, cred->cred[0].enc, cred->cred[0].privacy, + data, data_len, rx, buf)) { + return 0; + } + + if (sub->kr_phase == BLE_MESH_KR_NORMAL) { + continue; + } + + if (NID(data) == cred->cred[1].nid && + !net_decrypt(sub, cred->cred[1].enc, cred->cred[1].privacy, + data, data_len, rx, buf)) { + rx->new_key = 1U; + return 0; + } + } + + return -ENOENT; +} +#endif + +static bool net_find_and_decrypt(const u8_t *data, size_t data_len, + struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_subnet *sub = NULL; + u32_t array_size = 0; + int i; + + BT_DBG("%s", __func__); + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (!bt_mesh_is_provisioner_en()) { + array_size = ARRAY_SIZE(bt_mesh.sub); + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + array_size = ARRAY_SIZE(bt_mesh.p_sub); + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + array_size = ARRAY_SIZE(bt_mesh.sub); + if (bt_mesh_is_provisioner_en()) { + array_size += ARRAY_SIZE(bt_mesh.p_sub); + } +#endif + + if (!array_size) { + BT_ERR("%s, Unable to get subnet size", __func__); + return false; + } + + for (i = 0; i < array_size; i++) { +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (!bt_mesh_is_provisioner_en()) { + sub = &bt_mesh.sub[i]; + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + sub = bt_mesh.p_sub[i]; + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (i < ARRAY_SIZE(bt_mesh.sub)) { + sub = &bt_mesh.sub[i]; + } else { + sub = bt_mesh.p_sub[i - ARRAY_SIZE(bt_mesh.sub)]; + } +#endif + + if (!sub) { + BT_DBG("%s, NULL subnet", __func__); + continue; + } + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + +#if CONFIG_BLE_MESH_NODE + if (bt_mesh_is_provisioned()) { +#if (defined(CONFIG_BLE_MESH_LOW_POWER) || defined(CONFIG_BLE_MESH_FRIEND)) + if (!friend_decrypt(sub, data, data_len, rx, buf)) { + rx->friend_cred = 1; + rx->ctx.net_idx = sub->net_idx; + rx->sub = sub; + return true; + } +#endif + } +#endif /* CONFIG_BLE_MESH_NODE */ + + if (NID(data) == sub->keys[0].nid && + !net_decrypt(sub, sub->keys[0].enc, sub->keys[0].privacy, + data, data_len, rx, buf)) { + rx->ctx.net_idx = sub->net_idx; + rx->sub = sub; + return true; + } + + if (sub->kr_phase == BLE_MESH_KR_NORMAL) { + continue; + } + + if (NID(data) == sub->keys[1].nid && + !net_decrypt(sub, sub->keys[1].enc, sub->keys[1].privacy, + data, data_len, rx, buf)) { + rx->new_key = 1U; + rx->ctx.net_idx = sub->net_idx; + rx->sub = sub; + return true; + } + } + + return false; +} + +/* Relaying from advertising to the advertising bearer should only happen + * if the Relay state is set to enabled. Locally originated packets always + * get sent to the advertising bearer. If the packet came in through GATT, + * then we should only relay it if the GATT Proxy state is enabled. + */ +#if CONFIG_BLE_MESH_NODE + +static bool relay_to_adv(enum bt_mesh_net_if net_if) +{ + switch (net_if) { + case BLE_MESH_NET_IF_LOCAL: + return true; + case BLE_MESH_NET_IF_ADV: + return (bt_mesh_relay_get() == BLE_MESH_RELAY_ENABLED); + case BLE_MESH_NET_IF_PROXY: + return (bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_ENABLED); + default: + return false; + } +} + +static void bt_mesh_net_relay(struct net_buf_simple *sbuf, + struct bt_mesh_net_rx *rx) +{ + const u8_t *enc, *priv; + struct net_buf *buf; + u8_t nid, transmit; + + if (rx->net_if == BLE_MESH_NET_IF_LOCAL) { + /* Locally originated PDUs with TTL=1 will only be delivered + * to local elements as per Mesh Profile 1.0 section 3.4.5.2: + * "The output filter of the interface connected to + * advertising or GATT bearers shall drop all messages with + * TTL value set to 1." + */ + if (rx->ctx.recv_ttl == 1U) { + return; + } + } else { + if (rx->ctx.recv_ttl <= 1U) { + return; + } + } + + if (rx->net_if == BLE_MESH_NET_IF_ADV && + bt_mesh_relay_get() != BLE_MESH_RELAY_ENABLED && + bt_mesh_gatt_proxy_get() != BLE_MESH_GATT_PROXY_ENABLED) { + return; + } + + BT_DBG("TTL %u CTL %u dst 0x%04x", rx->ctx.recv_ttl, rx->ctl, + rx->ctx.recv_dst); + + /* The Relay Retransmit state is only applied to adv-adv relaying. + * Anything else (like GATT to adv, or locally originated packets) + * use the Network Transmit state. + */ + if (rx->net_if == BLE_MESH_NET_IF_ADV) { + transmit = bt_mesh_relay_retransmit_get(); + } else { + transmit = bt_mesh_net_transmit_get(); + if (rx->net_if == BLE_MESH_NET_IF_PROXY && + transmit < BLE_MESH_TRANSMIT(5, 20)) { + /** + * Add this in case EspBleMesh APP just send a message once, and + * the Proxy Node will send this message using advertising bearer + * with duration not less than 180ms. + */ + transmit = BLE_MESH_TRANSMIT(5, 20); + } + } + + buf = bt_mesh_adv_create(BLE_MESH_ADV_DATA, transmit, K_NO_WAIT); + if (!buf) { + BT_ERR("%s, Out of relay buffers", __func__); + return; + } + + /* Only decrement TTL for non-locally originated packets */ + if (rx->net_if != BLE_MESH_NET_IF_LOCAL) { + /* Leave CTL bit intact */ + sbuf->data[1] &= 0x80; + sbuf->data[1] |= rx->ctx.recv_ttl - 1U; + } + + net_buf_add_mem(buf, sbuf->data, sbuf->len); + + enc = rx->sub->keys[rx->sub->kr_flag].enc; + priv = rx->sub->keys[rx->sub->kr_flag].privacy; + nid = rx->sub->keys[rx->sub->kr_flag].nid; + + BT_DBG("Relaying packet. TTL is now %u", TTL(buf->data)); + + /* Update NID if RX or RX was with friend credentials */ + if (rx->friend_cred) { + buf->data[0] &= 0x80; /* Clear everything except IVI */ + buf->data[0] |= nid; + } + + /* We re-encrypt and obfuscate using the received IVI rather than + * the normal TX IVI (which may be different) since the transport + * layer nonce includes the IVI. + */ + if (bt_mesh_net_encrypt(enc, &buf->b, BLE_MESH_NET_IVI_RX(rx), false)) { + BT_ERR("%s, Re-encrypting failed", __func__); + goto done; + } + + if (bt_mesh_net_obfuscate(buf->data, BLE_MESH_NET_IVI_RX(rx), priv)) { + BT_ERR("%s, Re-obfuscating failed", __func__); + goto done; + } + + /* Sending to the GATT bearer should only happen if GATT Proxy + * is enabled or the message originates from the local node. + */ + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY) && + (bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_ENABLED || + rx->net_if == BLE_MESH_NET_IF_LOCAL)) { + if (bt_mesh_proxy_relay(&buf->b, rx->ctx.recv_dst) && + BLE_MESH_ADDR_IS_UNICAST(rx->ctx.recv_dst)) { + goto done; + } + } + + if (relay_to_adv(rx->net_if)) { + bt_mesh_adv_send(buf, NULL, NULL); + } + +done: + net_buf_unref(buf); +} + +#endif /* CONFIG_BLE_MESH_NODE */ + +int bt_mesh_net_decode(struct net_buf_simple *data, enum bt_mesh_net_if net_if, + struct bt_mesh_net_rx *rx, struct net_buf_simple *buf) +{ + if (data->len < BLE_MESH_NET_MIN_PDU_LEN) { + BT_WARN("Dropping too short mesh packet (len %u)", data->len); + BT_WARN("%s", bt_hex(data->data, data->len)); + return -EINVAL; + } + + if (net_if == BLE_MESH_NET_IF_ADV && check_dup(data)) { + return -EINVAL; + } + + BT_DBG("%u bytes: %s", data->len, bt_hex(data->data, data->len)); + + rx->net_if = net_if; + + if (!net_find_and_decrypt(data->data, data->len, rx, buf)) { + BT_DBG("Unable to find matching net for packet"); + return -ENOENT; + } + + /* Initialize AppIdx to a sane value */ + rx->ctx.app_idx = BLE_MESH_KEY_UNUSED; + + rx->ctx.recv_ttl = TTL(buf->data); + + /* Default to responding with TTL 0 for non-routed messages */ + if (rx->ctx.recv_ttl == 0U) { + rx->ctx.send_ttl = 0U; + } else { + rx->ctx.send_ttl = BLE_MESH_TTL_DEFAULT; + } + + rx->ctl = CTL(buf->data); + rx->seq = SEQ(buf->data); + rx->ctx.recv_dst = DST(buf->data); + + BT_DBG("Decryption successful. Payload len %u", buf->len); + + if (net_if != BLE_MESH_NET_IF_PROXY_CFG && + rx->ctx.recv_dst == BLE_MESH_ADDR_UNASSIGNED) { + BT_ERR("%s, Destination address is unassigned; dropping packet", __func__); + return -EBADMSG; + } + + if (BLE_MESH_ADDR_IS_RFU(rx->ctx.recv_dst)) { + BT_ERR("%s, Destination address is RFU; dropping packet", __func__); + return -EBADMSG; + } + + if (net_if != BLE_MESH_NET_IF_LOCAL && bt_mesh_elem_find(rx->ctx.addr)) { + BT_DBG("Dropping locally originated packet"); + return -EBADMSG; + } + + BT_DBG("src 0x%04x dst 0x%04x ttl %u", rx->ctx.addr, rx->ctx.recv_dst, + rx->ctx.recv_ttl); + BT_DBG("PDU: %s", bt_hex(buf->data, buf->len)); + + return 0; +} + +void bt_mesh_net_recv(struct net_buf_simple *data, s8_t rssi, + enum bt_mesh_net_if net_if) +{ + NET_BUF_SIMPLE_DEFINE(buf, 29); + struct bt_mesh_net_rx rx = { .rssi = rssi }; + struct net_buf_simple_state state; + + BT_DBG("rssi %d net_if %u", rssi, net_if); + +#if CONFIG_BLE_MESH_NODE + if (!bt_mesh_is_provisioner_en()) { + if (!bt_mesh_is_provisioned()) { + return; + } + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (!bt_mesh_is_provisioner_en()) { + BT_WARN("%s, Provisioner is disabled", __func__); + return; + } + if (!provisioner_get_prov_node_count()) { + return; + } +#endif + + if (bt_mesh_net_decode(data, net_if, &rx, &buf)) { + return; + } + + /* Save the state so the buffer can later be relayed */ + net_buf_simple_save(&buf, &state); + +#if CONFIG_BLE_MESH_NODE + if (bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY) && + net_if == BLE_MESH_NET_IF_PROXY) { + bt_mesh_proxy_addr_add(data, rx.ctx.addr); + } + } +#endif + + rx.local_match = (bt_mesh_fixed_group_match(rx.ctx.recv_dst) || + bt_mesh_elem_find(rx.ctx.recv_dst)); + + bt_mesh_trans_recv(&buf, &rx); + + /* Relay if this was a group/virtual address, or if the destination + * was neither a local element nor an LPN we're Friends for. + */ +#if CONFIG_BLE_MESH_NODE + if (bt_mesh_is_provisioned()) { + if (!BLE_MESH_ADDR_IS_UNICAST(rx.ctx.recv_dst) || + (!rx.local_match && !rx.friend_match)) { + net_buf_simple_restore(&buf, &state); + bt_mesh_net_relay(&buf, &rx); + } + } +#endif +} + +static void ivu_refresh(struct k_work *work) +{ + bt_mesh.ivu_duration += BLE_MESH_IVU_HOURS; + + BT_DBG("%s for %u hour%s", + bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS) ? + "IVU in Progress" : "IVU Normal mode", + bt_mesh.ivu_duration, bt_mesh.ivu_duration == 1U ? "" : "s"); + + if (bt_mesh.ivu_duration < BLE_MESH_IVU_MIN_HOURS) { + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_iv(true); + } + + k_delayed_work_submit(&bt_mesh.ivu_timer, BLE_MESH_IVU_TIMEOUT); + return; + } + + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS)) { +#if CONFIG_BLE_MESH_NODE + bt_mesh_beacon_ivu_initiator(true); +#endif + bt_mesh_net_iv_update(bt_mesh.iv_index, false); + } else if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_iv(true); + } +} + +#if defined(CONFIG_BLE_MESH_NODE) +void bt_mesh_net_start(void) +{ + if (bt_mesh_beacon_get() == BLE_MESH_BEACON_ENABLED) { + bt_mesh_beacon_enable(); + } else { + bt_mesh_beacon_disable(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY) && + bt_mesh_gatt_proxy_get() != BLE_MESH_GATT_PROXY_NOT_SUPPORTED) { + bt_mesh_proxy_gatt_enable(); + bt_mesh_adv_update(); + } + +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) + /* Add Mesh beacon type (Secure Network Beacon) to the exceptional list */ + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_ADD, + BLE_MESH_EXCEP_INFO_MESH_BEACON, NULL); +#endif + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + /* TODO: Enable duplicate scan in Low Power Mode */ + bt_mesh_lpn_init(); + } else { + bt_mesh_scan_enable(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + bt_mesh_friend_init(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROV)) { + u16_t net_idx = bt_mesh.sub[0].net_idx; + u16_t addr = bt_mesh_primary_addr(); + u32_t iv_index = bt_mesh.iv_index; + u8_t flags = (u8_t)bt_mesh.sub[0].kr_flag; + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS)) { + flags |= BLE_MESH_NET_FLAG_IVU; + } + + bt_mesh_prov_complete(net_idx, addr, flags, iv_index); + } +} +#endif + +void bt_mesh_net_init(void) +{ + k_delayed_work_init(&bt_mesh.ivu_timer, ivu_refresh); + + k_work_init(&bt_mesh.local_work, bt_mesh_net_local); +} diff --git a/components/bt/ble_mesh/mesh_core/net.h b/components/bt/ble_mesh/mesh_core/net.h new file mode 100644 index 0000000000..90515fd2ca --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/net.h @@ -0,0 +1,388 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _NET_H_ +#define _NET_H_ + +#include "mesh_util.h" +#include "mesh_kernel.h" +#include "mesh_access.h" + +#define BLE_MESH_NET_FLAG_KR BIT(0) +#define BLE_MESH_NET_FLAG_IVU BIT(1) + +#define BLE_MESH_KR_NORMAL 0x00 +#define BLE_MESH_KR_PHASE_1 0x01 +#define BLE_MESH_KR_PHASE_2 0x02 +#define BLE_MESH_KR_PHASE_3 0x03 + +#define BLE_MESH_IV_UPDATE(flags) ((flags >> 1) & 0x01) +#define BLE_MESH_KEY_REFRESH(flags) (flags & 0x01) + +/* How many hours in between updating IVU duration */ +#define BLE_MESH_IVU_MIN_HOURS 96 +#define BLE_MESH_IVU_HOURS (BLE_MESH_IVU_MIN_HOURS / \ + CONFIG_BLE_MESH_IVU_DIVIDER) +#define BLE_MESH_IVU_TIMEOUT K_HOURS(BLE_MESH_IVU_HOURS) + +struct bt_mesh_app_key { + u16_t net_idx; + u16_t app_idx; + bool updated; + struct bt_mesh_app_keys { + u8_t id; + u8_t val[16]; + } keys[2]; +}; + +struct bt_mesh_subnet { + u32_t beacon_sent; /* Timestamp of last sent beacon */ + u8_t beacons_last; /* Number of beacons during last + * observation window + */ + u8_t beacons_cur; /* Number of beaconds observed during + * currently ongoing window. + */ + + u8_t beacon_cache[21]; /* Cached last authenticated beacon */ + + u16_t net_idx; /* NetKeyIndex */ + + bool kr_flag; /* Key Refresh Flag */ + u8_t kr_phase; /* Key Refresh Phase */ + + u8_t node_id; /* Node Identity State */ + u32_t node_id_start; /* Node Identity started timestamp */ + + u8_t auth[8]; /* Beacon Authentication Value */ + + struct bt_mesh_subnet_keys { + u8_t net[16]; /* NetKey */ + u8_t nid; /* NID */ + u8_t enc[16]; /* EncKey */ + u8_t net_id[8]; /* Network ID */ +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + u8_t identity[16]; /* IdentityKey */ +#endif + u8_t privacy[16]; /* PrivacyKey */ + u8_t beacon[16]; /* BeaconKey */ + } keys[2]; +}; + +struct bt_mesh_rpl { + u16_t src; + bool old_iv; +#if defined(CONFIG_BLE_MESH_SETTINGS) + bool store; +#endif + u32_t seq; +}; + +#if defined(CONFIG_BLE_MESH_FRIEND) +#define FRIEND_SEG_RX CONFIG_BLE_MESH_FRIEND_SEG_RX +#define FRIEND_SUB_LIST_SIZE CONFIG_BLE_MESH_FRIEND_SUB_LIST_SIZE +#else +#define FRIEND_SEG_RX 0 +#define FRIEND_SUB_LIST_SIZE 0 +#endif + +struct bt_mesh_friend { + u16_t lpn; + u8_t recv_delay; + u8_t fsn: 1, + send_last: 1, + pending_req: 1, + sec_update: 1, + pending_buf: 1, + valid: 1, + established: 1; + s32_t poll_to; + u8_t num_elem; + u16_t lpn_counter; + u16_t counter; + + u16_t net_idx; + + u16_t sub_list[FRIEND_SUB_LIST_SIZE]; + + struct k_delayed_work timer; + + struct bt_mesh_friend_seg { + sys_slist_t queue; + } seg[FRIEND_SEG_RX]; + + struct net_buf *last; + + sys_slist_t queue; + u32_t queue_size; + + /* Friend Clear Procedure */ + struct { + u32_t start; /* Clear Procedure start */ + u16_t frnd; /* Previous Friend's address */ + u16_t repeat_sec; /* Repeat timeout in seconds */ + struct k_delayed_work timer; /* Repeat timer */ + } clear; +}; + +#if defined(CONFIG_BLE_MESH_LOW_POWER) +#define LPN_GROUPS CONFIG_BLE_MESH_LPN_GROUPS +#else +#define LPN_GROUPS 0 +#endif + +/* Low Power Node state */ +struct bt_mesh_lpn { + enum __packed { + BLE_MESH_LPN_DISABLED, /* LPN feature is disabled */ + BLE_MESH_LPN_CLEAR, /* Clear in progress */ + BLE_MESH_LPN_TIMER, /* Waiting for auto timer expiry */ + BLE_MESH_LPN_ENABLED, /* LPN enabled, but no Friend */ + BLE_MESH_LPN_REQ_WAIT, /* Wait before scanning for offers */ + BLE_MESH_LPN_WAIT_OFFER, /* Friend Req sent */ + BLE_MESH_LPN_ESTABLISHED, /* Friendship established */ + BLE_MESH_LPN_RECV_DELAY, /* Poll sent, waiting ReceiveDelay */ + BLE_MESH_LPN_WAIT_UPDATE, /* Waiting for Update or message */ + BLE_MESH_LPN_OFFER_RECV, /* Friend offer received */ + } state; + + /* Transaction Number (used for subscription list) */ + u8_t xact_next; + u8_t xact_pending; + u8_t sent_req; + + /* Address of our Friend when we're a LPN. Unassigned if we don't + * have a friend yet. + */ + u16_t frnd; + + /* Value from the friend offer */ + u8_t recv_win; + + u8_t req_attempts; /* Number of Request attempts */ + + s32_t poll_timeout; + + u8_t groups_changed: 1, /* Friend Subscription List needs updating */ + pending_poll: 1, /* Poll to be sent after subscription */ + disable: 1, /* Disable LPN after clearing */ + fsn: 1, /* Friend Sequence Number */ + established: 1, /* Friendship established */ + clear_success: 1; /* Friend Clear Confirm received */ + + /* Friend Queue Size */ + u8_t queue_size; + + /* LPNCounter */ + u16_t counter; + + /* Previous Friend of this LPN */ + u16_t old_friend; + + /* Duration reported for last advertising packet */ + u16_t adv_duration; + + /* Next LPN related action timer */ + struct k_delayed_work timer; + + /* Subscribed groups */ + u16_t groups[LPN_GROUPS]; + + /* Bit fields for tracking which groups the Friend knows about */ + BLE_MESH_ATOMIC_DEFINE(added, LPN_GROUPS); + BLE_MESH_ATOMIC_DEFINE(pending, LPN_GROUPS); + BLE_MESH_ATOMIC_DEFINE(to_remove, LPN_GROUPS); +}; + +/* bt_mesh_net.flags */ +enum { + BLE_MESH_VALID, /* We have been provisioned */ + BLE_MESH_SUSPENDED, /* Network is temporarily suspended */ + BLE_MESH_IVU_IN_PROGRESS, /* IV Update in Progress */ + BLE_MESH_IVU_INITIATOR, /* IV Update initiated by us */ + BLE_MESH_IVU_TEST, /* IV Update test mode */ + BLE_MESH_IVU_PENDING, /* Update blocked by SDU in progress */ + + /* pending storage actions */ + BLE_MESH_RPL_PENDING, + BLE_MESH_KEYS_PENDING, + BLE_MESH_NET_PENDING, + BLE_MESH_IV_PENDING, + BLE_MESH_SEQ_PENDING, + BLE_MESH_HB_PUB_PENDING, + BLE_MESH_CFG_PENDING, + BLE_MESH_MOD_PENDING, + + /* Don't touch - intentionally last */ + BLE_MESH_FLAG_COUNT, +}; + +struct bt_mesh_net { + u32_t iv_index; /* Current IV Index */ + u32_t seq; /* Next outgoing sequence number (24 bits) */ + + BLE_MESH_ATOMIC_DEFINE(flags, BLE_MESH_FLAG_COUNT); + + /* Local network interface */ + struct k_work local_work; + sys_slist_t local_queue; + +#if defined(CONFIG_BLE_MESH_FRIEND) + /* Friend state, unique for each LPN that we're Friends for */ + struct bt_mesh_friend frnd[CONFIG_BLE_MESH_FRIEND_LPN_COUNT]; +#endif + +#if defined(CONFIG_BLE_MESH_LOW_POWER) + struct bt_mesh_lpn lpn; /* Low Power Node state */ +#endif + + /* Number of hours in current IV Update state */ + u8_t ivu_duration; + + /* Timer to track duration in current IV Update state */ + struct k_delayed_work ivu_timer; + + u8_t dev_key[16]; + + struct bt_mesh_app_key app_keys[CONFIG_BLE_MESH_APP_KEY_COUNT]; + + struct bt_mesh_subnet sub[CONFIG_BLE_MESH_SUBNET_COUNT]; + + struct bt_mesh_rpl rpl[CONFIG_BLE_MESH_CRPL]; + +#if defined(CONFIG_BLE_MESH_PROVISIONER) + /* Application keys stored by provisioner */ + struct bt_mesh_app_key *p_app_keys[CONFIG_BLE_MESH_PROVISIONER_APP_KEY_COUNT]; + /* Next app_idx can be assigned */ + u16_t p_app_idx_next; + + /* Network keys stored by provisioner */ + struct bt_mesh_subnet *p_sub[CONFIG_BLE_MESH_PROVISIONER_SUBNET_COUNT]; + /* Next net_idx can be assigned */ + u16_t p_net_idx_next; +#endif +}; + +/* Network interface */ +enum bt_mesh_net_if { + BLE_MESH_NET_IF_ADV, + BLE_MESH_NET_IF_LOCAL, + BLE_MESH_NET_IF_PROXY, + BLE_MESH_NET_IF_PROXY_CFG, +}; + +/* Decoding context for Network/Transport data */ +struct bt_mesh_net_rx { + struct bt_mesh_subnet *sub; + struct bt_mesh_msg_ctx ctx; + u32_t seq; /* Sequence Number */ + u8_t old_iv: 1, /* iv_index - 1 was used */ + new_key: 1, /* Data was encrypted with updated key */ + friend_cred: 1, /* Data was encrypted with friend cred */ + ctl: 1, /* Network Control */ + net_if: 2, /* Network interface */ + local_match: 1, /* Matched a local element */ + friend_match: 1; /* Matched an LPN we're friends for */ + s8_t rssi; +}; + +/* Encoding context for Network/Transport data */ +struct bt_mesh_net_tx { + struct bt_mesh_subnet *sub; + struct bt_mesh_msg_ctx *ctx; + u16_t src; + u8_t xmit; + u8_t friend_cred: 1, + aszmic: 1, + aid: 6; +}; + +extern struct bt_mesh_net bt_mesh; + +#define BLE_MESH_NET_IVI_TX (bt_mesh.iv_index - \ + bt_mesh_atomic_test_bit(bt_mesh.flags, \ + BLE_MESH_IVU_IN_PROGRESS)) +#define BLE_MESH_NET_IVI_RX(rx) (bt_mesh.iv_index - (rx)->old_iv) + +#define BLE_MESH_NET_HDR_LEN 9 + +int bt_mesh_net_keys_create(struct bt_mesh_subnet_keys *keys, + const u8_t key[16]); + +int bt_mesh_net_create(u16_t idx, u8_t flags, const u8_t key[16], + u32_t iv_index); + +u8_t bt_mesh_net_flags(struct bt_mesh_subnet *sub); + +bool bt_mesh_kr_update(struct bt_mesh_subnet *sub, u8_t new_kr, bool new_key); + +void bt_mesh_net_revoke_keys(struct bt_mesh_subnet *sub); + +int bt_mesh_net_beacon_update(struct bt_mesh_subnet *sub); + +void bt_mesh_rpl_reset(void); + +bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update); + +void bt_mesh_net_sec_update(struct bt_mesh_subnet *sub); + +struct bt_mesh_subnet *bt_mesh_subnet_get(u16_t net_idx); + +struct bt_mesh_subnet *bt_mesh_subnet_find(const u8_t net_id[8], u8_t flags, + u32_t iv_index, const u8_t auth[8], + bool *new_key); + +int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct net_buf_simple *buf, + bool proxy); + +int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct net_buf *buf, + const struct bt_mesh_send_cb *cb, void *cb_data); + +int bt_mesh_net_resend(struct bt_mesh_subnet *sub, struct net_buf *buf, + bool new_key, const struct bt_mesh_send_cb *cb, + void *cb_data); + +int bt_mesh_net_decode(struct net_buf_simple *data, enum bt_mesh_net_if net_if, + struct bt_mesh_net_rx *rx, struct net_buf_simple *buf); + +void bt_mesh_net_recv(struct net_buf_simple *data, s8_t rssi, + enum bt_mesh_net_if net_if); + +u32_t bt_mesh_next_seq(void); + +void bt_mesh_net_start(void); + +void bt_mesh_net_init(void); + +/* Friendship Credential Management */ +struct friend_cred { + u16_t net_idx; + u16_t addr; + + u16_t lpn_counter; + u16_t frnd_counter; + + struct { + u8_t nid; /* NID */ + u8_t enc[16]; /* EncKey */ + u8_t privacy[16]; /* PrivacyKey */ + } cred[2]; +}; + +int friend_cred_get(struct bt_mesh_subnet *sub, u16_t addr, u8_t *nid, + const u8_t **enc, const u8_t **priv); +int friend_cred_set(struct friend_cred *cred, u8_t idx, const u8_t net_key[16]); +void friend_cred_refresh(u16_t net_idx); +int friend_cred_update(struct bt_mesh_subnet *sub); +struct friend_cred *friend_cred_create(struct bt_mesh_subnet *sub, u16_t addr, + u16_t lpn_counter, u16_t frnd_counter); +void friend_cred_clear(struct friend_cred *cred); +int friend_cred_del(u16_t net_idx, u16_t addr); + +#endif /* _NET_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/prov.c b/components/bt/ble_mesh/mesh_core/prov.c new file mode 100644 index 0000000000..0a3329ff65 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/prov.c @@ -0,0 +1,1774 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_PROV) + +#include "mesh_util.h" +#include "mesh_main.h" +#include "mesh_uuid.h" +#include "mesh_trace.h" +#include "mesh_proxy.h" + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "net.h" +#include "access.h" +#include "foundation.h" +#include "proxy.h" +#include "prov.h" + +#if CONFIG_BLE_MESH_NODE + +/* 3 transmissions, 20ms interval */ +#define PROV_XMIT BLE_MESH_TRANSMIT(2, 20) + +#define AUTH_METHOD_NO_OOB 0x00 +#define AUTH_METHOD_STATIC 0x01 +#define AUTH_METHOD_OUTPUT 0x02 +#define AUTH_METHOD_INPUT 0x03 + +#define OUTPUT_OOB_BLINK 0x00 +#define OUTPUT_OOB_BEEP 0x01 +#define OUTPUT_OOB_VIBRATE 0x02 +#define OUTPUT_OOB_NUMBER 0x03 +#define OUTPUT_OOB_STRING 0x04 + +#define INPUT_OOB_PUSH 0x00 +#define INPUT_OOB_TWIST 0x01 +#define INPUT_OOB_NUMBER 0x02 +#define INPUT_OOB_STRING 0x03 + +#define PROV_ERR_NONE 0x00 +#define PROV_ERR_NVAL_PDU 0x01 +#define PROV_ERR_NVAL_FMT 0x02 +#define PROV_ERR_UNEXP_PDU 0x03 +#define PROV_ERR_CFM_FAILED 0x04 +#define PROV_ERR_RESOURCES 0x05 +#define PROV_ERR_DECRYPT 0x06 +#define PROV_ERR_UNEXP_ERR 0x07 +#define PROV_ERR_ADDR 0x08 + +#define PROV_INVITE 0x00 +#define PROV_CAPABILITIES 0x01 +#define PROV_START 0x02 +#define PROV_PUB_KEY 0x03 +#define PROV_INPUT_COMPLETE 0x04 +#define PROV_CONFIRM 0x05 +#define PROV_RANDOM 0x06 +#define PROV_DATA 0x07 +#define PROV_COMPLETE 0x08 +#define PROV_FAILED 0x09 + +#define PROV_ALG_P256 0x00 + +#define GPCF(gpc) (gpc & 0x03) +#define GPC_START(last_seg) (((last_seg) << 2) | 0x00) +#define GPC_ACK 0x01 +#define GPC_CONT(seg_id) (((seg_id) << 2) | 0x02) +#define GPC_CTL(op) (((op) << 2) | 0x03) + +#define START_PAYLOAD_MAX 20 +#define CONT_PAYLOAD_MAX 23 + +#define START_LAST_SEG(gpc) (gpc >> 2) +#define CONT_SEG_INDEX(gpc) (gpc >> 2) + +#define BEARER_CTL(gpc) (gpc >> 2) +#define LINK_OPEN 0x00 +#define LINK_ACK 0x01 +#define LINK_CLOSE 0x02 + +#define CLOSE_REASON_SUCCESS 0x00 +#define CLOSE_REASON_TIMEOUT 0x01 +#define CLOSE_REASON_FAILED 0x02 + +#define XACT_SEG_DATA(_seg) (&link.rx.buf->data[20 + ((_seg - 1) * 23)]) +#define XACT_SEG_RECV(_seg) (link.rx.seg &= ~(1 << (_seg))) + +#define XACT_NVAL 0xff + +enum { + REMOTE_PUB_KEY, /* Remote key has been received */ + OOB_PUB_KEY, /* OOB public key is available */ + LINK_ACTIVE, /* Link has been opened */ + HAVE_DHKEY, /* DHKey has been calcualted */ + SEND_CONFIRM, /* Waiting to send Confirm value */ + WAIT_NUMBER, /* Waiting for number input from user */ + WAIT_STRING, /* Waiting for string input from user */ + TIMEOUT_START, /* Provision timeout timer has started */ + + NUM_FLAGS, +}; + +struct prov_link { + BLE_MESH_ATOMIC_DEFINE(flags, NUM_FLAGS); +#if defined(CONFIG_BLE_MESH_PB_GATT) + struct bt_mesh_conn *conn; /* GATT connection */ +#endif + u8_t dhkey[32]; /* Calculated DHKey */ + u8_t expect; /* Next expected PDU */ + + bool oob_pk_flag; /* Flag indicates whether using OOB public key */ + + u8_t oob_method; + u8_t oob_action; + u8_t oob_size; + + u8_t conf[16]; /* Remote Confirmation */ + u8_t rand[16]; /* Local Random */ + u8_t auth[16]; /* Authentication Value */ + + u8_t conf_salt[16]; /* ConfirmationSalt */ + u8_t conf_key[16]; /* ConfirmationKey */ + u8_t conf_inputs[145]; /* ConfirmationInputs */ + u8_t prov_salt[16]; /* Provisioning Salt */ + +#if defined(CONFIG_BLE_MESH_PB_ADV) + u32_t id; /* Link ID */ + u8_t tx_pdu_type; /* The previously transmitted Provisioning PDU type */ + + struct { + u8_t id; /* Transaction ID */ + u8_t prev_id; /* Previous Transaction ID */ + u8_t seg; /* Bit-field of unreceived segments */ + u8_t last_seg; /* Last segment (to check length) */ + u8_t fcs; /* Expected FCS value */ + struct net_buf_simple *buf; + } rx; + + struct { + /* Start timestamp of the transaction */ + s64_t start; + + /* Transaction id*/ + u8_t id; + + /* Pending outgoing buffer(s) */ + struct net_buf *buf[3]; + + /* Retransmit timer */ + struct k_delayed_work retransmit; + } tx; +#endif + + /* Provision timeout timer */ + struct k_delayed_work timeout; +}; + +struct prov_rx { + u32_t link_id; + u8_t xact_id; + u8_t gpc; +}; + +#define BUF_TIMEOUT K_MSEC(400) + +#if defined(CONFIG_BLE_MESH_FAST_PROV) +#define RETRANSMIT_TIMEOUT K_MSEC(360) +#define TRANSACTION_TIMEOUT K_SECONDS(3) +#define PROVISION_TIMEOUT K_SECONDS(6) +#else +#define RETRANSMIT_TIMEOUT K_MSEC(500) +#define TRANSACTION_TIMEOUT K_SECONDS(30) +#define PROVISION_TIMEOUT K_SECONDS(60) +#endif /* CONFIG_BLE_MESH_FAST_PROV */ + +#if defined(CONFIG_BLE_MESH_PB_GATT) +#define PROV_BUF_HEADROOM 5 +#else +#define PROV_BUF_HEADROOM 0 +NET_BUF_SIMPLE_DEFINE_STATIC(rx_buf, 65); +#endif + +#define PROV_BUF(name, len) \ + NET_BUF_SIMPLE_DEFINE(name, PROV_BUF_HEADROOM + len) + +static struct prov_link link; + +static const struct bt_mesh_prov *prov; + +static void close_link(u8_t err, u8_t reason); + +static void reset_state(void) +{ + /* Disable Attention Timer if it was set */ + if (link.conf_inputs[0]) { + bt_mesh_attention(NULL, 0); + } + +#if defined(CONFIG_BLE_MESH_PB_GATT) + if (link.conn) { + bt_mesh_conn_unref(link.conn); + } +#endif + +#if defined(CONFIG_BLE_MESH_PB_ADV) + /* Clear everything except the retransmit delayed work config */ + (void)memset(&link, 0, offsetof(struct prov_link, tx.retransmit)); + link.rx.prev_id = XACT_NVAL; + +#if defined(CONFIG_BLE_MESH_PB_GATT) + link.rx.buf = bt_mesh_proxy_get_buf(); +#else + net_buf_simple_reset(&rx_buf); + link.rx.buf = &rx_buf; +#endif /* PB_GATT */ + +#else + (void)memset(&link, 0, offsetof(struct prov_link, timeout)); +#endif /* PB_ADV */ +} + +#if defined(CONFIG_BLE_MESH_PB_ADV) +static void buf_sent(int err, void *user_data) +{ + if (!link.tx.buf[0]) { + return; + } + + k_delayed_work_submit(&link.tx.retransmit, RETRANSMIT_TIMEOUT); +} + +static struct bt_mesh_send_cb buf_sent_cb = { + .end = buf_sent, +}; + +static void free_segments(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(link.tx.buf); i++) { + struct net_buf *buf = link.tx.buf[i]; + + if (!buf) { + break; + } + + link.tx.buf[i] = NULL; + /* Mark as canceled */ + BLE_MESH_ADV(buf)->busy = 0U; + /** Changed by Espressif. Add this to avoid buf->ref is 2 which will + * cause lack of buf. + */ + if (buf->ref > 1) { + buf->ref = 1; + } + net_buf_unref(buf); + } +} + +static void prov_clear_tx(void) +{ + BT_DBG("%s", __func__); + + k_delayed_work_cancel(&link.tx.retransmit); + + free_segments(); +} + +static void reset_link(void) +{ + prov_clear_tx(); + + if (bt_mesh_atomic_test_and_clear_bit(link.flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link.timeout); + } + + if (prov->link_close) { + prov->link_close(BLE_MESH_PROV_ADV); + } + +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) + /* Remove the link id from exceptional list */ + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_REMOVE, + BLE_MESH_EXCEP_INFO_MESH_LINK_ID, &link.id); +#endif + + reset_state(); +} + +static struct net_buf *adv_buf_create(void) +{ + struct net_buf *buf; + + buf = bt_mesh_adv_create(BLE_MESH_ADV_PROV, PROV_XMIT, BUF_TIMEOUT); + if (!buf) { + BT_ERR("%s, Out of provisioning buffers", __func__); + return NULL; + } + + return buf; +} + +static u8_t pending_ack = XACT_NVAL; + +static void ack_complete(u16_t duration, int err, void *user_data) +{ + BT_DBG("xact %u complete", (u8_t)pending_ack); + pending_ack = XACT_NVAL; +} + +static void gen_prov_ack_send(u8_t xact_id) +{ + static const struct bt_mesh_send_cb cb = { + .start = ack_complete, + }; + const struct bt_mesh_send_cb *complete; + struct net_buf *buf; + + BT_DBG("xact_id %u", xact_id); + + if (pending_ack == xact_id) { + BT_DBG("Not sending duplicate ack"); + return; + } + + buf = adv_buf_create(); + if (!buf) { + return; + } + + if (pending_ack == XACT_NVAL) { + pending_ack = xact_id; + complete = &cb; + } else { + complete = NULL; + } + + net_buf_add_be32(buf, link.id); + net_buf_add_u8(buf, xact_id); + net_buf_add_u8(buf, GPC_ACK); + + bt_mesh_adv_send(buf, complete, NULL); + net_buf_unref(buf); +} + +static void send_reliable(void) +{ + int i; + + link.tx.start = k_uptime_get(); + + for (i = 0; i < ARRAY_SIZE(link.tx.buf); i++) { + struct net_buf *buf = link.tx.buf[i]; + + if (!buf) { + break; + } + + if (i + 1 < ARRAY_SIZE(link.tx.buf) && link.tx.buf[i + 1]) { + bt_mesh_adv_send(buf, NULL, NULL); + } else { + bt_mesh_adv_send(buf, &buf_sent_cb, NULL); + } + } +} + +static int bearer_ctl_send(u8_t op, void *data, u8_t data_len) +{ + struct net_buf *buf; + + BT_DBG("op 0x%02x data_len %u", op, data_len); + + prov_clear_tx(); + + buf = adv_buf_create(); + if (!buf) { + return -ENOBUFS; + } + + net_buf_add_be32(buf, link.id); + /* Transaction ID, always 0 for Bearer messages */ + net_buf_add_u8(buf, 0x00); + net_buf_add_u8(buf, GPC_CTL(op)); + net_buf_add_mem(buf, data, data_len); + + link.tx.buf[0] = buf; + send_reliable(); + + return 0; +} + +static u8_t last_seg(u8_t len) +{ + if (len <= START_PAYLOAD_MAX) { + return 0; + } + + len -= START_PAYLOAD_MAX; + + return 1 + (len / CONT_PAYLOAD_MAX); +} + +static inline u8_t next_transaction_id(void) +{ + if (link.tx.id != 0U && link.tx.id != 0xFF) { + return ++link.tx.id; + } + + link.tx.id = 0x80; + return link.tx.id; +} + +static int prov_send_adv(struct net_buf_simple *msg) +{ + struct net_buf *start, *buf; + u8_t seg_len, seg_id; + u8_t xact_id; + s32_t timeout = PROVISION_TIMEOUT; + + BT_DBG("%s, len %u: %s", __func__, msg->len, bt_hex(msg->data, msg->len)); + + prov_clear_tx(); + + start = adv_buf_create(); + if (!start) { + return -ENOBUFS; + } + + xact_id = next_transaction_id(); + net_buf_add_be32(start, link.id); + net_buf_add_u8(start, xact_id); + + net_buf_add_u8(start, GPC_START(last_seg(msg->len))); + net_buf_add_be16(start, msg->len); + net_buf_add_u8(start, bt_mesh_fcs_calc(msg->data, msg->len)); + + link.tx.buf[0] = start; + /* Changed by Espressif, get message type */ + link.tx_pdu_type = msg->data[0]; + + seg_len = MIN(msg->len, START_PAYLOAD_MAX); + BT_DBG("seg 0 len %u: %s", seg_len, bt_hex(msg->data, seg_len)); + net_buf_add_mem(start, msg->data, seg_len); + net_buf_simple_pull(msg, seg_len); + + buf = start; + for (seg_id = 1U; msg->len > 0; seg_id++) { + if (seg_id >= ARRAY_SIZE(link.tx.buf)) { + BT_ERR("%s, Too big message", __func__); + free_segments(); + return -E2BIG; + } + + buf = adv_buf_create(); + if (!buf) { + free_segments(); + return -ENOBUFS; + } + + link.tx.buf[seg_id] = buf; + + seg_len = MIN(msg->len, CONT_PAYLOAD_MAX); + + BT_DBG("seg_id %u len %u: %s", seg_id, seg_len, + bt_hex(msg->data, seg_len)); + + net_buf_add_be32(buf, link.id); + net_buf_add_u8(buf, xact_id); + net_buf_add_u8(buf, GPC_CONT(seg_id)); + net_buf_add_mem(buf, msg->data, seg_len); + net_buf_simple_pull(msg, seg_len); + } + + send_reliable(); + + /* Changed by Espressif, add provisioning timeout timer operations. + * When sending a provisioning PDU successfully, restart the 60s timer. + */ + if (bt_mesh_atomic_test_and_clear_bit(link.flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link.timeout); + } +#if defined(CONFIG_BLE_MESH_FAST_PROV) + if (link.tx_pdu_type >= PROV_COMPLETE) { + timeout = K_SECONDS(60); + } +#endif + if (!bt_mesh_atomic_test_and_set_bit(link.flags, TIMEOUT_START)) { + k_delayed_work_submit(&link.timeout, timeout); + } + + return 0; +} + +#endif /* CONFIG_BLE_MESH_PB_ADV */ + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static int prov_send_gatt(struct net_buf_simple *msg) +{ + int err = 0; + + if (!link.conn) { + return -ENOTCONN; + } + + /* Changed by Espressif, add provisioning timeout timer operations. + * When sending a provisioning PDU successfully, restart the 60s timer. + */ + err = bt_mesh_proxy_send(link.conn, BLE_MESH_PROXY_PROV, msg); + if (err) { + BT_ERR("%s, Failed to send provisioning PDU", __func__); + return err; + } + + if (bt_mesh_atomic_test_and_clear_bit(link.flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link.timeout); + } + if (msg->data[1] != PROV_COMPLETE && msg->data[1] != PROV_FAILED) { + if (!bt_mesh_atomic_test_and_set_bit(link.flags, TIMEOUT_START)) { + k_delayed_work_submit(&link.timeout, PROVISION_TIMEOUT); + } + } + + return 0; +} +#endif /* CONFIG_BLE_MESH_PB_GATT */ + +static inline int prov_send(struct net_buf_simple *buf) +{ +#if defined(CONFIG_BLE_MESH_PB_GATT) + if (link.conn) { + return prov_send_gatt(buf); + } +#endif +#if defined(CONFIG_BLE_MESH_PB_ADV) + return prov_send_adv(buf); +#else + return 0; +#endif +} + +static void prov_buf_init(struct net_buf_simple *buf, u8_t type) +{ + net_buf_simple_reserve(buf, PROV_BUF_HEADROOM); + net_buf_simple_add_u8(buf, type); +} + +static void prov_send_fail_msg(u8_t err) +{ + PROV_BUF(buf, 2); + + prov_buf_init(&buf, PROV_FAILED); + net_buf_simple_add_u8(&buf, err); + prov_send(&buf); +} + +static void prov_invite(const u8_t *data) +{ + PROV_BUF(buf, 12); + + BT_DBG("Attention Duration: %u seconds", data[0]); + + if (data[0]) { + bt_mesh_attention(NULL, data[0]); + } + + link.conf_inputs[0] = data[0]; + + prov_buf_init(&buf, PROV_CAPABILITIES); + + /* Number of Elements supported */ + net_buf_simple_add_u8(&buf, bt_mesh_elem_count()); + + /* Supported algorithms - FIPS P-256 Eliptic Curve */ + net_buf_simple_add_be16(&buf, BIT(PROV_ALG_P256)); + + /* Public Key Type */ + net_buf_simple_add_u8(&buf, prov->oob_pub_key); + + /* Static OOB Type */ + net_buf_simple_add_u8(&buf, prov->static_val ? BIT(0) : 0x00); + + /* Output OOB Size */ + net_buf_simple_add_u8(&buf, prov->output_size); + + /* Output OOB Action */ + net_buf_simple_add_be16(&buf, prov->output_actions); + + /* Input OOB Size */ + net_buf_simple_add_u8(&buf, prov->input_size); + + /* Input OOB Action */ + net_buf_simple_add_be16(&buf, prov->input_actions); + + memcpy(&link.conf_inputs[1], &buf.data[1], 11); + + if (prov_send(&buf)) { + BT_ERR("%s, Failed to send capabilities", __func__); + close_link(PROV_ERR_RESOURCES, CLOSE_REASON_FAILED); + return; + } + + link.expect = PROV_START; +} + +static void prov_capabilities(const u8_t *data) +{ + u16_t algorithms, output_action, input_action; + + BT_DBG("Elements: %u", data[0]); + + algorithms = sys_get_be16(&data[1]); + BT_DBG("Algorithms: %u", algorithms); + + BT_DBG("Public Key Type: 0x%02x", data[3]); + BT_DBG("Static OOB Type: 0x%02x", data[4]); + BT_DBG("Output OOB Size: %u", data[5]); + + output_action = sys_get_be16(&data[6]); + BT_DBG("Output OOB Action: 0x%04x", output_action); + + BT_DBG("Input OOB Size: %u", data[8]); + + input_action = sys_get_be16(&data[9]); + BT_DBG("Input OOB Action: 0x%04x", input_action); +} + +static bt_mesh_output_action_t output_action(u8_t action) +{ + switch (action) { + case OUTPUT_OOB_BLINK: + return BLE_MESH_BLINK; + case OUTPUT_OOB_BEEP: + return BLE_MESH_BEEP; + case OUTPUT_OOB_VIBRATE: + return BLE_MESH_VIBRATE; + case OUTPUT_OOB_NUMBER: + return BLE_MESH_DISPLAY_NUMBER; + case OUTPUT_OOB_STRING: + return BLE_MESH_DISPLAY_STRING; + default: + return BLE_MESH_NO_OUTPUT; + } +} + +static bt_mesh_input_action_t input_action(u8_t action) +{ + switch (action) { + case INPUT_OOB_PUSH: + return BLE_MESH_PUSH; + case INPUT_OOB_TWIST: + return BLE_MESH_TWIST; + case INPUT_OOB_NUMBER: + return BLE_MESH_ENTER_NUMBER; + case INPUT_OOB_STRING: + return BLE_MESH_ENTER_STRING; + default: + return BLE_MESH_NO_INPUT; + } +} + +static int prov_auth(u8_t method, u8_t action, u8_t size) +{ + bt_mesh_output_action_t output; + bt_mesh_input_action_t input; + + switch (method) { + case AUTH_METHOD_NO_OOB: + if (action || size) { + return -EINVAL; + } + + (void)memset(link.auth, 0, sizeof(link.auth)); + return 0; + case AUTH_METHOD_STATIC: + if (action || size) { + return -EINVAL; + } + + memcpy(link.auth + 16 - prov->static_val_len, + prov->static_val, prov->static_val_len); + (void)memset(link.auth, 0, + sizeof(link.auth) - prov->static_val_len); + return 0; + + case AUTH_METHOD_OUTPUT: + output = output_action(action); + if (!output) { + return -EINVAL; + } + + if (!(prov->output_actions & output)) { + return -EINVAL; + } + + if (size > prov->output_size) { + return -EINVAL; + } + + if (output == BLE_MESH_DISPLAY_STRING) { + unsigned char str[9]; + u8_t i; + + bt_mesh_rand(str, size); + + /* Normalize to '0' .. '9' & 'A' .. 'Z' */ + for (i = 0U; i < size; i++) { + str[i] %= 36; + if (str[i] < 10) { + str[i] += '0'; + } else { + str[i] += 'A' - 10; + } + } + str[size] = '\0'; + + memcpy(link.auth, str, size); + (void)memset(link.auth + size, 0, + sizeof(link.auth) - size); + + return prov->output_string((char *)str); + } else { + u32_t div[8] = { 10, 100, 1000, 10000, 100000, + 1000000, 10000000, 100000000 + }; + u32_t num; + + bt_mesh_rand(&num, sizeof(num)); + num %= div[size - 1]; + + sys_put_be32(num, &link.auth[12]); + (void)memset(link.auth, 0, 12); + + return prov->output_number(output, num); + } + + case AUTH_METHOD_INPUT: + input = input_action(action); + if (!input) { + return -EINVAL; + } + + if (!(prov->input_actions & input)) { + return -EINVAL; + } + + if (size > prov->input_size) { + return -EINVAL; + } + + if (input == BLE_MESH_ENTER_STRING) { + bt_mesh_atomic_set_bit(link.flags, WAIT_STRING); + } else { + bt_mesh_atomic_set_bit(link.flags, WAIT_NUMBER); + } + + return prov->input(input, size); + + default: + return -EINVAL; + } +} + +static void prov_start(const u8_t *data) +{ + BT_DBG("Algorithm: 0x%02x", data[0]); + BT_DBG("Public Key: 0x%02x", data[1]); + BT_DBG("Auth Method: 0x%02x", data[2]); + BT_DBG("Auth Action: 0x%02x", data[3]); + BT_DBG("Auth Size: 0x%02x", data[4]); + + if (data[0] != PROV_ALG_P256) { + BT_ERR("%s, Unknown algorithm 0x%02x", __func__, data[0]); + prov_send_fail_msg(PROV_ERR_NVAL_FMT); + return; + } + + if (data[1] > 0x01) { + BT_ERR("%s, Invalid public key value: 0x%02x", __func__, data[1]); + prov_send_fail_msg(PROV_ERR_NVAL_FMT); + return; + } + + memcpy(&link.conf_inputs[12], data, 5); + + link.expect = PROV_PUB_KEY; + + /* If Provisioning Start PDU indicates that provisioner chooses + * OOB public key, then callback to the application layer to let + * users input public & private key pair. + */ + link.oob_pk_flag = data[1] ? true : false; + if (link.oob_pk_flag) { + prov->oob_pub_key_cb(); + } + + if (prov_auth(data[2], data[3], data[4]) < 0) { + BT_ERR("%s, Invalid authentication method: 0x%02x; " + "action: 0x%02x; size: 0x%02x", + __func__, data[2], data[3], data[4]); + prov_send_fail_msg(PROV_ERR_NVAL_FMT); + } +} + +static void send_confirm(void) +{ + PROV_BUF(cfm, 17); + + BT_DBG("ConfInputs[0] %s", bt_hex(link.conf_inputs, 64)); + BT_DBG("ConfInputs[64] %s", bt_hex(&link.conf_inputs[64], 64)); + BT_DBG("ConfInputs[128] %s", bt_hex(&link.conf_inputs[128], 17)); + + if (bt_mesh_prov_conf_salt(link.conf_inputs, link.conf_salt)) { + BT_ERR("%s, Unable to generate confirmation salt", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + BT_DBG("ConfirmationSalt: %s", bt_hex(link.conf_salt, 16)); + + if (bt_mesh_prov_conf_key(link.dhkey, link.conf_salt, link.conf_key)) { + BT_ERR("%s, Unable to generate confirmation key", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + BT_DBG("ConfirmationKey: %s", bt_hex(link.conf_key, 16)); + + if (bt_mesh_rand(link.rand, 16)) { + BT_ERR("%s, Unable to generate random number", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + BT_DBG("LocalRandom: %s", bt_hex(link.rand, 16)); + + prov_buf_init(&cfm, PROV_CONFIRM); + + if (bt_mesh_prov_conf(link.conf_key, link.rand, link.auth, + net_buf_simple_add(&cfm, 16))) { + BT_ERR("%s, Unable to generate confirmation value", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + if (prov_send(&cfm)) { + BT_ERR("%s, Unable to send Provisioning Confirm", __func__); + close_link(PROV_ERR_RESOURCES, CLOSE_REASON_FAILED); + return; + } + + link.expect = PROV_RANDOM; +} + +static void send_input_complete(void) +{ + PROV_BUF(buf, 1); + + prov_buf_init(&buf, PROV_INPUT_COMPLETE); + prov_send(&buf); +} + +int bt_mesh_input_number(u32_t num) +{ + BT_DBG("%u", num); + + if (!bt_mesh_atomic_test_and_clear_bit(link.flags, WAIT_NUMBER)) { + return -EINVAL; + } + + sys_put_be32(num, &link.auth[12]); + + send_input_complete(); + + if (!bt_mesh_atomic_test_bit(link.flags, HAVE_DHKEY)) { + return 0; + } + + if (bt_mesh_atomic_test_and_clear_bit(link.flags, SEND_CONFIRM)) { + send_confirm(); + } + + return 0; +} + +int bt_mesh_input_string(const char *str) +{ + BT_DBG("%s", str); + + if (!bt_mesh_atomic_test_and_clear_bit(link.flags, WAIT_STRING)) { + return -EINVAL; + } + + (void)memcpy(link.auth, str, prov->input_size); + + send_input_complete(); + + if (!bt_mesh_atomic_test_bit(link.flags, HAVE_DHKEY)) { + return 0; + } + + if (bt_mesh_atomic_test_and_clear_bit(link.flags, SEND_CONFIRM)) { + send_confirm(); + } + + return 0; +} + +static void prov_dh_key_cb(const u8_t key[32], const u8_t idx) +{ + BT_DBG("%p", key); + + if (!key) { + BT_ERR("%s, DHKey generation failed", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + sys_memcpy_swap(link.dhkey, key, 32); + + BT_DBG("DHkey: %s", bt_hex(link.dhkey, 32)); + + bt_mesh_atomic_set_bit(link.flags, HAVE_DHKEY); + + if (bt_mesh_atomic_test_bit(link.flags, WAIT_NUMBER) || + bt_mesh_atomic_test_bit(link.flags, WAIT_STRING)) { + return; + } + + if (bt_mesh_atomic_test_and_clear_bit(link.flags, SEND_CONFIRM)) { + send_confirm(); + } +} + +static void send_pub_key(void) +{ + PROV_BUF(buf, 65); + const u8_t *key; + + key = bt_mesh_pub_key_get(); + if (!key) { + BT_ERR("%s, No public key available", __func__); + close_link(PROV_ERR_RESOURCES, CLOSE_REASON_FAILED); + return; + } + + BT_DBG("Local Public Key: %s", bt_hex(key, 64)); + + prov_buf_init(&buf, PROV_PUB_KEY); + + /* Swap X and Y halves independently to big-endian */ + sys_memcpy_swap(net_buf_simple_add(&buf, 32), key, 32); + sys_memcpy_swap(net_buf_simple_add(&buf, 32), &key[32], 32); + + memcpy(&link.conf_inputs[81], &buf.data[1], 64); + + prov_send(&buf); + + /* Copy remote key in little-endian for bt_mesh_dh_key_gen(). + * X and Y halves are swapped independently. + */ + net_buf_simple_reset(&buf); + sys_memcpy_swap(buf.data, &link.conf_inputs[17], 32); + sys_memcpy_swap(&buf.data[32], &link.conf_inputs[49], 32); + + if (bt_mesh_dh_key_gen(buf.data, prov_dh_key_cb, 0)) { + BT_ERR("%s, Unable to generate DHKey", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + link.expect = PROV_CONFIRM; +} + +static int bt_mesh_calc_dh_key(void) +{ + NET_BUF_SIMPLE_DEFINE(buf, 64); + + /* Copy remote key in little-endian for bt_mesh_dh_key_gen(). + * X and Y halves are swapped independently. + */ + net_buf_simple_reset(&buf); + sys_memcpy_swap(buf.data, &link.conf_inputs[17], 32); + sys_memcpy_swap(&buf.data[32], &link.conf_inputs[49], 32); + + if (bt_mesh_dh_key_gen(buf.data, prov_dh_key_cb, 0)) { + BT_ERR("%s, Unable to generate DHKey", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return -EIO; + } + + return 0; +} + +int bt_mesh_set_oob_pub_key(const u8_t pub_key_x[32], const u8_t pub_key_y[32], + const u8_t pri_key[32]) +{ + if (!pub_key_x || !pub_key_y || !pri_key) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + /* Copy OOB public key in big-endian to Provisioning ConfirmationInputs, + * X and Y halves are swapped independently. + * And set input private key to mesh_bearer_adapt.c + */ + sys_memcpy_swap(&link.conf_inputs[81], pub_key_x, 32); + sys_memcpy_swap(&link.conf_inputs[81] + 32, pub_key_y, 32); + bt_mesh_set_private_key(pri_key); + + bt_mesh_atomic_set_bit(link.flags, OOB_PUB_KEY); + + /* If remote public key is not got, just return */ + if (!bt_mesh_atomic_test_bit(link.flags, REMOTE_PUB_KEY)) { + return 0; + } + + return bt_mesh_calc_dh_key(); +} + +static void prov_pub_key(const u8_t *data) +{ + BT_DBG("Remote Public Key: %s", bt_hex(data, 64)); + + /* BLE Mesh BQB test case MESH/NODE/PROV/UPD/BI-13-C needs to + * check the public key using the following rules: + * (1) X > 0, Y > 0 + * (2) X > 0, Y = 0 + * (3) X = 0, Y = 0 + */ + if (!bt_mesh_check_public_key(data)) { + BT_ERR("%s, Invalid public key", __func__); + prov_send_fail_msg(PROV_ERR_UNEXP_PDU); + return; + } + + memcpy(&link.conf_inputs[17], data, 64); + bt_mesh_atomic_set_bit(link.flags, REMOTE_PUB_KEY); + + if (!bt_mesh_pub_key_get()) { + /* Clear retransmit timer */ +#if defined(CONFIG_BLE_MESH_PB_ADV) + prov_clear_tx(); +#endif + BT_WARN("Waiting for a local public key"); + return; + } + + if (!link.oob_pk_flag) { + send_pub_key(); + } else { + link.expect = PROV_CONFIRM; + } +} + +static void prov_input_complete(const u8_t *data) +{ + BT_DBG("%s", __func__); +} + +static void prov_confirm(const u8_t *data) +{ + BT_DBG("Remote Confirm: %s", bt_hex(data, 16)); + + memcpy(link.conf, data, 16); + + if (!bt_mesh_atomic_test_bit(link.flags, HAVE_DHKEY)) { +#if defined(CONFIG_BLE_MESH_PB_ADV) + prov_clear_tx(); +#endif + bt_mesh_atomic_set_bit(link.flags, SEND_CONFIRM); + /* If using OOB public key and it has already got, calculates dhkey */ + if (link.oob_pk_flag && bt_mesh_atomic_test_bit(link.flags, OOB_PUB_KEY)) { + bt_mesh_calc_dh_key(); + } + } else { + send_confirm(); + } +} + +static void prov_random(const u8_t *data) +{ + PROV_BUF(rnd, 17); + u8_t conf_verify[16]; + + BT_DBG("Remote Random: %s", bt_hex(data, 16)); + + if (bt_mesh_prov_conf(link.conf_key, data, link.auth, conf_verify)) { + BT_ERR("%s, Unable to calculate confirmation verification", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + if (memcmp(conf_verify, link.conf, 16)) { + BT_ERR("%s, Invalid confirmation value", __func__); + BT_DBG("Received: %s", bt_hex(link.conf, 16)); + BT_DBG("Calculated: %s", bt_hex(conf_verify, 16)); + close_link(PROV_ERR_CFM_FAILED, CLOSE_REASON_FAILED); + return; + } + + prov_buf_init(&rnd, PROV_RANDOM); + net_buf_simple_add_mem(&rnd, link.rand, 16); + + if (prov_send(&rnd)) { + BT_ERR("%s, Failed to send Provisioning Random", __func__); + close_link(PROV_ERR_RESOURCES, CLOSE_REASON_FAILED); + return; + } + + if (bt_mesh_prov_salt(link.conf_salt, data, link.rand, + link.prov_salt)) { + BT_ERR("%s, Failed to generate provisioning salt", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + BT_DBG("ProvisioningSalt: %s", bt_hex(link.prov_salt, 16)); + + link.expect = PROV_DATA; +} + +static inline bool is_pb_gatt(void) +{ +#if defined(CONFIG_BLE_MESH_PB_GATT) + return !!link.conn; +#else + return false; +#endif +} + +static void prov_data(const u8_t *data) +{ + PROV_BUF(msg, 1); + u8_t session_key[16]; + u8_t nonce[13]; + u8_t dev_key[16]; + u8_t pdu[25]; + u8_t flags; + u32_t iv_index; + u16_t addr; + u16_t net_idx; + int err; + bool identity_enable; + + BT_DBG("%s", __func__); + + err = bt_mesh_session_key(link.dhkey, link.prov_salt, session_key); + if (err) { + BT_ERR("%s, Unable to generate session key", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + BT_DBG("SessionKey: %s", bt_hex(session_key, 16)); + + err = bt_mesh_prov_nonce(link.dhkey, link.prov_salt, nonce); + if (err) { + BT_ERR("%s, Unable to generate session nonce", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + BT_DBG("Nonce: %s", bt_hex(nonce, 13)); + + err = bt_mesh_prov_decrypt(session_key, nonce, data, pdu); + if (err) { + BT_ERR("%s, Unable to decrypt provisioning data", __func__); + close_link(PROV_ERR_DECRYPT, CLOSE_REASON_FAILED); + return; + } + + err = bt_mesh_dev_key(link.dhkey, link.prov_salt, dev_key); + if (err) { + BT_ERR("%s, Unable to generate device key", __func__); + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_FAILED); + return; + } + + BT_DBG("DevKey: %s", bt_hex(dev_key, 16)); + + net_idx = sys_get_be16(&pdu[16]); + flags = pdu[18]; + iv_index = sys_get_be32(&pdu[19]); + addr = sys_get_be16(&pdu[23]); + + BT_DBG("net_idx %u iv_index 0x%08x, addr 0x%04x", + net_idx, iv_index, addr); + + prov_buf_init(&msg, PROV_COMPLETE); + prov_send(&msg); + + /* Ignore any further PDUs on this link */ + link.expect = 0U; + + /* Store info, since bt_mesh_provision() will end up clearing it */ + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY)) { + identity_enable = is_pb_gatt(); + } else { + identity_enable = false; + } + + err = bt_mesh_provision(pdu, net_idx, flags, iv_index, addr, dev_key); + if (err) { + BT_ERR("Failed to provision (err %d)", err); + return; + } + + /* After PB-GATT provisioning we should start advertising + * using Node Identity. + */ + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY) && identity_enable) { + bt_mesh_proxy_identity_enable(); + } +} + +static void prov_complete(const u8_t *data) +{ + BT_DBG("%s", __func__); +} + +static void prov_failed(const u8_t *data) +{ + BT_WARN("Error: 0x%02x", data[0]); +} + +static const struct { + void (*func)(const u8_t *data); + u16_t len; +} prov_handlers[] = { + { prov_invite, 1 }, + { prov_capabilities, 11 }, + { prov_start, 5, }, + { prov_pub_key, 64 }, + { prov_input_complete, 0 }, + { prov_confirm, 16 }, + { prov_random, 16 }, + { prov_data, 33 }, + { prov_complete, 0 }, + { prov_failed, 1 }, +}; + +static void close_link(u8_t err, u8_t reason) +{ +#if defined(CONFIG_BLE_MESH_PB_GATT) + if (link.conn) { + bt_mesh_pb_gatt_close(link.conn); + return; + } +#endif + +#if defined(CONFIG_BLE_MESH_PB_ADV) + if (err) { + prov_send_fail_msg(err); + } + + link.rx.seg = 0U; + bearer_ctl_send(LINK_CLOSE, &reason, sizeof(reason)); +#endif + + reset_state(); +} + +/* Changed by Espressif, add provisioning timeout timer callback */ +static void prov_timeout(struct k_work *work) +{ + BT_DBG("%s", __func__); + + close_link(PROV_ERR_UNEXP_ERR, CLOSE_REASON_TIMEOUT); +} + +#if defined(CONFIG_BLE_MESH_PB_ADV) +static void prov_retransmit(struct k_work *work) +{ + s64_t timeout = TRANSACTION_TIMEOUT; + int i; + + BT_DBG("%s", __func__); + + if (!bt_mesh_atomic_test_bit(link.flags, LINK_ACTIVE)) { + BT_WARN("Link not active"); + return; + } + +#if defined(CONFIG_BLE_MESH_FAST_PROV) + /* When Provisioning Failed PDU is sent, 3s may be used here. */ + if (link.tx_pdu_type >= PROV_COMPLETE) { + timeout = K_SECONDS(30); + } +#endif + if (k_uptime_get() - link.tx.start > timeout) { + BT_WARN("Node timeout, giving up transaction"); + reset_link(); + return; + } + + for (i = 0; i < ARRAY_SIZE(link.tx.buf); i++) { + struct net_buf *buf = link.tx.buf[i]; + + if (!buf) { + break; + } + + if (BLE_MESH_ADV(buf)->busy) { + continue; + } + + BT_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (i + 1 < ARRAY_SIZE(link.tx.buf) && link.tx.buf[i + 1]) { + bt_mesh_adv_send(buf, NULL, NULL); + } else { + bt_mesh_adv_send(buf, &buf_sent_cb, NULL); + } + + } +} + +static void link_open(struct prov_rx *rx, struct net_buf_simple *buf) +{ + BT_DBG("len %u", buf->len); + + if (buf->len < 16) { + BT_ERR("%s, Too short bearer open message (len %u)", __func__, buf->len); + return; + } + + if (bt_mesh_atomic_test_bit(link.flags, LINK_ACTIVE)) { + /* Send another link ack if the provisioner missed the last */ + if (link.id == rx->link_id && link.expect == PROV_INVITE) { + BT_DBG("Resending link ack"); + bearer_ctl_send(LINK_ACK, NULL, 0); + } else { + BT_WARN("Ignoring bearer open: link already active"); + } + + return; + } + + if (memcmp(buf->data, prov->uuid, 16)) { + BT_DBG("Bearer open message not for us"); + return; + } + + if (prov->link_open) { + prov->link_open(BLE_MESH_PROV_ADV); + } + + link.id = rx->link_id; + bt_mesh_atomic_set_bit(link.flags, LINK_ACTIVE); + net_buf_simple_reset(link.rx.buf); + +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) + /* Add the link id into exceptional list */ + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_ADD, + BLE_MESH_EXCEP_INFO_MESH_LINK_ID, &link.id); +#endif + + bearer_ctl_send(LINK_ACK, NULL, 0); + + link.expect = PROV_INVITE; +} + +static void link_ack(struct prov_rx *rx, struct net_buf_simple *buf) +{ + BT_DBG("len %u", buf->len); +} + +static void link_close(struct prov_rx *rx, struct net_buf_simple *buf) +{ + BT_DBG("len %u", buf->len); + + reset_link(); +} + +static void gen_prov_ctl(struct prov_rx *rx, struct net_buf_simple *buf) +{ + BT_DBG("op 0x%02x len %u", BEARER_CTL(rx->gpc), buf->len); + + switch (BEARER_CTL(rx->gpc)) { + case LINK_OPEN: + link_open(rx, buf); + break; + case LINK_ACK: + if (!bt_mesh_atomic_test_bit(link.flags, LINK_ACTIVE)) { + return; + } + + link_ack(rx, buf); + break; + case LINK_CLOSE: + if (!bt_mesh_atomic_test_bit(link.flags, LINK_ACTIVE)) { + return; + } + + link_close(rx, buf); + break; + default: + BT_ERR("%s, Unknown bearer opcode: 0x%02x", __func__, BEARER_CTL(rx->gpc)); + return; + } +} + +static void prov_msg_recv(void) +{ + u8_t type = link.rx.buf->data[0]; + + BT_DBG("type 0x%02x len %u", type, link.rx.buf->len); + + if (!bt_mesh_fcs_check(link.rx.buf, link.rx.fcs)) { + BT_ERR("%s, Incorrect FCS", __func__); + return; + } + + gen_prov_ack_send(link.rx.id); + link.rx.prev_id = link.rx.id; + link.rx.id = 0U; + + if (type != PROV_FAILED && type != link.expect) { + BT_WARN("Unexpected msg 0x%02x != 0x%02x", type, link.expect); + prov_send_fail_msg(PROV_ERR_UNEXP_PDU); + return; + } + + if (type >= ARRAY_SIZE(prov_handlers)) { + BT_ERR("%s, Unknown provisioning PDU type 0x%02x", __func__, type); + close_link(PROV_ERR_NVAL_PDU, CLOSE_REASON_FAILED); + return; + } + + if (1 + prov_handlers[type].len != link.rx.buf->len) { + BT_ERR("%s, Invalid length %u for type 0x%02x", + __func__, link.rx.buf->len, type); + close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED); + return; + } + + /* Changed by Espressif, add provisioning timeout timer operations. + * When received a provisioning PDU, restart the 60s timer. + */ + if (bt_mesh_atomic_test_and_clear_bit(link.flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link.timeout); + } + if (!bt_mesh_atomic_test_and_set_bit(link.flags, TIMEOUT_START)) { + k_delayed_work_submit(&link.timeout, PROVISION_TIMEOUT); + } + + prov_handlers[type].func(&link.rx.buf->data[1]); +} + +static void gen_prov_cont(struct prov_rx *rx, struct net_buf_simple *buf) +{ + u8_t seg = CONT_SEG_INDEX(rx->gpc); + + BT_DBG("len %u, seg_index %u", buf->len, seg); + + if (!link.rx.seg && link.rx.prev_id == rx->xact_id) { + BT_WARN("Resending ack"); + gen_prov_ack_send(rx->xact_id); + return; + } + + /* An issue here: + * If the Transaction Start PDU is lost and the device receives corresponding + * Transaction Continuation PDU fist, this will trigger the following error - + * handling code to be executed and the device must wait for the timeout of + * PB-ADV provisioning procedure. Then another provisioning procedure can be + * started (link.rx.id will be reset after each provisioning PDU is received + * completely). This issue also exists in Provisioner. + */ + if (rx->xact_id != link.rx.id) { + BT_WARN("Data for unknown transaction (%u != %u)", + rx->xact_id, link.rx.id); + return; + } + + if (seg > link.rx.last_seg) { + BT_ERR("%s, Invalid segment index %u", __func__, seg); + close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED); + return; + } else if (seg == link.rx.last_seg) { + u8_t expect_len; + + expect_len = (link.rx.buf->len - 20U - + ((link.rx.last_seg - 1) * 23U)); + if (expect_len != buf->len) { + BT_ERR("%s, Incorrect last seg len: %u != %u", + __func__, expect_len, buf->len); + close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED); + return; + } + } + + if (!(link.rx.seg & BIT(seg))) { + BT_WARN("Ignoring already received segment"); + return; + } + + memcpy(XACT_SEG_DATA(seg), buf->data, buf->len); + XACT_SEG_RECV(seg); + + if (!link.rx.seg) { + prov_msg_recv(); + } +} + +static void gen_prov_ack(struct prov_rx *rx, struct net_buf_simple *buf) +{ + BT_DBG("len %u", buf->len); + + if (!link.tx.buf[0]) { + return; + } + + if (rx->xact_id == link.tx.id) { + prov_clear_tx(); + } +} + +static void gen_prov_start(struct prov_rx *rx, struct net_buf_simple *buf) +{ + if (link.rx.seg) { + BT_WARN("Got Start while there are unreceived segments"); + return; + } + + if (link.rx.prev_id == rx->xact_id) { + BT_WARN("Resending ack"); + gen_prov_ack_send(rx->xact_id); + return; + } + + link.rx.buf->len = net_buf_simple_pull_be16(buf); + link.rx.id = rx->xact_id; + link.rx.fcs = net_buf_simple_pull_u8(buf); + + BT_DBG("len %u last_seg %u total_len %u fcs 0x%02x", buf->len, + START_LAST_SEG(rx->gpc), link.rx.buf->len, link.rx.fcs); + + if (link.rx.buf->len < 1) { + BT_ERR("%s, Ignoring zero-length provisioning PDU", __func__); + close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED); + return; + } + + if (link.rx.buf->len > link.rx.buf->size) { + BT_ERR("%s, Too large provisioning PDU (%u bytes)", + __func__, link.rx.buf->len); + // close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED); + return; + } + + if (START_LAST_SEG(rx->gpc) > 0 && link.rx.buf->len <= 20U) { + BT_ERR("%s, Too small total length for multi-segment PDU", __func__); + close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED); + return; + } + + link.rx.seg = (1 << (START_LAST_SEG(rx->gpc) + 1)) - 1; + link.rx.last_seg = START_LAST_SEG(rx->gpc); + memcpy(link.rx.buf->data, buf->data, buf->len); + XACT_SEG_RECV(0); + + if (!link.rx.seg) { + prov_msg_recv(); + } +} + +static const struct { + void (*func)(struct prov_rx *rx, struct net_buf_simple *buf); + bool require_link; + u8_t min_len; +} gen_prov[] = { + { gen_prov_start, true, 3 }, + { gen_prov_ack, true, 0 }, + { gen_prov_cont, true, 0 }, + { gen_prov_ctl, false, 0 }, +}; + +static void gen_prov_recv(struct prov_rx *rx, struct net_buf_simple *buf) +{ + if (buf->len < gen_prov[GPCF(rx->gpc)].min_len) { + BT_ERR("%s, Too short GPC message type %u", __func__, GPCF(rx->gpc)); + return; + } + + if (!bt_mesh_atomic_test_bit(link.flags, LINK_ACTIVE) && + gen_prov[GPCF(rx->gpc)].require_link) { + BT_DBG("Ignoring message that requires active link"); + return; + } + + gen_prov[GPCF(rx->gpc)].func(rx, buf); +} + +void bt_mesh_pb_adv_recv(struct net_buf_simple *buf) +{ + struct prov_rx rx; + + if (!bt_prov_active() && bt_mesh_is_provisioned()) { + BT_DBG("Ignoring provisioning PDU - already provisioned"); + return; + } + + if (buf->len < 6) { + BT_WARN("Too short provisioning packet (len %u)", buf->len); + return; + } + + rx.link_id = net_buf_simple_pull_be32(buf); + rx.xact_id = net_buf_simple_pull_u8(buf); + rx.gpc = net_buf_simple_pull_u8(buf); + + BT_DBG("link_id 0x%08x xact_id %u", rx.link_id, rx.xact_id); + + if (bt_mesh_atomic_test_bit(link.flags, LINK_ACTIVE) && link.id != rx.link_id) { + BT_DBG("Ignoring mesh beacon for unknown link"); + return; + } + + gen_prov_recv(&rx, buf); +} +#endif /* CONFIG_BLE_MESH_PB_ADV */ + +#if defined(CONFIG_BLE_MESH_PB_GATT) +int bt_mesh_pb_gatt_recv(struct bt_mesh_conn *conn, struct net_buf_simple *buf) +{ + u8_t type; + + BT_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (link.conn != conn) { + BT_WARN("Data for unexpected connection"); + return -ENOTCONN; + } + + if (buf->len < 1) { + BT_WARN("Too short provisioning packet (len %u)", buf->len); + return -EINVAL; + } + + type = net_buf_simple_pull_u8(buf); + if (type != PROV_FAILED && type != link.expect) { + BT_WARN("Unexpected msg 0x%02x != 0x%02x", type, link.expect); + prov_send_fail_msg(PROV_ERR_UNEXP_PDU); + return -EINVAL; + } + + if (type >= ARRAY_SIZE(prov_handlers)) { + BT_ERR("%s, Unknown provisioning PDU type 0x%02x", __func__, type); + return -EINVAL; + } + + if (prov_handlers[type].len != buf->len) { + BT_ERR("%s, Invalid length %u for type 0x%02x", __func__, buf->len, type); + return -EINVAL; + } + + /* Changed by Espressif, add provisioning timeout timer operations. + * When received a provisioning PDU, restart the 60s timer. + */ + if (bt_mesh_atomic_test_and_clear_bit(link.flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link.timeout); + } + if (!bt_mesh_atomic_test_and_set_bit(link.flags, TIMEOUT_START)) { + k_delayed_work_submit(&link.timeout, PROVISION_TIMEOUT); + } + + prov_handlers[type].func(buf->data); + + return 0; +} + +int bt_mesh_pb_gatt_open(struct bt_mesh_conn *conn) +{ + BT_DBG("conn %p", conn); + + if (bt_mesh_atomic_test_and_set_bit(link.flags, LINK_ACTIVE)) { + return -EBUSY; + } + + link.conn = bt_mesh_conn_ref(conn); + link.expect = PROV_INVITE; + + if (prov->link_open) { + prov->link_open(BLE_MESH_PROV_GATT); + } + + return 0; +} + +int bt_mesh_pb_gatt_close(struct bt_mesh_conn *conn) +{ + BT_DBG("conn %p", conn); + + if (link.conn != conn) { + BT_ERR("%s, Not connected", __func__); + return -ENOTCONN; + } + + if (prov->link_close) { + prov->link_close(BLE_MESH_PROV_GATT); + } + + reset_state(); + + return 0; +} +#endif /* CONFIG_BLE_MESH_PB_GATT */ + +const struct bt_mesh_prov *bt_mesh_prov_get(void) +{ + return prov; +} + +bool bt_prov_active(void) +{ + return bt_mesh_atomic_test_bit(link.flags, LINK_ACTIVE); +} + +int bt_mesh_prov_init(const struct bt_mesh_prov *prov_info) +{ + const u8_t *key = NULL; + + if (!prov_info) { + BT_ERR("%s, No provisioning context provided", __func__); + return -EINVAL; + } + + /* Changed by Espressif. Use micro-ecc to generate public key now. */ + key = bt_mesh_pub_key_get(); + if (!key) { + BT_ERR("%s, Failed to generate public key", __func__); + return -EIO; + } + + prov = prov_info; + +#if defined(CONFIG_BLE_MESH_PB_ADV) + k_delayed_work_init(&link.tx.retransmit, prov_retransmit); +#endif + + /* Changed by Espressif, add provisioning timeout timer init */ + k_delayed_work_init(&link.timeout, prov_timeout); + + reset_state(); + + return 0; +} + +void bt_mesh_prov_complete(u16_t net_idx, u16_t addr, u8_t flags, u32_t iv_index) +{ + if (prov->complete) { + prov->complete(net_idx, addr, flags, iv_index); + } +} + +void bt_mesh_prov_reset(void) +{ + if (prov->reset) { + prov->reset(); + } +} + +#endif /* CONFIG_BLE_MESH_NODE */ diff --git a/components/bt/ble_mesh/mesh_core/prov.h b/components/bt/ble_mesh/mesh_core/prov.h new file mode 100644 index 0000000000..f96ed7707f --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/prov.h @@ -0,0 +1,34 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _PROV_H_ +#define _PROV_H_ + +#include "mesh_main.h" +#include "mesh_buf.h" +#include "mesh_bearer_adapt.h" + +void bt_mesh_pb_adv_recv(struct net_buf_simple *buf); + +bool bt_prov_active(void); + +int bt_mesh_pb_gatt_open(struct bt_mesh_conn *conn); +int bt_mesh_pb_gatt_close(struct bt_mesh_conn *conn); +int bt_mesh_pb_gatt_recv(struct bt_mesh_conn *conn, struct net_buf_simple *buf); + +int bt_mesh_set_oob_pub_key(const u8_t pub_key_x[32], const u8_t pub_key_y[32], + const u8_t pri_key[32]); + +const struct bt_mesh_prov *bt_mesh_prov_get(void); + +int bt_mesh_prov_init(const struct bt_mesh_prov *prov); + +void bt_mesh_prov_complete(u16_t net_idx, u16_t addr, u8_t flags, u32_t iv_index); +void bt_mesh_prov_reset(void); + +#endif /* _PROV_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/provisioner_beacon.c b/components/bt/ble_mesh/mesh_core/provisioner_beacon.c new file mode 100644 index 0000000000..dfe4e39fa0 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/provisioner_beacon.c @@ -0,0 +1,71 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_BEACON) + +#include "mesh_util.h" +#include "mesh_buf.h" +#include "mesh_main.h" +#include "mesh_trace.h" + +#include "adv.h" +#include "mesh.h" +#include "net.h" +#include "prov.h" +#include "crypto.h" +#include "beacon.h" +#include "foundation.h" +#include "provisioner_prov.h" + +#define BEACON_TYPE_UNPROVISIONED 0x00 +#define BEACON_TYPE_SECURE 0x01 + +#if CONFIG_BLE_MESH_PROVISIONER + +static void provisioner_secure_beacon_recv(struct net_buf_simple *buf) +{ + // TODO: Provisioner receive and handle Secure Network Beacon +} + +void provisioner_beacon_recv(struct net_buf_simple *buf) +{ + u8_t type; + + BT_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (buf->len < 1) { + BT_ERR("%s, Too short beacon", __func__); + return; + } + + type = net_buf_simple_pull_u8(buf); + switch (type) { + case BEACON_TYPE_UNPROVISIONED: + BT_DBG("Unprovisioned device beacon received"); + provisioner_unprov_beacon_recv(buf); + break; + case BEACON_TYPE_SECURE: + provisioner_secure_beacon_recv(buf); + break; + default: + BT_DBG("%s, Unknown beacon type 0x%02x", __func__, type); + break; + } +} + +#endif /* CONFIG_BLE_MESH_PROVISIONER */ diff --git a/components/bt/ble_mesh/mesh_core/provisioner_beacon.h b/components/bt/ble_mesh/mesh_core/provisioner_beacon.h new file mode 100644 index 0000000000..0b3cfae6e6 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/provisioner_beacon.h @@ -0,0 +1,20 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _PROVISIONER_BEACON_H_ +#define _PROVISIONER_BEACON_H_ + +void provisioner_beacon_recv(struct net_buf_simple *buf); + +#endif /* _PROVISIONER_BEACON_H_ */ \ No newline at end of file diff --git a/components/bt/ble_mesh/mesh_core/provisioner_main.c b/components/bt/ble_mesh/mesh_core/provisioner_main.c new file mode 100644 index 0000000000..d78cc1484a --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/provisioner_main.c @@ -0,0 +1,1278 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "sdkconfig.h" +#include "osi/allocator.h" + +#include "mesh_util.h" +#include "mesh_main.h" +#include "mesh_trace.h" +#include "mesh_bearer_adapt.h" + +#include "mesh.h" +#include "crypto.h" +#include "adv.h" +#include "net.h" +#include "access.h" + +#include "provisioner_prov.h" +#include "provisioner_proxy.h" +#include "provisioner_main.h" + +#if CONFIG_BLE_MESH_PROVISIONER + +static const struct bt_mesh_prov *prov; +static const struct bt_mesh_comp *comp; + +static struct bt_mesh_node_t *mesh_nodes[CONFIG_BLE_MESH_MAX_STORED_NODES]; +static u32_t mesh_node_count; + +static bool prov_upper_init = false; + +static int provisioner_index_check(int node_index) +{ + struct bt_mesh_node_t *node = NULL; + + BT_DBG("%s", __func__); + + if (node_index < 0) { + BT_ERR("%s, Invalid node index %d", __func__, node_index); + return -EINVAL; + } + + if (node_index >= ARRAY_SIZE(mesh_nodes)) { + BT_ERR("%s, Too big node index", __func__); + return -EINVAL; + } + + node = mesh_nodes[node_index]; + if (!node) { + BT_ERR("%s, Node is not found", __func__); + return -EINVAL; + } + + return 0; +} + +int provisioner_node_provision(int node_index, const u8_t uuid[16], u16_t oob_info, + u16_t unicast_addr, u8_t element_num, u16_t net_idx, + u8_t flags, u32_t iv_index, const u8_t dev_key[16]) +{ + struct bt_mesh_node_t *node = NULL; + + BT_DBG("%s", __func__); + + if (mesh_node_count >= ARRAY_SIZE(mesh_nodes)) { + BT_ERR("%s, Node queue is full", __func__); + return -ENOMEM; + } + + if (node_index >= ARRAY_SIZE(mesh_nodes) || !uuid || !dev_key) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + node = osi_calloc(sizeof(struct bt_mesh_node_t)); + if (!node) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + BT_DBG("node_index: 0x%x, unicast_addr: 0x%x, element_num: 0x%x, net_idx: 0x%x", + node_index, unicast_addr, element_num, net_idx); + BT_DBG("dev_uuid: %s", bt_hex(uuid, 16)); + BT_DBG("dev_key: %s", bt_hex(dev_key, 16)); + + mesh_nodes[node_index] = node; + + memcpy(node->dev_uuid, uuid, 16); + node->oob_info = oob_info; + node->unicast_addr = unicast_addr; + node->element_num = element_num; + node->net_idx = net_idx; + node->flags = flags; + node->iv_index = iv_index; + memcpy(node->dev_key, dev_key, 16); + + mesh_node_count++; + + return 0; +} + +int provisioner_node_reset(int node_index) +{ + struct bt_mesh_node_t *node = NULL; + struct bt_mesh_rpl *rpl = NULL; + int i; + + BT_DBG("%s, reset node %d", __func__, node_index); + + if (!mesh_node_count) { + BT_ERR("%s, Node queue is empty", __func__); + return -ENODEV; + } + + if (provisioner_index_check(node_index)) { + BT_ERR("%s, Failed to check node index", __func__); + return -EINVAL; + } + + node = mesh_nodes[node_index]; + + /* Reset corresponding rpl when reset the node */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) { + rpl = &bt_mesh.rpl[i]; + if (rpl->src >= node->unicast_addr && + rpl->src < node->unicast_addr + node->element_num) { + memset(rpl, 0, sizeof(struct bt_mesh_rpl)); + } + } + + osi_free(mesh_nodes[node_index]); + mesh_nodes[node_index] = NULL; + + mesh_node_count--; + + return 0; +} + +int provisioner_upper_reset_all_nodes(void) +{ + int i, err; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + err = provisioner_node_reset(i); + if (err == -ENODEV) { + return 0; + } + } + + return 0; +} + +/** For Provisioner, we use the same data structure + * (like, struct bt_mesh_subnet, etc.) for netkey + * & appkey because if not we need to change a lot + * of APIs. + */ +int provisioner_upper_init(void) +{ + struct bt_mesh_subnet *sub = NULL; + u8_t p_key[16] = {0}; + + BT_DBG("%s", __func__); + + if (prov_upper_init) { + return 0; + } + + comp = bt_mesh_comp_get(); + if (!comp) { + BT_ERR("%s, NULL composition data", __func__); + return -EINVAL; + } + + prov = provisioner_get_prov_info(); + if (!prov) { + BT_ERR("%s, NULL provisioning context", __func__); + return -EINVAL; + } + + /* If the device only acts as a Provisioner, need to initialize + each element's address. */ + bt_mesh_comp_provision(prov->prov_unicast_addr); + + /* Generate the primary netkey */ + if (bt_mesh_rand(p_key, 16)) { + BT_ERR("%s, Failed to generate Primary NetKey", __func__); + return -EIO; + } + + sub = osi_calloc(sizeof(struct bt_mesh_subnet)); + if (!sub) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + sub->kr_flag = BLE_MESH_KEY_REFRESH(prov->flags); + if (sub->kr_flag) { + if (bt_mesh_net_keys_create(&sub->keys[1], p_key)) { + BT_ERR("%s, Failed to generate net-related keys", __func__); + osi_free(sub); + return -EIO; + } + sub->kr_phase = BLE_MESH_KR_PHASE_2; + } else { + /* Currently provisioner only use keys[0] */ + if (bt_mesh_net_keys_create(&sub->keys[0], p_key)) { + BT_ERR("%s, Failed to create net-related keys", __func__); + osi_free(sub); + return -EIO; + } + sub->kr_phase = BLE_MESH_KR_NORMAL; + } + sub->net_idx = BLE_MESH_KEY_PRIMARY; + sub->node_id = BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED; + + bt_mesh.p_sub[0] = sub; + + /* Dynamically added appkey & netkey will use these key_idx */ + bt_mesh.p_app_idx_next = 0x0000; + bt_mesh.p_net_idx_next = 0x0001; + + /* In this function, we use the values of struct bt_mesh_prov + which has been initialized in the application layer */ + bt_mesh.iv_index = prov->iv_index; + bt_mesh_atomic_set_bit_to(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS, + BLE_MESH_IV_UPDATE(prov->flags)); + + /* Set minimum required hours, since the 96-hour minimum requirement + * doesn't apply straight after provisioning (since we can't know how + * long has actually passed since the network changed its state). + * This operation is the same with node initialization. + */ + bt_mesh.ivu_duration = BLE_MESH_IVU_MIN_HOURS; + + prov_upper_init = true; + + BT_DBG("kr_flag: %d, kr_phase: %d, net_idx: 0x%02x, node_id %d", + sub->kr_flag, sub->kr_phase, sub->net_idx, sub->node_id); + BT_DBG("netkey: %s, nid: 0x%x", bt_hex(sub->keys[0].net, 16), sub->keys[0].nid); + BT_DBG("enckey: %s", bt_hex(sub->keys[0].enc, 16)); + BT_DBG("network id: %s", bt_hex(sub->keys[0].net_id, 8)); + BT_DBG("identity: %s", bt_hex(sub->keys[0].identity, 16)); + BT_DBG("privacy: %s", bt_hex(sub->keys[0].privacy, 16)); + BT_DBG("beacon: %s", bt_hex(sub->keys[0].beacon, 16)); + + return 0; +} + +/* The following APIs are for provisioner upper layers internal use */ + +const u8_t *provisioner_net_key_get(u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + if (!sub || (sub->net_idx != net_idx)) { + continue; + } + if (sub->kr_flag) { + return sub->keys[1].net; + } + return sub->keys[0].net; + } + + return NULL; +} + +struct bt_mesh_subnet *provisioner_subnet_get(u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + int i; + + BT_DBG("%s", __func__); + + if (net_idx == BLE_MESH_KEY_ANY) { + return bt_mesh.p_sub[0]; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + if (!sub || (sub->net_idx != net_idx)) { + continue; + } + return sub; + } + + return NULL; +} + +bool provisioner_check_msg_dst_addr(u16_t dst_addr) +{ + struct bt_mesh_node_t *node = NULL; + int i; + + BT_DBG("%s", __func__); + + if (!BLE_MESH_ADDR_IS_UNICAST(dst_addr)) { + return true; + } + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (node && dst_addr >= node->unicast_addr && + dst_addr < node->unicast_addr + node->element_num) { + return true; + } + } + + return false; +} + +const u8_t *provisioner_get_device_key(u16_t dst_addr) +{ + /* Device key is only used to encrypt configuration messages. + * Configuration model shall only be supported by the primary + * element which uses the primary unicast address. + */ + struct bt_mesh_node_t *node = NULL; + int i; + + BT_DBG("%s", __func__); + + if (!BLE_MESH_ADDR_IS_UNICAST(dst_addr)) { + BT_ERR("%s, Not a unicast address 0x%04x", __func__, dst_addr); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (node && node->unicast_addr == dst_addr) { + return node->dev_key; + } + } + + return NULL; +} + +struct bt_mesh_app_key *provisioner_app_key_find(u16_t app_idx) +{ + struct bt_mesh_app_key *key = NULL; + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + key = bt_mesh.p_app_keys[i]; + if (!key) { + continue; + } + if (key->net_idx != BLE_MESH_KEY_UNUSED && + key->app_idx == app_idx) { + return key; + } + } + + return NULL; +} + +u32_t provisioner_get_prov_node_count(void) +{ + return mesh_node_count; +} + +/* The following APIs are for provisioner application use */ + +#if 0 +static int bt_mesh_provisioner_set_kr_flag(u16_t net_idx, bool kr_flag) +{ + struct bt_mesh_subnet *sub = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + if (!sub || (sub->net_idx != net_idx)) { + continue; + } + sub->kr_flag = kr_flag; + break; + } + if (i == ARRAY_SIZE(bt_mesh.p_sub)) { + return -ENODEV; + } + + /* TODO: When kr_flag is changed, provisioner may need + * to change the netkey of the subnet and update + * corresponding appkey. + */ + + return 0; +} + +static void bt_mesh_provisioner_set_iv_index(u32_t iv_index) +{ + bt_mesh.iv_index = iv_index; + + /* TODO: When iv_index is changed, provisioner may need to + * start iv update procedure. And the ivu_initiator + * & iv_update flags may also need to be set. + */ +} +#endif + +int bt_mesh_provisioner_store_node_info(struct bt_mesh_node_t *node_info) +{ + struct bt_mesh_node_t *node = NULL; + int i; + + if (!node_info) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + /* Check if the device uuid already exists */ + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (node && !memcmp(node->dev_uuid, node_info->dev_uuid, 16)) { + BT_WARN("%s, Node info already exists", __func__); + return -EEXIST; + } + } + + /* 0 ~ (CONFIG_BLE_MESH_MAX_PROV_NODES-1) are left for self-provisioned nodes */ + for (i = CONFIG_BLE_MESH_MAX_PROV_NODES; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (!node) { + node = osi_calloc(sizeof(struct bt_mesh_node_t)); + if (!node) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + memcpy(node, node_info, sizeof(struct bt_mesh_node_t)); + mesh_nodes[i] = node; + mesh_node_count++; + return 0; + } + } + + BT_ERR("%s, Node info is full", __func__); + return -ENOMEM; +} + +int bt_mesh_provisioner_get_all_node_unicast_addr(struct net_buf_simple *buf) +{ + struct bt_mesh_node_t *node = NULL; + int i; + + if (!buf) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (!node || !BLE_MESH_ADDR_IS_UNICAST(node->unicast_addr)) { + continue; + } + net_buf_simple_add_le16(buf, node->unicast_addr); + } + + return 0; +} + +int bt_mesh_provisioner_set_node_name(int node_index, const char *name) +{ + size_t length, name_len; + int i; + + BT_DBG("%s", __func__); + + if (!name) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (provisioner_index_check(node_index)) { + BT_ERR("%s, Failed to check node index", __func__); + return -EINVAL; + } + + BT_DBG("name len is %d, name is %s", strlen(name), name); + + length = (strlen(name) <= MESH_NAME_SIZE) ? strlen(name) : MESH_NAME_SIZE; + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + if (!mesh_nodes[i] || !mesh_nodes[i]->node_name) { + continue; + } + name_len = strlen(mesh_nodes[i]->node_name); + if (length != name_len) { + continue; + } + if (!strncmp(mesh_nodes[i]->node_name, name, length)) { + BT_WARN("%s, Name %s already exists", __func__, name); + return -EEXIST; + } + } + + strncpy(mesh_nodes[node_index]->node_name, name, length); + + return 0; +} + +const char *bt_mesh_provisioner_get_node_name(int node_index) +{ + BT_DBG("%s", __func__); + + if (provisioner_index_check(node_index)) { + BT_ERR("%s, Failed to check node index", __func__); + return NULL; + } + + return mesh_nodes[node_index]->node_name; +} + +int bt_mesh_provisioner_get_node_index(const char *name) +{ + size_t length, name_len; + int i; + + BT_DBG("%s", __func__); + + if (!name) { + return -EINVAL; + } + + length = (strlen(name) <= MESH_NAME_SIZE) ? strlen(name) : MESH_NAME_SIZE; + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + if (!mesh_nodes[i] || !mesh_nodes[i]->node_name) { + continue; + } + name_len = strlen(mesh_nodes[i]->node_name); + if (length != name_len) { + continue; + } + if (!strncmp(mesh_nodes[i]->node_name, name, length)) { + return i; + } + } + + return -ENODEV; +} + +struct bt_mesh_node_t *bt_mesh_provisioner_get_node_info(u16_t unicast_addr) +{ + struct bt_mesh_node_t *node = NULL; + int i; + + BT_DBG("%s", __func__); + + if (!BLE_MESH_ADDR_IS_UNICAST(unicast_addr)) { + BT_ERR("%s, Not a unicast address 0x%04x", __func__, unicast_addr); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (!node) { + continue; + } + if (unicast_addr >= node->unicast_addr && + unicast_addr < (node->unicast_addr + node->element_num)) { + return node; + } + } + + return NULL; +} + +u32_t bt_mesh_provisioner_get_net_key_count(void) +{ + return ARRAY_SIZE(bt_mesh.p_sub); +} + +u32_t bt_mesh_provisioner_get_app_key_count(void) +{ + return ARRAY_SIZE(bt_mesh.p_app_keys); +} + +static int provisioner_check_app_key(const u8_t app_key[16], u16_t *app_idx) +{ + struct bt_mesh_app_key *key = NULL; + int i; + + if (!app_key) { + return 0; + } + + /* Check if app_key is already existed */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + key = bt_mesh.p_app_keys[i]; + if (key && (!memcmp(key->keys[0].val, app_key, 16) || + !memcmp(key->keys[1].val, app_key, 16))) { + *app_idx = key->app_idx; + return -EEXIST; + } + } + + return 0; +} + +static int provisioner_check_app_idx(u16_t app_idx, bool exist) +{ + struct bt_mesh_app_key *key = NULL; + int i; + + if (exist) { + /* Check if app_idx is already existed */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + key = bt_mesh.p_app_keys[i]; + if (key && (key->app_idx == app_idx)) { + return -EEXIST; + } + } + return 0; + } + + /* Check if app_idx is not existed */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + key = bt_mesh.p_app_keys[i]; + if (key && (key->app_idx == app_idx)) { + return 0; + } + } + + return -ENODEV; +} + +static int provisioner_check_app_key_full(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + if (!bt_mesh.p_app_keys[i]) { + return i; + } + } + + return -ENOMEM; +} + +static int provisioner_check_net_key(const u8_t net_key[16], u16_t *net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + int i; + + if (!net_key) { + return 0; + } + + /* Check if net_key is already existed */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + if (sub && (!memcmp(sub->keys[0].net, net_key, 16) || + !memcmp(sub->keys[1].net, net_key, 16))) { + *net_idx = sub->net_idx; + return -EEXIST; + } + } + + return 0; +} + +static int provisioner_check_net_idx(u16_t net_idx, bool exist) +{ + struct bt_mesh_subnet *sub = NULL; + int i; + + if (exist) { + /* Check if net_idx is already existed */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + if (sub && (sub->net_idx == net_idx)) { + return -EEXIST; + } + } + return 0; + } + + /* Check if net_idx is not existed */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + if (sub && (sub->net_idx == net_idx)) { + return 0; + } + } + + return -ENODEV; +} + +static int provisioner_check_net_key_full(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + if (!bt_mesh.p_sub[i]) { + return i; + } + } + + return -ENOMEM; +} + +int bt_mesh_provisioner_local_app_key_add(const u8_t app_key[16], u16_t net_idx, u16_t *app_idx) +{ + struct bt_mesh_app_key *key = NULL; + struct bt_mesh_app_keys *keys = NULL; + u8_t p_key[16] = {0}; + int add = -1; + + if (bt_mesh.p_app_idx_next >= 0x1000) { + BT_ERR("%s, No AppKey Index available", __func__); + return -EIO; + } + + if (!app_idx || (*app_idx != 0xFFFF && *app_idx >= 0x1000)) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + /* Check if the same application key already exists */ + if (provisioner_check_app_key(app_key, app_idx)) { + BT_WARN("%s, AppKey already exists, AppKey Index updated", __func__); + return 0; + } + + /* Check if the net_idx exists */ + if (provisioner_check_net_idx(net_idx, false)) { + BT_ERR("%s, NetKey Index does not exist", __func__); + return -ENODEV; + } + + /* Check if the same app_idx already exists */ + if (provisioner_check_app_idx(*app_idx, true)) { + BT_ERR("%s, AppKey Index already exists", __func__); + return -EEXIST; + } + + add = provisioner_check_app_key_full(); + if (add < 0) { + BT_ERR("%s, AppKey queue is full", __func__); + return -ENOMEM; + } + + if (!app_key) { + if (bt_mesh_rand(p_key, 16)) { + BT_ERR("%s, Failed to generate AppKey", __func__); + return -EIO; + } + } else { + memcpy(p_key, app_key, 16); + } + + key = osi_calloc(sizeof(struct bt_mesh_app_key)); + if (!key) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + keys = &key->keys[0]; + if (bt_mesh_app_id(p_key, &keys->id)) { + BT_ERR("%s, Failed to generate AID", __func__); + osi_free(key); + return -EIO; + } + + memcpy(keys->val, p_key, 16); + key->net_idx = net_idx; + if (*app_idx != 0xFFFF) { + key->app_idx = *app_idx; + } else { + key->app_idx = bt_mesh.p_app_idx_next; + while (1) { + if (provisioner_check_app_idx(key->app_idx, true)) { + key->app_idx = (++bt_mesh.p_app_idx_next); + if (key->app_idx >= 0x1000) { + BT_ERR("%s, No AppKey Index available", __func__); + osi_free(key); + return -EIO; + } + } else { + break; + } + } + *app_idx = key->app_idx; + } + key->updated = false; + + bt_mesh.p_app_keys[add] = key; + + return 0; +} + +const u8_t *bt_mesh_provisioner_local_app_key_get(u16_t net_idx, u16_t app_idx) +{ + struct bt_mesh_app_key *key = NULL; + int i; + + BT_DBG("%s", __func__); + + if (provisioner_check_net_idx(net_idx, false)) { + BT_ERR("%s, NetKey Index does not exist", __func__); + return NULL; + } + + if (provisioner_check_app_idx(app_idx, false)) { + BT_ERR("%s, AppKey Index does not exist", __func__); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + key = bt_mesh.p_app_keys[i]; + if (key && key->net_idx == net_idx && + key->app_idx == app_idx) { + if (key->updated) { + return key->keys[1].val; + } + return key->keys[0].val; + } + } + + return NULL; +} + +int bt_mesh_provisioner_local_app_key_delete(u16_t net_idx, u16_t app_idx) +{ + struct bt_mesh_app_key *key = NULL; + int i; + + BT_DBG("%s", __func__); + + if (provisioner_check_net_idx(net_idx, false)) { + BT_ERR("%s, NetKey Index does not exist", __func__); + return -ENODEV; + } + + if (provisioner_check_app_idx(app_idx, false)) { + BT_ERR("%s, AppKey Index does not exist", __func__); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + key = bt_mesh.p_app_keys[i]; + if (key && key->net_idx == net_idx && + key->app_idx == app_idx) { + osi_free(bt_mesh.p_app_keys[i]); + bt_mesh.p_app_keys[i] = NULL; + return 0; + } + } + + /* Shall never reach here */ + return -ENODEV; +} + +int bt_mesh_provisioner_local_net_key_add(const u8_t net_key[16], u16_t *net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + u8_t p_key[16] = {0}; + int add = -1; + + if (bt_mesh.p_net_idx_next >= 0x1000) { + BT_ERR("%s, No NetKey Index available", __func__); + return -EIO; + } + + if (!net_idx || (*net_idx != 0xFFFF && *net_idx >= 0x1000)) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + /* Check if the same network key already exists */ + if (provisioner_check_net_key(net_key, net_idx)) { + BT_WARN("%s, NetKey already exists, NetKey Index updated", __func__); + return 0; + } + + /* Check if the same net_idx already exists */ + if (provisioner_check_net_idx(*net_idx, true)) { + BT_ERR("%s, NetKey Index already exists", __func__); + return -EEXIST; + } + + add = provisioner_check_net_key_full(); + if (add < 0) { + BT_ERR("%s, NetKey queue is full", __func__); + return -ENOMEM; + } + + if (!net_key) { + if (bt_mesh_rand(p_key, 16)) { + BT_ERR("%s, Failed to generate NetKey", __func__); + return -EIO; + } + } else { + memcpy(p_key, net_key, 16); + } + + sub = osi_calloc(sizeof(struct bt_mesh_subnet)); + if (!sub) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + if (bt_mesh_net_keys_create(&sub->keys[0], p_key)) { + BT_ERR("%s, Failed to generate NID", __func__); + osi_free(sub); + return -EIO; + } + + if (*net_idx != 0xFFFF) { + sub->net_idx = *net_idx; + } else { + sub->net_idx = bt_mesh.p_net_idx_next; + while (1) { + if (provisioner_check_net_idx(sub->net_idx, true)) { + sub->net_idx = (++bt_mesh.p_net_idx_next); + if (sub->net_idx >= 0x1000) { + BT_ERR("%s, No NetKey Index available", __func__); + osi_free(sub); + return -EIO; + } + } else { + break; + } + } + *net_idx = sub->net_idx; + } + sub->kr_phase = BLE_MESH_KR_NORMAL; + sub->kr_flag = false; + sub->node_id = BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED; + + bt_mesh.p_sub[add] = sub; + + return 0; +} + +const u8_t *bt_mesh_provisioner_local_net_key_get(u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + int i; + + BT_DBG("%s", __func__); + + if (provisioner_check_net_idx(net_idx, false)) { + BT_ERR("%s, NetKey Index does not exist", __func__); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + if (sub && sub->net_idx == net_idx) { + if (sub->kr_flag) { + return sub->keys[1].net; + } + return sub->keys[0].net; + } + } + + return NULL; +} + +int bt_mesh_provisioner_local_net_key_delete(u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + int i; + + BT_DBG("%s", __func__); + + if (provisioner_check_net_idx(net_idx, false)) { + BT_ERR("%s, NetKey Index does not exist", __func__); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + if (sub && sub->net_idx == net_idx) { + osi_free(bt_mesh.p_sub[i]); + bt_mesh.p_sub[i] = NULL; + return 0; + } + } + + /* Shall never reach here */ + return -ENODEV; +} + +int bt_mesh_provisioner_get_own_unicast_addr(u16_t *addr, u8_t *elem_num) +{ + if (!addr || !elem_num || !prov || !comp) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + *addr = prov->prov_unicast_addr; + *elem_num = comp->elem_count; + + return 0; +} + +int bt_mesh_provisioner_bind_local_model_app_idx(u16_t elem_addr, u16_t mod_id, + u16_t cid, u16_t app_idx) +{ + struct bt_mesh_elem *elem = NULL; + struct bt_mesh_model *model = NULL; + int i; + + if (!comp) { + BT_ERR("%s, NULL composition data", __func__); + return -EINVAL; + } + + for (i = 0; i < comp->elem_count; i++) { + elem = &comp->elem[i]; + if (elem->addr == elem_addr) { + break; + } + } + if (i == comp->elem_count) { + BT_ERR("%s, No element is found", __func__); + return -ENODEV; + } + + if (cid == 0xFFFF) { + model = bt_mesh_model_find(elem, mod_id); + } else { + model = bt_mesh_model_find_vnd(elem, cid, mod_id); + } + if (!model) { + BT_ERR("%s, No model is found", __func__); + return -ENODEV; + } + + if (provisioner_check_app_idx(app_idx, false)) { + BT_ERR("%s, AppKey Index does not exist", __func__); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(model->keys); i++) { + if (model->keys[i] == app_idx) { + BT_WARN("%s, AppKey Index is already binded with model", __func__); + return 0; + } + } + + for (i = 0; i < ARRAY_SIZE(model->keys); i++) { + if (model->keys[i] == BLE_MESH_KEY_UNUSED) { + model->keys[i] = app_idx; + return 0; + } + } + + BT_ERR("%s, Model AppKey queue is full", __func__); + return -ENOMEM; +} + +int bt_mesh_provisioner_bind_local_app_net_idx(u16_t net_idx, u16_t app_idx) +{ + struct bt_mesh_app_key *key = NULL; + int i; + + BT_DBG("%s", __func__); + + if (provisioner_check_net_idx(net_idx, false)) { + BT_ERR("%s, NetKey Index does not exist", __func__); + return -ENODEV; + } + + if (provisioner_check_app_idx(app_idx, false)) { + BT_ERR("%s, AppKey Index does not exist", __func__); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + key = bt_mesh.p_app_keys[i]; + if (!key || (key->app_idx != app_idx)) { + continue; + } + key->net_idx = net_idx; + return 0; + } + + return -ENODEV; +} + +int bt_mesh_provisioner_print_local_element_info(void) +{ + struct bt_mesh_elem *elem = NULL; + struct bt_mesh_model *model = NULL; + int i, j; + + if (!comp) { + BT_ERR("%s, NULL composition data", __func__); + return -EINVAL; + } + + BT_WARN("************************************************"); + BT_WARN("* cid: 0x%04x pid: 0x%04x vid: 0x%04x *", comp->cid, comp->pid, comp->vid); + BT_WARN("* Element Number: 0x%02x *", comp->elem_count); + for (i = 0; i < comp->elem_count; i++) { + elem = &comp->elem[i]; + BT_WARN("* Element %d: 0x%04x *", i, elem->addr); + BT_WARN("* Loc: 0x%04x NumS: 0x%02x NumV: 0x%02x *", elem->loc, elem->model_count, elem->vnd_model_count); + for (j = 0; j < elem->model_count; j++) { + model = &elem->models[j]; + BT_WARN("* sig_model %d: id - 0x%04x *", j, model->id); + } + for (j = 0; j < elem->vnd_model_count; j++) { + model = &elem->vnd_models[j]; + BT_WARN("* vnd_model %d: id - 0x%04x, cid - 0x%04x *", j, model->vnd.id, model->vnd.company); + } + } + BT_WARN("************************************************"); + + return 0; +} + +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +/* The following APIs are for fast provisioning */ + +#if CONFIG_BLE_MESH_FAST_PROV + +const u8_t *get_fast_prov_device_key(u16_t addr) +{ + struct bt_mesh_node_t *node = NULL; + + BT_DBG("%s", __func__); + + if (!BLE_MESH_ADDR_IS_UNICAST(addr)) { + BT_ERR("%s, Not a unicast address 0x%04x", __func__, addr); + return NULL; + } + + if (addr == bt_mesh_primary_addr()) { + return bt_mesh.dev_key; + } + + for (int i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (node && node->unicast_addr == addr) { + return node->dev_key; + } + } + + return NULL; +} + +struct bt_mesh_subnet *get_fast_prov_subnet(u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + + BT_DBG("%s", __func__); + + for (int i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + sub = &bt_mesh.sub[i]; + if (sub->net_idx == net_idx) { + return sub; + } + } + + for (int i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + if (sub && sub->net_idx == net_idx) { + return sub; + } + } + + return NULL; +} + +struct bt_mesh_app_key *get_fast_prov_app_key(u16_t net_idx, u16_t app_idx) +{ + struct bt_mesh_app_key *key = NULL; + + BT_DBG("%s", __func__); + + for (int i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) { + key = &bt_mesh.app_keys[i]; + if (key->net_idx == net_idx && key->app_idx == app_idx) { + return key; + } + } + + for (int i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + key = bt_mesh.p_app_keys[i]; + if (key && key->net_idx == net_idx && key->app_idx == app_idx) { + return key; + } + } + + return NULL; +} + +u8_t bt_mesh_set_fast_prov_net_idx(u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + struct bt_mesh_subnet_keys *key = NULL; + + sub = get_fast_prov_subnet(net_idx); + if (sub) { + key = BLE_MESH_KEY_REFRESH(sub->kr_flag) ? &sub->keys[1] : &sub->keys[0]; + return provisioner_set_fast_prov_net_idx(key->net, net_idx); + } + + /* If net_idx is not found, set net_idx to fast_prov first, + * and wait for primary provisioner to add net_key */ + return provisioner_set_fast_prov_net_idx(NULL, net_idx); +} + +u8_t bt_mesh_add_fast_prov_net_key(const u8_t net_key[16]) +{ + const u8_t *keys = NULL; + u16_t net_idx; + int err; + + net_idx = provisioner_get_fast_prov_net_idx(); + bt_mesh.p_net_idx_next = net_idx; + + err = bt_mesh_provisioner_local_net_key_add(net_key, &net_idx); + if (err) { + return 0x01; /* status: add net_key fail */ + }; + + keys = bt_mesh_provisioner_local_net_key_get(net_idx); + if (!keys) { + return 0x01; /* status: add net_key fail */ + } + + return provisioner_set_fast_prov_net_idx(keys, net_idx); +} + +const u8_t *bt_mesh_get_fast_prov_net_key(u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + + sub = get_fast_prov_subnet(net_idx); + if (!sub) { + BT_ERR("%s, Failed to get subnet", __func__); + return NULL; + } + + return (sub->kr_flag ? sub->keys[1].net : sub->keys[0].net); +} + +const u8_t *bt_mesh_get_fast_prov_app_key(u16_t net_idx, u16_t app_idx) +{ + struct bt_mesh_app_key *key = NULL; + + key = get_fast_prov_app_key(net_idx, app_idx); + if (!key) { + BT_ERR("%s, Failed to get AppKey", __func__); + return NULL; + } + + return (key->updated ? key->keys[1].val : key->keys[0].val); +} + +#endif /* CONFIG_BLE_MESH_FAST_PROV */ diff --git a/components/bt/ble_mesh/mesh_core/provisioner_main.h b/components/bt/ble_mesh/mesh_core/provisioner_main.h new file mode 100644 index 0000000000..a4585d4009 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/provisioner_main.h @@ -0,0 +1,122 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _PROVISIONER_MAIN_H_ +#define _PROVISIONER_MAIN_H_ + +#include "mesh_util.h" +#include "mesh_kernel.h" +#include "mesh_access.h" +#include "net.h" + +#define MESH_NAME_SIZE 31 + +/* Each node information stored by provisioner */ +struct bt_mesh_node_t { + char node_name[MESH_NAME_SIZE]; /* Node name */ + u8_t dev_uuid[16]; /* Device UUID pointer, stored in provisioner_prov.c */ + u16_t oob_info; /* Node OOB information */ + u16_t unicast_addr; /* Node unicast address */ + u8_t element_num; /* Node element number */ + u16_t net_idx; /* Node provision net_idx */ + u8_t flags; /* Node key refresh flag and iv update flag */ + u32_t iv_index; /* Node IV Index */ + u8_t dev_key[16]; /* Node device key */ +} __packed; + +/* The following APIs are for key init, node provision & node reset. */ + +int provisioner_node_provision(int node_index, const u8_t uuid[16], u16_t oob_info, + u16_t unicast_addr, u8_t element_num, u16_t net_idx, + u8_t flags, u32_t iv_index, const u8_t dev_key[16]); + +int provisioner_node_reset(int node_index); + +int provisioner_upper_reset_all_nodes(void); + +int provisioner_upper_init(void); + +/* The following APIs are for provisioner upper layers internal usage. */ + +const u8_t *provisioner_net_key_get(u16_t net_idx); + +struct bt_mesh_subnet *provisioner_subnet_get(u16_t net_idx); + +bool provisioner_check_msg_dst_addr(u16_t dst_addr); + +const u8_t *provisioner_get_device_key(u16_t dst_addr); + +struct bt_mesh_app_key *provisioner_app_key_find(u16_t app_idx); + +u32_t provisioner_get_prov_node_count(void); + +/* The following APIs are for provisioner application use. */ + +int bt_mesh_provisioner_store_node_info(struct bt_mesh_node_t *node_info); + +int bt_mesh_provisioner_get_all_node_unicast_addr(struct net_buf_simple *buf); + +int bt_mesh_provisioner_set_node_name(int node_index, const char *name); + +const char *bt_mesh_provisioner_get_node_name(int node_index); + +int bt_mesh_provisioner_get_node_index(const char *name); + +struct bt_mesh_node_t *bt_mesh_provisioner_get_node_info(u16_t unicast_addr); + +u32_t bt_mesh_provisioner_get_net_key_count(void); + +u32_t bt_mesh_provisioner_get_app_key_count(void); + +int bt_mesh_provisioner_local_app_key_add(const u8_t app_key[16], u16_t net_idx, u16_t *app_idx); + +const u8_t *bt_mesh_provisioner_local_app_key_get(u16_t net_idx, u16_t app_idx); + +int bt_mesh_provisioner_local_app_key_delete(u16_t net_idx, u16_t app_idx); + +int bt_mesh_provisioner_local_net_key_add(const u8_t net_key[16], u16_t *net_idx); + +const u8_t *bt_mesh_provisioner_local_net_key_get(u16_t net_idx); + +int bt_mesh_provisioner_local_net_key_delete(u16_t net_idx); + +int bt_mesh_provisioner_get_own_unicast_addr(u16_t *addr, u8_t *elem_num); + +/* Provisioner bind local client model with proper appkey index */ +int bt_mesh_provisioner_bind_local_model_app_idx(u16_t elem_addr, u16_t mod_id, + u16_t cid, u16_t app_idx); + +/* This API can be used to change the net_idx binded with the app_idx. */ +int bt_mesh_provisioner_bind_local_app_net_idx(u16_t net_idx, u16_t app_idx); + +/* Provisioner print own element information */ +int bt_mesh_provisioner_print_local_element_info(void); + +/* The following APIs are for fast provisioning */ + +const u8_t *get_fast_prov_device_key(u16_t dst_addr); + +struct bt_mesh_subnet *get_fast_prov_subnet(u16_t net_idx); + +struct bt_mesh_app_key *get_fast_prov_app_key(u16_t net_idx, u16_t app_idx); + +u8_t bt_mesh_set_fast_prov_net_idx(u16_t net_idx); + +u8_t bt_mesh_add_fast_prov_net_key(const u8_t net_key[16]); + +const u8_t *bt_mesh_get_fast_prov_net_key(u16_t net_idx); + +const u8_t *bt_mesh_get_fast_prov_app_key(u16_t net_idx, u16_t app_idx); + +#endif /* _PROVISIONER_MAIN_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/provisioner_prov.c b/components/bt/ble_mesh/mesh_core/provisioner_prov.c new file mode 100644 index 0000000000..a07c8cec87 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/provisioner_prov.c @@ -0,0 +1,3287 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "sdkconfig.h" +#include "osi/allocator.h" +#include "osi/mutex.h" + +#include "mesh_main.h" +#include "mesh_trace.h" +#include "mesh_bearer_adapt.h" + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "provisioner_prov.h" +#include "provisioner_proxy.h" +#include "provisioner_main.h" + +#if CONFIG_BLE_MESH_PROVISIONER + +/* Service data length has minus 1 type length & 2 uuid length*/ +#define BLE_MESH_PROV_SRV_DATA_LEN 0x12 +#define BLE_MESH_PROXY_SRV_DATA_LEN1 0x09 +#define BLE_MESH_PROXY_SRV_DATA_LEN2 0x11 + +/* 3 transmissions, 20ms interval */ +#define PROV_XMIT BLE_MESH_TRANSMIT(2, 20) + +#define AUTH_METHOD_NO_OOB 0x00 +#define AUTH_METHOD_STATIC 0x01 +#define AUTH_METHOD_OUTPUT 0x02 +#define AUTH_METHOD_INPUT 0x03 + +#define OUTPUT_OOB_BLINK 0x00 +#define OUTPUT_OOB_BEEP 0x01 +#define OUTPUT_OOB_VIBRATE 0x02 +#define OUTPUT_OOB_NUMBER 0x03 +#define OUTPUT_OOB_STRING 0x04 + +#define INPUT_OOB_PUSH 0x00 +#define INPUT_OOB_TWIST 0x01 +#define INPUT_OOB_NUMBER 0x02 +#define INPUT_OOB_STRING 0x03 + +#define PROV_ERR_NONE 0x00 +#define PROV_ERR_NVAL_PDU 0x01 +#define PROV_ERR_NVAL_FMT 0x02 +#define PROV_ERR_UNEXP_PDU 0x03 +#define PROV_ERR_CFM_FAILED 0x04 +#define PROV_ERR_RESOURCES 0x05 +#define PROV_ERR_DECRYPT 0x06 +#define PROV_ERR_UNEXP_ERR 0x07 +#define PROV_ERR_ADDR 0x08 + +#define PROV_INVITE 0x00 +#define PROV_CAPABILITIES 0x01 +#define PROV_START 0x02 +#define PROV_PUB_KEY 0x03 +#define PROV_INPUT_COMPLETE 0x04 +#define PROV_CONFIRM 0x05 +#define PROV_RANDOM 0x06 +#define PROV_DATA 0x07 +#define PROV_COMPLETE 0x08 +#define PROV_FAILED 0x09 + +#define PROV_ALG_P256 0x00 + +#define GPCF(gpc) (gpc & 0x03) +#define GPC_START(last_seg) (((last_seg) << 2) | 0x00) +#define GPC_ACK 0x01 +#define GPC_CONT(seg_id) (((seg_id) << 2) | 0x02) +#define GPC_CTL(op) (((op) << 2) | 0x03) + +#define START_PAYLOAD_MAX 20 +#define CONT_PAYLOAD_MAX 23 + +#define START_LAST_SEG(gpc) (gpc >> 2) +#define CONT_SEG_INDEX(gpc) (gpc >> 2) + +#define BEARER_CTL(gpc) (gpc >> 2) +#define LINK_OPEN 0x00 +#define LINK_ACK 0x01 +#define LINK_CLOSE 0x02 + +#define CLOSE_REASON_SUCCESS 0x00 +#define CLOSE_REASON_TIMEOUT 0x01 +#define CLOSE_REASON_FAILED 0x02 + +#define PROV_AUTH_VAL_SIZE 0x10 +#define PROV_CONF_SALT_SIZE 0x10 +#define PROV_CONF_KEY_SIZE 0x10 +#define PROV_DH_KEY_SIZE 0x20 +#define PROV_CONFIRM_SIZE 0x10 +#define PROV_PROV_SALT_SIZE 0x10 +#define PROV_CONF_INPUTS_SIZE 0x91 + +#define XACT_SEG_DATA(_idx, _seg) (&link[_idx].rx.buf->data[20 + ((_seg - 1) * 23)]) +#define XACT_SEG_RECV(_idx, _seg) (link[_idx].rx.seg &= ~(1 << (_seg))) + +#define XACT_NVAL 0xff + +enum { + REMOTE_PUB_KEY, /* Remote key has been received */ + LOCAL_PUB_KEY, /* Local public key is available */ + LINK_ACTIVE, /* Link has been opened */ + WAIT_GEN_DHKEY, /* Waiting for remote public key to generate DHKey */ + HAVE_DHKEY, /* DHKey has been calcualted */ + SEND_CONFIRM, /* Waiting to send Confirm value */ + WAIT_NUMBER, /* Waiting for number input from user */ + WAIT_STRING, /* Waiting for string input from user */ + TIMEOUT_START, /* Provision timeout timer has started */ + NUM_FLAGS, +}; + +/** Provisioner link structure allocation + * |--------------------------------------------------------| + * | Link(PB-ADV) | Link(PB-GATT) | + * |--------------------------------------------------------| + * |<----------------------Total Link---------------------->| + */ +struct prov_link { + BLE_MESH_ATOMIC_DEFINE(flags, NUM_FLAGS); + u8_t uuid[16]; /* check if device is being provisioned*/ + u16_t oob_info; /* oob info of this device */ + u8_t element_num; /* element num of device */ + u8_t ki_flags; /* Key refresh flag and iv update flag */ + u32_t iv_index; /* IV Index */ + u8_t auth_method; /* choosed authentication method */ + u8_t auth_action; /* choosed authentication action */ + u8_t auth_size; /* choosed authentication size */ + u16_t unicast_addr; /* unicast address assigned for device */ + bt_mesh_addr_t addr; /* Device address */ +#if defined(CONFIG_BLE_MESH_PB_GATT) + bool connecting; /* start connecting with device */ + struct bt_mesh_conn *conn; /* GATT connection */ +#endif + u8_t expect; /* Next expected PDU */ + + u8_t *dhkey; /* Calculated DHKey */ + u8_t *auth; /* Authentication Value */ + + u8_t *conf_salt; /* ConfirmationSalt */ + u8_t *conf_key; /* ConfirmationKey */ + u8_t *conf_inputs; /* ConfirmationInputs */ + + u8_t *rand; /* Local Random */ + u8_t *conf; /* Remote Confirmation */ + + u8_t *prov_salt; /* Provisioning Salt */ + +#if defined(CONFIG_BLE_MESH_PB_ADV) + bool linking; /* Linking is being establishing */ + u16_t send_link_close; /* Link close is being sent flag */ + u32_t link_id; /* Link ID */ + u8_t pending_ack; /* Decide which transaction id ack is pending */ + u8_t expect_ack_for; /* Transaction ACK expected for provisioning pdu */ + u8_t tx_pdu_type; /* The current transmitted Provisioning PDU type */ + + struct { + u8_t trans_id; /* Transaction ID */ + u8_t prev_id; /* Previous Transaction ID */ + u8_t seg; /* Bit-field of unreceived segments */ + u8_t last_seg; /* Last segment (to check length) */ + u8_t fcs; /* Expected FCS value */ + u8_t adv_buf_id; /* index of buf allocated in adv_buf_data */ + struct net_buf_simple *buf; + } rx; + + struct { + /* Start timestamp of the transaction */ + s64_t start; + + /* Transaction id*/ + u8_t trans_id; + + /* Pending outgoing buffer(s) */ + struct net_buf *buf[3]; + + /* Retransmit timer */ + struct k_delayed_work retransmit; + } tx; +#endif + + /** Provision timeout timer. Spec P259 says: The provisioning protocol + * shall have a minimum timeout of 60 seconds that is reset each time + * a provisioning protocol PDU is sent or received. + */ + struct k_delayed_work timeout; +}; + +/* Number of devices can be provisioned at the same time equals to PB-ADV + PB-GATT */ +#define BLE_MESH_PROV_SAME_TIME \ + (CONFIG_BLE_MESH_PBA_SAME_TIME + CONFIG_BLE_MESH_PBG_SAME_TIME) + +static struct prov_link link[BLE_MESH_PROV_SAME_TIME]; + +struct prov_rx { + u32_t link_id; + u8_t xact_id; + u8_t gpc; +}; + +#define BLE_MESH_ALREADY_PROV_NUM (CONFIG_BLE_MESH_MAX_PROV_NODES + 10) + +struct prov_ctx_t { + /* If provisioning random have been generated, set BIT0 to 1 */ + u8_t rand_gen_done; + + /* Provisioner random */ + u8_t random[16]; + + /* Number of provisioned devices */ + u16_t node_count; + + /* Current number of PB-ADV provisioned devices simultaneously */ + u8_t pba_count; + + /* Current number of PB-GATT provisioned devices simultaneously */ + u8_t pbg_count; + + /* Current unicast address going to assigned */ + u16_t current_addr; + + /* Current net_idx going to be used in provisioning data */ + u16_t curr_net_idx; + + /* Current flags going to be used in provisioning data */ + u16_t curr_flags; + + /* Current iv_index going to be used in provisioning data */ + u16_t curr_iv_index; + + /* Offset of the device uuid to be matched, based on zero */ + u8_t match_offset; + + /* Length of the device uuid to be matched (start from the match_offset) */ + u8_t match_length; + + /* Value of the device uuid to be matched */ + u8_t *match_value; + + /* Indicate when received uuid_match adv_pkts, can provision it at once */ + bool prov_after_match; + + /* Mutex used to protect the PB-ADV procedure */ + osi_mutex_t pb_adv_lock; + + /* Mutex used to protect the PB-GATT procedure */ + osi_mutex_t pb_gatt_lock; + + /** This structure is used to store the information of the device which + * provisioner has successfully sent provisioning data to. In this + * structure, we don't care if the device is currently in the mesh + * network, or has been removed, or failed to send provisioning + * complete pdu after receiving the provisioning data pdu. + */ + struct already_prov_info { + u8_t uuid[16]; /* device uuid */ + u8_t element_num; /* element number of the deleted node */ + u16_t unicast_addr; /* Primary unicast address of the deleted node */ + } already_prov[BLE_MESH_ALREADY_PROV_NUM]; +}; + +static struct prov_ctx_t prov_ctx; + +struct prov_node_info { + bool provisioned; /* device provisioned flag */ + bt_mesh_addr_t addr; /* device address */ + u8_t uuid[16]; /* node uuid */ + u16_t oob_info; /* oob info contained in adv pkt */ + u8_t element_num; /* element contained in this node */ + u16_t unicast_addr; /* primary unicast address of this node */ + u16_t net_idx; /* Netkey index got during provisioning */ + u8_t flags; /* Key refresh flag and iv update flag */ + u32_t iv_index; /* IV Index */ +}; + +static struct prov_node_info prov_nodes[CONFIG_BLE_MESH_MAX_PROV_NODES]; + +struct unprov_dev_queue { + bt_mesh_addr_t addr; + u8_t uuid[16]; + u16_t oob_info; + u8_t bearer; + u8_t flags; +} __packed unprov_dev[CONFIG_BLE_MESH_WAIT_FOR_PROV_MAX_DEV_NUM] = { + [0 ... (CONFIG_BLE_MESH_WAIT_FOR_PROV_MAX_DEV_NUM - 1)] = { + .addr.type = 0xff, + .bearer = 0, + .flags = false, + }, +}; + +static unprov_adv_pkt_cb_t notify_unprov_adv_pkt_cb; + +#define BUF_TIMEOUT K_MSEC(400) + +#if defined(CONFIG_BLE_MESH_FAST_PROV) +#define RETRANSMIT_TIMEOUT K_MSEC(360) +#define TRANSACTION_TIMEOUT K_SECONDS(3) +#define PROVISION_TIMEOUT K_SECONDS(6) +#else +#define RETRANSMIT_TIMEOUT K_MSEC(500) +#define TRANSACTION_TIMEOUT K_SECONDS(30) +#define PROVISION_TIMEOUT K_SECONDS(60) +#endif /* CONFIG_BLE_MESH_FAST_PROV */ + +#if defined(CONFIG_BLE_MESH_PB_GATT) +#define PROV_BUF_HEADROOM 5 +#else +#define PROV_BUF_HEADROOM 0 +#endif + +#define PROV_BUF(name, len) \ + NET_BUF_SIMPLE_DEFINE(name, PROV_BUF_HEADROOM + len) + +static const struct bt_mesh_prov *prov; + +#if defined(CONFIG_BLE_MESH_PB_ADV) +static void send_link_open(const u8_t idx); +#endif + +static void prov_gen_dh_key(const u8_t idx); + +static void send_pub_key(const u8_t idx, u8_t oob); + +static void close_link(const u8_t idx, u8_t reason); + +#if defined(CONFIG_BLE_MESH_PB_ADV) +#define ADV_BUF_SIZE 65 + +static struct prov_adv_buf { + struct net_buf_simple buf; +} adv_buf[CONFIG_BLE_MESH_PBA_SAME_TIME]; + +static u8_t adv_buf_data[ADV_BUF_SIZE * CONFIG_BLE_MESH_PBA_SAME_TIME]; +#endif + +#define PROV_FREE_MEM(_idx, member) \ +{ \ + if (link[_idx].member) { \ + osi_free(link[_idx].member); \ + } \ +} + +/* Fast provisioning uses this structure for provisioning data */ +static struct bt_mesh_fast_prov_info { + u16_t net_idx; + const u8_t *net_key; + u8_t flags; + u32_t iv_index; + u16_t unicast_addr_min; + u16_t unicast_addr_max; +} fast_prov_info; + +static bool fast_prov_flag; + +#define FAST_PROV_FLAG_GET() fast_prov_flag + +void provisioner_pbg_count_dec(void) +{ + if (prov_ctx.pbg_count) { + prov_ctx.pbg_count--; + } +} + +void provisioner_pbg_count_inc(void) +{ + prov_ctx.pbg_count++; +} + +void provisioner_clear_link_conn_info(const u8_t addr[6]) +{ +#if defined(CONFIG_BLE_MESH_PB_GATT) + u8_t i; + + if (!addr) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + BT_DBG("%s, Clear device %s info", __func__, bt_hex(addr, BLE_MESH_ADDR_LEN)); + + for (i = CONFIG_BLE_MESH_PBA_SAME_TIME; i < BLE_MESH_PROV_SAME_TIME; i++) { + if (!memcmp(link[i].addr.val, addr, BLE_MESH_ADDR_LEN)) { + link[i].connecting = false; + link[i].conn = NULL; + link[i].oob_info = 0x0; + memset(link[i].uuid, 0, 16); + memset(&link[i].addr, 0, sizeof(bt_mesh_addr_t)); + bt_mesh_atomic_test_and_clear_bit(link[i].flags, LINK_ACTIVE); + if (bt_mesh_atomic_test_and_clear_bit(link[i].flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link[i].timeout); + } + return; + } + } + + BT_WARN("%s, Address %s is not found", __func__, bt_hex(addr, BLE_MESH_ADDR_LEN)); +#endif + return; +} + +const struct bt_mesh_prov *provisioner_get_prov_info(void) +{ + return prov; +} + +int provisioner_prov_reset_all_nodes(void) +{ + u16_t i; + + BT_DBG("%s", __func__); + + for (i = 0U; i < ARRAY_SIZE(prov_nodes); i++) { + if (prov_nodes[i].provisioned) { + memset(&prov_nodes[i], 0, sizeof(struct prov_node_info)); + } + } + + prov_ctx.node_count = 0; + + return 0; +} + +static int provisioner_dev_find(const bt_mesh_addr_t *addr, const u8_t uuid[16], u16_t *index) +{ + bool uuid_match = false; + bool addr_match = false; + u8_t zero[16] = {0}; + u16_t i = 0, j = 0; + int comp = 0; + + if (addr) { + comp = memcmp(addr->val, zero, BLE_MESH_ADDR_LEN); + } + + if ((!uuid && (!addr || (comp == 0) || (addr->type > BLE_MESH_ADDR_RANDOM))) || !index) { + return -EINVAL; + } + + /** Note: user may add a device into two unprov_dev array elements, + * one with device address, address type and another only + * with device UUID. We need to take this into consideration. + */ + if (uuid && memcmp(uuid, zero, 16)) { + for (i = 0; i < ARRAY_SIZE(unprov_dev); i++) { + if (!memcmp(unprov_dev[i].uuid, uuid, 16)) { + uuid_match = true; + break; + } + } + } + + if (addr && comp && (addr->type <= BLE_MESH_ADDR_RANDOM)) { + for (j = 0; j < ARRAY_SIZE(unprov_dev); j++) { + if (!memcmp(unprov_dev[j].addr.val, addr->val, BLE_MESH_ADDR_LEN) && + unprov_dev[j].addr.type == addr->type) { + addr_match = true; + break; + } + } + } + + if (!uuid_match && !addr_match) { + BT_DBG("%s, Device does not exist in queue", __func__); + return -ENODEV; + } + + if (uuid_match && addr_match && (i != j)) { + /** + * In this situation, copy address & type into device uuid + * array element, reset another element, rm_flag will be + * decided by uuid element. + */ + unprov_dev[i].addr.type = unprov_dev[j].addr.type; + memcpy(unprov_dev[i].addr.val, unprov_dev[j].addr.val, BLE_MESH_ADDR_LEN); + unprov_dev[i].bearer |= unprov_dev[j].bearer; + memset(&unprov_dev[j], 0x0, sizeof(struct unprov_dev_queue)); + } + + *index = uuid_match ? i : j; + return 0; +} + +static bool is_unprov_dev_being_provision(const u8_t uuid[16]) +{ + u16_t i; + +#if defined(CONFIG_BLE_MESH_FAST_PROV) + /** + * During Fast Provisioning test, we found that if a device has already being + * provisioned, there is still a chance that the Provisioner can receive the + * Unprovisioned Device Beacon from the device (because the device will stop + * Unprovisioned Device Beacon when Transaction ACK for Provisioning Complete + * is received). So in Fast Provisioning the Provisioner should ignore this. + */ + for (i = 0U; i < ARRAY_SIZE(prov_nodes); i++) { + if (prov_nodes[i].provisioned) { + if (!memcmp(prov_nodes[i].uuid, uuid, 16)) { + BT_WARN("Device has already been provisioned"); + return -EALREADY; + } + } + } +#endif + + for (i = 0U; i < BLE_MESH_PROV_SAME_TIME; i++) { +#if defined(CONFIG_BLE_MESH_PB_ADV) && defined(CONFIG_BLE_MESH_PB_GATT) + if (link[i].linking || link[i].connecting || + bt_mesh_atomic_test_bit(link[i].flags, LINK_ACTIVE)) { +#elif defined(CONFIG_BLE_MESH_PB_ADV) && !defined(CONFIG_BLE_MESH_PB_GATT) + if (link[i].linking || bt_mesh_atomic_test_bit(link[i].flags, LINK_ACTIVE)) { +#else + if (link[i].connecting || bt_mesh_atomic_test_bit(link[i].flags, LINK_ACTIVE)) { +#endif + if (!memcmp(link[i].uuid, uuid, 16)) { + BT_DBG("%s, Device is being provisioned", __func__); + return true; + } + } + } + + return false; +} + +static bool is_unprov_dev_uuid_match(const u8_t uuid[16]) +{ + if (prov_ctx.match_length && prov_ctx.match_value) { + if (memcmp(uuid + prov_ctx.match_offset, + prov_ctx.match_value, prov_ctx.match_length)) { + return false; + } + } + + return true; +} + +static int provisioner_check_unprov_dev_info(const u8_t uuid[16]) +{ + u16_t i; + + if (!uuid) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + /* Check if the device uuid matches configured value */ + if (is_unprov_dev_uuid_match(uuid) == false) { + BT_DBG("%s, Device uuid is not matched", __func__); + return -EIO; + } + + /* Check if this device is currently being provisioned. + * According to Zephyr's device code, if we connect with + * one device and start to provision it, we may still can + * receive the connectable prov adv pkt from this device. + * Here we check both PB-GATT and PB-ADV link status. + */ + if (is_unprov_dev_being_provision(uuid)) { + return -EALREADY; + } + + /* Check if this device is currently being provisioned. + * According to Zephyr's device code, if we connect with + * one device and start to provision it, we may still can + * receive the connectable prov adv pkt from this device. + * Here we check both PB-GATT and PB-ADV link status. + */ + if (is_unprov_dev_being_provision(uuid)) { + return -EALREADY; + } + + /* Check if the device has already been provisioned */ + for (i = 0U; i < ARRAY_SIZE(prov_nodes); i++) { + if (prov_nodes[i].provisioned) { + if (!memcmp(prov_nodes[i].uuid, uuid, 16)) { + BT_WARN("Provisioned before, start to provision again"); + provisioner_node_reset(i); + memset(&prov_nodes[i], 0, sizeof(struct prov_node_info)); + if (prov_ctx.node_count) { + prov_ctx.node_count--; + } + return 0; + } + } + } + + /* Check if the prov_nodes queue is full */ + if (prov_ctx.node_count == ARRAY_SIZE(prov_nodes)) { + BT_WARN("Current provisioned devices reach max limit"); + return -ENOMEM; + } + + return 0; +} + +#if defined(CONFIG_BLE_MESH_PB_ADV) +static int provisioner_start_prov_pb_adv(const u8_t uuid[16], + const bt_mesh_addr_t *addr, u16_t oob_info) +{ + u8_t zero[6] = {0}; + int addr_cmp; + u8_t i; + + if (!uuid || !addr) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + osi_mutex_lock(&prov_ctx.pb_adv_lock, OSI_MUTEX_MAX_TIMEOUT); + + if (is_unprov_dev_being_provision(uuid)) { + osi_mutex_unlock(&prov_ctx.pb_adv_lock); + return -EALREADY; + } + + addr_cmp = memcmp(addr->val, zero, BLE_MESH_ADDR_LEN); + + for (i = 0U; i < CONFIG_BLE_MESH_PBA_SAME_TIME; i++) { + if (!bt_mesh_atomic_test_bit(link[i].flags, LINK_ACTIVE) && !link[i].linking) { + memcpy(link[i].uuid, uuid, 16); + link[i].oob_info = oob_info; + if (addr_cmp && (addr->type <= BLE_MESH_ADDR_RANDOM)) { + link[i].addr.type = addr->type; + memcpy(link[i].addr.val, addr->val, BLE_MESH_ADDR_LEN); + } + send_link_open(i); + osi_mutex_unlock(&prov_ctx.pb_adv_lock); + return 0; + } + } + + BT_ERR("%s, No PB-ADV link is available", __func__); + osi_mutex_unlock(&prov_ctx.pb_adv_lock); + return -ENOMEM; +} +#endif /* CONFIG_BLE_MESH_PB_ADV */ + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static int provisioner_start_prov_pb_gatt(const u8_t uuid[16], + const bt_mesh_addr_t *addr, u16_t oob_info) +{ + u8_t zero[6] = {0}; + int addr_cmp; + u8_t i; + + if (!uuid || !addr) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + osi_mutex_lock(&prov_ctx.pb_gatt_lock, OSI_MUTEX_MAX_TIMEOUT); + + if (is_unprov_dev_being_provision(uuid)) { + osi_mutex_unlock(&prov_ctx.pb_gatt_lock); + return -EALREADY; + } + + addr_cmp = memcmp(addr->val, zero, BLE_MESH_ADDR_LEN); + + for (i = CONFIG_BLE_MESH_PBA_SAME_TIME; i < BLE_MESH_PROV_SAME_TIME; i++) { + if (!link[i].connecting && !bt_mesh_atomic_test_bit(link[i].flags, LINK_ACTIVE)) { + memcpy(link[i].uuid, uuid, 16); + link[i].oob_info = oob_info; + if (addr_cmp && (addr->type <= BLE_MESH_ADDR_RANDOM)) { + link[i].addr.type = addr->type; + memcpy(link[i].addr.val, addr->val, BLE_MESH_ADDR_LEN); + } + if (bt_mesh_gattc_conn_create(&link[i].addr, BLE_MESH_UUID_MESH_PROV_VAL)) { + memset(link[i].uuid, 0, 16); + link[i].oob_info = 0x0; + memset(&link[i].addr, 0, sizeof(bt_mesh_addr_t)); + osi_mutex_unlock(&prov_ctx.pb_gatt_lock); + return -EIO; + } + /* If creating connection successfully, set connecting flag to 1 */ + link[i].connecting = true; + osi_mutex_unlock(&prov_ctx.pb_gatt_lock); + return 0; + } + } + + BT_ERR("%s, No PB-GATT link is available", __func__); + osi_mutex_unlock(&prov_ctx.pb_gatt_lock); + return -ENOMEM; +} +#endif /* CONFIG_BLE_MESH_PB_GATT */ + +int bt_mesh_provisioner_add_unprov_dev(struct bt_mesh_unprov_dev_add *add_dev, u8_t flags) +{ + bt_mesh_addr_t add_addr = {0}; + u8_t zero[16] = {0}; + int addr_cmp = 0; + int uuid_cmp = 0; + u16_t i; + int err; + + if (!add_dev) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + addr_cmp = memcmp(add_dev->addr, zero, BLE_MESH_ADDR_LEN); + uuid_cmp = memcmp(add_dev->uuid, zero, 16); + + if (add_dev->bearer == 0x0 || ((uuid_cmp == 0) && + ((addr_cmp == 0) || add_dev->addr_type > BLE_MESH_ADDR_RANDOM))) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if ((add_dev->bearer & BLE_MESH_PROV_ADV) && (add_dev->bearer & BLE_MESH_PROV_GATT) && + (flags & START_PROV_NOW)) { + BT_ERR("%s, Can not start PB-ADV & PB-GATT simultaneouly", __func__); + return -EINVAL; + } + + if ((uuid_cmp == 0) && (flags & START_PROV_NOW)) { + BT_ERR("%s, Can not start provisioning with zero uuid", __func__); + return -EINVAL; + } + + if ((add_dev->bearer & BLE_MESH_PROV_GATT) && (flags & START_PROV_NOW) && + ((addr_cmp == 0) || add_dev->addr_type > BLE_MESH_ADDR_RANDOM)) { + BT_ERR("%s, Invalid device address for PB-GATT", __func__); + return -EINVAL; + } + + if (add_dev->bearer & BLE_MESH_PROV_GATT) { +#if !CONFIG_BLE_MESH_PB_GATT + BT_ERR("%s, Not support PB-GATT", __func__); + return -EINVAL; +#endif + } + + if (add_dev->bearer & BLE_MESH_PROV_ADV) { +#if !CONFIG_BLE_MESH_PB_ADV + BT_ERR("%s, Not support PB-ADV", __func__); + return -EINVAL; +#endif + } + + add_addr.type = add_dev->addr_type; + memcpy(add_addr.val, add_dev->addr, BLE_MESH_ADDR_LEN); + + err = provisioner_dev_find(&add_addr, add_dev->uuid, &i); + if (err == -EINVAL) { + BT_ERR("%s, Invalid parameter", __func__); + return err; + } else if (err == 0) { + if (!(add_dev->bearer & unprov_dev[i].bearer)) { + BT_WARN("Add device with only bearer updated"); + unprov_dev[i].bearer |= add_dev->bearer; + } else { + BT_WARN("Device already exists in queue"); + } + goto start; + } + + for (i = 0U; i < ARRAY_SIZE(unprov_dev); i++) { + if (unprov_dev[i].bearer) { + continue; + } + if (addr_cmp && (add_dev->addr_type <= BLE_MESH_ADDR_RANDOM)) { + unprov_dev[i].addr.type = add_dev->addr_type; + memcpy(unprov_dev[i].addr.val, add_dev->addr, BLE_MESH_ADDR_LEN); + } + if (uuid_cmp) { + memcpy(unprov_dev[i].uuid, add_dev->uuid, 16); + } + unprov_dev[i].bearer = add_dev->bearer & BIT_MASK(2); + unprov_dev[i].flags = flags & BIT_MASK(3); + goto start; + } + + /* If queue is full, find flushable device and replace it */ + for (i = 0U; i < ARRAY_SIZE(unprov_dev); i++) { + if (unprov_dev[i].flags & FLUSHABLE_DEV) { + memset(&unprov_dev[i], 0, sizeof(struct unprov_dev_queue)); + if (addr_cmp && (add_dev->addr_type <= BLE_MESH_ADDR_RANDOM)) { + unprov_dev[i].addr.type = add_dev->addr_type; + memcpy(unprov_dev[i].addr.val, add_dev->addr, BLE_MESH_ADDR_LEN); + } + if (uuid_cmp) { + memcpy(unprov_dev[i].uuid, add_dev->uuid, 16); + } + unprov_dev[i].bearer = add_dev->bearer & BIT_MASK(2); + unprov_dev[i].flags = flags & BIT_MASK(3); + goto start; + } + } + + BT_ERR("%s, Unprovisioned device queue is full", __func__); + return -ENOMEM; + +start: + if (!(flags & START_PROV_NOW)) { + return 0; + } + + /* Check if current provisioned node count + active link reach max limit */ + if (prov_ctx.node_count + prov_ctx.pba_count + \ + prov_ctx.pbg_count >= ARRAY_SIZE(prov_nodes)) { + BT_WARN("%s, Node count + active link count reach max limit", __func__); + return -EIO; + } + + if ((err = provisioner_check_unprov_dev_info(add_dev->uuid))) { + return err; + } + + if (add_dev->bearer & BLE_MESH_PROV_ADV) { +#if defined(CONFIG_BLE_MESH_PB_ADV) + if (prov_ctx.pba_count == CONFIG_BLE_MESH_PBA_SAME_TIME) { + BT_WARN("%s, Current PB-ADV links reach max limit", __func__); + return -EIO; + } + if ((err = provisioner_start_prov_pb_adv( + add_dev->uuid, &add_addr, add_dev->oob_info))) { + return err; + } +#endif + } else if (add_dev->bearer & BLE_MESH_PROV_GATT) { +#if defined(CONFIG_BLE_MESH_PB_GATT) + if (prov_ctx.pbg_count == CONFIG_BLE_MESH_PBG_SAME_TIME) { + BT_WARN("%s, Current PB-GATT links reach max limit", __func__); + return -EIO; + } + if ((err = provisioner_start_prov_pb_gatt( + add_dev->uuid, &add_addr, add_dev->oob_info))) { + return err; + } +#endif + } + + return 0; +} + +int bt_mesh_provisioner_delete_device(struct bt_mesh_device_delete *del_dev) +{ + /** + * Three Situations: + * 1. device is not being/been provisioned, just remove from device queue. + * 2. device is being provisioned, need to close link & remove from device queue. + * 3. device is been provisioned, need to send config_node_reset and may need to + * remove from device queue. config _node_reset can be added in function + * provisioner_node_reset() in provisioner_main.c. + */ + bt_mesh_addr_t del_addr = {0}; + u8_t zero[16] = {0}; + bool addr_match = false; + bool uuid_match = false; + int addr_cmp = 0; + int uuid_cmp = 0; + u16_t i; + int err; + + if (!del_dev) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + addr_cmp = memcmp(del_dev->addr, zero, BLE_MESH_ADDR_LEN); + uuid_cmp = memcmp(del_dev->uuid, zero, 16); + + if ((uuid_cmp == 0) && ((addr_cmp == 0) || del_dev->addr_type > BLE_MESH_ADDR_RANDOM)) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + del_addr.type = del_dev->addr_type; + memcpy(del_addr.val, del_dev->addr, BLE_MESH_ADDR_LEN); + + /* First: find if the device is in the device queue */ + err = provisioner_dev_find(&del_addr, del_dev->uuid, &i); + if (err) { + BT_DBG("%s, Device is not in the queue", __func__); + } else { + memset(&unprov_dev[i], 0x0, sizeof(struct unprov_dev_queue)); + } + + /* Second: find if the device is being provisioned */ + for (i = 0U; i < ARRAY_SIZE(link); i++) { + if (addr_cmp && (del_dev->addr_type <= BLE_MESH_ADDR_RANDOM)) { + if (!memcmp(link[i].addr.val, del_dev->addr, BLE_MESH_ADDR_LEN) && + link[i].addr.type == del_dev->addr_type) { + addr_match = true; + } + } + if (uuid_cmp) { + if (!memcmp(link[i].uuid, del_dev->uuid, 16)) { + uuid_match = true; + } + } + if (addr_match || uuid_match) { + close_link(i, CLOSE_REASON_FAILED); + break; + } + } + + /* Third: find if the device is been provisioned */ + for (i = 0U; i < ARRAY_SIZE(prov_nodes); i++) { + if (addr_cmp && (del_dev->addr_type <= BLE_MESH_ADDR_RANDOM)) { + if (!memcmp(prov_nodes[i].addr.val, del_dev->addr, BLE_MESH_ADDR_LEN) && + prov_nodes[i].addr.type == del_dev->addr_type) { + addr_match = true; + } + } + if (uuid_cmp) { + if (!memcmp(prov_nodes[i].uuid, del_dev->uuid, 16)) { + uuid_match = true; + } + } + if (addr_match || uuid_match) { + memset(&prov_nodes[i], 0, sizeof(struct prov_node_info)); + provisioner_node_reset(i); + if (prov_ctx.node_count) { + prov_ctx.node_count--; + } + break; + } + } + + return 0; +} + +int bt_mesh_provisioner_set_dev_uuid_match(u8_t offset, u8_t length, + const u8_t *match, bool prov_flag) +{ + if (length && (!match || (offset + length > 16))) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (length && !prov_ctx.match_value) { + prov_ctx.match_value = osi_calloc(16); + if (!prov_ctx.match_value) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + } + + prov_ctx.match_offset = offset; + prov_ctx.match_length = length; + if (length) { + memcpy(prov_ctx.match_value, match, length); + } + prov_ctx.prov_after_match = prov_flag; + + return 0; +} + +int bt_mesh_prov_adv_pkt_cb_register(unprov_adv_pkt_cb_t cb) +{ + if (!cb) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + notify_unprov_adv_pkt_cb = cb; + return 0; +} + +int bt_mesh_provisioner_set_prov_data_info(struct bt_mesh_prov_data_info *info) +{ + const u8_t *key = NULL; + + if (!info || info->flag == 0) { + return -EINVAL; + } + + if (info->flag & NET_IDX_FLAG) { + key = provisioner_net_key_get(info->net_idx); + if (!key) { + BT_ERR("%s, Failed to get NetKey", __func__); + return -EINVAL; + } + prov_ctx.curr_net_idx = info->net_idx; + } else if (info->flag & FLAGS_FLAG) { + prov_ctx.curr_flags = info->flags; + } else if (info->flag & IV_INDEX_FLAG) { + prov_ctx.curr_iv_index = info->iv_index; + } + + return 0; +} + +/* The following APIs are for fast provisioning */ + +void provisioner_set_fast_prov_flag(bool flag) +{ + fast_prov_flag = flag; +} + +u8_t provisioner_set_fast_prov_net_idx(const u8_t *net_key, u16_t net_idx) +{ + fast_prov_info.net_idx = net_idx; + fast_prov_info.net_key = net_key; + + if (!net_key) { + BT_WARN("%s, Wait for NetKey for fast provisioning", __func__); + return 0x01; /*status: wait for net_key */ + } + + return 0x0; /* status: success */ +} + +u16_t provisioner_get_fast_prov_net_idx(void) +{ + return fast_prov_info.net_idx; +} + +u8_t bt_mesh_set_fast_prov_unicast_addr_range(u16_t min, u16_t max) +{ + if (!BLE_MESH_ADDR_IS_UNICAST(min) || !BLE_MESH_ADDR_IS_UNICAST(max)) { + BT_ERR("%s, Not a unicast address", __func__); + return 0x01; /* status: not a unicast address */ + } + + if (min > max) { + BT_ERR("%s, Min bigger than max", __func__); + return 0x02; /* status: min is bigger than max */ + } + + if (min <= fast_prov_info.unicast_addr_max) { + BT_ERR("%s, Address overlap", __func__); + return 0x03; /* status: address overlaps with current value */ + } + + fast_prov_info.unicast_addr_min = min; + fast_prov_info.unicast_addr_max = max; + + prov_ctx.current_addr = fast_prov_info.unicast_addr_min; + + return 0x0; /* status: success */ +} + +void bt_mesh_set_fast_prov_flags_iv_index(u8_t flags, u32_t iv_index) +{ + /* BIT0: Key Refreash flag, BIT1: IV Update flag */ + fast_prov_info.flags = flags & BIT_MASK(2); + fast_prov_info.iv_index = iv_index; +} + +#if defined(CONFIG_BLE_MESH_PB_ADV) +static struct net_buf_simple *bt_mesh_pba_get_buf(const u8_t idx) +{ + struct net_buf_simple *buf = &(adv_buf[idx].buf); + + net_buf_simple_reset(buf); + + return buf; +} +#endif /* CONFIG_BLE_MESH_PB_ADV */ + +static void prov_memory_free(const u8_t idx) +{ + PROV_FREE_MEM(idx, dhkey); + PROV_FREE_MEM(idx, auth); + PROV_FREE_MEM(idx, conf); + PROV_FREE_MEM(idx, conf_salt); + PROV_FREE_MEM(idx, conf_key); + PROV_FREE_MEM(idx, conf_inputs); + PROV_FREE_MEM(idx, prov_salt); +} + +#if defined(CONFIG_BLE_MESH_PB_ADV) +static void buf_sent(int err, void *user_data) +{ + u8_t idx = (int)user_data; + + if (!link[idx].tx.buf[0]) { + return; + } + + k_delayed_work_submit(&link[idx].tx.retransmit, RETRANSMIT_TIMEOUT); +} + +static struct bt_mesh_send_cb buf_sent_cb = { + .end = buf_sent, +}; + +static void free_segments(const u8_t idx) +{ + u8_t i; + + for (i = 0U; i < ARRAY_SIZE(link[idx].tx.buf); i++) { + struct net_buf *buf = link[idx].tx.buf[i]; + + if (!buf) { + break; + } + + link[idx].tx.buf[i] = NULL; + /* Mark as canceled */ + BLE_MESH_ADV(buf)->busy = 0; + /** Change by Espressif. Add this to avoid buf->ref is 2 which will + * cause lack of buf. + */ + if (buf->ref > 1) { + buf->ref = 1; + } + net_buf_unref(buf); + } +} + +static void prov_clear_tx(const u8_t idx) +{ + BT_DBG("%s", __func__); + + k_delayed_work_cancel(&link[idx].tx.retransmit); + + free_segments(idx); +} + +static void reset_link(const u8_t idx, u8_t reason) +{ + prov_clear_tx(idx); + + if (bt_mesh_atomic_test_and_clear_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link[idx].timeout); + } + + if (prov->prov_link_close) { + prov->prov_link_close(BLE_MESH_PROV_ADV, reason); + } + + prov_memory_free(idx); + +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) + /* Remove the link id from exceptional list */ + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_REMOVE, + BLE_MESH_EXCEP_INFO_MESH_LINK_ID, &link[idx].link_id); +#endif + + /* Clear everything except the retransmit delayed work config */ + memset(&link[idx], 0, offsetof(struct prov_link, tx.retransmit)); + + link[idx].pending_ack = XACT_NVAL; + link[idx].rx.prev_id = XACT_NVAL; + + if (bt_mesh_pub_key_get()) { + bt_mesh_atomic_set_bit(link[idx].flags, LOCAL_PUB_KEY); + } + + link[idx].rx.buf = bt_mesh_pba_get_buf(idx); + + if (prov_ctx.pba_count) { + prov_ctx.pba_count--; + } +} + +static struct net_buf *adv_buf_create(void) +{ + struct net_buf *buf; + + buf = bt_mesh_adv_create(BLE_MESH_ADV_PROV, PROV_XMIT, BUF_TIMEOUT); + if (!buf) { + BT_ERR("Out of provisioning buffers"); + return NULL; + } + + return buf; +} + +static void ack_complete(u16_t duration, int err, void *user_data) +{ + u8_t idx = (int)user_data; + + BT_DBG("xact %u complete", link[idx].pending_ack); + + link[idx].pending_ack = XACT_NVAL; +} + +static void gen_prov_ack_send(const u8_t idx, u8_t xact_id) +{ + static const struct bt_mesh_send_cb cb = { + .start = ack_complete, + }; + const struct bt_mesh_send_cb *complete; + struct net_buf *buf; + + BT_DBG("xact_id %u", xact_id); + + if (link[idx].pending_ack == xact_id) { + BT_DBG("Not sending duplicate ack"); + return; + } + + buf = adv_buf_create(); + if (!buf) { + return; + } + + if (link[idx].pending_ack == XACT_NVAL) { + link[idx].pending_ack = xact_id; + complete = &cb; + } else { + complete = NULL; + } + + net_buf_add_be32(buf, link[idx].link_id); + net_buf_add_u8(buf, xact_id); + net_buf_add_u8(buf, GPC_ACK); + + bt_mesh_adv_send(buf, complete, (void *)(int)idx); + net_buf_unref(buf); +} + +static void send_reliable(const u8_t idx) +{ + u8_t i; + + link[idx].tx.start = k_uptime_get(); + + for (i = 0U; i < ARRAY_SIZE(link[idx].tx.buf); i++) { + struct net_buf *buf = link[idx].tx.buf[i]; + + if (!buf) { + break; + } + + if (i + 1 < ARRAY_SIZE(link[idx].tx.buf) && link[idx].tx.buf[i + 1]) { + bt_mesh_adv_send(buf, NULL, NULL); + } else { + bt_mesh_adv_send(buf, &buf_sent_cb, (void *)(int)idx); + } + } +} + +static int bearer_ctl_send(const u8_t idx, u8_t op, void *data, u8_t data_len) +{ + struct net_buf *buf; + + BT_DBG("op 0x%02x data_len %u", op, data_len); + + prov_clear_tx(idx); + + buf = adv_buf_create(); + if (!buf) { + return -ENOBUFS; + } + + net_buf_add_be32(buf, link[idx].link_id); + /* Transaction ID, always 0 for Bearer messages */ + net_buf_add_u8(buf, 0x00); + net_buf_add_u8(buf, GPC_CTL(op)); + net_buf_add_mem(buf, data, data_len); + + link[idx].tx.buf[0] = buf; + send_reliable(idx); + + /** We can also use buf->ref and a flag to decide that + * link close has been sent 3 times. + * Here we use another way: use retransmit timer and need + * to make sure the timer is not cancelled during sending + * link close pdu, so we add link[i].tx.id = 0 + */ + if (op == LINK_CLOSE) { + u8_t reason = *(u8_t *)data; + link[idx].send_link_close = ((reason & BIT_MASK(2)) << 1) | BIT(0); + link[idx].tx.trans_id = 0; + } + + return 0; +} + +static void send_link_open(const u8_t idx) +{ + u8_t j; + + /** Generate link ID, and may need to check if this id is + * currently being used, which may will not happen ever. + */ + bt_mesh_rand(&link[idx].link_id, sizeof(u32_t)); + while (1) { + for (j = 0U; j < CONFIG_BLE_MESH_PBA_SAME_TIME; j++) { + if (bt_mesh_atomic_test_bit(link[j].flags, LINK_ACTIVE) || link[j].linking) { + if (link[idx].link_id == link[j].link_id) { + bt_mesh_rand(&link[idx].link_id, sizeof(u32_t)); + break; + } + } + } + if (j == CONFIG_BLE_MESH_PBA_SAME_TIME) { + break; + } + } + +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) + /* Add the link id into exceptional list */ + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_ADD, + BLE_MESH_EXCEP_INFO_MESH_LINK_ID, &link[idx].link_id); +#endif + + bearer_ctl_send(idx, LINK_OPEN, link[idx].uuid, 16); + + /* If Provisioner sets LINK_ACTIVE flag once Link Open is sent, we have + * no need to use linking flag (like PB-GATT connecting) to prevent the + * stored device info (UUID, oob_info) being replaced by other received + * unprovisioned device beacons. + * But if Provisioner sets LINK_ACTIVE flag after Link ACK is received, + * we need to use linking flag to prevent device info being replaced. + * Currently we set LINK_ACTIVE flag after sending Link Open. + */ + link[idx].linking = true; + + /* Set LINK_ACTIVE just to be in compatibility with current Zephyr code */ + bt_mesh_atomic_set_bit(link[idx].flags, LINK_ACTIVE); + + if (prov->prov_link_open) { + prov->prov_link_open(BLE_MESH_PROV_ADV); + } + + prov_ctx.pba_count++; +} + +static u8_t last_seg(u8_t len) +{ + if (len <= START_PAYLOAD_MAX) { + return 0; + } + + len -= START_PAYLOAD_MAX; + + return 1 + (len / CONT_PAYLOAD_MAX); +} + +static inline u8_t next_transaction_id(const u8_t idx) +{ + if (link[idx].tx.trans_id > 0x7F) { + link[idx].tx.trans_id = 0x0; + } + return link[idx].tx.trans_id++; +} + +static int prov_send_adv(const u8_t idx, struct net_buf_simple *msg) +{ + struct net_buf *start, *buf; + u8_t seg_len, seg_id; + u8_t xact_id; + s32_t timeout = PROVISION_TIMEOUT; + + BT_DBG("%s, len %u: %s", __func__, msg->len, bt_hex(msg->data, msg->len)); + + prov_clear_tx(idx); + + start = adv_buf_create(); + if (!start) { + return -ENOBUFS; + } + + xact_id = next_transaction_id(idx); + net_buf_add_be32(start, link[idx].link_id); + net_buf_add_u8(start, xact_id); + + net_buf_add_u8(start, GPC_START(last_seg(msg->len))); + net_buf_add_be16(start, msg->len); + net_buf_add_u8(start, bt_mesh_fcs_calc(msg->data, msg->len)); + + link[idx].tx.buf[0] = start; + /* Changed by Espressif, get message type */ + link[idx].tx_pdu_type = msg->data[0]; + + seg_len = MIN(msg->len, START_PAYLOAD_MAX); + BT_DBG("seg 0 len %u: %s", seg_len, bt_hex(msg->data, seg_len)); + net_buf_add_mem(start, msg->data, seg_len); + net_buf_simple_pull(msg, seg_len); + + buf = start; + for (seg_id = 1; msg->len > 0; seg_id++) { + if (seg_id >= ARRAY_SIZE(link[idx].tx.buf)) { + BT_ERR("%s, Too big message", __func__); + free_segments(idx); + return -E2BIG; + } + + buf = adv_buf_create(); + if (!buf) { + free_segments(idx); + return -ENOBUFS; + } + + link[idx].tx.buf[seg_id] = buf; + + seg_len = MIN(msg->len, CONT_PAYLOAD_MAX); + + BT_DBG("seg_id %u len %u: %s", seg_id, seg_len, + bt_hex(msg->data, seg_len)); + + net_buf_add_be32(buf, link[idx].link_id); + net_buf_add_u8(buf, xact_id); + net_buf_add_u8(buf, GPC_CONT(seg_id)); + net_buf_add_mem(buf, msg->data, seg_len); + net_buf_simple_pull(msg, seg_len); + } + + send_reliable(idx); + +#if defined(CONFIG_BLE_MESH_FAST_PROV) + if (link[idx].tx_pdu_type >= PROV_DATA) { + timeout = K_SECONDS(60); + } +#endif + if (!bt_mesh_atomic_test_and_set_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_submit(&link[idx].timeout, timeout); + } + + return 0; +} +#endif /* CONFIG_BLE_MESH_PB_ADV */ + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static int prov_send_gatt(const u8_t idx, struct net_buf_simple *msg) +{ + int err; + + if (!link[idx].conn) { + return -ENOTCONN; + } + + err = provisioner_proxy_send(link[idx].conn, BLE_MESH_PROXY_PROV, msg); + if (err) { + BT_ERR("%s, Failed to send PB-GATT pdu", __func__); + return err; + } + + if (!bt_mesh_atomic_test_and_set_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_submit(&link[idx].timeout, PROVISION_TIMEOUT); + } + + return 0; +} +#endif /* CONFIG_BLE_MESH_PB_GATT */ + +static inline int prov_send(const u8_t idx, struct net_buf_simple *buf) +{ +#if defined(CONFIG_BLE_MESH_PB_ADV) + if (idx < CONFIG_BLE_MESH_PBA_SAME_TIME) { + return prov_send_adv(idx, buf); + } +#endif + +#if defined(CONFIG_BLE_MESH_PB_GATT) + if (idx < BLE_MESH_PROV_SAME_TIME +#if defined(CONFIG_BLE_MESH_PB_ADV) + && idx >= CONFIG_BLE_MESH_PBA_SAME_TIME +#endif + ) { + return prov_send_gatt(idx, buf); + } +#endif + + BT_ERR("%s, Invalid link index %d", __func__, idx); + return -EINVAL; +} + +static void prov_buf_init(struct net_buf_simple *buf, u8_t type) +{ + net_buf_simple_reserve(buf, PROV_BUF_HEADROOM); + net_buf_simple_add_u8(buf, type); +} + +static void prov_invite(const u8_t idx, const u8_t *data) +{ + BT_DBG("%s", __func__); +} + +static void prov_start(const u8_t idx, const u8_t *data) +{ + BT_DBG("%s", __func__); +} + +static void prov_data(const u8_t idx, const u8_t *data) +{ + BT_DBG("%s", __func__); +} + +static void send_invite(const u8_t idx) +{ + PROV_BUF(buf, 2); + + prov_buf_init(&buf, PROV_INVITE); + + net_buf_simple_add_u8(&buf, prov->prov_attention); + + link[idx].conf_inputs[0] = prov->prov_attention; + + if (prov_send(idx, &buf)) { + BT_ERR("%s, Failed to send Provisioning Invite", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + link[idx].expect = PROV_CAPABILITIES; +} + +static void prov_capabilities(const u8_t idx, const u8_t *data) +{ + PROV_BUF(buf, 6); + u16_t algorithms, output_action, input_action; + u8_t element_num, pub_key_oob, static_oob, + output_size, input_size; + u8_t auth_method, auth_action, auth_size; + + element_num = data[0]; + BT_DBG("Elements: %u", element_num); + if (!element_num) { + BT_ERR("%s, Invalid element number", __func__); + goto fail; + } + link[idx].element_num = element_num; + + algorithms = sys_get_be16(&data[1]); + BT_DBG("Algorithms: %u", algorithms); + if (algorithms != BIT(PROV_ALG_P256)) { + BT_ERR("%s, Invalid algorithms", __func__); + goto fail; + } + + pub_key_oob = data[3]; + BT_DBG("Public Key Type: 0x%02x", pub_key_oob); + if (pub_key_oob > 0x01) { + BT_ERR("%s, Invalid public key type", __func__); + goto fail; + } + pub_key_oob = ((prov->prov_pub_key_oob && + prov->prov_pub_key_oob_cb) ? pub_key_oob : 0x00); + + static_oob = data[4]; + BT_DBG("Static OOB Type: 0x%02x", static_oob); + if (static_oob > 0x01) { + BT_ERR("%s, Invalid Static OOB type", __func__); + goto fail; + } + static_oob = (prov->prov_static_oob_val ? static_oob : 0x00); + + output_size = data[5]; + BT_DBG("Output OOB Size: %u", output_size); + if (output_size > 0x08) { + BT_ERR("%s, Invalid Output OOB size", __func__); + goto fail; + } + + output_action = sys_get_be16(&data[6]); + BT_DBG("Output OOB Action: 0x%04x", output_action); + if (output_action > 0x1f) { + BT_ERR("%s, Invalid Output OOB action", __func__); + goto fail; + } + + /* Provisioner select output action */ + if (prov->prov_input_num && output_size) { + output_action = __builtin_ctz(output_action); + } else { + output_size = 0x0; + output_action = 0x0; + } + + input_size = data[8]; + BT_DBG("Input OOB Size: %u", input_size); + if (input_size > 0x08) { + BT_ERR("%s, Invalid Input OOB size", __func__); + goto fail; + } + + input_action = sys_get_be16(&data[9]); + BT_DBG("Input OOB Action: 0x%04x", input_action); + if (input_action > 0x0f) { + BT_ERR("%s, Invalid Input OOB action", __func__); + goto fail; + } + + /* Make sure received pdu is ok and cancel the timeout timer */ + if (bt_mesh_atomic_test_and_clear_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link[idx].timeout); + } + + /* Provisioner select input action */ + if (prov->prov_output_num && input_size) { + input_action = __builtin_ctz(input_action); + } else { + input_size = 0x0; + input_action = 0x0; + } + + if (static_oob) { + /* if static oob is valid, just use static oob */ + auth_method = AUTH_METHOD_STATIC; + auth_action = 0x00; + auth_size = 0x00; + } else { + if (!output_size && !input_size) { + auth_method = AUTH_METHOD_NO_OOB; + auth_action = 0x00; + auth_size = 0x00; + } else if (!output_size && input_size) { + auth_method = AUTH_METHOD_INPUT; + auth_action = (u8_t)input_action; + auth_size = input_size; + } else { + auth_method = AUTH_METHOD_OUTPUT; + auth_action = (u8_t)output_action; + auth_size = output_size; + } + } + + /* Store provisioning capbilities value in conf_inputs */ + memcpy(&link[idx].conf_inputs[1], data, 11); + + prov_buf_init(&buf, PROV_START); + net_buf_simple_add_u8(&buf, prov->prov_algorithm); + net_buf_simple_add_u8(&buf, pub_key_oob); + net_buf_simple_add_u8(&buf, auth_method); + net_buf_simple_add_u8(&buf, auth_action); + net_buf_simple_add_u8(&buf, auth_size); + + memcpy(&link[idx].conf_inputs[12], &buf.data[1], 5); + + if (prov_send(idx, &buf)) { + BT_ERR("%s, Failed to send Provisioning Start", __func__); + goto fail; + } + + link[idx].auth_method = auth_method; + link[idx].auth_action = auth_action; + link[idx].auth_size = auth_size; + + /** After prov start sent, use OOB to get remote public key. + * And we just follow the procedure in Figure 5.15 of Section + * 5.4.2.3 of Mesh Profile Spec. + */ + if (pub_key_oob) { + if (prov->prov_pub_key_oob_cb(idx)) { + BT_ERR("%s, Failed to notify input OOB Public Key", __func__); + goto fail; + } + } + + /** If using PB-ADV, need to listen for transaction ack, + * after ack is received, provisioner can send public key. + */ +#if defined(CONFIG_BLE_MESH_PB_ADV) + if (idx < CONFIG_BLE_MESH_PBA_SAME_TIME) { + link[idx].expect_ack_for = PROV_START; + return; + } +#endif /* CONFIG_BLE_MESH_PB_ADV */ + + send_pub_key(idx, pub_key_oob); + return; + +fail: + close_link(idx, CLOSE_REASON_FAILED); + return; +} + +static bt_mesh_output_action_t output_action(u8_t action) +{ + switch (action) { + case OUTPUT_OOB_BLINK: + return BLE_MESH_BLINK; + case OUTPUT_OOB_BEEP: + return BLE_MESH_BEEP; + case OUTPUT_OOB_VIBRATE: + return BLE_MESH_VIBRATE; + case OUTPUT_OOB_NUMBER: + return BLE_MESH_DISPLAY_NUMBER; + case OUTPUT_OOB_STRING: + return BLE_MESH_DISPLAY_STRING; + default: + return BLE_MESH_NO_OUTPUT; + } +} + +static bt_mesh_input_action_t input_action(u8_t action) +{ + switch (action) { + case INPUT_OOB_PUSH: + return BLE_MESH_PUSH; + case INPUT_OOB_TWIST: + return BLE_MESH_TWIST; + case INPUT_OOB_NUMBER: + return BLE_MESH_ENTER_NUMBER; + case INPUT_OOB_STRING: + return BLE_MESH_ENTER_STRING; + default: + return BLE_MESH_NO_INPUT; + } +} + +static int prov_auth(const u8_t idx, u8_t method, u8_t action, u8_t size) +{ + bt_mesh_output_action_t output; + bt_mesh_input_action_t input; + + link[idx].auth = (u8_t *)osi_calloc(PROV_AUTH_VAL_SIZE); + if (!link[idx].auth) { + BT_ERR("%s, Failed to allocate memory", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return -ENOMEM; + } + + switch (method) { + case AUTH_METHOD_NO_OOB: + if (action || size) { + return -EINVAL; + } + memset(link[idx].auth, 0, 16); + return 0; + + case AUTH_METHOD_STATIC: + if (action || size) { + return -EINVAL; + } + memcpy(link[idx].auth + 16 - prov->prov_static_oob_len, + prov->prov_static_oob_val, prov->prov_static_oob_len); + memset(link[idx].auth, 0, 16 - prov->prov_static_oob_len); + return 0; + + case AUTH_METHOD_OUTPUT: + /* Use auth_action to get device output action */ + output = output_action(action); + if (!output) { + return -EINVAL; + } + return prov->prov_input_num(AUTH_METHOD_OUTPUT, output, size, idx); + + case AUTH_METHOD_INPUT: + /* Use auth_action to get device input action */ + input = input_action(action); + if (!input) { + return -EINVAL; + } + + /* Provisioner ouputs number/string and wait for device's Provisioning Input Complete PDU */ + link[idx].expect = PROV_INPUT_COMPLETE; + + if (input == BLE_MESH_ENTER_STRING) { + unsigned char str[9]; + u8_t j; + + bt_mesh_rand(str, size); + /* Normalize to '0' .. '9' & 'A' .. 'Z' */ + for (j = 0; j < size; j++) { + str[j] %= 36; + if (str[j] < 10) { + str[j] += '0'; + } else { + str[j] += 'A' - 10; + } + } + str[size] = '\0'; + + memcpy(link[idx].auth, str, size); + memset(link[idx].auth + size, 0, sizeof(link[idx].auth) - size); + + return prov->prov_output_num(AUTH_METHOD_INPUT, input, str, size, idx); + } else { + u32_t div[8] = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + u32_t num; + + bt_mesh_rand(&num, sizeof(num)); + num %= div[size - 1]; + + sys_put_be32(num, &link[idx].auth[12]); + memset(link[idx].auth, 0, 12); + + return prov->prov_output_num(AUTH_METHOD_INPUT, input, &num, size, idx); + } + + default: + return -EINVAL; + } +} + +static void send_confirm(const u8_t idx) +{ + PROV_BUF(buf, 17); + + BT_DBG("ConfInputs[0] %s", bt_hex(link[idx].conf_inputs, 64)); + BT_DBG("ConfInputs[64] %s", bt_hex(link[idx].conf_inputs + 64, 64)); + BT_DBG("ConfInputs[128] %s", bt_hex(link[idx].conf_inputs + 128, 17)); + + link[idx].conf_salt = (u8_t *)osi_calloc(PROV_CONF_SALT_SIZE); + if (!link[idx].conf_salt) { + BT_ERR("%s, Failed to allocate memory", __func__); + goto fail; + } + + link[idx].conf_key = (u8_t *)osi_calloc(PROV_CONF_KEY_SIZE); + if (!link[idx].conf_key) { + BT_ERR("%s, Failed to allocate memory", __func__); + goto fail; + } + + if (bt_mesh_prov_conf_salt(link[idx].conf_inputs, link[idx].conf_salt)) { + BT_ERR("%s, Failed to generate confirmation salt", __func__); + goto fail; + } + + BT_DBG("ConfirmationSalt: %s", bt_hex(link[idx].conf_salt, 16)); + + if (bt_mesh_prov_conf_key(link[idx].dhkey, link[idx].conf_salt, link[idx].conf_key)) { + BT_ERR("%s, Failed to generate confirmation key", __func__); + goto fail; + } + + BT_DBG("ConfirmationKey: %s", bt_hex(link[idx].conf_key, 16)); + + /** Provisioner use the same random number for each provisioning + * device, if different random need to be used, here provisioner + * should allocate memory for rand and call bt_mesh_rand() every time. + */ + if (!(prov_ctx.rand_gen_done & BIT(0))) { + if (bt_mesh_rand(prov_ctx.random, 16)) { + BT_ERR("%s, Failed to generate random number", __func__); + goto fail; + } + link[idx].rand = prov_ctx.random; + prov_ctx.rand_gen_done |= BIT(0); + } else { + /* Provisioner random has already been generated. */ + link[idx].rand = prov_ctx.random; + } + + BT_DBG("LocalRandom: %s", bt_hex(link[idx].rand, 16)); + + prov_buf_init(&buf, PROV_CONFIRM); + + if (bt_mesh_prov_conf(link[idx].conf_key, link[idx].rand, link[idx].auth, + net_buf_simple_add(&buf, 16))) { + BT_ERR("%s, Failed to generate confirmation value", __func__); + goto fail; + } + + if (prov_send(idx, &buf)) { + BT_ERR("%s, Failed to send Provisioning Confirm", __func__); + goto fail; + } + + link[idx].expect = PROV_CONFIRM; + return; + +fail: + close_link(idx, CLOSE_REASON_FAILED); + return; +} + +int bt_mesh_prov_set_oob_input_data(const u8_t idx, const u8_t *val, bool num_flag) +{ + /** This function should be called in the prov_input_num + * callback, after the data output by device has been + * input by provisioner. + * Paramter size is used to indicate the length of data + * indicated by Pointer val, for example, if device output + * data is 12345678(decimal), the data in auth value will + * be 0xBC614E. + * Parameter num_flag is used to indicate whether the value + * input by provisioner is number or string. + */ + if (!link[idx].auth) { + BT_ERR("%s, Link auth is NULL", __func__); + return -EINVAL; + } + + memset(link[idx].auth, 0, 16); + if (num_flag) { + /* Provisioner inputs number */ + memcpy(link[idx].auth + 12, val, sizeof(u32_t)); + } else { + /* Provisioner inputs string */ + memcpy(link[idx].auth, val, link[idx].auth_size); + } + + send_confirm(idx); + return 0; +} + +#if 0 +int bt_mesh_prov_set_oob_output_data(const u8_t idx, u8_t *num, u8_t size, bool num_flag) +{ + /** This function should be called in the prov_output_num + * callback, after the data has been output by provisioner. + * Parameter size is used to indicate the length of data + * indicated by Pointer num, for example, if provisioner + * output data is 12345678(decimal), the data in auth value + * will be 0xBC614E. + * Parameter num_flag is used to indicate whether the value + * output by provisioner is number or string. + */ + if (!link[idx].auth) { + BT_ERR("%s, link auth is NULL", __func__); + return -EINVAL; + } + + if (num_flag) { + /* Provisioner output number */ + memset(link[idx].auth, 0, 16); + memcpy(link[idx].auth + 16 - size, num, size); + } else { + /* Provisioner output string */ + memset(link[idx].auth, 0, 16); + memcpy(link[idx].auth, num, size); + } + + link[idx].expect = PROV_INPUT_COMPLETE; + + return 0; +} +#endif + +int bt_mesh_prov_read_oob_pub_key(const u8_t idx, const u8_t pub_key_x[32], const u8_t pub_key_y[32]) +{ + if (!link[idx].conf_inputs) { + BT_ERR("%s, Link conf_inputs is NULL", __func__); + return -EINVAL; + } + + /* Swap X and Y halves independently to big-endian */ + sys_memcpy_swap(&link[idx].conf_inputs[81], pub_key_x, 32); + sys_memcpy_swap(&link[idx].conf_inputs[81] + 32, pub_key_y, 32); + + bt_mesh_atomic_set_bit(link[idx].flags, REMOTE_PUB_KEY); + + if (bt_mesh_atomic_test_and_clear_bit(link[idx].flags, WAIT_GEN_DHKEY)) { + prov_gen_dh_key(idx); + } + + return 0; +} + +static void prov_dh_key_cb(const u8_t key[32], const u8_t idx) +{ + BT_DBG("%p", key); + + if (!key) { + BT_ERR("%s, Failed to generate DHKey", __func__); + goto fail; + } + + link[idx].dhkey = (u8_t *)osi_calloc(PROV_DH_KEY_SIZE); + if (!link[idx].dhkey) { + BT_ERR("%s, Failed to allocate memory", __func__); + goto fail; + } + sys_memcpy_swap(link[idx].dhkey, key, 32); + + BT_DBG("DHkey: %s", bt_hex(link[idx].dhkey, 32)); + + bt_mesh_atomic_set_bit(link[idx].flags, HAVE_DHKEY); + + /** After dhkey is generated, if auth_method is No OOB or + * Static OOB, provisioner can start to send confirmation. + * If output OOB is used by the device, provisioner need + * to watch out the output number and input it as auth_val. + * If input OOB is used by the device, provisioner need + * to output a value, and wait for prov input complete pdu. + */ + if (prov_auth(idx, link[idx].auth_method, + link[idx].auth_action, link[idx].auth_size) < 0) { + BT_ERR("%s, Failed to authenticate", __func__); + goto fail; + } + if (link[idx].auth_method == AUTH_METHOD_OUTPUT || + link[idx].auth_method == AUTH_METHOD_INPUT) { + return; + } + + if (link[idx].expect != PROV_INPUT_COMPLETE) { + send_confirm(idx); + } + return; + +fail: + close_link(idx, CLOSE_REASON_FAILED); + return; +} + +static void prov_gen_dh_key(const u8_t idx) +{ + u8_t pub_key[64]; + + /* Copy device public key in little-endian for bt_mesh_dh_key_gen(). + * X and Y halves are swapped independently. + */ + sys_memcpy_swap(&pub_key[0], &link[idx].conf_inputs[81], 32); + sys_memcpy_swap(&pub_key[32], &link[idx].conf_inputs[113], 32); + + if (bt_mesh_dh_key_gen(pub_key, prov_dh_key_cb, idx)) { + BT_ERR("%s, Failed to generate DHKey", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } +} + +static void send_pub_key(const u8_t idx, u8_t oob) +{ + PROV_BUF(buf, 65); + const u8_t *key = NULL; + + key = bt_mesh_pub_key_get(); + if (!key) { + BT_ERR("%s, No public key available", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + BT_DBG("Local Public Key: %s", bt_hex(key, 64)); + + bt_mesh_atomic_set_bit(link[idx].flags, LOCAL_PUB_KEY); + + prov_buf_init(&buf, PROV_PUB_KEY); + + /* Swap X and Y halves independently to big-endian */ + sys_memcpy_swap(net_buf_simple_add(&buf, 32), key, 32); + sys_memcpy_swap(net_buf_simple_add(&buf, 32), &key[32], 32); + + /* Store provisioner public key value in conf_inputs */ + memcpy(&link[idx].conf_inputs[17], &buf.data[1], 64); + + if (prov_send(idx, &buf)) { + BT_ERR("%s, Failed to send Provisioning Public Key", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + if (!oob) { + link[idx].expect = PROV_PUB_KEY; + } else { + /** Have already got device public key. If next is to + * send confirm(not wait for input complete), need to + * wait for transactiona ack for public key then send + * provisioning confirm pdu. + */ +#if defined(CONFIG_BLE_MESH_PB_ADV) + if (idx < CONFIG_BLE_MESH_PBA_SAME_TIME) { + link[idx].expect_ack_for = PROV_PUB_KEY; + return; + } +#endif /* CONFIG_BLE_MESH_PB_ADV */ + + /* If remote public key has been read, then start to generate DHkey, + * otherwise wait for device oob public key. + */ + if (bt_mesh_atomic_test_bit(link[idx].flags, REMOTE_PUB_KEY)) { + prov_gen_dh_key(idx); + } else { + bt_mesh_atomic_set_bit(link[idx].flags, WAIT_GEN_DHKEY); + } + } +} + +static void prov_pub_key(const u8_t idx, const u8_t *data) +{ + BT_DBG("Remote Public Key: %s", bt_hex(data, 64)); + + /* Make sure received pdu is ok and cancel the timeout timer */ + if (bt_mesh_atomic_test_and_clear_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link[idx].timeout); + } + + memcpy(&link[idx].conf_inputs[81], data, 64); + + if (!bt_mesh_atomic_test_bit(link[idx].flags, LOCAL_PUB_KEY)) { + /* Clear retransmit timer */ +#if defined(CONFIG_BLE_MESH_PB_ADV) + prov_clear_tx(idx); +#endif + bt_mesh_atomic_set_bit(link[idx].flags, REMOTE_PUB_KEY); + BT_WARN("%s, Waiting for local public key", __func__); + return; + } + + prov_gen_dh_key(idx); +} + +static void prov_input_complete(const u8_t idx, const u8_t *data) +{ + /* Make sure received pdu is ok and cancel the timeout timer */ + if (bt_mesh_atomic_test_and_clear_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link[idx].timeout); + } + + /* Provisioner receives input complete and send confirm */ + send_confirm(idx); +} + +static void prov_confirm(const u8_t idx, const u8_t *data) +{ + /** + * Zephyr uses PROV_BUF(16). Currently test with PROV_BUF(16) + * and PROV_BUF(17) on branch feature/btdm_ble_mesh_debug both + * work fine. + */ + PROV_BUF(buf, 17); + + BT_DBG("Remote Confirm: %s", bt_hex(data, 16)); + + /* Make sure received pdu is ok and cancel the timeout timer */ + if (bt_mesh_atomic_test_and_clear_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link[idx].timeout); + } + + link[idx].conf = (u8_t *)osi_calloc(PROV_CONFIRM_SIZE); + if (!link[idx].conf) { + BT_ERR("%s, Failed to allocate memory", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + memcpy(link[idx].conf, data, 16); + + if (!bt_mesh_atomic_test_bit(link[idx].flags, HAVE_DHKEY)) { +#if defined(CONFIG_BLE_MESH_PB_ADV) + prov_clear_tx(idx); +#endif + bt_mesh_atomic_set_bit(link[idx].flags, SEND_CONFIRM); + } + + prov_buf_init(&buf, PROV_RANDOM); + + net_buf_simple_add_mem(&buf, link[idx].rand, 16); + + if (prov_send(idx, &buf)) { + BT_ERR("%s, Failed to send Provisioning Random", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + link[idx].expect = PROV_RANDOM; +} + +static void send_prov_data(const u8_t idx) +{ + PROV_BUF(buf, 34); + const u8_t *netkey = NULL; + bool already_flag = false; + u8_t session_key[16]; + u8_t nonce[13]; + u8_t pdu[25]; + u16_t max_addr; + u16_t j; + int err; + + err = bt_mesh_session_key(link[idx].dhkey, link[idx].prov_salt, session_key); + if (err) { + BT_ERR("%s, Failed to generate session key", __func__); + goto fail; + } + BT_DBG("SessionKey: %s", bt_hex(session_key, 16)); + + err = bt_mesh_prov_nonce(link[idx].dhkey, link[idx].prov_salt, nonce); + if (err) { + BT_ERR("%s, Failed to generate session nonce", __func__); + goto fail; + } + BT_DBG("Nonce: %s", bt_hex(nonce, 13)); + + /* Assign provisioning data for the device. Currently all provisioned devices + * will be added to the primary subnet, and may add an API to choose to which + * subnet will the device be provisioned later. + */ + if (FAST_PROV_FLAG_GET()) { + netkey = fast_prov_info.net_key; + if (!netkey) { + BT_ERR("%s, Failed to get NetKey for fast provisioning", __func__); + goto fail; + } + memcpy(pdu, netkey, 16); + sys_put_be16(fast_prov_info.net_idx, &pdu[16]); + pdu[18] = fast_prov_info.flags; + sys_put_be32(fast_prov_info.iv_index, &pdu[19]); + } else { + netkey = provisioner_net_key_get(prov_ctx.curr_net_idx); + if (!netkey) { + BT_ERR("%s, Failed to get NetKey for provisioning data", __func__); + goto fail; + } + memcpy(pdu, netkey, 16); + sys_put_be16(prov_ctx.curr_net_idx, &pdu[16]); + pdu[18] = prov_ctx.curr_flags; + sys_put_be32(prov_ctx.curr_iv_index, &pdu[19]); + } + + /* 1. The Provisioner must not reuse unicast addresses that have been + * allocated to a device and sent in a Provisioning Data PDU until + * the Provisioner receives an Unprovisioned Device beacon or + * Service Data for the Mesh Provisioning Service from that same + * device, identified using the Device UUID of the device. + * 2. Once the provisioning data for the device has been sent, we will + * add the data sent to this device into the already_prov_info. + * 3. Another situation here is: + * If the device is a re-provisioned one, but the element num has + * changed and is larger than the previous number, here we will + * assign new address for the device. + */ + + /* Check if this device is a re-provisioned device */ + for (j = 0U; j < ARRAY_SIZE(prov_ctx.already_prov); j++) { + if (!memcmp(link[idx].uuid, prov_ctx.already_prov[j].uuid, 16)) { + if (link[idx].element_num <= prov_ctx.already_prov[j].element_num) { + already_flag = true; + sys_put_be16(prov_ctx.already_prov[j].unicast_addr, &pdu[23]); + link[idx].unicast_addr = prov_ctx.already_prov[j].unicast_addr; + break; + } else { + /* TODO: If the device has a larger element number during the + * second provisioning, then if the device is provisioned the + * third time later, already_prov struct will have two elements + * containing the same device UUID but with different element + * number. So we may add a flag to indicate the unicast address + * in the smaller element can be reused by other devices when + * unicast address is exhausted. + */ + } + } + } + + max_addr = FAST_PROV_FLAG_GET() ? fast_prov_info.unicast_addr_max : 0x7FFF; + + if (!already_flag) { + /* If this device to be provisioned is a new device */ + if (!prov_ctx.current_addr) { + BT_ERR("%s, No unicast address can be assigned", __func__); + goto fail; + } + + if (prov_ctx.current_addr + link[idx].element_num - 1 > max_addr) { + BT_ERR("%s, Not enough unicast address for the device", __func__); + goto fail; + } + + sys_put_be16(prov_ctx.current_addr, &pdu[23]); + link[idx].unicast_addr = prov_ctx.current_addr; + } + + prov_buf_init(&buf, PROV_DATA); + + err = bt_mesh_prov_encrypt(session_key, nonce, pdu, net_buf_simple_add(&buf, 33)); + if (err) { + BT_ERR("%s, Failed to encrypt provisioning data", __func__); + goto fail; + } + + if (prov_send(idx, &buf)) { + BT_ERR("%s, Failed to send Provisioning Data", __func__); + goto fail; + } + + /* If provisioning data is sent successfully, add the assigned information + * into the already_prov_info struct if this device is a new one. And if + * sent successfully, update the current_addr in prov_ctx struct. + */ + if (!already_flag) { + for (j = 0U; j < ARRAY_SIZE(prov_ctx.already_prov); j++) { + if (!prov_ctx.already_prov[j].element_num) { + memcpy(prov_ctx.already_prov[j].uuid, link[idx].uuid, 16); + prov_ctx.already_prov[j].element_num = link[idx].element_num; + prov_ctx.already_prov[j].unicast_addr = link[idx].unicast_addr; + break; + } + } + + /* We update the next unicast address to be assigned here because + * if provisioner is provisioning two devices at the same time, we + * need to assign the unicast address for them correctly. Hence we + * should not update the prov_ctx.current_addr after the proper + * provisioning complete pdu is received. + */ + prov_ctx.current_addr += link[idx].element_num; + if (prov_ctx.current_addr > max_addr) { + /* No unicast address will be used for further provisioning */ + prov_ctx.current_addr = 0x0000; + } + } + + if (FAST_PROV_FLAG_GET()) { + link[idx].ki_flags = fast_prov_info.flags; + link[idx].iv_index = fast_prov_info.iv_index; + } else { + link[idx].ki_flags = prov_ctx.curr_flags; + link[idx].iv_index = prov_ctx.curr_iv_index; + } + + link[idx].expect = PROV_COMPLETE; + return; + +fail: + close_link(idx, CLOSE_REASON_FAILED); + return; +} + +static void prov_random(const u8_t idx, const u8_t *data) +{ + u8_t conf_verify[16]; + + BT_DBG("Remote Random: %s", bt_hex(data, 16)); + + if (bt_mesh_prov_conf(link[idx].conf_key, data, link[idx].auth, conf_verify)) { + BT_ERR("%s, Failed to calculate confirmation verification", __func__); + goto fail; + } + + if (memcmp(conf_verify, link[idx].conf, 16)) { + BT_ERR("%s, Invalid confirmation value", __func__); + BT_DBG("Received: %s", bt_hex(link[idx].conf, 16)); + BT_DBG("Calculated: %s", bt_hex(conf_verify, 16)); + goto fail; + } + + /*Verify received confirm is ok and cancel the timeout timer */ + if (bt_mesh_atomic_test_and_clear_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link[idx].timeout); + } + + /** After provisioner receives provisioning random from device, + * and successfully check the confirmation, the following + * should be done: + * 1. osi_calloc memory for prov_salt + * 2. calculate prov_salt + * 3. prepare provisioning data and send + */ + link[idx].prov_salt = (u8_t *)osi_calloc(PROV_PROV_SALT_SIZE); + if (!link[idx].prov_salt) { + BT_ERR("%s, Failed to allocate memory", __func__); + goto fail; + } + + if (bt_mesh_prov_salt(link[idx].conf_salt, link[idx].rand, data, + link[idx].prov_salt)) { + BT_ERR("%s, Failed to generate ProvisioningSalt", __func__); + goto fail; + } + + BT_DBG("ProvisioningSalt: %s", bt_hex(link[idx].prov_salt, 16)); + + send_prov_data(idx); + return; + +fail: + close_link(idx, CLOSE_REASON_FAILED); + return; +} + +static void prov_complete(const u8_t idx, const u8_t *data) +{ + u8_t device_key[16]; + u16_t rm = 0; + u16_t j; + int err; + + /* Make sure received pdu is ok and cancel the timeout timer */ + if (bt_mesh_atomic_test_and_clear_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link[idx].timeout); + } + + /* If provisioning complete is received, the provisioning device + * will be stored into the prov_node_info structure and become a + * node within the mesh network + */ + err = bt_mesh_dev_key(link[idx].dhkey, link[idx].prov_salt, device_key); + if (err) { + BT_ERR("%s, Failed to generate device key", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + for (j = 0U; j < ARRAY_SIZE(prov_nodes); j++) { + if (!prov_nodes[j].provisioned) { + prov_nodes[j].provisioned = true; + prov_nodes[j].oob_info = link[idx].oob_info; + prov_nodes[j].element_num = link[idx].element_num; + prov_nodes[j].unicast_addr = link[idx].unicast_addr; + if (FAST_PROV_FLAG_GET()) { + prov_nodes[j].net_idx = fast_prov_info.net_idx; + } else { + prov_nodes[j].net_idx = prov_ctx.curr_net_idx; + } + prov_nodes[j].flags = link[idx].ki_flags; + prov_nodes[j].iv_index = link[idx].iv_index; + prov_nodes[j].addr.type = link[idx].addr.type; + memcpy(prov_nodes[j].addr.val, link[idx].addr.val, BLE_MESH_ADDR_LEN); + memcpy(prov_nodes[j].uuid, link[idx].uuid, 16); + break; + } + } + + if (j == ARRAY_SIZE(prov_nodes)) { + BT_ERR("%s, Provisioned node queue is full", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + prov_ctx.node_count++; + + err = provisioner_node_provision(j, prov_nodes[j].uuid, prov_nodes[j].oob_info, + prov_nodes[j].unicast_addr, prov_nodes[j].element_num, + prov_nodes[j].net_idx, prov_nodes[j].flags, + prov_nodes[j].iv_index, device_key); + if (err) { + BT_ERR("%s, Failed to store node info", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + if (prov->prov_complete) { + prov->prov_complete(j, prov_nodes[j].uuid, prov_nodes[j].unicast_addr, + prov_nodes[j].element_num, prov_nodes[j].net_idx); + } + + err = provisioner_dev_find(&link[idx].addr, link[idx].uuid, &rm); + if (!err) { + if (unprov_dev[rm].flags & RM_AFTER_PROV) { + memset(&unprov_dev[rm], 0, sizeof(struct unprov_dev_queue)); + } + } else if (err == -ENODEV) { + BT_DBG("%s, Device is not found in queue", __func__); + } else { + BT_WARN("%s, Failed to remove device from queue", __func__); + } + + close_link(idx, CLOSE_REASON_SUCCESS); +} + +static void prov_failed(const u8_t idx, const u8_t *data) +{ + BT_WARN("%s, Error 0x%02x", __func__, data[0]); + + close_link(idx, CLOSE_REASON_FAILED); +} + +static const struct { + void (*func)(const u8_t idx, const u8_t *data); + u16_t len; +} prov_handlers[] = { + { prov_invite, 1 }, + { prov_capabilities, 11 }, + { prov_start, 5 }, + { prov_pub_key, 64 }, + { prov_input_complete, 0 }, + { prov_confirm, 16 }, + { prov_random, 16 }, + { prov_data, 33 }, + { prov_complete, 0 }, + { prov_failed, 1 }, +}; + +static void close_link(const u8_t idx, u8_t reason) +{ +#if defined(CONFIG_BLE_MESH_PB_ADV) + if (idx < CONFIG_BLE_MESH_PBA_SAME_TIME) { + bearer_ctl_send(idx, LINK_CLOSE, &reason, sizeof(reason)); + return; + } +#endif + +#if defined(CONFIG_BLE_MESH_PB_GATT) + if (idx < BLE_MESH_PROV_SAME_TIME +#if defined(CONFIG_BLE_MESH_PB_ADV) + && idx >= CONFIG_BLE_MESH_PBA_SAME_TIME +#endif + ) { + if (link[idx].conn) { + bt_mesh_gattc_disconnect(link[idx].conn); + } + return; + } +#endif + + BT_ERR("%s, Invalid link index %d", __func__, idx); + return; +} + +static void prov_timeout(struct k_work *work) +{ + u8_t idx = (u8_t)work->index; + + BT_DBG("%s", __func__); + + close_link(idx, CLOSE_REASON_TIMEOUT); +} + +#if defined(CONFIG_BLE_MESH_PB_ADV) +static void prov_retransmit(struct k_work *work) +{ + s64_t timeout = TRANSACTION_TIMEOUT; + u8_t idx = (u8_t)work->index; + u8_t i; + + BT_DBG("%s", __func__); + + if (!bt_mesh_atomic_test_bit(link[idx].flags, LINK_ACTIVE)) { + BT_WARN("%s, Link is not active", __func__); + return; + } + +#if defined(CONFIG_BLE_MESH_FAST_PROV) + if (link[idx].tx_pdu_type >= PROV_DATA) { + timeout = K_SECONDS(30); + } +#endif + if (k_uptime_get() - link[idx].tx.start > timeout) { + BT_WARN("Provisioner timeout, giving up transaction"); + reset_link(idx, CLOSE_REASON_TIMEOUT); + return; + } + + if (link[idx].send_link_close & BIT(0)) { + u8_t reason = (link[idx].send_link_close >> 1) & BIT_MASK(2); + u16_t count = (link[idx].send_link_close >> 3); + if (count >= 2) { + reset_link(idx, reason); + return; + } + link[idx].send_link_close += BIT(3); + } + + for (i = 0U; i < ARRAY_SIZE(link[idx].tx.buf); i++) { + struct net_buf *buf = link[idx].tx.buf[i]; + + if (!buf) { + break; + } + + if (BLE_MESH_ADV(buf)->busy) { + continue; + } + + BT_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (i + 1 < ARRAY_SIZE(link[idx].tx.buf) && link[idx].tx.buf[i + 1]) { + bt_mesh_adv_send(buf, NULL, NULL); + } else { + bt_mesh_adv_send(buf, &buf_sent_cb, (void *)(int)idx); + } + } +} + +static void link_ack(const u8_t idx, struct prov_rx *rx, struct net_buf_simple *buf) +{ + BT_DBG("len %u", buf->len); + + if (buf->len) { + BT_ERR("%s, Invalid Link ACK length", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + if (link[idx].expect == PROV_CAPABILITIES) { + BT_WARN("%s, Link ACK is already received", __func__); + return; + } + + link[idx].conf_inputs = (u8_t *)osi_calloc(PROV_CONF_INPUTS_SIZE); + if (!link[idx].conf_inputs) { + BT_ERR("%s, Failed to allocate memory", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + send_invite(idx); +} + +static void link_close(const u8_t idx, struct prov_rx *rx, struct net_buf_simple *buf) +{ + u8_t reason; + + BT_DBG("len %u", buf->len); + + reason = net_buf_simple_pull_u8(buf); + + reset_link(idx, reason); +} + +static void gen_prov_ctl(const u8_t idx, struct prov_rx *rx, struct net_buf_simple *buf) +{ + BT_DBG("op 0x%02x len %u", BEARER_CTL(rx->gpc), buf->len); + + switch (BEARER_CTL(rx->gpc)) { + case LINK_OPEN: + break; + + case LINK_ACK: + if (!bt_mesh_atomic_test_bit(link[idx].flags, LINK_ACTIVE)) { + return; + } + link_ack(idx, rx, buf); + break; + + case LINK_CLOSE: + if (!bt_mesh_atomic_test_bit(link[idx].flags, LINK_ACTIVE)) { + return; + } + link_close(idx, rx, buf); + break; + + default: + BT_ERR("%s, Unknown bearer opcode 0x%02x", __func__, BEARER_CTL(rx->gpc)); + return; + } +} + +static void prov_msg_recv(const u8_t idx) +{ + u8_t type = link[idx].rx.buf->data[0]; + + BT_DBG("type 0x%02x len %u", type, link[idx].rx.buf->len); + + /** + * Provisioner first checks information within the received + * Provisioning PDU. If the check succeeds then check fcs. + */ + if (type != PROV_FAILED && type != link[idx].expect) { + BT_ERR("%s, Unexpected msg 0x%02x != 0x%02x", __func__, type, link[idx].expect); + goto fail; + } + + if (type >= 0x0A) { + BT_ERR("%s, Unknown provisioning PDU type 0x%02x", __func__, type); + goto fail; + } + + if (1 + prov_handlers[type].len != link[idx].rx.buf->len) { + BT_ERR("%s, Invalid length %u for type 0x%02x", __func__, link[idx].rx.buf->len, type); + goto fail; + } + + if (!bt_mesh_fcs_check(link[idx].rx.buf, link[idx].rx.fcs)) { + BT_ERR("%s, Incorrect FCS", __func__); + goto fail; + } + + gen_prov_ack_send(idx, link[idx].rx.trans_id); + link[idx].rx.prev_id = link[idx].rx.trans_id; + link[idx].rx.trans_id = 0; + + prov_handlers[type].func(idx, &link[idx].rx.buf->data[1]); + return; + +fail: + close_link(idx, CLOSE_REASON_FAILED); + return; +} + +static void gen_prov_cont(const u8_t idx, struct prov_rx *rx, struct net_buf_simple *buf) +{ + u8_t seg = CONT_SEG_INDEX(rx->gpc); + + BT_DBG("len %u, seg_index %u", buf->len, seg); + + if (!link[idx].rx.seg && link[idx].rx.prev_id == rx->xact_id) { + BT_WARN("%s, Resending ack", __func__); + gen_prov_ack_send(idx, rx->xact_id); + return; + } + + if (rx->xact_id != link[idx].rx.trans_id) { + BT_WARN("%s, Data for unknown transaction (%u != %u)", + __func__, rx->xact_id, link[idx].rx.trans_id); + /** + * If Provisioner receives a Provisioning PDU with a mismatch + * transaction number, it just ignore it. + */ + return; + } + + if (seg > link[idx].rx.last_seg) { + BT_ERR("%s, Invalid segment index %u", __func__, seg); + goto fail; + } else if (seg == link[idx].rx.last_seg) { + u8_t expect_len; + + expect_len = (link[idx].rx.buf->len - 20 - + (23 * (link[idx].rx.last_seg - 1))); + if (expect_len != buf->len) { + BT_ERR("%s, Incorrect last seg len: %u != %u", + __func__, expect_len, buf->len); + goto fail; + } + } + + if (!(link[idx].rx.seg & BIT(seg))) { + BT_WARN("%s, Ignore already received segment", __func__); + return; + } + + memcpy(XACT_SEG_DATA(idx, seg), buf->data, buf->len); + XACT_SEG_RECV(idx, seg); + + if (!link[idx].rx.seg) { + prov_msg_recv(idx); + } + return; + +fail: + close_link(idx, CLOSE_REASON_FAILED); + return; +} + +static void gen_prov_ack(const u8_t idx, struct prov_rx *rx, struct net_buf_simple *buf) +{ + u8_t ack_type, pub_key_oob; + + BT_DBG("len %u", buf->len); + + if (!link[idx].tx.buf[0]) { + return; + } + + if (!link[idx].tx.trans_id) { + return; + } + + if (rx->xact_id == (link[idx].tx.trans_id - 1)) { + prov_clear_tx(idx); + + ack_type = link[idx].expect_ack_for; + switch (ack_type) { + case PROV_START: + pub_key_oob = link[idx].conf_inputs[13]; + send_pub_key(idx, pub_key_oob); + break; + case PROV_PUB_KEY: + prov_gen_dh_key(idx); + break; + default: + break; + } + link[idx].expect_ack_for = 0x00; + } +} + +static void gen_prov_start(const u8_t idx, struct prov_rx *rx, struct net_buf_simple *buf) +{ + if (link[idx].rx.seg) { + BT_WARN("%s, Get Start while there are unreceived segments", __func__); + return; + } + + if (link[idx].rx.prev_id == rx->xact_id) { + BT_WARN("%s, Resending ack", __func__); + gen_prov_ack_send(idx, rx->xact_id); + return; + } + + link[idx].rx.buf->len = net_buf_simple_pull_be16(buf); + link[idx].rx.trans_id = rx->xact_id; + link[idx].rx.fcs = net_buf_simple_pull_u8(buf); + + BT_DBG("len %u last_seg %u total_len %u fcs 0x%02x", buf->len, + START_LAST_SEG(rx->gpc), link[idx].rx.buf->len, link[idx].rx.fcs); + + /* Provisioner can not receive zero-length provisioning pdu */ + if (link[idx].rx.buf->len < 1) { + BT_ERR("%s, Ignoring zero-length provisioning PDU", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + if (link[idx].rx.buf->len > link[idx].rx.buf->size) { + BT_ERR("%s, Too large provisioning PDU (%u bytes)", + __func__, link[idx].rx.buf->len); + // close_link(i, CLOSE_REASON_FAILED); + return; + } + + if (START_LAST_SEG(rx->gpc) > 0 && link[idx].rx.buf->len <= 20) { + BT_ERR("%s, Too small total length for multi-segment PDU", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + link[idx].rx.seg = (1 << (START_LAST_SEG(rx->gpc) + 1)) - 1; + link[idx].rx.last_seg = START_LAST_SEG(rx->gpc); + memcpy(link[idx].rx.buf->data, buf->data, buf->len); + XACT_SEG_RECV(idx, 0); + + if (!link[idx].rx.seg) { + prov_msg_recv(idx); + } +} + +static const struct { + void (*const func)(const u8_t idx, struct prov_rx *rx, struct net_buf_simple *buf); + const u8_t require_link; + const u8_t min_len; +} gen_prov[] = { + { gen_prov_start, true, 3 }, + { gen_prov_ack, true, 0 }, + { gen_prov_cont, true, 0 }, + { gen_prov_ctl, true, 0 }, +}; + +static void gen_prov_recv(const u8_t idx, struct prov_rx *rx, struct net_buf_simple *buf) +{ + if (buf->len < gen_prov[GPCF(rx->gpc)].min_len) { + BT_ERR("%s, Too short GPC message type %u", __func__, GPCF(rx->gpc)); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + /** + * require_link can be used combining with link[].linking flag to + * set LINK_ACTIVE status after Link ACK is received. In this case + * there is no need to check LINK_ACTIVE status in find_link(). + */ + if (!bt_mesh_atomic_test_bit(link[idx].flags, LINK_ACTIVE) && + gen_prov[GPCF(rx->gpc)].require_link) { + BT_DBG("Ignoring message that requires active link"); + return; + } + + gen_prov[GPCF(rx->gpc)].func(idx, rx, buf); +} + +static int find_link(u32_t link_id, u8_t *idx) +{ + u8_t i; + + /* link for PB-ADV is from 0 to CONFIG_BLE_MESH_PBA_SAME_TIME */ + for (i = 0U; i < CONFIG_BLE_MESH_PBA_SAME_TIME; i++) { + if (bt_mesh_atomic_test_bit(link[i].flags, LINK_ACTIVE)) { + if (link[i].link_id == link_id) { + if (idx) { + *idx = i; + } + return 0; + } + } + } + + return -1; +} + +void provisioner_pb_adv_recv(struct net_buf_simple *buf) +{ + struct prov_rx rx = {0}; + u8_t idx; + + rx.link_id = net_buf_simple_pull_be32(buf); + if (find_link(rx.link_id, &idx) < 0) { + BT_DBG("%s, Data for unexpected link", __func__); + return; + } + + if (buf->len < 2) { + BT_ERR("%s, Too short provisioning packet (len %u)", __func__, buf->len); + close_link(idx, CLOSE_REASON_FAILED); + return; + } + + rx.xact_id = net_buf_simple_pull_u8(buf); + rx.gpc = net_buf_simple_pull_u8(buf); + + BT_DBG("link_id 0x%08x xact_id %u", rx.link_id, rx.xact_id); + + gen_prov_recv(idx, &rx, buf); +} +#endif /* CONFIG_BLE_MESH_PB_ADV */ + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static struct bt_mesh_conn *find_conn(struct bt_mesh_conn *conn, u8_t *idx) +{ + u8_t i; + + /* link for PB-GATT is from CONFIG_BLE_MESH_PBA_SAME_TIME to BLE_MESH_PROV_SAME_TIME */ + for (i = CONFIG_BLE_MESH_PBA_SAME_TIME; i < BLE_MESH_PROV_SAME_TIME; i++) { + if (bt_mesh_atomic_test_bit(link[i].flags, LINK_ACTIVE)) { + if (link[i].conn == conn) { + if (idx) { + *idx = i; + } + return conn; + } + } + } + + return NULL; +} + +int provisioner_pb_gatt_recv(struct bt_mesh_conn *conn, struct net_buf_simple *buf) +{ + u8_t type; + u8_t idx; + + BT_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (!find_conn(conn, &idx)) { + BT_ERR("%s, Data for unexpected connection", __func__); + return -ENOTCONN; + } + + if (buf->len < 1) { + BT_ERR("%s, Too short provisioning packet (len %u)", __func__, buf->len); + goto fail; + } + + type = net_buf_simple_pull_u8(buf); + if (type != PROV_FAILED && type != link[idx].expect) { + BT_ERR("%s, Unexpected msg 0x%02x != 0x%02x", __func__, type, link[idx].expect); + goto fail; + } + + if (type >= 0x0A) { + BT_ERR("%s, Unknown provisioning PDU type 0x%02x", __func__, type); + goto fail; + } + + if (prov_handlers[type].len != buf->len) { + BT_ERR("%s, Invalid length %u for type 0x%02x", __func__, buf->len, type); + goto fail; + } + + prov_handlers[type].func(idx, buf->data); + + return 0; + +fail: + /* Mesh Spec Section 5.4.4 Provisioning errors */ + close_link(idx, CLOSE_REASON_FAILED); + return -EINVAL; +} + +int provisioner_set_prov_conn(const u8_t addr[6], struct bt_mesh_conn *conn) +{ + u8_t i; + + if (!addr || !conn) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + for (i = CONFIG_BLE_MESH_PBA_SAME_TIME; i < BLE_MESH_PROV_SAME_TIME; i++) { + if (!memcmp(link[i].addr.val, addr, BLE_MESH_ADDR_LEN)) { + link[i].conn = bt_mesh_conn_ref(conn); + return 0; + } + } + + BT_ERR("%s, Address %s is not found", __func__, bt_hex(addr, BLE_MESH_ADDR_LEN)); + return -ENOMEM; +} + +int provisioner_pb_gatt_open(struct bt_mesh_conn *conn, u8_t *addr) +{ + u8_t idx = 0, i; + + BT_DBG("conn %p", conn); + + /** + * Double check if the device is currently being provisioned using PB-ADV. + * Provisioner binds conn with proper device when proxy_prov_connected() + * is invoked, and here after proper GATT procedures are completed, we just + * check if this conn already exists in the proxy servers array. + */ + for (i = CONFIG_BLE_MESH_PBA_SAME_TIME; i < BLE_MESH_PROV_SAME_TIME; i++) { + if (link[i].conn == conn) { + idx = i; + break; + } + } + + if (i == BLE_MESH_PROV_SAME_TIME) { + BT_ERR("%s, Link is not found", __func__); + return -ENOTCONN; + } + +#if defined(CONFIG_BLE_MESH_PB_ADV) + for (i = 0U; i < CONFIG_BLE_MESH_PBA_SAME_TIME; i++) { + if (bt_mesh_atomic_test_bit(link[i].flags, LINK_ACTIVE)) { + if (!memcmp(link[i].uuid, link[idx].uuid, 16)) { + BT_WARN("%s, Provision using PB-GATT & PB-ADV same time", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return -EALREADY; + } + } + } +#endif + + bt_mesh_atomic_set_bit(link[idx].flags, LINK_ACTIVE); + link[idx].conn = bt_mesh_conn_ref(conn); + + /* May use lcd to indicate starting provisioning each device */ + if (prov->prov_link_open) { + prov->prov_link_open(BLE_MESH_PROV_GATT); + } + + link[idx].conf_inputs = (u8_t *)osi_calloc(PROV_CONF_INPUTS_SIZE); + if (!link[idx].conf_inputs) { + /* Disconnect this connection, clear corresponding informations */ + BT_ERR("%s, Failed to allocate memory", __func__); + close_link(idx, CLOSE_REASON_FAILED); + return -ENOMEM; + } + + send_invite(idx); + return 0; +} + +int provisioner_pb_gatt_close(struct bt_mesh_conn *conn, u8_t reason) +{ + u8_t idx; + + BT_DBG("conn %p", conn); + + if (!find_conn(conn, &idx)) { + BT_ERR("%s, Conn %p is not found", __func__, conn); + return -ENOTCONN; + } + + if (bt_mesh_atomic_test_and_clear_bit(link[idx].flags, TIMEOUT_START)) { + k_delayed_work_cancel(&link[idx].timeout); + } + + if (prov->prov_link_close) { + prov->prov_link_close(BLE_MESH_PROV_GATT, reason); + } + + prov_memory_free(idx); + + memset(&link[idx], 0, offsetof(struct prov_link, timeout)); + + if (bt_mesh_pub_key_get()) { + bt_mesh_atomic_set_bit(link[idx].flags, LOCAL_PUB_KEY); + } + + return 0; +} +#endif /* CONFIG_BLE_MESH_PB_GATT */ + +int provisioner_prov_init(const struct bt_mesh_prov *prov_info) +{ + const u8_t *key = NULL; + u8_t i; + + if (!prov_info) { + BT_ERR("%s, No provisioning context provided", __func__); + return -EINVAL; + } + + if (CONFIG_BLE_MESH_PBG_SAME_TIME > BLE_MESH_MAX_CONN) { + BT_ERR("%s, PB-GATT same time exceeds max connection", __func__); + return -EINVAL; + } + + key = bt_mesh_pub_key_get(); + if (!key) { + BT_ERR("%s, Failed to generate Public Key", __func__); + return -EIO; + } + + prov = prov_info; + +#if defined(CONFIG_BLE_MESH_PB_ADV) + for (i = 0U; i < CONFIG_BLE_MESH_PBA_SAME_TIME; i++) { + struct prov_adv_buf *adv = &adv_buf[i]; + adv->buf.size = ADV_BUF_SIZE; + adv->buf.__buf = adv_buf_data + (i * ADV_BUF_SIZE); + + link[i].pending_ack = XACT_NVAL; + k_delayed_work_init(&link[i].tx.retransmit, prov_retransmit); + link[i].tx.retransmit.work.index = (int)i; + link[i].rx.prev_id = XACT_NVAL; + link[i].rx.buf = bt_mesh_pba_get_buf(i); + } +#endif + + for (i = 0U; i < BLE_MESH_PROV_SAME_TIME; i++) { + k_delayed_work_init(&link[i].timeout, prov_timeout); + link[i].timeout.work.index = (int)i; + } + + /* for PB-GATT, use servers[] array in proxy_provisioner.c */ + + prov_ctx.current_addr = prov->prov_start_address; + prov_ctx.curr_net_idx = BLE_MESH_KEY_PRIMARY; + prov_ctx.curr_flags = prov->flags; + prov_ctx.curr_iv_index = prov->iv_index; + + osi_mutex_new(&prov_ctx.pb_adv_lock); + osi_mutex_new(&prov_ctx.pb_gatt_lock); + + return 0; +} + +static bool is_unprov_dev_info_callback_to_app(bt_mesh_prov_bearer_t bearer, + const u8_t uuid[16], const bt_mesh_addr_t *addr, u16_t oob_info) +{ + u16_t index; + + if (prov_ctx.prov_after_match == false) { + u8_t adv_type = (bearer == BLE_MESH_PROV_ADV) ? + BLE_MESH_ADV_NONCONN_IND : BLE_MESH_ADV_IND; + + if (provisioner_dev_find(addr, uuid, &index)) { + BT_DBG("%s, Device is not in queue, notify to upper layer", __func__); + if (notify_unprov_adv_pkt_cb) { + notify_unprov_adv_pkt_cb(addr->val, addr->type, adv_type, uuid, oob_info, bearer); + } + return true; + } + + if (!(unprov_dev[index].bearer & bearer)) { + BT_WARN("Device in queue not support PB-%s", + (bearer == BLE_MESH_PROV_ADV) ? "ADV" : "GATT"); + if (notify_unprov_adv_pkt_cb) { + notify_unprov_adv_pkt_cb(addr->val, addr->type, adv_type, uuid, oob_info, bearer); + } + return true; + } + } + + return false; +} + +void provisioner_unprov_beacon_recv(struct net_buf_simple *buf) +{ +#if defined(CONFIG_BLE_MESH_PB_ADV) + const bt_mesh_addr_t *addr = NULL; + const u8_t *uuid = NULL; + u16_t oob_info; + + if (buf->len != 0x12 && buf->len != 0x16) { + BT_ERR("%s, Invalid Unprovisioned Device Beacon length", __func__); + return; + } + + if (prov_ctx.pba_count == CONFIG_BLE_MESH_PBA_SAME_TIME) { + BT_DBG("Current PB-ADV devices reach max limit"); + return; + } + + addr = bt_mesh_pba_get_addr(); + uuid = buf->data; + net_buf_simple_pull(buf, 16); + /* Mesh beacon uses big-endian to send beacon data */ + oob_info = net_buf_simple_pull_be16(buf); + + if (provisioner_check_unprov_dev_info(uuid)) { + return; + } + + if (is_unprov_dev_info_callback_to_app( + BLE_MESH_PROV_ADV, uuid, addr, oob_info)) { + return; + } + + provisioner_start_prov_pb_adv(uuid, addr, oob_info); +#endif /* CONFIG_BLE_MESH_PB_ADV */ +} + +bool provisioner_flags_match(struct net_buf_simple *buf) +{ + u8_t flags; + + if (buf->len != 1) { + BT_DBG("%s, Unexpected flags length", __func__); + return false; + } + + flags = net_buf_simple_pull_u8(buf); + + BT_DBG("Received adv pkt with flags: 0x%02x", flags); + + /* Flags context will not be checked curently */ + + return true; +} + +u16_t provisioner_srv_uuid_recv(struct net_buf_simple *buf) +{ + u16_t uuid; + + if (buf->len != 2) { + BT_DBG("Length not match mesh service uuid"); + return false; + } + + uuid = net_buf_simple_pull_le16(buf); + + BT_DBG("Received adv pkt with service UUID: %d", uuid); + + if ((uuid != BLE_MESH_UUID_MESH_PROV_VAL) && (uuid != BLE_MESH_UUID_MESH_PROXY_VAL)) { + return false; + } + + return uuid; +} + +static void provisioner_prov_srv_data_recv(struct net_buf_simple *buf, const bt_mesh_addr_t *addr); + +void provisioner_srv_data_recv(struct net_buf_simple *buf, const bt_mesh_addr_t *addr, u16_t uuid) +{ + u16_t uuid_type; + + if (!buf || !addr) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + uuid_type = net_buf_simple_pull_le16(buf); + if (uuid_type != uuid) { + BT_DBG("%s, Invalid Mesh Service Data UUID 0x%04x", __func__, uuid_type); + return; + } + + switch (uuid) { + case BLE_MESH_UUID_MESH_PROV_VAL: + if (buf->len != BLE_MESH_PROV_SRV_DATA_LEN) { + BT_WARN("%s, Invalid Mesh Prov Service Data length %d", __func__, buf->len); + return; + } + BT_DBG("Start to deal with Mesh Prov Service Data"); + provisioner_prov_srv_data_recv(buf, addr); + break; + case BLE_MESH_UUID_MESH_PROXY_VAL: + if (buf->len != BLE_MESH_PROXY_SRV_DATA_LEN1 && + buf->len != BLE_MESH_PROXY_SRV_DATA_LEN2) { + BT_ERR("%s, Invalid Mesh Proxy Service Data length %d", __func__, buf->len); + return; + } + BT_DBG("Start to deal with Mesh Proxy Service Data"); + provisioner_proxy_srv_data_recv(buf); + break; + default: + break; + } +} + +static void provisioner_prov_srv_data_recv(struct net_buf_simple *buf, const bt_mesh_addr_t *addr) +{ +#if defined(CONFIG_BLE_MESH_PB_GATT) + const u8_t *uuid = NULL; + u16_t oob_info; + + if (prov_ctx.pbg_count == CONFIG_BLE_MESH_PBG_SAME_TIME) { + BT_DBG("Current PB-GATT devices reach max limit"); + return; + } + + uuid = buf->data; + net_buf_simple_pull(buf, 16); + /* Mesh beacon uses big-endian to send beacon data */ + oob_info = net_buf_simple_pull_be16(buf); + + if (provisioner_check_unprov_dev_info(uuid)) { + return; + } + + if (is_unprov_dev_info_callback_to_app( + BLE_MESH_PROV_GATT, uuid, addr, oob_info)) { + return; + } + + /* Provisioner will copy the device uuid, oob info, etc. into an unused link + * struct, and at this moment the link has not been activated. Even if we + * receive an Unprovisioned Device Beacon and a Connectable Provisioning adv + * pkt from the same device, and store the device info received within each + * adv pkt into two link structs which will has no impact on the provisioning + * of this device, because no matter which link among PB-GATT and PB-ADV is + * activated first, the other one will be dropped finally and the link struct + * occupied by the dropped link will be used by other devices (because the link + * is not activated). + * Use connecting flag to prevent if two devices's adv pkts are both received, + * the previous one info will be replaced by the second one. + */ + provisioner_start_prov_pb_gatt(uuid, addr, oob_info); +#endif /* CONFIG_BLE_MESH_PB_GATT */ +} + +#endif /* CONFIG_BLE_MESH_PROVISIONER */ diff --git a/components/bt/ble_mesh/mesh_core/provisioner_prov.h b/components/bt/ble_mesh/mesh_core/provisioner_prov.h new file mode 100644 index 0000000000..30a2f94d76 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/provisioner_prov.h @@ -0,0 +1,379 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _PROVISIONER_PROV_H_ +#define _PROVISIONER_PROV_H_ + +#include "mesh_bearer_adapt.h" +#include "mesh_main.h" + +#if !CONFIG_BLE_MESH_PROVISIONER + +#define CONFIG_BLE_MESH_PBA_SAME_TIME 0 +#define CONFIG_BLE_MESH_PBG_SAME_TIME 0 + +#else + +#if !defined(CONFIG_BLE_MESH_PB_ADV) +#define CONFIG_BLE_MESH_PBA_SAME_TIME 0 +#endif /* !CONFIG_BLE_MESH_PB_ADV */ + +#if !defined(CONFIG_BLE_MESH_PB_GATT) +#define CONFIG_BLE_MESH_PBG_SAME_TIME 0 +#endif /* !CONFIG_BLE_MESH_PB_GATT */ + +#endif /* !CONFIG_BLE_MESH_PROVISIONER */ + +#define RM_AFTER_PROV BIT(0) +#define START_PROV_NOW BIT(1) +#define FLUSHABLE_DEV BIT(2) + +struct bt_mesh_unprov_dev_add { + u8_t addr[6]; + u8_t addr_type; + u8_t uuid[16]; + u16_t oob_info; + u8_t bearer; +}; + +struct bt_mesh_device_delete { + u8_t addr[6]; + u8_t addr_type; + u8_t uuid[16]; +}; + +#define NET_IDX_FLAG BIT(0) +#define FLAGS_FLAG BIT(1) +#define IV_INDEX_FLAG BIT(2) + +struct bt_mesh_prov_data_info { + union { + u16_t net_idx; + u8_t flags; + u32_t iv_index; + }; + u8_t flag; +}; + +/* The following APIs are for primary provisioner internal use */ + +/** + * @brief This function decrements the current PB-GATT count. + * + * @return None + */ +void provisioner_pbg_count_dec(void); + +/** + * @brief This function increments the current PB-GATT count. + * + * @return None + */ +void provisioner_pbg_count_inc(void); + +/** + * @brief This function clears the part of the link info of the proper device. + * + * @param[in] addr: Remote device address + * + * @return None + */ +void provisioner_clear_link_conn_info(const u8_t addr[6]); + +/** + * @brief This function handles the received PB-ADV PDUs. + * + * @param[in] buf: Pointer to the buffer containing generic provisioning PDUs + * + * @return Zero - success, otherwise - fail + */ +void provisioner_pb_adv_recv(struct net_buf_simple *buf); + +/** + * @brief This function sends provisioning invite to start + * provisioning this unprovisioned device. + * + * @param[in] addr: Remote device address + * @param[in] conn: Pointer to the bt_conn structure + * + * @return Zero - success, otherwise - fail + */ +int provisioner_set_prov_conn(const u8_t addr[6], struct bt_mesh_conn *conn); + +/** + * @brief This function sends provisioning invite to start + * provisioning this unprovisioned device. + * + * @param[in] conn: Pointer to the bt_conn structure + * @param[in] addr: Address of the connected device + * + * @return Zero - success, otherwise - fail + */ +int provisioner_pb_gatt_open(struct bt_mesh_conn *conn, u8_t *addr); + +/** + * @brief This function resets the used information when + * related connection is terminated. + * + * @param[in] conn: Pointer to the bt_conn structure + * @param[in] reason: Connection terminated reason + * + * @return Zero - success, otherwise - fail + */ +int provisioner_pb_gatt_close(struct bt_mesh_conn *conn, u8_t reason); + +/** + * @brief This function handles the received PB-GATT provision + * PDUs. + * + * @param[in] conn: Pointer to the bt_conn structure + * @param[in] buf: Pointer to the buffer containing provision PDUs + * + * @return Zero - success, otherwise - fail + */ +int provisioner_pb_gatt_recv(struct bt_mesh_conn *conn, struct net_buf_simple *buf); + +/** + * @brief This function initializes provisioner's PB-GATT and PB-ADV + * related information. + * + * @param[in] prov_info: Pointer to the application-initialized provisioner info. + * + * @return Zero - success, otherwise - fail + */ +int provisioner_prov_init(const struct bt_mesh_prov *prov_info); + +/** + * @brief This function parses the received unprovisioned device + * beacon advertising packets, and if checked, starts to provision this device + * using PB-ADV bearer. + * + * @param[in] buf: Pointer to the buffer containing unprovisioned device beacon + * + * @return None + */ +void provisioner_unprov_beacon_recv(struct net_buf_simple *buf); + +/** + * @brief This function parses the flags part of the + * received connectable mesh provisioning advertising packets. + * + * @param[in] buf: Pointer to the buffer containing advertising flags part + * + * @return True - success, False - fail + */ +bool provisioner_flags_match(struct net_buf_simple *buf); + +/** + * @brief This function parses the service UUID part of the + * received connectable mesh provisioning advertising packets. + * + * @param[in] buf: Pointer to the buffer containing service UUID part + * + * @return Zero - fail, otherwise - Service UUID(0x1827 or 0x1828) + */ +u16_t provisioner_srv_uuid_recv(struct net_buf_simple *buf); + +/** + * @brief This function parses the service data part of the + * received connectable mesh provisioning advertising packets. + * + * @param[in] buf: Pointer to the buffer containing the remianing service data part + * @param[in] addr: Pointer to the received device address + * @param[in] uuid: Service UUID contained in the service UUID part + * + * @return None + */ +void provisioner_srv_data_recv(struct net_buf_simple *buf, const bt_mesh_addr_t *addr, u16_t uuid); + +/** + * @brief This function gets the bt_mesh_prov pointer. + * + * @return bt_mesh_prov pointer(prov) + */ +const struct bt_mesh_prov *provisioner_get_prov_info(void); + +/** + * @brief This function resets all nodes information in provisioner_prov.c. + * + * @return Zero + */ +int provisioner_prov_reset_all_nodes(void); + +/* The following APIs are for primary provisioner application use */ + +/** @brief Add unprovisioned device info to unprov_dev queue + * + * @param[in] add_dev: Pointer to the structure containing the device information + * @param[in] flags: Flags indicate several operations of the device information + * - Remove device information from queue after it is provisioned (BIT0) + * - Start provisioning as soon as device is added to queue (BIT1) + * - Device can be flushed when device queue is full (BIT2) + * + * @return Zero on success or (negative) error code otherwise. + * + * @Note: 1. Currently address type only supports public address and static random address. + * 2. If device UUID and/or device address and address type already exist in the + * device queue, but the bearer differs from the existing one, add operation + * will also be successful and it will update the provision bearer supported by + * the device. + */ +int bt_mesh_provisioner_add_unprov_dev(struct bt_mesh_unprov_dev_add *add_dev, u8_t flags); + +/** @brief Delete device from queue, reset current provisioning link and reset the node + * + * @param[in] del_dev: Pointer to the structure containing the device information + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_provisioner_delete_device(struct bt_mesh_device_delete *del_dev); + +/** + * @brief This function sets a part of the device UUID for comparison before + * starting to provision the device. + * + * @param[in] offset: offset of the device UUID to be compared + * @param[in] length: length of the device UUID to be compared + * @param[in] match: value to be compared + * @param[in] prov_flag: flags indicate if uuid_match advertising packets are received, after that + * the device will be provisioned at once or reported to the application layer + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_provisioner_set_dev_uuid_match(u8_t offset, u8_t length, + const u8_t *match, bool prov_flag); + +/** @brief Callback for provisioner receiving advertising packet from unprovisioned devices which are + * not in the unprovisioned device queue. + * + * Report on the unprovisioned device beacon and mesh provisioning service advertising data to application layer + * + * @param addr Unprovisioned device address pointer + * @param addr_type Unprovisioned device address type + * @param dev_uuid Unprovisioned device device UUID pointer + * @param bearer Advertising packet received from PB-GATT or PB-ADV bearer + * @param adv_type Adv packet type, currently this is not used and we can use bearer to device + * the adv_type(ADV_IND or ADV_NONCONN_IND). This parameter will be used, when + * scan response data will be supported. + * + */ +typedef void (*unprov_adv_pkt_cb_t)(const u8_t addr[6], const u8_t addr_type, + const u8_t adv_type, const u8_t dev_uuid[16], + u16_t oob_info, bt_mesh_prov_bearer_t bearer); + +/** + * @brief This function registers the callback which notifies the application + * layer of the received mesh provisioning or unprovisioned device + * beacon advertizing packets (from devices not in the unprov device queue). + * + * @param[in] cb: Callback of the notifying adv pkts function + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_prov_adv_pkt_cb_register(unprov_adv_pkt_cb_t cb); + +/** + * @brief This function changes net_idx or flags or iv_index used in provisioning data. + * + * @param[in] info: Pointer of structure containing net_idx or flags or iv_index + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_provisioner_set_prov_data_info(struct bt_mesh_prov_data_info *info); + +/** + * @brief This function is called to input number/string out-put by unprovisioned device. + * + * @param[in] idx The provisioning link index + * @param[in] val Pointer of the input number/string + * @param[in] num_flag Flag indicates if it is a number or string + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_prov_set_oob_input_data(const u8_t idx, const u8_t *val, bool num_flag); + +/** + * @brief This function is called to output number/string which will be input by unprovisioned device. + * + * @param[in] idx The provisioning link index + * @param[in] num Pointer of the output number/string + * @param[in] size Size of the output number/string + * @param[in] num_flag Flag indicates if it is a number or string + * + * @return Zero - success, otherwise - fail + */ +#if 0 +int bt_mesh_prov_set_oob_output_data(const u8_t idx, u8_t *num, u8_t size, bool num_flag); +#endif + +/** + * @brief This function is called to read unprovisioned device's oob public key. + * + * @param[in] idx The provisioning link index + * @param[in] pub_key_x Unprovisioned device's Public Key X + * @param[in] pub_key_y Unprovisioned device's Public Key Y + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_prov_read_oob_pub_key(const u8_t idx, const u8_t pub_key_x[32], const u8_t pub_key_y[32]); + +/* The following APIs are for fast provisioning */ + +/** + * @brief This function is called to set fast_prov_flag. + * + * @param[in] flag: Flag set to fast_prov_flag + * + * @return None + */ +void provisioner_set_fast_prov_flag(bool flag); + +/** + * @brief This function is called to set netkey index used for fast provisioning. + * + * @param[in] net_key: Netkey value + * @param[in] net_idx: Netkey index + * + * @return status for set netkey index msg + */ +u8_t provisioner_set_fast_prov_net_idx(const u8_t *net_key, u16_t net_idx); + +/** + * @brief This function is called to get netkey index used for fast provisioning. + * + * @return net_idx of fast provisioning + */ +u16_t provisioner_get_fast_prov_net_idx(void); + +/** + * @brief This function is called to set unicast address range used for fast provisioning. + * + * @param[in] min: Minimum unicast address + * @param[in] max: Maximum unicast address + * + * @return status for set unicast address range message + */ +u8_t bt_mesh_set_fast_prov_unicast_addr_range(u16_t min, u16_t max); + +/** + * @brief This function is called to set flags & iv_index used for fast provisioning. + * + * @param[in] flags: Key refresh flag and iv update flag + * @param[in] iv_index: IV index + * + * @return None + */ +void bt_mesh_set_fast_prov_flags_iv_index(u8_t flags, u32_t iv_index); + +#endif /* _PROVISIONER_PROV_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/provisioner_proxy.c b/components/bt/ble_mesh/mesh_core/provisioner_proxy.c new file mode 100644 index 0000000000..2961af3c42 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/provisioner_proxy.c @@ -0,0 +1,608 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "sdkconfig.h" + +#include "mesh_bearer_adapt.h" +#include "mesh_trace.h" + +#include "net.h" +#include "beacon.h" +#include "foundation.h" +#include "provisioner_prov.h" +#include "provisioner_proxy.h" +#include "provisioner_beacon.h" + +#if CONFIG_BLE_MESH_PROVISIONER + +#define PDU_TYPE(data) (data[0] & BIT_MASK(6)) +#define PDU_SAR(data) (data[0] >> 6) + +#define SAR_COMPLETE 0x00 +#define SAR_FIRST 0x01 +#define SAR_CONT 0x02 +#define SAR_LAST 0x03 + +#define CFG_FILTER_SET 0x00 +#define CFG_FILTER_ADD 0x01 +#define CFG_FILTER_REMOVE 0x02 +#define CFG_FILTER_STATUS 0x03 + +#define PDU_HDR(sar, type) (sar << 6 | (type & BIT_MASK(6))) + +#define SERVER_BUF_SIZE 68 + +#define ID_TYPE_NET 0x00 +#define ID_TYPE_NODE 0x01 + +#define NODE_ID_LEN 19 +#define NET_ID_LEN 11 + +#define CLOSE_REASON_PROXY 0xFF + +static int conn_count; + +static struct bt_mesh_proxy_server { + struct bt_mesh_conn *conn; + /* Provisioner can use filter to double check the dst within mesh messages */ + u16_t filter[CONFIG_BLE_MESH_PROXY_FILTER_SIZE]; + enum __packed { + NONE, + WHITELIST, + BLACKLIST, + PROV, + } filter_type; + u8_t msg_type; + struct net_buf_simple buf; +} servers[BLE_MESH_MAX_CONN]; + +static u8_t server_buf_data[SERVER_BUF_SIZE * BLE_MESH_MAX_CONN]; + +static struct bt_mesh_proxy_server *find_server(struct bt_mesh_conn *conn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + if (servers[i].conn == conn) { + return &servers[i]; + } + } + + return NULL; +} + +static int filter_status(struct bt_mesh_proxy_server *server, + struct net_buf_simple *buf) +{ + /* TODO: Deal with received proxy configuration status messages */ + return 0; +} + +#if 0 +static void send_filter_set(struct bt_mesh_proxy_server *server, + struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + /* TODO: Act as proxy client, send proxy configuration set messages */ +} + +static void send_filter_add(struct bt_mesh_proxy_server *server, + struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + /* TODO: Act as proxy client, send proxy configuration add messages */ +} + +static void send_filter_remove(struct bt_mesh_proxy_server *server, + struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + /* TODO: Act as proxy client, send proxy configuration remove messages */ +} +#endif + +static void proxy_cfg(struct bt_mesh_proxy_server *server) +{ + NET_BUF_SIMPLE_DEFINE(buf, 29); + struct bt_mesh_net_rx rx; + u8_t opcode; + int err; + + /** In order to deal with proxy configuration messages, provisioner should + * do sth. like create mesh network after each device is provisioned. + */ + err = bt_mesh_net_decode(&server->buf, BLE_MESH_NET_IF_PROXY_CFG, + &rx, &buf); + if (err) { + BT_ERR("%s, Failed to decode Proxy Configuration (err %d)", __func__, err); + return; + } + + /* Remove network headers */ + net_buf_simple_pull(&buf, BLE_MESH_NET_HDR_LEN); + + BT_DBG("%u bytes: %s", buf.len, bt_hex(buf.data, buf.len)); + + if (buf.len < 1) { + BT_WARN("Too short proxy configuration PDU"); + return; + } + + opcode = net_buf_simple_pull_u8(&buf); + switch (opcode) { + case CFG_FILTER_STATUS: + filter_status(server, &buf); + break; + default: + BT_WARN("Unhandled configuration OpCode 0x%02x", opcode); + break; + } +} + +static void proxy_complete_pdu(struct bt_mesh_proxy_server *server) +{ + switch (server->msg_type) { +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + case BLE_MESH_PROXY_NET_PDU: + BT_DBG("Mesh Network PDU"); + bt_mesh_net_recv(&server->buf, 0, BLE_MESH_NET_IF_PROXY); + break; + case BLE_MESH_PROXY_BEACON: + BT_DBG("Mesh Beacon PDU"); + provisioner_beacon_recv(&server->buf); + break; + case BLE_MESH_PROXY_CONFIG: + BT_DBG("Mesh Configuration PDU"); + proxy_cfg(server); + break; +#endif +#if defined(CONFIG_BLE_MESH_PB_GATT) + case BLE_MESH_PROXY_PROV: + BT_DBG("Mesh Provisioning PDU"); + provisioner_pb_gatt_recv(server->conn, &server->buf); + break; +#endif + default: + BT_WARN("Unhandled Message Type 0x%02x", server->msg_type); + break; + } + + net_buf_simple_reset(&server->buf); +} + +#define ATTR_IS_PROV(uuid) (uuid == BLE_MESH_UUID_MESH_PROV_VAL) + +static ssize_t proxy_recv(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, const void *buf, + u16_t len, u16_t offset, u8_t flags) +{ + struct bt_mesh_proxy_server *server = find_server(conn); + const u8_t *data = buf; + u16_t srvc_uuid = 0; + + if (!server) { + return -ENOTCONN; + } + + if (len < 1) { + BT_WARN("Too small Proxy PDU"); + return -EINVAL; + } + + srvc_uuid = bt_mesh_gattc_get_service_uuid(conn); + if (!srvc_uuid) { + BT_ERR("%s, No service uuid found", __func__); + return -ENOTCONN; + } + + if (ATTR_IS_PROV(srvc_uuid) != (PDU_TYPE(data) == BLE_MESH_PROXY_PROV)) { + BT_WARN("Proxy PDU type doesn't match GATT service uuid"); + return -EINVAL; + } + + if (len - 1 > net_buf_simple_tailroom(&server->buf)) { + BT_WARN("Too big proxy PDU"); + return -EINVAL; + } + + switch (PDU_SAR(data)) { + case SAR_COMPLETE: + if (server->buf.len) { + BT_WARN("Complete PDU while a pending incomplete one"); + return -EINVAL; + } + + server->msg_type = PDU_TYPE(data); + net_buf_simple_add_mem(&server->buf, data + 1, len - 1); + proxy_complete_pdu(server); + break; + + case SAR_FIRST: + if (server->buf.len) { + BT_WARN("First PDU while a pending incomplete one"); + return -EINVAL; + } + + server->msg_type = PDU_TYPE(data); + net_buf_simple_add_mem(&server->buf, data + 1, len - 1); + break; + + case SAR_CONT: + if (!server->buf.len) { + BT_WARN("Continuation with no prior data"); + return -EINVAL; + } + + if (server->msg_type != PDU_TYPE(data)) { + BT_WARN("Unexpected message type in continuation"); + return -EINVAL; + } + + net_buf_simple_add_mem(&server->buf, data + 1, len - 1); + break; + + case SAR_LAST: + if (!server->buf.len) { + BT_WARN("Last SAR PDU with no prior data"); + return -EINVAL; + } + + if (server->msg_type != PDU_TYPE(data)) { + BT_WARN("Unexpected message type in last SAR PDU"); + return -EINVAL; + } + + net_buf_simple_add_mem(&server->buf, data + 1, len - 1); + proxy_complete_pdu(server); + break; + } + + return len; +} + +static void proxy_prov_connected(const u8_t addr[6], struct bt_mesh_conn *conn, int id) +{ + struct bt_mesh_proxy_server *server = NULL; + + conn_count++; + + if (!servers[id].conn) { + server = &servers[id]; + } + + if (!server) { + BT_ERR("%s, No matching Proxy Client objects", __func__); + /** Disconnect current connection, clear part of prov_link + * information, like uuid, dev_addr, linking flag, etc. + */ + + return; + } + + server->conn = bt_mesh_conn_ref(conn); + server->filter_type = NONE; + memset(server->filter, 0, sizeof(server->filter)); + net_buf_simple_reset(&server->buf); + +#if defined(CONFIG_BLE_MESH_PB_GATT) + if (provisioner_set_prov_conn(addr, server->conn)) { + BT_ERR("%s, provisioner_set_prov_conn failed", __func__); + bt_mesh_gattc_disconnect(server->conn); + return; + } +#endif + + bt_mesh_gattc_exchange_mtu(id); +} + +static void proxy_prov_disconnected(struct bt_mesh_conn *conn, u8_t reason) +{ + struct bt_mesh_proxy_server *server = NULL; + int i; + + BT_DBG("conn %p, handle is %d, reason 0x%02x", conn, conn->handle, reason); + + if (conn_count) { + conn_count--; + } + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + server = &servers[i]; + if (server->conn == conn) { + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + server->filter_type == PROV) { + provisioner_pb_gatt_close(conn, reason); + } + server->conn = NULL; + break; + } + } +} + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static ssize_t prov_write_ccc_descr(struct bt_mesh_conn *conn, u8_t *addr) +{ + struct bt_mesh_proxy_server *server; + + server = find_server(conn); + + if (!server) { + BT_ERR("%s, No Proxy Server found", __func__); + return -ENOTCONN; + } + + if (server->filter_type == NONE) { + server->filter_type = PROV; + return provisioner_pb_gatt_open(conn, addr); + } + + return -EINVAL; +} + +static ssize_t prov_notification(struct bt_mesh_conn *conn, u8_t *data, u16_t len) +{ + struct bt_mesh_proxy_server *server; + + server = find_server(conn); + + if (!server) { + BT_ERR("%s, No Proxy Server found", __func__); + return -ENOTCONN; + } + + if (server->filter_type == PROV) { + return proxy_recv(conn, NULL, data, len, 0, 0); + } + + return -EINVAL; +} + +int provisioner_pb_gatt_enable(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + if (servers[i].conn) { + servers[i].filter_type = PROV; + } + } + + return 0; +} + +int provisioner_pb_gatt_disable(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + struct bt_mesh_proxy_server *server = &servers[i]; + + if (server->conn && server->filter_type == PROV) { + bt_mesh_gattc_disconnect(server->conn); + server->filter_type = NONE; + } + } + + return 0; +} + +#endif /* CONFIG_BLE_MESH_PB_GATT */ + +#if defined(CONFIG_BLE_MESH_GATT_PROXY) +static ssize_t proxy_write_ccc_descr(struct bt_mesh_conn *conn) +{ + struct bt_mesh_proxy_server *server; + + server = find_server(conn); + + if (!server) { + BT_ERR("%s, No Proxy Server found", __func__); + return -ENOTCONN; + } + + if (server->filter_type == NONE) { + server->filter_type = WHITELIST; + return 0; + } + + return -EINVAL; +} + +static ssize_t proxy_notification(struct bt_mesh_conn *conn, u8_t *data, u16_t len) +{ + return proxy_recv(conn, NULL, data, len, 0, 0); +} + +/** Currently provisioner does't need bt_mesh_provisioner_proxy_enable() + * and bt_mesh_provisioner_proxy_disable() functions, and once they are + * used, provisioner can be enabled to parse node_id_adv and net_id_adv + * in order to support proxy client role. + * And if gatt_proxy is disabled, provisioner can stop dealing with + * these two kinds of connectable advertising packets. + */ +int bt_mesh_provisioner_proxy_enable(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + if (servers[i].conn) { + servers[i].filter_type = WHITELIST; + } + } + + /** TODO: Once at leat one device has been provisioned, provisioner + * can be set to allow receiving and parsing node_id & net_id adv + * packets, and we may use a global flag to indicate this. + */ + + return 0; +} + +static void bt_mesh_proxy_gatt_proxy_disconnect(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + struct bt_mesh_proxy_server *server = &servers[i]; + + if (server->conn && (server->filter_type == WHITELIST || + server->filter_type == BLACKLIST)) { + server->filter_type = NONE; + bt_mesh_gattc_disconnect(server->conn); + } + } +} + +int bt_mesh_provisioner_proxy_disable(void) +{ + BT_DBG("%s", __func__); + + /** TODO: Once this function is invoked, provisioner shall stop + * receiving and parsing node_id & net_id adv packets, and if + * proxy connection exists, we should disconnect it. + */ + + bt_mesh_proxy_gatt_proxy_disconnect(); + + return 0; +} + +#endif /* CONFIG_BLE_MESH_GATT_PROXY */ + +static int proxy_send(struct bt_mesh_conn *conn, const void *data, u16_t len) +{ + BT_DBG("%u bytes: %s", len, bt_hex(data, len)); + +#if defined(CONFIG_BLE_MESH_GATT_PROXY) || defined(CONFIG_BLE_MESH_PB_GATT) + return bt_mesh_gattc_write_no_rsp(conn, NULL, data, len); +#endif + + return 0; +} + +static int proxy_prov_segment_and_send(struct bt_mesh_conn *conn, u8_t type, + struct net_buf_simple *msg) +{ + u16_t mtu; + + if (conn == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + BT_DBG("conn %p type 0x%02x len %u: %s", conn, type, msg->len, + bt_hex(msg->data, msg->len)); + + mtu = bt_mesh_gattc_get_mtu_info(conn); + if (!mtu) { + BT_ERR("%s, Conn used to get mtu does not exist", __func__); + return -ENOTCONN; + } + + /* ATT_MTU - OpCode (1 byte) - Handle (2 bytes) */ + mtu -= 3; + if (mtu > msg->len) { + net_buf_simple_push_u8(msg, PDU_HDR(SAR_COMPLETE, type)); + return proxy_send(conn, msg->data, msg->len); + } + + net_buf_simple_push_u8(msg, PDU_HDR(SAR_FIRST, type)); + proxy_send(conn, msg->data, mtu); + net_buf_simple_pull(msg, mtu); + + while (msg->len) { + if (msg->len + 1 < mtu) { + net_buf_simple_push_u8(msg, PDU_HDR(SAR_LAST, type)); + proxy_send(conn, msg->data, msg->len); + break; + } + + net_buf_simple_push_u8(msg, PDU_HDR(SAR_CONT, type)); + proxy_send(conn, msg->data, mtu); + net_buf_simple_pull(msg, mtu); + } + + return 0; +} + +int provisioner_proxy_send(struct bt_mesh_conn *conn, u8_t type, + struct net_buf_simple *msg) +{ + struct bt_mesh_proxy_server *server = find_server(conn); + + if (!server) { + BT_ERR("$%s, No Proxy Server found", __func__); + return -ENOTCONN; + } + + if ((server->filter_type == PROV) != (type == BLE_MESH_PROXY_PROV)) { + BT_ERR("%s, Invalid PDU type for Proxy Client", __func__); + return -EINVAL; + } + + return proxy_prov_segment_and_send(conn, type, msg); +} + +static struct bt_mesh_prov_conn_cb conn_callbacks = { + .connected = proxy_prov_connected, + .disconnected = proxy_prov_disconnected, +#if defined(CONFIG_BLE_MESH_PB_GATT) + .prov_write_descr = prov_write_ccc_descr, + .prov_notify = prov_notification, +#endif /* CONFIG_BLE_MESH_PB_GATT */ +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + .proxy_write_descr = proxy_write_ccc_descr, + .proxy_notify = proxy_notification, +#endif /* CONFIG_BLE_MESH_GATT_PROXY */ +}; + +void provisioner_proxy_srv_data_recv(struct net_buf_simple *buf) +{ + /** TODO: Parse node_id_adv or net_id_adv pkts. Currently we + * don't support this function, and if realized later, proxy + * client need to check if there is server structure left + * before create connection with a server. + * check conn_count & CONFIG_BLE_MESH_PBG_SAME_TIME + */ +} + +int provisioner_proxy_init(void) +{ + int i; + + /* Initialize the server receive buffers */ + for (i = 0; i < ARRAY_SIZE(servers); i++) { + struct bt_mesh_proxy_server *server = &servers[i]; + + server->buf.size = SERVER_BUF_SIZE; + server->buf.__buf = server_buf_data + (i * SERVER_BUF_SIZE); + } + + bt_mesh_gattc_conn_cb_register(&conn_callbacks); + + return 0; +} + +#endif /* CONFIG_BLE_MESH_PROVISIONER */ diff --git a/components/bt/ble_mesh/mesh_core/provisioner_proxy.h b/components/bt/ble_mesh/mesh_core/provisioner_proxy.h new file mode 100644 index 0000000000..5da92f433b --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/provisioner_proxy.h @@ -0,0 +1,89 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _PROVISIONER_PROXY_H_ +#define _PROVISIONER_PROXY_H_ + +#include "mesh_buf.h" + +#define BLE_MESH_PROXY_NET_PDU 0x00 +#define BLE_MESH_PROXY_BEACON 0x01 +#define BLE_MESH_PROXY_CONFIG 0x02 +#define BLE_MESH_PROXY_PROV 0x03 + +/** + * @brief This function is called to send proxy protocol messages. + * + * @param[in] conn: Pointer to bt_conn structure + * @param[in] type: Proxy protocol message type + * @param[in] msg: Pointer to the buffer contains sending message. + * + * @return Zero-success, other-fail + */ +int provisioner_proxy_send(struct bt_mesh_conn *conn, u8_t type, struct net_buf_simple *msg); + +/** + * @brief This function is called to parse received node identity and net + * id adv pkts and create connection if deceided to. + * + * @param[in] buf: Pointer to the buffer contains received message. + * + * @return None + */ +void provisioner_proxy_srv_data_recv(struct net_buf_simple *buf); + +/** + * @brief This function is called to initialize proxy provisioner structure + * and register proxy connection related callbacks. + * + * @return Zero-success, other-fail + */ +int provisioner_proxy_init(void); + +/** + * @brief This function is called to enable dealing with proxy provisioning + * messages. + * + * @return Zero-success, other-fail + */ +int provisioner_pb_gatt_enable(void); + +/** + * @brief This function is called to disable dealing with proxy provisioning + * messages and if proxy provisioning connections exist, the connections + * will be disconnected. + * + * @return Zero-success, other-fail + */ +int provisioner_pb_gatt_disable(void); + +/* The following APIs are for application use */ +/** + * @brief This function is called to enable receiving node identity and net + * id adv pkts. + * + * @return Zero-success, other-fail + */ +int bt_mesh_provisioner_proxy_enable(void); + +/** + * @brief This function is called to disable receiving node identity and net + * id adv pkts, and if proxy connections exist, these connections will + * be disconnected. + * + * @return Zero-success, other-fail + */ +int bt_mesh_provisioner_proxy_disable(void); + +#endif /* _PROVISIONER_PROXY_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/proxy.c b/components/bt/ble_mesh/mesh_core/proxy.c new file mode 100644 index 0000000000..fd9807f54b --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/proxy.c @@ -0,0 +1,1393 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_PROXY) + +#include "mesh_buf.h" +#include "mesh_util.h" +#include "mesh_bearer_adapt.h" +#include "mesh_trace.h" + +#include "mesh.h" +#include "adv.h" +#include "net.h" +#include "prov.h" +#include "beacon.h" +#include "foundation.h" +#include "access.h" +#include "proxy.h" + +#if CONFIG_BLE_MESH_NODE + +#define PDU_TYPE(data) (data[0] & BIT_MASK(6)) +#define PDU_SAR(data) (data[0] >> 6) + +#define SAR_COMPLETE 0x00 +#define SAR_FIRST 0x01 +#define SAR_CONT 0x02 +#define SAR_LAST 0x03 + +#define CFG_FILTER_SET 0x00 +#define CFG_FILTER_ADD 0x01 +#define CFG_FILTER_REMOVE 0x02 +#define CFG_FILTER_STATUS 0x03 + +#define PDU_HDR(sar, type) (sar << 6 | (type & BIT_MASK(6))) + +#define CLIENT_BUF_SIZE 68 + +#define ADV_OPT (BLE_MESH_ADV_OPT_CONNECTABLE | BLE_MESH_ADV_OPT_ONE_TIME) + +static const struct bt_mesh_adv_param slow_adv_param = { + .options = ADV_OPT, + .interval_min = BLE_MESH_GAP_ADV_SLOW_INT_MIN, + .interval_max = BLE_MESH_GAP_ADV_SLOW_INT_MAX, +}; + +static const struct bt_mesh_adv_param fast_adv_param = { + .options = ADV_OPT, + .interval_min = BLE_MESH_GAP_ADV_FAST_INT_MIN_0, + .interval_max = BLE_MESH_GAP_ADV_FAST_INT_MAX_0, +}; + +static bool proxy_adv_enabled; + +#if defined(CONFIG_BLE_MESH_GATT_PROXY) +static void proxy_send_beacons(struct k_work *work); +static u16_t proxy_ccc_val; +#endif + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static u16_t prov_ccc_val; +static bool prov_fast_adv; +#endif + +enum { + SAR_TIMER_START, /* Timer for SAR transfer has been started */ + NUM_FLAGS, +}; + +#define PROXY_SAR_TRANS_TIMEOUT K_SECONDS(20) + +static struct bt_mesh_proxy_client { + struct bt_mesh_conn *conn; + u16_t filter[CONFIG_BLE_MESH_PROXY_FILTER_SIZE]; + enum __packed { + NONE, + WHITELIST, + BLACKLIST, + PROV, + } filter_type; + u8_t msg_type; +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + struct k_work send_beacons; +#endif + struct net_buf_simple buf; + /* Proxy Server: 20s timeout for each segmented proxy pdu */ + BLE_MESH_ATOMIC_DEFINE(flags, NUM_FLAGS); + struct k_delayed_work sar_timer; +} clients[BLE_MESH_MAX_CONN] = { + [0 ... (BLE_MESH_MAX_CONN - 1)] = { +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + .send_beacons = _K_WORK_INITIALIZER(proxy_send_beacons), +#endif + }, +}; + +static u8_t client_buf_data[CLIENT_BUF_SIZE * BLE_MESH_MAX_CONN]; + +/* Track which service is enabled */ +static enum { + MESH_GATT_NONE, + MESH_GATT_PROV, + MESH_GATT_PROXY, +} gatt_svc = MESH_GATT_NONE; + +static char device_name[DEVICE_NAME_SIZE] = "ESP-BLE-MESH"; + +int bt_mesh_set_device_name(const char *name) +{ + if (!name) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (strlen(name) > DEVICE_NAME_SIZE) { + BT_ERR("%s, Too long device name", __func__); + return -EINVAL; + } + + memset(device_name, 0x0, sizeof(device_name)); + memcpy(device_name, name, strlen(name)); + + return 0; +} + +static struct bt_mesh_proxy_client *find_client(struct bt_mesh_conn *conn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(clients); i++) { + if (clients[i].conn == conn) { + return &clients[i]; + } + } + + return NULL; +} + +#if defined(CONFIG_BLE_MESH_GATT_PROXY) +/* Next subnet in queue to be advertised */ +static int next_idx; + +static int proxy_segment_and_send(struct bt_mesh_conn *conn, u8_t type, + struct net_buf_simple *msg); + +static int filter_set(struct bt_mesh_proxy_client *client, + struct net_buf_simple *buf) +{ + u8_t type; + + if (buf->len < 1) { + BT_WARN("Too short Filter Set message"); + return -EINVAL; + } + + type = net_buf_simple_pull_u8(buf); + BT_DBG("type 0x%02x", type); + + switch (type) { + case 0x00: + (void)memset(client->filter, 0, sizeof(client->filter)); + client->filter_type = WHITELIST; + break; + case 0x01: + (void)memset(client->filter, 0, sizeof(client->filter)); + client->filter_type = BLACKLIST; + break; + default: + BT_WARN("Prohibited Filter Type 0x%02x", type); + return -EINVAL; + } + + return 0; +} + +static void filter_add(struct bt_mesh_proxy_client *client, u16_t addr) +{ + int i; + + BT_DBG("addr 0x%04x", addr); + + if (addr == BLE_MESH_ADDR_UNASSIGNED) { + return; + } + + for (i = 0; i < ARRAY_SIZE(client->filter); i++) { + if (client->filter[i] == addr) { + return; + } + } + + for (i = 0; i < ARRAY_SIZE(client->filter); i++) { + if (client->filter[i] == BLE_MESH_ADDR_UNASSIGNED) { + client->filter[i] = addr; + return; + } + } +} + +static void filter_remove(struct bt_mesh_proxy_client *client, u16_t addr) +{ + int i; + + BT_DBG("addr 0x%04x", addr); + + if (addr == BLE_MESH_ADDR_UNASSIGNED) { + return; + } + + for (i = 0; i < ARRAY_SIZE(client->filter); i++) { + if (client->filter[i] == addr) { + client->filter[i] = BLE_MESH_ADDR_UNASSIGNED; + return; + } + } +} + +static void send_filter_status(struct bt_mesh_proxy_client *client, + struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_net_tx tx = { + .sub = rx->sub, + .ctx = &rx->ctx, + .src = bt_mesh_primary_addr(), + }; + u16_t filter_size; + int i, err; + + /* Configuration messages always have dst unassigned */ + tx.ctx->addr = BLE_MESH_ADDR_UNASSIGNED; + + net_buf_simple_reset(buf); + net_buf_simple_reserve(buf, 10); + + net_buf_simple_add_u8(buf, CFG_FILTER_STATUS); + + if (client->filter_type == WHITELIST) { + net_buf_simple_add_u8(buf, 0x00); + } else { + net_buf_simple_add_u8(buf, 0x01); + } + + for (filter_size = 0U, i = 0; i < ARRAY_SIZE(client->filter); i++) { + if (client->filter[i] != BLE_MESH_ADDR_UNASSIGNED) { + filter_size++; + } + } + + net_buf_simple_add_be16(buf, filter_size); + + BT_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + err = bt_mesh_net_encode(&tx, buf, true); + if (err) { + BT_ERR("%s, Encoding Proxy cfg message failed (err %d)", __func__, err); + return; + } + + err = proxy_segment_and_send(client->conn, BLE_MESH_PROXY_CONFIG, buf); + if (err) { + BT_ERR("%s, Failed to send proxy cfg message (err %d)", __func__, err); + } +} + +static void proxy_cfg(struct bt_mesh_proxy_client *client) +{ + NET_BUF_SIMPLE_DEFINE(buf, 29); + struct bt_mesh_net_rx rx; + u8_t opcode; + int err; + + err = bt_mesh_net_decode(&client->buf, BLE_MESH_NET_IF_PROXY_CFG, + &rx, &buf); + if (err) { + BT_ERR("%s, Failed to decode Proxy Configuration (err %d)", __func__, err); + return; + } + + /* Remove network headers */ + net_buf_simple_pull(&buf, BLE_MESH_NET_HDR_LEN); + + BT_DBG("%u bytes: %s", buf.len, bt_hex(buf.data, buf.len)); + + if (buf.len < 1) { + BT_WARN("Too short proxy configuration PDU"); + return; + } + + opcode = net_buf_simple_pull_u8(&buf); + switch (opcode) { + case CFG_FILTER_SET: + filter_set(client, &buf); + send_filter_status(client, &rx, &buf); + break; + case CFG_FILTER_ADD: + while (buf.len >= 2) { + u16_t addr; + + addr = net_buf_simple_pull_be16(&buf); + filter_add(client, addr); + } + send_filter_status(client, &rx, &buf); + break; + case CFG_FILTER_REMOVE: + while (buf.len >= 2) { + u16_t addr; + + addr = net_buf_simple_pull_be16(&buf); + filter_remove(client, addr); + } + send_filter_status(client, &rx, &buf); + break; + default: + BT_WARN("Unhandled configuration OpCode 0x%02x", opcode); + break; + } +} + +static int beacon_send(struct bt_mesh_conn *conn, struct bt_mesh_subnet *sub) +{ + NET_BUF_SIMPLE_DEFINE(buf, 23); + + net_buf_simple_reserve(&buf, 1); + bt_mesh_beacon_create(sub, &buf); + + return proxy_segment_and_send(conn, BLE_MESH_PROXY_BEACON, &buf); +} + +static void proxy_send_beacons(struct k_work *work) +{ + struct bt_mesh_proxy_client *client; + int i; + + client = CONTAINER_OF(work, struct bt_mesh_proxy_client, send_beacons); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (sub->net_idx != BLE_MESH_KEY_UNUSED) { + beacon_send(client->conn, sub); + } + } +} + +void bt_mesh_proxy_beacon_send(struct bt_mesh_subnet *sub) +{ + int i; + + if (!sub) { + /* NULL means we send on all subnets */ + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + if (bt_mesh.sub[i].net_idx != BLE_MESH_KEY_UNUSED) { + bt_mesh_proxy_beacon_send(&bt_mesh.sub[i]); + } + } + + return; + } + + for (i = 0; i < ARRAY_SIZE(clients); i++) { + if (clients[i].conn) { + beacon_send(clients[i].conn, sub); + } + } +} + +void bt_mesh_proxy_identity_start(struct bt_mesh_subnet *sub) +{ + sub->node_id = BLE_MESH_NODE_IDENTITY_RUNNING; + sub->node_id_start = k_uptime_get_32(); + + /* Prioritize the recently enabled subnet */ + next_idx = sub - bt_mesh.sub; +} + +void bt_mesh_proxy_identity_stop(struct bt_mesh_subnet *sub) +{ + sub->node_id = BLE_MESH_NODE_IDENTITY_STOPPED; + sub->node_id_start = 0U; +} + +int bt_mesh_proxy_identity_enable(void) +{ + int i, count = 0; + + BT_DBG("%s", __func__); + + if (!bt_mesh_is_provisioned()) { + return -EAGAIN; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + if (sub->node_id == BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED) { + continue; + } + + bt_mesh_proxy_identity_start(sub); + count++; + } + + if (count) { + bt_mesh_adv_update(); + } + + return 0; +} + +#endif /* GATT_PROXY */ + +static void proxy_complete_pdu(struct bt_mesh_proxy_client *client) +{ + switch (client->msg_type) { +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + case BLE_MESH_PROXY_NET_PDU: + BT_DBG("Mesh Network PDU"); + bt_mesh_net_recv(&client->buf, 0, BLE_MESH_NET_IF_PROXY); + break; + case BLE_MESH_PROXY_BEACON: + BT_DBG("Mesh Beacon PDU"); + bt_mesh_beacon_recv(&client->buf); + break; + case BLE_MESH_PROXY_CONFIG: + BT_DBG("Mesh Configuration PDU"); + proxy_cfg(client); + break; +#endif +#if defined(CONFIG_BLE_MESH_PB_GATT) + case BLE_MESH_PROXY_PROV: + BT_DBG("Mesh Provisioning PDU"); + bt_mesh_pb_gatt_recv(client->conn, &client->buf); + break; +#endif + default: + BT_WARN("Unhandled Message Type 0x%02x", client->msg_type); + break; + } + + net_buf_simple_reset(&client->buf); +} + +#define ATTR_IS_PROV(attr) (attr->user_data != NULL) + +static ssize_t proxy_recv(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, const void *buf, + u16_t len, u16_t offset, u8_t flags) +{ + struct bt_mesh_proxy_client *client = find_client(conn); + const u8_t *data = buf; + + if (!client) { + return -ENOTCONN; + } + + if (len < 1) { + BT_WARN("Too small Proxy PDU"); + return -EINVAL; + } + + if (ATTR_IS_PROV(attr) != (PDU_TYPE(data) == BLE_MESH_PROXY_PROV)) { + BT_WARN("Proxy PDU type doesn't match GATT service"); + return -EINVAL; + } + + if (len - 1 > net_buf_simple_tailroom(&client->buf)) { + BT_WARN("Too big proxy PDU"); + return -EINVAL; + } + + switch (PDU_SAR(data)) { + case SAR_COMPLETE: + if (client->buf.len) { + BT_WARN("Complete PDU while a pending incomplete one"); + return -EINVAL; + } + + client->msg_type = PDU_TYPE(data); + net_buf_simple_add_mem(&client->buf, data + 1, len - 1); + proxy_complete_pdu(client); + break; + + case SAR_FIRST: + if (client->buf.len) { + BT_WARN("First PDU while a pending incomplete one"); + return -EINVAL; + } + + if (!bt_mesh_atomic_test_and_set_bit(client->flags, SAR_TIMER_START)) { + k_delayed_work_submit(&client->sar_timer, PROXY_SAR_TRANS_TIMEOUT); + } + + client->msg_type = PDU_TYPE(data); + net_buf_simple_add_mem(&client->buf, data + 1, len - 1); + break; + + case SAR_CONT: + if (!client->buf.len) { + BT_WARN("Continuation with no prior data"); + return -EINVAL; + } + + if (client->msg_type != PDU_TYPE(data)) { + BT_WARN("Unexpected message type in continuation"); + return -EINVAL; + } + + net_buf_simple_add_mem(&client->buf, data + 1, len - 1); + break; + + case SAR_LAST: + if (!client->buf.len) { + BT_WARN("Last SAR PDU with no prior data"); + return -EINVAL; + } + + if (client->msg_type != PDU_TYPE(data)) { + BT_WARN("Unexpected message type in last SAR PDU"); + return -EINVAL; + } + + if (bt_mesh_atomic_test_and_clear_bit(client->flags, SAR_TIMER_START)) { + k_delayed_work_cancel(&client->sar_timer); + } + + net_buf_simple_add_mem(&client->buf, data + 1, len - 1); + proxy_complete_pdu(client); + break; + } + + return len; +} + +static int conn_count; + +static void proxy_connected(struct bt_mesh_conn *conn, u8_t err) +{ + struct bt_mesh_proxy_client *client; + int i; + + BT_DBG("conn %p err 0x%02x", conn, err); + + conn_count++; + + /* Since we use ADV_OPT_ONE_TIME */ + proxy_adv_enabled = false; + + /* Try to re-enable advertising in case it's possible */ + if (conn_count < BLE_MESH_MAX_CONN) { + bt_mesh_adv_update(); + } + + for (client = NULL, i = 0; i < ARRAY_SIZE(clients); i++) { + if (!clients[i].conn) { + client = &clients[i]; + break; + } + } + + if (!client) { + BT_ERR("%s, No free Proxy Client objects", __func__); + return; + } + + client->conn = bt_mesh_conn_ref(conn); + client->filter_type = NONE; + (void)memset(client->filter, 0, sizeof(client->filter)); + net_buf_simple_reset(&client->buf); +} + +static void proxy_disconnected(struct bt_mesh_conn *conn, u8_t reason) +{ + int i; + + BT_DBG("conn %p reason 0x%02x", conn, reason); + + conn_count--; + + for (i = 0; i < ARRAY_SIZE(clients); i++) { + struct bt_mesh_proxy_client *client = &clients[i]; + + if (client->conn == conn) { + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + client->filter_type == PROV) { + bt_mesh_pb_gatt_close(conn); + } + + if (bt_mesh_atomic_test_and_clear_bit(client->flags, SAR_TIMER_START)) { + k_delayed_work_cancel(&client->sar_timer); + } + + bt_mesh_conn_unref(client->conn); + client->conn = NULL; + break; + } + } + + bt_mesh_adv_update(); +} + +struct net_buf_simple *bt_mesh_proxy_get_buf(void) +{ + struct net_buf_simple *buf = &clients[0].buf; + + net_buf_simple_reset(buf); + + return buf; +} + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static ssize_t prov_ccc_write(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + const void *buf, u16_t len, + u16_t offset, u8_t flags) +{ + struct bt_mesh_proxy_client *client; + u16_t *value = attr->user_data; + + BT_DBG("len %u: %s", len, bt_hex(buf, len)); + + if (len != sizeof(*value)) { + return BLE_MESH_GATT_ERR(BLE_MESH_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + *value = sys_get_le16(buf); + if (*value != BLE_MESH_GATT_CCC_NOTIFY) { + BT_WARN("Client wrote 0x%04x instead enabling notify", *value); + return len; + } + + /* If a connection exists there must be a client */ + client = find_client(conn); + __ASSERT(client, "No client for connection"); + + if (client->filter_type == NONE) { + client->filter_type = PROV; + bt_mesh_pb_gatt_open(conn); + } + + return len; +} + +static ssize_t prov_ccc_read(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + void *buf, u16_t len, u16_t offset) +{ + u16_t *value = attr->user_data; + + return bt_mesh_gatts_attr_read(conn, attr, buf, len, offset, value, + sizeof(*value)); +} + +/* Mesh Provisioning Service Declaration */ +static struct bt_mesh_gatt_attr prov_attrs[] = { + BLE_MESH_GATT_PRIMARY_SERVICE(BLE_MESH_UUID_MESH_PROV), + + BLE_MESH_GATT_CHARACTERISTIC(BLE_MESH_UUID_MESH_PROV_DATA_IN, + BLE_MESH_GATT_CHRC_WRITE_WITHOUT_RESP), + BLE_MESH_GATT_DESCRIPTOR(BLE_MESH_UUID_MESH_PROV_DATA_IN, BLE_MESH_GATT_PERM_WRITE, + NULL, proxy_recv, (void *)1), + + BLE_MESH_GATT_CHARACTERISTIC(BLE_MESH_UUID_MESH_PROV_DATA_OUT, + BLE_MESH_GATT_CHRC_NOTIFY), + BLE_MESH_GATT_DESCRIPTOR(BLE_MESH_UUID_MESH_PROV_DATA_OUT, BLE_MESH_GATT_PERM_NONE, + NULL, NULL, NULL), + /* Add custom CCC as clients need to be tracked individually */ + BLE_MESH_GATT_DESCRIPTOR(BLE_MESH_UUID_GATT_CCC, + BLE_MESH_GATT_PERM_WRITE | BLE_MESH_GATT_PERM_READ, + prov_ccc_read, prov_ccc_write, &prov_ccc_val), +}; + +struct bt_mesh_gatt_service prov_svc = BLE_MESH_GATT_SERVICE(prov_attrs); + +int bt_mesh_proxy_prov_enable(void) +{ + int i; + + BT_DBG("%s", __func__); + + if (gatt_svc == MESH_GATT_PROV) { + BT_WARN("%s, Already", __func__); + return -EALREADY; + } + + if (gatt_svc != MESH_GATT_NONE) { + BT_WARN("%s, Busy", __func__); + return -EBUSY; + } + + bt_mesh_gatts_service_start(&prov_svc); + gatt_svc = MESH_GATT_PROV; + prov_fast_adv = true; + + for (i = 0; i < ARRAY_SIZE(clients); i++) { + if (clients[i].conn) { + clients[i].filter_type = PROV; + } + } + + + return 0; +} + +int bt_mesh_proxy_prov_disable(bool disconnect) +{ + int i; + + BT_DBG("%s", __func__); + + if (gatt_svc == MESH_GATT_NONE) { + BT_WARN("%s, Already", __func__); + return -EALREADY; + } + + if (gatt_svc != MESH_GATT_PROV) { + BT_WARN("%s, Busy", __func__); + return -EBUSY; + } + + bt_mesh_gatts_service_stop(&prov_svc); + gatt_svc = MESH_GATT_NONE; + + for (i = 0; i < ARRAY_SIZE(clients); i++) { + struct bt_mesh_proxy_client *client = &clients[i]; + + if (!client->conn || client->filter_type != PROV) { + continue; + } + + if (disconnect) { + bt_mesh_gatts_disconnect(client->conn, 0x13); + } else { + bt_mesh_pb_gatt_close(client->conn); + client->filter_type = NONE; + } + } + + bt_mesh_adv_update(); + + return 0; +} + +#endif /* CONFIG_BLE_MESH_PB_GATT */ + +#if defined(CONFIG_BLE_MESH_GATT_PROXY) +static ssize_t proxy_ccc_write(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + const void *buf, u16_t len, + u16_t offset, u8_t flags) +{ + struct bt_mesh_proxy_client *client; + u16_t value; + + BT_DBG("len %u: %s", len, bt_hex(buf, len)); + + if (len != sizeof(value)) { + return BLE_MESH_GATT_ERR(BLE_MESH_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + value = sys_get_le16(buf); + if (value != BLE_MESH_GATT_CCC_NOTIFY) { + BT_WARN("Client wrote 0x%04x instead enabling notify", value); + return len; + } + + /* If a connection exists there must be a client */ + client = find_client(conn); + __ASSERT(client, "No client for connection"); + + if (client->filter_type == NONE) { + client->filter_type = WHITELIST; + k_work_submit(&client->send_beacons); + } + + return len; +} + +static ssize_t proxy_ccc_read(struct bt_mesh_conn *conn, + const struct bt_mesh_gatt_attr *attr, + void *buf, u16_t len, u16_t offset) +{ + u16_t *value = attr->user_data; + + return bt_mesh_gatts_attr_read(conn, attr, buf, len, offset, value, + sizeof(*value)); +} + +/* Mesh Proxy Service Declaration */ +static struct bt_mesh_gatt_attr proxy_attrs[] = { + BLE_MESH_GATT_PRIMARY_SERVICE(BLE_MESH_UUID_MESH_PROXY), + + BLE_MESH_GATT_CHARACTERISTIC(BLE_MESH_UUID_MESH_PROXY_DATA_IN, + BLE_MESH_GATT_CHRC_WRITE_WITHOUT_RESP), + BLE_MESH_GATT_DESCRIPTOR(BLE_MESH_UUID_MESH_PROXY_DATA_IN, BLE_MESH_GATT_PERM_WRITE, + NULL, proxy_recv, NULL), + + BLE_MESH_GATT_CHARACTERISTIC(BLE_MESH_UUID_MESH_PROXY_DATA_OUT, + BLE_MESH_GATT_CHRC_NOTIFY), + BLE_MESH_GATT_DESCRIPTOR(BLE_MESH_UUID_MESH_PROXY_DATA_OUT, BLE_MESH_GATT_PERM_NONE, + NULL, NULL, NULL), + /* Add custom CCC as clients need to be tracked individually */ + BLE_MESH_GATT_DESCRIPTOR(BLE_MESH_UUID_GATT_CCC, + BLE_MESH_GATT_PERM_READ | BLE_MESH_GATT_PERM_WRITE, + proxy_ccc_read, proxy_ccc_write, &proxy_ccc_val), +}; + +struct bt_mesh_gatt_service proxy_svc = BLE_MESH_GATT_SERVICE(proxy_attrs); + +int bt_mesh_proxy_gatt_enable(void) +{ + int i; + + BT_DBG("%s", __func__); + + if (gatt_svc == MESH_GATT_PROXY) { + BT_WARN("%s, Already", __func__); + return -EALREADY; + } + + if (gatt_svc != MESH_GATT_NONE) { + BT_WARN("%s, Busy", __func__); + return -EBUSY; + } + + bt_mesh_gatts_service_start(&proxy_svc); + gatt_svc = MESH_GATT_PROXY; + + for (i = 0; i < ARRAY_SIZE(clients); i++) { + if (clients[i].conn) { + clients[i].filter_type = WHITELIST; + } + } + + return 0; +} + +void bt_mesh_proxy_gatt_disconnect(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(clients); i++) { + struct bt_mesh_proxy_client *client = &clients[i]; + + if (client->conn && (client->filter_type == WHITELIST || + client->filter_type == BLACKLIST)) { + client->filter_type = NONE; + bt_mesh_gatts_disconnect(client->conn, 0x13); + } + } +} + +int bt_mesh_proxy_gatt_disable(void) +{ + BT_DBG("%s", __func__); + + if (gatt_svc == MESH_GATT_NONE) { + BT_WARN("%s, Already", __func__); + return -EALREADY; + } + + if (gatt_svc != MESH_GATT_PROXY) { + BT_WARN("%s, Busy", __func__); + return -EBUSY; + } + + bt_mesh_proxy_gatt_disconnect(); + + bt_mesh_gatts_service_stop(&proxy_svc); + gatt_svc = MESH_GATT_NONE; + + return 0; +} + +void bt_mesh_proxy_addr_add(struct net_buf_simple *buf, u16_t addr) +{ + struct bt_mesh_proxy_client *client = + CONTAINER_OF(buf, struct bt_mesh_proxy_client, buf); + + BT_DBG("filter_type %u addr 0x%04x", client->filter_type, addr); + + if (client->filter_type == WHITELIST) { + filter_add(client, addr); + } else if (client->filter_type == BLACKLIST) { + filter_remove(client, addr); + } +} + +static bool client_filter_match(struct bt_mesh_proxy_client *client, + u16_t addr) +{ + int i; + + BT_DBG("filter_type %u addr 0x%04x", client->filter_type, addr); + + if (client->filter_type == WHITELIST) { + for (i = 0; i < ARRAY_SIZE(client->filter); i++) { + if (client->filter[i] == addr) { + return true; + } + } + + return false; + } + + if (client->filter_type == BLACKLIST) { + for (i = 0; i < ARRAY_SIZE(client->filter); i++) { + if (client->filter[i] == addr) { + return false; + } + } + + return true; + } + + return false; +} + +bool bt_mesh_proxy_relay(struct net_buf_simple *buf, u16_t dst) +{ + bool relayed = false; + int i; + + BT_DBG("%u bytes to dst 0x%04x", buf->len, dst); + + for (i = 0; i < ARRAY_SIZE(clients); i++) { + struct bt_mesh_proxy_client *client = &clients[i]; + NET_BUF_SIMPLE_DEFINE(msg, 32); + + if (!client->conn) { + continue; + } + + if (!client_filter_match(client, dst)) { + continue; + } + + /* Proxy PDU sending modifies the original buffer, + * so we need to make a copy. + */ + net_buf_simple_reserve(&msg, 1); + net_buf_simple_add_mem(&msg, buf->data, buf->len); + + bt_mesh_proxy_send(client->conn, BLE_MESH_PROXY_NET_PDU, &msg); + relayed = true; + } + + return relayed; +} + +#endif /* CONFIG_BLE_MESH_GATT_PROXY */ + +static int proxy_send(struct bt_mesh_conn *conn, const void *data, u16_t len) +{ + BT_DBG("%u bytes: %s", len, bt_hex(data, len)); + +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + if (gatt_svc == MESH_GATT_PROXY) { + return bt_mesh_gatts_notify(conn, &proxy_attrs[4], data, len); + } +#endif + +#if defined(CONFIG_BLE_MESH_PB_GATT) + if (gatt_svc == MESH_GATT_PROV) { + return bt_mesh_gatts_notify(conn, &prov_attrs[4], data, len); + } +#endif + + return 0; +} + +static int proxy_segment_and_send(struct bt_mesh_conn *conn, u8_t type, + struct net_buf_simple *msg) +{ + u16_t mtu; + + BT_DBG("conn %p type 0x%02x len %u: %s", conn, type, msg->len, + bt_hex(msg->data, msg->len)); + + /* ATT_MTU - OpCode (1 byte) - Handle (2 bytes) */ + mtu = bt_mesh_gatt_get_mtu(conn) - 3; + if (mtu > msg->len) { + net_buf_simple_push_u8(msg, PDU_HDR(SAR_COMPLETE, type)); + return proxy_send(conn, msg->data, msg->len); + } + + net_buf_simple_push_u8(msg, PDU_HDR(SAR_FIRST, type)); + proxy_send(conn, msg->data, mtu); + net_buf_simple_pull(msg, mtu); + + while (msg->len) { + if (msg->len + 1 < mtu) { + net_buf_simple_push_u8(msg, PDU_HDR(SAR_LAST, type)); + proxy_send(conn, msg->data, msg->len); + break; + } + + net_buf_simple_push_u8(msg, PDU_HDR(SAR_CONT, type)); + proxy_send(conn, msg->data, mtu); + net_buf_simple_pull(msg, mtu); + } + + return 0; +} + +int bt_mesh_proxy_send(struct bt_mesh_conn *conn, u8_t type, + struct net_buf_simple *msg) +{ + struct bt_mesh_proxy_client *client = find_client(conn); + + if (!client) { + BT_ERR("%s, No Proxy Client found", __func__); + return -ENOTCONN; + } + + if ((client->filter_type == PROV) != (type == BLE_MESH_PROXY_PROV)) { + BT_ERR("%s, Invalid PDU type for Proxy Client", __func__); + return -EINVAL; + } + + return proxy_segment_and_send(conn, type, msg); +} + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static u8_t prov_svc_data[20] = { 0x27, 0x18, }; + +static const struct bt_mesh_adv_data prov_ad[] = { + BLE_MESH_ADV_DATA_BYTES(BLE_MESH_DATA_FLAGS, (BLE_MESH_AD_GENERAL | BLE_MESH_AD_NO_BREDR)), + BLE_MESH_ADV_DATA_BYTES(BLE_MESH_DATA_UUID16_ALL, 0x27, 0x18), + BLE_MESH_ADV_DATA(BLE_MESH_DATA_SVC_DATA16, prov_svc_data, sizeof(prov_svc_data)), +}; +#endif /* PB_GATT */ + +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + +#define ID_TYPE_NET 0x00 +#define ID_TYPE_NODE 0x01 + +#define NODE_ID_LEN 19 +#define NET_ID_LEN 11 + +#define NODE_ID_TIMEOUT K_SECONDS(CONFIG_BLE_MESH_NODE_ID_TIMEOUT) + +static u8_t proxy_svc_data[NODE_ID_LEN] = { 0x28, 0x18, }; + +static const struct bt_mesh_adv_data node_id_ad[] = { + BLE_MESH_ADV_DATA_BYTES(BLE_MESH_DATA_FLAGS, (BLE_MESH_AD_GENERAL | BLE_MESH_AD_NO_BREDR)), + BLE_MESH_ADV_DATA_BYTES(BLE_MESH_DATA_UUID16_ALL, 0x28, 0x18), + BLE_MESH_ADV_DATA(BLE_MESH_DATA_SVC_DATA16, proxy_svc_data, NODE_ID_LEN), +}; + +static const struct bt_mesh_adv_data net_id_ad[] = { + BLE_MESH_ADV_DATA_BYTES(BLE_MESH_DATA_FLAGS, (BLE_MESH_AD_GENERAL | BLE_MESH_AD_NO_BREDR)), + BLE_MESH_ADV_DATA_BYTES(BLE_MESH_DATA_UUID16_ALL, 0x28, 0x18), + BLE_MESH_ADV_DATA(BLE_MESH_DATA_SVC_DATA16, proxy_svc_data, NET_ID_LEN), +}; + +static int node_id_adv(struct bt_mesh_subnet *sub) +{ + u8_t tmp[16]; + int err; + + BT_DBG("%s", __func__); + + proxy_svc_data[2] = ID_TYPE_NODE; + + err = bt_mesh_rand(proxy_svc_data + 11, 8); + if (err) { + return err; + } + + (void)memset(tmp, 0, 6); + memcpy(tmp + 6, proxy_svc_data + 11, 8); + sys_put_be16(bt_mesh_primary_addr(), tmp + 14); + + err = bt_mesh_encrypt_be(sub->keys[sub->kr_flag].identity, tmp, tmp); + if (err) { + return err; + } + + memcpy(proxy_svc_data + 3, tmp + 8, 8); + + err = bt_le_adv_start(&fast_adv_param, node_id_ad, + ARRAY_SIZE(node_id_ad), NULL, 0); + if (err) { + BT_WARN("Failed to advertise using Node ID (err %d)", err); + return err; + } + + proxy_adv_enabled = true; + + return 0; +} + +static int net_id_adv(struct bt_mesh_subnet *sub) +{ + int err; + + BT_DBG("%s", __func__); + + proxy_svc_data[2] = ID_TYPE_NET; + + BT_DBG("Advertising with NetId %s", + bt_hex(sub->keys[sub->kr_flag].net_id, 8)); + + memcpy(proxy_svc_data + 3, sub->keys[sub->kr_flag].net_id, 8); + + err = bt_le_adv_start(&slow_adv_param, net_id_ad, + ARRAY_SIZE(net_id_ad), NULL, 0); + if (err) { + BT_WARN("Failed to advertise using Network ID (err %d)", err); + return err; + } + + proxy_adv_enabled = true; + + return 0; +} + +static bool advertise_subnet(struct bt_mesh_subnet *sub) +{ + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + return false; + } + + return (sub->node_id == BLE_MESH_NODE_IDENTITY_RUNNING || + bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_ENABLED); +} + +static struct bt_mesh_subnet *next_sub(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub; + + sub = &bt_mesh.sub[(i + next_idx) % ARRAY_SIZE(bt_mesh.sub)]; + if (advertise_subnet(sub)) { + next_idx = (next_idx + 1) % ARRAY_SIZE(bt_mesh.sub); + return sub; + } + } + + return NULL; +} + +static int sub_count(void) +{ + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + + if (advertise_subnet(sub)) { + count++; + } + } + + return count; +} + +static s32_t gatt_proxy_advertise(struct bt_mesh_subnet *sub) +{ + s32_t remaining = K_FOREVER; + int subnet_count; + + BT_DBG("%s", __func__); + + if (conn_count == BLE_MESH_MAX_CONN) { + BT_WARN("Connectable advertising deferred (max connections)"); + return remaining; + } + + if (!sub) { + BT_WARN("No subnets to advertise on"); + return remaining; + } + + if (sub->node_id == BLE_MESH_NODE_IDENTITY_RUNNING) { + u32_t active = k_uptime_get_32() - sub->node_id_start; + + if (active < NODE_ID_TIMEOUT) { + remaining = NODE_ID_TIMEOUT - active; + BT_DBG("Node ID active for %u ms, %d ms remaining", + active, remaining); + node_id_adv(sub); + } else { + bt_mesh_proxy_identity_stop(sub); + BT_DBG("Node ID stopped"); + } + } + + if (sub->node_id == BLE_MESH_NODE_IDENTITY_STOPPED) { + if (bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_ENABLED) { + net_id_adv(sub); + } else { + return gatt_proxy_advertise(next_sub()); + } + } + + subnet_count = sub_count(); + BT_DBG("sub_count %u", subnet_count); + if (subnet_count > 1) { + s32_t max_timeout; + + /* We use NODE_ID_TIMEOUT as a starting point since it may + * be less than 60 seconds. Divide this period into at least + * 6 slices, but make sure that a slice is at least one + * second long (to avoid excessive rotation). + */ + max_timeout = NODE_ID_TIMEOUT / MAX(subnet_count, 6); + max_timeout = MAX(max_timeout, K_SECONDS(1)); + + if (remaining > max_timeout || remaining < 0) { + remaining = max_timeout; + } + } + + BT_DBG("Advertising %d ms for net_idx 0x%04x", remaining, sub->net_idx); + + return remaining; +} +#endif /* GATT_PROXY */ + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static size_t gatt_prov_adv_create(struct bt_mesh_adv_data prov_sd[2]) +{ + const struct bt_mesh_prov *prov = bt_mesh_prov_get(); + const char *name = device_name; + size_t name_len = strlen(name); + size_t prov_sd_len = 0; + size_t sd_space = 31; + + memcpy(prov_svc_data + 2, prov->uuid, 16); + sys_put_be16(prov->oob_info, prov_svc_data + 18); + + if (prov->uri) { + size_t uri_len = strlen(prov->uri); + + if (uri_len > 29) { + /* There's no way to shorten an URI */ + BT_WARN("Too long URI to fit advertising packet"); + } else { + prov_sd[0].type = BLE_MESH_DATA_URI; + prov_sd[0].data_len = uri_len; + prov_sd[0].data = (const u8_t *)prov->uri; + sd_space -= 2 + uri_len; + prov_sd_len++; + } + } + + if (sd_space > 2 && name_len > 0) { + sd_space -= 2; + + if (sd_space < name_len) { + prov_sd[prov_sd_len].type = BLE_MESH_DATA_NAME_SHORTENED; + prov_sd[prov_sd_len].data_len = sd_space; + } else { + prov_sd[prov_sd_len].type = BLE_MESH_DATA_NAME_COMPLETE; + prov_sd[prov_sd_len].data_len = name_len; + } + + prov_sd[prov_sd_len].data = (const u8_t *)name; + prov_sd_len++; + } + + return prov_sd_len; +} +#endif /* CONFIG_BLE_MESH_PB_GATT */ + +s32_t bt_mesh_proxy_adv_start(void) +{ + BT_DBG("%s", __func__); + + if (gatt_svc == MESH_GATT_NONE) { + return K_FOREVER; + } + +#if defined(CONFIG_BLE_MESH_PB_GATT) + if (!bt_mesh_is_provisioned()) { + const struct bt_mesh_adv_param *param; + struct bt_mesh_adv_data prov_sd[2]; + size_t prov_sd_len; + + if (prov_fast_adv) { + param = &fast_adv_param; + } else { + param = &slow_adv_param; + } + + prov_sd_len = gatt_prov_adv_create(prov_sd); + + if (bt_le_adv_start(param, prov_ad, ARRAY_SIZE(prov_ad), + prov_sd, prov_sd_len) == 0) { + proxy_adv_enabled = true; + + /* Advertise 60 seconds using fast interval */ + if (prov_fast_adv) { + prov_fast_adv = false; + return K_SECONDS(60); + } + } + } +#endif /* PB_GATT */ + +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + if (bt_mesh_is_provisioned()) { + return gatt_proxy_advertise(next_sub()); + } +#endif /* GATT_PROXY */ + + return K_FOREVER; +} + +void bt_mesh_proxy_adv_stop(void) +{ + int err; + + BT_DBG("adv_enabled %u", proxy_adv_enabled); + + if (!proxy_adv_enabled) { + return; + } + + err = bt_le_adv_stop(); + if (err) { + BT_ERR("%s, Failed to stop advertising (err %d)", __func__, err); + } else { + proxy_adv_enabled = false; + } +} + +static struct bt_mesh_conn_cb conn_callbacks = { + .connected = proxy_connected, + .disconnected = proxy_disconnected, +}; + +static void proxy_recv_timeout(struct k_work *work) +{ + struct bt_mesh_proxy_client *client = NULL; + + BT_DBG("%s", __func__); + + client = CONTAINER_OF(work, struct bt_mesh_proxy_client, sar_timer.work); + if (!client || !client->conn) { + BT_ERR("%s, Invalid proxy client parameter", __func__); + return; + } + + bt_mesh_atomic_clear_bit(client->flags, SAR_TIMER_START); + net_buf_simple_reset(&client->buf); + bt_mesh_gatts_disconnect(client->conn, 0x13); +} + +int bt_mesh_proxy_init(void) +{ + int i; + + /* Initialize the client receive buffers */ + for (i = 0; i < ARRAY_SIZE(clients); i++) { + struct bt_mesh_proxy_client *client = &clients[i]; + + client->buf.size = CLIENT_BUF_SIZE; + client->buf.__buf = client_buf_data + (i * CLIENT_BUF_SIZE); + k_delayed_work_init(&client->sar_timer, proxy_recv_timeout); + } + + bt_mesh_gatts_conn_cb_register(&conn_callbacks); + +#if defined(CONFIG_BLE_MESH_PB_GATT) + const struct bt_mesh_prov *prov = bt_mesh_prov_get(); + __ASSERT(prov && prov->uuid, "%s, Device UUID is not initialized", __func__); +#endif + + return 0; +} + +#endif /* CONFIG_BLE_MESH_NODE */ diff --git a/components/bt/ble_mesh/mesh_core/proxy.h b/components/bt/ble_mesh/mesh_core/proxy.h new file mode 100644 index 0000000000..07120e0950 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/proxy.h @@ -0,0 +1,51 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _PROXY_H_ +#define _PROXY_H_ + +#include "net.h" +#include "mesh_buf.h" +#include "mesh_bearer_adapt.h" + +#define BLE_MESH_PROXY_NET_PDU 0x00 +#define BLE_MESH_PROXY_BEACON 0x01 +#define BLE_MESH_PROXY_CONFIG 0x02 +#define BLE_MESH_PROXY_PROV 0x03 + +#define DEVICE_NAME_SIZE 29 + +int bt_mesh_set_device_name(const char *name); + +int bt_mesh_proxy_send(struct bt_mesh_conn *conn, u8_t type, + struct net_buf_simple *msg); + +int bt_mesh_proxy_prov_enable(void); +int bt_mesh_proxy_prov_disable(bool disconnect); + +int bt_mesh_proxy_gatt_enable(void); +int bt_mesh_proxy_gatt_disable(void); +void bt_mesh_proxy_gatt_disconnect(void); + +void bt_mesh_proxy_beacon_send(struct bt_mesh_subnet *sub); + +struct net_buf_simple *bt_mesh_proxy_get_buf(void); + +s32_t bt_mesh_proxy_adv_start(void); +void bt_mesh_proxy_adv_stop(void); + +void bt_mesh_proxy_identity_start(struct bt_mesh_subnet *sub); +void bt_mesh_proxy_identity_stop(struct bt_mesh_subnet *sub); + +bool bt_mesh_proxy_relay(struct net_buf_simple *buf, u16_t dst); +void bt_mesh_proxy_addr_add(struct net_buf_simple *buf, u16_t addr); + +int bt_mesh_proxy_init(void); + +#endif /* _PROXY_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/settings.c b/components/bt/ble_mesh/mesh_core/settings.c new file mode 100644 index 0000000000..6606142c2b --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/settings.c @@ -0,0 +1,1578 @@ +/* + * Copyright (c) 2018 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_SETTINGS) + +#include "mesh_types.h" +#include "mesh_util.h" +#include "mesh_access.h" +#include "mesh_main.h" +#include "mesh_buf.h" +#include "mesh_kernel.h" +#include "mesh_trace.h" +#include "mesh_common.h" + +#include "mesh.h" +#include "net.h" +#include "crypto.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "proxy.h" +#include "cfg_srv.h" + +#include "settings_nvs.h" + +/* BLE Mesh NVS Key and corresponding data struct. + * Note: The length of nvs key must be <= 15. + * "xxxx" (2 octet) means the rpl_src, net_idx, app_idx, model_key, etc. + * Model model_key is a combination "elem_idx << 8 | model_idx". + * key: "mesh/net" -> write/read to set/get NET data + * key: "mesh/iv" -> write/read to set/get IV data + * key: "mesh/seq" -> write/read to set/get SEQ data + * key: "mesh/hb_pub" -> write/read to set/get CFG HB_PUB data + * key: "mesh/cfg" -> write/read to set/get CFG data + * key: "mesh/rpl" -> write/read to set/get all RPL src. + * key: "mesh/rpl/xxxx" -> write/read to set/get the "xxxx" RPL data + * key: "mesh/netkey" -> write/read to set/get all NetKey Indexes + * key: "mesh/nk/xxxx" -> write/read to set/get the "xxxx" NetKey data + * key: "mesh/appkey" -> write/read to set/get all AppKey Indexes + * key: "mesh/ak/xxxx" -> write/read to set/get the "xxxx" AppKey data + * key: "mesh/sig" -> write/read to set/get all SIG MODEL model_keys. + * key: "mesh/s/xxxx/b" -> write/read to set/get SIG MODEL Bind AppKey List + * key: "mesh/s/xxxx/s" -> write/read to set/get SIG MODEL Subscription List + * key: "mesh/s/xxxx/p" -> write/read to set/get SIG MODEL Publication + * key: "mesh/vnd" -> write/read to set/get all VENDOR MODEL model_keys. + * key: "mesh/v/xxxx/b" -> write/read to set/get VENDOR MODEL Bind AppKey List + * key: "mesh/v/xxxx/s" -> write/read to set/get VENDOR MODEL Subscription List + * key: "mesh/v/xxxx/p" -> write/read to set/get VENDOR MODEL Publication + */ + +#if CONFIG_BLE_MESH_SETTINGS + +#define GET_ELEMENT_IDX(x) ((u8_t)((x) >> 8)) +#define GET_MODEL_IDX(x) ((u8_t)(x)) +#define GET_MODEL_KEY(a, b) ((u16_t)(((u16_t)((a) << 8)) | b)) + +/* Tracking of what storage changes are pending for App and Net Keys. We + * track this in a separate array here instead of within the respective + * bt_mesh_app_key and bt_mesh_subnet structs themselves, since once a key + * gets deleted its struct becomes invalid and may be reused for other keys. + */ +static struct key_update { + u16_t key_idx: 12, /* AppKey or NetKey Index */ + valid: 1, /* 1 if this entry is valid, 0 if not */ + app_key: 1, /* 1 if this is an AppKey, 0 if a NetKey */ + clear: 1; /* 1 if key needs clearing, 0 if storing */ +} key_updates[CONFIG_BLE_MESH_APP_KEY_COUNT + CONFIG_BLE_MESH_SUBNET_COUNT]; + +static struct k_delayed_work pending_store; + +/* Mesh network storage information */ +struct net_val { + u16_t primary_addr; + u8_t dev_key[16]; +} __packed; + +/* Sequence number storage */ +struct seq_val { + u8_t val[3]; +} __packed; + +/* Heartbeat Publication storage */ +struct hb_pub_val { + u16_t dst; + u8_t period; + u8_t ttl; + u16_t feat; + u16_t net_idx: 12, + indefinite: 1; +}; + +/* Miscelaneous configuration server model states */ +struct cfg_val { + u8_t net_transmit; + u8_t relay; + u8_t relay_retransmit; + u8_t beacon; + u8_t gatt_proxy; + u8_t frnd; + u8_t default_ttl; +}; + +/* IV Index & IV Update storage */ +struct iv_val { + u32_t iv_index; + u8_t iv_update: 1, + iv_duration: 7; +} __packed; + +/* Replay Protection List storage */ +struct rpl_val { + u32_t seq: 24, + old_iv: 1; +}; + +/* NetKey storage information */ +struct net_key_val { + u8_t kr_flag: 1, + kr_phase: 7; + u8_t val[2][16]; +} __packed; + +/* AppKey storage information */ +struct app_key_val { + u16_t net_idx; + bool updated; + u8_t val[2][16]; +} __packed; + +struct mod_pub_val { + u16_t addr; + u16_t key; + u8_t ttl; + u8_t retransmit; + u8_t period; + u8_t period_div: 4, + cred: 1; +}; + +/* We need this so we don't overwrite app-hardcoded values in case FCB + * contains a history of changes but then has a NULL at the end. + */ +static struct { + bool valid; + struct cfg_val cfg; +} stored_cfg; + +static int net_set(const char *name) +{ + struct net_val net = {0}; + bool exist; + int err; + + BT_DBG("%s", __func__); + + err = bt_mesh_load_core_settings(name, (u8_t *)&net, sizeof(net), &exist); + if (err) { + BT_WARN("%s, Clear NET", __func__); + memset(bt_mesh.dev_key, 0, sizeof(bt_mesh.dev_key)); + bt_mesh_comp_unprovision(); + return 0; + } + + if (exist == false) { + return 0; + } + + memcpy(bt_mesh.dev_key, net.dev_key, sizeof(bt_mesh.dev_key)); + bt_mesh_comp_provision(net.primary_addr); + + BT_DBG("Provisioned with primary address 0x%04x", net.primary_addr); + BT_DBG("Recovered DevKey %s", bt_hex(bt_mesh.dev_key, 16)); + + return 0; +} + +static int iv_set(const char *name) +{ + struct iv_val iv = {0}; + bool exist; + int err; + + BT_DBG("%s", __func__); + + err = bt_mesh_load_core_settings(name, (u8_t *)&iv, sizeof(iv), &exist); + if (err) { + BT_WARN("%s, Clear IV", __func__); + bt_mesh.iv_index = 0U; + bt_mesh_atomic_clear_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS); + return 0; + } + + if (exist == false) { + return 0; + } + + bt_mesh.iv_index = iv.iv_index; + bt_mesh_atomic_set_bit_to(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS, iv.iv_update); + bt_mesh.ivu_duration = iv.iv_duration; + + BT_DBG("IV Index 0x%04x (IV Update Flag %u) duration %u hours", + iv.iv_index, iv.iv_update, iv.iv_duration); + + return 0; +} + +static int seq_set(const char *name) +{ + struct seq_val seq = {0}; + bool exist; + int err; + + BT_DBG("%s", __func__); + + err = bt_mesh_load_core_settings(name, (u8_t *)&seq, sizeof(seq), &exist); + if (err) { + BT_WARN("%s, Clear SEQ", __func__); + bt_mesh.seq = 0U; + return 0; + } + + if (exist == false) { + return 0; + } + + bt_mesh.seq = ((u32_t)seq.val[0] | ((u32_t)seq.val[1] << 8) | + ((u32_t)seq.val[2] << 16)); + + if (CONFIG_BLE_MESH_SEQ_STORE_RATE > 0) { + /* Make sure we have a large enough sequence number. We + * subtract 1 so that the first transmission causes a write + * to the settings storage. + */ + bt_mesh.seq += (CONFIG_BLE_MESH_SEQ_STORE_RATE - + (bt_mesh.seq % CONFIG_BLE_MESH_SEQ_STORE_RATE)); + bt_mesh.seq--; + } + + BT_DBG("Sequence Number 0x%06x", bt_mesh.seq); + + return 0; +} + +static struct bt_mesh_rpl *rpl_find(u16_t src) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) { + if (bt_mesh.rpl[i].src == src) { + return &bt_mesh.rpl[i]; + } + } + + return NULL; +} + +static struct bt_mesh_rpl *rpl_alloc(u16_t src) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) { + if (bt_mesh.rpl[i].src == BLE_MESH_ADDR_UNASSIGNED) { + bt_mesh.rpl[i].src = src; + return &bt_mesh.rpl[i]; + } + } + + return NULL; +} + +static int rpl_set(const char *name) +{ + struct net_buf_simple *buf = NULL; + struct bt_mesh_rpl *entry = NULL; + struct rpl_val rpl = {0}; + char get[16] = {'\0'}; + size_t length; + bool exist; + int err = 0; + int i; + + BT_DBG("%s", __func__); + + buf = bt_mesh_get_core_settings_item(name); + if (!buf) { + return 0; + } + + length = buf->len; + + for (i = 0; i < length / SETTINGS_ITEM_SIZE; i++) { + u16_t src = net_buf_simple_pull_le16(buf); + sprintf(get, "mesh/rpl/%04x", src); + + err = bt_mesh_load_core_settings(get, (u8_t *)&rpl, sizeof(rpl), &exist); + if (err) { + BT_ERR("%s, Failed to load RPL %s, reset RPL", __func__, get); + bt_mesh_rpl_reset(); + goto free; + } + + if (exist == false) { + continue; + } + + entry = rpl_find(src); + if (!entry) { + entry = rpl_alloc(src); + if (!entry) { + BT_ERR("%s, No space for a new RPL 0x%04x", __func__, src); + err = -ENOMEM; + goto free; + } + } + + BT_DBG("RPL 0x%04x: Seq 0x%06x, old_iv %u", src, rpl.seq, rpl.old_iv); + entry->src = src; + entry->seq = rpl.seq; + entry->old_iv = rpl.old_iv; + } + +free: + bt_mesh_free_buf(buf); + return err; +} + +static struct bt_mesh_subnet *subnet_alloc(u16_t net_idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + if (bt_mesh.sub[i].net_idx == BLE_MESH_KEY_UNUSED) { + bt_mesh.sub[i].net_idx = net_idx; + return &bt_mesh.sub[i]; + } + } + + return NULL; +} + +static int net_key_set(const char *name) +{ + struct net_buf_simple *buf = NULL; + struct bt_mesh_subnet *sub = NULL; + struct net_key_val key = {0}; + char get[16] = {'\0'}; + size_t length; + bool exist; + int err = 0; + int i; + + BT_DBG("%s", __func__); + + buf = bt_mesh_get_core_settings_item(name); + if (!buf) { + return 0; + } + + length = buf->len; + + for (i = 0; i < length / SETTINGS_ITEM_SIZE; i++) { + u16_t net_idx = net_buf_simple_pull_le16(buf); + sprintf(get, "mesh/nk/%04x", net_idx); + + err = bt_mesh_load_core_settings(get, (u8_t *)&key, sizeof(key), &exist); + if (err) { + BT_ERR("%s, Failed to load NetKey %s", __func__, get); + goto free; + } + + if (exist == false) { + continue; + } + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) { + sub = subnet_alloc(net_idx); + if (!sub) { + BT_ERR("%s, No space for a new subnet 0x%03x", __func__, net_idx); + err = -ENOMEM; + goto free; + } + } + + BT_DBG("NetKeyIndex 0x%03x recovered from storage", net_idx); + sub->net_idx = net_idx; + sub->kr_flag = key.kr_flag; + sub->kr_phase = key.kr_phase; + memcpy(sub->keys[0].net, &key.val[0], 16); + memcpy(sub->keys[1].net, &key.val[1], 16); + } + +free: + bt_mesh_free_buf(buf); + return err; +} + +static int app_key_set(const char *name) +{ + struct bt_mesh_app_key *app = NULL; + struct bt_mesh_subnet *sub = NULL; + struct net_buf_simple *buf = NULL; + struct app_key_val key = {0}; + char get[16] = {'\0'}; + size_t length; + bool exist; + int err = 0; + int i; + + BT_DBG("%s", __func__); + + buf = bt_mesh_get_core_settings_item(name); + if (!buf) { + return 0; + } + + length = buf->len; + + for (i = 0; i < length / SETTINGS_ITEM_SIZE; i++) { + u16_t app_idx = net_buf_simple_pull_le16(buf); + sprintf(get, "mesh/ak/%04x", app_idx); + + err = bt_mesh_load_core_settings(get, (u8_t *)&key, sizeof(key), &exist); + if (err) { + BT_ERR("%s, Failed to load AppKey %s", __func__, get); + goto free; + } + + if (exist == false) { + continue; + } + + sub = bt_mesh_subnet_get(key.net_idx); + if (!sub) { + BT_ERR("%s, Failed to find subnet 0x%03x", __func__, key.net_idx); + err = -ENOENT; + goto free; + } + + app = bt_mesh_app_key_find(app_idx); + if (!app) { + app = bt_mesh_app_key_alloc(app_idx); + if (!app) { + BT_ERR("%s, No space for a new app key 0x%03x", __func__, app_idx); + err = -ENOMEM; + goto free; + } + } + + BT_DBG("AppKeyIndex 0x%03x recovered from storage", app_idx); + app->net_idx = key.net_idx; + app->app_idx = app_idx; + app->updated = key.updated; + memcpy(app->keys[0].val, key.val[0], 16); + memcpy(app->keys[1].val, key.val[1], 16); + bt_mesh_app_id(app->keys[0].val, &app->keys[0].id); + bt_mesh_app_id(app->keys[1].val, &app->keys[1].id); + } + +free: + bt_mesh_free_buf(buf); + return err; +} + +static int hb_pub_set(const char *name) +{ + struct bt_mesh_hb_pub *hb_pub = bt_mesh_hb_pub_get(); + struct hb_pub_val hb_val = {0}; + bool exist; + int err; + + BT_DBG("%s", __func__); + + if (!hb_pub) { + BT_ERR("%s, NULL cfg hb pub", __func__); + return -EINVAL; + } + + err = bt_mesh_load_core_settings(name, (u8_t *)&hb_val, sizeof(hb_val), &exist); + if (err) { + BT_WARN("%s, Cleared heartbeat publication", __func__); + hb_pub->dst = BLE_MESH_ADDR_UNASSIGNED; + hb_pub->count = 0U; + hb_pub->ttl = 0U; + hb_pub->period = 0U; + hb_pub->feat = 0U; + return 0; + } + + if (exist == false) { + return 0; + } + + hb_pub->dst = hb_val.dst; + hb_pub->period = hb_val.period; + hb_pub->ttl = hb_val.ttl; + hb_pub->feat = hb_val.feat; + hb_pub->net_idx = hb_val.net_idx; + if (hb_val.indefinite) { + hb_pub->count = 0xffff; + } else { + hb_pub->count = 0U; + } + + BT_DBG("Restore Heartbeat Publication"); + + return 0; +} + +static int cfg_set(const char *name) +{ + struct bt_mesh_cfg_srv *cfg = bt_mesh_cfg_get(); + struct cfg_val val = {0}; + bool exist; + int err; + + BT_DBG("%s", __func__); + + if (!cfg) { + BT_ERR("%s, NULL cfg", __func__); + stored_cfg.valid = false; + return -EINVAL; + } + + err = bt_mesh_load_core_settings(name, (u8_t *)&val, sizeof(val), &exist); + if (err) { + BT_WARN("%s, Cleared configuration", __func__); + stored_cfg.valid = false; + return 0; + } + + if (exist == false) { + return 0; + } + + stored_cfg.valid = true; + BT_DBG("Restore configuration state"); + return 0; +} + +static int model_set_bind(bool vnd, struct bt_mesh_model *model, u16_t model_key) +{ + char name[16] = {'\0'}; + bool exist; + int i, err; + + /* Start with empty array regardless of cleared or set value */ + for (i = 0; i < ARRAY_SIZE(model->keys); i++) { + model->keys[i] = BLE_MESH_KEY_UNUSED; + } + + sprintf(name, "mesh/%s/%04x/b", vnd ? "v" : "s", model_key); + err = bt_mesh_load_core_settings(name, (u8_t *)model->keys, sizeof(model->keys), &exist); + if (err) { + BT_ERR("%s, Failed to get model bind keys", __func__); + return -EIO; + } + + return 0; +} + +static int model_set_sub(bool vnd, struct bt_mesh_model *model, u16_t model_key) +{ + char name[16] = {'\0'}; + bool exist; + int i, err; + + /* Start with empty array regardless of cleared or set value */ + for (i = 0; i < ARRAY_SIZE(model->groups); i++) { + model->groups[i] = BLE_MESH_ADDR_UNASSIGNED; + } + + sprintf(name, "mesh/%s/%04x/s", vnd ? "v" : "s", model_key); + err = bt_mesh_load_core_settings(name, (u8_t *)model->groups, sizeof(model->groups), &exist); + if (err) { + BT_ERR("%s, Failed to get model subscriptions", __func__); + return -EIO; + } + + return 0; +} + +static int model_set_pub(bool vnd, struct bt_mesh_model *model, u16_t model_key) +{ + struct mod_pub_val pub = {0}; + char name[16] = {'\0'}; + bool exist; + int err; + + if (!model->pub) { + BT_WARN("%s, Model has no publication context", __func__); + return 0; + } + + sprintf(name, "mesh/%s/%04x/p", vnd ? "v" : "s", model_key); + err = bt_mesh_load_core_settings(name, (u8_t *)&pub, sizeof(pub), &exist); + if (err) { + BT_WARN("%s, Cleared model publication", __func__); + model->pub->addr = BLE_MESH_ADDR_UNASSIGNED; + model->pub->key = 0U; + model->pub->cred = 0U; + model->pub->ttl = 0U; + model->pub->period = 0U; + model->pub->retransmit = 0U; + model->pub->count = 0U; + return 0; + } + + if (exist == false) { + return 0; + } + + model->pub->addr = pub.addr; + model->pub->key = pub.key; + model->pub->cred = pub.cred; + model->pub->ttl = pub.ttl; + model->pub->period = pub.period; + model->pub->retransmit = pub.retransmit; + model->pub->count = 0U; + + BT_DBG("Restore model publication, pub_addr 0x%04x app_idx 0x%03x", + pub.addr, pub.key); + + return 0; +} + +static int model_set(bool vnd, const char *name) +{ + struct bt_mesh_model *model = NULL; + struct net_buf_simple *buf = NULL; + u8_t elem_idx, model_idx; + size_t length; + int err = 0; + int i; + + BT_DBG("%s", __func__); + + buf = bt_mesh_get_core_settings_item(name); + if (!buf) { + return 0; + } + + length = buf->len; + + for (i = 0; i < length / SETTINGS_ITEM_SIZE; i++) { + u16_t model_key = net_buf_simple_pull_le16(buf); + elem_idx = GET_ELEMENT_IDX(model_key); + model_idx = GET_MODEL_IDX(model_key); + + model = bt_mesh_model_get(vnd, elem_idx, model_idx); + if (!model) { + BT_ERR("%s, Failed to get %s model, elem_idx %u model_idx %u", + __func__, vnd ? "vnd" : "sig", elem_idx, model_idx); + err = -ENOENT; + goto free; + } + + err = model_set_bind(vnd, model, model_key); + if (err) { + BT_ERR("%s, model_set_bind fail", __func__); + goto free; + } + + err = model_set_sub(vnd, model, model_key); + if (err) { + BT_ERR("%s, model_set_sub fail", __func__); + goto free; + } + + err = model_set_pub(vnd, model, model_key); + if (err) { + BT_ERR("%s, model_set_pub fail", __func__); + goto free; + } + } + +free: + bt_mesh_free_buf(buf); + return err; +} + +static int sig_mod_set(const char *name) +{ + return model_set(false, name); +} + +static int vnd_mod_set(const char *name) +{ + return model_set(true, name); +} + +const struct bt_mesh_setting { + const char *name; + int (*func)(const char *name); +} settings[] = { + { "mesh/net", net_set }, + { "mesh/iv", iv_set }, + { "mesh/seq", seq_set }, + { "mesh/rpl", rpl_set }, + { "mesh/netkey", net_key_set }, + { "mesh/appkey", app_key_set }, + { "mesh/hb_pub", hb_pub_set }, + { "mesh/cfg", cfg_set }, + { "mesh/sig", sig_mod_set }, + { "mesh/vnd", vnd_mod_set }, +}; + +int settings_core_load(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(settings); i++) { + settings[i].func(settings[i].name); + } + + return 0; +} + +static int subnet_init(struct bt_mesh_subnet *sub) +{ + int err; + + err = bt_mesh_net_keys_create(&sub->keys[0], sub->keys[0].net); + if (err) { + BT_ERR("%s, Unable to generate keys for subnet", __func__); + return -EIO; + } + + if (sub->kr_phase != BLE_MESH_KR_NORMAL) { + err = bt_mesh_net_keys_create(&sub->keys[1], sub->keys[1].net); + if (err) { + BT_ERR("%s, Unable to generate keys for subnet", __func__); + (void)memset(&sub->keys[0], 0, sizeof(sub->keys[0])); + return -EIO; + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY)) { + sub->node_id = BLE_MESH_NODE_IDENTITY_STOPPED; + } else { + sub->node_id = BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED; + } + + /* Make sure we have valid beacon data to be sent */ + bt_mesh_net_beacon_update(sub); + + return 0; +} + +static void commit_model(struct bt_mesh_model *model, struct bt_mesh_elem *elem, + bool vnd, bool primary, void *user_data) +{ + if (model->pub && model->pub->update && + model->pub->addr != BLE_MESH_ADDR_UNASSIGNED) { + s32_t ms = bt_mesh_model_pub_period_get(model); + if (ms) { + BT_DBG("Starting publish timer (period %u ms)", ms); + k_delayed_work_submit(&model->pub->timer, ms); + } + } +} + +int settings_core_commit(void) +{ + struct bt_mesh_hb_pub *hb_pub = NULL; + struct bt_mesh_cfg_srv *cfg = NULL; + int i; + + BT_DBG("sub[0].net_idx 0x%03x", bt_mesh.sub[0].net_idx); + + if (bt_mesh.sub[0].net_idx == BLE_MESH_KEY_UNUSED) { + /* Nothing to do since we're not yet provisioned */ + return 0; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) { +#if defined(CONFIG_BLE_MESH_NODE) + bt_mesh_proxy_prov_disable(true); +#endif + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + struct bt_mesh_subnet *sub = &bt_mesh.sub[i]; + int err; + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + err = subnet_init(sub); + if (err) { + BT_ERR("%s, Failed to init subnet 0x%03x", __func__, sub->net_idx); + } + } + + if (bt_mesh.ivu_duration < BLE_MESH_IVU_MIN_HOURS) { + k_delayed_work_submit(&bt_mesh.ivu_timer, BLE_MESH_IVU_TIMEOUT); + } + + bt_mesh_model_foreach(commit_model, NULL); + + hb_pub = bt_mesh_hb_pub_get(); + if (hb_pub && hb_pub->dst != BLE_MESH_ADDR_UNASSIGNED && + hb_pub->count && hb_pub->period) { + BT_DBG("Starting heartbeat publication"); + k_work_submit(&hb_pub->timer.work); + } + + cfg = bt_mesh_cfg_get(); + if (cfg && stored_cfg.valid) { + cfg->net_transmit = stored_cfg.cfg.net_transmit; + cfg->relay = stored_cfg.cfg.relay; + cfg->relay_retransmit = stored_cfg.cfg.relay_retransmit; + cfg->beacon = stored_cfg.cfg.beacon; + cfg->gatt_proxy = stored_cfg.cfg.gatt_proxy; + cfg->frnd = stored_cfg.cfg.frnd; + cfg->default_ttl = stored_cfg.cfg.default_ttl; + } + + bt_mesh_atomic_set_bit(bt_mesh.flags, BLE_MESH_VALID); + +#if defined(CONFIG_BLE_MESH_NODE) + bt_mesh_net_start(); +#endif + + return 0; +} + +static void schedule_store(int flag) +{ + s32_t timeout; + + bt_mesh_atomic_set_bit(bt_mesh.flags, flag); + + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_NET_PENDING) || + bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IV_PENDING) || + bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_SEQ_PENDING)) { + timeout = K_NO_WAIT; + } else if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_RPL_PENDING) && + (CONFIG_BLE_MESH_RPL_STORE_TIMEOUT < + CONFIG_BLE_MESH_STORE_TIMEOUT)) { + timeout = K_SECONDS(CONFIG_BLE_MESH_RPL_STORE_TIMEOUT); + } else { + timeout = K_SECONDS(CONFIG_BLE_MESH_STORE_TIMEOUT); + } + + BT_DBG("Waiting %d seconds", timeout / MSEC_PER_SEC); + + if (timeout) { + k_delayed_work_submit(&pending_store, timeout); + } else { + k_work_submit(&pending_store.work); + } +} + +static void clear_iv(void) +{ + BT_DBG("Clearing IV"); + bt_mesh_save_core_settings("mesh/iv", NULL, 0); +} + +static void clear_net(void) +{ + BT_DBG("Clearing Network"); + bt_mesh_save_core_settings("mesh/net", NULL, 0); +} + +static void store_pending_net(void) +{ + struct net_val net = {0}; + + BT_DBG("addr 0x%04x DevKey %s", bt_mesh_primary_addr(), + bt_hex(bt_mesh.dev_key, 16)); + + net.primary_addr = bt_mesh_primary_addr(); + memcpy(net.dev_key, bt_mesh.dev_key, 16); + + bt_mesh_save_core_settings("mesh/net", (const u8_t *)&net, sizeof(net)); +} + +void bt_mesh_store_net(void) +{ + schedule_store(BLE_MESH_NET_PENDING); +} + +static void store_pending_iv(void) +{ + struct iv_val iv = {0}; + + iv.iv_index = bt_mesh.iv_index; + iv.iv_update = bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS); + iv.iv_duration = bt_mesh.ivu_duration; + + bt_mesh_save_core_settings("mesh/iv", (const u8_t *)&iv, sizeof(iv)); +} + +void bt_mesh_store_iv(bool only_duration) +{ + schedule_store(BLE_MESH_IV_PENDING); + + if (!only_duration) { + /* Always update Seq whenever IV changes */ + schedule_store(BLE_MESH_SEQ_PENDING); + } +} + +static void store_pending_seq(void) +{ + struct seq_val seq = {0}; + + seq.val[0] = bt_mesh.seq; + seq.val[1] = bt_mesh.seq >> 8; + seq.val[2] = bt_mesh.seq >> 16; + + bt_mesh_save_core_settings("mesh/seq", (const u8_t *)&seq, sizeof(seq)); +} + +void bt_mesh_store_seq(void) +{ + if (CONFIG_BLE_MESH_SEQ_STORE_RATE && + (bt_mesh.seq % CONFIG_BLE_MESH_SEQ_STORE_RATE)) { + return; + } + + schedule_store(BLE_MESH_SEQ_PENDING); +} + +static void store_rpl(struct bt_mesh_rpl *entry) +{ + struct rpl_val rpl = {0}; + char name[16] = {'\0'}; + int err; + + BT_DBG("src 0x%04x seq 0x%06x old_iv %u", entry->src, entry->seq, entry->old_iv); + + rpl.seq = entry->seq; + rpl.old_iv = entry->old_iv; + + sprintf(name, "mesh/rpl/%04x", entry->src); + err = bt_mesh_save_core_settings(name, (const u8_t *)&rpl, sizeof(rpl)); + if (err) { + BT_ERR("%s, Failed to save RPL %s", __func__, name); + return; + } + + err = bt_mesh_add_core_settings_item("mesh/rpl", entry->src); + if (err) { + BT_ERR("%s, Failed to add 0x%04x to mesh/rpl", __func__, entry->src); + } + + return; +} + +static void clear_rpl(void) +{ + struct net_buf_simple *buf = NULL; + char name[16] = {'\0'}; + size_t length; + u16_t src; + int i; + + BT_DBG("%s", __func__); + + bt_mesh_rpl_clear(); + + buf = bt_mesh_get_core_settings_item("mesh/rpl"); + if (!buf) { + BT_WARN("%s, Erase RPL", __func__); + bt_mesh_save_core_settings("mesh/rpl", NULL, 0); + return; + } + + length = buf->len; + + for (i = 0; i < length / SETTINGS_ITEM_SIZE; i++) { + src = net_buf_simple_pull_le16(buf); + sprintf(name, "mesh/rpl/%04x", src); + bt_mesh_save_core_settings(name, NULL, 0); + } + + bt_mesh_save_core_settings("mesh/rpl", NULL, 0); + + bt_mesh_free_buf(buf); + return; +} + +static void store_pending_rpl(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) { + struct bt_mesh_rpl *rpl = &bt_mesh.rpl[i]; + + if (rpl->store) { + rpl->store = false; + store_rpl(rpl); + } + } +} + +static void store_pending_hb_pub(void) +{ + struct bt_mesh_hb_pub *hb_pub = bt_mesh_hb_pub_get(); + struct hb_pub_val val = {0}; + + if (!hb_pub) { + BT_WARN("%s, NULL hb_pub", __func__); + return; + } + + val.indefinite = (hb_pub->count = 0xffff); + val.dst = hb_pub->dst; + val.period = hb_pub->period; + val.ttl = hb_pub->ttl; + val.feat = hb_pub->feat; + val.net_idx = hb_pub->net_idx; + + bt_mesh_save_core_settings("mesh/hb_pub", (const u8_t *)&val, sizeof(val)); +} + +static void store_pending_cfg(void) +{ + struct bt_mesh_cfg_srv *cfg = bt_mesh_cfg_get(); + struct cfg_val val = {0}; + + if (!cfg) { + BT_WARN("%s, NULL cfg", __func__); + return; + } + + val.net_transmit = cfg->net_transmit; + val.relay = cfg->relay; + val.relay_retransmit = cfg->relay_retransmit; + val.beacon = cfg->beacon; + val.gatt_proxy = cfg->gatt_proxy; + val.frnd = cfg->frnd; + val.default_ttl = cfg->default_ttl; + + bt_mesh_save_core_settings("mesh/cfg", (const u8_t *)&val, sizeof(val)); +} + +static void clear_cfg(void) +{ + BT_DBG("Clearing configuration"); + bt_mesh_save_core_settings("mesh/cfg", NULL, 0); +} + +static void clear_app_key(u16_t app_idx) +{ + char name[16] = {'\0'}; + int err; + + BT_DBG("AppKeyIndex 0x%03x", app_idx); + + sprintf(name, "mesh/ak/%04x", app_idx); + bt_mesh_save_core_settings(name, NULL, 0); + + err = bt_mesh_remove_core_settings_item("mesh/appkey", app_idx); + if (err) { + BT_ERR("%s, Failed to remove 0x%04x from mesh/appkey", __func__, app_idx); + } + + return; +} + +static void clear_net_key(u16_t net_idx) +{ + char name[16] = {'\0'}; + int err; + + BT_DBG("NetKeyIndex 0x%03x", net_idx); + + sprintf(name, "mesh/nk/%04x", net_idx); + bt_mesh_save_core_settings(name, NULL, 0); + + err = bt_mesh_remove_core_settings_item("mesh/netkey", net_idx); + if (err) { + BT_ERR("%s, Failed to remove 0x%04x from mesh/netkey", __func__, net_idx); + } + + return; +} + +static void store_net_key(struct bt_mesh_subnet *sub) +{ + struct net_key_val key = {0}; + char name[16] = {'\0'}; + int err; + + BT_DBG("NetKeyIndex 0x%03x NetKey %s", sub->net_idx, + bt_hex(sub->keys[0].net, 16)); + + memcpy(&key.val[0], sub->keys[0].net, 16); + memcpy(&key.val[1], sub->keys[1].net, 16); + key.kr_flag = sub->kr_flag; + key.kr_phase = sub->kr_phase; + + sprintf(name, "mesh/nk/%04x", sub->net_idx); + err = bt_mesh_save_core_settings(name, (const u8_t *)&key, sizeof(key)); + if (err) { + BT_ERR("%s, Failed to save NetKey %s", __func__, name); + return; + } + + err = bt_mesh_add_core_settings_item("mesh/netkey", sub->net_idx); + if (err) { + BT_ERR("%s, Failed to add 0x%04x to mesh/netkey", __func__, sub->net_idx); + } + + return; +} + +static void store_app_key(struct bt_mesh_app_key *app) +{ + struct app_key_val key = {0}; + char name[16] = {'\0'}; + int err; + + key.net_idx = app->net_idx; + key.updated = app->updated; + memcpy(key.val[0], app->keys[0].val, 16); + memcpy(key.val[1], app->keys[1].val, 16); + + sprintf(name, "mesh/ak/%04x", app->app_idx); + err = bt_mesh_save_core_settings(name, (const u8_t *)&key, sizeof(key)); + if (err) { + BT_ERR("%s, Failed to save AppKey %s", __func__, name); + return; + } + + err = bt_mesh_add_core_settings_item("mesh/appkey", app->app_idx); + if (err) { + BT_ERR("%s, Failed to add 0x%04x to mesh/appkey", __func__, app->app_idx); + } + + return; +} + +static void store_pending_keys(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(key_updates); i++) { + struct key_update *update = &key_updates[i]; + + if (!update->valid) { + continue; + } + + if (update->clear) { + if (update->app_key) { + clear_app_key(update->key_idx); + } else { + clear_net_key(update->key_idx); + } + } else { + if (update->app_key) { + struct bt_mesh_app_key *key = NULL; + key = bt_mesh_app_key_find(update->key_idx); + if (key) { + store_app_key(key); + } else { + BT_WARN("AppKeyIndex 0x%03x not found", update->key_idx); + } + } else { + struct bt_mesh_subnet *sub = NULL; + sub = bt_mesh_subnet_get(update->key_idx); + if (sub) { + store_net_key(sub); + } else { + BT_WARN("NetKeyIndex 0x%03x not found", update->key_idx); + } + } + } + + update->valid = 0U; + } +} + +static void store_pending_mod_bind(struct bt_mesh_model *model, bool vnd) +{ + char name[16] = {'\0'}; + u16_t model_key; + int err; + + model_key = GET_MODEL_KEY(model->elem_idx, model->model_idx); + + sprintf(name, "mesh/%s/%04x/b", vnd ? "v" : "s", model_key); + + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + bt_mesh_save_core_settings(name, NULL, 0); + return; + } + + err = bt_mesh_save_core_settings(name, (const u8_t *)model->keys, sizeof(model->keys)); + if (err) { + BT_ERR("%s, Failed to save %s", __func__, name); + return; + } + + err = bt_mesh_add_core_settings_item(vnd ? "mesh/vnd" : "mesh/sig", model_key); + if (err) { + BT_ERR("%s, Failed to add 0x%04x to %s", __func__, model_key, + vnd ? "mesh/vnd" : "mesh/sig"); + } + + return; +} + +static void store_pending_mod_sub(struct bt_mesh_model *model, bool vnd) +{ + char name[16] = {'\0'}; + u16_t model_key; + int err; + + model_key = GET_MODEL_KEY(model->elem_idx, model->model_idx); + + sprintf(name, "mesh/%s/%04x/s", vnd ? "v" : "s", model_key); + + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + bt_mesh_save_core_settings(name, NULL, 0); + return; + } + + err = bt_mesh_save_core_settings(name, (const u8_t *)model->groups, sizeof(model->groups)); + if (err) { + BT_ERR("%s, Failed to save %s", __func__, name); + return; + } + + err = bt_mesh_add_core_settings_item(vnd ? "mesh/vnd" : "mesh/sig", model_key); + if (err) { + BT_ERR("%s, Failed to add 0x%04x to %s", __func__, model_key, + vnd ? "mesh/vnd" : "mesh/sig"); + } + + return; +} + +static void store_pending_mod_pub(struct bt_mesh_model *model, bool vnd) +{ + struct mod_pub_val pub = {0}; + char name[16] = {'\0'}; + u16_t model_key; + int err; + + if (!model->pub) { + BT_WARN("%s, No model publication to store", __func__); + return; + } + + pub.addr = model->pub->addr; + pub.key = model->pub->key; + pub.ttl = model->pub->ttl; + pub.retransmit = model->pub->retransmit; + pub.period = model->pub->period; + pub.period_div = model->pub->period_div; + pub.cred = model->pub->cred; + + model_key = GET_MODEL_KEY(model->elem_idx, model->model_idx); + + sprintf(name, "mesh/%s/%04x/p", vnd ? "v" : "s", model_key); + + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + bt_mesh_save_core_settings(name, NULL, 0); + return; + } + + err = bt_mesh_save_core_settings(name, (const u8_t *)&pub, sizeof(pub)); + if (err) { + BT_ERR("%s, Failed to save %s", __func__, name); + return; + } + + err = bt_mesh_add_core_settings_item(vnd ? "mesh/vnd" : "mesh/sig", model_key); + if (err) { + BT_ERR("%s, Failed to add 0x%04x to %s", __func__, model_key, + vnd ? "mesh/vnd" : "mesh/sig"); + } + + return; +} + +static void store_pending_mod(struct bt_mesh_model *model, + struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + if (!model->flags) { + return; + } + + if (model->flags & BLE_MESH_MOD_BIND_PENDING) { + model->flags &= ~BLE_MESH_MOD_BIND_PENDING; + store_pending_mod_bind(model, vnd); + } + + if (model->flags & BLE_MESH_MOD_SUB_PENDING) { + model->flags &= ~BLE_MESH_MOD_SUB_PENDING; + store_pending_mod_sub(model, vnd); + } + + if (model->flags & BLE_MESH_MOD_PUB_PENDING) { + model->flags &= ~BLE_MESH_MOD_PUB_PENDING; + store_pending_mod_pub(model, vnd); + } +} + +static void store_pending(struct k_work *work) +{ + BT_DBG("%s", __func__); + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_RPL_PENDING)) { + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + store_pending_rpl(); + } else { + clear_rpl(); + } + } + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_KEYS_PENDING)) { + store_pending_keys(); + } + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_NET_PENDING)) { + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + store_pending_net(); + } else { + clear_net(); + } + } + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_IV_PENDING)) { + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + store_pending_iv(); + } else { + clear_iv(); + } + } + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_SEQ_PENDING)) { + store_pending_seq(); + } + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_HB_PUB_PENDING)) { + store_pending_hb_pub(); + } + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_CFG_PENDING)) { + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + store_pending_cfg(); + } else { + clear_cfg(); + } + } + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_MOD_PENDING)) { + bt_mesh_model_foreach(store_pending_mod, NULL); + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID)) { + bt_mesh_save_core_settings("mesh/sig", NULL, 0); + bt_mesh_save_core_settings("mesh/vnd", NULL, 0); + } + } +} + +void bt_mesh_store_rpl(struct bt_mesh_rpl *entry) +{ + entry->store = true; + schedule_store(BLE_MESH_RPL_PENDING); +} + +static struct key_update *key_update_find(bool app_key, u16_t key_idx, + struct key_update **free_slot) +{ + struct key_update *match = NULL; + int i; + + *free_slot = NULL; + + for (i = 0; i < ARRAY_SIZE(key_updates); i++) { + struct key_update *update = &key_updates[i]; + + if (!update->valid) { + *free_slot = update; + continue; + } + + if (update->app_key != app_key) { + continue; + } + + if (update->key_idx == key_idx) { + match = update; + } + } + + return match; +} + +void bt_mesh_store_subnet(struct bt_mesh_subnet *sub) +{ + struct key_update *free_slot = NULL; + struct key_update *update = NULL; + + BT_DBG("NetKeyIndex 0x%03x", sub->net_idx); + + update = key_update_find(false, sub->net_idx, &free_slot); + if (update) { + update->clear = 0U; + schedule_store(BLE_MESH_KEYS_PENDING); + return; + } + + if (!free_slot) { + store_net_key(sub); + return; + } + + free_slot->valid = 1U; + free_slot->key_idx = sub->net_idx; + free_slot->app_key = 0U; + free_slot->clear = 0U; + + schedule_store(BLE_MESH_KEYS_PENDING); +} + +void bt_mesh_store_app_key(struct bt_mesh_app_key *key) +{ + struct key_update *free_slot = NULL; + struct key_update *update = NULL; + + BT_DBG("AppKeyIndex 0x%03x", key->app_idx); + + update = key_update_find(true, key->app_idx, &free_slot); + if (update) { + update->clear = 0U; + schedule_store(BLE_MESH_KEYS_PENDING); + return; + } + + if (!free_slot) { + store_app_key(key); + return; + } + + free_slot->valid = 1U; + free_slot->key_idx = key->app_idx; + free_slot->app_key = 1U; + free_slot->clear = 0U; + + schedule_store(BLE_MESH_KEYS_PENDING); +} + +void bt_mesh_store_hb_pub(void) +{ + schedule_store(BLE_MESH_HB_PUB_PENDING); +} + +void bt_mesh_store_cfg(void) +{ + schedule_store(BLE_MESH_CFG_PENDING); +} + +void bt_mesh_clear_net(void) +{ + schedule_store(BLE_MESH_NET_PENDING); + schedule_store(BLE_MESH_IV_PENDING); + schedule_store(BLE_MESH_CFG_PENDING); +} + +void bt_mesh_clear_subnet(struct bt_mesh_subnet *sub) +{ + struct key_update *free_slot = NULL; + struct key_update *update = NULL; + + BT_DBG("NetKeyIndex 0x%03x", sub->net_idx); + + update = key_update_find(false, sub->net_idx, &free_slot); + if (update) { + update->clear = 1U; + schedule_store(BLE_MESH_KEYS_PENDING); + return; + } + + if (!free_slot) { + clear_net_key(sub->net_idx); + return; + } + + free_slot->valid = 1U; + free_slot->key_idx = sub->net_idx; + free_slot->app_key = 0U; + free_slot->clear = 1U; + + schedule_store(BLE_MESH_KEYS_PENDING); +} + +void bt_mesh_clear_app_key(struct bt_mesh_app_key *key) +{ + struct key_update *free_slot = NULL; + struct key_update *update = NULL; + + BT_DBG("AppKeyIndex 0x%03x", key->app_idx); + + update = key_update_find(true, key->app_idx, &free_slot); + if (update) { + update->clear = 1U; + schedule_store(BLE_MESH_KEYS_PENDING); + return; + } + + if (!free_slot) { + clear_app_key(key->app_idx); + return; + } + + free_slot->valid = 1U; + free_slot->key_idx = key->app_idx; + free_slot->app_key = 1U; + free_slot->clear = 1U; + + schedule_store(BLE_MESH_KEYS_PENDING); +} + +void bt_mesh_clear_rpl(void) +{ + schedule_store(BLE_MESH_RPL_PENDING); +} + +void bt_mesh_store_mod_bind(struct bt_mesh_model *model) +{ + model->flags |= BLE_MESH_MOD_BIND_PENDING; + schedule_store(BLE_MESH_MOD_PENDING); +} + +void bt_mesh_store_mod_sub(struct bt_mesh_model *model) +{ + model->flags |= BLE_MESH_MOD_SUB_PENDING; + schedule_store(BLE_MESH_MOD_PENDING); +} + +void bt_mesh_store_mod_pub(struct bt_mesh_model *model) +{ + model->flags |= BLE_MESH_MOD_PUB_PENDING; + schedule_store(BLE_MESH_MOD_PENDING); +} + +int settings_core_init(void) +{ + BT_DBG("%s", __func__); + + k_delayed_work_init(&pending_store, store_pending); + + return 0; +} + +int bt_mesh_settings_init(void) +{ + BT_DBG("%s", __func__); + + bt_mesh_settings_foreach(); + + return 0; +} + +#endif /* CONFIG_BLE_MESH_SETTINGS */ diff --git a/components/bt/ble_mesh/mesh_core/settings.h b/components/bt/ble_mesh/mesh_core/settings.h new file mode 100644 index 0000000000..84b5e182b4 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/settings.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ + +#include "sdkconfig.h" + +#include "net.h" +#include "mesh_access.h" +#include "mesh_bearer_adapt.h" + +int settings_core_init(void); +int settings_core_load(void); +int settings_core_commit(void); + +void bt_mesh_store_net(void); +void bt_mesh_store_iv(bool only_duration); +void bt_mesh_store_seq(void); +void bt_mesh_store_rpl(struct bt_mesh_rpl *rpl); +void bt_mesh_store_subnet(struct bt_mesh_subnet *sub); +void bt_mesh_store_app_key(struct bt_mesh_app_key *key); +void bt_mesh_store_hb_pub(void); +void bt_mesh_store_cfg(void); +void bt_mesh_store_mod_bind(struct bt_mesh_model *mod); +void bt_mesh_store_mod_sub(struct bt_mesh_model *mod); +void bt_mesh_store_mod_pub(struct bt_mesh_model *mod); + +void bt_mesh_clear_net(void); +void bt_mesh_clear_subnet(struct bt_mesh_subnet *sub); +void bt_mesh_clear_app_key(struct bt_mesh_app_key *key); +void bt_mesh_clear_rpl(void); + +int bt_mesh_settings_init(void); + +#endif /* _SETTINGS_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/settings/settings_nvs.c b/components/bt/ble_mesh/mesh_core/settings/settings_nvs.c new file mode 100644 index 0000000000..1a19bee297 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/settings/settings_nvs.c @@ -0,0 +1,374 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "nvs.h" +#include "sdkconfig.h" + +#include "mesh_util.h" +#include "mesh_types.h" +#include "mesh_common.h" + +#include "settings_nvs.h" +#include "settings.h" + +#if CONFIG_BLE_MESH_SETTINGS + +enum settings_type { + SETTINGS_CORE, + SETTINGS_SERVER, +}; + +struct settings_context { + char *nvs_name; + nvs_handle handle; + + int (*settings_init)(void); + int (*settings_load)(void); + int (*settings_commit)(void); +}; + +static struct settings_context settings_ctx[] = { + [SETTINGS_CORE] = { + .nvs_name = "mesh_core", + .settings_init = settings_core_init, + .settings_load = settings_core_load, + .settings_commit = settings_core_commit, + }, + [SETTINGS_SERVER] = { + .nvs_name = "mesh_server", + .settings_init = NULL, + .settings_load = NULL, + .settings_commit = NULL, + }, +}; + +/* API used to initialize, load and commit BLE Mesh related settings */ + +void bt_mesh_settings_foreach(void) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(settings_ctx); i++) { + struct settings_context *ctx = &settings_ctx[i]; + + err = nvs_open(ctx->nvs_name, NVS_READWRITE, &ctx->handle); + if (err != ESP_OK) { + BT_ERR("%s, Open nvs failed, name %s, err %d", __func__, ctx->nvs_name, err); + continue; + } + + if (ctx->settings_init && ctx->settings_init()) { + BT_ERR("%s, Init settings failed, name %s", __func__, ctx->nvs_name); + continue; + } + + if (ctx->settings_load && ctx->settings_load()) { + BT_ERR("%s, Load settings failed, name %s", __func__, ctx->nvs_name); + continue; + } + + if (ctx->settings_commit && ctx->settings_commit()) { + BT_ERR("%s, Commit settings failed, name %s", __func__, ctx->nvs_name); + continue; + } + } +} + +/* API used to get BLE Mesh related nvs handle */ + +static inline nvs_handle settings_get_nvs_handle(enum settings_type type) +{ + return settings_ctx[type].handle; +} + +/* API used to store/erase BLE Mesh related settings */ + +static int settings_save(nvs_handle handle, const char *key, const u8_t *val, size_t len) +{ + int err; + + if (key == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + BT_DBG("%s, nvs %s, key %s", __func__, val ? "set" : "erase", key); + + if (val) { + err = nvs_set_blob(handle, key, val, len); + } else { + err = nvs_erase_key(handle, key); + if (err == ESP_ERR_NVS_NOT_FOUND) { + BT_DBG("%s, %s does not exist", __func__, key); + return 0; + } + } + if (err != ESP_OK) { + BT_ERR("%s, Failed to %s %s data (err %d)", __func__, + val ? "set" : "erase", key, err); + return -EIO; + } + + err = nvs_commit(handle); + if (err != ESP_OK) { + BT_ERR("%s, Failed to commit settings (err %d)", __func__, err); + return -EIO; + } + + return 0; +} + +int bt_mesh_save_core_settings(const char *key, const u8_t *val, size_t len) +{ + nvs_handle handle = settings_get_nvs_handle(SETTINGS_CORE); + return settings_save(handle, key, val, len); +} + +/* API used to load BLE Mesh related settings */ + +static int settings_load(nvs_handle handle, const char *key, + u8_t *buf, size_t buf_len, bool *exist) +{ + int err; + + if (key == NULL || buf == NULL || exist == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + err = nvs_get_blob(handle, key, buf, &buf_len); + if (err != ESP_OK) { + if (err == ESP_ERR_NVS_NOT_FOUND) { + BT_DBG("%s, Settings %s is not found", __func__, key); + *exist = false; + return 0; + } + + BT_ERR("%s, Failed to get %s data (err %d)", __func__, key, err); + return -EIO; + } + + *exist = true; + return 0; +} + +int bt_mesh_load_core_settings(const char *key, u8_t *buf, size_t buf_len, bool *exist) +{ + nvs_handle handle = settings_get_nvs_handle(SETTINGS_CORE); + return settings_load(handle, key, buf, buf_len, exist); +} + +/* API used to get length of BLE Mesh related settings */ + +static size_t settings_get_length(nvs_handle handle, const char *key) +{ + size_t len = 0; + int err; + + if (key == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return 0; + } + + err = nvs_get_blob(handle, key, NULL, &len); + if (err != ESP_OK) { + if (err != ESP_ERR_NVS_NOT_FOUND) { + BT_ERR("%s, Failed to get %s length (err %d)", __func__, key, err); + } + return 0; + } + + return len; +} + +/* API used to get BLE Mesh related items. Here items mean model key, NetKey/AppKey + * Index, etc. which are going to be used as the prefix of the nvs keys of the BLE + * Mesh settings. + */ + +static struct net_buf_simple *settings_get_item(nvs_handle handle, const char *key) +{ + struct net_buf_simple *buf = NULL; + size_t length; + bool exist; + int err; + + length = settings_get_length(handle, key); + if (!length) { + BT_DBG("%s, Empty %s", __func__, key); + return NULL; + } + + buf = bt_mesh_alloc_buf(length); + if (!buf) { + BT_ERR("%s, Failed to allocate memory", __func__); + /* TODO: in this case, erase all related settings? */ + return NULL; + } + + err = settings_load(handle, key, buf->data, length, &exist); + if (err) { + BT_ERR("%s, Failed to load %s", __func__, key); + /* TODO: in this case, erase all related settings? */ + bt_mesh_free_buf(buf); + return NULL; + } + + if (exist == false) { + bt_mesh_free_buf(buf); + return NULL; + } + + buf->len = length; + return buf; +} + +struct net_buf_simple *bt_mesh_get_core_settings_item(const char *key) +{ + nvs_handle handle = settings_get_nvs_handle(SETTINGS_CORE); + return settings_get_item(handle, key); +} + +/* API used to check if the settings item exists */ + +static bool is_settings_item_exist(struct net_buf_simple *buf, const u16_t val) +{ + struct net_buf_simple_state state = {0}; + size_t length; + int i; + + if (!buf) { + return false; + } + + net_buf_simple_save(buf, &state); + + length = buf->len; + for (i = 0; i < length / SETTINGS_ITEM_SIZE; i++) { + u16_t item = net_buf_simple_pull_le16(buf); + if (item == val) { + net_buf_simple_restore(buf, &state); + return true; + } + } + + net_buf_simple_restore(buf, &state); + return false; +} + +/* API used to add the settings item */ + +static int settings_add_item(nvs_handle handle, const char *key, const u16_t val) +{ + struct net_buf_simple *store = NULL; + struct net_buf_simple *buf = NULL; + size_t length = 0; + int err; + + buf = settings_get_item(handle, key); + + /* Check if val already exists */ + if (is_settings_item_exist(buf, val) == true) { + BT_DBG("%s, 0x%04x already exists", __func__, val); + bt_mesh_free_buf(buf); + return 0; + } + + length = (buf ? buf->len : 0) + sizeof(val); + + store = bt_mesh_alloc_buf(length); + if (!store) { + BT_ERR("%s, Failed to allocate memory", __func__); + bt_mesh_free_buf(buf); + return -ENOMEM; + } + + if (buf) { + net_buf_simple_add_mem(store, buf->data, buf->len); + } + net_buf_simple_add_mem(store, &val, sizeof(val)); + + err = settings_save(handle, key, store->data, store->len); + + bt_mesh_free_buf(store); + bt_mesh_free_buf(buf); + return err; +} + +int bt_mesh_add_core_settings_item(const char *key, const u16_t val) +{ + nvs_handle handle = settings_get_nvs_handle(SETTINGS_CORE); + return settings_add_item(handle, key, val); +} + +/* API used to remove the settings item */ + +static int settings_remove_item(nvs_handle handle, const char *key, const u16_t val) +{ + struct net_buf_simple *store = NULL; + struct net_buf_simple *buf = NULL; + size_t length = 0; + size_t buf_len; + int i, err; + + buf = settings_get_item(handle, key); + + /* Check if val does exist */ + if (is_settings_item_exist(buf, val) == false) { + BT_DBG("%s, 0x%04x does not exist", __func__, val); + bt_mesh_free_buf(buf); + return 0; + } + + length = buf->len - sizeof(val); + if (!length) { + settings_save(handle, key, NULL, 0); + bt_mesh_free_buf(buf); + return 0; + } + + store = bt_mesh_alloc_buf(length); + if (!store) { + BT_ERR("%s, Failed to allocate memory", __func__); + bt_mesh_free_buf(buf); + return -ENOMEM; + } + + buf_len = buf->len; + for (i = 0; i < buf_len / SETTINGS_ITEM_SIZE; i++) { + u16_t item = net_buf_simple_pull_le16(buf); + if (item != val) { + net_buf_simple_add_le16(store, item); + } + } + + err = settings_save(handle, key, store->data, store->len); + + bt_mesh_free_buf(store); + bt_mesh_free_buf(buf); + return err; +} + +int bt_mesh_remove_core_settings_item(const char *key, const u16_t val) +{ + nvs_handle handle = settings_get_nvs_handle(SETTINGS_CORE); + return settings_remove_item(handle, key, val); +} + +#endif /* CONFIG_BLE_MESH_SETTINGS */ diff --git a/components/bt/ble_mesh/mesh_core/settings/settings_nvs.h b/components/bt/ble_mesh/mesh_core/settings/settings_nvs.h new file mode 100644 index 0000000000..b1e929adf8 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/settings/settings_nvs.h @@ -0,0 +1,44 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BLE_MESH_SETTINGS_NVS_H_ +#define _BLE_MESH_SETTINGS_NVS_H_ + +#include +#include "mesh_types.h" +#include "mesh_buf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SETTINGS_ITEM_SIZE sizeof(u16_t) + +void bt_mesh_settings_foreach(void); + +int bt_mesh_save_core_settings(const char *key, const u8_t *val, size_t len); + +int bt_mesh_load_core_settings(const char *key, u8_t *buf, size_t buf_len, bool *exist); + +struct net_buf_simple *bt_mesh_get_core_settings_item(const char *key); + +int bt_mesh_add_core_settings_item(const char *key, const u16_t val); + +int bt_mesh_remove_core_settings_item(const char *key, const u16_t val); + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_SETTINGS_NVS_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/test.c b/components/bt/ble_mesh/mesh_core/test.c new file mode 100644 index 0000000000..f61beb8720 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/test.c @@ -0,0 +1,131 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "mesh_trace.h" +#include "mesh_main.h" +#include "mesh_access.h" + +#include "mesh.h" +#include "test.h" +#include "crypto.h" +#include "net.h" +#include "foundation.h" +#include "access.h" + +#if defined(CONFIG_BLE_MESH_SELF_TEST) + +int bt_mesh_test(void) +{ + return 0; +} +#endif /* #if defined(CONFIG_BLE_MESH_SELF_TEST) */ + +int bt_mesh_device_auto_enter_network(struct bt_mesh_device_network_info *info) +{ + const struct bt_mesh_comp *comp = NULL; + struct bt_mesh_model *model = NULL; + struct bt_mesh_elem *elem = NULL; + struct bt_mesh_app_keys *keys = NULL; + struct bt_mesh_app_key *key = NULL; + struct bt_mesh_subnet *sub = NULL; + int i, j, k; + int err; + + if (info == NULL || !BLE_MESH_ADDR_IS_UNICAST(info->unicast_addr) || + !BLE_MESH_ADDR_IS_GROUP(info->group_addr)) { + return -EINVAL; + } + + /* The device becomes a node and enters the network */ + err = bt_mesh_provision(info->net_key, info->net_idx, info->flags, info->iv_index, + info->unicast_addr, info->dev_key); + if (err) { + BT_ERR("%s, bt_mesh_provision() failed (err %d)", __func__, err); + return err; + } + + /* Adds application key to device */ + sub = bt_mesh_subnet_get(info->net_idx); + if (!sub) { + BT_ERR("%s, Failed to find subnet 0x%04x", __func__, info->net_idx); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) { + key = &bt_mesh.app_keys[i]; + if (key->net_idx == BLE_MESH_KEY_UNUSED) { + break; + } + } + if (i == ARRAY_SIZE(bt_mesh.app_keys)) { + BT_ERR("%s, Failed to allocate memory, AppKeyIndex 0x%04x", __func__, info->app_idx); + return -ENOMEM; + } + + keys = sub->kr_flag ? &key->keys[1] : &key->keys[0]; + + if (bt_mesh_app_id(info->app_key, &keys->id)) { + BT_ERR("%s, Failed to calculate AID, AppKeyIndex 0x%04x", __func__, info->app_idx); + return -EIO; + } + + key->net_idx = info->net_idx; + key->app_idx = info->app_idx; + memcpy(keys->val, info->app_key, 16); + + /* Binds AppKey with all non-config models, adds group address to all these models */ + comp = bt_mesh_comp_get(); + if (!comp) { + BT_ERR("%s, Composition data is NULL", __func__); + return -ENODEV; + } + + for (i = 0; i < comp->elem_count; i++) { + elem = &comp->elem[i]; + for (j = 0; j < elem->model_count; j++) { + model = &elem->models[j]; + if (model->id == BLE_MESH_MODEL_ID_CFG_SRV || + model->id == BLE_MESH_MODEL_ID_CFG_CLI) { + continue; + } + for (k = 0; k < ARRAY_SIZE(model->keys); k++) { + if (model->keys[k] == BLE_MESH_KEY_UNUSED) { + model->keys[k] = info->app_idx; + break; + } + } + for (k = 0; k < ARRAY_SIZE(model->groups); k++) { + if (model->groups[k] == BLE_MESH_ADDR_UNASSIGNED) { + model->groups[k] = info->group_addr; + break; + } + } + } + for (j = 0; j < elem->vnd_model_count; j++) { + model = &elem->vnd_models[j]; + for (k = 0; k < ARRAY_SIZE(model->keys); k++) { + if (model->keys[k] == BLE_MESH_KEY_UNUSED) { + model->keys[k] = info->app_idx; + break; + } + } + for (k = 0; k < ARRAY_SIZE(model->groups); k++) { + if (model->groups[k] == BLE_MESH_ADDR_UNASSIGNED) { + model->groups[k] = info->group_addr; + break; + } + } + } + } + + return 0; +} diff --git a/components/bt/ble_mesh/mesh_core/test.h b/components/bt/ble_mesh/mesh_core/test.h new file mode 100644 index 0000000000..60e3d0d8f1 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/test.h @@ -0,0 +1,43 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BLE_MESH_TEST_H_ +#define _BLE_MESH_TEST_H_ + +#include +#include +#include + +#include "mesh_buf.h" +#include "sdkconfig.h" + +#if defined(CONFIG_BLE_MESH_SELF_TEST) +int bt_mesh_test(void); +#else +static inline int bt_mesh_test(void) +{ + return 0; +} +#endif + +struct bt_mesh_device_network_info { + u8_t net_key[16]; + u16_t net_idx; + u8_t flags; + u32_t iv_index; + u16_t unicast_addr; + u8_t dev_key[16]; + u8_t app_key[16]; + u16_t app_idx; + u16_t group_addr; +}; + +int bt_mesh_device_auto_enter_network(struct bt_mesh_device_network_info *info); + +#endif /* _BLE_MESH_TEST_H_ */ diff --git a/components/bt/ble_mesh/mesh_core/transport.c b/components/bt/ble_mesh/mesh_core/transport.c new file mode 100644 index 0000000000..a456ebbfc8 --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/transport.c @@ -0,0 +1,1681 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "sdkconfig.h" +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_TRANS) + +#include "mesh_types.h" +#include "mesh_util.h" +#include "mesh_buf.h" +#include "mesh_trace.h" +#include "mesh_main.h" +#include "settings.h" + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "net.h" +#include "lpn.h" +#include "friend.h" +#include "access.h" +#include "foundation.h" +#include "settings.h" +#include "transport.h" +#include "mesh_common.h" +#include "model_common.h" +#include "provisioner_main.h" + +/* The transport layer needs at least three buffers for itself to avoid + * deadlocks. Ensure that there are a sufficient number of advertising + * buffers available compared to the maximum supported outgoing segment + * count. + */ +_Static_assert(CONFIG_BLE_MESH_ADV_BUF_COUNT >= (CONFIG_BLE_MESH_TX_SEG_MAX + 3), + "Too small BLE Mesh adv buffer count"); + +#define AID_MASK ((u8_t)(BIT_MASK(6))) + +#define SEG(data) ((data)[0] >> 7) +#define AKF(data) (((data)[0] >> 6) & 0x01) +#define AID(data) ((data)[0] & AID_MASK) +#define ASZMIC(data) (((data)[1] >> 7) & 1) + +#define APP_MIC_LEN(aszmic) ((aszmic) ? 8 : 4) + +#define UNSEG_HDR(akf, aid) ((akf << 6) | (aid & AID_MASK)) +#define SEG_HDR(akf, aid) (UNSEG_HDR(akf, aid) | 0x80) + +#define BLOCK_COMPLETE(seg_n) (u32_t)(((u64_t)1 << (seg_n + 1)) - 1) + +#define SEQ_AUTH(iv_index, seq) (((u64_t)iv_index) << 24 | (u64_t)seq) + +/* Number of retransmit attempts (after the initial transmit) per segment */ +#define SEG_RETRANSMIT_ATTEMPTS 4 + +/* "This timer shall be set to a minimum of 200 + 50 * TTL milliseconds.". + * We use 400 since 300 is a common send duration for standard HCI, and we + * need to have a timeout that's bigger than that. + */ +#define SEG_RETRANSMIT_TIMEOUT(tx) (K_MSEC(400) + 50 * (tx)->ttl) + +/* How long to wait for available buffers before giving up */ +#define BUF_TIMEOUT K_NO_WAIT + +static struct seg_tx { + struct bt_mesh_subnet *sub; + struct net_buf *seg[CONFIG_BLE_MESH_TX_SEG_MAX]; + u64_t seq_auth; + u16_t dst; + u8_t seg_n: 5, /* Last segment index */ + new_key: 1; /* New/old key */ + u8_t nack_count; /* Number of unacked segs */ + u8_t ttl; + const struct bt_mesh_send_cb *cb; + void *cb_data; + struct k_delayed_work retransmit; /* Retransmit timer */ +} seg_tx[CONFIG_BLE_MESH_TX_SEG_MSG_COUNT]; + +static struct seg_rx { + struct bt_mesh_subnet *sub; + u64_t seq_auth; + u8_t seg_n: 5, + ctl: 1, + in_use: 1, + obo: 1; + u8_t hdr; + u8_t ttl; + u16_t src; + u16_t dst; + u32_t block; + u32_t last; + struct k_delayed_work ack; + struct net_buf_simple buf; +} seg_rx[CONFIG_BLE_MESH_RX_SEG_MSG_COUNT] = { + [0 ... (CONFIG_BLE_MESH_RX_SEG_MSG_COUNT - 1)] = { + .buf.size = CONFIG_BLE_MESH_RX_SDU_MAX, + }, +}; + +static u8_t seg_rx_buf_data[(CONFIG_BLE_MESH_RX_SEG_MSG_COUNT * + CONFIG_BLE_MESH_RX_SDU_MAX)]; + +static u16_t hb_sub_dst = BLE_MESH_ADDR_UNASSIGNED; + +void bt_mesh_set_hb_sub_dst(u16_t addr) +{ + hb_sub_dst = addr; +} + +static int send_unseg(struct bt_mesh_net_tx *tx, struct net_buf_simple *sdu, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + struct net_buf *buf; + + BT_DBG("src 0x%04x dst 0x%04x app_idx 0x%04x sdu_len %u", + tx->src, tx->ctx->addr, tx->ctx->app_idx, sdu->len); + + buf = bt_mesh_adv_create(BLE_MESH_ADV_DATA, tx->xmit, BUF_TIMEOUT); + if (!buf) { + BT_ERR("%s, Out of network buffers", __func__); + return -ENOBUFS; + } + + net_buf_reserve(buf, BLE_MESH_NET_HDR_LEN); + + if (tx->ctx->app_idx == BLE_MESH_KEY_DEV) { + net_buf_add_u8(buf, UNSEG_HDR(0, 0)); + } else { + net_buf_add_u8(buf, UNSEG_HDR(1, tx->aid)); + } + + net_buf_add_mem(buf, sdu->data, sdu->len); + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + if (bt_mesh_friend_enqueue_tx(tx, BLE_MESH_FRIEND_PDU_SINGLE, + NULL, &buf->b) && + BLE_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) { + /* PDUs for a specific Friend should only go + * out through the Friend Queue. + */ + net_buf_unref(buf); + return 0; + } + } + } + + return bt_mesh_net_send(tx, buf, cb, cb_data); +} + +bool bt_mesh_tx_in_progress(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) { + if (seg_tx[i].nack_count) { + return true; + } + } + + return false; +} + +static void seg_tx_reset(struct seg_tx *tx) +{ + int i; + + k_delayed_work_cancel(&tx->retransmit); + + tx->cb = NULL; + tx->cb_data = NULL; + tx->seq_auth = 0U; + tx->sub = NULL; + tx->dst = BLE_MESH_ADDR_UNASSIGNED; + + if (!tx->nack_count) { + return; + } + + for (i = 0; i <= tx->seg_n; i++) { + if (!tx->seg[i]) { + continue; + } + + /** Change by Espressif. Add this to avoid buf->ref is 2 which will + * cause lack of buf. + */ + if (tx->seg[i]->ref > 1) { + tx->seg[i]->ref = 1; + } + net_buf_unref(tx->seg[i]); + tx->seg[i] = NULL; + } + + tx->nack_count = 0U; + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_IVU_PENDING)) { + BT_DBG("Proceding with pending IV Update"); + /* bt_mesh_net_iv_update() will re-enable the flag if this + * wasn't the only transfer. + */ + if (bt_mesh_net_iv_update(bt_mesh.iv_index, false)) { + bt_mesh_net_sec_update(NULL); + } + } +} + +static inline void seg_tx_complete(struct seg_tx *tx, int err) +{ + if (tx->cb && tx->cb->end) { + tx->cb->end(err, tx->cb_data); + } + + seg_tx_reset(tx); +} + +static void seg_first_send_start(u16_t duration, int err, void *user_data) +{ + struct seg_tx *tx = user_data; + + if (tx->cb && tx->cb->start) { + tx->cb->start(duration, err, tx->cb_data); + } +} + +static void seg_send_start(u16_t duration, int err, void *user_data) +{ + struct seg_tx *tx = user_data; + + /* If there's an error in transmitting the 'sent' callback will never + * be called. Make sure that we kick the retransmit timer also in this + * case since otherwise we risk the transmission of becoming stale. + */ + if (err) { + k_delayed_work_submit(&tx->retransmit, + SEG_RETRANSMIT_TIMEOUT(tx)); + } +} + +static void seg_sent(int err, void *user_data) +{ + struct seg_tx *tx = user_data; + + k_delayed_work_submit(&tx->retransmit, + SEG_RETRANSMIT_TIMEOUT(tx)); +} + +static const struct bt_mesh_send_cb first_sent_cb = { + .start = seg_first_send_start, + .end = seg_sent, +}; + +static const struct bt_mesh_send_cb seg_sent_cb = { + .start = seg_send_start, + .end = seg_sent, +}; + +static void seg_tx_send_unacked(struct seg_tx *tx) +{ + int i, err; + + for (i = 0; i <= tx->seg_n; i++) { + struct net_buf *seg = tx->seg[i]; + + if (!seg) { + continue; + } + + if (BLE_MESH_ADV(seg)->busy) { + BT_DBG("Skipping segment that's still advertising"); + continue; + } + + if (!(BLE_MESH_ADV(seg)->seg.attempts--)) { + BT_WARN("Ran out of retransmit attempts"); + seg_tx_complete(tx, -ETIMEDOUT); + return; + } + + BT_DBG("resending %u/%u", i, tx->seg_n); + + err = bt_mesh_net_resend(tx->sub, seg, tx->new_key, + &seg_sent_cb, tx); + if (err) { + BT_ERR("%s, Sending segment failed", __func__); + seg_tx_complete(tx, -EIO); + return; + } + } +} + +static void seg_retransmit(struct k_work *work) +{ + struct seg_tx *tx = CONTAINER_OF(work, struct seg_tx, retransmit); + + seg_tx_send_unacked(tx); +} + +static int send_seg(struct bt_mesh_net_tx *net_tx, struct net_buf_simple *sdu, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + u8_t seg_hdr, seg_o; + u16_t seq_zero; + struct seg_tx *tx; + int i; + + BT_DBG("src 0x%04x dst 0x%04x app_idx 0x%04x aszmic %u sdu_len %u", + net_tx->src, net_tx->ctx->addr, net_tx->ctx->app_idx, + net_tx->aszmic, sdu->len); + + if (sdu->len < 1) { + BT_ERR("%s, Zero-length SDU not allowed", __func__); + return -EINVAL; + } + + if (sdu->len > BLE_MESH_TX_SDU_MAX) { + BT_ERR("%s, Not enough segment buffers for length %u", __func__, sdu->len); + return -EMSGSIZE; + } + + for (tx = NULL, i = 0; i < ARRAY_SIZE(seg_tx); i++) { + if (!seg_tx[i].nack_count) { + tx = &seg_tx[i]; + break; + } + } + + if (!tx) { + BT_ERR("%s, No multi-segment message contexts available", __func__); + return -EBUSY; + } + + if (net_tx->ctx->app_idx == BLE_MESH_KEY_DEV) { + seg_hdr = SEG_HDR(0, 0); + } else { + seg_hdr = SEG_HDR(1, net_tx->aid); + } + + seg_o = 0U; + tx->dst = net_tx->ctx->addr; + tx->seg_n = (sdu->len - 1) / 12U; + tx->nack_count = tx->seg_n + 1; + tx->seq_auth = SEQ_AUTH(BLE_MESH_NET_IVI_TX, bt_mesh.seq); + tx->sub = net_tx->sub; + tx->new_key = net_tx->sub->kr_flag; + tx->cb = cb; + tx->cb_data = cb_data; + + if (net_tx->ctx->send_ttl == BLE_MESH_TTL_DEFAULT) { + tx->ttl = bt_mesh_default_ttl_get(); + } else { + tx->ttl = net_tx->ctx->send_ttl; + } + + seq_zero = tx->seq_auth & 0x1fff; + + BT_DBG("SeqZero 0x%04x", seq_zero); + + for (seg_o = 0U; sdu->len; seg_o++) { + struct net_buf *seg; + u16_t len; + int err; + + seg = bt_mesh_adv_create(BLE_MESH_ADV_DATA, net_tx->xmit, + BUF_TIMEOUT); + if (!seg) { + BT_ERR("%s, Out of segment buffers", __func__); + seg_tx_reset(tx); + return -ENOBUFS; + } + + BLE_MESH_ADV(seg)->seg.attempts = SEG_RETRANSMIT_ATTEMPTS; + + net_buf_reserve(seg, BLE_MESH_NET_HDR_LEN); + + net_buf_add_u8(seg, seg_hdr); + net_buf_add_u8(seg, (net_tx->aszmic << 7) | seq_zero >> 6); + net_buf_add_u8(seg, (((seq_zero & 0x3f) << 2) | + (seg_o >> 3))); + net_buf_add_u8(seg, ((seg_o & 0x07) << 5) | tx->seg_n); + + len = MIN(sdu->len, 12); + net_buf_add_mem(seg, sdu->data, len); + net_buf_simple_pull(sdu, len); + + tx->seg[seg_o] = net_buf_ref(seg); + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + enum bt_mesh_friend_pdu_type type; + + if (seg_o == tx->seg_n) { + type = BLE_MESH_FRIEND_PDU_COMPLETE; + } else { + type = BLE_MESH_FRIEND_PDU_PARTIAL; + } + + if (bt_mesh_friend_enqueue_tx(net_tx, type, + &tx->seq_auth, + &seg->b) && + BLE_MESH_ADDR_IS_UNICAST(net_tx->ctx->addr)) { + /* PDUs for a specific Friend should only go + * out through the Friend Queue. + */ + net_buf_unref(seg); + return 0; + } + } + } + + BT_DBG("Sending %u/%u", seg_o, tx->seg_n); + + err = bt_mesh_net_send(net_tx, seg, + seg_o ? &seg_sent_cb : &first_sent_cb, + tx); + if (err) { + BT_ERR("%s, Sending segment failed", __func__); + seg_tx_reset(tx); + return err; + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER) && + bt_mesh_lpn_established()) { + bt_mesh_lpn_poll(); + } + } + + return 0; +} + +struct bt_mesh_app_key *bt_mesh_app_key_find(u16_t app_idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) { + struct bt_mesh_app_key *key = &bt_mesh.app_keys[i]; + + if (key->net_idx != BLE_MESH_KEY_UNUSED && + key->app_idx == app_idx) { + return key; + } + } + + return NULL; +} + +int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct net_buf_simple *msg, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + const u8_t *key = NULL; + u8_t *ad, role; + int err; + + if (net_buf_simple_tailroom(msg) < 4) { + BT_ERR("%s, Insufficient tailroom for Transport MIC", __func__); + return -EINVAL; + } + + if (msg->len > 11) { + tx->ctx->send_rel = 1U; + } + + BT_DBG("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", tx->sub->net_idx, + tx->ctx->app_idx, tx->ctx->addr); + BT_DBG("len %u: %s", msg->len, bt_hex(msg->data, msg->len)); + + role = bt_mesh_get_model_role(tx->ctx->model, tx->ctx->srv_send); + if (role == ROLE_NVAL) { + BT_ERR("%s, Failed to get model role", __func__); + return -EINVAL; + } + + if (tx->ctx->app_idx == BLE_MESH_KEY_DEV) { +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (role == NODE) { + if (!bt_mesh_is_provisioner_en()) { + key = bt_mesh.dev_key; + } + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + key = provisioner_get_device_key(tx->ctx->addr); + } + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (role == NODE) { + key = bt_mesh.dev_key; + } else if (role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + key = provisioner_get_device_key(tx->ctx->addr); + } + } else if (role == FAST_PROV) { +#if CONFIG_BLE_MESH_FAST_PROV + key = get_fast_prov_device_key(tx->ctx->addr); +#endif + } +#endif + + if (!key) { + BT_ERR("%s, Failed to get Device Key", __func__); + return -EINVAL; + } + + tx->aid = 0U; + } else { + struct bt_mesh_app_key *app_key = NULL; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (role == NODE) { + if (!bt_mesh_is_provisioner_en()) { + app_key = bt_mesh_app_key_find(tx->ctx->app_idx); + } + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + app_key = provisioner_app_key_find(tx->ctx->app_idx); + } + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (role == NODE) { + app_key = bt_mesh_app_key_find(tx->ctx->app_idx); + } else if (role == PROVISIONER) { + if (bt_mesh_is_provisioner_en()) { + app_key = provisioner_app_key_find(tx->ctx->app_idx); + } + } else if (role == FAST_PROV) { +#if CONFIG_BLE_MESH_FAST_PROV + app_key = get_fast_prov_app_key(tx->ctx->net_idx, tx->ctx->app_idx); +#endif + } +#endif + + if (!app_key) { + BT_ERR("%s, Failed to get AppKey", __func__); + return -EINVAL; + } + + if (tx->sub->kr_phase == BLE_MESH_KR_PHASE_2 && + app_key->updated) { + key = app_key->keys[1].val; + tx->aid = app_key->keys[1].id; + } else { + key = app_key->keys[0].val; + tx->aid = app_key->keys[0].id; + } + } + + if (!tx->ctx->send_rel || net_buf_simple_tailroom(msg) < 8) { + tx->aszmic = 0U; + } else { + tx->aszmic = 1U; + } + + if (BLE_MESH_ADDR_IS_VIRTUAL(tx->ctx->addr)) { + ad = bt_mesh_label_uuid_get(tx->ctx->addr); + } else { + ad = NULL; + } + + err = bt_mesh_app_encrypt(key, tx->ctx->app_idx == BLE_MESH_KEY_DEV, + tx->aszmic, msg, ad, tx->src, + tx->ctx->addr, bt_mesh.seq, + BLE_MESH_NET_IVI_TX); + if (err) { + return err; + } + + if (tx->ctx->send_rel) { + err = send_seg(tx, msg, cb, cb_data); + } else { + err = send_unseg(tx, msg, cb, cb_data); + } + + return err; +} + +int bt_mesh_trans_resend(struct bt_mesh_net_tx *tx, struct net_buf_simple *msg, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + struct net_buf_simple_state state; + int err; + + net_buf_simple_save(msg, &state); + + if (tx->ctx->send_rel || msg->len > 15) { + err = send_seg(tx, msg, cb, cb_data); + } else { + err = send_unseg(tx, msg, cb, cb_data); + } + + net_buf_simple_restore(msg, &state); + + return err; +} + +static bool is_replay(struct bt_mesh_net_rx *rx) +{ + int i; + + /* Don't bother checking messages from ourselves */ + if (rx->net_if == BLE_MESH_NET_IF_LOCAL) { + return false; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) { + struct bt_mesh_rpl *rpl = &bt_mesh.rpl[i]; + + /* Empty slot */ + if (!rpl->src) { + rpl->src = rx->ctx.addr; + rpl->seq = rx->seq; + rpl->old_iv = rx->old_iv; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_rpl(rpl); + } + + return false; + } + + /* Existing slot for given address */ + if (rpl->src == rx->ctx.addr) { + if (rx->old_iv && !rpl->old_iv) { + return true; + } + +#if !CONFIG_BLE_MESH_PATCH_FOR_SLAB_APP_1_1_0 + if ((!rx->old_iv && rpl->old_iv) || + rpl->seq < rx->seq) { +#else /* CONFIG_BLE_MESH_PATCH_FOR_SLAB_APP_1_1_0 */ + /** + * Added 10 here to fix the bug of Silicon Lab Android App 1.1.0 when + * reconnection will cause its sequence number recounting from 0. + */ + if ((!rx->old_iv && rpl->old_iv) || + (rpl->seq < rx->seq) || (rpl->seq > rx->seq + 10)) { +#endif /* #if !CONFIG_BLE_MESH_PATCH_FOR_SLAB_APP_1_1_0 */ + rpl->seq = rx->seq; + rpl->old_iv = rx->old_iv; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_rpl(rpl); + } + + return false; + } else { + return true; + } + } + } + + BT_ERR("%s, RPL is full!", __func__); + return true; +} + +static int sdu_recv(struct bt_mesh_net_rx *rx, u32_t seq, u8_t hdr, + u8_t aszmic, struct net_buf_simple *buf) +{ + struct net_buf_simple *sdu = NULL; + u32_t array_size = 0; + u8_t *ad; + u16_t i; + int err; + + BT_DBG("ASZMIC %u AKF %u AID 0x%02x", aszmic, AKF(&hdr), AID(&hdr)); + BT_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (buf->len < 1 + APP_MIC_LEN(aszmic)) { + BT_ERR("%s, Too short SDU + MIC", __func__); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND) && !rx->local_match) { + BT_DBG("Ignoring PDU for LPN 0x%04x of this Friend", + rx->ctx.recv_dst); + return 0; + } + + if (BLE_MESH_ADDR_IS_VIRTUAL(rx->ctx.recv_dst)) { + ad = bt_mesh_label_uuid_get(rx->ctx.recv_dst); + } else { + ad = NULL; + } + + /* Adjust the length to not contain the MIC at the end */ + buf->len -= APP_MIC_LEN(aszmic); + + /* Use bt_mesh_alloc_buf() instead of NET_BUF_SIMPLE_DEFINE to avoid + * causing btu task stackoverflow. + */ + sdu = bt_mesh_alloc_buf(CONFIG_BLE_MESH_RX_SDU_MAX - 4); + if (!sdu) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + if (!AKF(&hdr)) { +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (!bt_mesh_is_provisioner_en()) { + array_size = 1; + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + array_size = 1; + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + array_size = 1; + if (bt_mesh_is_provisioner_en()) { + array_size += 1; + } +#endif + + for (i = 0; i < array_size; i++) { + const u8_t *dev_key = NULL; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (!bt_mesh_is_provisioner_en()) { + dev_key = bt_mesh.dev_key; + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + dev_key = provisioner_get_device_key(rx->ctx.addr); + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (i < 1) { + dev_key = bt_mesh.dev_key; + } else { + dev_key = provisioner_get_device_key(rx->ctx.addr); + } +#endif + + if (!dev_key) { + BT_DBG("%s, NULL Device Key", __func__); + continue; + } + + net_buf_simple_reset(sdu); + err = bt_mesh_app_decrypt(dev_key, true, aszmic, buf, + sdu, ad, rx->ctx.addr, + rx->ctx.recv_dst, seq, + BLE_MESH_NET_IVI_RX(rx)); + if (err) { + continue; + } + + rx->ctx.app_idx = BLE_MESH_KEY_DEV; + bt_mesh_model_recv(rx, sdu); + + bt_mesh_free_buf(sdu); + return 0; + } + + BT_WARN("%s, Unable to decrypt with DevKey", __func__); + bt_mesh_free_buf(sdu); + return -ENODEV; + } + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (!bt_mesh_is_provisioner_en()) { + array_size = ARRAY_SIZE(bt_mesh.app_keys); + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + array_size = ARRAY_SIZE(bt_mesh.p_app_keys); + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + array_size = ARRAY_SIZE(bt_mesh.app_keys); + if (bt_mesh_is_provisioner_en()) { + array_size += ARRAY_SIZE(bt_mesh.p_app_keys); + } +#endif + + for (i = 0; i < array_size; i++) { + struct bt_mesh_app_key *key = NULL; + struct bt_mesh_app_keys *keys = NULL; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (!bt_mesh_is_provisioner_en()) { + key = &bt_mesh.app_keys[i]; + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + key = bt_mesh.p_app_keys[i]; + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (i < ARRAY_SIZE(bt_mesh.app_keys)) { + key = &bt_mesh.app_keys[i]; + } else { + key = bt_mesh.p_app_keys[i - ARRAY_SIZE(bt_mesh.app_keys)]; + } +#endif + + if (!key) { + BT_DBG("%s, NULL AppKey", __func__); + continue; + } + + /* Make sure that this AppKey matches received net_idx */ + if (key->net_idx != rx->sub->net_idx) { + continue; + } + + if (rx->new_key && key->updated) { + keys = &key->keys[1]; + } else { + keys = &key->keys[0]; + } + + /* Check that the AppKey ID matches */ + if (AID(&hdr) != keys->id) { + continue; + } + + net_buf_simple_reset(sdu); + err = bt_mesh_app_decrypt(keys->val, false, aszmic, buf, + sdu, ad, rx->ctx.addr, + rx->ctx.recv_dst, seq, + BLE_MESH_NET_IVI_RX(rx)); + if (err) { + BT_DBG("Unable to decrypt with AppKey 0x%03x", + key->app_idx); + continue; + } + + rx->ctx.app_idx = key->app_idx; + bt_mesh_model_recv(rx, sdu); + + bt_mesh_free_buf(sdu); + return 0; + } + + BT_WARN("%s, No matching AppKey", __func__); + bt_mesh_free_buf(sdu); + return -EINVAL; +} + +static struct seg_tx *seg_tx_lookup(u16_t seq_zero, u8_t obo, u16_t addr) +{ + struct seg_tx *tx; + int i; + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) { + tx = &seg_tx[i]; + + if ((tx->seq_auth & 0x1fff) != seq_zero) { + continue; + } + + if (tx->dst == addr) { + return tx; + } + + /* If the expected remote address doesn't match, + * but the OBO flag is set and this is the first + * acknowledgement, assume it's a Friend that's + * responding and therefore accept the message. + */ + if (obo && tx->nack_count == tx->seg_n + 1) { + tx->dst = addr; + return tx; + } + } + + return NULL; +} + +static int trans_ack(struct bt_mesh_net_rx *rx, u8_t hdr, + struct net_buf_simple *buf, u64_t *seq_auth) +{ + struct seg_tx *tx; + unsigned int bit; + u32_t ack; + u16_t seq_zero; + u8_t obo; + + if (buf->len < 6) { + BT_ERR("%s, Too short ack message", __func__); + return -EINVAL; + } + + seq_zero = net_buf_simple_pull_be16(buf); + obo = seq_zero >> 15; + seq_zero = (seq_zero >> 2) & 0x1fff; + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND) && rx->friend_match) { + BT_DBG("Ack for LPN 0x%04x of this Friend", rx->ctx.recv_dst); + /* Best effort - we don't have enough info for true SeqAuth */ + *seq_auth = SEQ_AUTH(BLE_MESH_NET_IVI_RX(rx), seq_zero); + return 0; + } + + ack = net_buf_simple_pull_be32(buf); + + BT_DBG("OBO %u seq_zero 0x%04x ack 0x%08x", obo, seq_zero, ack); + + tx = seg_tx_lookup(seq_zero, obo, rx->ctx.addr); + if (!tx) { + BT_WARN("No matching TX context for ack"); + return -EINVAL; + } + + *seq_auth = tx->seq_auth; + + if (!ack) { + BT_WARN("SDU canceled"); + seg_tx_complete(tx, -ECANCELED); + return 0; + } + + if (find_msb_set(ack) - 1 > tx->seg_n) { + BT_ERR("%s, Too large segment number in ack", __func__); + return -EINVAL; + } + + k_delayed_work_cancel(&tx->retransmit); + + while ((bit = find_lsb_set(ack))) { + if (tx->seg[bit - 1]) { + BT_DBG("seg %u/%u acked", bit - 1, tx->seg_n); + net_buf_unref(tx->seg[bit - 1]); + tx->seg[bit - 1] = NULL; + tx->nack_count--; + } + + ack &= ~BIT(bit - 1); + } + + if (tx->nack_count) { + seg_tx_send_unacked(tx); + } else { + BT_DBG("SDU TX complete"); + seg_tx_complete(tx, 0); + } + + return 0; +} + +static int trans_heartbeat(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + u8_t init_ttl, hops; + u16_t feat; + + if (buf->len < 3) { + BT_ERR("%s, Too short heartbeat message", __func__); + return -EINVAL; + } + + if (rx->ctx.recv_dst != hb_sub_dst) { + BT_WARN("Ignoring heartbeat to non-subscribed destination"); + return 0; + } + + init_ttl = (net_buf_simple_pull_u8(buf) & 0x7f); + feat = net_buf_simple_pull_be16(buf); + + hops = (init_ttl - rx->ctx.recv_ttl + 1); + + BT_DBG("src 0x%04x TTL %u InitTTL %u (%u hop%s) feat 0x%04x", + rx->ctx.addr, rx->ctx.recv_ttl, init_ttl, hops, + (hops == 1U) ? "" : "s", feat); + + bt_mesh_heartbeat(rx->ctx.addr, rx->ctx.recv_dst, hops, feat); + + return 0; +} + +static int ctl_recv(struct bt_mesh_net_rx *rx, u8_t hdr, + struct net_buf_simple *buf, u64_t *seq_auth) +{ + u8_t ctl_op = TRANS_CTL_OP(&hdr); + + BT_DBG("OpCode 0x%02x len %u", ctl_op, buf->len); + + switch (ctl_op) { + case TRANS_CTL_OP_ACK: + return trans_ack(rx, hdr, buf, seq_auth); + case TRANS_CTL_OP_HEARTBEAT: + return trans_heartbeat(rx, buf); + } + + /* Only acks and heartbeats may need processing without local_match */ + if (!rx->local_match) { + return 0; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND) && !bt_mesh_lpn_established()) { + switch (ctl_op) { + case TRANS_CTL_OP_FRIEND_POLL: + return bt_mesh_friend_poll(rx, buf); + case TRANS_CTL_OP_FRIEND_REQ: + return bt_mesh_friend_req(rx, buf); + case TRANS_CTL_OP_FRIEND_CLEAR: + return bt_mesh_friend_clear(rx, buf); + case TRANS_CTL_OP_FRIEND_CLEAR_CFM: + return bt_mesh_friend_clear_cfm(rx, buf); + case TRANS_CTL_OP_FRIEND_SUB_ADD: + return bt_mesh_friend_sub_add(rx, buf); + case TRANS_CTL_OP_FRIEND_SUB_REM: + return bt_mesh_friend_sub_rem(rx, buf); + } + } + +#if defined(CONFIG_BLE_MESH_LOW_POWER) + if (ctl_op == TRANS_CTL_OP_FRIEND_OFFER) { + return bt_mesh_lpn_friend_offer(rx, buf); + } + + if (rx->ctx.addr == bt_mesh.lpn.frnd) { + if (ctl_op == TRANS_CTL_OP_FRIEND_CLEAR_CFM) { + return bt_mesh_lpn_friend_clear_cfm(rx, buf); + } + + if (!rx->friend_cred) { + BT_WARN("Message from friend with wrong credentials"); + return -EINVAL; + } + + switch (ctl_op) { + case TRANS_CTL_OP_FRIEND_UPDATE: + return bt_mesh_lpn_friend_update(rx, buf); + case TRANS_CTL_OP_FRIEND_SUB_CFM: + return bt_mesh_lpn_friend_sub_cfm(rx, buf); + } + } +#endif /* CONFIG_BLE_MESH_LOW_POWER */ + } + + BT_WARN("Unhandled TransOpCode 0x%02x", ctl_op); + + return -ENOENT; +} + +static int trans_unseg(struct net_buf_simple *buf, struct bt_mesh_net_rx *rx, + u64_t *seq_auth) +{ + u8_t hdr; + + BT_DBG("AFK %u AID 0x%02x", AKF(buf->data), AID(buf->data)); + + if (buf->len < 1) { + BT_ERR("%s, Too small unsegmented PDU", __func__); + return -EINVAL; + } + + if (rx->local_match && is_replay(rx)) { + BT_WARN("Replay: src 0x%04x dst 0x%04x seq 0x%06x", + rx->ctx.addr, rx->ctx.recv_dst, rx->seq); + return -EINVAL; + } + + hdr = net_buf_simple_pull_u8(buf); + + if (rx->ctl) { + return ctl_recv(rx, hdr, buf, seq_auth); + } else { + /* SDUs must match a local element or an LPN of this Friend. */ + if (!rx->local_match && !rx->friend_match) { + return 0; + } + + return sdu_recv(rx, rx->seq, hdr, 0, buf); + } +} + +static inline s32_t ack_timeout(struct seg_rx *rx) +{ + s32_t to; + u8_t ttl; + + if (rx->ttl == BLE_MESH_TTL_DEFAULT) { + ttl = bt_mesh_default_ttl_get(); + } else { + ttl = rx->ttl; + } + + /* The acknowledgment timer shall be set to a minimum of + * 150 + 50 * TTL milliseconds. + */ + to = K_MSEC(150 + (ttl * 50U)); + + /* 100 ms for every not yet received segment */ + to += K_MSEC(((rx->seg_n + 1) - popcount(rx->block)) * 100U); + + /* Make sure we don't send more frequently than the duration for + * each packet (default is 300ms). + */ + return MAX(to, K_MSEC(400)); +} + +int bt_mesh_ctl_send(struct bt_mesh_net_tx *tx, u8_t ctl_op, void *data, + size_t data_len, u64_t *seq_auth, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + struct net_buf *buf; + + BT_DBG("src 0x%04x dst 0x%04x ttl 0x%02x ctl 0x%02x", tx->src, + tx->ctx->addr, tx->ctx->send_ttl, ctl_op); + BT_DBG("len %u: %s", data_len, bt_hex(data, data_len)); + + buf = bt_mesh_adv_create(BLE_MESH_ADV_DATA, tx->xmit, BUF_TIMEOUT); + if (!buf) { + BT_ERR("%s, Out of transport buffers", __func__); + return -ENOBUFS; + } + + net_buf_reserve(buf, BLE_MESH_NET_HDR_LEN); + + net_buf_add_u8(buf, TRANS_CTL_HDR(ctl_op, 0)); + + net_buf_add_mem(buf, data, data_len); + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + if (bt_mesh_friend_enqueue_tx(tx, BLE_MESH_FRIEND_PDU_SINGLE, + seq_auth, &buf->b) && + BLE_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) { + /* PDUs for a specific Friend should only go + * out through the Friend Queue. + */ + net_buf_unref(buf); + return 0; + } + } + + return bt_mesh_net_send(tx, buf, cb, cb_data); +} + +static int send_ack(struct bt_mesh_subnet *sub, u16_t src, u16_t dst, + u8_t ttl, u64_t *seq_auth, u32_t block, u8_t obo) +{ + struct bt_mesh_msg_ctx ctx = { + .net_idx = sub->net_idx, + .app_idx = BLE_MESH_KEY_UNUSED, + .addr = dst, + .send_ttl = ttl, + }; + struct bt_mesh_net_tx tx = { + .sub = sub, + .ctx = &ctx, + .src = obo ? bt_mesh_primary_addr() : src, + .xmit = bt_mesh_net_transmit_get(), + }; + u16_t seq_zero = *seq_auth & 0x1fff; + u8_t buf[6]; + + BT_DBG("SeqZero 0x%04x Block 0x%08x OBO %u", seq_zero, block, obo); + + if (bt_mesh_lpn_established()) { + BT_WARN("Not sending ack when LPN is enabled"); + return 0; + } + + /* This can happen if the segmented message was destined for a group + * or virtual address. + */ + if (!BLE_MESH_ADDR_IS_UNICAST(src)) { + BT_WARN("Not sending ack for non-unicast address"); + return 0; + } + + sys_put_be16(((seq_zero << 2) & 0x7ffc) | (obo << 15), buf); + sys_put_be32(block, &buf[2]); + + return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_ACK, buf, sizeof(buf), + NULL, NULL, NULL); +} + +static void seg_rx_reset(struct seg_rx *rx, bool full_reset) +{ + BT_DBG("rx %p", rx); + + k_delayed_work_cancel(&rx->ack); + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND) && rx->obo && + rx->block != BLOCK_COMPLETE(rx->seg_n)) { + BT_WARN("Clearing incomplete buffers from Friend queue"); + bt_mesh_friend_clear_incomplete(rx->sub, rx->src, rx->dst, + &rx->seq_auth); + } + + rx->in_use = 0U; + + /* We don't always reset these values since we need to be able to + * send an ack if we receive a segment after we've already received + * the full SDU. + */ + if (full_reset) { + rx->seq_auth = 0U; + rx->sub = NULL; + rx->src = BLE_MESH_ADDR_UNASSIGNED; + rx->dst = BLE_MESH_ADDR_UNASSIGNED; + } +} + +static void seg_ack(struct k_work *work) +{ + struct seg_rx *rx = CONTAINER_OF(work, struct seg_rx, ack); + + BT_DBG("rx %p", rx); + + if (k_uptime_get_32() - rx->last > K_SECONDS(60)) { + BT_WARN("Incomplete timer expired"); + seg_rx_reset(rx, false); + return; + } + + send_ack(rx->sub, rx->dst, rx->src, rx->ttl, &rx->seq_auth, + rx->block, rx->obo); + + k_delayed_work_submit(&rx->ack, ack_timeout(rx)); +} + +static inline u8_t seg_len(bool ctl) +{ + if (ctl) { + return 8; + } else { + return 12; + } +} + +static inline bool sdu_len_is_ok(bool ctl, u8_t seg_n) +{ + return ((seg_n * seg_len(ctl) + 1) <= CONFIG_BLE_MESH_RX_SDU_MAX); +} + +static struct seg_rx *seg_rx_find(struct bt_mesh_net_rx *net_rx, + const u64_t *seq_auth) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) { + struct seg_rx *rx = &seg_rx[i]; + + if (rx->src != net_rx->ctx.addr || + rx->dst != net_rx->ctx.recv_dst) { + continue; + } + + /* Return newer RX context in addition to an exact match, so + * the calling function can properly discard an old SeqAuth. + * Note: in Zephyr v1.14.0, ">=" is used here which does not + * seem to be a right operation, hence we still use the original + * "==" here. + */ + if (rx->seq_auth == *seq_auth) { + return rx; + } + + if (rx->in_use) { + BT_WARN("Duplicate SDU from src 0x%04x", + net_rx->ctx.addr); + + /* Clear out the old context since the sender + * has apparently started sending a new SDU. + */ + seg_rx_reset(rx, true); + + /* Return non-match so caller can re-allocate */ + return NULL; + } + } + + return NULL; +} + +static bool seg_rx_is_valid(struct seg_rx *rx, struct bt_mesh_net_rx *net_rx, + const u8_t *hdr, u8_t seg_n) +{ + if (rx->hdr != *hdr || rx->seg_n != seg_n) { + BT_ERR("%s, Invalid segment for ongoing session", __func__); + return false; + } + + if (rx->src != net_rx->ctx.addr || rx->dst != net_rx->ctx.recv_dst) { + BT_ERR("%s, Invalid source or destination for segment", __func__); + return false; + } + + if (rx->ctl != net_rx->ctl) { + BT_ERR("%s, Inconsistent CTL in segment", __func__); + return false; + } + + return true; +} + +static struct seg_rx *seg_rx_alloc(struct bt_mesh_net_rx *net_rx, + const u8_t *hdr, const u64_t *seq_auth, + u8_t seg_n) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) { + struct seg_rx *rx = &seg_rx[i]; + + if (rx->in_use) { + continue; + } + + rx->in_use = 1U; + net_buf_simple_reset(&rx->buf); + rx->sub = net_rx->sub; + rx->ctl = net_rx->ctl; + rx->seq_auth = *seq_auth; + rx->seg_n = seg_n; + rx->hdr = *hdr; + rx->ttl = net_rx->ctx.send_ttl; + rx->src = net_rx->ctx.addr; + rx->dst = net_rx->ctx.recv_dst; + rx->block = 0U; + + BT_DBG("New RX context. Block Complete 0x%08x", + BLOCK_COMPLETE(seg_n)); + + return rx; + } + + return NULL; +} + +static int trans_seg(struct net_buf_simple *buf, struct bt_mesh_net_rx *net_rx, + enum bt_mesh_friend_pdu_type *pdu_type, u64_t *seq_auth) +{ + struct seg_rx *rx; + u8_t *hdr = buf->data; + u16_t seq_zero; + u8_t seg_n; + u8_t seg_o; + int err; + + if (buf->len < 5) { + BT_ERR("%s, Too short segmented message (len %u)", __func__, buf->len); + return -EINVAL; + } + + BT_DBG("ASZMIC %u AKF %u AID 0x%02x", ASZMIC(hdr), AKF(hdr), AID(hdr)); + + net_buf_simple_pull(buf, 1); + + seq_zero = net_buf_simple_pull_be16(buf); + seg_o = (seq_zero & 0x03) << 3; + seq_zero = (seq_zero >> 2) & 0x1fff; + seg_n = net_buf_simple_pull_u8(buf); + seg_o |= seg_n >> 5; + seg_n &= 0x1f; + + BT_DBG("SeqZero 0x%04x SegO %u SegN %u", seq_zero, seg_o, seg_n); + + if (seg_o > seg_n) { + BT_ERR("%s, SegO greater than SegN (%u > %u)", __func__, seg_o, seg_n); + return -EINVAL; + } + + /* According to Mesh 1.0 specification: + * "The SeqAuth is composed of the IV Index and the sequence number + * (SEQ) of the first segment" + * + * Therefore we need to calculate very first SEQ in order to find + * seqAuth. We can calculate as below: + * + * SEQ(0) = SEQ(n) - (delta between seqZero and SEQ(n) by looking into + * 14 least significant bits of SEQ(n)) + * + * Mentioned delta shall be >= 0, if it is not then seq_auth will + * be broken and it will be verified by the code below. + */ + *seq_auth = SEQ_AUTH(BLE_MESH_NET_IVI_RX(net_rx), + (net_rx->seq - + ((((net_rx->seq & BIT_MASK(14)) - seq_zero)) & + BIT_MASK(13)))); + + /* Look for old RX sessions */ + rx = seg_rx_find(net_rx, seq_auth); + if (rx) { + /* Discard old SeqAuth packet */ + if (rx->seq_auth > *seq_auth) { + BT_WARN("Ignoring old SeqAuth"); + return -EINVAL; + } + + if (!seg_rx_is_valid(rx, net_rx, hdr, seg_n)) { + return -EINVAL; + } + + if (rx->in_use) { + BT_DBG("Existing RX context. Block 0x%08x", rx->block); + goto found_rx; + } + + if (rx->block == BLOCK_COMPLETE(rx->seg_n)) { + BT_WARN("Got segment for already complete SDU"); + send_ack(net_rx->sub, net_rx->ctx.recv_dst, + net_rx->ctx.addr, net_rx->ctx.send_ttl, + seq_auth, rx->block, rx->obo); + return -EALREADY; + } + + /* We ignore instead of sending block ack 0 since the + * ack timer is always smaller than the incomplete + * timer, i.e. the sender is misbehaving. + */ + BT_WARN("Got segment for canceled SDU"); + return -EINVAL; + } + + /* Bail out early if we're not ready to receive such a large SDU */ + if (!sdu_len_is_ok(net_rx->ctl, seg_n)) { + BT_ERR("%s, Too big incoming SDU length", __func__); + send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr, + net_rx->ctx.send_ttl, seq_auth, 0, + net_rx->friend_match); + return -EMSGSIZE; + } + + /* Look for free slot for a new RX session */ + rx = seg_rx_alloc(net_rx, hdr, seq_auth, seg_n); + if (!rx) { + /* Warn but don't cancel since the existing slots willl + * eventually be freed up and we'll be able to process + * this one. + */ + BT_WARN("No free slots for new incoming segmented messages"); + return -ENOMEM; + } + + rx->obo = net_rx->friend_match; + +found_rx: + if (BIT(seg_o) & rx->block) { + BT_WARN("Received already received fragment"); + return -EALREADY; + } + + /* All segments, except the last one, must either have 8 bytes of + * payload (for 64bit Net MIC) or 12 bytes of payload (for 32bit + * Net MIC). + */ + if (seg_o == seg_n) { + /* Set the expected final buffer length */ + rx->buf.len = seg_n * seg_len(rx->ctl) + buf->len; + BT_DBG("Target len %u * %u + %u = %u", seg_n, seg_len(rx->ctl), + buf->len, rx->buf.len); + + if (rx->buf.len > CONFIG_BLE_MESH_RX_SDU_MAX) { + BT_ERR("Too large SDU len"); + send_ack(net_rx->sub, net_rx->ctx.recv_dst, + net_rx->ctx.addr, net_rx->ctx.send_ttl, + seq_auth, 0, rx->obo); + seg_rx_reset(rx, true); + return -EMSGSIZE; + } + } else { + if (buf->len != seg_len(rx->ctl)) { + BT_ERR("%s, Incorrect segment size for message type", __func__); + return -EINVAL; + } + } + + /* Reset the Incomplete Timer */ + rx->last = k_uptime_get_32(); + + if (!k_delayed_work_remaining_get(&rx->ack) && + !bt_mesh_lpn_established()) { + k_delayed_work_submit(&rx->ack, ack_timeout(rx)); + } + + /* Location in buffer can be calculated based on seg_o & rx->ctl */ + memcpy(rx->buf.data + (seg_o * seg_len(rx->ctl)), buf->data, buf->len); + + BT_DBG("Received %u/%u", seg_o, seg_n); + + /* Mark segment as received */ + rx->block |= BIT(seg_o); + + if (rx->block != BLOCK_COMPLETE(seg_n)) { + *pdu_type = BLE_MESH_FRIEND_PDU_PARTIAL; + return 0; + } + + BT_DBG("Complete SDU"); + + if (net_rx->local_match && is_replay(net_rx)) { + BT_WARN("Replay: src 0x%04x dst 0x%04x seq 0x%06x", + net_rx->ctx.addr, net_rx->ctx.recv_dst, net_rx->seq); + /* Clear the segment's bit */ + rx->block &= ~BIT(seg_o); + return -EINVAL; + } + + *pdu_type = BLE_MESH_FRIEND_PDU_COMPLETE; + + k_delayed_work_cancel(&rx->ack); + send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr, + net_rx->ctx.send_ttl, seq_auth, rx->block, rx->obo); + + if (net_rx->ctl) { + err = ctl_recv(net_rx, *hdr, &rx->buf, seq_auth); + } else { + err = sdu_recv(net_rx, (rx->seq_auth & 0xffffff), *hdr, + ASZMIC(hdr), &rx->buf); + } + + seg_rx_reset(rx, false); + + return err; +} + +int bt_mesh_trans_recv(struct net_buf_simple *buf, struct bt_mesh_net_rx *rx) +{ + u64_t seq_auth = TRANS_SEQ_AUTH_NVAL; + enum bt_mesh_friend_pdu_type pdu_type = BLE_MESH_FRIEND_PDU_SINGLE; + struct net_buf_simple_state state; + int err; + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + rx->friend_match = bt_mesh_friend_match(rx->sub->net_idx, + rx->ctx.recv_dst); + } else { + rx->friend_match = false; + } + + BT_DBG("src 0x%04x dst 0x%04x seq 0x%08x friend_match %u", + rx->ctx.addr, rx->ctx.recv_dst, rx->seq, rx->friend_match); + + /* Remove network headers */ + net_buf_simple_pull(buf, BLE_MESH_NET_HDR_LEN); + + BT_DBG("Payload %s", bt_hex(buf->data, buf->len)); + + /* If LPN mode is enabled messages are only accepted when we've + * requested the Friend to send them. The messages must also + * be encrypted using the Friend Credentials. + */ + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER) && + bt_mesh_lpn_established() && rx->net_if == BLE_MESH_NET_IF_ADV && + (!bt_mesh_lpn_waiting_update() || !rx->friend_cred)) { + BT_WARN("Ignoring unexpected message in Low Power mode"); + return -EAGAIN; + } + } + + /* Save the app-level state so the buffer can later be placed in + * the Friend Queue. + */ + net_buf_simple_save(buf, &state); + + if (SEG(buf->data)) { + /* Segmented messages must match a local element or an + * LPN of this Friend. + */ + if (!rx->local_match && !rx->friend_match) { + return 0; + } + + err = trans_seg(buf, rx, &pdu_type, &seq_auth); + } else { + err = trans_unseg(buf, rx, &seq_auth); + } + + /* Notify LPN state machine so a Friend Poll will be sent. If the + * message was a Friend Update it's possible that a Poll was already + * queued for sending, however that's fine since then the + * bt_mesh_lpn_waiting_update() function will return false: + * we still need to go through the actual sending to the bearer and + * wait for ReceiveDelay before transitioning to WAIT_UPDATE state. + * Another situation where we want to notify the LPN state machine + * is if it's configured to use an automatic Friendship establishment + * timer, in which case we want to reset the timer at this point. + * + */ + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER) && + (bt_mesh_lpn_timer() || + (bt_mesh_lpn_established() && bt_mesh_lpn_waiting_update()))) { + bt_mesh_lpn_msg_received(rx); + } + } + + net_buf_simple_restore(buf, &state); + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND) && rx->friend_match && !err) { + if (seq_auth == TRANS_SEQ_AUTH_NVAL) { + bt_mesh_friend_enqueue_rx(rx, pdu_type, NULL, buf); + } else { + bt_mesh_friend_enqueue_rx(rx, pdu_type, &seq_auth, buf); + } + } + } + + return err; +} + +void bt_mesh_rx_reset(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) { + seg_rx_reset(&seg_rx[i], true); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_rpl(); + } else { + (void)memset(bt_mesh.rpl, 0, sizeof(bt_mesh.rpl)); + } +} + +void bt_mesh_tx_reset(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) { + seg_tx_reset(&seg_tx[i]); + } +} + +void bt_mesh_trans_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) { + k_delayed_work_init(&seg_tx[i].retransmit, seg_retransmit); + } + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) { + k_delayed_work_init(&seg_rx[i].ack, seg_ack); + seg_rx[i].buf.__buf = (seg_rx_buf_data + + (i * CONFIG_BLE_MESH_RX_SDU_MAX)); + seg_rx[i].buf.data = seg_rx[i].buf.__buf; + } +} + +void bt_mesh_rpl_clear(void) +{ + BT_DBG("%s", __func__); + (void)memset(bt_mesh.rpl, 0, sizeof(bt_mesh.rpl)); +} diff --git a/components/bt/ble_mesh/mesh_core/transport.h b/components/bt/ble_mesh/mesh_core/transport.h new file mode 100644 index 0000000000..13845f345c --- /dev/null +++ b/components/bt/ble_mesh/mesh_core/transport.h @@ -0,0 +1,102 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _TRANSPORT_H_ +#define _TRANSPORT_H_ + +#define TRANS_SEQ_AUTH_NVAL 0xffffffffffffffff + +#define BLE_MESH_TX_SDU_MAX (CONFIG_BLE_MESH_TX_SEG_MAX * 12) + +#define TRANS_CTL_OP_MASK ((u8_t)BIT_MASK(7)) +#define TRANS_CTL_OP(data) ((data)[0] & TRANS_CTL_OP_MASK) +#define TRANS_CTL_HDR(op, seg) ((op & TRANS_CTL_OP_MASK) | (seg << 7)) + +#define TRANS_CTL_OP_ACK 0x00 +#define TRANS_CTL_OP_FRIEND_POLL 0x01 +#define TRANS_CTL_OP_FRIEND_UPDATE 0x02 +#define TRANS_CTL_OP_FRIEND_REQ 0x03 +#define TRANS_CTL_OP_FRIEND_OFFER 0x04 +#define TRANS_CTL_OP_FRIEND_CLEAR 0x05 +#define TRANS_CTL_OP_FRIEND_CLEAR_CFM 0x06 +#define TRANS_CTL_OP_FRIEND_SUB_ADD 0x07 +#define TRANS_CTL_OP_FRIEND_SUB_REM 0x08 +#define TRANS_CTL_OP_FRIEND_SUB_CFM 0x09 +#define TRANS_CTL_OP_HEARTBEAT 0x0a + +struct bt_mesh_ctl_friend_poll { + u8_t fsn; +} __packed; + +struct bt_mesh_ctl_friend_update { + u8_t flags; + u32_t iv_index; + u8_t md; +} __packed; + +struct bt_mesh_ctl_friend_req { + u8_t criteria; + u8_t recv_delay; + u8_t poll_to[3]; + u16_t prev_addr; + u8_t num_elem; + u16_t lpn_counter; +} __packed; + +struct bt_mesh_ctl_friend_offer { + u8_t recv_win; + u8_t queue_size; + u8_t sub_list_size; + s8_t rssi; + u16_t frnd_counter; +} __packed; + +struct bt_mesh_ctl_friend_clear { + u16_t lpn_addr; + u16_t lpn_counter; +} __packed; + +struct bt_mesh_ctl_friend_clear_confirm { + u16_t lpn_addr; + u16_t lpn_counter; +} __packed; + +#define BLE_MESH_FRIEND_SUB_MIN_LEN (1 + 2) +struct bt_mesh_ctl_friend_sub { + u8_t xact; + u16_t addr_list[5]; +} __packed; + +struct bt_mesh_ctl_friend_sub_confirm { + u8_t xact; +} __packed; + +void bt_mesh_set_hb_sub_dst(u16_t addr); + +struct bt_mesh_app_key *bt_mesh_app_key_find(u16_t app_idx); + +bool bt_mesh_tx_in_progress(void); + +void bt_mesh_rx_reset(void); +void bt_mesh_tx_reset(void); + +int bt_mesh_ctl_send(struct bt_mesh_net_tx *tx, u8_t ctl_op, void *data, + size_t data_len, u64_t *seq_auth, + const struct bt_mesh_send_cb *cb, void *cb_data); + +int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct net_buf_simple *msg, + const struct bt_mesh_send_cb *cb, void *cb_data); + +int bt_mesh_trans_recv(struct net_buf_simple *buf, struct bt_mesh_net_rx *rx); + +void bt_mesh_trans_init(void); + +void bt_mesh_rpl_clear(void); + +#endif /* _TRANSPORT_H_ */ diff --git a/components/bt/ble_mesh/mesh_docs/BLE-Mesh_FAQs_EN.md b/components/bt/ble_mesh/mesh_docs/BLE-Mesh_FAQs_EN.md new file mode 100644 index 0000000000..3e3ba69684 --- /dev/null +++ b/components/bt/ble_mesh/mesh_docs/BLE-Mesh_FAQs_EN.md @@ -0,0 +1,9 @@ +# Frequently Asked Questions + +## General Questions + +### Why I do not get a reply from the remote device when I perform get operation immediately after set operation has been performed? +* Any Client Model operation needs to wait for the completion event of an ongoing operation. Once the completion event is received the next command can be executed. If a command is executed before the completion event is received, a timeout error will occur. + +### When I use the API `esp_ble_mesh_client_model_send_msg`, why does it crash with the log messages *Invalid client value when sent client msg* or *Invalid client value when sent client msg*? +* You should initialize a structure of the type `esp_ble_mesh_client_t` and set its value as the user data of client model. diff --git a/components/bt/ble_mesh/mesh_docs/BLE-Mesh_Feature_List_EN.md b/components/bt/ble_mesh/mesh_docs/BLE-Mesh_Feature_List_EN.md new file mode 100644 index 0000000000..cf54f7985e --- /dev/null +++ b/components/bt/ble_mesh/mesh_docs/BLE-Mesh_Feature_List_EN.md @@ -0,0 +1,89 @@ +# Espressif BLE Mesh Feature List + +## Currently Supported Features + +### Mesh Core + +* Provisioning: Node Role + * Advertising and GATT bearer + * Authentication OOB + +* Provisioning: Provisioner Role + * Advertising and GATT bearer + * Authentication OOB + +* Networking + * Relay + * Segmentation and Reassembly + * Key Refresh + * IV Update + +* Proxy Support + +* Multiple Client Models Run Simultaneously + * Support multiple client models send packets to different nodes simultaneously + * No blocking between client model and server + +* NVS Storing + * Store Provisioning Data of The Node Device + +### Mesh Applications + +* Fast Provisioning + * Fast Provisioning Server Model + * Fast Provisioning Client Model + * Example & Demo Video + +* Wi-Fi & BLE Mesh Coexistence + * Example & Demo Video(coming soon) + +* Mesh Console Commands + * Example + + +### Mesh Models + +* Foundation Models + * Configuration Server Model + * Configuration Client Model + * Health Server Model + * Health Client Model + +* Generic Client Models + * Generic OnOff Client + * Generic Level Client + * Generic Location Client + * Generic Default Transition Timer Client + * Generic Power OnOff Client + * Generic Power Level Client + * Generic Battery Client + * Generic Property Client + +* Generic Server Models + * Generic OnOff Server (Simple) + +* Lighting Client Models + * Light Lightness Client + * Light CTL Client + * Light HSL Client + +* Sensor Client Models + * Sensor Client + +* Time and Scenes Client Models + * Scene Client + + +## Future Release Features + +### Mesh Core + +* BLE Mesh BQB Certification +* Friend Feature +* Low Power Node Feature + +### Mesh Applications + +* Fast OTA + +### Mesh Models diff --git a/components/bt/ble_mesh/mesh_docs/BLE-Mesh_Getting_Started_EN.md b/components/bt/ble_mesh/mesh_docs/BLE-Mesh_Getting_Started_EN.md new file mode 100644 index 0000000000..5ac15427a7 --- /dev/null +++ b/components/bt/ble_mesh/mesh_docs/BLE-Mesh_Getting_Started_EN.md @@ -0,0 +1,155 @@ +# Introduction + +Bluetooth mesh networking enables many-to-many (m:m) device communications and is optimized for creating large-scale device networks. + +Devices may relay data to other devices not in direct radio range of the originating device. In this way, mesh networks can span very large physical areas and contain large numbers of devices. It is ideally suited for building automation, sensor networks, and other IoT solutions where tens, hundreds, or thousands of devices need to reliably and securely communicate with one another. + +Bluetooth mesh is not a wireless communications technology, but a networking technology. This technology is dependent upon Bluetooth Low Energy (BLE) - a wireless communications protocol stack. + + +# Specifications + +The official specifications for Bluetooth mesh can be found [here](https://www.bluetooth.com/specifications/mesh-specifications) + + +# Getting Started with BLE Mesh on ESP32 + +If you are new to ESP32, you may first need to go through the [Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html). + +Built on top of Zephyr BLE Mesh stack, the ESP BLE Mesh implementation supports device provisioning and node control. It also supports such node features as Proxy, Relay, Low power and Friend. + + +## Access to ESP BLE Mesh +Since you are on this page, you should already have access to Espressif BLE Mesh SDK. If you do not have access, please get in touch with your point of contact. + +## Documentation + +The ESP BLE Mesh code in the SDK is organized as below. Each folder contains source files related to it as well as a subfolder with header files for the exposed functionality. + +``` +$tree components/bt/ble_mesh/ + +├── api /* BLE Mesh functionality exposed through esp_ble_mesh_* APIs for the applications */ +│   ├── core /* BLE Mesh Core APIs */ +│   │   └── include +│   └── models /* Foundation Models and other Client Models APIs */ +│   └── include +├── btc +│   └── include +├── mesh_core /* BLE mesh core based on Zephyr BLE stack with miscellaneous modifications and +│ │ an adaptation layer to make it work with ESP32 */ +│   └── include +├── mesh_docs /* BLE Mesh docs */ +└── mesh_models /* Foundation Models and other Client Models implementations */ + └── include +``` + +To demonstrate the features supported by BLE Mesh SDK, a few sample examples have been added. Each example has a README.md file for quick start as well as a walkthrough file that explains the functionality in detail. + +Below is a snapshot of the BLE Mesh examples directory + +``` +$ tree examples/bluetooth/ble_mesh/ +├── ble_mesh_client_model +│   ├── main +│   │   ├── ble_mesh_client_model_main.c +│   │   ├── board.c +│   │   ├── board.h +│   │   ├── component.mk +│   │   └── Kconfig.projbuild +│   ├── Makefile +│   ├── README.md +│   └── sdkconfig.defaults +├── ble_mesh_node +│   ├── main +│   │   ├── ble_mesh_demo_main.c +│   │   ├── board.c +│   │   ├── board.h +│   │   ├── component.mk +│   │   └── Kconfig.projbuild +│   ├── Makefile +│   ├── README.md +│   ├── sdkconfig.defaults +│   └── tutorial +│   └── Ble_Mesh_Node_Example_Walkthrough.md +├── ble_mesh_provisioner +│ ├── main +│ │   ├── ble_mesh_demo_main.c +│ │   ├── board.c +│ │   ├── board.h +│ │   ├── component.mk +│ │   └── Kconfig.projbuild +│ ├── Makefile +│ ├── README.md +│ ├── sdkconfig.defaults +│ └── tutorial +│ └── Ble_Mesh_Provisioner_Example_Walkthrough.md +├──ble_mesh_console +│ ├── ble_mesh_node +│ └── ble_mesh_provisioner +├──ble_mesh_fast_provision +│ ├── ble_mesh_fast_prov_client +│ └── ble_mesh_fast_prov_server +├──ble_mesh_vendor_models +│ ├── fast_prov_vendor_model +└──ble_mesh_wifi_coexist + ├── main + ├── components + └── tutorial + └── ble_mesh_wifi_coexist.md +8 directories, 26 files +``` + + +## Hardware and Setup + +At present ESP32-DevKitC and ESP-WROVER-KIT are supported for BLE Mesh implementation. You can find the details about the modules [here](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/modules-and-boards.html) + +You can choose the board through menuconfig: `make menuconfig -> Example Configuration -> Board selection for BLE Mesh` + +Note that if you plan to use ESP32-DevKitC, you need to connect an RGB LED to GPIO pins 25, 26 and 27. + + +## Sample Examples + +* BLE Mesh Node + +This example shows the use of BLE Mesh as a node device having a Configuration Server model and a Generic OnOff Server model. A BLE Mesh provisioner can then provision the node and control a RGB LED representing on/off state. + +* BLE Mesh Client Model + +This example shows how a Generic OnOff Client model within a node works. The node has a Configuration Server model, a Generic OnOff Server model and a Generic OnOff Client model. + +* BLE Mesh Provisioner + +This example shows how a device can act as a BLE Mesh provisioner to provision devices. The provisioner has a Configuration Server model, a Configuration Client model and a Generic OnOff Client model. + + +## Mobile Apps + +ESP BLE Mesh implementation is compatible with a few phone apps, including Silicon Labs BLE Mesh and nRF Mesh. These apps are available on Google Play and App Store. In addition, Espressif offers its own Android app which is currently being actively developed. You can find the latest APK file [here](http://download.espressif.com/BLE_MESH/BLE_Mesh_Tools/BLE_Mesh_App/EspBleMesh-0.9.4.apk). + +Note: The most recent tested version 1.1.0 of Silicon Labs App has a bug, which has been fixed by a workaround on the SDK side. The fix is implemented through a configuration option enabled by default. For other Android/iOS apps, this option needs to be disabled from menuconfig: +`make menuconfig -> Example Configuration -> This option fixes the bug of Silicon Lab Android App 1.1.0 when reconnection will cause the sequence number to recount from 0` + +## Building and Flashing + +If you build the application for the first time, the menuconfig screen will pop up. You can choose the board from the Example Configuration option. Additionally, you can modify the serial settings in the Serial flasher config option in accordance with your port configuration. + +BLE Mesh specific configuration options can also be modified through: `make menuconfig -> Component config -> Bluetooth Mesh support` + +You can still change options at any other time using `make menuconfig`. + +``` +$ export IDF_PATH=/path/to/esp-ble-mesh-sdk-v0.x + +$ cd examples/bluetooth/ble_mesh/ + +$ make -j8 flash monitor +``` + + +# Reporting Issues + +* If you find a bug or have a feature request, go to [the Issues section on GitHub](https://github.com/espressif/esp-idf/issues). Before reporting a new issue, please check the existing issues at the provided link and the FAQs document in the `mesh_docs` folder. +* When you submit an issue or a feature request on GitHub, please add the tag "BLE Mesh" in the issue title for our faster reaction. diff --git a/components/bt/ble_mesh/mesh_docs/BLE-Mesh_Known_Issues_EN.md b/components/bt/ble_mesh/mesh_docs/BLE-Mesh_Known_Issues_EN.md new file mode 100644 index 0000000000..034d2388d3 --- /dev/null +++ b/components/bt/ble_mesh/mesh_docs/BLE-Mesh_Known_Issues_EN.md @@ -0,0 +1 @@ +To be added. diff --git a/components/bt/ble_mesh/mesh_docs/README.md b/components/bt/ble_mesh/mesh_docs/README.md new file mode 100644 index 0000000000..65775ebf98 --- /dev/null +++ b/components/bt/ble_mesh/mesh_docs/README.md @@ -0,0 +1,50 @@ +# ESP BLE Mesh Framework + +This folder contains all the documents of ESP BLE Mesh. +* Note: breaking changes might be introduced into ESP BLE Mesh on [minor IDF versions](https://docs.espressif.com/projects/esp-idf/en/latest/versions.html) + + +## Demos + +* [Provisioning of BLE Mesh nodes using Smartphone App](http://download.espressif.com/BLE_MESH/Docs4Customers/esp-ble-mesh-demo.mp4) +* [Espressif Fast Provisioning using ESP BLE Mesh App](http://download.espressif.com/BLE_MESH/BLE_Mesh_Demo/V0.4_Demo_Fast_Provision/ESP32_BLE_Mesh_Fast_Provision.mp4) +* [Espressif BLE Mesh and Wi-Fi Coexistence](http://download.espressif.com/BLE_MESH/BLE_Mesh_Demo/V0.5_Demo_Coexistence/ESP_BLE_MESH_%26_WIFI_Coexistence.mp4) + +## Examples + +* [BLE Mesh Node Example Code](../../../../examples/bluetooth/ble_mesh/ble_mesh_node) +* [BLE_Mesh_Node_Example_Walkthrough](../../../../examples/bluetooth/ble_mesh/ble_mesh_node/tutorial/Ble_Mesh_Node_Example_Walkthrough.md) +* [BLE Mesh Provisioner Example Code](../../../../examples/bluetooth/ble_mesh/ble_mesh_provisioner) +* [BLE_Mesh_Provisioner_Example_Walkthrough](../../../../examples/bluetooth/ble_mesh/ble_mesh_provisioner/tutorial/Ble_Mesh_Provisioner_Example_Walkthrough.md) +* [BLE Mesh Client Model Example Code](../../../../examples/bluetooth/ble_mesh/ble_mesh_client_model) +* [BLE_Mesh_Client_Model_Example_Walkthrough](../../../../examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/ble_mesh_client_model.md) +* [BLE Mesh Console Example Code](../../../../examples/bluetooth/ble_mesh/ble_mesh_console) +* [BLE Mesh Fast Prov Client Example Code](../../../../examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client) +* [BLE_Mesh_Fast_Prov_Client_Example_Walkthrough](../../../../examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/tutorial/ble_mesh_fast_provision_client.md) +* [BLE Mesh Fast Prov Server Example Code](../../../../examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server) +* [BLE_Mesh_Fast_Prov_Server_Example_Walkthrough](../../../../examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/ble_mesh_fast_provision_server.md) +* [BLE Mesh Wifi Coexist Example Code](../../../../examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist) +* [BLE_Mesh_Wifi_Coexist_Example_Walkthrough](../../../../examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/tutorial%20%20%20%20%20%20/ble_mesh_wifi_coexist.md) +## Documentation + +### ESP BLE Mesh Development Documentation + +* [Getting started with ESP BLE Mesh](BLE-Mesh_Getting_Started_EN.md) +* [ESP BLE Mesh Feature List](BLE-Mesh_Feature_List_EN.md) +* [FAQs](BLE-Mesh_FAQs_EN.md) +* [Known Issues](BLE-Mesh_Known_Issues_EN.md) + +### BLE Mesh Protocol Documentation + +* [BLE Mesh Core Specification](https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=429633) +* [BLE Mesh Model Specification](https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=429634) +* [An Intro to Bluetooth Mesh Part 1](http://blog.bluetooth.com/an-intro-to-bluetooth-mesh-part1) +* [An Intro to Bluetooth Mesh Part 2](http://blog.bluetooth.com/an-intro-to-bluetooth-mesh-part2) +* [The Fundamental Concepts of Bluetooth Mesh Networking Part 1](http://blog.bluetooth.com/the-fundamental-concepts-of-bluetooth-mesh-networking-part-1) +* [The Fundamental Concepts of Bluetooth Mesh Networking, Part 2](http://blog.bluetooth.com/the-fundamental-concepts-of-bluetooth-mesh-networking-part-2) +* [Bluetooth Mesh Networking: Friendship](http://blog.bluetooth.com/bluetooth-mesh-networking-series-friendship) +* [Management of Devices in a Bluetooth Mesh Network](http://blog.bluetooth.com/management-of-devices-bluetooth-mesh-network) +* [Bluetooth Mesh Security Overview](http://blog.bluetooth.com/bluetooth-mesh-security-overview) +* [Provisioning a Bluetooth Mesh Network Part 1](http://blog.bluetooth.com/provisioning-a-bluetooth-mesh-network-part-1) +* [Provisioning a Bluetooth Mesh Network Part 2](http://blog.bluetooth.com/provisioning-a-bluetooth-mesh-network-part-2) + diff --git a/components/bt/ble_mesh/mesh_models/generic_client.c b/components/bt/ble_mesh/mesh_models/generic_client.c new file mode 100644 index 0000000000..7aeb5d00d3 --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/generic_client.c @@ -0,0 +1,1225 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "osi/allocator.h" +#include "sdkconfig.h" + +#include "mesh_types.h" +#include "mesh_kernel.h" +#include "mesh_trace.h" + +#include "mesh.h" +#include "model_opcode.h" +#include "mesh_common.h" +#include "generic_client.h" + +#include "btc_ble_mesh_generic_model.h" + +/** The following are the macro definitions of generic client + * model messages length, and a message is composed of three + * parts: Opcode + msg_value + MIC + */ +/* Generic onoff client messages length */ +#define BLE_MESH_GEN_ONOFF_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_ONOFF_SET_MSG_LEN (2 + 4 + 4) + +/* Generic level client messages length */ +#define BLE_MESH_GEN_LEVEL_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_LEVEL_SET_MSG_LEN (2 + 5 + 4) +#define BLE_MESH_GEN_DELTA_SET_MSG_LEN (2 + 7 + 4) +#define BLE_MESH_GEN_MOVE_SET_MSG_LEN (2 + 5 + 4) + +/* Generic default transition time client messages length */ +#define BLE_MESH_GEN_DEF_TRANS_TIME_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_DEF_TRANS_TIME_SET_MSG_LEN (2 + 1 + 4) + +/* Generic power onoff client messages length */ +#define BLE_MESH_GEN_ONPOWERUP_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_ONPOWERUP_SET_MSG_LEN (2 + 1 + 4) + +/* Generic power level client messages length */ +#define BLE_MESH_GEN_POWER_LEVEL_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_POWER_LEVEL_SET_MSG_LEN (2 + 5 + 4) +#define BLE_MESH_GEN_POWER_LAST_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_POWER_DEFAULT_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_POWER_DEFAULT_SET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_GEN_POWER_RANGE_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_POWER_RANGE_SET_MSG_LEN (2 + 4 + 4) + +/* Generic battery client messages length */ +#define BLE_MESH_GEN_BATTERY_GET_MSG_LEN (2 + 0 + 4) + +/* Generic location client messages length */ +#define BLE_MESH_GEN_LOC_GLOBAL_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_LOC_GLOBAL_SET_MSG_LEN (1 + 10 + 4) +#define BLE_MESH_GEN_LOC_LOCAL_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_LOC_LOCAL_SET_MSG_LEN (2 + 9 + 4) + +/* Generic property client messages length */ +#define BLE_MESH_GEN_USER_PROPERTIES_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_USER_PROPERTY_GET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_GEN_USER_PROPERTY_SET_MSG_LEN /* variable */ +#define BLE_MESH_GEN_ADMIN_PROPERTIES_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_ADMIN_PROPERTY_GET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_GEN_ADMIN_PROPERTY_SET_MSG_LEN /* variable */ +#define BLE_MESH_GEN_MANU_PROPERTIES_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_GEN_MANU_PROPERTY_GET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_GEN_MANU_PROPERTY_SET_MSG_LEN (1 + 3 + 4) +#define BLE_MESH_GEN_CLINET_PROPERTIES_GET_MSG_LEN (1 + 2 + 4) + +#define BLE_MESH_GEN_GET_STATE_MSG_LEN (2 + 2 + 4) + +static const bt_mesh_client_op_pair_t gen_op_pair[] = { + { BLE_MESH_MODEL_OP_GEN_ONOFF_GET, BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS }, + { BLE_MESH_MODEL_OP_GEN_ONOFF_SET, BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS }, + { BLE_MESH_MODEL_OP_GEN_LEVEL_GET, BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_LEVEL_SET, BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_DELTA_SET, BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_MOVE_SET, BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_GET, BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS }, + { BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET, BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS }, + { BLE_MESH_MODEL_OP_GEN_ONPOWERUP_GET, BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS }, + { BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET, BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS }, + { BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_GET, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_POWER_LAST_GET, BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS }, + { BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_GET, BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET, BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_GEN_POWER_RANGE_GET, BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET, BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_GEN_BATTERY_GET, BLE_MESH_MODEL_OP_GEN_BATTERY_STATUS }, + { BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_GET, BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET, BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_GET, BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET, BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS }, + { BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET, BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS }, + { BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET, BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS }, + { BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET, BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS }, + { BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET, BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS }, + { BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET, BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS }, + { BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET, BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS }, + { BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_GET, BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_STATUS }, + { BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_GET, BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_STATUS }, + { BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET, BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_STATUS }, + { BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET, BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS }, +}; + +static void timeout_handler(struct k_work *work) +{ + generic_internal_data_t *internal = NULL; + bt_mesh_generic_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + + BT_WARN("Receive generic status message timeout"); + + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (!node || !node->ctx.model) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + client = (bt_mesh_generic_client_t *)node->ctx.model->user_data; + if (!client) { + BT_ERR("%s, Generic Client user_data is NULL", __func__); + return; + } + + internal = (generic_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Generic Client internal_data is NULL", __func__); + return; + } + + bt_mesh_callback_generic_status_to_btc(node->opcode, 0x03, node->ctx.model, + &node->ctx, NULL, 0); + + bt_mesh_client_free_node(&internal->queue, node); + + return; +} + +static void generic_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + generic_internal_data_t *internal = NULL; + bt_mesh_generic_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + u8_t *val = NULL; + u8_t evt = 0xFF; + u32_t rsp = 0; + size_t len = 0; + + BT_DBG("%s, len %d, bytes %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + client = (bt_mesh_generic_client_t *)model->user_data; + if (!client) { + BT_ERR("%s, Generic Client user_data is NULL", __func__); + return; + } + + internal = (generic_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Generic Client internal_data is NULL", __func__); + return; + } + + rsp = ctx->recv_op; + + switch (rsp) { + case BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS: { + struct bt_mesh_gen_onoff_status *status = NULL; + if (buf->len != 1 && buf->len != 3) { + BT_ERR("Invalid Generic OnOff Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_onoff_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_onoff = net_buf_simple_pull_u8(buf); + if (buf->len) { + status->op_en = true; + status->target_onoff = net_buf_simple_pull_u8(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_onoff_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS: { + struct bt_mesh_gen_level_status *status = NULL; + if (buf->len != 2 && buf->len != 5) { + BT_ERR("Invalid Generic Level Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_level_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_level = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->target_level = net_buf_simple_pull_le16(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_level_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS: { + struct bt_mesh_gen_def_trans_time_status *status = NULL; + if (buf->len != 1) { + BT_ERR("Invalid Generic Default Trans Time Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_def_trans_time_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->trans_time = net_buf_simple_pull_u8(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_def_trans_time_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS: { + struct bt_mesh_gen_onpowerup_status *status = NULL; + if (buf->len != 1) { + BT_ERR("Invalid Generic OnPowerUp Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_onpowerup_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->onpowerup = net_buf_simple_pull_u8(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_onpowerup_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS: { + struct bt_mesh_gen_power_level_status *status = NULL; + if (buf->len != 2 && buf->len != 5) { + BT_ERR("Invalid Generic Power Level Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_power_level_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_power = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->target_power = net_buf_simple_pull_le16(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_power_level_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS: { + struct bt_mesh_gen_power_last_status *status = NULL; + if (buf->len != 2) { + BT_ERR("Invalid Generic Power Last Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_power_last_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->power = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_power_last_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS: { + struct bt_mesh_gen_power_default_status *status = NULL; + if (buf->len != 2) { + BT_ERR("Invalid Generic Power Default Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_power_default_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->power = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_power_default_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS: { + struct bt_mesh_gen_power_range_status *status = NULL; + if (buf->len != 5) { + BT_ERR("Invalid Generic Power Range Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_power_range_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->status_code = net_buf_simple_pull_u8(buf); + status->range_min = net_buf_simple_pull_le16(buf); + status->range_max = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_power_range_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_BATTERY_STATUS: { + struct bt_mesh_gen_battery_status *status = NULL; + if (buf->len != 8) { + BT_ERR("Invalid Generic Battery Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_battery_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + u32_t value = 0; + value = net_buf_simple_pull_le32(buf); + status->battery_level = (u8_t)value; + status->time_to_discharge = (value >> 8); + value = net_buf_simple_pull_le32(buf); + status->time_to_charge = (value & 0xffffff); + status->flags = (u8_t)(value >> 24); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_battery_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS: { + struct bt_mesh_gen_loc_global_status *status = NULL; + if (buf->len != 10) { + BT_ERR("Invalid Generic Location Global Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_loc_global_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->global_latitude = net_buf_simple_pull_le32(buf); + status->global_longitude = net_buf_simple_pull_le32(buf); + status->global_altitude = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_loc_global_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS: { + struct bt_mesh_gen_loc_local_status *status = NULL; + if (buf->len != 9) { + BT_ERR("Invalid Generic Location Local Status length %d", buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_gen_loc_local_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->local_north = net_buf_simple_pull_le16(buf); + status->local_east = net_buf_simple_pull_le16(buf); + status->local_altitude = net_buf_simple_pull_le16(buf); + status->floor_number = net_buf_simple_pull_u8(buf); + status->uncertainty = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_loc_local_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS: { + struct bt_mesh_gen_user_properties_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_gen_user_properties_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->user_property_ids = bt_mesh_alloc_buf(buf->len); + if (!status->user_property_ids) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->user_property_ids, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_user_properties_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS: { + struct bt_mesh_gen_user_property_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_gen_user_property_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->user_property_id = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->user_access = net_buf_simple_pull_u8(buf); + status->user_property_value = bt_mesh_alloc_buf(buf->len); + if (!status->user_property_value) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->user_property_value, buf->data, buf->len); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_user_property_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS: { + struct bt_mesh_gen_admin_properties_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_gen_admin_properties_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->admin_property_ids = bt_mesh_alloc_buf(buf->len); + if (!status->admin_property_ids) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->admin_property_ids, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_admin_properties_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS: { + struct bt_mesh_gen_admin_property_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_gen_admin_property_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->admin_property_id = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->admin_user_access = net_buf_simple_pull_u8(buf); + status->admin_property_value = bt_mesh_alloc_buf(buf->len); + if (!status->admin_property_value) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->admin_property_value, buf->data, buf->len); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_admin_property_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_STATUS: { + struct bt_mesh_gen_manu_properties_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_gen_manu_properties_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->manu_property_ids = bt_mesh_alloc_buf(buf->len); + if (!status->manu_property_ids) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->manu_property_ids, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_manu_properties_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_STATUS: { + struct bt_mesh_gen_manu_property_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_gen_manu_property_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->manu_property_id = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->manu_user_access = net_buf_simple_pull_u8(buf); + status->manu_property_value = bt_mesh_alloc_buf(buf->len); + if (!status->manu_property_value) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->manu_property_value, buf->data, buf->len); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_manu_property_status); + break; + } + case BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS: { + struct bt_mesh_gen_client_properties_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_gen_client_properties_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->client_property_ids = bt_mesh_alloc_buf(buf->len); + if (!status->client_property_ids) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->client_property_ids, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_gen_client_properties_status); + break; + } + default: + BT_ERR("%s, Not a Generic Status message opcode", __func__); + return; + } + + buf->data = val; + buf->len = len; + node = bt_mesh_is_model_message_publish(model, ctx, buf, true); + if (!node) { + BT_DBG("Unexpected generic status message 0x%x", rsp); + } else { + switch (node->opcode) { + case BLE_MESH_MODEL_OP_GEN_ONOFF_GET: + case BLE_MESH_MODEL_OP_GEN_LEVEL_GET: + case BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_GET: + case BLE_MESH_MODEL_OP_GEN_ONPOWERUP_GET: + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_GET: + case BLE_MESH_MODEL_OP_GEN_POWER_LAST_GET: + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_GET: + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_GET: + case BLE_MESH_MODEL_OP_GEN_BATTERY_GET: + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_GET: + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_GET: + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET: + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET: + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET: + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET: + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_GET: + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_GET: + case BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET: + evt = 0x00; + break; + case BLE_MESH_MODEL_OP_GEN_ONOFF_SET: + case BLE_MESH_MODEL_OP_GEN_LEVEL_SET: + case BLE_MESH_MODEL_OP_GEN_DELTA_SET: + case BLE_MESH_MODEL_OP_GEN_MOVE_SET: + case BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET: + case BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET: + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET: + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET: + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET: + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET: + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET: + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET: + evt = 0x01; + break; + default: + break; + } + + bt_mesh_callback_generic_status_to_btc(node->opcode, evt, model, ctx, val, len); + // Don't forget to release the node at the end. + bt_mesh_client_free_node(&internal->queue, node); + } + + switch (rsp) { + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS: { + struct bt_mesh_gen_user_properties_status *status; + status = (struct bt_mesh_gen_user_properties_status *)val; + bt_mesh_free_buf(status->user_property_ids); + break; + } + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS: { + struct bt_mesh_gen_user_property_status *status; + status = (struct bt_mesh_gen_user_property_status *)val; + bt_mesh_free_buf(status->user_property_value); + break; + } + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS: { + struct bt_mesh_gen_admin_properties_status *status; + status = (struct bt_mesh_gen_admin_properties_status *)val; + bt_mesh_free_buf(status->admin_property_ids); + break; + } + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS: { + struct bt_mesh_gen_admin_property_status *status; + status = (struct bt_mesh_gen_admin_property_status *)val; + bt_mesh_free_buf(status->admin_property_value); + break; + } + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_STATUS: { + struct bt_mesh_gen_manu_properties_status *status; + status = (struct bt_mesh_gen_manu_properties_status *)val; + bt_mesh_free_buf(status->manu_property_ids); + break; + } + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_STATUS: { + struct bt_mesh_gen_manu_property_status *status; + status = (struct bt_mesh_gen_manu_property_status *)val; + bt_mesh_free_buf(status->manu_property_value); + break; + } + case BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS: { + struct bt_mesh_gen_client_properties_status *status; + status = (struct bt_mesh_gen_client_properties_status *)val; + bt_mesh_free_buf(status->client_property_ids); + break; + } + default: + break; + } + + osi_free(val); + + return; +} + +const struct bt_mesh_model_op gen_onoff_cli_op[] = { + { BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, 1, generic_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op gen_level_cli_op[] = { + { BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS, 2, generic_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op gen_def_trans_time_cli_op[] = { + { BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS, 1, generic_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op gen_power_onoff_cli_op[] = { + { BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS, 1, generic_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op gen_power_level_cli_op[] = { + { BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS, 2, generic_status }, + { BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS, 2, generic_status }, + { BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS, 2, generic_status }, + { BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS, 5, generic_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op gen_battery_cli_op[] = { + { BLE_MESH_MODEL_OP_GEN_BATTERY_STATUS, 8, generic_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op gen_location_cli_op[] = { + { BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS, 10, generic_status }, + { BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS, 9, generic_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op gen_property_cli_op[] = { + { BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS, 2, generic_status }, + { BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS, 2, generic_status }, + { BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS, 2, generic_status }, + { BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS, 2, generic_status }, + { BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_STATUS, 2, generic_status }, + { BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_STATUS, 2, generic_status }, + { BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS, 2, generic_status }, + BLE_MESH_MODEL_OP_END, +}; + +static int gen_get_state(struct bt_mesh_common_param *common, void *value) +{ + NET_BUF_SIMPLE_DEFINE(msg, BLE_MESH_GEN_GET_STATE_MSG_LEN); + int err; + + bt_mesh_model_msg_init(&msg, common->opcode); + + if (value) { + switch (common->opcode) { + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET: { + struct bt_mesh_gen_user_property_get *get; + get = (struct bt_mesh_gen_user_property_get *)value; + net_buf_simple_add_le16(&msg, get->user_property_id); + break; + } + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET: { + struct bt_mesh_gen_admin_property_get *get; + get = (struct bt_mesh_gen_admin_property_get *)value; + net_buf_simple_add_le16(&msg, get->admin_property_id); + break; + } + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_GET: { + struct bt_mesh_gen_manu_property_get *get; + get = (struct bt_mesh_gen_manu_property_get *)value; + net_buf_simple_add_le16(&msg, get->manu_property_id); + break; + } + case BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET: { + struct bt_mesh_gen_client_properties_get *get; + get = (struct bt_mesh_gen_client_properties_get *)value; + net_buf_simple_add_le16(&msg, get->client_property_id); + break; + } + default: + BT_DBG("This generic message should be sent with NULL get pointer"); + break; + } + } + + err = bt_mesh_client_send_msg(common->model, common->opcode, &common->ctx, &msg, + timeout_handler, common->msg_timeout, true, + common->cb, common->cb_data); + if (err) { + BT_ERR("%s, Failed to send Generic Get message (err %d)", __func__, err); + } + + return err; +} + +static int gen_set_state(struct bt_mesh_common_param *common, + void *value, u16_t value_len, bool need_ack) +{ + struct net_buf_simple *msg = NULL; + int err; + + msg = bt_mesh_alloc_buf(value_len); + if (!msg) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + bt_mesh_model_msg_init(msg, common->opcode); + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_GEN_ONOFF_SET: + case BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK: { + struct bt_mesh_gen_onoff_set *set; + set = (struct bt_mesh_gen_onoff_set *)value; + net_buf_simple_add_u8(msg, set->onoff); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + + case BLE_MESH_MODEL_OP_GEN_LEVEL_SET: + case BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK: { + struct bt_mesh_gen_level_set *set; + set = (struct bt_mesh_gen_level_set *)value; + net_buf_simple_add_le16(msg, set->level); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + + case BLE_MESH_MODEL_OP_GEN_DELTA_SET: + case BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK: { + struct bt_mesh_gen_delta_set *set; + set = (struct bt_mesh_gen_delta_set *)value; + net_buf_simple_add_le32(msg, set->level); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + + case BLE_MESH_MODEL_OP_GEN_MOVE_SET: + case BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK: { + struct bt_mesh_gen_move_set *set; + set = (struct bt_mesh_gen_move_set *)value; + net_buf_simple_add_le16(msg, set->delta_level); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + + case BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET: + case BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET_UNACK: { + struct bt_mesh_gen_def_trans_time_set *set; + set = (struct bt_mesh_gen_def_trans_time_set *)value; + net_buf_simple_add_u8(msg, set->trans_time); + break; + } + + case BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET: + case BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET_UNACK: { + struct bt_mesh_gen_onpowerup_set *set; + set = (struct bt_mesh_gen_onpowerup_set *)value; + net_buf_simple_add_u8(msg, set->onpowerup); + break; + } + + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET: + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET_UNACK: { + struct bt_mesh_gen_power_level_set *set; + set = (struct bt_mesh_gen_power_level_set *)value; + net_buf_simple_add_le16(msg, set->power); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET: + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET_UNACK: { + struct bt_mesh_gen_power_default_set *set; + set = (struct bt_mesh_gen_power_default_set *)value; + net_buf_simple_add_le16(msg, set->power); + break; + } + + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET: + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET_UNACK: { + struct bt_mesh_gen_power_range_set *set; + set = (struct bt_mesh_gen_power_range_set *)value; + net_buf_simple_add_le16(msg, set->range_min); + net_buf_simple_add_le16(msg, set->range_max); + break; + } + + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET: + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET_UNACK: { + struct bt_mesh_gen_loc_global_set *set; + set = (struct bt_mesh_gen_loc_global_set *)value; + net_buf_simple_add_le32(msg, set->global_latitude); + net_buf_simple_add_le32(msg, set->global_longitude); + net_buf_simple_add_le16(msg, set->global_altitude); + break; + } + + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET: + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET_UNACK: { + struct bt_mesh_gen_loc_local_set *set; + set = (struct bt_mesh_gen_loc_local_set *)value; + net_buf_simple_add_le16(msg, set->local_north); + net_buf_simple_add_le16(msg, set->local_east); + net_buf_simple_add_le16(msg, set->local_altitude); + net_buf_simple_add_u8(msg, set->floor_number); + net_buf_simple_add_le16(msg, set->uncertainty); + break; + } + + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK: { + struct bt_mesh_gen_user_property_set *set; + set = (struct bt_mesh_gen_user_property_set *)value; + net_buf_simple_add_le16(msg, set->user_property_id); + net_buf_simple_add_mem(msg, set->user_property_value->data, set->user_property_value->len); + break; + } + + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK: { + struct bt_mesh_gen_admin_property_set *set; + set = (struct bt_mesh_gen_admin_property_set *)value; + net_buf_simple_add_le16(msg, set->admin_property_id); + net_buf_simple_add_u8(msg, set->admin_user_access); + net_buf_simple_add_mem(msg, set->admin_property_value->data, set->admin_property_value->len); + break; + } + + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET: + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET_UNACK: { + struct bt_mesh_gen_manu_property_set *set; + set = (struct bt_mesh_gen_manu_property_set *)value; + net_buf_simple_add_le16(msg, set->manu_property_id); + net_buf_simple_add_u8(msg, set->manu_user_access); + break; + } + + default: + BT_ERR("%s, Not a Generic Client Set message opcode", __func__); + err = -EINVAL; + goto end; + } + + err = bt_mesh_client_send_msg(common->model, common->opcode, &common->ctx, msg, + timeout_handler, common->msg_timeout, need_ack, + common->cb, common->cb_data); + if (err) { + BT_ERR("%s, Failed to send Generic Set message (err %d)", __func__, err); + } + +end: + bt_mesh_free_buf(msg); + + return err; +} + +int bt_mesh_generic_client_get_state(struct bt_mesh_common_param *common, void *get, void *status) +{ + bt_mesh_generic_client_t *client = NULL; + + if (!common || !common->model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_generic_client_t *)common->model->user_data; + if (!client || !client->internal_data) { + BT_ERR("%s, Generic Client user data is NULL", __func__); + return -EINVAL; + } + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_GEN_ONOFF_GET: + case BLE_MESH_MODEL_OP_GEN_LEVEL_GET: + case BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_GET: + case BLE_MESH_MODEL_OP_GEN_ONPOWERUP_GET: + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_GET: + case BLE_MESH_MODEL_OP_GEN_POWER_LAST_GET: + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_GET: + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_GET: + case BLE_MESH_MODEL_OP_GEN_BATTERY_GET: + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_GET: + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_GET: + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET: + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET: + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_GET: + break; + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET: + if (!get) { + BT_ERR("%s, Generic user_property_get is NULL", __func__); + return -EINVAL; + } + break; + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET: + if (!get) { + BT_ERR("%s, Generic admin_property_get is NULL", __func__); + return -EINVAL; + } + break; + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_GET: + if (!get) { + BT_ERR("%s, Generic manu_property_get is NULL", __func__); + return -EINVAL; + } + break; + case BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET: + if (!get) { + BT_ERR("%s, Generic client_properties_get is NULL", __func__); + return -EINVAL; + } + break; + default: + BT_ERR("%s, Not a Generic Client Get message opcode", __func__); + return -EINVAL; + } + + return gen_get_state(common, get); +} + +int bt_mesh_generic_client_set_state(struct bt_mesh_common_param *common, void *set, void *status) +{ + bt_mesh_generic_client_t *client = NULL; + u16_t length = 0; + bool need_ack = false; + + if (!common || !common->model || !set) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_generic_client_t *)common->model->user_data; + if (!client || !client->internal_data) { + BT_ERR("%s, Generic Client user data is NULL", __func__); + return -EINVAL; + } + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_GEN_ONOFF_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK: { + struct bt_mesh_gen_onoff_set *value; + value = (struct bt_mesh_gen_onoff_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Generic OnOff Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_GEN_ONOFF_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_GEN_LEVEL_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK: { + struct bt_mesh_gen_level_set *value; + value = (struct bt_mesh_gen_level_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Generic Level Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_GEN_LEVEL_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_GEN_DELTA_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK: { + struct bt_mesh_gen_delta_set *value; + value = (struct bt_mesh_gen_delta_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Generic Delta Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_GEN_DELTA_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_GEN_MOVE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK: { + struct bt_mesh_gen_move_set *value; + value = (struct bt_mesh_gen_move_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Generic Move Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_GEN_MOVE_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET_UNACK: { + u8_t value = *(u8_t *)set; + if ((value & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Generic Default Trans Time Set transition time", __func__); + return -EINVAL; + } + length = BLE_MESH_GEN_DEF_TRANS_TIME_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET_UNACK: + length = BLE_MESH_GEN_ONPOWERUP_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET_UNACK: { + struct bt_mesh_gen_power_level_set *value; + value = (struct bt_mesh_gen_power_level_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Generic Power Level Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_GEN_POWER_LEVEL_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET_UNACK: + length = BLE_MESH_GEN_POWER_DEFAULT_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET_UNACK: { + struct bt_mesh_gen_power_range_set *value; + value = (struct bt_mesh_gen_power_range_set *)set; + if (value->range_min > value->range_max) { + BT_ERR("%s, Generic Power Level Set range min is greater than range max", __func__); + return -EINVAL; + } + length = BLE_MESH_GEN_POWER_RANGE_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET_UNACK: + length = BLE_MESH_GEN_LOC_GLOBAL_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET_UNACK: + length = BLE_MESH_GEN_LOC_LOCAL_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK: { + struct bt_mesh_gen_user_property_set *value; + value = (struct bt_mesh_gen_user_property_set *)set; + if (!value->user_property_value) { + BT_ERR("%s, Generic user_property_value is NULL", __func__); + return -EINVAL; + } + length = (1 + 2 + value->user_property_value->len + 4); + break; + } + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK: { + struct bt_mesh_gen_admin_property_set *value; + value = (struct bt_mesh_gen_admin_property_set *)set; + if (!value->admin_property_value) { + BT_ERR("%s, Generic admin_property_value is NULL", __func__); + return -EINVAL; + } + length = (1 + 2 + 1 + value->admin_property_value->len + 4); + break; + } + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET_UNACK: + length = BLE_MESH_GEN_MANU_PROPERTY_SET_MSG_LEN; + break; + default: + BT_ERR("%s, Not a Generic Client Set message opcode", __func__); + return -EINVAL; + } + + return gen_set_state(common, set, length, need_ack); +} + +static int generic_client_init(struct bt_mesh_model *model, bool primary) +{ + generic_internal_data_t *internal = NULL; + bt_mesh_generic_client_t *client = NULL; + + BT_DBG("primary %u", primary); + + if (!model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_generic_client_t *)model->user_data; + if (!client) { + BT_ERR("%s, Generic Client user_data is NULL", __func__); + return -EINVAL; + } + + /* TODO: call osi_free() when deinit function is invoked*/ + internal = osi_calloc(sizeof(generic_internal_data_t)); + if (!internal) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + sys_slist_init(&internal->queue); + + client->model = model; + client->op_pair_size = ARRAY_SIZE(gen_op_pair); + client->op_pair = gen_op_pair; + client->internal_data = internal; + + return 0; +} + +int bt_mesh_gen_onoff_cli_init(struct bt_mesh_model *model, bool primary) +{ + return generic_client_init(model, primary); +} + +int bt_mesh_gen_level_cli_init(struct bt_mesh_model *model, bool primary) +{ + return generic_client_init(model, primary); +} + +int bt_mesh_gen_def_trans_time_cli_init(struct bt_mesh_model *model, bool primary) +{ + return generic_client_init(model, primary); +} + +int bt_mesh_gen_pwr_onoff_cli_init(struct bt_mesh_model *model, bool primary) +{ + return generic_client_init(model, primary); +} + +int bt_mesh_gen_pwr_level_cli_init(struct bt_mesh_model *model, bool primary) +{ + return generic_client_init(model, primary); +} + +int bt_mesh_gen_battery_cli_init(struct bt_mesh_model *model, bool primary) +{ + return generic_client_init(model, primary); +} + +int bt_mesh_gen_location_cli_init(struct bt_mesh_model *model, bool primary) +{ + return generic_client_init(model, primary); +} + +int bt_mesh_gen_property_cli_init(struct bt_mesh_model *model, bool primary) +{ + return generic_client_init(model, primary); +} diff --git a/components/bt/ble_mesh/mesh_models/include/generic_client.h b/components/bt/ble_mesh/mesh_models/include/generic_client.h new file mode 100644 index 0000000000..3f272cb952 --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/include/generic_client.h @@ -0,0 +1,491 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @file + * @brief Bluetooth Mesh Generic Client Model APIs. + */ + +#ifndef _GENERIC_CLIENT_H_ +#define _GENERIC_CLIENT_H_ + +#include "mesh_access.h" +#include "mesh_kernel.h" + +#include "model_common.h" + +/* Generic client model common structure */ +typedef bt_mesh_client_common_t bt_mesh_generic_client_t; +typedef bt_mesh_internal_data_t generic_internal_data_t; + +/* Generic OnOff Client Model Context */ +extern const struct bt_mesh_model_op gen_onoff_cli_op[]; + +/** @def BLE_MESH_MODEL_GEN_ONOFF_CLI + * + * Define a new generic onoff client model. Note that this API + * needs to be repeated for each element which the application + * wants to have a generic onoff client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_gen_onoff_cli. + * + * @return New generic onoff client model instance. + */ +#define BLE_MESH_MODEL_GEN_ONOFF_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_GEN_ONOFF_CLI, \ + gen_onoff_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_gen_onoff_cli_t; + +struct bt_mesh_gen_onoff_status { + bool op_en; /* Indicate whether optional parameters included */ + u8_t present_onoff; /* Present value of Generic OnOff state */ + u8_t target_onoff; /* Target value of Generic OnOff state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_gen_onoff_set { + bool op_en; /* Indicate whether optional parameters included */ + u8_t onoff; /* Target value of Generic OnOff state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +/* Generic Level Client Model Context */ +extern const struct bt_mesh_model_op gen_level_cli_op[]; + +/** @def BLE_MESH_MODEL_GEN_LEVEL_CLI + * + * Define a new generic level client model. Note that this API + * needs to be repeated for each element which the application + * wants to have a generic level client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_gen_level_cli. + * + * @return New generic level client model instance. + */ +#define BLE_MESH_MODEL_GEN_LEVEL_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_GEN_LEVEL_CLI, \ + gen_level_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_gen_level_cli_t; + +struct bt_mesh_gen_level_status { + bool op_en; /* Indicate whether optional parameters included */ + s16_t present_level; /* Present value of Generic Level state */ + s16_t target_level; /* Target value of the Generic Level state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_gen_level_set { + bool op_en; /* Indicate whether optional parameters included */ + s16_t level; /* Target value of Generic Level state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_gen_delta_set { + bool op_en; /* Indicate whether optional parameters included */ + s32_t level; /* Delta change of Generic Level state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_gen_move_set { + bool op_en; /* Indicate whether optional parameters included */ + s16_t delta_level; /* Delta Level step to calculate Move speed for Generic Level state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +/* Generic Default Transition Time Client Model Context */ +extern const struct bt_mesh_model_op gen_def_trans_time_cli_op[]; + +/** @def BLE_MESH_MODEL_GEN_DEF_TRANS_TIME_CLI + * + * Define a new generic default transition time client model. Note + * that this API needs to be repeated for each element that the + * application wants to have a generic default transition client + * model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_gen_def_trans_time_cli. + * + * @return New generic default transition time client model instance. + */ +#define BLE_MESH_MODEL_GEN_DEF_TRANS_TIME_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI, \ + gen_def_trans_time_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_gen_def_trans_time_cli_t; + +struct bt_mesh_gen_def_trans_time_set { + u8_t trans_time; /* The value of the Generic Default Transition Time state */ +}; + +struct bt_mesh_gen_def_trans_time_status { + u8_t trans_time; /* The value of the Generic Default Transition Time state */ +}; + +/* Generic Power OnOff Client Model Context */ +extern const struct bt_mesh_model_op gen_power_onoff_cli_op[]; + +/** @def BLE_MESH_MODEL_GEN_POWER_ONOFF_CLI + * + * Define a new generic power onoff client model. Note that this API + * needs to be repeated for each element which the application wants + * to have a generic power onoff client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_gen_power_onoff_cli. + * + * @return New generic power onoff client model instance. + */ +#define BLE_MESH_MODEL_GEN_POWER_ONOFF_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI, \ + gen_power_onoff_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_gen_power_onoff_cli_t; + +struct bt_mesh_gen_onpowerup_set { + u8_t onpowerup; /* The value of the Generic OnPowerUp state */ +}; + +struct bt_mesh_gen_onpowerup_status { + u8_t onpowerup; /* The value of the Generic OnPowerUp state */ +}; + +/* Generic Power Level Client Model Context */ +extern const struct bt_mesh_model_op gen_power_level_cli_op[]; + +/** @def BLE_MESH_MODEL_GEN_POWER_LEVEL_CLI + * + * Define a new generic power level client model. Note that this API + * needs to be repeated for each element which the application wants + * to have a generic power level client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_gen_power_level_cli. + * + * @return New generic power level client model instance. + */ +#define BLE_MESH_MODEL_GEN_POWER_LEVEL_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI, \ + gen_power_level_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_gen_power_level_cli_t; + +struct bt_mesh_gen_power_level_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t present_power; /* Present value of Generic Power Actual state */ + u16_t target_power; /* Target value of Generic Power Actual state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_gen_power_last_status { + u16_t power; /* The value of the Generic Power Last state */ +}; + +struct bt_mesh_gen_power_default_status { + u16_t power; /* The value of the Generic Default Last state */ +}; + +struct bt_mesh_gen_power_range_status { + u8_t status_code; /* Status Code for the requesting message */ + u16_t range_min; /* Value of Range Min field of Generic Power Range state */ + u16_t range_max; /* Value of Range Max field of Generic Power Range state */ +}; + +struct bt_mesh_gen_power_level_set { + bool op_en; /* Indicate whether optional parameters included */ + u16_t power; /* Target value of Generic Power Actual state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_gen_power_default_set { + u16_t power; /* The value of the Generic Power Default state */ +}; + +struct bt_mesh_gen_power_range_set { + u16_t range_min; /* Value of Range Min field of Generic Power Range state */ + u16_t range_max; /* Value of Range Max field of Generic Power Range state */ +}; + +/* Generic Battery Client Model Context */ +extern const struct bt_mesh_model_op gen_battery_cli_op[]; + +/** @def BLE_MESH_MODEL_GEN_BATTERY_CLI + * + * Define a new generic battery client model. Note that this API + * needs to be repeated for each element which the application + * wants to have a generic battery client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_gen_battery_cli. + * + * @return New generic battery client model instance. + */ +#define BLE_MESH_MODEL_GEN_BATTERY_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_GEN_BATTERY_CLI, \ + gen_battery_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_gen_battery_cli_t; + +struct bt_mesh_gen_battery_status { + u32_t battery_level : 8; /* Value of Generic Battery Level state */ + u32_t time_to_discharge : 24; /* Value of Generic Battery Time to Discharge state */ + u32_t time_to_charge : 24; /* Value of Generic Battery Time to Charge state */ + u32_t flags : 8; /* Value of Generic Battery Flags state */ +}; + +/* Generic Location Client Model Context */ +extern const struct bt_mesh_model_op gen_location_cli_op[]; + +/** @def BLE_MESH_MODEL_GEN_LOCATION_CLI + * + * Define a new generic location client model. Note that this API + * needs to be repeated for each element which the application + * wants to have a generic location client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_gen_location_cli. + * + * @return New generic location client model instance. + */ +#define BLE_MESH_MODEL_GEN_LOCATION_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_GEN_LOCATION_CLI, \ + gen_location_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_gen_location_cli_t; + +struct bt_mesh_gen_loc_global_status { + s32_t global_latitude; /* Global Coordinates (Latitude) */ + s32_t global_longitude; /* Global Coordinates (Longitude) */ + s16_t global_altitude; /* Global Altitude */ +}; + +struct bt_mesh_gen_loc_local_status { + s16_t local_north; /* Local Coordinates (North) */ + s16_t local_east; /* Local Coordinates (East) */ + s16_t local_altitude; /* Local Altitude */ + u8_t floor_number; /* Floor Number */ + u16_t uncertainty; /* Uncertainty */ +}; + +struct bt_mesh_gen_loc_global_set { + s32_t global_latitude; /* Global Coordinates (Latitude) */ + s32_t global_longitude; /* Global Coordinates (Longitude) */ + s16_t global_altitude; /* Global Altitude */ +}; + +struct bt_mesh_gen_loc_local_set { + s16_t local_north; /* Local Coordinates (North) */ + s16_t local_east; /* Local Coordinates (East) */ + s16_t local_altitude; /* Local Altitude */ + u8_t floor_number; /* Floor Number */ + u16_t uncertainty; /* Uncertainty */ +}; + +/* Generic Property Client Model Context */ +extern const struct bt_mesh_model_op gen_property_cli_op[]; + +/** @def BLE_MESH_MODEL_GEN_LOCATION_CLI + * + * Define a new generic location client model. Note that this API + * needs to be repeated for each element which the application + * wants to have a generic location client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_gen_location_cli. + * + * @return New generic location client model instance. + */ +#define BLE_MESH_MODEL_GEN_PROPERTY_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_GEN_PROP_CLI, \ + gen_property_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_gen_property_cli_t; + +struct bt_mesh_gen_user_properties_status { + struct net_buf_simple *user_property_ids; /* Buffer contains a sequence of N User Property IDs */ +}; + +struct bt_mesh_gen_user_property_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t user_property_id; /* Property ID identifying a Generic User Property */ + u8_t user_access; /* Enumeration indicating user access (optional) */ + struct net_buf_simple *user_property_value; /* Raw value for the User Property (C.1) */ +}; + +struct bt_mesh_gen_admin_properties_status { + struct net_buf_simple *admin_property_ids; /* Buffer contains a sequence of N Admin Property IDs */ +}; + +struct bt_mesh_gen_admin_property_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t admin_property_id; /* Property ID identifying a Generic Admin Property */ + u8_t admin_user_access; /* Enumeration indicating user access (optional) */ + struct net_buf_simple *admin_property_value; /* Raw value for the Admin Property (C.1) */ +}; + +struct bt_mesh_gen_manu_properties_status { + struct net_buf_simple *manu_property_ids; /* Buffer contains a sequence of N Manufacturer Property IDs */ +}; + +struct bt_mesh_gen_manu_property_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t manu_property_id; /* Property ID identifying a Generic Manufacturer Property */ + u8_t manu_user_access; /* Enumeration indicating user access (optional) */ + struct net_buf_simple *manu_property_value; /* Raw value for the Manufacturer Property (C.1) */ +}; + +struct bt_mesh_gen_client_properties_status { + struct net_buf_simple *client_property_ids; /* Buffer contains a sequence of N Client Property IDs */ +}; + +struct bt_mesh_gen_user_property_get { + u16_t user_property_id; /* Property ID identifying a Generic User Property */ +}; + +struct bt_mesh_gen_user_property_set { + u16_t user_property_id; /* Property ID identifying a Generic User Property */ + struct net_buf_simple *user_property_value; /* Raw value for the User Property */ +}; + +struct bt_mesh_gen_admin_property_get { + u16_t admin_property_id; /* Property ID identifying a Generic Admin Property */ +}; + +struct bt_mesh_gen_admin_property_set { + u16_t admin_property_id; /* Property ID identifying a Generic Admin Property */ + u8_t admin_user_access; /* Enumeration indicating user access */ + struct net_buf_simple *admin_property_value; /* Raw value for the Admin Property */ +}; + +struct bt_mesh_gen_manu_property_get { + u16_t manu_property_id; /* Property ID identifying a Generic Manufacturer Property */ +}; + +struct bt_mesh_gen_manu_property_set { + u16_t manu_property_id; /* Property ID identifying a Generic Manufacturer Property */ + u8_t manu_user_access; /* Enumeration indicating user access */ +}; + +struct bt_mesh_gen_client_properties_get { + u16_t client_property_id; /* A starting Client Property ID present within an element */ +}; + +/** + * @brief This function is called to initialize generic onoff client model user_data. + * + * @param[in] model: Pointer to generic onoff client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_gen_onoff_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize generic level client model user_data. + * + * @param[in] model: Pointer to generic level client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_gen_level_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize generic default transition time + * client model user_data. + * + * @param[in] model: Pointer to generic default transition time client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_gen_def_trans_time_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize generic power onoff client model user_data. + * + * @param[in] model: Pointer to generic power onoff client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_gen_pwr_onoff_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize generic power level client model user_data. + * + * @param[in] model: Pointer to generic power level client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_gen_pwr_level_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize generic battery client model user_data. + * + * @param[in] model: Pointer to generic battery client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_gen_battery_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize generic location client model user_data. + * + * @param[in] model: Pointer to generic location client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_gen_location_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize generic property client model user_data. + * + * @param[in] model: Pointer to generic property client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_gen_property_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to get generic states. + * + * @param[in] common: Message common information structure + * @param[in] get: Pointer of generic get message value + * @param[out] status: Pointer of generic status message value + * + * @return Zero-success, other-fail + */ +int bt_mesh_generic_client_get_state(struct bt_mesh_common_param *common, void *get, void *status); + +/** + * @brief This function is called to set generic states. + * + * @param[in] common: Message common information structure + * @param[in] set: Pointer of generic set message value + * @param[out] status: Pointer of generic status message value + * + * @return Zero-success, other-fail + */ +int bt_mesh_generic_client_set_state(struct bt_mesh_common_param *common, void *set, void *status); + +#endif /* _GENERIC_CLIENT_H_ */ diff --git a/components/bt/ble_mesh/mesh_models/include/lighting_client.h b/components/bt/ble_mesh/mesh_models/include/lighting_client.h new file mode 100644 index 0000000000..5b6b92a5ae --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/include/lighting_client.h @@ -0,0 +1,492 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @file + * @brief Bluetooth Mesh Lighting Client Model APIs. + */ + +#ifndef _LIGHTING_CLIENT_H_ +#define _LIGHTING_CLIENT_H_ + +#include "mesh_access.h" +#include "mesh_kernel.h" + +#include "model_common.h" + +/* Light client model common structure */ +typedef bt_mesh_client_common_t bt_mesh_light_client_t; +typedef bt_mesh_internal_data_t light_internal_data_t; + +/* Light Lightness Client Model Context */ +extern const struct bt_mesh_model_op light_lightness_cli_op[]; + +/** @def BLE_MESH_MODEL_LIGHT_LIGHTNESS_CLI + * + * Define a new light lightness client model. Note that this API + * needs to be repeated for each element which the application + * wants to have a light lightness client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_light_lightness_cli. + * + * @return New light lightness client model instance. + */ +#define BLE_MESH_MODEL_LIGHT_LIGHTNESS_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI, \ + light_lightness_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_light_lightness_cli_t; + +struct bt_mesh_light_lightness_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t present_lightness; /* Present value of light lightness actual state */ + u16_t target_lightness; /* Target value of light lightness actual state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_light_lightness_linear_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t present_lightness; /* Present value of light lightness linear state */ + u16_t target_lightness; /* Target value of light lightness linear state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_light_lightness_last_status { + u16_t lightness; /* The value of the Light Lightness Last state */ +}; + +struct bt_mesh_light_lightness_default_status { + u16_t lightness; /* The value of the Light Lightness default state */ +}; + +struct bt_mesh_light_lightness_range_status { + u8_t status_code; /* Status Code for the requesting message */ + u16_t range_min; /* Value of range min field of light lightness range state */ + u16_t range_max; /* Value of range max field of light lightness range state */ +}; + +struct bt_mesh_light_lightness_set { + bool op_en; /* Indicate whether optional parameters included */ + u16_t lightness; /* Target value of light lightness actual state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_light_lightness_linear_set { + bool op_en; /* Indicate whether optional parameters included */ + u16_t lightness; /* Target value of light lightness linear state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_light_lightness_default_set { + u16_t lightness; /* The value of the Light Lightness Default state */ +}; + +struct bt_mesh_light_lightness_range_set { + u16_t range_min; /* Value of range min field of light lightness range state */ + u16_t range_max; /* Value of range max field of light lightness range state */ +}; + +/* Light CTL Client Model Context */ +extern const struct bt_mesh_model_op light_ctl_cli_op[]; + +/** @def BLE_MESH_MODEL_LIGHT_CTL_CLI + * + * Define a new light CTL client model. Note that this API needs + * to be repeated for each element which the application wants to + * have a light CTL client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_light_ctl_cli. + * + * @return New light CTL client model instance. + */ +#define BLE_MESH_MODEL_LIGHT_CTL_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_LIGHT_CTL_CLI, \ + light_ctl_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_light_ctl_cli_t; + +struct bt_mesh_light_ctl_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t present_ctl_lightness; /* Present value of light ctl lightness state */ + u16_t present_ctl_temperature; /* Present value of light ctl temperature state */ + u16_t target_ctl_lightness; /* Target value of light ctl lightness state (optional) */ + u16_t target_ctl_temperature; /* Target value of light ctl temperature state (C.1) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_light_ctl_temperature_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t present_ctl_temperature; /* Present value of light ctl temperature state */ + u16_t present_ctl_delta_uv; /* Present value of light ctl delta UV state */ + u16_t target_ctl_temperature; /* Target value of light ctl temperature state (optional) */ + u16_t target_ctl_delta_uv; /* Target value of light ctl delta UV state (C.1) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_light_ctl_temperature_range_status { + u8_t status_code; /* Status code for the requesting message */ + u16_t range_min; /* Value of temperature range min field of light ctl temperature range state */ + u16_t range_max; /* Value of temperature range max field of light ctl temperature range state */ +}; + +struct bt_mesh_light_ctl_default_status { + u16_t lightness; /* Value of light lightness default state */ + u16_t temperature; /* Value of light temperature default state */ + s16_t delta_uv; /* Value of light delta UV default state */ +}; + +struct bt_mesh_light_ctl_set { + bool op_en; /* Indicate whether optional parameters included */ + u16_t ctl_lightness; /* Target value of light ctl lightness state */ + u16_t ctl_temperature; /* Target value of light ctl temperature state */ + s16_t ctl_delta_uv; /* Target value of light ctl delta UV state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_light_ctl_temperature_set { + bool op_en; /* Indicate whether optional parameters included */ + u16_t ctl_temperature; /* Target value of light ctl temperature state */ + s16_t ctl_delta_uv; /* Target value of light ctl delta UV state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_light_ctl_temperature_range_set { + u16_t range_min; /* Value of temperature range min field of light ctl temperature range state */ + u16_t range_max; /* Value of temperature range max field of light ctl temperature range state */ +}; + +struct bt_mesh_light_ctl_default_set { + u16_t lightness; /* Value of light lightness default state */ + u16_t temperature; /* Value of light temperature default state */ + s16_t delta_uv; /* Value of light delta UV default state */ +}; + +/* Light HSL Client Model Context */ +extern const struct bt_mesh_model_op light_hsl_cli_op[]; + +/** @def BLE_MESH_MODEL_LIGHT_HSL_CLI + * + * Define a new light HSL client model. Note that this API needs + * to be repeated for each element which the application wants to + * have a light HSL client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_light_hsl_cli. + * + * @return New light HSL client model instance. + */ +#define BLE_MESH_MODEL_LIGHT_HSL_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_LIGHT_HSL_CLI, \ + light_hsl_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_light_hsl_cli_t; + +struct bt_mesh_light_hsl_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t hsl_lightness; /* Present value of light hsl lightness state */ + u16_t hsl_hue; /* Present value of light hsl hue state */ + u16_t hsl_saturation; /* Present value of light hsl saturation state */ + u8_t remain_time; /* Time to complete state transition (optional) */ +}; + +struct bt_mesh_light_hsl_target_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t hsl_lightness_target; /* Target value of light hsl lightness state */ + u16_t hsl_hue_target; /* Target value of light hsl hue state */ + u16_t hsl_saturation_target; /* Target value of light hsl saturation state */ + u8_t remain_time; /* Time to complete state transition (optional) */ +}; + +struct bt_mesh_light_hsl_hue_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t present_hue; /* Present value of light hsl hue state */ + u16_t target_hue; /* Target value of light hsl hue state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_light_hsl_saturation_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t present_saturation; /* Present value of light hsl saturation state */ + u16_t target_saturation; /* Target value of light hsl saturation state (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_light_hsl_default_status { + u16_t lightness; /* Value of light lightness default state */ + u16_t hue; /* Value of light hue default state */ + u16_t saturation; /* Value of light saturation default state */ +}; + +struct bt_mesh_light_hsl_range_status { + u8_t status_code; /* Status code for the requesting message */ + u16_t hue_range_min; /* Value of hue range min field of light hsl hue range state */ + u16_t hue_range_max; /* Value of hue range max field of light hsl hue range state */ + u16_t saturation_range_min; /* Value of saturation range min field of light hsl saturation range state */ + u16_t saturation_range_max; /* Value of saturation range max field of light hsl saturation range state */ +}; + +struct bt_mesh_light_hsl_set { + bool op_en; /* Indicate whether optional parameters included */ + u16_t hsl_lightness; /* Target value of light hsl lightness state */ + u16_t hsl_hue; /* Target value of light hsl hue state */ + u16_t hsl_saturation; /* Target value of light hsl saturation state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_light_hsl_hue_set { + bool op_en; /* Indicate whether optional parameters included */ + u16_t hue; /* Target value of light hsl hue state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_light_hsl_saturation_set { + bool op_en; /* Indicate whether optional parameters included */ + u16_t saturation; /* Target value of light hsl hue state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_light_hsl_default_set { + u16_t lightness; /* Value of light lightness default state */ + u16_t hue; /* Value of light hue default state */ + u16_t saturation; /* Value of light saturation default state */ +}; + +struct bt_mesh_light_hsl_range_set { + u16_t hue_range_min; /* Value of hue range min field of light hsl hue range state */ + u16_t hue_range_max; /* Value of hue range max field of light hsl hue range state */ + u16_t saturation_range_min; /* Value of saturation range min field of light hsl saturation range state */ + u16_t saturation_range_max; /* Value of saturation range max field of light hsl saturation range state */ +}; + +/* Light xyL Client Model Context */ +extern const struct bt_mesh_model_op light_xyl_cli_op[]; + +/** @def BLE_MESH_MODEL_LIGHT_XYL_CLI + * + * Define a new light xyL client model. Note that this API needs + * to be repeated for each element which the application wants + * to have a light xyL client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_light_xyl_cli. + * + * @return New light xyL client model instance. + */ +#define BLE_MESH_MODEL_LIGHT_XYL_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_LIGHT_XYL_CLI, \ + light_xyl_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_light_xyl_cli_t; + +struct bt_mesh_light_xyl_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t xyl_lightness; /* The present value of the Light xyL Lightness state */ + u16_t xyl_x; /* The present value of the Light xyL x state */ + u16_t xyl_y; /* The present value of the Light xyL y state */ + u8_t remain_time; /* Time to complete state transition (optional) */ +}; + +struct bt_mesh_light_xyl_target_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t target_xyl_lightness; /* The target value of the Light xyL Lightness state */ + u16_t target_xyl_x; /* The target value of the Light xyL x state */ + u16_t target_xyl_y; /* The target value of the Light xyL y state */ + u8_t remain_time; /* Time to complete state transition (optional) */ +}; + +struct bt_mesh_light_xyl_default_status { + u16_t lightness; /* The value of the Light Lightness Default state */ + u16_t xyl_x; /* The value of the Light xyL x Default state */ + u16_t xyl_y; /* The value of the Light xyL y Default state */ +}; + +struct bt_mesh_light_xyl_range_status { + u8_t status_code; /* Status Code for the requesting message */ + u16_t xyl_x_range_min; /* The value of the xyL x Range Min field of the Light xyL x Range state */ + u16_t xyl_x_range_max; /* The value of the xyL x Range Max field of the Light xyL x Range state */ + u16_t xyl_y_range_min; /* The value of the xyL y Range Min field of the Light xyL y Range state */ + u16_t xyl_y_range_max; /* The value of the xyL y Range Max field of the Light xyL y Range state */ +}; + +struct bt_mesh_light_xyl_set { + bool op_en; /* Indicate whether optional parameters included */ + u16_t xyl_lightness; /* The target value of the Light xyL Lightness state */ + u16_t xyl_x; /* The target value of the Light xyL x state */ + u16_t xyl_y; /* The target value of the Light xyL y state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_light_xyl_default_set { + u16_t lightness; /* The value of the Light Lightness Default state */ + u16_t xyl_x; /* The value of the Light xyL x Default state */ + u16_t xyl_y; /* The value of the Light xyL y Default state */ +}; + +struct bt_mesh_light_xyl_range_set { + u16_t xyl_x_range_min; /* The value of the xyL x Range Min field of the Light xyL x Range state */ + u16_t xyl_x_range_max; /* The value of the xyL x Range Max field of the Light xyL x Range state */ + u16_t xyl_y_range_min; /* The value of the xyL y Range Min field of the Light xyL y Range state */ + u16_t xyl_y_range_max; /* The value of the xyL y Range Max field of the Light xyL y Range state */ +}; + +/* Light LC Client Model Context */ +extern const struct bt_mesh_model_op light_lc_cli_op[]; + +/** @def BLE_MESH_MODEL_LIGHT_LC_CLI + * + * Define a new light lc client model. Note that this API needs + * to be repeated for each element which the application wants + * to have a light lc client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_light_lc_cli. + * + * @return New light lc client model instance. + */ +#define BLE_MESH_MODEL_LIGHT_LC_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_LIGHT_LC_CLI, \ + light_lc_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_light_lc_cli_t; + +struct bt_mesh_light_lc_mode_status { + u8_t mode; /* The present value of the Light LC Mode state */ +}; + +struct bt_mesh_light_lc_om_status { + u8_t mode; /* The present value of the Light LC Occupancy Mode state */ +}; + +struct bt_mesh_light_lc_light_onoff_status { + bool op_en; /* Indicate whether optional parameters included */ + u8_t present_light_onoff; /* The present value of the Light LC Light OnOff state */ + u8_t target_light_onoff; /* The target value of the Light LC Light OnOff state (Optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_light_lc_property_status { + u16_t light_lc_property_id; /* Property ID identifying a Light LC Property */ + struct net_buf_simple *light_lc_property_value; /* Raw value for the Light LC Property */ +}; + +struct bt_mesh_light_lc_mode_set { + u8_t mode; /* The target value of the Light LC Mode state */ +}; + +struct bt_mesh_light_lc_om_set { + u8_t mode; /* The target value of the Light LC Occupancy Mode state */ +}; + +struct bt_mesh_light_lc_light_onoff_set { + bool op_en; /* Indicate whether optional parameters included */ + u8_t light_onoff; /* The target value of the Light LC Light OnOff state */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_light_lc_property_get { + u16_t light_lc_property_id; /* Property ID identifying a Light LC Property */ +}; + +struct bt_mesh_light_lc_property_set { + u16_t light_lc_property_id; /* Property ID identifying a Light LC Property */ + struct net_buf_simple *light_lc_property_value; /* Raw value for the Light LC Property */ +}; + +/** + * @brief This function is called to initialize light lightness client model user_data. + * + * @param[in] model: Pointer to light lightness client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_light_lightness_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize light ctl client model user_data. + * + * @param[in] model: Pointer to light ctl client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_light_ctl_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize light hsl client model user_data. + * + * @param[in] model: Pointer to light hsl client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_light_hsl_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize light xyl client model user_data. + * + * @param[in] model: Pointer to light xyl client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_light_xyl_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize light lc client model user_data. + * + * @param[in] model: Pointer to light lc client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_light_lc_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to get light states. + * + * @param[in] common: Message common information structure + * @param[in] get: Pointer of light get message value + * @param[out] status: Pointer of light status message value + * + * @return Zero-success, other-fail + */ +int bt_mesh_light_client_get_state(struct bt_mesh_common_param *common, void *get, void *status); + +/** + * @brief This function is called to set light states. + * + * @param[in] common: Message common information structure + * @param[in] set: Pointer of light set message value + * @param[out] status: Pointer of light status message value + * + * @return Zero-success, other-fail + */ +int bt_mesh_light_client_set_state(struct bt_mesh_common_param *common, void *set, void *status); + +#endif /* _LIGHTING_CLIENT_H_ */ diff --git a/components/bt/ble_mesh/mesh_models/include/mesh_common.h b/components/bt/ble_mesh/mesh_models/include/mesh_common.h new file mode 100644 index 0000000000..2a116be01e --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/include/mesh_common.h @@ -0,0 +1,46 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @file + * @brief Bluetooth Mesh Model Common APIs. + */ + +#ifndef _MESH_COMMON_H_ +#define _MESH_COMMON_H_ + +#include "osi/allocator.h" + +#include "mesh_types.h" +#include "mesh_buf.h" +#include "mesh_trace.h" + +/** + * @brief This function allocates memory to store outgoing message. + * + * @param[in] size: Length of memory allocated to store message value + * + * @return NULL-fail, pointer of a net_buf_simple structure-success + */ +struct net_buf_simple *bt_mesh_alloc_buf(u16_t size); + +/** + * @brief This function releases the memory allocated for the outgoing message. + * + * @param[in] buf: Pointer to the net_buf_simple structure to be freed + * + * @return none + */ +void bt_mesh_free_buf(struct net_buf_simple *buf); + +#endif /* _MESH_COMMON_H_ */ \ No newline at end of file diff --git a/components/bt/ble_mesh/mesh_models/include/model_common.h b/components/bt/ble_mesh/mesh_models/include/model_common.h new file mode 100644 index 0000000000..fd9cc6dfda --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/include/model_common.h @@ -0,0 +1,133 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _MODEL_COMMON_H_ +#define _MODEL_COMMON_H_ + +#include "mesh_access.h" + +/** Mesh Client Model Context */ +typedef struct { + u32_t cli_op; /* The client opcode */ + u32_t status_op; /* The server status opcode corresponding to the client opcode */ +} bt_mesh_client_op_pair_t; + +/** Mesh Client Model Context */ +typedef struct { + struct bt_mesh_model *model; + int op_pair_size; /* the size of op_pair */ + const bt_mesh_client_op_pair_t *op_pair; + /** + * @brief This function is a callback function used to push the received unsolicited + * messages to the application layer. + * + * @param[in] opcode: Opcode of received status message + * @param[in] model: Model associated with the status message + * @param[in] ctx: Context information of the status message + * @param[in] buf: Buffer contains the status message value + * + * @return None + */ + void (*publish_status)(u32_t opcode, struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf); + void *internal_data; /* Pointer of the structure of internal data */ + u8_t msg_role; /* device role of the tx message */ +} bt_mesh_client_common_t; + +typedef struct { + sys_slist_t queue; +} bt_mesh_internal_data_t; + +typedef struct { + sys_snode_t client_node; + struct bt_mesh_msg_ctx ctx; + u32_t opcode; /* Indicate the opcode of the message sending */ + u32_t op_pending; /* Indicate the status message waiting for */ + struct k_delayed_work timer; /* Message send Timer. Only for stack-internal use. */ +} bt_mesh_client_node_t; + +int bt_mesh_client_init(struct bt_mesh_model *model); + +/** + * @brief Check the msg is a publish msg or not + * + * @param model Mesh (client) Model that the message belongs to. + * @param ctx Message context, includes keys, TTL, etc. + * @param buf The message buffer + * @param need_pub Indicate if the msg sent to app layer as a publish msg + * @return 0 on success, or (negative) error code on failure. + */ +bt_mesh_client_node_t *bt_mesh_is_model_message_publish(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, + bool need_pub); + +bool bt_mesh_client_find_opcode_in_list(sys_slist_t *list, u32_t opcode); + +bool bt_mesh_client_check_node_in_list(sys_slist_t *list, uint16_t tx_dst); + +bt_mesh_client_node_t *bt_mesh_client_pick_node(sys_slist_t *list, u16_t tx_dst); + +int bt_mesh_client_send_msg(struct bt_mesh_model *model, + u32_t opcode, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *msg, + k_work_handler_t timer_handler, + s32_t timeout, bool need_ack, + const struct bt_mesh_send_cb *cb, void *cb_data); + +int bt_mesh_client_free_node(sys_slist_t *queue, bt_mesh_client_node_t *node); + +enum { + NODE = 0, + PROVISIONER, + FAST_PROV, +}; + +#define ROLE_NVAL 0xFF + +struct bt_mesh_common_param { + u32_t opcode; /* Message opcode */ + struct bt_mesh_model *model; /* Pointer to cli structure */ + struct bt_mesh_msg_ctx ctx; /* Message context */ + s32_t msg_timeout; /* Time to get response messages */ + const struct bt_mesh_send_cb *cb; /* User defined callback function */ + void *cb_data; /* Data as parameter of the cb function */ +}; + +typedef struct bt_mesh_role_param { + struct bt_mesh_model *model; /* The client model structure */ + u8_t role; /* Role of the device - Node/Provisioner */ +} bt_mesh_role_param_t; + +/** + * @brief This function copies node_index for stack internal use. + * + * @param[in] common: Pointer to the struct bt_mesh_role_param structure + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_set_model_role(bt_mesh_role_param_t *common); + +/** + * @brief This function gets msg role for stack internal use. + * + * @param[in] model: Pointer to the model structure + * @param[in] srv_send: Indicate if the message is sent by a server model + * + * @return 0 - Node, 1 - Provisioner + */ +u8_t bt_mesh_get_model_role(struct bt_mesh_model *model, bool srv_send); + +#endif /* _MODEL_COMMON_H_ */ + diff --git a/components/bt/ble_mesh/mesh_models/include/model_opcode.h b/components/bt/ble_mesh/mesh_models/include/model_opcode.h new file mode 100644 index 0000000000..01929d5cfe --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/include/model_opcode.h @@ -0,0 +1,276 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _MODEL_OPCODE_H_ +#define _MODEL_OPCODE_H_ + +#include "mesh_main.h" + +/* Generic OnOff Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_ONOFF_GET BLE_MESH_MODEL_OP_2(0x82, 0x01) +#define BLE_MESH_MODEL_OP_GEN_ONOFF_SET BLE_MESH_MODEL_OP_2(0x82, 0x02) +#define BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x03) +#define BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x04) + +/* Generic Level Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_LEVEL_GET BLE_MESH_MODEL_OP_2(0x82, 0x05) +#define BLE_MESH_MODEL_OP_GEN_LEVEL_SET BLE_MESH_MODEL_OP_2(0x82, 0x06) +#define BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x07) +#define BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x08) +#define BLE_MESH_MODEL_OP_GEN_DELTA_SET BLE_MESH_MODEL_OP_2(0x82, 0x09) +#define BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x0A) +#define BLE_MESH_MODEL_OP_GEN_MOVE_SET BLE_MESH_MODEL_OP_2(0x82, 0x0B) +#define BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x0C) + +/* Generic Default Transition Time Message Opcode*/ +#define BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_GET BLE_MESH_MODEL_OP_2(0x82, 0x0D) +#define BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET BLE_MESH_MODEL_OP_2(0x82, 0x0E) +#define BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x0F) +#define BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x10) + +/* Generic Power OnOff Message Opcode*/ +#define BLE_MESH_MODEL_OP_GEN_ONPOWERUP_GET BLE_MESH_MODEL_OP_2(0x82, 0x11) +#define BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x12) + +/* Generic Power OnOff Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET BLE_MESH_MODEL_OP_2(0x82, 0x13) +#define BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x14) + +/* Generic Power Level Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_GET BLE_MESH_MODEL_OP_2(0x82, 0x15) +#define BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET BLE_MESH_MODEL_OP_2(0x82, 0x16) +#define BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x17) +#define BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x18) +#define BLE_MESH_MODEL_OP_GEN_POWER_LAST_GET BLE_MESH_MODEL_OP_2(0x82, 0x19) +#define BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x1A) +#define BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_GET BLE_MESH_MODEL_OP_2(0x82, 0x1B) +#define BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x1C) +#define BLE_MESH_MODEL_OP_GEN_POWER_RANGE_GET BLE_MESH_MODEL_OP_2(0x82, 0x1D) +#define BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x1E) + +/* Generic Power Level Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET BLE_MESH_MODEL_OP_2(0x82, 0x1F) +#define BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x20) +#define BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET BLE_MESH_MODEL_OP_2(0x82, 0x21) +#define BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x22) + +/* Generic Battery Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_BATTERY_GET BLE_MESH_MODEL_OP_2(0x82, 0x23) +#define BLE_MESH_MODEL_OP_GEN_BATTERY_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x24) + +/* Generic Location Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_GET BLE_MESH_MODEL_OP_2(0x82, 0x25) +#define BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS BLE_MESH_MODEL_OP_1(0x40) +#define BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_GET BLE_MESH_MODEL_OP_2(0x82, 0x26) +#define BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x27) + +/* Generic Location Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET BLE_MESH_MODEL_OP_1(0x41) +#define BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET_UNACK BLE_MESH_MODEL_OP_1(0x42) +#define BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET BLE_MESH_MODEL_OP_2(0x82, 0x28) +#define BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x29) + +/* Generic Manufacturer Property Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_GET BLE_MESH_MODEL_OP_2(0x82, 0x2A) +#define BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_STATUS BLE_MESH_MODEL_OP_1(0x43) +#define BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_GET BLE_MESH_MODEL_OP_2(0x82, 0x2B) +#define BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET BLE_MESH_MODEL_OP_1(0x44) +#define BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET_UNACK BLE_MESH_MODEL_OP_1(0x45) +#define BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_STATUS BLE_MESH_MODEL_OP_1(0x46) + +/* Generic Admin Property Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET BLE_MESH_MODEL_OP_2(0x82, 0x2C) +#define BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS BLE_MESH_MODEL_OP_1(0x47) +#define BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET BLE_MESH_MODEL_OP_2(0x82, 0x2D) +#define BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET BLE_MESH_MODEL_OP_1(0x48) +#define BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK BLE_MESH_MODEL_OP_1(0x49) +#define BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS BLE_MESH_MODEL_OP_1(0x4A) + +/* Generic User Property Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET BLE_MESH_MODEL_OP_2(0x82, 0x2E) +#define BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS BLE_MESH_MODEL_OP_1(0x4B) +#define BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET BLE_MESH_MODEL_OP_2(0x82, 0x2F) +#define BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET BLE_MESH_MODEL_OP_1(0x4C) +#define BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK BLE_MESH_MODEL_OP_1(0x4D) +#define BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS BLE_MESH_MODEL_OP_1(0x4E) + +/* Generic Client Property Message Opcode */ +#define BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET BLE_MESH_MODEL_OP_1(0x4F) +#define BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS BLE_MESH_MODEL_OP_1(0x50) + +/* Sensor Message Opcode */ +#define BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET BLE_MESH_MODEL_OP_2(0x82, 0x30) +#define BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS BLE_MESH_MODEL_OP_1(0x51) +#define BLE_MESH_MODEL_OP_SENSOR_GET BLE_MESH_MODEL_OP_2(0x82, 0x31) +#define BLE_MESH_MODEL_OP_SENSOR_STATUS BLE_MESH_MODEL_OP_1(0x52) +#define BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET BLE_MESH_MODEL_OP_2(0x82, 0x32) +#define BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS BLE_MESH_MODEL_OP_1(0x53) +#define BLE_MESH_MODEL_OP_SENSOR_SERIES_GET BLE_MESH_MODEL_OP_2(0x82, 0x33) +#define BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS BLE_MESH_MODEL_OP_1(0x54) + +/* Sensor Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET BLE_MESH_MODEL_OP_2(0x82, 0x34) +#define BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET BLE_MESH_MODEL_OP_1(0x55) +#define BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK BLE_MESH_MODEL_OP_1(0x56) +#define BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS BLE_MESH_MODEL_OP_1(0x57) +#define BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET BLE_MESH_MODEL_OP_2(0x82, 0x35) +#define BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS BLE_MESH_MODEL_OP_1(0x58) +#define BLE_MESH_MODEL_OP_SENSOR_SETTING_GET BLE_MESH_MODEL_OP_2(0x82, 0x36) +#define BLE_MESH_MODEL_OP_SENSOR_SETTING_SET BLE_MESH_MODEL_OP_1(0x59) +#define BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK BLE_MESH_MODEL_OP_1(0x5A) +#define BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS BLE_MESH_MODEL_OP_1(0x5B) + +/* Time Message Opcode */ +#define BLE_MESH_MODEL_OP_TIME_GET BLE_MESH_MODEL_OP_2(0x82, 0x37) +#define BLE_MESH_MODEL_OP_TIME_SET BLE_MESH_MODEL_OP_1(0x5C) +#define BLE_MESH_MODEL_OP_TIME_STATUS BLE_MESH_MODEL_OP_1(0x5D) +#define BLE_MESH_MODEL_OP_TIME_ROLE_GET BLE_MESH_MODEL_OP_2(0x82, 0x38) +#define BLE_MESH_MODEL_OP_TIME_ROLE_SET BLE_MESH_MODEL_OP_2(0x82, 0x39) +#define BLE_MESH_MODEL_OP_TIME_ROLE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x3A) +#define BLE_MESH_MODEL_OP_TIME_ZONE_GET BLE_MESH_MODEL_OP_2(0x82, 0x3B) +#define BLE_MESH_MODEL_OP_TIME_ZONE_SET BLE_MESH_MODEL_OP_2(0x82, 0x3C) +#define BLE_MESH_MODEL_OP_TIME_ZONE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x3D) +#define BLE_MESH_MODEL_OP_TAI_UTC_DELTA_GET BLE_MESH_MODEL_OP_2(0x82, 0x3E) +#define BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET BLE_MESH_MODEL_OP_2(0x82, 0x3F) +#define BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x40) + +/* Scene Message Opcode */ +#define BLE_MESH_MODEL_OP_SCENE_GET BLE_MESH_MODEL_OP_2(0x82, 0x41) +#define BLE_MESH_MODEL_OP_SCENE_RECALL BLE_MESH_MODEL_OP_2(0x82, 0x42) +#define BLE_MESH_MODEL_OP_SCENE_RECALL_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x43) +#define BLE_MESH_MODEL_OP_SCENE_STATUS BLE_MESH_MODEL_OP_1(0x5E) +#define BLE_MESH_MODEL_OP_SCENE_REGISTER_GET BLE_MESH_MODEL_OP_2(0x82, 0x44) +#define BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x45) + +/* Scene Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_SCENE_STORE BLE_MESH_MODEL_OP_2(0x82, 0x46) +#define BLE_MESH_MODEL_OP_SCENE_STORE_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x47) +#define BLE_MESH_MODEL_OP_SCENE_DELETE BLE_MESH_MODEL_OP_2(0x82, 0x9E) +#define BLE_MESH_MODEL_OP_SCENE_DELETE_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x9F) + +/* Scheduler Message Opcode */ +#define BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET BLE_MESH_MODEL_OP_2(0x82, 0x48) +#define BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS BLE_MESH_MODEL_OP_1(0x5F) +#define BLE_MESH_MODEL_OP_SCHEDULER_GET BLE_MESH_MODEL_OP_2(0x82, 0x49) +#define BLE_MESH_MODEL_OP_SCHEDULER_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x4A) + +/* Scheduler Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET BLE_MESH_MODEL_OP_1(0x60) +#define BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET_UNACK BLE_MESH_MODEL_OP_1(0x61) + +/* Light Lightness Message Opcode */ +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_GET BLE_MESH_MODEL_OP_2(0x82, 0x4B) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET BLE_MESH_MODEL_OP_2(0x82, 0x4C) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x4D) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x4E) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_GET BLE_MESH_MODEL_OP_2(0x82, 0x4F) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET BLE_MESH_MODEL_OP_2(0x82, 0x50) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x51) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x52) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_GET BLE_MESH_MODEL_OP_2(0x82, 0x53) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x54) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_GET BLE_MESH_MODEL_OP_2(0x82, 0x55) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x56) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_GET BLE_MESH_MODEL_OP_2(0x82, 0x57) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x58) + +/* Light Lightness Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET BLE_MESH_MODEL_OP_2(0x82, 0x59) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x5A) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET BLE_MESH_MODEL_OP_2(0x82, 0x5B) +#define BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x5C) + +/* Light CTL Message Opcode */ +#define BLE_MESH_MODEL_OP_LIGHT_CTL_GET BLE_MESH_MODEL_OP_2(0x82, 0x5D) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_SET BLE_MESH_MODEL_OP_2(0x82, 0x5E) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x5F) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x60) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_GET BLE_MESH_MODEL_OP_2(0x82, 0x61) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_GET BLE_MESH_MODEL_OP_2(0x82, 0x62) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x63) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET BLE_MESH_MODEL_OP_2(0x82, 0x64) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x65) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x66) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_GET BLE_MESH_MODEL_OP_2(0x82, 0x67) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x68) + +/* Light CTL Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET BLE_MESH_MODEL_OP_2(0x82, 0x69) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x6A) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET BLE_MESH_MODEL_OP_2(0x82, 0x6B) +#define BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x6C) + +/* Light HSL Message Opcode */ +#define BLE_MESH_MODEL_OP_LIGHT_HSL_GET BLE_MESH_MODEL_OP_2(0x82, 0x6D) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_GET BLE_MESH_MODEL_OP_2(0x82, 0x6E) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET BLE_MESH_MODEL_OP_2(0x82, 0x6F) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x70) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x71) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_GET BLE_MESH_MODEL_OP_2(0x82, 0x72) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET BLE_MESH_MODEL_OP_2(0x82, 0x73) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x74) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x75) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_SET BLE_MESH_MODEL_OP_2(0x82, 0x76) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x77) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x78) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_GET BLE_MESH_MODEL_OP_2(0x82, 0x79) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x7A) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_GET BLE_MESH_MODEL_OP_2(0x82, 0x7B) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x7C) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_GET BLE_MESH_MODEL_OP_2(0x82, 0x7D) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x7E) + +/* Light HSL Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET BLE_MESH_MODEL_OP_2(0x82, 0x7F) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x80) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET BLE_MESH_MODEL_OP_2(0x82, 0x81) +#define BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x82) /* Model spec is wrong */ + +/* Light xyL Message Opcode */ +#define BLE_MESH_MODEL_OP_LIGHT_XYL_GET BLE_MESH_MODEL_OP_2(0x82, 0x83) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_SET BLE_MESH_MODEL_OP_2(0x82, 0x84) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x85) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x86) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_GET BLE_MESH_MODEL_OP_2(0x82, 0x87) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x88) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_GET BLE_MESH_MODEL_OP_2(0x82, 0x89) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x8A) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_GET BLE_MESH_MODEL_OP_2(0x82, 0x8B) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x8C) + +/* Light xyL Setup Message Opcode */ +#define BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET BLE_MESH_MODEL_OP_2(0x82, 0x8D) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x8E) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET BLE_MESH_MODEL_OP_2(0x82, 0x8F) +#define BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x90) + +/* Light Control Message Opcode */ +#define BLE_MESH_MODEL_OP_LIGHT_LC_MODE_GET BLE_MESH_MODEL_OP_2(0x82, 0x91) +#define BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET BLE_MESH_MODEL_OP_2(0x82, 0x92) +#define BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x93) +#define BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x94) +#define BLE_MESH_MODEL_OP_LIGHT_LC_OM_GET BLE_MESH_MODEL_OP_2(0x82, 0x95) +#define BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET BLE_MESH_MODEL_OP_2(0x82, 0x96) +#define BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x97) +#define BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x98) +#define BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_GET BLE_MESH_MODEL_OP_2(0x82, 0x99) +#define BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET BLE_MESH_MODEL_OP_2(0x82, 0x9A) +#define BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET_UNACK BLE_MESH_MODEL_OP_2(0x82, 0x9B) +#define BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS BLE_MESH_MODEL_OP_2(0x82, 0x9C) +#define BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET BLE_MESH_MODEL_OP_2(0x82, 0x9D) +#define BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET BLE_MESH_MODEL_OP_1(0x62) +#define BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK BLE_MESH_MODEL_OP_1(0x63) +#define BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS BLE_MESH_MODEL_OP_1(0x64) + +#endif /* _MODEL_OPCODE_H_ */ diff --git a/components/bt/ble_mesh/mesh_models/include/sensor_client.h b/components/bt/ble_mesh/mesh_models/include/sensor_client.h new file mode 100644 index 0000000000..2b259a8953 --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/include/sensor_client.h @@ -0,0 +1,167 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @file + * @brief Bluetooth Mesh Sensor Client Model APIs. + */ + +#ifndef _SENSOR_CLIENT_H_ +#define _SENSOR_CLIENT_H_ + +#include "mesh_access.h" +#include "mesh_kernel.h" + +#include "model_common.h" + +/* Sensor Client Model Context */ +extern const struct bt_mesh_model_op sensor_cli_op[]; + +/** @def BLE_MESH_MODEL_SENSOR_CLI + * + * Define a new sensor client model. Note that this API needs to + * be repeated for each element which the application wants to + * have a sensor client model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_sensor_cli. + * + * @return New sensor client model instance. + */ +#define BLE_MESH_MODEL_SENSOR_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_SENSOR_CLI, \ + sensor_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_sensor_client_t; +typedef bt_mesh_internal_data_t sensor_internal_data_t; + +struct bt_mesh_sensor_descriptor_status { + struct net_buf_simple *descriptor; /* Sequence of 8-octet sensor descriptors (optional) */ +}; + +struct bt_mesh_sensor_cadence_status { + u16_t property_id; /* Property for the sensor */ + struct net_buf_simple *sensor_cadence_value; /* Value of sensor cadence state */ +}; + +struct bt_mesh_sensor_settings_status { + u16_t sensor_property_id; /* Property ID identifying a sensor */ + struct net_buf_simple *sensor_setting_property_ids; /* A sequence of N sensor setting property IDs (optional) */ +}; + +struct bt_mesh_sensor_setting_status { + bool op_en; /* Indicate whether optional parameters included */ + u16_t sensor_property_id; /* Property ID identifying a sensor */ + u16_t sensor_setting_property_id; /* Setting ID identifying a setting within a sensor */ + u8_t sensor_setting_access; /* Read/Write access rights for the setting (optional) */ + struct net_buf_simple *sensor_setting_raw; /* Raw value for the setting */ +}; + +struct bt_mesh_sensor_status { + struct net_buf_simple *marshalled_sensor_data; /* Value of sensor data state (optional) */ +}; + +struct bt_mesh_sensor_column_status { + u16_t property_id; /* Property identifying a sensor and the Y axis */ + struct net_buf_simple *sensor_column_value; /* Left values of sensor column status */ +}; + +struct bt_mesh_sensor_series_status { + u16_t property_id; /* Property identifying a sensor and the Y axis */ + struct net_buf_simple *sensor_series_value; /* Left values of sensor series status */ +}; + +struct bt_mesh_sensor_descriptor_get { + bool op_en; /* Indicate whether optional parameters included */ + u16_t property_id; /* Property ID for the sensor (optional) */ +}; + +struct bt_mesh_sensor_cadence_get { + u16_t property_id; /* Property ID for the sensor */ +}; + +struct bt_mesh_sensor_cadence_set { + u16_t property_id; /* Property ID for the sensor */ + u8_t fast_cadence_period_divisor : 7, /* Divisor for the publish period */ + status_trigger_type : 1; /* The unit and format of the Status Trigger Delta fields */ + struct net_buf_simple *status_trigger_delta_down; /* Delta down value that triggers a status message */ + struct net_buf_simple *status_trigger_delta_up; /* Delta up value that triggers a status message */ + u8_t status_min_interval; /* Minimum interval between two consecutive Status messages */ + struct net_buf_simple *fast_cadence_low; /* Low value for the fast cadence range */ + struct net_buf_simple *fast_cadence_high; /* Fast value for the fast cadence range */ +}; + +struct bt_mesh_sensor_settings_get { + u16_t sensor_property_id; /* Property ID for the sensor */ +}; + +struct bt_mesh_sensor_setting_get { + u16_t sensor_property_id; /* Property ID identifying a sensor */ + u16_t sensor_setting_property_id; /* Setting ID identifying a setting within a sensor */ +}; + +struct bt_mesh_sensor_setting_set { + u16_t sensor_property_id; /* Property ID identifying a sensor */ + u16_t sensor_setting_property_id; /* Setting ID identifying a setting within a sensor */ + struct net_buf_simple *sensor_setting_raw; /* Raw value for the setting */ +}; + +struct bt_mesh_sensor_get { + bool op_en; /* Indicate whether optional parameters included */ + u16_t property_id; /* Property ID for the sensor (optional) */ +}; + +struct bt_mesh_sensor_column_get { + u16_t property_id; /* Property identifying a sensor */ + struct net_buf_simple *raw_value_x; /* Raw value identifying a column */ +}; + +struct bt_mesh_sensor_series_get { + bool op_en; /* Indicate whether optional parameters included */ + u16_t property_id; /* Property identifying a sensor */ + struct net_buf_simple *raw_value_x1; /* Raw value identifying a starting column (optional) */ + struct net_buf_simple *raw_value_x2; /* Raw value identifying a ending column (C.1) */ +}; + +/** + * @brief This function is called to initialize sensor client model user_data. + * + * @param[in] model: Pointer to sensor client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_sensor_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to get sensor states. + * + * @param[in] common: Message common information structure + * @param[in] get: Pointer of sensor get message value + * @param[out] status: Pointer of sensor status message value + * + * @return Zero-success, other-fail + */ +int bt_mesh_sensor_client_get_state(struct bt_mesh_common_param *common, void *get, void *status); + +/** + * @brief This function is called to set sensor states. + * + * @param[in] common: Message common information structure + * @param[in] set: Pointer of sensor set message value + * @param[out] status: Pointer of sensor status message value + * + * @return Zero-success, other-fail + */ +int bt_mesh_sensor_client_set_state(struct bt_mesh_common_param *common, void *set, void *status); + +#endif /* _SENSOR_CLIENT_H_ */ diff --git a/components/bt/ble_mesh/mesh_models/include/time_scene_client.h b/components/bt/ble_mesh/mesh_models/include/time_scene_client.h new file mode 100644 index 0000000000..baa0d4abc0 --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/include/time_scene_client.h @@ -0,0 +1,257 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @file + * @brief Bluetooth Mesh Time and Scene Client Model APIs. + */ + +#ifndef _TIME_SCENE_CLIENT_H_ +#define _TIME_SCENE_CLIENT_H_ + +#include "mesh_access.h" +#include "mesh_kernel.h" + +#include "model_common.h" + +/* Time scene client model common structure */ +typedef bt_mesh_client_common_t bt_mesh_time_scene_client_t; +typedef bt_mesh_internal_data_t time_scene_internal_data_t; + +/* Time Client Model Context */ +extern const struct bt_mesh_model_op time_cli_op[]; + +/** @def BLE_MESH_MODEL_TIME_CLI + * + * Define a new time client model. Note that this API needs to + * be repeated for each element which the application wants to + * have a time model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_time_cli. + * + * @return New time client model instance. + */ +#define BLE_MESH_MODEL_TIME_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_TIME_CLI, \ + time_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_time_cli_t; + +struct bt_mesh_time_status { + u8_t tai_seconds[5]; /* The current TAI time in seconds */ + u8_t sub_second; /* The sub-second time in units of 1/256 second */ + u8_t uncertainty; /* The estimated uncertainty in 10-millisecond steps */ + u16_t time_authority : 1; /* 0 = No Time Authority, 1 = Time Authority */ + u16_t tai_utc_delta : 15; /* Current difference between TAI and UTC in seconds */ + u8_t time_zone_offset; /* The local time zone offset in 15-minute increments */ +}; + +struct bt_mesh_time_zone_status { + u8_t time_zone_offset_curr; /* Current local time zone offset */ + u8_t time_zone_offset_new; /* Upcoming local time zone offset */ + u8_t tai_zone_change[5]; /* TAI Seconds time of the upcoming Time Zone Offset change */ +}; + +struct bt_mesh_tai_utc_delta_status { + u16_t tai_utc_delta_curr : 15; /* Current difference between TAI and UTC in seconds */ + u16_t padding_1 : 1; /* Always 0b0. Other values are Prohibited. */ + u16_t tai_utc_delta_new : 15; /* Upcoming difference between TAI and UTC in seconds */ + u16_t padding_2 : 1; /* Always 0b0. Other values are Prohibited. */ + u8_t tai_delta_change[5]; /* TAI Seconds time of the upcoming TAI-UTC Delta change */ +}; + +struct bt_mesh_time_role_status { + u8_t time_role; /* The Time Role for the element */ +}; + +struct bt_mesh_time_set { + u8_t tai_seconds[5]; /* The current TAI time in seconds */ + u8_t sub_second; /* The sub-second time in units of 1/256 second */ + u8_t uncertainty; /* The estimated uncertainty in 10-millisecond steps */ + u16_t time_authority : 1; /* 0 = No Time Authority, 1 = Time Authority */ + u16_t tai_utc_delta : 15; /* Current difference between TAI and UTC in seconds */ + u8_t time_zone_offset; /* The local time zone offset in 15-minute increments */ +}; + +struct bt_mesh_time_zone_set { + u8_t time_zone_offset_new; /* Upcoming local time zone offset */ + u8_t tai_zone_change[5]; /* TAI Seconds time of the upcoming Time Zone Offset change */ +}; + +struct bt_mesh_tai_utc_delta_set { + u16_t tai_utc_delta_new : 15; /* Upcoming difference between TAI and UTC in seconds */ + u16_t padding : 1; /* Always 0b0. Other values are Prohibited. */ + u8_t tai_delta_change[5]; /* TAI Seconds time of the upcoming TAI-UTC Delta change */ +}; + +struct bt_mesh_time_role_set { + u8_t time_role; /* The Time Role for the element */ +}; + +/* Scene Client Model Context */ +extern const struct bt_mesh_model_op scene_cli_op[]; + +/** @def BLE_MESH_MODEL_SCENE_CLI + * + * Define a new scene client model. Note that this API needs to + * be repeated for each element which the application wants to + * have a scene model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_scene_cli. + * + * @return New scene client model instance. + */ +#define BLE_MESH_MODEL_SCENE_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_SCENE_CLI, \ + scene_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_scene_cli_t; + +struct bt_mesh_scene_status { + bool op_en; /* Indicate whether optional parameters included */ + u8_t status_code; /* Status code for the last operation */ + u16_t current_scene; /* Scene Number of a current scene */ + u16_t target_scene; /* Scene Number of a target scene (optional) */ + u8_t remain_time; /* Time to complete state transition (C.1) */ +}; + +struct bt_mesh_scene_register_status { + u8_t status_code; /* Status code for the previous operation */ + u16_t current_scene; /* Scene Number of a current scene */ + struct net_buf_simple *scenes; /* A list of scenes stored within an element */ +}; + +struct bt_mesh_scene_store { + u16_t scene_number; /* The number of the scene to be stored */ +}; + +struct bt_mesh_scene_recall { + bool op_en; /* Indicate whether optional parameters included */ + u16_t scene_number; /* The number of the scene to be recalled */ + u8_t tid; /* Transaction Identifier */ + u8_t trans_time; /* Time to complete state transition (optional) */ + u8_t delay; /* Indicate message execution delay (C.1) */ +}; + +struct bt_mesh_scene_delete { + u16_t scene_number; /* The number of the scene to be deleted */ +}; + +/* Scheduler Client Model Context */ +extern const struct bt_mesh_model_op scheduler_cli_op[]; + +/** @def BLE_MESH_MODEL_SCHEDULER_CLI + * + * Define a new scheduler client model. Note that this API needs to + * be repeated for each element which the application wants to + * have a scheduler model on. + * @param cli_pub Pointer to a unique struct bt_mesh_model_pub. + * @param cli_data Pointer to a unique struct bt_mesh_scheduler_cli. + * + * @return New scheduler client model instance. + */ +#define BLE_MESH_MODEL_SCHEDULER_CLI(cli_pub, cli_data) \ + BLE_MESH_MODEL(BLE_MESH_MODEL_ID_SCHEDULER_CLI, \ + scheduler_cli_op, cli_pub, cli_data) + +typedef bt_mesh_client_common_t bt_mesh_scheduler_cli_t; + +struct bt_mesh_scheduler_status { + u16_t schedules; /* Bit field indicating defined Actions in the Schedule Register */ +}; + +struct bt_mesh_scheduler_act_status { + u64_t index : 4; /* Enumerates (selects) a Schedule Register entry */ + u64_t year : 7; /* Scheduled year for the action */ + u64_t month : 12; /* Scheduled month for the action */ + u64_t day : 5; /* Scheduled day of the month for the action */ + u64_t hour : 5; /* Scheduled hour for the action */ + u64_t minute : 6; /* Scheduled minute for the action */ + u64_t second : 6; /* Scheduled second for the action */ + u64_t day_of_week : 7; /* Schedule days of the week for the action */ + u64_t action : 4; /* Action to be performed at the scheduled time */ + u64_t trans_time : 8; /* Transition time for this action */ + u16_t scene_number; /* Transition time for this action */ +}; + +struct bt_mesh_scheduler_act_get { + u8_t index; /* Index of the Schedule Register entry to get */ +}; + +struct bt_mesh_scheduler_act_set { + u64_t index : 4; /* Index of the Schedule Register entry to set */ + u64_t year : 7; /* Scheduled year for the action */ + u64_t month : 12; /* Scheduled month for the action */ + u64_t day : 5; /* Scheduled day of the month for the action */ + u64_t hour : 5; /* Scheduled hour for the action */ + u64_t minute : 6; /* Scheduled minute for the action */ + u64_t second : 6; /* Scheduled second for the action */ + u64_t day_of_week : 7; /* Schedule days of the week for the action */ + u64_t action : 4; /* Action to be performed at the scheduled time */ + u64_t trans_time : 8; /* Transition time for this action */ + u16_t scene_number; /* Transition time for this action */ +}; + +/** + * @brief This function is called to initialize time client model user_data. + * + * @param[in] model: Pointer to time client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_time_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize scene client model user_data. + * + * @param[in] model: Pointer to scene client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_scene_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to initialize scheduler client model user_data. + * + * @param[in] model: Pointer to scheduler client model + * @param[in] primary: Whether belongs to primary element + * + * @return Zero-success, other-fail + */ +int bt_mesh_scheduler_cli_init(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to get scene states. + * + * @param[in] common: Message common information structure + * @param[in] get: Pointer of time scene get message value + * @param[out] status: Pointer of time scene status message value + * + * @return Zero-success, other-fail + */ +int bt_mesh_time_scene_client_get_state(struct bt_mesh_common_param *common, void *get, void *status); + +/** + * @brief This function is called to set scene states. + * + * @param[in] common: Message common information structure + * @param[in] set: Pointer of time scene set message value + * @param[out] status: Pointer of time scene status message value + * + * @return Zero-success, other-fail + */ +int bt_mesh_time_scene_client_set_state(struct bt_mesh_common_param *common, void *set, void *status); + +#endif /* _TIME_SCENE_CLIENT_H_ */ diff --git a/components/bt/ble_mesh/mesh_models/lighting_client.c b/components/bt/ble_mesh/mesh_models/lighting_client.c new file mode 100644 index 0000000000..3aea99a8a4 --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/lighting_client.c @@ -0,0 +1,1400 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "osi/allocator.h" +#include "sdkconfig.h" + +#include "mesh_types.h" +#include "mesh_kernel.h" +#include "mesh_trace.h" + +#include "mesh.h" +#include "model_opcode.h" +#include "mesh_common.h" +#include "lighting_client.h" + +#include "btc_ble_mesh_lighting_model.h" + +/** The following are the macro definitions of lighting client + * model messages length, and a message is composed of three + * parts: Opcode + msg_value + MIC + */ +/* Light lightness client messages length */ +#define BLE_MESH_LIGHT_LIGHTNESS_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_LIGHTNESS_SET_MSG_LEN (2 + 5 + 4) +#define BLE_MESH_LIGHT_LIGHTNESS_LINEAR_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_LIGHTNESS_LINEAR_SET_MSG_LEN (2 + 5 + 4) +#define BLE_MESH_LIGHT_LIGHTNESS_LAST_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_LIGHTNESS_DEFAULT_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_LIGHTNESS_DEFAULT_SET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_LIGHT_LIGHTNESS_RANGE_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_LIGHTNESS_RANGE_SET_MSG_LEN (2 + 4 + 4) + +/* Light CTL client messages length */ +#define BLE_MESH_LIGHT_CTL_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_CTL_SET_MSG_LEN (2 + 9 + 4) +#define BLE_MESH_LIGHT_CTL_TEMPERATURE_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_CTL_TEMPERATURE_SET_MSG_LEN (2 + 7 + 4) +#define BLE_MESH_LIGHT_CTL_TEMPERATURE_RANGE_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_CTL_TEMPERATURE_RANGE_SET_MSG_LEN (2 + 4 + 4) +#define BLE_MESH_LIGHT_CTL_DEFAULT_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_CTL_DEFAULT_SET_MSG_LEN (2 + 6 + 4) + +/* Light HSL client messages length */ +#define BLE_MESH_LIGHT_HSL_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_HSL_SET_MSG_LEN (2 + 9 + 4) +#define BLE_MESH_LIGHT_HSL_TARGET_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_HSL_HUE_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_HSL_HUE_SET_MSG_LEN (2 + 5 + 4) +#define BLE_MESH_LIGHT_HSL_SATURATION_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_HSL_SATURATION_SET_MSG_LEN (2 + 5 + 4) +#define BLE_MESH_LIGHT_HSL_DEFAULT_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_HSL_DEFAULT_SET_MSG_LEN (2 + 6 + 4) +#define BLE_MESH_LIGHT_HSL_RANGE_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_LIGHT_HSL_RANGE_SET_MSG_LEN (2 + 8 + 4) + +/* Light xyL client messages length */ +#define BLE_MESH_LIGHT_XYL_SET_MSG_LEN (2 + 9 + 4) +#define BLE_MESH_LIGHT_XYL_DEFAULT_SET_MSG_LEN (2 + 6 + 4) +#define BLE_MESH_LIGHT_XYL_RANGE_SET_MSG_LEN (2 + 8 + 4) + +/* Light LC client messages length */ +#define BLE_MESH_LIGHT_LC_MODE_SET_MSG_LEN (2 + 1 + 4) +#define BLE_MESH_LIGHT_LC_OM_SET_MSG_LEN (2 + 1 + 4) +#define BLE_MESH_LIGHT_LC_LIGHT_ONOFF_SET_MSG_LEN (2 + 4 + 4) +#define BLE_MESH_LIGHT_LC_PROPERTY_GET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_LIGHT_LC_PROPERTY_SET_MSG_LEN /* variable */ + +#define BLE_MESH_LIGHT_GET_STATE_MSG_LEN (2 + 2 + 4) + +static const bt_mesh_client_op_pair_t light_op_pair[] = { + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_GET, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_GET, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_GET, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_GET, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_GET, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_GET, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_SET, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_GET, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_GET, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_GET, BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET, BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_GET, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_SET, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_GET, BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_GET, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_GET, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_GET, BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET, BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_GET, BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET, BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_GET, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_SET, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_GET, BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_GET, BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET, BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_GET, BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET, BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LC_MODE_GET, BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET, BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LC_OM_GET, BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET, BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_GET, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET, BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS }, + { BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET, BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS }, +}; + +static void timeout_handler(struct k_work *work) +{ + light_internal_data_t *internal = NULL; + bt_mesh_light_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + + BT_WARN("Receive light status message timeout"); + + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (!node || !node->ctx.model) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + client = (bt_mesh_light_client_t *)node->ctx.model->user_data; + if (!client) { + BT_ERR("%s, Lighting Client user_data is NULL", __func__); + return; + } + + internal = (light_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Lighting Client internal_data is NULL", __func__); + return; + } + + bt_mesh_callback_light_status_to_btc(node->opcode, 0x03, node->ctx.model, + &node->ctx, NULL, 0); + + bt_mesh_client_free_node(&internal->queue, node); + + return; +} + +static void light_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + light_internal_data_t *internal = NULL; + bt_mesh_light_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + u8_t *val = NULL; + u8_t evt = 0xFF; + u32_t rsp = 0; + size_t len = 0; + + BT_DBG("%s, len %d, bytes %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + client = (bt_mesh_light_client_t *)model->user_data; + if (!client) { + BT_ERR("%s, Lighting Client user_data is NULL", __func__); + return; + } + + internal = (light_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Lighting Client internal_data is NULL", __func__); + return; + } + + rsp = ctx->recv_op; + + switch (rsp) { + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS: { + struct bt_mesh_light_lightness_status *status = NULL; + if (buf->len != 2 && buf->len != 5) { + BT_ERR("%s, Invalid Light Lightness Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_lightness_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_lightness = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->target_lightness = net_buf_simple_pull_le16(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_lightness_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS: { + struct bt_mesh_light_lightness_linear_status *status = NULL; + if (buf->len != 2 && buf->len != 5) { + BT_ERR("%s, Invalid Light Lightness Linear Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_lightness_linear_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_lightness = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->target_lightness = net_buf_simple_pull_le16(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_lightness_linear_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS: { + struct bt_mesh_light_lightness_last_status *status = NULL; + if (buf->len != 2) { + BT_ERR("%s, Invalid Light Lightness Last Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_lightness_last_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->lightness = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_lightness_last_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS: { + struct bt_mesh_light_lightness_default_status *status = NULL; + if (buf->len != 2) { + BT_ERR("%s, Invalid Light Lightness Default Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_lightness_default_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->lightness = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_lightness_default_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS: { + struct bt_mesh_light_lightness_range_status *status = NULL; + if (buf->len != 5) { + BT_ERR("%s, Invalid Light Lightness Range Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_lightness_range_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->status_code = net_buf_simple_pull_u8(buf); + status->range_min = net_buf_simple_pull_le16(buf); + status->range_max = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_lightness_range_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS: { + struct bt_mesh_light_ctl_status *status = NULL; + if (buf->len != 4 && buf->len != 9) { + BT_ERR("%s, Invalid Light CTL Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_ctl_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_ctl_lightness = net_buf_simple_pull_le16(buf); + status->present_ctl_temperature = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->target_ctl_lightness = net_buf_simple_pull_le16(buf); + status->target_ctl_temperature = net_buf_simple_pull_le16(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_ctl_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS: { + struct bt_mesh_light_ctl_temperature_status *status = NULL; + if (buf->len != 4 && buf->len != 9) { + BT_ERR("%s, Invalid Light CTL Temperature Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_ctl_temperature_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_ctl_temperature = net_buf_simple_pull_le16(buf); + status->present_ctl_delta_uv = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->target_ctl_temperature = net_buf_simple_pull_le16(buf); + status->target_ctl_delta_uv = net_buf_simple_pull_le16(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_ctl_temperature_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS: { + struct bt_mesh_light_ctl_temperature_range_status *status = NULL; + if (buf->len != 5) { + BT_ERR("%s, Invalid Light CTL Temperature Range Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_ctl_temperature_range_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->status_code = net_buf_simple_pull_u8(buf); + status->range_min = net_buf_simple_pull_le16(buf); + status->range_max = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_ctl_temperature_range_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS: { + struct bt_mesh_light_ctl_default_status *status = NULL; + if (buf->len != 6) { + BT_ERR("%s, Invalid Light CTL Default Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_ctl_default_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->lightness = net_buf_simple_pull_le16(buf); + status->temperature = net_buf_simple_pull_le16(buf); + status->delta_uv = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_ctl_default_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS: { + struct bt_mesh_light_hsl_status *status = NULL; + if (buf->len != 6 && buf->len != 7) { + BT_ERR("%s, Invalid Light HSL Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_hsl_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->hsl_lightness = net_buf_simple_pull_le16(buf); + status->hsl_hue = net_buf_simple_pull_le16(buf); + status->hsl_saturation = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_hsl_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS: { + struct bt_mesh_light_hsl_target_status *status = NULL; + if (buf->len != 6 && buf->len != 7) { + BT_ERR("%s, Invalid Light HSL Target Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_hsl_target_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->hsl_lightness_target = net_buf_simple_pull_le16(buf); + status->hsl_hue_target = net_buf_simple_pull_le16(buf); + status->hsl_saturation_target = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_hsl_target_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS: { + struct bt_mesh_light_hsl_hue_status *status = NULL; + if (buf->len != 2 && buf->len != 5) { + BT_ERR("%s, Invalid Light HSL Hue Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_hsl_hue_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_hue = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->target_hue = net_buf_simple_pull_le16(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_hsl_hue_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS: { + struct bt_mesh_light_hsl_saturation_status *status = NULL; + if (buf->len != 2 && buf->len != 5) { + BT_ERR("%s, Invalid Light HSL Saturation Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_hsl_saturation_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_saturation = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->target_saturation = net_buf_simple_pull_le16(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_hsl_saturation_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS: { + struct bt_mesh_light_hsl_default_status *status = NULL; + if (buf->len != 6) { + BT_ERR("%s, Invalid Light HSL Default Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_hsl_default_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->lightness = net_buf_simple_pull_le16(buf); + status->hue = net_buf_simple_pull_le16(buf); + status->saturation = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_hsl_default_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS: { + struct bt_mesh_light_hsl_range_status *status = NULL; + if (buf->len != 9) { + BT_ERR("%s, Invalid Light HSL Range Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_hsl_range_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->status_code = net_buf_simple_pull_u8(buf); + status->hue_range_min = net_buf_simple_pull_le16(buf); + status->hue_range_max = net_buf_simple_pull_le16(buf); + status->saturation_range_min = net_buf_simple_pull_le16(buf); + status->saturation_range_max = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_hsl_range_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS: { + struct bt_mesh_light_xyl_status *status = NULL; + if (buf->len != 6 && buf->len != 7) { + BT_ERR("%s, Invalid Light xyL Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_xyl_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->xyl_lightness = net_buf_simple_pull_le16(buf); + status->xyl_x = net_buf_simple_pull_le16(buf); + status->xyl_y = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_xyl_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS: { + struct bt_mesh_light_xyl_target_status *status = NULL; + if (buf->len != 6 && buf->len != 7) { + BT_ERR("%s, Invalid Light xyL Target Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_xyl_target_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->target_xyl_lightness = net_buf_simple_pull_le16(buf); + status->target_xyl_x = net_buf_simple_pull_le16(buf); + status->target_xyl_y = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_xyl_target_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS: { + struct bt_mesh_light_xyl_default_status *status = NULL; + if (buf->len != 6) { + BT_ERR("%s, Invalid Light xyL Default Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_xyl_default_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->lightness = net_buf_simple_pull_le16(buf); + status->xyl_x = net_buf_simple_pull_le16(buf); + status->xyl_y = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_xyl_default_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS: { + struct bt_mesh_light_xyl_range_status *status = NULL; + if (buf->len != 9) { + BT_ERR("%s, Invalid Light xyL Range Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_xyl_range_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->status_code = net_buf_simple_pull_u8(buf); + status->xyl_x_range_min = net_buf_simple_pull_le16(buf); + status->xyl_x_range_max = net_buf_simple_pull_le16(buf); + status->xyl_y_range_min = net_buf_simple_pull_le16(buf); + status->xyl_y_range_max = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_xyl_range_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS: { + struct bt_mesh_light_lc_mode_status *status = NULL; + if (buf->len != 1) { + BT_ERR("%s, Invalid Light LC Mode Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_lc_mode_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->mode = net_buf_simple_pull_u8(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_lc_mode_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS: { + struct bt_mesh_light_lc_om_status *status = NULL; + if (buf->len != 1) { + BT_ERR("%s, Invalid Light LC OM Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_lc_om_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->mode = net_buf_simple_pull_u8(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_lc_om_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS: { + struct bt_mesh_light_lc_light_onoff_status *status = NULL; + if (buf->len != 1 && buf->len != 3) { + BT_ERR("%s, Invalid Light LC Light OnOff Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_light_lc_light_onoff_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->present_light_onoff = net_buf_simple_pull_u8(buf); + if (buf->len) { + status->op_en = true; + status->target_light_onoff = net_buf_simple_pull_u8(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_lc_light_onoff_status); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS: { + struct bt_mesh_light_lc_property_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_light_lc_property_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->light_lc_property_id = net_buf_simple_pull_le16(buf); + status->light_lc_property_value = bt_mesh_alloc_buf(buf->len); + if (!status->light_lc_property_value) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->light_lc_property_value, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_light_lc_property_status); + break; + } + default: + BT_ERR("%s, Not a Lighting Status message opcode", __func__); + return; + } + + buf->data = val; + buf->len = len; + node = bt_mesh_is_model_message_publish(model, ctx, buf, true); + if (!node) { + BT_DBG("Unexpected light status message 0x%x", rsp); + } else { + switch (node->opcode) { + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_GET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_GET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_GET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_GET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_GET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_GET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_GET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_GET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_GET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_GET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_GET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_GET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_GET: + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_GET: + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_GET: + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_GET: + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET: + evt = 0x00; + break; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_SET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_SET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET: + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET: + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET: + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET: + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET: + evt = 0x01; + break; + default: + break; + } + + bt_mesh_callback_light_status_to_btc(node->opcode, evt, model, ctx, val, len); + // Don't forget to release the node at the end. + bt_mesh_client_free_node(&internal->queue, node); + } + + switch (rsp) { + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS: { + struct bt_mesh_light_lc_property_status *status; + status = (struct bt_mesh_light_lc_property_status *)val; + bt_mesh_free_buf(status->light_lc_property_value); + break; + } + default: + break; + } + + osi_free(val); + + return; +} + +const struct bt_mesh_model_op light_lightness_cli_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS, 2, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS, 2, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS, 2, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS, 2, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS, 5, light_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op light_ctl_cli_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS, 4, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS, 4, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS, 5, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS, 6, light_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op light_hsl_cli_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS, 6, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS, 6, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS, 2, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS, 2, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS, 6, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS, 9, light_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op light_xyl_cli_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS, 6, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS, 6, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS, 6, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS, 9, light_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op light_lc_cli_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS, 1, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS, 1, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS, 1, light_status }, + { BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS, 2, light_status }, + BLE_MESH_MODEL_OP_END, +}; + +static int light_get_state(struct bt_mesh_common_param *common, void *value) +{ + NET_BUF_SIMPLE_DEFINE(msg, BLE_MESH_LIGHT_GET_STATE_MSG_LEN); + int err; + + bt_mesh_model_msg_init(&msg, common->opcode); + + if (value) { + switch (common->opcode) { + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET: { + struct bt_mesh_light_lc_property_get *get; + get = (struct bt_mesh_light_lc_property_get *)value; + net_buf_simple_add_le16(&msg, get->light_lc_property_id); + break; + } + default: + BT_DBG("This lighting message should be sent with NULL get pointer"); + break; + } + } + + err = bt_mesh_client_send_msg(common->model, common->opcode, &common->ctx, &msg, + timeout_handler, common->msg_timeout, true, + common->cb, common->cb_data); + if (err) { + BT_ERR("%s, Failed to send Lighting Client Get message (err %d)", __func__, err); + } + + return err; +} + +static int light_set_state(struct bt_mesh_common_param *common, + void *value, u16_t value_len, bool need_ack) +{ + struct net_buf_simple *msg = NULL; + int err; + + msg = bt_mesh_alloc_buf(value_len); + if (!msg) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + bt_mesh_model_msg_init(msg, common->opcode); + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET_UNACK: { + struct bt_mesh_light_lightness_set *set; + set = (struct bt_mesh_light_lightness_set *)value; + net_buf_simple_add_le16(msg, set->lightness); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET_UNACK: { + struct bt_mesh_light_lightness_linear_set *set; + set = (struct bt_mesh_light_lightness_linear_set *)value; + net_buf_simple_add_le16(msg, set->lightness); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET_UNACK: { + struct bt_mesh_light_lightness_default_set *set; + set = (struct bt_mesh_light_lightness_default_set *)value; + net_buf_simple_add_le16(msg, set->lightness); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET_UNACK: { + struct bt_mesh_light_lightness_range_set *set; + set = (struct bt_mesh_light_lightness_range_set *)value; + net_buf_simple_add_le16(msg, set->range_min); + net_buf_simple_add_le16(msg, set->range_max); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_SET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_SET_UNACK: { + struct bt_mesh_light_ctl_set *set; + set = (struct bt_mesh_light_ctl_set *)value; + net_buf_simple_add_le16(msg, set->ctl_lightness); + net_buf_simple_add_le16(msg, set->ctl_temperature); + net_buf_simple_add_le16(msg, set->ctl_delta_uv); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET_UNACK: { + struct bt_mesh_light_ctl_temperature_set *set; + set = (struct bt_mesh_light_ctl_temperature_set *)value; + net_buf_simple_add_le16(msg, set->ctl_temperature); + net_buf_simple_add_le16(msg, set->ctl_delta_uv); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET_UNACK: { + struct bt_mesh_light_ctl_temperature_range_set *set; + set = (struct bt_mesh_light_ctl_temperature_range_set *)value; + net_buf_simple_add_le16(msg, set->range_min); + net_buf_simple_add_le16(msg, set->range_max); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET_UNACK: { + struct bt_mesh_light_ctl_default_set *set; + set = (struct bt_mesh_light_ctl_default_set *)value; + net_buf_simple_add_le16(msg, set->lightness); + net_buf_simple_add_le16(msg, set->temperature); + net_buf_simple_add_le16(msg, set->delta_uv); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_SET_UNACK: { + struct bt_mesh_light_hsl_set *set; + set = (struct bt_mesh_light_hsl_set *)value; + net_buf_simple_add_le16(msg, set->hsl_lightness); + net_buf_simple_add_le16(msg, set->hsl_hue); + net_buf_simple_add_le16(msg, set->hsl_saturation); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET_UNACK: { + struct bt_mesh_light_hsl_hue_set *set; + set = (struct bt_mesh_light_hsl_hue_set *)value; + net_buf_simple_add_le16(msg, set->hue); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET_UNACK: { + struct bt_mesh_light_hsl_saturation_set *set; + set = (struct bt_mesh_light_hsl_saturation_set *)value; + net_buf_simple_add_le16(msg, set->saturation); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET_UNACK: { + struct bt_mesh_light_hsl_default_set *set; + set = (struct bt_mesh_light_hsl_default_set *)value; + net_buf_simple_add_le16(msg, set->lightness); + net_buf_simple_add_le16(msg, set->hue); + net_buf_simple_add_le16(msg, set->saturation); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET_UNACK: { + struct bt_mesh_light_hsl_range_set *set; + set = (struct bt_mesh_light_hsl_range_set *)value; + net_buf_simple_add_le16(msg, set->hue_range_min); + net_buf_simple_add_le16(msg, set->hue_range_max); + net_buf_simple_add_le16(msg, set->saturation_range_min); + net_buf_simple_add_le16(msg, set->saturation_range_max); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_SET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_SET_UNACK: { + struct bt_mesh_light_xyl_set *set; + set = (struct bt_mesh_light_xyl_set *)value; + net_buf_simple_add_le16(msg, set->xyl_lightness); + net_buf_simple_add_le16(msg, set->xyl_x); + net_buf_simple_add_le16(msg, set->xyl_y); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET_UNACK: { + struct bt_mesh_light_xyl_default_set *set; + set = (struct bt_mesh_light_xyl_default_set *)value; + net_buf_simple_add_le16(msg, set->lightness); + net_buf_simple_add_le16(msg, set->xyl_x); + net_buf_simple_add_le16(msg, set->xyl_y); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET_UNACK: { + struct bt_mesh_light_xyl_range_set *set; + set = (struct bt_mesh_light_xyl_range_set *)value; + net_buf_simple_add_le16(msg, set->xyl_x_range_min); + net_buf_simple_add_le16(msg, set->xyl_x_range_max); + net_buf_simple_add_le16(msg, set->xyl_y_range_min); + net_buf_simple_add_le16(msg, set->xyl_y_range_max); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET: + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET_UNACK: { + struct bt_mesh_light_lc_mode_set *set; + set = (struct bt_mesh_light_lc_mode_set *)value; + net_buf_simple_add_u8(msg, set->mode); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET: + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET_UNACK: { + struct bt_mesh_light_lc_om_set *set; + set = (struct bt_mesh_light_lc_om_set *)value; + net_buf_simple_add_u8(msg, set->mode); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET: + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET_UNACK: { + struct bt_mesh_light_lc_light_onoff_set *set; + set = (struct bt_mesh_light_lc_light_onoff_set *)value; + net_buf_simple_add_u8(msg, set->light_onoff); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET: + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK: { + struct bt_mesh_light_lc_property_set *set; + set = (struct bt_mesh_light_lc_property_set *)value; + net_buf_simple_add_le16(msg, set->light_lc_property_id); + net_buf_simple_add_mem(msg, set->light_lc_property_value->data, set->light_lc_property_value->len); + break; + } + default: + BT_ERR("%s, Not a Lighting Client Set message opcode", __func__); + err = -EINVAL; + goto end; + } + + err = bt_mesh_client_send_msg(common->model, common->opcode, &common->ctx, msg, + timeout_handler, common->msg_timeout, need_ack, + common->cb, common->cb_data); + if (err) { + BT_ERR("%s, Failed to send Lighting Client Set message (err %d)", __func__, err); + } + +end: + bt_mesh_free_buf(msg); + + return err; +} + +int bt_mesh_light_client_get_state(struct bt_mesh_common_param *common, void *get, void *status) +{ + bt_mesh_light_client_t *client = NULL; + + if (!common || !common->model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_light_client_t *)common->model->user_data; + if (!client || !client->internal_data) { + BT_ERR("%s, Lighting Client user data is NULL", __func__); + return -EINVAL; + } + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_GET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_GET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_GET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_GET: + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_GET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_GET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_GET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_GET: + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_GET: + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_GET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_GET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_GET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_GET: + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_GET: + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_GET: + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_GET: + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_GET: + break; + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET: + if (!get) { + BT_ERR("%s, Lighting lc_property_get is NULL", __func__); + return -EINVAL; + } + break; + default: + BT_ERR("%s, Not a Lighting Client Get message opcode", __func__); + return -EINVAL; + } + + return light_get_state(common, get); +} + +int bt_mesh_light_client_set_state(struct bt_mesh_common_param *common, void *set, void *status) +{ + bt_mesh_light_client_t *client = NULL; + u16_t length = 0; + bool need_ack = false; + + if (!common || !common->model || !set) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_light_client_t *)common->model->user_data; + if (!client || !client->internal_data) { + BT_ERR("%s, Lighting Client user data is NULL", __func__); + return -EINVAL; + } + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET_UNACK: { + struct bt_mesh_light_lightness_set *value; + value = (struct bt_mesh_light_lightness_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Light Lightness Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_LIGHT_LIGHTNESS_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET_UNACK: { + struct bt_mesh_light_lightness_linear_set *value; + value = (struct bt_mesh_light_lightness_linear_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Light Lightness Linear Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_LIGHT_LIGHTNESS_LINEAR_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET_UNACK: + length = BLE_MESH_LIGHT_LIGHTNESS_DEFAULT_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET_UNACK: { + struct bt_mesh_light_lightness_range_set *value; + value = (struct bt_mesh_light_lightness_range_set *)set; + if (value->range_min > value->range_max) { + BT_ERR("%s, Light Lightness Range Set range min is greater than range max", __func__); + return -EINVAL; + } + length = BLE_MESH_LIGHT_LIGHTNESS_RANGE_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_CTL_SET_UNACK: { + struct bt_mesh_light_ctl_set *value; + value = (struct bt_mesh_light_ctl_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Light CTL Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_LIGHT_CTL_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET_UNACK: { + struct bt_mesh_light_ctl_temperature_set *value; + value = (struct bt_mesh_light_ctl_temperature_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Light CTL Temperature Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_LIGHT_CTL_TEMPERATURE_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET_UNACK: { + struct bt_mesh_light_ctl_temperature_range_set *value; + value = (struct bt_mesh_light_ctl_temperature_range_set *)set; + if (value->range_min > value->range_max) { + BT_ERR("%s, Light CTL Temperature Range Set range min is greater than range max", __func__); + return -EINVAL; + } + length = BLE_MESH_LIGHT_CTL_TEMPERATURE_RANGE_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET_UNACK: + length = BLE_MESH_LIGHT_CTL_DEFAULT_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_LIGHT_HSL_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_HSL_SET_UNACK: { + struct bt_mesh_light_hsl_set *value; + value = (struct bt_mesh_light_hsl_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Light HSL Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_LIGHT_HSL_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET_UNACK: { + struct bt_mesh_light_hsl_hue_set *value; + value = (struct bt_mesh_light_hsl_hue_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Light HSL Hue Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_LIGHT_HSL_HUE_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET_UNACK: { + struct bt_mesh_light_hsl_saturation_set *value; + value = (struct bt_mesh_light_hsl_saturation_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Light HSL Saturation Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_LIGHT_HSL_SATURATION_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET_UNACK: + length = BLE_MESH_LIGHT_HSL_DEFAULT_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET_UNACK: { + struct bt_mesh_light_hsl_range_set *value; + value = (struct bt_mesh_light_hsl_range_set *)set; + if (value->hue_range_min > value->hue_range_max || + value->saturation_range_min > value->saturation_range_max) { + BT_ERR("%s, Light HSL Range Set range min is greater than range max", __func__); + return -EINVAL; + } + length = BLE_MESH_LIGHT_HSL_RANGE_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_XYL_SET_UNACK: { + struct bt_mesh_light_xyl_set *value; + value = (struct bt_mesh_light_xyl_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Light xyL Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_LIGHT_XYL_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET_UNACK: + length = BLE_MESH_LIGHT_XYL_DEFAULT_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET_UNACK: { + struct bt_mesh_light_xyl_range_set *value; + value = (struct bt_mesh_light_xyl_range_set *)set; + if (value->xyl_x_range_min > value->xyl_x_range_max || + value->xyl_y_range_min > value->xyl_y_range_max) { + BT_ERR("%s, Light xyL Range Set range min is greater than range max", __func__); + return -EINVAL; + } + length = BLE_MESH_LIGHT_XYL_RANGE_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET_UNACK: + length = BLE_MESH_LIGHT_LC_MODE_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET_UNACK: + length = BLE_MESH_LIGHT_LC_OM_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET_UNACK: { + struct bt_mesh_light_lc_light_onoff_set *value; + value = (struct bt_mesh_light_lc_light_onoff_set *)set; + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Light LC Light OnOff Set transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_LIGHT_LC_LIGHT_ONOFF_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK: { + struct bt_mesh_light_lc_property_set *value; + value = (struct bt_mesh_light_lc_property_set *)set; + if (!value->light_lc_property_value) { + BT_ERR("%s, Lighting light_lc_property_value is NULL", __func__); + return -EINVAL; + } + length = (1 + 2 + value->light_lc_property_value->len + 4); + break; + } + default: + BT_ERR("%s, Not a Lighting Client Set message opcode", __func__); + return -EINVAL; + } + + return light_set_state(common, set, length, need_ack); +} + +static int light_client_init(struct bt_mesh_model *model, bool primary) +{ + light_internal_data_t *internal = NULL; + bt_mesh_light_client_t *client = NULL; + + BT_DBG("primary %u", primary); + + if (!model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_light_client_t *)model->user_data; + if (!client) { + BT_ERR("%s, Lighting Client user_data is NULL", __func__); + return -EINVAL; + } + + /* TODO: call osi_free() when deinit function is invoked*/ + internal = osi_calloc(sizeof(light_internal_data_t)); + if (!internal) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + sys_slist_init(&internal->queue); + + client->model = model; + client->op_pair_size = ARRAY_SIZE(light_op_pair); + client->op_pair = light_op_pair; + client->internal_data = internal; + + return 0; +} + +int bt_mesh_light_lightness_cli_init(struct bt_mesh_model *model, bool primary) +{ + return light_client_init(model, primary); +} + +int bt_mesh_light_ctl_cli_init(struct bt_mesh_model *model, bool primary) +{ + return light_client_init(model, primary); +} + +int bt_mesh_light_hsl_cli_init(struct bt_mesh_model *model, bool primary) +{ + return light_client_init(model, primary); +} + +int bt_mesh_light_xyl_cli_init(struct bt_mesh_model *model, bool primary) +{ + return light_client_init(model, primary); +} + +int bt_mesh_light_lc_cli_init(struct bt_mesh_model *model, bool primary) +{ + return light_client_init(model, primary); +} \ No newline at end of file diff --git a/components/bt/ble_mesh/mesh_models/mesh_common.c b/components/bt/ble_mesh/mesh_models/mesh_common.c new file mode 100644 index 0000000000..801644ace1 --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/mesh_common.c @@ -0,0 +1,46 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "mesh_common.h" + +struct net_buf_simple *bt_mesh_alloc_buf(u16_t size) +{ + struct net_buf_simple *buf = NULL; + u8_t *data = NULL; + + buf = (struct net_buf_simple *)osi_calloc(sizeof(struct net_buf_simple) + size); + if (!buf) { + BT_ERR("%s, Failed to allocate memory", __func__); + return NULL; + } + + data = (u8_t *)buf + sizeof(struct net_buf_simple); + + buf->data = data; + buf->len = 0; + buf->size = size; + buf->__buf = data; + + return buf; +} + +void bt_mesh_free_buf(struct net_buf_simple *buf) +{ + if (buf) { + osi_free(buf); + } +} diff --git a/components/bt/ble_mesh/mesh_models/model_common.c b/components/bt/ble_mesh/mesh_models/model_common.c new file mode 100644 index 0000000000..c904543de5 --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/model_common.c @@ -0,0 +1,336 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "osi/allocator.h" + +#include "mesh_access.h" +#include "mesh_buf.h" +#include "mesh_slist.h" +#include "mesh_main.h" + +#include "mesh.h" +#include "model_common.h" + +bt_mesh_client_node_t *bt_mesh_is_model_message_publish(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, + bool need_pub) +{ + bt_mesh_internal_data_t *data = NULL; + bt_mesh_client_common_t *cli = NULL; + bt_mesh_client_node_t *node = NULL; + u32_t rsp; + + if (!model || !ctx || !buf) { + BT_ERR("%s, Invalid parameter", __func__); + return NULL; + } + + cli = (bt_mesh_client_common_t *)model->user_data; + if (!cli) { + BT_ERR("%s, Clinet user_data is NULL", __func__); + return NULL; + } + + rsp = ctx->recv_op; + + /** If the received message address is not a unicast address, + * the address may be a group/virtual address, and we push + * this message to the application layer. + */ + if (!BLE_MESH_ADDR_IS_UNICAST(ctx->recv_dst)) { + BT_DBG("Unexpected status message 0x%x", rsp); + if (cli->publish_status && need_pub) { + cli->publish_status(rsp, model, ctx, buf); + } + return NULL; + } + + /** If the source address of the received status message is + * different with the destination address of the sending + * message, then the message is from another element and + * push it to application layer. + */ + data = (bt_mesh_internal_data_t *)cli->internal_data; + if (!data) { + BT_ERR("%s, Client internal_data is NULL", __func__); + return NULL; + } + + if ((node = bt_mesh_client_pick_node(&data->queue, ctx->addr)) == NULL) { + BT_DBG("Unexpected status message 0x%x", rsp); + if (cli->publish_status && need_pub) { + cli->publish_status(rsp, model, ctx, buf); + } + return NULL; + } + + if (node->op_pending != rsp) { + BT_DBG("Unexpected status message 0x%x", rsp); + if (cli->publish_status && need_pub) { + cli->publish_status(rsp, model, ctx, buf); + } + return NULL; + } + + return node; +} + +bool bt_mesh_client_find_opcode_in_list(sys_slist_t *list, u32_t opcode) +{ + if (sys_slist_is_empty(list)) { + return false; + } + + sys_snode_t *cur = NULL; bt_mesh_client_node_t *node = NULL; + for (cur = sys_slist_peek_head(list); + cur != NULL; cur = sys_slist_peek_next(cur)) { + node = (bt_mesh_client_node_t *)cur; + if (node->op_pending == opcode) { + return true; + } + return NULL; + } + + return node; +} + +bool bt_mesh_client_check_node_in_list(sys_slist_t *list, u16_t tx_dst) +{ + if (sys_slist_is_empty(list)) { + return false; + } + + sys_snode_t *cur = NULL; bt_mesh_client_node_t *node = NULL; + for (cur = sys_slist_peek_head(list); + cur != NULL; cur = sys_slist_peek_next(cur)) { + node = (bt_mesh_client_node_t *)cur; + if (node->ctx.addr == tx_dst) { + return true; + } + } + + return false; +} + +bt_mesh_client_node_t *bt_mesh_client_pick_node(sys_slist_t *list, u16_t tx_dst) +{ + if (sys_slist_is_empty(list)) { + return NULL; + } + + sys_snode_t *cur = NULL; bt_mesh_client_node_t *node = NULL; + for (cur = sys_slist_peek_head(list); + cur != NULL; cur = sys_slist_peek_next(cur)) { + node = (bt_mesh_client_node_t *)cur; + if (node->ctx.addr == tx_dst) { + return node; + } + } + + return NULL; +} + +static u32_t bt_mesh_client_get_status_op(const bt_mesh_client_op_pair_t *op_pair, + int size, u32_t opcode) +{ + if (!op_pair || size == 0) { + return 0; + } + + const bt_mesh_client_op_pair_t *op = op_pair; + for (int i = 0; i < size; i++) { + if (op->cli_op == opcode) { + return op->status_op; + } + op++; + } + + return 0; +} + +int bt_mesh_client_send_msg(struct bt_mesh_model *model, + u32_t opcode, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *msg, + k_work_handler_t timer_handler, + s32_t timeout, bool need_ack, + const struct bt_mesh_send_cb *cb, + void *cb_data) +{ + bt_mesh_internal_data_t *internal = NULL; + bt_mesh_client_common_t *cli = NULL; + bt_mesh_client_node_t *node = NULL; + int err; + + if (!model || !ctx || !msg) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + cli = (bt_mesh_client_common_t *)model->user_data; + __ASSERT(cli, "Invalid client value when sent client msg."); + internal = (bt_mesh_internal_data_t *)cli->internal_data; + __ASSERT(internal, "Invalid internal value when sent client msg."); + + if (!need_ack) { + /* If this is an unack message, send it directly. */ + return bt_mesh_model_send(model, ctx, msg, cb, cb_data); + } + + if (bt_mesh_client_check_node_in_list(&internal->queue, ctx->addr)) { + BT_ERR("%s, Busy sending message to DST 0x%04x", __func__, ctx->addr); + err = -EBUSY; + } else { + /* Don't forget to free the node in the timeout (timer_handler) function. */ + node = (bt_mesh_client_node_t *)osi_calloc(sizeof(bt_mesh_client_node_t)); + if (!node) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + memcpy(&node->ctx, ctx, sizeof(struct bt_mesh_msg_ctx)); + node->ctx.model = model; + node->opcode = opcode; + if ((node->op_pending = bt_mesh_client_get_status_op(cli->op_pair, cli->op_pair_size, opcode)) == 0) { + BT_ERR("%s, Not found the status opcode in the op_pair list", __func__); + osi_free(node); + return -EINVAL; + } + if ((err = bt_mesh_model_send(model, ctx, msg, cb, cb_data)) != 0) { + osi_free(node); + } else { + sys_slist_append(&internal->queue, &node->client_node); + k_delayed_work_init(&node->timer, timer_handler); + k_delayed_work_submit(&node->timer, timeout ? timeout : CONFIG_BLE_MESH_CLIENT_MSG_TIMEOUT); + } + } + + return err; +} + +int bt_mesh_client_init(struct bt_mesh_model *model) +{ + bt_mesh_internal_data_t *data = NULL; + bt_mesh_client_common_t *cli = NULL; + + if (!model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (!model->op) { + BT_ERR("%s, Client model op is NULL", __func__); + return -EINVAL; + } + + cli = model->user_data; + if (!cli) { + BT_ERR("%s, Client user_data is NULL", __func__); + return -EINVAL; + } + + /* TODO: call osi_free() when deinit function is invoked */ + data = osi_calloc(sizeof(bt_mesh_internal_data_t)); + if (!data) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + /* Init the client data queue */ + sys_slist_init(&data->queue); + + cli->model = model; + cli->internal_data = data; + + return 0; +} + +int bt_mesh_client_free_node(sys_slist_t *queue, bt_mesh_client_node_t *node) +{ + if (!queue || !node) { + return -EINVAL; + } + + // Free the node timer + k_delayed_work_free(&node->timer); + // Release the client node from the queue + sys_slist_find_and_remove(queue, &node->client_node); + // Free the node + osi_free(node); + + return 0; +} + +int bt_mesh_set_model_role(bt_mesh_role_param_t *common) +{ + bt_mesh_client_common_t *client = NULL; + + if (!common || !common->model || !common->model->user_data) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_client_common_t *)common->model->user_data; + + switch (common->role) { +#if CONFIG_BLE_MESH_NODE + case NODE: + /* no matter if provisioner is enabled/disabled , node role can be used to send messages */ + client->msg_role = NODE; + break; +#endif +#if CONFIG_BLE_MESH_PROVISIONER + case PROVISIONER: + /* if provisioner is not enabled, provisioner role can't be used to send messages */ + if (!bt_mesh_is_provisioner_en()) { + BT_ERR("%s, Provisioner is disabled", __func__); + return -EINVAL; + } + client->msg_role = PROVISIONER; + break; +#endif +#if CONFIG_BLE_MESH_FAST_PROV + case FAST_PROV: + client->msg_role = FAST_PROV; + break; +#endif + default: + BT_WARN("%s, Unknown model role %x", __func__, common->role); + return -EINVAL; + } + + return 0; +} + +u8_t bt_mesh_get_model_role(struct bt_mesh_model *model, bool srv_send) +{ + bt_mesh_client_common_t *client = NULL; + + if (srv_send) { + BT_DBG("%s, Message is sent by a server model", __func__); + return NODE; + } + + if (!model || !model->user_data) { + BT_ERR("%s, Invalid parameter", __func__); + return ROLE_NVAL; + } + + client = (bt_mesh_client_common_t *)model->user_data; + + return client->msg_role; +} diff --git a/components/bt/ble_mesh/mesh_models/sensor_client.c b/components/bt/ble_mesh/mesh_models/sensor_client.c new file mode 100644 index 0000000000..1933d1027a --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/sensor_client.c @@ -0,0 +1,616 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "osi/allocator.h" +#include "sdkconfig.h" + +#include "mesh_types.h" +#include "mesh_kernel.h" +#include "mesh_trace.h" + +#include "mesh.h" +#include "model_opcode.h" +#include "mesh_common.h" +#include "sensor_client.h" + +#include "btc_ble_mesh_sensor_model.h" + +/** The following are the macro definitions of sensor client + * model messages length, and a message is composed of three + * parts: Opcode + msg_value + MIC + */ +/* Sensor client messages length */ +#define BLE_MESH_SENSOR_DESCRIPTOR_GET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_SENSOR_CADENCE_GET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_SENSOR_CADENCE_SET_MSG_LEN /* variable */ +#define BLE_MESH_SENSOR_SETTINGS_GET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_SENSOR_SETTING_GET_MSG_LEN (2 + 4 + 4) +#define BLE_MESH_SENSOR_SETTING_SET_MSG_LEN /* variable */ +#define BLE_MESH_SENSOR_GET_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_SENSOR_COLUMN_GET_MSG_LEN /* variable */ +#define BLE_MESH_SENSOR_SERIES_GET_MSG_LEN /* variable */ + +static const bt_mesh_client_op_pair_t sensor_op_pair[] = { + { BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET, BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS }, + { BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET, BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS }, + { BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET, BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS }, + { BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET, BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS }, + { BLE_MESH_MODEL_OP_SENSOR_SETTING_GET, BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS }, + { BLE_MESH_MODEL_OP_SENSOR_SETTING_SET, BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS }, + { BLE_MESH_MODEL_OP_SENSOR_GET, BLE_MESH_MODEL_OP_SENSOR_STATUS }, + { BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET, BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS }, + { BLE_MESH_MODEL_OP_SENSOR_SERIES_GET, BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS }, +}; + +static void timeout_handler(struct k_work *work) +{ + sensor_internal_data_t *internal = NULL; + bt_mesh_sensor_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + + BT_WARN("Receive sensor status message timeout"); + + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (!node || !node->ctx.model) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + client = (bt_mesh_sensor_client_t *)node->ctx.model->user_data; + if (!client) { + BT_ERR("%s, Sensor Client user_data is NULL", __func__); + return; + } + + internal = (sensor_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Sensor Client internal_data is NULL", __func__); + return; + } + + bt_mesh_callback_sensor_status_to_btc(node->opcode, 0x03, node->ctx.model, + &node->ctx, NULL, 0); + + bt_mesh_client_free_node(&internal->queue, node); + + return; +} + +static void sensor_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + sensor_internal_data_t *internal = NULL; + bt_mesh_sensor_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + u8_t *val = NULL; + u8_t evt = 0xFF; + u32_t rsp = 0; + size_t len = 0; + + BT_DBG("%s, len %d, bytes %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + client = (bt_mesh_sensor_client_t *)model->user_data; + if (!client) { + BT_ERR("%s, Sensor Client user_data is NULL", __func__); + return; + } + + internal = (sensor_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Sensor Client internal_data is NULL", __func__); + return; + } + + rsp = ctx->recv_op; + switch (rsp) { + case BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS: { + struct bt_mesh_sensor_descriptor_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_sensor_descriptor_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->descriptor = bt_mesh_alloc_buf(buf->len); + if (!status->descriptor) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->descriptor, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_sensor_descriptor_status); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS: { + struct bt_mesh_sensor_cadence_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_sensor_cadence_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->property_id = net_buf_simple_pull_le16(buf); + status->sensor_cadence_value = bt_mesh_alloc_buf(buf->len); + if (!status->sensor_cadence_value) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->sensor_cadence_value, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_sensor_cadence_status); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS: { + struct bt_mesh_sensor_settings_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_sensor_settings_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->sensor_property_id = net_buf_simple_pull_le16(buf); + status->sensor_setting_property_ids = bt_mesh_alloc_buf(buf->len); + if (!status->sensor_setting_property_ids) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->sensor_setting_property_ids, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_sensor_settings_status); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS: { + struct bt_mesh_sensor_setting_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_sensor_setting_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->sensor_property_id = net_buf_simple_pull_le16(buf); + status->sensor_setting_property_id = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->sensor_setting_access = net_buf_simple_pull_u8(buf); + status->sensor_setting_raw = bt_mesh_alloc_buf(buf->len); + if (!status->sensor_setting_raw) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->sensor_setting_raw, buf->data, buf->len); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_sensor_setting_status); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_STATUS: { + struct bt_mesh_sensor_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_sensor_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->marshalled_sensor_data = bt_mesh_alloc_buf(buf->len); + if (!status->marshalled_sensor_data) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->marshalled_sensor_data, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_sensor_status); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS: { + struct bt_mesh_sensor_column_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_sensor_column_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->property_id = net_buf_simple_pull_le16(buf); + status->sensor_column_value = bt_mesh_alloc_buf(buf->len); + if (!status->sensor_column_value) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->sensor_column_value, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_sensor_column_status); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS: { + struct bt_mesh_sensor_series_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_sensor_series_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->property_id = net_buf_simple_pull_le16(buf); + status->sensor_series_value = bt_mesh_alloc_buf(buf->len); + if (!status->sensor_series_value) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->sensor_series_value, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_sensor_series_status); + break; + } + default: + BT_ERR("%s, Not a Sensor Status message opcode", __func__); + return; + } + + buf->data = val; + buf->len = len; + node = bt_mesh_is_model_message_publish(model, ctx, buf, true); + if (!node) { + BT_DBG("Unexpected sensor status message 0x%x", rsp); + } else { + switch (node->opcode) { + case BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET: + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET: + case BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET: + case BLE_MESH_MODEL_OP_SENSOR_SETTING_GET: + case BLE_MESH_MODEL_OP_SENSOR_GET: + case BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET: + case BLE_MESH_MODEL_OP_SENSOR_SERIES_GET: + evt = 0x00; + break; + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET: + case BLE_MESH_MODEL_OP_SENSOR_SETTING_SET: + evt = 0x01; + break; + default: + break; + } + + bt_mesh_callback_sensor_status_to_btc(node->opcode, evt, model, ctx, val, len); + // Don't forget to release the node at the end. + bt_mesh_client_free_node(&internal->queue, node); + } + + switch (rsp) { + case BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS: { + struct bt_mesh_sensor_descriptor_status *status; + status = (struct bt_mesh_sensor_descriptor_status *)val; + bt_mesh_free_buf(status->descriptor); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS: { + struct bt_mesh_sensor_cadence_status *status; + status = (struct bt_mesh_sensor_cadence_status *)val; + bt_mesh_free_buf(status->sensor_cadence_value); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS: { + struct bt_mesh_sensor_settings_status *status; + status = (struct bt_mesh_sensor_settings_status *)val; + bt_mesh_free_buf(status->sensor_setting_property_ids); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS: { + struct bt_mesh_sensor_setting_status *status; + status = (struct bt_mesh_sensor_setting_status *)val; + bt_mesh_free_buf(status->sensor_setting_raw); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_STATUS: { + struct bt_mesh_sensor_status *status; + status = (struct bt_mesh_sensor_status *)val; + bt_mesh_free_buf(status->marshalled_sensor_data); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS: { + struct bt_mesh_sensor_column_status *status; + status = (struct bt_mesh_sensor_column_status *)val; + bt_mesh_free_buf(status->sensor_column_value); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS: { + struct bt_mesh_sensor_series_status *status; + status = (struct bt_mesh_sensor_series_status *)val; + bt_mesh_free_buf(status->sensor_series_value); + break; + } + default: + break; + } + + osi_free(val); + + return; +} + +const struct bt_mesh_model_op sensor_cli_op[] = { + { BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS, 0, sensor_status }, + { BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS, 2, sensor_status }, + { BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS, 2, sensor_status }, + { BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS, 4, sensor_status }, + { BLE_MESH_MODEL_OP_SENSOR_STATUS, 0, sensor_status }, + { BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS, 2, sensor_status }, + { BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS, 2, sensor_status }, + BLE_MESH_MODEL_OP_END, +}; + +static int sensor_act_state(struct bt_mesh_common_param *common, + void *value, u16_t value_len, bool need_ack) +{ + struct net_buf_simple *msg = NULL; + int err; + + msg = bt_mesh_alloc_buf(value_len); + if (!msg) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + bt_mesh_model_msg_init(msg, common->opcode); + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET: { + struct bt_mesh_sensor_descriptor_get *act; + act = (struct bt_mesh_sensor_descriptor_get *)value; + if (act->op_en) { + net_buf_simple_add_le16(msg, act->property_id); + } + break; + } + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET: { + struct bt_mesh_sensor_cadence_get *act; + act = (struct bt_mesh_sensor_cadence_get *)value; + net_buf_simple_add_le16(msg, act->property_id); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET: + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK: { + struct bt_mesh_sensor_cadence_set *act; + act = (struct bt_mesh_sensor_cadence_set *)value; + net_buf_simple_add_le16(msg, act->property_id); + net_buf_simple_add_u8(msg, act->status_trigger_type << 7 | act->fast_cadence_period_divisor); + net_buf_simple_add_mem(msg, act->status_trigger_delta_down->data, act->status_trigger_delta_down->len); + net_buf_simple_add_mem(msg, act->status_trigger_delta_up->data, act->status_trigger_delta_up->len); + net_buf_simple_add_u8(msg, act->status_min_interval); + net_buf_simple_add_mem(msg, act->fast_cadence_low->data, act->fast_cadence_low->len); + net_buf_simple_add_mem(msg, act->fast_cadence_high->data, act->fast_cadence_high->len); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET: { + struct bt_mesh_sensor_settings_get *act; + act = (struct bt_mesh_sensor_settings_get *)value; + net_buf_simple_add_le16(msg, act->sensor_property_id); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SETTING_GET: { + struct bt_mesh_sensor_setting_get *act; + act = (struct bt_mesh_sensor_setting_get *)value; + net_buf_simple_add_le16(msg, act->sensor_property_id); + net_buf_simple_add_le16(msg, act->sensor_setting_property_id); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SETTING_SET: + case BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK: { + struct bt_mesh_sensor_setting_set *act; + act = (struct bt_mesh_sensor_setting_set *)value; + net_buf_simple_add_le16(msg, act->sensor_property_id); + net_buf_simple_add_le16(msg, act->sensor_setting_property_id); + net_buf_simple_add_mem(msg, act->sensor_setting_raw->data, act->sensor_setting_raw->len); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_GET: { + struct bt_mesh_sensor_get *act; + act = (struct bt_mesh_sensor_get *)value; + if (act->op_en) { + net_buf_simple_add_le16(msg, act->property_id); + } + break; + } + case BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET: { + struct bt_mesh_sensor_column_get *act; + act = (struct bt_mesh_sensor_column_get *)value; + net_buf_simple_add_le16(msg, act->property_id); + net_buf_simple_add_mem(msg, act->raw_value_x->data, act->raw_value_x->len); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SERIES_GET: { + struct bt_mesh_sensor_series_get *act; + act = (struct bt_mesh_sensor_series_get *)value; + net_buf_simple_add_le16(msg, act->property_id); + if (act->op_en) { + net_buf_simple_add_mem(msg, act->raw_value_x1->data, act->raw_value_x1->len); + net_buf_simple_add_mem(msg, act->raw_value_x2->data, act->raw_value_x2->len); + } + break; + } + default: + BT_ERR("%s, Not a Sensor Client message opcode", __func__); + err = -EINVAL; + goto end; + } + + err = bt_mesh_client_send_msg(common->model, common->opcode, &common->ctx, msg, + timeout_handler, common->msg_timeout, need_ack, + common->cb, common->cb_data); + if (err) { + BT_ERR("%s, Failed to send Sensor Client message (err %d)", __func__, err); + } + +end: + bt_mesh_free_buf(msg); + + return err; +} + +int bt_mesh_sensor_client_get_state(struct bt_mesh_common_param *common, void *get, void *status) +{ + bt_mesh_sensor_client_t *client = NULL; + u16_t length = 0; + + if (!common || !common->model || !get) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_sensor_client_t *)common->model->user_data; + if (!client || !client->internal_data) { + BT_ERR("%s, Sensor Client user data is NULL", __func__); + return -EINVAL; + } + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET: + length = BLE_MESH_SENSOR_DESCRIPTOR_GET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET: + length = BLE_MESH_SENSOR_CADENCE_GET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET: + length = BLE_MESH_SENSOR_SETTINGS_GET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_SENSOR_SETTING_GET: + length = BLE_MESH_SENSOR_SETTING_GET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_SENSOR_GET: + length = BLE_MESH_SENSOR_GET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET: { + struct bt_mesh_sensor_column_get *value; + value = (struct bt_mesh_sensor_column_get *)get; + if (!value->raw_value_x) { + BT_ERR("%s, Sensor column_get is NULL", __func__); + return -EINVAL; + } + length = (2 + 2 + value->raw_value_x->len + 4); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SERIES_GET: { + struct bt_mesh_sensor_series_get *value; + value = (struct bt_mesh_sensor_series_get *)get; + if (value->op_en) { + if (!value->raw_value_x1 || !value->raw_value_x2) { + BT_ERR("%s, Sensor series_get is NULL", __func__); + return -EINVAL; + } + } + if (value->op_en) { + length = value->raw_value_x1->len + value->raw_value_x2->len; + } + length += (2 + 2 + 4); + break; + } + default: + BT_ERR("%s, Not a Sensor Client Get message opcode", __func__); + return -EINVAL; + } + + return sensor_act_state(common, get, length, true); +} + +int bt_mesh_sensor_client_set_state(struct bt_mesh_common_param *common, void *set, void *status) +{ + bt_mesh_sensor_client_t *client = NULL; + u16_t length = 0; + bool need_ack = false; + + if (!common || !common->model || !set) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_sensor_client_t *)common->model->user_data; + if (!client || !client->internal_data) { + BT_ERR("%s, Sensor Client user data is NULL", __func__); + return -EINVAL; + } + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK: { + struct bt_mesh_sensor_cadence_set *value; + value = (struct bt_mesh_sensor_cadence_set *)set; + if (!value->status_trigger_delta_down || !value->status_trigger_delta_up || + !value->fast_cadence_low || !value->fast_cadence_high) { + BT_ERR("%s, Sensor cadence_set is NULL", __func__); + return -EINVAL; + } + length = value->status_trigger_delta_down->len + \ + value->status_trigger_delta_up->len + \ + value->fast_cadence_low->len + \ + value->fast_cadence_high->len; + length += (1 + 2 + 1 + 1 + 4); + break; + } + case BLE_MESH_MODEL_OP_SENSOR_SETTING_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK: { + struct bt_mesh_sensor_setting_set *value; + value = (struct bt_mesh_sensor_setting_set *)set; + if (!value->sensor_setting_raw) { + BT_ERR("%s, Sensor setting_raw is NULL", __func__); + return -EINVAL; + } + length = (1 + 2 + 2 + value->sensor_setting_raw->len + 4); + break; + } + default: + BT_ERR("%s, Not a Sensor Client Set message opcode", __func__); + return -EINVAL; + } + + return sensor_act_state(common, set, length, need_ack); +} + +int bt_mesh_sensor_cli_init(struct bt_mesh_model *model, bool primary) +{ + sensor_internal_data_t *internal = NULL; + bt_mesh_sensor_client_t *client = NULL; + + BT_DBG("primary %u", primary); + + if (!model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_sensor_client_t *)model->user_data; + if (!client) { + BT_ERR("%s, Sensor Client user_data is NULL", __func__); + return -EINVAL; + } + + /* TODO: call osi_free() when deinit function is invoked*/ + internal = osi_calloc(sizeof(sensor_internal_data_t)); + if (!internal) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + sys_slist_init(&internal->queue); + + client->model = model; + client->op_pair_size = ARRAY_SIZE(sensor_op_pair); + client->op_pair = sensor_op_pair; + client->internal_data = internal; + + return 0; +} \ No newline at end of file diff --git a/components/bt/ble_mesh/mesh_models/time_scene_client.c b/components/bt/ble_mesh/mesh_models/time_scene_client.c new file mode 100644 index 0000000000..3296edf925 --- /dev/null +++ b/components/bt/ble_mesh/mesh_models/time_scene_client.c @@ -0,0 +1,694 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "osi/allocator.h" +#include "sdkconfig.h" + +#include "mesh_types.h" +#include "mesh_kernel.h" +#include "mesh_trace.h" + +#include "mesh.h" +#include "model_opcode.h" +#include "mesh_common.h" +#include "time_scene_client.h" + +#include "btc_ble_mesh_time_scene_model.h" + +/** The following are the macro definitions of time and client + * scene model messages length, and a message is composed of + * three parts: Opcode + msg_value + MIC + */ +/* Time client messages length */ +#define BLE_MESH_TIME_SET_MSG_LEN (1 + 10 + 4) +#define BLE_MESH_TIME_ZONE_SET_MSG_LEN (2 + 6 + 4) +#define BLE_MESH_TAI_UTC_DELTA_SET_MSG_LEN (2 + 7 + 4) +#define BLE_MESH_TIME_ROLE_SET_MSG_LEN (2 + 1 + 4) + +/* Scene client messages length */ +#define BLE_MESH_SCENE_STORE_MSG_LEN (2 + 2 + 4) +#define BLE_MESH_SCENE_RECALL_MSG_LEN (2 + 5 + 4) +#define BLE_MESH_SCENE_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_SCENE_REGISTER_GET_MSG_LEN (2 + 0 + 4) +#define BLE_MESH_SCENE_DELETE_MSG_LEN (2 + 2 + 4) + +/* Scheduler client messages length */ +#define BLE_MESH_SCHEDULER_ACT_GET_MSG_LEN (2 + 1 + 4) +#define BLE_MESH_SCHEDULER_ACT_SET_MSG_LEN (1 + 10 + 4) + +#define BLE_MESH_SCENE_GET_STATE_MSG_LEN (2 + 1 + 4) +#define BLE_MESH_SCENE_ACT_STATE_MSG_LEN (2 + 2 + 4) + +static const bt_mesh_client_op_pair_t time_scene_op_pair[] = { + { BLE_MESH_MODEL_OP_TIME_GET, BLE_MESH_MODEL_OP_TIME_STATUS }, + { BLE_MESH_MODEL_OP_TIME_SET, BLE_MESH_MODEL_OP_TIME_STATUS }, + { BLE_MESH_MODEL_OP_TIME_ZONE_GET, BLE_MESH_MODEL_OP_TIME_ZONE_STATUS }, + { BLE_MESH_MODEL_OP_TIME_ZONE_SET, BLE_MESH_MODEL_OP_TIME_ZONE_STATUS }, + { BLE_MESH_MODEL_OP_TAI_UTC_DELTA_GET, BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS }, + { BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET, BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS }, + { BLE_MESH_MODEL_OP_TIME_ROLE_GET, BLE_MESH_MODEL_OP_TIME_ROLE_STATUS }, + { BLE_MESH_MODEL_OP_TIME_ROLE_SET, BLE_MESH_MODEL_OP_TIME_ROLE_STATUS }, + { BLE_MESH_MODEL_OP_SCENE_STORE, BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS }, + { BLE_MESH_MODEL_OP_SCENE_RECALL, BLE_MESH_MODEL_OP_SCENE_STATUS }, + { BLE_MESH_MODEL_OP_SCENE_GET, BLE_MESH_MODEL_OP_SCENE_STATUS }, + { BLE_MESH_MODEL_OP_SCENE_REGISTER_GET, BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS }, + { BLE_MESH_MODEL_OP_SCENE_DELETE, BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS }, + { BLE_MESH_MODEL_OP_SCHEDULER_GET, BLE_MESH_MODEL_OP_SCHEDULER_STATUS }, + { BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET, BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS }, + { BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET, BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS }, +}; + +static void timeout_handler(struct k_work *work) +{ + time_scene_internal_data_t *internal = NULL; + bt_mesh_time_scene_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + + BT_WARN("Receive time scene status message timeout"); + + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (!node || !node->ctx.model) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + client = (bt_mesh_time_scene_client_t *)node->ctx.model->user_data; + if (!client) { + BT_ERR("%s, Time Scene Client user_data is NULL", __func__); + return; + } + + internal = (time_scene_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Time Scene Client internal_data is NULL", __func__); + return; + } + + bt_mesh_callback_time_scene_status_to_btc(node->opcode, 0x03, node->ctx.model, + &node->ctx, NULL, 0); + + bt_mesh_client_free_node(&internal->queue, node); + + return; +} + +static void time_scene_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + time_scene_internal_data_t *internal = NULL; + bt_mesh_time_scene_client_t *client = NULL; + bt_mesh_client_node_t *node = NULL; + u8_t *val = NULL; + u8_t evt = 0xFF; + u32_t rsp = 0; + size_t len = 0; + + BT_DBG("%s, len %d, bytes %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + client = (bt_mesh_time_scene_client_t *)model->user_data; + if (!client) { + BT_ERR("%s, Time Scene Client user_data is NULL", __func__); + return; + } + + internal = (time_scene_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Time Scene Client internal_data is NULL", __func__); + return; + } + + rsp = ctx->recv_op; + switch (rsp) { + case BLE_MESH_MODEL_OP_TIME_STATUS: { + struct bt_mesh_time_status *status = NULL; + if (buf->len != 5 && buf->len != 10) { + BT_ERR("%s, Invalid Time Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_time_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + memcpy(status->tai_seconds, buf->data, 5); + net_buf_simple_pull(buf, 5); + status->sub_second = net_buf_simple_pull_u8(buf); + status->uncertainty = net_buf_simple_pull_u8(buf); + u16_t temp = net_buf_simple_pull_le16(buf); + status->time_authority = temp & BIT(0); + status->tai_utc_delta = temp >> 15; + status->time_zone_offset = net_buf_simple_pull_u8(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_time_status); + break; + } + case BLE_MESH_MODEL_OP_TIME_ZONE_STATUS: { + struct bt_mesh_time_zone_status *status = NULL; + if (buf->len != 7) { + BT_ERR("%s, Invalid Time Zone Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_time_zone_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->time_zone_offset_curr = net_buf_simple_pull_u8(buf); + status->time_zone_offset_new = net_buf_simple_pull_u8(buf); + memcpy(status->tai_zone_change, buf->data, 5); + net_buf_simple_pull(buf, 5); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_time_zone_status); + break; + } + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS: { + struct bt_mesh_tai_utc_delta_status *status = NULL; + if (buf->len != 9) { + BT_ERR("%s, Invalid TAI UTC Delta Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_tai_utc_delta_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + u16_t temp = net_buf_simple_pull_le16(buf); + status->tai_utc_delta_curr = temp & BIT_MASK(15); + status->padding_1 = (temp >> 15) & BIT(0); + temp = net_buf_simple_pull_le16(buf); + status->tai_utc_delta_new = temp & BIT_MASK(15); + status->padding_2 = (temp >> 15) & BIT(0); + memcpy(status->tai_delta_change, buf->data, 5); + net_buf_simple_pull(buf, 5); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_tai_utc_delta_status); + break; + } + case BLE_MESH_MODEL_OP_TIME_ROLE_STATUS: { + struct bt_mesh_time_role_status *status = NULL; + if (buf->len != 1) { + BT_ERR("%s, Invalid Time Role Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_time_role_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->time_role = net_buf_simple_pull_u8(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_time_role_status); + break; + } + case BLE_MESH_MODEL_OP_SCENE_STATUS: { + struct bt_mesh_scene_status *status = NULL; + if (buf->len != 3 && buf->len != 6) { + BT_ERR("%s, Invalid Scene Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_scene_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->status_code = net_buf_simple_pull_u8(buf); + status->current_scene = net_buf_simple_pull_le16(buf); + if (buf->len) { + status->op_en = true; + status->target_scene = net_buf_simple_pull_le16(buf); + status->remain_time = net_buf_simple_pull_u8(buf); + } + val = (u8_t *)status; + len = sizeof(struct bt_mesh_scene_status); + break; + } + case BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS: { + struct bt_mesh_scene_register_status *status = NULL; + status = osi_calloc(sizeof(struct bt_mesh_scene_register_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->status_code = net_buf_simple_pull_u8(buf); + status->current_scene = net_buf_simple_pull_le16(buf); + status->scenes = bt_mesh_alloc_buf(buf->len); + if (!status->scenes) { + BT_ERR("%s, Failed to allocate memory", __func__); + osi_free(status); + return; + } + net_buf_simple_add_mem(status->scenes, buf->data, buf->len); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_scene_register_status); + break; + } + case BLE_MESH_MODEL_OP_SCHEDULER_STATUS: { + struct bt_mesh_scheduler_status *status = NULL; + if (buf->len != 2) { + BT_ERR("%s, Invalid Scheduler Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_scheduler_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + status->schedules = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_scheduler_status); + break; + } + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS: { + struct bt_mesh_scheduler_act_status *status = NULL; + if (buf->len != 10) { + BT_ERR("%s, Invalid Scheduler Action Status length %d", __func__, buf->len); + return; + } + status = osi_calloc(sizeof(struct bt_mesh_scheduler_act_status)); + if (!status) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + memcpy(status, buf->data, offsetof(struct bt_mesh_scheduler_act_status, scene_number)); + net_buf_simple_pull(buf, offsetof(struct bt_mesh_scheduler_act_status, scene_number)); + status->scene_number = net_buf_simple_pull_le16(buf); + val = (u8_t *)status; + len = sizeof(struct bt_mesh_scheduler_act_status); + break; + } + default: + BT_ERR("%s, Not a Time Scene Status message opcode", __func__); + return; + } + + buf->data = val; + buf->len = len; + node = bt_mesh_is_model_message_publish(model, ctx, buf, true); + if (!node) { + BT_DBG("Unexpected time scene status message 0x%x", rsp); + } else { + switch (node->opcode) { + case BLE_MESH_MODEL_OP_TIME_GET: + case BLE_MESH_MODEL_OP_TIME_ZONE_GET: + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_GET: + case BLE_MESH_MODEL_OP_TIME_ROLE_GET: + case BLE_MESH_MODEL_OP_SCENE_GET: + case BLE_MESH_MODEL_OP_SCENE_REGISTER_GET: + case BLE_MESH_MODEL_OP_SCHEDULER_GET: + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET: + evt = 0x00; + break; + case BLE_MESH_MODEL_OP_TIME_SET: + case BLE_MESH_MODEL_OP_TIME_ZONE_SET: + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET: + case BLE_MESH_MODEL_OP_TIME_ROLE_SET: + case BLE_MESH_MODEL_OP_SCENE_STORE: + case BLE_MESH_MODEL_OP_SCENE_RECALL: + case BLE_MESH_MODEL_OP_SCENE_DELETE: + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET: + evt = 0x01; + break; + default: + break; + } + + bt_mesh_callback_time_scene_status_to_btc(node->opcode, evt, model, ctx, val, len); + // Don't forget to release the node at the end. + bt_mesh_client_free_node(&internal->queue, node); + } + + switch (rsp) { + case BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS: { + struct bt_mesh_scene_register_status *status; + status = (struct bt_mesh_scene_register_status *)val; + bt_mesh_free_buf(status->scenes); + break; + } + default: + break; + } + + osi_free(val); + + return; +} + +const struct bt_mesh_model_op time_cli_op[] = { + { BLE_MESH_MODEL_OP_TIME_STATUS, 5, time_scene_status }, + { BLE_MESH_MODEL_OP_TIME_ZONE_STATUS, 7, time_scene_status }, + { BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS, 9, time_scene_status }, + { BLE_MESH_MODEL_OP_TIME_ROLE_STATUS, 1, time_scene_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op scene_cli_op[] = { + { BLE_MESH_MODEL_OP_SCENE_STATUS, 3, time_scene_status }, + { BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS, 3, time_scene_status }, + BLE_MESH_MODEL_OP_END, +}; + +const struct bt_mesh_model_op scheduler_cli_op[] = { + { BLE_MESH_MODEL_OP_SCHEDULER_STATUS, 2, time_scene_status }, + { BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS, 10, time_scene_status }, + BLE_MESH_MODEL_OP_END, +}; + +static int time_scene_get_state(struct bt_mesh_common_param *common, void *value) +{ + NET_BUF_SIMPLE_DEFINE(msg, BLE_MESH_SCENE_GET_STATE_MSG_LEN); + int err; + + bt_mesh_model_msg_init(&msg, common->opcode); + + if (value) { + switch (common->opcode) { + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET: { + struct bt_mesh_scheduler_act_get *get; + get = (struct bt_mesh_scheduler_act_get *)value; + net_buf_simple_add_u8(&msg, get->index); + break; + } + default: + BT_DBG("This time scene message should be sent with NULL get pointer"); + break; + } + } + + err = bt_mesh_client_send_msg(common->model, common->opcode, &common->ctx, &msg, + timeout_handler, common->msg_timeout, true, + common->cb, common->cb_data); + if (err) { + BT_ERR("%s, Failed to send Time Scene Get message (err %d)", __func__, err); + } + + return err; +} + +static int time_scene_set_state(struct bt_mesh_common_param *common, + void *value, u16_t value_len, bool need_ack) +{ + struct net_buf_simple *msg = NULL; + int err; + + msg = bt_mesh_alloc_buf(value_len); + if (!msg) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + bt_mesh_model_msg_init(msg, common->opcode); + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_TIME_SET: { + struct bt_mesh_time_set *set; + set = (struct bt_mesh_time_set *)value; + net_buf_simple_add_mem(msg, set->tai_seconds, 5); + net_buf_simple_add_u8(msg, set->sub_second); + net_buf_simple_add_u8(msg, set->uncertainty); + net_buf_simple_add_le16(msg, set->tai_utc_delta << 1 | set->time_authority); + net_buf_simple_add_u8(msg, set->time_zone_offset); + break; + } + case BLE_MESH_MODEL_OP_TIME_ZONE_SET: { + struct bt_mesh_time_zone_set *set; + set = (struct bt_mesh_time_zone_set *)value; + net_buf_simple_add_u8(msg, set->time_zone_offset_new); + net_buf_simple_add_mem(msg, set->tai_zone_change, 5); + break; + } + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET: { + struct bt_mesh_tai_utc_delta_set *set; + set = (struct bt_mesh_tai_utc_delta_set *)value; + net_buf_simple_add_le16(msg, set->padding << 15 | set->tai_utc_delta_new); + net_buf_simple_add_mem(msg, set->tai_delta_change, 5); + break; + } + case BLE_MESH_MODEL_OP_TIME_ROLE_SET: { + struct bt_mesh_time_role_set *set; + set = (struct bt_mesh_time_role_set *)value; + net_buf_simple_add_u8(msg, set->time_role); + break; + } + case BLE_MESH_MODEL_OP_SCENE_STORE: + case BLE_MESH_MODEL_OP_SCENE_STORE_UNACK: { + struct bt_mesh_scene_store *set; + set = (struct bt_mesh_scene_store *)value; + net_buf_simple_add_le16(msg, set->scene_number); + break; + } + case BLE_MESH_MODEL_OP_SCENE_RECALL: + case BLE_MESH_MODEL_OP_SCENE_RECALL_UNACK: { + struct bt_mesh_scene_recall *set; + set = (struct bt_mesh_scene_recall *)value; + net_buf_simple_add_le16(msg, set->scene_number); + net_buf_simple_add_u8(msg, set->tid); + if (set->op_en) { + net_buf_simple_add_u8(msg, set->trans_time); + net_buf_simple_add_u8(msg, set->delay); + } + break; + } + case BLE_MESH_MODEL_OP_SCENE_DELETE: + case BLE_MESH_MODEL_OP_SCENE_DELETE_UNACK: { + struct bt_mesh_scene_delete *set; + set = (struct bt_mesh_scene_delete *)value; + net_buf_simple_add_le16(msg, set->scene_number); + break; + } + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET: + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET_UNACK: { + struct bt_mesh_scheduler_act_set *set; + set = (struct bt_mesh_scheduler_act_set *)value; + net_buf_simple_add_mem(msg, set, offsetof(struct bt_mesh_scheduler_act_set, scene_number)); + net_buf_simple_add_le16(msg, set->scene_number); + break; + } + default: + BT_ERR("%s, Not a Time Scene Client set message opcode", __func__); + err = -EINVAL; + goto end; + } + + err = bt_mesh_client_send_msg(common->model, common->opcode, &common->ctx, msg, + timeout_handler, common->msg_timeout, need_ack, + common->cb, common->cb_data); + if (err) { + BT_ERR("%s, Failed to send Time Scene Set message (err %d)", __func__, err); + } + +end: + bt_mesh_free_buf(msg); + return err; +} + +int bt_mesh_time_scene_client_get_state(struct bt_mesh_common_param *common, void *get, void *status) +{ + bt_mesh_time_scene_client_t *client = NULL; + + if (!common || !common->model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_time_scene_client_t *)common->model->user_data; + if (!client || !client->internal_data) { + BT_ERR("%s, Time Scene Client user data is NULL", __func__); + return -EINVAL; + } + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_TIME_GET: + case BLE_MESH_MODEL_OP_TIME_ZONE_GET: + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_GET: + case BLE_MESH_MODEL_OP_TIME_ROLE_GET: + case BLE_MESH_MODEL_OP_SCENE_GET: + case BLE_MESH_MODEL_OP_SCENE_REGISTER_GET: + case BLE_MESH_MODEL_OP_SCHEDULER_GET: + break; + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET: + if (!get) { + BT_ERR("%s, Scheduler action index is NULL", __func__); + return -EINVAL; + } + break; + default: + BT_ERR("%s, Not a Time Scene Client Get message opcode", __func__); + return -EINVAL; + } + + return time_scene_get_state(common, get); +} + +int bt_mesh_time_scene_client_set_state(struct bt_mesh_common_param *common, void *set, void *status) +{ + bt_mesh_time_scene_client_t *client = NULL; + u16_t length = 0; + bool need_ack = false; + + if (!common || !common->model || !set) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_time_scene_client_t *)common->model->user_data; + if (!client || !client->internal_data) { + BT_ERR("%s, Time Scene Client user data is NULL", __func__); + return -EINVAL; + } + + switch (common->opcode) { + case BLE_MESH_MODEL_OP_TIME_SET: + need_ack = true; + length = BLE_MESH_TIME_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_TIME_ZONE_SET: + need_ack = true; + length = BLE_MESH_TIME_ZONE_SET_MSG_LEN; + break; + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET: { + struct bt_mesh_tai_utc_delta_set *value; + value = (struct bt_mesh_tai_utc_delta_set *)set; + if (value->padding) { + BT_ERR("%s, Non-zero padding value is prohibited", __func__); + return -EINVAL; + } + need_ack = true; + length = BLE_MESH_TAI_UTC_DELTA_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_TIME_ROLE_SET: { + struct bt_mesh_time_role_set *value; + value = (struct bt_mesh_time_role_set *)set; + if (value->time_role > 0x03) { + BT_ERR("%s, Time role value is prohibited", __func__); + return -EINVAL; + } + need_ack = true; + length = BLE_MESH_TIME_ROLE_SET_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_SCENE_STORE: + need_ack = true; + case BLE_MESH_MODEL_OP_SCENE_STORE_UNACK: { + struct bt_mesh_scene_store *value; + value = (struct bt_mesh_scene_store *)set; + if (!value->scene_number) { + BT_ERR("%s, Scene store scene_number 0x0000 is prohibited", __func__); + return -EINVAL; + } + length = BLE_MESH_SCENE_STORE_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_SCENE_RECALL: + need_ack = true; + case BLE_MESH_MODEL_OP_SCENE_RECALL_UNACK: { + struct bt_mesh_scene_recall *value; + value = (struct bt_mesh_scene_recall *)set; + if (!value->scene_number) { + BT_ERR("%s, Scene recall scene_number 0x0000 is prohibited", __func__); + return -EINVAL; + } + if (value->op_en) { + if ((value->trans_time & 0x3F) > 0x3E) { + BT_ERR("%s, Invalid Scene Recall transition time", __func__); + return -EINVAL; + } + } + length = BLE_MESH_SCENE_RECALL_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_SCENE_DELETE: + need_ack = true; + case BLE_MESH_MODEL_OP_SCENE_DELETE_UNACK: { + length = BLE_MESH_SCENE_DELETE_MSG_LEN; + break; + } + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET: + need_ack = true; + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET_UNACK: { + struct bt_mesh_scheduler_act_set *value; + value = (struct bt_mesh_scheduler_act_set *)set; + if (value->year > 0x64) { + BT_ERR("%s, Scheduler register year value is prohibited", __func__); + return -EINVAL; + } + if (value->hour > 0x19) { + BT_ERR("%s, Scheduler register hour value is prohibited", __func__); + return -EINVAL; + } + length = BLE_MESH_SCHEDULER_ACT_SET_MSG_LEN; + break; + } + default: + BT_ERR("%s, Not a Time Scene Set message opcode", __func__); + return -EINVAL; + } + + return time_scene_set_state(common, set, length, need_ack); +} + +static int time_scene_client_init(struct bt_mesh_model *model, bool primary) +{ + time_scene_internal_data_t *internal = NULL; + bt_mesh_time_scene_client_t *client = NULL; + + BT_DBG("primary %u", primary); + + if (!model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_time_scene_client_t *)model->user_data; + if (!client) { + BT_ERR("%s, Time Scene Client user_data is NULL", __func__); + return -EINVAL; + } + + /* TODO: call osi_free() when deinit function is invoked*/ + internal = osi_calloc(sizeof(time_scene_internal_data_t)); + if (!internal) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + sys_slist_init(&internal->queue); + + client->model = model; + client->op_pair_size = ARRAY_SIZE(time_scene_op_pair); + client->op_pair = time_scene_op_pair; + client->internal_data = internal; + + return 0; +} + +int bt_mesh_time_cli_init(struct bt_mesh_model *model, bool primary) +{ + return time_scene_client_init(model, primary); +} + +int bt_mesh_scene_cli_init(struct bt_mesh_model *model, bool primary) +{ + return time_scene_client_init(model, primary); +} + +int bt_mesh_scheduler_cli_init(struct bt_mesh_model *model, bool primary) +{ + return time_scene_client_init(model, primary); +} \ No newline at end of file diff --git a/components/bt/bluedroid/btc/core/btc_task.c b/components/bt/bluedroid/btc/core/btc_task.c index df1e1ddfdc..edfc4a63f1 100644 --- a/components/bt/bluedroid/btc/core/btc_task.c +++ b/components/bt/bluedroid/btc/core/btc_task.c @@ -49,6 +49,15 @@ #include "btc_hf_client.h" #endif /* #if BTC_HF_CLIENT_INCLUDED */ #endif /* #if CLASSIC_BT_INCLUDED */ +#if CONFIG_BLE_MESH +#include "btc_ble_mesh_prov.h" +#include "btc_ble_mesh_health_model.h" +#include "btc_ble_mesh_config_model.h" +#include "btc_ble_mesh_generic_model.h" +#include "btc_ble_mesh_lighting_model.h" +#include "btc_ble_mesh_sensor_model.h" +#include "btc_ble_mesh_time_scene_model.h" +#endif /* #if CONFIG_BLE_MESH */ #define BTC_TASK_PINNED_TO_CORE (TASK_PINNED_TO_CORE) #define BTC_TASK_STACK_SIZE (BT_BTC_TASK_STACK_SIZE + BT_TASK_EXTRA_STACK_SIZE) //by menuconfig @@ -98,6 +107,18 @@ static const btc_func_t profile_tab[BTC_PID_NUM] = { [BTC_PID_HF_CLIENT] = {btc_hf_client_call_handler, btc_hf_client_cb_handler}, #endif /* #if BTC_HF_CLIENT_INCLUDED */ #endif /* #if CLASSIC_BT_INCLUDED */ +#if CONFIG_BLE_MESH + [BTC_PID_PROV] = {btc_mesh_prov_call_handler, btc_mesh_prov_cb_handler}, + [BTC_PID_MODEL] = {btc_mesh_model_call_handler, btc_mesh_model_cb_handler}, + [BTC_PID_HEALTH_CLIENT] = {btc_mesh_health_client_call_handler, btc_mesh_health_client_cb_handler}, + [BTC_PID_HEALTH_SERVER] = {btc_mesh_health_server_call_handler, btc_mesh_health_server_cb_handler}, + [BTC_PID_CFG_CLIENT] = {btc_mesh_cfg_client_call_handler, btc_mesh_cfg_client_cb_handler}, + [BTC_PID_CFG_SERVER] = {NULL , btc_mesh_cfg_server_cb_handler}, + [BTC_PID_GENERIC_CLIENT] = {btc_mesh_generic_client_call_handler, btc_mesh_generic_client_cb_handler}, + [BTC_PID_LIGHT_CLIENT] = {btc_mesh_light_client_call_handler, btc_mesh_light_client_cb_handler}, + [BTC_PID_SENSOR_CLIENT] = {btc_mesh_sensor_client_call_handler, btc_mesh_sensor_client_cb_handler}, + [BTC_PID_TIME_SCENE_CLIENT] = {btc_mesh_time_scene_client_call_handler, btc_mesh_time_scene_client_cb_handler}, +#endif /* #if CONFIG_BLE_MESH */ }; /***************************************************************************** diff --git a/components/bt/bluedroid/btc/include/btc/btc_task.h b/components/bt/bluedroid/btc/include/btc/btc_task.h index 0ed02d911a..7c5ec80072 100644 --- a/components/bt/bluedroid/btc/include/btc/btc_task.h +++ b/components/bt/bluedroid/btc/include/btc/btc_task.h @@ -64,6 +64,18 @@ typedef enum { BTC_PID_HF_CLIENT, #endif /* BTC_HF_CLIENT_INCLUDED */ #endif /* CLASSIC_BT_INCLUDED */ +#if CONFIG_BLE_MESH + BTC_PID_PROV, + BTC_PID_MODEL, + BTC_PID_HEALTH_CLIENT, + BTC_PID_HEALTH_SERVER, + BTC_PID_CFG_CLIENT, + BTC_PID_CFG_SERVER, + BTC_PID_GENERIC_CLIENT, + BTC_PID_LIGHT_CLIENT, + BTC_PID_SENSOR_CLIENT, + BTC_PID_TIME_SCENE_CLIENT, +#endif /* CONFIG_BLE_MESH */ BTC_PID_NUM, } btc_pid_t; //btc profile id diff --git a/components/bt/component.mk b/components/bt/component.mk index b77e8f6640..529da89139 100644 --- a/components/bt/component.mk +++ b/components/bt/component.mk @@ -72,7 +72,19 @@ COMPONENT_PRIV_INCLUDEDIRS += bluedroid/bta/include \ bluedroid/utils/include \ bluedroid/common/include -COMPONENT_ADD_INCLUDEDIRS += bluedroid/api/include/api +COMPONENT_ADD_INCLUDEDIRS += bluedroid/api/include/api \ + bluedroid/osi/include \ + +ifdef CONFIG_BLE_MESH + COMPONENT_ADD_INCLUDEDIRS += ble_mesh/mesh_core \ + ble_mesh/mesh_core/include \ + ble_mesh/mesh_core/settings \ + ble_mesh/btc/include \ + ble_mesh/mesh_models/include \ + ble_mesh/api/core/include \ + ble_mesh/api/models/include \ + ble_mesh/api +endif COMPONENT_SRCDIRS += bluedroid/bta/dm \ bluedroid/bta/gatt \ @@ -122,6 +134,15 @@ COMPONENT_SRCDIRS += bluedroid/bta/dm \ bluedroid/api \ bluedroid +ifdef CONFIG_BLE_MESH + COMPONENT_SRCDIRS += ble_mesh/mesh_core \ + ble_mesh/mesh_core/settings \ + ble_mesh/btc \ + ble_mesh/mesh_models \ + ble_mesh/api/core \ + ble_mesh/api/models +endif + ifeq ($(GCC_NOT_5_2_0), 1) bluedroid/bta/sdp/bta_sdp_act.o: CFLAGS += -Wno-unused-const-variable bluedroid/btc/core/btc_config.o: CFLAGS += -Wno-unused-const-variable diff --git a/docs/en/COPYRIGHT.rst b/docs/en/COPYRIGHT.rst index e9aa5a4ed8..cb4272af64 100644 --- a/docs/en/COPYRIGHT.rst +++ b/docs/en/COPYRIGHT.rst @@ -56,6 +56,7 @@ These third party libraries can be included into the application (firmware) prod * :component:`Asio `, Copyright (c) 2003-2018 Christopher M. Kohlhoff is licensed under the Boost Software License. * :component:`ESP-MQTT ` MQTT Package (contiki-mqtt) - Copyright (c) 2014, Stephen Robinson, MQTT-ESP - Tuan PM is licensed under Apache License 2.0. +* :component:`BLE Mesh ` is adapted from Zephyr Project, Copyright (c) 2017-2018 Intel Corporation and licensed under Apache License 2.0 Build Tools ----------- @@ -158,3 +159,4 @@ Copyright (C) 2011, ChaN, all right reserved. .. _spiffs: https://github.com/pellepl/spiffs .. _asio: https://github.com/chriskohlhoff/asio .. _mqtt: https://github.com/espressif/esp-mqtt +.. _zephyr: https://github.com/zephyrproject-rtos/zephyr diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_client_model/CMakeLists.txt new file mode 100644 index 0000000000..f384288f31 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_mesh_client_model) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/Makefile b/examples/bluetooth/ble_mesh/ble_mesh_client_model/Makefile new file mode 100644 index 0000000000..384ad8ed5e --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_mesh_client_model + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/README.md b/examples/bluetooth/ble_mesh/ble_mesh_client_model/README.md new file mode 100644 index 0000000000..38aa2caee1 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/README.md @@ -0,0 +1,14 @@ +ESP BLE Mesh Client Model Demo +======================== + +This demo shows how to use the Generic OnOff Client Model to get/set the generic on/off state. The basic procedures are as follows: + +1. Download and run this demo. +2. Use any app for BLE Mesh to provision this device as well as the device running the Generic OnOff Server demo. +3. After both onoff client and server devices are provisioned, use UART1 to input the unicast address of the element within the server device. +4. The Generic OnOff Client will start to get and set Generic OnOff states periodically. + +>**Notes:** +> +>1. The NetKey index and AppKey index are fixed to 0x0000 in this demo. +>2. If the client device is re-provisioned, but the server device is not, the first few get/set messages from the client will be treated as replay attacks. To avoid this, both devices should be re-provisioned prior to transmitting messages. \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/CMakeLists.txt new file mode 100644 index 0000000000..1eb2d87ed2 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCS "ble_mesh_demo_main.c" + "board.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/Kconfig.projbuild b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/Kconfig.projbuild new file mode 100644 index 0000000000..3bde11ff6d --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/Kconfig.projbuild @@ -0,0 +1,22 @@ +menu "Example Configuration" + + choice BLE_MESH_EXAMPLE_BOARD + prompt "Board selection for BLE Mesh" + default BLE_MESH_ESP_WROOM_32 + help + Select this option to choose the board for BLE Mesh. The default is ESP32-WROOM-32 + + config BLE_MESH_ESP_WROOM_32 + bool "ESP32-WROOM-32" + + config BLE_MESH_ESP_WROVER + bool "ESP32-WROVER" + endchoice + + config BLE_MESH_PATCH_FOR_SLAB_APP_1_1_0 + bool "Fix bug of Silicon Lab Android App v1.1.0 when reconnection will cause sequence number to recount from 0" + default y + help + It is an ad hoc solution and needs further modifications. + +endmenu diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/ble_mesh_demo_main.c b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/ble_mesh_demo_main.c new file mode 100644 index 0000000000..3dde33207d --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/ble_mesh_demo_main.c @@ -0,0 +1,532 @@ +/* main.c - Application main entry point */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "esp_log.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_ble_mesh_common_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_config_model_api.h" +#include "esp_ble_mesh_generic_model_api.h" + +#include "board.h" + +#define TAG "ble_mesh_client" + +#define CID_ESP 0x02E5 + +#define MSG_SEND_TTL 3 +#define MSG_SEND_REL false +#define MSG_TIMEOUT 0 /* 0 indicates that timeout value from menuconfig will be used */ +#define MSG_ROLE ROLE_NODE + +extern struct _led_state led_state[3]; + +static uint8_t dev_uuid[16] = { 0xdd, 0xdd }; +static uint16_t node_net_idx = ESP_BLE_MESH_KEY_UNUSED; +static uint16_t node_app_idx = ESP_BLE_MESH_KEY_UNUSED; +static uint8_t remote_onoff = LED_OFF; + +/* The remote node address shall be input through UART1, see board.c */ +uint16_t remote_addr = ESP_BLE_MESH_ADDR_UNASSIGNED; + +static esp_ble_mesh_client_t onoff_client; + +static esp_ble_mesh_cfg_srv_t config_server = { + .relay = ESP_BLE_MESH_RELAY_DISABLED, + .beacon = ESP_BLE_MESH_BEACON_ENABLED, +#if defined(CONFIG_BLE_MESH_FRIEND) + .friend_state = ESP_BLE_MESH_FRIEND_ENABLED, +#else + .friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED, +#endif +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_ENABLED, +#else + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED, +#endif + .default_ttl = 7, + + /* 3 transmissions with 20ms interval */ + .net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20), + .relay_retransmit = ESP_BLE_MESH_TRANSMIT(2, 20), +}; + +ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_srv_pub, 2 + 1, MSG_ROLE); +ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_cli_pub, 2 + 1, MSG_ROLE); + +static esp_ble_mesh_model_op_t onoff_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET, 0), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, 2), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2), + /* Each model operation struct array must use this terminator + * as the end tag of the operation uint. */ + ESP_BLE_MESH_MODEL_OP_END, +}; + +static esp_ble_mesh_model_t root_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&config_server), + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, onoff_op, + &onoff_srv_pub, &led_state[0]), + ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(&onoff_cli_pub, &onoff_client), +}; + +static esp_ble_mesh_elem_t elements[] = { + ESP_BLE_MESH_ELEMENT(0, root_models, ESP_BLE_MESH_MODEL_NONE), +}; + +static esp_ble_mesh_comp_t composition = { + .cid = CID_ESP, + .elements = elements, + .element_count = ARRAY_SIZE(elements), +}; + +/* Disable OOB security for SILabs Android app */ +static esp_ble_mesh_prov_t provision = { + .uuid = dev_uuid, +#if 0 + .output_size = 4, + .output_actions = ESP_BLE_MESH_DISPLAY_NUMBER, + .input_actions = ESP_BLE_MESH_PUSH, + .input_size = 4, +#else + .output_size = 0, + .output_actions = 0, +#endif +}; + +static int output_number(esp_ble_mesh_output_action_t action, uint32_t number) +{ + board_output_number(action, number); + return 0; +} + +static void prov_complete(uint16_t net_idx, uint16_t addr, uint8_t flags, uint32_t iv_index) +{ + ESP_LOGI(TAG, "net_idx: 0x%04x, addr: 0x%04x", net_idx, addr); + ESP_LOGI(TAG, "flags: 0x%02x, iv_index: 0x%08x", flags, iv_index); + board_prov_complete(); + node_net_idx = net_idx; +} + +static void gen_onoff_get_handler(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t length, uint8_t *data) +{ + struct _led_state *led = (struct _led_state *)model->user_data; + uint8_t send_data; + esp_err_t err; + + ESP_LOGI(TAG, "%s, addr 0x%04x onoff 0x%02x", __func__, model->element->element_addr, led->current); + + send_data = led->current; + /* Send Generic OnOff Status as a response to Generic OnOff Get */ + err = esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(send_data), &send_data); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Generic OnOff Status failed", __func__); + return; + } +} + +static void gen_onoff_set_unack_handler(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t length, uint8_t *data) +{ + struct _led_state *led = (struct _led_state *)model->user_data; + uint8_t prev_onoff; + esp_err_t err; + + ESP_LOGI(TAG, "%s, addr 0x%02x onoff 0x%02x", __func__, model->element->element_addr, led->current); + + prev_onoff = led->previous; + led->current = data[0]; + remote_onoff = led->current; + + board_led_operation(led->pin, led->current); + + /* If Generic OnOff state is changed, and the publish address of Generic OnOff Server + * model is valid, Generic OnOff Status will be published. + */ + if (prev_onoff != led->current && model->pub->publish_addr != ESP_BLE_MESH_ADDR_UNASSIGNED) { + ESP_LOGI(TAG, "Publish previous 0x%02x current 0x%02x", prev_onoff, led->current); + err = esp_ble_mesh_model_publish(model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(led->current), &led->current, ROLE_NODE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Publish Generic OnOff Status failed", __func__); + return; + } + } +} + +static void gen_onoff_set_handler(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t length, uint8_t *data) +{ + struct net_buf_simple *msg = model->pub->msg; + struct _led_state *led = (struct _led_state *)model->user_data; + uint8_t prev_onoff, send_data; + esp_err_t err; + + ESP_LOGI(TAG, "%s, addr 0x%02x onoff 0x%02x", __func__, model->element->element_addr, led->current); + + prev_onoff = led->previous; + led->current = data[0]; + remote_onoff = led->current; + + board_led_operation(led->pin, led->current); + + send_data = led->current; + /* Send Generic OnOff Status as a response to Generic OnOff Get */ + err = esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(send_data), &send_data); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Generic OnOff Status failed", __func__); + return; + } + + /* If Generic OnOff state is changed, and the publish address of Generic OnOff Server + * model is valid, Generic OnOff Status will be published. + */ + if (prev_onoff != led->current && model->pub->publish_addr != ESP_BLE_MESH_ADDR_UNASSIGNED) { + ESP_LOGI(TAG, "Publish previous 0x%02x current 0x%02x", prev_onoff, led->current); + bt_mesh_model_msg_init(msg, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS); + err = esp_ble_mesh_model_publish(model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(send_data), &send_data, ROLE_NODE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Publish Generic OnOff Status failed", __func__); + return; + } + } +} + +static char *esp_ble_mesh_prov_event_to_str(esp_ble_mesh_prov_cb_event_t event) +{ + switch (event) { + case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT: + return "ESP_BLE_MESH_PROV_REGISTER_COMP_EVT"; + case ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT: + return "ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT"; + case ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT: + return "ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT"; + case ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT: + return "ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT"; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT: + return "ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT"; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT: + return "ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT"; + case ESP_BLE_MESH_NODE_PROV_INPUT_EVT: + return "ESP_BLE_MESH_NODE_PROV_INPUT_EVT"; + case ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT: + return "ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT"; + case ESP_BLE_MESH_NODE_PROV_RESET_EVT: + return "ESP_BLE_MESH_NODE_PROV_RESET_EVT"; + default: + return "Invalid BLE Mesh provision event"; + } + + return NULL; +} + +static void esp_ble_mesh_prov_cb(esp_ble_mesh_prov_cb_event_t event, + esp_ble_mesh_prov_cb_param_t *param) +{ + ESP_LOGI(TAG, "%s, event = %s", __func__, esp_ble_mesh_prov_event_to_str(event)); + + switch (event) { + case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROV_REGISTER_COMP_EVT, err_code %d", param->prov_register_comp.err_code); + break; + case ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT, err_code %d", param->node_prov_enable_comp.err_code); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT, bearer %s", + param->node_prov_link_open.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT, bearer %s", + param->node_prov_link_close.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); + break; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT: + output_number(param->node_prov_output_num.action, param->node_prov_output_num.number); + break; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT: + break; + case ESP_BLE_MESH_NODE_PROV_INPUT_EVT: + break; + case ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT: + prov_complete(param->node_prov_complete.net_idx, param->node_prov_complete.addr, + param->node_prov_complete.flags, param->node_prov_complete.iv_index); + break; + case ESP_BLE_MESH_NODE_PROV_RESET_EVT: + break; + case ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT, err_code %d", param->node_set_unprov_dev_name_comp.err_code); + break; + default: + break; + } + + return; +} + +static esp_err_t esp_ble_mesh_set_msg_common(esp_ble_mesh_client_common_param_t *common, + esp_ble_mesh_generic_client_set_state_t *set, + esp_ble_mesh_model_t *model, uint32_t opcode, + uint8_t onoff) +{ + if (!common || !set || !model) { + return ESP_ERR_INVALID_ARG; + } + + common->opcode = opcode; + common->model = model; + common->ctx.net_idx = node_net_idx; + common->ctx.app_idx = node_app_idx; + common->ctx.addr = remote_addr; + common->ctx.send_ttl = MSG_SEND_TTL; + common->ctx.send_rel = MSG_SEND_REL; + common->msg_timeout = MSG_TIMEOUT; + common->msg_role = MSG_ROLE; + set->onoff_set.op_en = false; + set->onoff_set.onoff = onoff; + set->onoff_set.tid = 0x0; + + return ESP_OK; +} + + +static void esp_ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, + esp_ble_mesh_model_cb_param_t *param) +{ + esp_ble_mesh_client_common_param_t common = {0}; + int err; + + switch (event) { + case ESP_BLE_MESH_MODEL_OPERATION_EVT: { + if (!param->model_operation.model || !param->model_operation.model->op || !param->model_operation.ctx) { + ESP_LOGE(TAG, "ESP_BLE_MESH_MODEL_OPERATION_EVT parameter is NULL"); + return; + } + if (node_app_idx == ESP_BLE_MESH_KEY_UNUSED) { + /* Generic OnOff Server/Client Models need to bind with the same app key */ + node_app_idx = param->model_operation.ctx->app_idx; + } + switch (param->model_operation.opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET: + gen_onoff_get_handler(param->model_operation.model, param->model_operation.ctx, + param->model_operation.length, param->model_operation.msg); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: { + esp_ble_mesh_generic_client_set_state_t set_state = {0}; + gen_onoff_set_handler(param->model_operation.model, param->model_operation.ctx, + param->model_operation.length, param->model_operation.msg); + /* This node has a Generic OnOff Client and Server Model. + * When Generic OnOff Server Model receives a Generic OnOff Set message, after + * this message is been handled, the Generic OnOff Client Model will send the + * Generic OnOff Set message to another node(contains Generic OnOff Server Model) + * identified by the remote_addr. + */ + esp_ble_mesh_set_msg_common(&common, &set_state, onoff_client.model, + ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, remote_onoff); + err = esp_ble_mesh_generic_client_set_state(&common, &set_state); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Generic OnOff Set failed", __func__); + return; + } + break; + } + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK: { + esp_ble_mesh_generic_client_set_state_t set_state = {0}; + gen_onoff_set_unack_handler(param->model_operation.model, param->model_operation.ctx, + param->model_operation.length, param->model_operation.msg); + /* This node has a Generic OnOff Client and Server Model. + * When Generic OnOff Client Model receives a Generic OnOff Set Unack message, + * after this message is been handled, the Generic OnOff Client Model will send + * the Generic OnOff Set Unack message to another node(contains Generic OnOff + * Server Model) identified by the remote_addr. + */ + esp_ble_mesh_set_msg_common(&common, &set_state, onoff_client.model, + ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, remote_onoff); + err = esp_ble_mesh_generic_client_set_state(&common, &set_state); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Generic OnOff Set Unack failed", __func__); + return; + } + break; + } + default: + break; + } + break; + } + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: + break; + case ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT: + break; + default: + break; + } + return; +} + +static void esp_ble_mesh_generic_cb(esp_ble_mesh_generic_client_cb_event_t event, + esp_ble_mesh_generic_client_cb_param_t *param) +{ + uint32_t opcode; + int err; + + ESP_LOGI(TAG, "%s: event is %d, error code is %d, opcode is 0x%x", + __func__, event, param->error_code, param->params->opcode); + + opcode = param->params->opcode; + + switch (event) { + case ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT"); + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET: + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET: onoff %d", param->status_cb.onoff_status.present_onoff); + break; + default: + break; + } + break; + case ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT"); + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: onoff %d", param->status_cb.onoff_status.present_onoff); + break; + default: + break; + } + break; + case ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT"); + break; + case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT: { + ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT"); + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: { + esp_ble_mesh_client_common_param_t common = {0}; + esp_ble_mesh_generic_client_set_state_t set_state = {0}; + /* If failed to get the response of Generic OnOff Set, resend Generic OnOff Set */ + esp_ble_mesh_set_msg_common(&common, &set_state, onoff_client.model, + ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, remote_onoff); + err = esp_ble_mesh_generic_client_set_state(&common, &set_state); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Generic OnOff Set failed", __func__); + return; + } + break; + } + default: + break; + } + break; + } + default: + break; + } + return; +} + +static int ble_mesh_init(void) +{ + int err = 0; + + memcpy(dev_uuid + 2, esp_bt_dev_get_address(), ESP_BD_ADDR_LEN); + + esp_ble_mesh_register_prov_callback(esp_ble_mesh_prov_cb); + esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb); + esp_ble_mesh_register_generic_client_callback(esp_ble_mesh_generic_cb); + + err = esp_ble_mesh_init(&provision, &composition); + if (err) { + ESP_LOGE(TAG, "Initializing mesh failed (err %d)", err); + return err; + } + + esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT); + + ESP_LOGI(TAG, "BLE Mesh Node initialized"); + + board_led_operation(LED_G, LED_ON); + + return err; +} + +static esp_err_t bluetooth_init(void) +{ + esp_err_t ret; + + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "%s initialize controller failed", __func__); + return ret; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "%s enable controller failed", __func__); + return ret; + } + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(TAG, "%s init bluetooth failed", __func__); + return ret; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "%s enable bluetooth failed", __func__); + return ret; + } + + return ret; +} + +void app_main(void) +{ + int err; + + ESP_LOGI(TAG, "Initializing..."); + + board_init(); + + err = bluetooth_init(); + if (err) { + ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err); + return; + } + + /* Initialize the Bluetooth Mesh Subsystem */ + err = ble_mesh_init(); + if (err) { + ESP_LOGE(TAG, "Bluetooth mesh init failed (err %d)", err); + } +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/board.c b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/board.c new file mode 100644 index 0000000000..d81bc58815 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/board.c @@ -0,0 +1,115 @@ +/* board.c - Board-specific hooks */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "driver/uart.h" +#include "esp_log.h" + +#include "esp_ble_mesh_provisioning_api.h" +#include "board.h" + +#define TAG "BOARD" + +#define MESH_UART_NUM UART_NUM_1 +#define MESH_UART (&UART1) + +#define UART_BUF_SIZE 128 + +#define UART1_TX_PIN GPIO_NUM_16 +#define UART1_RX_PIN GPIO_NUM_17 + +extern uint16_t remote_addr; + +struct _led_state led_state[3] = { + { LED_OFF, LED_OFF, LED_R, "red" }, + { LED_OFF, LED_OFF, LED_G, "green" }, + { LED_OFF, LED_OFF, LED_B, "blue" }, +}; + +void board_output_number(esp_ble_mesh_output_action_t action, uint32_t number) +{ + ESP_LOGI(TAG, "Board output number %d", number); +} + +void board_prov_complete(void) +{ + board_led_operation(LED_G, LED_OFF); +} + +void board_led_operation(uint8_t pin, uint8_t onoff) +{ + for (int i = 0; i < ARRAY_SIZE(led_state); i++) { + if (led_state[i].pin != pin) { + continue; + } + if (onoff == led_state[i].previous) { + ESP_LOGW(TAG, "led %s is already %s", + led_state[i].name, (onoff ? "on" : "off")); + return; + } + gpio_set_level(pin, onoff); + led_state[i].previous = onoff; + return; + } + ESP_LOGE(TAG, "LED is not found!"); +} + +static void board_uart_task(void *p) +{ + uint8_t *data = calloc(1, UART_BUF_SIZE); + uint32_t input; + + while (1) { + int len = uart_read_bytes(MESH_UART_NUM, data, UART_BUF_SIZE, 100 / portTICK_RATE_MS); + if (len > 0) { + input = strtoul((const char *)data, NULL, 16); + remote_addr = input & 0xFFFF; + ESP_LOGI(TAG, "%s: input 0x%08x, remote_addr 0x%04x", __func__, input, remote_addr); + memset(data, 0, UART_BUF_SIZE); + } + } + + vTaskDelete(NULL); +} + +static void board_led_init(void) +{ + for (int i = 0; i < ARRAY_SIZE(led_state); i++) { + gpio_pad_select_gpio(led_state[i].pin); + gpio_set_direction(led_state[i].pin, GPIO_MODE_OUTPUT); + gpio_set_level(led_state[i].pin, LED_OFF); + led_state[i].previous = LED_OFF; + } +} + +static void board_uart_init(void) +{ + uart_config_t uart_config = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE + }; + uart_param_config(MESH_UART_NUM, &uart_config); + uart_set_pin(MESH_UART_NUM, UART1_TX_PIN, UART1_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + uart_driver_install(MESH_UART_NUM, UART_BUF_SIZE * 2, 0, 0, NULL, 0); +} + +void board_init(void) +{ + board_led_init(); + board_uart_init(); + xTaskCreate(board_uart_task, "board_uart_task", 2048, NULL, 5, NULL); +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/board.h b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/board.h new file mode 100644 index 0000000000..4ae04b2ed4 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/board.h @@ -0,0 +1,42 @@ +/* board.h - Board-specific hooks */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BOARD_H_ +#define _BOARD_H_ + +#include "driver/gpio.h" +#include "esp_ble_mesh_defs.h" + +#if defined(CONFIG_BLE_MESH_ESP_WROOM_32) +#define LED_R GPIO_NUM_25 +#define LED_G GPIO_NUM_26 +#define LED_B GPIO_NUM_27 +#elif defined(CONFIG_BLE_MESH_ESP_WROVER) +#define LED_R GPIO_NUM_0 +#define LED_G GPIO_NUM_2 +#define LED_B GPIO_NUM_4 +#endif + +#define LED_ON 1 +#define LED_OFF 0 + +struct _led_state { + uint8_t current; + uint8_t previous; + uint8_t pin; + char *name; +}; + +void board_output_number(esp_ble_mesh_output_action_t action, uint32_t number); + +void board_prov_complete(void); + +void board_led_operation(uint8_t pin, uint8_t onoff); + +void board_init(void); + +#endif diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/component.mk b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/sdkconfig.defaults b/examples/bluetooth/ble_mesh/ble_mesh_client_model/sdkconfig.defaults new file mode 100644 index 0000000000..e1aee38f72 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/sdkconfig.defaults @@ -0,0 +1,45 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y +CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY= +CONFIG_BTDM_CONTROLLER_MODE_BTDM= +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_BLE_SCAN_DUPLICATE=y +CONFIG_SCAN_DUPLICATE_TYPE=2 +CONFIG_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_MESH_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BTDM_CONTROLLER_FULL_SCAN_SUPPORTED=y +CONFIG_GATTS_ENABLE=y +CONFIG_GATTS_SEND_SERVICE_CHANGE_MANUAL=y +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_HCI_5_0=y +CONFIG_BLE_MESH_USE_DUPLICATE_SCAN=y +CONFIG_BLE_MESH_NODE=y +CONFIG_BLE_MESH_PROV=y +CONFIG_BLE_MESH_NET_BUF_POOL_USAGE=y +CONFIG_BLE_MESH_PROXY=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_GATT_PROXY=y +CONFIG_BLE_MESH_NODE_ID_TIMEOUT=60 +CONFIG_BLE_MESH_PROXY_FILTER_SIZE=1 +CONFIG_BLE_MESH_IV_UPDATE_TEST= +CONFIG_BLE_MESH_SUBNET_COUNT=1 +CONFIG_BLE_MESH_APP_KEY_COUNT=1 +CONFIG_BLE_MESH_MODEL_KEY_COUNT=1 +CONFIG_BLE_MESH_MODEL_GROUP_COUNT=1 +CONFIG_BLE_MESH_LABEL_COUNT=1 +CONFIG_BLE_MESH_CRPL=10 +CONFIG_BLE_MESH_MSG_CACHE_SIZE=10 +CONFIG_BLE_MESH_ADV_BUF_COUNT=60 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=6 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=1 +CONFIG_BLE_MESH_RX_SDU_MAX=384 +CONFIG_BLE_MESH_TX_SEG_MAX=32 +CONFIG_BLE_MESH_RELAY=y +CONFIG_BLE_MESH_LOW_POWER= +CONFIG_BLE_MESH_FRIEND= +CONFIG_BLE_MESH_CFG_CLI= +CONFIG_BLE_MESH_GENERIC_ONOFF_CLI=y +CONFIG_BTU_TASK_STACK_SIZE=4512 \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/ble_mesh_client_model.md b/examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/ble_mesh_client_model.md new file mode 100644 index 0000000000..49697c2ab0 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/ble_mesh_client_model.md @@ -0,0 +1,252 @@ +# 1. Introduction +## 1.1 Demo Function + +1. This demo forwards the message sent by the nRF Mesh app. +2. The user enters the address of the destination node and use it to forwarded packet. +3. The types of the forwarded message include: + * `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET`, + * `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET`, + * `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK`. +4. The destination node reports its Onoff state with the `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS` message. + +Example: The nRF Mesh app sends a `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET` message to the node that runs the `ble_mesh_client_model` project. Then this node sends a `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET` message to the destination node that runs the `ble_mesh_node` project. The address of the destination node is entered by the user via the serial port. + +## 1.1.1 What You Need + +* 1 x Device that runs the `ble_mesh_client_model` project. +* 1 x Device that runs the `ble_mesh_node` project. +* 1 x Phone that installs the nRF Mesh app for controlling these two devices + +## 1.2 Node Composition + +This demo has only one element, in which the following three Models are implemented: + +- **Configuration Server Model** is mainly to represent a mesh network configuration, such as its AppKey, Relay State, TTL State, Subscription List State, etc. +- **Generic OnOff Client Model** controls a Generic OnOff Server via messages defined by the Generic OnOff Model, that is, turning on and off the lights. +- **Generic OnOff Server Model** implements the nodes' Onoff state. + +## 1.3 Message Sequence + +You can choose from the 4 message sequences described below: + +1. Acknowledged Get +2. Acknowledged Set +3. Unacknowledged Set +4. Acknowledged Set with Periodic Publishing + +![Packet interaction](images/message.png) + +## 2. Code Analysis + +Code initialization part reference [Initializing the Bluetooth and Initializing the BLE Mesh](../../ble_mesh_wifi_coexist/tutorial%20%20%20%20%20%20/ble_mesh_wifi_coexist.md) + +### 2.1 Model Definition + +#### 2.1.1 Generic OnOff Server Model + +```c +//Allocating memory for publishing messages. +ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_srv_pub, 2 + 1, MSG_ROLE); +//Registering the minimum length of messages. For example, the minimum length of the ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET message is registered as 2 octets. +static esp_ble_mesh_model_op_t onoff_op[] = { + { ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET, 0, 0}, + { ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, 2, 0}, + { ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2, 0}, + ESP_BLE_MESH_MODEL_OP_END, +}; +//Registering the Generic Onoff Server Model. +static esp_ble_mesh_model_t root_models[] = { + //onoff server's onoff state init + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, onoff_op, + &onoff_srv_pub, &led_state[0]), +}; +``` +#### 2.1.2 Generic OnOff Client Model + +```c +//Allocating memory for publishing messages. +ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_cli_pub, 2 + 1, MSG_ROLE); +//Registering the Generic Onoff Client Model. +static esp_ble_mesh_model_t root_models[] = { + ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(&onoff_cli_pub, &onoff_client), +}; +``` + +### 2.2 Model Callback Function + +#### 2.2.1 The Callback function for the Generic Onoff Client Model + +```c +esp_ble_mesh_register_generic_client_callback(esp_ble_mesh_generic_cb); + +``` +1. The callback function will be triggered when the Client Model: + + * Receives a message that indicates the Onoff state of the Sever Model; Or + * Calls any APIs that the Server Model send messages. + +2. The events that the callback function handle: + +| Event name | Opcode |Description | +| ------------- | ------------|------------------------------------------- | +| ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT | ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET |The event triggered when the Generic Onoff Client Model receives acknowledgment after sending the `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET` message | +| ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT | ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET | The event triggered when the Generic Onoff Client Model receives acknowledgment after sending the `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET` message | +| ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT | NA | The event triggered when the Generic Onoff Client Model receives publishing messages. | +| ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT | ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET | The event triggered when API (that send messages) calling times out | + + +#### 2.2.2 The Callback function for the Generic Onoff Server Model + +```c +esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb); + +``` +1. The callback function will be triggered when the Server Model: + + * Receives a message that indicates operating the Onoff state of the Server Model from the Generic Onoff Client Model; Or + * Calls any APIs that the Server Model send messages. + +2. The events of the callback function: + +| Event Name | Opcode | Description | +|-------------------------------------|-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ESP_BLE_MESH_MODEL_OPERATION_EVT | ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET | The event triggered when the Generic Onoff Server Model receives the ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET message that **gets** the Onoff state of the server from the Client Model | +| ESP_BLE_MESH_MODEL_OPERATION_EVT | ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET | The event triggered when the Generic Onoff Server Model receives the ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET message that **sets** the Onoff state of the server from the Client Model | +| ESP_BLE_MESH_MODEL_OPERATION_EVT | ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK | The event triggered when the Generic Onoff Server Model receives the ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK message that **sets** the Onoff state of the server from the Client Model | +| ESP_BLE_MESH_MODEL_SEND_COMP_EVT | NA | The event triggered when the `esp_ble_mesh_server_model_send_msg` API calling completes | +| ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT | NA | The event triggered when the `esp_ble_mesh_model_publish` API calling completes | + + +### 2.3 Model that Sends Message +#### 2.3.1 Message Control + +The `esp_ble_mesh_set_msg_common` function is used to set the message controlling parameters. + +| Parameter Name |Description | +| ----------------------|------------------------- | +| `opcode` | The message opcode | +| `model` | The pointer to the client Model struct | +| `ctx.net_idx` | The NetKey Index of the subnet through which the message is sent | +| `ctx.app_idx` | The AppKey Index for message encryption | +| `ctx.addr` | The address of the destination nodes | +| `ctx.send_ttl`| The TTL State, which determines how many times a message will be relayed | +| `ctx.send_rel`| This parameter determines if the Model will wait for an acknowledgement after sending a message | +| `msg_timeout` | The maximum time the Model will wait for an acknowledgement | +| `msg_role` | The role of message (node/provisioner) | + +> Note: +> +> Please check the `ESP_BLE_MESH_MODEL_SEND_COMP_EVT` event to see if the message is sent successfully. +> This event is just for sending sig Model and vendor Model messages, not for all Models. + +#### 2.3.2 The Generic Onoff Client sends message + +The Generic Onoff Client Model calls the `esp_ble_mesh_generic_client_get_state` API to get the state of the server Model, such as the Onoff state. + +```c +esp_ble_mesh_generic_client_get_state_t get_state = {0}; +esp_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET); +err = esp_ble_mesh_generic_client_get_state(&common, &get_state); +if (err) { + ESP_LOGE(TAG, "%s: Generic OnOff Get failed", __func__); + return; +} +``` + +The Generic Onoff Client Model calls the `esp_ble_mesh_generic_client_set_state` API to set the state of the server Model, such as the Onoff state. + +```c +esp_ble_mesh_generic_client_set_state_t set_state = {0}; +esp_ble_mesh_set_msg_common(&common, &set_state, onoff_client.model, + ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, remote_onoff); +err = esp_ble_mesh_generic_client_set_state(&common, &set_state); +if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Generic OnOff Set failed", __func__); + return; +} +``` + +#### 2.3.3 The Generic Onoff Server sends message + +The Generic Onoff Server Model has to bind its Appkey before calling the `esp_ble_mesh_server_model_send_msg` API to send a message. + +```c +err = esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(send_data), &send_data); +``` +The Generic Onoff Server Model calls the `esp_ble_mesh_model_publish` API to publish messages. Only the Models that have subscribed to this destination address receive the published messages. + +```c +err = esp_ble_mesh_model_publish(model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(led->current), &led->current, ROLE_NODE); +``` + +### 2.4 Users Enter the Address of the Destination Node via Serial Port + +Please connect your devices and enters the address of the destination node via the serial port. + +Users can adjust the address of the destination node. + + +> Note: +> Please connect the pin 16 and pin 17 of the device that runs the `ble_mesh_client_model` project to the USB-to-UART tool. + +```c +#define UART1_TX_PIN GPIO_NUM_16 +#define UART1_RX_PIN GPIO_NUM_17 +``` + +The `board_uart_task` task is used to receive commands sent via the serial port, among which, the`remote_addr` represents the address of destination node that the message is forwarded to. Please enters hexadecimal string, such as 5, for this parameter. The address will be converted to 0x05 automatically. + +```c +static void board_uart_task(void *p) +{ + uint8_t *data = calloc(1, UART_BUF_SIZE); + uint32_t input; + + while (1) { + int len = uart_read_bytes(MESH_UART_NUM, data, UART_BUF_SIZE, 100 / portTICK_RATE_MS); + if (len > 0) { + input = strtoul((const char *)data, NULL, 16); + remote_addr = input & 0xFFFF; + ESP_LOGI(TAG, "%s: input 0x%08x, remote_addr 0x%04x", __func__, input, remote_addr); + memset(data, 0, UART_BUF_SIZE); + } + } + + vTaskDelete(NULL); +} +``` + + +# 3. Timing Sequence Diagram + +The steps for this demo: + +1. The nRF Mesh App provisionings the unprovisioned devices into nodes; +2. The nRF Mesh App adds a Appkey to these nodes, and bind the Models of these nodes to this Appkey. +3. The nRF Mesh App sends a controlling message to the Generic Onoff Client Model. Then the Client Model forwards this message to the server Model of the other node. + +The timing sequence diagram of this demo is shown below: + +![Packet interaction](images/picture5.png)
+ +>Note: +> +>The node **only forwards the message after it receives the controlling message sent by the app**. That is said, the node will **not** forwards messages to the other nodes every time the user enters the address of the destination node through the serial port. + + +# 4. The nRF Mesh App + +![Packet interaction](images/app.png) + +1. Scan the unprovisioned devices. +2. Identity the the capability of the unprovisioned devices. +3. Provisioning the unprovisioned devices. +4. Check if the Mesh node has been configured successful. +5. Configure the Models of the nodes. +6. Click on the Generic On Off Client option. +7. Bind the Generic On Off Client Model to the Appkey. +8. Check if the binding is successfully. +9. Bind the Generic On Off Server Model to the Appkey. +10. Send controlling messages. diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/images/app.png b/examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/images/app.png new file mode 100644 index 0000000000000000000000000000000000000000..9701c13ac91fcd87be1a2fac5f38255b0c79d33b GIT binary patch literal 443575 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%0AK# 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%0AY1Em=Dk5t-zahVEeaUwnH8BNE!<)x~W`F~h zbum&~S?JAtO*y+->&D*X9Hcn%*lVj7nX8)-HHFJkp!gB$6{6w=xWnEpUZz!5{8Vzq zdc&PDe#!h{0Rz{#03w89u5i4RdLU0+cOwd{e9xW~<6B+j9Fs9hXvQ$)DX<7wPJ`Al zx9ek66$x(#>e|!?(Va|vreVHM>pv7whOz<`Qs#q8$1+v#(fh8A97E(u>I06Js#Lp- zqm7u72joMmrVCU9rac2OU=>|ewq;M5!A5FuDMkub^E*DaL=*q}q4(}4XE}%M{4ttT zbZA{lnz?U|mX)<^nkX^#akzucAUrb8Q<}U{<*c3TUF9HuzylUp6^2YrpqIJ z1^7MdI^EU$NhLY?>#Bth{hc)&$T=*xkEW?{=$onTq3KB{rK{ZBHyC7C>oPNkcN%4+ z63?pb5!s|_=qNR3Np#2FKUUV5qts6FMeUpVuB>30z=fUbc3izDJR zV}^m_%Kf`zUVj}d+g!(J$VWW84Mkj$gsWgX;W;BjJc;&SXA=pEkvEdpByN;hm4RXA zwrQV^_cI0yUGJkJ`>7Oysy1n-p8k$$?>F{!A3qDsRYC@@IhbKVmShiAT4wcP zhM`*j3FfPLpXA5g|H^5sk15}o#T-z`lKgUMd>ljC?E1F8C2lA)qE?Yf5JgP>waJYA z16A2aGSs3+kT@RJO4bhU>?y+X6^?zy`4Egt8mQZz0$)sh=(QM1XGIcFCnzden4MNJLcdf7+o2U@x|^!OYD zUxMZ_^4^fYKxjxT^03zKKqgZ~W_K+iCjDTrl*ldH${D31NK}bKpH~riXm8<6roIFb zyL|elYf#kHCt$|2_NsgST^aL#4u~do@I}QN{c>+MUL|w5g&4XUNu0>L&BMiBi!=mk zOokULsj2!;si|)ruSir&&XSB$vW6qP3LebUBShH62s%SqhkljnJ^bWM9ZgUEV#3kb zF|tn`^6u0ANiyKjWXxt;bu(j_uvRow(q9ZFL2tc8CH$@~hJ3}TGxj@8Ei$t8rNNvjU5<#jXxMr}kG3kuyD#Ar4S9fThfD`*2mL7u3^Jl0z(SJE zy7A2gbvJSKuV^0oA>j_;>^3gOWrJ=G;xKyt5A8++1?B?EWpk#rBQ_^*`h3Lqq$B7v zC-KaxlSvqCljg~KCQZ$}qTdAalLhIEQr;D03fD>35vQ-7x*8!{w@?-eiG*yQP(bXWQO47Vg1V7z4ttZ#5YB{+dy%}3Cm zL*~d?0?xcx#OcSWuk~{nSGMKZnI#EOaQA*3mkVkuw4KVTWtil11d8p}XlR&aI4j$P=Pq1W}Mo2Aw*ae3xo zr}n)ob4JN}QQ;ikiHx`$QGrMLe}32yBIIjB=m;AB{~J#lL@bj>S;Car#69-aVM%go z^k2`*P?Qo-%e&y%HpRsrjA&SxlLVD@vG{E_>~u9W01{I75DSPEWvBWW7@JI5=Rbhk z7BKdKd2%1|wRf!O@H$`BG{H>=So+UZ@4x(021mA~Mpvi6BKL*QaV6L$YT@X=KY-wb zpKzIn)W0Tw9d2Kx4ESI;_|@d>e)UsLLJlF zP(=Ic7IEqMZTdK3aJf(r{AO3sbw%1Bt~mk_ZQg2174sb^`?6eA-hAApgG+c z9=f0Xz|x0(ct(X|zf0l8@p2`PzmguqcZa8^w@lNm2E}*`$8Qz6!<(ggnpbxdCIkjf zw|uB6J@y;tP$Twvx?CwNiak6oyS@QZ;wCjgUiiZfC(N*}IdD~Hxvn|0@1g(o$I_(y zDsaWD{=^LXQf`xZ(blc*9{gA%2gQMb@X)S|#BA62 zeN5H6>zr^@@DIZMAddKdDCX0bX2yZu#y0ZZY7XFT8T?1Uux5AhQ^FWzy%2l~+HY5# z4U8-JU>WJFl{j{1VmWbgCanAm#S^xtbxYiRuX+F;1r#hw1p-O&0rerL&KZ-y>ZCX9o zcu;07VsC73LCXL(4!>=xzLL5VDcO4+IV(hXJz(J*t8Z{|wC~e>uuxVrcKRTYJT@>; z8=ylj6CcA9a^pMpka0yjkRfw@WQ32eM*}y{esT9_P>qMWjPPH>B39I%x)wrgTcZkv znLU*$z3X8A&z@{hgM=&D7&=NP)7bmz1eS&>f;aT7Lv{Z7vRKhw^nIbgiDUkmZsX@@ znDb-WC(WE`#0_#zgft%q1-n&F6DVeI>ZPy)nEU%#oe-%UA`Bjl2**EBx0^LR3>|qG z98%=50ny11><6M+$4J`9MD7cQvj0}+Ar-64kMV$l7d+_Ief>x7zV0ke}A&a{TOm= zvCw2#GIPQV-KF$h4u}yZIkRFTdBk_r@wj6V?drWC?%|4hZ0e{vw}>;#zI%|#NmqZ& zxT`7l4V}5v+0;{b?pVIhxCiHZW&l()N|lvB>xn*;O3(By8fPEBcQ=2z>ssVW^)epQ zbwLGK!Ni!dlYXt=vCRRef_FxeZ)P(WQQm=AI*(cQ`DPD>J%IU{qd=x{u9hF{F&+2C zALn>W4|ioVf8%La0IYM`%mE4T;^dBL&Wd&8Q440{>-u}DizjC2Q@w@p*{%NCE`DzQ zzlQ3+sGcj-8BlWjzp5q;Dyd$Ee8Z%itK+poFws0t?Jcm>_@`umN+xb+ zCl^KxG=|(JG_Hkc>r1vSjz-{wt^?cPDHj{sj<-H+Yo5-P$v5C{HyN!d_1b`tt9Y+& z$ms*|Xxfh{OFD}mn6maV%kDLOK8{7>JrW9NM>;IRpt)DmS*pB9V<>GY9#BTX3)>@* z10ZFx!B(@}Qq>EBk=Hg`O3N1`9kV!=#%#gHT^;UW%=DN-Zw2-{qZ^w!iz>WTk2(Y6 zM$-P!vo}pUMfw{6SYvqVNRiGHfK>vMt%*(qK&?mR-+ETn?_xM-4;tqL>JvCfEF>+) zrM;IJX*AM1Y7Rwguka-TLo1%`xFZzLqf+rLKKfm<{;!`FLFTd?i?4xldXE3O`}BOj zYVfavpH4MZ=A1Ofsr_j|JzJ-=22OWPc>f>}WUY5Zu+&@LHGy?e4Nw)43K=hw!*H$r ze5SDglU5o7RU1cQ5-UU`iYl3o;*v0=wzPC~Rmyi0;Pa-6SC`h2L+F3=uDAoQx**)? zMnw2o?#SQPR$Hmz|BB_YqQW#&B`1;XIgzOc6ih1r&2Nqig_y@7%Pyh+vD`}Oau+e| zzhDeUG+WSf;sen_9FfEh(Z9zNf(U>d)@(_2X@d4|0YzU=Bjsp3@`wnP2- z;tB5j^Ea2aewJD^dBp5oL_Mp#p1<-I8&>DPsfpVLt`iKk+Fr+(=o!MJCK`_J4#otK z8o&1Z06Qct!2Z)~)4Ol;74xqxZu+{HYkiOq8jKG{pSz?Ld6jug&HqoMln8waxVzB) zpV-KT2q{-*fpfzM5KD!F*q0kxwZh~$Oi;-Slu;q4R|()&Q}zoeIBrqKYx8{;o7w%D zYVj2W-07?KrL}NW>yW*xo>A^fuhk{yvt}L?zAj9p9o-)2uF2n4#?xz;h^ZC5vtozS=U%sg+GOT zKQcvB+0Wi;iT^dIIe6eH0%4&1SsAwfx;*f;;4jQsMsnv;fBa2G4`=jEeK_7uR=F7tR&>(H1 z-I!jDdbm>=?_i#heqN~ZdKkO6Efd}s`Q(mJy5R7%S)Qe}HTfRnk|XAjuy6QyO4k0r z;9Ew3ou(0@H=h~t>#HJvV5}^B86zwncC|&fXdFI;54*9IGVe-7FwcD`8&`pSIdABr z3|l|iyNEegw_tvFF(-jVRMI77RaDKWK@ozn14hBLpcnI~MrSJsBleKm zITfZ`E4M`39cR|j)m-diyEgGLOi`VYcrWADQhdt=^kl z=qah!kG)C<%gC83w=98^9q~kFx`VWjBqay@M$+^mN5Di5oGLIz|MvzM|t^ zv`F%z^U*g)1#`beuQn>JjrJ0s!iKCp0q0REc=?ApgD*>@SYS?bK!CyQL7Zy>PRGYL zBTV)|nD{S^PG?J?#8dC10EkgT-!T0rOg_TvKVk5H_yH3_h+*mEK43y(ChC{mYGR2` zaEIp8bZMC$QDy2@DB3NnW#{%$2`a`Urg#jg8Hg5zL3CGqn7f`bzNe%0=t_9TwnkM> zi1s9@eC1o&EQQL`aDqM8niJ(Z2k3Phh6snoP!^AcrpsjnegdVr7nE}-R>Px-SEl$K zw{^jtbLk4OcUuyUA-&Gii{eE5oniIXiR|P)NhU-3+)9giHJ0~OvfpMPE;`NDX46#6 zwr2rJ4&BS#ar(ua@%Uud?0vX+HiuuVt4V*=rk2po1-LPqO}>16x0snPTM!UDE?LYH zV@!E8rgfn@^l_uqwwG4A0)f}Om(y4|e#)WEn;PFbm`7}BJT%u@J0rsh;iV$~a5vz1 z{J)B>K5Rq^DL#gg8=`tjZlrn*XvB-zln(CI{b zi!@}OeT{>7;IlafV4_!t;qkp`TQP`q{8hc+t#O%O(kJmvK88Bu?(OgEqnER%A6)Z1 zC-N#S1`)^fNn~M%Dp;du_e%xMr4CJ0jqh)2F^FfBYqj%oHd!LH#uLzZgH#1Z5>hyx zxKBu@6ziGiE~685=gOQWQG)Q)L?3Rvn&RYX*_(8;7VO%tD|3}S2^%ALY9_zX;ETsM zc(1GHK#)ZayTY>EiitxUKm>_}o&si+p|6_rXPzO;JvAEc$XoS^;-W?0h`O6jGHp#~ z#($F<^jk0NSvRY2M*dJ9Fm8?|0tNraO#3qnm5>lA*xwl0_4cr@UoUQKg@^R(P61{r zVZh?)(M#{I<}Wattw@|=-xFd8cVFGI30Zic^SL0>vwl zE00pHH10B89@r!IW^TRx-KhQB_im#%ymqvnKZe@AgSy^l;->t8K~3GvEtm=X!Hnv8 z-%N2`tOoZ!82|iqLK)vU%~I0rZh^2sZ7mngSReD5a5)YmnnPEvb$AlqghROOT5e2X z6RiiX>`Toz8e2j)Ax?Xv?}Sv}=JX@|z=aIEIproj8)419n6R0XYhLBJ*O{fM`NW8& z@>=<%fF_9|s-Cr4wurs7(fBkUp8JHENt0+{)7e&s>TbkMHHpS5a{+M0MOQ8VDLekJ zrOJa~$4*`shuk7EI19trkV9Dc0ON5AQaX`BmWU%S<4$NpLd1!tuKCA0gGjGli>M4iTkN> zZ@6w^>-~*HEbZw=@KJ%K`5<#zxl_j*dYcja!d#x|ye;A)j8UOLkLpu+>PyS&_f*|PT7_;YD$*83r#~=aNAkO1`QHi# z>(>-2RhM1b542;GbgD}rEu$0?x`h#$#a_Dd%xWG572x-px#x`Ld5MRkIZB!dL-F#g z-dgw2DQP;eC6ySfn=Da{1@nCAtj@hpH#$}LX8+`#OM=9-HGS33Ir9bP#{RXAsgpUL z$be^a1o5=81jX82IN4I`NwTZOYdCKMtWx5{F37pTP$Wz~MMI|N+)3O$EJ+WIJ7POc zR;d_P@>aB$R5g4E;y;#EZI_EPH3=A?-7>`m+MWiL)Y15m+4iyjFSq}jn6y%t~-!F?M+|&TI1!ic1wunLxOZwzC3Jj2dVPc`Es8mw-+_>!f~mU z_rXoOyw>MUWhP2z^Ql}JL-U1~xTka)T3$u1yo8*G3a33tVP*m#4tgGmc%jtQU!5rZ zd#$W-Wn13V^NYW8FwAPCg&OosBXcI?h|J|kl`pKd-aq7FG+HNo{yoGq>$5?70%J?- zSuEw8@<^+T(VzCECtkQLwQ`zpt?cK_)F+_%k;WaLuRUG+8aOEM*13_v{2h%q>#d4f z^VOQ6Xl`xLP^DI z%c&qcS@=N|EFv0fV&)jh$O<$@{;$d{!owCtNOhl@U;EzQ?myq8{R5hnq)@AEO6Q*l z{Q2!KG%zWPXfpJP62Q}6ldVrB|M?-rhAO7Ir@cMp{!_7BkYsZ#NXFIFUN#Zo#Z&nT zytR3VTcNdV=&wM0o;wUSuh-@+JfsGP<4IbG-K2S|ELayNVd8|aDJ!Hmg?6L`HuCuF zh)I3|3xODjNKb#-`B$zFPY*rUvd`AsMlxcVWDm1XNTv7&qI=`Fk7oP<71DJO7SCZa z+o?Vm;f%i(|0{CnPG=HV+m4Cg8><4SyEJgKc;VI}c3|ga20RBpxgDZ9IM2>oUHC0f zB};^Zek$fRsRGM?^b3wzRwL?veP+c+iZ%0Yw-g{g|0UJGjD$>~QJ~EH8;$x!pGxn; zMiKI6ZQu_fVmiJh3xGiCqeZkxgLAn1B-?yc`L7XX)b7#fyn0qsH~B~?;`rln>d5O{ z8d1WXLjPW)5+IH`Ljwz_{mM(6($#f6=H^#XVZsPC@^Ap7`e54gr=HN9{{rJ6Sd?(9Z?(0-(ZcujO&3d^tDfVw&H?*N-WQ9;$vQ3!@rZ8-k}AJ~#MIRI z)<|c*YTC$)-FFk{_NCdjFcg1lX}CTA=8O5$n95S)pw!$wXCryq7wn~_#cr->J#gU6 zsf}n6{`h$5rbFk3E_P=s{~V?-tCYYUb?!OZRsacqk_tiY+O}bbj*!#h-3%_d4vqu{ z=+b3IKjbOxz|5HLShGTz^$Mn%yCmssLL-)+fc7w zKJZjlR^CRh3oT?gYhIWW_q(v(oh)*=zuKZN^;J11ZLOQP;oq6BJ9-Kd?Hf4H5cGW0 z8$}wQtsV@9B%zRdDV2TSOwDas*FN3+@ucD;x%F1UW!{?G9H5h}MH7^D>J}WrKihSn ziL{tzde@421E0{$I*te$_s7vX95r7Ici276!q>S!IL$O6XFl5lXD5pE9H%2q! zKY4qzPpI`rSs!MP{>+_13B;C%`aa6`xU*goq|lPaAO>VF~iwP21{>N;!W$iZ#@5g zKBcuG{BSk$6eZk~K-zD!IIoz*wowiK2G9v?#$S}VidZPQ6vlxH_H!ib`qRGylSqmK{Cd3u?oM( za-;^im%XUt=@k`z6WhQ&++VZ~&fB&!qZ6uGt+(lks&YlcJbM zt@i;FkI6Y@MCP#f?_IMc9rR8QI{vj@{RO1j(7p&9K?s(Ch2}sEj~ZIJur`Bu{(UP0 zoxd=eZ?{mTH&%*_(8n<{IPe5?$}ZxM#0y&6{E z2{Rn4Zt6a%M3kMD`Wag@0CyD`1$l~KGTUxCIne3DZL`1wUDQMBxNt(@!yo_rPE|J6 z>@E-)^C{n9L8ea}xz8mf^ku9~<3TOtwFLeVE@A}pBoU$E(Kp<4W$z)u+bBHl{Hlnv z8q1=i#vQN|;@sKG{#*}WJ~#U5GZR>J8fZ=>1~)ywf%&p`QYEz~01 zZ;U+$JzI_5W}Mwwt5;CV=DTqb(%58(-!)GRL&K< zDK(IJzo9ptj<|x3TO+>IuPUvtO8chZ z-e{H>1%(}}>uyoOn?{#|j>b!1^3lrJE)kTN|6cO6o%K5X5qSP|Dn;_663eu`Sa$?CBD<7W)pkBCYSyf0-n3c~|h+AQDk>~}V z4{?kHVZzs2Q3nmcTxO@F%LJb5Aph)ANt!-$id&5e{05tS{}2eKZ)T+ zeV2=$Gp!@s-~({n+}~=^|EME>#J6XFNp|C%YyGP~Nhcu^qFN#`&^h6@sO7~>Xi$l_{kPquo{0hI9eSO=h@WUzl$d~%xD)7=?`K($T zWOyFpmS8X^VOF?O*oU?TO3-cr@%UFvG>=g7dygV7AW`{$rVtu_oFGP3Wi8jc5bdVh zlR*UdCmK$(#>uMEa2lQz5;3IdPJNUiK@PE<<>H_-hRPMq=-&{WEP9*zRpeRVoyPMm zo@6KJp4{M7|Ff#!eBw7Ux16ls&>{S%jfJN z6qgzn!7+?Fm8yrv8`9K;E_&p^Ix?cFu0spNaM#hj?TbJxzL)h#cPoL|h_1eRSWC)l zFSSOZlh;uvX#j~@M|kdZ}IHnx#+P8Nf@j}Bx} zWcX86owHc=pJ(=SPi z#d4$Fv7C^h&%7Kl{4{-T*Fl%&{D<|1p$Fpg6}tiA9mW*aVk7?Mlc^lkuLR?%l|q1Q z>-1R23<|Ez-eqfL%HqVM*xtL%ov``%D`mO_=6--Nim{Jb|x zp@zr5F9U9g1Do{b%EWgZ`QGj660WK%Yuz?bE2$!KEjmp=XbcHY#${QNvVr}}%3^?b z$%&IfvYlC&R{nJ=(S!VO@MY#F;b?&q=~2TNqCgaEg%$$n#_0vwN>4si~p-N z0S{Vy(gaQJ+=<=dZ)B)OsLpsW3^WxqK?Iqgr9d1p^_36I@eSpp{^FXR2Q{7E3(+i) z0)fkwK&AxvD`Ye&Auvi2{+*=nsN@+-Yi#eyn5`6vy?6|PXi2uwWgNC8!N@x2YY zp20|pREyR8<=C|&UJe>%aqSNGYqv>ka3?!XhD#uX)vo1#d~MVhQ+|vlBiXQ>9o|8o zBu)boW1bBtIPf`IYW8&aRYM1w8r($@K0?iWDrS2}s+>_Fl_pzZROcExG&}mn|Np!6UNdKqgCqhZyyJrTU_k3L(^$ zG%f@&);6q_DOZZzyE=h?(5Z4=EzYA{;A2CJ+>b#zc)Tk{D%DXP==K-SHlny*REk~E zZ@-Agk(2$P%nsTBGG7${nobCuybOK>r74tf%J|;QFL%&itR!J2dsYYfbg0CcMz}$s z@6>|yRj((G>=7*x0EhoCw!%o)$J|{Kb}=*v*!cfev=Z?FnVa*BzHRJQyRRaMo+kd z&)jKXI1^xKJZLJCEbgZrj)Isa8L&|FG9AFb@V4_c@73c@Yhm;h9@Q zyTxIqMStcZ9Ac<_=c&{}5>SJ%LjB9PqxU$#7!@X-EB+`Jh$R3{M`6MT>mg&2yjWl| z*m&}*>!#P!KBubFz;MX4hAS8FO7$A^GIoBJB(cqC@bD$Pyoo)%TToJ=Y6iUmt0m^V zi>dRe!4J-942gAQtU)N4!knIe{i-q87>C^+n}x&vrpE;S_zO#4X4NoXv1s;JNwU|h zKr<;7kU`hmzWacm^3?m3tcAns{)I>R4g^sccIY24?d;dKk*rGwnzy^@?w~<8geK_e z8#Yq>n4_EM?v_i_->4|>6^~D!1g#_2L25^PjTRQ-mANkTW8+H`=2#p%djhPD0u4;%zSHe5+99#T{?)^#fiV${Ocq>DFRj!;Rhs}2R;S~VS#UAhh zXS(Y$<{|eE_HG5#5r?nZ8OXKRU9}p_t62CiZ-k4mebldlHzq3a{=WKYQnm`$-)Gl_e1g(eefvZIwDgu3$n%qUofU<<=nf&P0yTK2; zLSjBZn+_R*GCH1TwEa=HfnfNZz;e>o5AJ1*!z%3iVXNZavd+>4x%KRBr%So!Kks>K8#Xs^OtCk zs^bvg)NLff%S3FT9|_t}w_fiS!~B1akh(759s+2%S2lLZ_x{SX{Hdsk_6n|Rl-ImX zqxFd`X6p`p-}6^~^=t%rFxi5-x5l)Oe8Fw`8lkX{mtC=pQ26STTAXtbHdcTaP_{h% zq}8!VdskZxH*rv^RKlZI zkV12*aTC&w!fn7E6>TPE{=+=$5q5X0A>>$@pc3NjzBrAOcWSjdu8W)tc-;ZZIAR;! z$odr{Yrh0ehdBC!jnklz+!z%dxxA^s!Pj`D+Wjb(Jpy^Tq0I+=qS`PyTqZMUh*4Gn6`fd3!n%xIu+tGd}u!GivON;7F*9 zhf*fApTq5V1yU~R_a!;BJy;+H1Nze&$l0r;n|_~O>BN6682>I`B>>%r+y>&?d%3w% z%27BCc-XcdSB(gvgMyomwk4}R!N0ZZdWwRc2EZ|7gZ|ylIh<+NYIJqCgmxCsV~W{j zVrlli=LYm5YWQ_1-|bk{)iT9Lh(S={wE+B+t4Ct1>zO?asBJ#j9RjzBeVO$n%=v=^ zj&;=*B4H%#gIl0Lr6si2;9UG>oAnykQqk!eD6~=*zO2{+`-sUtazG3}Ngv!tRr-SUS3R;THT6f70OkR0{hts_AD}qYBB7X1!ekgc$nTHZ=`FV-M^_ zm+h>Ofe*wsP)4YmacoC3)?wSg8!)mca_nf0PNPdA(0EfClYZKz?MLHJqJQE1IwfP1 zB>ZEaEq^cCoO}tfoxcA#LPuI69BjZaS};jXeAi#QQUWsArEt5w9N@&Qb+@4Cg|Q}| zkcgcUi=t9o;#J)bju^i?XxG8ZB?_9AAkcZ{k{_RmL=oQX-#y0CxR2Mz1>&EeMP&xP zX0CW;bDq!Api-7jO*Z-nQLVk`lO$h{dKqVujvS%B3-W931phYNFl4h~Y+zM&EdlCF ze=9$@(ul_HNspK(;TeTUw9Adodmz*&p`=y7_K~5{X*LZqZd>Is?E|<7B!ePGlofht zvFKOfOj4m}XZ&myZp*eUSDShVuE*^tD5UN{jf$owsTb%ec?N%t&z|ZCr4tF&LmF_E z#xiOIDCWQxDc6{69=-N1WcZRh@RWkZd@S}L^2vuYlr9{# zvOX+2nq7e_SbQL7Tuq`UWf(=vRikl|^b|IfQC~t_11dO9P@i!Fu(BElrUmRHC0jpi z5z8Am2W|Dn>;f1&*0OfOj29vPB$>%yI}EQ zMa-al7SvaSwP>{XX2|<;u&~T_n#KOiR4=(EE8qg4uR!iv>`1-KwZ4E=E!tCIoy&nDyn2WaD9b&;}?e_)UiEl6#g0SQC#sNM|j6FYF%6a7;+4zTAf84SxXj5eVURfhc zLRbw#Spis?<;9w0m>Z)c;+POft?EGX6|h(1DJKbHc94;C2|-Sfy)){U?Vc>BW}HOs zH=1cb<1{AiSOL~I(`41is4S73xuC>xOkhfgIz4A2Z-BsiT0W!lIBY3AkpU40BzQ~8 zJ_tT5S|q3SEAJ7x$lYX;%_!8~v17r;_f(J*mN-=*Pf})WK2zZdRVAPeDPh@TttvC) zvHe{4nizGG4&Lab!AE<^vFN(}-0<_U)B_-t^bj8!!`Kn5s9?O`hM}y<^IMgFFc%y` ztuimcXdfX0DsXo34+OPc7C1WR&7@EX5f>9l`qF*4u2S(@yl{t&g9H<$9&)gYWu;qc z!;PSp!=wps?I)!)XOBk^07x512=ze*u7XA}-$P^*sT1K0Lfm!eCdP2A_ftazI^){o z|3JD8N(dM@H!p$Z8!iNTRF2!WR{oQ#(uCH(dlZl*a8Nz8H&z{@f7P<${d9?*e7FXL zm9G!do2KYMOfzl$K6(Dczj9OWJjZ$I=^rq%hPomopsw=13$8Lfc0aqvSjmF#eDZCl z0hCX-BM~W;bnWuF>9n&33jRCoQRkg;i4WN1?`Ihu=!E%adA+tIhMmID|JVuIo}wOe zqcAan`-hD2e0~3>fdL?)$c8GXb+92?AO!{-_&kgHVUW8-aTczJj~ckYRYqGj4={%021hu$FI8<{ta7;Q&*(W%00IO~S_#V;dZk{lZ!f7Us^MGhFoRf_ zO~&cr*dVe0BEn1C2FAUB2A?6m-1lFol^75KXN8KL`=6!%7SW*}_yQ^gT`nTEVx*y- z&x}2>RE%aoxH|n5e7ikbE!L`dNtlyQYDy?x$Tm>BW5HXZRa^GceUp4AaaYZeQcJO- ztb4LB7{txUeV$6(P&ne4R;VS18J=PkZ`e3Yzm$PI%l2sZ1H+zO5LX20A~t)hGu0OENML*#x~$DyCLeLQhSq@J`i zynB}KNgLDOhD(osVKAI9VR){9>vt^ST}D@&2AM zl-=+jYNsVTk102ynz_Q2B8eTk0Mh*6tZ8QYZKMD|(9y-xSe=Whka-&fV&eqg}Y!JU}4KeBHA z4I*vKe!Ix7-+ltW0*yU&aBl*+#j$9xr|4p6$JSeJ4$*_K5fPIhT|z7Tho0rPMPa04 zh*{lv3&8~MUqk^)N}P#Mr?WAs5J&CKQZit7*NlIT`v2CsL~()J$3S3b{5_-m)WqLm zCodVf5~&ig&XyB58Cmv)bY?h9!%LD$_a*nO#MvC-yR*0T$nK5{_PxPJi;b>;=Xjn4 z%CP7#$eee02OMn=Y#)*I06g-l&|G_Sb)90SP?j|fLXz0Zet9RBKg|DZcdAsY{)5D` zdOi4r-FvJ-U`zSfp-Qu^P+X?!_E2hRI_gqDA0-%&$PdG(ANb7c7i17tQj& zJr)rc0_AJ0?lySZ9ecppVAGuF&x5f-bpX>wHPCX62yIMFv(>A`Qo~CE5jv zw#M^-5?bi94WP1?D^;!sVhf>OGyyD#`B9*e{};-`#5f z8P?WY6*|N&8LF|U%G);TUZD0REyEWJbpXJFsej3u54h~V*c|@Vm++V01NbB4kUE9~ z5hv*t4!B+*FgKJ8heDeJA9oGW9mT*qV442e}a-5O!7`cx^MIZ0d#P{Juf-VGQ;uU~3=oc~leZa$xi z2S#6AQXC)?{-@jdH`|+YC}bE1QqLM_wAwaK;{j=~gBZ-X_q{j~9oA#$ys)Bkh)|F|K4u3m55)%SYm50Z_`bO>>s7T{lsah1~oOn9QYf$I)CmD z5V~7(6QAMU?0f{Efi9NNrCvL&js|c*cID0^Rc~FlsI~;;(cIt>fbRf9cOhMn89-eU z@wr?Lf#e_08~lQ3i?l(yya4w}AlXn*I%>?qmmQB*E zVRskHib>tJ2Inyp{=f>wZ%KQOz3jSRFZF0%+32H=P5_h?YHe~3lZdl}d2TN$mitSp z>GNq_RzQsR8UI1DAws6_p74ffV<>(<;GfU?*EElzeB6`>HNo_cnSo*u6=#}%UZ12A zoK5hz9?KfsS6E->kc_kZK2!Qpkd-&F8PLpeNrwQMwICD@NLD&|shE@q8dbYriwA(P z{|QNYjYd`?{n?Lr86SKwkuAMPS<^M+TOW70+^KYhr0u)euiQ-OYr+5B&CotFBzQ6J zBmRKdMDe!iR7SK^f#S(b1}kciEu{4$ITbkoz9vCS!+RcKkGi8o#RUt0nG zu^#^VC;pyOC`@qyaQ_lNNZ?+M?j>Lq`QLKQU!R$6qr#rUPmb~r{^831%Ut~H-TYga zqyvcC1hh99kFBBq)7|{_A3P>+0ntZG$}X7TF?s(-SI9lK*W)J$ME@E6|KP6Ng8-t^ zeNuzv>wkJh3I!nS{O1D&G&!OI?ZhXARod_Vv!S&^WPik?a!Mxx|G@+N`3em8(Q=?{ z`n~W!y}~E}c0d zF*Ri7f6G6R`^W7a?=Q0I9$%jW2|V50SVB0uHxQ%an|{w(@(Cl=| zEo;kVwiiHCFimF8U8FpH(HwmQSjIBEu6GLbV|mUsG#i}Ep^)LtY?pio$y09tFP)r3P1t1tznvG>MCSCzKUS=di5Q$V<)mu4sKY`zLnB@CW%MC05oh1f^%#Gr<%rad$ zcEM*6c#g_wz`is72`GW?IW}qe5S$|+y8>U!Hhx|Ip}G5z9#?wsdpkm42)`^*jn>;6 zUQM^&Cu*A+Lau?xiMr*;Ab5M+=>TB5tvG4IL4^DqspupzEMA&*&&u>)N4^i9P;Gg6 zRh?1!=A-?pk)%PEM(_cG{oYjR22e941q1|GaTlv>R1|svkTKD7q|$g0#_D?&{_Od4 z)vs0~`>5|g{skExZ&-j73A2tN1uRevp8-jI{JY~;b$6oakGE5e7-LQV)a!3@KOLa8 z8({PV?0M|gbFWtyUlvDw?K8%NEdgI10BQsV_4n?1aw+T*EZ<>xvq0oFy1%_j4E}iv zWD59!8 zm3+Xp?>o~(Cq(QA$M(wQXcrX#w{*T&bVk}VAyv`%QTNVq5{Al4# zi8A=~3JDz@9eNcw@;kdg221+vb<*$Lk|vCA8E^usWD1Plaz-2wgYz~CVLs6Tq~FJ^ z%?)iWX}!Ph?{2Nk=-+ntJW^Q}-d(w)`<3p>buQ#H-xymacgS`!b!PW2Gw;wKGAXuf z!Rb1HOYAn{1)Hc%^SPS)=@0IWi)G(rz|uHHP6I4gGd+MZjc~2sQJap&+($azoU%mx z3|H|t z0t8egFyHcN4f+DZvudU^Wz~QZK_v+^t>c=$;I7>rPM|Z%J-;*_0n`!iDtpL21y1qDK^}hN2yd~Z$_R~%I z<}83YI(RckuY*p~UYk?h>{lteB}D<2Lp$=V{z0Q&P-fknMe*n`DPV*RHVr$epvl$& zd>+^*hF20gzavPzj@z@W9GOD2xQ%qIYktB2O_1g1)}y7u&1T0o!xYpRd1*BM6A;zx z$k9jWY@PI5hC;?zc@Q)pex{KZTL5N>1*#-zxA(x^ZRHNPdCvoXVt52zM=*Kr#b-{~ z*VYB;E^l@TorsPIL9aJ(H<~!0^%e684uJoS(X#A&e$&~wZ2uHw#-MbjRIBdT=Zk5M zarUyCuzQHQrg`2M-}03dLC;#Nif#}>tc1Z6cn`npQQKS07d&=fhCZT0 zo6kklX_jUITbdS6=17+>hzyst1J|tl&pd42>qWkK>ejhOiBQW>08s4Xz`C|OZq9F_ znfV-|*b@y6;#fAq=+DpjnoFPQ#Xhw2B5lgpKyL7JTt%TZ&l)$B3{aJv8B(VY0I;Dd z0ZB}x|65Y>ClZA?JId)Gc0Xb?egK4vGG}`KCiK_taMh_tUv_S1%MUF6#B$l|bWHTN zvLHd6uMp#kPCoV7$&Mdae&A3{h+^3eR)L*7iV2&}Ym8;*F^=1*#ii9cmhs*MtEbC3 zGV(CPX3q<(tu#(c&^+KJ4zy}`L3rr9nPM7uzH4A17nWl8o@1NfUBb8U z%vJ8NlLW}qt)G}$m!;^%S)~nj#3yzK0I`6+3@o!nC9)UA{4GYhc0YV3T%4ajFJo}W z9e_2mwDfGF)5;j|Wub0;M;W@m8u6u@5A)J~JMEEmB-3}(Lu}h+M(i-gMeA(vt?A@V z-(c31{;J~%te=fGA@1NU0>h-R1FTO8%efV(Jt@C6_=VpA>nuy|m8#_zyA02{I$PbR zo+rmvN=ce|wDa9ZY=%8SO#;5^I?k$>3Z#95j*!MHn-dFg;xR_yUa=9c018cH$g|zi z;5`^;hT?94u1e;Sis&`A6a_l!qY1SFutiblvlVnSc5zrA(H^0UwBpgR(b^d{z`gJU zG>Fc|vjxYI?nzO)rU`*w*hai-;%QOC+tS;Pn?exRD$x<#(c+`f=rjZ5RJC&&v!>#Ni^(8!Pp#4IltRm?>~{{m=g1qNzlU`bzrxdhc7OXgzai zRp^K-U8aR`91X>%P@=IIg%@QNs3Yy!)ZrgqtdtpJ&qWrf@b{`$3BuFq234;LbQe`#1RWhmDrbiJKZ zJ)|4WS{X%3Ra;` zkZXIare#JyI}9-BLu_|!eQ&hnO5r*bjmEzm^mo4nMNvkmOaKOYBD-vb#^_)z>TgcT z>L(hZEa0k7LjhIH&^?>VPnz2_M}byVXqbrcn(T-qJ1D49)GbiID7q5}Tt%CeIUo#u zEPDQ~k{JvEcD8?#m0N)j=bi#L>H;%0$X3(tx3s9iAqL9)%hDtrwi;&1#3!#zH=my;bTJG~mba3bg+JfRm4cp#pixHM!vvKE zGe2ooB01ze8hqe%ASq3)7Mujqs=FfGr!7EqP49P2aeY>arh_Om;Q0+CmPK|LB0avC z>|ZV_S!06{6r~BG-K%KWw+(GKNB?Yr`9gZnY%Lvj3`2cwgh<2=54&bl06IIY`iR=* zGe%Hn1lXLldp-zkvP;5=AWOW}xKMo_PVnB1Qs%bl{(4XUwudJUGei<~wy8FoZ&cGuaR0nF(;}*NiTU9n<6l*ot^lY0dy^r8lZ3=qfr`mz zicy8nWl$w;4!ipb8bx5Rz$wj3@0W!(YJ0J1iz%jU7%DB`93P^8l7_emhKk|z#!)sag{04A7%~| zbJS>;hxb@(0>HQUnX6Q3aoqWu+E>P~+I5SzxNfWzz$wZ9SYP?Efw)>AA&`Xrvz@3LGSB23bvWEz#?DOwaM($9H^;}l z9>DV|9R-*xzpTqm7k10r6^F4b0(~CFfZraU)=->zHNa;@z<=ES;lrMoc4yyjV$hibWXt@+nRkewlzET5P^BCD)~@KDgIDusrTAGj|>B4X54%~ zRB8+=T67|Dp4A^p7S%vg$}$BwGFDI%A^}W(`69aWJg1cbsC8m65wkrK*wjvXdS!yBaGzLYYOHo+8)dh{SLewIqk#b*|A`8@Hvf zNT9S~fHe?}+XmbbQj!z1*-ZLm!Ia1&Nzk)g1=N~%-x)P=A{DsIGOtoizUWS6*Y+Du z?+6$7t~uu}4IT|#7IW=~VFzhrzhifio&w`to z#!fDy-ut~WuJSw7!*sw^429Zk8{pC4;afP-^NpR!o^hO0zGEtk5l+3dHs-nvnSuwX zel$RGDVo*x4GL}QY6E^R@-lm9FnW3>y#czci@V7!ReTS}M6Xm}|0NY15 zdj|2os)(8q@7{dpY@azK`aMR>>azj$wm%Bl3$BWj`7NImA&ra?+&-a4VvGDO>!bUE z#Q!-?xk!EkF>V0ACf`xhX$NG|<|$%@UHxXg3MiUMffTT!xm~o0nA145=X1{ z_y*9+Ln>o3)8)syyp5SpfJT8n%EL)oZ3mR+;xC}Nbme+_@}7hd#XDZ*9}Se`-7(5t z0fU6m!^|BfCQ$LLrVtCb*^QcdeVwnf({s)_eBF5( zX#+_%=>Jed-oT;rhTin-v-j59#Xh7x0q4Aw&v0qOYt6@NH^FlIO$$yF^6^vA z+dkk_ip`&Qrg6_$Ek)(s!DMD6IpywtUfWF949+7uaByAWF%rLg zfcn}Aap*eb2YZCMRR&3SumBf%J3`N4ztLMoiOcNHm+@V01Si42@^P8G=Crw;C!!Or z9SgRRCCk_@Oz#H;Zh_JrZT2UrmV+Ugqp!0t!SMP|1EAASpJjxH*cM-n+z^U)>N-U@ z+3jB6Bfz{!r`!|q!C2!!iq6@Pyu@>85ZO=_&svS(S9>7h`74wzqBo1-A=eb29<>kP zta*UfSzI{;I+LRyPbB!a7xer0SvD@1IzCq$ zGFNsIU4pPGf2KgY6ZSR}r9yjVwQXw`(h6z0v{}wBEa$#_Gj@y^!f7WiJrq_)Zh{}t zsF(p(8tm}NZ_n!LbdhF!s>n|P}_G(UIl3z^F9m8`wdMc4>?42w{`9^`TWXF!2 z8Pu((BNfIkIkSWhcwxX@efsL%BmkEM_GY6ES2a(XhD=9r8f*E9rt z9eX?5R$rUR@ePPHQjezv=m3t}q27XNz*qqE|Ml=cdJksI;`!7J1$zBeJ-DPZU*aY7 zl1w%6ziRE$47s**4a;*;#oL(sK)LzW>Rvw{M`pd_gW~Cjb~g~a)cWOz8h9>(Ig?_b zyk^9%T8$6dTQ?Jxs?yj6cn3wBU0HfzLk%v<3gv-;FJz(QP0r<6AA>vv zBK3*6lq<*}WShUZY|5T;0cUTlpuPiqYTo^WSPe?&4tENFFI_Qkq!$@7_{?gkvKwiv zk1aAWo&r43Uiphp9+*^!C)7l~dF+A9PQFz2nWxY~y`ATv=R|B2jO1OYl(QC$rI>Ya*K|O(=KaUo38T`X zQ|M2w$w_bMLrlc6%$?5L^G$>AXY@fxL17xNL@_vay1Z!-k-KojabWB)SND-uWyH*C z;;M5uKuQdGVOj6Yk=}h8DqGL+Ha|#)!KnYS4^5f@m2B z8Wi)Ck)J}vwhctkVx!No&=80_K7?^M;Dxt6Hnx3A;DGPV{spwpqIK~ja`A|iRR%sA z{}6T$mL>sI$a?R1s^9;ATq0ysW;pgbc1HH5Y|6;q*(+pZ&+JWBHYM4P zy*UVF?>#~`*?h0l>-BzrKA+$3ziv0(IM4HZKCkO~+$Ytq;*ZLI6<{Yk8|w%Z(Ax#y zl!8y_V<-k3kMCma#5<6SePSP`a|PHMDR<5ulPVC`Ei3ILDmZK!877Ca3HjT z(l>ny$ts-hbbnFodd;UNj8EBukS;wGVonlBiE_!F?5CNvLzMkXA^9$EqD!}xF09FO|6CcNA5kw zZ<1A&Ien#bRFwSql-MxYKC&fZV(2AB8s>iN0MJzNA=k)&X(%#gUR~p@x6vf<=z>$v z)z^c|C;r#6NEDJy^T687^L^hQtc`2JH4M74&9>2Ia|+GcajUseF52CxtPNuoriCbY z6|V*})3=`MkxD(9PK`d+#xV}>TFe9+B%}7akw;OPIw&R10iHCpo#2XcOsSsme#IQ|uvmdfDnktaFCj6;gRzQ&8QwA0cW?#FZOh%zv1?Zrc^`$OJ1o;+@$ApatT|nUq3bmrj3Ui3?Q!sN+pp_nLjQ0y#%*d0S06~{$44D$Zs&pI+l#1b&}7b%W1C=!XgC>Ncd z$m*VFJ@F5T7MH5Kt>`Q3-Z0_riWfY~IeKHWmFTjZ@hwb-5Nn9KJ6D z#qH|-TyC^|vq&An!I!*-gB-~g9^4s{*H5>iXkp_Wo^AvuCRkm}ixzyrppx~(Noh?y)5YNE>G}eBI_%NBxR3cbIB_p6~&=0C0u@OI10W!$CG+4 zN+heMPG*Oq7f_IM;WQ;<*ISY zq{|)o4vQ@kY4{`pDe)J6bw~qAaf5D4M`+<k?HEYJh=IMGNz}@$mzq#`?q%vL+#B<~ zK7rnIqX@QIrtzpr124NN5%`)DJeIl=NI>WqH>N*+(%PsGSN5y_#%@*|+4T3behv}0 zBM=91&l7S!|9I!ksoOlqfNzQkh z*1`4^5B`43BSnou#W+MLK<{_J_7TZ{eIrQ;uK@mCMyJnM6aQRSDBxb1&)d7I@ZvtI z4q?sdq>AI`!d;kiQk^uM!Bg>9SI_+Oyt9vL5TGr2WAA@1*@92{?FryN3d&~p?r-<3 z@jk8_Lw)2!K#p8_F?eDoLfv>96 zazzq|*5V{L5D^V(sNIw<#&K7CqIO`oP~U+nWc9!MQ)0y-^>}&h@0^1GK?;QNQ?);m z(x&8oAa$b}ePLqQHk2^J+*5h`DPO}cnSY)t;Ej*Q+*W_N4d(?ggNFS^P>QjvZia#$ zR>Y0=!+=*bB6z{=mR)JL$==B72Gknj3ro4E$Lfmq*o@y5pA949Xo_nqzCY???Ax*6 zLRoDE0a|Gu?e(T~TIOeCM*_o`})H|6%Z*QNXgIE|^=1f`>_- z>SgjLeCKHZ%y=7)+zZUbiPfb*@M8@T0p~5;dcX}y85t9aL_ga|6&E1o>NLSL-J+3tAb^*SO5Et3sj$&r`Xm%Yea5qL(hQhpa z-ELki=a0{9%-wC*;Ek&$|JAPCLoALcs;}6*SgUrpM&+VqGOYOkNJQo^{AGMcvWC-{XQ$!BIwb}svbp_AD_Cj;x$=uf7s#{l%XqS$o^@P5 zaM*6YK_}0M4Zn^Qvr36lVvcZP-qVKRx-*^duDY*@XBBq~!hTwC=20mU&D`^WOJx2L zru7B$WAPOXJM^3Bv-*+@xT%TH1TkCPr7yfPT$UY`Qddv^W+sdVd>lOj# zTsEosNw35bbQrv$oOJ=33ELMJ$AkMD+ooI>R5mRndOhwkHYp$jwnCTr`Q@ybO7Y=Ya8^cnqgXhXKb` z=?f(|$RQ2kE~!2Nb3^dApZblzhj`6VHGWIml)V*Ucy}11_h(guSMCLO?Ni}jjN86IhKoFLQg@0y8H=GFRf&?qxp9_4ha%h)ir9i4>1A0aWIo)X7Mi4%&9_#89=ki*Ay*7e$SVXL-EfN`@tu}-OA~^L1r5-8VUs2m%uGa)8 z!|lpZ&GbNjlU}u%3C^+AAE70@m&rk4ylCHz(Q>4 zdU_%=K~7iW4~rwt4fDJ)wz@eQp!s(!FYk~!MC42@nM@rl9RtfOb{Q&lBrMwnj`m!3 z{<;OY_}2{YF16a82uA~EaxH|uOsY`4&A7Wu+ZRMD==z`2;PIIH0X>w+ST{)uS7V=~ zt|-tdi7#POi8dt`kO++K8+C4QT#{UuHEe(OGk#>4rDB#td3lIXh$8SNa31Z&6}vuA zuar8eu(AP9Gww~dY&pVWkX9txJjwerd8QKSGsg6n*M&QB2rS;k8u#rhL-W?*|D4N< zA0a`NqEXo8O!o}#*Cl~SjM1gEaM$47KJ_xmWtIEX3if!rY1U5vO*ZHpzMxI z4~F9FRaWcj`^#O{fE3woBnoBMypGnKo79Xz@!Fa{{9U(hvBv5wA%<@q3!Nni#N|2n zjGKb!A4II#w)y{ta1r?nv}KY`j6*N0&WE)lxgcdV@)lMduIR`vQaz0J-ST*Ev`u5D zp4?w1pm0zlCR(tKHf|>+2wH?oi6&qSIIM9+FoiK6xu{Y_T*oTI!y_{Sp~iWti!JXH zOkefH#6HpMEFd9&4?!yz%r)R_Zze^-MnO*IkL;6|{j5qHM9Mvzii3>0@@CeqPHCcB zl4iFKTh_Y>dkOWPY_5Gdx4Ua~dL_i5{1G?5)z1preUeoRbOr)DS=r9c(p{&}@7jXf z%I^TT?SGx4@{8x7K$tbVf|2{6@7dhpvf!ZJ#`LlA4bsqeiMFnOBYQt+^?1)phN%-p z06KE4W%#u%Q6OBQZkc(e7cQm$M|jE`DYBlJChD?yk}sd+7H)ga?Qpb)o*3-+&z;s6 z`|s%j8`nRB#t#t}!+98_YMcvaOn|^2i#k!YyH>H3^zCumP$uL-Ssyd#!jjwO(dZrv z?N6G;rxKr1iE1(!C>91|L(kQoq8&z07Hn z&JN_&+&;;U6VjG2e%9Si(%9rvdJoNh!0-un~cOWc>5)531N!=#S z?jX((gMHsIA>@j{&1S~FjfGfmC`z%3ZCZNV`;+OjuDzdcOjg=x{B&q5s(vyQ@#*xK zqYA0$1Ixyk#yYEgJtM^}rjK7|o(Z=VSd_lRgx^FFgmSt@a5gfr^pUMSr zava<4p#=rI_B{I?#DC@d5)Fq-z5_HE!XUR*S$>$ zH9XWyU|SA{Tw@iG0E}z?M!C%}{@QoK+Mjs#3ZagEpE$LwjQk(t!({m_bjV`|OfZb& zIzj#5>WiKRZL12x8w7^Csqq}r6I1&<+>6wZoU&^UYU4uWL7ffFFsHQCsT{ z)CDjX(E{I|9?3wm7LK-2X7+Z7XS!W2#yTQIt8X)yZ>1&1NoeoJ5nG$+UY7?SjBBC@ z3nxo1jK813r?`d+3m#)mTJ{@`=>s2fF>+zt(K?2xE-qL(sQdvL0Ely{>7NBgj=uk_ z)o?Tv=MP9GM=3+O-EK8b*zP#yN`;m8U`c~}LdP*fdvXk2ie_wa`v_5M>f7bLz&s`#QKui=O_Q} zwEW?WaVSNH0EU(sw8d29IL7yyR@=E0qYK-W>(GLc!Hh5nqt!{jU8Htk*iH8+9Tm$BL!CGqsvJ8Krv%j$=d57m5{!GQ zMFICW$DLFLQgWjlsY=ub$R{g%K|3hK7HGNn(uwzQVx=+yyJo;-oFFjgdv{|zX}67% zjEYKR`n%`%c+cz6lwT z$U%0XX%%14hEm-!{02;OJMSc*SSUrE6ha2riKM6%&xc52D$u*|Ohrls%inH1@wF`f z)J{Q?Oo^umqv`z0X3C^{P&5cbv!DiN`~>5gz$>mWlrZ`pn^;!uR@DdTZz00vAB=6g z-VvPHSq$Fu=zO;Y#CJH(AI$|;)%)9X(G}>SdgU|J8$S)ab|3Q*HUrIU4wR3@$>;oF z+26JSXoovs$;a^inqEpTMP-rGv9(3zBQ?fV)2iflk!gTHQQj_a-*zUE(V;F<=ad^* z(&CxEFspJ}i=~zdpvPH#HPfL@tXTg09`TBw2}ReU-F7zt=1P<^X#x$^&Hbzs#+y*Z zM1&4{0O5b64aOs>U! zB;HO7q*+ENX+~aIkuSzk(L@D-_Ve7R?_7|uSmKo*&}|RRdb9x+=pek)f@ed7RxTQg z{T}s|;vc|$3Bmn@ZDsQe+fpq@dq>l(DCG_Hg!uB;z(PbT*m-Iy<+;=R^*>XypYx-7 zX(TRVvj8(8I=g_*p!7Z}LI#Ghf3^co6zzQDoWNkZx9E#bNm52uEqOfN)Lq%cU?2*k z!J{{@PBh$#8ST(@sN)UfAc|#o$Q}-$cOb0HzGMAu+4z) zZAxWvoO-?eiO0|;DVmCRF^@x{bawZ#HAy2anS0?VVWd!9NGx;@oBQbOgY(5~a1HM! zoP7R!AeNqKyC4grtF|rKOeE?Tz+UV)%nEUWeD%I2s!35$kfoBM|r=n2b_v=(Y>7wq*+6%s#`-`Z*N zLH;YVgRIN5oZRaP3)&aCtruB)w@jpxS-+Y~CVzX%Ov&zqC#%KjZIu-zzj}<7D^M7{ zuEF&pR73Cy5EBX#%ZF;Pv}~1EUMWr#e>Xoh)p>8ao-&?duCFrbV1B9sewHOgwe#|1 z0@}KOhw8>!HWI^%ojLFF+{-9yCl_{o^sudGc=6eG7+&*PJd zBSxT?gx>lbP|ew6jFnSjJA9?Z!-KnjA!7`J7Qgh#h^NZc6+9*kn8m5|9)A2Posao zI*1rPTnCv+Z24`;|NNIvWq8FZ?29ZNKc0X3fOoWW5rDLBw;jh(+4%pt=D**-njo@= z6w>kv#+kB03Ok!C@ZSU7+I+Mh#wX^$xUixs4*?DLfDmy`EJc8&mb`hVSX;$@In zeiS@E{JSC$9#DWYw7gLQl>g&Bk8=OA4gd<%1gd_gXCOKWl_sQ9J~bGVxF5V{&;$l0 zmRE}a9ikl46&2lGc-ameUg?g|f>Y5)Btv!7w*{rOJm_B5+&e)2&)4NIEd7Z(UQ8Kz zeK~NO=KuFu{`q7zk>dUY4?Ro`xUI*E#s5eXt`M@Kn-p6PXN?EovgQIbLLcFoR{#mK z>xkXW+#Wy<)Um>@5=i=Qf69y$Dv>_}0=D>5iQ8BN31J2@Dt#{K6Gs7If*06Du=`05 z>WzSNREf;*0IfLE^4@C;*q-5@fT|b(10ns_*EJ7Y>lbomFt^6PXq6m&9)HvNU%C%& z3S`0;guAu2%dv8u9l}1=`fsI-G>31OZ9<(|=_H=J58=Cj96*0k9t-)+p-dk3nN!mq zRS#LYPwfyR-zxRHmO~(Fg8KF*gGBI91iRr0q`XG zQJwv~pf;`@u!9@{5k@~qDqRO@nMwrWdSV-?re&s(jdp_Hq-t@=A`1ERf5Rla-aGx(?cacw496QE`BkrOaY477^LHB z|NCebSP475kvT`8t2t2oljz+|rBWYuE^Tw%-^cAK5-_v9*fLwW72i0RZuRwj2ITAs zbTK+e5=eHHeGI@qM}siwtWD@TBq8#U0fNec0L7X>us6a_HVte>Qr@|&WiZJqQ+}>eM%@6XJoODN^hpJT{PrVJ}*%Fp{k~^ z>vP+u{O9_b<=SrTW$r8rgi^kVFPZoL<9v=5zmaIG@qA&pVuE9`0S;ZKgh zVe{+=%;9JtOU<82%-UJu87y7su#2$Mb?!&p9JKDXDQtn@Ul6vRI+k==tu&aO(0ktc zQS$+**o%|w)GXR9_m=MDU|Ky|hx5UUT*t0+%01nWOgv86SykLp9*H>~B^-$*r=hfS z#KXFJrRE=!N$HjZ&Dq3a{aKxKV4=e>??dkano5q|udCU4sCu6hQV*_{NyavKj4&;P zTj_izEp807I(-&c^EhsFKnvK1v(_`;6H?B3wV(Cg@9kOg$VI;W!;ygy1EBTGj{){C zJm|Yfcso6knX)zaO2P94h9ls80bH$=q%93P1v(Y{6R-**5D|Ic5Yh?TshW6ggGv(? z6dc#h(J}Jo*N+_kw={b(I+xnb+GGe1%rcn=rRT34$G#iY&0EY30-z>DtR!Rf$XG4{ zFY+OpG!e;<`*P#WE9F~G}OB&xaIhb9x*Sf6GY&q zym(>a>EI6?Q$f$qCAcXw+_b5WINfhfE_Clb<{J^r|D;;=m}!|Xp`b#RyH)$!&IK|B zD>RBX8P(IE&ihS$_vC=d|JX8>s zh()smz^53Kh$Z{qa0mI1{0{6O${z=12>gf?4w0up&qu^lWO;0) zi8Ez-ZkOZ;%hQzAS&y^8PXHwW2gfJkZAd+;DxV=L$H2dff*~IK`(@}v*H@1fI#qux z!Bb-^*P|;>Z;0LSwYFdupQKy;tv>TZZ{=`X=To_ni%9&BulmU79cHqRT5KJ+W-Aq2 zQ-1l&sMZl?CyWpI@Z-s5D`hc$9Xgkv$@-I1EqtMG<$kT;+}&t8CRh{~&x`T2Ud3GC zashwjPa>`vSAJ|iw@uGR9`6-J#2uGZ{w)u@yBF^QS?Z>v0Z@ z^0T92$=^0zr~-%v8do=-4`_%bZu(|4N~V~1K4db?x}WmjlNMsTZxDyLb_O`;6-O8x z2>yEuxMkW*kz``IBR+!X6Qg&jJupy4#X7*x-bqy&A~V`RHo~e1N&3D^?g2Xk ze#%&od=NS%O84a~#n1<1vf|Oolf^-^)Fa?yqSvM{=91(b_xVIa;b!o2 zoYnNZUjtiLTuh)$JRH%#ZSoPa%58Oc=g_2gbmDN-V?{nt%SzlYsO3%5Iz*`BIK9EG z_55QrV-wp*b(*mI_nAtCeK+;uve}uelMmHz#$Pk&w?u)0mn>V4RVGreZrKsZfyS+` zPR`RdtLugDJ^Q$%v^r`TVpolth zk;re>Y@ol^y*fF572qHDXgfvoW8>AdgTir7@X>7dtUON**0#^gabFt+l(2gsIie^dX&4ou?3n{=s6|c7K2DnNFEt4x z+E8ah&y*kkJZYbDAWKBzaanv3ACoj+GsDa}zv?!0d4y5JNn2d9>wT+Wq8XuZM3OR7 zhdj+*ptdyvrw<6V*$|BOVgl{+t@SN{%0#!{DlH8;iuDxw&uiuXnf8-!9w9%P?x#Yf zf=r*<@zrxS&*E>jlB8mGwTUP@uZvU8@!iWbnKEbilA0Z^WH;ncIQbo@W4L*DPxC+n z-9oL1xp-)5#`S?p4%U(JE@7SI2VXpl;=Ac@XkT(2IA)pg)NFZ|_r3E1;vwX6Ud>U% zJ+FH@8Ex4KiR%*MMv3}yWunZjORI!^XDpTt{w@J{^AdwJ9}~nb=F-_4|1Or;xc6?O zou1Cyr-w|w`&-)rHZ0(90}JR2t%BF%Q~SRxt@p`{J1^d@VQoTX=4+?lmu%*G(dGzY3{<|?WNQ+~9gx!Z3VLh5_);~S;u3s6IsqFFCf1Vy7Q+d`fpNKXxNTf~| z+;yzcn0b(*%WpLICkA_r-X72|JmTG)a zNpUFuU@LSj_`$>9DpiG*o4aE0F>^Na6W&>P|KQV4&Jq<*C%j>wuB5)kD0{dDP_C{A zrQ9WwlC7e}Tg}=J7IX)2RSqZ__CK&}f_!;u$fWz$wv-$CMZv7uUJk;FKDDBA!o!N{ zMg6}|tYdh{8+~XFBF0lxD<39oD;qw)R?vs*z0x^86+K2zQocPZTetDj-@8U+99(a) zrXYHK{ylzv4i_)Y7pL^yaq(E$9X3B4F>x)aReh2A&3KTUSt#h>r>`L2hdzp{(azLy);N>xhHK>{^ziu(|lv4r!rqj#p7~}V$1|? z{fzWQS%Hp9heyO-I11bH?Vk)|52`lWZ zYqGhRvq%vULxfU@UG^3HPXdxZ6!N3`oxius-o#P2jxIciwJv#@%zX2tw>658%fHP3 zEmw*iedSAx?E9gAVu!jl%tBcglL`hBkv`0#1$#dqVe)2ah?yY0v%rwC*IE?(MIWfzZax^TFi7WbWCrqo%`G>)irAHUcjp? zt}I4g6UnFNZdLlo9jywKZMc(J+;awwtpP;jS48GZ0n|RMwHYMf zSU~0Z;JBEm{*#mugjp3xq-N5x(ZDYKl89Z8gfEBpmDa@AtMQ=k**=#g)i#r5vSwwU z^-i}$O&8_SZD}AEC*8TDBbfN*_6N7tfo&x0n!GX;i)> zdh|4I&$9V%Us|hPlm6U`V)5{g<_haS?%qT0$j9|I3>tMlr8Q*T)?Vi6LaWNA4NRgw z$`@%EB%OM>#@5~OK2=paB$2c*&u_#$ZT(Z!8kiIJy~%m@T_Vkl){oMcDwaVCBEEc0 z9_zG5n6IKWF3?{V8$M}kk~8XwBpm@uaFatQ|34QV666=Gs@?Fmt9W1Ju#( zG6U&r=1x9i#cGwn6#t5x4^Fb;n+_dk=T!HrF$YJoIDid8tE<4Yh={%TvG=(U3?JHV zAaW<|nTtgUY;z1P?G=8=tFLuAb3nVc*at*&bfD%5lgl8_>g3l?YE9uF10AkS@`uU` zLo*00V5WD@=6^lB)-28J^SpLYC}PJc*bc5hx4wGM!v=hE-t?c4Y_rDkbhtVN+?U(lzvqNGcJbScL<4^ZJqnRzaQ_`NE|2nDevf=yafA8Z znBJ_Y=dHTV&fMAfHY0;WR(9fZfx znopG*AAtzF{u!HZY#=D9{~_ifi06z)C=8u}VCz8#sw7H89>_L+)$n$UfDC>O_b32Ty?^G5QJ60OmkPv<%-rHfsdx zwe=uEWppATyoYO{PgBKwTZ94XQ)$*m@a)&8!1#5ieE%1NM& z^V7{1eQ$=~5_8@sDv^tUNG-_y;Rl*8lVLb56oPMKK^aINL%J=28JFZP@YJDC=z@9) zoYN7vtR%4F@Cg$lgoYU)hA#)^wAvGVyPgILvR}obUW3LVRs?(7_6AHleYI*tQ^$IK z&G-uNF5ZJ2u{uzwqCQN?t+sc2wNGWHusizEH=#M?wHy*ARst(>s}O_a-xKgC&gW>_FV#%VxqnxbHR)u7v;r7XFVIoJIhu0u4cpA8h>%V zELz_?)g;rk6F*|xac z%+DY7uacpy&$tgIAfgu??SZlfI~zoeL^1c_mH9t*W?m*JtFano@QC{38&r4=m z7CmC#BGfoyS$a3XRu|M%g5i-g6~??p&|fZWQ(7Tg3M!5&{_M{7CURm0EWSKYz3O7O$JL-=6NA z*o1LYw)XPP&VtuIMHW|$0nX-@;gSE>c%S8k5{ zS{Yl4#&GhZ?(n>>i}m{CGtcgKPpcX8op-&c4)>Zc^jB3dyEVM7px0JiV;z>ksQZVj zy`iW+L|4xd@^Qtlw8&hh5r-4WU;-(5bnmItN|$5!5%ue_BYK^0ad_JV&{{-hE+G33 zyUNe)w~hula3jvw%yX!mJHjZ!u=eOB=pc>sAOU1s<_~~V>Az7|y>941Flwo<3+Vb{ z5m`!)?n#TtPLX6?YwqNWhfML->|qFqf0QOXHFTRc&++b`u`0?$Chc;NFBUnQWpn6xJjV+h=cvWjyIU{C z`K=_H(%&Cn&n8;0{+_NfwKh)_<~QjIBjMIKYX$K?2Z}oCU#k2#wh&UlwTOZk(MzU6 zBJJg(*TumtwS0FVJ6@v~3Oo8{9|10ZK+=oWBWq?Poyo6l9wUbCidXd^` zh+xvOLDrz9q!D)TZ604NhWuGsaYCT9M`E{wx^Uf1!mZ3+7q-II)xaUh~<)lEuKv zH6j(v8R$@gy&NgrN0s+E5}A9|2Ff=N!7k8Ah%Be>_7`y{&xTFRwYppbFYUFC@YtS z_5kLFwOE3y*Y-niL#H05n)j`(WA0LBz4MgPek?Y;^1@C)y$2BiHDkLj`%Nu6-Zod- z;8((%c|EU-^%NO4%Xqu|VaIQ1$tS3-PVGf&MvhKJL>A8LNm?P-MEB8C>|mLzIvxO@ z?WlQ!1=;Q$r&^D7ydR3mU(TbmCAFRF>y+Kg6SKpocO4gMV^k!q-c9?yr>rV(LR^j*?$p2Gm7hj=jx#y@6M*=4)p?4AqMZn}i4zlD zk09hc%oFGuWd0!_cu0FF6f5YNxm4uY5~mJbi_%~_^dfgM6d3khTQ|UAlVh?eS*2>ePeU2{`@RkW1;5uGCl7^ zh=fDv)-VGaGhkZDcGs*|R=LJ=)7T`Uw$jq_C$|oPvH{NE^S0cm<$%~~gH3%_#P(dV z0T00&4;wu(i&je=m?c*h39q$(LaAT3Lzw>?lZp(P{UZX*yDl~VcSiVG*NFUPZPjmz zCTdxg+KP->cx%Y{i4BsNhX(RQv`nvh`p2XUwsV`|^5ni3>%Z(v}`h$NP7VkSr}ex+8j*=~ZOMzsGOBs@#%Sl?`L`LSHZt{MA)T*LBFJ+ptX z420E6J6c?aPBo*{_)Msi0*7Al;n;^laYn!8B_T~-JZ8O7Vbt(_Yn}V%o%nl78&!*O zl^xCDNAa#s9IhHZxqAmztx61L2-d+VEZKHOrxn~eV}OIn6I?uWCG!Bcf&7k@sZB%KK-~=4~t%%JEASS zQt##5Ix%f}@8s-NpXGL=ZO}?i+QtZWs&Z2$L`+-Itag}XoY(KEigtovVQqUOb7M2) z&EM{AqQ>)xxWWe!ghvQXer{Q1heTSe1oT?hr$3s2)xxgpnX3dND14NHsFY7FKOvF< zov;e*)!9xxq|QL*#gN|L(bC|D3BdmsO%SSx6ZoJjR1r#D)x-U8Ld*!ixzN5}(x)uj zW$0PoADioZGocaVL6Jn6S>7_`5kVAZG#wa`Qmn4FK$g;)-DzR@@@9Mk&#g2{GwB zAhL>;70w1TNzEHQnt%Ad_dxgfTo^JduWg>jOERIK=&=?j7xE{cplB-#S1hF!;pw~H z6#{JXf6)gT^f60rI(@lKbuT*_d*{Li9eEL|`4X*URIOKfl?DB36PPAPX{;CnDfVc_ zG8{TOLWEz`HDz4K$4-xCROkN`EM%*|#vU#3YS?!iF|=o(tT5cR7Cv^*>oqr$cq(y) z=OB0>=G1{60XNH%o_JZmN_TejXn&{cy}#~_mqeN12Si~Dk;Nb0$%JT)Ev0s4t~=bU%SzMs(2DS2QJ2&Z2<0lLv#Q%>JWa^Y6#*Dv); z*v5&e{i&gq)pyh;0DSUVe>pk&VdnMHFeGJUAnjFrQ$rwgi>UIc%}m zu)anR$D!$XuebRGlTJy{h5|kOZ!4x)XA~JpYC%HW%MIk{N<^f~k^zp+*0*R1p$C}6 zGZ9~S(Al8nAn7KSij~>rUhmD$*E$17HuM~@73GRn)h-)c28f1aAoFxSVx=ZvzZV5t znHE>h!WDg{A_VbryeDTs;JP#tvuS#;)Xojuq6fOB4^jE;0FT;&1)K%ir{d>7dzX6L z%>tUbXmA41v3#x%<(4lxk0Nh5>s-fzo8Z9%QdyS$l5R5~bgKx?JE^B}S#WH4iVVC^ z+cNx~wEyA5@5v#dIEOzf>7(qcahutD&Va+`QSGv1M7T5Etf{A0^6;nWl&t%nj>ym9 z%1ekVTFLqsmS++kwN-H#j;{tv`Ya1`D+g?(syVP16=&e@)wGz>PLyPvFHcXT|tVjP%k4kE{8k6P*sA!YS6O0_CC^MFbR&bJdICU?x8|@Px287nI zAB2W@)N%WRwhsm-^W@%BQwxE0>$10(PRie&ddjg;xX6eVCVbnS$_`kF`f~VjJf?mo zWtl^=!-R3WkX7&j&$P~14>GcP*Ia8Fjt_9_b_DnqDQCt{@M^LED z7~Bh#hJ@gEwF4*HYwPdN4Zj98#kSOjpPyrd%fKcOl1DpmQCIeg69l zDnaligmxs#3H!bYg+HlBX=VvIZfSO@op!JIcPEF>v%FOy0e-nExi`kE3cyS@>W6{y zkZut)Z6-H8VAtmK=eOgDV7bKP#+H1b11W`Z%#)-Dde}q4;HFbEJbhIU`tGaAU&t;w zQ3a8;dTiuQ75!oJ*jfb3*{gE&j-y_aCu$7#$6h1JGvN|a1oIVKgelbFL3DTpEGw-XUck*uP`7xQsK%&D)MT7wmjz1iGJQo ztw?X1Alq@@q$WDQ{*f4{^|Gb~KyZwWC(6FaPBdtH8`eiPl%Ztr4Z>%#?&k-7??^)w)K3Dr3NyZhe5IpPMj#{ zL>!TagC7_LWy{ztWRY}AGq;mB&xDZcBmH`j`qvknrvGQ%_%wp9t@rS_)nE(R`QU*~ zL=D@dv>A6#S#^LbKMvjFF5F(KVNsU4Cr+QL$&VADS4W-YRp00MNia->z6{3j=i`W$ zOea}**!CJ#&%-ugKnL2%t~5vG4Z?(vz+A|&#V;Z#QSA&9yQJX9tFjzcCeSSL>CY4T zmFFxwz?tUQj(_Tr^^tMj!6s!qVMe(#Df7Krle#$LN;quGYUAIh% z&nMYQ>6d9`wnVm0VkY*S&b#$rRgQ|4RA@Y7fID~p!Ym-;V(wl2=?$~nud`@l#{xFx~q&rRuriO?aZ(*S{P0TM#Yk{h%@izrX8@cY!D9 zN#UIxj*sqNM{5v~{A5?CEmtIkTea(5C{1s8Wz+`TZ)E9>p=y=%ijDB`e?qIq-pGOR zC_)DP)Fp~YbB;3pJje{ zKf5c)E)XEQ3f1_-Taz{pq*kauI)erl`~G5 zPX@``ke%MwT8~d_JJeShYm&E$LkliHQKej82V!0HQaJxWjp@78wR?o_jNpkOZ36LJ z^roYTRsk!DEbIDcU2bULZ-@I?4?bI$7!t96e@;SZ**NF@3oWH$nHq4#ojWf7A&&tl z_X!W)i`wZ;?W3p+^|lwi_IsSgdR#GxDxU&)H#R)vSWEywf0+Xs>~^hZi-$4T7lGfb z<+H);&5Vbo-O4EuNkEa_-u8l)8o3F(jq>5w5rLK=|}NdZ9`B&8)S zq&o#^25HcLjr-Yqzu))$&wCtfHrr#|%-r|7*SfCrIwR~XVG$E?FE;0^kPS>$5p-*s zW}zcX^XC{6%W)Ao9WzI?)SMrLF?vYNCNPQcfUQIaaBWg`#m^1V#w3}{3O0iA|3heihqE<`d z;0RkS3)Icwe7|JnFG9mj4v)^#a=am^yNJ!74>{j)pQ2B@ape}|XJ0x-Wz9_L1+~_F z5u84D5bPS?&V_wow0x`f?xo9=<>?yEe1Agr+tm+2c-0t0yaRJxb2l)hr(Iz zc$F`}X&BqX0`s|w(GnrF`EqsvwTKfFZN4WPJff@8kC`d)JPG~Y+jd72qlFufA6iw{+!=T(} zYvQYq{@9SCp+yOWt>E*vx&4T(lUIVPFI0jPO~-fGB+H^olg0Nbw7ZF;r_br%`;q?m zR#yk6impQ1v+u}07d;XZ^V}HFta$sawPeVcMaa)ZcZM)4wdfV0I_;pl_65f`s_vBw zX*|_5ssyzOJckcSmVV2c=4g3bo_||?e*7A5 zF!z~%ty-dAqu!}J6j(6HA~0rI`o#@z5}9v_mK47@D`t8LeN#Ms+4QI0Bc{fj050mBIa~<;uVtd$}Kf^vc9lEHq|F>m)ow3GOC<>25z%s?_B52B^1&V#Q(^O^A^RP zBm16b80JK{fhA@P#O`66B2N(}gYHb$u;;f{ZoKpQ>hY92yzVoY3G$fBg=p zX(o)k4NH@q=8EcC&cEF}wOx`jt6um;{La;VBa+(!^~nAXSK98RwhU7k&|TDNm8{I{GhkVD+YD`X9i%_$-A)lK3& z45NM2q?%ar>*wM7>u)N{r7BmKkx2KyA ze)E9&XM6bYWoHiY5uTEA}fHaw-GhJ~_kh(>QFD3t6+4~q!! z*j=61Gw}ZEnBqL1FQogv-+Ii%`{ji$FGoDKUW}{VNTo{Y_C%SE)Nme>9nnmwmDAj~$APcyR?@hf9}}3F4x&~G^K6K z%KzMl(-`1;b2{xK(D?6dv8F-k$#cD&Za~9cJ{lk35gz9c;vsZSG4ETM)3ZeQx0%*x z!{XMxf+=Dm8^oViPN8N{>lRg%o3u-(3;HBJHhfZ+C>%8#DiK`??pFb);Vm6!_`K9* z$p}|O;2*r6gQS#hrnxZ5FiUo?9S7&fPY~eoWT~n)w*|w8sRy0}dkzBY3D4>gYe4#8 zz~MCJUFL}}Lo|uwGlw6NKF8HHvKMUQ7ftA48TdoOO?Y&w9C&3azL2bcCq7vDJn>fI zKFCJZ{N5|nm(-sXMV~IEkj;sU9mD~7si5)7FzOD+jk=bv5yvdg7({}z`2_SGn{A&d zcAbQ8M%-Ax#_F45{`Elf=P<71VWRi5wEhAkLXiqH($zP!adR~}4M^LEexKtq1U^5O zR{JGWTT^>)kU43R!lLrq8+VPd?XmlDbH0?57D&g&_0zBD)$Y>{T>4eMZyVnoj1Hc= z7Y{a=Yh86EEfg6(%b;R2`S3M6rDr;D^L#+&pqz<)-DN!f^r&F`Sqo*7?7ply@om{# z1bSkh^Ts#+SR|LTfG4T^D0Xo5Z|1i+L?R67$EKw;?(v$;h@9;qw~bP@Ax#~9R7G7% z922?eg?8FF-))4k;m-gt!mtL26dJ(g1K^+RTJc;b7|Bc%MmvWFZj1PkRZ!=^o^xV2 z2uWBOH>{Kqj>VbUGwD^u_UJ-#%@H-DhU74*j!{}FS-2P&uobre1vIcs2HY5aaI|W& zJX}TgA*KPsPHoc6EZI%6-3UsPRq)G5Jir+bC94DV)gaJ(;8>)7nj)Znm~se?KQ-WT zV-1ik66e?3q<{?J=J*cy<)u~Ahr%|$6~#+|05fI|((?TwmJ+GFXO0a``6iXV7~7CV zo9D={!J69-rk-AOdqNa;A`7}!6O9O%2~Ygq&_GAL`5ZppEe<+eo3W{06=52{GiVVE zm_3U4xjmynGO5@v&u&0VNv)>AA1W`)Q!=5dMx)9cTru^gDywdrXShI-08w{^C3U@~ zwM?AjeD<`2sBud{VlO+f`tCf{ZK}7~ZU}0RAD8ZVT=h;RDeHEg>@MZM+I|h!JD{@m zCP;;fo;oU_hu1lbvk=0|8@*#bYmGA@T@{=x@gFl56P(Tvp>1p^Bo_Z<_(OIs;t?)ds@@zpO1FD7j z5=?wM25>ELEKx5czMhDS1{Ttzg(FRhi1=O>y8?W2F&jh#C{%vx*dQ>f{G!Gl-{88^m$(>g z$)YRvs`i<;4i&*WjTBrtWhUfRcTz?8b0xTKisl3IBG^TPa;E?B4#U9HdWKUjQktq@ zi1GFO*`M5pVY;`fc_Wz)XQ(yb`aMdQhI6r3LHv%#)r7o1-Qga2&#R|g*!p2&vew7$ zN60(T?R>H}%`}>rXzR~j{(Z5y>MNs69DS9e&8oXvZ^JZo62JTX%B&hzwmR9W{Jcg5Y;0%jc1DQ=8DS~z#tc8Bda>erdNG9q!KB}GGwRE5ca^F@ zQLXt=hXb2~_ogeQH@=OY;j6!DFw(9X8&%^^_BOM-SBH!?7Hk!NXo+C-p>Ie^y6aAU z`wH!WcFFte40@C?w|{CG?I3C<7Zav2M{eFR7^oe5yl}=SJb-ctyMdOK5|S| zDNx)Ugl0a9w^R~0e5_OzmCai^=f5|xW=OF_<)hXtc}~8lzUAm~{^04`P>HMLU$yzh;iss*3@g3E3JZ01A8jgR>>Y09F!5RgETdJbl zY_YI@|5Q_y(*q-u4M_-8>%imu{MWP~=1B+}%TQNMjVz^an0yh{u7S zTM6dnUv%C+pncd}dfsw!`R9t=g#J-R*{BjhypmvJj&lC{{br-4=_x9WkI$#)S3uC$ z)j+JJ+tKZM^#WVUc*s<=J^3C?HUe`;BO__uErt9;C(&EGXyzhUKP|UuoZd;aX|NQEK4dD4Dv3C?*fZ$4AaCT8rC4zt=+JVn_ z*mu9ycw?p=Kr68tfa~;4PjD$Wu7}a!qTc?j09h27;=GWjC)Ycr(5dLF)_(hLN9N5B>{sm6&IkELuMNejI7d7 zAu$X-*Y~H&VCyrz;sPrI>Wox1XsUtA>qF9O*O36ECT#@Fk7TcJ@AU{7J~0z=Cs#X+ zgUNKLNTRfI)--_ltwDwn>T0OlZ7)G6%PsrR9e5j-?y70D<;4!PXfJG z4X`|m9iblSSUjYeXsXQZ_h+>ecgH_Rjn7w-702&pQXq-X(~ri)q@WL3>kTz?Gd@?3 zU*-5ur5T6(4&$>E7HTUpTf>BusvFgOnZPu(XrPkAWeLj%At7q&8JLKftS(~O?hk-+ zlExPIgoJ^%+=3u%6(n&5L9lB<#xb(ijkLQ5KqlEOk!XaFI1ttCs^7eRP+IYxcBd=P z9_S~Nf$9z9p#B!0ExDNtk9mN6#|uHTFRPap9}?-=pw^X#1;LXKTLvL$$rywAmD7{ucpDI51lw6tt!Vq@U%6sb zX^z?g;}Mi%m9G-ektTucG9wbntxD@zI6qt&T=6C%CK(gZ3OBhCSKENJ_2F8FyC|Y2 zP~!dJ)!HO7rQ565*nX6kj+Z_%tk&4T%P-z+0z5P|z?$F#(xOIs_inO|ss1u|#AC#0 zd#vJVf;Ql^mSY?xP$5e*7>i3V9ILzl+%2_1w(@5nE4F^l-w{xGwHn$0^A+{85rDa& zL?9wO?gH>~KgGZMSU3~_BG|cho4?RuHg*Sip8;WxlaDP270Hi{N4)t?$Kh!Pl+ZWm z3(=o;=96?qdx*yhRKSHEc2C`6n1q+tkr2F#V)$bIH zlXc3o3ywjMmj?ME>@w4UY>?C120S`BEm$rvzmS0&rf3>>%qFwy(1A~=N5>Vw0M?$) zH0D2~yLsJOSfx%6o# zzQcq-{z(ug$bs#^kqHc75|jj-Tr0k?-YwEfb(w8S^$NDa2g)3!T>12d;+z@FXx8rtKR*FcID}Bukt(=@|XE zxm+iD6KsGF?WK3Ny`Z#s80+XDSvybNlDsi-T^&*Rx=B)wh+`EcZKsc>K9J(?m4``x zLsl3N6WHkB11RgxnI+)O5nF!}n-5H8V@I$c?pHQ+OhJdSt8qMV#~&t;cb6{SA7Npq zAViee%A72OA+IJfl~VIP;lih>gokb+EZCt&P>FCyQ8WuRsE#)}7&7~9p>UVA22ump zkq&uMzbdKF+B^Z)fMQpWahM8n!q~F99^#`Ttf6o(9j0yNU`q<-m>%M$TjVNZ=>ZU^RLGM_A5XR>5%URZM^=N|l97#R1sI5?7`29gzww$6gmg^Z1x?rxnOx zl&?WMrmmT+Uw{h^q}bvx$TWfeG_*4L#_90g$;592Ia>?sh5w;<|IP9i#rWI)Bwe{e z$gINPrT0|W6Eu5I8lq`p-eHs8JrusMv$ig2<1ww&W}dp z<)#XzaC+qxkn${9jgim}(z4ziCPM;mWx^^iQIL!Tm+hPB1bK$~LIFox3o;*J2tK!` zbm7gSPZ>?B$TsaT1>0c;u8toRergDJoo(`}4D|;b8->6HdebnXQZ)Jrz_ zu-3!dO6X`#;+mVmu4Mw4Fm=c&WU(8gW%QAu6T*>HUm;M;k!i|~N_W8v_*g{u%uG(bmz9}lQ<*D!eHZ%#>#=6b00WrRxy;!osHNP`QVir z?-S20Od*OH;{Wge0?a|eoW5`7`vHiXstJS%{I-QwY!N*#7w@Ma78z@%m@CNGL3pza zfBdLV2QGCVpg7Q@6>{Le@x&gjh#Axmt86d2xWO4XKB9TO;>&(I2< zl9yrC)9_{u6W0zhx0sl?T)AO6Xnlj`grSG~3{Atx@v2{&v9$M7I{g?&@@Mq@`IA9l zPR|B$!PZM&_n*YM)GeSto3)`av{wS?JS%EZ=#$t96T;xaNhxOM<6Mg2K!(*>9eyNW;&9?1M7aq zycD&&QKHtXekTWizN^D0mS|~hD_XTGd-&k@=Q%$tW!*rFi^9mcfO~I3Cg5~eqH~`9 z;-3iE4ZSL-tEhMtyW|LrkV0^wJ<+#y6L?yWNtjn<=bKxsJWZ0+J-E{X^LuBvKYeOB z-JY}ce(&dcvm!v-bN;fop?Wx9hJg#m>x4y)LA98)7uQ9rLh$4ExPpn_ug_-FfxU%| zXQo+YIQ6U*920iVsulAOUOB5URN+tS5Y_8o@vJ1 zKm#FgHp&N~2}v7dek5Cj3A#VIV1mBKs@y+T3u4rM%m*et)0RTB(=YRXRpfY3B6Ip- z;Fw`?(7BgETwDJ;YB-^{-u7yFpi1HEwD_P(irpU1I{GU}{PJY^^Q%rzaP3U5PW>?N z0dEKMrDI9pG*KxxE;a))E2C`MpCn@uTzw3=HlqlA@>(_(C)-CUq zzXp?iSZfWlJ`VzKKCxew3x7UHlmT8?Da&D6c?+?0!Y8pOI3W3@9x3^jxMzRpE*4Lm zU8*y<9EiWZP$qEXwfnobs2jOLBNMO)5|E1Ui59<_kx~OeYh%C1L^PDbNmvAm;@Et5 z+cEF28g_DFoz<)TCfP903aJp21bi1~7IQDB{< zu0r0ogNNxm0en+%T<*TK3%RA?K$Tv5iE@5{3*(f*wT}^ZoYmbW&##6=Pt({0{Iw=9 z-w|oh>+Su2Rs=f=3!2m9wfSU5@89dJmGkJwFxea=LmR@D;0bx}<|_G89NqTy+sazR zCc6lS9IZiIvKr1U^^e8YjgMx0&<9EU)ovq&NyRMM877Ap@j?#$0!E@M8b1!F-l4y5 zNpJL1EFT&0QwzqMc~zTyfdL?%N@9Pt-p95-pp8FyR*j{Or+d(V)H`u3s#rqPyYX@@ zE}P{Wm9w{{Zs^UoRGhL+O@osA-`iUIJ!Wty*n7A&3`^@x0%J{l<#GJBW}cI8lI;FG zQ8cRb&g5foM{PZOmgX-ldk<>(p;*JR<&lHw-4CY0wQLRkV-ka;&F2)90BJ?Q0CnO zw~KPqW+7>!bDy0V?n>d(mAV_vldWc%Z!e7)k{QHye<97xv`|}~1JS4dJc<8a`@f$} z@F()DSQRgBu2Fgp1-m;OW>_bSWEMe(CKu{Tez#lsg~q9*toaaC^S!OU{&{0Jv*I4@UqEh zr|nJtP{5f9cQTw;<>)2C_|Bct=3=vW($%86D)hs7!j;mR+kB5E$b zzc<_;K1k9Uo==^v)nccS40>g9yg_t$ZHm@ZYpf{Mi&0Ld;?>(5_g1P8npA%eYNuLd zjwPiPxh9HU%HEUAI%1p7EA1m*^G*m(%VIe;{XW7*oxT}>>;!0vhQ z<(bVyq!q@A`p10R<348n94D5?mDWoH14J4i*Dr_9xL#uunDwM#2n>)+^J9>oEle4k zz_k5i`dx(-n3+s{0I|uo4=d^j`~rTiGlK>T{f@>7umPBDqK&-uy^nWhagP=m_TMoJ z0pDNE?Jn;Lx>yJ3uMhS@1emA)zkFR)gV0ugY<<3?mXJ)x);Bq-bf054?T13s?H(b5 zq?N1T*3^0Y=P4XPas0fOas2$AOcDx~!$*T(e17XMu5zwT-mUm)_QUIuk;KR|>EA+? z$s>H!tQA3$3csd)C}uU>LI>?yt#nRt4_8@6^D)leOR`*AO}Behg@t z1pp)9xw1G~Wu@W=^f1CO9{8;USqTY=FgJ)Uj%9RegvZA&yhG#8{rcI>Nvr89Vw+6U zj+PW1e6tDa?G(dW2LMf<$@uOq5FN3z%l-bzPJaN%A8Np>2oSXy6G0lmKsyS@t2s)@ zO#3BhyNuuHzkTBeB67-|8tiKi3O8vL+dYzM~2 z9w76tA8?iEa%FilH2<76f1No0&-X;6d7HpjPt|nm^!i6>M5n>2goRX`nmlX+Z&b8W z2g(Wd;8)n4AGwHUTHfxjdF+yuvhGXcX2IKWOTJgMjEnauHL-N=1^@JnxSOBY%=@#i z-&S{ojRz;RZN(!T-qn`t-_EnL8p$tiqNAHfD{?v*w6#P_8LF#}pGC!au72p-2~s0Z z4m6jN40FD5qq<8?nl69II+yT5^s!1-ZQ0e;ix+%_eXomsjW#=H-g^6uNlMljM?HaCg<{;BgrAB`$OaraQF3XR|Cnj4@h9?2CXm8pv|zZTJ{Hr!n1l;x ze5uB`U%c6oc(#oN_wI;jA zX-|%S^YHKdHd>;fSgHAC*MtsYUg{3okn(>r(tKE~5}fu*S<<_j<@Fq!zYA&k&G~a~r@h z-LSzytpOazfDAUx+=*BeHiM-;p_WRiHvY^x(L-qQ5TLOSsim?JExr`cVsqy<&}@y| zT?4vrYS@EfI!4Xa!L;$NXV?P~hPcg|L}4)NddoIvu?mjBcgxH~e$si-o1_Wb{}w&} zR6qaxfa+wyn(XOGOd8fA$XM?z-M%)bW!BX4nr|Y7MRVs;-&;w-fkOc*9=PvZ33oc% ziJKQ+fli|P_N(m|s?c+_s~*dl!ZX(@d1Y@YeaL$J!YQbwHPAmuVKO;|!rL3zfQlZ1V(Q!Vfe}9aerSf4$JLa%D3syvFPbk}(6$ z0v{kUlZ7Q70aUW|ou;{vHuFm4H5_bw$1&)Y8yrp}Z;cI<5WdIlHMrBvkC5vq_??nF z?34;%b?4uc^PQ}=iMi=IXCkYhSoOuxx7T0xyGXc5aLeL~e5?4vR|+PkU22muP4B?# zHwe#p^vk#~=pS(LG63eXkJ^H$;yxM6I*DoX99%)a-wa}w)?nBW*42325T~1?nOpzt z^#8YB_~#F+ITOAsK94XyRxNC9OSq@?G|Fqa+JIi)IGABZ?Wo{`B(`e7ol}n_+GUR8 z%?9T;2QQ)$p9S1u6ZpU>Sbz+7mnSvhUYdQ~RJdHAF(r_G>+#DikB;$&vzJv;7d=F4 zYAGD1vcL?w~v z<9f@4V>UR2{x0wBv&U8PN9|NWa<|YD?k5*1CUkJzL1%3@%VCM$k(60?%6nL7f_qgw z{5_3=g)=W6wQOi!4XJLs6D>y)E*Ee#X?M>Fh1ds~nD$)t?NAHLAH#(_X6<44j+0Od zUX2%W#W&0Ba4NEaI{z>S4T&Hf4AF6%v+n!~fI4d+kP7uFE2ZEXv-aUJr&cTnV2!}H zGI;0FcGu_5w3ekY5a3YPs_zOiudI8bDc>`tQ`9CPe(6=1nYK7r2EntPoLMTqSS`Wh zBC+)AohG+3IxqjXZ3zi!WM69%%-Ul>44Q0s>lH&;w<}=Ceta>%dc-aGK4g?w6*NXd&+;87{uPP^x#`uH{BjpNZIQ}j@Q zO;Uqv4BJDqf~!Ui!XLKd6KSUNY+l#+d~~^jY-|l=*%0?#0Irfqgd5^wVjz`wH2CRN z_)P@SBCdr!Fq&u*ATguh)cP5)ZW!iH1ieb=_{7Z~{(_vG{81)>I+ztDg6W>tyH=1% zG~D2B#a{!QS!gqe^ZHeof(lkywYR70?x}YAgu4ao_fQM_e)2_SR))9O$4|z4Md+w( zj=ep+1f%Y+V0i(Q%?7x=HC=|0$|QKK4me1|c_Lg z6xUw*WJl|%J_y{8Xp05qi@tQx7sd$>KxcPktn>xQS#5Svw*PH~g&T?grIP!r!doFl zt>2{P9`HNnWAb{w5^9 z_+s)(AJ6$}^ZS?V1KVk>lI$`zMOow==s8w68;NHpC zdy^%#XI$bwuIRVEobC}ceS5gk!A9-T($_bMrZ#J@y@nU!1PgRuMOFKs%NU9Lyvd!} zG#@4HkW}f|N|97)sGFhZzN?ZpEATl;O#4V;mOFS}pF4Ajp8L)sYURWJu}dnWl}s81 zULfSFwIG|76Of>5&7*^<#=C zU{78Wv1C5Wz;-Zl3RuUh7zZ{5Qfa+kK-XRp@)PLUA4;nNAJDibVUi$v#O4&e%EQK- zH4D^O`8GLQU>fqK748GfNQ54rVvzUA8avF`MQ=-Jm&5JJQ|K;R*{hVyJ#PuA?IEqd zYsmzy_RYq@wvHA)uro)qVh4Pm29SV^c0`8nW1`fn2Vik2@?ba1PNAWRQzTQM22G%Y z^lOQtZYJ^ce~QG*9bLFbv8fj(9(%W_F~s%L!H2i&0s-Gnb z*o~v$`~Xa5XjPghBWBzQcAnaEP81XhB2t#*ySF`UIGWvENA!94`~A!s_8rnxvJ7;S z&a=Wl3-quA8o%)@cRMOj>>UVxN3*Kbc($;X98N9YAYp7SeKpSaVtHeJ^8ja9bm6Yk zo0y~3mf*ZBYVx=z#3i4o9xy&IZ16n}ExPNjaWw?;>5T!Yux@8PE=f4_p`Fq8@s2R6 z_s!9Bin7@DL(dit<6q{Y{+seVxPnFV<<|mVRPQ1>npuRx0!24pbQquhW_%Q9&RWArYEzW52g4hz6poo~055dgn zb>FwHby+`McW}nOcL>RX4zDlYBbwp?q6XQ8y0#({aB!l(uJ*vPH)xMxOMi@N#hKOh>ox1CDnz`J#$M;(9Pjm$;@ynm%cERGD&8uF_!B3opsdW6+7F|X6a zI_^cU4n{T`K3Dk*-Fgb;!lGme)c+q1h*DHL7{wPjO;*!>NR7h$mS|pV*euLq7La@r zGW*W0`ifs90+c3{VByzOub1iI6_^*1VqZb_teI4^V}Ab&q-`!+jWCU)s|BK-D^hW0 zlL{6dL_k0wml-B%4%D6J>@tkH_k#NALWMZPKKAzuusEMBJkp9Aj1xHq5L?4U|IRTb zg=ZzN)FfuJL%Oq824DJ@=;kZ;eO$V{VXZf$3SomULZi_&cH#MjrRB%xZDBz(nNmTbwfS*xLuV>D!mBA<)HH0+;wRss4h`=Cf2}mf}M6E-z z9;m)ls1AO;U^N-i`1=9Gyd@|MK%fv#6bEM2Jcht!5m-UsETN$##Kw`oGPAYFO!30Z zcQK;U?{^C@SnvS5|3g2s{X%er2${EIH3I)6&p;M)u_=sMY(H}(ebSG87m+MJHN-n_oM@2d(7zI5{HIF+!a8Lv7V*EK!av3G)0<%ZvTVnG zH3LigDcb_E@pose7Ar7jF3SBH>R0o^kb)$48Mu+80!s<4QWa2m=*VtFAi{Smaq~2a z4_EcE?l*cA3be%f>$nz2L3D}>Uc~>sAQ8Oaf9@Of*BSNK_Y^3^WbqwamQ6iaI5|25 zNE1{spB%yaIx`Wn54QoDT$}i&F99D0TJiyA!2_z1IXN5TiJYor0dSxr9J`hw9pF7Nrp^=V z*?#N4LWGDc@UTTBa|~h{GL#bxxmQ)`3Rl^yu5>HBotgyZ(|4s&2-vE@mG8PEHFnx> zN#w6=d}xj+Ahg9l!hMdT%>WboEPb5Ci19_nbC$Pd)PyG_wD*Ry1v1G7U&v+a!e0z6 z<7eMi#W?KZ=3F${8w&gvXbag*mV6oX2yXjU|Ly%dZ|{pxY66x*nNs@!eyE<-w`fYYnFE3-`wgk=Z(ff)kRN0P0g~pk^Ni zHo7MFuy>i%x!Qo)63>@FFvn$1^8vA_59eq_9u`-6f@xi`n9lWh=BM&hw?9EkFMeiR zX(Yq!jsHCV|K|@%;aIpiEFLWOcwq0^=fJ9hY))bbJ5Bmpwo4>Oi@2nSvM8K=)|pP_ zMvv8t&Ljd2_-7dLP zSOB)+;P@i{!;K%JY-PsXe`XT@$EAgzLiq~bDHSXPOv&lq>I>KKC00BV(Mj1E(cQ7U z&3hZ~E7hOJm>cTSu!!f(GlnA{`fhMgur15=`2ErngHXx?%}~v)fZQ|3keA2>@dG_XLqi36+mtVU-7m> zpN7@v5y6mD8=t10NpVqt1REB0Ok)%X(Ar~NAukb-$SQ>jdWMjQkQA@L!{X35F(Rxa_psyiK0EUnH%LsmkZ^kveEe5DAQ zDgpARY!F<2tkol#wCUw5Xp_BPh6%Ms)C7t$*cwxVpY7)RIJ>DZ1pz+f>7BcJ z>0CT7H?uC7iRhp57ZdUtwxYs%aBT=Tng3qRT(nR=EwF8u+5k|KnJ(h+J$Nb;t^eNi&GCQUtP$(HCzq9nz!SIn%D2;Njdt#ACa4 z4+>mlP2E!zq|Q+K`|iQRv08p(IyX$RyU^Ch0pr)et`Pl2f%H@<8KlnHCxYY1Ze2b1 zK^Xjnp(|ez!DSgZew9E|$A*JPlmMy~d`h2$U7*oS0i1O;1zR(Zes9{iT!h=|W#Vyf68hXyQtjWu1=K6x z3D`4wa&iMOk}%dR7y3)gzd_LN_--Smp#0%R#~Hn0QCE1Dd2x#k@Js~Yfl;+}l3%K_KQlRZHMtspGxsAxn_Zj*VH_ z`@gt4&siU%z;lQ|Vm}?n20MouLMH`^n148>5e!-GjW^iGC(N*VLM3H@Nqv6#@dra$ zC2n7O>ftcrEQ-LGn5qqj+ZZ%O&VD)5yAIbGi!PDFhlyUG6d53^qp~i!yN|+UjP@ zY7IC(i~mT-#HbEVi9M+AW2qMsyYaY>8L9yT1)MH=kbq1EW?!3BK5g$)mQ?I*4%7^J z4%P~DB)(5~tjdsA!1Z32hBvH_XrQ=G6?!E}R|HOo67vDEnMS_A;{46MQoRWjB0QYt zRaYeGxK(-U(fz~l4h1a64d$~8z;Wg|GDk4qxS`t<2S6yfuE-oBm<0ThPK6lrhd}er zDu8I!llbNYjzo$7w5P+9dVhIjOj!kKwMrnp@tr@n6~nc?qQUhIo#_mZSzv~CaJzlM z@rxv)$qVDVwk{_hP^A5;Jd^}4IOC>6UKmX=^(GW>RmTL`ItU35SuW_&=H}2|g&B#R zhUHorm>gIhWsLE1lrTHd1!1oZy;Gp!TLIR-ecP^Kz5k$+;Zaz>7`qPeE^)%2D9qzq ze7bV$Ua^kcxuWWNZ_a$gFKVghUtDW`5!JKmnSc@P&86ggGVpGsfS!g>!u<|fbd zJ6BTLIjUYc-_A4Hy=YNa=ch82IK5rTbT%M|*sB^$al=}Y+@v-X%vyHdpS!fdQ!*2p za6e`pf;VBU8FH*L$8<{f*CE-Lh2-iajT8OK>Hdq4Kb8ev!9y;N1hrp6DK_c1Ygot& zZ>fak2ynZ&TLI7T9-G8gW!F#aOTdsD>GAt02@9i{Fe}yP=cjSpNBYY70V-W{w1m}% z*hJ8TZXmJ4nZvcie2B)zs=|f9pkIdJQOFk*HU&%!bc2KDH}L2o6az})qC{pB?d`&! zrSiSxOgg8KdmLBT{aS-t>iy!0MNhEK$&MMNntXQ4y>cSs!Z4Xc8KuE5Wf$g zi4k<|R7?jhz6>h_SFlltRIO9?I=%udKeByL&eEPfVta3RjZ)^vEl& z)=OZJ0jWnCC#WxBa@gH5y$Yh;xI|b8xdaRqT^35;nZsN`d~JeOn(7YpWdPqv4AhnW zDPm8r>d?2$-*?(K;wlkIJIT;b#ZY)ydOkd{r394b_%9z!-~?3VR1@5-SB~R1%o>f= zrDN-mlz)a;tYM(IO~XNNk!wXbi|5MnROjTq+oC*;$io!>d#J(Rzm8=en--IX2~%<4 z)`XJ{6FY}+!t1RNxCEh81GRGWg4!cp=~XS*7^DYN9WTO>^Vq`EAL+)=)K8{kKkt<* z%MvCVwJ?e<){dRsthYssi75WYA+WIg8d+JXfjac>67f5yfV)-(e*rZ29A@vYR4o;z z%}0oDVX4IXgxLcaq=VGnH&8p{rWz-y1tj*6?(Ow5g{Y|Opo6Z_dLx%1?rtf5qlyn) zQ3yx-dA{H+#f2JLurmcRSKs=(vPY52g+LP4ZZ2ap#{87uL;317dpi^Vw%zPAPsjo0 z=b)4Hq}10aK&8b*v2>Kvh94oP^R#A3KL}0R1YQk4C)?^63J8Ji6_9%aC7w+PNB^m z5GS~n-~^TqVDug~0G)(mX8VWm8}xR}=<9T8|FniVd$1P^p)$i@)8Yo`T-2|(@A?t8 z=|Pbb*q`kwP@Vix47HxNiQ191bF<-IslJfa2cko`@{8G-0)WY%M6i}6KTW7#4?`9Z zuv=&<|KxTjYKg2AAlp({H~txMy|FLp?X10{cBd6R!KrvZ`_-u-1&C54IA14U5zaBy z*mcwYbHcDC+y#pg^3+0IY1{cH6yy(WSx_u$ zA;-N#+Jw9DO6a6QT{bu{9$CJ7k^P;?fHh{wXd>cTtZrqH(JH5%nJ2D0arou;^hgLd z7H)f3Lbd zEed@dJ+{nhe1dUx>D53%9D*6W&Q`oY(+VczDpjxkjHH#O*>bbKzc*o-Nr z02snZWeyq6D?^0FxX~m|hK<)&4BLWR8S#x1+R5@r<%<_iK*cl&+JRj=+MzkA3oT!+ zzo98439LPT6?8OX@QS^aLw~ku#W>8252PQ_f5fRsyMT*SYLQ9>13;(nQcS~zM#|SM zdmQ$2>Y*c4j_8VrDF{YG1M=7#{4coFCJ0d6C1YwePDn3zmvhKj6r}aVAD5 z<}&id_Bo^93K>7$YHk*QA^IN=0V@@f42GEML{c+ZQC7@7-y(6)%#CVuA3Vs%_1l7jR#6_;d9N|= zS2CI1`|xoCX5t77O(Jesxb{@To>wM63mm9-w|#+Q?@iIi3r@>!m-dDv|6Fo62#VD7 zNcuW7E25^ZZL@=Mtg~a{jCZ|e!aoM}GH8=@vMEa6)s|6hE)W=)kM;|3QCNg3sKE<> z{*34DYmG!Ymj=1>JL~w&d<=@r8swG$PT&d#HQ>;%EcCAz(yZSRs3pZ5gzZ92+|u%o z#qE6j>~Zt+>VeOjc_${+N~b1Nh36umi=XU!xW{Z5IUB&zigOJ$y*)9c)#5|tu&_1W z9@Jr*X6vnCyWKyOZ9a{&NqQt*8C)yed`n}7pkA3Q?oi+=gQ0$9bt^<~p z2t)8ih680UTxU^a%W&;2mIH;gWH{FE4 zrp}%QlA>{VgX*4WuQkCw8Kphb&NIOx_{LT^xzj4ib}a~4x{k2YEKV;3Z67{D zGdenViIxRD0zx{6ARun4E?=H_bC37%N*hp^jeeVXvDjcPOt0Wz(BNB553a)nYx}v? z8zuqp{(W9nS(9@;gnse4BAVDxJgFAI5{X=;BpjXic$i`B`))h&8~Od)kW&T3hqt^3 zI=`-dyz@_6hkaL?%Eo`|vC*;3i6j*i9LwK}^8FPj_ z%rO62J?Po{L3+ig%u|t~ERX0N1(`sQF5Y|ZdT5ar0Os^As;<`-<1c=9b2>lTE(Cfg z+9j&Rh$@$r8@=@T^ZSipDiDhMS(h^$;Y(pbe0zdKI|l>K%}zv~S;)pkyd#^6xK;ho zAlS_&9he5gFkR(iyNH**k5?|MDl6p1v>~{fh-NMcTxU{H!a)%mTQnk{T-jun@4YcN zCVH~myxm0-Evt0iGTao(4qvsMDxh>qEqnI&Oa^@7oC5fsx5$1w(}}dK_=|B3E}Z|5 zu=kE?Dr?)ufdois2_2;+bOi-O=_Le2R8+7a2tw#0AXtFVNeES%iWKSCn31Z8sGuQK zk*Y{1^dd!i7yS0|nP+Cc_x;xIw^shhnnIGZ&)NIFulp(-&Iy+>FFne2pY%p8knraT zm;2Hi`h@4ylhQ6(mKQ5+PBduPfv?kvz^qEtPSZ@nmv$z1OO@KxXa7N4Nn}|ZWBzd3 ztKV(XUwric97!a+lqneD;N5j!{PW*~CkqR&U`d=ZWWD<>p14pwTHx^B*XNqjiG!Za zJ_~D(H@_QH)mN6z!4j7G+}A9#aOnz%gPeVs`=&jz)t&`8s|r0Ru>Uq@@k&D}yF1uH z?4aZ%G@%?E=?g(G4~*}fK`uM@QvFSO7r-FXwhm49-QKGi0P>G(GS ze&ewll~1t-5se_bWB_juIy7Gx&kWlPktGJ1I#@-n^iZz}U12P9pe@sku^dCaz7CH@PxPg^o9i zud3`fW4$lRdXt&<+0rFb8f-Qj?B>;#DbPTLT_6l7mj-f}l6q$gYvShtMH)<~jC`@; zwa-Af>2X1m2qkY2FVP5;dM`%X*cB@=0AlKxw>Y{5dYwzc;>2Obx1%xg5L@aUb*aqt z>n*SK)QA82S>SH|-!Ce_f|sq?ZAdlR78q;zGhe#jiQ&O8_y`kSO*X>|Z@Q2wnV@~X zK0$n;O$)TN5Iz)hQf80Jn-3V?!KB*vqIga)&4pnbD;!^HWpHV4r?bN?DAKzJO9|Y6B8YM56a1e zFt+t?e_Uu#JYOxD4^-$^T5mRpJ|_z;=QIO~%C)ON_{~G)`~PA3faYU7gMk@?Qi&1; zO6p74NwB$IG-JY_B#^I>ORgE0n{!gKsFdYayHJfA|M%8Co;EC7RLK|it_@CheX zT^_+HJW~qMNf(US1r$t4tVsI@;Wxwbmw^!U&&0BuV@5543KT1!6xa-V`T9AZhU#D` z;zrJVnzRE+vsrh?-8*2OOU6F@pu3Zc3dBeFyV?F9Zycr2hdZ7v$Aq7^wi&Ufxy8X_ z*Z>bAONFs?65t09dU`B0zCU>|-f=?wq0{GN(K|&LNB6;ZmzenwhKo&~nyr{6SZQ@t zzzpZzBxhw%V)GcYH#V(@bi`v`jMxm95s5q&IQ$1-#8gihHIk-GL9KeEitF=3`RCYP zCf*tHBE^@8a7r!#@sV9;*~(Bh`ZnAx>j5kJ(|HURFW%rS3;TaPD&XP4@F2zwsfd(o zyhv;OYh`PbTermUuK}|YqXxl^`TC)okX{_1=m)RhI!j;sDpc-!IRyNg4sv1jsepf! zP+M2$v{*}`+BHbY$jn^P06WHF0E6XQFbPival+RuPbVvIBNWl*@w(gBIA29j#!Ok7 z3IH$`apQ$WY2I)Tc&J>n2oi}%Ie{B3%L+D?7693QLO-ihsE5VsJGW&O zZpog_kQ21_(!WvUDrWFc0rNlZz`A&xTps=`Jl38yA8|}hb_$!ca)t89=z>HnE=ibp z?yge$%C`Z)Q=fgLp04I;4jMgGP;5Q8Cwbx&FgAK(5Xp7r7ue#)!v$?jxnWs{Ek3f& zb`dXt2q|x!-0R=|bZtCJ(bWPH_KOz&5OkUwDR!QOwCBOs>JRtm+V2JB#XP7f#6^wLimR1&d%!h$8>e|XWp}2@8Z!- z7iW5=uwud+u>rs{GpIiNEdoc+!3l}p}M zPx4TBvc9|*OY6RUWI!F(En%?bEDrbJZL>@8yB5*U@)tn~e2nQGXvn8IMNa+=!JL#z z>?P)wBdk#z zLn{uuiak?jNZuCxm9wQzE?WOQ*oKF}Lwhga$2AS;NjKb2BEVMo{3YfUjEbr%_art3 z!av_)!;MH-);1x3-dsVQ=GM|b8VP43Q32=F31R?`;uiBSJdclvs@=BEWm6QOE4K%V z`~s)vDNqXUjz~W@Nz2QXK4jupA`?&*Ttc@1$BKbWm;+#KTy_N>9pzZ-f}oQpPaahA zJFYOs*wG~K819bvO)>ht`xuIVZ^-T2bWI8RhK?t5Y(EBXD_noTy|FN)2oPmOuj}`Y z_GDR75||>BBiU*?SUhVJwNuYKqBy%g7{5DvSD^=SB$mk(o>^de=C!!8q4>g-X@ZL` zsH#U#GPnhSfdT%{%f&^Ul;^D=U)(#h7=WIC%H6-(Q*O$PNwO`n)_~yB@r|L7Hoj{% zaLk=)tc`x{l@Eb_<+7aRth}MkJFF-+YXL>8*yjK4>vd!>SG2#mzZU_m{UR@Qe~+;Z z<1`S*wEE&nu;EQh0T;Eb} zdj!~mo1i7y5zPXkB-O9u3W(u`{oUV>Dkq3ne%*;o;Q*uD6e_?%;KiIEIS`QE9Km)6 zEC{IXb=eu`2TC0xe*!E;Lr3As&*{)X;8@Z}vMRL?_nA)pf0Q4Xk4VB?iw%EDONwQ& zo?^-ycd5)jQ&R38erLP~;QgPf*zO63#a{qNPrmZ{dicD?ei{vuE$?leWqX32puC*qOldUK}M<+@ye7<>Yz##m^Rt4xIh2863?XZ}5r;LVX` z@MmY%3HI?w*((3F``{6_(Y!N_0mc+}d}IXP?#lcm{FHj(wW}K8zCfR?Y&;gjG7$yV zMr6d^8Cn_$I%1@QO}$lp=>zS8f7jET8!LB68z^mW-Mvi*9far;;6qbyO9iptzNV~< zqK8)mWyHC??FaJhN}T#3NUS- z`$#rh1DlsJv4vSsp=_iayESecVwucZ-W!_>5N8R4t0iMlStEArdgo%~Yb}86>^hOd zd^siWh(-V$dZ zR}E;#gSRC&h}@1nYrd)*FjdsGD9)Tt58mopXDFF!m#x`_-B4v=X>H?c{OdPz3ei@Yp-ybsI!PFR~r0; zT8Y;7eR{!_@2nnwjfhWDdws0~51pD$k88R>DQFUYA5v$(g(gp}RQ-mQbQYADLYwwV ztVz1+87(bd8I#2~DA6ns%oTE-UGqf`5Ig?jXsD?Nb}^^XM}`3>=v%A2<{}u;cKIu( zBc`qcpu*oe?TAwuZ`8U(hFH9QBquV{jc8GQ0qK40T07>mrJ9B&>Hp$S{%3g;J>l+w zEm}hHP3}yRS*X(w4?cAp9rg2^Y);!&h@D@II&d9X4)L?HQ@|pnKR3o)dtA8H4}5$0 zk1uM1P{h~0m;SuivEX-R@e1Hzb=nWV4qNnq_%tty-ko|=UBl`Cua>!DzXPWAbEt4n zUNhw%p-V!P2zI{XUSc1&XXMwE@WR}uhGdrPtzrEKFXrc9Cu=fIRb?jP_o*0xFvLPH z!R*;(aR_*Cj@SMrKk#ebv9K2Zk=d$8{#PM^6Wpto+|*`Fl!!&rEg%=z z#y331Kr}D7VzUi62LAA|2Fg;&kZ(3A!-S9neR=TIz59@a{PAoZG$)vPl7va)my9SiTX9=A+~@>X$)K zQ*uZ-8sB~uFz2u6X}(?bndy7|qAR*rYq5-GA}N(b+Ww0I{O?`ymJvgn+dA1Ls?arT z%4A{XaZq_9&$`0C>qjGSBeLzMw7-s7?w`PqU!zyPb*@&K1dfqwD_}=i+sBP3bz8yP z-iZe;R_k+~$ikU1Znd*$iOsJiGb;+qe||4IziRZ2D#RdTEg=eu!dn0qbJJL#yU}pi z@};5f?&^h)9@UlBC7crmo3s_JzIYdFljYiR<1*p4kBzQi2xe6|@M zoonpF*{2#?O!Qd#v61yaJmjZYFgVK()hq{eI~%1dJM+YHwm$u~P?bpcn-UcjUP|6a z%w@hsmWxmp()y9Zu0k*%L{-c!-wqk!&0)B>Ifa=MfEO`R*yNbPMOE0PNZQ;bdNT-H zXGN<}<#WuDUW-6aiUsCW&tin9rE(2OXRqXo+Pz@Plpua`Olf!h!}j9`aQ#kbd7v{P zQcWhm? zYK3IRI@?CTD;duKm z9{OMFPkm#4w`y3LQ86B9pe}U~Wo1TH%Ez2Wstq`M!aop!(oCIXMl9BpM*I>mYvRd~Z^BWIqmpe(2{$^G+z*)`C4e%89kWq5% z{Z&knoE70pbH6}q z&h_r-h!f?Ag0}Dxs%|IgQwH%w|G`UtK3N;~>lcozwZhb+Oi_3$YmuFU+h%GaMQM$` zLTW?*iGEu1UbGyZrRJHbs>`H!z>_=(jodNXmB)FiRSS428{i^;m%5aMsz&(q`jNWP zeQf&3en4Ck+COM(Y`SSGz$)Z>0_TR^rhMXO5?XGdnLR_?jq~MJh`VXL^wJ7Q2=j z|3Ewt)&|~X{LM3#6<)b)%G0rVy!N6-o~+m&ccB+5ovkWOI2T`_DtQS=^z%}7b|wYk zVSGK#p3ikkf_l_$jv5Ze#;bm)SqGc7SXsMs$vRQ1czW^5vQJXsRU0kL{oD(yP*=hs zTG?)yrQJJq(+{|_UKi%(@mw0MBPaUHnC6O9Ja?$)uT{IV3^0(ljxt}Qe!5`|8{;G) zd2v`TOGi`z>Zt$fwHB6Qna;42h3e-P&Dl&W9t({zmsHO+91AV4u0hQ5OWH9Vm_wPf z;3pmMiabU9WiZ|#?wfFXF&;+Ys{G6n8CIm0P?2y5rj!rP0iFecmj82I|-4|a$nq}Qso)b=SYx`gVn96kuEV9 z2iAYvda40ZDkCfnAj0Y`L=|&ufM=bUuFT_&igzNUzA5O7txO&4%r#)0PbAvr12}<4 z-Cid3ZG%NQcOsyD>qMnZ*%IhY z+k>W)?7GkbnTAs$Zt&c&mj6we1(qFjm%f07s>=q^l788T?y*YL0y)N$ojJc_FOZSB zF?w>IK+kfeh_iL9m#@uV$g5%eM5V*>Q=~Kk8xLPV7n}@Z4ADkO?t)vARUfwxt&JY# zu$xF!!l8D~V5mOY4<0L|%k7gZA6C~!IWWn|$d3{|^L+ywR*v!1OAjN-Q&)T9DXPp4 z&TGUCr+W28q6m968=1LPoJac^h+>$5UrH+V=vS>0P+BJ-vlFy_s9%L47LrSnQR~cZ z!rIr}1Dqe+DIJnome^*UGu-qgp3Aikm~2QrmE*$OB}m&oj)t41WJ)HvmfKr_htB(u zlhvA7u!G?*B8>{1ta1+zpv5o>!{K^; z=>2Rexhwv_^=i6+`wq3;56Ksbe#xccq@;pSNl{p}bXIXG5q)JhtgPZQXW^})y2PA$ zZ_asu=oiXc>8fPasQr9SR8+3#F8uNiz_6?!h&(eWtXJ7spq}$^H zd;d>B4QD1r2wP0`>IT{yj<)1U*Ri;YdzXZ7N$ll_@S3@!MTfYrbJ_5(le^=>In@B! zU7mQhPUQ%_<+d}xVf0&S#*MU#?Wtlm`;8&$>4%ycL|YG$Zs-^FlH_7qfO2T-l{xRo zzuc^Sx5T{o%qeC)#0i1c0XLB*)wa|E_%SURuP?GQO7GFPxLiR=q~9-oYAfyAgMK%G zS$0ddr@_28(cf#+#qAdEDH!HHj843hjK0TwAn6m;TosNT+W<}C>=NR+3)0EX1ipnG z#xlFH*83z9U`2z>>;n0$0K9r|wD#&*-s3qLJn|JkK{+4qP5S0&kE+XNGH8u4#>g?= zzQiTtHJosa@&FtwmSY|E5p@6^1g<(rt@a9%bS=^%3|yL(Z$v z)<(vn--8<8!{I}Ybm)^w-tIT5j z=j&apx>#9DRcelnU?t1m?kP3m6xlr8eVkWxSx4ccDq zFzqMC*Rb0g1xHv!ohgzSXu4w>nK_sjh!ocNKJI}(XM6ecX3K(75LD<5<5*wC(ms%k6mvV7R7cc)DD5FN9E##D5_*dyh%vEq*OX~1Y;K` z`-Ff$H>@hpYI6!G7|-=llDA5Jb5}1S>>`JOWA_ zj>v!QH=5lfCTzr9!D-o?D*>_t- zJ9N0zu7AXA>}o=n8+()f6SUOAiU$a+J9>+mmf;X5EYA+xOgsLHn)AW$J3RhG4p(RR znv+s>N0t-pcpP<#u&|=E*?0Spp%Sl_0t){HJC~^w$r@l|q@^rIP9!cGeQIV$#A0Vg zDkUC8`Cb(>98>b-A7AoU^3&Mw@(LBWsPXD1x2Ns`sKa4Ps0U8U_XOz{<`GM;lsOG~ z7}J6OiIpsag9$4WmR3KOAu%G<{guNv1Bww397D8q=HK zH_OcBiK-sx>LK=r01HFcCxTKYJ!}5>aQ~FX?+bFBx6%|n0fIddXZu0?s?gnQ%GTB& zvdzs+$p}JM0|+1(_bhG6LarWOlF%1RA9Q$y8}@*m6JTSNo*uQk@b`##ISy1>fd-$+ z&_BTpAyeO+JS%(5-b=Iro_-DS`1TPZr(FX}Be$+7_EXiT86`8OSsJEOvnxMM%`;2y zh3|bQIVLpz1Hb~#NZJqv1y{E9N%WVd#o>aV&oa>bwE^kCteR!^tHn!Ip*K|CyT=?L zG#eOI4tnO*17)z(#-Oh)OKF|kIq-KXFJOYP%xu@dxez6?XagyTQEX=fd)7^va{&Gb z07k1jK~I5Xg+=F$^J!2#IsyAbvgt1X1!+cQ_o8DMD9V^vtbP>oI0NmSp1LQxPHE2( z*>RGf2FCLG#x}XGfQB}s969bpNer}*XX_U1y48BJ>C=L&?8FgIIuZ!yv}&wV&@mMk zgzQbsUdwmq_+gNIJS0Na!Bi~en3p@YiSW#>i`lsX($MVDd@t+4AVW1j3rNkH|EEL#44arBsepxhpn|;eO*JH|Bx>SkslthTAl6YYoiyTY- z_5@S0wxyEpu3*xeyWwn+E+`rQyjW9s4xl=Izd|z?pqq68R7F8Ncr#Z|!T&5AQ|tle zQ0AUI#N3=X#0y9`>2&aszT-<|k;RyxNA};UMUIjgmbn>6?A~|eoaFc$ zQ{O0~6)=@+1eMV95%|4Z`;`Kg;|`P;afCvG=XD^6x6LVChqK|-1>9EXU>LeTnct(yeE{ZTz(-OaJp-PZ zO9}kNZ+M%IVda$O0`tXiaxgKRMU7BF}Q?>HR&6)<2pw$M%Duq1O$_B_VGG#M)AG6e(UlBsZs# zchtW6+-Ii2in+;kfm;TEEu31x!fPmDIs8dpV7XCVU(9v@fuYMg6NXVmZ$rmW+rY=8 z%z>;oZc)Z9s#~dafhDB&p7>!pUr0-&CUV75&S0vaGTD;8 zGZvLzB`2J#ntb@`pRZ6=sF#+5FAnW#;Dd+t`BfMUPF7c*e5J`1;sCtM71w6F1u348Bu37YF z2UIR8V`=WyLWuY<9m}v2=>OX1 zQKL*Eb8Pnxvx-%8l?+kjdg2A=m~M~V=q@eX9p`-SvNr0^)KDN%Ll9RV6;(t;n}5`a zBlSUGh_NZ5$vt^ROysin1>EJ~JJ&{~7Z%xM%wC#KCJ8=II$dTF+P#Ez3 zH+^F6iv&((*3#Zm(;MccLuNG~H{fu}Ri8A-xy|LPAMcN^7T5TdO94psA>I^;IAD+E zC0n|p63`)#@Z{}u-d+6lSe=qVfD68BSD0%$Sa|ilxl51)yb*}`(nfMcRdFJkfZp-j z2LiHykrGag1{MvZ`v-xv1}N-r{?vJOMmiys^V!s$JVzfl24>0cqMw_-_hcglde7k4 z_@bNRO%qUMTCbLmygyI=21w4my@IFqZQWdpIPd9GKGj)dI>Vt|ysq!tPSP(&Ksiep8nx|@%C|cMIgM2X^jY8p#Ym}eVAquc$S%_|j^E}5XVZL*1^`&?n`mFT*Z^~($&5{0rgkFt((+#B{c;sp9 z+#LJUSX0YMDObg^?ADGt&$aRuswvVK}@qPY2=UK z%>NotFzAw^U?~BpX_0^xak$2=>qO12VCk2oEPm8@$jZF78|d>->4o8_)Z`ZDQKkXX}wpcIh^dgU@H4^ z+U@O>7HTSV3S4}n^D@Ak2+w-L^eiZC?&MUbLvqtq04;z z=oxswow$In%Hh;|>`Mrb7)pEi7w4$&R*yr1(-muH*nLZn zwm1){P|qRqQiuGBLpWkJdk`a06K{d^{$gx{=xx@lZ8aJKM_5I%1jl{>E>&T#g*w_B zba)A89viL9u4C}RIh{*DP-b{_L)PLQ#C!tNqP+?_V#f~xTIKHa*hPM?>IztZqgK0H zHD?uEy9&|(bjzCdGL)Eb+!;7x|508Ef*|(61re5z_)HN0p}N1bKj9}R6v_^lF=EF{ zh$XvMNoUZ>ULrk^H1l&kt!y!Cd+67mEJVD_03*DE2)iLL-;)J{ly)?+g!L}0hGi}+ z^&0?%4)J<;g6Tv=o_y3wMv5Dy;Xcledl)Duf&7Mjw!6XqB*qiY^q7g6D|xE$j@TKd zaDyC-gf0Ks&ld8`7++4}U{Cq4%|bT4Pa%6xV8w_wi1PW#^W*MhHurT@3th?8Mqk$J z{Z(hx#=8sj_^b^J4m8H+{UM%+O8OX+qUD^{2!;0!uFly-qB8nWxpTRNsO7`(>hB=i zfx1u^R`*@`8i~1SPMx?7WK5+?_XvtJH3SB+mg9_>y@WV}PgLSZ-E%(l*H<(mp(VKO@02R^3m)WpZ1c7(TU^MWjiCPiSEwHHt?3aJDTzbn?wSRmQTcMKEC}!yJQfJ}(KLJAV7Jujc+39kAJUtH_T`$+cN~Vl{_qxBt(jW_ zKhpe7fFS!%;N4*ho@$vgqx8ka2BRyUpaUpCAM12H^fwcP2>%Jj1 zD-2QQs?K97hWA+7oUA-KjfPcz-1AwenNn$0H>k4r64^FL&F7uli>@T*7De;QHi0ry zb|`QqjxHXF5-$=n22`PC@tB>1+I}VCv@cQIl1HL;>3gwfT1h?RwGhme+=Ot7Dotkf zaeIP+A2j4EuM>!w2fhh=LCT(D2u&GuuGqkk9cs=f>I*1GK0{taie;icv{wl1bRxzO z6ow~f@oCr8Y89VAc2(v@)&~HS&{PrAb2s?to)4ra7cu#*(DDlz&m_TPSQ**ybz|De zPt4&l$(yZ5g<2koFlGSkcY|Dtiqv)RXDXq zhvkA2ngA1p%qEf!UOFWf^7G~)mRA~PTrA=xJ&NvJs$t?P>UMXfIWQQ#)Eu!g)E}3N zZI*!m#cfM(clANhIgmtPTPO}|miZEja!$fym`=>R-T|lB zHT3~Xu#lQ{cupbbqwnlt9K$5vlH=sKk@#H8z>qz6OMM>d0#7m2x3F(8BHnDqrA>)^ z7`w7QS-mD2@Ay%J~H2f{wC zLY@uv)pH8HRXw9u$Fd^FmVyDNsu8%*=ivo@Jg}+mHw>N)>7nou*>khW?>a66_xJdZ zhogQ$l7Y36c15PgRP^nxY=2;HIzeMsyr`8Z$O~&n8ITV>kUeL9ZvsfSzXe^s>irO6 zgbfKqX{4HjejtD!@B|n(zZ>fF2zn!5TzOu{k01$`S)FQsj^DR99`OdX9aVzd8Gg;+ zDZZL=Z)m$nPLRw)`-B|=I??=D!JA}U@YM+gwJ*p;P#Z}C)x)0rvt2242F{0!u9r$P z&dEVI%C7gUT`^rR$s)^|j>2;a1PW;ehwv8El{$RLu-#8UIOt||=e1pAxN#RLDa9fd zcqWRgJwRY5UT(yTY3+=YP8y!s9ZQO3$FyCQqFLH1Pp@WVN=2%hPXaznmR4)-NzAz)>lE>SRq?gZcq&t1vOqf`1XlN{tXqf;}3189D#^@n(v=#HOul8|LUP_GY z;n&#D`HOGPy;4i)=2a@zt{PRQEprH~n%q6w!4mcWeN|bmA6Vi7Yl|3v7#0lPUsZ71 zAU5=(UR$7GTQ?P{I?U0i54f@Kj ze>)}HM}5FL^y)>w{A7=%el{!b4UCtgz$4xUY=JNkb5@dwR#fT}??yXV~j z0|ES080!NM3||$9^*Iv!lgRJi9#X3;*R5St#s0dlDhOa1Gc9zWrVlA4jrh+m!~baGP;Q3{(pqz~S$43-xdQ<5XabDCLVLb#vN8Hm`;g&BjyWvHh;`)3g5}cBhb5P! z9|D=fZ?}W?I2eZPM5FPkM*ezJ+**7)YOKL^AqVDBT6gc#*aH<3Q~ z7cm~FYND5sIwz%y%03FyoJQ19BCQn;#zuYz!g0$gUj+(+!ozWd;3M5svtI-8f|Pb& z=uT)<*l{BGO`e>Qq;2pNj#tB8N%P)Z3{8l1<0zA;Wvv8W7O$8G(t-LmOGA}`p$%3W z(!g+-MG{>v$>)1pyG%0Ip$`yAc4JwCo39+VQhFf3#91rv`SK-bAG53F875NIOr56~ z=ysO-`z0whQ!{s=q5gWvo)GB-7-u9A64 zO-vDi=#5tLdx{*fAEuJ}d=13eM%8{?l1a)5o|WybQfikec9GncOcfbh+t21KDN{{d zVYd5OSwg;S_i{<+Z0GbfjEn>NHv6Wi}8jkb+GIn?4bm_>o#{2-o9DF z8ibcxIk?;>I$ag*0A`KFG2n(-IP_C-`|0A0eu%(>EJ8|ny}?IRkXjFm=LMpE?mYTW zh=aui1d(7r0IS>zj7m~)(CQl9%pbQAw0y1oTmq#NKos3a;=wZbjR`q>&m0O&D-2Q& z*$d=UWGQVJeJa_^f{*EeY15oidq^HOX3VJ0MGS59y|E7mi+E*e_Yy%&K_C7WSb*)m zS88YT6k8~`pna_$R#i2o&oly8x}euH%(njc@xCT^N#1gDxDVc{bWm7tDE9TsLmAPW z1&(i0tJI$9%pEmXwHOn9TxpHq|{fGD1>_l_buws6n zQBSMT{==McUg@g)kn~532pUBQp>VARaLjV)`d#8ymBdoL3=x@K0-m!=>ScLuNcuM^ z3}vql&QQP_ndn8d+m%{9)bTvrr487A6oyDDjUef|>e<({wDHQN$SHx584L&L>Q2e7 z|F2rW7$Za)h%s2j#0<b!Cr~s)p73qNIb04 zX%Xk)W*}W;2wUVI{X*Uis)$cjjF`Fn@NB6Q@L0y%TlXD6uocU#wiPTEp6{iV_kL(_D zQ|W#{E5k#rV%;dXa8{#E5$<55#=<#rNF7YeL`7`N#^N-X-yQBjqP7i$$3O)J?> zAG}?j4v*0yQ9t(67tlzhT;>O^?H|c0M+{*DYDyiFFC1>y98$`-QliWHBW!Uv7}WJg zYo+nS63zSKUq|Cg_^~(aetmzQU1XyU<)cREayJ=eH{fSScR(!Na~gM-XRW^hKsCDp zCXuokIW5ZipJwC6oIvI1X8jcQ7G!#cHq36v|w3dJlBR;rUzt0MxW@?0r;JGnn-0T6opUrIVE&oHI{o}5iD?g?Hu zDS9n=_%&s*5|JAlNMP@XiiV#$2%7E-KR5j7;(j3te{3*+%TApLx0oYhf&nsqENQ=3 zeQ6gLkF%ddC989ceViyL-Y&A8Uzu$2#*BbAZe5Li9hjt93630BM{7?zpc&kVJq)lp zk}R)SZ7C;eBsDW;-y?LI0=8_wK=z4r)5Bdu`Wb$dO`L_6yTO(m47If=f!9(}Ujkpp z5f@ht7m0iwPlYpam6P_-da$axa0w1|gVkyivoc$W zEQ~!0jFU>K%Fd5yG90n)e+*sJ34``6K#o#*eY1_~sr;T2z@#m))NfRQ0Vovd{%IQg5T=#y+YH>Xv**X}H`AUvo!IBS3y27PDF!`n2oo68wk)7Xk^J9LAG72t zb}Zn3v5P59^X7@K021Z35V)Hv?J;XZo;w1M*7a(u?bDua!)aIV0TqI4Hgk~FDsb;x z#1Ept6ha>#8ktJ2%;#0bB!qve^C_r__#n9*O|nxrI}AutWOg`xSY6B~59KJP0a)wMEeg0J+kFvQte$48-R)DK7ZO8yuL=S^6A6ya6Mpb(Sqp-|z_z}%EAIgL zI{INlm%e&E89l;sLTsdBzlD*Rr0E#Qdww$hq#Ga)Sekkebik!zKF6@ZSdfWmv-8YmOzx@WUWH*Y z2wzyY<&xX9!wMG|Mwr~hC(Rk~nWU9;JhynW*n&>$Y+dYpnx8W3jSVqD3nOzE+4k0g zzI#`Nt9HU33OCmiJlZ~eOX9D|NU8x~4;s#PiUAmKI;^tN#e**U4)4Ci6!~=lYb_kp z)=7*t?PPGD(5YS#1CDT>6+oo7mu8sgnw9N2`AU7JT-S(j;u|f9@ZPulnJ?z(VzsbS zo^}-fBBIGX6uXEOZLriy^XBtt7qCzC!&uS@4b;|V7NVVP$uZvo9h)6@Q`sJB%s0D{ zi|y+LEYVe}xh>aN=_A}pm$vG;r)qorS0Y#5MU{!G&jXkZ8iSC?m{46NnLLb$WzL&Z z=4)f>v$rj5S!@m=gau;ISJ5605P`Vv0ZtzY*`&73Mi&01V}Sl=W2~t=9K*GIqDlI) zy3g{qoXC%Y|ESP%9MOws4V4+EA-oc*3RyV!KM`xI=F!gPKFBevc!jp4YA40lmU&-4 zwNMIBG}K6BzEFkhu!V#X-Mb3^Hh#otF?IAh4FhYse+d{HV?x8D!@Cb_`j*Ahu?FoF@+03cmtN*-m#<}GL z#Bi%WK%vRY_sHl3lY(=xY_`Qo2J=@JL8;iM8zgWo*KBZB(30JM>p6eSqC+_@bZ z=8;3yA`G{T4vM>*V!fcaKm zFrSY=Te|_!<`0;TDMR1yzb`*&bO)1ZMOBEBkR20Tb6VBZE{wlNu%cy0XVH<6{c!4P zZO?n1PTth}_wT!!zdGh+TH~%FY#Q-R^i-Ak4Ra3;^Rqc<=QWAqn@Y*_N*klCV>koF z9Me|q|C+$yejwm`S2y-NPDGavmG@uKqw8AyxUTEPH?s*2!=oITYowCNc3_74GxM81Pj2-C*`k*90&?{2rN|dD@H`yiIZtAj$kYe zrHB6V5!nH2-UMZ}LQ#Pc^QubrVHlfIxbjk)=4^pUe;=?$tDduLe)oD6xDB2L1tjD} zybK%_u3J0=UM8->03i1@9i-EiLLpy}eQGx{B3yy9mQ(qb?_3WSK%O6<)X}{>2$F}o zc_`20b7+v<4nWgQ)2U^^;CGJ^yJ%ZNAYClHa_J2-mq_@S<5Yt;FV}kuqYharHx&GH=|pe2M>@o~jR*X&K{en(206e%NW-Hk zpdd&AoW+v>gL?v;sWdz(Q;2~Vz)YAOgo!)@idLg;aXj%YWX*aOka?ox;#x3Fe#ieX zt;2X`E1s)gCV>41FInQ!CUyWI45V*uI}W5==i`6hQQ(ajli3e_wL|BlDg7{n+z%*j z=*!c)T53DxsbS2-)WgcM z^ak*K#$zFy%~kqE)98%`vP6sjKU;Dr{0KUff$tC33xoA`#|Am^Pu9MFm2GL^SxcEU zOgh?adG5A^>7$kPj`_0{#oY&tl67fE;Y>K?47mQ=My6n zwJ*!`_BLxmGSWw8Jz;$llal?O(Hc}-o?pHO41-NnKbRUpDB~MbG9i4hwu*;)1rq>c4aXJ8qIA13~A<#$F z?#Z_J1wDVu-1oIz?^c$(DHgk{%MRLNoFi|EOJ?JZPd!y&PhMl~1nFM`GCH0sW`oss|1F*O6MJi`ZAc=l( z6R`KYKZBO&PbZAHwv7_3tMWz$P6|BomlY+OW5TD{XLgrAD|$yLIG2oS(H;&9i6o?z zs&o06q#cp&-9MI=YpUjyI;aE2ScL`rL}Yn^-jW>3}kG zH_~rxV{8S|*(D$1v8br#@nG)@r?i3Kza8$py5;blftT8UU$*#DD*zHSc@2 zMHlzT#e2tshjOGt!0Kb8m1)kj)2M*E;UHp=3`X^Z!e}|o+`zK0EQDK5>ZpH}U1%hvXgfF_ z2xI|!lI?1zO-xpc2cwX3Hl#+YquUA=vbEc18oPmEEz}9sed6_KBM%hoSAq4F&7*v6vN3TjGjJYv#oc8t)HLmj|BnE`+ z;lOl%MgTXS{T;Pn7Int6A(t!aK#%k2X#knt(JOy^c140V`YDz44p@p_eIS?QNPaL; zheJ45NibWvu;L$HhQBPgvjT@!r<<(&66==T$7meg*X^#TZ6Zw0`j>3di*UdHR%b*s z&KAb&F+AcBbkBfG!*H@S^F`3%DE-468U8Tlb+>{LaC%kg z+;joTc zF#d+zz@8}IB(yw&G2yJu*d-IRRaar?xIX<;I%*V9rABqt6+Rt>d+8l zWoW_5kXQ^nAjtck(kULG!YSrDLKw^d2lyvw`h<_Q^0MgWq?K+A+)hXio0l|-UF;qN z$(p+ycE~)=nOcsENzoqa0!|nXA!(%zGeRLiMqFff%8io&#s@ovRX^aV5U!=7X1u3L*i4?%Xn9oHRi*fF0~6boic1 zIb7)TI2O2TUdy~Dyl(a09k9#8986hfxF$1``sEd^yHDPjox1t=?XY!@Dtx1Bo0|?y zu&~=0ES_q1a(<$0`_0we~r(x4lX_;|#yX z%Y*Xo<4Z-;znOf+b$kjab(|V@YH)UTF!$h^$m8${B%~LJ6mL%Q=i&c|{O>ae`XuUU zYCmiB6k$kZHj;Vr#M|ofd+JcXVkiN?F3F{Wj^7H||D0isq9pqCoNVD!r=z-LL;T>_pIV(PB6aYE8l4ivr%%QE;U^p(gOr&=BRfnkzjVY-B%p zW6j^ZQ)p|*Nd>;57fsG7r2lP|3@sB0SIW-FdJa|Dyb?C`-jOF~^TLrfEvtWBmVbZH zU+;$jRH7YPDVsd<@f6*u^|sA&tA^FT11fM#8qzov;H5kKy9Ff~OPLlhW3;e*%o0mq zn%}mpZhz8H5&PZc|NSXo7h=x2PweXb*9DUEgP)=>jPsMxV)1_vxpSzOD_do){%`Ar ztuugS7tHJYJG0}zEz)rkE*khzXV~kTRm{;GU~NuB^_P5qT~j5d0|yRJet)_&83e)t zfl=gu`M&~TF)}hTG%MW7f39OC{8R>mW1QL&!_aqrJ`Fv#tT&Asm&=mCrL904dvjLH zpZV3z$3L-^@!`;+C@;d+s;HBp6=M+x-*>iH8cb%r*LMzQ`Q7j9z=qt#*m<*7%C!y8 z`gdQXx;*-`Z6YHB_?oCUuMoGVdN8sw8bNByT2^J5o2=_k-+b$Jm8O;-O;K#Bc+x-V zz(-|N?IG=zTl9bV{N`PmO6WZl`#wtT@y(|qE zNCZ^l8}Xz_s*3U6v1rSk*<)g$2iTcY^v` z#I#zy<3#QHvpZOLKg6=r?|t<(A@XX@{RR_Ec@R`sKJduEsy*CjNHkNH1lu}=SMH<) z4RX|g02Tu58#>h9!2z<4S3GLx1PZ`qut$FWcG>n&q^M7-jU@mK9y~hcb_)EPH7%K= z9HN>RL2qoLvDSG(^5@Lst$QX6A0?qgP~^5HI;4biBMI0vO)vZ*HE0|2G*COFc86AOHGu3RU||{r?d5 z7En>G?He~8f-r=jA}}B@G}0g)N_RI12na}bcS(1LA|NT!(jgrp(ka~~9e&T~Ip=-< z=X=-pt;ITPILpP%-m~|9?&rGi>vy$Ot@6!dko6RpK2_>_M@?ST%Jr66qEEmzZ#m=T zg-T{^+|`?9BPChik)(OT=0D4-iI}e?!Xf@2D((}zg?Zo%VSyt034 zZoO*iqWe;xZ(a%=Fin!cp1PQ663)5m0ibd!fGa!677i6oq*uE$v-q?r?0lx@E}||s z=<_FmGEtVyV}}*&OslA1Fqszb8`Y}L>hw7qO74@RlpXlExIbQ3&JIhC?HY97cGN@* z{>*?dBU!}#=!~2j>R*din=v_>wH|%!d^RoelNFgowdr*Cft+V<&)JywSl~i)-#-P!g74I zD$9a3yH)ScCZAMkRivjI+;hx!9j6C|8EPNV@(U!8Sd~5^XX+j=qg`O0hjRAO^x%E( zuN%!MSveZ8b@sU$RxeWFl$Wihs1E8C)Gyx;^_QH15%67!@$;Qi8xfx3wA@@7}~V9P>3n;thiY0rnQlV~HqiY3vEQannxD zC`(>IyarG6g6`#7uz#Tsi;hkPg22X!B_)2}m;E~v_V5r6u-9pb-cBKXlZvDmDx~iI zE)atreX558z2XlQ>`oX0O59x8V~v6{;!1(iHwn>liF2RhsiqEn;YuH}_bm z=ssk0zTdN}_iE-fjjQd6T(3&{NT9(Z0`k<1Gklknt?s<;4h! zRwI*~^P4S7yPCAIG**?<6$D$5%#4v)&lwNtFZ9Ix|Q9sudJhAik=Fc^2xo}zkGh{qAGWBo~9o6 zgY5cgeYX>=(WFP zZ|Mz0vJM)ozo)FBKHo~D$+wE9c_u%0BB*ZH?fx@)^N0`V8KoumxNAQjfTOXAJnFtQ4 z=D&IK#2!xJfU9y{u(5z$NXz0)r;|>*k9A@oz8$Fu!WMwO!FRCJCc51aZaXhvg5%$) zb^_{lv1FL#*Tw~?W(`p1yf0RXTPDLH_Ou-+S#WILv&Y5gPD_{72cgt{ZiUt>(y|nB zq8M!ZSU-Z!?!928eyRgf-riF`#3A`Dhd)bcE%-FvS2K3R_A4x^(&Bux@}~FGJKZrd z-uzz@L!ZB(%0`6S&C}+atWUC=PNcA#oTadv9;+nxESuHDkiG@;k;8F!?5^_dcW|bb zPM5C4h)MSwb#>3VU=`3eBuIr)MJTBP@8Zc?EMc)I|EhUb^I7*J<`C0!v6c(} z3W_(njASqKMjdMuuzw6lRhBrM>_41%#ud%47MI~8J6vn_dABHT<0ZD~DO9BolLOQ4 zkY3i>pu2~XbxS2hu%huG=EcY8CC|ou0vNYHz4cW zh~kJ-W4{)YbO#uLoAx-jJ@}?72$m2Dv85f(vRNcG%4Wq70+Z-%IhS>I%N*oUAnVM} zmFhUhw*c)lnv_qyi(>L+yJ|C{X?VBW%_yH2|f zWa7~UB5xt*UBG=lbB|;Oqv7QmGwA!2*XkAHkZ)~pZ`#p^9(f4c$H&QC64zksjoYkp z5O)5p4-LK_Rz&*VmpIPp;hnlqc0a>K&Y$4*t;Y|>3l~-Hld-AfAD#DM&7FM1DXtjo z7EYQyTW81&I~$_H%?WF>)q1y+RbI>rm5txduvORF8>xey z&rjXox6ivq(ZFpqh1?mceIJb_P>?%=BX0rG9@Ti_zV(?pZRzkh-GW#1YQ4dDTk_19 zz~uNcvVmo4>Z+cgi}{tB{)f)Jc$KT>?M6q>)z9^=d#glmzH{+;=zcX^jxY1XEdB*$ zoDEg*Nv`bGS*;T5`^9f-^Qr#(>)Jus{d&hW)|1tJ>%*fC`!nHS({RVw z$C_XM^xFkm>SYrFG)MTr&n(J~(GWti33Rq&n+`z?hNSg3>_a2NKzbN! zTstxL$42X8#qb8w5jo+PJ8qRaLa3`0KZ&eFFqphfZA~d1iK2ptW2rsi*(}!!3cRa? z)!pKG*Cu_H5!{IT57oy<4QCn&LAxyp@lK;x1(PjlS-r#h%X88fHw;&#OJ^8(iJOLi z7vAouh7wM4Dv$zEe$<&;GJQ{W2NCx#A4$(+gzRJ$96DGe0aq{Q@Nmqi7mZA`i&^2d zBAp&bm{WM?%Ia1A%U}ENM6(Z-n|=|lOR4nr5{C|BO?4+%eOnhN^MbSqc`vQ|W_)B~ zdd?+5-J=dmfAoFZI81u}@m+e+aZHM5-MvP*@=0iCKCo4Hx!CY9rK6Ru!EX%yBZRsL z{uB_CgWI#w@60-J?p$rpt>hg2Cwj1=6KYP13-Q>;caxe96HR`uGOhZ_bDA1y-<={z z&mog;eim6FT&z$i;@~u%v)~CMq#&xCsde7grz2l~`KyS3<<@~&Acf)H?v_F2%TF^= zNTcuMHuXk%+%p}$jufQAWfCSkQKX(-??GaoVExtc`_pQS5uV4GTy$Ezd>k!!Ss2_X zy1^6piX9jI7~#nK49Da#2T31%d|j;sWM(Fl{Q_-y6WWdv971oTpvN|Xt_T#mnJ%rQ z&W~qF(E6Ktw_}a6Kams;lSq;2WnfM^g>B!>-c8g?-%UFX&zKBe2tMNuu|D60lA~w= zmu3_5HQG{aNOh^}4O+qA$AF!(cK*F#+57FMJK_oL_~D`-8H$~ic6yheb^P9j=Y--( z+LAH-!rrAhCg)X)vQd=JN^Ms8aGFZ_%W7f&n{CFrOcTB7N-)7zk2P!}IbFVl>F`6q zIlpCN=3T6%B#|2tbtagcysPVnu|00JS98ZrR*|KQ*$eK0WnKiz_PMjK`I?GmzZZSf zb(ck*+7hjaG^o=Th~>yf-9Lr?BsrzP)^mKrGWKYiYUr`VSw0LlS4gZ9?XoWQqEDS} zzs6uLH`M`CI!VJ@Z?hkBRf3u&d(zQ1p~mr}lnW0FwH`_@e~f4SQfVME#Y3agxA$Sf zH$OM(;}U+n+!XlriE8((8ph7cK&3wL@0*PQ>AuOA%f|;vA1l;SOKsxRQ18!@aapB6 zJ=Fk>!RcfkY(0&5jD;{aCGp+X1`pA&6ltRzqpJ*2$ztfbc>OL2!@+^Qt)x7?H?j49r0 zPmFC{6yY&mD4Qm`W#?Z)?)@PlOeXO(pt27ZW*_Nkp0A-;mj>IP|EL&aQbE(9DQp&t+dxf*W&!aF~zl`x;a`GKC|2YMTEre zB9`R=8OL7TN}=XDCPArPrBi`JZT)gW!|10Dila5PZpp!oO#8vQ>`ZuKvsKu0UoL|O z=>E<54VANZ1UJbz<+dkmyP%=zpC+0*ZUtWZlq;r|LI*SPA(<&U8iau zzSH{M>}&pEH}u^&P2zHJx8}%tR9PrwWOW}6}ro(MI+$;Ue^03qua-Q zx=OAOX(jU!ahVJ94a@;tSAEZ8xOL8G1qicdM%NpSO(aCshy`uRKT-F6C#KdTOK1GZ z>~Qt`p5xsd+U}8)wiMQfD&^H_*{39|L$a@Fj zp_%XV($l|Oi&aVyVHp_3G!oKIp(y++I2PI=C^glVrzZFUmF zthO44Df3Qy!A4J~B|hCCotl_SDn9tI*M6NrNb0+Cuf3;i+N1T$r0zug`VUD2#d)VP z`z~t@Y_hN0uMq1Q0?MO4QBMbTf6h~L<8Aulz4`Q#dtHu0#QM_AtJ2fWft^@?Xq|6G zhYd4#eN>lE{Fc-$$>V;Y5B_!1zd4Y10ub8ef!SCK9#y2*0Z-?pM}SjmDAEI-*hB0u zPj`#Sur*~cMCE{J!p8I#kRahm;#aqZNRZAVxHsZG;DkCaaavDv)B`ARzAD|>LAyB8 zh2&yI2TnYE>ODs!;O9d)Q=5v43f^^pn51ntD25`iUGnK2Ku{gEBI9mwMlIlYM`Psn zI6gt-=H*Nlp_YpOThGrqCs1TbGLf>U=v#X|$5igcZjd%!%Di4(L|wf+i_KY}GB8)E zb6RI>mZF97~Pin1G@+0-K zgoMa`9wBPEe#t}=;&$t{nx>;Im!V+y>ea@##WK|?CR5pQ^g46z>hnLJS%wwDh6K(z zN)-Dr>0)w~O*e;3rW(m9W6HNOw`QMRdkgzc|MK09XB2? z`&8QagzI`sKjy9C5G{Mwzr*D5&R(=?Oy*5&p=K>@KEq>bUbWjf9F{B&-o9$w&b4~a ztBf=G?LO0t$>o>pybN(F-%N<2qN5YV`k=bHCGU7%N4U`R&cqW-557uVZLi_XzthQv zPDjTnojO5#55}pRxyJTue_Y^owq&t%KGis@(Vu5=53iqGI+s);{Q>u1_*>dd6#u)C z+UNB9&4x4Z1iiewX!Wt&w!5SigJq1aH_&&F_+6})JRXG{mB`vW`0TPj&ZY#*!t-Bk zJM0NjZQ#gi8=WAj?!iu-d^I6j;hgYuJ8%~_(pRtc0O?NS`+!8lmXC%Lg#yTCGzP+s zT~0Vl-xzF5F@>8LJd)?l5+D2ntT2S(08~( z=fT5*xLK;OA?UjdL0GQt{k<(=u6y-)N;oT2wyk2xSRU(E;m_2oJJy#1XJ4_5pCd9% zh(w0j*R=ac$T{w|HAq566j2*<6vL7*11s8g9gOxyGkghGQ{FF8C=A+XDbUycg-u^c z0CmOREezUbluCTxkyko1RD^h4{Df8ze%m>MgnM;NDI;&A8MAn=<`JzE{8p;bvyqP{ zUG`6Rt`TCm*(r+8^%1SK>M$3l?qg24JHL>9f=HWE_#kEYiiDM73=BkOF1@xw0${f!2l-4!;YH3ob|5b=>gO7=_2DG*2bbT?ut+h z>=-{_mlxtcNVL}5P_wgjn6z#kIdM8$&CBlJtZlX5w!3s9^?PJ2h7W8o?7Jf+{`xw9 z?|{XL{3@`7fzTbnh*XsXznT3ZfSt%&|5t_m`yUa)qm05g!%RQ@)nfiV2FM1%_>53x z+>JvCZ~yy+!W7GN>qBjjN&fD={8ne-czOK?@Rxu8Q2Pl_qCy!D{$zy4%IAPdhuC?P z>3;_PVhE5LBvbc&-yo8ESq5FGTvGP6b+A)v7-~u~jMScLrTscbE`C3S=VwG;J^eE? zjW2>HX|HWaqlQx~8tX+wHDo(#^YS(Nj-GAyZY?V zL(T^(o?%I`^je)(9k+Iuhz6~#h+iEkBlLlGl#)Migl&Az-fD>L`gN=6U+&122|qL8 z)WB`rbyQWU1dhdxS{RYG90fk+_V*2Zvb1qn10op+`ZRu2_$w2?j9 zl|dgT7b8o-k4H=QoJowW*;qt*NmucmHS&3|qY*u7wLgQEt#y>W^|(z1QxmjSwW7s4 zp$9$x;QC3R zLIM+)%u<7~?2&8EJisR2%UoYW`F-vpFbkKVrOLzVchVTZc8o=;-BvXUQ{kw(udk02Eq zk!Vb!8gD6I2)p;=?do@f$ZDRGx_x!d0@PVC_PEp+b?OM*MYMYJDsY;7=?+5NnU-9>? zW*S)Jva)MbX@;~Ir0A6FuPN^3Yjl56F1E@r2%cI_NYz<5mrI){qPmf{$wT^D^(@`= z=h(Ap^=!#y!6Ke})G$(Qr82&cv5r124UXpODrFAWd3naBy{BGmuSY2_tT&zMG@fb? z+33lXz1Go?=B+lDQ4V+XxgoYtuKn;+=u>{DDAeJ{R}~eao}EM;lUX&GB%LWqvp6EH zC!>}!CKODfgf$2y`7%81r+J~=4&n+-fJGgy(__Lyl72Gy@520gV32uz$CYD4tpC1M z!E0$KVjyq&CpxLyv$6f7{otFUmdC}C7xt#BNiIZv4npM#H}bRR_P1mgW?~7dMT#N@ ziF0Xw_xcl(MllbS>pfA6w2Q*8>#!-6Kd3Sq=zmBkr^?62A8oSsRMfCih*KbX*LRz8 zN4d!J*%S=(XtC4O{@Z5<(`bk03lF_Y&%=GPsr0x#zvaMat?jdZ*zsxDH%pV-FJ4C% zvrTcDWofD*o;KtsL)T?>HUc5 z(q)l?#DBY!u{K8;VqV3^$FG7}*(A^>yV{Nb^)=5~Ei(<8VCnBRC1eVYE;_w17c=77Wb_AkEh;d@4PtLcgYa`xp+}uxzHDJ(5)dz<{sf_QlK$zPc#K9Z z+^F8&ps1oG#2W;w80d);`cht_jC%gHZTT~v0R{c$Hp z!RAd#x3l-gPWc(;#7S1k$0Z^>pQvX}dn-wMhi1BJpUcT&tB?DMFE>jV4?Z$D=kgS+ zP*=Qs#n-U#eWUaKPPzJ1^*9yXDWHM7J0eIMG~;{aUyk2z{{^w<;&I7M{CxU3#$Lz7 zN4m1=gHm6RQU8-itIg-*oJURke0mRrn5HQY<)yY}8^f{c*V@0<-{|S6#$&ovB^hre zeCG^P-QNQv&*_^C|_wDB@^=0=blvkyf_r56pTnR;gCkw!mhkAa&^042WbA zqhQPl&Io_KKlizI79a-VKsFFavH8C5YP0~xdQW%~{Ol)6bqZze8QH9ga4rTV$6jG%Vs}>X#?wu3nr{OLR%qn*EB4m$;?iZsIi1{?5cg zTy1B$dyZTyavxA0=FqT945c+%Qetp&sr-=^zBu+e4;bY}7g)b%o zdACiXE&GB>>HG5~R1+S&zR8-hbSajQue!v@ZT!jg*(G6L2bE5s=zanXpYv{PU;eVA zb+-AsMqrI9vySw!&0J=}-VD{~d-V*^WnqG~G9|ZUB7~5BeXU1hJ9vMDxg3_>4e*#N zp}*bBYa9My83S-f{jBqhHT{^b^OUJQ5VwmF0{0!324hTw|qm)5|0&&N&@2{ z;`pF$igm0pXZV+Tdke7*;IiBMUJR76iJM)MG1N$EPPZ1_lWE-tXUl8`;%qRnjOllA zjXvM-j@lw6dOWzvnL@rZ7PJo`3T4U;$hbuKQN?YIpIz*fS0AmsqdrQ)`Z%6zMCZ2O zpsvh`XnFUXP4q}ulH2E;+x4~(^+#ZWiQ8J=_|exviC%|f?&P3t=bjClHPZv)qo62u z-Y@fBWSp*D4beq=kNF)(J#XdYqrwv36>26rpEm67UssDf9H)hmIMc|iN+(uzp51CD zGSiy+Jk0xsUh_iFHIr(6X(c{u@rEjHWn9;n`tW13gfh>SaCpOTP)D&A&GWq**RmaZ z?N}cebPANp82${3!58_EGY%x%^niJlUE((*RjkHeBmtx)A4Cy8d{GACz?4Cf z13WI^PW@^L9E(H+s8*_RLtHb`|0%}dcMl{1p)-v5eR+~Ziw@)WAKzA{-&*!)xYQ)R z-AYcz^)}cXFE|XcDvnK|-`z@zRoO~RjNeL#I~uY?(`ObeCw({IspdI@w=(!FB3`4+ zio$`MNB<=tOEgUZVpVewUD>~&;4P*q5jMb^5 zYBgG9TRjWq8+hkDV^;2%yO6j1PAc+9Fnz8@b)8p@nnFFe?j+D#t7axBduFZrX#zb! zqe()^0f-2|vjlb3@zkg~$#1Z-nK70efH8!?SkLqEasw6; zE~^+Y_0YdJHKypsR(8dDqWM&uzj@ZSfGbcn92a#+lO> zyQl^Td~o+_JBVilzRe()lY5bAawUEUCYae22b!TgR^6TG`+TP$WoVrZMCd<?P1z z796WXiTAtB3n&F3GT~X;$$xKW!D~V-q=T~h?J=>{A8+Yp7S3*CACqNJ6$o)@Nq1BA zxBlWx)GQFCqS}wKF=I}2##K0ILQ_bv%*tA641ns?t-L(`Qm_*jSk?59nd!#I;6C?y zW`IVO&SP5E-Xhwe2#Guh&fRE}X5!$0AuDw)cc&5-mfH$lZJ{SpPjPPaKO{va;Stxy zp}ZESShU8;Q1q0ehH;xhVjk^AWQhxL&-C<6PL(0;D| zq_$UXGPh9URoY4jPtwkRk?}-D?^XAHtc$lcBb`OrpnPMv+&qJw6yMLr4<-3fUbVHMx_omRE7GQ|+= zfN`NlC&Mbf?Qy=UW5O6ia$(+`wVSfpj}m65<4WZewIXI0(^~>R+6$=r8w8}3KUW=Z z*sZ#+BW$;S8zP|X!lYwx-iz-dIhAsQHCGj?YxmV@zB+(R|A;bZAyz-f7ya^`F%gRS zRsN;@i&3;%o8#u2tcMhZp<~60+FU!+uwHqiA`NDdRT|A|I=W}KdY^TyZMLShn-X-4 zdIk-j^Yu}8N>z9Agvnqs@-3RdVqi)i+@T;E)ESX>z+Jg*)X*Bfq{PTWC#@dMFSxqF zZ#D^}3@^<`!}K!=BuEd*KQ2(`p_NHT8{|D@@U4n@e91hpaVHhDz7%d1)`mP&2*L(>^`}UOB78r^!?^Y!OvBC z&2RGrPR^}3lo-4sDWW}B!W}s(>|?e3{0|1L4l$(!UkEyDx2SJMsd#VJY0s6g5iRDcQGM{IMMlK%9{>?+ zAISKY@O_Krwq^}E@Qhx&`aUG{SF z1y(^J=*(KbgD}y@FUEuE$Vg*;%brMKwI&Q-qkbmt-*Z5sqKlMVCr~LVK`uR>a%xrR z^s~&JHzsH+hpC2B9OQshX2{yZt zDa=5&>8&-y9*kZwz+t)YlG6Xc=8}8vgeA*u?SWS9>&(vX?tWlaFBB!g$N%<#l2I5An>!t$Y%=y|j&w z4yd0hz8_xNT&x_e;8JP`iM}{HZzdOBzWTAFVMFPja$zM8 zi(`&TtwT{KdN*|c5^c0S5sXYmQ1#Q{0db7qwwxXj=wo(r=p3UYyN`wV?H5f6HD7y= zso)Qj&VWJq7y+>GOtHVffv@$7NS|FkKMVaGXOokH*D5OkAL)Q#5IQ3jxdx?x#OJrJ z6}FBCcvDj`dZf8~!y7V=2TJ4PitT0jSSE;;q@;_f_qdCBHM4{jJ}Ax*X$#&F&b52N zjDLXFvp3&)WqiOu%?5ZiTYt2;aALn!Ivx8O#=3>0r|bY}CApwQawFN1-`+_TzAc*ETRQ^ImPqk*hU!7tR`JF!l%`P0e|@`d|+UzAbsLTP{=c95 ze;y3&OQ7)F`$W*U_OJK)AHNE#rpa{vIhGg!KQLZvnjOXqe`{zv2B2Tiv@^c1haYH+ z%RkndCMbOS{a-)NQV;}u!w-6ME&lU=6=P7Iu!K4U+==7a20bQPY^3`7iPg1~c5{+# zc(2{B5n?Fxgyp9zBm8H7@6WC?gs zfdSi=xaQN}$4%*Xe(pN;*`=HVv`I}Ezy<%{9y}>g{0ZVL`SwIPc^gyC z=AX|BsDptBD7ouppZAV%xl$&Lb0r>fm_va-+o#5C_y?O9N1tV71G06me5kaP)YlKJ z4n#gYVBt*8r|bFT^UK>0Avi(rLhIg3)4_CZ1<->}(m+84R-(`cEXW6)XSNyNkSrk# zf6Zq2VNh29fQb98_(uKPDewV!fBQM$IKdwD!~gPg00v9s0nx}`;uVV@B?W>}%r9W8 zKZzjM|B9MT5!H%X4gL}OUFnqM5nw%L1g6}8XXe(K@P!3m#lp~SLHrwGEe(5dcuKv zRAv1I!{DjY7QLi@!v+H zSZQE7dY8pJ{kwQ=!(0(i*S_D2R(4{%Am)`VEG!I;-(@#BvU40d;=U47| zEw;TVA82$d!A>yF{o%jVA-E#qcFOyNyie5Adxx9lZ3#+L>+(doLAkZRIf~tCI78Wi z$ATuU00a5LAd%IvJ7ccy%zE`q4QjAU|DHX3gY#~a%_G3`j%;6t74LqRTlB#j}PQU zw*ZJ?gi_z@M6C@QGtjVDx4se#QuCNMuV;D4OT)wSklR$?zyy(FkvHamij{=!w-y5& zsI)&x#qV~OeDPk{24Q~kX)t)!&m9IZ$yysu3+$Mq%T4o70UkC6{1FRD-bvpE2y+=| zFFk0CsKC7dTyuKOa?I03Cs19j_0WW}5(Vc1#mReKe>elU(Z#HNncxo$xC><$?Fw$r z#}&Gc$1uko($@LO>8FpCnIT?BV}N1N`$4RSZQL)t%Eco~a$; zQeT~4)7P1`4Dm44eb<)UeMFS^_0LGzQKE3{9Yo@~<{|Jb1SK4qQZ6zXHGB#YE&-ro zmD3vP(*Q_yeujM?oe?71emp#vsG?>BfN%@)#40$wny-&!ctLdU7+I(=gw;xbG*sdo z=L(81BI$37jE7>_E|gVAzC(g=t5-d8Zb-02b{B^Wa@b2BZct z8w?jP1FuB_JVyw)vFQQ0DVa{?J#99Kq@l&K+y=7YwG~u1SFnUNFz~4n-`~^L393lg zh#Jbn{4;V$)X@lHt%;2eLqy=&9{3zuDb`9YqaBxiO`;<`uU9AW+VhZmia>2V=u|4c zZj7oZrbf)$X9K&);Hh@rTumV@H;dpPrig2D$;35IB-!ur1mUlb72gT??lm6E-=lcq zimMO_AFmIJ5YD9)st~;v@z3#XIP|AQVj;HiKM&x5hu1)$1SLxDI^T3$$4bxsnb@W@G&4(6c+}tl{a6$V!|ckoCLR|P>TelS&YLU9m{HfSwu46 ze$W`m4)gK>v;HSdDh2ZBZV&xV02|}ONjq5;`H3*2sMb8h2JW+83lIR{j#0J(x%8w= zDfa=8W3WOz)C#keqsHI{IL3)e=#H>0k^+Vba5^aQRlFpKb3Le0uM(+%kE9z0~rTz>N0C zSJd)c@6{5%1bD|9jPEBURMF05t$q3Nr#9yypdBR>#z;l8TZFS-S>l&ELd*`XKN2_q zd3Ouy!^7{(i1IKHcVC+uk`1?P-L*q2#~%v`bkXra`$5==*?2I;?>kV8dVxIA`EmIp zX*)Wck?g%6%6 z4e{L3)zspF*)*Lb>Du>F-&R8Utifu(Riyg%`Q2Y9h)dWRvW$WE1BXeUIw~sRQY_V2 z`4K$%`!bF3#mhm5l%s;8C{EOuOqhCA_ERQ&$)$!&kqH-7%ID?3ET^Ny%*W**)fe3C zl@i3Sb}85)o0EDHIUTvh`+&B~tIu26NnVRh=yy>8_@>jy7`gRO47kYt@b)exBER$# zUtBwcfJ+RPU_Y-=lqJAt(3hYk-|iPSZ`%et%+bq5&KE{wirJVn^D&No&Wq>&l(ItWS#5Di9_3-Mh_Gg3QYn?w0>}RLI zSh|M1z|oiHA#(#D8CO3^RGk0lF@MURfBqXQwK#^D&C3lX9=b~=sARVpY1@&FalEE) z!91q(O4lv55_KE3p16~OnQ3v`hsYWr23niIqsYwHF0Jb`0*R*&1SCB^*)gb~*DBKjEoHS)M+xLCNM%N5LcolLGg6cnmtjbGpl#3p@N zluAW!O+E}u9<4M0>477J+^(J-n*S+>@k0$kwH>|~FYyg*-t5J}CCA&|H2_X9b*Qk3 z2SUEo6gpu3%$$ZNNIM2NAHKHjeKz^^i4DYaw4ccqj8*6T6%O42b+M;{?~Id20| z)t}jcs~UyC9N2xsEWKihd}VEgX;}jWJ3hMwX{NB2=OE#=Hfq~@(0nAr5l2h#0TlR) z?SS(amW!?(A*sH8hy)nyL^wG5G&Bfq&!|65@Fy|KK0L~?hCGe2@*)i-8h`1DhJMZZ zv;gKrUB@JRD)DF%sJ06zK_kLV(sb zAkI;Oi{e}}k9%a;Q~>we@~1Bb{h1$vpQ*%F5KS!o>f6+7aNFXGBMc+BdVdT>&E?2E z>sF*?#RWypJA&35)=JbIRXT?SmP(K8hy#3F#2&c=d8h2A{_ECd7^ikrDfQ78-g}wn z@${^(@g42}Ct8cAg%H>?BgjN2RH?p0g<{Vd=PdAxJQ=}_MaA6SN`aaLTrEL-;d}+l z4($6+gvkJ61bYIx6SLR>*eD-L4;|Tf!-k|=Mc!w? znpsTrKY0NW#9rEf5{am?vShZpTbEK|(9$$(k`d4z%th5!`&0~mCSO=;vIm%>4|*xF zA6?2FEKaESoaYWrVoD_ zdHB7dr(7T)E_N78LJLqB%Svw0Y#ZGK+-p~>bl2dp5wI0+?Zp4>_@Tu_fg*ed1;@&J zFvakXY5zWP#B2g%`sj;yugaw;F;{ijEB0_jTk;E3&mPPRf9CFQX4RwmvKxU1gKWBs z)RV}((N(X?0DJUz5L$dB2%5mJ%WW3Dg04m{Q2|6Eww=PaQE}<78>CRfW@%j4JONsA zYYNE@R>3%iinj)4j5X{`J45m4`Gudb%p9q+Vnv{_=z^BsIR40rT%$|Kt$2Z;e9jUFdk`B_4mLKc7;=DzVkt=N(D zai}ivp2xw9!=}7OtHx~_5h`{O$jl(olh*n#^&R#1Im}~{Vw1OFwG*V#45=voVP8(u ze>iEfm1EYVp@btLV%z?(X>t*jR;q~Lf)`!Fbr_SM9^^5tsKWV`x@LZ>a1G@d()`Eb zJaYqcE?(B|NY=TZd)RJvMEu?rUuusJ+8zoQxke{VR#9Cvuj7rDy|kzuwhl3a%+nzR z1*g;lrG+v|I2Phd8KsT+29V|xL7E2an|eC>NM{&9KFDgXKW*(}P{%Gb zUVKC*3|TOo0Mv&gZN_MrpXXx)mZww%bauOx8IM)=FeU|BqqszvT=EZT@o7_08VT$+ zBEc~s!jFj$F`XxPJ@7aELifLP6{zi~oe3B6izuDpZp^3qEYl1ck*SPyte|)DMUF^e zp2gHOec+*c%_C)cW-l%1G0qV)K4B_T!FPCh5c9xq_H%y>9VQ_K(t&OJr5FY&`XTye zqJ}=5DA6b(KJ8PKk&huRb3pqr0lTuT2O$fJ71eE(m6yQRt@5=RYz2d+d0dt9nApvR ze%2yym$uaavk&_>NM_J04hEPXrL=FJ0-gXXM+DHBc2MlM>;|ulClwH}ek)p@P{{5| zHZAcvlQy$O2JK9@Q{v#;nf^DxrGubY134j_QJ6iYEtf|ur-08>~O4(#)aRv3n*?12ih zva+fyEkK=mMcWBzms>eb-IbP(6(@C-d^AF*%VdEyj~oVA!KSO_d_t%{x=qm^S`w?VUYw!@d`8XW<%d zfR_v|5NRSHeo}9HhKy+w*d;M)<$c-&E5J2lwAeXHs5r(ibxq{wDQzK$hJhpP2xlPU z;l`3BXw3X2r60a9g1%6wo{goaVP-jq1XR_Da_R&DA?a@y?s~hN53W0+S=TAO^)7iv zL`+;j8N+R4U^GL#clsZ47%3Kij^VJ0QV}MBotnwoOGW-4kY;>tX zHzf}2D0_Z%MVxSBeI#zTprV;|0qyhxQAc3gr~HUnl?p!v|K|tFh%(wm20IV+=z2%Z z_Z2q9bQfi*yYyl?%jb{m{@3t>bEx%T=}fsBGGWpIzNXFV89Yjwmyi8oFrnh9K`M=V z4cjY#4w($kWC>CUB~CgZX;@9tL2-lIUoHS((tag2K2hXcphUM``BN20D_Eeoo-7s# zKvT{YMfK_F4h#$o=u7~SK6h$>K&9QZ8?)mw0ZN{mH^ia?MlC@ho$E~=XRqpb3Bvmm z8DJoPq^IH(B3=j}X*$rAG&4L=-n7CuY|sPY6>uMTsO3CQr6pf&s{sP%6|14ZlLR*q zt#NK0aDH#n*;8nzqT|}lb{4QQ36CyJpa1>6{O1eG6GrIBaQ$-HnD=AW<*fa4Q@DRo zl^^`n-3bMPJQC92EBM94j7T6sao%{;$Kd@H{&k@T;;s0UE1AGn!P@pUEYqrC3*!%PT!5_ z{+IRezf|%6mgyi$C*mfVSjQNMXrfrtCf%Z*A`hP!`Zj0VeR;shwlsVQtVnSbs_jLPqANYK}PX()LR z2+H;Nn*BH#W~H{f`f`&kFS_oO;?A@(e6oMM_2ccwf3{rzdO;lrzDInw=_#MnJ;LU71oAu-C0RoAHA8@rFb?1vM$<7BdkP>ACm+n+(+jVvT8Z9* zrSY<(CRY|KdmK0D;wY*jzt%XEdIr0omG& z#}pFPxOB|Mmrbna7`rE$iLo1Oe9(R=QMeD*_YoIYvL(`TFw1l<_HC-By+I%Bss4vb z@w+Xvk_3)oHg4JO_tJrdG+$k`NMVeG+(}$=;{Xq<5=LaDO(~FL{xBw{NYodEf#iqm zf2UCgUUdEF0l~CtDR;1ClJ&f3?rWv+t%PXAa_iZ3-5Z6cx~sN$d);e>1n+|3sh<%% za8Ue>rcBRH*VWr-L3+4OP-Xs=QY_UxBq+DP@AfnlLR_2RTk$oXfr^*qA(6T3=9T4AoUshZ*ttED;M!9HW@oP!q}`zy`t$S z`wI&!mxkmuA(SPWdTrwLp;GgK{4F=9>EW`VzE9s1t*gZ$ecA)3#4RlO1)hr99+vF} zK-?Oehn2LLV9>;MWMj+_P6)2+tY-rNcMxcHqjx1K$IyWiTw|2)}&0pLvA zDw_PZWW3Y^D8yL}QqMBtj`CKhcCqXa@E+ zMod_gSfgMWew^)fn(7@qL2&d7j0;@e0KIGU432q5eGq8Ej^^3|7_KW|-;(fv{pCtb z1&fEFP6Zm?mV}q&_+GsHQz?*V<1|tXn18(Zo%W_w|HWzi=h^kw65|O4^Pc26m6Gs> z8wM4^*EWU(SF!h6`ZlgSGixW)y;y1qH!Z(z6e7Tex2UYkc%BuawRkT8b0AjY?dyYHR@rgb&gj`k87sK2A29 z^y1H2yoaRpI)|aO@V*!yd&OWIXwO}Ho()-5UCxtWN!N8{tAm+pvNAW(7hKJ-h4U)^yG-E&HknAb| zOj;>8hO+idfmoc)RXO)MkwHTmxP3VqX94mA%$3m7BTQ)v^V9%>E_~363Ar46fkg)^ za)npGRG$D(V+^Xa%?yZ95QI0({v7lQ1EQ>+j!dgY(R`Van@e(0{r%}Up^|uH7r16US&=&nx-D#Qi8rV(iK!T1ADdG{@TPAU18`pf`~-Rc z(}bQ&rYF#x=p`vAhAj#*-JMornEG?#$aC}i?*MI2zLm95{Ms}On%{8?_n zv9>E10!_eQvHbt4X^I(`Bgln3*?KjR;9}^3_~sb-OUl1tV@a8Pl&&QJ(OHy3E`NY^ z6MRIm;o3#>|IqanP*H|!w}3+n3=IN94@ifU3=NVJDlkfyqBKZJ_s~cqDWcLPF?2|S zAO?*zC|%Ovea933Isd(PtywN`xsWg3C-&aYvkzV=_!IE=wSo1I4iVmm@F#n@NvlVr@^Z$7RypWgozC`7yY z+xL1F9khRoNEA+^io;6D%>x5V?!$ctL_PuD_XmA7e-(4r;?ihDBk_Vl1lB4-VZ zxJT*gy7bAam-glA$tQyEs_nCzu(wGfhgLh)tQSw6s0=KZ7)s=`}f zp7UBF@dlNO`&I>Ja-GXcn^MudQ?wGiYEm+{N>5p@SU9*84cQ9d+d&xb))$(xz;V^0 zEIJIsmg@i+4b8~G0ERmM=h2tP2mtn`{!<*lZ2%E!m$IJ_GO5@E)uthKAuyV245u>% zB*3xHNaz>mgR`6Vqx3LhWa|O2b{%#9)*Sgm?sb+rMveje5&u)mkj#fEw8u^#+x>eI z3n*$R+vit7zT*Qec3b%l?!sTu4%i_+DBR1)j{(Nwh;o-RJ+bdyr6wNu1FZEpI5|}W(DOLnU?($Xg6sy*7MLDS1H^lV ztY2d**c5F&ny-S97d`dY#4L~lv{E1FOfT79djDYwoxzY=7&k&dv%x>!MvUQwJ~>~A zU|t5%;(K+A-S0e7-?@6H+@Ty9Be+&N3xEMW@1&Bsx0eF|>}lU^ltEuK2x(lH9phkO z`2x#>N*^F!5&?uiC}=knaA*hSs;nX|z*7khTcZ^)rgLFKnpThjA~;M1Y~a7uGPiq$ zL>jVe{L=~os40rT^G*3CI^qGytbaXoS@L!k<}Mnrt7_^NvI#DPspT63hQK~_jdURj z(j*okR)@J(8KqgUTP+QDmP)B}qwui$W+P(!o(1Vyb)_EV;Ijj_ylqZ51d5Q}|NnzMN%wqUq z*>n4+V6*PjE_fwMT0m2s41hfK2vVj3AQk@t)*gej;`5w&jI~o1b?3(|?|o|$?}*F3 zfNy%L{UDqrcKD-YZ3QXntHu{LGK4P8DQ|GUoWI~H9EyNn~tXJ^s$0XcALRY0v){91a%#b&CQF{ zh{ubIi_3ua(BEN0-Inn=deDJK_}iShIUAH8p#`9AA2f1m;Utqi$%_((HVHH=Q~drJ zq!udX$1)}V`PW!kX6jQ9exQ472>{C}Sns~NrT4gUd~=_auYCib?F$B{0?k&sC2{yGZ=FoUYLI}Pj1*$X%G zf@;{{%>_UX+2=M0-+$r*=tbTx26aFjmHtOt8%2#xAbA83@#GorwQL?!yT(0im>WtMBI}+`TM?v{PU*Jf}5g}gw?r~&um=*u4?V}En)a#yB)(# zM#@Z_$c%Babp-;OjrW$pFY8Hm?Vrq*+nPFYcJLU#M|A}{PZH?eb*k-@+W_I{594Q? zf8`u-9z7ttpN-0mr5b<(b^%bvPRVXuqzRgt;wFM1>tu>2$H@+r0oC#fsyoAJbe!22G4|d%M}#8#Fo!NH(EOjwS=aUped3dP1k-9Irc zdn9%W>X1V*U>A4OsIC;aDQllDAKQP=5_#fJ!Cel-h0B*!XwzwXTCgcNXs7jDyXCcT z2mb5m1x}LqZH(7|+V9v>vJJ>sR9TdI_X$@C zYn&S~AAX%a|F7-o-1*U#kFGK*xC&KEhdGlp|2~l5i%#wK$4%@*49(i0VJk2dg336y zU3y?7MiVjtp9w0SXaS>*?;r6Lp`mF&;7086PUgAKUjqlv=*4lL0OrSv=k-zu^OUQuih{`^P+WJY&l=w7pi zvR=2C?aNPS_LJ8_1kYc~bZ~`+=7DO_41Z=1TuX9YNF;sG`05 ztRn<3G>1|Lp{G=!P!q&00_4?*>A=Q>`J#}}h~1{`MokRkvw>|PeiISSg7+G@7ntg) z5q!5GoZd618cyG{Wv5sNv!K8MU;=>UBTtd&c3MPAaM=3b%>WDy&`1A|xu8kvk2Bzy zEWpkE?iqccxD+WZ@d9wV-Gr(Aad>5CVL zANXEg%b4*hP!=SVykn2al-ZoeW+u~k%m4vPj2`{E0){I67)>0g-^UHXAeERhER@G5 zZzKg9fdYUm#uI4&sxKEqOD#j&Wv0(Of8L*J7gPLc8BEs9D0{K1)bR)b^6Jn7NN4+V zu!yBbCn}+RS40sd&QYo#sjf&je?%mY*(2FzMx%E9Qk6g^h_Kgcm+A6>k>{68{NF%h z4-$&ubQP&}uB@N+mjd;5{`ksnTOH8zoy zrcglbM7SEh+K(s96(;D_1=d?H4sBa#%z#wOM3a~jLkql{cK&3US%3ZFDhB{*EpZNQ zDkj{09;%8aU?^#UZ}dOFLlhL}=du$pw`nm~^aIsdz~pew_1aVDt9hGH{BbayISAG# z^Nk=5Vq~4!IT&EiSYKp4|Dt}c-qD1l0nY*trsUQ4695yPgjIHEWtZ{&BCXmFgx_nBK+EGr-A3C!%G#K$f`hS zgSPCcwH-hpebv}xN5PddbFn~+J?8w5`{YYjo`sp>N6Oa{{yZz(u&ULev7y2yx4ng< ze%#dD+Znhav548PM7LAq>=f372L33rKkn;WPX;=7`q7_yyKI6OC_)^MKDPQYz0*<2 zmtP8$W5X_HtEp2+F3z3F^xYnjh;w;(7r6(T7Np!Z(d+$S`|Y`8hJQD;VOJe3Ep2a@ zs>F=5l0CzDU#|=wp{D16xpXHv4#jb$=d8Qoo7h;MVJ95NqMnA;$b+$^WP`)f4*yoo zth6krtR#-b6xZ#l*e6nPYY&OZQ7R5u$FL`;HU$2Q)(iM|f->UWWMz5C|9tYa2}1QF z0Ic(Zrxw5khUf0XQfmd00tm>6I|Da6f~o)*%dI{@VJ8#Sk7pQqq1D2^XBFwlo^p(J zZvY3~ySNfhGY|_woYb(=r8Q9gO4sq0YDDLbBduZ|Kx0L`LvZzYN<(A3)hsMos3G|e zpkV{w8`g_U-UZu*Xk~ZY=A!P(cXinK`d)_zJ&y9mBW0vJ#`SckhuM>nt!K!3|Csfd z=3gb*i@nDr?J<-*3H;a}q(ATb*1uOjfamnK zJibv@{6zgdk%~N|m;=h!dtX7Qe`bV}Y+hCnCB7&7t9npC6W4{)t*uKe+YoAcPk}Eo z0+%@KB;$gkEr6YQx-&R4oeLcteg4tfgNILcVCEVI`z>PmY#pal7gpWL_U0S#L?4(H z#UFoEREfn+Z9LGuY1 z?oP$=TY>HA5=KTFTUUr=1rS?)cU|M2BWqR7Qf;7^D}i#=&Ik-I%!^kUOIcu>@({e+ zLy!^^EP3*yW20B9KmA#s$x3p?&b*=Ec`t>H1?S(NPcc~aDb2{2-6f5L$5zPvll97V znBha&sg41aixex1(BH({1i>gP5cqi4(r&bUJrVD{%b2R5z!6y@vuD-(c@N6(itcJ);PXUbroU_WwYEWl-}zSkD4Cdn*q}g@eT>{P??M*&_HrSm**LLa zbEQM`UQd;&!LY^K(BD*Btd11@gFP*bi2%e&2pqb!dz0bKtP)vg3ek zq4=CtrTxWri|?ysACZ}Qi?<=TiqI$d-PmJu$Px5g=y3%y+{yw5FI)1vVbhSga4;+_ z_=ECAGi;l%3a9r~xIR51s*GtKKLD z&LHe8H0&HW+fed=m~C#=I_!*U2o zUHm!0(l1_h3)16&Rytu?IaOO*$$B3O$!k7+Rml492h8@lhLB^N`ulQEQ(dqAox#E7 z3c0*LcPXtq-j4R z_w&h`5$#%2$N;77b?iQ)ybY_XG{<%)dWTW0My;;ZPIG8wWTS)q&lbs-6v6 zx*t#k<1MiJU#5F-SS>Z#u&U??3=On&MXO>fL;4gKC6!?i7Mk^hlc9p&Ku>7f!6O}| zO3gWQ{VRNi^hw2<&Q)C{+1@(XFcw3c2la4%IMm5ToA0cun)T)ysU*vN8p;hmsO5gP z$c2A8t}5_h^MBsoy+}B4bUtkCUxUWv`Sq{o;o+1$`;YL~K&f`7 zRiSQX=VNT+&S7YCc1<|oLs?Aq!RZqi55lS1ui?k}HD{aC=CX!;j*Wf+UT@k?`nQ@yIIzT;SoI_R=O+ATKKT1}F(!=X(6`=M5S>=6Sc#GEmPx@X`0#&S+W%a2UN^Zsf^{J(mUep@ z^)Q&B1|;EBN;_x2a2AT-5e9*8s1I&H5&d}am9>!rMbb;J}Q#s8kyj;@9VA;>*BjnL|kqRy3ls;_`A!ne8! zOX4Q5nD32|VAoA!`%yAZ@;;To14m3@B7DaQq}mgvhWTLP{O^o&Nm2Le?2KjCC~hRB zKKG?{w&?RrI5_xrUnka*vMT&Sx=ioYU3uZaVo3(q(?=D9lzsZ2>}i?YA4UvNV3R}t z`;&e*@3j?HWD5Z*5c7GUjHMSAc@RQ+yKsOmnE(NReH*Iib)VsT>~I{m&j^A(oWevG z4z(#T%ZIFZXJuS^nQvkSf94n*)$j{wYM>zlHzWW4q<{Rp|MllB1orARmj=MX)ck}w z0uVknwy!R{&vEjGU8w~SNttGqBum$YGfRBu#<}}45Ryl9hh#1_U5X9s`xEC6%V90W z(Y_8kLIaq=kTSqp)cts|F$ri57TGbrr4tbqNN|fCha!-NZV14qh$T_y&v^!fR{cM2DfmlyVwTF`+Z$aIa{8aBAHQLdOO+qeoTqa3%KEPWcsU7F$+Uj%KwdHpz&IPs6m7#>sNyG(aUDVb6l3`bSS?#Z zo@OGvUBi}w+C`n`+ikC;fVY|v5Wv5g{|feyNdu}(^miL5O+5gQGYv*j=?6PEDPP-m zh7#&QUx1#`t(z9!GcVr=|Lb#VFn>%V>U@K33ssRO-GV<)jhFgXq-8!z5O!a%F>2T3F;GoPjbkpC26g-LNF;(}AUeh*|o(Lkb_H;T(b zt6>^&=`MYBvh*fBXyp^&EtWRG5Pl^fao=^o^GKzZp+?V&-sVpP|0biX*Luu03^kAK z3QHGIPk0Q-z?OPB4R@R%(|MH6PyyD?9)ao$j3vO)pDJyU}sWkC3#H8DRQgB^8Kmdav-{ zMdI{DOZ{MgbTe6l|6>`aeWclneuhvwU-ZS{-03;`NxY2+nI$RX?A>304tBUa`yCy9 zgYHS)c`MLh-R$YI@=0{6HoI!tSi2t3BGcN=?hQMunp_!u{Kw(Y|E0t)Z82}HBd4d) z==8#*f3zp=8s>oe3h%|`{9AworQQvELfpJBG}7xvI8x%pq3Goeyqq@yOfZN@+r99a zbsx70To5MMU<|n8d`vddmi%#sA%Ku@4**YHPk06%%Z}vIOJQVMvC%#1n@@&EWUjkh zkM0IGQ!`$Sron%)jJ$V{8?!hlJobDJu39iAWYe@+t$D|HkFW>i$!{2?K9SjGZssko zU2_bbB)S-%rZVjuf|+r;LykwB)lFkG&I7ZtI1dxXo8*Q%Y2 zS|AVe+uex6l6Y=cT6L$#$;smrv)`fKozL@+$+6Sz@W!b^$wKGf#NB=tw{&xyYnm2m z9)v877Q=u%tBnJD$oq}XA@mKzx1VIY8?3G-*~LUdjdcia|FVQA){obA67hCbqs84q z27XUf1Yp1s^kBg}rqR7mD^cc8vV3-@sae*s*hFXo%vy-JyIg9%v+sy_LEfik9N!X) zIuQu9KImEeaz1jmh0E9(Ow>fu^Jp42d5wU08U7g`!zkj3BD0%i#c^qg?>B~HP zJ9&J*L@@^gbz;K|k)!|f#4&`CW5e4PAih5WX5}{=4#i#gI_6Ja?m6gZL(oNTyrISW z&p>@6KMQTwh^Iy%?Eeq&oq@;^nUbcM>~cWLmJC6g0D-#VN0dKljazNtQ5RhBe+ zlZL7mBpJ;L?!-6v?jqpOE$eOy8g9mV(CkcO<1;2SNc{j9&LRLX|NMY?FTWh}2o1E> zcvMlDZDlJFAR};N89XlP%_It-824 z)2T#*{J+~N+nW%t)%N{}C+VE2NH*D!-R9mIX4s2#rwu?;cXfzn2sSU%zoI2R-Ngx2 z3+YpGHFQB(?O!y``JZ>Moq;!T3>njRK;>N+y~eqE4e6_9GDNnB8(J;HNqe_0*+owd zbOh_Em{)H5EUc}RK>MBV^?j3+iA3X3T{?{v$Ex#lXB0#7Ox_}3!2jb4r zn)&Jt<1$dL&8Q`!JH!zc1gN1i$j1$h9BR2!6C`Pb zA$H_^sWUN51b=|FSu-CSZC z1lD}`x9R?YO6z^$E*0*`?(38NOHEbTc9M!QTxfd$mZf-nKmSs6|GlL6Vb~$id!$N} zMl(YgVQ^?AQg=ZB*8nuC=f`b-va9f1>mkV`ax?@d=!5gcwvo+ad{|F5E238fK+G0YC@+^XUz~oD>n>nyTud)xlE!i36pCBo*z(Jg+ zp+mg|p#Fas;^2!yICf4N&_mOe1t61q4>(Olkq-<<-SDGZyjK+wmZLWWEid=8LZ&5k zCVLMMJqJ`HI~=R>-U1lq-I6HJh#7*NRn6OVhdSW$x1pF%fjuTo>gLFs0$_~}ur}X^ zmbtgTBK?LmtjxrQpIMO^d{UV-WIk7^Wi`MRz1a;hP$4AdG^l*iv{y8k6rX~vl@rG( zC8Ax7>AsgGu*>z(MaoEZIJ~*apT+wk1&qf{fhHuiD!H#U)j3OA|_7q z{Nc^T9^>VAvEsG%vnmqGg^UfGNQ^ODvbirX!xQ~+)z=HXyLWT}WTeli?zz5Q&c7Bg z;$jLDxRw;TReIxN3)3Qvi@zxD@<_Y^b6O7Tg;A|urR&oK*PSXk}Tv_PyEtM zb?nh5sQO(v8CNKm;=8b#bJ$nIynodOsN@SfUN#F}y&kr_0Z zKx6Iir$dbyGd3;1pMj>4$*eT%$P3SyDg)VNW2{t4WC}!DZ_e|hC@3woo{f1LA!|pL z(?Q~qzoMLf5q47wzJ!iH4DH4(Z&rgD;@go($gSP(jLn%7IwRqbNFfkKcP&bmvZ1%p z8B$5hIn_>&l7t7He}zR0PzW><`1kuO6VICqLGo5~Bf|0-$8>)8Zz`5X|$*cKMQI zJFci4-ClwlDuM!LZQEq@d;a6Abk~}%z3l;-Ma;rNf6_`k?ND#hGZlM4Pt1H|A@h8<1HTua6(T)DTAf{2brSSKG9XLm z$_)FP;(wiED43I)AE89f2=(@K%3W2{k+{6ie`1<@z|MEgao&sd#$G3G{rk-|jhqOt zI13j>n**($PAN<5y_v2mQ1{#T!ZZY)AN!v6=xl*ZV0#1eY%Lc>=yj*LQhaTM0x}BO z8-B6xkcT4(q;h2p6u!!vog17H?Vi!3Kbuozx(VKgyX`7;rALVdHzC2xQ(=RAU%)&) zIlsEE3Upk`0IPx7LXfrt;^~64bmo`u>yQwZ2uCkjF6P>Cl_WTSNYz5xJ8yNpBL8}T z(fMnaTE+PTy?OT$^Z?%?OvG8V_ZbI#yTPKn5wek2V58;{@HDCS>~%R{Q2CBB8z&+@ zhn(Qho&_6;4#3Cg+2kzAaXQ~+IzIZWEyBU?KdN*>^Wc2DGm9hSq9 z%~?PfuVjA)moZh@HK^FG?7y)ec?ZJX7?&^VTq@&Bt?7wtH*?&9nmOfRrRr(_lR*;k z36@nLTatgAleo0^EdG=1TP&=uOm$_22g`0At%ZV<;xxwln?G?V+&@5vWe~fYcci{; z{XU&5&WW`flQStKnrXS_Qa&I-MmB1uM$oeYVG=EbgJV%}Hs|uXpU8 z=Bs@X(>;(g`L09`upRUr&bAUz4m)iUByFPRw4PxU#d}i`O}w&ACH(OAs@pESq=NW^ zH1Lhc`%4B1$ZNv%@g-8q9FB|+RYJwc`&Ebr3NB1MCOIG3?B>vJbzEfaJ9~x;Dbjbf zy%s3{sI|^cItup;ORaga=U_^^h}Vm0w_FzIrbmvzS~tPe_`Fa%Nj{lMqSwqwCa@QX z|Ht)n%fQW1Rjk(zhY_yi3AtXcXN2*Ct$B4S8- zmwqG%7R8X;Pk4f!eA?7!i#om|ig++D^{8p?Rz>i$+W&e}mYK10nCd4iqtSVZ-JXa! zA^d((5*M3UlH2@iry*f4IdYaB%Aa;o>U4Fd@#QZ)OhOiIONIE#zVGiG(iCXmc=2c6 zd-#|YV&SfAY?8C&P0Ce8Fr3@w>ryB;zQ(Lls|HbKnIohMCDtewX2>YbQD+oLE~R_? zB|31SaE>E4RI^cpZJ2aAgUYhD^4o3?qxyG#5HOm)yMG#iaM%eW23704$u0dq9V z{hkkOUna-DTFX|vXJhV}+|sIJHEP%JAO4ZH8NYLIJ866`DsGT^hkG*KUes$OcV>I1 zg)~kl3Nq>2;yabY*IR!s?l?@|iegUA=BBj3Oa(_Z2~Bz$diL)>t%%k{argT*oc(GG zV2pr!FWYUJ+Ny~!v@~ZQIEYsXq&v() zVaHxU<2W?f)jwl#le!c+_v}LpKjv#FGj%c1tYD^SoUiSSSRuw)h{!dw)?~U^jjOnF zb75ZdL1^dreV!LN;{*WEN6FhjZkS*V^e!xP4yiC2YL&8ax#;z9Bom95`vG6CXgNnW zB!BE+=BB!Y!L`a)ubA<3<=JI%c=5-;1P&ea2Uo>O5KB2e?{U)hW}hZwDCj^$C}~5* z0ovf^q9T)u&8T#We!hQn54JK-8s%a6Y(SamG>P&lW@L?O)>*YH%6=}wE*r2ZoL5*4 zU~r8}IS(T!6ctwVWpv@K0W@4U_2rdP2BVcf9pUPhLo?`g*hJwEF7c`GYEr1@kR&I? z7eEQi3X2fK7Do6|%$^T<7}RnFs6A|1*fL+tMnEbeNce08OJpwar+Sl8_JF#;Y3QR% z>7ysPA3A2z@bBcFgsK2dlBHCPR6p0pXk93yxcder3GHJgR>mcivly1gvlO zR822wKwTYl#?|h&48S#57;9+eD|oJ)wF`5qI+*5Aep}UN_o#Ira1b?oII-I}0=dp^ zK|SA+B3r@e*6mjo)lr#|?o|H9lXq>C5_3erWoDslZD^4CpMsj znq3>$ZGN)PTrY*%BMjX~ReF2@jPc46Ald|XuBA`!|Zu$EXJBF<$BDcJ?`sUb=FL0Mx? z)&v%7;}^f)j`An|L7vIRDdlx=CjNnbg)JI*DbBD@F#Fxnl>(X6N^18b6S=!CsUX~9 za`@DB?BSDc;eBN-lG0J=C*~VBKqO>I;u5Q2I`Todp+OEu%DbWUyUpUO+~@U%X*MN%|6NvLp6Uh5yeXq| z#3vln(&&HSadm(e;_MS9A6Ay35!5=Xdd0xYu&9=hp8fldjTU#QP!A|DwpXK;GPF2sr3fve zPRN(L*KrkkxbVo;fDW3;tXVZs9@Wkkn)0j5kcD-#@|8IN2xA=DMY>e5;-o;+YI%xW zs8ozQGLKcvb3F3?e0yL~GBg|zvbXS+u!Y}0fu5T85GJ7luF3m2BsVM3=b<&PA|SjOtCpNDdE;`0t)dhy-&a=Gr# zLd9v~Yw=6bH24!cV(&+M3z$J(^5HYb0Ix{Wt2o5nquf*b9;2vQKCxH^$}DV!F1*bTqR3mjr8Hj1=CJ-;|Y& zVOqPP<$bC_Mtd#E@>TDI^B~9C&YnVZEc%LF4}WW|BA3>;=PfmxzN@Q`qX5Tk1IR1U zpyHV_{n|FE4|;RTESZ6S!cSWitRAMpr}fet`nJtXvax@E|Izp?cL4MZ={`7(vSV?t z%j0X8-o3#9!)aI}C_vA>INZFK>7$qHV;!e9k{K(VT5JM&bq$+PK2lGMh?RgzK-auh zq4O+k-X87X_OdU&;~DO;k#RwWc^i-sSd}usH>>Cr?ODmma5Qt}u1-D*tvnaz8t?Ba zknJRPU%ZM{_oY=$q~wJWOb@ackXw@X&ew52)q4(@2<1Tv3I}$N1TNab6RkONZ7>sG z{*#n6n&C{v8`(N95~ELm&Sf9117@B*jZg$?VM$^Gt^Tq06+ZEC9TzUuk+(ewt#M^S z=lASmGGZpaY9KiM)i=jqR~?{59^1E}NP_TMcoj!YMa{{qiPJC#$b7wKH*FNxVT%85 zv34gL5v{NK2Fs$9w~=J8Ejx`I+Ww;-(^71u!6*sNWw@A?4~8wJU9pl;ZdX*kwRl6= zg9B;l(Cy{rze5?|&aRjJ@&W80JVPrk;*X0^Xf<)ZR~ zZ%0NhZO=h(IPctPW;FIC?+s(dUi{1m;_(sKD5GlVyj;OuMOKAsBu_&ng|H;XRD6u@ z1biQmg9M;i1XFK8B0olTaZyOpcv@@n*FKmDPgj;svit&QzD3zC0tlJq^-;Xwac{Od z5*j#4`P*HF88IeHGCvPPHDtRvVVEQ~S!kpx%$tLVl0dS*pEXq6iFlE0dj^a-Gnd#{ zhFv0!7ZT$pB$0LnXLh;0sJs-Qpyml=SX<&(3R({0VTgINS6CHu6Dw0uR_|-t& znR?voW5N!?dOrc0#E2957!rpLcV?>?d9@i>I!jJRO}=t~6dtYb)uQC0#Hcu51vLAj znN99I@$@Fc%OU=kuHAOUgV8bn)TG#6LC^t~RSI8l(&v=?lO862N!TkXOvB|xN`YHB z{+sQcF1|2+0GP`|jsq&&xO_*9$z@>4l$?08z-Q`J>YEE{N-xlZE(O0c_HBsg^msS1 zalM!foUQdUB=efSNovuCmC2U}57~4jh#ivLv>%~MK4?Ic%^eBVur#2{`5G5Dj5AnM zIjm%aB1e>@T#TU4F_1Yo$8M#zFMaON8r~nEV2G>ofZj!shorX`v{nRi%I!Hsqj%=jQI$aqx2H%{LzyHSb``bQyLhIePCft< z+=j2xHG3It>qhI?&wNLjbAZzz;kf0t8O1snPV(FWiruJ5-VZ~?+TMrwL8Z6lz9IjB zVJptnY#^qHwTkd!B6tA?6zpQQmOCGo*L^VzZ3kFY_w6L!xRS3DEC0l^gY0oG>MF59 z=bZcp5q{vlj=G9BOmxv|oS1o<4OrsR%r95d49LE#9=Vbp-m%!1kU39ZzSMUg)H!0P zvRm$ZxmQm$-u|7RJ!51?pZl7f)wb}Z=j_2~JmBvTj~=ZpA}24k-KUpr9!Lb(Z1w5i zsrF^wS+0xv!8ND>OPl>s+>>~@yK*(eKbU00e&N+TXuPGh4yekR8@da-|0_@B%!^znCWm7=ivf?Kdh|f@guKj)LlmzL7?3)fxB{ag!jDS4@=d@i z?5kXch??61v5G^Vp!H0yE3PE}bEQol^jAadc|l3&*fQ^yAN$KStp#MEaxOQZC6) zGHT{dZsRb-RNmIuKFGXtrX%gwTzS;+>s{vJ6DQ`SaC7`OR`D|8+SVojRn+^e`RG6w z?hcJ7kg%)yx?yp>O4pUvXW8m{y00^@@PK7=#;wIH`3Kf9Qo|;fdN2p{>1w9cq<`GK zaN^{Pm2^NU2=6O-St5Q?ZdoqIB4m#UTr%ye)obz<3$1;h?kq+NmY0Cm>+k1V6js~d zg@K!3hZApG?ZuVV??ERy0gcnyCC}^Qbx1}qUbw$UxBY%1JVo#r=OSmHA&W|}k_B!G zYqLY!uKWm@8v|$?=MUOF293qma36O6<5Z+&q-~IvbRjKqytTbTsb^(q&uuR-A3scR zZKikOD6N|w`F-C=-Gnb|UV^5C&GOb2pn+V-p=}k(dtfnv^~_{7R(PZD>+V9mH;XP^ z3mc)9h%I?o`qhOqTFj+RU+Qud7T)^# zz1SF%{U}Ua7&h|;+Lm~X6e3^R14@w?zxmdf{fUwM%oKx1MaHMgr+|%G^kXy_2UE83 z*XgVzM`7Fg3tcV3#}bFRXTX&FUiv+}DzOHqH(GHkS$T{n_ejf84LWag?T^FcWFP)q zBXeL$WnVdMp>bhf=tr8z_W04dyYgw7YiGoe9Uo?q&CF5olFRP0)Nweh%q)tg+VI`l z?^fJ>tyMQF(I#hQUv=`Hq;jqK@LahIe$)}7VvO+7g}Jl8x`)@>&grdVRwDSGv^W84 zY5oVrI%N_@BfpLra_}>?dQkVzXZS!J3tO!S5#*edGYN&=S@O9Gk7#?~)fW@s{W5Xq zl4bve|47nIDxYXZd~^T4_sHPjd<(N?%a7sBI&bOb*alf2pXO$3>7+}oIc5%ZDAab*- z(Ht6@`2ZSa$^6kW`T;VKd>W<3hP|(1-7dgAaFx;DW=J1s@?|YQ4AKu#xT1x`5lgCoSV0`1$FzMZ>16CKjJ}I z@680_Ze5QqV{2mB?ry8ONucb)>>d$@bYZxBGotW3W(9v#gc|a`FIwDfgqnu6yNa0R zPt~UmlY2u9G*7`VsbGl64bEIc%C6}_vF|H9AIehQwP9Wpuo3nc z`&pXhspY@+#8##$4n8y@{~}(m7_dw4t%{!#d6qt_GH<^vuJv5C#I!W5O2ixJ_xQCu zR7;5Yyqu3|x&m!q2X_sBlE^Jl~ojW&ZgwRvn!PhjQd>^X%SPWRl=#e+^EpB?S zYOSgg=L1Yi^idr}?=D78pGu89Fb~t>`zX`^v!YpeL_$0Tl0R<`4+mz?=3{hScVl{8 z(_1m{;|1@F({Lhv%(J!VzlFIIx?4;}>tDBVXjn-dO$_mOVf1Uye`RBLY&(fEywdR*o#i)a9it*Yg{Y9OVQ;5YT`c$#1RK^QEHF;mzKLCIP&#ksM{c&$ zNOU3J2yC@n2!DHt8TW|qV8^0S$#OzM^uz@s z)zQk!Gi**a!b)pLP1A|-PPW7>n3w1qc$<1teLv~X0m>fJ*1Zg@5&lP0vQD0mqV%=S z_4CQoi|3@vviQZGxTT1+3TLwXJ2j||L{>EWUECm6E9}tkchiUBa4lqlzF!*?wCI$3 z^w4~Z96q;J$<%AMm47qe< zo75ZBhox|rB&3Q{1aUGSB1$8#Ko_;sAE5%>S`nqWjRen^(ijTj=Yw+0*0Niv zD+Y?+;&G`idvUl?4}r)(m_OA7x$b46;MI@B#@(yR!i6$c5fE8w)Xe9PIJ$?{4q?Pt z5!6>;H*_-5qV_u2FAqW*caG}e%}mOzRbMMJsMq4)G?nrj+mw&8`#@NH9x_40-#6d4 z0rq>vjHzd(#oQcIQb>*|%~p4&P0M!zIz}HvTS!SO($G1BmcO7d(`q4RWfZ89VW$=* zH6XsL4vL)RIDFTqozqVd&XiX&!uoZVens-Hh{1_LF)@%=IH&FeYAUJtFRBSwX7)S13qhj7hJ)S(kbe6eMrTL7cxb^%K4WSV#YGBB72p_VD7|r_b)A;NK*muxvql%b zO**EG#Iwi_aVMz?Yi6v+TbL-IE=&7G!m@qcp`Gno3FYDrxf1Fm26?6{=dkA$!bsZ{ zO;;b`XCWe+n#(&DS3f(wc$qJ|*igBiiDZYpCo3TwfX>^mhqd^snY85i9=g={^OYvHmIB%rB#Co=B$k5g9l)VThe#y@WVI~^d`{>rTM z`|pVZhYo!9!?BW9(HouJw2BE_%49Ro7S^Ntk3fRsClc!5VNc@n`I5_ol11|?b+y|( ztZc6YZ-cb`Fh9i z8;it*jy0U)6AL0FRRk6xmjsgArljP=dR$%CqE;Z9`F+EK7QT~rh*DMdjDsGEjCyuu zfF=F>jj10=vbd%q&n27~{PtZ!<<{7?UdtL+RSBlqS@)0|S~y!@IM0UL$#uOoFQe>k zG4H3IMZ0Xz6Ojh(VbsR#agv=mK+!6>YX32e`ILN#djo7IL7ksCVvL^jIEbp^EFZ?W zRZ>OI7NMNwbo1m;;e+E?5YPhdKaWy1bUg1o8 z2nPLCC0sFV2T#{!WO-vbSYr5-l!Vst8w2G3F!$C`QFd?GC?F^}($bPjm!Nc)h=epq zcMsj&AtllvA%dXf5JL$_D{(`MxZXO7Di=$jXwKNT5fS^}|86<$G83R`p$Ni~L}t7j4)&736Xly|I&z^x}sM zW};LWIO+j+#p(CyTUr)%4-dX^%ztOxUU}&@^vL5ptoOlm{CNcpru4Bj;Qoqs!`R4B z$Ahbain?71pTe0e)sM^$!x{C3=h%GqqXU+cVRaAR;r(Q&=^&7o_Ar^+ZgYGZfoPy% zl1{a)=IW4tZoXYTqVx{0g^5ba4-l)ltb+!LFLkvLm_btK6Q-_hwz`RY_<05M{a@5woypaEM4ndea*wNPPd(C8l>weW|uKqIf+bG^MjX_ z*@U#G@`LGB?{Ic~BJ56#7ricD$ZzoqvP)x#_m0kE<`UyIeS=QBKE?Lc_X)aaHUhHn z6JeNN49}{%b^;aR8`iPgt4z@P8M`WvXJ?Y`h?%GCO8<#}{!#o=a4yko>%y0~y>NU} zibc9k(FkoRSXSL0gihIxkmEpdGuQLhD%!bcyshB0{AHckkAu8=eKGQ4G3;eDrQhBb zKybp!T;6N_sRPm|e-!K2oK@}qy?uXwUC%-pC#P)=3zKt(5N?ni8)&x~F%zJNSm_59 z+$6@QEsMoKrM+I?O%Un_o?9V!8&K;ptk$Eg_u<$V1N6j0W5jMoTS2V6YpLyHk95C5%WP^c>7)6~Jo!6aY(oW=H)t5nqnE?OWQ)XwT~@RbV6c*wXaticPW=L& z*#iQC8k#`q>)P{!d^qY=Jaor2svS^TDy6sVc8r|M+;Q4%qKoU;y&P;Q71eEHv?(HYq^JcLIvAd(2!Cs(i*wp{A+~+^g=l6_ zh1m2--j;u}^Y$L4u^vo%Nkmhzz9eb=67p4n^dk+JosY!PUVOp* zx5cPun74gi$yhYCZSqKU^l_KZ>wPof3_UK#m&=S3oEn7+oMMfML^bKYQ*LI|8yxNL zGhURxf2DWXqhaChm%O(NwWedgU1CGq|nID#AEi?Vqf#Cp5My@qiwqbgPUcBC@F?Fmk(Jer>em1GVskff; zSBfM!&;(@+Ed%G@9i>c9X;Q5cUyUAoZrb$yJn+F|Bh{{X>eSiq$dm<<%2u1l>(j`J zFjNs@wegT4JQ;)9B5&~1%Dh)o%qDDipkuTZ4N}a=mf&2I&Ry{Zt8651xess!*#O9u z^6h{R*_$myHHCD|9oTJoU8R06m(Al1%~$21EpI;iYmKI6ypkoVxhsHE|7G?YeELYH zd-(>cKDA4{l+4^#hC#2JA6wdzch<(4{*;4`TMKi8sAg)?J`WY@t2-L2W1j5l{Ya}n zIEZeBIy%^)k@M$Hf2V}L;iDQp8({JNy5t^{BodSc#ZJ_7GfeoXk!i0QUv5y|>ufth zOgZ9<^)Mep`JiC(!sqRV=>fIrVMRSY6E@|3&Gv_!tQTRydgNEjS>x?|wFz1Qz*w*_ z(4aoHjQF2_h5U!&(R4JZMKaj6f*?6KXo5*|7K*_>&T_=hj7ocYyMz71@Od)Q*5uOT zz4$gu@aUP}e>y(@>HZ9|P@s{{W+M#f3C&hN(N(pRAzQd{POTWmyQC(r4-ieCB*e8u zvWR~ldH>h1imy;Oz78vWWx#m7nnQzmsWI8p6?*)jot6E{ilsUVw=#nhw1Y~aIiqSx z8TD}d1LFVlX#nZ{2}5s$N(|t%!Br2VgU_0IU-;32>`3&6$Zn~~Q9nN`haE*{Zr&)H zZjseqW@KuGl!F zjKl*3ttg>DazW;@e0mlfG}A>%D&QSagiLI9Pi#BoL&`!TO^NM%?Xn3CE99U)p&oWv zmrNfk3l%WiZWe8hc&r}PEDNG5GHGKGg%f;3D~rmhwgHw&ya3GF8v4JtY9u<~=j2TI z6$TJX7|ql<0eF3{W@9WeMCrpme}L=nkkRrhBS@(qqbU=TJM(Cuue|&S;4UJm^becb=}~x^xYVNm zzG>CYl$)YWkRG%a3t#tc8GmcT`85*%mZ}n+U0i%E^hy!o?SdYvTd#XWvr(4dl@VRs zIrvrj(vAw+658;z`y!;H?d{7Yl{e%KozmFuHm%=(t1PPFs3)OUPMV|K`?6eBp+vhw zKt}uZ_vRi;ErVLthiVD2zwi&B)lD1a@OvxE-a5TLb#b>`J$MEgqCsdTs}IxF3ATUK z=O|76cve-?o=%N{TI6^3Rq*RXMKfV{e@M5GM?UByuvuW9knBZ<@yeWxg%(TKusJnR0Vd{K$ zFpXH>H}gisp|c(vGlXp;Sf&00C1kbLB3l%*V@U7K$b+4iPI&*@2>F~A-dqXLL!ug*`Mkybu|B-fqa|CrC^*l_4UeyYXYG$WOpK zPmzNIj!m*@(b@WPEhJ$HdzDbQ4bI6@t>NtoQwoEn^3s3%>Dp2EqMW8jq)PI#wC$W; zsJ2E%2-c?ZyORrmuI{0XC8Z&Ic0ijl{^R6&x1}p`uhlZ(_9n#R!iSkh`UE|2_`b5W zu^@z8yd6=LZ6LKd79pGnmKm)^&wNtxQ@%=@1n}4Tpi4+S^`XXJXJn*!2;RIgClS?r z83q5alGhq%X1S3Go}b0!T$qSm6^6wmq`SmY=<8m3(<$QK`Rcl2iRtZ_Cw8mn&9#>P z>E;hO_1oh-=dU%M+g2%+K7S)J7&;6er|0>6^}*)B1_63LXJK_^bE==!mg<92+019W zu9}Rx6|OOt&QrwWyqNb>TTY9!ettpU5VZBxmeV#d)jh7(C&{$NrJ5P=PuH~T)xU_X zVa+mu1{c?-V?d#ryYg4lPQ}yLn;NI5C;aW^uyd>)ludOogx(n0w)C^#f>@vjS-n+c z$0ispG4a{W)v%jbZkQSzgm*suIVpJi%PkI;=S$?Wc7Ys-_oQnDI zLM*k+VJ>H4fPvVx@*wA&=hJ-*`10w%vkm4q&>uvXTNBY&=5vO_6X8T2l@VfOHWK@= zDnl}8#8KwlRfEX`3a3*G_s+Km({JXKs*GE0m;}?(xW@eJjQl8ErowqwhMP47UEXhb zZz1;fDm_*!KGPOm~W2h{KdpKA2Ku&#$c*s?7w~ zYsBIej1~S%GF;b{AD2Ea z(J20LK6o#)&@aKwVkpl;aI-yG57mA_5;3gueAvJ5muvKQQYF`%Akt%YFI#}J<^goi z2Fy~8!sFRo#h|U?`RehuoyJPxwY&T&=Q}?PfAtbsm7%ZCV9bD)g$!2K zLwRrxW8i-WvIda#(gqeZ#dwqyt~z}mx82)_v!k8T``QyF+zwG9UPmkN$JLo!h_jXW z&8=42m4TYfXE#hx!o~uGW)72^`1#F8+S}(_l=tOozW8zy12ak8Mm8xHJuS zl~>V}XL%Iq|G`m<+oLh-@g<4ODc)o~0z$DQjUMwx6hfu3(0a235kG|h#PXMct=OFI z>AIgl#%e}v({qCFX!wO{ru*8o3QEJ$fr*eMLZ*}{lI2n&qko;}seW7~QrD zK~9exKe~4_{+#3)@H1|I>rqkm;3r&#Z`-c%>t#XH$rgX6Z~NPDMO;ns)tR zZ4ysta_d^*<^E>)E&mu=UgI0)gOTv}Y&}MKFYthp7U>tCK1FGb z@Nqz=j^AH85@`;Lz+?;i-EC-&H&`OkOKN$j`e%1%kOa+NK|%el`}Pej=)%JGb7*P> zzLvdp?N(!T)4uFG#hV`db3#cbYvr}_f0A0+|oCaVt92m^j^{S9@&G4~Ja zZkfp}H-n!h^kWW0$d+-%U%IcQ99=!LojQqY+R8?4nZD__f;%o++G7_x_&oTY#$XqR z&mJqqkBLxi(Xu)J24~YuNBv!%uWvh=&%CTU<1-{4PD7VZH)j;jCKVgmEF&B$a{ajB z8j&*J?B`Y8O{`V}+_eRpIS@|EC)&jToBuCbFS>d3VXmMT{x_X&=pVJYIqTrOY~;&g zMIH+Az==xXTUm$waiSi2p}lM4<>DQnv;5$o6(HRI1fnbBWL_Y%%9Bad@p{*PiO6TN zFWCKPLSZxPX8Sqs(X>`b>Fkey#U(>0xr(<~Uh=^wsxNKKm!8Yp5d^D7_P6iJzq)!4 zb(@XQ)EQ+FjQTzzGI`eb$~@rdE1N<);u&eUjj2|X(7os4^b#=*mx(IwM-+smiWA#T zx@#qiM(+|Uaau)YD1A5`Omz?89 z<6MHSR1b3Y7zccczY#r5qp)|PLvW@`uJ`P}i~{_qB%Um1@}i1i>%PEAw|a&Se0THM zTJOI)L;8bJL_f&OTTH3M<5mhaGs7_;jM}O2(#=}yExXp?maWy^+SuNNp{Xb2t^SwDvbvKsYTebgcjM%IF6P<2Pj$*8vT82Z z`%Q$$u#mRK$_m7o6aNX$#ZB=*cQ)y$M&V!H_eVYaNC~|Vxm%j|@4meOTj`bKb?%c! zcwBlxW9_%36moK}LVsF@2Z&NW=-dvl3@vTm42G{(QkJSce8pTrn^-u#OBVTH&9OpX zhiK}izMyuary_R=NbhM3{H(~L77j@1$lJ}}! zVOP$Q&V`B5u<}_LrW9Euf6@}iU8eW}^W@G;18N7!&}?PP=?PDk>v>s};o}I| zbLR3AJL(aI`cYj?i%s0qp6M08Tlg=2*)ef+8zoTI`0I4|9Zui{y_FS@>iYxMqY0Dq4MO^|13G^Mwj-oKnV;IjaPO}-=>46l=o_W{vjVDc`r zuN~1+ta)uSO%|VprC82OP_9ae^CFhYF(1inJM*>wg+a<>%2FNp0^h#$8 z<-!}>cQv2&z2Fe367hS?F!RjrYSZ6Kt?io4l;#=Pw6bRW&(h+(myo&!x_8dfxqF!3 z&(fN|j2|At_8zJ8AcG;$D0%}drb7u0^M5Ae_YHs4Bg8a$Y@^9$?&}mZd$r)`bC4Rv z>!KM{I#$?3S>?~ETjyfixO_=_wDd{NeYQml?yiEu0{cn9KiYhUYs!Aw&)jFRs`aQZ zH%fE#eGO6gX&V)#?v#CTpXJ8UnoZd2i?w%>J}12`Tj@L9h&1L-_~r$r

DGM?{E} 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�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 literal 0 HcmV?d00001 diff --git a/examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/images/picture5.png b/examples/bluetooth/ble_mesh/ble_mesh_client_model/tutorial/images/picture5.png new file mode 100644 index 0000000000000000000000000000000000000000..ff9329eb146acb2b117c1f23aefc6faecfc7cff4 GIT binary patch literal 26425 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 Z=re;BL7JjV;OA~msVHd3m)count != 0) {\ + ESP_LOGD(TAG, "\n%s, %s\n", __func__, message);\ + dst_msg = src_msg->ival[0];\ + } \ +} while(0) \ + +#define ble_mesh_node_get_value(index, key, value) do { \ + uint16_t _index = 0; \ + xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY); \ + for (_index = 0; _index < NODE_MAX_GROUP_CONFIG; _index) { \ + if (node_set_prestore_params[_index].key == value) { \ + break; \ + } \ + } \ + index = _index; \ + xSemaphoreGive(ble_mesh_node_sema); \ +} while(0) \ + +#define ble_mesh_node_set_state(status) do { \ + xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY); \ + node_status.previous = node_status.current; \ + node_status.current = status; \ + xSemaphoreGive(ble_mesh_node_sema); \ +}while(0) \ + +#define ble_mesh_node_get_state(status) do { \ + xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY); \ + status = node_status.current; \ + xSemaphoreGive(ble_mesh_node_sema); \ +}while(0) \ + +#define ble_mesh_callback_check_err_code(err_code, message) do { \ + if (err_code == ESP_OK) { \ + ESP_LOGI(TAG, "%s,OK\n", message); \ + } else { \ + ESP_LOGE(TAG, "%s,Fail,%d\n", message, err_code); \ + } \ +}while(0) \ + +void ble_mesh_node_init(); +void ble_mesh_set_node_prestore_params(uint16_t netkey_index, uint16_t unicast_addr); +esp_ble_mesh_model_t *ble_mesh_get_model(uint16_t model_id); +esp_ble_mesh_comp_t *ble_mesh_get_component(uint16_t model_id); +void ble_mesh_node_statistics_get(); +int ble_mesh_node_statistics_accumultate(uint8_t *data, uint32_t value, uint16_t type); +int ble_mesh_node_statistics_init(uint16_t package_num); +void ble_mesh_node_statistics_destroy(); + +#endif //_BLE_MESH_ADAOTER_H_ diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.c new file mode 100644 index 0000000000..2e7c938460 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.c @@ -0,0 +1,208 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ble_mesh_cfg_srv_model.h" + +uint8_t dev_uuid[16] = {0xdd, 0xdd}; + +#if CONFIG_BLE_MESH_NODE +esp_ble_mesh_prov_t prov = { + .uuid = dev_uuid, +}; +#endif //CONFIG_BLE_MESH_NODE + +#if CONFIG_BLE_MESH_PROVISIONER +esp_ble_mesh_prov_t prov = { + .prov_uuid = dev_uuid, + .prov_unicast_addr = 0x0001, + .prov_start_address = 0x0005, + .prov_attention = 0x00, + .prov_algorithm = 0x00, + .prov_pub_key_oob = 0x00, + .prov_pub_key_oob_cb = NULL, + .prov_static_oob_val = NULL, + .prov_static_oob_len = 0x00, + .prov_input_num = NULL, + .prov_output_num = NULL, + .flags = 0x00, + .iv_index = 0x00, +}; +#endif //CONFIG_BLE_MESH_PROVISIONER + +ESP_BLE_MESH_MODEL_PUB_DEFINE(model_pub_config, 2 + 1, ROLE_NODE); + +esp_ble_mesh_model_pub_t vendor_model_pub_config; + +// configure server module +esp_ble_mesh_cfg_srv_t cfg_srv = { + .relay = ESP_BLE_MESH_RELAY_ENABLED, + .beacon = ESP_BLE_MESH_BEACON_ENABLED, +#if defined(CONFIG_BLE_MESH_FRIEND) + .friend_state = ESP_BLE_MESH_FRIEND_ENABLED, +#else + .friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED, +#endif +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_ENABLED, +#else + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED, +#endif + .default_ttl = 7, + + /* 3 transmissions with 20ms interval */ + .net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20), + .relay_retransmit = ESP_BLE_MESH_TRANSMIT(0, 20), +}; + +esp_ble_mesh_model_t config_server_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), +}; + +esp_ble_mesh_elem_t config_server_elements[] = { + ESP_BLE_MESH_ELEMENT(0, config_server_models, ESP_BLE_MESH_MODEL_NONE), +}; + +esp_ble_mesh_comp_t config_server_comp = { + .cid = CID_ESP, + .elements = config_server_elements, + .element_count = ARRAY_SIZE(config_server_elements), +}; + +// config client model +esp_ble_mesh_model_t config_client_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), + ESP_BLE_MESH_MODEL_CFG_CLI(&cfg_cli), +}; + +esp_ble_mesh_elem_t config_client_elements[] = { + ESP_BLE_MESH_ELEMENT(0, config_client_models, ESP_BLE_MESH_MODEL_NONE), +}; + +esp_ble_mesh_comp_t config_client_comp = { + .cid = CID_ESP, + .elements = config_client_elements, + .element_count = ARRAY_SIZE(config_client_elements), +}; + +// configure special module +esp_ble_mesh_model_op_t gen_onoff_srv_model_op_config[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET, 0), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, 2), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2), + ESP_BLE_MESH_MODEL_OP_END, +}; + +esp_ble_mesh_model_t gen_onoff_srv_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, gen_onoff_srv_model_op_config, &model_pub_config, NULL), +}; + +esp_ble_mesh_elem_t gen_onoff_srv_elements[] = { + ESP_BLE_MESH_ELEMENT(0, gen_onoff_srv_models, ESP_BLE_MESH_MODEL_NONE), +}; + +esp_ble_mesh_comp_t gen_onoff_srv_comp = { + .cid = CID_ESP, + .elements = gen_onoff_srv_elements, + .element_count = ARRAY_SIZE(gen_onoff_srv_elements), +}; + +// config generic onoff client +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) + +esp_ble_mesh_client_t gen_onoff_cli; + +esp_ble_mesh_model_t gen_onoff_cli_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), + ESP_BLE_MESH_MODEL_CFG_CLI(&cfg_cli), + ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(&model_pub_config, &gen_onoff_cli), +}; + +esp_ble_mesh_elem_t gen_onoff_cli_elements[] = { + ESP_BLE_MESH_ELEMENT(0, gen_onoff_cli_models, ESP_BLE_MESH_MODEL_NONE), +}; + +esp_ble_mesh_comp_t gen_onoff_cli_comp = { + .cid = CID_ESP, + .elements = gen_onoff_cli_elements, + .element_count = ARRAY_SIZE(gen_onoff_cli_elements), +}; +#endif //CONFIG_BLE_MESH_GENERIC_ONOFF_CLI + +//CONFIG VENDOR MODEL TEST PERFORMANCE +#define ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_SRV 0x2000 +#define ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_CLI 0x2001 + +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_GET ESP_BLE_MESH_MODEL_OP_3(0x01, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET ESP_BLE_MESH_MODEL_OP_3(0x02, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK ESP_BLE_MESH_MODEL_OP_3(0x03, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS ESP_BLE_MESH_MODEL_OP_3(0x04, CID_ESP) + +esp_ble_mesh_client_op_pair_t test_perf_cli_op_pair[] = { + {ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_GET, ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS}, + {ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET, ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS}, + {ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK, ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS}, +}; + +esp_ble_mesh_client_t test_perf_cli = { + .op_pair_size = ARRAY_SIZE(test_perf_cli_op_pair), + .op_pair = test_perf_cli_op_pair, +}; + +esp_ble_mesh_model_op_t test_perf_srv_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_GET, 1), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET, 1), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK, 1), + ESP_BLE_MESH_MODEL_OP_END, +}; + +esp_ble_mesh_model_op_t test_perf_cli_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS, 1), + ESP_BLE_MESH_MODEL_OP_END, +}; + +esp_ble_mesh_model_t config_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), + ESP_BLE_MESH_MODEL_CFG_CLI(&cfg_cli), +}; + +esp_ble_mesh_model_t test_perf_cli_models[] = { + ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_CLI, + test_perf_cli_op, &vendor_model_pub_config, &test_perf_cli), +}; + +esp_ble_mesh_elem_t test_perf_cli_elements[] = { + ESP_BLE_MESH_ELEMENT(0, config_models, test_perf_cli_models), +}; + +esp_ble_mesh_comp_t test_perf_cli_comp = { + .cid = CID_ESP, + .elements = test_perf_cli_elements, + .element_count = ARRAY_SIZE(test_perf_cli_elements), +}; + +esp_ble_mesh_model_t test_perf_srv_models[] = { + ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_SRV, + test_perf_srv_op, NULL, NULL), +}; + +esp_ble_mesh_elem_t test_perf_srv_elements[] = { + ESP_BLE_MESH_ELEMENT(0, config_models, test_perf_srv_models), +}; + +esp_ble_mesh_comp_t test_perf_srv_comp = { + .cid = CID_ESP, + .elements = test_perf_srv_elements, + .element_count = ARRAY_SIZE(test_perf_srv_elements), +}; diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.h b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.h new file mode 100644 index 0000000000..9e43333eb9 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.h @@ -0,0 +1,107 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BLE_MESH_CFG_SRV_MODEL_H_ +#define _BLE_MESH_CFG_SRV_MODEL_H_ + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_config_model_api.h" + +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) +#include "esp_ble_mesh_generic_model_api.h" +#endif //CONFIG_BLE_MESH_GENERIC_ONOFF_CLI + +#define NODE_MAX_GROUP_CONFIG 3 +#define CID_ESP 0x02C4 + +extern uint8_t dev_uuid[16]; + +typedef struct { + uint16_t net_idx; + uint16_t unicast_addr; +} ble_mesh_node_config_params; +ble_mesh_node_config_params ble_mesh_node_prestore_params[NODE_MAX_GROUP_CONFIG]; + +extern esp_ble_mesh_prov_t prov; + +extern esp_ble_mesh_model_pub_t vendor_model_pub_config; + +// configure server module +extern esp_ble_mesh_cfg_srv_t cfg_srv; + +extern esp_ble_mesh_model_t config_server_models[]; + +extern esp_ble_mesh_elem_t config_server_elements[]; + +extern esp_ble_mesh_comp_t config_server_comp; + +// config client model +esp_ble_mesh_client_t cfg_cli; +extern esp_ble_mesh_model_t config_client_models[]; + +extern esp_ble_mesh_elem_t config_client_elements[]; + +extern esp_ble_mesh_comp_t config_client_comp; + +// configure special module +extern esp_ble_mesh_model_op_t gen_onoff_srv_model_op_config[]; + +extern esp_ble_mesh_model_t gen_onoff_srv_models[]; + +extern esp_ble_mesh_elem_t gen_onoff_srv_elements[]; + +extern esp_ble_mesh_comp_t gen_onoff_srv_comp; + +// config generic onoff client +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) + +extern esp_ble_mesh_client_t gen_onoff_cli; + +extern esp_ble_mesh_model_t gen_onoff_cli_models[]; + +extern esp_ble_mesh_elem_t gen_onoff_cli_elements[]; + +extern esp_ble_mesh_comp_t gen_onoff_cli_comp; +#endif //CONFIG_BLE_MESH_GENERIC_ONOFF_CLI + +//CONFIG VENDOR MODEL TEST PERFORMANCE +#define ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_SRV 0x2000 +#define ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_CLI 0x2001 + +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_GET ESP_BLE_MESH_MODEL_OP_3(0x01, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET ESP_BLE_MESH_MODEL_OP_3(0x02, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK ESP_BLE_MESH_MODEL_OP_3(0x03, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS ESP_BLE_MESH_MODEL_OP_3(0x04, CID_ESP) + +extern esp_ble_mesh_client_t test_perf_cli; + +extern esp_ble_mesh_model_op_t test_perf_srv_op[]; + +extern esp_ble_mesh_model_op_t test_perf_cli_op[]; + +extern esp_ble_mesh_model_t config_models[]; + +extern esp_ble_mesh_model_t test_perf_cli_models[]; + +extern esp_ble_mesh_elem_t test_perf_cli_elements[]; + +extern esp_ble_mesh_comp_t test_perf_cli_comp; + +extern esp_ble_mesh_model_t test_perf_srv_models[]; + +extern esp_ble_mesh_elem_t test_perf_srv_elements[]; + +extern esp_ble_mesh_comp_t test_perf_srv_comp; + +#endif //_BLE_MESH_CFG_SRV_MODEL_H_ diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_decl.h b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_decl.h new file mode 100644 index 0000000000..22ab3a7f59 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_decl.h @@ -0,0 +1,28 @@ +/* Console example — declarations of command registration functions. + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#pragma once + +#include "esp_ble_mesh_defs.h" + +// Register system functions +void register_system(); + +// Register blutooth +void register_bluetooth(); + +// Register mesh node cmd +void ble_mesh_register_mesh_node(); + +// Register mesh config server and generic server operation cmd +void ble_mesh_register_server(); + +#if (CONFIG_BLE_MESH_CFG_CLI) +// Register mesh config client operation cmd +void ble_mesh_register_configuration_client_model(); +#endif diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.c new file mode 100644 index 0000000000..88b728bbab --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.c @@ -0,0 +1,128 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ble_mesh_console_lib.h" + +static int hex2num(char c); +static int hex2byte(const char *hex); + +static int hex2num(char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +static int hex2byte(const char *hex) +{ + int a, b; + a = hex2num(*hex++); + if (a < 0) { + return -1; + } + b = hex2num(*hex++); + if (b < 0) { + return -1; + } + return (a << 4) | b; +} + +int hexstr_2_bin(const char *hex, uint8_t *buf, uint32_t len) +{ + uint32_t i; + int a; + const char *ipos = hex; + uint8_t *opos = buf; + + for (i = 0; i < len; i++) { + a = hex2byte(ipos); + if (a < 0) { + return -1; + } + *opos ++ = a; + ipos += 2; + } + return 0; +} + +int get_value_string(char *value_in, char *buf) +{ + int result = -1; + + uint16_t length = strlen(value_in); + for(int i = 0; i 2) { + if (value_in[0] == '0' && value_in[1] == 'x') { + buf[(length - 2) / 2] = 0; + result = hexstr_2_bin(&value_in[2], (uint8_t *)buf, (length - 2) / 2); + length = (length - 2) / 2; + } else { + strcpy(buf, value_in); + result = 0; + } + } else { + strcpy(buf, value_in); + result = 0; + } + + return result; +} + +bool str_2_mac(uint8_t *str, uint8_t *dest) +{ + uint8_t loop = 0; + uint8_t tmp = 0; + uint8_t *src_p = str; + + if (strlen((char *)src_p) != 17) { // must be like 12:34:56:78:90:AB + return false; + } + + for (loop = 0; loop < 17 ; loop++) { + if (loop % 3 == 2) { + if (src_p[loop] != ':') { + return false; + } + + continue; + } + + if ((src_p[loop] >= '0') && (src_p[loop] <= '9')) { + tmp = tmp * 16 + src_p[loop] - '0'; + } else if ((src_p[loop] >= 'A') && (src_p[loop] <= 'F')) { + tmp = tmp * 16 + src_p[loop] - 'A' + 10; + } else if ((src_p[loop] >= 'a') && (src_p[loop] <= 'f')) { + tmp = tmp * 16 + src_p[loop] - 'a' + 10; + } else { + return false; + } + + if (loop % 3 == 1) { + *dest++ = tmp; + tmp = 0; + } + } + + return true; +} \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.h b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.h new file mode 100644 index 0000000000..8f8449eca4 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.h @@ -0,0 +1,31 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BLE_MESH_CONSOLE_LIB_H_ +#define _BLE_MESH_CONSOLE_LIB_H_ + +#include +#include + +#include "esp_system.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" + +bool str_2_mac(uint8_t *str, uint8_t *dest); +int hexstr_2_bin(const char *hex, uint8_t *buf, uint32_t len); +int get_value_string(char *value_in, char *buf); + +#endif //_BLE_MESH_CONSOLE_LIB_H_ \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_main.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_main.c new file mode 100644 index 0000000000..e629cf3054 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_main.c @@ -0,0 +1,215 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "esp_system.h" +#include "esp_log.h" +#include "esp_vfs_dev.h" +#include "driver/uart.h" +#include "nvs.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" + +#include "esp_console.h" +#include "linenoise/linenoise.h" +#include "argtable3/argtable3.h" + +#include "ble_mesh_console_decl.h" + +#if CONFIG_STORE_HISTORY + +#define MOUNT_PATH "/data" +#define HISTORY_PATH MOUNT_PATH "/history.txt" + +static void initialize_filesystem() +{ + static wl_handle_t wl_handle; + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = true + }; + esp_err_t err = esp_vfs_fat_spiflash_mount(MOUNT_PATH, "storage", &mount_config, &wl_handle); + if (err != ESP_OK) { + printf("Failed to mount FATFS (0x%x)", err); + return; + } +} +#endif // CONFIG_STORE_HISTORY + +static void initialize_console() +{ + /* Disable buffering on stdin and stdout */ + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + + /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install(CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + /* Initialize the console */ + esp_console_config_t console_config = { + .max_cmdline_args = 20, + .max_cmdline_length = 256, +#if CONFIG_LOG_COLORS + .hint_color = atoi(LOG_COLOR_CYAN) +#endif + }; + ESP_ERROR_CHECK( esp_console_init(&console_config) ); + + /* Configure linenoise line completion library */ + /* Enable multiline editing. If not set, long commands will scroll within + * single line. + */ + linenoiseSetMultiLine(1); + + /* Tell linenoise where to get command completions and hints */ + linenoiseSetCompletionCallback(&esp_console_get_completion); + linenoiseSetHintsCallback((linenoiseHintsCallback *) &esp_console_get_hint); + + /* Set command history size */ + linenoiseHistorySetMaxLen(100); + +#if CONFIG_STORE_HISTORY + /* Load command history from filesystem */ + linenoiseHistoryLoad(HISTORY_PATH); +#endif +} + + +esp_err_t bluetooth_init(void) +{ + esp_err_t ret; + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + printf("%s initialize controller failed\n", __func__); + return ret; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + printf("%s enable controller failed\n", __func__); + return ret; + } + ret = esp_bluedroid_init(); + if (ret) { + printf("%s init bluetooth failed\n", __func__); + return ret; + } + ret = esp_bluedroid_enable(); + if (ret) { + printf("%s enable bluetooth failed\n", __func__); + return ret; + } + + return ret; +} + +void app_main(void) +{ + esp_err_t res; + + nvs_flash_init(); + + // init and enable bluetooth + res = bluetooth_init(); + if (res) { + printf("esp32_bluetooth_init failed (ret %d)", res); + } + +#if CONFIG_STORE_HISTORY + initialize_filesystem(); +#endif + + initialize_console(); + + /* Register commands */ + esp_console_register_help_command(); + register_system(); + register_bluetooth(); + ble_mesh_register_mesh_node(); + ble_mesh_register_server(); + + /* Prompt to be printed before each line. + * This can be customized, made dynamic, etc. + */ + const char *prompt = LOG_COLOR_I "esp32> " LOG_RESET_COLOR; + + printf("\n" + "This is an example of ESP-IDF console component.\n" + "Type 'help' to get the list of commands.\n" + "Use UP/DOWN arrows to navigate through command history.\n" + "Press TAB when typing command name to auto-complete.\n"); + + /* Figure out if the terminal supports escape sequences */ + int probe_status = linenoiseProbe(); + if (probe_status) { /* zero indicates success */ + printf("\n" + "Your terminal application does not support escape sequences.\n" + "Line editing and history features are disabled.\n" + "On Windows, try using Putty instead.\n"); + linenoiseSetDumbMode(1); +#if CONFIG_LOG_COLORS + /* Since the terminal doesn't support escape sequences, + * don't use color codes in the prompt. + */ + prompt = "esp32> "; +#endif //CONFIG_LOG_COLORS + } + + /* Main loop */ + while (true) { + /* Get a line using linenoise. + * The line is returned when ENTER is pressed. + */ + char *line = linenoise(prompt); + if (line == NULL) { /* Ignore empty lines */ + continue; + } + /* Add the command to the history */ + linenoiseHistoryAdd(line); +#if CONFIG_STORE_HISTORY + /* Save command history to filesystem */ + linenoiseHistorySave(HISTORY_PATH); +#endif + + /* Try to run the command */ + int ret; + esp_err_t err = esp_console_run(line, &ret); + if (err == ESP_ERR_NOT_FOUND) { + printf("Unrecognized command\n"); + } else if (err == ESP_ERR_INVALID_ARG) { + // command was empty + } else if (err == ESP_OK && ret != ESP_OK) { + printf("\nCommand returned non-zero error code: 0x%x\n", ret); + } else if (err != ESP_OK) { + printf("Internal error: 0x%x\n", err); + } + /* linenoise allocates line buffer on the heap, so need to free it */ + linenoiseFree(line); + } +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_system.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_system.c new file mode 100644 index 0000000000..cc8805dbf2 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_system.c @@ -0,0 +1,183 @@ +/* Console example — various system commands + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include + +#include "esp_log.h" +#include "esp_console.h" +#include "esp_system.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" +#include "soc/rtc_cntl_reg.h" +#include "argtable3/argtable3.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "ble_mesh_console_decl.h" + +#if CONFIG_IDF_CMAKE +#define CONFIG_ESPTOOLPY_PORT "Which is choosen by Users for CMake" +#endif + +static void register_free(); +static void register_restart(); +static void register_make(); + +void register_system() +{ + register_free(); + register_restart(); + register_make(); +} + +/** 'restart' command restarts the program */ + +static int restart(int argc, char **argv) +{ + printf("%s, %s", __func__, "Restarting"); + esp_restart(); +} + +static void register_restart() +{ + const esp_console_cmd_t cmd = { + .command = "restart", + .help = "Restart the program", + .hint = NULL, + .func = &restart, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); +} + +/** 'free' command prints available heap memory */ + +static int free_mem(int argc, char **argv) +{ + printf("%d\n", esp_get_free_heap_size()); + return 0; +} + +static void register_free() +{ + const esp_console_cmd_t cmd = { + .command = "free", + .help = "Get the total size of heap memory available", + .hint = NULL, + .func = &free_mem, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); +} + +static int make(int argc, char **argv) +{ + int count = REG_READ(RTC_CNTL_STORE0_REG); + if (++count >= 3) { + printf("This is not the console you are looking for.\n"); + return 0; + } + REG_WRITE(RTC_CNTL_STORE0_REG, count); + + const char *make_output = + R"(LD build/console.elf +esptool.py v2.1-beta1 +)"; + + const char* flash_output[] = { +R"(Flashing binaries to serial port )" CONFIG_ESPTOOLPY_PORT R"( (app at offset 0x10000)... +esptool.py v2.1-beta1 +Connecting.... +)", +R"(Chip is ESP32D0WDQ6 (revision 0) +Uploading stub... +Running stub... +Stub running... +Changing baud rate to 921600 +Changed. +Configuring flash size... +Auto-detected Flash size: 4MB +Flash params set to 0x0220 +Compressed 15712 bytes to 9345... +)", +R"(Wrote 15712 bytes (9345 compressed) at 0x00001000 in 0.1 seconds (effective 1126.9 kbit/s)... +Hash of data verified. +Compressed 333776 bytes to 197830... +)", +R"(Wrote 333776 bytes (197830 compressed) at 0x00010000 in 3.3 seconds (effective 810.3 kbit/s)... +Hash of data verified. +Compressed 3072 bytes to 82... +)", +R"(Wrote 3072 bytes (82 compressed) at 0x00008000 in 0.0 seconds (effective 1588.4 kbit/s)... +Hash of data verified. +Leaving... +Hard resetting... +)" + }; + + const char* monitor_output = +R"(MONITOR +)" LOG_COLOR_W R"(--- idf_monitor on )" CONFIG_ESPTOOLPY_PORT R"( 115200 --- +--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H -- +)" LOG_RESET_COLOR; + + bool need_make = false; + bool need_flash = false; + bool need_monitor = false; + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "all") == 0) { + need_make = true; + } else if (strcmp(argv[i], "flash") == 0) { + need_make = true; + need_flash = true; + } else if (strcmp(argv[i], "monitor") == 0) { + need_monitor = true; + } else if (argv[i][0] == '-') { + /* probably -j option */ + } else if (isdigit((int) argv[i][0])) { + /* might be an argument to -j */ + } else { + printf("make: *** No rule to make target `%s'. Stop.\n", argv[i]); + /* Technically this is an error, but let's not spoil the output */ + return 0; + } + } + if (argc == 1) { + need_make = true; + } + if (need_make) { + printf("%s", make_output); + } + if (need_flash) { + size_t n_items = sizeof(flash_output) / sizeof(flash_output[0]); + for (int i = 0; i < n_items; ++i) { + printf("%s", flash_output[i]); + vTaskDelay(200/portTICK_PERIOD_MS); + } + } + if (need_monitor) { + printf("%s", monitor_output); + esp_restart(); + } + return 0; +} + +static void register_make() +{ + const esp_console_cmd_t cmd = { + .command = "make", + .help = NULL, /* Do not include in 'help' output */ + .hint = "all | flash | monitor", + .func = &make, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); +} + + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_node_cmd.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_node_cmd.c new file mode 100644 index 0000000000..da08884e35 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_node_cmd.c @@ -0,0 +1,547 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_bt.h" +#include "soc/soc.h" + +#include "esp_bt_device.h" + +#include "test.h" +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_common_api.h" +#include "esp_ble_mesh_provisioning_api.h" + +#include "ble_mesh_console_lib.h" +#include "ble_mesh_adapter.h" + +typedef struct { + struct arg_str *static_val; + struct arg_int *static_val_len; + struct arg_int *output_size; + struct arg_int *output_actions; + struct arg_int *input_size; + struct arg_int *input_actions; + struct arg_end *end; +} ble_mesh_prov_t; +static ble_mesh_prov_t oob; + +typedef struct { + struct arg_int *model_type; + struct arg_int *config_index; + struct arg_str *dev_uuid; + struct arg_int *pub_config; + struct arg_end *end; +} ble_mesh_comp_t; +static ble_mesh_comp_t component; + +typedef struct { + struct arg_int *bearer; + struct arg_int *enable; + struct arg_end *end; +} ble_mesh_bearer_t; +static ble_mesh_bearer_t bearer; + +typedef struct { + struct arg_str *action_type; + struct arg_int *package_num; + struct arg_end *end; +} ble_mesh_node_statistices_t; +ble_mesh_node_statistices_t node_statistices; + +typedef struct { + struct arg_str *action_type; + struct arg_int *tx_sense_power; + struct arg_end *end; +} ble_mesh_tx_sense_power; +static ble_mesh_tx_sense_power power_set; + +typedef struct { + struct arg_str *net_key; + struct arg_int *net_idx; + struct arg_int *unicast_addr; + struct arg_str *dev_key; + struct arg_str *app_key; + struct arg_int *app_idx; + struct arg_int *group_addr; + struct arg_end *end; +} ble_mesh_node_network_info_t; +ble_mesh_node_network_info_t node_network_info; + +ble_mesh_node_status node_status = { + .previous = 0x0, + .current = 0x0, +}; + +SemaphoreHandle_t ble_mesh_node_sema; + +void ble_mesh_register_node_cmd(); +// Register callback function +void ble_mesh_prov_cb(esp_ble_mesh_prov_cb_event_t event, esp_ble_mesh_prov_cb_param_t *param); +void ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, esp_ble_mesh_model_cb_param_t *param); + + +void ble_mesh_register_mesh_node() +{ + ble_mesh_register_node_cmd(); +} + +int ble_mesh_register_node_cb() +{ + ESP_LOGD(TAG, "enter %s\n", __func__); + ble_mesh_node_init(); + esp_ble_mesh_register_prov_callback(ble_mesh_prov_cb); + esp_ble_mesh_register_custom_model_callback(ble_mesh_model_cb); + ESP_LOGI(TAG, "Node:Reg,OK"); + ESP_LOGD(TAG, "exit %s\n", __func__); + return 0; +} + +void ble_mesh_prov_cb(esp_ble_mesh_prov_cb_event_t event, esp_ble_mesh_prov_cb_param_t *param) +{ + ESP_LOGD(TAG, "enter %s, event = %d", __func__, event); + switch (event) { + case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT: + ble_mesh_callback_check_err_code(param->prov_register_comp.err_code, "Provisioning:Register"); + break; + case ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_prov_enable_comp.err_code, "Node:EnBearer"); + break; + case ESP_BLE_MESH_NODE_PROV_DISABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_prov_disable_comp.err_code, "Node:DisBearer"); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT: + ESP_LOGI(TAG, "Node:LinkOpen,OK,%d", param->node_prov_link_open.bearer); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT: + ESP_LOGI(TAG, "Node:LinkClose,OK,%d", param->node_prov_link_close.bearer); + break; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT: + ESP_LOGI(TAG, "Node:OutPut,%d,%d", param->node_prov_output_num.action, param->node_prov_output_num.number); + break; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT: + ESP_LOGI(TAG, "Node:OutPutStr,%s", param->node_prov_output_str.string); + break; + case ESP_BLE_MESH_NODE_PROV_INPUT_EVT: + ESP_LOGI(TAG, "Node:InPut,%d,%d", param->node_prov_input.action, param->node_prov_input.size); + break; + case ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT: + ESP_LOGI(TAG, "Provisioning:Success,%d", param->node_prov_complete.addr); + ble_mesh_set_node_prestore_params(param->node_prov_complete.net_idx, param->node_prov_complete.addr); + break; + case ESP_BLE_MESH_NODE_PROV_RESET_EVT: + ESP_LOGI(TAG, "Node:Reset"); + break; + case ESP_BLE_MESH_NODE_PROV_INPUT_NUMBER_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_prov_input_num_comp.err_code, "Node:InputNum"); + break; + case ESP_BLE_MESH_NODE_PROV_INPUT_STRING_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_prov_input_str_comp.err_code, "Node:InputStr"); + break; + case ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_set_unprov_dev_name_comp.err_code, "Node:SetName"); + break; + case ESP_BLE_MESH_NODE_PROXY_IDENTITY_ENABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_proxy_identity_enable_comp.err_code, "Node:ProxyIndentity"); + break; + case ESP_BLE_MESH_NODE_PROXY_GATT_ENABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_proxy_gatt_enable_comp.err_code, "Node:EnProxyGatt"); + break; + case ESP_BLE_MESH_NODE_PROXY_GATT_DISABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_proxy_gatt_disable_comp.err_code, "Node:DisProxyGatt"); + break; + default: + break; + } + ESP_LOGD(TAG, "exit %s", __func__); +} + +void ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, esp_ble_mesh_model_cb_param_t *param) +{ + uint8_t status; + uint16_t result; + uint8_t data[4]; + + ESP_LOGD(TAG, "enter %s, event=%x\n", __func__, event); + printf("enter %s, event=%x\n", __func__, event); + switch (event) { + case ESP_BLE_MESH_MODEL_OPERATION_EVT: + if (param->model_operation.model != NULL && param->model_operation.model->op != NULL) { + if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET) { + ESP_LOGI(TAG, "Node:GetStatus,Success"); + ble_mesh_node_get_state(status); + esp_ble_mesh_server_model_send_msg(param->model_operation.model, param->model_operation.ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(status), &status); + } else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET) { + ESP_LOGI(TAG, "Node:SetAck,Success,%d,%d,%d", param->model_operation.msg[0], param->model_operation.ctx->recv_ttl, param->model_operation.length); + ble_mesh_node_set_state(param->model_operation.msg[0]); + ble_mesh_node_get_state(status); + esp_ble_mesh_server_model_send_msg(param->model_operation.model, param->model_operation.ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(status), &status); + } else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK) { + ble_mesh_node_set_state(param->model_operation.msg[0]); + ESP_LOGI(TAG, "Node:SetUnAck,Success,%d,%d", param->model_operation.msg[0], param->model_operation.ctx->recv_ttl); + } else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS) { + ESP_LOGI(TAG, "Node:Status,Success,%d", param->model_operation.length); + } else if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET) { + ESP_LOGI(TAG, "VendorModel:SetAck,Success,%d", param->model_operation.ctx->recv_ttl); + data[0] = param->model_operation.msg[0]; + data[1] = param->model_operation.msg[1]; + data[2] = param->model_operation.msg[2]; + data[3] = param->model_operation.ctx->recv_ttl; + result = ble_mesh_node_statistics_accumultate(param->model_operation.msg, param->model_operation.length, VENDOR_MODEL_PERF_OPERATION_TYPE_SET); + if (result == 0) { + esp_ble_mesh_server_model_send_msg(param->model_operation.model, param->model_operation.ctx, + ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS, sizeof(data), data); + } + } else if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK) { + ESP_LOGI(TAG, "VendorModel:SetUnAck,Success,%d,%d", param->model_operation.ctx->recv_ttl, param->model_operation.length); + result = ble_mesh_node_statistics_accumultate(param->model_operation.msg, param->model_operation.length, VENDOR_MODEL_PERF_OPERATION_TYPE_SET_UNACK); + if (result == 0) { + esp_ble_mesh_server_model_send_msg(param->model_operation.model, param->model_operation.ctx, + ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS, param->model_operation.length, param->model_operation.msg); + } + } + } + break; + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: + if (param->model_send_comp.err_code == ESP_OK) { + ESP_LOGI(TAG, "Node:ModelSend,OK"); + } else { + ESP_LOGE(TAG, "Node:ModelSend,Fail"); + } + break; + case ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT: + ESP_LOGI(TAG, "PublishSend,OK,0x%x,%d,", param->model_publish_comp.model->model_id, param->model_publish_comp.model->pub->msg->len); + break; + case ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT: + ESP_LOGI(TAG, "PublishUpdate,OK"); + break; + case ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT: + ESP_LOGI(TAG, "Node:TimeOut"); + break; + case ESP_BLE_MESH_MODEL_EVT_MAX: + ESP_LOGI(TAG, "Node:MaxEvt"); + break; + default: + break; + } + + ESP_LOGD(TAG, "exit %s\n", __func__); +} + +int ble_mesh_power_set(int argc, char **argv) +{ + esp_err_t result = ESP_OK; + ESP_LOGD(TAG, "enter %s\n", __func__); + int nerrors = arg_parse(argc, argv, (void **) &power_set); + if (nerrors != 0) { + arg_print_errors(stderr, power_set.end, argv[0]); + return 1; + } + + if (strcmp(power_set.action_type->sval[0], "tx") == 0) { + result = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, power_set.tx_sense_power->ival[0]); + } else if (strcmp(power_set.action_type->sval[0], "sense") == 0) { + uint32_t *reg = (uint32_t *)(0x6001c07c); + int reg_addr = 0x6001c07c; + uint32_t flag = 0x00FF0000; + uint32_t sense_new = power_set.tx_sense_power->ival[0]; + uint32_t reg_to_write = ((*reg) &= ~flag) | ((256 - sense_new) << 16); + REG_WRITE(reg_addr, reg_to_write); + + } + + if (result == ESP_OK) { + ESP_LOGI(TAG, "Node:SetPower,OK\n"); + } + ESP_LOGD(TAG, "exit %s\n", __func__); + return result; +} + +static int ble_mesh_load_oob(int argc, char **argv) +{ + uint8_t *static_val; + int nerrors = arg_parse(argc, argv, (void **) &oob); + + ESP_LOGD(TAG, "enter %s \n", __func__); + + if (nerrors != 0) { + arg_print_errors(stderr, oob.end, argv[0]); + return 1; + } + + //parsing prov + if (oob.static_val->count != 0) { + static_val = malloc(oob.static_val_len->ival[0] + 1); + get_value_string((char *)oob.static_val->sval[0], (char *)static_val); + prov.static_val = static_val; + } + + arg_int_to_value(oob.static_val_len, prov.static_val_len, "static_val_len"); + arg_int_to_value(oob.output_size, prov.output_size, "output_size"); + arg_int_to_value(oob.output_actions, prov.output_actions, "output_actions"); + arg_int_to_value(oob.input_size, prov.input_size, "input_size"); + arg_int_to_value(oob.input_actions, prov.input_actions, "input_action"); + + ESP_LOGI(TAG, "OOB:Load,OK\n"); + ESP_LOGD(TAG, "exit %s\n", __func__); + return 0; +} + +int ble_mesh_init(int argc, char **argv) +{ + int err; + esp_ble_mesh_comp_t *local_component = NULL; + uint8_t *device_uuid =NULL; + + int nerrors = arg_parse(argc, argv, (void **) &component); + if (nerrors != 0) { + arg_print_errors(stderr, component.end, argv[0]); + return 1; + } + + ESP_LOGD(TAG, "enter %s, module %x\n", __func__, component.model_type->ival[0]); + local_component = ble_mesh_get_component(component.model_type->ival[0]); + + if (component.dev_uuid->count != 0) { + device_uuid = malloc((16 + 1) * sizeof(uint8_t)); + if (device_uuid == NULL) { + ESP_LOGE(TAG, "ble mesh malloc failed, %d\n", __LINE__); + } + get_value_string((char *)component.dev_uuid->sval[0], (char *) device_uuid); + memcpy(dev_uuid, device_uuid, 16); + } else { + memcpy(dev_uuid, esp_bt_dev_get_address(), 6); + } + + err = esp_ble_mesh_init(&prov, local_component); + if (err) { + ESP_LOGE(TAG, "Initializing mesh failed (err %d)\n", err); + return err; + } + + free(device_uuid); + ESP_LOGD(TAG, "exit %s\n", __func__); + return err; +} + +int ble_mesh_node_enable_bearer(int argc, char **argv) +{ + esp_err_t err = 0; + + ESP_LOGD(TAG, "enter %s \n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &bearer); + if (nerrors != 0) { + arg_print_errors(stderr, bearer.end, argv[0]); + return 1; + } + + if (bearer.enable->count != 0) { + if (bearer.enable->ival[0]) { + //err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_N12); + err = esp_ble_mesh_node_prov_enable(bearer.bearer->ival[0]); + } else { + err = esp_ble_mesh_node_prov_disable(bearer.bearer->ival[0]); + } + } else { + return 1; + } + + ESP_LOGD(TAG, "exit %s\n", __func__); + return err; +} + +int ble_mesh_node_reset() +{ + esp_err_t err; + ESP_LOGD(TAG, "enter %s\n", __func__); + + err = esp_ble_mesh_node_local_reset(); + + ESP_LOGD(TAG, "exit %s\n", __func__); + return err; +} + +int ble_mesh_node_statistics_regist(int argc, char **argv) +{ + int result = ESP_OK; + + int nerrors = arg_parse(argc, argv, (void **) &node_statistices); + if (nerrors != 0) { + arg_print_errors(stderr, node_statistices.end, argv[0]); + return 1; + } + + ESP_LOGD(TAG, "enter %s\n", __func__); + + if (strcmp(node_statistices.action_type->sval[0], "init") == 0) { + result = ble_mesh_node_statistics_init(node_statistices.package_num->ival[0]); + ESP_LOGI(TAG, "Node:InitStatistics,OK\n"); + } else if (strcmp(node_statistices.action_type->sval[0], "get") == 0) { + ble_mesh_node_statistics_get(); + ESP_LOGI(TAG, "Node:GetStatistics,OK\n"); + } else if (strcmp(node_statistices.action_type->sval[0], "destroy") == 0) { + ble_mesh_node_statistics_destroy(); + ESP_LOGI(TAG, "Node:DestroyStatistics\n"); + } + + ESP_LOGD(TAG, "exit %s\n", __func__); + return result; +} + +int ble_mesh_node_enter_network_auto(int argc, char **argv) +{ + esp_err_t err = ESP_OK; + struct bt_mesh_device_network_info info = { + .flags = 0, + .iv_index = 0, + }; + + int nerrors = arg_parse(argc, argv, (void **) &node_network_info); + if (nerrors != 0) { + arg_print_errors(stderr, node_network_info.end, argv[0]); + return 1; + } + + ESP_LOGD(TAG, "enter %s\n", __func__); + + arg_int_to_value(node_network_info.net_idx, info.net_idx, "network key index"); + arg_int_to_value(node_network_info.unicast_addr, info.unicast_addr, "unicast address"); + arg_int_to_value(node_network_info.app_idx, info.app_idx, "appkey index"); + arg_int_to_value(node_network_info.group_addr, info.group_addr, "group address"); + err = get_value_string((char *)node_network_info.net_key->sval[0], (char *)info.net_key); + err = get_value_string((char *)node_network_info.dev_key->sval[0], (char *)info.dev_key); + err = get_value_string((char *)node_network_info.app_key->sval[0], (char *)info.app_key); + + err = bt_mesh_device_auto_enter_network(&info); + if (err == ESP_OK) { + ESP_LOGD(TAG, "NODE:EnNetwork,OK"); + } else { + ESP_LOGE(TAG, "NODE:EnNetwork,FAIL,%d", err); + } + + ESP_LOGD(TAG, "exit %s\n", __func__); + return err; +} + +void ble_mesh_register_node_cmd() +{ + const esp_console_cmd_t register_cmd = { + .command = "bmreg", + .help = "ble mesh: provisioner/node register callback", + .hint = NULL, + .func = &ble_mesh_register_node_cb, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(®ister_cmd)); + + oob.static_val = arg_str0("s", NULL, "", "Static OOB value"); + oob.static_val_len = arg_int0("l", NULL, "", "Static OOB value length"); + oob.output_size = arg_int0("x", NULL, "", "Maximum size of Output OOB"); + oob.output_actions = arg_int0("o", NULL, "", "Supported Output OOB Actions"); + oob.input_size = arg_int0("y", NULL, "", "Maximum size of Input OOB"); + oob.input_actions = arg_int0("i", NULL, "", "Supported Input OOB Actions"); + oob.end = arg_end(1); + + const esp_console_cmd_t oob_cmd = { + .command = "bmoob", + .help = "ble mesh: provisioner/node config OOB parameters", + .hint = NULL, + .func = &ble_mesh_load_oob, + .argtable = &oob, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&oob_cmd) ); + + component.model_type = arg_int0("m", NULL, "", "mesh model"); + component.config_index = arg_int0("c", NULL, "", "mesh model op"); + component.config_index->ival[0] = 0; // set default value + component.pub_config = arg_int0("p", NULL, "", "publish message buffer"); + component.dev_uuid = arg_str0("d", NULL, "", "device uuid"); + component.end = arg_end(1); + + const esp_console_cmd_t model_cmd = { + .command = "bminit", + .help = "ble mesh: provisioner/node init", + .hint = NULL, + .func = &ble_mesh_init, + .argtable = &component, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&model_cmd) ); + + bearer.bearer = arg_int0("b", NULL, "", "supported bearer"); + bearer.enable = arg_int0("e", NULL, "", "bearers node supported"); + bearer.end = arg_end(1); + + const esp_console_cmd_t bearer_cmd = { + .command = "bmnbearer", + .help = "ble mesh node: enable/disable different bearer", + .hint = NULL, + .func = &ble_mesh_node_enable_bearer, + .argtable = &bearer, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&bearer_cmd)); + + const esp_console_cmd_t reset_cmd = { + .command = "bmnreset", + .help = "ble mesh node: reset", + .hint = NULL, + .func = &ble_mesh_node_reset, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&reset_cmd)); + + node_statistices.action_type = arg_str1("z", NULL, "", "action type"); + node_statistices.package_num = arg_int0("p", NULL, "", "package number"); + node_statistices.end = arg_end(1); + + const esp_console_cmd_t node_statistices_cmd = { + .command = "bmsperf", + .help = "ble mesh server: performance statistics", + .hint = NULL, + .func = &ble_mesh_node_statistics_regist, + .argtable = &node_statistices, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&node_statistices_cmd)); + + power_set.action_type = arg_str1("z", NULL, "", "action type"); + power_set.tx_sense_power = arg_int0("t", NULL, "", "tx power or sense"); + power_set.end = arg_end(1); + + const esp_console_cmd_t power_set_cmd = { + .command = "bmtxpower", + .help = "ble mesh: set tx power or sense", + .hint = NULL, + .func = &ble_mesh_power_set, + .argtable = &power_set, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&power_set_cmd)); + + node_network_info.net_key = arg_str1("k", NULL, "", "network key"); + node_network_info.net_idx = arg_int1("n", NULL, "", "network key index"); + node_network_info.unicast_addr = arg_int1("u", NULL, "", "unicast address"); + node_network_info.dev_key = arg_str1("d", NULL, "", "device key"); + node_network_info.app_key = arg_str1("a", NULL, "", "app key"); + node_network_info.app_idx = arg_int1("i", NULL, "", "appkey index"); + node_network_info.group_addr = arg_int1("g", NULL, "", "group address"); + node_network_info.end = arg_end(1); + + const esp_console_cmd_t node_network_info_cmd = { + .command = "bmnnwk", + .help = "ble mesh node: auto join network", + .hint = NULL, + .func = &ble_mesh_node_enter_network_auto, + .argtable = &node_network_info, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&node_network_info_cmd)); +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_server_cmd.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_server_cmd.c new file mode 100644 index 0000000000..3abe85dc01 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_server_cmd.c @@ -0,0 +1,83 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_networking_api.h" + +#include "ble_mesh_console_lib.h" +#include "ble_mesh_adapter.h" + +void ble_mesh_register_server_operation(); + +typedef struct { + struct arg_str *data; + struct arg_int *opcode; + struct arg_int *model; + struct arg_int *role; + struct arg_end *end; +} ble_mesh_publish_message; +ble_mesh_publish_message msg_publish; + +void ble_mesh_register_server() +{ + ble_mesh_register_server_operation(); +} + +int ble_mesh_module_publish_message(int argc, char **argv) +{ + esp_err_t err; + esp_ble_mesh_model_t *model = NULL; + uint8_t *data = NULL; + uint8_t device_role = ROLE_NODE; + uint16_t length = 0; + + ESP_LOGD(TAG, "enter %s \n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &msg_publish); + if (nerrors != 0) { + arg_print_errors(stderr, msg_publish.end, argv[0]); + return 1; + } + + data = malloc(strlen(msg_publish.data->sval[0])); + get_value_string((char *)msg_publish.data->sval[0], (char *) data); + + arg_int_to_value(msg_publish.role, device_role, "device role"); + model = ble_mesh_get_model(msg_publish.model->ival[0]); + + err = esp_ble_mesh_model_publish(model, msg_publish.opcode->ival[0], length, data, device_role); + + ESP_LOGD(TAG, "exit %s \n", __func__); + free(data); + return err; +} + +void ble_mesh_register_server_operation() +{ + msg_publish.data = arg_str1("d", NULL, "", "message data"); + msg_publish.opcode = arg_int1("o", NULL, "", "operation opcode"); + msg_publish.model = arg_int1("m", NULL, "", "module published to"); + msg_publish.role = arg_int1("r", NULL, "", "device role"); + msg_publish.end = arg_end(1); + + const esp_console_cmd_t msg_publish_cmd = { + .command = "bmpublish", + .help = "ble mesh: publish message", + .hint = NULL, + .func = &ble_mesh_module_publish_message, + .argtable = &msg_publish, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&msg_publish_cmd)); +} + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/component.mk b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/component.mk new file mode 100644 index 0000000000..0b9d7585e7 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/register_bluetooth.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/register_bluetooth.c new file mode 100644 index 0000000000..5afaca813b --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/main/register_bluetooth.c @@ -0,0 +1,45 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_bt_device.h" +#include "esp_console.h" + +#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5] +#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x" + +void register_ble_address(); + +void register_bluetooth() +{ + register_ble_address(); +} + +int bt_mac() +{ + const uint8_t *mac = esp_bt_dev_get_address(); + printf("+BTMAC:"MACSTR"\n", MAC2STR(mac)); + return 0; +} + +void register_ble_address() +{ + const esp_console_cmd_t cmd = { + .command = "btmac", + .help = "get BT mac address", + .hint = NULL, + .func = (esp_console_cmd_func_t)&bt_mac, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/sdkconfig.defaults b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/sdkconfig.defaults new file mode 100644 index 0000000000..732947bb16 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_node/sdkconfig.defaults @@ -0,0 +1,46 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_CONSOLE_UART_BAUDRATE=921600 +CONFIG_ESPTOOLPY_BAUD_921600B=y +CONFIG_MONITOR_BAUD_921600B=y +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y +CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY= +CONFIG_BTDM_CONTROLLER_MODE_BTDM= +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_BLE_SCAN_DUPLICATE=y +CONFIG_SCAN_DUPLICATE_TYPE=2 +CONFIG_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_MESH_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BTDM_CONTROLLER_FULL_SCAN_SUPPORTED=y +CONFIG_GATTS_ENABLE=y +CONFIG_GATTS_SEND_SERVICE_CHANGE_MANUAL=y +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_HCI_5_0=y +CONFIG_BLE_MESH_USE_DUPLICATE_SCAN=y +CONFIG_BLE_MESH_NODE=y +CONFIG_BLE_MESH_PROV=y +CONFIG_BLE_MESH_NET_BUF_POOL_USAGE=y +CONFIG_BLE_MESH_PROXY=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_GATT_PROXY=y +CONFIG_BLE_MESH_NODE_ID_TIMEOUT=60 +CONFIG_BLE_MESH_PROXY_FILTER_SIZE=1 +CONFIG_BLE_MESH_SUBNET_COUNT=1 +CONFIG_BLE_MESH_APP_KEY_COUNT=1 +CONFIG_BLE_MESH_MODEL_KEY_COUNT=1 +CONFIG_BLE_MESH_MODEL_GROUP_COUNT=1 +CONFIG_BLE_MESH_LABEL_COUNT=1 +CONFIG_BLE_MESH_CRPL=10 +CONFIG_BLE_MESH_MSG_CACHE_SIZE=10 +CONFIG_BLE_MESH_ADV_BUF_COUNT=60 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=6 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=1 +CONFIG_BLE_MESH_RX_SDU_MAX=384 +CONFIG_BLE_MESH_TX_SEG_MAX=32 +CONFIG_BLE_MESH_RELAY=y +CONFIG_BLE_MESH_LOW_POWER= +CONFIG_BLE_MESH_FRIEND= +CONFIG_BLE_MESH_CFG_CLI=y +CONFIG_BTU_TASK_STACK_SIZE=4512 \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/CMakeLists.txt new file mode 100644 index 0000000000..6ce28d7d57 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_mesh_console_provisioner) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/Makefile b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/Makefile new file mode 100644 index 0000000000..6c7b6101fa --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_mesh_console_provisioner + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/README.md b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/README.md new file mode 100644 index 0000000000..3925d93cf4 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/README.md @@ -0,0 +1,10 @@ +# ble mesh provisioner demo +## Introduction +This demo implements ble mesh provisioner basic features.Based on this demo, provisioner can scan and prove unprovisioned device, send set/get message. Also can define new model. + +Demo steps: +1. Build the ble mesh provisioner demo with sdkconfig.default +2. register provisioner and set oob info, load model to init ble mesh provisioner +3. enable bearer, so that it can scan and prove unprovisioned devices +4. config appkey and other config info use config client model +5. send set/get message to nodes \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/CMakeLists.txt new file mode 100644 index 0000000000..339b93fc95 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/CMakeLists.txt @@ -0,0 +1,15 @@ +set(COMPONENT_SRCS "ble_mesh_adapter.c" + "ble_mesh_cfg_srv_model.c" + "ble_mesh_console_lib.c" + "ble_mesh_console_main.c" + "ble_mesh_console_system.c" + "ble_mesh_reg_cfg_client_cmd.c" + "ble_mesh_reg_gen_onoff_client_cmd.c" + "ble_mesh_reg_test_perf_client_cmd.c" + "ble_mesh_register_node_cmd.c" + "ble_mesh_register_provisioner_cmd.c" + "register_bluetooth.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.c new file mode 100644 index 0000000000..5f668718f3 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.c @@ -0,0 +1,300 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_ble_mesh_networking_api.h" +#include "ble_mesh_adapter.h" + +esp_ble_mesh_model_t *ble_mesh_get_model(uint16_t model_id) +{ + esp_ble_mesh_model_t *model = NULL; + + switch (model_id) { + case ESP_BLE_MESH_MODEL_ID_CONFIG_SRV: + model = config_server_models; + break; +#if (CONFIG_BLE_MESH_CFG_CLI) + case ESP_BLE_MESH_MODEL_ID_CONFIG_CLI: + model = &gen_onoff_cli_models[1]; + break; +#endif + case ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV: + model = &gen_onoff_srv_models[1]; + break; +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) + case ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI: + model = &gen_onoff_cli_models[2]; + break; +#endif + case ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_CLI: + model = &test_perf_cli_models[0]; + break; + case ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_SRV: + model = &test_perf_srv_models[0]; + break; + } + + return model; +} + +esp_ble_mesh_comp_t *ble_mesh_get_component(uint16_t model_id) +{ + esp_ble_mesh_comp_t *comp = NULL; + + switch (model_id) { + case ESP_BLE_MESH_MODEL_ID_CONFIG_SRV: + comp = &config_server_comp; + break; + case ESP_BLE_MESH_MODEL_ID_CONFIG_CLI: + comp = &config_client_comp; + break; + case ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV: + comp = &gen_onoff_srv_comp; + break; +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) + case ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI: + comp = &gen_onoff_cli_comp; + break; +#endif + case ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_CLI: + comp = &test_perf_cli_comp; + break; + case ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_SRV: + comp = &test_perf_srv_comp; + break; + } + + return comp; +} + +void ble_mesh_node_init() +{ + uint16_t i; + + for (i = 0; i < NODE_MAX_GROUP_CONFIG; i++) { + ble_mesh_node_prestore_params[i].net_idx = 0xFFFF; + ble_mesh_node_prestore_params[i].unicast_addr = 0xFFFF; + } + + ble_mesh_node_sema = xSemaphoreCreateMutex(); + if (!ble_mesh_node_sema) { + ESP_LOGE(TAG, "%s failed to init, failed to create mesh node semaphore", __func__); + } +} + +void ble_mesh_set_node_prestore_params(uint16_t netkey_index, uint16_t unicast_addr) +{ + uint16_t i; + xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY); + for (i = 0; i < NODE_MAX_GROUP_CONFIG; i++) { + if (ble_mesh_node_prestore_params[i].net_idx != 0xFFFF && ble_mesh_node_prestore_params[i].unicast_addr != 0xFFFF) { + ble_mesh_node_prestore_params[i].net_idx = netkey_index; + ble_mesh_node_prestore_params[i].unicast_addr = unicast_addr; + } + } + xSemaphoreGive(ble_mesh_node_sema); +} + +void ble_mesh_create_send_data(char *data, uint16_t byte_num, uint16_t sequence_num, uint32_t opcode) +{ + uint16_t i; + + // first two bytes are sequence num, third is type + data[0] = sequence_num >> 8; + data[1] = sequence_num & 0xFF; + switch (opcode) { + case ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_GET: + data[2] = VENDOR_MODEL_PERF_OPERATION_TYPE_GET; + break; + case ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET: + data[2] = VENDOR_MODEL_PERF_OPERATION_TYPE_SET; + break; + case ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK: + data[2] = VENDOR_MODEL_PERF_OPERATION_TYPE_SET_UNACK; + break; + } + + for (i = 3; i < byte_num; i++) { + data[i] = i; + } +} + +void ble_mesh_test_performance_client_model_get() +{ + uint32_t i, j; + uint32_t sum_time = 0; + + xSemaphoreTake(ble_mesh_test_perf_sema, portMAX_DELAY); + + for (i = 0, j = 0; i < test_perf_statistics.test_num; i++) { + if (test_perf_statistics.time[i] != 0) { + sum_time += test_perf_statistics.time[i]; + j += 1; + } else { + continue; + } + + if (j == test_perf_statistics.test_num - 1) { + break; + } + } + + ESP_LOGI(TAG, "VendorModel:Statistics,%d,%d\n", + test_perf_statistics.statistics, (sum_time / (j + 1))); + + xSemaphoreGive(ble_mesh_test_perf_sema); +} + +void ble_mesh_test_performance_client_model_get_received_percent() +{ + uint32_t i, j; + uint32_t max_time = 1400; + uint32_t min_time = 0; + uint32_t time_level_num = 0; + typedef struct { + uint16_t time_level; + uint16_t time_num; + } statistics_time_performance; + statistics_time_performance *statistics_time_percent; + + xSemaphoreTake(ble_mesh_test_perf_sema, portMAX_DELAY); + + time_level_num = ((max_time - min_time) / 50 + 1); + statistics_time_percent = malloc(sizeof(statistics_time_performance) * time_level_num); + + for (j = 0; j < time_level_num; j++) { + statistics_time_percent[j].time_level = min_time + 50 * j; + statistics_time_percent[j].time_num = 0; + } + + for (i = 0; i < test_perf_statistics.test_num; i++) { + for (j = 0; j < time_level_num; j++) { + if (test_perf_statistics.time[i] > max_time) { + j -= 1; + break; + } + if (test_perf_statistics.time[i] >= min_time + 50 * j + && test_perf_statistics.time[i] < min_time + 50 * (j + 1)) { + statistics_time_percent[j].time_num += 1; + break; + } + } + } + + // for script match + ESP_LOGI(TAG, "VendorModel:Statistics"); + for (j = 0; j < time_level_num; j++) { + printf(",%d:%d", statistics_time_percent[j].time_level, statistics_time_percent[j].time_num); + } + printf("\n"); + + free(statistics_time_percent); + xSemaphoreGive(ble_mesh_test_perf_sema); +} + +void ble_mesh_test_performance_client_model_accumulate_statistics(uint32_t value) +{ + xSemaphoreTake(ble_mesh_test_perf_sema, portMAX_DELAY); + test_perf_statistics.statistics += value; + xSemaphoreGive(ble_mesh_test_perf_sema); +} + +int ble_mesh_test_performance_client_model_accumulate_time(uint16_t time, uint8_t *data, uint8_t ack_ttl, uint16_t length) +{ + uint16_t i; + uint16_t sequence_num = 0; + uint16_t node_received_ttl = 0; + xSemaphoreTake(ble_mesh_test_perf_sema, portMAX_DELAY); + + // received fail + if (length != test_perf_statistics.test_length) { + xSemaphoreGive(ble_mesh_test_perf_sema); + return 1; + } + + if (data != NULL) { + sequence_num = (data[0] << 8) | data[1]; + if (data[2] == VENDOR_MODEL_PERF_OPERATION_TYPE_SET) { + node_received_ttl = data[3]; + } + } + + for (i = 0; i < test_perf_statistics.test_num; i++) { + if (test_perf_statistics.package_index[i] == sequence_num) { + xSemaphoreGive(ble_mesh_test_perf_sema); + return 1; + } + } + + for (i = 0; i < test_perf_statistics.test_num; i++) { + if (test_perf_statistics.package_index[i] == 0) { + test_perf_statistics.package_index[i] = sequence_num; + if (data[2] == VENDOR_MODEL_PERF_OPERATION_TYPE_SET) { + if (node_received_ttl == test_perf_statistics.ttl && ack_ttl == test_perf_statistics.ttl) { + test_perf_statistics.time[i] = time; + } else { + test_perf_statistics.time[i] = 0; + } + } else if (data[2] == VENDOR_MODEL_PERF_OPERATION_TYPE_SET_UNACK) { + test_perf_statistics.time[i] = time; + } + break; + } + } + + xSemaphoreGive(ble_mesh_test_perf_sema); + return 0; +} + +int ble_mesh_test_performance_client_model_init(uint16_t node_num, uint32_t test_num, uint8_t ttl) +{ + uint16_t i; + + // malloc time + test_perf_statistics.time = malloc(test_num * sizeof(uint16_t)); + if (test_perf_statistics.time == NULL) { + ESP_LOGE(TAG, " %s %d, malloc fail\n", __func__, __LINE__); + return 1; + } + + test_perf_statistics.package_index = malloc(test_num * sizeof(uint16_t)); + if (test_perf_statistics.package_index == NULL) { + ESP_LOGE(TAG, " %s %d, malloc fail\n", __func__, __LINE__); + } + for (i = 0; i < test_num; i++) { + test_perf_statistics.time[i] = 0; + test_perf_statistics.package_index[i] = 0; + } + + test_perf_statistics.test_num = test_num; + test_perf_statistics.node_num = node_num; + test_perf_statistics.ttl = ttl; + test_perf_statistics.statistics = 0; + return 0; +} + +void ble_mesh_test_performance_client_model_destroy() +{ + if (test_perf_statistics.time != NULL) { + free(test_perf_statistics.time); + } + + if (test_perf_statistics.package_index != NULL) { + free(test_perf_statistics.package_index); + } + + test_perf_statistics.test_num = 0; + test_perf_statistics.ttl = 0; + test_perf_statistics.node_num = 0; + test_perf_statistics.statistics = 0; +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.h b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.h new file mode 100644 index 0000000000..87bc7399a4 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.h @@ -0,0 +1,123 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BLE_MESH_ADAPTER_H_ +#define _BLE_MESH_ADAPTER_H_ + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#include "ble_mesh_console_lib.h" +#include "ble_mesh_cfg_srv_model.h" + +#define TAG "ble_mesh_prov_console" + +uint64_t start_time; +typedef enum { + VENDOR_MODEL_PERF_OPERATION_TYPE_GET = 1, + VENDOR_MODEL_PERF_OPERATION_TYPE_SET, + VENDOR_MODEL_PERF_OPERATION_TYPE_SET_UNACK +} ble_mesh_perf_operation_type; + +typedef struct { + uint8_t current; + uint8_t previous; + char *name; +} ble_mesh_node_status; + +typedef struct { + bool need_ack; + uint8_t ttl; + uint16_t length; + uint16_t test_num; + uint16_t address; + uint16_t app_idx; + uint16_t net_idx; + uint32_t opcode; + esp_ble_mesh_model_t *model; + esp_ble_mesh_dev_role_t device_role; +} ble_mesh_test_perf_throughput_data; + +typedef struct { + uint32_t statistics; + uint32_t test_num; + uint16_t test_length; + uint16_t node_num; + uint16_t *time; + uint16_t *package_index; + uint8_t ttl; +} ble_mesh_performance_statistics_t; +ble_mesh_performance_statistics_t test_perf_statistics; + +#define SEND_MESSAGE_TIMEOUT (30000/portTICK_RATE_MS) + +extern SemaphoreHandle_t ble_mesh_node_sema; +extern SemaphoreHandle_t ble_mesh_test_perf_send_sema; +extern SemaphoreHandle_t ble_mesh_test_perf_sema; + +#define arg_int_to_value(src_msg, dst_msg, message) do { \ + if (src_msg->count != 0) {\ + ESP_LOGD(TAG, " %s, %s\n", __func__, message);\ + dst_msg = src_msg->ival[0];\ + } \ +} while(0) \ + +#define ble_mesh_node_get_value(index, key, value) do { \ + uint16_t _index = 0; \ + xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY); \ + for (_index = 0; _index < NODE_MAX_GROUP_CONFIG; _index) { \ + if (node_set_prestore_params[_index].key == value) { \ + break; \ + } \ + } \ + index = _index; \ + xSemaphoreGive(ble_mesh_node_sema); \ +} while(0) \ + +#define ble_mesh_node_set_state(status) do { \ + xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY); \ + node_status.previous = node_status.current; \ + node_status.current = status; \ + xSemaphoreGive(ble_mesh_node_sema); \ +}while(0) \ + +#define ble_mesh_node_get_state(status) do { \ + xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY); \ + status = node_status.previous; \ + xSemaphoreGive(ble_mesh_node_sema); \ +}while(0) \ + +#define ble_mesh_callback_check_err_code(err_code, message) do { \ + if (err_code == ESP_OK) { \ + ESP_LOGI(TAG, "%s,OK\n", message); \ + } else { \ + ESP_LOGI(TAG, "%s,Fail,%d\n", message, err_code); \ + } \ +}while(0) \ + +void ble_mesh_node_init(); +void ble_mesh_set_node_prestore_params(uint16_t netkey_index, uint16_t unicast_addr); + +esp_ble_mesh_model_t *ble_mesh_get_model(uint16_t model_id); +esp_ble_mesh_comp_t *ble_mesh_get_component(uint16_t model_id); +void ble_mesh_create_send_data(char *data, uint16_t byte_num, uint16_t sequence_num, uint32_t opcode); + +void ble_mesh_test_performance_client_model_get(); +void ble_mesh_test_performance_client_model_get_received_percent(); +void ble_mesh_test_performance_client_model_accumulate_statistics(uint32_t value); +int ble_mesh_test_performance_client_model_accumulate_time(uint16_t time, uint8_t *data, uint8_t ack_ttl, uint16_t length); +int ble_mesh_test_performance_client_model_init(uint16_t node_num, uint32_t test_num, uint8_t ttl); +void ble_mesh_test_performance_client_model_destroy(); + +#endif //_BLE_MESH_ADAPTER_H_ \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.c new file mode 100644 index 0000000000..9518e74b42 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.c @@ -0,0 +1,205 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ble_mesh_cfg_srv_model.h" + +uint8_t dev_uuid[16] = {0xdd, 0xdd}; + +#if CONFIG_BLE_MESH_NODE +esp_ble_mesh_prov_t prov = { + .uuid = dev_uuid, +}; +#endif //CONFIG_BLE_MESH_NODE + +#if CONFIG_BLE_MESH_PROVISIONER +esp_ble_mesh_prov_t prov = { + .prov_uuid = dev_uuid, + .prov_unicast_addr = 0x0001, + .prov_start_address = 0x0005, + .prov_attention = 0x00, + .prov_algorithm = 0x00, + .prov_pub_key_oob = 0x00, + .prov_static_oob_val = NULL, + .prov_static_oob_len = 0x00, + .flags = 0x00, + .iv_index = 0x00, +}; +#endif //CONFIG_BLE_MESH_PROVISIONER + +ESP_BLE_MESH_MODEL_PUB_DEFINE(model_pub_config, 2 + 1, ROLE_PROVISIONER); + +esp_ble_mesh_model_pub_t vendor_model_pub_config; + +// configure server module +esp_ble_mesh_cfg_srv_t cfg_srv = { + .relay = ESP_BLE_MESH_RELAY_ENABLED, + .beacon = ESP_BLE_MESH_BEACON_ENABLED, +#if defined(CONFIG_BLE_MESH_FRIEND) + .friend_state = ESP_BLE_MESH_FRIEND_ENABLED, +#else + .friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED, +#endif +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_ENABLED, +#else + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED, +#endif + .default_ttl = 7, + + /* 3 transmissions with 20ms interval */ + .net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20), + .relay_retransmit = ESP_BLE_MESH_TRANSMIT(0, 20), +}; + +esp_ble_mesh_model_t config_server_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), +}; + +esp_ble_mesh_elem_t config_server_elements[] = { + ESP_BLE_MESH_ELEMENT(0, config_server_models, ESP_BLE_MESH_MODEL_NONE), +}; + +esp_ble_mesh_comp_t config_server_comp = { + .cid = CID_ESP, + .elements = config_server_elements, + .element_count = ARRAY_SIZE(config_server_elements), +}; + +// config client model +esp_ble_mesh_model_t config_client_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), + ESP_BLE_MESH_MODEL_CFG_CLI(&cfg_cli), +}; + +esp_ble_mesh_elem_t config_client_elements[] = { + ESP_BLE_MESH_ELEMENT(0, config_client_models, ESP_BLE_MESH_MODEL_NONE), +}; + +esp_ble_mesh_comp_t config_client_comp = { + .cid = CID_ESP, + .elements = config_client_elements, + .element_count = ARRAY_SIZE(config_client_elements), +}; + +// configure special module +esp_ble_mesh_model_op_t gen_onoff_srv_model_op_config[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET, 0), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, 2), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2), + ESP_BLE_MESH_MODEL_OP_END, +}; + +esp_ble_mesh_model_t gen_onoff_srv_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, gen_onoff_srv_model_op_config, &model_pub_config, NULL), +}; + +esp_ble_mesh_elem_t gen_onoff_srv_elements[] = { + ESP_BLE_MESH_ELEMENT(0, gen_onoff_srv_models, ESP_BLE_MESH_MODEL_NONE), +}; + +esp_ble_mesh_comp_t gen_onoff_srv_comp = { + .cid = CID_ESP, + .elements = gen_onoff_srv_elements, + .element_count = ARRAY_SIZE(gen_onoff_srv_elements), +}; + +// config generic onoff client +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) + +esp_ble_mesh_client_t gen_onoff_cli; + +esp_ble_mesh_model_t gen_onoff_cli_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), + ESP_BLE_MESH_MODEL_CFG_CLI(&cfg_cli), + ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(&model_pub_config, &gen_onoff_cli), +}; + +esp_ble_mesh_elem_t gen_onoff_cli_elements[] = { + ESP_BLE_MESH_ELEMENT(0, gen_onoff_cli_models, ESP_BLE_MESH_MODEL_NONE), +}; + +esp_ble_mesh_comp_t gen_onoff_cli_comp = { + .cid = CID_ESP, + .elements = gen_onoff_cli_elements, + .element_count = ARRAY_SIZE(gen_onoff_cli_elements), +}; +#endif //CONFIG_BLE_MESH_GENERIC_ONOFF_CLI + +//CONFIG VENDOR MODEL TEST PERFORMANCE +#define ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_SRV 0x2000 +#define ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_CLI 0x2001 + +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_GET ESP_BLE_MESH_MODEL_OP_3(0x01, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET ESP_BLE_MESH_MODEL_OP_3(0x02, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK ESP_BLE_MESH_MODEL_OP_3(0x03, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS ESP_BLE_MESH_MODEL_OP_3(0x04, CID_ESP) + +esp_ble_mesh_client_op_pair_t test_perf_cli_op_pair[] = { + {ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_GET, ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS}, + {ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET, ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS}, + {ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK, ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS}, +}; + +esp_ble_mesh_client_t test_perf_cli = { + .op_pair_size = ARRAY_SIZE(test_perf_cli_op_pair), + .op_pair = test_perf_cli_op_pair, +}; + +esp_ble_mesh_model_op_t test_perf_srv_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_GET, 1), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET, 1), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK, 1), + ESP_BLE_MESH_MODEL_OP_END, +}; + +esp_ble_mesh_model_op_t test_perf_cli_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS, 1), + ESP_BLE_MESH_MODEL_OP_END, +}; + +esp_ble_mesh_model_t config_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&cfg_srv), + ESP_BLE_MESH_MODEL_CFG_CLI(&cfg_cli), +}; + +esp_ble_mesh_model_t test_perf_cli_models[] = { + ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_CLI, + test_perf_cli_op, &vendor_model_pub_config, &test_perf_cli), +}; + +esp_ble_mesh_elem_t test_perf_cli_elements[] = { + ESP_BLE_MESH_ELEMENT(0, config_models, test_perf_cli_models), +}; + +esp_ble_mesh_comp_t test_perf_cli_comp = { + .cid = CID_ESP, + .elements = test_perf_cli_elements, + .element_count = ARRAY_SIZE(test_perf_cli_elements), +}; + +esp_ble_mesh_model_t test_perf_srv_models[] = { + ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_SRV, + test_perf_srv_op, NULL, NULL), +}; + +esp_ble_mesh_elem_t test_perf_srv_elements[] = { + ESP_BLE_MESH_ELEMENT(0, config_models, test_perf_srv_models), +}; + +esp_ble_mesh_comp_t test_perf_srv_comp = { + .cid = CID_ESP, + .elements = test_perf_srv_elements, + .element_count = ARRAY_SIZE(test_perf_srv_elements), +}; diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.h b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.h new file mode 100644 index 0000000000..9e43333eb9 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.h @@ -0,0 +1,107 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BLE_MESH_CFG_SRV_MODEL_H_ +#define _BLE_MESH_CFG_SRV_MODEL_H_ + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_config_model_api.h" + +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) +#include "esp_ble_mesh_generic_model_api.h" +#endif //CONFIG_BLE_MESH_GENERIC_ONOFF_CLI + +#define NODE_MAX_GROUP_CONFIG 3 +#define CID_ESP 0x02C4 + +extern uint8_t dev_uuid[16]; + +typedef struct { + uint16_t net_idx; + uint16_t unicast_addr; +} ble_mesh_node_config_params; +ble_mesh_node_config_params ble_mesh_node_prestore_params[NODE_MAX_GROUP_CONFIG]; + +extern esp_ble_mesh_prov_t prov; + +extern esp_ble_mesh_model_pub_t vendor_model_pub_config; + +// configure server module +extern esp_ble_mesh_cfg_srv_t cfg_srv; + +extern esp_ble_mesh_model_t config_server_models[]; + +extern esp_ble_mesh_elem_t config_server_elements[]; + +extern esp_ble_mesh_comp_t config_server_comp; + +// config client model +esp_ble_mesh_client_t cfg_cli; +extern esp_ble_mesh_model_t config_client_models[]; + +extern esp_ble_mesh_elem_t config_client_elements[]; + +extern esp_ble_mesh_comp_t config_client_comp; + +// configure special module +extern esp_ble_mesh_model_op_t gen_onoff_srv_model_op_config[]; + +extern esp_ble_mesh_model_t gen_onoff_srv_models[]; + +extern esp_ble_mesh_elem_t gen_onoff_srv_elements[]; + +extern esp_ble_mesh_comp_t gen_onoff_srv_comp; + +// config generic onoff client +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) + +extern esp_ble_mesh_client_t gen_onoff_cli; + +extern esp_ble_mesh_model_t gen_onoff_cli_models[]; + +extern esp_ble_mesh_elem_t gen_onoff_cli_elements[]; + +extern esp_ble_mesh_comp_t gen_onoff_cli_comp; +#endif //CONFIG_BLE_MESH_GENERIC_ONOFF_CLI + +//CONFIG VENDOR MODEL TEST PERFORMANCE +#define ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_SRV 0x2000 +#define ESP_BLE_MESH_VND_MODEL_ID_TEST_PERF_CLI 0x2001 + +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_GET ESP_BLE_MESH_MODEL_OP_3(0x01, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET ESP_BLE_MESH_MODEL_OP_3(0x02, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK ESP_BLE_MESH_MODEL_OP_3(0x03, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS ESP_BLE_MESH_MODEL_OP_3(0x04, CID_ESP) + +extern esp_ble_mesh_client_t test_perf_cli; + +extern esp_ble_mesh_model_op_t test_perf_srv_op[]; + +extern esp_ble_mesh_model_op_t test_perf_cli_op[]; + +extern esp_ble_mesh_model_t config_models[]; + +extern esp_ble_mesh_model_t test_perf_cli_models[]; + +extern esp_ble_mesh_elem_t test_perf_cli_elements[]; + +extern esp_ble_mesh_comp_t test_perf_cli_comp; + +extern esp_ble_mesh_model_t test_perf_srv_models[]; + +extern esp_ble_mesh_elem_t test_perf_srv_elements[]; + +extern esp_ble_mesh_comp_t test_perf_srv_comp; + +#endif //_BLE_MESH_CFG_SRV_MODEL_H_ diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_decl.h b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_decl.h new file mode 100644 index 0000000000..90e52a61d9 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_decl.h @@ -0,0 +1,38 @@ +/* Console example — declarations of command registration functions. + + This example code is in the Public Domain (or CC0 licensed, at your option). + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#pragma once + +#include "esp_ble_mesh_defs.h" + +// Register system functions +void register_system(); + +// Register bluetooth +void register_bluetooth(); + +// Register mesh node cmd +void ble_mesh_register_mesh_node(); + +// Register Test Perf client cmd +void ble_mesh_register_mesh_test_performance_client(); + +#if (CONFIG_BLE_MESH_CFG_CLI) +// Register mesh config client operation cmd +void ble_mesh_register_configuration_client_model(); +#endif + +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) +// Register mesh client operation cmd +void ble_mesh_register_gen_onoff_client(); +#endif + +#if CONFIG_BLE_MESH_PROVISIONER +// Regitster mesh provisioner cmd +void ble_mesh_register_mesh_provisioner(); +#endif diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.c new file mode 100644 index 0000000000..d15ee3adc1 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.c @@ -0,0 +1,124 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ble_mesh_console_lib.h" + +static int hex2num(char c); +static int hex2byte(const char *hex); + +static int hex2num(char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +static int hex2byte(const char *hex) +{ + int a, b; + a = hex2num(*hex++); + if (a < 0) { + return -1; + } + b = hex2num(*hex++); + if (b < 0) { + return -1; + } + return (a << 4) | b; +} + +int hexstr_2_bin(const char *hex, uint8_t *buf, uint32_t len) +{ + uint32_t i; + int a; + const char *ipos = hex; + uint8_t *opos = buf; + + for (i = 0; i < len; i++) { + a = hex2byte(ipos); + if (a < 0) { + return -1; + } + *opos ++ = a; + ipos += 2; + } + return 0; +} + +int get_value_string(char *value_in, char *buf) +{ + int result = -1; + + uint16_t length = strlen(value_in); + + if (length > 2) { + if (value_in[0] == '0' && value_in[1] == 'x') { + buf[(length - 2) / 2] = 0; + result = hexstr_2_bin(&value_in[2], (uint8_t *)buf, (length - 2) / 2); + length = (length - 2) / 2; + } else { + strcpy(buf, value_in); + result = 0; + } + } else { + strcpy(buf, value_in); + result = 0; + } + return result; +} + +bool str_2_mac(uint8_t *str, uint8_t *dest) +{ + uint8_t loop = 0; + uint8_t tmp = 0; + uint8_t *src_p = str; + + if (strlen((char *)src_p) != 17) { // must be like 12:34:56:78:90:AB + return false; + } + + for (loop = 0; loop < 17 ; loop++) { + if (loop % 3 == 2) { + if (src_p[loop] != ':') { + return false; + } + + continue; + } + + if ((src_p[loop] >= '0') && (src_p[loop] <= '9')) { + tmp = tmp * 16 + src_p[loop] - '0'; + } else if ((src_p[loop] >= 'A') && (src_p[loop] <= 'F')) { + tmp = tmp * 16 + src_p[loop] - 'A' + 10; + } else if ((src_p[loop] >= 'a') && (src_p[loop] <= 'f')) { + tmp = tmp * 16 + src_p[loop] - 'a' + 10; + } else { + return false; + } + + if (loop % 3 == 1) { + *dest++ = tmp; + tmp = 0; + } + } + + return true; +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.h b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.h new file mode 100644 index 0000000000..11dd05cb37 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.h @@ -0,0 +1,29 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BLE_MESH_CONSOLE_LIB_H_ +#define _BLE_MESH_CONSOLE_LIB_H_ + +#include +#include + +#include "esp_system.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" + +bool str_2_mac(uint8_t *str, uint8_t *dest); +int hexstr_2_bin(const char *hex, uint8_t *buf, uint32_t len); +int get_value_string(char *value_in, char *buf); + +#endif //_BLE_MESH_CONSOLE_LIB_H_ \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_main.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_main.c new file mode 100644 index 0000000000..f7c4eacf6b --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_main.c @@ -0,0 +1,228 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "esp_system.h" +#include "esp_log.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" +#include "driver/uart.h" +#include "linenoise/linenoise.h" +#include "argtable3/argtable3.h" + +#include "esp_vfs_fat.h" +#include "nvs.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" + +#include "ble_mesh_console_decl.h" + +#define TAG "ble_mesh_test" + +#if CONFIG_STORE_HISTORY + +#define MOUNT_PATH "/data" +#define HISTORY_PATH MOUNT_PATH "/history.txt" + +static void initialize_filesystem() +{ + static wl_handle_t wl_handle; + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = true + }; + esp_err_t err = esp_vfs_fat_spiflash_mount(MOUNT_PATH, "storage", &mount_config, &wl_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount FATFS (0x%x)", err); + return; + } +} +#endif // CONFIG_STORE_HISTORY + +static void initialize_console() +{ + /* Disable buffering on stdin and stdout */ + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + + /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install(CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + /* Initialize the console */ + esp_console_config_t console_config = { + .max_cmdline_args = 20, + .max_cmdline_length = 256, +#if CONFIG_LOG_COLORS + .hint_color = atoi(LOG_COLOR_CYAN) +#endif + }; + ESP_ERROR_CHECK( esp_console_init(&console_config) ); + + /* Configure linenoise line completion library */ + /* Enable multiline editing. If not set, long commands will scroll within + * a single line. + */ + linenoiseSetMultiLine(1); + + /* Tell linenoise where to get command completions and hints */ + linenoiseSetCompletionCallback(&esp_console_get_completion); + linenoiseSetHintsCallback((linenoiseHintsCallback *) &esp_console_get_hint); + + /* Set command history size */ + linenoiseHistorySetMaxLen(100); + +#if CONFIG_STORE_HISTORY + /* Load command history from filesystem */ + linenoiseHistoryLoad(HISTORY_PATH); +#endif +} + + +esp_err_t bluetooth_init(void) +{ + esp_err_t ret; + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "%s failed to initialize controller\n", __func__); + return ret; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "%s failed to enable controller\n", __func__); + return ret; + } + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(TAG, "%s failed to initialize bluetooth\n", __func__); + return ret; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "%s failed to enable bluetooth\n", __func__); + return ret; + } + + return ret; +} + +void app_main(void) +{ + esp_err_t res; + + nvs_flash_init(); + + // init and enable bluetooth + res = bluetooth_init(); + if (res) { + printf("esp32_bluetooth_init failed (ret %d)", res); + } + +#if CONFIG_STORE_HISTORY + initialize_filesystem(); +#endif + + initialize_console(); + + /* Register commands */ + esp_console_register_help_command(); + register_system(); + register_bluetooth(); + ble_mesh_register_mesh_node(); + ble_mesh_register_mesh_test_performance_client(); +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) + ble_mesh_register_gen_onoff_client(); +#endif +#if (CONFIG_BLE_MESH_PROVISIONER) + ble_mesh_register_mesh_provisioner(); +#endif +#if (CONFIG_BLE_MESH_CFG_CLI) + ble_mesh_register_configuration_client_model(); +#endif + + + /* Prompt to be printed before each line. + * This can be customized, made dynamic, etc. + */ + const char *prompt = LOG_COLOR_I "esp32> " LOG_RESET_COLOR; + + printf("\n" + "This is an example of an ESP-IDF console component.\n" + "Type 'help' to get the list of commands.\n" + "Use UP/DOWN arrows to navigate through the command history.\n" + "Press TAB when typing a command name to auto-complete.\n"); + + /* Figure out if the terminal supports escape sequences */ + int probe_status = linenoiseProbe(); + if (probe_status) { /* zero indicates OK */ + printf("\n" + "Your terminal application does not support escape sequences.\n" + "Line editing and history features are disabled.\n" + "On Windows, try using Putty instead.\n"); + linenoiseSetDumbMode(1); +#if CONFIG_LOG_COLORS + /* Since the terminal doesn't support escape sequences, + * don't use color codes in the prompt. + */ + prompt = "esp32> "; +#endif //CONFIG_LOG_COLORS + } + + /* Main loop */ + while (true) { + /* Get a line using linenoise. + * The line is returned when ENTER is pressed. + */ + char *line = linenoise(prompt); + if (line == NULL) { /* Ignore empty lines */ + continue; + } + /* Add the command to the history */ + linenoiseHistoryAdd(line); +#if CONFIG_STORE_HISTORY + /* Save command history to filesystem */ + linenoiseHistorySave(HISTORY_PATH); +#endif + + /* Try to run the command */ + int ret; + esp_err_t err = esp_console_run(line, &ret); + if (err == ESP_ERR_NOT_FOUND) { + printf("Unrecognized command\n"); + } else if (err == ESP_ERR_INVALID_ARG) { + // command was empty + } else if (err == ESP_OK && ret != ESP_OK) { + printf("\nCommand returned non-zero error code: 0x%x\n", ret); + } else if (err != ESP_OK) { + printf("Internal error: 0x%x\n", err); + } + /* linenoise allocates line buffer on the heap, so need to free it */ + linenoiseFree(line); + } +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_system.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_system.c new file mode 100644 index 0000000000..d3dc6c12f9 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_system.c @@ -0,0 +1,179 @@ +/* Console example — various system commands + + This example code is in the Public Domain (or CC0 licensed, at your option). + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include + +#include "esp_log.h" +#include "esp_console.h" +#include "esp_system.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" +#include "argtable3/argtable3.h" + +#include "ble_mesh_console_decl.h" + +#if CONFIG_IDF_CMAKE +#define CONFIG_ESPTOOLPY_PORT "Which is choosen by Users for CMake" +#endif + +static void register_free(); +static void register_restart(); +static void register_make(); + +void register_system() +{ + register_free(); + register_restart(); + register_make(); +} + +/** 'restart' command restarts the program */ + +static int restart(int argc, char **argv) +{ + printf("%s, %s", __func__, "Restarting"); + esp_restart(); +} + +static void register_restart() +{ + const esp_console_cmd_t cmd = { + .command = "restart", + .help = "Restart the program", + .hint = NULL, + .func = &restart, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); +} + +/** 'free' command prints available heap memory */ + +static int free_mem(int argc, char **argv) +{ + printf("%d\n", esp_get_free_heap_size()); + return 0; +} + +static void register_free() +{ + const esp_console_cmd_t cmd = { + .command = "free", + .help = "Get the total size of heap memory available", + .hint = NULL, + .func = &free_mem, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); +} + +static int make(int argc, char **argv) +{ + int count = REG_READ(RTC_CNTL_STORE0_REG); + if (++count >= 3) { + printf("This is not the console you are looking for.\n"); + return 0; + } + REG_WRITE(RTC_CNTL_STORE0_REG, count); + + const char *make_output = + R"(LD build/console.elf +esptool.py v2.1-beta1 +)"; + + const char* flash_output[] = { +R"(Flashing binaries to serial port )" CONFIG_ESPTOOLPY_PORT R"( (app at offset 0x10000)... +esptool.py v2.1-beta1 +Connecting.... +)", +R"(Chip is ESP32D0WDQ6 (revision 0) +Uploading stub... +Running stub... +Stub running... +Changing baud rate to 921600 +Changed. +Configuring flash size... +Auto-detected Flash size: 4MB +Flash params set to 0x0220 +Compressed 15712 bytes to 9345... +)", +R"(Wrote 15712 bytes (9345 compressed) at 0x00001000 in 0.1 seconds (effective 1126.9 kbit/s)... +Hash of data verified. +Compressed 333776 bytes to 197830... +)", +R"(Wrote 333776 bytes (197830 compressed) at 0x00010000 in 3.3 seconds (effective 810.3 kbit/s)... +Hash of data verified. +Compressed 3072 bytes to 82... +)", +R"(Wrote 3072 bytes (82 compressed) at 0x00008000 in 0.0 seconds (effective 1588.4 kbit/s)... +Hash of data verified. +Leaving... +Hard resetting... +)" + }; + + const char* monitor_output = +R"(MONITOR +)" LOG_COLOR_W R"(--- idf_monitor on )" CONFIG_ESPTOOLPY_PORT R"( 115200 --- +--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H -- +)" LOG_RESET_COLOR; + + bool need_make = false; + bool need_flash = false; + bool need_monitor = false; + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "all") == 0) { + need_make = true; + } else if (strcmp(argv[i], "flash") == 0) { + need_make = true; + need_flash = true; + } else if (strcmp(argv[i], "monitor") == 0) { + need_monitor = true; + } else if (argv[i][0] == '-') { + /* probably -j option */ + } else if (isdigit((int) argv[i][0])) { + /* might be an argument to -j */ + } else { + printf("make: *** No rule to make target `%s'. Stop.\n", argv[i]); + /* Technically this is an error, but let's not spoil the output */ + return 0; + } + } + if (argc == 1) { + need_make = true; + } + if (need_make) { + printf("%s", make_output); + } + if (need_flash) { + size_t n_items = sizeof(flash_output) / sizeof(flash_output[0]); + for (int i = 0; i < n_items; ++i) { + printf("%s", flash_output[i]); + vTaskDelay(200/portTICK_PERIOD_MS); + } + } + if (need_monitor) { + printf("%s", monitor_output); + esp_restart(); + } + return 0; +} + +static void register_make() +{ + const esp_console_cmd_t cmd = { + .command = "make", + .help = NULL, /* Do not include in 'help' output */ + .hint = "all | flash | monitor", + .func = &make, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); +} + + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_cfg_client_cmd.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_cfg_client_cmd.c new file mode 100644 index 0000000000..fd2852de82 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_cfg_client_cmd.c @@ -0,0 +1,390 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_ble_mesh_networking_api.h" +#include "ble_mesh_adapter.h" + +#if (CONFIG_BLE_MESH_CFG_CLI) +typedef struct { + struct arg_str *action_type; + struct arg_str *set_state; + struct arg_int *opcode; + struct arg_int *unicast_address; + struct arg_int *appkey_index; + struct arg_int *mod_id; + struct arg_int *addr; + struct arg_int *cid; + struct arg_int *value; + struct arg_int *relay_statue; + struct arg_int *relay_transmit; + struct arg_int *net_idx; + struct arg_end *end; +} ble_mesh_client_get_set_state_t; +ble_mesh_client_get_set_state_t configuration_client_model_operation; + +void ble_mesh_register_configuration_client_model_command(); +void ble_mesh_configuration_client_model_cb(esp_ble_mesh_cfg_client_cb_event_t event, + esp_ble_mesh_cfg_client_cb_param_t *param); + +void ble_mesh_register_configuration_client_model() +{ + ble_mesh_register_configuration_client_model_command(); +} + +void ble_mesh_configuration_client_model_cb(esp_ble_mesh_cfg_client_cb_event_t event, + esp_ble_mesh_cfg_client_cb_param_t *param) +{ + uint32_t opcode; + ESP_LOGD(TAG, "enter %s, event = %x\n, error_code = %x\n", __func__, event, param->error_code); + + if (!param->error_code) { + opcode = param->params->opcode; + switch (event) { + case ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_BEACON_GET: + ESP_LOGI(TAG, "CfgClient:beacon,0x%x", param->status_cb.beacon_status.beacon); + break; + case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: + ESP_LOGI(TAG, "CfgClient:page,0x%x,len,0x%x", param->status_cb.comp_data_status.page, param->status_cb.comp_data_status.composition_data->len); + break; + case ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_GET: + ESP_LOGI(TAG, "CfgClient:ttl,0x%x", param->status_cb.default_ttl_status.default_ttl); + break; + case ESP_BLE_MESH_MODEL_OP_GATT_PROXY_GET: + ESP_LOGI(TAG, "CfgClient:proxy,0x%x", param->status_cb.gatt_proxy_status.gatt_proxy); + break; + case ESP_BLE_MESH_MODEL_OP_RELAY_GET: + ESP_LOGI(TAG, "CfgClient:relay,0x%x,retransmit,0x%x", param->status_cb.relay_status.relay, param->status_cb.relay_status.retransmit); + break; + case ESP_BLE_MESH_MODEL_OP_MODEL_PUB_GET: + if (param->status_cb.model_pub_status.status == ESP_OK) { + ESP_LOGI(TAG, "CfgClient:PublishGet,OK,0x%x", param->status_cb.model_pub_status.publish_addr); + } else { + ESP_LOGI(TAG, "CfgClient:PublishGet,Fail"); + } + + break; + case ESP_BLE_MESH_MODEL_OP_FRIEND_GET: + ESP_LOGI(TAG, "CfgClient:friend,0x%x", param->status_cb.friend_status.friend_state); + break; + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_GET: + if (param->status_cb.heartbeat_pub_status.status == ESP_OK) { + ESP_LOGI(TAG, "CfgClient:HeartBeatPubGet,OK,destination:0x%x,countlog:0x%x,periodlog:0x%x,ttl:0x%x,features:0x%x,net_idx:0x%x", + param->status_cb.heartbeat_pub_status.dst, param->status_cb.heartbeat_pub_status.count, param->status_cb.heartbeat_pub_status.period, + param->status_cb.heartbeat_pub_status.ttl, param->status_cb.heartbeat_pub_status.features, param->status_cb.heartbeat_pub_status.net_idx); + } else { + ESP_LOGI(TAG, "CfgClient:HeartBeatGet,Fail,%d", param->status_cb.heartbeat_pub_status.status); + } + break; + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_GET: + if (param->status_cb.heartbeat_sub_status.status == ESP_OK) { + ESP_LOGI(TAG, "CfgClient:HeartBeatSubGet,OK,source:0x%x,destination:0x%x, periodlog:0x%x,countlog:0x%x,minhops:0x%x,maxhops:0x%x", + param->status_cb.heartbeat_sub_status.src, param->status_cb.heartbeat_sub_status.dst, param->status_cb.heartbeat_sub_status.period, + param->status_cb.heartbeat_sub_status.count, param->status_cb.heartbeat_sub_status.min_hops, param->status_cb.heartbeat_sub_status.max_hops); + } else { + ESP_LOGI(TAG, "CfgClient:HeartBeatSubGet,Fail,%d", param->status_cb.heartbeat_sub_status.status); + } + break; + default: + ESP_LOGI(TAG, "Not supported config client get message opcode"); + break; + } + break; + case ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_BEACON_SET: + ESP_LOGI(TAG, "CfgClient:beacon,0x%x", param->status_cb.beacon_status.beacon); + break; + case ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_SET: + ESP_LOGI(TAG, "CfgClient:ttl,0x%x", param->status_cb.default_ttl_status.default_ttl); + break; + case ESP_BLE_MESH_MODEL_OP_GATT_PROXY_SET: + ESP_LOGI(TAG, "CfgClient:proxy,0x%x", param->status_cb.gatt_proxy_status.gatt_proxy); + break; + case ESP_BLE_MESH_MODEL_OP_RELAY_SET: + ESP_LOGI(TAG, "CfgClient:relay,0x%x, retransmit: 0x%x", param->status_cb.relay_status.relay, param->status_cb.relay_status.retransmit); + break; + case ESP_BLE_MESH_MODEL_OP_MODEL_PUB_SET: + if (param->status_cb.model_pub_status.status == ESP_OK) { + ESP_LOGI(TAG, "CfgClient:PublishSet,OK,0x%x", param->status_cb.model_pub_status.publish_addr); + } else { + ESP_LOGI(TAG, "CfgClient:PublishSet,Fail"); + } + break; + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD: + if (param->status_cb.model_sub_status.status == ESP_OK) { + ESP_LOGI(TAG, "CnfClient:SubAdd,OK,%x,%x", param->status_cb.model_sub_status.element_addr, param->status_cb.model_sub_status.sub_addr); + } else { + ESP_LOGI(TAG, "CnfClient:SubAdd,Fail,%x", param->status_cb.model_sub_status.status); + } + break; + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE: + if (param->status_cb.model_sub_status.status == ESP_OK) { + ESP_LOGI(TAG, "CnfClient:SubDel,OK,%x,%x", param->status_cb.model_sub_status.element_addr, param->status_cb.model_sub_status.sub_addr); + } else { + ESP_LOGI(TAG, "CnfClient:SubDel,Fail,%x", param->status_cb.model_sub_status.status); + } + break; + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_OVERWRITE: + break; + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_ADD: + break; + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_DELETE: + break; + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_OVERWRITE: + break; + case ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD: + if (param->status_cb.netkey_status.status == ESP_OK) { + ESP_LOGI(TAG, "CfgClient:NetKeyAdd,OK"); + } else { + ESP_LOGI(TAG, "CfgClient:NetKeyAdd,Fail,%d", param->status_cb.netkey_status.status); + } + break; + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: + if (param->status_cb.appkey_status.status == ESP_OK) { + ESP_LOGI(TAG, "CnfClient:AddAppkey,OK,%x,%x,%x", param->status_cb.appkey_status.net_idx, param->status_cb.appkey_status.app_idx, param->params->ctx.addr); + } else { + ESP_LOGI(TAG, "CnfClient:AddAppkey,Fail,%x", param->status_cb.appkey_status.status); + } + break; + case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND: + if (param->status_cb.model_app_status.status == ESP_OK) { + ESP_LOGI(TAG, "CnfClient:AppkeyBind,OK,%x,%x,%x", param->status_cb.model_app_status.app_idx, param->status_cb.model_app_status.model_id, param->params->ctx.addr); + } else { + ESP_LOGI(TAG, "CnfClient:AppkeyBind,Fail,%x", param->status_cb.model_app_status.status); + } + break; + case ESP_BLE_MESH_MODEL_OP_FRIEND_SET: + ESP_LOGI(TAG, "CfgClient:friend: 0x%x", param->status_cb.friend_status.friend_state); + break; + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET: + if (param->status_cb.heartbeat_pub_status.status == ESP_OK) { + ESP_LOGI(TAG, "CfgClient:HeartBeatPubSet,OK,destination:0x%x,countlog:0x%x, periodlog:0x%x,ttl:0x%x,features:0x%x,net_idx: 0x%x", + param->status_cb.heartbeat_pub_status.dst, param->status_cb.heartbeat_pub_status.count, param->status_cb.heartbeat_pub_status.period, + param->status_cb.heartbeat_pub_status.ttl, param->status_cb.heartbeat_pub_status.features, param->status_cb.heartbeat_pub_status.net_idx); + } else { + ESP_LOGI(TAG, "CfgClient:HeartBeatSet,Fail,%d", param->status_cb.heartbeat_pub_status.status); + } + break; + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET: + if (param->status_cb.heartbeat_sub_status.status == ESP_OK) { + ESP_LOGI(TAG, "CfgClient:HeartBeatSubSet,OK,source:0x%x,destination:0x%x, periodlog:0x%x,countlog:0x%x,minhops:0x%x,maxhops:0x%x", + param->status_cb.heartbeat_sub_status.src, param->status_cb.heartbeat_sub_status.dst, param->status_cb.heartbeat_sub_status.period, + param->status_cb.heartbeat_sub_status.count, param->status_cb.heartbeat_sub_status.min_hops, param->status_cb.heartbeat_sub_status.max_hops); + } else { + ESP_LOGI(TAG, "CfgClient:HeartBeatSubSet,Fail,%d", param->status_cb.heartbeat_sub_status.status); + } + break; + default: + ESP_LOGI(TAG, "Not supported config client set message opcode"); + break; + } + break; + case ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT: + ESP_LOGI(TAG, "CnfClient:Publish,OK"); + break; + case ESP_BLE_MESH_CFG_CLIENT_EVT_MAX: + ESP_LOGI(TAG, "CnfClient:MaxEvt"); + break; + case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT: + ESP_LOGI(TAG, "CfgClient:TimeOut"); + break; + default: + ESP_LOGI(TAG, "CfgClient:InvalidEvent"); + break; + } + } else { + ESP_LOGI(TAG, "CnfClient:Fail,%d", param->error_code); + } + ESP_LOGD(TAG, "exit %s \n", __func__); +} + +int ble_mesh_configuration_client_model_operation(int argc, char **argv) +{ + int err = ESP_OK; + const uint8_t *app_key = NULL; + esp_ble_mesh_cfg_default_ttl_set_t ttl_set; + esp_ble_mesh_cfg_gatt_proxy_set_t proxy_set; + esp_ble_mesh_cfg_app_key_add_t app_key_add; + esp_ble_mesh_cfg_model_pub_set_t mod_pub_set = { + .company_id = 0xFFFF, + .cred_flag = false, + .publish_period = 0, + .publish_retransmit = 0, + }; + esp_ble_mesh_cfg_model_sub_add_t mod_sub_add = { + .company_id = 0xFFFF, + }; + esp_ble_mesh_cfg_model_sub_delete_t mod_sub_del = { + .company_id = 0xFFFF, + }; + esp_ble_mesh_cfg_relay_set_t relay_set; + esp_ble_mesh_client_common_param_t client_common = { + .msg_role = ROLE_PROVISIONER, + .msg_timeout = 0, + .ctx.send_ttl = 7, + }; + esp_ble_mesh_cfg_client_get_state_t get_state = { + .comp_data_get.page = 0, + .model_pub_get.company_id = 0xFFFF, + }; + esp_ble_mesh_cfg_model_app_bind_t mod_app_bind = { + .company_id = 0xFFFF, + }; + + client_common.model = ble_mesh_get_model(ESP_BLE_MESH_MODEL_ID_CONFIG_CLI); + + ESP_LOGD(TAG, "enter %s \n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &configuration_client_model_operation); + if (nerrors != 0) { + arg_print_errors(stderr, configuration_client_model_operation.end, argv[0]); + return 1; + } + + if (configuration_client_model_operation.opcode->count != 0) { + client_common.opcode = configuration_client_model_operation.opcode->ival[0]; + } + + if (configuration_client_model_operation.net_idx->count != 0) { + client_common.ctx.net_idx = configuration_client_model_operation.net_idx->ival[0]; + app_key_add.net_idx = configuration_client_model_operation.net_idx->ival[0]; + } + + if (configuration_client_model_operation.unicast_address->count != 0) { + client_common.ctx.addr = configuration_client_model_operation.unicast_address->ival[0]; + get_state.model_pub_get.element_addr = configuration_client_model_operation.unicast_address->ival[0]; + mod_app_bind.element_addr = configuration_client_model_operation.unicast_address->ival[0]; + mod_sub_add.element_addr = configuration_client_model_operation.unicast_address->ival[0]; + mod_sub_del.element_addr = configuration_client_model_operation.unicast_address->ival[0]; + mod_pub_set.element_addr = configuration_client_model_operation.unicast_address->ival[0]; + } + + if (configuration_client_model_operation.appkey_index->count != 0) { + client_common.ctx.app_idx = configuration_client_model_operation.appkey_index->ival[0]; + mod_app_bind.model_app_idx = configuration_client_model_operation.appkey_index->ival[0]; + app_key_add.app_idx = configuration_client_model_operation.appkey_index->ival[0]; + mod_pub_set.publish_app_idx = configuration_client_model_operation.appkey_index->ival[0]; + } + + if (configuration_client_model_operation.value->count != 0) { + ttl_set.ttl = configuration_client_model_operation.value->ival[0]; + proxy_set.gatt_proxy = configuration_client_model_operation.value->ival[0]; + mod_pub_set.publish_ttl = configuration_client_model_operation.value->ival[0]; + } + + if (configuration_client_model_operation.addr->count != 0) { + mod_sub_del.sub_addr = configuration_client_model_operation.addr->ival[0]; + mod_sub_add.sub_addr = configuration_client_model_operation.addr->ival[0]; + mod_pub_set.publish_addr = configuration_client_model_operation.addr->ival[0]; + } + + if (configuration_client_model_operation.mod_id->count != 0) { + mod_app_bind.model_id = configuration_client_model_operation.mod_id->ival[0]; + mod_sub_add.model_id = configuration_client_model_operation.mod_id->ival[0]; + mod_sub_del.model_id = configuration_client_model_operation.mod_id->ival[0]; + get_state.model_pub_get.model_id = configuration_client_model_operation.mod_id->ival[0];; + mod_pub_set.model_id = configuration_client_model_operation.mod_id->ival[0]; + } + + if (configuration_client_model_operation.relay_statue->count != 0) { + relay_set.relay = configuration_client_model_operation.relay_statue->ival[0]; + mod_pub_set.publish_period = configuration_client_model_operation.relay_statue->ival[0]; + } + + if (configuration_client_model_operation.relay_transmit->count != 0) { + relay_set.relay_retransmit = configuration_client_model_operation.relay_transmit->ival[0]; + mod_pub_set.publish_retransmit = configuration_client_model_operation.relay_transmit->ival[0]; + } + + if (configuration_client_model_operation.cid->count != 0) { + mod_app_bind.company_id = configuration_client_model_operation.cid->ival[0]; + mod_sub_del.company_id = configuration_client_model_operation.cid->ival[0]; + mod_sub_add.company_id = configuration_client_model_operation.cid->ival[0]; + mod_pub_set.company_id = configuration_client_model_operation.cid->ival[0]; + } + + if (configuration_client_model_operation.action_type->count != 0) { + if (strcmp(configuration_client_model_operation.action_type->sval[0], "get") == 0) { + err = esp_ble_mesh_config_client_get_state(&client_common, &get_state); + } else if (strcmp(configuration_client_model_operation.action_type->sval[0], "set") == 0) { + if (configuration_client_model_operation.set_state->count != 0) { + if (strcmp(configuration_client_model_operation.set_state->sval[0], "appkey") == 0) { + app_key = esp_ble_mesh_provisioner_get_local_app_key(app_key_add.net_idx, app_key_add.app_idx); + if (app_key == NULL) { + ESP_LOGE(TAG, "CnfClient:AddAppkey,Fail,app key or network key NULL"); + return ESP_FAIL; + } else { + memcpy(app_key_add.app_key, app_key, 16); + } + err = esp_ble_mesh_config_client_set_state(&client_common, (esp_ble_mesh_cfg_client_set_state_t *)&app_key_add); + } else if (strcmp(configuration_client_model_operation.set_state->sval[0], "appbind") == 0) { + err = esp_ble_mesh_config_client_set_state(&client_common, (esp_ble_mesh_cfg_client_set_state_t *)&mod_app_bind); + } else if (strcmp(configuration_client_model_operation.set_state->sval[0], "ttl") == 0) { + err = esp_ble_mesh_config_client_set_state(&client_common, (esp_ble_mesh_cfg_client_set_state_t *)&ttl_set); + } else if (strcmp(configuration_client_model_operation.set_state->sval[0], "proxy") == 0) { + err = esp_ble_mesh_config_client_set_state(&client_common, (esp_ble_mesh_cfg_client_set_state_t *)&proxy_set); + } else if (strcmp(configuration_client_model_operation.set_state->sval[0], "subadd") == 0) { + err = esp_ble_mesh_config_client_set_state(&client_common, (esp_ble_mesh_cfg_client_set_state_t *)&mod_sub_add); + } else if (strcmp(configuration_client_model_operation.set_state->sval[0], "subdel") == 0) { + err = esp_ble_mesh_config_client_set_state(&client_common, (esp_ble_mesh_cfg_client_set_state_t *)&mod_sub_del); + } else if (strcmp(configuration_client_model_operation.set_state->sval[0], "relay") == 0) { + err = esp_ble_mesh_config_client_set_state(&client_common, (esp_ble_mesh_cfg_client_set_state_t *)&relay_set); + } else if (strcmp(configuration_client_model_operation.set_state->sval[0], "pubset") == 0) { + err = esp_ble_mesh_config_client_set_state(&client_common, (esp_ble_mesh_cfg_client_set_state_t *)&mod_pub_set); + } + } + } else if (strcmp(configuration_client_model_operation.action_type->sval[0], "reg") == 0) { + err = esp_ble_mesh_register_config_client_callback(ble_mesh_configuration_client_model_cb); + } + } + + if (err == ESP_OK) { + ESP_LOGI(TAG, "ConfigClient:OK"); + } else { + ESP_LOGI(TAG, "ConfigClient:Fail"); + } + + ESP_LOGD(TAG, "exit %s %d\n", __func__, err); + return err; +} + + +void ble_mesh_register_configuration_client_model_command() +{ + configuration_client_model_operation.action_type = arg_str1("z", NULL, "", "action type"); + configuration_client_model_operation.set_state = arg_str0("x", NULL, "", "set state"); + configuration_client_model_operation.opcode = arg_int0("o", NULL, "", "message opcode"); + configuration_client_model_operation.unicast_address = arg_int0("u", NULL, "

", "unicast address"); + configuration_client_model_operation.net_idx = arg_int0("n", NULL, "", "net work index"); + configuration_client_model_operation.appkey_index = arg_int0("i", NULL, "", "appkey index"); + configuration_client_model_operation.relay_statue = arg_int0("r", NULL, "", "relay statue"); + configuration_client_model_operation.relay_transmit = arg_int0("t", NULL, "", "relay transmit"); + configuration_client_model_operation.cid = arg_int0("c", NULL, "", "company id"); + configuration_client_model_operation.value = arg_int0("v", NULL, "", "value"); + configuration_client_model_operation.addr = arg_int0("a", NULL, "
", "address"); + configuration_client_model_operation.mod_id = arg_int0("m", NULL, "", "model id"); + configuration_client_model_operation.end = arg_end(1); + + const esp_console_cmd_t client_stconfiguration_client_model_operationate_cmd = { + .command = "bmccm", + .help = "ble mesh configuration client model", + .hint = NULL, + .func = &ble_mesh_configuration_client_model_operation, + .argtable = &configuration_client_model_operation, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&client_stconfiguration_client_model_operationate_cmd)); +} +#endif diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_gen_onoff_client_cmd.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_gen_onoff_client_cmd.c new file mode 100644 index 0000000000..70a28162f7 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_gen_onoff_client_cmd.c @@ -0,0 +1,180 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_timer.h" +#include "ble_mesh_adapter.h" + +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) +typedef struct { + struct arg_str *action_type; + struct arg_int *op_en; + struct arg_int *unicast_address; + struct arg_int *onoff_state; + struct arg_int *trans_id; + struct arg_int *trans_time; + struct arg_int *delay; + struct arg_int *opcode; + struct arg_int *appkey_idx; + struct arg_int *role; + struct arg_int *net_idx; + struct arg_end *end; +} ble_mesh_gen_onoff_state_t; +ble_mesh_gen_onoff_state_t gen_onoff_state; + +void ble_mesh_register_gen_onoff_client_command(); +void ble_mesh_generic_onoff_client_model_cb(esp_ble_mesh_generic_client_cb_event_t event, + esp_ble_mesh_generic_client_cb_param_t *param); + +void ble_mesh_register_gen_onoff_client() +{ + ble_mesh_register_gen_onoff_client_command(); +} + +void ble_mesh_generic_onoff_client_model_cb(esp_ble_mesh_generic_client_cb_event_t event, + esp_ble_mesh_generic_client_cb_param_t *param) +{ + uint32_t opcode = param->params->opcode; + + ESP_LOGD(TAG, "enter %s: event is %d, error code is %d, opcode is 0x%x\n", + __func__, event, param->error_code, opcode); + + switch (event) { + case ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT: { + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET: + if (param->error_code == ESP_OK) { + ESP_LOGI(TAG, "GenOnOffClient:GetStatus,OK,%d", param->status_cb.onoff_status.present_onoff); + } else { + ESP_LOGE(TAG, "GenOnOffClient:GetStatus,Fail,%d", param->error_code); + } + break; + default: + break; + } + break; + } + case ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT: { + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: + if (param->error_code == ESP_OK) { + ESP_LOGI(TAG, "GenOnOffClient:SetStatus,OK,%d", param->status_cb.onoff_status.present_onoff); + } else { + ESP_LOGE(TAG, "GenOnOffClient:SetStatus,Fail,%d", param->error_code); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK: + if (param->error_code == ESP_OK) { + ESP_LOGI(TAG, "GenOnOffClient:SetUNACK,OK"); + } else { + ESP_LOGE(TAG, "GenOnOffClient:SetUNACK,Fail,%d", param->error_code); + } + break; + default: + break; + } + break; + } + case ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT: { + if (param->error_code == ESP_OK) { + ESP_LOGI(TAG, "GenOnOffClient:Publish,OK"); + } else { + ESP_LOGE(TAG, "GenOnOffClient:Publish,Fail,%d", param->error_code); + } + break; + } + case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT: + ESP_LOGE(TAG, "GenOnOffClient:TimeOut,%d", param->error_code); + break; + case ESP_BLE_MESH_GENERIC_CLIENT_EVT_MAX: + ESP_LOGE(TAG, "GenONOFFClient:InvalidEvt,%d", param->error_code); + break; + default: + break; + } + ESP_LOGD(TAG, "exit %s \n", __func__); +} + +int ble_mesh_generic_onoff_client_model(int argc, char **argv) +{ + int err = ESP_OK; + esp_ble_mesh_generic_client_set_state_t gen_client_set; + esp_ble_mesh_generic_client_get_state_t gen_client_get; + esp_ble_mesh_client_common_param_t onoff_common = { + .msg_timeout = 0, + .ctx.send_ttl = 7, + }; + + ESP_LOGD(TAG, "enter %s\n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &gen_onoff_state); + if (nerrors != 0) { + arg_print_errors(stderr, gen_onoff_state.end, argv[0]); + return 1; + } + + onoff_common.model = ble_mesh_get_model(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI); + + arg_int_to_value(gen_onoff_state.appkey_idx, onoff_common.ctx.app_idx, "appkey_index"); + arg_int_to_value(gen_onoff_state.opcode, onoff_common.opcode, "opcode"); + arg_int_to_value(gen_onoff_state.role, onoff_common.msg_role, "role"); + arg_int_to_value(gen_onoff_state.unicast_address, onoff_common.ctx.addr, "address"); + arg_int_to_value(gen_onoff_state.net_idx, onoff_common.ctx.net_idx, "network key index"); + arg_int_to_value(gen_onoff_state.op_en, gen_client_set.onoff_set.op_en, "op_en"); + arg_int_to_value(gen_onoff_state.onoff_state, gen_client_set.onoff_set.onoff, "onoff"); + arg_int_to_value(gen_onoff_state.trans_id, gen_client_set.onoff_set.tid, "tid"); + arg_int_to_value(gen_onoff_state.trans_time, gen_client_set.onoff_set.trans_time, "trans_time"); + arg_int_to_value(gen_onoff_state.delay, gen_client_set.onoff_set.delay, "delay"); + + if (gen_onoff_state.action_type->count != 0) { + if (strcmp(gen_onoff_state.action_type->sval[0], "get") == 0) { + err = esp_ble_mesh_generic_client_get_state(&onoff_common, &gen_client_get); + } else if (strcmp(gen_onoff_state.action_type->sval[0], "set") == 0) { + err = esp_ble_mesh_generic_client_set_state(&onoff_common, &gen_client_set); + } else if (strcmp(gen_onoff_state.action_type->sval[0], "reg") == 0) { + err = esp_ble_mesh_register_generic_client_callback(ble_mesh_generic_onoff_client_model_cb); + if (err == ESP_OK) { + ESP_LOGI(TAG, "GenONOFFClient:Reg,OK"); + } + } + } + ESP_LOGD(TAG, "exit %s\n", __func__); + return err; +} + +void ble_mesh_register_gen_onoff_client_command() +{ + gen_onoff_state.action_type = arg_str1("z", NULL, "", "action type"); + gen_onoff_state.opcode = arg_int0("o", NULL, "", "message opcode"); + gen_onoff_state.appkey_idx = arg_int0("a", NULL, "", "appkey index"); + gen_onoff_state.role = arg_int0("r", NULL, "", "role"); + gen_onoff_state.unicast_address = arg_int0("u", NULL, "
", "unicast address"); + gen_onoff_state.net_idx = arg_int0("n", NULL, "", "network key index"); + gen_onoff_state.op_en = arg_int0("e", NULL, "", "whether optional parameters included"); + gen_onoff_state.onoff_state = arg_int0("s", NULL, "", "present onoff state"); + gen_onoff_state.trans_id = arg_int0("i", NULL, "", "transaction identifier"); + gen_onoff_state.trans_time = arg_int0("t", NULL, "
", "unicast address"); + test_perf_client_model.ttl = arg_int0("t", NULL, "", "ttl"); + test_perf_client_model.app_idx = arg_int0("a", NULL, "", "appkey index"); + test_perf_client_model.net_idx = arg_int0("i", NULL, "", "network key index"); + test_perf_client_model.dev_role = arg_int0("d", NULL, "", "device role"); + test_perf_client_model.dev_role->ival[0] = ROLE_PROVISIONER; + test_perf_client_model.end = arg_end(1); + + const esp_console_cmd_t test_perf_client_model_cmd = { + .command = "bmtpcvm", + .help = "ble mesh test performance client vendor model", + .hint = NULL, + .func = &ble_mesh_test_performance_client_model, + .argtable = &test_perf_client_model, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&test_perf_client_model_cmd)); + + test_perf_client_model_statistics.action_type = arg_str1("z", NULL, "", "action type"); + test_perf_client_model_statistics.test_size = arg_int0("s", NULL, "", "test size"); + test_perf_client_model_statistics.node_num = arg_int0("n", NULL, "", "node number"); + test_perf_client_model_statistics.ttl = arg_int0("l", NULL, "", "ttl"); + test_perf_client_model_statistics.end = arg_end(1); + + const esp_console_cmd_t test_perf_client_model_performance_cmd = { + .command = "bmcperf", + .help = "ble mesh client: test performance", + .hint = NULL, + .func = &ble_mesh_test_performance_client_model_performance, + .argtable = &test_perf_client_model_statistics, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&test_perf_client_model_performance_cmd)); +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_node_cmd.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_node_cmd.c new file mode 100644 index 0000000000..57e89605c9 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_node_cmd.c @@ -0,0 +1,476 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "soc/soc.h" +#include "esp_bt.h" +#include "esp_bt_device.h" + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_common_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_config_model_api.h" + +#include "ble_mesh_adapter.h" + +typedef struct { + struct arg_str *static_val; + struct arg_int *static_val_len; + struct arg_int *output_size; + struct arg_int *output_actions; + struct arg_int *input_size; + struct arg_int *input_actions; + struct arg_int *prov_start_address; + struct arg_end *end; +} ble_mesh_prov_t; +static ble_mesh_prov_t oob; + +typedef struct { + struct arg_int *model_type; + struct arg_int *config_index; + struct arg_int *pub_config; + struct arg_end *end; +} ble_mesh_comp_t; +static ble_mesh_comp_t component; + +typedef struct { + struct arg_int *bearer; + struct arg_int *enable; + struct arg_end *end; +} ble_mesh_bearer_t; +static ble_mesh_bearer_t bearer; + +typedef struct { + struct arg_str *action_type; + struct arg_int *tx_sense_power; + struct arg_end *end; +} ble_mesh_tx_sense_power; +static ble_mesh_tx_sense_power power_set; + +ble_mesh_node_status node_status = { + .previous = 0x0, + .current = 0x0, +}; + +SemaphoreHandle_t ble_mesh_node_sema; + +void ble_mesh_register_node_cmd(); +// Register callback function +void ble_mesh_prov_cb(esp_ble_mesh_prov_cb_event_t event, esp_ble_mesh_prov_cb_param_t *param); +void ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, esp_ble_mesh_model_cb_param_t *param); + + +void ble_mesh_register_mesh_node() +{ + ble_mesh_register_node_cmd(); +} + +int ble_mesh_register_node_cb() +{ + ESP_LOGD(TAG, "enter %s\n", __func__); + ble_mesh_node_init(); + esp_ble_mesh_register_prov_callback(ble_mesh_prov_cb); + esp_ble_mesh_register_custom_model_callback(ble_mesh_model_cb); + ESP_LOGI(TAG, "Node:Reg,OK"); + ESP_LOGD(TAG, "exit %s\n", __func__); + return 0; +} + +void ble_mesh_prov_cb(esp_ble_mesh_prov_cb_event_t event, esp_ble_mesh_prov_cb_param_t *param) +{ + ESP_LOGD(TAG, "enter %s, event = %d", __func__, event); + switch (event) { + case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT: + ble_mesh_callback_check_err_code(param->prov_register_comp.err_code, "Provisioning:Register"); + break; + case ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_prov_enable_comp.err_code, "Node:EnBearer"); + break; + case ESP_BLE_MESH_NODE_PROV_DISABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_prov_disable_comp.err_code, "Node:DisBearer"); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT: + ESP_LOGI(TAG, "Node:LinkOpen,OK,%d", param->node_prov_link_open.bearer); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT: + ESP_LOGI(TAG, "Node:LinkClose,OK,%d", param->node_prov_link_close.bearer); + break; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT: + ESP_LOGI(TAG, "Node:OutPut,%d,%d", param->node_prov_output_num.action, param->node_prov_output_num.number); + break; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT: + ESP_LOGI(TAG, "Node:OutPutStr,%s", param->node_prov_output_str.string); + break; + case ESP_BLE_MESH_NODE_PROV_INPUT_EVT: + ESP_LOGI(TAG, "Node:InPut,%d,%d", param->node_prov_input.action, param->node_prov_input.size); + break; + case ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT: + ESP_LOGI(TAG, "Node:OK,%d,%d", param->node_prov_complete.net_idx, param->node_prov_complete.addr); + ble_mesh_set_node_prestore_params(param->node_prov_complete.net_idx, param->node_prov_complete.addr); + break; + case ESP_BLE_MESH_NODE_PROV_RESET_EVT: + ESP_LOGI(TAG, "Node:Reset"); + break; + case ESP_BLE_MESH_NODE_PROV_INPUT_NUMBER_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_prov_input_num_comp.err_code, "Node:InputNum"); + break; + case ESP_BLE_MESH_NODE_PROV_INPUT_STRING_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_prov_input_str_comp.err_code, "Node:InputStr"); + break; + case ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_set_unprov_dev_name_comp.err_code, "Node:SetName"); + break; + case ESP_BLE_MESH_NODE_PROXY_IDENTITY_ENABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_proxy_identity_enable_comp.err_code, "Node:ProxyIndentity"); + break; + case ESP_BLE_MESH_NODE_PROXY_GATT_ENABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_proxy_gatt_enable_comp.err_code, "Node:EnProxyGatt"); + break; + case ESP_BLE_MESH_NODE_PROXY_GATT_DISABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->node_proxy_gatt_disable_comp.err_code, "Node:DisProxyGatt"); + break; +#if (CONFIG_BLE_MESH_PROVISIONER) + case ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT: + ESP_LOGI(TAG, "Provisioner recv unprovisioned device beacon:"); + ESP_LOG_BUFFER_HEX("Device UUID %s", param->provisioner_recv_unprov_adv_pkt.dev_uuid, 16); + ESP_LOG_BUFFER_HEX("Address %s", param->provisioner_recv_unprov_adv_pkt.addr, 6); + ESP_LOGI(TAG, "Address type 0x%x, oob_info 0x%04x, adv_type 0x%x, bearer 0x%x", + param->provisioner_recv_unprov_adv_pkt.addr_type, param->provisioner_recv_unprov_adv_pkt.oob_info, + param->provisioner_recv_unprov_adv_pkt.adv_type, param->provisioner_recv_unprov_adv_pkt.bearer); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT: + ESP_LOGI(TAG, "Provisioner:LinkOpen,OK,%d", param->provisioner_prov_link_open.bearer); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT: + ESP_LOGI(TAG, "Provisioner:LinkClose,OK,%d,%d", + param->provisioner_prov_link_close.bearer, param->provisioner_prov_link_close.reason); + break; + case ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_add_unprov_dev_comp.err_code, "Provisioner:DevAdd"); + break; + case ESP_BLE_MESH_PROVISIONER_DELETE_DEV_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_delete_dev_comp.err_code, "Provisioner:DevDel"); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT: + ESP_LOGI(TAG, "Provisioner:OK,%d,%d", param->provisioner_prov_complete.netkey_idx, param->provisioner_prov_complete.unicast_addr); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_prov_enable_comp.err_code, "Provisioner:EnBearer"); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_prov_disable_comp.err_code, "Provisioner:DisBearer"); + break; + case ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_set_dev_uuid_match_comp.err_code, "Provisioner:UuidMatch"); + break; + case ESP_BLE_MESH_PROVISIONER_SET_PROV_DATA_INFO_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_set_prov_data_info_comp.err_code, "Provisioner:DataInfo"); + break; + case ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_set_node_name_comp.err_code, "Provisioner:NodeName"); + break; + case ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_add_app_key_comp.err_code, "Provisioner:AppKeyAdd"); + break; + case ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_bind_app_key_to_model_comp.err_code, "Provisioner:AppKeyBind"); + break; + case ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_NET_KEY_COMP_EVT: + ble_mesh_callback_check_err_code(param->provisioner_add_net_key_comp.err_code, "Provisioner:NetKeyAdd"); + break; +#endif + default: + break; + } + ESP_LOGD(TAG, "exit %s\n", __func__); +} + +void ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, esp_ble_mesh_model_cb_param_t *param) +{ + esp_err_t result = ESP_OK; + uint8_t status; + + ESP_LOGD(TAG, "enter %s, event=%x\n", __func__, event); + + switch (event) { + case ESP_BLE_MESH_MODEL_OPERATION_EVT: + if (param->model_operation.model != NULL && param->model_operation.model->op != NULL) { + if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET) { + ESP_LOGI(TAG, "Node:GetStatus,OK"); + ble_mesh_node_get_state(status); + result = esp_ble_mesh_server_model_send_msg(param->model_operation.model, param->model_operation.ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(status), &status); + } else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET) { + ble_mesh_node_set_state(param->model_operation.msg[0]); + ESP_LOGI(TAG, "Node:SetAck,OK,%d,%d", param->model_operation.msg[0], param->model_operation.ctx->recv_ttl); + result = esp_ble_mesh_server_model_send_msg(param->model_operation.model, param->model_operation.ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(status), param->model_operation.msg); + } else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK) { + ble_mesh_node_set_state(param->model_operation.msg[0]); + ESP_LOGI(TAG, "Node:SetUnAck,OK,%d,%d", param->model_operation.msg[0], param->model_operation.ctx->recv_ttl); + } else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS) { + ESP_LOGI(TAG, "Node:Status,Success,%d", param->model_operation.length); + } else if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET) { + ESP_LOGI(TAG, "VendorModel:SetAck,OK,%d", param->model_operation.ctx->recv_ttl); + } else if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS) { + uint64_t current_time = esp_timer_get_time(); + result = ble_mesh_test_performance_client_model_accumulate_time(((uint32_t)(current_time - start_time) / 1000), param->model_operation.msg, param->model_operation.ctx->recv_ttl, param->model_operation.length); + ESP_LOGI(TAG, "VendorModel:Status,OK,%d", param->model_operation.ctx->recv_ttl); + if (ble_mesh_test_perf_send_sema != NULL && result == ESP_OK) { + xSemaphoreGive(ble_mesh_test_perf_send_sema); + } + } + } + break; + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: + if (param->model_send_comp.err_code == ESP_OK) { + ESP_LOGI(TAG, "Node:ModelSend,OK"); + } else { + ESP_LOGE(TAG, "Node:ModelSend,Fail,%d,0x%X,0x%04X", param->model_send_comp.err_code, param->model_send_comp.model->model_id, param->model_send_comp.model->op->opcode); + } + break; + case ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT: + ESP_LOGI(TAG, "Node:PublishSend,OK,0x%X,%d", param->model_publish_comp.model->model_id, param->model_publish_comp.model->pub->msg->len); + break; + case ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT: + ESP_LOGI(TAG, "Node:PublishReceive,OK,0x%04X,%d,%d", param->client_recv_publish_msg.opcode, param->client_recv_publish_msg.length, param->client_recv_publish_msg.msg[1]); + uint64_t current_time = esp_timer_get_time(); + result = ble_mesh_test_performance_client_model_accumulate_time(((uint32_t)(current_time - start_time) / 2000), param->client_recv_publish_msg.msg, param->client_recv_publish_msg.ctx->recv_ttl, param->client_recv_publish_msg.length); + if (ble_mesh_test_perf_send_sema != NULL && param->client_recv_publish_msg.msg[2] == VENDOR_MODEL_PERF_OPERATION_TYPE_SET_UNACK && result == ESP_OK) { + xSemaphoreGive(ble_mesh_test_perf_send_sema); + } + break; + case ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT: + ESP_LOGI(TAG, "Node:PublishUpdate,OK"); + break; + case ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT: + ESP_LOGI(TAG, "Node:TimeOut, 0x%04X", param->client_send_timeout.opcode); + if (ble_mesh_test_perf_send_sema != NULL) { + xSemaphoreGive(ble_mesh_test_perf_send_sema); + } + break; + case ESP_BLE_MESH_MODEL_EVT_MAX: + ESP_LOGI(TAG, "Node:MaxEvt"); + break; + default: + break; + } + + ESP_LOGD(TAG, "exit %s\n", __func__); +} + +int ble_mesh_power_set(int argc, char **argv) +{ + esp_err_t result = ESP_OK; + int nerrors = arg_parse(argc, argv, (void **) &power_set); + + ESP_LOGD(TAG, "enter %s\n", __func__); + + if (nerrors != 0) { + arg_print_errors(stderr, power_set.end, argv[0]); + return 1; + } + + if (strcmp(power_set.action_type->sval[0], "tx") == 0) { + result = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, power_set.tx_sense_power->ival[0]); + } else if (strcmp(power_set.action_type->sval[0], "sense") == 0) { + uint32_t *reg = (uint32_t *)(0x6001c07c); + int reg_addr = 0x6001c07c; + uint32_t flag = 0x00FF0000; + uint32_t sense_new = power_set.tx_sense_power->ival[0]; + uint32_t reg_to_write = ((*reg) &= ~flag) | ((256 - sense_new) << 16); + REG_WRITE(reg_addr, reg_to_write); + + } + + if (result == ESP_OK) { + ESP_LOGI(TAG, "Node:SetPower,OK\n"); + } + + ESP_LOGD(TAG, "exit %s\n", __func__); + return result; +} + +static int ble_mesh_load_oob(int argc, char **argv) +{ + uint8_t *static_val; + + ESP_LOGD(TAG, "enter %s \n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &oob); + if (nerrors != 0) { + arg_print_errors(stderr, oob.end, argv[0]); + return 1; + } + + //parsing prov +#if CONFIG_BLE_MESH_NODE + prov.uuid = dev_uuid; + memcpy(dev_uuid, esp_bt_dev_get_address(), 6); + if (oob.static_val->count != 0) { + static_val = malloc(oob.static_val_len->ival[0] + 1); + if (static_val == NULL) { + ESP_LOGE(TAG, "malloc fail,%s,%d\n", __func__, __LINE__); + } + get_value_string((char *)oob.static_val->sval[0], (char *)static_val); + prov.static_val = static_val; + } + + arg_int_to_value(oob.static_val_len, prov.static_val_len, "static value length"); + arg_int_to_value(oob.output_size, prov.output_size, "output size"); + arg_int_to_value(oob.output_actions, prov.output_actions, "output actions"); + arg_int_to_value(oob.input_size, prov.input_size, "input size"); + arg_int_to_value(oob.input_actions, prov.input_actions, "input actions"); +#endif + +#if CONFIG_BLE_MESH_PROVISIONER + if (oob.static_val->count != 0) { + static_val = malloc(oob.static_val_len->ival[0] + 1); + if (static_val == NULL) { + ESP_LOGE(TAG, "malloc fail,%s,%d\n", __func__, __LINE__); + } + get_value_string((char *)oob.static_val->sval[0], (char *)static_val); + prov.prov_static_oob_val = static_val; + } + arg_int_to_value(oob.prov_start_address, prov.prov_start_address, "provisioner start address"); + arg_int_to_value(oob.static_val_len, prov.prov_static_oob_len, "provisioner static value length"); +#endif + + ESP_LOGI(TAG, "OOB:Load,OK\n"); + + ESP_LOGD(TAG, "exit %s\n", __func__); + return 0; +} + + +int ble_mesh_init(int argc, char **argv) +{ + int err; + esp_ble_mesh_comp_t *local_component = NULL; + + int nerrors = arg_parse(argc, argv, (void **) &component); + if (nerrors != 0) { + arg_print_errors(stderr, component.end, argv[0]); + return 1; + } + + ESP_LOGD(TAG, "enter %s, module %x\n", __func__, component.model_type->ival[0]); + local_component = ble_mesh_get_component(component.model_type->ival[0]); + + + err = esp_ble_mesh_init(&prov, local_component); + if (err) { + ESP_LOGE(TAG, "Initializing mesh failed (err %d)\n", err); + return err; + } + + ESP_LOGD(TAG, "exit %s\n", __func__); + return err; +} + +int ble_mesh_provisioner_enable_bearer(int argc, char **argv) +{ + esp_err_t err = 0; + + ESP_LOGD(TAG, "enter %s \n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &bearer); + if (nerrors != 0) { + arg_print_errors(stderr, bearer.end, argv[0]); + return 1; + } + + if (bearer.enable->count != 0) { + if (bearer.enable->ival[0]) { + err = esp_ble_mesh_provisioner_prov_enable(bearer.bearer->ival[0]); + } else { + err = esp_ble_mesh_provisioner_prov_disable(bearer.bearer->ival[0]); + } + } else { + return 1; + } + + ESP_LOGD(TAG, "exit %s\n", __func__); + return err; +} + +void ble_mesh_register_node_cmd() +{ + const esp_console_cmd_t register_cmd = { + .command = "bmreg", + .help = "ble mesh: provisioner/node register callback", + .hint = NULL, + .func = &ble_mesh_register_node_cb, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(®ister_cmd)); + oob.static_val = arg_str0("s", NULL, "", "Static OOB value"); + oob.static_val_len = arg_int0("l", NULL, "", "Static OOB value length"); + oob.output_size = arg_int0("x", NULL, "", "Maximum size of Output OOB"); + oob.output_actions = arg_int0("o", NULL, "", "Supported Output OOB Actions"); + oob.input_size = arg_int0("y", NULL, "", "Maximum size of Input OOB"); + oob.input_actions = arg_int0("i", NULL, "", "Supported Input OOB Actions"); + oob.prov_start_address = arg_int0("p", NULL, "
", "start address assigned by provisioner"); + oob.prov_start_address->ival[0] = 0x0005; + oob.end = arg_end(1); + + const esp_console_cmd_t oob_cmd = { + .command = "bmoob", + .help = "ble mesh: provisioner/node config OOB parameters", + .hint = NULL, + .func = &ble_mesh_load_oob, + .argtable = &oob, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&oob_cmd) ); + + component.model_type = arg_int0("m", NULL, "", "mesh model"); + component.config_index = arg_int0("c", NULL, "", "mesh model op"); + component.config_index->ival[0] = 0; // set default value + component.pub_config = arg_int0("p", NULL, "", "publish message buffer"); + component.end = arg_end(1); + + const esp_console_cmd_t model_cmd = { + .command = "bminit", + .help = "ble mesh: provisioner/node init", + .hint = NULL, + .func = &ble_mesh_init, + .argtable = &component, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&model_cmd) ); + + bearer.bearer = arg_int0("b", NULL, "", "supported bearer"); + bearer.enable = arg_int0("e", NULL, "", "bearers node supported"); + bearer.end = arg_end(1); + + const esp_console_cmd_t bearer_cmd = { + .command = "bmpbearer", + .help = "ble mesh provisioner: enable/disable different bearers", + .hint = NULL, + .func = &ble_mesh_provisioner_enable_bearer, + .argtable = &bearer, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&bearer_cmd)); + + power_set.tx_sense_power = arg_int0("t", NULL, "", "tx power or sense"); + power_set.action_type = arg_str1("z", NULL, "", "action type"); + power_set.end = arg_end(1); + + const esp_console_cmd_t power_set_cmd = { + .command = "bmtxpower", + .help = "ble mesh: set tx power or sense", + .hint = NULL, + .func = &ble_mesh_power_set, + .argtable = &power_set, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&power_set_cmd)); +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_provisioner_cmd.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_provisioner_cmd.c new file mode 100644 index 0000000000..c5a2d3ad1b --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_provisioner_cmd.c @@ -0,0 +1,424 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_bt_defs.h" + +#include "provisioner_prov.h" +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_config_model_api.h" + +#include "ble_mesh_adapter.h" +#include "ble_mesh_console_decl.h" + +#if CONFIG_BLE_MESH_PROVISIONER + +typedef struct { + struct arg_int *bearer; + struct arg_int *enable; + struct arg_end *end; +} ble_mesh_provisioner_bearer_t; +ble_mesh_provisioner_bearer_t provisioner_bearer; + +typedef struct { + struct arg_str *add_del; + struct arg_str *device_addr; + struct arg_str *device_uuid; + struct arg_int *addr_type; + struct arg_int *bearer; + struct arg_int *oob_info; + struct arg_int *flag; + struct arg_end *end; +} ble_mesh_provisioner_addr_t; +ble_mesh_provisioner_addr_t provisioner_addr; + +typedef struct { + struct arg_int *unicast_addr; + struct arg_end *end; +} ble_mesh_provisioner_get_node_t; +ble_mesh_provisioner_get_node_t provisioner_get_node; + +typedef struct { + struct arg_int *oob_info; + struct arg_int *unicast_addr; + struct arg_int *element_num; + struct arg_int *net_idx; + struct arg_str *dev_key; + struct arg_str *uuid; + struct arg_end *end; +} ble_mesh_provisioner_add_node_t; +ble_mesh_provisioner_add_node_t provisioner_add_node; + +typedef struct { + struct arg_int *appkey_index; + struct arg_int *element_address; + struct arg_int *network_index; + struct arg_int *mod_id; + struct arg_int *cid; + struct arg_end *end; +} ble_mesh_provisioner_bind_model_t; +ble_mesh_provisioner_bind_model_t provisioner_local_bind; + +typedef struct { + struct arg_str *action_type; + struct arg_int *net_idx; + struct arg_int *app_idx; + struct arg_str *key; + struct arg_end *end; +} ble_mesh_provisioner_add_key_t; +ble_mesh_provisioner_add_key_t provisioner_add_key; + +void ble_mesh_regist_provisioner_cmd(); + +void ble_mesh_prov_adv_cb(const esp_bd_addr_t addr, const esp_ble_addr_type_t addr_type, const uint8_t adv_type, + const uint8_t *dev_uuid, uint16_t oob_info, esp_ble_mesh_prov_bearer_t bearer); + +void ble_mesh_register_mesh_provisioner() +{ + ble_mesh_regist_provisioner_cmd(); +} + +void ble_mesh_prov_adv_cb(const esp_bd_addr_t addr, const esp_ble_addr_type_t addr_type, const uint8_t adv_type, + const uint8_t *dev_uuid, uint16_t oob_info, esp_ble_mesh_prov_bearer_t bearer) +{ + ESP_LOGD(TAG, "enter %s\n", __func__); + ESP_LOGI(TAG, "scan device address:"); + esp_log_buffer_hex(TAG, addr, sizeof(esp_bd_addr_t)); + ESP_LOGI(TAG, "scan device uuid:"); + esp_log_buffer_hex(TAG, dev_uuid, 16); + ESP_LOGD(TAG, "exit %s\n", __func__); +} + +int ble_mesh_provisioner_register() +{ + ESP_LOGD(TAG, "enter %s \n", __func__); + // esp_ble_mesh_register_unprov_adv_pkt_callback(ble_mesh_prov_adv_cb); + ESP_LOGI(TAG, "Provisioner:Reg,OK"); + ESP_LOGD(TAG, "exit %s \n", __func__); + return 0; +} + +int ble_mesh_provision_address(int argc, char **argv) +{ + esp_err_t err = ESP_OK; + esp_ble_mesh_unprov_dev_add_t device_addr = {0}; + uint8_t preset_addr_uuid[16] = {0x01, 0x02}; + esp_ble_mesh_device_delete_t del_dev = { + .flag = BIT(0), + }; + + ESP_LOGD(TAG, "enter %s \n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &provisioner_addr); + if (nerrors != 0) { + arg_print_errors(stderr, provisioner_addr.end, argv[0]); + return 1; + } + + if (provisioner_addr.device_addr->count != 0) { + if (provisioner_addr.device_uuid->count != 0) { + del_dev.flag = BIT(0) | BIT(1); + str_2_mac((uint8_t *)provisioner_addr.device_addr->sval[0], device_addr.uuid); + str_2_mac((uint8_t *)provisioner_addr.device_addr->sval[0], del_dev.uuid); + } else { + del_dev.flag = BIT(0); + memcpy(device_addr.uuid, preset_addr_uuid, 16); + memcpy(del_dev.uuid, preset_addr_uuid, 16); + } + str_2_mac((uint8_t *)provisioner_addr.device_addr->sval[0], device_addr.addr); + str_2_mac((uint8_t *)provisioner_addr.device_addr->sval[0], del_dev.addr); + arg_int_to_value(provisioner_addr.addr_type, device_addr.addr_type, "address type"); + arg_int_to_value(provisioner_addr.addr_type, del_dev.addr_type, "address type"); + } else if (provisioner_addr.device_uuid->count != 0) { + del_dev.flag = BIT(1); + memcpy(device_addr.addr, preset_addr_uuid, 6); + memcpy(del_dev.addr, preset_addr_uuid, 6); + str_2_mac((uint8_t *)provisioner_addr.device_addr->sval[0], device_addr.uuid); + str_2_mac((uint8_t *)provisioner_addr.device_addr->sval[0], del_dev.uuid); + } + + if (strcmp(provisioner_addr.add_del->sval[0], "add") == 0) { + arg_int_to_value(provisioner_addr.bearer, device_addr.bearer, "bearer"); + arg_int_to_value(provisioner_addr.oob_info, device_addr.oob_info, "oob information"); + err = esp_ble_mesh_provisioner_add_unprov_dev(&device_addr, provisioner_addr.flag->ival[0]); + } else if (strcmp(provisioner_addr.add_del->sval[0], "del") == 0) { + err = esp_ble_mesh_provisioner_delete_dev(&del_dev); + } + + ESP_LOGD(TAG, "exit %s \n", __func__); + return err; +} + +int ble_mesh_provisioner_bearer(int argc, char **argv) +{ + esp_err_t err; + + ESP_LOGD(TAG, "enter %s \n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &provisioner_bearer); + if (nerrors != 0) { + arg_print_errors(stderr, provisioner_bearer.end, argv[0]); + return 1; + } + + if (provisioner_bearer.enable->count != 0) { + if (provisioner_bearer.enable->ival[0]) { + err = esp_ble_mesh_provisioner_prov_enable(provisioner_bearer.bearer->ival[0]); + } else { + err = esp_ble_mesh_provisioner_prov_disable(provisioner_bearer.bearer->ival[0]); + } + } else { + return 1; + } + + ESP_LOGD(TAG, "exit %s \n", __func__); + return err; +} + +int ble_mesh_provisioner_get_node(int argc, char **argv) +{ + uint16_t unicast_addr = 0; + uint16_t i = 0; + struct bt_mesh_node_t *node_info; + + ESP_LOGD(TAG, "enter %s\n", __func__); + int nerrors = arg_parse(argc, argv, (void **) &provisioner_get_node); + if (nerrors != 0) { + arg_print_errors(stderr, provisioner_get_node.end, argv[0]); + return 1; + } + + arg_int_to_value(provisioner_get_node.unicast_addr, unicast_addr, "unicast address"); + node_info = bt_mesh_provisioner_get_node_info(unicast_addr); + + if (node_info == NULL) { + return ESP_FAIL; + } else { + printf("OobInfo:0x%x,Address:0x%x,EleNum:0x%x,NetIdx:0x%x,DevKey:", + node_info->oob_info, node_info->unicast_addr, node_info->element_num, node_info->net_idx); + for (i = 0; i < 16; i++) { + printf("%02x", node_info->dev_key[i]); + } + printf(",DevUuid:"); + for (i = 0; i < 16; i++) { + printf("%02x", node_info->dev_uuid[i]); + } + printf("\n"); + } + + ESP_LOGD(TAG, "exit %s\n", __func__); + return ESP_OK; +} + +int ble_mesh_provisioner_add_node(int argc, char **argv) +{ + struct bt_mesh_node_t node_info; + esp_err_t result; + ESP_LOGD(TAG, " enter %s\n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &provisioner_add_node); + if (nerrors != 0) { + arg_print_errors(stderr, provisioner_add_node.end, argv[0]); + return 1; + } + + arg_int_to_value(provisioner_add_node.oob_info, node_info.oob_info, "oob information"); + arg_int_to_value(provisioner_add_node.unicast_addr, node_info.unicast_addr, "unicast address"); + arg_int_to_value(provisioner_add_node.element_num, node_info.element_num, "element number"); + arg_int_to_value(provisioner_add_node.net_idx, node_info.net_idx, "network index"); + if (provisioner_add_node.dev_key->count != 0) { + get_value_string((char *)provisioner_add_node.dev_key->sval[0], (char *)node_info.dev_key); + } + if (provisioner_add_node.uuid->count != 0) { + get_value_string((char *)provisioner_add_node.uuid->sval[0], (char *)node_info.dev_uuid); + get_value_string((char *)provisioner_add_node.uuid->sval[0], (char *)node_info.dev_uuid); + } + + result = bt_mesh_provisioner_store_node_info(&node_info); + if (result == ESP_OK) { + ESP_LOGI(TAG, "Provisioner:AddNodeInfo,OK\n"); + } + + ESP_LOGD(TAG, "exit %s\n", __func__); + return result; +} + +int ble_mesh_provisioner_add_key(int argc, char **argv) +{ + esp_err_t err = ESP_OK; + uint8_t key[16] = {0}; + esp_ble_mesh_prov_data_info_t info = { + .net_idx = 1, + .flag = NET_IDX_FLAG, + }; + ESP_LOGD(TAG, " enter %s\n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &provisioner_add_key); + if (nerrors != 0) { + arg_print_errors(stderr, provisioner_add_key.end, argv[0]); + return 1; + } + + err = get_value_string((char *)provisioner_add_key.key->sval[0], (char *) key); + if (strcmp(provisioner_add_key.action_type->sval[0], "appkey") == 0) { + err = esp_ble_mesh_provisioner_add_local_app_key(key, provisioner_add_key.net_idx->ival[0], provisioner_add_key.app_idx->ival[0]); + } else if (strcmp(provisioner_add_key.action_type->sval[0], "netkey") == 0) { + // choose network key + info.net_idx = provisioner_add_key.net_idx->ival[0]; + err = esp_ble_mesh_provisioner_add_local_net_key(key, provisioner_add_key.net_idx->ival[0]); + err = err | esp_ble_mesh_provisioner_set_prov_data_info(&info); + } + + if (err != ESP_OK) { + ESP_LOGI(TAG, "Provisioner:KeyAction,Fail"); + } else { + ESP_LOGI(TAG, "Provisioner:KeyAction,OK"); + } + + ESP_LOGD(TAG, "exit %s\n", __func__); + return err; +} + +int ble_mesh_provision_bind_local_model(int argc, char **argv) +{ + esp_err_t err; + uint16_t element_addr = 0; + uint16_t app_idx = 0; + uint16_t model_id = 0; + uint16_t company_id = 0xFFFF; + + ESP_LOGD(TAG, " enter %s\n", __func__); + + int nerrors = arg_parse(argc, argv, (void **) &provisioner_local_bind); + if (nerrors != 0) { + arg_print_errors(stderr, provisioner_local_bind.end, argv[0]); + return 1; + } + + arg_int_to_value(provisioner_local_bind.element_address, element_addr, "element address"); + arg_int_to_value(provisioner_local_bind.appkey_index, app_idx, "appkey index"); + arg_int_to_value(provisioner_local_bind.mod_id, model_id, "model id"); + arg_int_to_value(provisioner_local_bind.cid, company_id, "company id"); + err = esp_ble_mesh_provisioner_bind_app_key_to_local_model(element_addr, app_idx, model_id, company_id); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Provisioner:BindModel,Fail,%x\n", err); + } else { + ESP_LOGI(TAG, "Provisioner:BindModel,OK\n"); + } + ESP_LOGD(TAG, "exit %s\n", __func__); + return err; +} + +void ble_mesh_regist_provisioner_cmd() +{ + const esp_console_cmd_t prov_register = { + .command = "bmpreg", + .help = "ble mesh provisioner: register callback", + .hint = NULL, + .func = &ble_mesh_provisioner_register, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&prov_register)); + + provisioner_addr.add_del = arg_str1("z", NULL, "", "action type"); + provisioner_addr.device_addr = arg_str0("d", NULL, "
", "device address"); + provisioner_addr.device_uuid = arg_str0("u", NULL, "", "device uuid"); + provisioner_addr.addr_type = arg_int0("a", NULL, "", "address type"); + provisioner_addr.flag = arg_int0("f", NULL, "", "address flag"); + provisioner_addr.flag->ival[0] = ADD_DEV_RM_AFTER_PROV_FLAG | ADD_DEV_FLUSHABLE_DEV_FLAG; + provisioner_addr.bearer = arg_int0("b", NULL, "", "used bearer"); + provisioner_addr.oob_info = arg_int0("o", NULL, "", "oob information"); + provisioner_addr.end = arg_end(1); + + const esp_console_cmd_t provisioner_addr_cmd = { + .command = "bmpdev", + .help = "ble mesh provisioner: add/delete unprovisioned device", + .hint = NULL, + .func = &ble_mesh_provision_address, + .argtable = &provisioner_addr, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&provisioner_addr_cmd)); + + provisioner_bearer.bearer = arg_int0("b", NULL, "", "bearer supported provisioner"); + provisioner_bearer.enable = arg_int0("e", NULL, "", "enable or disable bearer"); + provisioner_bearer.end = arg_end(1); + + const esp_console_cmd_t provisioner_bearer_cmd = { + .command = "bmpbearer", + .help = "ble mesh provisioner: enable/disable provisioner different bearer", + .hint = NULL, + .func = &ble_mesh_provisioner_bearer, + .argtable = &provisioner_bearer, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&provisioner_bearer_cmd)); + + provisioner_get_node.unicast_addr = arg_int1("u", NULL, "
", "get node by unicast address"); + provisioner_get_node.end = arg_end(1); + + const esp_console_cmd_t provisioner_get_node_cmd = { + .command = "bmpgetn", + .help = "ble mesh provisioner: get node", + .func = &ble_mesh_provisioner_get_node, + .argtable = &provisioner_get_node, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&provisioner_get_node_cmd)); + + provisioner_add_node.oob_info = arg_int0("o", NULL, "", "oob information"); + provisioner_add_node.unicast_addr = arg_int0("a", NULL, "", "unicast address"); + provisioner_add_node.element_num = arg_int0("e", NULL, "", "element num"); + provisioner_add_node.net_idx = arg_int0("n", NULL, "", "net index"); + provisioner_add_node.dev_key = arg_str0("d", NULL, "", "device key"); + provisioner_add_node.uuid = arg_str0("u", NULL, "", "device uuid"); + provisioner_add_node.end = arg_end(1); + + const esp_console_cmd_t provisioner_add_node_cmd = { + .command = "bmpaddn", + .help = "ble mesh provisioner: add node", + .func = &ble_mesh_provisioner_add_node, + .argtable = &provisioner_add_node, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&provisioner_add_node_cmd)); + + provisioner_local_bind.appkey_index = arg_int1("a", NULL, "", "appkey index"); + provisioner_local_bind.element_address = arg_int1("e", NULL, "", "element address"); + provisioner_local_bind.network_index = arg_int1("n", NULL, "", "network index"); + provisioner_local_bind.mod_id = arg_int1("m", NULL, "", "model id"); + provisioner_local_bind.cid = arg_int0("c", NULL, "", "company id"); + provisioner_local_bind.end = arg_end(1); + + const esp_console_cmd_t provisioner_local_bind_cmd = { + .command = "bmpbind", + .help = "ble mesh provisioner: bind local model", + .func = &ble_mesh_provision_bind_local_model, + .argtable = &provisioner_local_bind, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&provisioner_local_bind_cmd)); + + provisioner_add_key.action_type = arg_str1("z", NULL, "", "add appkey or network key"); + provisioner_add_key.net_idx = arg_int1("n", NULL, "", "network key index"); + provisioner_add_key.key = arg_str1("k", NULL, "", "appkey or network"); + provisioner_add_key.app_idx = arg_int0("a", NULL, "", "appkey index"); + provisioner_add_key.end = arg_end(1); + + const esp_console_cmd_t provisioner_add_key_cmd = { + .command = "bmpkey", + .help = "ble mesh provisioner: key", + .func = &ble_mesh_provisioner_add_key, + .argtable = &provisioner_add_key, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&provisioner_add_key_cmd)); +} +#endif + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/component.mk b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/component.mk new file mode 100644 index 0000000000..0b9d7585e7 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/register_bluetooth.c b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/register_bluetooth.c new file mode 100644 index 0000000000..bcb548520f --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/register_bluetooth.c @@ -0,0 +1,45 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_bt_device.h" +#include "esp_console.h" + +#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5] +#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x" + +void register_ble_address(); + +void register_bluetooth() +{ + register_ble_address(); +} + +int bt_mac(int argc, char** argv) +{ + const uint8_t *mac = esp_bt_dev_get_address(); + printf("+BTMAC:"MACSTR"\n", MAC2STR(mac)); + return 0; +} + +void register_ble_address() +{ + const esp_console_cmd_t cmd = { + .command = "btmac", + .help = "BLE address", + .hint = NULL, + .func = (esp_console_cmd_func_t)&bt_mac, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/sdkconfig.defaults b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/sdkconfig.defaults new file mode 100644 index 0000000000..9c0ad1b87c --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_console/ble_mesh_provisioner/sdkconfig.defaults @@ -0,0 +1,40 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y +CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY= +CONFIG_BTDM_CONTROLLER_MODE_BTDM= +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_BLE_SCAN_DUPLICATE=y +CONFIG_SCAN_DUPLICATE_TYPE=2 +CONFIG_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_MESH_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BTDM_CONTROLLER_FULL_SCAN_SUPPORTED=y +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_HCI_5_0=y +CONFIG_BLE_MESH_USE_DUPLICATE_SCAN=y +CONFIG_BLE_MESH_PROV=y +CONFIG_BLE_MESH_PROVISIONER=y +CONFIG_BLE_MESH_WAIT_FOR_PROV_MAX_DEV_NUM=20 +CONFIG_BLE_MESH_MAX_PROV_NODES=20 +CONFIG_BLE_MESH_PBA_SAME_TIME=10 +CONFIG_BLE_MESH_PBG_SAME_TIME=4 +CONFIG_BLE_MESH_PROVISIONER_SUBNET_COUNT=3 +CONFIG_BLE_MESH_PROVISIONER_APP_KEY_COUNT=9 +CONFIG_BLE_MESH_PB_ADV=y +CONFIG_BLE_MESH_NET_BUF_POOL_USAGE=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_GATT_PROXY=y +CONFIG_BLE_MESH_RELAY=y +CONFIG_BLE_MESH_LOW_POWER= +CONFIG_BLE_MESH_FRIEND= +CONFIG_BLE_MESH_MSG_CACHE_SIZE=10 +CONFIG_BLE_MESH_ADV_BUF_COUNT=60 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=6 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=1 +CONFIG_BLE_MESH_RX_SDU_MAX=384 +CONFIG_BLE_MESH_TX_SEG_MAX=32 +CONFIG_BTU_TASK_STACK_SIZE=4512 +CONFIG_BLE_MESH_CFG_CLI=y +CONFIG_BLE_MESH_GENERIC_ONOFF_CLI=y \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/CMakeLists.txt new file mode 100644 index 0000000000..df96eec280 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_mesh_fast_prov_client) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/Makefile b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/Makefile new file mode 100644 index 0000000000..06bba88878 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/Makefile @@ -0,0 +1,12 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_mesh_fast_prov_client + +COMPONENT_ADD_INCLUDEDIRS := components/include + +EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/README.md b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/README.md new file mode 100644 index 0000000000..9e45f4f31b --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/README.md @@ -0,0 +1,2 @@ +ESP BLE Mesh Fast Provisioning Client Demo +======================== \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/CMakeLists.txt new file mode 100644 index 0000000000..3d3bc6f9a5 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(COMPONENT_SRCS "ble_mesh_demo_main.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_main.c b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_main.c new file mode 100644 index 0000000000..3337277340 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_main.c @@ -0,0 +1,638 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "esp_system.h" +#include "esp_log.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_ble_api.h" +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_common_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_config_model_api.h" +#include "esp_ble_mesh_generic_model_api.h" + +#include "esp_fast_prov_common.h" +#include "esp_fast_prov_operation.h" +#include "esp_fast_prov_client_model.h" + +#define TAG "FAST_PROV_CLIENT_DEMO" + +#define PROV_OWN_ADDR 0x0001 +#define APP_KEY_OCTET 0x12 +#define GROUP_ADDRESS 0xC000 + +static uint8_t dev_uuid[16] = { 0xdd, 0xdd }; +static uint8_t match[] = { 0xdd, 0xdd }; + +static const esp_ble_mesh_client_op_pair_t fast_prov_cli_op_pair[] = { + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS }, +}; + +static esp_ble_mesh_cfg_srv_t config_server = { + .relay = ESP_BLE_MESH_RELAY_DISABLED, + .beacon = ESP_BLE_MESH_BEACON_ENABLED, +#if defined(CONFIG_BLE_MESH_FRIEND) + .friend_state = ESP_BLE_MESH_FRIEND_ENABLED, +#else + .friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED, +#endif +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_ENABLED, +#else + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED, +#endif + .default_ttl = 7, + /* 3 transmissions with a 20ms interval */ + .net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20), + .relay_retransmit = ESP_BLE_MESH_TRANSMIT(2, 20), +}; +esp_ble_mesh_client_t config_client; +esp_ble_mesh_client_t gen_onoff_client; +esp_ble_mesh_client_t fast_prov_client = { + .op_pair_size = ARRAY_SIZE(fast_prov_cli_op_pair), + .op_pair = fast_prov_cli_op_pair, +}; + +static esp_ble_mesh_model_op_t fast_prov_cli_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS, 1), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS, 2), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS, 2), + ESP_BLE_MESH_MODEL_OP_END, +}; + +static esp_ble_mesh_model_t root_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&config_server), + ESP_BLE_MESH_MODEL_CFG_CLI(&config_client), + ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(NULL, &gen_onoff_client), +}; + +static esp_ble_mesh_model_t vnd_models[] = { + ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI, + fast_prov_cli_op, NULL, &fast_prov_client), +}; + +static esp_ble_mesh_elem_t elements[] = { + ESP_BLE_MESH_ELEMENT(0, root_models, vnd_models), +}; + +static esp_ble_mesh_comp_t comp = { + .cid = CID_ESP, + .elements = elements, + .element_count = ARRAY_SIZE(elements), +}; + +static esp_ble_mesh_prov_t prov = { + .prov_uuid = dev_uuid, + .prov_unicast_addr = PROV_OWN_ADDR, + .prov_start_address = 0x0005, + .prov_attention = 0x00, + .prov_algorithm = 0x00, + .prov_pub_key_oob = 0x00, + .prov_static_oob_val = NULL, + .prov_static_oob_len = 0x00, + .flags = 0x00, + .iv_index = 0x00, +}; + +example_prov_info_t prov_info = { + .net_idx = ESP_BLE_MESH_KEY_PRIMARY, + .app_idx = ESP_BLE_MESH_KEY_PRIMARY, + .node_addr_cnt = 100, + .unicast_max = 0x7FFF, + .group_addr = GROUP_ADDRESS, + .max_node_num = 0x01, +}; + +static void provisioner_prov_link_open(esp_ble_mesh_prov_bearer_t bearer) +{ + ESP_LOGI(TAG, "%s link open", bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); +} + +static void provisioner_prov_link_close(esp_ble_mesh_prov_bearer_t bearer, uint8_t reason) +{ + ESP_LOGI(TAG, "%s link close, reason 0x%02x", + bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT", reason); + + if (bearer == ESP_BLE_MESH_PROV_ADV && reason != 0x00) { + prov_info.max_node_num++; + } +} + +static void provisioner_prov_complete(int node_index, const uint8_t uuid[16], uint16_t unicast_addr, + uint8_t elem_num, uint16_t net_idx) +{ + example_node_info_t *node = NULL; + char name[10]; + esp_err_t err; + + ESP_LOGI(TAG, "Node index: 0x%x, unicast address: 0x%02x, element num: %d, netkey index: 0x%02x", + node_index, unicast_addr, elem_num, net_idx); + ESP_LOGI(TAG, "Node uuid: %s", bt_hex(uuid, 16)); + + sprintf(name, "%s%d", "NODE-", node_index); + if (esp_ble_mesh_provisioner_set_node_name(node_index, name)) { + ESP_LOGE(TAG, "%s: Failed to set node name", __func__); + return; + } + + /* Sets node info */ + err = example_store_node_info(uuid, unicast_addr, elem_num, prov_info.net_idx, + prov_info.app_idx, LED_OFF); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to set node info", __func__); + return; + } + + /* Gets node info */ + node = example_get_node_info(unicast_addr); + if (!node) { + ESP_LOGE(TAG, "%s: Failed to get node info", __func__); + return; + } + + /* The Provisioner will send Config AppKey Add to the node. */ + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->unicast_addr, + .timeout = 0, + .role = ROLE_PROVISIONER, + }; + esp_ble_mesh_cfg_app_key_add_t add_key = { + .net_idx = prov_info.net_idx, + .app_idx = prov_info.app_idx, + }; + memcpy(add_key.app_key, prov_info.app_key, 16); + err = example_send_config_appkey_add(config_client.model, &info, &add_key); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Config AppKey Add message", __func__); + return; + } +} + +static void example_recv_unprov_adv_pkt(uint8_t dev_uuid[16], uint8_t addr[ESP_BD_ADDR_LEN], + esp_ble_addr_type_t addr_type, uint16_t oob_info, + uint8_t adv_type, esp_ble_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_unprov_dev_add_t add_dev = {0}; + esp_ble_mesh_dev_add_flag_t flag; + esp_err_t err; + bool reprov; + + if (bearer & ESP_BLE_MESH_PROV_ADV) { + /* Checks if the device has been provisioned previously. If the device + * is a re-provisioned one, we will ignore the 'max_node_num' count and + * start to provision it directly. + */ + reprov = example_is_node_exist(dev_uuid); + if (reprov) { + goto add; + } + + if (prov_info.max_node_num == 0) { + return; + } + + ESP_LOGI(TAG, "address: %s, address type: %d, adv type: %d", bt_hex(addr, 6), addr_type, adv_type); + ESP_LOGI(TAG, "dev uuid: %s", bt_hex(dev_uuid, 16)); + ESP_LOGI(TAG, "oob info: %d, bearer: %s", oob_info, (bearer & ESP_BLE_MESH_PROV_ADV) ? "PB-ADV" : "PB-GATT"); + +add: + memcpy(add_dev.addr, addr, 6); + add_dev.addr_type = (uint8_t)addr_type; + memcpy(add_dev.uuid, dev_uuid, 16); + add_dev.oob_info = oob_info; + add_dev.bearer = (uint8_t)bearer; + flag = ADD_DEV_RM_AFTER_PROV_FLAG | ADD_DEV_START_PROV_NOW_FLAG | ADD_DEV_FLUSHABLE_DEV_FLAG; + err = esp_ble_mesh_provisioner_add_unprov_dev(&add_dev, flag); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to start provisioning a device", __func__); + return; + } + + if (!reprov) { + if (prov_info.max_node_num) { + prov_info.max_node_num--; + } + } + } +} + +static void example_provisioning_callback(esp_ble_mesh_prov_cb_event_t event, + esp_ble_mesh_prov_cb_param_t *param) +{ + switch (event) { + case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROV_REGISTER_COMP_EVT, err_code: %d", + param->prov_register_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT"); + break; + case ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT: + example_recv_unprov_adv_pkt(param->provisioner_recv_unprov_adv_pkt.dev_uuid, param->provisioner_recv_unprov_adv_pkt.addr, + param->provisioner_recv_unprov_adv_pkt.addr_type, param->provisioner_recv_unprov_adv_pkt.oob_info, + param->provisioner_recv_unprov_adv_pkt.adv_type, param->provisioner_recv_unprov_adv_pkt.bearer); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT, bearer %s", + param->provisioner_prov_link_open.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); + provisioner_prov_link_open(param->provisioner_prov_link_open.bearer); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT, bearer %s reason 0x%02x", + param->provisioner_prov_link_close.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT", + param->provisioner_prov_link_close.reason); + provisioner_prov_link_close(param->provisioner_prov_link_close.bearer, + param->provisioner_prov_link_close.reason); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT"); + provisioner_prov_complete(param->provisioner_prov_complete.node_idx, + param->provisioner_prov_complete.device_uuid, + param->provisioner_prov_complete.unicast_addr, + param->provisioner_prov_complete.element_num, + param->provisioner_prov_complete.netkey_idx); + break; + case ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT, err_code: %d", + param->provisioner_add_unprov_dev_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT, err_code: %d", + param->provisioner_set_dev_uuid_match_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT, err_code: %d", + param->provisioner_set_node_name_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT: { + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT, err_code %d", param->provisioner_add_app_key_comp.err_code); + if (param->provisioner_add_app_key_comp.err_code == ESP_OK) { + esp_err_t err; + prov_info.app_idx = param->provisioner_add_app_key_comp.app_idx; + err = esp_ble_mesh_provisioner_bind_app_key_to_local_model(PROV_OWN_ADDR, prov_info.app_idx, + ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI, CID_NVAL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to bind AppKey with OnOff Client Model", __func__); + return; + } + err = esp_ble_mesh_provisioner_bind_app_key_to_local_model(PROV_OWN_ADDR, prov_info.app_idx, + ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI, CID_ESP); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to bind AppKey with Fast Prov Client Model", __func__); + return; + } + } + break; + } + case ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT, err_code %d", param->provisioner_bind_app_key_to_model_comp.err_code); + break; + default: + break; + } + return; +} + +static void example_custom_model_callback(esp_ble_mesh_model_cb_event_t event, + esp_ble_mesh_model_cb_param_t *param) +{ + uint32_t opcode; + esp_err_t err; + + switch (event) { + case ESP_BLE_MESH_MODEL_OPERATION_EVT: { + if (!param->model_operation.model || !param->model_operation.model->op || + !param->model_operation.ctx) { + ESP_LOGE(TAG, "%s: model_operation parameter is NULL", __func__); + return; + } + opcode = param->model_operation.opcode; + switch (opcode) { + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS: { + ESP_LOGI(TAG, "%s: Fast Prov Client Model receives status, opcode 0x%04x", __func__, opcode); + err = example_fast_prov_client_recv_status(param->model_operation.model, + param->model_operation.ctx, + param->model_operation.length, + param->model_operation.msg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to handle fast prov status message", __func__); + return; + } + break; + } + default: + ESP_LOGI(TAG, "%s: opcode 0x%04x", __func__, param->model_operation.opcode); + break; + } + break; + } + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_SEND_COMP_EVT, err_code %d", + param->model_send_comp.err_code); + break; + case ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT, err_code %d", + param->model_publish_comp.err_code); + break; + case ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_CLIENT_RECV_PUBLISH_MSG_EVT, opcode 0x%04x", + param->client_recv_publish_msg.opcode); + break; + case ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT, opcode 0x%04x, dst 0x%04x", + param->client_send_timeout.opcode, param->client_send_timeout.ctx->addr); + err = example_fast_prov_client_recv_timeout(param->client_send_timeout.opcode, + param->client_send_timeout.model, + param->client_send_timeout.ctx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to resend fast prov client message", __func__); + return; + } + break; + default: + break; + } +} + +static void example_config_client_callback(esp_ble_mesh_cfg_client_cb_event_t event, + esp_ble_mesh_cfg_client_cb_param_t *param) +{ + example_node_info_t *node = NULL; + uint32_t opcode; + uint16_t address; + esp_err_t err; + + ESP_LOGI(TAG, "%s, error_code = 0x%02x, event = 0x%02x, addr: 0x%04x", + __func__, param->error_code, event, param->params->ctx.addr); + + opcode = param->params->opcode; + address = param->params->ctx.addr; + + node = example_get_node_info(address); + if (!node) { + ESP_LOGE(TAG, "%s: Failed to get node info", __func__); + return; + } + + if (param->error_code) { + ESP_LOGE(TAG, "Failed to send config client message, opcode: 0x%04x", opcode); + return; + } + + switch (event) { + case ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT: + break; + case ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: { + example_fast_prov_info_set_t set = {0}; + if (!node->reprov || !ESP_BLE_MESH_ADDR_IS_UNICAST(node->unicast_min)) { + /* If the node is a new one or the node is re-provisioned but the information of the node + * has not been set before, here we will set the Fast Prov Info Set info to the node. + */ + node->node_addr_cnt = prov_info.node_addr_cnt; + node->unicast_min = prov_info.unicast_min; + node->unicast_max = prov_info.unicast_max; + node->flags = prov.flags; + node->iv_index = prov.iv_index; + node->fp_net_idx = prov_info.net_idx; + node->group_addr = prov_info.group_addr; + node->match_len = prov_info.match_len; + memcpy(node->match_val, prov_info.match_val, prov_info.match_len); + node->action = 0x81; + } + set.ctx_flags = 0x037F; + memcpy(&set.node_addr_cnt, &node->node_addr_cnt, + sizeof(example_node_info_t) - offsetof(example_node_info_t, node_addr_cnt)); + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->unicast_addr, + .timeout = 0, + .role = ROLE_PROVISIONER, + }; + err = example_send_fast_prov_info_set(fast_prov_client.model, &info, &set); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to set Fast Prov Info Set message", __func__); + return; + } + break; + } + default: + break; + } + break; + case ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT: + break; + case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: { + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->unicast_addr, + .timeout = 0, + .role = ROLE_PROVISIONER, + }; + esp_ble_mesh_cfg_app_key_add_t add_key = { + .net_idx = prov_info.net_idx, + .app_idx = prov_info.app_idx, + }; + memcpy(add_key.app_key, prov_info.app_key, 16); + err = example_send_config_appkey_add(config_client.model, &info, &add_key); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Config AppKey Add message", __func__); + return; + } + break; + } + default: + break; + } + break; + default: + ESP_LOGE(TAG, "Not a config client status message event"); + break; + } +} + +static void example_generic_client_callback(esp_ble_mesh_generic_client_cb_event_t event, + esp_ble_mesh_generic_client_cb_param_t *param) +{ + example_node_info_t *node = NULL; + uint32_t opcode; + uint16_t address; + + ESP_LOGI(TAG, "%s, error_code = 0x%02x, event = 0x%02x, addr: 0x%04x", + __func__, param->error_code, event, param->params->ctx.addr); + + opcode = param->params->opcode; + address = param->params->ctx.addr; + + node = example_get_node_info(address); + if (!node) { + ESP_LOGE(TAG, "%s: Failed to get node info", __func__); + return; + } + + if (param->error_code) { + ESP_LOGE(TAG, "Failed to send generic client message, opcode: 0x%04x", opcode); + return; + } + + switch (event) { + case ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT: + break; + case ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: + node->onoff = param->status_cb.onoff_status.present_onoff; + ESP_LOGI(TAG, "node->onoff: 0x%02x", node->onoff); + break; + default: + break; + } + break; + case ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT: + break; + case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT: + break; + default: + ESP_LOGE(TAG, "Not a generic client status message event"); + break; + } +} + +static esp_err_t ble_mesh_init(void) +{ + esp_err_t err; + + memcpy(dev_uuid, esp_bt_dev_get_address(), 6); + + prov_info.unicast_min = prov.prov_start_address + prov_info.max_node_num; + prov_info.match_len = sizeof(match); + memcpy(prov_info.match_val, match, sizeof(match)); + memset(prov_info.app_key, APP_KEY_OCTET, sizeof(prov_info.app_key)); + + esp_ble_mesh_register_prov_callback(example_provisioning_callback); + esp_ble_mesh_register_custom_model_callback(example_custom_model_callback); + esp_ble_mesh_register_config_client_callback(example_config_client_callback); + esp_ble_mesh_register_generic_client_callback(example_generic_client_callback); + + err = esp_ble_mesh_provisioner_set_dev_uuid_match(match, 0x02, 0x00, false); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to set matching device UUID", __func__); + return ESP_FAIL; + } + + err = esp_ble_mesh_init(&prov, &comp); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to initialize BLE Mesh", __func__); + return ESP_FAIL; + } + + err = esp_ble_mesh_client_model_init(&vnd_models[0]); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to initialize fast prov client model", __func__); + return ESP_FAIL; + } + + err = esp_ble_mesh_provisioner_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to enable provisioning", __func__); + return ESP_FAIL; + } + + err = esp_ble_mesh_provisioner_add_local_app_key(prov_info.app_key, prov_info.net_idx, prov_info.app_idx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to add local application key", __func__); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "BLE Mesh Provisioner initialized"); + + return err; +} + +esp_err_t bluetooth_init(void) +{ + esp_err_t ret; + + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "%s initialize controller failed", __func__); + return ret; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "%s enable controller failed", __func__); + return ret; + } + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(TAG, "%s init bluetooth failed", __func__); + return ret; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "%s enable bluetooth failed", __func__); + return ret; + } + + return ret; +} + +void app_main(void) +{ + int err; + + ESP_LOGI(TAG, "Initializing..."); + + err = bluetooth_init(); + if (err) { + ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err); + return; + } + + /* Initialize the Bluetooth Mesh Subsystem */ + err = ble_mesh_init(); + if (err) { + ESP_LOGE(TAG, "Failed to initialize BLE Mesh (err %d)", err); + } +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/component.mk b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/sdkconfig.defaults b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/sdkconfig.defaults new file mode 100644 index 0000000000..a5c4750931 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/sdkconfig.defaults @@ -0,0 +1,43 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y +CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY= +CONFIG_BTDM_CONTROLLER_MODE_BTDM= +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_BLE_SCAN_DUPLICATE=y +CONFIG_SCAN_DUPLICATE_TYPE=2 +CONFIG_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_MESH_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BTDM_CONTROLLER_FULL_SCAN_SUPPORTED=y +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_HCI_5_0=y +CONFIG_BLE_MESH_USE_DUPLICATE_SCAN=y +CONFIG_BLE_MESH_PROV=y +CONFIG_BLE_MESH_PROVISIONER=y +CONFIG_BLE_MESH_WAIT_FOR_PROV_MAX_DEV_NUM=10 +CONFIG_BLE_MESH_MAX_PROV_NODES=10 +CONFIG_BLE_MESH_MAX_STORED_NODES=10 +CONFIG_BLE_MESH_PBA_SAME_TIME=1 +CONFIG_BLE_MESH_PBG_SAME_TIME=1 +CONFIG_BLE_MESH_PROVISIONER_SUBNET_COUNT=3 +CONFIG_BLE_MESH_PROVISIONER_APP_KEY_COUNT=3 +CONFIG_BLE_MESH_PB_ADV=y +CONFIG_BLE_MESH_NET_BUF_POOL_USAGE=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_GATT_PROXY=y +CONFIG_BLE_MESH_RELAY=y +CONFIG_BLE_MESH_LOW_POWER= +CONFIG_BLE_MESH_FRIEND= +CONFIG_BTU_TASK_STACK_SIZE=4512 +CONFIG_BLE_MESH_CFG_CLI=y +CONFIG_BLE_MESH_GENERIC_ONOFF_CLI=y +CONFIG_BLE_MESH_ADV_BUF_COUNT=100 +CONFIG_BLE_MESH_CRPL=10 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SDU_MAX=384 +CONFIG_BLE_MESH_TX_SEG_MAX=32 +CONFIG_BLE_MESH_NO_LOG=n +CONFIG_BLE_MESH_STACK_TRACE_LEVEL=1 \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/tutorial/ble_mesh_fast_provision_client.md b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/tutorial/ble_mesh_fast_provision_client.md new file mode 100644 index 0000000000..60d27304ee --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/tutorial/ble_mesh_fast_provision_client.md @@ -0,0 +1,218 @@ +# 1. Introduction +## 1.1 Demo Function + +This demo completes the following functions: + +1. Provisioning an unprovisioned device and change it to a node. +2. Binding the provisioner's Appkey to its own models. +3. Sending messages to the node about the Appkey and the fast provisioning information. +4. Getting the addresses of all the nodes in the fast provisioning network. +5. Controlling the nodes by their group address. + +**Note: The demo's functionality is similar to that of the EspBleMesh app.** + +## 1.2 Node Composition + +This demo has only one element, in which the following four models are implemented: + +- The **Configuration Server** model is used to represent a mesh network configuration of a device. +- The **Configuration Client** model is used to represent an element that can control and monitor the configuration of a node. +- The **Generic OnOff Client** model controls a Generic OnOff Server via messages defined by the **Generic OnOff** model (turning on and off the lights in this demo). +- The **Vendor Client** model is used to control the `fast_prov_server` state, which defines the fast provisioning behavior of a node. + +**Note: For detailed information about these models, please refer to other BLE Mesh demos.** + +## 2. Code Analysis + +Code initialization part reference [Initializing the Bluetooth and Initializing the BLE Mesh](../../../ble_mesh_wifi_coexist/tutorial%20%20%20%20%20%20/ble_mesh_wifi_coexist.md) + +### 2.1 Data Structure + +`example_prov_info_t` is used to define the keys, the address range can be assigned by a node, and the maximum number of nodes supported by the mesh network. + +| Name |Description | +| ----------------------|------------------------- | +| `net_idx` | Netkey index value | +| `app_idx` | AppKey index value | +| `app_key[16]` | Appkey, which is used throughout the network | +| `node_addr_cnt`| The maximum number of nodes supported in the mesh network,which serves the same purpose of the `Fast provisioning count` parameter in the EspBleMesh app| +| `unicast_min` | Minimum unicast address to be assigned to the nodes in the mesh network | +| `unicast_max` | Maximum unicast address to be assigned to the nodes in the mesh network | +| `group_addr`| The group address, which is used to control the on/off state of all nodes in the mesh network, that is said, turning on and off the lights in this demo| +| `match_val[16]`| The value used by the Fast Provisioning Provisioner to filter the devices to be provisioned | +| `match_len` | The maximum length of `match_val[16]` | +| `max_node_num` | The maximum number of nodes can be provisioned by the client | + +### 2.2 Code Flow + +The events and APIs in this section are presented in the same order with code execution. + +### 2.2.1 Initialization + +#### 2.2.1.1 Set the UUID Filter + +The `esp_ble_mesh_provisioner_set_dev_uuid_match` API is called by the provisioner to set the part of the device UUID to be compared before starting to provision. + +``` +/** + * @brief This function is called by Provisioner to set the part of the device UUID + * to be compared before starting to provision. + * + * @param[in] match_val: Value to be compared with the part of the device UUID. + * @param[in] match_len: Length of the compared match value. + * @param[in] offset: Offset of the device UUID to be compared (based on zero). + * @param[in] prov_after_match: Flag used to indicate whether provisioner should start to provision + * the device immediately if the part of the UUID matches. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_set_dev_uuid_match(const uint8_t *match_val, uint8_t match_len, + uint8_t offset, bool prov_after_match); +``` + +```c +err = esp_ble_mesh_provisioner_set_dev_uuid_match(match, 0x02, 0x00, false); +if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to set matching device UUID", __func__); + return ESP_FAIL; +} +``` + + + +#### 2.2.1.2 Add local Appkey + +The provisioner has no Appkey right after it has been initialized. Therefore, you have to add a local Appkey for the provisioner by calling the `esp_ble_mesh_provisioner_add_local_app_key`. + +```c +err = esp_ble_mesh_provisioner_add_local_app_key(prov_info.app_key, prov_info.net_idx, prov_info.app_idx); +if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to add local application key", __func__); + return ESP_FAIL; +} +``` +Please check the return value of the API calling and the return value of `ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT`, and make sure that the Appkey has been added to this provisioner. + +#### 2.2.1.3 Bind Appkey to local model + +To control the server model, the client model uses messages to control the server model and these message must be encrypted by the Appkey. To that end, users must bind the Appkey of the provisioner to its local models, which are the **Generic OnOff Client** model and the **Vendor Client** model, by calling the `esp_ble_mesh_provisioner_add_local_app_key` api. + +```c +prov_info.app_idx = param->provisioner_add_app_key_comp.app_idx; +err = esp_ble_mesh_provisioner_bind_app_key_to_local_model(PROV_OWN_ADDR, prov_info.app_idx, + ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI, CID_NVAL); +if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to bind AppKey with OnOff Client Model", __func__); + return; +} +err = esp_ble_mesh_provisioner_bind_app_key_to_local_model(PROV_OWN_ADDR, prov_info.app_idx, + ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI, CID_ESP); +if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to bind AppKey with Fast Prov Client Model", __func__); + return; +} +``` +Please check the return value of the API calling and the return value of the `ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT` event, and make sure that the Appkey has been binded to the local models. + + +### 2.2.2 Provisioning a device + +The unprovisioned devices continuously send the **Unprovisioned Device** beacon, which contains the value of its UUID. + +* If the UUID matched, a `ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT` event will be triggered, which will add the unprovisioned device information to the queue of to-be-provisioned devices. + + ```c + err = esp_ble_mesh_provisioner_add_unprov_dev(&add_dev, flag); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to start provisioning a device", __func__); + return; + } + + if (!reprov) { + if (prov_info.max_node_num) { + prov_info.max_node_num--; + } + } + ``` +* If not, this device will be ignored. + +After that, all the devices in the queue will be provisioned automatically. + +### 2.2.3 Sending cache data + +Appkey is among the cache required for this node to become a provisioner. + +When the provisioning completes, an `ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT` event will be triggered, which will add the Appkey to the node's **Config Server** model by calling the `esp_ble_mesh_config_client_set_state` API: + +```c +common.opcode = ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD; +common.model = model; +common.ctx.net_idx = info->net_idx; +common.ctx.app_idx = 0x0000; /* not used for config messages */ +common.ctx.addr = info->dst; +common.ctx.send_rel = false; +common.ctx.send_ttl = 0; +common.msg_timeout = info->timeout; +common.msg_role = info->role; + +return esp_ble_mesh_config_client_set_state(&common, &set); +``` + +* If API calling succeeds, an `ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT` event will be triggered, which sends other cache information (`example_fast_prov_info_set_t`) to the node's **Vendor Server** model by calling the `example_send_fast_prov_info_set` function; + * If API calling (`example_send_fast_prov_info_set`) succeeded, a message with an opcode of `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET` will be sent, whose acknowledgement (with an opcode of `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS`) will further trigger an `ESP_BLE_MESH_MODEL_OPERATION_EVT` event + ```c + err = example_send_fast_prov_info_set(fast_prov_client.model, &info, &set); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to set Fast Prov Info Set message", __func__); + return; + } + ``` + * If API calling (`example_send_fast_prov_info_set`) times out, an `ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT` event will be triggered. +* If API calling times out, an `ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT` event is triggered. + +After that, this node has the ability to provisioning other nodes as a provisioner, and further controls other nodes. + +**Note: The message with an opcode of `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET` contains the group address of all the nodes. When a node receives this message, it will automatically subscribe the Onoff Server model of this address.** + +### 2.2.4 Controlling the node + +When the `ESP_BLE_MESH_MODEL_OPERATION_EVT` event is triggered, the provisioner starts a timer. + +```c + ESP_LOG_BUFFER_HEX("fast prov info status", data, len); +#if !defined(CONFIG_BLE_MESH_FAST_PROV) + prim_prov_addr = ctx->addr; + k_delayed_work_init(&get_all_node_addr_timer, example_get_all_node_addr); + k_delayed_work_submit(&get_all_node_addr_timer, GET_ALL_NODE_ADDR_TIMEOUT); +#endif + break; +``` +After the timers times out, the provisioner starts to get the addresses of all nodes in the mesh network by calling the `example_send_fast_prov_all_node_addr_get` function, which sends a message with an opcode of `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET`. + +```c +err = example_send_fast_prov_all_node_addr_get(model, &info); +if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Fast Prov Node Address Get message", __func__); + return; +} +``` + +After that, the provisioner will receive an acknowledgement, which is a message with an opcode of `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS`, which triggers the `ESP_BLE_MESH_MODEL_OPERATION_EVT` event. + +Then, the provisioner is able to turn on all the nodes (which are lights in this demo) by calling the `example_send_generic_onoff_set` function using the group address. + +```c +example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->group_addr, + .timeout = 0, + .role = ROLE_PROVISIONER, +}; +err = example_send_generic_onoff_set(cli_model, &info, LED_ON, 0x00, false); +if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Generic OnOff Set Unack message", __func__); + return ESP_FAIL; +} +``` diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/CMakeLists.txt new file mode 100644 index 0000000000..594effdbb8 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_mesh_fast_prov_server) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/Makefile b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/Makefile new file mode 100644 index 0000000000..f6ece484ac --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/Makefile @@ -0,0 +1,12 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_mesh_fast_prov_server + +COMPONENT_ADD_INCLUDEDIRS := components/include + +EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/README.md b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/README.md new file mode 100644 index 0000000000..2746ed629a --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/README.md @@ -0,0 +1,2 @@ +ESP BLE Mesh Fast Provisioning Server Demo +======================== \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/CMakeLists.txt new file mode 100644 index 0000000000..1eb2d87ed2 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCS "ble_mesh_demo_main.c" + "board.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/Kconfig.projbuild b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/Kconfig.projbuild new file mode 100644 index 0000000000..e48a853e0b --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/Kconfig.projbuild @@ -0,0 +1,16 @@ +menu "Example Configuration" + + choice BLE_MESH_EXAMPLE_BOARD + prompt "Board selection for BLE Mesh" + default BLE_MESH_ESP_WROOM_32 + help + Select this option to choose the board for BLE Mesh. The default is ESP32-WROOM-32 + + config BLE_MESH_ESP_WROOM_32 + bool "ESP32-WROOM-32" + + config BLE_MESH_ESP_WROVER + bool "ESP32-WROVER" + endchoice + +endmenu diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_main.c b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_main.c new file mode 100644 index 0000000000..87bc00e1d0 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_main.c @@ -0,0 +1,842 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "esp_log.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_common_api.h" +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_config_model_api.h" +#include "esp_ble_mesh_generic_model_api.h" + +#include "board.h" +#include "esp_fast_prov_operation.h" +#include "esp_fast_prov_client_model.h" +#include "esp_fast_prov_server_model.h" + +#define TAG "FAST_PROV_SERVER_DEMO" + +extern struct _led_state led_state[3]; +extern struct k_delayed_work send_self_prov_node_addr_timer; +extern bt_mesh_atomic_t fast_prov_cli_flags; + +static uint8_t dev_uuid[16] = { 0xdd, 0xdd }; +static uint8_t prov_start_num = 0; +static bool prov_start = false; + +static const esp_ble_mesh_client_op_pair_t fast_prov_cli_op_pair[] = { + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS }, +}; + +/* Configuration Client Model user_data */ +esp_ble_mesh_client_t config_client; + +/* Configuration Server Model user_data */ +esp_ble_mesh_cfg_srv_t config_server = { + .relay = ESP_BLE_MESH_RELAY_ENABLED, + .beacon = ESP_BLE_MESH_BEACON_DISABLED, +#if defined(CONFIG_BLE_MESH_FRIEND) + .friend_state = ESP_BLE_MESH_FRIEND_ENABLED, +#else + .friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED, +#endif +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_ENABLED, +#else + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED, +#endif + .default_ttl = 7, + /* 3 transmissions with 20ms interval */ + .net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20), + .relay_retransmit = ESP_BLE_MESH_TRANSMIT(2, 20), +}; + +/* Fast Prov Client Model user_data */ +esp_ble_mesh_client_t fast_prov_client = { + .op_pair_size = ARRAY_SIZE(fast_prov_cli_op_pair), + .op_pair = fast_prov_cli_op_pair, +}; + +/* Fast Prov Server Model user_data */ +example_fast_prov_server_t fast_prov_server = { + .primary_role = false, + .max_node_num = 6, + .prov_node_cnt = 0x0, + .unicast_min = ESP_BLE_MESH_ADDR_UNASSIGNED, + .unicast_max = ESP_BLE_MESH_ADDR_UNASSIGNED, + .unicast_cur = ESP_BLE_MESH_ADDR_UNASSIGNED, + .unicast_step = 0x0, + .flags = 0x0, + .iv_index = 0x0, + .net_idx = ESP_BLE_MESH_KEY_UNUSED, + .app_idx = ESP_BLE_MESH_KEY_UNUSED, + .group_addr = ESP_BLE_MESH_ADDR_UNASSIGNED, + .prim_prov_addr = ESP_BLE_MESH_ADDR_UNASSIGNED, + .match_len = 0x0, + .pend_act = FAST_PROV_ACT_NONE, + .state = STATE_IDLE, +}; + +static esp_ble_mesh_model_op_t onoff_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET, 0), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, 2), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2), + ESP_BLE_MESH_MODEL_OP_END, +}; + +ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_pub, 2 + 1, ROLE_FAST_PROV); + +static esp_ble_mesh_model_op_t fast_prov_srv_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET, 3), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD, 16), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR, 2), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET, 0), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_GROUP_ADD, 2), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_GROUP_DELETE, 2), + ESP_BLE_MESH_MODEL_OP_END, +}; + +static esp_ble_mesh_model_op_t fast_prov_cli_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS, 1), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS, 2), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK, 0), + ESP_BLE_MESH_MODEL_OP_END, +}; + +static esp_ble_mesh_model_t root_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&config_server), + ESP_BLE_MESH_MODEL_CFG_CLI(&config_client), + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, onoff_op, + &onoff_pub, &led_state[1]), +}; + +static esp_ble_mesh_model_t vnd_models[] = { + ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_SRV, + fast_prov_srv_op, NULL, &fast_prov_server), + ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI, + fast_prov_cli_op, NULL, &fast_prov_client), +}; + +static esp_ble_mesh_elem_t elements[] = { + ESP_BLE_MESH_ELEMENT(0, root_models, vnd_models), +}; + +static esp_ble_mesh_comp_t comp = { + .cid = CID_ESP, + .elements = elements, + .element_count = ARRAY_SIZE(elements), +}; + +static esp_ble_mesh_prov_t prov = { + .uuid = dev_uuid, + .output_size = 0, + .output_actions = 0, + .prov_attention = 0x00, + .prov_algorithm = 0x00, + .prov_pub_key_oob = 0x00, + .prov_static_oob_val = NULL, + .prov_static_oob_len = 0x00, + .flags = 0x00, + .iv_index = 0x00, +}; + +static void gen_onoff_get_handler(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t len, uint8_t *data) +{ + struct _led_state *led = NULL; + uint8_t send_data; + esp_err_t err; + + led = (struct _led_state *)model->user_data; + if (!led) { + ESP_LOGE(TAG, "%s: Failed to get generic onoff server model user_data", __func__); + return; + } + + send_data = led->current; + + /* Sends Generic OnOff Status as a reponse to Generic OnOff Get */ + err = esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(send_data), &send_data); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Generic OnOff Status message", __func__); + return; + } + + /* When the node receives the first Generic OnOff Get/Set/Set Unack message, it will + * start the timer used to disable fast provisioning functionality. + */ + if (!bt_mesh_atomic_test_and_set_bit(fast_prov_server.srv_flags, DISABLE_FAST_PROV_START)) { + k_delayed_work_submit(&fast_prov_server.disable_fast_prov_timer, DISABLE_FAST_PROV_TIMEOUT); + } +} + +static void gen_onoff_set_unack_handler(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t len, uint8_t *data) +{ + struct _led_state *led = NULL; + + led = (struct _led_state *)model->user_data; + if (!led) { + ESP_LOGE(TAG, "%s: Failed to get generic onoff server model user_data", __func__); + return; + } + + led->current = data[0]; + gpio_set_level(led->pin, led->current); + + /* When the node receives the first Generic OnOff Get/Set/Set Unack message, it will + * start the timer used to disable fast provisioning functionality. + */ + if (!bt_mesh_atomic_test_and_set_bit(fast_prov_server.srv_flags, DISABLE_FAST_PROV_START)) { + k_delayed_work_submit(&fast_prov_server.disable_fast_prov_timer, DISABLE_FAST_PROV_TIMEOUT); + } +} + +static void gen_onoff_set_handler(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t len, uint8_t *data) +{ + gen_onoff_set_unack_handler(model, ctx, len, data); + gen_onoff_get_handler(model, ctx, len, data); +} + +static void node_prov_complete(uint16_t net_idx, uint16_t addr, uint8_t flags, uint32_t iv_index) +{ + ESP_LOGI(TAG, "net_idx: 0x%04x, unicast_addr: 0x%04x", net_idx, addr); + ESP_LOGI(TAG, "flags: 0x%02x, iv_index: 0x%08x", flags, iv_index); + board_prov_complete(); + /* Updates the net_idx used by Fast Prov Server model, and it can also + * be updated if the Fast Prov Info Set message contains a valid one. + */ + fast_prov_server.net_idx = net_idx; +} + +static void provisioner_prov_link_open(esp_ble_mesh_prov_bearer_t bearer) +{ + ESP_LOGI(TAG, "%s: bearer %s", __func__, bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); +} + +static void provisioner_prov_link_close(esp_ble_mesh_prov_bearer_t bearer, uint8_t reason) +{ + ESP_LOGI(TAG, "%s: bearer %s, reason 0x%02x", __func__, + bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT", reason); + if (prov_start_num) { + prov_start_num--; + } +} + +static void provisioner_prov_complete(int node_idx, const uint8_t uuid[16], uint16_t unicast_addr, + uint8_t element_num, uint16_t net_idx) +{ + example_node_info_t *node = NULL; + esp_err_t err; + + if (example_is_node_exist(uuid) == false) { + fast_prov_server.prov_node_cnt++; + } + + ESP_LOG_BUFFER_HEX("Device uuid", uuid + 2, 6); + ESP_LOGI(TAG, "Unicast address 0x%04x", unicast_addr); + + /* Sets node info */ + err = example_store_node_info(uuid, unicast_addr, element_num, net_idx, + fast_prov_server.app_idx, LED_OFF); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to set node info", __func__); + return; + } + + /* Gets node info */ + node = example_get_node_info(unicast_addr); + if (!node) { + ESP_LOGE(TAG, "%s: Failed to get node info", __func__); + return; + } + + if (fast_prov_server.primary_role == true) { + /* If the Provisioner is the primary one (i.e. provisioned by the phone), it shall + * store self-provisioned node addresses; + * If the node_addr_cnt configured by the phone is small than or equal to the + * maximum number of nodes it can provision, it shall reset the timer which is used + * to send all node addresses to the phone. + */ + err = example_store_remote_node_address(unicast_addr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to store node address 0x%04x", __func__, unicast_addr); + return; + } + if (fast_prov_server.node_addr_cnt != FAST_PROV_NODE_COUNT_MIN && + fast_prov_server.node_addr_cnt <= fast_prov_server.max_node_num) { + if (bt_mesh_atomic_test_and_clear_bit(fast_prov_server.srv_flags, GATT_PROXY_ENABLE_START)) { + k_delayed_work_cancel(&fast_prov_server.gatt_proxy_enable_timer); + } + if (!bt_mesh_atomic_test_and_set_bit(fast_prov_server.srv_flags, GATT_PROXY_ENABLE_START)) { + k_delayed_work_submit(&fast_prov_server.gatt_proxy_enable_timer, GATT_PROXY_ENABLE_TIMEOUT); + } + } + } else { + /* When a device is provisioned, the non-primary Provisioner shall reset the timer + * which is used to send node addresses to the primary Provisioner. + */ + if (bt_mesh_atomic_test_and_clear_bit(&fast_prov_cli_flags, SEND_SELF_PROV_NODE_ADDR_START)) { + k_delayed_work_cancel(&send_self_prov_node_addr_timer); + } + if (!bt_mesh_atomic_test_and_set_bit(&fast_prov_cli_flags, SEND_SELF_PROV_NODE_ADDR_START)) { + k_delayed_work_submit(&send_self_prov_node_addr_timer, SEND_SELF_PROV_NODE_ADDR_TIMEOUT); + } + } + + if (bt_mesh_atomic_test_bit(fast_prov_server.srv_flags, DISABLE_FAST_PROV_START)) { + /* When a device is provisioned, and the stop_prov flag of the Provisioner has been + * set, the Provisioner shall reset the timer which is used to stop the provisioner + * functionality. + */ + k_delayed_work_cancel(&fast_prov_server.disable_fast_prov_timer); + k_delayed_work_submit(&fast_prov_server.disable_fast_prov_timer, DISABLE_FAST_PROV_TIMEOUT); + } + + /* The Provisioner will send Config AppKey Add to the node. */ + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->unicast_addr, + .timeout = 0, + .role = ROLE_FAST_PROV, + }; + err = example_send_config_appkey_add(config_client.model, &info, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Config AppKey Add message", __func__); + return; + } +} + +static void example_recv_unprov_adv_pkt(uint8_t dev_uuid[16], uint8_t addr[ESP_BD_ADDR_LEN], + esp_ble_addr_type_t addr_type, uint16_t oob_info, + uint8_t adv_type, esp_ble_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_unprov_dev_add_t add_dev = {0}; + esp_ble_mesh_dev_add_flag_t flag; + esp_err_t err; + + /* In Fast Provisioning, the Provisioner should only use PB-ADV to provision devices. */ + if (prov_start && (bearer & ESP_BLE_MESH_PROV_ADV)) { + /* Checks if the device is a reprovisioned one. */ + if (example_is_node_exist(dev_uuid) == false) { + if ((prov_start_num >= fast_prov_server.max_node_num) || + (fast_prov_server.prov_node_cnt >= fast_prov_server.max_node_num)) { + return; + } + } + + add_dev.addr_type = (uint8_t)addr_type; + add_dev.oob_info = oob_info; + add_dev.bearer = (uint8_t)bearer; + memcpy(add_dev.uuid, dev_uuid, 16); + memcpy(add_dev.addr, addr, ESP_BD_ADDR_LEN); + flag = ADD_DEV_RM_AFTER_PROV_FLAG | ADD_DEV_START_PROV_NOW_FLAG | ADD_DEV_FLUSHABLE_DEV_FLAG; + err = esp_ble_mesh_provisioner_add_unprov_dev(&add_dev, flag); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to start provisioning device", __func__); + return; + } + + /* If adding unprovisioned device successfully, increase prov_start_num */ + prov_start_num++; + } + + return; +} + +static void example_ble_mesh_provisioning_cb(esp_ble_mesh_prov_cb_event_t event, + esp_ble_mesh_prov_cb_param_t *param) +{ + esp_err_t err; + + switch (event) { + case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROV_REGISTER_COMP_EVT, err_code: %d", + param->prov_register_comp.err_code); + break; + case ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT, err_code: %d", + param->node_prov_enable_comp.err_code); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT, bearer: %s", + param->node_prov_link_open.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT, bearer: %s", + param->node_prov_link_close.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); + break; + case ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT"); + node_prov_complete(param->node_prov_complete.net_idx, param->node_prov_complete.addr, + param->node_prov_complete.flags, param->node_prov_complete.iv_index); + break; + case ESP_BLE_MESH_NODE_PROXY_GATT_DISABLE_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROXY_GATT_DISABLE_COMP_EVT"); + if (fast_prov_server.primary_role == true) { + config_server.relay = ESP_BLE_MESH_RELAY_DISABLED; + } + prov_start = true; + break; + case ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT: + example_recv_unprov_adv_pkt(param->provisioner_recv_unprov_adv_pkt.dev_uuid, param->provisioner_recv_unprov_adv_pkt.addr, + param->provisioner_recv_unprov_adv_pkt.addr_type, param->provisioner_recv_unprov_adv_pkt.oob_info, + param->provisioner_recv_unprov_adv_pkt.adv_type, param->provisioner_recv_unprov_adv_pkt.bearer); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT"); + provisioner_prov_link_open(param->provisioner_prov_link_open.bearer); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT"); + provisioner_prov_link_close(param->provisioner_prov_link_close.bearer, + param->provisioner_prov_link_close.reason); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT"); + provisioner_prov_complete(param->provisioner_prov_complete.node_idx, + param->provisioner_prov_complete.device_uuid, + param->provisioner_prov_complete.unicast_addr, + param->provisioner_prov_complete.element_num, + param->provisioner_prov_complete.netkey_idx); + break; + case ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT, err_code: %d", + param->provisioner_add_unprov_dev_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT, err_code: %d", + param->provisioner_set_dev_uuid_match_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT, err_code: %d", + param->provisioner_set_node_name_comp.err_code); + break; + case ESP_BLE_MESH_SET_FAST_PROV_INFO_COMP_EVT: { + ESP_LOGI(TAG, "ESP_BLE_MESH_SET_FAST_PROV_INFO_COMP_EVT"); + ESP_LOGI(TAG, "status_unicast: 0x%02x, status_net_idx: 0x%02x, status_match 0x%02x", + param->set_fast_prov_info_comp.status_unicast, + param->set_fast_prov_info_comp.status_net_idx, + param->set_fast_prov_info_comp.status_match); + err = example_handle_fast_prov_info_set_comp_evt(fast_prov_server.model, + param->set_fast_prov_info_comp.status_unicast, + param->set_fast_prov_info_comp.status_net_idx, + param->set_fast_prov_info_comp.status_match); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to handle Fast Prov Info Set complete event", __func__); + return; + } + break; + } + case ESP_BLE_MESH_SET_FAST_PROV_ACTION_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_SET_FAST_PROV_ACTION_COMP_EVT, status_action 0x%02x", + param->set_fast_prov_action_comp.status_action); + err = example_handle_fast_prov_action_set_comp_evt(fast_prov_server.model, + param->set_fast_prov_action_comp.status_action); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to handle Fast Prov Action Set complete event", __func__); + return; + } + break; + default: + break; + } + + return; +} + +static void example_ble_mesh_custom_model_cb(esp_ble_mesh_model_cb_event_t event, + esp_ble_mesh_model_cb_param_t *param) +{ + uint32_t opcode; + esp_err_t err; + + switch (event) { + case ESP_BLE_MESH_MODEL_OPERATION_EVT: { + if (!param->model_operation.model || !param->model_operation.model->op || + !param->model_operation.ctx) { + ESP_LOGE(TAG, "%s: model_operation parameter is NULL", __func__); + return; + } + opcode = param->model_operation.opcode; + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: + gen_onoff_set_handler(param->model_operation.model, param->model_operation.ctx, + param->model_operation.length, param->model_operation.msg); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK: + gen_onoff_set_unack_handler(param->model_operation.model, param->model_operation.ctx, + param->model_operation.length, param->model_operation.msg); + break; + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_GROUP_ADD: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_GROUP_DELETE: { + ESP_LOGI(TAG, "%s: Fast prov server receives msg, opcode 0x%04x", __func__, opcode); + struct net_buf_simple buf = { + .len = param->model_operation.length, + .data = param->model_operation.msg, + }; + err = example_fast_prov_server_recv_msg(param->model_operation.model, + param->model_operation.ctx, &buf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to handle fast prov client message", __func__); + return; + } + break; + } + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK: { + ESP_LOGI(TAG, "%s: Fast prov client receives msg, opcode 0x%04x", __func__, opcode); + err = example_fast_prov_client_recv_status(param->model_operation.model, + param->model_operation.ctx, + param->model_operation.length, + param->model_operation.msg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to handle fast prov server message", __func__); + return; + } + break; + } + default: + ESP_LOGI(TAG, "%s: opcode 0x%04x", __func__, param->model_operation.opcode); + break; + } + break; + } + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_SEND_COMP_EVT, err_code %d", param->model_send_comp.err_code); + switch (param->model_send_comp.opcode) { + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS: + err = example_handle_fast_prov_status_send_comp_evt(param->model_send_comp.err_code, + param->model_send_comp.opcode, + param->model_send_comp.model, + param->model_send_comp.ctx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to handle fast prov status send complete event", __func__); + return; + } + break; + default: + break; + } + break; + case ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT, err_code %d", + param->model_publish_comp.err_code); + break; + case ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_CLIENT_RECV_PUBLISH_MSG_EVT, opcode 0x%04x", + param->client_recv_publish_msg.opcode); + break; + case ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT, opcode 0x%04x, dst 0x%04x", + param->client_send_timeout.opcode, param->client_send_timeout.ctx->addr); + err = example_fast_prov_client_recv_timeout(param->client_send_timeout.opcode, + param->client_send_timeout.model, + param->client_send_timeout.ctx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Faield to resend fast prov client message", __func__); + return; + } + break; + default: + break; + } +} + +static void example_ble_mesh_config_client_cb(esp_ble_mesh_cfg_client_cb_event_t event, + esp_ble_mesh_cfg_client_cb_param_t *param) +{ + example_node_info_t *node = NULL; + uint32_t opcode; + uint16_t address; + esp_err_t err; + + ESP_LOGI(TAG, "%s, error_code = 0x%02x, event = 0x%02x, addr: 0x%04x", + __func__, param->error_code, event, param->params->ctx.addr); + + opcode = param->params->opcode; + address = param->params->ctx.addr; + + node = example_get_node_info(address); + if (!node) { + ESP_LOGE(TAG, "%s: Failed to get node info", __func__); + return; + } + + if (param->error_code) { + ESP_LOGE(TAG, "Failed to send config client message, opcode: 0x%04x", opcode); + return; + } + + switch (event) { + case ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT: + break; + case ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: { + example_fast_prov_info_set_t set = {0}; + if (node->reprov == false) { + /* After sending Config AppKey Add successfully, start to send Fast Prov Info Set */ + if (fast_prov_server.unicast_cur >= fast_prov_server.unicast_max) { + /* TODO: + * 1. If unicast_cur is >= unicast_max, we can also send the message to enable + * the Provisioner functionality on the node, and need to add another vendor + * message used by the node to require a new unicast address range from primary + * Provisioner, and before get the correct response, the node should pend + * the fast provisioning functionality. + * 2. Currently if address is not enough, the Provisioner will only add the group + * address to the node. + */ + ESP_LOGW(TAG, "%s: Not enough address to be assigned", __func__); + node->lack_of_addr = true; + } else { + /* Send fast_prov_info_set message to node */ + node->lack_of_addr = false; + node->unicast_min = fast_prov_server.unicast_cur; + if (fast_prov_server.unicast_cur + fast_prov_server.unicast_step >= fast_prov_server.unicast_max) { + node->unicast_max = fast_prov_server.unicast_max; + } else { + node->unicast_max = fast_prov_server.unicast_cur + fast_prov_server.unicast_step; + } + node->flags = fast_prov_server.flags; + node->iv_index = fast_prov_server.iv_index; + node->fp_net_idx = fast_prov_server.net_idx; + node->group_addr = fast_prov_server.group_addr; + node->prov_addr = fast_prov_server.prim_prov_addr; + node->match_len = fast_prov_server.match_len; + memcpy(node->match_val, fast_prov_server.match_val, fast_prov_server.match_len); + node->action = FAST_PROV_ACT_ENTER; + fast_prov_server.unicast_cur = node->unicast_max + 1; + } + } + if (node->lack_of_addr == false) { + set.ctx_flags = 0x03FE; + memcpy(&set.unicast_min, &node->unicast_min, + sizeof(example_node_info_t) - offsetof(example_node_info_t, unicast_min)); + } else { + set.ctx_flags = BIT(6); + set.group_addr = fast_prov_server.group_addr; + } + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->unicast_addr, + .timeout = 0, + .role = ROLE_FAST_PROV, + }; + err = example_send_fast_prov_info_set(fast_prov_client.model, &info, &set); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Fast Prov Info Set message", __func__); + return; + } + break; + } + default: + break; + } + break; + case ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT: + break; + case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: { + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->unicast_addr, + .timeout = 0, + .role = ROLE_FAST_PROV, + }; + err = example_send_config_appkey_add(config_client.model, &info, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Config AppKey Add message", __func__); + return; + } + break; + } + default: + break; + } + break; + default: + return; + } +} + +static void example_ble_mesh_config_server_cb(esp_ble_mesh_cfg_server_cb_event_t event, + esp_ble_mesh_cfg_server_cb_param_t *param) +{ + esp_err_t err; + + ESP_LOGI(TAG, "%s, event = 0x%02x, opcode = 0x%04x, addr: 0x%04x", + __func__, event, param->ctx.recv_op, param->ctx.addr); + + switch (event) { + case ESP_BLE_MESH_CFG_SERVER_RECV_MSG_EVT: + switch (param->ctx.recv_op) { + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: + ESP_LOGI(TAG, "Config Server get Config AppKey Add"); + err = example_handle_config_app_key_add_evt(param->status_cb.app_key_add.app_idx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to bind app_idx 0x%04x with non-config models", + __func__, param->status_cb.app_key_add.app_idx); + return; + } + break; + default: + break; + } + break; + default: + return; + } +} + +static esp_err_t ble_mesh_init(void) +{ + esp_err_t err; + + /* First two bytes of device uuid is compared with match value by Provisioner */ + memcpy(dev_uuid + 2, esp_bt_dev_get_address(), 6); + + esp_ble_mesh_register_prov_callback(example_ble_mesh_provisioning_cb); + esp_ble_mesh_register_custom_model_callback(example_ble_mesh_custom_model_cb); + esp_ble_mesh_register_config_client_callback(example_ble_mesh_config_client_cb); + esp_ble_mesh_register_config_server_callback(example_ble_mesh_config_server_cb); + + err = esp_ble_mesh_init(&prov, &comp); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to initialize BLE Mesh", __func__); + return err; + } + + err = example_fast_prov_server_init(&vnd_models[0]); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to initialize fast prov server model", __func__); + return err; + } + + err = esp_ble_mesh_client_model_init(&vnd_models[1]); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to initialize fast prov client model", __func__); + return err; + } + + k_delayed_work_init(&send_self_prov_node_addr_timer, example_send_self_prov_node_addr); + + err = esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to enable node provisioning", __func__); + return err; + } + + ESP_LOGI(TAG, "BLE Mesh Fast Prov Node initialized"); + + board_led_operation(LED_B, LED_ON); + + return ESP_OK; +} + +static esp_err_t bluetooth_init(void) +{ + esp_err_t ret; + + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "%s initialize controller failed", __func__); + return ret; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "%s enable controller failed", __func__); + return ret; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(TAG, "%s init bluetooth failed", __func__); + return ret; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "%s enable bluetooth failed", __func__); + return ret; + } + + return ret; +} + +void app_main(void) +{ + esp_err_t err; + + ESP_LOGI(TAG, "Initializing..."); + + err = board_init(); + if (err) { + ESP_LOGE(TAG, "board_init failed (err %d)", err); + return; + } + + err = bluetooth_init(); + if (err) { + ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err); + return; + } + + /* Initialize the Bluetooth Mesh Subsystem */ + err = ble_mesh_init(); + if (err) { + ESP_LOGE(TAG, "Bluetooth mesh init failed (err %d)", err); + return; + } +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.c b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.c new file mode 100644 index 0000000000..4c51d04af7 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.c @@ -0,0 +1,72 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "driver/gpio.h" +#include "board.h" +#include "esp_fast_prov_common.h" + +#define TAG "BOARD" + +struct _led_state led_state[3] = { + { LED_OFF, LED_OFF, LED_R, "red" }, + { LED_OFF, LED_OFF, LED_G, "green" }, + { LED_OFF, LED_OFF, LED_B, "blue" }, +}; + +void board_output_number(esp_ble_mesh_output_action_t action, uint32_t number) +{ + ESP_LOGI(TAG, "Board output number %d", number); +} + +void board_prov_complete(void) +{ + board_led_operation(LED_B, LED_OFF); +} + +void board_led_operation(uint8_t pin, uint8_t onoff) +{ + for (int i = 0; i < 3; i++) { + if (led_state[i].pin != pin) { + continue; + } + if (onoff == led_state[i].previous) { + ESP_LOGW(TAG, "led %s is already %s", + led_state[i].name, (onoff ? "on" : "off")); + return; + } + gpio_set_level(pin, onoff); + led_state[i].previous = onoff; + return; + } + + ESP_LOGE(TAG, "LED is not found!"); +} + +static void board_led_init(void) +{ + for (int i = 0; i < 3; i++) { + gpio_pad_select_gpio(led_state[i].pin); + gpio_set_direction(led_state[i].pin, GPIO_MODE_OUTPUT); + gpio_set_level(led_state[i].pin, LED_OFF); + led_state[i].previous = LED_OFF; + } +} + +esp_err_t board_init(void) +{ + board_led_init(); + return ESP_OK; +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.h b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.h new file mode 100644 index 0000000000..b00fc1e04b --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.h @@ -0,0 +1,47 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _BOARD_H_ +#define _BOARD_H_ + +#include "sdkconfig.h" +#include "driver/gpio.h" +#include "esp_ble_mesh_defs.h" + +#ifdef CONFIG_BLE_MESH_ESP_WROOM_32 +#define LED_R GPIO_NUM_25 +#define LED_G GPIO_NUM_26 +#define LED_B GPIO_NUM_27 +#elif defined(CONFIG_BLE_MESH_ESP_WROVER) +#define LED_R GPIO_NUM_0 +#define LED_G GPIO_NUM_2 +#define LED_B GPIO_NUM_4 +#endif + +struct _led_state { + uint8_t current; + uint8_t previous; + uint8_t pin; + char *name; +}; + +void board_output_number(esp_ble_mesh_output_action_t action, uint32_t number); + +void board_prov_complete(void); + +void board_led_operation(uint8_t pin, uint8_t onoff); + +esp_err_t board_init(void); + +#endif diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/component.mk b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/sdkconfig.defaults b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/sdkconfig.defaults new file mode 100644 index 0000000000..4f6ca5787a --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/sdkconfig.defaults @@ -0,0 +1,51 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y +CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY= +CONFIG_BTDM_CONTROLLER_MODE_BTDM= +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_BLE_SCAN_DUPLICATE=y +CONFIG_SCAN_DUPLICATE_TYPE=2 +CONFIG_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_MESH_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BTDM_CONTROLLER_FULL_SCAN_SUPPORTED=y +CONFIG_GATTS_ENABLE=y +CONFIG_GATTS_SEND_SERVICE_CHANGE_MANUAL=y +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_HCI_5_0=y +CONFIG_BLE_MESH_USE_DUPLICATE_SCAN=y +CONFIG_BLE_MESH_FAST_PROV=y +CONFIG_BLE_MESH_PROV=y +CONFIG_BLE_MESH_NODE=y +CONFIG_BLE_MESH_PROVISIONER=y +CONFIG_BLE_MESH_WAIT_FOR_PROV_MAX_DEV_NUM=20 +CONFIG_BLE_MESH_MAX_STORED_NODES=10 +CONFIG_BLE_MESH_MAX_PROV_NODES=6 +CONFIG_BLE_MESH_PBA_SAME_TIME=3 +CONFIG_BLE_MESH_PBG_SAME_TIME=3 +CONFIG_BLE_MESH_PROVISIONER_SUBNET_COUNT=3 +CONFIG_BLE_MESH_PROVISIONER_APP_KEY_COUNT=9 +CONFIG_BLE_MESH_PB_ADV=y +CONFIG_BLE_MESH_NET_BUF_POOL_USAGE=y +CONFIG_BLE_MESH_SUBNET_COUNT=2 +CONFIG_BLE_MESH_APP_KEY_COUNT=3 +CONFIG_BLE_MESH_MODEL_KEY_COUNT=3 +CONFIG_BLE_MESH_MODEL_GROUP_COUNT=3 +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_GATT_PROXY=y +CONFIG_BLE_MESH_RELAY=y +CONFIG_BLE_MESH_LOW_POWER= +CONFIG_BLE_MESH_FRIEND= +CONFIG_BTU_TASK_STACK_SIZE=4512 +CONFIG_BLE_MESH_CFG_CLI=y +CONFIG_BLE_MESH_CRPL=60 +CONFIG_BLE_MESH_MSG_CACHE_SIZE=60 +CONFIG_BLE_MESH_ADV_BUF_COUNT=200 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SDU_MAX=384 +CONFIG_BLE_MESH_TX_SEG_MAX=32 +CONFIG_BLE_MESH_NO_LOG=n +CONFIG_BLE_MESH_STACK_TRACE_LEVEL=0 \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/EspBleMesh.md b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/EspBleMesh.md new file mode 100644 index 0000000000..274ecf581d --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/EspBleMesh.md @@ -0,0 +1,93 @@ +# Demo Function + +This demo demonstrates the fast provisioning of ESP BLE Mesh network and how to use the EspBleMesh app to control an individual provisioned node or all the provisioned nodes. + +A video of this demo can be seen +[here](http://download.espressif.com/BLE_MESH/BLE_Mesh_Demo/V0.4_Demo_Fast_Provision/ESP32_BLE_Mesh_Fast_Provision.mp4) + +# What You Need + +* [EspBleMesh App for Android](http://download.espressif.com/BLE_MESH/BLE_Mesh_Tools/BLE_Mesh_App/EspBleMesh-0.9.4.apk) +* [ESP BLE Mesh SDK v0.6(Beta Version)](https://glab.espressif.cn/ble_mesh/esp-ble-mesh-v0.6) +* ESP32 Development Boards + +> Note: +> +> 1. Please flash the [`ble_mesh_fast_prov_server`](https://glab.espressif.cn/ble_mesh/esp-ble-mesh-v0.6/tree/ble_mesh_release/esp-ble-mesh-v0.6/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server) to your boards first; +> 2. To have a better understanding of the performance of the BLE Mesh network, we recommend that at least 3 devices should be added in your network. +> 3. We recommend that you solder LED indicators if your development board does not come with lights. +> 4. Please check the type of board and LED pin definition enabled in `Example BLE Mesh Config` by running `make menuconfig` + +![Board](images/device.png) + + +# Flash and Monitor + +1. Enter the directory: +examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server +2. Make sure that the `IDF_PATH` environment variable was set in accordance with your current IDF path +3. Check the version of your toolchain. Version 4.1 or newer should be used. + +![Checkenvironment](images/picture1.png) + +4. Run `make -j4 flash` to compile codes and flash the codes to the device. + +![compiledcode](images/picture2.png) + +> Note: +> +> Please click on the Exit button if you see the following windows. + + +5. Please establish a connection between your device and PC, using the correct serial number, if you want to monitor the operation of this device on PC. + +# How to Use the App + +Please launch the `EspBleMesh` app, and follow the steps described below to establish a BLE Mesh network and control any individual node or all the nodes. + +![App steps](images/app_ble.png) +1. Click on the upper left corner to see more options; +2. Click on **Provisioning** to scan nearby unprovisioned devices; +3. Choose any unprovisioned devices in the scanned list; +4. Enter the number of devices you want to add in your mesh network; +> Note: +> +> If you only want to use the normal provisioning feature,You don't check the option for fast provisioning. +5. Wait until all the devices are provisioned; +6. Click on the upper left corner to see more options; +7. Click on **Fast Provisioned** to see all the provisioned devices; +8. Control your devices. + +> Note: +> +> Please disable your Bluetooth function on your phone, enable it and try again, if you have encountered any connection issues. + + +# Procedure + +## Role + +* Phone - Top Provisioner +* The device that has been provisioned by Phone - Primary Provisioner +* Devices that have been provisioned and changed to the role of a provisioner - Temporary Provisioner +* Devices that have been provisioned but not changed to the role of a provisioner - Node + +## Interaction + +![Interaction](images/time.png) +1. The Top Provisioner configures the first device to access the network with the GATT bearer. +2. The Top Provisioner sends the `send_config_appkey_add` message to allocate the Appkey to this device. +3. The Top Provisioner sends the `send_fast_prov_info_set` message to provide the necessary information so the device can be changed to a Primary Provisioner. +4. The device calls the `esp_ble_mesh_set_fast_prov_action` API to change itself into the role of a Primary Provisioner and disconnects with the Top Provisioner. +5. The Primary Provisioner sends the `send_config_appkey_add` message to allocate the Appkey to an other device. +6. The Primary Provisioner sends the `send_fast_prov_info_set` message to provide the necessary information so the device can be changed to a Temporary Provisioner. +7. The device calls the `esp_ble_mesh_set_fast_prov_action` API to change itself into the role of a Temporary Provisioner and starts its address timer. +8. The Temporary Provisioner collects the addresses of nodes that it has provisioned and sends these addresses to the Primary Provisioner, when its address timer times out, which indicates the Temporary Provisioner hasn't provisioned any devices for 10s. +9. The Primary Provisioner reconnects to the Top Provisioner when its address timer times out, which indicates the Primary Provisioner hasn't received any messages from the Temporary Provisioners for 10s. +10. The Top Provisioner sends the `node_adress_Get` message automatically after reconnecting with the Primary Provisioner. +11. At this point, the Top Provisioner is able to control any nodes in the BLE Mesh Network. + +> Note: +> +> The nodes in the BLE Mesh Network only disable its provisioner functionality after it has been controlled by the Top Provisioner for at least one time. + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/ble_mesh_fast_provision_server.md b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/ble_mesh_fast_provision_server.md new file mode 100644 index 0000000000..d1e597cda6 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/ble_mesh_fast_provision_server.md @@ -0,0 +1,409 @@ +# 1. Introduction +## 1.1 Demo Function + +This demo is used for fast provisioning networks. It takes no more than 60 seconds to provisioning 100 devices in this demo. + +This demo must be used with the EspBleMesh app. For details about how to use the EspBleMesh app, please click [here](EspBleMesh.md). + +## 1.2 Node Composition + +This demo has only one element, where the following five Models are implemented: + +- The **Configuration Server** Model is used to represent a mesh network configuration of a device. +- The **Configuration Client** Model is used to represent an element that can control and monitor the configuration of a node. +- The **Generic OnOff Server** Model implements the node's Onoff state. +- The **Vendor Server** Model implements the node's `fast_prov_server` state. +- The **Vendor Client** Model is used to control the `fast_prov_server` state, which defines the fast provisioning behavior of a node. + + +## 2. Code Analysis + +Code initialization part reference [Initializing the Bluetooth and Initializing the BLE Mesh](../../../ble_mesh_wifi_coexist/tutorial%20%20%20%20%20%20/ble_mesh_wifi_coexist.md) + +### 2.1 Data Structure + +This section introduces the `example_fast_prov_server_t` strut for this demo, and its variables in groups. + +``` +typedef struct { + esp_ble_mesh_model_t *model; /* Fast Prov Server Model pointer */ + ATOMIC_DEFINE(srv_flags, SRV_MAX_FLAGS); + + bool primary_role; /* Indicate if the device is a Primary Provisioner */ + uint8_t max_node_num; /* The maximum number of devices can be provisioned by the Provisioner */ + uint8_t prov_node_cnt; /* Number of self-provisioned nodes */ + uint16_t app_idx; /* AppKey index of the application key added by other Provisioner */ + uint16_t top_address; /* Address of the device(e.g. phone) which triggers fast provisioning */ + + esp_ble_mesh_msg_ctx_t ctx; /* the context stored for sending fast prov status message */ + struct fast_prov_info_set *set_info; /* Used to store received fast prov info set context */ + + uint16_t node_addr_cnt; /* Number of node address shall be received */ + uint16_t unicast_min; /* Minimum unicast address can be send to other nodes */ + uint16_t unicast_max; /* Maximum unicast address can be send to other nodes */ + uint16_t unicast_cur; /* Current unicast address can be assigned */ + uint16_t unicast_step; /* Unicast address change step */ + uint8_t flags; /* Flags state */ + uint32_t iv_index; /* Iv_index state */ + uint16_t net_idx; /* Netkey index state */ + uint16_t group_addr; /* Subscribed group address */ + uint16_t prim_prov_addr; /* Unicast address of Primary Provisioner */ + uint8_t match_val[16]; /* Match value to be compared with unprovisioned device UUID */ + uint8_t match_len; /* Length of match value to be compared */ + + uint8_t pend_act; /* Pending action to be performed */ + uint8_t state; /* Fast prov state -> 0: idle, 1: pend, 2: active */ + + struct k_delayed_work disable_fast_prov_timer; /* Used to disable fast provisioning */ + struct k_delayed_work gatt_proxy_enable_timer; /* Used to Mesh GATT Proxy functionality */ +} __attribute__((packed)) example_fast_prov_server_t; +``` + + +#### 2.1.1 Provisioner Role and State + +Different provisioners have different behaviors and it’s helpful to understand the concepts of different roles so you can better understand the codes. + +In the struct, there are three variables that are related to roles and states, which are described in the following table: + +| Variable Name |Description | +| ---------------------|------------------------- | +| `primary_role` | Provisioner identity | +| `state` | Fast provisioner state (0: idle, 1: pend, 2: active) | +| `srv_flags` | Flags (`DISABLE_FAST_PROV_START`,`GATT_PROXY_ENABLE_START`,`RELAY_PROXY_DISABLED`,`SRV_MAX_FLAGS`) | + +Among which, there are four roles in this demo (`primary_role`): + +* Phone - Top Provisioner +* The device that has been provisioned by Phone - Primary Provisioner +* Devices that have been provisioned and changed to the role of a provisioner - Temporary Provisioner +* Devices that have been provisioned but not changed to the role of a provisioner - Node + + +#### 2.1.2 Provisioner Address Management + +The provisioner address management is used to assign a unicast address to each node, so as to prevent address conflicts by allocating address in an equally manner. Each provisioner has its own address range and a maximum number of the nodes it can provisioned. The provisioner will allocate a subset of its address range to the nodes it has provisioned. + +Example: A top provisioner's address range is 0 to 100 and the maximum number of nodes it can provisioned is 5. The provisioner address management will assign subsets of address range to these 5 nodes, which are 1 to 20, 21 to 40, 41 to 60, 61 to 80 and 81 to 100. + +The variables that are related to the address management are described in the following table: + +| Variable Name |Description | +| ----------------------|------------------------- | +| `unicast_min` | Minimum unicast address can be allocated to other nodes | +| `unicast_max` | Maximum unicast address can be allocated to other nodes | +| `unicast_cur` | Current unicast address | +| `unicast_step` | Unicast address change step Offset| + +#### 2.1.3 Provisioner Cache Data + +The cache data is required, so a node can change its role to become a provisioner. During this process, the `esp_ble_mesh_set_fast_prov_info` and `esp_ble_mesh_set_fast_prov_action` APIs are called. + +The node's cache data, which are described in the following table, is sent by the provisioner. + +| Variable Name |Description | +| ----------------------|------------------------- | +| `flags` |Flags state| +| `iv_index` |Iv_index state| +| `net_idx` |Netkey index state | +| `group_addr` |Subscribed group address | +| `prim_prov_addr` |Unicast address of Primary Provisioner | +| `match_val[16]` |Match value to be compared with unprovisioned device UUID | +| `match_len` | Length of match value to be compared | +| `max_node_num` | The maximum number of devices can be provisioned by the Provisioner | +| `prov_node_cnt` | Number of self-provisioned nodes | +| `app_idx` | AppKey index of the application key added by other Provisioner | +| `top_address` | Address of the device(e.g. phone) which triggers fast provisioning | + + +#### 2.1.4 Provisioner Timer + +There are two timers in this demo, which are: + +1. `gatt_proxy_enable_timer` is used to enable Mesh GATT Proxy functionality. + * The timer starts or resets and starts when a Temporary Provisioner provisions an unprovisioned device. + * The Temporary Provisioner will send a message (Address information) to the Primary Provisioner. +2. `disable_fast_prov_timer` is used to disable the provisioning capabilities. + * The node starts the timer when it receives a **Generic OnOff Get/Set/Set Unack** message sent by the EspBleMesh app. The group address should be used if you want to disable the provisioning capabilities of all nodes. + +The variables that are related to these two timers are described below: + +| Variable Name |Description | +| ----------------------|------------------------- | +| `disable_fast_prov_timer` |Used to disable fast provisioning| +| `gatt_proxy_enable_timer` |Used to enable Mesh GATT Proxy functionality| + +### 2.2 Model Definition + +#### 2.2.1 Vendor Server Model + +The **Vendor Server** Model implements the node's `fast_prov_server` state, which has been covered in the previous section. + +```c +example_fast_prov_server_t fast_prov_server = { + .primary_role = false, + .max_node_num = 6, + .prov_node_cnt = 0x0, + .unicast_min = ESP_BLE_MESH_ADDR_UNASSIGNED, + .unicast_max = ESP_BLE_MESH_ADDR_UNASSIGNED, + .unicast_cur = ESP_BLE_MESH_ADDR_UNASSIGNED, + .unicast_step = 0x0, + .flags = 0x0, + .iv_index = 0x0, + .net_idx = ESP_BLE_MESH_KEY_UNUSED, + .app_idx = ESP_BLE_MESH_KEY_UNUSED, + .group_addr = ESP_BLE_MESH_ADDR_UNASSIGNED, + .prim_prov_addr = ESP_BLE_MESH_ADDR_UNASSIGNED, + .match_len = 0x0, + .pend_act = FAST_PROV_ACT_NONE, + .state = STATE_IDLE, +}; +``` + +The `fast_prov_srv_op` is used to register the minimum length of messages. For example, the minimum length of the `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET` message is registered as 3 octets. + +```c +static esp_ble_mesh_model_op_t fast_prov_srv_op[] = { + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET, 3, NULL }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD, 16, NULL }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR, 2, NULL }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET, 0, NULL }, + ESP_BLE_MESH_MODEL_OP_END, +}; + +``` +The `example_fast_prov_server_init` function is used to register the callback function triggered when the timers timeout, and initializes the Model-related variables in the data struct. + +```c +err = example_fast_prov_server_init(&vnd_models[0]); +if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to initialize fast prov server model", __func__); + return err; +} +``` + +The `fast_prov_server` struct represents the Vendor server's states. The `CID_ESP` and `ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_SRV` constants, which consist of the vendor server Model's Model id `ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_SRV`, are used to identity the Vendor server Model. + + +```c +static esp_ble_mesh_model_t vnd_models[] = { + ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_SRV, + fast_prov_srv_op, NULL, &fast_prov_server), +}; +static esp_ble_mesh_elem_t elements[] = { + ESP_BLE_MESH_ELEMENT(0, root_models, vnd_models), +}; +``` + + +#### 2.2.2 Vendor Client Model + +The **Vendor Client** Model is used to control the `fast_prov_server` state, which defines the fast provisioning behavior of a node. + +The `fast_prov_cli_op_pair` struct is used to register the corresponding message acknowledgements. + +```c +static const esp_ble_mesh_client_op_pair_t fast_prov_cli_op_pair[] = { + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS }, +}; +``` + +Example: The **Vendor Client** Model sends message with an opcode of `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET`, which requires the **Vendor Server** Model to respond with a message with an opcode of `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS`. After that, the **Vendor Client** Model times out if it receives no corresponding acknowledgement. + +```c +static const esp_ble_mesh_client_op_pair_t fast_prov_cli_op_pair[] = { + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS }, +}; +``` +Note that you can also use the code below if you don't want the **Vendor Client** Model to wait for acknowledgement from the server Model, which means the client Model will never time out. + +```c +static const esp_ble_mesh_client_op_pair_t fast_prov_cli_op_pair[] = { + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET, NULL }, +}; +``` + +The `esp_ble_mesh_client_model_init` API is used to register the callback function triggered when the timers timeout, and initializes the Model-related variables in the data struct. + +```c +err = esp_ble_mesh_client_model_init(&vnd_models[1]); +if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to initialize fast prov client Model", __func__); + return err; +} +``` + +The `CID_ESP` and `ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI` constants, which consist of the vendor client Model's Model id `ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI`, are used to identity the Vendor client Model. + +```c + +esp_ble_mesh_client_t fast_prov_client = { + .op_pair_size = ARRAY_SIZE(fast_prov_cli_op_pair), + .op_pair = fast_prov_cli_op_pair, +}; + +static esp_ble_mesh_model_op_t fast_prov_cli_op[] = { + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS, 1, NULL }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS, 2, NULL }, + { ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK, 0, NULL }, + ESP_BLE_MESH_MODEL_OP_END, +}; + +static esp_ble_mesh_model_t vnd_models[] = { + ESP_BLE_MESH_VENDOR_MODEL(CID_ESP, ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI, + fast_prov_cli_op, NULL, &fast_prov_client), +}; +static esp_ble_mesh_elem_t elements[] = { + ESP_BLE_MESH_ELEMENT(0, root_models, vnd_models), +}; + +``` + +## 2.3 Message Opcode + +"Opcode-send" represents the message that the client sends to the server. + +"Opcode-ack" represents the message that the server sends to the client. + +* INFO_SET + +|Meaning | Opcode-send | Opcode-ack | +| -----| ------------- | -------------| +|Opcode| `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET` | `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS` | +|Function| This message contains all the information as a Provisioner |Checks each field of the Provisioner information and set the corresponding flag bit. The returned status is variable.| +|Parameter|structfast_prov_info_set|status_bit_mask, status_ctx_flag, status_unicast, status_net_idx, status_group, status_pri_prov, status_match, status_action| + + +* NODE_ADDR + +|Meaning | Opcode-send | Opcode-ack | +| -----| ------------- | -------------| +|Opcode| `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR` | `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK` | +|Function| Temporary Provisioner reports the address of the node it has provisioned. |Used to check if the message was sent successfully. | +|Parameter| Address array |NA | + +* ADDR_GET + +|Meaning | Opcode-send | Opcode-ack | +| -----| ------------- | -------------| +|Opcode| `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET` | `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS` | +|Function|Top Provisioner gets the address of all nodes obtained from Primary Provisioner. | Returns the address of all nodes, but does not contain its own. | +|Parameter|NA |Address array | + +* NET_KEY_ADD + +|Meaning | Opcode-send | Opcode-ack | +| -----| ------------- | -------------| +|Opcode | `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD` | `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS` | +|Function| Reserved for later use | Reserved for later use | +|Parameter| NA | NA | + + +### 2.4 Callback Function +#### 2.4.1 The Callback function for the Vendor Server Model + +```c + esp_ble_mesh_register_custom_model_callback(example_ble_mesh_custom_model_cb); + esp_ble_mesh_register_prov_callback(example_ble_mesh_provisioning_cb); +``` + +1. The callback function will be triggered when the **Vendor Server** Model: + * Receives a message that indicates the Onoff state of the client Model; or + * Calls any APIs that send messages. + +2. The events that this callback function handle: + +* Generic Onoff Server Model + +| Event Name | Opcode |Description | +| ------------- | ------------|------------------------------------------- | +| ESP_BLE_MESH_MODEL_OPERATION_EVT|ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET | This event is triggered when the **Generic Onoff Server** model receives the `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET` message | +| ESP_BLE_MESH_MODEL_OPERATION_EVT|ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK| This event is triggered when the **Generic Onoff Server** model receives the `ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK` message. | + +* Vendor Server Model + +| Event Name | Opcode |Description | +| ------------- | ------------|------------------------------------------- | +| ESP_BLE_MESH_MODEL_OPERATION_EVT | ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET | This event is triggered when the **Vendor Server** model receives the `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET` message.| +| ESP_BLE_MESH_MODEL_OPERATION_EVT | ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR | This event is triggered when the **Vendor Server** model receives the `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR` message.| +| ESP_BLE_MESH_MODEL_OPERATION_EVT | ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET | This event is triggered when the **Vendor Server** model receives the `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET` message.| + +* The **Configuration Client** Model + +| Event Name | Opcode |Description | +| ------------- | ------------|------------------------------------------- | +|ESP_BLE_MESH_SET_FAST_PROV_INFO_COMP_EVT| NA| This event is triggered when the `esp_ble_mesh_set_fast_prov_info` API is called. | +|ESP_BLE_MESH_SET_FAST_PROV_ACTION_COMP_EVT| NA| This event is triggered when the `esp_ble_mesh_set_fast_prov_action` API is called. | +|ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT|ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD|This event is triggered when the **Configuration Server** model receives and further triggers an API calling to send `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET` message. | +|ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT|ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD|This event is triggered when the API `example_send_config_appkey_add` times out.| + +#### 2.4.2 The Vendor Client Model + +```c + esp_ble_mesh_register_custom_model_callback(example_ble_mesh_custom_model_cb); +``` + +1. The callback function will be triggered when the **Vendor Client** model: + * Receives any message sent by the vendor server Model; or + * Calls any APIs that send messages. + +2. The events that this callback function handle: + +| Event Name | Opcode |Description | +| ------------- | ------------|------------------------------------------- | +| ESP_BLE_MESH_MODEL_OPERATION_EVT | ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS | This event is triggered when the **Vendor Client** model receives the `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS` message.| +| ESP_BLE_MESH_MODEL_OPERATION_EVT | ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS | This event is triggered when the **Vendor Client** model receives the `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS` message.| +| ESP_BLE_MESH_MODEL_OPERATION_EVT | ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK | This event is triggered when the **Vendor Client** model receives the `ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK` message | +| ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT | client_send_timeout.opcode | This event is triggered when the API `esp_ble_mesh_client_model_send_msg` times out.| + +### 2.5 Message Sending +#### 2.5.1 The Vendor Client sends messages + +The Vendor Client Model calls the `esp_ble_mesh_client_model_send_msg` API to send messages to the Vendor Server Model. + +| Parameter Name |Description | +| ----------------------|------------------------- | +| `model` | The pointer to the client Model struct | +| `ctx.net_idx` | The NetKey Index of the subnet through which the message is sent | +| `ctx.app_idx` | The AppKey Index for the message encryption | +| `ctx.addr` | The address of the destination nodes | +| `ctx.send_ttl`| The TTL State, which determines how many times a message can be relayed| +| `ctx.send_rel`| This parameter determines whether the Model will wait for an acknowledgment after sending a message | +| `opcode` | The message opcode | +| `msg->len` | The length of the `msg->data`| +| `msg->data` | The pointer to sent data| +| `msg_timeout` | The maximum duration (4000 ms by default) that the Model waits for an acknowledgment. | +|`true` | True: an acknowledgement is required; False: no acknowledgement is required | +| `msg_role` | The role of a message (node/provisioner) | + +```c +esp_ble_mesh_msg_ctx_t ctx = { + .net_idx = info->net_idx, + .app_idx = info->app_idx, + .addr = info->dst, + .send_rel = false, + .send_ttl = 0, + }; + err = esp_ble_mesh_client_model_send_msg(model, &ctx, + ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET, + msg->len, msg->data, info->timeout, true, info->role); +``` + +#### 2.5.2 The Vendor Server sends messages + +The **Vendor Server** Model has to bind its Appkey before calling the `esp_ble_mesh_server_model_send_msg` API to send a message. + +```c +esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS, + msg->len ,msg->data ); +``` +The **Vendor Server** Model calls the `esp_ble_mesh_model_publish` API to publish messages. Only the Models that have subscribed to this destination address receive the published messages. + +```c +esp_err_t esp_ble_mesh_model_publish(esp_ble_mesh_model_t *model, uint32_t opcode, + uint16_t length, uint8_t *data, + esp_ble_mesh_dev_role_t device_role); +``` diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/app_ble.png b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/app_ble.png new file mode 100644 index 0000000000000000000000000000000000000000..e62b7577899d5a1f3e205a3bce80af5e3f55617f GIT binary patch literal 182199 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 z

?R)>D?EBirv{5f53g<;)mnO6%j8vZpj+yOTy-u-#ru^GmZV?Sd-KBF>4c3x!Jw zYQ2@*UF>+Wn=N4!lG?LZFQ1DYp7WD_9*BN3_Dw05<;vnmd_y33(?#QecMxKT5B2QT z97HEb?R8DZBALX*#HYcRo{Dy6`w?n20=7cPk=lnB!&n0>i|s{W-2`$@c3EAI@cD~s zYow$~6T|qO&&&8iIq_*ct)E)^#YY}KFSvOFggpEm^ZUqPm5`jdO*k!ZoQNW4N`aoX z<@{2~vKYN=arCE8>l$WpdiJ$k=d0#G7|=3rGT?4nyMDx8$4vOFpqzm67r9TaI0@zE zm*T+}VL8y3+6I7C$1^M?Hf-wS#4F=B5_SfO47$Vqnv)fetwG}0xG%?HZHQ}naH;(fiPjb`Bm=Vuy5Cbc|{ImP)~77=&Iq^2Y4 zoj&l=ZsXXcO`cI7&Phpqve%VKG>NOCU^jCD$qqj@YZg=d;d9DRFD)abW3b$39#n1y zj|oN=$`nz)kU34bvj!*=-@-`dIMQaqS`X7uxQ4Mj(0nv*!sU%dtqwKLgHiJ#V1NdZ zQ!^$T+5RUcMZNVOVls*dEQB&w6fzGzZvCGHB~Z^%!m&CD&|A86ifB<8!cSjJpHmKz z%xz)!&3zjXJ~&EFQW*W}i`pzGcUeywk(z!+x#7~lIStQ7HDcC-Cmv3zXO43-w?r)P)m+-YQRiq;fVg(Jh7$TuY$dJ0*$uDB@GuE3UP* zU+Q~z+Y`KtU55owXzSxDwgms#2C}8sjiaYtP65X-!bL?WJgPDK3|y-)1;8{&Qu*g4 zg1V;XCygvEW{v;Xn24k4$OFF=dnnBJe+Qo=%WI*i^JVahp#NwV{%4>8F##HI(QhBT zrTl;0hB6cfUT-L<{oly{=LY=ehw#Hl;It7m&Q0rXS3*!*{qa}w&z_~)3Q|{m z>bai(uSdX$C~{;(lM66K4ZFX=fKX*o6`(!~JhCFh@@h_k8cJ&}qRzIu9nKhHvd${r znTDStefYSN8-kP>xvp0{4fuoPoqsJaq7onsRDtgWm-z$t72v)*0Ym*m4D}7EV+HKO z-yR%cGisGh8VFbymF@5}_GD}uRBHx1TAAnH>U4g1nenRK!ZwGzilg>G&6}sz$Je;0 z*Y^K)3#UmO1T|o3Sd?mpQzwSnoGaXSFVAt(zgr$s*S}aPnHy34UHi$6^|4~}9)9XI zEiWcHIXT#Va=e-ABS$qe712$HhLsn;S-@Y%WABR3DSGwqYwW^Q~BjRdWW2u<)6{_HV zj4j|=k8=XhiRQ{bymJEFgwSQ ze_#Im!vbZ;2}Z7v{geFds@5aP+b`~6WOu8yt?Oy0^IyYe01AB40L29=f$RTQU+Di^ zIN$?x+5ZeY;tC2+CeP%&lIxzc3Ngy~^Ko}}`C!)+vHOjlTgtP+wjgA1B^tYNpVS9BMfL8{M$c zZ8=ImrRDmG;dn2N>U>mG)Kk%StF`vJ``C$-vMO5fO6(itqT@}%{z@u{!U}n!bcmF7>W*gW_MEf`By)X zWLXLBYNz8Fb5&sjay)?VEjwDtexIo8kP1A|X+olLyL!XCy}upmxL()lg!)n(xS&mN;hOLeAv=D(=97Gqo;c zaSmXe4#05$whxqCMKeQROH(L${bn_lCX9Pphr?`!mbO#{~X1udkLNE zSZgYm5WH%GN!WPhdi_iA?z6Kt(eVQ2MQUJPtIK-TS-)V_a!o%$ijK?&vd!**p_cSHPrmc~u z^9)=9Y~>Fk&74UINskBu;x*|aBGv-0F65~h(k%5;T9!z;a9KXRH3yV zNJOSTk>p%cWb7t5(`apiDV{u$*nX4Q8vQR3;eRinfO*{jHJL(m-fku9b~=-b$+*s& znR5FML}p5Ox^odLRJ;`*6=ZUFmHxxHh(cY9%Qaj{)Z`D)`Sf`{mX{xV)Ew!tP7|+B zpa-H(pe6*OL0kd;l?j+l7x)CAnRnDAeSaqkaqM$D)u5xawZ&oX1zi<*>C!f7fE{!P_+fLL7Hw+OR4`orvnSC7Hr`}foTYH)$4B4XFuwq z&K_&Y{YkcU4;u$kQEGTKanr8lkxqX@6)5VirGz#3;lXT_a)K8UYfz=MevNk0<16g93s{mM?}O6 zpTa4M@<4j>9=*AXtzZRAlM@f+kHEG~8exad=C!ArgK^?WKtDE{*L%&ZmWc zb=N48Hd5fcMDUT;UWF?CyiCw4(OpAJeKiGyNdC`S<-e}-5Ir)9$q@bwUrIo zkQu$($EgT8@f}^dOC;A##n73l^?Cbh%8PZ+aF2V{=+R>L+t~dzpkO(@Gz+wizh!cU z#JqBW->KH%$5S7{pvCbLtpJb~UC$NXt8)69tEw6h3CO{j1lbhxtZw1LHHv3a!^*r( zYf;4K^?;rRL##KupCqb&kip0doK=~8c9#?aprq~h03?IU8$Secgk30MLp)e0yN%T8 zs5R7qpq$mW-ay7E3-9UiLRhr)#?4in++BAV&<4fK()@4P;s1Pb3q!$hy-8T?XTo-~ zf;U!w7*mWcrOc3NjSWIHD|~Mfo~NGn3n<3)7LA|BtP&j*EJ0{+AFCL`p!q7LZm#B%~Xb z5|-{Rk(LhWX6a7pTo#ZHLAtw@l$LJzeYns4eeZMcbN|}c);XUuGiT1soSAt`_((tB zA2v=ivFcyjdvK99#e7`eWg~v-t$fYe6&*4Z5`6y9P(Tw8!7(p_-s0YCpyB@9gh@kJ zcQJ86)54C*N}JTGRQL(BwzRM>RD2BG`Gu-gXs-viU~y!ta51IWC59G9&v&PRoPZGa zXdi6;5FqFHj?aminuqZ(xYOt1IaF1@X zmcN^5Lg8GW$ep?1s}*gt<3*k#j{CGVr=qf@JnPHu_8{iCCP(#1AM3=2@)wT?c>dC^ z?tcN`{2_-k{%D-DK<|MUevAfE&Q~J+%<8ODQ$oYNesKJ7D9P#%-2}!szBRXJQ7mCX z@bbeaJbm<^S>~_ui^%)}yKrR5^WeE>4&zx{WpHG~57JgoRGQ@hQxm|O1Kyxp>OOdt zn&`d}_AaGV7SM7^_29U>cQ^Bum>61)ld{t$@>c4qKxaXKe4-EJ`xdrA&4)<#{2{&k zq{FQaNH+^@lc?ugDM%XJdog0UKw^^Ku~=1}e*6|CH`gQmE^_a=3gI7sFMqdieItY) zAV}=M@RXWS+%iarm1=Q5;~o)=Z{zHyb=5^+mc-J@N(S|K-DcptVQyZL3>;%;)Hgxf z|9xW1pe&+PVaZPsBPVA57vSEX=RkxIfb?eLIo)ZQL*T6dtq82igm-b3iWv?0 z$w`RRXS`#11xxyxhr|3@t=weSA!1ACXaUr3EvA<;m-cXx`wxN94Pz&sVlM=y|c5QJ(4NWgm*loy^T~z zHcKmPZ(j$No9Ffmy|LQ}OJmC4t{EDQrqJD8>INi_OHaxeAe1HYy|t+t?WO?o+Y)<;heNRm zP`D$aF%Ls>)7SFx%htpuVh(BQ`pEQRWWLXgxpL+8=dvtGLao~=6{c-vLz9D{%5qd2 zv!e1Y$kiHOf=>PS&>*bJob)%zEUQk?!3jw z4o0vfA?f=2Mc|9gW8yqV{G>&^slYUC9%v59m^jf_SKJ8VTIv@}VZ zunfKc5xi<}+F~+cQy}w@h)1Jaak=) zatZD?#>musEMARoRhnf*SmI#=8;a$^@>bO7V8;;OCU@^g@BD$@=u(YeCrGlbecWaR zp_Mf@ILH8sh8&p)bv2B=y=#+$Y1g=zl+#q3&sOq;CPJ^yt9FAN%C-l$AR~1JU#?E# zFihEGU2ux5W6}~Z8;fq3|3^#CyhSEXdux1ay5>lq6YJ`duiC;p%P5$ZI|D=9X<7Z$ z&#JUAH2|c{ii#l}K_FC2bBq9{*|&zh<{JD6GV3Co+fo^VqK;7{d^UnIfve9KaJ$cC z=F2Q)<{t%Rhjj^Y8tQq*_dE)El0g+bpvwtT@rG3p+yx~ z%sa$vGwZj_A08bO&8`PxVNnxDQ}#uZES3=y_h%?Ou<-1Lsf?7B;P;`cjg-xdQJZzv zd_fH@ERAkl>(nsyY-IA4uG_wvkmts!hThgy!?+7(Qrz`w2M}X=guG6oph3@{GvWkq z%l5OtDkbam>pQ-1sDiB0=kl=H$v9{_@C`SLjW*v3lJ00y^K@ked&f3Cw&(W8&NrS6 z-ID20Mk+CW!lXoW`bF{dY{9kZ3r0<}uklOL8SWF*X}OiHQbwyI%Cap{##gi32f|ah zy>dC&Qu=6xC6RU|k@4dMr7l1*$Uii*kh^HsD5#9?^0CHa>915UC8Uf9!k z>(+Y1Z(>lf3`W9=%1PMsFZfJTt4Ynw5hh^PXbT)TVvlNl4>(d0S(D07xl$XFXJWM` zQ9n^ErulRwN0cZ%F#*5Sz8u$!`DM+Ca(b>h{N(o?Bx@1U*H;VRTSkvo1* zI`~eU@;kFJ5xvC7Fq{-&d{zpkR?*NHgE@d2nGNvAH+m8CkG33xnM#JPl4K`i5+i6l z)=4i&yw9Jg6pbCd`kuE}5zJ*(YyTynxl2 z6%LyuDBWZ{DT(VbLn&EAH)%_297CU8E4Ab-E=d?}SH_kycycI5$7Es4o!9?o%a#B0 zNFAM#iN(gCz4Vr!LZuY*Q&%$mXiMiBRG6o`CfYJ;f5Gl&V2v3yeO`+udof*-c}ADb zY%_k#x6_R7!96{bIIwt)`aXh%t4|-LORKJX=HOR9)okwAY^%`g!35UaeeBKR>J86P z?4jhd!wuy1sVq0Gsn$O!4q)NiOi=s^eMQ^;NL-D1!zBFBN-8x~mzc&JxKlj7?d71t z%uUHbrRp)EdVBdQGYyl9#9D<=dE!=C;A|+WF6?oluC0c~&rla~D~(A|8gHiob^wb(juu$?Y z2<@(Pin6M*zG?#sh`l#7w%p?iti>Vv`XgK;wj5Tyyt3r|4;`81Ni~wS8|i4Fv&rKb zi~lsSiJ5?@{v}LUN*Vv_ZEvWG3Rqv6`qitirOd)#5%a}9l&YJ&?a{OtA;ifg=|(V^D%-Py`y$!y z_dwP1(sM#J#jN3V8M>VCh{t_H>h^7K+8I$ZWp}ivICJ0LK7&!gw}-Lm=?OI*mAa{h)Z4$v^q}p3iRvV0Jgdp&d@l2*b{01ROs@?m<*cmoKtdSK&Q6;+eB*^|+o|nT??G8}R`iB6)-&|cbWc(tcds?j>}ePko-4Zt zD$CU~ILHKUD<5vTyvpNcx6vF?kcq{|c#$ZV07B!;I6mC%IjXzM0*bNH0C3SR$sX?4 zY@YZ==nXWmoPPh;D;FfnksnF$z!R?F+>g!F@lilKyo#2o=;A59f?wmAu#+SuFyl@D z;DKcAQT<4O*SdhUsU@?3@|FbnklT9mGLW_+mNmObCT%G^pdr=t+C4H}@3z3nErXa{ z%*xN=T{WF`a?v>a#YtZct@|0ARq#p|&+>@+!B``BSPDV8U;l$*0xo9<50HR$n9HJZ z@<}t@9$qGBl#6Fi_Nzo?BrG|p$y!zIe8sosY9C9D_<)NN&(=~JP+Sx2w9waAdh}Cr zdaC(619CGPo1#)j_>h7Ljso}xj;gQw1&j_fC<2Cm`seT@(pnCqY|O`Z*N{)&w-WY7 z2gkx7^L->4wJS7db^G*Q1EqzR#~7yl@#2T;HRZ9%F3Z(@-Q;ySvGSEUMv<1XZyT9` z1t?+ww~;rs4<#$Le!yh4_O#m1fGHmNM$lQ9sOi|mWAPhznR#}NrABd)wAK^&u~z!c zGFf*i+*<_TNvNM6c!*#Ka0^QJxBAb1k!9g9%TQhNtmv7;`}r}kpRLMoulBIy!7#AF z2Api0-#K5UfVw+zAnZ=x{CmkpGAn|5WNkNc{l29gvhSHS^=QXT=l+hKiGNq0X~t4k z7N20M^j=(BQ~SBw`xC**0)^=8ixM-GMITHMP7FkgeiaLx zE%rV9gh9|TN4b`FEu#@WETwG5m92kBPk)i3emGSAF8oUnBaxJy-P`0bRVvCdw~EGE z)0pNd8D*K5esa4y6)k--t@q=W$!Z{IBwYJgxB4GJ(FeYh1PJl+txGfAPaW271M?I! zlvtKAYbhD1MlpHbttw~?14V04hZd8favi^Q<`1*hQ!=t@TJ=Dcg;|;K$Weeo{By1I z`Eo4win{@Zgk)~-+_s~Re_0ir6LG6}GXvh2NM~D_J`h&~xtq2s>o*asIBjy|wE=GK zYJj@ip$w001|}&&ID8?Zymh{8dVX6WbL^MB?%(g^{&?pL4iRcti9}_o&fBN!%}Kh{ zhkmJ>Egp)6ROphS&8e#Cm_!Mlsn;|bY0aa@jTonvm2F4V&7~N#{-o$Ls2wrUKI*(UFcU9G(_e4Bf1esZ`uKmN4?__`4Ik%)!4g?;_{2T{-N*!S z?*4`)xX=*iXko9T`)9MYzZ2{c@W}b?KB=of>n?NK*4tV#paAQQ<1fW;u#E@U>3_zQ zBZbCs%we`GaEtL>;PR{1rfzWx$Q9?_DNd?z6qZRQGm7schgyaaiKRSCNc;;T{{NPr z^#y0E^J3UGo8Wsu+lXpNU*%l~<_S5mYTt8c-xSz=fr;>X$-ax}_ z4#&jn#mnZODfKgJ8?WCt>FzXnR)4WOz51Uh6QHMX5x2Nn>%SGp`3YWk*g($q*yf7E z?yQm@mx!7$Xmp`FFHaU{=y8)6$`HVSF#E%urYS0!W3)|G7eVdiMrzOu9Dv=Pn;HDB zsbrvZ8aiduiNs!VQO_lKQG}mq*MPqZH;QdL68XEB{PFHVmRH_CLz^XgKTmGmwcOa& zkl`Br<*F3?2)aEzEi-f*6YsT&$_1PMLy~;yXrkq@wvCDq;}6P*V{)0_sbq>< zHB-J12!K>hlS58IjI7sLuV+#+_3{;~5CldAH?291fKgXQ8Av@b}zdH+7Q=?+ptz3nWmte>3KQ0vQ{nbST!b+;dFl$o~w z#y}@z<@NP-u>78q^J!)oaDJ6t92aN;jq<8(>R`n=B1~yD9gv%oObC1(ZO3bfo>^<* z2$`YUJjTRq0R2cs;5aS*=9fF3xNW{S{-erKG6fApZnK(O=EymZ4R409JQS`S`u3!?k?L zv2;VM{8;+Q6tl}*YP#$3;O?%+ai7rfhaf6_v=@jhA&+t)gfwk07T0y2DK=O%+eTDUF)G$_3ELS7hSD1VNrhh^m zjV=8?N1_40jRL{ew^yN6ro2_DxYpLiP-T5hX7Yytbh25f*rTz^M1Drr&-0oNWZ6Zi zC$+w<%5}iNk~kvyo@UzbdY@(1%!?-5gQW^s6z4O9$j3wQx>W{-sb`cU`!JY=r06Dh;?*Mw?K4gG8!?ot&itmcm}4;9$M+{j3{Zc ziSR0SH>r*6KU5+{upB)p>>G|*z=(>w2@zcsk>?MJfqznjaR&NOjgX?fkhXdjSN2r2 z4Lg|7*I!ywEVedRjFGTBeq-?8Fu<9Aeglk#g(xwWf@;Rp1u*d`0_)?fa6ra$M!CIkju@$1F?mnld1AE-;7R`&dF zbNy%76%k-i?N=Jq|MFc5DzNUs&(-mUe_PxiQwnJVgN9+y$r}I5cgK|apfcBwwaWik z-ouo^7{H)*?ZzSh@|`(3z!5I2AxP*y==m_E9ToOVCZ>{5rZ2SY4|JmpK{GZnk$(9c z+Sk_y+OX94W9iXv5rM=jWo>QdbUqhnZZ*}}iI;ZI_nrYWOA#RY3JHbh(PDu6y8-&* zJ}h12qlO{yow*2djo7eKYkWCFkwaFT%~(ow|3(W4UlQ&^_D6= zP}0{YnNPFsK`2#3DGi|cOdF&leRdGlfHdU@F&3QV=d{ju?%Vyj+2>-3Kk1^K;9_vn zs=l%cDyv4x<4D!%xothQxjC;e)$WGhHZN;O*E`1p!IR`6Q^Po6yjLQkl{P)^s;-o6 z_LS6?ZGOG0n#*mmKOy%*VMoLrKtq>SlaP?`Pa~2pv5l)-2A-Bu^+7K(vKZJu2q-nS zp!7G#shHQ%?EZU+05fFlq@_U~&YrQy<~>?S3?sZ;#)cWJpt6?Uh8Xxqw!$b6`Y=xJ zym#A;1+PJa>zH8TCz=L9h`s&&4r1VnX|9PBx z28oL|8uw3?DeLO$R!GMR+W-PnhKGRr-o@}V5MElDrN~P_AY*EpPeLdcJHRfviVn(?5==vd<+*iDpD11-AH4*G4d$Odo5INS2_ zugxc z&YBUI*%?}6HPyqRAjopQ;90leeb8)AF{B`Hr!Es3wutiGyYUoq`Rg;5&MyUMbr6M{ zREFmUk>tXs7fM#uIz5aJ$MatsG@254R9xgz7P{qntHh?`GrptQiDBcFPHFowX?j^* z-3nV^%1yD448zw4`cY<0p|w*p(ycWZ=So!>~n??p2?>4W>3m;gr&*z~>3f`J zK>y-IAl0xp5R)XWo@QZ8Xvnk&@MvBFBu+$n#;@p?oV?FXZn|$y*82|$48{V)V>1f4ekKRu;4u+b{(VZ zUHvBYC^2J-Y%sg-)l6$AdCm7UV7ic(H2jS@LLCpv;{~kNMfCb? zZym@QjBWpn+V6dLOi};yYR6;_ZUHzW1w8<^d54b>nBb}L+p@X5X8Qi6>s^)u-w`ls z6$hr~{0;}i=O=RI4(O97n_WBlq+(Y=vTHz@}9lqZ%$(I5* z_D1h?vYZC{`a&<5r&Klajr6L%JuuN%MaXA0Mgk=N*i{Y%cpmflhogRePtSJG37zcn z1YB%|&8kiRBuuVe_`4%wIdVh*psxv!;L_1gp$Tu|xrF~)zRV?%2NtS`+U2Kn;y%S# z*Y`vMXfJvTr44=al__EanN#;#4Tk#mf1Hqi+8qxsg8_gcx*@_F(O)F|y~%B2BvMIq zgRiFeZsgkZdFpqvHZktM**QQ)9dODsEo`YJWqM3XbZ*Pw-D+J?>Er&-z+n#qcg@QH zXYSv(0iaank%_@$oS%F=13#P2SiFG83+Nk_{GHW6-n2P%gRrDbPQENdqO%n1g4%VP zQa>^X%ps#y{+H~IY+y?eqgrOQk{cjmjc&f z3!BPn-Z{3@adWHj;ycHkkP(57avp5efaQ>S4k{?#;2F0jtG-_nt)Pzz+x$=ue6ckDe^DtgYLcwFBt9`FF!n+Z^&4l>wALC4r1byom=0K z78dr6E;SL#H3dKiTG&dJ|9YGeIW&;O*aCBTUkofX6uroYfJKGK-h2K*-`n8%Qo{8$ z-WFl577=rf=hY0}>DRpZKjQg*rvY3l3FIjuQKm^Q|+7 z%Lx+xaCwthDXmd)_h=$65+q=`_W z@Yi*e953JV5=zOxs&eYA5$|Gmx}`&EY)4a(M)}@Ixj$UEuG5nppWVS&uU1Ui zZPG~BewIzI`}4D&#Bw&JA(C4M>VD%jJGoA`y^F?@J9mGX=5k`X<;C6C!y?tcwr}h4 z(+pp*nSa4#(J?*KE8!RJG#Gi%soG%A+Jy+QhUy1-d!cXJwS!0Z+0nL_yHwP|uK#a$ zgIPwN!+00GhHhMUBco3vairGlnHY!(pZB<$5Z)rUVhz2965vgKdLuM`84v>xbdkHiVHU|sA`^0IWx#>he@V=#FQHCi?H-WE%ZbE@RJDZc>!Saclt8w{xuX_bgV=MI_nUBvLn#v{iZJPlBEf zBT}t^6!`YkVU#CA9X(@Wi-=si-6R0kT?36bdJvoko5rTF>OHI&hkch`R%Yx*Os>ZI zb+EF3lIyuR8ZnoZ0W1Llnh^2s_M(?TqOG9Hp&hI$E3sEd&>j+$*;KRLBc${bzs&3zxKcFP32osk#`e;R-Hw5Z|L??l4Mp-|tF zmOfn_U8mOKf?Iyh+r8a`9h`yN7m7zxN=lxVbuHwJ1vw|?9+A;`>@+Wp8XV&YpUq-` zSR}uM?i>e9lB{)}w}vv6(S`>2MB4Cv7kg*aFthH&MPoh{_Q$p{Cq^zF;vM6(03(&H zr(h$FZiIixArc?cCN5vL&)HNLzi`YOU;>Z7Wg(W)xK3LbuJ=9YF#i``9)tbnIvKKC^J)vX%b{H zX)+1w_X^dCu;7CH^ir*EEp+@jH*KA04%JD|IXlCs1DASOu4L`$R#!}eEaYN+y;HOv zVcFwnG{f{2uUwepT6dd+9EYSw7VN$~72wdkDt zNeYd{*XPC2A?fq|gRv5Ql%LvNf^j}m-dP07gRMd}DcmMwC$5yIZWg)8LT|8lQtoD= zmz@hRPy5x?cxo2YU!pWBtd}@^ka}0LC91-G3_EpczQXxEp8H8TXdD!(rIR!nBy4k9 zwAUgwUr>Gpnkp!!#bonu3xAf9Vy`9?c+xWj)%VMHWV2qNqLde>9=c0-6e$z;`Ll1#^v#HEKl~?4UYB5(Co1XwIcv1d)%qjgMQ*w&ziD z2nvg%KQ|Ke&|F8xAOiSGwlLDNd4sB!EkiN0{Dt8`qGsgLW8C{rZZ}-*dHi21J5 z?wN^{h@ECc4d3YsF0dP=bOCmM~0lAb~ z%GT44RZ5J_DGygR5Cfqy4jM(L3v(c_5nhj9fHo04@~e4|1`nC5h6*RlRPw5h@I`0x zTSn>kgT$=SdF`ls!R2IY^`4SO;w^5Bq;$B(&qqy7?<{OcIil6A*&rB_1!1cK_;tO^ zp~RH2C7PtL?%lP+rD?@_;@`&gUO%pc-bsp@gw_oyb@5G0-5c{{2&n3jQ4i~U9g3lm zCt8IvK<=d|@1HU^_-6@YAamHEq>rWpfiaS)u|J(WINgJrnm;91*DxVH)}(&*;cOSD zhM2gXif^jsLB{$8Q<&R zZU5rDbDOZ!6Z?Kew=~n;I-~iJW>`ysOX9EECfeTkJREd0n%qEXjWgjnzY3 z#maKfu|(E01x_4;&*ME##MQbJXfA-bx$!AOFl96GyLqIfBUAL|841}Fh3F5jlA8j7 z@>U-ZOYq|a^dw&8*n}7a8{!an47_&a1|s<%Bh2?DvecNNzw)az0Pb9@6TuxjLw$F>yVu4^Hoizz(hsEJ;0Zfb?B!*ekUs2z`b(O$x>rxD` zYpMUg-9QAKB1H?lbg4l?Pb;We?=@}Y#ktCv5a7d>&erNL2#Ur~9oxU#GHF!QWkp2) zf15_AL>yRSiOn2DyGrxc&?AK#A}H*g1qn}59mJNxKvqdKJz-`^>Guc1#Sdm65MBaI z463gQPI#0*OzrM|y&%U6OH$Z3C7DGQQBMGd9Z^BsHGRCO@>rwg|0Y3y2r~tc6byzY z{APEH>$B#jo;HnvhyLA8&Z7eSq_N7;xuzcC!@U$zY z*(9)f9xIDg50?24C4peg?1LZlCMQ7;k0xuNKkSiObF9->Z3%Zrj^5Uz%t*X+ScULYSy$D=t2sbMcZYQ80g$r~l` zXk|jd->pH>HVXft)LazvvaVP`_hqYeB<^S5^QkPJlOnUIYf$-O_k8|s{aB2J-0%mM z8xE5vzw5+Qe#{S|XZk^Qk2;xYL;~NH&RV3b6yh|AN>gEkK3mAX)vV}-(qCzkA%nq$ zG{_M#WXc1k#aCYP-9%nJC6|i@zc^~*UvBFr6bO^V_P+R9aR1k~k zlN8txWZYO+{4xfo^U2G_JDtV=s;7DmV!1K#Sd~xXx|-M#FDhjG+PLx5gY3$mTCuuz z;^qSBQ#VbDwf&jR^imoZUuqF=#mWfAAcU-qw;SJP6y}JvxEq)M#!n#TNZIDfM3;I~ z*m=+BiHvtDfU)DuYyjc+%#G{9$-cli21LM_uI2!y(3z1)LHau)rXjL-i`?uWC5 zUq^;!(IN74Up^JwWU2Ws@a9)&bL1qR3<;~*6Ti!RlK+h=rAZu*OApIM)5sF_8*vBm zM(Wk8uXH2qP41~5@pD5c;nx3OivHt}DdQrfAH-(49xE}v^!ve4%xo7LYyLAn>JP*8*ZCPlg<(QUM{RQcV(y{kTLI3r?tITt@cP`uC`Bu z?nsFu>o1L$o2$q37uRe1k6{YJ<&LE<;|D_7T@>=N@wjeJa59HRw88t)G-i$mj}WzA z7Oe6YNlFqeXQw;%T3WJ`nu-cfG-tu3p7jmKjW^aL8a2QZMS8ky%+sHkn$#O7BxWzI zLvg5mBXn)_S&KD58B|*_u`EH(2cXT9ZP4=mqQG`995dj*bP@0c?*%$66Jd8!o% zFA2uT5W}yH{j(;l$nyyvA#Bvo8xc8`)jG0F2B~JKXO?qW7OBbhTCEc!ZiV=;veuY* z?-o9;J+bcLjL}?s&x!e|}juq{-3BO6VOCdTy0j$R*0{=}XhYtq8W`#_%u>>PMvAv5km_?qa+9*KNZw_v zh_>HM%*U6GsFhMjw?V>voMd-aS{yyjx85r#ahBj6xl8Na?YddGwBLle?9Epiq>YTo zP4xo$VT9R~2Rene+=NX``~que@Da}r<>uKQBaJX5G)&gSQ@Y-okug4pJ!+z`*J$@{X_RqUi&=#yF8O+O!nz68dcj6l{WeBdy;we7FZ6fG6HMPq znF8o=#)BzbFy}qZ^LHsf{3*@s?5eEi;L^3W%aO%u_0_adPm}UK%-0U~HhoxZ=&3jO zhC)wc5-BdArEJOsTt>+v|1L%S`nKy_*Xa9=O0h>%*f$)!lMQSEH{7**zN^N**tC%M z4il_*>>dXRpXQ6E2Jk8F#YuA>_6Op zV1LWTcOy$f+0z;3tuLQXiaAKcrDn^lL)CiosUhvOOQhcY(k;2UxhM6-1?r6P#A5%q z*u_KDsJJSDiEN9!RDsLAwVc=A*2;Tn)@)-XdM;^Dq=I&t6=+FM5kKlshg)G2nS2hm z|3Z!RDe^-iZ7b=lUZ79YrLd94E?kU*WD6D}&?BwH5km(_8=-=>hcY(`V|wW8VH>d;Vxch) zB#JL6)dK-2HtYjKUILaryW{mj5Rt~?iBcuWf|`Ye@=Q|EZgT4p|a{sY}cl0bmTC4opCoG zsb@BcG@>8R273JnVw_I4m1%rrPGDAFZSA^pv`iHv_m`AVOdr!K4AN=@GR+e~+@it+ zbad66lLB{Ew=<28By8}wlmUds=!dI@a3T)lUMMXNRN|7+hiAN0yI#4lD0C`EsYG>t zYudC3C32e6^Q3y8TQB@BA}`EUF;ej6Cl*lLo+$-13Lq7}-r#O!pig&%J|%stsOx>} zJWl*DmoXwzAEbk6uU|EuTZ%SC|4FTpmpkiQc7-y>$ZHikGA7wYzarF+7?Uhd zy6o@5&nTdY46USdqUC<^c;G2NUQ`(ooCI-V8f;g&Vr(s{_pGEds$T5y0@BBcv<*8W zZv7g5c2iMUURD$X(Kvg@Yx9b2q|-Ic;XmFUP*9g}5k;WT2|yvTz6g#t%+Vo-d+h6L z8D#4uNN#-uN!%?kgGT;(yMlqDzBnCWbEU6tKQq$~YCH1R3Pw*Y8lF-gsbL)GzPqT# z1?@G1`PTQp>|~G5z4X>=k>1y-5!Y?20ZVb#8oBf2xM%NbB+>7Yu1w;*nE!cA`RWjf zwHI zql;hshFmX9|HkX!7hMVqSazsle~moX&Cq82wkKm`A=dOWx!cbpod_NBoALvY>3bjE zXZ^!5IAlk$bM$eF$fE`GHCCI!a{wS;eJVky@n?&RqM*#uQElkjhsk1995AUjTM)(4 z#$c++bh!@K8Y=^X-9{|P=gz~t_^U}j!EC*Q875KO#P6##9*4Du-ny~pYq1Im2c8G+ z_M_s2y)G@sWIVf--?TL-ye_f_Qh@524d9A_bTp5DXS%}09_WtzdcmzN1vxrDqd$Sg zVNKrqy5g*&C?{?a#@!o#8nq#n>R)3VvtXZBx1e9^big8ynaWmN3H^+OImbCcNS>A(#5;deFN@P&pJVe ziUU;gyKzj{Kgkvd!*Z)U+jm$KAq>sG?*v*LGDuNfB3~zw7=INv!CD4|ESAm+Ii7|1 zCWYN(+0xg)*c1-np|mjN87tk3UJ0<(`&%dR$OW zq3q`NaK;U7=Yosq$tY|^d=kQMyZynNo(UM%iFX*tq#Eeltn%pbe z?e}_G*wK-U-|$K`4#>?SrLV(qZb^ssqLD`VvIRZ9o)OX19qgl8c?nal}r*{-*MxBwe@k)^^CHAm?tXeyMZ&e^=t^*TO8}Y3f z2$kqwF0@*qUUmfwMB7&07&}rV2e;~^PK|^I)=w2R^YuE>xbwDUf?YRb$`y{4@O7?o3hEY(VIpMKACo?_e<~O#Z zj-R)wYffqD=_~+Ns558}C~%L6j+nb53_SQ09M^QXkON% z7`32h@j$|-Hn5fGffgg-Ko0azk;|){3bNN8Kq6UM8h{s96&vHK^bA@Eq#nxLowwfC zFTX5+N=?OsqK*K--_}5FkWXpevDXz2~Iyg6XdPmLoesXCZM71dr4|lrX=(` zPfQBY-Jz)4*_d$`Nx?vfpD&14QJOP zI1^LC%@HnPQ=f)ir-vCxE1IAW%ZVQ!uZ!=Tl=*(f9lj9k`h8UWWtNjXW`rT>ijqSu zH^JgT>MODR^tS_Gh{GZIGp(^as-i;zCBI)tdmqm_IcD)rw|;m?%(4-4`$i%u%R=eU zqVrV!zFD)D`Oxuzx(%|oEX7z4#G4;y&}!zI@P6)QWa#=-OSb%L$q{=YWk`IaO;mpa zQivYCNWgV-!|+-Nm(qeu<2l8~;SZ8J1yZUYUvT+SgD81gv!Z}zqsE)Pg#clg)ZFSW z;BWF>l9}<`ZTnl{6?d$TK(?u(mJ`?BGnPuBUf0{yu8PXKPeg)a*;=$S zz9u|Dj02aE;x528U-1+uaBrdJ>xJGpQ;%DFL=N}I=siW+YTw?_H{Y7cneAz0GTsV0 z@cdZoz+gq0i@D)rV>>cJiXrdwGb&O6U zEj5=E&)x+Z9UZ^F2J(qtnq|0}<_**>xyb^sb*7g9ToW|_1=^ZMv2?OEP9{y`dj+(9 zq5+UXBTA5t?*V|jpHe@7jx~`!!e4MO7ar0H9@;-gn40~-y=rd;Y-!l1Lqo|9^{59h z2Kz;)>0ThUe7$e=003?bx&cyM?bl}v(DKL;UtxVqnkdRarekkjpGR_+c|t(8pW3QI zu2m(RfAI2D*9y~Hy3Qlr3RkX$g8lXE7w}`xV9|@A0I7_L`|DxD@Tpg|wjGQY#^0v- zdmIRslB4!GWXOF5FMM^-tFmU}*0~KPR|bi1ZSl3`Hujif$O?B#!Ko&*2{ub;VH4~Vlhq|Kdz34hIGJ<>rYEp z)759Qino;I@MH&_qa|_-wfOW|5-C>CaO|qPb6}CATD`H5TK)6u zPdbR34YC(5f*#$j3<|p4H599L6hHWJnB$N^HmJV8hIX2{Lm3%OHon`y>hqq8(NZo- zK??Ue`qTsqm)P1ECx5oFAtCk1GI94b%TD(Ser#z#_(pRxMAso_kIQMUBU}hE!uE0< zG<7Asj2~Ngx|vAnjm4r#d+(dPJ_eQ9dUo86Bs6q(Fe;5M++*6IwwL*;^=7kg@Mw1% z3^U$wCVK49yPCx_Pf~)c&68E;{LIJpAela}8MTBYjcrvs?eeR{@+-_Eie^^r$GB&+ zwbZWYEy2}_`|_ny)~JFhA@cWVe%&55x$4ecTTzu5PN{mDtWhFNjR;sB%Psgb(loYpkf3dM2=xW3yN$#2_m%`a=5w+xT3@*?y)J!2zc91v{QSS0k z)si?8gB57mA*y?fH-#8fKGH`lDrwfDF*-{<$=z{7>SX@>>J<4y+`uw zMsvjXdAaOCu@e_n8JwpR9#L;s(1^G!D!GG zTljIsv&@ch3$c#a4=JG0PCG^21k$;(Tt4?4n+`8p?U7@Jk!Batijo8F8gZqT`ID_@ ziJOKNx+>=km(@E6phlA$lri z=UWm;>xEQ`miQB^*J(Tmsw?Sp{j;y=cY3gQKD~6ODqgPoeBJkj=DN2+;xti@ihUWM z<|p2ZjDt$qaMh!#O@Qy!qCOGC%BphVl~ zO`!>y7{dGJI_KCMi!3QircRj(*KXG zyAFyYY#V%yI}8#W2DjiaxH|-g1PSi$4uiY9yM#b+cX!v|?gV#-!@J+Ndv^DnKfAhW zy1J%nq@U-$e%F5+tQOZn$&_G_5G5kHjH)~VjT~jjcB8>h$m5w+~Q4(tE9v!+~_Q3PaNn3UlK>IzuAhG7Fas1qsev%$d zyZFTKm_Cj9sq71)li#aw^|xwp6D$!L7^NY88FLcRl(sdf2meC*m`}`%;AC*y^MqE~ zRnPT0Bn0WE;-c|2l!Hq>_p(%y*lP->Z1$T~b1ul+ny?2hQv#8fA&oe{DSFzTUj&6**uvRC{H8vV~uHR&_e0Tf{w0<{8D zf31(k(XY)Fgd&=QBmIp=KfqD(f??dg4xO}-@RA9>9tdBcM zfl#j}mM#qL7YLG{8ZE$rAiTR{Sj|!$yrAn}5-MAlB&RBy=i z?}ZIh!bin2xt$R|b8v0&Xo(%g!5}FN6c`N$^CK9_fb|QaHy@6)h;8F(FW8NzvF9gz z@OUNCCO=m%d;#vsPL){}WpuC_Uu?>j!)I_aLY4~nQ;>GRom;IiV66IIEz#lEEP7UH zafSCYK6QPl7R=+td`zwMw#tKkX0Lz0qNtgOo|P0k8m3FA-$utq>X4=dc{926Tqu|qdfj;KT?#o^?|JHN#W9D|s`v>9(qr}+U=G0nF zP6ix%emm{eP92PX;8fasEn+8~COAGq+AKkC0?j3B?J2NpDHpdEM~wiTW+04 zc&HKe#B5he58r7ckWMWw(|<8vo?fN+q| zF*l~J>;B^+B-SMRcIb;LgY+!QD;-1LI$&akrtRyMps|MO-AE;nx@mYK42HFWk3|u&9SGntxC?f|q87S>pXdowF$Hs@rC-&xH>!`}>j{qL3og*bcojQ|ck{ zW$WU-q|ZBTO~9f#Yx6fdj(>|pjO^)(qq7KK3-s#YEB19ZoUbSxt`qz)>F(BT0OZN9 zlCe!?;7QMo9#-h+FX*;0TI1w0!IM*DyuL0W6tAJ)!#A!%&ygeH;4FCGCqDZNqvnhJ zj?j^bl9Ti$)f9g`@>QN{{$WvaAC)F>3-Y@Iz{6x6xup_vGI6^Rk_jheeWHeatBWM5 zrr90V-lVI*`k}B3TxDm5AAGXiQKfzG9y$xhFV-CjArEFZUb)AlRa_V?(|D!rY0vV1 zvnA$IlJrmW4ht@&$+PJE!X%T}_GMU`7V912SMQug969s10}L9O8{P(^^v}72cq$S* zM%QBJTrsQKU?+Fy=Zfm>cEXIj6H|uxGSiBDO{-q+r<8h(57vsbz#TOV%pRNFvyO7YKaEbyy zY*B9AVo4}{i3Eh1h~~9C{qx!9&2URp*w-Q_y`DVokh(~DxP!--V3l`x@a}64$v{UF zh0i0|OHJo?yKrN7yCK7xV$U*_t99iWJKPuS@6r5^9RazzI)5ICX~L;}7h4_u=7wXw zNKi0@=#@)p9%&AuiC zYxth53p3#F?hdzQeAeu{63%kcjt%{4Ri(id-}^*y?cYtd zALO_($>m?AbxSM?s}`fVHjZ9f@d$DSbtylUhVsMDxnff9m-}dOC*v;?!15kPJtMHIo&p0|^b)udlovFcpu?(gHNT3HJ|RpScewp%dHwR< zv3azP+C%qsFFkl{Pd=@OVWm+O>eoQRA(VPQudKFkB>1tqq0bDBcy^9@bnTp?v~5Qh z#h@Z7V}fV?tsTcV3sOBaGW0eH%YYMva`32wuKkN*5^`FcI0A86!an|SV+)vY0gi=z zAFv;_%ca}w^ByAC|4WLV!!Os%x1Ez;EzEiUfW!&Txa3~^jzgY-6A%pgFL{7oMH(C>G!b*x0uW*5aYrjV{02QHRE> zIPIJa?f~7?0jey*Wp+dkh;805MOeCP!5$hS&QZ-!2gI6etY(B5< zhem@|QESpRsY2SFn3z;Exq+Fv@$w zhn62O&j|x^5FD6OI8-1v#X~9&m(nJS3B#cE`Q+=cI=tg`{+%-L>Atz+$jq9$_ji9F zjx7m-O|yn~a9mAM^`p8Vi+|I9!|^~$B%36|qb#J^s2I9CC-*XlOhaKvlONFiWk*)^ z#22CcNS~u;v;6|?^krPewa1!np)Jg1>z?Iy>^$ayHoVWX-AyRq>HUWG!eg(kqJCUL zLlBjqEIzFS+fRCyh8Z3v;^J3MiJ6_80q1sS>5}K600RTlU|Siw%4uxeBTbJYm<3_w z^-SWLA5=L-I<@GKVrbx{XKmAWWKS_&#pd7VIv(u0Gms+dB#&O)LkjZ4UZUPZ4lC_Q+Dm0kB4kCznDgJ$@}1bp@eR`g{S91X=Z~WW;79m0V)KZD5xk>fjwf8zoXH=%pR6;78Cm9zLL;6#*6&uHTh(MMh^>W649 zXGXe-E;afoxv^iMV_=pYG03vRt&+cN5wBxk#Iow}8PsY~RTaLP`S&eW6YPlIyFZT_ z>3y|DkrNsK>w7XZI&O)c=*KNiE!d=_VkE`fi~wesvQ&u!c!~WzRJj<^?8jg;xN?Ht z9lul`?A#MIgQsynsJ7bMQ7+KO+dyG*?r?uWYMh)YjhHaf^Y%WCsBc885giR4pen{UuSX0e5eok%ys*hlBicHeM#RKF08M{xJ(MfYsS44mxKJqj}RZS{Ak z`UE5=87cWg@lzTcs!d_cSJXEo3`YKubXS&C$1LM|w2uAzM`pe48<$*l>Gq1BNL2ji zbs^zyMgdj6u82s$M73@@iY%Y6d!^tEc#|;mkKHOVf?V3SMl=u z>`0M%A+`C=m3AE;KC5tTL8Ex>Ph<1ds|Sfrga&yP9E$%V&GrWuiu&o=23wA5 z`i^b)u!@OBVKIF<$9ZfOdlFgi?(dQz=$jM+KxZ7$+V4;zp}f?+U=Ph^G^R2XI(SJF z_jrs_L-aL|>fav(%%jEJuP^?rzLr@>sSTgVJzqevzmPPGw52()#NfrE(f#diaqK^# zw=l`G&wQ85_LYnzuiH7%4A3FbVneESfDO++=-udH)3<9b#(~{3Ls8P|SN$X<_!hfR zj$a1lOs4?m%m+!EzC1Sb|O<6g#43$i_t-#jMm|jYk4Q(W* zUPtWptX$GTPPv(laXsD2(lrRCx9lf>3sm?B3&Kz*DdD0>CJUaaDW%PN4U6;OrT;ad z2K^d`0j=CsNUQykTQ}Pdng;8U2aVj;d9Mfj?A0rBIdvt$+~$*Pa3EvR@^*S}Y_Ql9 zkB-y+ngS1Z-F{Zg^7VwmG{4Jae7rtDa~h|S*@W`Yk*@E>Y_Jlu#dH$s>3DH$&x}1~ zl)39CtF>z6tq{yTo>puyekx+BW#n4$xtb`84gN^eq! zY;i(|sw#lx9zLBA`uZ=>f=hJQlLp-9d;6EX<;#<9Kx^05(+R5ofd%D~GN9;p=_yLu zlilE`x1#yv{UJC4QfUj28hHlmnU55e){Y{6mz=z0mk`;UrPzG;^C!7d{a4(lbeNQP zD`#2FAZOp7^=dx6PU+gLRy^Z%Y9qhhg${bwoG?L(Pwt8>&+h*G;EhnUOe2ibGv_oA z8~WIH^zGH{%$wcxG38hjM`#uw{2*3 zfF5Ittbd|)Vp-!>Y7fEIPps7S}iiBe%4A8?~LNsuZZ&xEm2#6GHUK!F_)xEmH@PC zqPTAc2O&QMGP=y!nzR>4=m)G2I^g3rO(gLmeEo*E}@#7G; zW!o2xu+Se085_ZRsz3?kOym%*c(jwluQR4(Xq9QsMmXox00k{v^|>o-e_vgizu}FM zhWq4NRR4bW<64H41B^#-e=M^BTxdi1&&;WjT3Wv1AUX#W-E1c|;Sq4D`?$t{q3&)X zH6)^CZ((?;!vmgm=q#p%$)l2_ZEz=m$DxwsVhMw!L?{Tow82elj*$!+)!pf`Y^`M8 zlX#$@coH31_u>$ce-Qxhrtl!L|1eGr%_09oCd?d4np}MLi@YufkkH(5zcRKy#`1)+ zt4LlH6!v6~WtH*$c_j1Pqxc{yDRS@AzES)Vaz^7ot~qZ5jRy+Oe=U7dwITFgPNSKt zuI!I0iZnUq5Sp2jPD;vN#OF6TTWL7wI}x@iw3x*_DoFt$B5-qva+9FsuqY&L#QBnP4#&Q%H4HEcTEAOweIttcm_*8wHrsQSH8}>WvC}u>E3UTHpVH* z-+W}^_gIl~aUlY7TUcNxZsmmUqFFn!K$i9!hmC?!l#giAtAH+TN?b7K=7a!FckK`( z3=fG1+|uQ1Bbo6MLBEyc=2%`vkbAL5b86)8lQ>htO^$kSkB!Dc0suo;|0}g+b$8;f zK9HN>I;akX+3;K#Co=AFH~UvDN+Rb*BIIIR;njOC!kn-qT_-AXF?-(nwkTs0hHwVvHLkZol zjGOej+6*j6MPxtnx|xr*LZO(@U@geOItBNf`vLGvtcLL_NhGp;^xdmNBC{TabOzVo zA>ld#0<=odIy_0k=jPq@8a5}W)9Mev{eD!rc%9$BLKgw@Z|>Kuel7trQtDZS`u`k| z#tk|nAw5A^jxq#~D!xv;uFEm;7JO>M=55~;DI~*oAlRYAZRz1y&)sSfyE>p7NU5I(>NP-3MQfH8EYox0Gai(UlGT_%L9^cBi1kn4RIJWj>+k60LW1# zc+ftPv0lGOUGaVt`{E%cKbKh$Bv3wQoYhSU#&o}Zwop?(Q#(5ULB^!A@R9;46##I6 z{Dr9E=y~!Pspz1n&C=w;5%Ra0B!q!K6o{yEY|N4dMUpW+@*0&nNxPqWRGeX?W#ZtV zm3=uUG9T)|m=dF-UKg#OyPa^k4yY}R)*n{wglOpHY4Op6353qwifJDFDV~hp-#czf z{3+0y)?G8l5{nXi+$cNOoHIS}Wn@k58ryfIAPP~QonfUHvI-nqa5J))S;#g~&iv-q zj95I;Fs^KaT?1yEKkD5B*8&C_k8Wa{CpX_)zF z&sHUWY=eog9zPhhXcTvxnB)QBgd6N9-@d_CN%9~S?H-DGl4(ejv=I2rp|rCh6#v^k z)6;%LD(w|Oc1<2F#Jf>viy#jD^)$rruuN?;zF5W18wG zfAv8a(>P;3Z)oxY37{@{Y!3RO(yT3L@VlrtevPgSsGs!;v}Q1J55I4ZcE}BUQ483!mkj05LWk5xOFxT&kPru? zA5ZRXwmlC_EN1*odVt5SyJWrI-0Mt)Kcc7$YrMHuu`P=*$0j@_f|(3&d+VatlH^#{X_gb53{FSUN5I zdtHB!H{Z)F4HQtHZ~aX24yJuZhHBFez4;C8HQY%$lCAsz&I&~0BlV;zce;i= zbQw;)MjSOHttF-`uT;GBsDpU@9G9)&`87#y1ZK;D0WF?YmpFWpvzr>+k>k3xdOZw) zyiyChiiUs~%9Ifo`R=jORh^v`?GSEyj<3+NZDW?-CtJ?r6=2U9P;{(6B*IE7%ZkVE zk6m$?_d_ed-w6UPg~XunfI?q&jhRhZ!7M6oIw_XDiGt6-fS zZqOEXy1&S!SQ^8m2;Lhe+(WltD-QJGca-{7k>SB!;0u3|0A~NfvgTa)Cpjl13=Ab# zv<08JA(q*?5y$ad>klAO+F`)i+Dh}}YnkzM`YKuX8}+@5Ai4&2?HI|8iV0W zG23YyjnD(z_yTOcxBT@lq=P@_`JwiN6`>0G8nzA9T>yG6Iab7Z(Lm}kqh09L&j!`lkOS*QyZbzMB^9w` zOk~Y6Pj?$jR@j#nlOO>;0b9r+Zz0kv)Zqh^A;^XbNZ?4QR8`;^u;~3JHIm% zb^XbJxLqHMsEF%vSq>ygTkwE#Kf_M~*$YuV4(0F-`l`ip4s1@ckQ-u9BcABY#ReX7 zif4FpEWVO)^MMCR{W5coT2PjAVKgU1@)1~89I`eDpf8Yi2~&u4gQLHu3HW1JUZ%Qf zym$wlZQnB^$;^4`Deby(Uw|6ougEZGdet$Pxo#A~3+tU$58E#vYSwTwyAa##Jys=Y zjP@kE*uDX(rj2JuBKQvV z;QoU!AENG43@@U|lN*Ud;33f(;fIh-P~YBd_(G5hOj8Bhnd>!KJObi}($k2}V&7Q2 zda5Fj6AAFA0aOMFVM5Aj@)wNg<4X)0)u=h%06o zOHCVW4lzBO1o|Z^7B6P=Z$fWD5`uaegditQ?J&F$U)cJM0gW))L&GSq`?4+eRguYF z>V;i&{;>DFoOK76eem3E$UHZx-MA%@li}f^w1g998XIfY8SK)-sn=4qCx6(OHGld; z@Aw7hX|9TmdFrW!lh~h}ErBR*i9$QGIvpko_OGnw&dIrkCb3dHAieKpburr`l;9$k z&$+o@dVTM&uyKpzXqMLE84UJw@~6tzp&cP(kUD%J*;5@N)BFB)Uu0!J0#IKn+#xH) zN>D<^g$U#GY=iZnqZxewPLiE?P5TFJJ+)kL*J9|Xkzbd9EF4=gRP}a%3#|ZtW!Q{w zU$$2Q(~Py@+HM_D5^xQE+=PZDfPnYwAo#5;e`X@CBUWd&O<&T83;pgcuYENSgOQ*3 z)h5xe9=WWJw6A?QhM&zW3)yXTB{-}_$79WQo%40vPNlG&fd=}7!LIFrH`0Lq@rv89 zjF1TzceP>vFt7`q+6Y?cjukN|KyhaPJb@e8F7h|}oN1kvG}g>0PLLRFw|_U-q-=jf zqHJHD;aAFTr|mSKVnFi1kFe(f1Wle2JxC}oN?GVauh<#u*v-$qD1bmk#`tHSE4Q3~ zi4yFh951S>LYOR;rLUCYfle%6DLLePCWvp8HY+q+^T)X1d_CA#d`$^DLJc;Z3?a(ILCDQn)26@CA) zs9>qA2d$!JW%+ra2x!`5Y)R{nWI{k@m^pZ1HenzPj-WK9eOYzStK8A^Be@8lQwRA6 zbux6MdoY#LO!n+uwO#&AN01GpO0)kU^g;I3td<-twFoz-XlJc*uv;mvP}ONU*&&!< ztNF=~aM3x3vA2D2C8i&fLguU8u23nKecZM!9j(y%Bxtm!9);~O-M}`7^iUuzAI9{1=Mt7V~#P?~1mmiD2l_oEiMFx8oUjZdXzT!cGh=QI^ajEatv4y(x z`VQwVtfJQ+=mf>eq*97~G#4V{EjgRb$Oo)=saLwEVP;mOP}X4R6~90OH6EnvMzI(B zNj5CwM>-fgFa5tb%Mh|96zE5LPcnhTny>Jn!Np$OYO!Oc7T9?Fsoss|C`Y%2da7%T zVLezx++5XR;aP9g`+j0r$9v3sdvAz$$0|eBhYtk;@*!9HIBbn}A-jK2>Cv!@hWg(& z8{;+dI*oOgXO<8jYfYos{e+VOj6fZX-{yIq*wV!w;F664hEKz?eEuXipHWpc592fuvW@Znk&S>=_c~W(AyY%f>cu$>dkv6E9B}a z-&Q1L`v`?RoW&ozBBU_O(5>D8?jCoHc_|9>YTg zZFGe9DKNO?^}UTiGi;gXSAiR#{^>K?15Q(`38yZQ@oi)j z6C=fdS?iXmYs_m8cUxaaz`5cXAK>;7gv}`UOz?(6d~%?^){lk@2(r1-JQi^&O4HO7 z)~P+PUr@C%d~~kOV}EoaA}B6lCAvKeGs7Z!`bY(XBi{B>#0Ki&)NpTMoiZ5K6t9zm zC3?>DG1HY=R3(MgMWaVlGryr58`o&VbTxK~^)({GP?5kG6Ul83lI@V0vD-SVCV%S! zP0F{EYUug_xMr%XYWo-4TBmM+&!)RXbA@G5gEFd}(bCY*&=yyKU5_O;s(<=FSW;{T z?Ia#IAR`yC@(2C}C@#`8OCbM~@2bdWaWY1uzf{mg_61R)l9aDf+Ldo?)d$@UWujd7 zSC}k7MasJ$VjIvGwIaZA1ptu<1d0t)Z~d4zk=o}-dcivp%k=)+;3w>W@@YjYh;hUg zDILgBL_2s4IO)y#d1sq3@k{otiB`5KZ_e@VfO7Z)QmAz3Tjn5ZYol|WMo5ziKbIft z`a?%s-ENHcPH)BomUO-RVU^@#N`rdU==R8yLbE~c%aTv*IW0ncW*-XX1s})(op8vM zSzbz7Qct}4tog*odjY(w%n;@5BG5LO>K-Z`RT3nTfO)Kt~%NvJJXN!unge5VL|g!B6WjXT<7?1FG44C z^VL7Oa*8ZkC`p)V5#>qf_gu=&-wDfjn8`e6pweD`J>&<8O1V{T_3S|~Ja1~qzdU@+ zDr*|`wZFb0E0=!54cx9H{aWS!SPrR)v^wAw9o$otKpny|MN#7gMdm=#FDwRsimyv`ScVv^`ny;LN%v|F0+H-aoQE4-)B7 z@8Juf;Zy_B+z`2so`z7eU7DrC$#nd!Jdb~bv?m6ylM@=pb|RXq-Z=yJf*_JO!#8}vDN|A`Lzc#`X9lpbYsBn;{nV2ohq zfQv@3J3N^y$vzHh_l0bnjQ4WLi^5~oof3y4tSKPyQ7Gs}C(WUJ$7`wLXDEf)Y&S^s^PVY0g9s8MF*5LbYq#MR@}BiV$n1iA_gRBzrIC~DBMet3`R4v4vw4R~I-M!9gU4|# zY(6oluh(q70Q-m*$Op|ea2~d~)CpFcW6UQTqLym`YQ3#4QW%`^{0VGitiN7)iY zPP6voOnKSI4*0?pinzkWFzLAL@q6*yOU$cD#?aPH5EVux}rvKB!y6tcB( zaIyW0V21vd{=%6P%nBB=EBuFx?sv`+bwgj>-S2ZS?#7c6ALKD5j(a;tVQ}n1P>ynB zm~;la?clU1dE2lT57I+cb?5Eh2u+UlAjSNJ;o`_xsM&gl2HHcbpH1bX-|J~u710-- z7M=$NmG<_LvQPO7dw0}nyV~#CRhAr=5SE&`55&p(QjuEK3HJm0Bc|o;xj#_r^?gHH z`g}!vnlGT#L!IZTDA$6{`87n5Hg3%;Q@6JI|+XNM&O zrNWIaL7Hth3C~JD=0L+~)wJTWUWf$a29yZFI=WGe>-)ss2()W74y;L%qD*_Dv|m(0 z(82h8u5L4nT$e3ge`&syTpeS16oMmvU_sbwbKh2i9)VR6cx=FH_VJH4+0;{HtRyat zf(R$~o;%``Le0SJG2clWbzxavA%{Zwh9RyW`7Ov@kC|DxuM6eC5PJK^K#Q7s z$fBDR#l~zYT192c@hz9=e6QY7VSTh338l&725eP*e2YlA(&4af8aGMk^sxZTcCn^_ z{LL|sc(j7hqf9niNv<|`d)MkuQ>c3y!l2v0&(9tx!_=NF0C$tfZY?&Jdz0lE+1c8w z(gK9s4*swe#Qo-IabU9+X}LtAs7h$kjq|>YZ5CV{hmnrEE84{YgnFM@--WS z?1A-)TC6160y63MD#lPf;Ijl%*t85OC%iv*R3PE7blT8)V(j$85>YG3zgkrCj#$|B z%M@0FwTPLfMt!nWHCkANtpQRJE&p9)1f{xwyNWP_6!G|~8=;aBgSN#Lxy0-OgB)><+T zZEg2d__2{ye^F()k?7*c##uM={GJ!~RtKOro1p(IkY}Q{TssXw(M)g&M6ua*)5qk2 zeSfzGoU6ORbbh$&tMgQGk^2M$U7d5J1v0=K8#8k^+Hvt<6=xd>-%1r$5knVvGH6q- zz5nS}ZA$%nxAWb%U#|1h^ghE1t2q?EO%7yXst6?c7sz7Zi}o$5C5rUq_Qqh{L!AL7 zJBrmuks-+5--u`2u?5`)B!@jlYUHv z@tAP~QZqwQPkv-8tZpPOqb}r(S7w!5yelUO(Zl6<$Ty!0W_dFcbe@K8N!(7n3+%8V z%e_14^9%+;Oh~qTtZavpoQs~7EgSPc{LEPJ_Y3i~@0lwl%h#*o3ZPaIgUfVxUcVQ& zwQyevtHK$ElKX>09wyB?m@AS7PAvp7^ZXI|pO*F<|8wobPj z5RcYY+cQpqXr;uk4@_}tcANfr*pau92vbZD^WWoX$!zF~>1~#&lW;`zKzo52$G}yh zbPP`ROP?j=9`XAgBzIsDA+(Ac%l#@ru$5l7*@`CE-hZBk`YSge~BUg3l# zpEt7oN#V^P`>-6)f^cG2T0;#SZm7-;x|z>E+|Y~*i_e{!9okV*NxZw&#EgERiGqb= zCz)WLxaT7i=j)Vm#cH-6AznMMBRje>J0X}z`Ao{{gen|eh-2PsiD44o9SQxg-u6oy zj*xcR00@ijTcW4~W7VI}_YcD`KEHpK!Ykm}vf-TYRt{O@G86+hmn}NbjZrsWpmCWZ z+EF4iF!gJ2C==eT*O)7i15>thc4u78{K|>_1Q&4QIa@4v-}Hon2d_CEJJe^N$0kTl z&Fc`vGjbD^8NTM*&7pX}e!;BjZaw*7S&3hcwo6jToAc@JQ%pCt7Hmchnn4~g+wCrQ zSRSYx&z4S0_!2`2HOz$7v5+98{a|8I>U&|uB_%0cQr{^!tF)U@1$;kY;v?x%RT;CwP&pWyQ;)$UQ>0WEttY}LAacXU#sbwkUELFBGZngF1+S@^sN&{L1H;InP5+my3x_Y61-yq1n2Xj*mi;ulslpKG{ z@SFL3%o0DX>wns1mk@vHpeIO<=OleOeBK*I3h3*oX(U?{8XFGTw58koi4<@NlzVLwncI0) zAnHL|q=7%<#B(9jJPJnqOcg)41I`+epPYwYD6l*j{C&4G)4~Lh+#t@ae)hizbMggf z-@}mowgC36`}&0oos?eee4GD;(B)v2&7mQz?`*O$hgOTd`}{lZjjs7iJ49sM(`A)u z$srjzX+Qwm%6!14gWwdM&%(yHDLk{;q+EEqw9dNm3Q8N$IimO@KZ+sAa& zseHGcWevYW7tS1uXTJo~D~rP1Lwt=Sya{@K$ErfPx7MthyUL-$V zRmAk(q#&Rq30FR)ASjlengeR6-V_PZ)6MZytnm0Q|HVr9Oir&?D%U>VL^fpPT{uf^ zeIJuj7O-Kcl0v}mEK_54oJbsjb-ALJ=XK~Eb7xU|nC$U!xq?}eUKcb=z=x_J^slF~ zuzl_C-Yo|-|B(L0F0m`9d0~k8#6!Ium3K0+(!t8TzM?{dZ!m#3G>d)`fymdukrh^6 z@DWeg)zSJSe;Ah|0y6*F1xFeF@}t5({K(qee#1ZIK<4qLVo*FJ@s^64t5;qhTWqec zDXpg($4D!ku{$~OKAF}GjrxTWKxsA=s9!F;@(pf^+(CqgFke|S24oGLu|Csv%q};o z3FMH77d8XeB;8eBjKc=OTxQ$!oJ4muFkUsm^0+zxVO=fAO@_x{8wo;N zh$|iFmczn!#gcR=3{S`)Ckm)+R&ihV81z9mjxdZ>|GgRn6Auv1$ttCey_+!ca;m8x zx~)iC9kEQ1I+Q9L2EAdD)Nl=6vo~t=<_p5)J^yQvAao0Z$t9@BEO+lS5RW5OGP5xb zVV-8}8{#PlkGc^`y4)5Qat4~P;f)zR%A!~Y$|Q1)AUq!2J+>kiH(Fh0*}8ahSer;r zFnen$hH|<=3?egevmW$7YtR~6!wed3;Wa@|;pevCrc^n3!G{>~lAy77lNhb$A=*FYB1&D43F?u~3B!;V z-zvx+8Eol6m~13vi^#f@S~}eY$qgW;?*7{%S70Mi#6uk-DYIhHD|VX6?0C@z*<_sy z#=aVP1={<>1w(#+T(@EFcHT%qsI1&3g~veG#xmFPbZ-+z(ACi<^jf62FyEMLrzL#h zJ?R}#rAmDyTw!`>TCE+9w3W(gNA2v>s77h5`#o~K())Js3{zPL)YT7JUv&F%&SDTl z#6;q&)8QO{ICI!{@y9NsJ_8`H^)i;J*lH-s78WtdO+r75?FD#XrF^pZx+lwSVK?nv$D!1wZ$-t&$s3i5w^tDj8; ztqEf`n4hdr;F_D;GklyH2AMfa`m3FwoJ6xj--<*6g@lfgcA;o@41A8vU5O~w;LX?3hwSaEmYG`F-%PL=H*Y1uKOW4uuX7WJy|4a_ z30&L?-c`9idDN=HrR)!XFOLXib=-cAr-tx=N{YndQ&(qHdI?RH>R()6P0`|L#1Q&Q z^~V~kN~c~k>-01bD40c43r;#wuuC-i3nA6-Y7)Zb7lRJs(xl42+M|S%6XR({MUhu0 zUf_T82?sK@S9%Y|8xP2aH`CP9XIlLDoW5LcAaihf40dqt7bLP6bTijOStRRrg|LkpKC@;B^kHWqf<>E~ zkq~ylGJ;6c*ZH7{I_GI;G9Eppe8Eqp(*|36jS$;*j?uDy{ScOyseR&R$9BPW_27Nj zSr9*>TCXJ%-6C+G+rJ62+QDDAItt1apPoaJSmaYlQEZfcdHHjZhmZ-DGRYm3Ea+}C{F+fbV#S4jM^t}VW^`WWC*5I|*CzRu)*aG{;UU22zQ!kp- z?426|ksm3go`ZbrBrtvni31K{k;_6sD zpe+3vF&O}F13}{O8<3gP7O!LWZ=^pWm&8se=4)t`AA;^i(&XySkgD&Gv9sYET<0Rm zTXQ|lKUDMlI=b(!3oKKwYRZmX=#d#? zYt^H4LGD&->t~YX@wtOZ{D~2Y93q$to=!~97ZK*H(B{_?AIoSxPr{G+n;qa`NTv(E zOr5Ufe;L^SG5Qfn(FLyVsWnIZHC%p?GFy?(-|6U>bgp@h zqA@cLAfj@!VM0Z8OF#pn$ia7*l2!KK&a}j?z$PV{t1~uu^BPcrBxI6>)+n9OnHv8#eDn^;y3o3eB zioVj8VZ=wjRJHVtl-3}enAfil2mQuYwNWX5UI-N_YA*2{z@*3k^V+B}Hd8qY;MlsX z>(PrVdz*3%1nlgO5RHj*D>E34F8dAm)%&Q)KR#lVobb#AV|_qo7WKsS`a|GJz10J}x#Q2ymyrfR?9_^H2Tib1T$3ZR>jqQ4Wgba#oq&dH+p6n}$iR@;A5 z8(@~(P52R__?oJ$NfzpIMmj~rqQI8c@}Fu$Tmk~jC5qWrennCq3>zC2MH(5{5E@ai z>A!{b_yiYF{{Orae|L-$UT{53=iP?-e*!n-vsA?X2y_%OH%I9z91q71RV>Q8xn$^| zqJ0;)khSAkkIk~LeMJ?2YE=eZnUd)5SrbxjL+Vga{f)`}%((o_+D79+=%6fZjn5Nu zag5CWMLhdEi*(1=hYGSMq%ff_5bQh8k5TCV6q+ux%)v1=ePkXVy;V)*HpJ=u@qQpp zU``}W&~;O%_*<06b*ctY@%|^pf*Oq&sR>4y`B=?&XvY+h1j$;{jQS)SHmvdgLV@#+ z(d{X17Roh?WvqCk7!SEINL#K+YTg&D3`7qs7Nkc-NsYPhTHhz80s^2u`IGOxi5=Yk z4_R*=7G>A24@-z3J*adKAOaE&CEYWmq=ZO!N=So*g!GKm08&GjlyoTF-AIFkboY1j z?B{*={vF?c9(v%cS**Iw>x}jvroe;^VczG|pugcz_;;|C_k&&QO#pUlGP8|Hz}Dm1 zG~036k!w_hm2oU2SK_D(dG|_8%m9mk$`V|B;zC^rqXhD|j5OuoAx{m7B&0#dFuf|1gob+KNR6)vUeb}n z?;}y!s-wwy|E$MKB}^*Vk>tkGN+xWD>CL-$w%}sBk0D?4U46hDGo;0Qeh@Jub$C4B zo~B>t0&_1<;8{$h0}>0Rf$V#ZcGvvdtwX8%s=6Dc}RRj0m0 zZqRd9R$y@$Yf($(nhMuCG&UixQ&fFi3yJG zjiFq3=cXZ6`Lhodd87>aF)8FnfP6kvAZ&>@0GGVsKYLKTj((`o3vlt?oUklbopt8S z0@bt;RhB7eZKrASEF5*fj}$^ObNE~Z!{+&jEuX_ye&K~7|C6o~EdH7?COO##%#lj@ zy{cD$Wkk7MY>ApMZCX-0+DB&+zfd`%m(p2zii&cx2e>AZk@yYB!oGJN#4P zn)2P`)8S#2t*sFhK)VgMH2lL;*{&xn7 z@d4-g;$oR%(f>@ze+|E5Chkwa?e$kn|F3g3aDt=*$qc|qie*OgL^pR4&l-gqXgz<| z7neZFkn)T=hfUIZCs)V3GW?{i^T57URoeh-blO(<4uT%rx z(#9YSV6d>iGgS&7cjq=m!_Y8zzt0(RC32M&mAp8gaiXV z4$6U0!VhK_UwL&*BRyW~{ZQPk{X?VE=vl>R(O0jYtL${SncJHYLbJ7>{;ajqiuLeQ zb{Sa?Okt(t)$qTQND&E)gaSXqZ0H%WcC~$~_sRI%a^v>LxiSnAjDi>Yt%TGbUgt|8 zZlgita`ERv-T1tkK=O7RugwHl`n!h78DJvVtF9u#M9FXOQqHtOw{*3U6%9DX{;e2H z>9B42gf>$6mQ$zPG)x#!a~uNpoR5DTIj#;hU)01qv~iGg7Rc zK?HxgY=}X<@%ngu(Ai5YM@9ee;lrpl;o~7G9@j06={na!M%^-OUHw~=GHfM%@L<|) zarHbLFf7*MbC`b@h`QJ-?XT`5j9IvbQ$VZ<4cwO=Zcdh_QTZGX9|CS}>H`8Rc+&uZ z)NCL{@DT70efx}Edl6zge6hbb14YKG>)k9L^oMth_^J4;3RtE7;`#R_`_JVLq-+9f zks&c&b6ZXv=fU`p&*gEf&*^k$Y$ZU1*ZapsZlbzrt-p)chlt5K%*3V>@e{eb+i&K7Edn~+{IBn$H0>2 zUH%s^z#9M`)oqs`?u{HLN)0#xfXT=seNlPC^Oc9B@91u>&fL~SkNRE>u*c368MOxA zTm7W~!E#P>A3AHxcbl&4F91JDM(M~iLK@*I;@%SddU<7KWt1=oJTDbFr{a4^LJAHt z{rgDlTmn_1*ylNzCbrQ1yUE8ZftL5nv0gpE|4g}HUdv_$^E!e*vWMsjS}%q^JX-vD z)e%j*=Y60p8n(n%+OX`)7}b`Dq-=I^^CHI#MGw0ed4PoT=}Ifa(T+)7 zU!S5J>fVn@uHd4YIzJkizHDF0q@aI~CZ!N@#3kxOCUgPmakP%(<9=Oux8+x1In1C_ z|?giYJ=h_rlfXtDwHCt>5724@ilh4&_4iXsF6*~<3isS0yYOk4d zG)D<>p9h%mQlE--_J>_EBNg!H;&E^cg&;^6#10ajS16b8ObyK`L8DKFNbs1)x9$K))2jdwzj%Awivq+>vD+7Q6^j; ztW5L0%y;xoKaHeln0)WmuWvHlhcFwd-UF;lT?NW)M{{@^{AN@C_v9Y(#)c%-GKP`f zXbT7;MZ7+uWGNU@5Ms*T=Dqer_$Ahs(!kba&dN0T^ov1Xhxc5@8!za};duV%#BV?? zzVgNrkpxvRFfG^`LVkU-=f}za+XeB?xu5qwk5Uj_wp?TOR(Vk5EX5nJs*b|c_c<9K zC_4Njc31nV0!+-{ku2~b^PTIsCXdIVDY>Y3%{!-Vrk5PuBw>%M{J?r+x7?m!vzmK% z^Jh^MoYu>kHxt(sLLhdyc%v*x#<=Y&PsW72h%HUEKO_yXw?piC{}Cz?wyuEU>5ziB ztvj_zjV1aSdCl&v(Pv(P-WfxGkyFnYwB8hYz}Jfm%O8w8nzMYM?u6YmN?BwL z>6MibbIYUnz(ILF%#U*7Rf=Qj2VmHWF(%G1f73x^R{$ojF@$hc0$rl`O;KQTB$c3A z3E)h*IbD&fFjDX4Ab2+C{M*9kC%3jJuL|Dj!H$EU5Zw28W4L^;=}NYb)WnW5{qVWb z@&bD_3bA&jf>cu5F1CUqnH3Wk6{$`x{%DJX_#79-bBEQBqzX=8h8~f_tbjLjGFJwJ zL>8d(E$9(va37L*B$yP&S(aD$Z6fVI$IpE|0)2=Hmme8>@`VDUBwiiS^7KPVCBpQB z6N*IscVz+z3!F1mgY|gtG zzP;S0UaDIPrd&8(g@Q2w>K>=74|t=7|><7qNf74GQt< zL1Iy`93<}%;^N?Sn%jinH%8s70pXh_*X!i+!}L>5omo&DV{-QYE&b9nu)&e2i>%h+ zhFEZb z_fBoJB{Kw~&R-GCk-7wDFrl5w(YbCzTCn70k+ogu>R zA#^$ut~6^|FZk^N+dsjX|5G!<{Q(9CQdwG%*tnAaa*FE{I!ny4{>7l*?FzFe@Q^D; z^VbIZV+Q$4);yOu5=S0$$ArEVpz_?v7&zOyz1oXT8H?g}5y|4>8Imaq)Lp|M5D>ow z9EIyE@4Z=PA;~O0{TptYvl4o!-D%Lt?r^Hdt(+P}~-J#@#2z`|+K(s=73jZ=JwOC&MK& z?O4?W%0b1brB;!9zVAEn0jL+1UrlC6JtD;nqTqkIOT)M$KRqgn3AYW!R5 zG4ZR3xLbS3XGLk;$(hTd@%yyyf6M0)41YR?KK(R2RY9wj)yzs}pZaI)aqWO+=QZtuSshNF77ETSOkr{skQ;q7XAj{RaNO1JyN;+ zrkwhEd`*JNlCPVN8jJ!vNA%R)n;Jyc8K)2On*DK`2rnnXkS$oPX<1}$2-%UID1!Ll8%_+4XeclrT6Xn&EKil6BNVhw0B}RoX3&--|K+j&45pOeJaeb71P5gp#bc&i@eN?{S;FfD?E&X zuYX|=AEfA|qc?$iTUU~QOY=}Q@9BX?_k|$Y@K1l@r`5ad(NvVxa{YQp(gWkkzM?F^ z^De3F`W16qX9V%tAlr<+;Gqjuega+GaFfNed?p{DAgn_gMN9Q6&&PrSE(L$r0~=}g zRJ|>2zMY9=YddbiAat4GTZY1A(G@clKzguP&oRZ$09T7Febt&gaeKz5Z{wcTUQ0?)X>uDUVXetKgxS2kaM>*? zqoh#Iyf!xWMtyz3S>b5HGKyWj%5CedjK|a;*j64+7IUc6PUG49uHdjZ#or_EC`G&}?u-On{@cBV4Kq6N z&9jN5vp0IO{bbuxk(AX`bepEHmlcbJKllNK*zfnY_pZ{^Ki5R4gvbKYW~nxq7iP4& zw+Qo(AXP76Q=-FHEA8r1ckVInnvEKerI<)G%b)bJ^tBbCx(? zXNg9+ZUUUg7#92F8^c*4+h>=?ng%>~3Yvb5sto>&!z;X(ZZZz1eNM0_Xa{s2>JBwM zUNpwp`*Q?UqE+E1az|#@T~|Tyt8~5RS=rVsZi{gJqQ6C0iDVuuy8eD2SPsNZPzc2_ zA-_9Q5?Xg92IKPT(rnAj!A5Gh`Kp;0a~A429^jxR7R0p3V1;{|w$lqhhl2eUpX0Y! zjx%=-mC3V(6=A6i`J+d6&%YncKl(`ZfLmS3#at`L)WR%efdp(TEHFU{3nx_t+M1PQ z=rAU$x<^lZjLGbxF#BjZqSiIRmUC0_y01!t5#ApHI>z1K{}KNs@huHK-owt`o@Cz_aksrY>*k|z5&*{ngr=5R!)AEa;{RRW)CE9=j2G? zN9JPsp&TMQm;zFTLaB&MBnkt9K@$^H_TbyQlZkYOh)UUhG(W$qo$6FST%}wx=qG;+ zcOD{tWWK6b`nqPrz@B2OS8ciS4wAYU!#~)PA34iX>`M2$&iZFNT@?MXztC!oR0<5N z#GY0C#8SV?{&y93TII3rLf0?Kw%litIZMK9>6*Ak_ALl$rRG@+GK6VL9OO*yeVnEv zT8o=SCV^95`wLLz_iQ=s-e=rb%7gaiYLw`9=>D=-sEfCQ8+(!llqK=QRIr=jIqOJ^ znYNaP!W+52Ab4agXek%x{mBBgnS(f^++sQ5@AF5!Ep@HrJQ)m;z!0i6z9N}Z@T4UJ zI^s;MS12Vf)ywSdRGCLn*>4N4I-Pv~c-80CK!2Ff$IU=eAGsTh6gpWk?AU8z06y9~ z-wnq_n;Tb6-r-4BkGn1;JVH3WL40N}X9MFEc9wc)(WLu}`@BSLWJi!q)I~w7bVQ90 zRAMLWb@4ZMBJ_FubJVzs3hu|Jhfhu`{djpb;GB zT5Xw&)k}Edz8+QQMv=``tb0`XIc~%q(~_^zWmB!%hd2>jCX?Hw<8Kqc z;fjpqDHRZ$K22YZ{+}Aa|B^VbAC+5`d^Q#xk$n#&{sum{`gNLVG zrb@Iz*K1VRt8XmvLpZFfg0=qpnnOKBA2#i6P7T$PW@O?a3i)=cG@Z4_jh>*T-x72m zE>7sLZIWFS|SnjmovLow!p{J6?l%fBWY?ZQ@UyEw{Q;9Rann&OJ zA%w7Cg%rF;Do^W zZ)FiQzK3q_-iY83wQ1XOBO zCV?6p$@CdY*aq(@@SpO@4{p)<4>N+ZoJE^h2+WSB{_vY=s6A6DdB^+F%fN`I6@QNW ziDY01#Da(zmkOj}!|OnSOEuEqu4mOP@#!YM%@%;HUy5Vjtp|JsaU=Ph`m)v|!Zn1; zD=M-9z>x>AiIkh!sFxMv9d9_O0Nmju)2iahXeqx$*qQ%+v--CQMF;fCSh;kWgT)J_ z(#@b0ZioVS!Qtb4#^tL!tUQ4BSvLdbE0|exe24P$I|;#1V@Q~08NeV z51rI30AdwAmqswy7z^=wsrv-j@EnhupW0)i*wHzpM)G*{oi3o+1JCHYP@-An9*V?JkcUBo)F_-Z=#RH?Un&%{*oIoUdy@pX7ZxS0c_LfygOGr0`w8cg+AKOoFC|7`+6Dw_jD;) zj(?_LdD^LV(JrS}W?LUrN6VNbM@_FIM@yH4U;H2k?g2qzv4U&{a$M_jzRm6TSubOU86vrY8yF3r;d=~)VY}Pqc`TY2gB?o=s zwso+}F$|Wjr=uoCS4=#_9o=N^t(AYVHBp>g>98PbkDs_MapZs$b|NlNNfR9ig0CaO z*hRC^6Mx#2)UID@5Ve^C8kocVje;;`wPS~y)yLd#4JqK&RiVHUSBZySSAg7b-lio4 z#Wz?cYxtbkFzRPK2Ar<$qE=IQp6Qk=kL#GZeHAwstTe~Xb{cq=KNG_UAB6>k5F#Sz zu757GYrv3LSosdF0^e*kue!#~@61c9jG#PnclaRwd8uXsB*_K!`3ful>&PEyuryqI zH63CaKM}7XcW!IwbbWUKF1rf1p}0T(Hh;~V`FjMMgukb^u8{m`*egE+08Jm2xBpoi z#scLS>=y{59OT8eY*|QrInmwC+-rX83tC7ab1d&Ubas^Wex{es0U!PrFSjit-bo#$ zz@}B?XhOz&^S9#Cw&P0F@U`fq_iZ9!nvyR0kV9mX)uvO8rwNggDH@mlmbp_78oK#z zB37g+0|h$ACvk;DAuT%tmW%{HHZv7*p&w71%q`xr33i^r4bvJZur=%q=y>*IST9aq zsnzBsW1foWmhTA}1PJP=CA4Q5gtvxa<50jN-bcHkg9-(&Jw}(l`I0r{n7-Z1S3bRP_p$Ta2G?5AFmcrG!;?uz5dAS^e}zxum+0Y`4agI+>X4M#GAc}MbM{4s;4kasS`%EncUq=d#EVDA<8KXW5y)G{XyK8IY*wQ}hm^6h zP!qOpnluySYbV0045<)#QsL^|554~A!;p#3RqP`iOd{=Q+h9h z#b3}5F(50yfN*TTal}wpsUQURitl zZxua@XTCRAg6c0W`&XAdS($?<`R!cRDjm`|KY#wL%r;HBiA6vG7WwW;OTY3>5x<>( z_(~BG|J*pfiHUg88IC}#FuYQ`YA=JYPmS4gYJaDbtb;bV43nafjld*{;jGqV~gwVXxA`M9H=Vt#)tQZzgSCkG;4GG@)qD` zF)|c9SS`FF;1+p{w4#|DVtfik8k{;2J%{Ia(s=bHiAB=9(#`o>Oh7r0#U z5O+47BA+A9=Jl}fD4t8jMg@Jxzy@N+gHNB=mlo5DtI0TZw37hvLFkix<6zk z4g6ll9aTf~YO*k|TK|5)xb;2LXY~TYpgv_W9gRs5YnvuATAnmwT(AF5fjELd#cT3d z%Uc-j;}$1j1)K&wk{6aUa`+@HeE8(izm1s>RT*YRM6GNQ#V!79yR<&Xu~d#NXotlP zetM_kLDlqh@9`8{!YKQtI!*4Flvkl6^X@mUtP&bE!&!2@7eKwOn8+p35kZa-z%P3` zWkR^QP@2YQm9>9GwpwL!MR0+B#aA|;o8noJ;=RTC($-yURV9+LtSE!GxKsjuUT-x5 z$IdYuU&YS*k+w29Y2e3kWhC(#m)iRWS$?d^<4#@>H+A(nB@TWkhim}2Y$X#{4J>d&&q%~$AOTT9za7(mF zrZahuXhaq0CudvV2A^W^m2L|rpCPEUh~rf7pvYCx+=r{3u2M-~rwcQuWf)8C?VIG*<_lE&Yz4c%p3Eon za24T$pcK~$oe{!t2CzBU()vY-Nqkgl-S^G|q!=?U)7aAg!6cUy&CMk zd~~8eui*MRr9+7ICdmw)<=bMFq}qCDk{h4QiuSjETM-UU`FaY8?{S|5UBYDoSHODm@l|-y}RRy-J(|G=mF$TgsZdJ3Rs7VX0Sy zgJs?Het45#cgX8wDwC2ul(zYeFH0y2iH-76B%oxRSkIrgy${36s zXcRJyCNlrebKieVCS3+%6}Of+TpIsnk~aQJwP@_&=OJdFJK(kHS800M;ZQE~B26;% zHo+hP5P*Zn&P=<7*B!e>jZ;jIH{X=1AgzrpPi57qtw*kPOH51u!VQPS7{Afc`6^K; z1qpP++Bi8hd9D5~IW(WdU++xl{g%J}OsWsIFg9xfO*Kc-F^t7OOvlOk_%|Jc@aqx~ zRZ?|ID>vHS9Nt(=m0G?rDoOYF{j$Vbztmuro(mC>Hu_AT}*kRyo=< zy858!EA%ydRD1)-zCg7HBH@)8joHgraJ`1%)y0mFjn3Y&*hv;`Fo9=B#r8wjr|v!xV(BlrHe zEqKW7)&MbMRn7_Ck8aH%ZkQ`@o02A7(GXf4bFR@HkJBTAYe{8+%{FXUR=nMETiEXF zGct9CZO4P1s}7<&`4KYr&)^-?m`ZFuZqLuLl@p=?IyV&I;yh3aQZ38f^B%U+F5q?l z2OTY&64pCKCcU98wB1QZBCs^~B~pry1@J6Rff9M|B8 zb$%tYfYz`uEaos$)#lo)+2lve{>5vCQ#%J!rR=o6X2+n>{^Dkzc@L+Zs>$xp3-!>) zLeJ`$qY|IL7IUuQOHaLSdU>!2Qfh&y+*#QCoY*qLhCD^Toa4R=wj91b6O4R<<#ac# z;lv`e(2^tc?Uj-)dY-up$JZ3rET*hizc~L4DfCAL--9H<3>Ihqs6^H$QFaLhRTZe~ zOB>Yn5w#lcxnuX`$sUsp7dz8yX`@DG;$KJ2>`ZTkS>tfI4$`m@okE%?oY(_&bIt_d+hKR86jA}fBOMuHmQ;A~xJF!u% zg<{%E!nwN#eK}Y)k%d6@?8k4!_Tojyy!_GHda^wn_dc)npzS;J&KHAgHLVieFr_?} zZj}u3UXw*4d-LK)EZ#K311)D3-+Xm%Ppg{+cb<=?0li-sAtnOqwShTa#a5XGwC>bm z7Up9ess$Dw=9gV4WajB!u1kJ0|1!uxHwd&z@yWRi#RI>r9YG_{fi^2$h_SlIWqJ1K zQWq1}2LRF;vo3}L>Y(C0?&250;viy6U-%D&f{FgvFO+^vYiT9$H)j}3=Yh@8)23R` zUtRS=jVjj?jebup`BFdC7o`4ikz&&2cix{(PkZ7cUe`qWHDuK?!jQOSfAyB^o(K8D z+xqpawz>jI4reOGM#Zt_r_gwG)gJeQvsgXb(sdXp zJ%817Byx9%cqM()^m}8Guc*{$s%qbJFGYJC{xu(3I&t#_|vG4IceqO4Ix;&M_vvO|-|^ z#E#lQJ6`@wne$oZ_lpl3+g2dS{pc4zX|$(nN*}p^1P>IlnLmTA@p;$siy) z3GqW-RtB6U-}hk4u=C7zgOzxN?$>Gv7l6fNoVD3#y7cR_Q>*J_uj}MH#^$@5y|rOE zTGtuo;OV!*JQY0Il_(AKz%kX{k3hg8AOeqUIGwRsqnJCR*ILh)a1L>ZX11HWbaW7e z6q!oV`4H_V8}Un9NBIL?#^%jSMI-={ayXoUAEJ$-m>$aZwh>vnVndpl@tt?z9BRCK zs;qZcHfjgASU4v%j6X?~6N*|-zZRAKZY3+defMJCN3(}UVm}6Ohvque=oUVp>b{+D z#G#vU&|f$4<&k|w1!6^tz^U3+wl&@|7EYM{u5&HN%2>wIKu*U{pvx zuR9o=sGpNro3)%hBaNl1^B~{{p1DRg7fyCZ^a;!Dj`%Xb0mFCB`)1!dm#>~{O!Bw* zrKyBA0f9{XIq|ZDBxpE^Z!JtHto^g?R zmaTg8WKTMNwe@r>TB-B zPM}M=CHcpBbW0j?{hDT2*9c&dNdD?9ma!a2RSt=*bg?ehdR`b?sWp|S#Uw?CgIx)M z6~35ZE;q^#`k2l$*ICzr)?7t*s=U1*hrZ)%v_AFyvCSuZ>rv?UbAA?Liqqd|Hu~U5 zW|4+8&SQcp+ayyJDP=i@9Fr2%;DOJ1~%2m_h(l6Zsaf<{hjFv>k6RQ z^B|!d08+7+qEJl6pb{THApiPh4@WU>Fh1q_v(k}Gu6x*Q{IMA?eBv?Xc!4Q47|lPP z*$S2?*{HK29bo zhy`_D7t6TWZa%YYT}x&z#^-O+1Q2)({~surI+-PNSIqTT-oa{6P+P=$v^9D+MEIPa z*L>{T2OcS>XFGutWc}KisiP39FoucaO(n#{F_|8YQphK))r@Y&Dr<?ZeV)AVp2x=^8*PsS!=w zb(2F>5?t&M|3oC`n&_c9L|51Sh=875CSMn0pjo$B5|m6}ylYuLoAI`WEkZX0WDkY< z!J{0aj7+Kw@;!(p^P6|L3fZve;q6VlO? z25pHAR+xdf0oXdlJB~>KVlH?v9hx?G=}e`Lp(Qno^;G|+STwXfoU=2v=||9X!gZQy znyY8l5v(2Wg=o?XCb2usEE(Kg!S_zSCr3etI4-+jw8Zr3VMolF*Faydc6}^8@9FF)}VQvNiv)@xrtcprg4H58YBxLt%TRcI=9A+&uH1m!rU5!nXdXGC~9l!*RS9 zxVqvd!KmRL;T1@?@bZvswG@^X5}kS_I&o|RJ()Z3MWS@jM3J3KSVmGs_TSTHmai%U zu5V%M{#Pt6(M%;_Kws(HQ@TJ=9C(mVzVA=!0Z^4pJ2PHh^w%A2Gnvx86QYC7H^Yv} zx<7Jy7NEN7^fnhApT+g)tRPD4ez)y22QBlmzJktok*q3amPy%+(eU8y)J*cE00&r!a%Z^!c0j~l9Kdo77e3Ty978=HAe-slB8 zRpE?_7)VVL}J=0ym7VxaZWKj-fmF!4I zgL6BItA9y&=22}1kAH^$gvqp$S;+_b@$VcfQ#R8;LP-yHv?b2%bF^ z8ob0Ej$_6bj!8-)|MuMDaMZMgSaEL;g{!R+{PlDB17AgjiC1}?G;U~<)Q#dO}zkUCSe)%^{(6NzeW#V z;{>I(G09AM@?^|8G@it>0lla*SK>qvJ$;(bX;P7PnPimMt^30P_1ODpoCKUvYD zFMx+$Z3G3ce3a;=<>tf7_YV!E{q(Fad-BtyaQTaaL(vjj*RKzAe?bv;Yrrk=ootPx4lts6tnb$z0Y0#dl6fJGyK`nA=&23QQ}GsYR+V$r@b>o zP@#J%l|G{Cr9Y+2NC6T4he@9NYtM`z^Ae|)Jgw(JFiFikc}aJPhk@jDj2FssGv|UK(mnRW&VVPok`QgNX%0GxqWXWgNz)O?-k8$77@eR%Q3^q}v$<9cJhJJB|!Lxbu+ zKqA0oS_MeT2}`XZ^*=(h9~QgsVdS6huOPu3jL+4EB+zR*saswi)c|Fe%AQ7%lbR_H zJpKt;G6!O#oiXZXcD7kLF0xXe&T4Jys`!iE;~4RCm~;hfD){jsl#D~923rp%DTlRIc z3Uuv{fxP9ex7+%A_ofDT0*9ab9<|O`ad;7$#OzdUmhDU>tCQ0&9tvMMuq;?|7~H=% z7;lMjI1gkz55%^KoKCeie!8HOqqUwn2T1=t03g52*9nla>uu*7rmZFbz@G(Z4aT*% zgj0&0{+%8rZn})JoZde4DBP>&U+^BaQxgOzVw-qr4VMsOB<+oU)9s~=&SyRfG2aG+ z9(P_LVf%I(X<^jKNxycJYeR_L-ERF}&-KMown4LRIWUU4NM6{8vDF9<*0EbG{f$M( zcY3vhF28K2Ez%|5(1sK(d=jGv&mV}Rf4i{u*-3cW%=YnQiAFyyo@YUc^j)&NGH){h z0KAS-_8LdUe8z9q+}?{Q^5|y-osv&jGaca>tFqv(vJ0`za)6u;A{SsY3_GUDI{f2V zh8Gm*FsT-fqJ+4(3zrpG?<}Aa*pf+CMOOCk#Fo;=_sAI}4CQ7sbm}u_1bQmuIYb4$ zXiaX>vQMJ_nZF0CpIv|tt>S{WDLdr{x=ogzYgM-^b(fY(NjFzB4P{rXUZ3iAlMQjI zC9ubYdEFk-UL2*}v4_z5*4#6mqn_uP2e~f0!C91tviMW8-+>k7UuYy}at>OO$ljI& zg*Pc(-vErpLPplN?>(}Q_ZAl7+8HO-qrM(p{7FkFEUA+ZCZOKJCMN_@!yj4CAOhwd)+kID;6U7N;WQ8h8jYW)zk=-8S-$#`D%| z{aAvM?hPQ%skAN${eH{cI3l9(R82>9^BXJHAkOeFw8P%N;KoLIh~AUVR9t z$tQH3GJ9?7WF4HVG)WL&K|iF83D6SCuxD-}CDXaV#RWy$s~6kZhqc*iRj#NmUa^>= zbV;15LV~e3C+&COm8t}$y=hSQ`7kGVyM`6c`$AF8m>SqSsJ{khFxloNI?Kf4U%_I| zcQ@cS+CGE^o&d)>no+)!$~od5rY#4U^5a|vM>bH)_!`5X=yJj5`e8j*xvA@YPla5! zR;8CtrYkIZNmPWFAn#MUVj70<_JP=wucAJeUFBxItKBSVU+;Ma!L=(!A=Jz)sV-{x z{Sk0HcqOTUw<-|Xv>=$4_v_bwlVzXZeF+5UafB*bflzqy`7yxU|GzcJIs)(F?E#&0&`0C1%IaJnU(h>1t72qn) z6TebhKLWC)U-Ky7gGbBk0NOd7#W#QZ101x)rPzp)ka@!xYyz$$UrEX4;Ae79R zX8hX=w92k+{?J)M`_p&b!aJtjDZ*u{gC;M2+k!dbbQ!sI=Eyp^Bk#m}EY~*-(!;VP zl#4JG^l8y~Ajh-pgEIt)8pdQbj^We&Zm36pKglf!J*yu6a30( z`9e8Z65;U^cMQjOIWy$S*B1TAwJw~%caESz#82T37Czv+cddTj+^Cl3ss6Acd+z+T z&Sg;eC_+I&Qj$TuycN;rj|6`!Fe=1Xx(j++^&XrI;YMwy&a0?N!)5v@!?3#X`UP~b zfPYYiRDo5EbOG_uMj)Z6W1HfdG0hlN?I*PN$56P4^={MU7MmMqsUJX9iE82Un~21W zlnylm1Vkf7;>NvKq4a^DpVMcugPnm|he8Yxp}gR1JchT%nQ<8yL#Ls#(=>|po+s8B z2PE;4bRuVu}(A$QVIK-xE;Al;p*v>JVvn#fsZq!-Ix&OVW98eLuy1vdK{c4A1q zJdSKXlKzfwMbIsZ^bp|V9qjqq!$VdS0+2wF_gupQgD0fy0!xeC56Oj+pJ6O3 z_6ZIkQUn9mQwPLmg_7$CwQqq|#gG8-tl%XOtFQk|gl)P8UEHUIF0v$qUT+XtHhTYj zCCTOYPE0cZP_H_wyWvo|q+mXBMHu2Gu*orS5tq83;3o<+vDQxNGyTNzn$TmL?+oJ) z>)}hxjBVRTkbG2L6^v#6xzzRz9Q>2}_BdCp_6WcfTup<(Qc5_mh`iq!IpO9JZu1_w z*XNyLA4z;bc*@u0j>}txrSXhW0>7;MeGkG7x|H~TgL6}yv#C%zQV5EV-n$Cxp(eU-fK@L^wwq0hOGo1^cfc1vM2#Ct5s(Djk!10xWncMUM)clHHWB3`9%>hi#l&1JQN z?{^(a;tIxXvi}a>Nfb6H6$n=hcnW<6+)I(z{@BF(*TrKRQ=+ZR`~4qw*Z-Ke6@_`$ zo7rGPsDT$Tl0dJwHk;@iE+A`!6Q zW1g4!uz|d#3qZ88jot1BV8w1DZP6sYxAVA0=YlX63BwlbOa0iCeIcb=4ayF5*k9;l zXd9$wIfhP!zJfwLp^4>c-!ny=S5qsG1~BJnO^qdy2c|SsI0`PIu1(`}XsNcz-^w&>E`<1gh_b!D2_jK;JbyFV@$ve2RE4_Re7+5_ zo-s244p5@@;knAh{a(ZM{)->xLLZaD%2sQ9*Z=AP)JTEyD1VlZSx1WK`7FDCq=CYI z_&8j3UJ1RmQ+{Hf03K_g(04)zJ=~q*B=r5t^j3ZD4XH8nCe2Z`XuZFmN|&J?5fgX5iBWoIQ56Oaw|X zo*RXV2?~=ATZfN%ZvI@@kjtF~#|Rfhr{`p(QSt_X{l9*03Iz|0m6Z8jr-GHL;BCRW z(Dq1E;iOrvv57}1v`nGSOxL67zichvmI3;7S;jZXe=2|58m)PEua7z3#g(694cuj1)%O4HrYtGO))|Te@|Mh=)Z|^h<}K^|M={!=_Dk zj!lG@P-${%s+v_zU4B;5>9gX6ji*Pw0;eYPa#KHNi#QS| zdY})WuOWj(-&$e;$xmIYPA3k~Mj7lKg``_}A|=Zett;{#GA3o@gb_d6G{|9VkT*Tg zpR_E2`UxW=-UAZBD3{^E?JsO4F@87O2HdS~Z27CXa3--IV=k%$cAS2-Wm`pOmtuE& zcejI8;ol%n*W4~KM{Lz*Q_U_8LOailOc}J{a;=# zgz^6HBerEL2qT^neUz*-*p4-^G11P*gjpfXNO=)!lqaCLKIilJNYW&F?hR&-5kwIw%&yZb;akpUk%#g9h5MyQGs5eS_ z;%GPR%E;oU+dCFqrH*GOse`0Oq1G!jzUM}yGQ)l`iacD{Fy?-+4$XF#9fr%OZ6tHP zSKY z8~qzQBR&z%a%5Z&qD1nR+aF(KZJI%d?OrJ9TCTldy=~WobGyfw`ke{mr2FX2c1(m- zCS?D&p`?ooOo^_7g06`4#6UtzxeUCF+{@dQl2ucr9%i(WL-g>9N%oG*gO1cKiXi4X}~spc5OdG9y1>Vi+-Ra8X8w zaYHfRxx9ssP_){R;gL5dz9cZQ?r<5No0*D@EwwBebvY#M$ZB+xGZ0xytVF!I(B9Zb zwh{QK$_nBEE;ISPanxZ0wi$NHgVBvTv7rZ-vHUdqP6(2RbB@Ps%GPWRc9w_Y_dkc( z0`~)>Aaj1E6+=}_1O%oopEGJ1U+}aj{Mspv{}LBu0i=5#7+dgkcf~+jQAJ}T9qe43JDI=y6A!qRS+-}DHI~NR7k+iEZ0flg74f+xS-Z`+j@>gPB=#tmnBeoHuk) zjQ)b#S*M5~e@*uXLWD5^K01OUNKySiZCDwZfm8{s(ytYi-=j=xdsQcdpN)$}L*$X0 zi&^1FjYu9h2uu+C1=0S6gEtH2aGGOovoXR`xWG7~vLK0TbduG6F*tY`Ix|jj|2yDT z(;EHM>${f?61+*h=IXIX5B*eKAVD=uVFq#o$-Cc7is4_9de7X*L-t+$3@b8Jt|sb; zliHnuP4A7g`t!P!L^p^nqlnEP_+}nXDd8zD8ah5L$9FoV$R+7t_?k~!Ze*nu?+wnL zwfOUj{G2^|kV@!HvdH%)pt&pxx}a25_`lbfsUDO+Ai9j+(ohF{wSi@a!eRDvN+nC& zJd1$nBe{C?e2CxuEzPd^P3$g)ztUM2H&%QN+d!1#y1z_O&fespmM;HBu2J%lX(3QdmH#A!1Z>eQiJERS;!~i!hG;Fj>>74N^q%L8 zGQ$LPy%j=A_XhS1Qo7q-GNaMZ7Zqgl99051J8PgX__NO! z9_4ku$=|HqVP6MwYV2Xo)Is(=e8Sp~nk5kUBC;)llE{QK*xOuO;AmJ0AaprIS0J&Q zW**!Bi{2#ldJTq_z3+pmcMGAbuOUz^UKK|(B0JnF=udl5gctGuTl9OTfnJ|96q!XF z1ex}rcq|C+UKbxKQp*V?7z{O!F?|xt=|*`4>4Ovz)(Ncy`)`<^4bC1V)lPo zub#){X|+uP#a7QFtc2{#jY=Oua5xO}JkufH>qC1pL2*d{QRv&6r^dH6w@zd9>`}%i zeR${@CdgN^>#6{aSITUhRpCsHPHanHXWKW^|5O?#%u#JM-~bP#{xtRKs}zpJ_jPK8 z-@1gIK?R$m$qMnAnqUk>!@syTQjgnmqO>sden_&56y$4ZKVbm>qX*>&l%Q0i#IFA@ zhL3QgDWutygaiDEW8l|VLJ86Fs!kmZ_VB#=U&;-f|9!|aLH}4)xHP|kYO?wzXN;3g z$jVFG01SL;TAoONa^wag8Q-IbG_@IRvcqeoML%{5aZw{q{-MRBXx$%D*q;JDF!g%v zA#r>c(Q$9LcS(mebnUA4v~S++ZgqE1z`^4IukrfdXu}ae%u2b9!Vn=c16grNpYQR~ z+RuAjNN6mcfM^10$wb+%tc@)2C3-HG-Awhc%j0()e^+NC&-W-2dqac`{l36Gju6)=a zEly)#dgJ_4h!`2{SW=dnCzAn64#vas>9C^DkEvr_IQU=o5iD@VmNC^)0<}LgvI~i2 zZlw|eUveuPadm5tV$KI{s#Ioie^O3XPFrzB|1att3X;|j=|8Pj;(jn-3k8ecJ+4b0 zWVE1}Am9*vgsKzY&;8uZb8t>ZMkFc0{!QCm=^O7G=q$~jRIID+)*Nn!xTOd`;vau3 zccV7-ze%d8e_z>b&G(Sj8+epPpSB7O{I!36%dW8(zAOd*Fpm5w$Ppi(7&`?j|Gk76 z*nv5aHX!eZ*eF%e#2KS;B%SKgUk+~8IoY6)_?{Qa^toK^`(M5eO+l#y^8CECFdrhd zb#}71HpT$|=;%EiEI$4E0%mXm6bcRef8OhUb^p%GC04Ct6mB;VqA*Ob_1wshBb?u< zn~S)({(N62k;>PJ;t73Hm?ys#jz^b_YJd}Tqzu1wWmsW*M8qZM8J*< zFQblv0*uH6BHe_Q|BPQ*N&pS6T#3cxNeKu>AJwTXcAmu!xxp)lWkHLhZR}02KSvtg z9IW}ao+kYK_uG7{4;uV0n{8s{-=u+ZshawUBI%bDfgJA}bExG3PI{vb?HmHPs(O4(F@yX9uJAovqB(tAO zR(6zraDQD^eYjGG&LO=jJH02zoca8}9rFJl>>h%ycsJt%*NQ53*ZYAW! zwB?C>bfbJ3+!nD6%AKK&Yj2L#S5ag-Uc0mR9(9(|gfhMQ`4U&xdW+cq@$CP%fIk|^ zp9KK5s;lfriS&hJUU5iphQD?1)dGkU2Sb_!(V&!+$P1Ujkr77`dKo*rYRlD@*>r9o zgnl$d|Kk_9>gvk=o(od#K>}JSNxEOIaI8!OJ?}9qLX+3KmVd2>+up%})X4lb+4^5K z$xBa94+>*#TTZAMP^RZZvE1a4AQ-=V`7%=!FeHi>f**whyP1)h*{Q)vGabb=T`vaW zCIH|k_E!ABV!qB!oJ;b%GWbr5AJL-=!YM}Rna;2n0xwjhZ)4+gIP7`EUaMyeP*b0P z5}kBn0wc$N|I{fEesQ-F7S`YfKp}es07xWI#5k5{*#StLl!8AWo_~M+zQS|fexE^# z@~?a@oy;iG;(`oAql+(I#`~}VDaQZeq+i!fz~kOj2O-n)9N+6q%v=nq*iaIKf#%XDc#MIo-C>|xX6q=h zC*_|Hvr8`feAZe?njaV6d-jj3MA!K;waE=srshR~PE@794xqvPozQc`fshFUoa=(W z^Y;I}l8ReFDJ0~H#D0=pdW90AxEQ?p^bG9o+-2*0jBo_v9cJ1&aOm~o|H99CKIkVTWx?bo$0{kSh0BA(8NEpVT)4bOH0L-;gImYoH&iI4` z$x7{Zpc{f5ZxF?!?BA{1djrOz$3Gro{5P?+z7%_H6`Fr|)U*#JmEbN~ZP||G zN=rBa)bUCI=a&WW%8LCk4WrE(w+d3dpSfVsq3T+mMC?CjZ#{y&dabhKsRU4leC!y# zo|o4X0KkJtf_H>uU~%C; z6LzF8NV@`IIavc?!5ZM6HP6m*Nr6gIoXqFbLHY$mC+)JJ7u@V{wKEu**7kBWTmwY0 z3QvjQatq9MhvFl}O*=+x00vCt#ArH~3V=hxp5FN(K`DV*F9QP2@K#fsH;503qpbtQ zl|wSO3Xw;LL(FOhkUr00k>Gyo^vt1LsE%1ww(l{^ z{{xLMsewEqc*Q@6(^JB;n7spXZCWU4J)3(QK#s9rG)f#Ke18yI^m%_xm)Jq}zJR=E zs^?PCv=af*TmzX@FyY%p@L1vzP+2M_4J{!v5ZsfY`6BZ~+q41kcp2qGU6}5i!W0jz zmQ9$El=n^$sO3w601V<;*#H-SNF+QZn#u*Hlot~B7a?(6wrZR$Lbkc7{Pq~iU_?6T z@vkllq>vqJYI($O07B0aj<>V4*tD?zJz#BeDvY@6ct7sSvTnTr;PKL+f;1+%+@st)Fk z4w-Pg#9e;~de(xBd4SZVN0i$u{m7AoJVB_Pn(3`@n@o(Kw+I4=F?n^XyK403dEEcN^(yIO4RMb zxy($-u{i#}L#d4WySp45;uwJM^EH%q@LAllcs3W~26%BedYeoJn-A4le}f#JH;s6S zSgHoFCLGg40H8(vpI39wWkjR~LIRl`0bMSPgtdQ1yPu;d(VTy(?3)wytV?KQ<=d~W zgXd}Nu%9uj?{7cLzCItYUTQ==bk@>3wFfKaR~(WDkMWp$h~{ASzrybvDyzh@y`sQs z0|c%4!jgp>r<}WAy#g$ZPyCZEp(&}V1)-rc!Lc6D4_Soh5*V+!Mcugtk(r=SV2Z&? zdtl-jBt2iO(hG=<;MKbMm}|-0LmI04Met1)5u4B)x&sR!!7<$`5>kVypBAeLm^VSy z*A`JPZ$_X%>dc*A9U8qsd+{?r3UNaij1 z-ph^zAD8w$x-wZl&P*VA;!PAOc5X4*u^8j9FMgbW*Jm|8^Og)Ct!zS297AzvB5Fl) zPO4_~`p`JU&nP>J{p>4CYdR(Fpa2%sm#kIYl8coRZJi;aZVQHDTq9yKmIV(?E4rxv zU3pj2(2>?IuHWBT!g#kEN97^C+JJA!b9t})BNmkCe7}+;#vYN#-By)znB4gm1WG}! zysTM1?|n{hYzj@RL^)T8aFixI?4;%r{o11z^j;qna9eI+0bV*Xja@J|d`Wbic)Gkbw?!-vVKeb6SA`H9aj zvI9fQ-f@{(ld*Lgr6}~nel?ZM6hI)|6Gx+XeOy#2DrtOsQ6$qG_)tf7d zD3^GVko$&$nNuh$)a!?=D7soqUxC$Yd{1662=c7ZR>Y_VskUe$g8f#|KV$m^&H-h? z<;@*r-s}73F_PJvQo{@2AX{UY!LmaD9Z`oJ+ZBzp;bZ95M;N?~l*Bhzp?9R zP4Zp5inbjn5h_AJyH2MlA=-`7ZrVEs9i;(TCnZkH3u3f4n^-PZjI)jA%gv`Ebn8wk z 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 literal 0 HcmV?d00001 diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/device.png b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/device.png new file mode 100644 index 0000000000000000000000000000000000000000..5caaa973acb18ce1e9f0f9b633435e454fcc2669 GIT binary patch literal 356234 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�U7~=`OM%DHy(gd8T|M4`3E2C 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%0A|kJEd_g@=Irpn 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

sG{6n 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 z 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 lR5bR})a^n@&mK~dDs~iRzp6nSx*d|*p`saH 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 literal 0 HcmV?d00001 diff --git a/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/time.png b/examples/bluetooth/ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/time.png new file mode 100644 index 0000000000000000000000000000000000000000..5f11ae49b41ee95f25fd42309209e5054744a709 GIT binary patch literal 56788 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%0AyH;JiAc495) zqZbX9I`3@GEzsE*$syH}Jjxrjn;5THjHWky^&Yuq$45FiHrAfnpJDsLcOyf75;9~l|Z(bWxHPKMO9^)3j#CU&+l>9)wMlxxME zu#Nk@jmiB;uboLv;Z)(S>I{~0cb`vGSmA z7B}j>zZ0{w+uK)YxMi_Khg%+#cYZ8bak~a z^H2sHSB|uQWF#~uCPqh3F9cCy7pf+E*Q|4CVRY2s)m08|ZmQnXCol7~!n!tf>yexM z;e*B|d={G+_glGDKHG!U@3fUer9^ar`Nmqw!9dOUt4vyE^WyiaXJFHq(RYxY^XZW{ z6dtl@TV5zpBFovSt$6Tv(ZD(n!hqyb{s(+9?9ry=5jtkL8i_|wb(Y=a?<#{2Xuvwt u;7}YftA2i;o2dV;`hSUQ_}}PqYJ8*oI56 Example Configuration --> This option facilitates sending with 20ms non-connectable interval...` + +For a better demonstration effect, an RGB LED can be soldered onto the ESP32-DevKitC board, by connecting their corresponding GPIO pins are GPIO\_NUM\_25, GPIO\_NUM\_26, GPIO\_NUM\_27. Then you need to select the following option in menuconfig: + `make menuconfig --> Example Configuration --> Board selection for BLE Mesh --> ESP-WROOM-32` + +Please check the [tutorial](tutorial/Ble_Mesh_Node_Example_Walkthrough.md) for more information about this example. diff --git a/examples/bluetooth/ble_mesh/ble_mesh_node/main/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_node/main/CMakeLists.txt new file mode 100644 index 0000000000..1eb2d87ed2 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_node/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCS "ble_mesh_demo_main.c" + "board.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/bluetooth/ble_mesh/ble_mesh_node/main/Kconfig.projbuild b/examples/bluetooth/ble_mesh/ble_mesh_node/main/Kconfig.projbuild new file mode 100644 index 0000000000..18776a7a39 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_node/main/Kconfig.projbuild @@ -0,0 +1,22 @@ +menu "Example Configuration" + + choice BLE_MESH_EXAMPLE_BOARD + prompt "Board selection for BLE Mesh" + default BLE_MESH_ESP_WROOM_32 + help + Select this option to choose the board for BLE Mesh. The default is ESP32-WROOM-32 + + config BLE_MESH_ESP_WROOM_32 + bool "ESP32-WROOM-32" + + config BLE_MESH_ESP_WROVER + bool "ESP32-WROVER" + endchoice + + config BLE_MESH_PATCH_FOR_SLAB_APP_1_1_0 + bool "Fix bug of Silicon Lab Android App v1.1.0 when reconnection will cause sequence number to recount from 0" + default y + help + It is an ad hoc solution and needs further modifications + +endmenu diff --git a/examples/bluetooth/ble_mesh/ble_mesh_node/main/ble_mesh_demo_main.c b/examples/bluetooth/ble_mesh/ble_mesh_node/main/ble_mesh_demo_main.c new file mode 100644 index 0000000000..0e0aeaf845 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_node/main/ble_mesh_demo_main.c @@ -0,0 +1,395 @@ +/* main.c - Application main entry point */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "esp_log.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_common_api.h" +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_config_model_api.h" + +#include "board.h" + +#define TAG "ble_mesh_node" + +#define CID_ESP 0x02E5 + +extern struct _led_state led_state[3]; + +static uint8_t dev_uuid[16] = { 0xdd, 0xdd }; + +static esp_ble_mesh_cfg_srv_t config_server = { + .relay = ESP_BLE_MESH_RELAY_DISABLED, + .beacon = ESP_BLE_MESH_BEACON_ENABLED, +#if defined(CONFIG_BLE_MESH_FRIEND) + .friend_state = ESP_BLE_MESH_FRIEND_ENABLED, +#else + .friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED, +#endif +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_ENABLED, +#else + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED, +#endif + .default_ttl = 7, + /* 3 transmissions with 20ms interval */ + .net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20), + .relay_retransmit = ESP_BLE_MESH_TRANSMIT(2, 20), +}; + +ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_pub, 2 + 1, ROLE_NODE); + +static esp_ble_mesh_model_op_t onoff_op[] = { + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET, 0), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, 2), + ESP_BLE_MESH_MODEL_OP(ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2), + /* Each model operation struct array must use this terminator + * as the end tag of the operation uint. */ + ESP_BLE_MESH_MODEL_OP_END, +}; + +static esp_ble_mesh_model_t root_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&config_server), + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, onoff_op, + &onoff_pub, &led_state[0]), +}; + +static esp_ble_mesh_model_t extend_model_0[] = { + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, onoff_op, + &onoff_pub, &led_state[1]), +}; + +static esp_ble_mesh_model_t extend_model_1[] = { + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, onoff_op, + &onoff_pub, &led_state[2]), +}; + +static esp_ble_mesh_elem_t elements[] = { + ESP_BLE_MESH_ELEMENT(0, root_models, ESP_BLE_MESH_MODEL_NONE), + ESP_BLE_MESH_ELEMENT(0, extend_model_0, ESP_BLE_MESH_MODEL_NONE), + ESP_BLE_MESH_ELEMENT(0, extend_model_1, ESP_BLE_MESH_MODEL_NONE), +}; + +static esp_ble_mesh_comp_t composition = { + .cid = CID_ESP, + .elements = elements, + .element_count = ARRAY_SIZE(elements), +}; + +/* Disable OOB security for SILabs Android app */ +static esp_ble_mesh_prov_t provision = { + .uuid = dev_uuid, +#if 0 + .output_size = 4, + .output_actions = ESP_BLE_MESH_DISPLAY_NUMBER, + .input_actions = ESP_BLE_MESH_PUSH, + .input_size = 4, +#else + .output_size = 0, + .output_actions = 0, +#endif +}; + +static int output_number(esp_ble_mesh_output_action_t action, uint32_t number) +{ + board_output_number(action, number); + return 0; +} + +static void prov_complete(uint16_t net_idx, uint16_t addr, uint8_t flags, uint32_t iv_index) +{ + ESP_LOGI(TAG, "net_idx: 0x%04x, addr: 0x%04x", net_idx, addr); + ESP_LOGI(TAG, "flags: 0x%02x, iv_index: 0x%08x", flags, iv_index); + board_prov_complete(); +} + +static void gen_onoff_get_handler(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t length, uint8_t *data) +{ + struct _led_state *led = (struct _led_state *)model->user_data; + uint8_t send_data; + esp_err_t err; + + ESP_LOGI(TAG, "%s, addr 0x%04x onoff 0x%02x", __func__, model->element->element_addr, led->current); + + send_data = led->current; + /* Send Generic OnOff Status as a response to Generic OnOff Get */ + err = esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(send_data), &send_data); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Generic OnOff Status failed", __func__); + return; + } +} + +static void gen_onoff_set_unack_handler(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t length, uint8_t *data) +{ + struct _led_state *led = (struct _led_state *)model->user_data; + uint8_t prev_onoff; + esp_err_t err; + + ESP_LOGI(TAG, "%s, addr 0x%02x onoff 0x%02x", __func__, model->element->element_addr, led->current); + + prev_onoff = led->previous; + led->current = data[0]; + + board_led_operation(led->pin, led->current); + + /* If Generic OnOff state is changed, and the publish address of Generic OnOff Server + * model is valid, Generic OnOff Status will be published. + */ + if (prev_onoff != led->current && model->pub->publish_addr != ESP_BLE_MESH_ADDR_UNASSIGNED) { + ESP_LOGI(TAG, "Publish previous 0x%02x current 0x%02x", prev_onoff, led->current); + err = esp_ble_mesh_model_publish(model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(led->current), &led->current, ROLE_NODE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Publish Generic OnOff Status failed", __func__); + return; + } + } +} + +static void gen_onoff_set_handler(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t length, uint8_t *data) +{ + struct _led_state *led = (struct _led_state *)model->user_data; + uint8_t prev_onoff, send_data; + esp_err_t err; + + ESP_LOGI(TAG, "%s, addr 0x%02x onoff 0x%02x", __func__, model->element->element_addr, led->current); + + prev_onoff = led->previous; + led->current = data[0]; + + board_led_operation(led->pin, led->current); + + send_data = led->current; + /* Send Generic OnOff Status as a response to Generic OnOff Get */ + err = esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(send_data), &send_data); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Generic OnOff Status failed", __func__); + return; + } + + /* If Generic OnOff state is changed, and the publish address of Generic OnOff Server + * model is valid, Generic OnOff Status will be published. + */ + if (prev_onoff != led->current && model->pub->publish_addr != ESP_BLE_MESH_ADDR_UNASSIGNED) { + ESP_LOGI(TAG, "Publish previous 0x%02x current 0x%02x", prev_onoff, led->current); + err = esp_ble_mesh_model_publish(model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, + sizeof(send_data), &send_data, ROLE_NODE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Publish Generic OnOff Status failed", __func__); + return; + } + } +} + +static char *esp_ble_mesh_prov_event_to_str(esp_ble_mesh_prov_cb_event_t event) +{ + switch (event) { + case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT: + return "ESP_BLE_MESH_PROV_REGISTER_COMP_EVT"; + case ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT: + return "ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT"; + case ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT: + return "ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT"; + case ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT: + return "ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT"; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT: + return "ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT"; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT: + return "ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT"; + case ESP_BLE_MESH_NODE_PROV_INPUT_EVT: + return "ESP_BLE_MESH_NODE_PROV_INPUT_EVT"; + case ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT: + return "ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT"; + case ESP_BLE_MESH_NODE_PROV_RESET_EVT: + return "ESP_BLE_MESH_NODE_PROV_RESET_EVT"; + default: + return "Invalid BLE Mesh provision event"; + } + + return NULL; +} + +static void esp_ble_mesh_prov_cb(esp_ble_mesh_prov_cb_event_t event, + esp_ble_mesh_prov_cb_param_t *param) +{ + ESP_LOGI(TAG, "%s, event = %s", __func__, esp_ble_mesh_prov_event_to_str(event)); + switch (event) { + case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROV_REGISTER_COMP_EVT, err_code %d", param->prov_register_comp.err_code); + break; + case ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT, err_code %d", param->node_prov_enable_comp.err_code); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT, bearer %s", + param->node_prov_link_open.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); + break; + case ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT, bearer %s", + param->node_prov_link_close.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); + break; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT: + output_number(param->node_prov_output_num.action, param->node_prov_output_num.number); + break; + case ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT: + break; + case ESP_BLE_MESH_NODE_PROV_INPUT_EVT: + break; + case ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT: + prov_complete(param->node_prov_complete.net_idx, param->node_prov_complete.addr, + param->node_prov_complete.flags, param->node_prov_complete.iv_index); + break; + case ESP_BLE_MESH_NODE_PROV_RESET_EVT: + break; + case ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT, err_code %d", param->node_set_unprov_dev_name_comp.err_code); + break; + default: + break; + } + return; +} + +static void esp_ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, + esp_ble_mesh_model_cb_param_t *param) +{ + switch (event) { + case ESP_BLE_MESH_MODEL_OPERATION_EVT: { + if (!param->model_operation.model || !param->model_operation.model->op || !param->model_operation.ctx) { + ESP_LOGE(TAG, "ESP_BLE_MESH_MODEL_OPERATION_EVT parameter is NULL"); + return; + } + switch (param->model_operation.opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET: + gen_onoff_get_handler(param->model_operation.model, param->model_operation.ctx, + param->model_operation.length, param->model_operation.msg); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: + gen_onoff_set_handler(param->model_operation.model, param->model_operation.ctx, + param->model_operation.length, param->model_operation.msg); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK: + gen_onoff_set_unack_handler(param->model_operation.model, param->model_operation.ctx, + param->model_operation.length, param->model_operation.msg); + break; + default: + break; + } + break; + } + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: + break; + case ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT: + break; + default: + break; + } +} + +static esp_err_t ble_mesh_init(void) +{ + int err = 0; + + memcpy(dev_uuid + 2, esp_bt_dev_get_address(), ESP_BD_ADDR_LEN); + + esp_ble_mesh_register_prov_callback(esp_ble_mesh_prov_cb); + esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb); + + err = esp_ble_mesh_init(&provision, &composition); + if (err) { + ESP_LOGE(TAG, "Initializing mesh failed (err %d)", err); + return err; + } + + esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT); + + ESP_LOGI(TAG, "BLE Mesh Node initialized"); + + board_led_operation(LED_G, LED_ON); + + return err; +} + +static esp_err_t bluetooth_init(void) +{ + esp_err_t ret; + + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "%s initialize controller failed", __func__); + return ret; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "%s enable controller failed", __func__); + return ret; + } + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(TAG, "%s init bluetooth failed", __func__); + return ret; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "%s enable bluetooth failed", __func__); + return ret; + } + + return ret; +} + +void app_main(void) +{ + int err; + + ESP_LOGI(TAG, "Initializing..."); + + board_init(); + + err = bluetooth_init(); + if (err) { + ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err); + return; + } + + /* Initialize the Bluetooth Mesh Subsystem */ + err = ble_mesh_init(); + if (err) { + ESP_LOGE(TAG, "Bluetooth mesh init failed (err %d)", err); + } +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_node/main/board.c b/examples/bluetooth/ble_mesh/ble_mesh_node/main/board.c new file mode 100644 index 0000000000..50035a7ddd --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_node/main/board.c @@ -0,0 +1,130 @@ +/* board.c - Board-specific hooks */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "driver/gpio.h" +#include "esp_log.h" +#include "board.h" +#include "esp_ble_mesh_provisioning_api.h" + +#define TAG "BOARD" + +#define INTR_FLAG_DEFAULT 0 + +static xQueueHandle s_evt_queue; + +struct _led_state led_state[3] = { + { LED_OFF, LED_OFF, LED_R, "red" }, + { LED_OFF, LED_OFF, LED_G, "green" }, + { LED_OFF, LED_OFF, LED_B, "blue" }, +}; + +void board_output_number(esp_ble_mesh_output_action_t action, uint32_t number) +{ + ESP_LOGI(TAG, "Board output number %d", number); +} + +void board_prov_complete(void) +{ + board_led_operation(LED_G, LED_OFF); +} + +void board_led_operation(uint8_t pin, uint8_t onoff) +{ + for (int i = 0; i < 3; i++) { + if (led_state[i].pin != pin) { + continue; + } + if (onoff == led_state[i].previous) { + ESP_LOGW(TAG, "led %s is already %s", + led_state[i].name, (onoff ? "on" : "off")); + return; + } + gpio_set_level(pin, onoff); + led_state[i].previous = onoff; + return; + } + + ESP_LOGE(TAG, "LED is not found!"); +} + +static void board_led_init(void) +{ + for (int i = 0; i < 3; i++) { + gpio_pad_select_gpio(led_state[i].pin); + gpio_set_direction(led_state[i].pin, GPIO_MODE_OUTPUT); + gpio_set_level(led_state[i].pin, LED_OFF); + led_state[i].previous = LED_OFF; + } +} + +static void IRAM_ATTR switch_isr_handler(void *arg) +{ + uint32_t gpio_num = (uint32_t) arg; + xQueueSendFromISR(s_evt_queue, &gpio_num, NULL); +} + +static void switch_key_init(uint32_t key) +{ + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_NEGEDGE; + io_conf.pin_bit_mask = 1 << key; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + io_conf.pull_down_en = 0; + gpio_config(&io_conf); + + gpio_set_intr_type(key, GPIO_INTR_NEGEDGE); + gpio_install_isr_service(INTR_FLAG_DEFAULT); + gpio_isr_handler_add(key, switch_isr_handler, (void *)key); +} + +static void switch_task_entry(void *arg) +{ + while (1) { + uint32_t io_num; + if (xQueueReceive(s_evt_queue, &io_num, portMAX_DELAY) == pdTRUE) { + uint8_t onoff = led_state[0].previous; + ESP_LOGI(TAG, "GPIO[%d] intr, val: %d", io_num, gpio_get_level(io_num)); + board_led_operation(LED_R, !onoff); + led_state[0].previous = !onoff; + //TODO: publish state change message + } + } +} + +static void switch_init(gpio_num_t gpio_num) +{ + s_evt_queue = xQueueCreate(3, sizeof(uint32_t)); + if (!s_evt_queue) { + return; + } + + BaseType_t ret = xTaskCreate(switch_task_entry, "switch", 4096, NULL, 4, NULL); + if (ret == pdFAIL) { + goto fail; + } + + switch_key_init(gpio_num); + return; + +fail: + vQueueDelete(s_evt_queue); +} + +void board_init(void) +{ + board_led_init(); + switch_init(GPIO_NUM_18); +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_node/main/board.h b/examples/bluetooth/ble_mesh/ble_mesh_node/main/board.h new file mode 100644 index 0000000000..4ae04b2ed4 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_node/main/board.h @@ -0,0 +1,42 @@ +/* board.h - Board-specific hooks */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _BOARD_H_ +#define _BOARD_H_ + +#include "driver/gpio.h" +#include "esp_ble_mesh_defs.h" + +#if defined(CONFIG_BLE_MESH_ESP_WROOM_32) +#define LED_R GPIO_NUM_25 +#define LED_G GPIO_NUM_26 +#define LED_B GPIO_NUM_27 +#elif defined(CONFIG_BLE_MESH_ESP_WROVER) +#define LED_R GPIO_NUM_0 +#define LED_G GPIO_NUM_2 +#define LED_B GPIO_NUM_4 +#endif + +#define LED_ON 1 +#define LED_OFF 0 + +struct _led_state { + uint8_t current; + uint8_t previous; + uint8_t pin; + char *name; +}; + +void board_output_number(esp_ble_mesh_output_action_t action, uint32_t number); + +void board_prov_complete(void); + +void board_led_operation(uint8_t pin, uint8_t onoff); + +void board_init(void); + +#endif diff --git a/examples/bluetooth/ble_mesh/ble_mesh_node/main/component.mk b/examples/bluetooth/ble_mesh/ble_mesh_node/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_node/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_node/sdkconfig.defaults b/examples/bluetooth/ble_mesh/ble_mesh_node/sdkconfig.defaults new file mode 100644 index 0000000000..565e251ebd --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_node/sdkconfig.defaults @@ -0,0 +1,43 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y +CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY= +CONFIG_BTDM_CONTROLLER_MODE_BTDM= +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_BLE_SCAN_DUPLICATE=y +CONFIG_SCAN_DUPLICATE_TYPE=2 +CONFIG_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_MESH_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BTDM_CONTROLLER_FULL_SCAN_SUPPORTED=y +CONFIG_GATTS_ENABLE=y +CONFIG_GATTS_SEND_SERVICE_CHANGE_MANUAL=y +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_HCI_5_0=y +CONFIG_BLE_MESH_USE_DUPLICATE_SCAN=y +CONFIG_BLE_MESH_NODE=y +CONFIG_BLE_MESH_PROV=y +CONFIG_BLE_MESH_NET_BUF_POOL_USAGE=y +CONFIG_BLE_MESH_PROXY=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_GATT_PROXY=y +CONFIG_BLE_MESH_NODE_ID_TIMEOUT=60 +CONFIG_BLE_MESH_PROXY_FILTER_SIZE=1 +CONFIG_BLE_MESH_SUBNET_COUNT=1 +CONFIG_BLE_MESH_APP_KEY_COUNT=1 +CONFIG_BLE_MESH_MODEL_KEY_COUNT=1 +CONFIG_BLE_MESH_MODEL_GROUP_COUNT=1 +CONFIG_BLE_MESH_LABEL_COUNT=1 +CONFIG_BLE_MESH_CRPL=10 +CONFIG_BLE_MESH_MSG_CACHE_SIZE=10 +CONFIG_BLE_MESH_ADV_BUF_COUNT=60 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=6 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=1 +CONFIG_BLE_MESH_RX_SDU_MAX=384 +CONFIG_BLE_MESH_TX_SEG_MAX=32 +CONFIG_BLE_MESH_RELAY=y +CONFIG_BLE_MESH_LOW_POWER= +CONFIG_BLE_MESH_FRIEND= +CONFIG_BLE_MESH_CFG_CLI=y +CONFIG_BTU_TASK_STACK_SIZE=4512 \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_node/tutorial/Ble_Mesh_Node_Example_Walkthrough.md b/examples/bluetooth/ble_mesh/ble_mesh_node/tutorial/Ble_Mesh_Node_Example_Walkthrough.md new file mode 100644 index 0000000000..639d10de3f --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_node/tutorial/Ble_Mesh_Node_Example_Walkthrough.md @@ -0,0 +1,471 @@ + +# ESP BLE Mesh Node demo + +## 1. Introduction + +ESP BLE Mesh is built on top of Zephyr BLE Mesh stack. ESP BLE Mesh nodes support: + +* Network provisioning +* Node control, including the use of PB\_GATT and PB\_ADV bearers +* Encryption +* Node features of Proxy, Relay, Low power and Friend + +This demo has only one element, in which the following two models are implemented: + +- **Configuration Server model**: The role of this model is mainly to configure Provisioner device’s AppKey and set up its relay function, TTL size, subscription, etc. +- **Generic OnOff Server model**: This model implements the most basic function of turning the lights on and off. + +## 2. Code Analysis + +### 2.1 Foler Structure + +The folder `ble_mesh_node` contains the following files and subfolders: + +``` +$ tree examples/bluetooth/ble_mesh/ble_mesh/ble_mesh_node +├── Makefile /* Compiling parameters for the demo */ +├── README.md /* Quick start guide */ +├── build +├── main /* Stores the `.c` and `.h` application code files for this demo */ +├── sdkconfig /* Current parameters of `make menuconfig` */ +├── sdkconfig.defaults /* Default parameters of `make menuconfig` */ +├── sdkconfig.old /* Previously saved parameters of `make menuconfig` */ +└── tutorial /* More in-depth information about the demo */ +``` + +The contents of the `main` subfolder are as follows: + +``` +main +├── Kconfig.projbuild +├── ble_mesh_demo_main.c /* main application codes, more info below */ +├── board.c /* Codes for implementation +├── board.h of the RGB LED driver */ +└── component.mk +``` + +- `ble_mesh_demo_main.c`: contains the following main application codes, which are needed to implement the BLE Mesh demo + - Initialize Bluetooth Controller stack and Host stack (bluedroid) + - Initialize BLE Mesh stack + - Register the callback function of BLE Mesh provision and BLE Mesh model + - Implement and initialize BLE Mesh element + - Implement and initialize BLE Mesh Configuration Server model and Generic OnOff Server model + - Function as BLE Mesh Configuration Server Model Get Opcode and BLE Mesh Configuration Server Model Set Opcode + - Declare and define the RGB LED structure. + +### 2.2 Code Analysis of BLE Mesh Node demo + +For better understanding of the demo implementation, this section provides a detailed analysis of the codes in the file `ble_mesh_demo_main.c`. + +#### 2.2.1 Initializing and Enabling BLE Mesh + +When ESP32 system initialization is completed, `app_main` is called. The code block below demonstrates the implementation of the functions in `app_main`. + +```c +void app_main(void) +{ + int err; + + ESP_LOGI(TAG, "Initializing..."); + + board_init(); + + err = bluetooth_init(); + + if (err) { + ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err); + return; + } + + /* Initializes the Bluetooth Mesh Subsystem */ + err = ble_mesh_init(); + if (err) { + ESP_LOGE(TAG, "Bluetooth mesh init failed (err %d)", err); + } +} +``` + +In particular, the code includes: + +- `err = bluetooth_init()`: initialization related to the Bluetooth protocol stack (including Controller and Host) +- `err = ble_mesh_init()`: initialization related to BLE Mesh + +Further, the code for initialization of the BLE Mesh protocol stack is introduced, together with the description of the required actions to initialize BLE Mesh. + +```c +static esp_err_t ble_mesh_init(void) +{ + int err = 0; + + memcpy(dev_uuid + 2, esp_bt_dev_get_address(), ESP_BD_ADDR_LEN); + + // See comment 1 + esp_ble_mesh_register_prov_callback(esp_ble_mesh_prov_cb); + esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb); + + err = esp_ble_mesh_init(&provision, &composition); + if (err) { + ESP_LOGE(TAG, "Initializing mesh failed (err %d)", err); + return err; + } + + esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT); + + ESP_LOGI(TAG, "BLE Mesh Node initialized"); + + board_led_operation(LED_G, LED_ON); + + return err; +} +``` + +The code includes the following: + +- `esp_ble_mesh_register_prov_callback(esp_ble_mesh_prov_cb)`: registers the provisioning callback function in the BLE Mesh stack. This callback function gets executed during the BLE Mesh network configuration process. It allows the BLE Mesh stack to generate events and notify the application layer about important network configuration processes. This callback function mainly implements the following events: + - `ESP_BLE_MESH_PROVISION_REG_EVT`: Generated when the BLE Mesh initialization process is completed after calling the API function `esp_ble_mesh_init`. It returns the initialization status of the BLE Mesh application. + - `ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT`: Generated when a Provisioner and an unprovisioned device establish a link. + - `ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT`: Generated to notify the application layer that a link has been broken after BLE Mesh bottom-layer protocol sends or receives the message `The Link Broken`. + - `ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT`: Received by the application layer if during the configuration process `output_actions` is set as `ESP_BLE_MESH_DISPLAY_NUMBER`, and the target peer `input_actions` is set as `ESP_BLE_MESH_ENTER_NUMBER`. + - `ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT`: Received by the application layer if during the configuration process `output_actions` is set as `ESP_BLE_MESH_DISPLAY_STRING`, and the target peer `input_actions` is set as `ESP_BLE_MESH_ENTER_STRING`. + - `ESP_BLE_MESH_NODE_PROV_INPUT_EVT`: Received by the application layer if during the configuration process `input_actions` is set as anything but `ESP_BLE_MESH_NO_INPUT`. + - `ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT`: Received by the application layer when the provisioning is completed. + - `ESP_BLE_MESH_NODE_PROV_RESET_EVT`: Received by the application layer when the network reset is completed. + +- `esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb)`: registers the model operation callback function. This callback function is used when the target peer operates the model state of the source peer after BLE Mesh has completed network configuration. This callback function mainly implements the following events: + - `ESP_BLE_MESH_MODEL_OPERATION_EVT`: Can be triggered by the two scenarios below: + - Server model receives `Get Status` or `Set Status` from Client model. + - Client model receives `Status state` from Server model. + - `ESP_BLE_MESH_MODEL_SEND_COMP_EVT`: Generated after the Server model sends `Status state` by calling the API function `esp_ble_mesh_server_model_send_msg`. + - `ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT`: Generated after the application has completed calling the API `esp_ble_mesh_model_publish_msg` to publish messages + - `ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT`: Generated when the Client model calls the API function `esp_ble_mesh_client_model_send_msg`, but fails to receive ACK from the target peer due to timeout + - `ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT`: Generated after the application sets up the publish function to regularly send messages to the target peer. + +- `esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT)`: enables the Advertising and Scan functions when the BLE Mesh initialization is completed. It makes the devices visible to Provisioners for network provisioning. +- `board_led_operation(LED_G, LED_ON)`: initializes the RGB LED. + +At this point, initialization and enabling of BLE Mesh as a node port is completed, which means a Provisioner can identify devices for network provisioning and data transmission. + +#### 2.2.2 Implementation of BLE Mesh Element Structure + +The section above shows how to initialize BLE Mesh as a node port. You may still have the following questions: + +- What else needs to be done before initialization? +- How to add an element and a model to ESP BLE Mesh stack? +- How to choose a different encryption approach? +- How to declare the features of Proxy, Relay, Low Power and Friend? + +This section provides the answers to these questions. + +First of all, before calling the API `esp_ble_mesh_init` to initialize BLE Mesh, an element and a model need to be declared and defined. + +The code block below shows the declaration of an element structure. + +```c + +/*!< Abstraction that describes a BLE Mesh Element. + This structure is associated with bt_mesh_elem in mesh_access.h */ +typedef struct { + /* Element Address, it is assigned during provisioning. */ + uint16_t element_addr; + + /* Location Descriptor (GATT Bluetooth Namespace Descriptors) */ + const uint16_t location; + + /* Model count */ + const uint8_t sig_model_count; + const uint8_t vnd_model_count; + + /* Models */ + esp_ble_mesh_model_t *sig_models; + esp_ble_mesh_model_t *vnd_models; +} esp_ble_mesh_elem_t; +``` + +The next code block shows the definition of an element structure, which only requires to call the macro `ESP_BLE_MESH_ELEMENT`. + +```c +static esp_ble_mesh_elem_t elements[] = { + ESP_BLE_MESH_ELEMENT(0, root_models, ESP_BLE_MESH_MODEL_NONE), +}; +``` +Another code block provides the codes needed to implement the macro `ESP_BLE_MESH_ELEMENT`. + +```c +#define ESP_BLE_MESH_ELEMENT(_loc, _mods, _vnd_mods) \ +{ \ + .location = (_loc), \ + .sig_model_count = ARRAY_SIZE(_mods), \ + .sig_models = (_mods), \ + .vnd_model_count = ARRAY_SIZE(_vnd_mods), \ + .vnd_models = (_vnd_mods), \ +} + +``` + +The variables of the element structure are as follows: + +- `addr`: stores the element primary address, used by Mesh Stack during the configuration process. It can be ignored for the higher level applications. +- `loc`: location descriptor defined by SIG. For this demo, set its value to `0`. +- `model_count`: number of SIG models supported in this element. +- `vnd_model_count`: number of the Vendor model supported in this element. +- `models`: pointer to the SIG Models that have already been defined. +- `vnd_models`: pointer to the Vendor Model that has already been defined. + +
Note: the SIG Model count and the Vendor Model count work separately. For example, if two SIG Models and one Vendor model are supported in an element, the variables would be model_count = 2, vnd_model_count = 1.
+ + +If a defined element does not support the Vendor model, the third parameter (the last one) of the macro `ESP_BLE_MESH_ELEMENT` should be set to `ESP_BLE_MESH_MODEL_NODE`. Likewise, if the SIG Model is not supported, the second parameter should be set to `ESP_BLE_MESH_MODEL_NODE`. + +#### 2.2.3 Implementation of BLE Mesh Model Structure + +The preceding section has introduced the specific ways to implement and define an element by passing specific model pointers to it. This section explains how to implement and define a Model structure, which is shown in the code blocks below. + +```c +/** Abstraction that describes a Mesh Model instance. + * This structure is associated with bt_mesh_model in mesh_access.h + */ +struct esp_ble_mesh_model { + /* Model ID */ + union { + const uint16_t model_id; + struct { + uint16_t company_id; + uint16_t model_id; + } vnd; + }; + + /* The Element to which this Model belongs */ + esp_ble_mesh_elem_t *element; + + /* Model Publication */ + esp_ble_mesh_model_pub_t *const pub; + + /* AppKey List */ + uint16_t keys[CONFIG_BLE_MESH_MODEL_KEY_COUNT]; + + /* Subscription List (group or virtual addresses) */ + uint16_t groups[CONFIG_BLE_MESH_MODEL_GROUP_COUNT]; + + /* Model operation context */ + esp_ble_mesh_model_op_t *op; + + /* Model-specific user data */ + void *user_data; +}; +``` + +The block above shows a specific implementation of the model structure. Although this structure has many variables, only the following four ones are used for applications: + +- `id` and `vnd`: union variables, defining the SIG Model and the Vendor Model respectively. +- `op`: structure with a set of variables for the Model Operation, declaring the opcode that corresponds to Get, Set, or Status State, as well as the minimum value lengths that are supported in this module. +- `pub`: structure that needs to be defined if the Model structure supports the Publish function. +- `user_data`: optional variable for storing the application layer data. + +The other structures and variables (keys, group, element) get their values through the BLE Mesh stack during the initialization or configuration stages. You are not reqiured to initialize them. + +The next code block presents the definition of the model structure, and the `root_models[]` array. This array is used for indicating the number of the existing model structures. A model is implemented by using a macro. + +```c + +static esp_ble_mesh_model_t root_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&config_server), + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, onoff_op, + &onoff_pub, &led_state[0]), +}; +``` + +Different models require different macros. The exisitng types of models and their respective macros needed for implementation are given in the table below. + +| | Model Name | Macro Required for its Definition | +| --------------- | ---- | ----------------------------- | +| **SIG Models Implemented in ESP32 BLE Mesh Stack** | Configuration Server Model | `ESP_BLE_MESH_MODEL_CFG_SRV` | +| | Configuration Client Model | `ESP_BLE_MESH_MODEL_CFG_CLI` | +| | Generic OnOff Client Model | `ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI` | +| | Generic Level Client Model | `ESP_BLE_MESH_MODEL_GEN_LEVEL_CLI` | +| | Generic Default Transition Time Client Model | `ESP_BLE_MESH_MODEL_GEN_DEF_TRANS_TIME_CLI` | +| | Generic Power OnOff Client Model | `ESP_BLE_MESH_MODEL_GEN_POWER_ONOFF_CLI` | +| | Generic Power Level Client Model | `ESP_BLE_MESH_MODEL_GEN_POWER_LEVEL_CLI` | +| | Generic Battery Client Model | `ESP_BLE_MESH_MODEL_GEN_BATTERY_CLI` | +| | Generic Location Client Model | `ESP_BLE_MESH_MODEL_GEN_LOCATION_CLI` | +| | Generic Property Client Model | `ESP_BLE_MESH_MODEL_GEN_PROPERTY_CLI` | +| | Light Lightness Client Model | `ESP_BLE_MESH_MODEL_LIGHT_LIGHTNESS_CLI` | +| | Light CTL Client Model | `ESP_BLE_MESH_MODEL_LIGHT_CTL_CLI` | +| | Light HSL Client Model | `ESP_BLE_MESH_MODEL_LIGHT_HSL_CLI` | +| | Sensor Client Model | `ESP_BLE_MESH_MODEL_SENSOR_CLI` | +| | Scene Client Model | `ESP_BLE_MESH_MODEL_SCENE_CLI` | +| **SIG Models Not Implemented in ESP32 BLE Mesh Stack** | - | `ESP_BLE_MESH_SIG_MODEL` | +| **Vendor Models** | - | `ESP_BLE_MESH_VENDOR_MODEL` | + +Another important structure in a model is `esp_ble_mesh_model_op_t *op` pointers. These structures point to the operation structure that defines the Model state. Generally, there are two types of models in BLE Mesh: + +- Server Model: + - Consists of one or multiple states that can exist across different elements + - Defines the messages sent/received by the model, along with the element's behavior. + - Example:On/Off switch --- Indicates the On/Off status. +- Client Model: + - Defines the messages used by the client to request, change or use the relevant state of the server. + - Example:On/Off switch --- Indicates the On or Off message sent by the Client. + +Operation structure defines the state value supported by a model. A specific operation structure is given below. + +The following code block shows the declaration of the Model operation structure. + +```c +/*!< Model operation context. + This structure is associated with bt_mesh_model_op in mesh_access.h */ +typedef struct { + const uint32_t opcode; /* Opcode encoded with the ESP_BLE_MESH_MODEL_OP_* macro */ + const size_t min_len; /* Minimum required message length */ + esp_ble_mesh_cb_t param_cb; /* The callback is only used for the BLE Mesh stack, not for the app layer. */ +} esp_ble_mesh_model_op_t; +``` + +There are three variables in the declaration of the operation structure: + +- `opcode`: opcode corresponding to a state. As specified in BLE Mesh, the SIG Model opcode should be 1~2 bytes, and the Vendor Model opcode should be 3 bytes. +- `min_len`: min length of the messages received by the state. For example, OnOff Get state is 0 bytes, and OnOff Set State is 2 bytes. +- `param_cb`: used for the BLE Mesh protocol only. Applications need to set its value to `0`. + +The block below shows the operation structure array defined by the OnOff Server in this demo. + +```c +static esp_ble_mesh_model_op_t onoff_op[] = { + { ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET, 0, 0}, + { ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, 2, 0}, + { ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2, 0}, + /* Each model operation struct array must use this terminator + * as the end tag of the operation uint. */ + ESP_BLE_MESH_MODEL_OP_END, +}; +``` + +It presents the opcodes, corresponding to the three states, defined in this demo: OnOff Get, OnOff Set, and OnOff Set Unack. + +
Note: for the Server Model, the corresponding request state of the Client Model needs to be written in the operation definition. For example, if the Generic OnOff Server Model is to be implemented in this demo, the following three states requested by the Generic OnOff Client Model need to be written in the operation definition: OnOff Get, OnOff Set, and OnOff Set Unack. Likewise, for the Client Model, the corresponding State of the messages received by the Server needs to be written in the operation definition.
+ +#### 2.2.4. Encryption and Authentication of BLE Mesh + +In the project developing process, different security levels are required. BLE Mesh offers various kinds of encryption and authentication features which can be split into two categories - Input and Output. + +The classification of the Input features is given in the table below. + +| Feature Name | Action Supported | +| ------------ | ------------------ | +| `ESP_BLE_MESH_NO_INPUT` | Input is not supported by device's IO | +| `ESP_BLE_MESH_PUSH` | PUSH | +| `ESP_BLE_MESH_TWIST` | TWIST | +| `ESP_BLE_MESH_ENTER_NUMBER` | ENTER NUMBER | +| `ESP_BLE_MESH_ENTER_STRING` | ENTER STRING | + +The classification of the Output features is given in the table below. + +| Feature Name | Action Supported | +| ------------ | ------------------ | +| `ESP_BLE_MESH_NO_OUTPUT ` | Output is not supported by device's IO | +| `ESP_BLE_MESH_BLINK ` | BLINK | +| `ESP_BLE_MESH_BEEP ` | BEEP | +| `ESP_BLE_MESH_VIBRATE ` | VIBRATE | +| `ESP_BLE_MESH_DISPLAY_NUMBER ` | DISPLAY NUMBER | +| `ESP_BLE_MESH_DISPLAY_STRING ` | DISPLAY STRING | + +The above Input and Output categories in the declaration of the structure `esp_ble_mesh_prov_t` can be defined by the following four variables: + +- `output_size` +- `output_actions` +- `input_actions` +- `input_size` + +These variables should be set to `0` for this demo, as it uses the most basic authentication features. + +## 3. Configuration of BLE Mesh Menuconfig + +To be functional across different applications, the BLE Mesh menuconfig is specifically designed to offer a variety of configuration options, which can be helpful in tailoring your own configuration. + +The list of configuration options in BLE Mesh menuconfig is stored in `Component config` ---> `[]Bluetooth Mesh support` and can be accessed with the command `make menuconfig`. This configuration option list is shown below. + +``` +—— Bluetooth Mesh support +[*] Suppoft for BLE Mesh Node +[ ] Support for BLE lqesh Provisioner +[*] Provisiosing support using the advertising bearer (PB-ADV) +[*] net buffer pool usage +[*] Provisioning support using GATT (PB-GATT) +[*] GATT Proxy Service +(60) Node Identity advertising timeout +(1) Maximum number of filter entries per Proxy Client +[*] Perform self-tests +[*] Test the IV Update Procedure +(1) Maximum number of mesh subnets per network +(1) Maximum number of application keys per network +(1) Maximum number of application keys per model +(1) Maximum number of group address subscriptions per model +(1) Maximum number of Label UUIDs used for virtual Addresses +(10) Maximum capacity of the replay protection list +(10) Network message cache size +(20) Number of advertising buffers +(6) Maximum number of simultaneous outgoing segmented messages +(1) Maximum number of simultaneous incoming segmented messages +(384) Maximum incoming Upper Transport Access PDU length +[*] Relay support +[ ] Support for Low Power features +[ ] Support for acting as a Friend Node +[*] Support for Configuration Client Model +[*] Support for Health Client Model +[ ] Support for Generic OnOff Client Model +[ ] Support for Generic Level Client Model +[ ] Support for Generic Default Transition Time Client Model +[ ] Support for Generic Power OnOff Client Model +[ ] Support for Generic Power Level Client Model +[ ] Support for Generic Battery Client Model +[ ] Support for Generic Location Client Model +[ ] Support for Generic Property Client Model +[ ] Support for Sensor Client Model +[ ] Support for Scene Client Model +[ ] Support for Light Lightness Client Model +[ ] Support for Light CTL Client Model +[ ] Support for Light HSL Client Model +[ ] Enable Bluetooth Mesh shell +``` + +The detailed information about the roles and functions of these options is presented below. + +- **Support for BLE Mesh Node**: Indicates if the role of a node is supported. There are two roles in BLE Mesh: a Provisioner and a node. In this demo only a node is supported. +- **Support for BLE Mesh Provisioner**: Indicates if the role of a Provisioner is supported. +- **Provisioning support using the advertising bearer (PB-ADV)**: Indicates if the bearer PB-ADV is supported for network provisioning. In BLE Mesh,the bearers PB-ADV and PB-GATT are supported for network provisioning. This demo supports both of them. +- **net buffer pool usage**: When enabled, BLE Mesh monitors the usage of the Adv buffer. +- **Provisioning support using GATT (PB-GATT)**: Indicates if the PB-GATT bearer is supported for network provisioning. +- **GATT Proxy Service**: Indicates if the GATT proxy service is supported. + - **Node Identity advertising timeout**: Indicates the time (in seconds) after which advertising stops. This value gets assigned by BLE Mesh protocol stack when using the GATT proxy service. +- **Maximum number of filter entries per Proxy Client**: Used to configure the maximum number of filtered addresses. To reduce the number of Network PDUs exchanges between a Proxy Client and a Proxy Server, a proxy filter can be used. The output filter of the network interface instantiated by the Proxy Server can be configured by the Proxy Client. This allows the Proxy Client to explicitly request to receive only mesh messages with certain destination addresses. For example, a Proxy Client that is subscribed to a group address may want to only receive packets addressed to the unicast address of one of its elements and to that group address. Thus, the Proxy Client has full control over the packets it receives using the Proxy protocol. +- **Perform self-tests**: +- **Test the IV Update Procedure**: +- **Maximum number of mesh subnets per network**: Indicates the maximum number of the subnets supported in a BLE Mesh network. +- **Maximum number of application keys per network**: Indicates the maximum number of AppKeys supported in a BLE Mesh network. +- **Maximum number of application keys per model**: Indicates the maximum number of AppKeys bound in each Model. +- **Maximum number of group address subscriptions per model**: Indicates the maximum number of group address subscriptions supported in each model in BLE Mesh. +- **Maximum number of Label UUIDs used for Virtual Addresses**: Indicates the maximum number of Label UUIDs supported in BLE Mesh. +- **Maximum capacity of the replay protection list**: Indicates what the name suggests. +- **Network message cache size**: Configures the size of the cache, which is used to store the forwarded messages to avoid multiple forwarding in BLE Mesh. +- **Number of advertising buffers**: Indicates what the name suggests. +- **Maximum number of simultaneous outgoing segmented messages**: Indicates what the name suggests. +- **Maximum number of simultaneous incoming segmented messages**: Indicates what the name suggests. +- **Maximum incoming Upper Transport Access PDU length**: Indicates that the access layer can receive the maximum length of a complete packet. +- **Relay support**: Indicates if the Relay feature is supported. +- **Support for Low Power features**: Indicates if the Low Power features are supported. +- **Support for acting as a Friend Node**: Indicates if the Friend feature is supported. +- **Support for Configuration Client Model**: Indicates if the Configuration Client model is supported. +- **Support for Health Client Model**: Indicates if the given model is supported. +- **Support for Generic OnOff Client Model**: Indicates if the given model is supported. +- **Support for Generic Level Client Model**: Indicates if the given model is supported. +- **Support for Generic Default Transition Time Client Model**: Indicates if the given model is supported. +- **Support for Generic Power Onoff Client Model**: Indicates if the given model is supported. +- **Support for Generic Power Level Client Model**: Indicates if the given model is supported. +- **Support for Generic Battery Client Model**: Indicates if the given model is supported. +- **Support for Generic Location Client Model**: Indicates if the given model is supported. +- **Support for Generic Property Client Model**: Indicates if the given model is supported. +- **Support for Sensor Client Model**: Indicates if the given model is supported. +- **Support for Scene Client Model**: Indicates if the given model is supported. +- **Support for Light Lightness Client Model**: Indicates if the given model is supported. +- **Support for Light CTL Client Model**: Indicates if the given model is supported. +- **Support for Light HSL Client Model**: Indicates if the given model is supported. +- **Enable Bluetooth Mesh shell**: \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_provisioner/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/CMakeLists.txt new file mode 100644 index 0000000000..dfffbf786f --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_mesh_provisioner) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_provisioner/Makefile b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/Makefile new file mode 100644 index 0000000000..bceab36a3e --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_mesh_provisioner + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/ble_mesh/ble_mesh_provisioner/README.md b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/README.md new file mode 100644 index 0000000000..2ccf84fd30 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/README.md @@ -0,0 +1,6 @@ +ESP BLE Mesh Provisioner demo +================================ + +This demo shows how a BLE Mesh device can function as a provisioner. + +Please check the [tutorial](tutorial/Ble_Mesh_Provisioner_Example_Walkthrough.md) for more information about this example. \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/CMakeLists.txt new file mode 100644 index 0000000000..3d3bc6f9a5 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(COMPONENT_SRCS "ble_mesh_demo_main.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/ble_mesh_demo_main.c b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/ble_mesh_demo_main.c new file mode 100644 index 0000000000..7f3ddeb405 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/ble_mesh_demo_main.c @@ -0,0 +1,691 @@ +/* main.c - Application main entry point */ + +/* + * Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "esp_log.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_common_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_config_model_api.h" +#include "esp_ble_mesh_generic_model_api.h" + +#define TAG "ble_mesh_provisioner" + +#define LED_OFF 0x0 +#define LED_ON 0x1 + +#define CID_ESP 0x02E5 +#define CID_NVAL 0xFFFF + +#define PROV_OWN_ADDR 0x0001 + +#define MSG_SEND_TTL 3 +#define MSG_SEND_REL false +#define MSG_TIMEOUT 0 +#define MSG_ROLE ROLE_PROVISIONER + +#define COMP_DATA_PAGE_0 0x00 + +#define APP_KEY_IDX 0x0000 +#define APP_KEY_OCTET 0x12 + +static uint8_t dev_uuid[16]; + +typedef struct { + uint8_t uuid[16]; + uint16_t unicast; + uint8_t elem_num; + uint8_t onoff; +} esp_ble_mesh_node_info_t; + +static esp_ble_mesh_node_info_t nodes[CONFIG_BLE_MESH_MAX_PROV_NODES] = { + [0 ... (CONFIG_BLE_MESH_MAX_PROV_NODES - 1)] = { + .unicast = ESP_BLE_MESH_ADDR_UNASSIGNED, + .elem_num = 0, + .onoff = LED_OFF, + } +}; + +static struct esp_ble_mesh_key { + uint16_t net_idx; + uint16_t app_idx; + uint8_t app_key[16]; +} prov_key; + +static esp_ble_mesh_client_t config_client; +static esp_ble_mesh_client_t onoff_client; + +static esp_ble_mesh_cfg_srv_t config_server = { + .relay = ESP_BLE_MESH_RELAY_DISABLED, + .beacon = ESP_BLE_MESH_BEACON_ENABLED, +#if defined(CONFIG_BLE_MESH_FRIEND) + .friend_state = ESP_BLE_MESH_FRIEND_ENABLED, +#else + .friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED, +#endif +#if defined(CONFIG_BLE_MESH_GATT_PROXY) + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_ENABLED, +#else + .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED, +#endif + .default_ttl = 7, + /* 3 transmissions with 20ms interval */ + .net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20), + .relay_retransmit = ESP_BLE_MESH_TRANSMIT(2, 20), +}; + +static esp_ble_mesh_model_t root_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&config_server), + ESP_BLE_MESH_MODEL_CFG_CLI(&config_client), + ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(NULL, &onoff_client), +}; + +static esp_ble_mesh_elem_t elements[] = { + ESP_BLE_MESH_ELEMENT(0, root_models, ESP_BLE_MESH_MODEL_NONE), +}; + +static esp_ble_mesh_comp_t composition = { + .cid = CID_ESP, + .elements = elements, + .element_count = ARRAY_SIZE(elements), +}; + +static esp_ble_mesh_prov_t provision = { + .prov_uuid = dev_uuid, + .prov_unicast_addr = PROV_OWN_ADDR, + .prov_start_address = 0x0005, + .prov_attention = 0x00, + .prov_algorithm = 0x00, + .prov_pub_key_oob = 0x00, + .prov_static_oob_val = NULL, + .prov_static_oob_len = 0x00, + .flags = 0x00, + .iv_index = 0x00, +}; + +static esp_err_t esp_ble_mesh_store_node_info(const uint8_t uuid[16], uint16_t unicast, + uint8_t elem_num, uint8_t onoff_state) +{ + int i; + + if (!uuid || !ESP_BLE_MESH_ADDR_IS_UNICAST(unicast)) { + return ESP_ERR_INVALID_ARG; + } + + /* Judge if the device has been provisioned before */ + for (i = 0; i < ARRAY_SIZE(nodes); i++) { + if (!memcmp(nodes[i].uuid, uuid, 16)) { + ESP_LOGW(TAG, "%s: reprovisioned device 0x%04x", __func__, unicast); + nodes[i].unicast = unicast; + nodes[i].elem_num = elem_num; + nodes[i].onoff = onoff_state; + return ESP_OK; + } + } + + for (i = 0; i < ARRAY_SIZE(nodes); i++) { + if (nodes[i].unicast == ESP_BLE_MESH_ADDR_UNASSIGNED) { + memcpy(nodes[i].uuid, uuid, 16); + nodes[i].unicast = unicast; + nodes[i].elem_num = elem_num; + nodes[i].onoff = onoff_state; + return ESP_OK; + } + } + + return ESP_FAIL; +} + +static esp_ble_mesh_node_info_t *esp_ble_mesh_get_node_info(uint16_t unicast) +{ + int i; + + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(unicast)) { + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(nodes); i++) { + if (nodes[i].unicast <= unicast && + nodes[i].unicast + nodes[i].elem_num > unicast) { + return &nodes[i]; + } + } + + return NULL; +} + +static esp_err_t esp_ble_mesh_set_msg_common(esp_ble_mesh_client_common_param_t *common, + esp_ble_mesh_node_info_t *node, + esp_ble_mesh_model_t *model, uint32_t opcode) +{ + if (!common || !node || !model) { + return ESP_ERR_INVALID_ARG; + } + + common->opcode = opcode; + common->model = model; + common->ctx.net_idx = prov_key.net_idx; + common->ctx.app_idx = prov_key.app_idx; + common->ctx.addr = node->unicast; + common->ctx.send_ttl = MSG_SEND_TTL; + common->ctx.send_rel = MSG_SEND_REL; + common->msg_timeout = MSG_TIMEOUT; + common->msg_role = MSG_ROLE; + + return ESP_OK; +} + +static esp_err_t prov_complete(int node_idx, const esp_ble_mesh_octet16_t uuid, + uint16_t unicast, uint8_t elem_num, uint16_t net_idx) +{ + esp_ble_mesh_client_common_param_t common = {0}; + esp_ble_mesh_cfg_client_get_state_t get_state = {0}; + esp_ble_mesh_node_info_t *node = NULL; + char name[10]; + int err; + + ESP_LOGI(TAG, "node index: 0x%x, unicast address: 0x%02x, element num: %d, netkey index: 0x%02x", + node_idx, unicast, elem_num, net_idx); + ESP_LOGI(TAG, "device uuid: %s", bt_hex(uuid, 16)); + + sprintf(name, "%s%d", "NODE-", node_idx); + err = esp_ble_mesh_provisioner_set_node_name(node_idx, name); + if (err) { + ESP_LOGE(TAG, "%s: Set node name failed", __func__); + return ESP_FAIL; + } + + err = esp_ble_mesh_store_node_info(uuid, unicast, elem_num, LED_OFF); + if (err) { + ESP_LOGE(TAG, "%s: Store node info failed", __func__); + return ESP_FAIL; + } + + node = esp_ble_mesh_get_node_info(unicast); + if (!node) { + ESP_LOGE(TAG, "%s: Get node info failed", __func__); + return ESP_FAIL; + } + + esp_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET); + get_state.comp_data_get.page = COMP_DATA_PAGE_0; + err = esp_ble_mesh_config_client_get_state(&common, &get_state); + if (err) { + ESP_LOGE(TAG, "%s: Send config comp data get failed", __func__); + return ESP_FAIL; + } + + return ESP_OK; +} + +static void prov_link_open(esp_ble_mesh_prov_bearer_t bearer) +{ + ESP_LOGI(TAG, "%s link open", bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT"); +} + +static void prov_link_close(esp_ble_mesh_prov_bearer_t bearer, uint8_t reason) +{ + ESP_LOGI(TAG, "%s link close, reason 0x%02x", + bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT", reason); +} + +static void recv_unprov_adv_pkt(uint8_t dev_uuid[16], uint8_t addr[ESP_BD_ADDR_LEN], + esp_ble_addr_type_t addr_type, uint16_t oob_info, + uint8_t adv_type, esp_ble_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_unprov_dev_add_t add_dev = {0}; + int err; + + /* Due to the API esp_ble_mesh_provisioner_set_dev_uuid_match, Provisioner will only + * use this callback to report the devices, whose device UUID starts with 0xdd & 0xdd, + * to the application layer. + */ + + ESP_LOGI(TAG, "address: %s, address type: %d, adv type: %d", bt_hex(addr, ESP_BD_ADDR_LEN), addr_type, adv_type); + ESP_LOGI(TAG, "device uuid: %s", bt_hex(dev_uuid, 16)); + ESP_LOGI(TAG, "oob info: %d, bearer: %s", oob_info, (bearer & ESP_BLE_MESH_PROV_ADV) ? "PB-ADV" : "PB-GATT"); + + memcpy(add_dev.addr, addr, ESP_BD_ADDR_LEN); + add_dev.addr_type = (uint8_t)addr_type; + memcpy(add_dev.uuid, dev_uuid, 16); + add_dev.oob_info = oob_info; + add_dev.bearer = (uint8_t)bearer; + /* Note: If unprovisioned device adv packets have not been received, we should not add + device with ADD_DEV_START_PROV_NOW_FLAG set. */ + err = esp_ble_mesh_provisioner_add_unprov_dev(&add_dev, + ADD_DEV_RM_AFTER_PROV_FLAG | ADD_DEV_START_PROV_NOW_FLAG | ADD_DEV_FLUSHABLE_DEV_FLAG); + if (err) { + ESP_LOGE(TAG, "%s: Add unprovisioned device into queue failed", __func__); + } + + return; +} + +static void esp_ble_mesh_prov_cb(esp_ble_mesh_prov_cb_event_t event, + esp_ble_mesh_prov_cb_param_t *param) +{ + switch (event) { + case ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT, err_code %d", param->provisioner_prov_enable_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT, err_code %d", param->provisioner_prov_disable_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT"); + recv_unprov_adv_pkt(param->provisioner_recv_unprov_adv_pkt.dev_uuid, param->provisioner_recv_unprov_adv_pkt.addr, + param->provisioner_recv_unprov_adv_pkt.addr_type, param->provisioner_recv_unprov_adv_pkt.oob_info, + param->provisioner_recv_unprov_adv_pkt.adv_type, param->provisioner_recv_unprov_adv_pkt.bearer); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT: + prov_link_open(param->provisioner_prov_link_open.bearer); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT: + prov_link_close(param->provisioner_prov_link_close.bearer, param->provisioner_prov_link_close.reason); + break; + case ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT: + prov_complete(param->provisioner_prov_complete.node_idx, param->provisioner_prov_complete.device_uuid, + param->provisioner_prov_complete.unicast_addr, param->provisioner_prov_complete.element_num, + param->provisioner_prov_complete.netkey_idx); + break; + case ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT, err_code %d", param->provisioner_add_unprov_dev_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT, err_code %d", param->provisioner_set_dev_uuid_match_comp.err_code); + break; + case ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT: { + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT, err_code %d", param->provisioner_set_node_name_comp.err_code); + if (param->provisioner_set_node_name_comp.err_code == ESP_OK) { + const char *name = NULL; + name = esp_ble_mesh_provisioner_get_node_name(param->provisioner_set_node_name_comp.node_index); + if (!name) { + ESP_LOGE(TAG, "Get node name failed"); + return; + } + ESP_LOGI(TAG, "Node %d name is: %s", param->provisioner_set_node_name_comp.node_index, name); + } + break; + } + case ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT: { + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT, err_code %d", param->provisioner_add_app_key_comp.err_code); + if (param->provisioner_add_app_key_comp.err_code == ESP_OK) { + esp_err_t err = 0; + prov_key.app_idx = param->provisioner_add_app_key_comp.app_idx; + err = esp_ble_mesh_provisioner_bind_app_key_to_local_model(PROV_OWN_ADDR, prov_key.app_idx, + ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI, CID_NVAL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Provisioner bind local model appkey failed"); + return; + } + } + break; + } + case ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT, err_code %d", param->provisioner_bind_app_key_to_model_comp.err_code); + break; + default: + break; + } + + return; +} + +static void esp_ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, + esp_ble_mesh_model_cb_param_t *param) +{ + switch (event) { + case ESP_BLE_MESH_MODEL_OPERATION_EVT: + break; + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: + break; + case ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT: + break; + default: + break; + } +} + +static void esp_ble_mesh_config_client_cb(esp_ble_mesh_cfg_client_cb_event_t event, + esp_ble_mesh_cfg_client_cb_param_t *param) +{ + esp_ble_mesh_client_common_param_t common = {0}; + esp_ble_mesh_node_info_t *node = NULL; + uint32_t opcode; + uint16_t addr; + int err; + + opcode = param->params->opcode; + addr = param->params->ctx.addr; + + ESP_LOGI(TAG, "%s, error_code = 0x%02x, event = 0x%02x, addr: 0x%04x, opcode: 0x%04x", + __func__, param->error_code, event, param->params->ctx.addr, opcode); + + if (param->error_code) { + ESP_LOGE(TAG, "Send config client message failed, opcode 0x%04x", opcode); + return; + } + + node = esp_ble_mesh_get_node_info(addr); + if (!node) { + ESP_LOGE(TAG, "%s: Get node info failed", __func__); + return; + } + + switch (event) { + case ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: { + ESP_LOGI(TAG, "composition data %s", bt_hex(param->status_cb.comp_data_status.composition_data->data, + param->status_cb.comp_data_status.composition_data->len)); + esp_ble_mesh_cfg_client_set_state_t set_state = {0}; + esp_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD); + set_state.app_key_add.net_idx = prov_key.net_idx; + set_state.app_key_add.app_idx = prov_key.app_idx; + memcpy(set_state.app_key_add.app_key, prov_key.app_key, 16); + err = esp_ble_mesh_config_client_set_state(&common, &set_state); + if (err) { + ESP_LOGE(TAG, "%s: Config AppKey Add failed", __func__); + return; + } + break; + } + default: + break; + } + break; + case ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: { + esp_ble_mesh_cfg_client_set_state_t set_state = {0}; + esp_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND); + set_state.model_app_bind.element_addr = node->unicast; + set_state.model_app_bind.model_app_idx = prov_key.app_idx; + set_state.model_app_bind.model_id = ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV; + set_state.model_app_bind.company_id = CID_NVAL; + err = esp_ble_mesh_config_client_set_state(&common, &set_state); + if (err) { + ESP_LOGE(TAG, "%s: Config Model App Bind failed", __func__); + return; + } + break; + } + case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND: { + esp_ble_mesh_generic_client_get_state_t get_state = {0}; + esp_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET); + err = esp_ble_mesh_generic_client_get_state(&common, &get_state); + if (err) { + ESP_LOGE(TAG, "%s: Generic OnOff Get failed", __func__); + return; + } + break; + } + default: + break; + } + break; + case ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_STATUS: + ESP_LOG_BUFFER_HEX("composition data %s", param->status_cb.comp_data_status.composition_data->data, + param->status_cb.comp_data_status.composition_data->len); + break; + case ESP_BLE_MESH_MODEL_OP_APP_KEY_STATUS: + break; + default: + break; + } + break; + case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: { + esp_ble_mesh_cfg_client_get_state_t get_state = {0}; + esp_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET); + get_state.comp_data_get.page = COMP_DATA_PAGE_0; + err = esp_ble_mesh_config_client_get_state(&common, &get_state); + if (err) { + ESP_LOGE(TAG, "%s: Config Composition Data Get failed", __func__); + return; + } + break; + } + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: { + esp_ble_mesh_cfg_client_set_state_t set_state = {0}; + esp_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD); + set_state.app_key_add.net_idx = prov_key.net_idx; + set_state.app_key_add.app_idx = prov_key.app_idx; + memcpy(set_state.app_key_add.app_key, prov_key.app_key, 16); + err = esp_ble_mesh_config_client_set_state(&common, &set_state); + if (err) { + ESP_LOGE(TAG, "%s: Config AppKey Add failed", __func__); + return; + } + break; + } + case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND: { + esp_ble_mesh_cfg_client_set_state_t set_state = {0}; + esp_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND); + set_state.model_app_bind.element_addr = node->unicast; + set_state.model_app_bind.model_app_idx = prov_key.app_idx; + set_state.model_app_bind.model_id = ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV; + set_state.model_app_bind.company_id = CID_NVAL; + err = esp_ble_mesh_config_client_set_state(&common, &set_state); + if (err) { + ESP_LOGE(TAG, "%s: Config Model App Bind failed", __func__); + return; + } + break; + } + default: + break; + } + break; + default: + ESP_LOGE(TAG, "Not a config client status message event"); + break; + } +} + +static void esp_ble_mesh_generic_client_cb(esp_ble_mesh_generic_client_cb_event_t event, + esp_ble_mesh_generic_client_cb_param_t *param) +{ + esp_ble_mesh_client_common_param_t common = {0}; + esp_ble_mesh_node_info_t *node = NULL; + uint32_t opcode; + uint16_t addr; + int err; + + opcode = param->params->opcode; + addr = param->params->ctx.addr; + + ESP_LOGI(TAG, "%s, error_code = 0x%02x, event = 0x%02x, addr: 0x%04x, opcode: 0x%04x", + __func__, param->error_code, event, param->params->ctx.addr, opcode); + + if (param->error_code) { + ESP_LOGE(TAG, "Send generic client message failed, opcode 0x%04x", opcode); + return; + } + + node = esp_ble_mesh_get_node_info(addr); + if (!node) { + ESP_LOGE(TAG, "%s: Get node info failed", __func__); + return; + } + + switch (event) { + case ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET: { + esp_ble_mesh_generic_client_set_state_t set_state = {0}; + node->onoff = param->status_cb.onoff_status.present_onoff; + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET onoff: 0x%02x", node->onoff); + /* After Generic OnOff Status for Generic OnOff Get is received, Generic OnOff Set will be sent */ + esp_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET); + set_state.onoff_set.op_en = false; + set_state.onoff_set.onoff = !node->onoff; + set_state.onoff_set.tid = 0; + err = esp_ble_mesh_generic_client_set_state(&common, &set_state); + if (err) { + ESP_LOGE(TAG, "%s: Generic OnOff Set failed", __func__); + return; + } + break; + } + default: + break; + } + break; + case ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT: + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: + node->onoff = param->status_cb.onoff_status.present_onoff; + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET onoff: 0x%02x", node->onoff); + break; + default: + break; + } + break; + case ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT: + break; + case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT: + /* If failed to receive the responses, these messages will be resend */ + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET: { + esp_ble_mesh_generic_client_get_state_t get_state = {0}; + esp_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET); + err = esp_ble_mesh_generic_client_get_state(&common, &get_state); + if (err) { + ESP_LOGE(TAG, "%s: Generic OnOff Get failed", __func__); + return; + } + break; + } + case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET: { + esp_ble_mesh_generic_client_set_state_t set_state = {0}; + node->onoff = param->status_cb.onoff_status.present_onoff; + ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET onoff: 0x%02x", node->onoff); + esp_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET); + set_state.onoff_set.op_en = false; + set_state.onoff_set.onoff = !node->onoff; + set_state.onoff_set.tid = 0; + err = esp_ble_mesh_generic_client_set_state(&common, &set_state); + if (err) { + ESP_LOGE(TAG, "%s: Generic OnOff Set failed", __func__); + return; + } + break; + } + default: + break; + } + break; + default: + ESP_LOGE(TAG, "Not a generic client status message event"); + break; + } +} + +static int ble_mesh_init(void) +{ + uint8_t match[2] = {0xdd, 0xdd}; + int err = 0; + + prov_key.net_idx = ESP_BLE_MESH_KEY_PRIMARY; + prov_key.app_idx = APP_KEY_IDX; + memset(prov_key.app_key, APP_KEY_OCTET, sizeof(prov_key.app_key)); + + memcpy(dev_uuid, esp_bt_dev_get_address(), ESP_BD_ADDR_LEN); + + esp_ble_mesh_register_prov_callback(esp_ble_mesh_prov_cb); + esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb); + esp_ble_mesh_register_config_client_callback(esp_ble_mesh_config_client_cb); + esp_ble_mesh_register_generic_client_callback(esp_ble_mesh_generic_client_cb); + + esp_ble_mesh_provisioner_set_dev_uuid_match(match, sizeof(match), 0x0, false); + + err = esp_ble_mesh_init(&provision, &composition); + if (err) { + ESP_LOGE(TAG, "Initializing mesh failed (err %d)", err); + return err; + } + + esp_ble_mesh_provisioner_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT); + + esp_ble_mesh_provisioner_add_local_app_key(prov_key.app_key, prov_key.net_idx, prov_key.app_idx); + + ESP_LOGI(TAG, "BLE Mesh Provisioner initialized"); + + return err; +} + +static esp_err_t bluetooth_init(void) +{ + esp_err_t ret; + + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "%s initialize controller failed", __func__); + return ret; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "%s enable controller failed", __func__); + return ret; + } + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(TAG, "%s init bluetooth failed", __func__); + return ret; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "%s enable bluetooth failed", __func__); + return ret; + } + + return ret; +} + +void app_main(void) +{ + int err; + + ESP_LOGI(TAG, "Initializing..."); + + err = bluetooth_init(); + if (err) { + ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err); + return; + } + + /* Initialize the Bluetooth Mesh Subsystem */ + err = ble_mesh_init(); + if (err) { + ESP_LOGE(TAG, "Bluetooth mesh init failed (err %d)", err); + } +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/component.mk b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_provisioner/sdkconfig.defaults b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/sdkconfig.defaults new file mode 100644 index 0000000000..12c5be5d12 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/sdkconfig.defaults @@ -0,0 +1,40 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CONTROLLER_MODE_BLE_ONLY=y +CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY= +CONFIG_BTDM_CONTROLLER_MODE_BTDM= +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_BLE_SCAN_DUPLICATE=y +CONFIG_SCAN_DUPLICATE_TYPE=2 +CONFIG_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_MESH_DUPLICATE_SCAN_CACHE_SIZE=200 +CONFIG_BTDM_CONTROLLER_FULL_SCAN_SUPPORTED=y +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_HCI_5_0=y +CONFIG_BLE_MESH_USE_DUPLICATE_SCAN=y +CONFIG_BLE_MESH_PROV=y +CONFIG_BLE_MESH_PROVISIONER=y +CONFIG_BLE_MESH_WAIT_FOR_PROV_MAX_DEV_NUM=10 +CONFIG_BLE_MESH_MAX_STORED_NODES=10 +CONFIG_BLE_MESH_MAX_PROV_NODES=10 +CONFIG_BLE_MESH_PBA_SAME_TIME=3 +CONFIG_BLE_MESH_PBG_SAME_TIME=2 +CONFIG_BLE_MESH_PROVISIONER_SUBNET_COUNT=3 +CONFIG_BLE_MESH_PROVISIONER_APP_KEY_COUNT=3 +CONFIG_BLE_MESH_PB_ADV=y +CONFIG_BLE_MESH_NET_BUF_POOL_USAGE=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_GATT_PROXY=y +CONFIG_BLE_MESH_RELAY=y +CONFIG_BLE_MESH_LOW_POWER= +CONFIG_BLE_MESH_FRIEND= +CONFIG_BLE_MESH_ADV_BUF_COUNT=60 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=6 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=6 +CONFIG_BLE_MESH_RX_SDU_MAX=384 +CONFIG_BLE_MESH_TX_SEG_MAX=32 +CONFIG_BTU_TASK_STACK_SIZE=4512 +CONFIG_BLE_MESH_CFG_CLI=y +CONFIG_BLE_MESH_GENERIC_ONOFF_CLI=y \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_provisioner/tutorial/Ble_Mesh_Provisioner_Example_Walkthrough.md b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/tutorial/Ble_Mesh_Provisioner_Example_Walkthrough.md new file mode 100644 index 0000000000..53d0f9d04e --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_provisioner/tutorial/Ble_Mesh_Provisioner_Example_Walkthrough.md @@ -0,0 +1,129 @@ + +# Demo for ESP BLE Mesh Provisioner + +## 1. Introduction + +This demo shows how a BLE Mesh device can function as a Provisioner. If you are new to BLE Mesh, please start by checking [Demo for ESP BLE Mesh Node](../../ble_mesh_node/README.md). + +## 2. Code Flow + +### 2.1 Initialization + +The code block below shows the initialization of BLE Mesh. + +```c +static int ble_mesh_init(void) +{ + uint8_t match[2] = {0xdd, 0xdd}; + int err = 0; + + prov_key.net_idx = ESP_BLE_MESH_KEY_PRIMARY; + prov_key.app_idx = ESP_BLE_MESH_APP_IDX; + memset(prov_key.app_key, APP_KEY_OCTET, sizeof(prov_key.app_key)); + + memcpy(dev_uuid, esp_bt_dev_get_address(), ESP_BD_ADDR_LEN); + + esp_ble_mesh_register_prov_callback(esp_ble_mesh_prov_cb); + esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb); + esp_ble_mesh_register_config_client_callback(esp_ble_mesh_config_client_cb); + esp_ble_mesh_register_generic_client_callback(esp_ble_mesh_generic_client_cb); + + esp_ble_mesh_provisioner_set_dev_uuid_match(match, sizeof(match), 0x0, false); + + err = esp_ble_mesh_init(&provision, &composition); + if (err) { + ESP_LOGE(TAG, "Initializing mesh failed (err %d)", err); + return err; + } + + esp_ble_mesh_provisioner_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT); + + esp_ble_mesh_provisioner_add_local_app_key(prov_key.app_key, prov_key.net_idx, prov_key.app_idx); + + ESP_LOGI(TAG, "Provisioner initialized"); + + return err; +} +``` + +The following procedures are needed for the initialization with `ble_mesh_init`. + +1. `???`: adds the device to the list of devices for provisioning. Once a device is added, BLE Mesh protocol stack automatically provisions this device. +2. `esp_ble_mesh_provisioner_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT)`: call this API function to scan for the unprovisioned devices in the surrounding environment. + +### 2.2 Set and Bind AppKey + +While working as a Provisioner, a BLE Mesh device also needs to configure such parameters as AppKey, TTL, and Proxy after provisioning is finished. It is not required if a BLE Mesh device works as a node. + +Please note that successful configuring of AppKeys is of vital importance. App can only send/receive data (set a state, get a state and publish) after an AppKey has been set and bound. + +```c +static esp_err_t prov_complete(int node_idx, const esp_ble_mesh_octet16_t uuid, + uint16_t unicast, uint8_t elem_num, uint16_t net_idx) +{ + esp_ble_mesh_client_common_param_t common = {0}; + esp_ble_mesh_cfg_client_get_state_t get_state = {0}; + esp_ble_mesh_node_info_t *node = NULL; + char name[10]; + int err; + + ESP_LOGI(TAG, "node index: 0x%x, unicast address: 0x%02x, element num: %d, netkey index: 0x%02x", + node_idx, unicast, elem_num, net_idx); + ESP_LOGI(TAG, "device uuid: %s", bt_hex(uuid, 16)); + + sprintf(name, "%s%d", "NODE-", node_idx); + err = esp_ble_mesh_provisioner_set_node_name(node_idx, name); + if (err) { + ESP_LOGE(TAG, "%s: Set node name failed", __func__); + return ESP_FAIL; + } + + err = esp_ble_mesh_store_node_info(uuid, unicast, elem_num, LED_OFF); + if (err) { + ESP_LOGE(TAG, "%s: Store node info failed", __func__); + return ESP_FAIL; + } + + node = esp_ble_mesh_get_node_info(unicast); + if (!node) { + ESP_LOGE(TAG, "%s: Get node info failed", __func__); + return ESP_FAIL; + } + + esp_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET); + get_state.comp_data_get.page = COMPOSITION_DATA_PAGE_0; + err = esp_ble_mesh_config_client_get_state(&common, &get_state); + if (err) { + ESP_LOGE(TAG, "%s: Send config comp data get failed", __func__); + return ESP_FAIL; + } + + return ESP_OK; +} +``` + +After provisioning is completed, the API function `esp_ble_mesh_provisioner_add_local_app_key` must be called to set and bind an AppKey. After that, the device can exchange model messages with peer devices. + +
Note: Set and bind AppKey is used to configure and bind an AppKey through the Configuration Client model. For this reason, a Provisioner must also register the Configuration Client model.
+ +### 2.3 Register the Configuration Client Model + +The process of registering the Configuration Client model is similar to registering other models, as can be seen below. + +```c +static esp_ble_mesh_client_t config_client; +``` +```c +static esp_ble_mesh_model_t root_models[] = { + ESP_BLE_MESH_MODEL_CFG_SRV(&config_server), + ESP_BLE_MESH_MODEL_CFG_CLI(&config_client), + ESP_BLE_MESH_MODEL_GEN_ONOFF_CLI(NULL, &onoff_client), +}; +``` + +The procedures to register the Configuration Client model include: + +1. `static esp_ble_mesh_client_t config_client`: defines a variable `config_client` of the type `esp_ble_mesh_client_t` +2. `ESP_BLE_MESH_MODEL_CFG_CLI(&config_client)`: use the macro `ESP_BLE_MESH_MODEL_CFG_CLI` to add the Configuration Client model to the `root_models` array. + +After the definition is completed, the Configuration Client model can be registered simply by passing the pointer of `element` to the BLE Mesh protocol stack. diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/CMakeLists.txt new file mode 100644 index 0000000000..cb92f4b374 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(fast_prov_vendor_model) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/Makefile b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/Makefile new file mode 100644 index 0000000000..274dd33494 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := fast_prov_vendor_model + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/CMakeLists.txt new file mode 100644 index 0000000000..44699eb00e --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/CMakeLists.txt @@ -0,0 +1,9 @@ +set(COMPONENT_SRCS "esp_fast_prov_client_model.c" + "esp_fast_prov_server_model.c" + "esp_fast_prov_operation.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +set(COMPONENT_REQUIRES bt) + +register_component() diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/component.mk b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/component.mk new file mode 100644 index 0000000000..646c890db8 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/component.mk @@ -0,0 +1,6 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) +# +COMPONENT_ADD_INCLUDEDIRS := . \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.c b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.c new file mode 100644 index 0000000000..a9d3d8bc8b --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.c @@ -0,0 +1,407 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_local_data_operation_api.h" + +#include "esp_fast_prov_operation.h" +#include "esp_fast_prov_client_model.h" +#include "esp_fast_prov_server_model.h" + +#define TAG "FAST_PROV_CLIENT" + +/* Note: these variables are used by ble_mesh_fast_prov_client demo */ + +#define GET_ALL_NODE_ADDR_TIMEOUT K_SECONDS(60) + +/* Timer used to send Fast Prov All Node Addr Get message */ +#if !defined(CONFIG_BLE_MESH_FAST_PROV) +static struct k_delayed_work get_all_node_addr_timer; +#endif + +/* Unicast address of the Primary Provisioner */ +static uint16_t prim_prov_addr; + +/* Note: these variables are used by ble_mesh_fast_prov_server demo */ + +/* Send 4 node addresses (8 octets) most each time to prevent segmentation */ +#define NODE_ADDR_SEND_MAX_LEN 8 + +/* Timer used to send self-provisioned node addresses to Primary Provisioner */ +struct k_delayed_work send_self_prov_node_addr_timer; +bt_mesh_atomic_t fast_prov_cli_flags; + +/* Self-provisioned node addresses that are being sent */ +typedef struct { + struct net_buf_simple *addr; /* Unicast addresses of self-provisioned nodes being sent */ + bool send_succeed; /* Indicate if sent operation is successful */ + bool ack_received; /* Indicate if sent address has been acked */ +} example_send_prov_node_addr_t; + +NET_BUF_SIMPLE_DEFINE_STATIC(send_addr, NODE_ADDR_SEND_MAX_LEN); +static example_send_prov_node_addr_t node_addr_send = { + .addr = &send_addr, + .send_succeed = true, + .ack_received = true, +}; + +/* Self-provisioned node addresses that have been sent successfully */ +uint16_t addr_already_sent[CONFIG_BLE_MESH_MAX_PROV_NODES]; + +static example_fast_prov_server_t *get_fast_prov_srv_user_data(void) +{ + esp_ble_mesh_model_t *model = NULL; + + model = example_find_model(esp_ble_mesh_get_primary_element_address(), + ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_SRV, CID_ESP); + if (!model) { + ESP_LOGE(TAG, "%s: Failed to get config server model", __func__); + return NULL; + } + + return (example_fast_prov_server_t *)(model->user_data); +} + +/* Timeout handler for send_self_prov_node_addr_timer */ +void example_send_self_prov_node_addr(struct k_work *work) +{ + example_fast_prov_server_t *fast_prov_srv = NULL; + esp_ble_mesh_model_t *model = NULL; + int i, j, err; + + bt_mesh_atomic_test_and_clear_bit(&fast_prov_cli_flags, SEND_SELF_PROV_NODE_ADDR_START); + + fast_prov_srv = get_fast_prov_srv_user_data(); + if (!fast_prov_srv) { + ESP_LOGE(TAG, "%s: Failed to get fast prov server model user_data", __func__); + return; + } + + model = example_find_model(esp_ble_mesh_get_primary_element_address(), + ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI, CID_ESP); + if (!model) { + ESP_LOGE(TAG, "%s: Failed to get fast prov client model", __func__); + return; + } + + if (node_addr_send.send_succeed == true && node_addr_send.ack_received == false) { + ESP_LOGW(TAG, "%s: Previous node address message is being sent", __func__); + return; + } + + if (node_addr_send.send_succeed == true) { + /* If the previous node address message has been sent successfully, and when + * timeout event comes, we will update the send buffer (node_addr_send). + */ + net_buf_simple_reset(node_addr_send.addr); + for (i = 0; i < CONFIG_BLE_MESH_MAX_PROV_NODES; i++) { + uint16_t addr = example_get_node_address(i); + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(addr)) { + continue; + } + for (j = 0; j < ARRAY_SIZE(addr_already_sent); j++) { + if (addr == addr_already_sent[j]) { + ESP_LOGW(TAG, "%s: node addr 0x%04x has already been sent", __func__, addr); + break; + } + } + if (j != ARRAY_SIZE(addr_already_sent)) { + continue; + } + net_buf_simple_add_le16(node_addr_send.addr, addr); + if (node_addr_send.addr->len == NODE_ADDR_SEND_MAX_LEN) { + break; + } + } + } + + if (node_addr_send.addr->len) { + example_msg_common_info_t info = { + .net_idx = fast_prov_srv->net_idx, + .app_idx = fast_prov_srv->app_idx, + .dst = fast_prov_srv->prim_prov_addr, + .timeout = 0, + .role = ROLE_FAST_PROV, + }; + err = example_send_fast_prov_self_prov_node_addr(model, &info, node_addr_send.addr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send node address", __func__); + node_addr_send.send_succeed = false; + } else { + node_addr_send.send_succeed = true; + } + node_addr_send.ack_received = false; + } + + /* If sending node addresses failed, the Provisioner will start the timer in case + * no other devices will be provisioned and the timer will never start. + */ + if (node_addr_send.send_succeed == false && node_addr_send.ack_received == false) { + if (!bt_mesh_atomic_test_and_set_bit(&fast_prov_cli_flags, SEND_SELF_PROV_NODE_ADDR_START)) { + k_delayed_work_submit(&send_self_prov_node_addr_timer, SEND_SELF_PROV_NODE_ADDR_TIMEOUT); + } + } + + return; +} + +#if !defined(CONFIG_BLE_MESH_FAST_PROV) +/* Timeout handler for get_all_node_addr_timer */ +static void example_get_all_node_addr(struct k_work *work) +{ + esp_ble_mesh_model_t *model = NULL; + example_node_info_t *node = NULL; + esp_err_t err; + + node = example_get_node_info(prim_prov_addr); + if (!node) { + ESP_LOGE(TAG, "%s: Failed to get node info", __func__); + return; + } + + model = example_find_model(esp_ble_mesh_get_primary_element_address(), + ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI, CID_ESP); + if (!model) { + ESP_LOGE(TAG, "%s: Failed to get model info", __func__); + return; + } + + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->unicast_addr, + .timeout = 10000, + .role = ROLE_PROVISIONER, + }; + err = example_send_fast_prov_all_node_addr_get(model, &info); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Fast Prov Node Address Get message", __func__); + return; + } +} +#endif + +esp_err_t example_fast_prov_client_recv_timeout(uint32_t opcode, esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx) +{ +#if defined(CONFIG_BLE_MESH_FAST_PROV) + example_fast_prov_server_t *fast_prov_srv = NULL; +#endif + example_node_info_t *node = NULL; + esp_err_t err; + + ESP_LOGW(TAG, "%s: Receive fast prov server status timeout", __func__); + + if (!model || !ctx) { + return ESP_ERR_INVALID_ARG; + } + +#if defined(CONFIG_BLE_MESH_FAST_PROV) + fast_prov_srv = get_fast_prov_srv_user_data(); + if (!fast_prov_srv) { + ESP_LOGE(TAG, "%s: Failed to get fast prov server model user_data", __func__); + return ESP_FAIL; + } +#endif + + switch (opcode) { + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET: { + example_fast_prov_info_set_t set = {0}; + node = example_get_node_info(ctx->addr); + if (!node) { + return ESP_FAIL; + } + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->unicast_addr, + .timeout = 0, + }; +#if defined(CONFIG_BLE_MESH_FAST_PROV) + if (node->lack_of_addr == false) { + set.ctx_flags = 0x03FE; + memcpy(&set.unicast_min, &node->unicast_min, + sizeof(example_node_info_t) - offsetof(example_node_info_t, unicast_min)); + } else { + set.ctx_flags = BIT(6); + set.group_addr = fast_prov_srv->group_addr; + } + info.role = ROLE_FAST_PROV; +#else + set.ctx_flags = 0x037F; + memcpy(&set.node_addr_cnt, &node->node_addr_cnt, + sizeof(example_node_info_t) - offsetof(example_node_info_t, node_addr_cnt)); + info.role = ROLE_PROVISIONER; +#endif + err = example_send_fast_prov_info_set(model, &info, &set); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Fast Prov Info Set message", __func__); + return ESP_FAIL; + } + break; + } + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD: + break; +#if defined(CONFIG_BLE_MESH_FAST_PROV) + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR: + if (node_addr_send.addr->len) { + example_msg_common_info_t info = { + .net_idx = fast_prov_srv->net_idx, + .app_idx = fast_prov_srv->app_idx, + .dst = fast_prov_srv->prim_prov_addr, + .timeout = 0, + .role = ROLE_FAST_PROV, + }; + err = example_send_fast_prov_self_prov_node_addr(model, &info, node_addr_send.addr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Fast Prov Node Addr message", __func__); + node_addr_send.send_succeed = false; + } else { + node_addr_send.send_succeed = true; + } + node_addr_send.ack_received = false; + } + if (node_addr_send.send_succeed == false && node_addr_send.ack_received == false) { + if (!bt_mesh_atomic_test_and_set_bit(&fast_prov_cli_flags, SEND_SELF_PROV_NODE_ADDR_START)) { + k_delayed_work_submit(&send_self_prov_node_addr_timer, SEND_SELF_PROV_NODE_ADDR_TIMEOUT); + } + } + break; +#endif + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET: { + node = example_get_node_info(ctx->addr); + if (!node) { + return ESP_FAIL; + } + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->unicast_addr, + .timeout = 10000, + .role = ROLE_PROVISIONER, + }; + err = example_send_fast_prov_all_node_addr_get(model, &info); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Fast Prov Node Address message", __func__); + return ESP_FAIL; + } + break; + } + default: + break; + } + + return ESP_OK; +} + +esp_err_t example_fast_prov_client_recv_status(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t len, const uint8_t *data) +{ + if (!model || !ctx) { + return ESP_ERR_INVALID_ARG; + } + + ESP_LOG_BUFFER_HEX("fast prov client receives", data, len); + + switch (ctx->recv_op) { + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS: + ESP_LOG_BUFFER_HEX("fast prov info status", data, len); +#if !defined(CONFIG_BLE_MESH_FAST_PROV) + prim_prov_addr = ctx->addr; + k_delayed_work_init(&get_all_node_addr_timer, example_get_all_node_addr); + k_delayed_work_submit(&get_all_node_addr_timer, GET_ALL_NODE_ADDR_TIMEOUT); +#endif + break; + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS: + ESP_LOGI(TAG, "status_key: 0x%02x, status_act: 0x%02x", data[0], data[1]); + break; + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK: { + /* node address message has been acked */ + int i, j; + uint8_t length = node_addr_send.addr->len; + node_addr_send.send_succeed = true; + node_addr_send.ack_received = true; + for (i = 0; i < (length >> 1); i++) { + uint16_t addr = net_buf_simple_pull_le16(node_addr_send.addr); + if (ESP_BLE_MESH_ADDR_IS_UNICAST(addr)) { + for (j = 0; j < ARRAY_SIZE(addr_already_sent); j++) { + if (addr_already_sent[j] == addr) { + break; + } + } + if (j != ARRAY_SIZE(addr_already_sent)) { + continue; + } + for (j = 0; j < ARRAY_SIZE(addr_already_sent); j++) { + if (addr_already_sent[j] == ESP_BLE_MESH_ADDR_UNASSIGNED) { + addr_already_sent[j] = addr; + break; + } + } + if (j == ARRAY_SIZE(addr_already_sent)) { + ESP_LOGE(TAG, "%s: No place to store the sent node address", __func__); + return ESP_FAIL; + } + } + } + /* In case spending too much time on the first node address message(i.e. failed + * to receive ack), and the timer for the second or further messages is timeout, + * thus the Provisioner will never be able to send other addresses. + */ + if (!bt_mesh_atomic_test_and_set_bit(&fast_prov_cli_flags, SEND_SELF_PROV_NODE_ADDR_START)) { + k_delayed_work_submit(&send_self_prov_node_addr_timer, SEND_SELF_PROV_NODE_ADDR_TIMEOUT); + } + break; + } + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS: { + ESP_LOG_BUFFER_HEX("Node address", data, len); + esp_ble_mesh_model_t *cli_model = NULL; + example_node_info_t *node = NULL; + esp_err_t err; + node = example_get_node_info(prim_prov_addr); + if (!node) { + ESP_LOGE(TAG, "%s: Failed to get node info", __func__); + return ESP_FAIL; + } + cli_model = example_find_model(esp_ble_mesh_get_primary_element_address(), + ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI, CID_NVAL); + if (!cli_model) { + ESP_LOGE(TAG, "%s: Failed to get Generic OnOff Client Model info", __func__); + return ESP_FAIL; + } + example_msg_common_info_t info = { + .net_idx = node->net_idx, + .app_idx = node->app_idx, + .dst = node->group_addr, + .timeout = 0, + .role = ROLE_PROVISIONER, + }; + err = example_send_generic_onoff_set(cli_model, &info, LED_ON, 0x00, false); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Generic OnOff Set Unack message", __func__); + return ESP_FAIL; + } + break; + } + default: + ESP_LOGE(TAG, "%s: Invalid fast prov status opcode", __func__); + return ESP_FAIL; + } + + return ESP_OK; +} \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.h b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.h new file mode 100644 index 0000000000..9dac3c241c --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.h @@ -0,0 +1,36 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_FAST_PROV_CLIENT_MODEL_H +#define _ESP_FAST_PROV_CLIENT_MODEL_H + +#include "esp_ble_mesh_defs.h" + +enum { + SEND_SELF_PROV_NODE_ADDR_START, /* Flag indicates the timer used to send self-provisioned node addresses has been started */ + CLI_MAX_FLAGS, +}; + +#define SEND_SELF_PROV_NODE_ADDR_TIMEOUT K_SECONDS(5) + +void example_send_self_prov_node_addr(struct k_work *work); + +esp_err_t example_fast_prov_client_recv_timeout(uint32_t opcode, esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx); + +esp_err_t example_fast_prov_client_recv_status(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint16_t len, const uint8_t *data); + +#endif /* _ESP_FAST_PROV_CLIENT_MODEL_H */ \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_common.h b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_common.h new file mode 100644 index 0000000000..6f4771239f --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_common.h @@ -0,0 +1,121 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_FAST_PROV_COMMON_H +#define _ESP_FAST_PROV_COMMON_H + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_config_model_api.h" + +#define LED_OFF 0x00 +#define LED_ON 0x01 + +#define CID_ESP 0x02E5 +#define CID_NVAL 0xFFFF + +/* Fast Prov Model ID */ +#define ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_CLI 0x0000 +#define ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_SRV 0x0001 + +/* Fast Prov Message Opcode */ +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET ESP_BLE_MESH_MODEL_OP_3(0x00, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS ESP_BLE_MESH_MODEL_OP_3(0x01, CID_ESP) + +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD ESP_BLE_MESH_MODEL_OP_3(0x02, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS ESP_BLE_MESH_MODEL_OP_3(0x03, CID_ESP) + +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR ESP_BLE_MESH_MODEL_OP_3(0x04, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK ESP_BLE_MESH_MODEL_OP_3(0x05, CID_ESP) + +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET ESP_BLE_MESH_MODEL_OP_3(0x06, CID_ESP) +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS ESP_BLE_MESH_MODEL_OP_3(0x07, CID_ESP) + +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_GROUP_ADD ESP_BLE_MESH_MODEL_OP_3(0x08, CID_ESP) + +#define ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_GROUP_DELETE ESP_BLE_MESH_MODEL_OP_3(0x09, CID_ESP) + +typedef struct { + uint16_t net_idx; + uint16_t app_idx; + uint16_t dst; + int32_t timeout; + esp_ble_mesh_dev_role_t role; +} example_msg_common_info_t; + +typedef struct { + uint16_t net_idx; + uint16_t app_idx; + uint8_t app_key[16]; + + uint16_t node_addr_cnt; /* Number of BLE Mesh nodes in the network */ + uint16_t unicast_min; /* Minimum unicast address to be assigned to the nodes in the network */ + uint16_t unicast_max; /* Maximum unicast address to be assigned to the nodes in the network */ + uint16_t group_addr; /* Group address which will be subscribed by the nodes in the network */ + uint8_t match_val[16]; /* Match value used by Fast Provisoning Provisioner */ + uint8_t match_len; + + uint8_t max_node_num; /* Maximum number of nodes can be provisioned by the client */ +} __attribute__((packed)) example_prov_info_t; + +/* Fast Prov Info Set Message Context */ +typedef struct { + uint16_t ctx_flags; /* Flags indicate which part of context exists */ + uint16_t node_addr_cnt; /* Number of the nodes going to be provisioned */ + uint16_t unicast_min; /* Assigned minimum unicast address */ + uint16_t unicast_max; /* Assigned maximum unicast address */ + uint8_t flags; /* Flags used for provisioning data */ + uint32_t iv_index; /* IV_index used for provisioning data */ + uint16_t net_idx; /* Netkey index used for provisioning data */ + uint16_t group_addr; /* Group address going to be added to model */ + uint16_t prov_addr; /* Primary Provisioner address */ + uint8_t match_val[16]; /* Match value used for provisioning */ + uint8_t match_len; + uint8_t action; /* Action used to enbale/disable Provisioner functionality */ +} __attribute__((packed)) example_fast_prov_info_set_t; + +typedef struct { + /* The following is the basic information of a node */ + bool reprov; + uint8_t uuid[16]; + uint16_t unicast_addr; + uint8_t element_num; + uint16_t net_idx; + uint16_t app_idx; + uint8_t onoff; + + /* The following is the information which will be/has been sent to the node */ + bool lack_of_addr; + uint16_t node_addr_cnt; + uint16_t unicast_min; + uint16_t unicast_max; + uint8_t flags; + uint32_t iv_index; + uint16_t fp_net_idx; + uint16_t group_addr; + uint16_t prov_addr; + uint8_t match_val[16]; + uint8_t match_len; + uint8_t action; +} __attribute__((packed)) example_node_info_t; + +typedef struct { + uint8_t net_key[16]; /* Network key going to be added */ +} example_fast_prov_net_key_add_t; + +typedef struct { + uint8_t status_key; /* Indicate the result of adding network key */ + uint8_t status_act; /* Indicate the result of action */ +} example_fast_prov_net_key_status_t; + +#endif /* _ESP_FAST_PROV_COMMON_H */ \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.c b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.c new file mode 100644 index 0000000000..c9a2bb795b --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.c @@ -0,0 +1,577 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_config_model_api.h" +#include "esp_ble_mesh_generic_model_api.h" +#include "esp_ble_mesh_local_data_operation_api.h" + +#include "esp_fast_prov_common.h" +#include "esp_fast_prov_operation.h" +#include "esp_fast_prov_client_model.h" +#include "esp_fast_prov_server_model.h" + +#define TAG "FAST_PROV_OP" + +/* Provisioned node information context */ +static example_node_info_t nodes_info[CONFIG_BLE_MESH_MAX_PROV_NODES] = { + [0 ... (CONFIG_BLE_MESH_MAX_PROV_NODES - 1)] = { + .reprov = false, + .unicast_addr = ESP_BLE_MESH_ADDR_UNASSIGNED, + .element_num = 0x0, + .net_idx = ESP_BLE_MESH_KEY_UNUSED, + .app_idx = ESP_BLE_MESH_KEY_UNUSED, + .onoff = LED_OFF, + .lack_of_addr = false, + .unicast_min = ESP_BLE_MESH_ADDR_UNASSIGNED, + .unicast_max = ESP_BLE_MESH_ADDR_UNASSIGNED, + .flags = 0x0, + .iv_index = 0x0, + .group_addr = ESP_BLE_MESH_ADDR_UNASSIGNED, + .prov_addr = ESP_BLE_MESH_ADDR_UNASSIGNED, + .match_len = 0, + .action = FAST_PROV_ACT_NONE, + } +}; + +esp_err_t example_store_node_info(const uint8_t uuid[16], uint16_t node_addr, + uint8_t elem_num, uint16_t net_idx, + uint16_t app_idx, uint8_t onoff) +{ + example_node_info_t *node = NULL; + + if (!uuid || !ESP_BLE_MESH_ADDR_IS_UNICAST(node_addr) || !elem_num) { + return ESP_ERR_INVALID_ARG; + } + + for (int i = 0; i < ARRAY_SIZE(nodes_info); i++) { + node = &nodes_info[i]; + if (!memcmp(node->uuid, uuid, 16)) { + ESP_LOGW(TAG, "%s: reprovisioned node", __func__); + node->reprov = true; + node->unicast_addr = node_addr; + node->element_num = elem_num; + node->net_idx = net_idx; + node->app_idx = app_idx; + node->onoff = onoff; + return ESP_OK; + } + } + + for (int i = 0; i < ARRAY_SIZE(nodes_info); i++) { + node = &nodes_info[i]; + if (node->unicast_addr == ESP_BLE_MESH_ADDR_UNASSIGNED) { + memcpy(node->uuid, uuid, 16); + node->reprov = false; + node->unicast_addr = node_addr; + node->element_num = elem_num; + node->net_idx = net_idx; + node->app_idx = app_idx; + node->onoff = onoff; + node->lack_of_addr = false; + return ESP_OK; + } + } + + ESP_LOGE(TAG, "%s: nodes_info is full", __func__); + return ESP_FAIL; +} + +example_node_info_t *example_get_node_info(uint16_t node_addr) +{ + example_node_info_t *node = NULL; + + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(node_addr)) { + return NULL; + } + + for (int i = 0; i < ARRAY_SIZE(nodes_info); i++) { + node = &nodes_info[i]; + if (node_addr >= node->unicast_addr && + node_addr < node->unicast_addr + node->element_num) { + return node; + } + } + + return NULL; +} + +bool example_is_node_exist(const uint8_t uuid[16]) +{ + example_node_info_t *node = NULL; + + if (!uuid) { + return false; + } + + for (int i = 0; i < ARRAY_SIZE(nodes_info); i++) { + node = &nodes_info[i]; + if (ESP_BLE_MESH_ADDR_IS_UNICAST(node->unicast_addr)) { + if (!memcmp(node->uuid, uuid, 16)) { + return true; + } + } + } + + return false; +} + +uint16_t example_get_node_address(int node_idx) +{ + return nodes_info[node_idx].unicast_addr; +} + +esp_ble_mesh_model_t *example_find_model(uint16_t element_addr, uint16_t model_id, + uint16_t company_id) +{ + esp_ble_mesh_elem_t *element = NULL; + + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(element_addr)) { + return NULL; + } + + element = esp_ble_mesh_find_element(element_addr); + if (!element) { + return NULL; + } + + if (company_id == CID_NVAL) { + return esp_ble_mesh_find_sig_model(element, model_id); + } else { + return esp_ble_mesh_find_vendor_model(element, company_id, model_id); + } +} + +static esp_err_t example_set_app_idx_to_user_data(uint16_t app_idx) +{ + example_fast_prov_server_t *srv_data = NULL; + esp_ble_mesh_model_t *srv_model = NULL; + + srv_model = example_find_model(esp_ble_mesh_get_primary_element_address(), + ESP_BLE_MESH_VND_MODEL_ID_FAST_PROV_SRV, CID_ESP); + if (!srv_model) { + return ESP_FAIL; + } + + srv_data = (example_fast_prov_server_t *)(srv_model->user_data); + if (!srv_data) { + return ESP_FAIL; + } + + srv_data->app_idx = app_idx; + return ESP_OK; +} + +esp_err_t example_handle_config_app_key_add_evt(uint16_t app_idx) +{ + const esp_ble_mesh_comp_t *comp = NULL; + esp_ble_mesh_elem_t *element = NULL; + esp_ble_mesh_model_t *model = NULL; + int i, j, k; + + comp = esp_ble_mesh_get_composition_data(); + if (!comp) { + return ESP_FAIL; + } + + for (i = 0; i < comp->element_count; i++) { + element = &comp->elements[i]; + /* Bind app_idx with SIG models except the Config Client & Server models */ + for (j = 0; j < element->sig_model_count; j++) { + model = &element->sig_models[j]; + if (model->model_id == ESP_BLE_MESH_MODEL_ID_CONFIG_SRV || + model->model_id == ESP_BLE_MESH_MODEL_ID_CONFIG_CLI) { + continue; + } + for (k = 0; k < ARRAY_SIZE(model->keys); k++) { + if (model->keys[k] == app_idx) { + break; + } + } + if (k != ARRAY_SIZE(model->keys)) { + continue; + } + for (k = 0; k < ARRAY_SIZE(model->keys); k++) { + if (model->keys[k] == ESP_BLE_MESH_KEY_UNUSED) { + model->keys[k] = app_idx; + break; + } + } + if (k == ARRAY_SIZE(model->keys)) { + ESP_LOGE(TAG, "%s: SIG model (model_id 0x%04x) is full of AppKey", + __func__, model->model_id); + } + } + /* Bind app_idx with Vendor models */ + for (j = 0; j < element->vnd_model_count; j++) { + model = &element->vnd_models[j]; + for (k = 0; k < ARRAY_SIZE(model->keys); k++) { + if (model->keys[k] == app_idx) { + break; + } + } + if (k != ARRAY_SIZE(model->keys)) { + continue; + } + for (k = 0; k < ARRAY_SIZE(model->keys); k++) { + if (model->keys[k] == ESP_BLE_MESH_KEY_UNUSED) { + model->keys[k] = app_idx; + break; + } + } + if (k == ARRAY_SIZE(model->keys)) { + ESP_LOGE(TAG, "%s: Vendor model (model_id 0x%04x, cid: 0x%04x) is full of AppKey", + __func__, model->vnd.model_id, model->vnd.company_id); + } + } + } + + return example_set_app_idx_to_user_data(app_idx); +} + +esp_err_t example_add_fast_prov_group_address(uint16_t model_id, uint16_t group_addr) +{ + const esp_ble_mesh_comp_t *comp = NULL; + esp_ble_mesh_elem_t *element = NULL; + esp_ble_mesh_model_t *model = NULL; + int i, j; + + if (!ESP_BLE_MESH_ADDR_IS_GROUP(group_addr)) { + return ESP_ERR_INVALID_ARG; + } + + comp = esp_ble_mesh_get_composition_data(); + if (!comp) { + return ESP_FAIL; + } + + for (i = 0; i < comp->element_count; i++) { + element = &comp->elements[i]; + model = esp_ble_mesh_find_sig_model(element, model_id); + if (!model) { + continue; + } + for (j = 0; j < ARRAY_SIZE(model->groups); j++) { + if (model->groups[j] == group_addr) { + break; + } + } + if (j != ARRAY_SIZE(model->groups)) { + ESP_LOGW(TAG, "%s: Group address already exists, element index: %d", __func__, i); + continue; + } + for (j = 0; j < ARRAY_SIZE(model->groups); j++) { + if (model->groups[j] == ESP_BLE_MESH_ADDR_UNASSIGNED) { + model->groups[j] = group_addr; + break; + } + } + if (j == ARRAY_SIZE(model->groups)) { + ESP_LOGE(TAG, "%s: Model is full of group address, element index: %d", __func__, i); + } + } + + return ESP_OK; +} + +esp_err_t example_delete_fast_prov_group_address(uint16_t model_id, uint16_t group_addr) +{ + const esp_ble_mesh_comp_t *comp = NULL; + esp_ble_mesh_elem_t *element = NULL; + esp_ble_mesh_model_t *model = NULL; + int i, j; + + if (!ESP_BLE_MESH_ADDR_IS_GROUP(group_addr)) { + return ESP_ERR_INVALID_ARG; + } + + comp = esp_ble_mesh_get_composition_data(); + if (comp == NULL) { + return ESP_FAIL; + } + + for (i = 0; i < comp->element_count; i++) { + element = &comp->elements[i]; + + model = esp_ble_mesh_find_sig_model(element, model_id); + if (model == NULL) { + continue; + } + for (j = 0; j < ARRAY_SIZE(model->groups); j++) { + if (model->groups[j] == group_addr) { + model->groups[j] = ESP_BLE_MESH_ADDR_UNASSIGNED; + break; + } + } + } + + return ESP_OK; +} + +esp_err_t example_send_config_appkey_add(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + esp_ble_mesh_cfg_app_key_add_t *add_key) +{ + esp_ble_mesh_client_common_param_t common = {0}; + esp_ble_mesh_cfg_client_set_state_t set = {0}; + const uint8_t *key = NULL; + + if (!model || !info) { + return ESP_ERR_INVALID_ARG; + } + + if (add_key) { + set.app_key_add.net_idx = add_key->net_idx; + set.app_key_add.app_idx = add_key->app_idx; + memcpy(set.app_key_add.app_key, add_key->app_key, 16); + } else { +#if defined(CONFIG_BLE_MESH_FAST_PROV) + key = esp_ble_mesh_get_fast_prov_app_key(info->net_idx, info->app_idx); +#endif + if (!key) { + return ESP_FAIL; + } + set.app_key_add.net_idx = info->net_idx; + set.app_key_add.app_idx = info->app_idx; + memcpy(set.app_key_add.app_key, key, 16); + } + + common.opcode = ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD; + common.model = model; + common.ctx.net_idx = info->net_idx; + common.ctx.app_idx = 0x0000; /* not used for config messages */ + common.ctx.addr = info->dst; + common.ctx.send_rel = false; + common.ctx.send_ttl = 0; + common.msg_timeout = info->timeout; + common.msg_role = info->role; + + return esp_ble_mesh_config_client_set_state(&common, &set); +} + +esp_err_t example_send_generic_onoff_get(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info) +{ + esp_ble_mesh_generic_client_get_state_t get = {0}; + esp_ble_mesh_client_common_param_t common = {0}; + + if (!model || !info) { + return ESP_ERR_INVALID_ARG; + } + + common.opcode = ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET; + common.model = model; + common.ctx.net_idx = info->net_idx; + common.ctx.app_idx = info->app_idx; + common.ctx.addr = info->dst; + common.ctx.send_rel = false; + common.ctx.send_ttl = 0; + common.msg_timeout = info->timeout; + common.msg_role = info->role; + + return esp_ble_mesh_generic_client_get_state(&common, &get); +} + +esp_err_t example_send_generic_onoff_set(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + uint8_t onoff, uint8_t tid, bool need_ack) +{ + esp_ble_mesh_generic_client_set_state_t set = {0}; + esp_ble_mesh_client_common_param_t common = {0}; + + if (!model || !info) { + return ESP_ERR_INVALID_ARG; + } + + set.onoff_set.onoff = onoff; + set.onoff_set.tid = tid; + set.onoff_set.op_en = false; + + if (need_ack) { + common.opcode = ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET; + } else { + common.opcode = ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK; + } + common.model = model; + common.ctx.net_idx = info->net_idx; + common.ctx.app_idx = info->app_idx; + common.ctx.addr = info->dst; + common.ctx.send_rel = false; + common.ctx.send_ttl = 0; + common.msg_timeout = info->timeout; + common.msg_role = info->role; + + return esp_ble_mesh_generic_client_set_state(&common, &set); +} + +esp_err_t example_send_fast_prov_info_set(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + example_fast_prov_info_set_t *set) +{ + struct net_buf_simple *msg = NULL; + esp_err_t err; + + if (!model || !set || !set->ctx_flags || !info) { + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGI(TAG, "min: 0x%04x, max: 0x%04x", set->unicast_min, set->unicast_max); + ESP_LOGI(TAG, "flags: 0x%02x, iv_index: 0x%08x", set->flags, set->iv_index); + ESP_LOGI(TAG, "net_idx: 0x%04x, group_addr: 0x%04x", set->net_idx, set->group_addr); + ESP_LOGI(TAG, "action: 0x%02x", set->action); + ESP_LOG_BUFFER_HEX("FAST_PROV_OP: match_val", set->match_val, set->match_len); + + msg = bt_mesh_alloc_buf(18 + set->match_len); + if (!msg) { + return ESP_FAIL; + } + + net_buf_simple_add_le16(msg, set->ctx_flags); + if (set->ctx_flags & BIT(0)) { + net_buf_simple_add_le16(msg, set->node_addr_cnt); + } + if (set->ctx_flags & BIT(1)) { + net_buf_simple_add_le16(msg, set->unicast_min); + } + if (set->ctx_flags & BIT(2)) { + net_buf_simple_add_le16(msg, set->unicast_max); + } + if (set->ctx_flags & BIT(3)) { + net_buf_simple_add_u8(msg, set->flags); + } + if (set->ctx_flags & BIT(4)) { + net_buf_simple_add_le32(msg, set->iv_index); + } + if (set->ctx_flags & BIT(5)) { + net_buf_simple_add_le16(msg, set->net_idx); + } + if (set->ctx_flags & BIT(6)) { + net_buf_simple_add_le16(msg, set->group_addr); + } + if (set->ctx_flags & BIT(7)) { + net_buf_simple_add_le16(msg, set->prov_addr); + } + if (set->ctx_flags & BIT(8)) { + net_buf_simple_add_mem(msg, set->match_val, set->match_len); + } + if (set->ctx_flags & BIT(9)) { + net_buf_simple_add_u8(msg, set->action); + } + + esp_ble_mesh_msg_ctx_t ctx = { + .net_idx = info->net_idx, + .app_idx = info->app_idx, + .addr = info->dst, + .send_rel = false, + .send_ttl = 0, + }; + err = esp_ble_mesh_client_model_send_msg(model, &ctx, + ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET, + msg->len, msg->data, info->timeout, true, info->role); + + bt_mesh_free_buf(msg); + return err; +} + +esp_err_t example_send_fast_prov_net_key_add(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + uint8_t net_key[16]) +{ + if (!model || !info || !net_key) { + return ESP_ERR_INVALID_ARG; + } + + esp_ble_mesh_msg_ctx_t ctx = { + .net_idx = info->net_idx, + .app_idx = info->app_idx, + .addr = info->dst, + .send_rel = false, + .send_ttl = 0, + }; + + return esp_ble_mesh_client_model_send_msg(model, &ctx, + ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD, + 16, net_key, info->timeout, true, info->role); +} + +esp_err_t example_send_fast_prov_self_prov_node_addr(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + struct net_buf_simple *node_addr) +{ + if (!model || !info || !node_addr || (node_addr->len % 2)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_LOG_BUFFER_HEX("Send node address", node_addr->data, node_addr->len); + + esp_ble_mesh_msg_ctx_t ctx = { + .net_idx = info->net_idx, + .app_idx = info->app_idx, + .addr = info->dst, + .send_rel = false, + .send_ttl = 0, + }; + + return esp_ble_mesh_client_model_send_msg(model, &ctx, + ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR, + node_addr->len, node_addr->data, info->timeout, true, info->role); +} + +esp_err_t example_send_fast_prov_all_node_addr_get(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info) +{ + if (!model || !info) { + return ESP_ERR_INVALID_ARG; + } + + esp_ble_mesh_msg_ctx_t ctx = { + .net_idx = info->net_idx, + .app_idx = info->app_idx, + .addr = info->dst, + .send_rel = false, + .send_ttl = 0, + }; + + return esp_ble_mesh_client_model_send_msg(model, &ctx, + ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET, + 0, NULL, info->timeout, true, info->role); +} + +esp_err_t example_send_fast_prov_status_msg(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint32_t opcode, struct net_buf_simple *msg) +{ + if (!model || !ctx) { + return ESP_ERR_INVALID_ARG; + } + + switch (opcode) { + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK: + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS: + ctx->send_ttl = 0; + ctx->send_rel = false; + break; + default: + ESP_LOGW(TAG, "%s: Invalid fast prov status opcode 0x%04x", __func__, opcode); + return ESP_FAIL; + } + + return esp_ble_mesh_server_model_send_msg(model, ctx, opcode, msg ? msg->len : 0, msg ? msg->data : NULL); +} \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.h b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.h new file mode 100644 index 0000000000..3a3135062e --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.h @@ -0,0 +1,69 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_FAST_PROV_OPERATION_H +#define _ESP_FAST_PROV_OPERATION_H + +#include "esp_fast_prov_common.h" + +esp_err_t example_store_node_info(const uint8_t uuid[16], uint16_t node_addr, + uint8_t elem_num, uint16_t net_idx, + uint16_t app_idx, uint8_t onoff); + +example_node_info_t *example_get_node_info(uint16_t node_addr); + +bool example_is_node_exist(const uint8_t uuid[16]); + +uint16_t example_get_node_address(int node_idx); + +esp_ble_mesh_model_t *example_find_model(uint16_t element_addr, uint16_t model_id, + uint16_t company_id); + +esp_err_t example_handle_config_app_key_add_evt(uint16_t app_idx); + +esp_err_t example_add_fast_prov_group_address(uint16_t model_id, uint16_t group_addr); + +esp_err_t example_delete_fast_prov_group_address(uint16_t model_id, uint16_t group_addr); + +esp_err_t example_send_config_appkey_add(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + esp_ble_mesh_cfg_app_key_add_t *add_key); + +esp_err_t example_send_generic_onoff_get(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info); + +esp_err_t example_send_generic_onoff_set(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + uint8_t onoff, uint8_t tid, bool need_ack); + +esp_err_t example_send_fast_prov_info_set(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + example_fast_prov_info_set_t *set); + +esp_err_t example_send_fast_prov_net_key_add(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + uint8_t net_key[16]); + +esp_err_t example_send_fast_prov_self_prov_node_addr(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info, + struct net_buf_simple *node_addr); + +esp_err_t example_send_fast_prov_all_node_addr_get(esp_ble_mesh_model_t *model, + example_msg_common_info_t *info); + +esp_err_t example_send_fast_prov_status_msg(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, + uint32_t opcode, struct net_buf_simple *msg); + +#endif /* _ESP_FAST_PROV_OPERATION_H */ \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.c b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.c new file mode 100644 index 0000000000..99f4facff5 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.c @@ -0,0 +1,640 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "esp_ble_mesh_defs.h" +#include "esp_ble_mesh_local_data_operation_api.h" +#include "esp_ble_mesh_provisioning_api.h" +#include "esp_ble_mesh_networking_api.h" +#include "esp_ble_mesh_proxy_api.h" +#include "esp_ble_mesh_config_model_api.h" + +#include "esp_fast_prov_operation.h" +#include "esp_fast_prov_server_model.h" + +#define TAG "FAST_PROV_SERVER" + +/* Array used to store all node addresses */ +static uint16_t all_node_addr[120]; +static uint16_t all_node_addr_cnt; + +esp_err_t example_store_remote_node_address(uint16_t node_addr) +{ + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(node_addr)) { + ESP_LOGE(TAG, "%s: Not a unicast address", __func__); + return ESP_ERR_INVALID_ARG; + } + + for (int i = 0; i < ARRAY_SIZE(all_node_addr); i++) { + if (all_node_addr[i] == node_addr) { + ESP_LOGW(TAG, "%s: Node address 0x%04x already exists", __func__, node_addr); + return ESP_OK; + } + } + + for (int i = 0; i < ARRAY_SIZE(all_node_addr); i++) { + if (all_node_addr[i] == ESP_BLE_MESH_ADDR_UNASSIGNED) { + all_node_addr[i] = node_addr; + all_node_addr_cnt++; + return ESP_OK; + } + } + + ESP_LOGE(TAG, "%s: remote node address queue is full", __func__); + return ESP_FAIL; +} + +esp_ble_mesh_cfg_srv_t *get_cfg_srv_user_data(void) +{ + esp_ble_mesh_model_t *model = NULL; + + model = example_find_model(esp_ble_mesh_get_primary_element_address(), + ESP_BLE_MESH_MODEL_ID_CONFIG_SRV, CID_NVAL); + if (!model) { + ESP_LOGE(TAG, "%s: Failed to get config server model", __func__); + return NULL; + } + + return (esp_ble_mesh_cfg_srv_t *)(model->user_data); +} + +/* Timeout handler for disable_fast_prov_timer */ +static void disable_fast_prov_cb(struct k_work *work) +{ + example_fast_prov_server_t *srv = NULL; + + srv = CONTAINER_OF(work, example_fast_prov_server_t, disable_fast_prov_timer.work); + if (!srv) { + ESP_LOGE(TAG, "%s: Failed to get fast prov server model user_data", __func__); + return; + } + + if (esp_ble_mesh_set_fast_prov_action(FAST_PROV_ACT_SUSPEND)) { + ESP_LOGE(TAG, "%s: Failed to disable fast provisioning", __func__); + return; + } +} + +/* Timeout handler for gatt_proxy_enable_timer */ +static void enable_gatt_proxy_cb(struct k_work *work) +{ + example_fast_prov_server_t *srv = NULL; + + srv = CONTAINER_OF(work, example_fast_prov_server_t, gatt_proxy_enable_timer.work); + if (!srv) { + ESP_LOGE(TAG, "%s: Failed to get fast prov server model user_data", __func__); + return; + } + + if (bt_mesh_atomic_test_and_clear_bit(srv->srv_flags, RELAY_PROXY_DISABLED)) { + ESP_LOGI(TAG, "%s: Enable BLE Mesh Relay & GATT Proxy", __func__); + /* For Primary Provisioner, Relay will not be enabled */ + esp_ble_mesh_proxy_gatt_enable(); + esp_ble_mesh_proxy_identity_enable(); + } + + return; +} + +static void example_free_set_info(example_fast_prov_server_t *srv) +{ + if (srv && srv->set_info) { + osi_free(srv->set_info); + srv->set_info = NULL; + } +} + +esp_err_t example_fast_prov_server_recv_msg(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, struct net_buf_simple *buf) +{ + example_fast_prov_server_t *srv = NULL; + struct net_buf_simple *msg = NULL; + uint32_t opcode = 0; + esp_err_t err; + + if (!model || !model->user_data || !ctx || !buf) { + return ESP_ERR_INVALID_ARG; + } + + srv = (example_fast_prov_server_t *)model->user_data; + + ESP_LOG_BUFFER_HEX("fast prov server recv", buf->data, buf->len); + + switch (ctx->recv_op) { + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_SET: { + /* fast prov info status (maximum 9 octets): + * status_bit_mask (2) + status_ctx_flag (1) + status_unicast (1) + status_net_idx (1) + + * status_group (1) + status_pri_prov (1) + status_match (1) + status_action (1). + */ + uint8_t match_len = 0, match_val[16]; + uint8_t status_unicast = 0; + uint8_t flags = 0; + + msg = bt_mesh_alloc_buf(9); + opcode = ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS; + + if (srv->set_info) { + ESP_LOGW(TAG, "%s: Set fast prov info is already in progress", __func__); + net_buf_simple_add_le16(msg, 0xFFFF); + break; + } + + /* If fast prov server state is pending, can not set fast prov info, + * and send response message with all status set to 0x01 (i.e. fail). + */ + if (srv->state == STATE_PEND) { + uint8_t val[7] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; + net_buf_simple_add_le16(msg, 0x7f); + net_buf_simple_add_mem(msg, val, sizeof(val)); + break; + } + + uint16_t ctx_flags = net_buf_simple_pull_le16(buf); + if (ctx_flags == 0) { + net_buf_simple_add_le16(msg, BIT(0)); + net_buf_simple_add_u8(msg, 0x01); /* invalid ctx_flags */ + break; + } + + uint16_t node_addr_cnt = (ctx_flags & BIT(0)) ? net_buf_simple_pull_le16(buf) : 0x0; + uint16_t unicast_min = (ctx_flags & BIT(1)) ? net_buf_simple_pull_le16(buf) : (esp_ble_mesh_get_primary_element_address() + esp_ble_mesh_get_element_count()); + uint16_t unicast_max = (ctx_flags & BIT(2)) ? net_buf_simple_pull_le16(buf) : 0x7FFF; + if (ctx_flags & BIT(3)) { + flags = net_buf_simple_pull_u8(buf); + } else { + flags = (uint8_t)bt_mesh.sub[0].kr_flag; + if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS)) { + flags |= BLE_MESH_NET_FLAG_IVU; + } + } + uint32_t iv_index = (ctx_flags & BIT(4)) ? net_buf_simple_pull_le32(buf) : bt_mesh.iv_index; + uint16_t net_idx = (ctx_flags & BIT(5)) ? net_buf_simple_pull_le16(buf) : srv->net_idx; + uint16_t group_addr = (ctx_flags & BIT(6)) ? net_buf_simple_pull_le16(buf) : ESP_BLE_MESH_ADDR_UNASSIGNED; + uint16_t pri_prov_addr = (ctx_flags & BIT(7)) ? net_buf_simple_pull_le16(buf) : ESP_BLE_MESH_ADDR_UNASSIGNED; + if (ctx_flags & BIT(8)) { + match_len = buf->len - ((ctx_flags & BIT(9)) ? 1 : 0); + memcpy(match_val, buf->data, match_len); + net_buf_simple_pull(buf, match_len); + } + uint8_t action = (ctx_flags & BIT(9)) ? net_buf_simple_pull_u8(buf) : FAST_PROV_ACT_NONE; + + /* If fast prov server state is active, the device can only suspend or exit fast provisioning */ + if (srv->state == STATE_ACTIVE) { + net_buf_simple_add_le16(msg, BIT(6)); + switch (action & BIT_MASK(2)) { + case FAST_PROV_ACT_SUSPEND: + case FAST_PROV_ACT_EXIT: + srv->pend_act = action & BIT_MASK(2); + if (!bt_mesh_atomic_test_and_set_bit(srv->srv_flags, DISABLE_FAST_PROV_START)) { + k_delayed_work_submit(&srv->disable_fast_prov_timer, DISABLE_FAST_PROV_TIMEOUT); + } + net_buf_simple_add_u8(msg, 0x00); /* action succeed */ + break; + default: + net_buf_simple_add_u8(msg, 0x04); /* action already in progress */ + break; + } + break; + } + + if ((ctx_flags & BIT(1)) || (ctx_flags & BIT(2))) { + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(unicast_min) || !ESP_BLE_MESH_ADDR_IS_UNICAST(unicast_max)) { + status_unicast = 0x01; /* not a unicast address */ + } else if (unicast_min > unicast_max) { + status_unicast = 0x02; /* min bigger than max */ + } else if (unicast_min < (esp_ble_mesh_get_primary_element_address() + esp_ble_mesh_get_element_count())) { + status_unicast = 0x04; /* overlap with own element address */ + } + if (status_unicast) { + net_buf_simple_add_le16(msg, BIT(1)); + net_buf_simple_add_u8(msg, status_unicast); + break; + } + } + + if (ctx_flags & BIT(6)) { + if (!ESP_BLE_MESH_ADDR_IS_GROUP(group_addr)) { + net_buf_simple_add_le16(msg, BIT(3)); + net_buf_simple_add_u8(msg, 0x01); /* not a group address */ + break; + } + err = example_add_fast_prov_group_address(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, group_addr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to add group address 0x%04x", __func__, group_addr); + net_buf_simple_add_le16(msg, BIT(3)); + net_buf_simple_add_u8(msg, 0x02); /* add group address failed */ + break; + } + srv->group_addr = group_addr; + } + + if (ctx_flags & BIT(7)) { + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(pri_prov_addr)) { + net_buf_simple_add_le16(msg, BIT(4)); + net_buf_simple_add_u8(msg, 0x01); /* not a unicast address */ + break; + } + } + + if (ctx_flags & BIT(8)) { + if (match_len > 16) { + net_buf_simple_add_le16(msg, BIT(5)); + net_buf_simple_add_u8(msg, 0x01); /* too large match value length */ + break; + } + } + + if (ctx_flags & BIT(9)) { + if ((action & BIT_MASK(2)) != FAST_PROV_ACT_ENTER) { + net_buf_simple_add_le16(msg, BIT(6)); + net_buf_simple_add_u8(msg, 0x01); /* action failed */ + break; + } + } else { + net_buf_simple_add_le16(msg, BIT(6)); + net_buf_simple_add_u8(msg, 0x03); /* none action */ + break; + } + + memcpy(&srv->ctx, ctx, sizeof(esp_ble_mesh_msg_ctx_t)); + srv->set_info = osi_calloc(sizeof(struct fast_prov_info_set)); + if (!srv->set_info) { + ESP_LOGE(TAG, "%s: Failed to allocate memory", __func__); + bt_mesh_free_buf(msg); + return ESP_FAIL; + } + + if (unicast_max < unicast_min + srv->max_node_num - 1) { + srv->max_node_num = unicast_max - unicast_min + 1; + } + + srv->set_info->set_succeed = false; + srv->set_info->node_addr_cnt = node_addr_cnt; + srv->set_info->unicast_min = unicast_min; + srv->set_info->unicast_max = unicast_max; + srv->set_info->flags = flags; + srv->set_info->iv_index = iv_index; + srv->set_info->net_idx = net_idx; + srv->set_info->pri_prov_addr = pri_prov_addr; + srv->set_info->match_len = match_len; + memcpy(srv->set_info->match_val, match_val, match_len); + srv->set_info->action = action; + + esp_ble_mesh_fast_prov_info_t info_set = { + .unicast_min = unicast_min, + .unicast_max = unicast_min + srv->max_node_num - 1, + .flags = flags, + .iv_index = iv_index, + .net_idx = net_idx, + .offset = 0x00, + .match_len = match_len, + }; + memcpy(info_set.match_val, match_val, match_len); + err = esp_ble_mesh_set_fast_prov_info(&info_set); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to set fast prov info", __func__); + net_buf_simple_add_le16(msg, BIT(5) | BIT(2) | BIT(1)); + net_buf_simple_add_u8(msg, 0x01); /* set unicast failed */ + net_buf_simple_add_u8(msg, 0x01); /* set net_idx failed */ + net_buf_simple_add_u8(msg, 0x01); /* set UUID match failed */ + break; + } + /* If setting fast prov info successfully, wait for the event callback */ + bt_mesh_free_buf(msg); + return ESP_OK; + } + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_ADD: { + uint8_t status_net_key, status_action; + + msg = bt_mesh_alloc_buf(2); + + if (srv->state == STATE_PEND) { + /* Add fast prov net_key, wait for event callback and call esp_ble_mesh_set_fast_prov_act() to set action */ + } else { + /* If state is not pending, can not add net_key */ + status_net_key = 0x01; /* status_net_key: fail */ + status_action = 0x01; /* status_action: fail */ + } + + opcode = ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS; + net_buf_simple_add_u8(msg, status_net_key); + net_buf_simple_add_u8(msg, status_action); + break; + } + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR: { + if (buf->len % 2) { + ESP_LOGE(TAG, "%s: Invalid Fast Prov Node Addr message length", __func__); + return ESP_FAIL; + } + + if (bt_mesh_atomic_test_and_clear_bit(srv->srv_flags, GATT_PROXY_ENABLE_START)) { + k_delayed_work_cancel(&srv->gatt_proxy_enable_timer); + } + + for (; buf->len; ) { + uint16_t node_addr = net_buf_simple_pull_le16(buf); + ESP_LOGI(TAG, "Node address: 0x%04x", node_addr); + err = example_store_remote_node_address(node_addr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to store node address 0x%04x", __func__, node_addr); + } + } + + opcode = ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK; + break; + } + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_GET: { + /* Top device (e.g. phone) tries to get all node addresses */ + msg = bt_mesh_alloc_buf(all_node_addr_cnt * 2); + if (!msg) { + ESP_LOGE(TAG, "%s: Failed to allocate memory", __func__); + return ESP_FAIL; + } + + for (int i = 0; i < all_node_addr_cnt; i++) { + net_buf_simple_add_le16(msg, all_node_addr[i]); + } + ESP_LOG_BUFFER_HEX("All node address", msg->data, msg->len); + + opcode = ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS; + break; + } + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_GROUP_ADD: { + uint16_t own_addr = esp_ble_mesh_get_primary_element_address(); + uint16_t group_addr = net_buf_simple_pull_le16(buf); + ESP_LOGI(TAG, "%s, group address 0x%04x", __func__, group_addr); + if (!ESP_BLE_MESH_ADDR_IS_GROUP(group_addr)) { + return ESP_FAIL; + } + for (; buf->len; ) { + uint16_t dst = net_buf_simple_pull_le16(buf); + ESP_LOGI(TAG, "%s, dst 0x%04x, own address 0x%04x", __func__, dst, own_addr); + if (dst == own_addr) { + err = example_add_fast_prov_group_address(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, group_addr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to add group address 0x%04x", __func__, group_addr); + } + return err; + } + } + return ESP_OK; + } + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_GROUP_DELETE: { + uint16_t own_addr = esp_ble_mesh_get_primary_element_address(); + uint16_t group_addr = net_buf_simple_pull_le16(buf); + ESP_LOGI(TAG, "%s, group address 0x%04x", __func__, group_addr); + if (!ESP_BLE_MESH_ADDR_IS_GROUP(group_addr)) { + return ESP_FAIL; + } + for (; buf->len; ) { + uint16_t dst = net_buf_simple_pull_le16(buf); + ESP_LOGI(TAG, "%s, dst 0x%04x, own address 0x%04x", __func__, dst, own_addr); + if (dst == own_addr) { + err = example_delete_fast_prov_group_address(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, group_addr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to delete group address 0x%04x", __func__, group_addr); + } + return err; + } + } + return ESP_OK; + } + default: + ESP_LOGW(TAG, "%s: Not a Fast Prov Client message opcode", __func__); + return ESP_FAIL; + } + + err = example_send_fast_prov_status_msg(model, ctx, opcode, msg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Fast Prov Status message", __func__); + example_free_set_info(srv); + } + + bt_mesh_free_buf(msg); + return err; +} + +esp_err_t example_handle_fast_prov_info_set_comp_evt(esp_ble_mesh_model_t *model, uint8_t status_unicast, + uint8_t status_net_idx, uint8_t status_match) +{ + example_fast_prov_server_t *srv = NULL; + struct net_buf_simple *msg = NULL; + esp_err_t err; + + if (!model || !model->user_data) { + return ESP_ERR_INVALID_ARG; + } + + srv = (example_fast_prov_server_t *)model->user_data; + if (!srv->set_info) { + return ESP_FAIL; + } + + msg = bt_mesh_alloc_buf(9); + + if (status_unicast || status_match) { + net_buf_simple_add_le16(msg, BIT(5) | BIT(1)); + net_buf_simple_add_u8(msg, status_unicast); + net_buf_simple_add_u8(msg, status_match); + goto send; + } + + /* Update Fast Prov Server Model user_data */ + srv->unicast_min = srv->set_info->unicast_min + srv->max_node_num; + srv->unicast_max = srv->set_info->unicast_max; + srv->unicast_cur = srv->set_info->unicast_min + srv->max_node_num; + if (srv->unicast_max <= srv->unicast_min) { + srv->unicast_step = 0; + } else { + srv->unicast_step = (srv->unicast_max - srv->unicast_min) / srv->max_node_num; + } + srv->flags = srv->set_info->flags; + srv->iv_index = srv->set_info->iv_index; + srv->net_idx = srv->set_info->net_idx; + if (srv->set_info->action & BIT(7)) { + srv->primary_role = true; + srv->node_addr_cnt = srv->set_info->node_addr_cnt; + srv->prim_prov_addr = esp_ble_mesh_get_primary_element_address(); + srv->top_address = srv->ctx.addr; + } else { + srv->primary_role = false; + srv->prim_prov_addr = srv->set_info->pri_prov_addr; + } + srv->match_len = srv->set_info->match_len; + memcpy(srv->match_val, srv->set_info->match_val, srv->set_info->match_len); + + if (status_net_idx) { + ESP_LOGW(TAG, "%s: Wait for fast prov netkey to be added", __func__); + srv->pend_act = FAST_PROV_ACT_ENTER; + srv->state = STATE_PEND; + net_buf_simple_add_le16(msg, BIT(6) | BIT(2)); + net_buf_simple_add_u8(msg, status_net_idx); /* wait for net_key */ + net_buf_simple_add_u8(msg, 0x02); /* pending action */ + goto send; + } + + /* Sets fast prov action */ + err = esp_ble_mesh_set_fast_prov_action(FAST_PROV_ACT_ENTER); + if (err == ESP_OK) { + bt_mesh_free_buf(msg); + return ESP_OK; + } + + ESP_LOGE(TAG, "%s: Failed to set fast prov action", __func__); + net_buf_simple_add_le16(msg, BIT(6)); + net_buf_simple_add_u8(msg, 0x01); /* action failed */ + +send: + err = example_send_fast_prov_status_msg(model, &srv->ctx, ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS, msg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Fast Prov Status message", __func__); + example_free_set_info(srv); + } + + bt_mesh_free_buf(msg); + return err; +} + +esp_err_t example_handle_fast_prov_action_set_comp_evt(esp_ble_mesh_model_t *model, uint8_t status_action) +{ + example_fast_prov_server_t *srv = NULL; + struct net_buf_simple *msg = NULL; + uint32_t opcode; + esp_err_t err; + + if (!model || !model->user_data) { + return ESP_ERR_INVALID_ARG; + } + + srv = (example_fast_prov_server_t *)model->user_data; + + msg = bt_mesh_alloc_buf(9); + + switch (srv->state) { + case STATE_IDLE: { /* fast prov info set (enter) */ + const uint8_t zero[6] = {0}; + net_buf_simple_add_le16(msg, 0x7f); + net_buf_simple_add_mem(msg, zero, 6); + net_buf_simple_add_u8(msg, status_action); + opcode = ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS; + /** + * Disable relay should not have a impact on Mesh Proxy PDU, and + * we can also move "disabling relay" in the event of "disabling + * gatt proxy" here. + */ + if (srv->node_addr_cnt == FAST_PROV_NODE_COUNT_MIN) { + if (bt_mesh_atomic_test_and_clear_bit(srv->srv_flags, GATT_PROXY_ENABLE_START)) { + k_delayed_work_cancel(&srv->gatt_proxy_enable_timer); + } + if (!bt_mesh_atomic_test_and_set_bit(srv->srv_flags, GATT_PROXY_ENABLE_START)) { + k_delayed_work_submit(&srv->gatt_proxy_enable_timer, K_SECONDS(3)); + } + } + break; + } + case STATE_ACTIVE: /* fast prov info set (suspend/exit) */ + /* Currently we only support suspend/exit fast prov after Generic + * OnOff Set/Set Unack is received. So no fast prov status message + * will be sent. + */ + case STATE_PEND: /* fast prov net_key add */ + /* In this case, we should send fast prov net_key status */ + default: + bt_mesh_free_buf(msg); + return ESP_OK; + } + + err = example_send_fast_prov_status_msg(model, &srv->ctx, opcode, msg); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to send Fast Prov Status message", __func__); + example_free_set_info(srv); + bt_mesh_free_buf(msg); + return ESP_FAIL; + } + + if (status_action == 0x00) { + if (srv->state == STATE_IDLE || srv->state == STATE_PEND) { + srv->state = STATE_ACTIVE; + } else if (srv->state == STATE_ACTIVE) { + srv->state = STATE_IDLE; + } + if (srv->set_info) { + srv->set_info->set_succeed = true; + } + } + + bt_mesh_free_buf(msg); + return ESP_OK; +} + +esp_err_t example_handle_fast_prov_status_send_comp_evt(int err_code, uint32_t opcode, + esp_ble_mesh_model_t *model, esp_ble_mesh_msg_ctx_t *ctx) +{ + example_fast_prov_server_t *srv = NULL; + + if (!model || !model->user_data) { + return ESP_ERR_INVALID_ARG; + } + + srv = (example_fast_prov_server_t *)model->user_data; + + ESP_LOGI(TAG, "%s: opcode 0x%06x", __func__, opcode); + + switch (opcode) { + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_INFO_STATUS: + if (err_code == 0 && srv->set_info && srv->set_info->set_succeed == true) { + if (!bt_mesh_atomic_test_and_set_bit(srv->srv_flags, RELAY_PROXY_DISABLED)) { + /* For Primary Provisioner: disable Relay and GATT Proxy; + * For other Provisioners: only disable GATT Proxy + */ + ESP_LOGW(TAG, "%s: Disable BLE Mesh Relay & GATT Proxy", __func__); + esp_ble_mesh_proxy_gatt_disable(); + } + } + example_free_set_info(srv); + break; + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NET_KEY_STATUS: + break; + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_ACK: + if (!bt_mesh_atomic_test_and_set_bit(srv->srv_flags, GATT_PROXY_ENABLE_START)) { + k_delayed_work_submit(&srv->gatt_proxy_enable_timer, GATT_PROXY_ENABLE_TIMEOUT); + } + break; + case ESP_BLE_MESH_VND_MODEL_OP_FAST_PROV_NODE_ADDR_STATUS: + break; + default: + break; + } + + return ESP_OK; +} + +esp_err_t example_fast_prov_server_init(esp_ble_mesh_model_t *model) +{ + example_fast_prov_server_t *srv = NULL; + + if (!model || !model->user_data) { + return ESP_ERR_INVALID_ARG; + } + + srv = (example_fast_prov_server_t *)model->user_data; + srv->model = model; + + k_delayed_work_init(&srv->disable_fast_prov_timer, disable_fast_prov_cb); + k_delayed_work_init(&srv->gatt_proxy_enable_timer, enable_gatt_proxy_cb); + + return ESP_OK; +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.h b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.h new file mode 100644 index 0000000000..b4dec616a6 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.h @@ -0,0 +1,102 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_FAST_PROV_SERVER_MODEL_H +#define _ESP_FAST_PROV_SERVER_MODEL_H + +#include "esp_fast_prov_common.h" + +#define DISABLE_FAST_PROV_TIMEOUT K_SECONDS(10) +#define GATT_PROXY_ENABLE_TIMEOUT K_SECONDS(10) + +#define FAST_PROV_NODE_COUNT_MIN 0x01 + +enum { + DISABLE_FAST_PROV_START, /* Flag indicates the timer used to disable fast provisioning has been started */ + GATT_PROXY_ENABLE_START, /* Flag indicates the timer used to enable Mesh GATT Proxy has been started */ + RELAY_PROXY_DISABLED, /* Flag indicates if relay & proxy_adv are enabled or disabled */ + SRV_MAX_FLAGS, +}; + +enum { + STATE_IDLE, + STATE_PEND, + STATE_ACTIVE, + STATE_MAX, +}; + +struct fast_prov_info_set { + bool set_succeed; + uint16_t node_addr_cnt; + uint16_t unicast_min; + uint16_t unicast_max; + uint8_t flags; + uint32_t iv_index; + uint16_t net_idx; + uint16_t group_addr; + uint16_t pri_prov_addr; + uint8_t match_val[16]; + uint8_t match_len; + uint8_t action; +}; + +typedef struct { + esp_ble_mesh_model_t *model; /* Fast Prov Server model pointer */ + BLE_MESH_ATOMIC_DEFINE(srv_flags, SRV_MAX_FLAGS); + + bool primary_role; /* Indicate if the device is a Primary Provisioner */ + uint8_t max_node_num; /* The maximum number of devices can be provisioned by the Provisioner */ + uint8_t prov_node_cnt; /* Number of self-provisioned nodes */ + uint16_t app_idx; /* AppKey index of the application key added by other Provisioner */ + uint16_t top_address; /* Address of the device(e.g. phone) which triggers fast provisioning */ + + esp_ble_mesh_msg_ctx_t ctx; /* the context stored for sending fast prov status message */ + struct fast_prov_info_set *set_info; /* Used to store received fast prov info set context */ + + uint16_t node_addr_cnt; /* Number of node address shall be received */ + uint16_t unicast_min; /* Minimum unicast address can be send to other nodes */ + uint16_t unicast_max; /* Maximum unicast address can be send to other nodes */ + uint16_t unicast_cur; /* Current unicast address can be assigned */ + uint16_t unicast_step; /* Unicast address change step */ + uint8_t flags; /* Flags state */ + uint32_t iv_index; /* Iv_index state */ + uint16_t net_idx; /* Netkey index state */ + uint16_t group_addr; /* Subscribed group address */ + uint16_t prim_prov_addr; /* Unicast address of Primary Provisioner */ + uint8_t match_val[16]; /* Match value to be compared with unprovisioned device UUID */ + uint8_t match_len; /* Length of match value to be compared */ + + uint8_t pend_act; /* Pending action to be performed */ + uint8_t state; /* Fast prov state -> 0: idle, 1: pend, 2: active */ + + struct k_delayed_work disable_fast_prov_timer; /* Used to disable fast provisioning */ + struct k_delayed_work gatt_proxy_enable_timer; /* Used to enable Mesh GATT Proxy functionality */ +} __attribute__((packed)) example_fast_prov_server_t; + +esp_err_t example_store_remote_node_address(uint16_t node_addr); + +esp_err_t example_fast_prov_server_recv_msg(esp_ble_mesh_model_t *model, + esp_ble_mesh_msg_ctx_t *ctx, struct net_buf_simple *buf); + +esp_err_t example_handle_fast_prov_info_set_comp_evt(esp_ble_mesh_model_t *model, uint8_t status_unicast, + uint8_t status_net_idx, uint8_t status_match); + +esp_err_t example_handle_fast_prov_action_set_comp_evt(esp_ble_mesh_model_t *model, uint8_t status_action); + +esp_err_t example_handle_fast_prov_status_send_comp_evt(int err_code, uint32_t opcode, + esp_ble_mesh_model_t *model, esp_ble_mesh_msg_ctx_t *ctx); + +esp_err_t example_fast_prov_server_init(esp_ble_mesh_model_t *model); + +#endif /* _ESP_FAST_PROV_SERVER_MODEL_H */ \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/CMakeLists.txt new file mode 100644 index 0000000000..f475ba4612 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "main.c") + +register_component() diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/component.mk b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/component.mk new file mode 100644 index 0000000000..d68c5375e9 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) +# \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/main.c b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/main.c new file mode 100644 index 0000000000..889f706f78 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/main.c @@ -0,0 +1,21 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +void app_main(void) +{ + /* This main.c is for CI. The fast_prov_vendor_model shall be + * included by other ble mesh examples which need vendor fast + * provisioning client/server models. + */ +} diff --git a/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/sdkconfig.defaults b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/sdkconfig.defaults new file mode 100644 index 0000000000..da5392232d --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/sdkconfig.defaults @@ -0,0 +1,33 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_FAST_PROV=y +CONFIG_BLE_MESH_PROV=y +CONFIG_BLE_MESH_NODE=y +CONFIG_BLE_MESH_PROVISIONER=y +CONFIG_BLE_MESH_WAIT_FOR_PROV_MAX_DEV_NUM=20 +CONFIG_BLE_MESH_MAX_STORED_NODES=10 +CONFIG_BLE_MESH_MAX_PROV_NODES=6 +CONFIG_BLE_MESH_PBA_SAME_TIME=3 +CONFIG_BLE_MESH_PBG_SAME_TIME=3 +CONFIG_BLE_MESH_PROVISIONER_SUBNET_COUNT=3 +CONFIG_BLE_MESH_PROVISIONER_APP_KEY_COUNT=9 +CONFIG_BLE_MESH_PB_ADV=y +CONFIG_BLE_MESH_NET_BUF_POOL_USAGE=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_GATT_PROXY=y +CONFIG_BLE_MESH_RELAY=y +CONFIG_BLE_MESH_LOW_POWER= +CONFIG_BLE_MESH_FRIEND= +CONFIG_BTU_TASK_STACK_SIZE=4512 +CONFIG_BLE_MESH_CFG_CLI=y +CONFIG_BLE_MESH_CRPL=60 +CONFIG_BLE_MESH_MSG_CACHE_SIZE=60 +CONFIG_BLE_MESH_ADV_BUF_COUNT=200 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SDU_MAX=384 +CONFIG_BLE_MESH_TX_SEG_MAX=32 +CONFIG_BLE_MESH_NO_LOG=n +CONFIG_BLE_MESH_STACK_TRACE_LEVEL=0 \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/CMakeLists.txt new file mode 100644 index 0000000000..8f5e74e32f --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_mesh_wifi_coexist) diff --git a/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/Makefile b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/Makefile new file mode 100644 index 0000000000..2db430c14f --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/Makefile @@ -0,0 +1,12 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_mesh_wifi_coexist + +COMPONENT_ADD_INCLUDEDIRS := components/include + +EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/bluetooth/ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/README.md b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/README.md new file mode 100644 index 0000000000..8173486ffa --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/README.md @@ -0,0 +1,20 @@ +ESP BLE Mesh and WiFi Coexist example +======================== + +此demo是用来测试当BLE Mesh实现正常的配网和收发消息的正常功能时,WiFi 所能达到的最大throughput值。用户可以根据此demo中的throughput值来实现自己需要的应用场景。如果用户对ESP32 BLE Mesh使用方法还不熟悉的话, 可以参考[BLE Mesh brief introduction](../ble_mesh_node/README.md) + +此demo主要分为两个部分: + +- WiFi Iperf协议 +- BLE Mesh fast provision Server + +WiFi的Iperf协议使用方法请参考[WiFi Iperf README](../../../wifi/iperf/README.md) + +当WiFi的Iperf开起来之后,BLE Mesh就可以使用fast provsion 进行配网了,此demo只实现了fast provsion 的server功能。此demo 的BLE Mesh模块实现了一个Element和以下几个Model: + +- Config Server Model: 此Model是当对方需要配置APP Key, Dev Key,等信息时,需要使用此Model +- Config Client Model: 此Model是当需要配置APP Key, Dev Key,等信息时,需要使用此Model +- Generic OnOff Server Model:此Model通过暴露自己的OnOff State,从而实现LED 灯的开关功能 +- Generic OnOff Client Model: 使用此Model可以实现开关功能,控制别的node 的LED灯的开关 +- Fast Provision Server Model: 此Model是为了进行快速配网而实现的自定义Model,通过此Model当节点作为临时provisioner进行配网成功后,需要将生成的Element地址通过此Model进行传给provisioner +- Fast Provision Client Model:此Model和Fast Provision Server Model是配合使用的 \ No newline at end of file diff --git a/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/CMakeLists.txt b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/CMakeLists.txt new file mode 100644 index 0000000000..5073db6d0f --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/CMakeLists.txt @@ -0,0 +1,8 @@ +set(COMPONENT_SRCS "cmd_wifi.c" + "iperf.c") + +set(COMPONENT_ADD_INCLUDEDIRS .) + +set(COMPONENT_REQUIRES lwip console) + +register_component() diff --git a/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_decl.h b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_decl.h new file mode 100644 index 0000000000..78707eabd5 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_decl.h @@ -0,0 +1,14 @@ +/* Iperf example — declarations of command registration functions. + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#pragma once + +// Register WiFi functions +void register_wifi(void); +void initialise_wifi(void); + diff --git a/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_wifi.c b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_wifi.c new file mode 100644 index 0000000000..8d258dc923 --- /dev/null +++ b/examples/bluetooth/ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_wifi.c @@ -0,0 +1,477 @@ +/* Iperf Example - wifi commands + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "cmd_decl.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_wifi.h" +#include "tcpip_adapter.h" +#include "esp_event_loop.h" +#include "iperf.h" + +typedef struct { + struct arg_str *ip; + struct arg_lit *server; + struct arg_lit *udp; + struct arg_int *port; + struct arg_int *interval; + struct arg_int *time; + struct arg_lit *abort; + struct arg_end *end; +} wifi_iperf_t; +static wifi_iperf_t iperf_args; + +typedef struct { + struct arg_str *ssid; + struct arg_str *password; + struct arg_end *end; +} wifi_args_t; + +typedef struct { + struct arg_str *ssid; + struct arg_end *end; +} wifi_scan_arg_t; + +static wifi_args_t sta_args; +static wifi_scan_arg_t scan_args; +static wifi_args_t ap_args; +static bool reconnect = true; +static const char *TAG = "iperf"; + +static EventGroupHandle_t wifi_event_group; +const int CONNECTED_BIT = BIT0; +const int DISCONNECTED_BIT = BIT1; + +static void scan_done_handler(void) +{ + uint16_t sta_number = 0; + uint8_t i; + wifi_ap_record_t *ap_list_buffer; + + esp_wifi_scan_get_ap_num(&sta_number); + ap_list_buffer = malloc(sta_number * sizeof(wifi_ap_record_t)); + if (ap_list_buffer == NULL) { + ESP_LOGE(TAG, "Failed to malloc buffer to print scan results"); + return; + } + + if (esp_wifi_scan_get_ap_records(&sta_number, (wifi_ap_record_t *)ap_list_buffer) == ESP_OK) { + for (i = 0; i < sta_number; i++) { + ESP_LOGI(TAG, "[%s][rssi=%d]", ap_list_buffer[i].ssid, ap_list_buffer[i].rssi); + } + } + free(ap_list_buffer); +} + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupClearBits(wifi_event_group, DISCONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + ESP_LOGI(TAG, "got ip"); + break; + case SYSTEM_EVENT_SCAN_DONE: + scan_done_handler(); + ESP_LOGI(TAG, "sta scan done"); + break; + case SYSTEM_EVENT_STA_CONNECTED: + ESP_LOGI(TAG, "L2 connected"); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + if (reconnect) { + ESP_LOGI(TAG, "sta disconnect, reconnect..."); + esp_wifi_connect(); + } else { + ESP_LOGI(TAG, "sta disconnect"); + } + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, DISCONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void initialise_wifi(void) +{ + esp_log_level_set("wifi", ESP_LOG_WARN); + static bool initialized = false; + + if (initialized) { + return; + } + + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_ps(WIFI_PS_MIN_MODEM) ); //must call this + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_start() ); + initialized = true; +} + +static bool wifi_cmd_sta_join(const char *ssid, const char *pass) +{ + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + + wifi_config_t wifi_config = { 0 }; + + strlcpy((char *) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); + if (pass) { + strncpy((char *) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password)); + } + + if (bits & CONNECTED_BIT) { + reconnect = false; + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + ESP_ERROR_CHECK( esp_wifi_disconnect() ); + xEventGroupWaitBits(wifi_event_group, DISCONNECTED_BIT, 0, 1, portTICK_RATE_MS); + } + + reconnect = true; + esp_wifi_disconnect(); + //ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); //by snake + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_connect() ); + + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 5000 / portTICK_RATE_MS); + + return true; +} + +static int wifi_cmd_sta(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &sta_args); + + if (nerrors != 0) { + arg_print_errors(stderr, sta_args.end, argv[0]); + return 1; + } + + ESP_LOGI(TAG, "sta connecting to '%s'", sta_args.ssid->sval[0]); + wifi_cmd_sta_join(sta_args.ssid->sval[0], sta_args.password->sval[0]); + return 0; +} + +static bool wifi_cmd_sta_scan(const char *ssid) +{ + wifi_scan_config_t scan_config = { 0 }; + scan_config.ssid = (uint8_t *) ssid; + + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_scan_start(&scan_config, false) ); + + return true; +} + +static int wifi_cmd_scan(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &scan_args); + + if (nerrors != 0) { + arg_print_errors(stderr, scan_args.end, argv[0]); + return 1; + } + + ESP_LOGI(TAG, "sta start to scan"); + if ( scan_args.ssid->count == 1 ) { + wifi_cmd_sta_scan(scan_args.ssid->sval[0]); + } else { + wifi_cmd_sta_scan(NULL); + } + return 0; +} + + +static bool wifi_cmd_ap_set(const char *ssid, const char *pass) +{ + wifi_config_t wifi_config = { + .ap = { + .ssid = "", + .ssid_len = 0, + .max_connection = 4, + .password = "", + .authmode = WIFI_AUTH_WPA_WPA2_PSK + }, + }; + + reconnect = false; + strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); + if (pass) { + if (strlen(pass) != 0 && strlen(pass) < 8) { + reconnect = true; + ESP_LOGE(TAG, "password less than 8"); + return false; + } + strncpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); + } + + if (strlen(pass) == 0) { + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config)); + return true; +} + +static int wifi_cmd_ap(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &ap_args); + + if (nerrors != 0) { + arg_print_errors(stderr, ap_args.end, argv[0]); + return 1; + } + + wifi_cmd_ap_set(ap_args.ssid->sval[0], ap_args.password->sval[0]); + ESP_LOGI(TAG, "AP mode, %s %s", ap_args.ssid->sval[0], ap_args.password->sval[0]); + return 0; +} + +static int wifi_cmd_query(int argc, char **argv) +{ + wifi_config_t cfg; + wifi_mode_t mode; + + esp_wifi_get_mode(&mode); + if (WIFI_MODE_AP == mode) { + esp_wifi_get_config(WIFI_IF_AP, &cfg); + ESP_LOGI(TAG, "AP mode, %s %s", cfg.ap.ssid, cfg.ap.password); + } else if (WIFI_MODE_STA == mode) { + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + if (bits & CONNECTED_BIT) { + esp_wifi_get_config(WIFI_IF_STA, &cfg); + ESP_LOGI(TAG, "sta mode, connected %s", cfg.ap.ssid); + } else { + ESP_LOGI(TAG, "sta mode, disconnected"); + } + } else { + ESP_LOGI(TAG, "NULL mode"); + return 0; + } + + return 0; +} + +static uint32_t wifi_get_local_ip(void) +{ + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + tcpip_adapter_if_t ifx = TCPIP_ADAPTER_IF_AP; + tcpip_adapter_ip_info_t ip_info; + wifi_mode_t mode; + + esp_wifi_get_mode(&mode); + if (WIFI_MODE_STA == mode) { + bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + if (bits & CONNECTED_BIT) { + ifx = TCPIP_ADAPTER_IF_STA; + } else { + ESP_LOGE(TAG, "sta has no IP"); + return 0; + } + } + + tcpip_adapter_get_ip_info(ifx, &ip_info); + return ip_info.ip.addr; +} + +static int wifi_cmd_iperf(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &iperf_args); + iperf_cfg_t cfg; + + if (nerrors != 0) { + arg_print_errors(stderr, iperf_args.end, argv[0]); + return 0; + } + + memset(&cfg, 0, sizeof(cfg)); + + if ( iperf_args.abort->count != 0) { + iperf_stop(); + return 0; + } + + if ( ((iperf_args.ip->count == 0) && (iperf_args.server->count == 0)) || + ((iperf_args.ip->count != 0) && (iperf_args.server->count != 0)) ) { + ESP_LOGE(TAG, "should specific client/server mode"); + return 0; + } + + if (iperf_args.ip->count == 0) { + cfg.flag |= IPERF_FLAG_SERVER; + } else { + cfg.dip = ipaddr_addr(iperf_args.ip->sval[0]); + cfg.flag |= IPERF_FLAG_CLIENT; + } + + cfg.sip = wifi_get_local_ip(); + if (cfg.sip == 0) { + return 0; + } + + if (iperf_args.udp->count == 0) { + cfg.flag |= IPERF_FLAG_TCP; + } else { + cfg.flag |= IPERF_FLAG_UDP; + } + + if (iperf_args.port->count == 0) { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + if (cfg.flag & IPERF_FLAG_SERVER) { + cfg.sport = iperf_args.port->ival[0]; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = iperf_args.port->ival[0]; + } + } + + if (iperf_args.interval->count == 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } else { + cfg.interval = iperf_args.interval->ival[0]; + if (cfg.interval <= 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } + } + + if (iperf_args.time->count == 0) { + cfg.time = IPERF_DEFAULT_TIME; + } else { + cfg.time = iperf_args.time->ival[0]; + if (cfg.time <= cfg.interval) { + cfg.time = cfg.interval; + } + } + + ESP_LOGI(TAG, "mode=%s-%s sip=%d.%d.%d.%d:%d, dip=%d.%d.%d.%d:%d, interval=%d, time=%d", + cfg.flag & IPERF_FLAG_TCP ? "tcp" : "udp", + cfg.flag & IPERF_FLAG_SERVER ? "server" : "client", + cfg.sip & 0xFF, (cfg.sip >> 8) & 0xFF, (cfg.sip >> 16) & 0xFF, (cfg.sip >> 24) & 0xFF, cfg.sport, + cfg.dip & 0xFF, (cfg.dip >> 8) & 0xFF, (cfg.dip >> 16) & 0xFF, (cfg.dip >> 24) & 0xFF, cfg.dport, + cfg.interval, cfg.time); + + iperf_start(&cfg); + + return 0; +} + +static int restart(int argc, char **argv) +{ + ESP_LOGI(TAG, "Restarting"); + esp_restart(); +} + +static int heap_size(int argc, char **argv) +{ + uint32_t heap_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); + ESP_LOGI(TAG, "min heap size: %u", heap_size); + return 0; +} + +void register_wifi() +{ + sta_args.ssid = arg_str1(NULL, NULL, "", "SSID of AP"); + sta_args.password = arg_str0(NULL, NULL, "", "password of AP"); + sta_args.end = arg_end(2); + + const esp_console_cmd_t sta_cmd = { + .command = "sta", + .help = "WiFi is station mode, join specified soft-AP", + .hint = NULL, + .func = &wifi_cmd_sta, + .argtable = &sta_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&sta_cmd) ); + + scan_args.ssid = arg_str0(NULL, NULL, "", "SSID of AP want to be scanned"); + scan_args.end = arg_end(1); + + const esp_console_cmd_t scan_cmd = { + .command = "scan", + .help = "WiFi is station mode, start scan ap", + .hint = NULL, + .func = &wifi_cmd_scan, + .argtable = &scan_args + }; + + ap_args.ssid = arg_str1(NULL, NULL, "", "SSID of AP"); + ap_args.password = arg_str0(NULL, NULL, "", "password of AP"); + ap_args.end = arg_end(2); + + + ESP_ERROR_CHECK( esp_console_cmd_register(&scan_cmd) ); + + const esp_console_cmd_t ap_cmd = { + .command = "ap", + .help = "AP mode, configure ssid and password", + .hint = NULL, + .func = &wifi_cmd_ap, + .argtable = &ap_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&ap_cmd) ); + + const esp_console_cmd_t query_cmd = { + .command = "query", + .help = "query WiFi info", + .hint = NULL, + .func = &wifi_cmd_query, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&query_cmd) ); + + const esp_console_cmd_t restart_cmd = { + .command = "restart", + .help = "Restart the program", + .hint = NULL, + .func = &restart, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&restart_cmd) ); + + iperf_args.ip = arg_str0("c", "client", "", "run in client mode, connecting to "); + iperf_args.server = arg_lit0("s", "server", "run in server mode"); + iperf_args.udp = arg_lit0("u", "udp", "use UDP rather than TCP"); + iperf_args.port = arg_int0("p", "port", "", "server port to listen on/connect to"); + iperf_args.interval = arg_int0("i", "interval", "", "seconds between periodic bandwidth reports"); + iperf_args.time = arg_int0("t", "time", "