From b19671e0d401fcdabe80a2558f5a912e374d771a Mon Sep 17 00:00:00 2001 From: lly Date: Fri, 1 Nov 2019 16:08:47 +0800 Subject: [PATCH] ble_mesh: Add ESP BLE Mesh implementation 1. BLE Mesh Core * Provisioning: Node Role * PB-ADV and PB-GATT * Authentication OOB * Provisioning: Provisioner Role * PB-ADV and PB-GATT * 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 BLE Mesh node related information in flash * Store BLE Mesh Provisioner related information in flash 2. BLE Mesh Models * Foundation Models * Configuration Server Model * Configuration Client Model * Health Server Model * Health Client Model * Generic * Generic OnOff Server * Generic OnOff Client * Generic Level Server * Generic Level Client * Generic Default Transition Time Server * Generic Default Transition Time Client * Generic Power OnOff Server * Generic Power OnOff Setup Server * Generic Power OnOff Client * Generic Power Level Server * Generic Power Level Setup Server * Generic Power Level Client * Generic Battery Server * Generic Battery Client * Generic Location Server * Generic Location Setup Server * Generic Location Client * Generic Admin Property Server * Generic Manufacturer Property Server * Generic User Property Server * Generic Client Property Server * Generic Property Client * Sensor Server Model * Sensor Server * Sensor Setup Server * Sensor Client * Time and Scenes * Time Server * Time Setup Server * Time Client * Scene Server * Scene Setup Server * Scene Client * Scheduler Server * Scheduler Setup Server * Scheduler Client * Lighting * Light Lightness Server * Light Lightness Setup Server * Light Lightness Client * Light CTL Server * Light CTL Setup Server * Light CTL Client * Light CTL Temperature Server * Light HSL Server * Light HSL Setup Server * Light HSL Client * Light HSL Hue Server * Light HSL Saturation Server * Light xyL Server * Light xyL Setup Server * Light xyL Client * Light LC Server * Light LC Setup Server * Light LC Client 3. BLE Mesh Applications * BLE Mesh Node * OnOff Client Example * OnOff Server Example * BLE Mesh Provisioner * Example * Fast Provisioning * Vendor Fast Prov Server Model * Vendor Fast Prov Client Model * Examples * Wi-Fi & BLE Mesh Coexistence * Example * BLE Mesh Console Commands * Examples --- components/bt/CMakeLists.txt | 82 + components/bt/Kconfig | 968 +++++ components/bt/bluedroid/btc/core/btc_task.c | 25 + .../bt/bluedroid/btc/include/btc/btc_task.h | 16 + components/bt/component.mk | 29 + components/bt/esp_ble_mesh/README.md | 21 + .../api/core/esp_ble_mesh_common_api.c | 82 + .../esp_ble_mesh_local_data_operation_api.c | 76 + .../api/core/esp_ble_mesh_low_power_api.c | 65 + .../api/core/esp_ble_mesh_networking_api.c | 511 +++ .../api/core/esp_ble_mesh_provisioning_api.c | 494 +++ .../api/core/esp_ble_mesh_proxy_api.c | 177 + .../core/include/esp_ble_mesh_common_api.h | 49 + .../esp_ble_mesh_local_data_operation_api.h | 107 + .../core/include/esp_ble_mesh_low_power_api.h | 61 + .../include/esp_ble_mesh_networking_api.h | 384 ++ .../include/esp_ble_mesh_provisioning_api.h | 370 ++ .../api/core/include/esp_ble_mesh_proxy_api.h | 118 + .../bt/esp_ble_mesh/api/esp_ble_mesh_defs.h | 2024 +++++++++ .../models/esp_ble_mesh_config_model_api.c | 99 + .../models/esp_ble_mesh_generic_model_api.c | 92 + .../models/esp_ble_mesh_health_model_api.c | 99 + .../models/esp_ble_mesh_lighting_model_api.c | 79 + .../models/esp_ble_mesh_sensor_model_api.c | 80 + .../esp_ble_mesh_time_scene_model_api.c | 80 + .../include/esp_ble_mesh_config_model_api.h | 818 ++++ .../include/esp_ble_mesh_generic_model_api.h | 1297 ++++++ .../include/esp_ble_mesh_health_model_api.h | 406 ++ .../include/esp_ble_mesh_lighting_model_api.h | 1675 ++++++++ .../include/esp_ble_mesh_sensor_model_api.h | 626 +++ .../esp_ble_mesh_time_scene_model_api.h | 912 ++++ .../btc/btc_ble_mesh_config_model.c | 801 ++++ .../btc/btc_ble_mesh_generic_model.c | 776 ++++ .../btc/btc_ble_mesh_health_model.c | 661 +++ .../btc/btc_ble_mesh_lighting_model.c | 591 +++ .../bt/esp_ble_mesh/btc/btc_ble_mesh_prov.c | 2024 +++++++++ .../btc/btc_ble_mesh_sensor_model.c | 908 ++++ .../btc/btc_ble_mesh_time_scene_model.c | 495 +++ .../btc/include/btc_ble_mesh_config_model.h | 74 + .../btc/include/btc_ble_mesh_generic_model.h | 76 + .../btc/include/btc_ble_mesh_health_model.h | 87 + .../btc/include/btc_ble_mesh_lighting_model.h | 78 + .../btc/include/btc_ble_mesh_prov.h | 301 ++ .../btc/include/btc_ble_mesh_sensor_model.h | 77 + .../include/btc_ble_mesh_time_scene_model.h | 78 + .../mesh_common/include/mesh_aes_encrypt.h | 171 + .../mesh_common/include/mesh_atomic.h | 305 ++ .../mesh_common/include/mesh_buf.h | 1260 ++++++ .../mesh_common/include/mesh_common.h | 90 + .../mesh_common/include/mesh_dlist.h | 496 +++ .../mesh_common/include/mesh_kernel.h | 280 ++ .../mesh_common/include/mesh_slist.h | 468 +++ .../mesh_common/include/mesh_trace.h | 130 + .../mesh_common/include/mesh_types.h | 46 + .../mesh_common/include/mesh_util.h | 440 ++ .../mesh_common/mesh_aes_encrypt.c | 408 ++ .../bt/esp_ble_mesh/mesh_common/mesh_atomic.c | 173 + .../bt/esp_ble_mesh/mesh_common/mesh_buf.c | 594 +++ .../bt/esp_ble_mesh/mesh_common/mesh_common.c | 126 + .../bt/esp_ble_mesh/mesh_common/mesh_kernel.c | 293 ++ .../bt/esp_ble_mesh/mesh_common/mesh_util.c | 78 + components/bt/esp_ble_mesh/mesh_core/access.c | 1326 ++++++ components/bt/esp_ble_mesh/mesh_core/access.h | 77 + components/bt/esp_ble_mesh/mesh_core/adv.c | 866 ++++ components/bt/esp_ble_mesh/mesh_core/adv.h | 100 + components/bt/esp_ble_mesh/mesh_core/beacon.c | 475 +++ components/bt/esp_ble_mesh/mesh_core/beacon.h | 27 + .../bluedroid_host/mesh_bearer_adapt.c | 1972 +++++++++ .../bt/esp_ble_mesh/mesh_core/cfg_cli.c | 1714 ++++++++ .../bt/esp_ble_mesh/mesh_core/cfg_srv.c | 3659 +++++++++++++++++ components/bt/esp_ble_mesh/mesh_core/crypto.c | 873 ++++ components/bt/esp_ble_mesh/mesh_core/crypto.h | 166 + .../bt/esp_ble_mesh/mesh_core/foundation.h | 184 + components/bt/esp_ble_mesh/mesh_core/friend.c | 1708 ++++++++ components/bt/esp_ble_mesh/mesh_core/friend.h | 59 + .../bt/esp_ble_mesh/mesh_core/health_cli.c | 516 +++ .../bt/esp_ble_mesh/mesh_core/health_srv.c | 550 +++ .../esp_ble_mesh/mesh_core/include/cfg_cli.h | 295 ++ .../esp_ble_mesh/mesh_core/include/cfg_srv.h | 214 + .../mesh_core/include/health_cli.h | 74 + .../mesh_core/include/health_srv.h | 97 + .../mesh_core/include/mesh_access.h | 496 +++ .../mesh_core/include/mesh_bearer_adapt.h | 766 ++++ .../esp_ble_mesh/mesh_core/include/mesh_hci.h | 133 + .../mesh_core/include/mesh_main.h | 636 +++ .../mesh_core/include/mesh_proxy.h | 37 + .../mesh_core/include/mesh_uuid.h | 530 +++ components/bt/esp_ble_mesh/mesh_core/lpn.c | 1104 +++++ components/bt/esp_ble_mesh/mesh_core/lpn.h | 70 + components/bt/esp_ble_mesh/mesh_core/main.c | 672 +++ components/bt/esp_ble_mesh/mesh_core/mesh.h | 24 + components/bt/esp_ble_mesh/mesh_core/net.c | 1557 +++++++ components/bt/esp_ble_mesh/mesh_core/net.h | 420 ++ .../mesh_core/nimble_host/mesh_bearer_adapt.c | 1824 ++++++++ components/bt/esp_ble_mesh/mesh_core/prov.c | 1814 ++++++++ components/bt/esp_ble_mesh/mesh_core/prov.h | 34 + .../esp_ble_mesh/mesh_core/provisioner_main.c | 1706 ++++++++ .../esp_ble_mesh/mesh_core/provisioner_main.h | 141 + .../esp_ble_mesh/mesh_core/provisioner_prov.c | 3497 ++++++++++++++++ .../esp_ble_mesh/mesh_core/provisioner_prov.h | 416 ++ .../bt/esp_ble_mesh/mesh_core/proxy_client.c | 999 +++++ .../bt/esp_ble_mesh/mesh_core/proxy_client.h | 103 + .../bt/esp_ble_mesh/mesh_core/proxy_server.c | 1460 +++++++ .../bt/esp_ble_mesh/mesh_core/proxy_server.h | 65 + .../bt/esp_ble_mesh/mesh_core/settings.c | 2592 ++++++++++++ .../bt/esp_ble_mesh/mesh_core/settings.h | 61 + .../mesh_core/storage/settings_nvs.c | 406 ++ .../mesh_core/storage/settings_nvs.h | 47 + components/bt/esp_ble_mesh/mesh_core/test.c | 127 + components/bt/esp_ble_mesh/mesh_core/test.h | 38 + .../bt/esp_ble_mesh/mesh_core/transport.c | 1726 ++++++++ .../bt/esp_ble_mesh/mesh_core/transport.h | 111 + .../mesh_models/client/client_common.c | 408 ++ .../mesh_models/client/generic_client.c | 1298 ++++++ .../client/include/client_common.h | 130 + .../client/include/generic_client.h | 569 +++ .../client/include/lighting_client.h | 539 +++ .../client/include/sensor_client.h | 174 + .../client/include/time_scene_client.h | 284 ++ .../mesh_models/client/lighting_client.c | 1458 +++++++ .../mesh_models/client/sensor_client.c | 650 +++ .../mesh_models/client/time_scene_client.c | 743 ++++ .../mesh_models/common/include/model_opcode.h | 276 ++ .../mesh_models/server/device_property.c | 145 + .../mesh_models/server/generic_server.c | 2795 +++++++++++++ .../server/include/device_property.h | 1045 +++++ .../server/include/generic_server.h | 389 ++ .../server/include/lighting_server.h | 530 +++ .../server/include/sensor_server.h | 252 ++ .../server/include/server_common.h | 126 + .../server/include/state_binding.h | 94 + .../server/include/state_transition.h | 92 + .../server/include/time_scene_server.h | 398 ++ .../mesh_models/server/lighting_server.c | 3553 ++++++++++++++++ .../mesh_models/server/sensor_server.c | 1108 +++++ .../mesh_models/server/server_common.c | 258 ++ .../mesh_models/server/state_binding.c | 340 ++ .../mesh_models/server/state_transition.c | 1020 +++++ .../mesh_models/server/time_scene_server.c | 1500 +++++++ docs/Doxyfile | 14 + docs/_static/ble-mesh-config-complete.png | Bin 0 -> 94673 bytes docs/_static/ble-mesh-device-power-on.png | Bin 0 -> 381459 bytes docs/_static/ble-mesh-generic-onoff.png | Bin 0 -> 42920 bytes docs/_static/ble-mesh-identify-provision.png | Bin 0 -> 73312 bytes docs/_static/ble-mesh-initial-config-fail.png | Bin 0 -> 108974 bytes docs/_static/ble-mesh-model-bind-appkey.png | Bin 0 -> 144581 bytes docs/_static/ble-mesh-provision.png | Bin 0 -> 47459 bytes .../ble-mesh-reconnect-initial-config.png | Bin 0 -> 144717 bytes docs/_static/ble-mesh-reconnect-three.png | Bin 0 -> 91013 bytes docs/_static/ble-mesh-scanner.png | Bin 0 -> 29899 bytes docs/_static/ble-mesh-three-nodes-on.png | Bin 0 -> 385568 bytes docs/_static/esp-ble-mesh-architecture.png | Bin 0 -> 111714 bytes docs/_static/esp-ble-mesh-architecture.xml | 1 + docs/_static/esp-ble-mesh-interface.png | Bin 0 -> 55845 bytes docs/_static/esp-ble-mesh-interface.xml | 1 + docs/en/COPYRIGHT.rst | 2 + .../esp-ble-mesh/ble-mesh-architecture.rst | 395 ++ .../api-guides/esp-ble-mesh/ble-mesh-faq.rst | 663 +++ .../esp-ble-mesh/ble-mesh-feature-list.rst | 147 + .../esp-ble-mesh/ble-mesh-index.rst | 274 ++ .../esp-ble-mesh/ble-mesh-terminology.rst | 221 + docs/en/api-guides/index.rst | 1 + .../api-reference/bluetooth/esp-ble-mesh.rst | 154 + docs/en/api-reference/bluetooth/index.rst | 2 +- .../esp-ble-mesh/ble-mesh-architecture.rst | 388 ++ .../api-guides/esp-ble-mesh/ble-mesh-faq.rst | 1 + .../esp-ble-mesh/ble-mesh-feature-list.rst | 1 + .../esp-ble-mesh/ble-mesh-index.rst | 276 ++ .../esp-ble-mesh/ble-mesh-terminology.rst | 221 + docs/zh_CN/api-guides/index.rst | 1 + .../api-reference/bluetooth/esp-ble-mesh.rst | 1 + examples/bluetooth/esp_ble_mesh/README.md | 78 + .../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 | 23 + .../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 | 21 + .../ble_mesh_fast_prov_client/CMakeLists.txt | 8 + .../ble_mesh_fast_prov_client/Makefile | 12 + .../ble_mesh_fast_prov_client/README.md | 6 + .../main/CMakeLists.txt | 6 + .../main/ble_mesh_demo_init.c | 143 + .../main/ble_mesh_demo_init.h | 18 + .../main/ble_mesh_demo_main.c | 601 +++ .../main/component.mk | 4 + .../sdkconfig.defaults | 21 + ...sh_Fast_Prov_Client_Example_Walkthrough.md | 218 + .../ble_mesh_fast_prov_server/CMakeLists.txt | 8 + .../ble_mesh_fast_prov_server/Makefile | 12 + .../ble_mesh_fast_prov_server/README.md | 6 + .../main/CMakeLists.txt | 7 + .../main/Kconfig.projbuild | 16 + .../main/ble_mesh_demo_init.c | 143 + .../main/ble_mesh_demo_init.h | 18 + .../main/ble_mesh_demo_main.c | 764 ++++ .../ble_mesh_fast_prov_server/main/board.c | 72 + .../ble_mesh_fast_prov_server/main/board.h | 47 + .../main/component.mk | 4 + .../sdkconfig.defaults | 25 + ...sh_Fast_Prov_Server_Example_Walkthrough.md | 409 ++ .../tutorial/EspBleMesh.md | 93 + .../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_node/onoff_client/CMakeLists.txt | 6 + .../ble_mesh_node/onoff_client/Makefile | 10 + .../ble_mesh_node/onoff_client/README.md | 14 + .../components/button/CMakeLists.txt | 7 + .../onoff_client/components/button/Kconfig | 21 + .../onoff_client/components/button/README.md | 47 + .../onoff_client/components/button/button.c | 434 ++ .../components/button/button_obj.cpp | 48 + .../components/button/component.mk | 4 + .../components/button/include/iot_button.h | 230 ++ .../onoff_client/main/CMakeLists.txt | 7 + .../onoff_client/main/Kconfig.projbuild | 22 + .../onoff_client/main/ble_mesh_demo_init.c | 143 + .../onoff_client/main/ble_mesh_demo_init.h | 18 + .../onoff_client/main/ble_mesh_demo_main.c | 272 ++ .../ble_mesh_node/onoff_client/main/board.c | 78 + .../ble_mesh_node/onoff_client/main/board.h | 39 + .../onoff_client/main/component.mk | 4 + .../onoff_client/sdkconfig.defaults | 19 + ...h_Node_OnOff_Client_Example_Walkthrough.md | 252 ++ .../onoff_client/tutorial/images/app.png | Bin 0 -> 443575 bytes .../onoff_client/tutorial/images/message.png | Bin 0 -> 67886 bytes .../onoff_client/tutorial/images/picture5.png | Bin 0 -> 26425 bytes .../ble_mesh_node/onoff_server/CMakeLists.txt | 6 + .../ble_mesh_node/onoff_server/Makefile | 10 + .../ble_mesh_node/onoff_server/README.md | 16 + .../onoff_server/main/CMakeLists.txt | 7 + .../onoff_server/main/Kconfig.projbuild | 22 + .../onoff_server/main/ble_mesh_demo_init.c | 143 + .../onoff_server/main/ble_mesh_demo_init.h | 18 + .../onoff_server/main/ble_mesh_demo_main.c | 335 ++ .../ble_mesh_node/onoff_server/main/board.c | 56 + .../ble_mesh_node/onoff_server/main/board.h | 38 + .../onoff_server/main/component.mk | 4 + .../onoff_server/sdkconfig.defaults | 18 + ...h_Node_OnOff_Server_Example_Walkthrough.md | 471 +++ .../ble_mesh_provisioner/CMakeLists.txt | 6 + .../ble_mesh_provisioner/Makefile | 10 + .../ble_mesh_provisioner/README.md | 6 + .../ble_mesh_provisioner/main/CMakeLists.txt | 6 + .../main/ble_mesh_demo_init.c | 143 + .../main/ble_mesh_demo_init.h | 18 + .../main/ble_mesh_demo_main.c | 640 +++ .../ble_mesh_provisioner/main/component.mk | 4 + .../ble_mesh_provisioner/sdkconfig.defaults | 19 + ...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 | 7 + .../ble_mesh_wifi_coexist/CMakeLists.txt | 8 + .../ble_mesh_wifi_coexist/Makefile | 12 + .../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 | 7 + .../main/Kconfig.projbuild | 16 + .../main/ble_mesh_demo_init.c | 143 + .../main/ble_mesh_demo_init.h | 18 + .../main/ble_mesh_demo_main.c | 899 ++++ .../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 | 59 + ...E_Mesh_WiFi_Coexist_Example_Walkthrough.md | 301 ++ 321 files changed, 102046 insertions(+), 1 deletion(-) create mode 100644 components/bt/esp_ble_mesh/README.md create mode 100644 components/bt/esp_ble_mesh/api/core/esp_ble_mesh_common_api.c create mode 100644 components/bt/esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c create mode 100644 components/bt/esp_ble_mesh/api/core/esp_ble_mesh_low_power_api.c create mode 100644 components/bt/esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c create mode 100644 components/bt/esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c create mode 100644 components/bt/esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c create mode 100644 components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_common_api.h create mode 100644 components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_local_data_operation_api.h create mode 100644 components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_low_power_api.h create mode 100644 components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_networking_api.h create mode 100644 components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_provisioning_api.h create mode 100644 components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h create mode 100644 components/bt/esp_ble_mesh/api/esp_ble_mesh_defs.h create mode 100644 components/bt/esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c create mode 100644 components/bt/esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c create mode 100644 components/bt/esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c create mode 100644 components/bt/esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c create mode 100644 components/bt/esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c create mode 100644 components/bt/esp_ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c create mode 100644 components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_config_model_api.h create mode 100644 components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h create mode 100644 components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_health_model_api.h create mode 100644 components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h create mode 100644 components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h create mode 100644 components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_time_scene_model_api.h create mode 100644 components/bt/esp_ble_mesh/btc/btc_ble_mesh_config_model.c create mode 100644 components/bt/esp_ble_mesh/btc/btc_ble_mesh_generic_model.c create mode 100644 components/bt/esp_ble_mesh/btc/btc_ble_mesh_health_model.c create mode 100644 components/bt/esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c create mode 100644 components/bt/esp_ble_mesh/btc/btc_ble_mesh_prov.c create mode 100644 components/bt/esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c create mode 100644 components/bt/esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c create mode 100644 components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_config_model.h create mode 100644 components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_generic_model.h create mode 100644 components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_health_model.h create mode 100644 components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_lighting_model.h create mode 100644 components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_prov.h create mode 100644 components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_sensor_model.h create mode 100644 components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_time_scene_model.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_aes_encrypt.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_atomic.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_buf.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_common.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_dlist.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_kernel.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_slist.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_trace.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_types.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/include/mesh_util.h create mode 100644 components/bt/esp_ble_mesh/mesh_common/mesh_aes_encrypt.c create mode 100644 components/bt/esp_ble_mesh/mesh_common/mesh_atomic.c create mode 100644 components/bt/esp_ble_mesh/mesh_common/mesh_buf.c create mode 100644 components/bt/esp_ble_mesh/mesh_common/mesh_common.c create mode 100644 components/bt/esp_ble_mesh/mesh_common/mesh_kernel.c create mode 100644 components/bt/esp_ble_mesh/mesh_common/mesh_util.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/access.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/access.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/adv.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/adv.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/beacon.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/beacon.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/bluedroid_host/mesh_bearer_adapt.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/cfg_cli.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/cfg_srv.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/crypto.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/crypto.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/foundation.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/friend.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/friend.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/health_cli.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/health_srv.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/cfg_cli.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/cfg_srv.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/health_cli.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/health_srv.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/mesh_access.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/mesh_bearer_adapt.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/mesh_hci.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/mesh_main.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/mesh_proxy.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/include/mesh_uuid.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/lpn.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/lpn.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/main.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/mesh.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/net.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/net.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/nimble_host/mesh_bearer_adapt.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/prov.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/prov.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/provisioner_main.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/provisioner_main.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/provisioner_prov.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/provisioner_prov.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/proxy_client.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/proxy_client.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/proxy_server.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/proxy_server.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/settings.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/settings.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/storage/settings_nvs.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/storage/settings_nvs.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/test.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/test.h create mode 100644 components/bt/esp_ble_mesh/mesh_core/transport.c create mode 100644 components/bt/esp_ble_mesh/mesh_core/transport.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/client_common.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/generic_client.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/include/client_common.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/include/generic_client.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/include/lighting_client.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/include/sensor_client.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/include/time_scene_client.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/lighting_client.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/sensor_client.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/client/time_scene_client.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/common/include/model_opcode.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/device_property.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/generic_server.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/include/device_property.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/include/generic_server.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/include/lighting_server.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/include/sensor_server.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/include/server_common.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/include/state_binding.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/include/state_transition.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/include/time_scene_server.h create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/lighting_server.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/sensor_server.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/server_common.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/state_binding.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/state_transition.c create mode 100644 components/bt/esp_ble_mesh/mesh_models/server/time_scene_server.c create mode 100644 docs/_static/ble-mesh-config-complete.png create mode 100644 docs/_static/ble-mesh-device-power-on.png create mode 100644 docs/_static/ble-mesh-generic-onoff.png create mode 100644 docs/_static/ble-mesh-identify-provision.png create mode 100644 docs/_static/ble-mesh-initial-config-fail.png create mode 100644 docs/_static/ble-mesh-model-bind-appkey.png create mode 100644 docs/_static/ble-mesh-provision.png create mode 100644 docs/_static/ble-mesh-reconnect-initial-config.png create mode 100644 docs/_static/ble-mesh-reconnect-three.png create mode 100644 docs/_static/ble-mesh-scanner.png create mode 100644 docs/_static/ble-mesh-three-nodes-on.png create mode 100644 docs/_static/esp-ble-mesh-architecture.png create mode 100644 docs/_static/esp-ble-mesh-architecture.xml create mode 100644 docs/_static/esp-ble-mesh-interface.png create mode 100644 docs/_static/esp-ble-mesh-interface.xml create mode 100644 docs/en/api-guides/esp-ble-mesh/ble-mesh-architecture.rst create mode 100644 docs/en/api-guides/esp-ble-mesh/ble-mesh-faq.rst create mode 100644 docs/en/api-guides/esp-ble-mesh/ble-mesh-feature-list.rst create mode 100644 docs/en/api-guides/esp-ble-mesh/ble-mesh-index.rst create mode 100644 docs/en/api-guides/esp-ble-mesh/ble-mesh-terminology.rst create mode 100644 docs/en/api-reference/bluetooth/esp-ble-mesh.rst create mode 100644 docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-architecture.rst create mode 100644 docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-faq.rst create mode 100644 docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-feature-list.rst create mode 100644 docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-index.rst create mode 100644 docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-terminology.rst create mode 100644 docs/zh_CN/api-reference/bluetooth/esp-ble-mesh.rst create mode 100644 examples/bluetooth/esp_ble_mesh/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/Makefile create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_decl.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_main.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_system.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_node_cmd.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_server_cmd.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/register_bluetooth.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/sdkconfig.defaults create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/Makefile create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_decl.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_main.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_system.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_cfg_client_cmd.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_gen_onoff_client_cmd.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_test_perf_client_cmd.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_node_cmd.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_provisioner_cmd.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/register_bluetooth.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/sdkconfig.defaults create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/Makefile create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_init.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_init.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/sdkconfig.defaults create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/tutorial/BLE_Mesh_Fast_Prov_Client_Example_Walkthrough.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/Makefile create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/Kconfig.projbuild create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_init.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_init.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/sdkconfig.defaults create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/BLE_Mesh_Fast_Prov_Server_Example_Walkthrough.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/EspBleMesh.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/app_ble.png create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/device.png create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/picture1.png create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/picture2.png create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/time.png create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/Makefile create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/components/button/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/components/button/Kconfig create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/components/button/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/components/button/button.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/components/button/button_obj.cpp create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/components/button/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/components/button/include/iot_button.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/main/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/main/Kconfig.projbuild create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/main/ble_mesh_demo_init.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/main/ble_mesh_demo_init.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/main/board.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/main/board.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/main/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/sdkconfig.defaults create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/tutorial/BLE_Mesh_Node_OnOff_Client_Example_Walkthrough.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/tutorial/images/app.png create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/tutorial/images/message.png create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_client/tutorial/images/picture5.png create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/Makefile create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/main/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/main/Kconfig.projbuild create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/main/ble_mesh_demo_init.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/main/ble_mesh_demo_init.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/main/board.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/main/board.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/main/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/sdkconfig.defaults create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server/tutorial/BLE_Mesh_Node_OnOff_Server_Example_Walkthrough.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/Makefile create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/main/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/main/ble_mesh_demo_init.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/main/ble_mesh_demo_init.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/main/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/sdkconfig.defaults create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_provisioner/tutorial/BLE_Mesh_Provisioner_Example_Walkthrough.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/Makefile create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_client_model.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_common.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_operation.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components/esp_fast_prov_server_model.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/main/main.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/sdkconfig.defaults create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/Makefile create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/README.md create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/components/iperf/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_decl.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/components/iperf/cmd_wifi.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/components/iperf/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/components/iperf/iperf.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/components/iperf/iperf.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/main/CMakeLists.txt create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/main/Kconfig.projbuild create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/main/ble_mesh_demo_init.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/main/ble_mesh_demo_init.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/main/ble_mesh_demo_main.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/main/board.c create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/main/board.h create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/main/component.mk create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/partitions.csv create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/sdkconfig.defaults create mode 100644 examples/bluetooth/esp_ble_mesh/ble_mesh_wifi_coexist/tutorial/BLE_Mesh_WiFi_Coexist_Example_Walkthrough.md diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index cbd03e7139..337fcfe977 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -279,6 +279,88 @@ if(CONFIG_BT_ENABLED) "bluedroid/stack/smp/smp_l2c.c" "bluedroid/stack/smp/smp_main.c" "bluedroid/stack/smp/smp_utils.c") + + if(CONFIG_BLE_MESH) + list(APPEND COMPONENT_ADD_INCLUDEDIRS bluedroid/osi/include) + list(APPEND COMPONENT_SRCS "esp_ble_mesh/mesh_core/bluedroid_host/mesh_bearer_adapt.c") + endif() + + endif() + + if(CONFIG_BLE_MESH) + list(APPEND COMPONENT_ADD_INCLUDEDIRS + "esp_ble_mesh/mesh_common/include" + "esp_ble_mesh/mesh_core" + "esp_ble_mesh/mesh_core/include" + "esp_ble_mesh/mesh_core/storage" + "esp_ble_mesh/btc/include" + "esp_ble_mesh/mesh_models/common/include" + "esp_ble_mesh/mesh_models/client/include" + "esp_ble_mesh/mesh_models/server/include" + "esp_ble_mesh/api/core/include" + "esp_ble_mesh/api/models/include" + "esp_ble_mesh/api") + + list(APPEND COMPONENT_SRCS + "esp_ble_mesh/api/core/esp_ble_mesh_common_api.c" + "esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c" + "esp_ble_mesh/api/core/esp_ble_mesh_low_power_api.c" + "esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c" + "esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c" + "esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c" + "esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c" + "esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c" + "esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c" + "esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c" + "esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c" + "esp_ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c" + "esp_ble_mesh/btc/btc_ble_mesh_config_model.c" + "esp_ble_mesh/btc/btc_ble_mesh_generic_model.c" + "esp_ble_mesh/btc/btc_ble_mesh_health_model.c" + "esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c" + "esp_ble_mesh/btc/btc_ble_mesh_prov.c" + "esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c" + "esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c" + "esp_ble_mesh/mesh_common/mesh_aes_encrypt.c" + "esp_ble_mesh/mesh_common/mesh_atomic.c" + "esp_ble_mesh/mesh_common/mesh_buf.c" + "esp_ble_mesh/mesh_common/mesh_common.c" + "esp_ble_mesh/mesh_common/mesh_kernel.c" + "esp_ble_mesh/mesh_common/mesh_util.c" + "esp_ble_mesh/mesh_core/storage/settings_nvs.c" + "esp_ble_mesh/mesh_core/access.c" + "esp_ble_mesh/mesh_core/adv.c" + "esp_ble_mesh/mesh_core/beacon.c" + "esp_ble_mesh/mesh_core/cfg_cli.c" + "esp_ble_mesh/mesh_core/cfg_srv.c" + "esp_ble_mesh/mesh_core/crypto.c" + "esp_ble_mesh/mesh_core/friend.c" + "esp_ble_mesh/mesh_core/health_cli.c" + "esp_ble_mesh/mesh_core/health_srv.c" + "esp_ble_mesh/mesh_core/lpn.c" + "esp_ble_mesh/mesh_core/main.c" + "esp_ble_mesh/mesh_core/net.c" + "esp_ble_mesh/mesh_core/prov.c" + "esp_ble_mesh/mesh_core/provisioner_main.c" + "esp_ble_mesh/mesh_core/provisioner_prov.c" + "esp_ble_mesh/mesh_core/proxy_client.c" + "esp_ble_mesh/mesh_core/proxy_server.c" + "esp_ble_mesh/mesh_core/settings.c" + "esp_ble_mesh/mesh_core/test.c" + "esp_ble_mesh/mesh_core/transport.c" + "esp_ble_mesh/mesh_models/client/client_common.c" + "esp_ble_mesh/mesh_models/client/generic_client.c" + "esp_ble_mesh/mesh_models/client/lighting_client.c" + "esp_ble_mesh/mesh_models/client/sensor_client.c" + "esp_ble_mesh/mesh_models/client/time_scene_client.c" + "esp_ble_mesh/mesh_models/server/device_property.c" + "esp_ble_mesh/mesh_models/server/generic_server.c" + "esp_ble_mesh/mesh_models/server/lighting_server.c" + "esp_ble_mesh/mesh_models/server/sensor_server.c" + "esp_ble_mesh/mesh_models/server/server_common.c" + "esp_ble_mesh/mesh_models/server/state_binding.c" + "esp_ble_mesh/mesh_models/server/state_transition.c" + "esp_ble_mesh/mesh_models/server/time_scene_server.c") endif() endif() diff --git a/components/bt/Kconfig b/components/bt/Kconfig index f5c8b6fbe1..ec8c20317b 100644 --- a/components/bt/Kconfig +++ b/components/bt/Kconfig @@ -1616,3 +1616,971 @@ menu Bluetooth computations. endmenu + +menuconfig BLE_MESH + bool "ESP BLE Mesh Support" + help + This option enables ESP 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" + depends on BLUEDROID_ENABLED + 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 by choosing the + option in the Bluetooth Controller section in menuconfig, which is + "Scan Duplicate By Device Address and Advertising Data". + + config BLE_MESH_ALLOC_FROM_PSRAM_FIRST + bool "BLE Mesh will first allocate memory from PSRAM" + default n + help + When this option is enabled, BLE Mesh stack will try to allocate memory + from PSRAM firstly. This will save the internal RAM if PSRAM exists. + + 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. + When there are multiple unprovisioned devices around, fast provisioning can + greatly reduce the time consumption of the whole provisioning process. + When this option is enabled, and after an unprovisioned device is provisioned + into a node successfully, it can be changed to a temporary Provisioner. + + config BLE_MESH_NODE + bool "Support for BLE Mesh Node" + help + Enable the device to be provisioned into a node. This option should be + enabled when an unprovisioned device is going to be provisioned into a + node and communicate with other nodes in the BLE Mesh network. + + config BLE_MESH_PROVISIONER + bool "Support for BLE Mesh Provisioner" + help + Enable the device to be a Provisioner. The option should be enabled when + a device is going to act as a Provisioner and provision unprovisioned + devices into the BLE Mesh network. + + 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 many unprovisioned devices can be added to device + queue for provisioning. Users can use this option to define the size of the + queue in the bottom layer which is used to store unprovisioned device + information (e.g. Device UUID, address). + + config BLE_MESH_MAX_STORED_NODES + int "Maximum number of nodes whose information can be stored" + default 20 + range BLE_MESH_MAX_PROV_NODES 1000 + help + This option specifies the maximum number of nodes whose information can be + stored by a Provisioner in its upper layer. + Users can change this value according to the number of nodes whose information + (e.g. Device UUID, unicast address, element number) are going to be stored by + a Provisioner. And the nodes include the provisioned ones and user-added ones. + + config BLE_MESH_MAX_PROV_NODES + int "Maximum number of devices that can be provisioned by Provisioner" + default 20 + range 1 1000 + help + This option specifies how many devices can be provisioned by a Provisioner. + This value indicates the maximum number of unprovisioned devices which can be + provisioned by a Provisioner. For instance,if the value is 6, it means the + Provisioner can provision up to 6 unprovisioned devices. + Theoretically a Provisioner without the limitation of its memory can provision + up to 32766 unprovisioned devices, here we limit the maximum number to 100 + just to limit the memory used by a Provisioner. The bigger the value is, the + more memory it will cost by a Provisioner to store the information of nodes. + + 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. For examples, if the value is 2, it means a Provisioner can + provision two unprovisioned devices with PB-ADV at the same time. + + 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. For example, if the value is 2, it means a Provisioner + can provision two unprovisioned devices with PB-GATT at the same time. + + 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. + Indeed, this value decides the number of network keys which can be added by a Provisioner. + + config BLE_MESH_PROVISIONER_APP_KEY_COUNT + int "Maximum number of application keys that can be owned by Provisioner" + default 3 + range 1 4096 + help + This option specifies how many application keys the Provisioner can have. + Indeed, this value decides the number of the application keys which can be added by a Provisioner. + + 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. This option should be enabled if PB-ADV is + going to be used during provisioning procedure. + + 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. + This option should be enabled if PB-GATT is going to be used during + provisioning procedure. + + # 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_SERVER + bool "BLE Mesh GATT Proxy Server" + select BLE_MESH_PROXY + depends on BLE_MESH_NODE + default y + 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. + This option should be enabled if a node is going to be a Proxy Server. + + config BLE_MESH_NODE_ID_TIMEOUT + int "Node Identity advertising timeout" + depends on BLE_MESH_GATT_PROXY_SERVER + 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. + When an unprovisioned device is provisioned successfully and becomes a + node, it will start to advertise using Node Identity during the time + set by this option. And after that, Network ID will be advertised. + + config BLE_MESH_PROXY_FILTER_SIZE + int "Maximum number of filter entries per Proxy Client" + depends on BLE_MESH_GATT_PROXY_SERVER + default 4 + range 1 32767 + help + This option specifies how many Proxy Filter entries the local node supports. + The entries of Proxy filter (whitelist or blacklist) are used to store a + list of addresses which can be used to decide which messages will be forwarded + to the Proxy Client by the Proxy Server. + + config BLE_MESH_GATT_PROXY_CLIENT + bool "BLE Mesh GATT Proxy Client" + select BLE_MESH_PROXY + default n + help + This option enables support for Mesh GATT Proxy Client. The Proxy Client + can use the GATT bearer to send mesh messages to a node that supports the + advertising bearer. + + 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. This option is used to introduce another + variable in the bottom layer to record the usage of advertising buffers of BLE Mesh + devices. Recommend to enable this option as default. + + config BLE_MESH_SETTINGS + bool "Store BLE Mesh configuration persistently" + default n + help + When selected, the BLE Mesh stack will take care of storing/restoring the BLE + Mesh configuration persistently in flash. + If the device is a BLE Mesh node, when this option is enabled, the configuration + of the device will be stored persistently, including unicast address, NetKey, + AppKey, etc. + And if the device is a BLE Mesh Provisioner, the information of the device will + be stored persistently, including the information of provisioned nodes, NetKey, + AppKey, etc. + + if BLE_MESH_SETTINGS + + config BLE_MESH_SPECIFIC_PARTITION + bool "Use a specific NVS partition for BLE Mesh" + default n + help + When selected, the mesh stack will use a specified NVS partition instead of + default NVS partition. Note that the specified partition must be registered + with NVS using nvs_flash_init_partition() API, and the partition must exists + in the csv file. + When Provisioner needs to store a large amount of nodes' information in the + flash (e.g. more than 20), this option is recommended to be enabled. + + if BLE_MESH_SPECIFIC_PARTITION + + config BLE_MESH_PARTITION_NAME + string "Name of the NVS partition for BLE Mesh" + default "ble_mesh" + help + This value defines the name of the specified NVS partition used by the + mesh stack. + + endif # BLE_MESH_SPECIFIC_PARTITION + + 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. + The option allows nodes to delay a certain period of time to save proper + information to flash. The default value is 0, which means information + will be stored immediately once there are updates. + + config BLE_MESH_SEQ_STORE_RATE + int "How often the sequence number gets updated in storage" + range 0 1000000 + default 6 + 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 RPL 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. + Indeed, this value decides the number of the network keys which can be owned by a node. + + 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. + Indeed, this value decides the number of the application keys which can be owned by a node. + + 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. + Indeed, this value decides the number of the Virtual Addresses can be supported by a node. + + 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. + The replay protection list is used to prevent a node from replay attack, + which will store the source address and sequence number of the received + mesh messages. + + 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. + A node is not required to cache the entire Network PDU and may cache + only part of it for tracking, such as values for SRC/SEQ or others. + + 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. + The default value is 1, which means the device can only send one segmented + message at a time. And if another segmented message is going to be sent, it + should wait for the completion of the previous one. + If users are going to send multiple segmented messages at the same time, this + value should be configured properly. + + 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. + The default value is 1, which means the device can only receive one segmented + message at a time. And if another segmented message is going to be received, + it should wait for the completion of the previous one. + If users are going to receive multiple segmented messages at the same time, this + value should be configured properly. + + 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 32 + 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" + depends on BLE_MESH_NODE + default y + help + Support for acting as a Mesh Relay Node. Enabling this option will allow + a node to support the Relay feature, and the Relay feature can still + be enabled or disabled by proper configuration messages. Disabling this + option will let a node not support the Relay feature. + + if BLE_MESH_RELAY + + config BLE_MESH_RELAY_ADV_BUF + bool "Use separate advertising buffers for relay packets" + default n + help + When selected, self-send packets will be put in a high-priority + queue and relay packets will be put in a low-priority queue. + + if BLE_MESH_RELAY_ADV_BUF + + config BLE_MESH_RELAY_ADV_BUF_COUNT + int "Number of advertising buffers for relay packets" + default 60 + range 6 256 + help + Number of advertising buffers for relay packets available. + + endif # BLE_MESH_RELAY_ADV_BUF + + endif # BLE_MESH_RELAY + + config BLE_MESH_LOW_POWER + bool "Support for Low Power features" + depends on BLE_MESH_NODE + help + Enable this option to operate as a Low Power Node. If low power consumption + is required by a node, this option should be enabled. And once the node + enters the mesh network, it will try to find a Friend node and establish a + friendship. + + if BLE_MESH_LOW_POWER + + config BLE_MESH_LPN_ESTABLISHMENT + bool "Perform Friendship establishment using low power" + default n + 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. + When this option is enabled, the node will stop scanning for a period of + time after a Friend Request or Friend Poll is sent, so as to reduce more + power consumption. + + config BLE_MESH_LPN_AUTO + bool "Automatically start looking for Friend nodes once provisioned" + default n + 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). + When an unprovisioned device is provisioned successfully and becomes a node, + enabling this option will trigger the node starts to send Friend Request at + a certain period until it finds a proper Friend node. + + 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 6 + 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. + RSSIFactor, one of the parameters carried by Friend Request sent by Low Power + node, which is used to calculate the Friend Offer Delay. + + 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. + ReceiveWindowFactor, one of the parameters carried by Friend Request sent by + Low Power node, which is used to calculate the Friend Offer Delay. + + 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. + The smaller the value, the faster the Low Power node tries to get + messages from corresponding Friend node and vice versa. + + 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 Friend feature" + 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. + This option decides the size of each buffer which can be used by a Friend node + to store messages for each Low Power node. + + 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. And Low Power node can send Friend Subscription List Add or + Friend Subscription List Remove messages to the Friend node to add or remove + subscription addresses. + + 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. + A Friend node can have friendship with multiple Low Power nodes at the same time, + while a Low Power node can only establish friendship with only one Friend node at + the same time. + + 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. Enabling this option + will disable the output of BLE Mesh debug log. + + 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_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. + This value indicates the maximum time that a client model waits for + the response of the sent acknowledged messages. If a client model + uses 0 as the timeout value when sending acknowledged messages, then + the default value will be used which is four seconds. + + 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. + If IV Update test mode is going to be used, this option should be enabled. + + 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/bluedroid/btc/core/btc_task.c b/components/bt/bluedroid/btc/core/btc_task.c index 1022e770d1..21726cf898 100644 --- a/components/bt/bluedroid/btc/core/btc_task.c +++ b/components/bt/bluedroid/btc/core/btc_task.c @@ -47,6 +47,15 @@ #endif /* #if BTC_HF_CLIENT_INCLUDED */ #endif /* #if CONFIG_CLASSIC_BT_ENABLED */ +#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 */ static xTaskHandle xBtcTaskHandle = NULL; static xQueueHandle xBtcQueue = 0; @@ -87,6 +96,22 @@ static 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 CONFIG_CLASSIC_BT_ENABLED */ +#if CONFIG_BLE_MESH + [BTC_PID_PROV] = {btc_ble_mesh_prov_call_handler, btc_ble_mesh_prov_cb_handler }, + [BTC_PID_MODEL] = {btc_ble_mesh_model_call_handler, btc_ble_mesh_model_cb_handler }, + [BTC_PID_HEALTH_CLIENT] = {btc_ble_mesh_health_client_call_handler, btc_ble_mesh_health_client_cb_handler }, + [BTC_PID_HEALTH_SERVER] = {btc_ble_mesh_health_server_call_handler, btc_ble_mesh_health_server_cb_handler }, + [BTC_PID_CONFIG_CLIENT] = {btc_ble_mesh_config_client_call_handler, btc_ble_mesh_config_client_cb_handler }, + [BTC_PID_CONFIG_SERVER] = {NULL, btc_ble_mesh_config_server_cb_handler }, + [BTC_PID_GENERIC_CLIENT] = {btc_ble_mesh_generic_client_call_handler, btc_ble_mesh_generic_client_cb_handler }, + [BTC_PID_LIGHTING_CLIENT] = {btc_ble_mesh_lighting_client_call_handler, btc_ble_mesh_lighting_client_cb_handler }, + [BTC_PID_SENSOR_CLIENT] = {btc_ble_mesh_sensor_client_call_handler, btc_ble_mesh_sensor_client_cb_handler }, + [BTC_PID_TIME_SCENE_CLIENT] = {btc_ble_mesh_time_scene_client_call_handler, btc_ble_mesh_time_scene_client_cb_handler}, + [BTC_PID_GENERIC_SERVER] = {NULL, btc_ble_mesh_generic_server_cb_handler }, + [BTC_PID_LIGHTING_SERVER] = {NULL, btc_ble_mesh_lighting_server_cb_handler }, + [BTC_PID_SENSOR_SERVER] = {NULL, btc_ble_mesh_sensor_server_cb_handler }, + [BTC_PID_TIME_SCENE_SERVER] = {NULL, btc_ble_mesh_time_scene_server_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 5813c52178..aa916d6f0e 100644 --- a/components/bt/bluedroid/btc/include/btc/btc_task.h +++ b/components/bt/bluedroid/btc/include/btc/btc_task.h @@ -63,6 +63,22 @@ typedef enum { BTC_PID_HF_CLIENT, #endif /* BTC_HF_CLIENT_INCLUDED */ #endif /* CONFIG_CLASSIC_BT_ENABLED */ +#if CONFIG_BLE_MESH + BTC_PID_PROV, + BTC_PID_MODEL, + BTC_PID_HEALTH_CLIENT, + BTC_PID_HEALTH_SERVER, + BTC_PID_CONFIG_CLIENT, + BTC_PID_CONFIG_SERVER, + BTC_PID_GENERIC_CLIENT, + BTC_PID_LIGHTING_CLIENT, + BTC_PID_SENSOR_CLIENT, + BTC_PID_TIME_SCENE_CLIENT, + BTC_PID_GENERIC_SERVER, + BTC_PID_LIGHTING_SERVER, + BTC_PID_SENSOR_SERVER, + BTC_PID_TIME_SCENE_SERVER, +#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 eb9b39c02d..73208b868c 100644 --- a/components/bt/component.mk +++ b/components/bt/component.mk @@ -127,4 +127,33 @@ bluedroid/stack/btm/btm_sec.o: CFLAGS += -Wno-unused-const-variable bluedroid/stack/smp/smp_keys.o: CFLAGS += -Wno-unused-const-variable endif +ifdef CONFIG_BLE_MESH +COMPONENT_ADD_INCLUDEDIRS += bluedroid/osi/include + +COMPONENT_SRCDIRS += esp_ble_mesh/mesh_core/bluedroid_host +endif + +endif + +ifdef CONFIG_BLE_MESH +COMPONENT_ADD_INCLUDEDIRS += esp_ble_mesh/mesh_common/include \ + esp_ble_mesh/mesh_core \ + esp_ble_mesh/mesh_core/include \ + esp_ble_mesh/mesh_core/storage \ + esp_ble_mesh/btc/include \ + esp_ble_mesh/mesh_models/common/include \ + esp_ble_mesh/mesh_models/client/include \ + esp_ble_mesh/mesh_models/server/include \ + esp_ble_mesh/api/core/include \ + esp_ble_mesh/api/models/include \ + esp_ble_mesh/api + +COMPONENT_SRCDIRS += esp_ble_mesh/mesh_common \ + esp_ble_mesh/mesh_core \ + esp_ble_mesh/mesh_core/storage \ + esp_ble_mesh/btc \ + esp_ble_mesh/mesh_models/client \ + esp_ble_mesh/mesh_models/server \ + esp_ble_mesh/api/core \ + esp_ble_mesh/api/models endif diff --git a/components/bt/esp_ble_mesh/README.md b/components/bt/esp_ble_mesh/README.md new file mode 100644 index 0000000000..e25e0b3a36 --- /dev/null +++ b/components/bt/esp_ble_mesh/README.md @@ -0,0 +1,21 @@ +# ESP-BLE-MESH Component + +This is Espressif Bluetooth Low Energy Mesh component folder. + +This component is a part of Espressif IoT Development Framework (ESP-IDF). For the latest documentation please refer to [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/index.html). + +The ESP-BLE-MESH networking enables many-to-many (m:m) device communications and is optimized for creating large-scale device networks. + + +### [ESP-BLE-MESH Documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/esp_ble_mesh/index.html) + +- [Getting Started](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/esp_ble_mesh/index.html##getting-started-with-ble-mesh) +- [Architecture](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/esp_ble_mesh/arhitecture.html) +- [Feature List](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/esp_ble_mesh/ble_mesh-feature-list.html) +- [FAQ](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/esp_ble_mesh/ble_mesh_faq.html) +- [API Reference](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/bluetooth/ble_mesh.html) + + +### [ESP-BLE-MESH Examples](https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/esp_ble_mesh) + +- Refer to **ESP-BLE-MESH Examples** of [Getting Started](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/esp_ble_mesh/index.html##getting-started-with-ble-mesh) for the tutorials of ESP BLE Mesh examples. diff --git a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_common_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_common_api.c new file mode 100644 index 0000000000..b0b29edfea --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_common_api.c @@ -0,0 +1,82 @@ +// Copyright 2017-2019 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_manage.h" + +#include "esp_err.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}; + esp_err_t ret = ESP_OK; + + if (prov == NULL || comp == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + ret = bt_mesh_host_init(); + if (ret != ESP_OK) { + return ret; + } + + // Create a semaphore + if ((semaphore = xSemaphoreCreateCounting(1, 0)) == NULL) { + BT_ERR("%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); + BT_ERR("%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; +} + +esp_err_t esp_ble_mesh_deinit(esp_ble_mesh_deinit_param_t *param) +{ + if (param == NULL) { + return ESP_ERR_INVALID_ARG; + } + + return btc_ble_mesh_deinit(param); +} + diff --git a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c new file mode 100644 index 0000000000..656cae4f0f --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c @@ -0,0 +1,76 @@ +// Copyright 2017-2019 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_manage.h" + +#include "esp_err.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/esp_ble_mesh/api/core/esp_ble_mesh_low_power_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_low_power_api.c new file mode 100644 index 0000000000..c4942b4a66 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_low_power_api.c @@ -0,0 +1,65 @@ +// Copyright 2017-2019 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_manage.h" + +#include "esp_err.h" + +#include "btc_ble_mesh_prov.h" +#include "esp_ble_mesh_defs.h" + +esp_err_t esp_ble_mesh_lpn_enable(void) +{ + btc_msg_t msg = {0}; + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_LPN_ENABLE; + + return (btc_transfer_context(&msg, NULL, 0, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_lpn_disable(bool force) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_LPN_DISABLE; + + arg.lpn_disable.force = force; + + 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_lpn_poll(void) +{ + btc_msg_t msg = {0}; + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_LPN_POLL; + + return (btc_transfer_context(&msg, NULL, 0, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} diff --git a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c new file mode 100644 index 0000000000..7d30b3a646 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c @@ -0,0 +1,511 @@ +// Copyright 2017-2019 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 "esp_err.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_model_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_OK; + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + if (ctx && ctx->addr == ESP_BLE_MESH_ADDR_UNASSIGNED) { + BT_ERR("%s, Invalid destination address 0x0000", __func__); + return ESP_ERR_INVALID_ARG; + } + + if (device_role > ROLE_FAST_PROV) { + BT_ERR("%s, Invalid device role 0x%02x", __func__, device_role); + 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) { + BT_ERR("%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)) { + BT_ERR("%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 *)bt_mesh_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_model_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); + + bt_mesh_free(msg_data); + + return status; +} + +esp_err_t esp_ble_mesh_register_custom_model_callback(esp_ble_mesh_model_cb_t callback) +{ + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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 = 0; + + 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; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return btc_ble_mesh_client_model_init(model); +} + +esp_err_t esp_ble_mesh_client_model_deinit(esp_ble_mesh_model_t *model) +{ + if (model == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return btc_ble_mesh_client_model_deinit(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_model_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_model_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_model_send_msg(model, NULL, opcode, BTC_BLE_MESH_ACT_MODEL_PUBLISH, + length, data, 0, false, device_role); +} + +esp_err_t esp_ble_mesh_server_model_update_state(esp_ble_mesh_model_t *model, + esp_ble_mesh_server_state_type_t type, + esp_ble_mesh_server_state_value_t *value) +{ + btc_ble_mesh_model_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!model || !value || type >= ESP_BLE_MESH_SERVER_MODEL_STATE_MAX) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + arg.model_update_state.model = model; + arg.model_update_state.type = type; + arg.model_update_state.value = value; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_MODEL; + msg.act = BTC_BLE_MESH_ACT_SERVER_MODEL_UPDATE_STATE; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_model_args_t), btc_ble_mesh_model_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_node_local_reset(void) +{ + btc_msg_t msg = {0}; + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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(uint16_t 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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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(uint16_t index) +{ + return bt_mesh_provisioner_get_node_name(index); +} + +uint16_t esp_ble_mesh_provisioner_get_node_index(const char *name) +{ + if (!name || (strlen(name) > ESP_BLE_MESH_NODE_NAME_MAX_LEN)) { + return ESP_BLE_MESH_INVALID_NODE_INDEX; + } + + return bt_mesh_provisioner_get_node_index(name); +} + +esp_err_t esp_ble_mesh_provisioner_store_node_comp_data(uint16_t unicast_addr, uint8_t *data, uint16_t length) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(unicast_addr) || !data || length <= 14) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_STORE_NODE_COMP_DATA; + + arg.store_node_comp_data.unicast_addr = unicast_addr; + arg.store_node_comp_data.length = length; + arg.store_node_comp_data.data = data; + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), btc_ble_mesh_prov_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_ble_mesh_node_t *esp_ble_mesh_provisioner_get_node_with_uuid(const uint8_t uuid[16]) +{ + if (!uuid) { + return NULL; + } + + return btc_ble_mesh_provisioner_get_node_with_uuid(uuid); +} + +esp_ble_mesh_node_t *esp_ble_mesh_provisioner_get_node_with_addr(uint16_t unicast_addr) +{ + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(unicast_addr)) { + return NULL; + } + + return btc_ble_mesh_provisioner_get_node_with_addr(unicast_addr); +} + +esp_err_t esp_ble_mesh_provisioner_delete_node_with_uuid(const uint8_t uuid[16]) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!uuid) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_DELETE_NODE_WITH_UUID; + + memcpy(arg.delete_node_with_uuid.uuid, 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_delete_node_with_addr(uint16_t unicast_addr) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(unicast_addr)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_DELETE_NODE_WITH_ADDR; + + arg.delete_node_with_addr.unicast_addr = unicast_addr; + + 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_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_ADD_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); +} + +esp_err_t esp_ble_mesh_provisioner_update_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}; + + if (app_key == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_UPDATE_LOCAL_APP_KEY; + + memcpy(arg.update_local_app_key.app_key, app_key, 16); + arg.update_local_app_key.net_idx = net_idx; + arg.update_local_app_key.app_idx = app_idx; + 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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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); +} + +esp_err_t esp_ble_mesh_provisioner_update_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_key == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_UPDATE_LOCAL_NET_KEY; + + memcpy(arg.update_local_net_key.net_key, net_key, 16); + arg.update_local_net_key.net_idx = net_idx; + 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); +} + +uint16_t esp_ble_mesh_provisioner_get_prov_node_count(void) +{ + return btc_ble_mesh_provisioner_get_prov_node_count(); +} + +#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/esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c new file mode 100644 index 0000000000..c3dfdc04a9 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c @@ -0,0 +1,494 @@ +// Copyright 2017-2019 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 "esp_err.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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_ble_mesh_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_prov_device_with_addr(const uint8_t uuid[16], + esp_ble_mesh_bd_addr_t addr, esp_ble_mesh_addr_type_t addr_type, + esp_ble_mesh_prov_bearer_t bearer, uint16_t oob_info, uint16_t unicast_addr) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (uuid == NULL || (bearer == ESP_BLE_MESH_PROV_GATT && (addr == NULL || + addr_type > ESP_BLE_MESH_ADDR_TYPE_RANDOM)) || + (bearer != ESP_BLE_MESH_PROV_ADV && bearer != ESP_BLE_MESH_PROV_GATT) || + !ESP_BLE_MESH_ADDR_IS_UNICAST(unicast_addr)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_PROV_DEV_WITH_ADDR; + + memcpy(arg.provisioner_prov_dev_with_addr.uuid, uuid, 16); + if (addr) { + memcpy(arg.provisioner_prov_dev_with_addr.addr, addr, BD_ADDR_LEN); + arg.provisioner_prov_dev_with_addr.addr_type = addr_type; + } + arg.provisioner_prov_dev_with_addr.bearer = bearer; + arg.provisioner_prov_dev_with_addr.oob_info = oob_info; + arg.provisioner_prov_dev_with_addr.unicast_addr = unicast_addr; + 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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_ble_mesh_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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); +} + +esp_err_t esp_ble_mesh_provisioner_set_static_oob_value(const uint8_t *value, uint8_t length) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (value == NULL || length == 0 || length > 16) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_SET_STATIC_OOB_VAL; + + arg.set_static_oob_val.length = length; + memcpy(arg.set_static_oob_val.value, value, length); + 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_primary_elem_addr(uint16_t addr) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!ESP_BLE_MESH_ADDR_IS_UNICAST(addr)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROVISIONER_SET_PRIMARY_ELEM_ADDR; + + arg.set_primary_elem_addr.addr = addr; + 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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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/esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c new file mode 100644 index 0000000000..071e0e37fc --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c @@ -0,0 +1,177 @@ +// Copyright 2017-2019 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 "esp_err.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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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); +} + +esp_err_t esp_ble_mesh_proxy_client_connect(esp_ble_mesh_bd_addr_t addr, + esp_ble_mesh_addr_type_t addr_type, uint16_t net_idx) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!addr || addr_type > ESP_BLE_MESH_ADDR_TYPE_RANDOM) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROXY_CLIENT_CONNECT; + + memcpy(arg.proxy_client_connect.addr, addr, BD_ADDR_LEN); + arg.proxy_client_connect.addr_type = addr_type; + arg.proxy_client_connect.net_idx = net_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_proxy_client_disconnect(uint8_t conn_handle) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROXY_CLIENT_DISCONNECT; + + arg.proxy_client_disconnect.conn_handle = conn_handle; + + 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_proxy_client_set_filter_type(uint8_t conn_handle, + uint16_t net_idx, esp_ble_mesh_proxy_filter_type_t filter_type) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (filter_type > PROXY_FILTER_BLACKLIST) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROXY_CLIENT_SET_FILTER_TYPE; + + arg.proxy_client_set_filter_type.conn_handle = conn_handle; + arg.proxy_client_set_filter_type.net_idx = net_idx; + arg.proxy_client_set_filter_type.filter_type = filter_type; + + 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_proxy_client_add_filter_addr(uint8_t conn_handle, + uint16_t net_idx, uint16_t *addr, uint16_t addr_num) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!addr || addr_num == 0) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROXY_CLIENT_ADD_FILTER_ADDR; + + arg.proxy_client_add_filter_addr.conn_handle = conn_handle; + arg.proxy_client_add_filter_addr.net_idx = net_idx; + arg.proxy_client_add_filter_addr.addr_num = addr_num; + arg.proxy_client_add_filter_addr.addr = addr; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), btc_ble_mesh_prov_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_proxy_client_remove_filter_addr(uint8_t conn_handle, + uint16_t net_idx, uint16_t *addr, uint16_t addr_num) +{ + btc_ble_mesh_prov_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!addr || addr_num == 0) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_PROV; + msg.act = BTC_BLE_MESH_ACT_PROXY_CLIENT_REMOVE_FILTER_ADDR; + + arg.proxy_client_remove_filter_addr.conn_handle = conn_handle; + arg.proxy_client_remove_filter_addr.net_idx = net_idx; + arg.proxy_client_remove_filter_addr.addr_num = addr_num; + arg.proxy_client_remove_filter_addr.addr = addr; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), btc_ble_mesh_prov_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} diff --git a/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_common_api.h b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_common_api.h new file mode 100644 index 0000000000..e218c51b74 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_common_api.h @@ -0,0 +1,49 @@ +// Copyright 2017-2019 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); + +/** + * @brief De-initialize BLE Mesh module. + * + * @note This function shall be invoked after esp_ble_mesh_client_model_deinit(). + * + * @param[in] param: Pointer to the structure of BLE Mesh deinit parameters. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_deinit(esp_ble_mesh_deinit_param_t *param); + +#endif /* _ESP_BLE_MESH_COMMON_API_H_ */ diff --git a/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_local_data_operation_api.h b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_local_data_operation_api.h new file mode 100644 index 0000000000..a1b0ff00de --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_local_data_operation_api.h @@ -0,0 +1,107 @@ +// Copyright 2017-2019 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. + * + * @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. + * + * @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. + * + * @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/esp_ble_mesh/api/core/include/esp_ble_mesh_low_power_api.h b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_low_power_api.h new file mode 100644 index 0000000000..c579b2e05f --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_low_power_api.h @@ -0,0 +1,61 @@ +// Copyright 2017-2019 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" + +/** + * @brief Enable BLE Mesh device LPN functionality. + * + * @note This API enables LPN functionality. Once called, the proper + * Friend Request will be sent. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_lpn_enable(void); + +/** + * @brief Disable BLE Mesh device LPN functionality. + * + * @param[in] force: when disabling LPN functionality, use this flag to indicate + * whether directly clear corresponding information or just + * send friend clear to disable it if friendship has already + * been established. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_lpn_disable(bool force); + +/** + * @brief LPN tries to poll messages from the Friend Node. + * + * @note The Friend Poll message is sent by a Low Power node to ask the Friend + * node to send a message that it has stored for the Low Power node. + * Users can call this API to send Friend Poll message manually. If this + * API is not invoked, the bottom layer of the Low Power node will send + * Friend Poll before the PollTimeout timer expires. + * If the corresponding Friend Update is received and MD is set to 0, + * which means there are no messages for the Low Power node, then the + * Low Power node will stop scanning. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_lpn_poll(void); + +#endif /* _ESP_BLE_MESH_LOW_POWER_API_H_ */ diff --git a/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_networking_api.h b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_networking_api.h new file mode 100644 index 0000000000..0d5bcec30b --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_networking_api.h @@ -0,0 +1,384 @@ +// Copyright 2017-2019 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 De-initialize the user-defined client model. + * + * @note This function shall be invoked before esp_ble_mesh_deinit() is called. + * + * @param[in] model: Pointer of the Client model. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_client_model_deinit(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 Update a server model state value. If the model publication + * state is set properly (e.g. publish address is set to a valid + * address), it will publish corresponding status message. + * + * @note Currently this API is used to update bound state value, not + * for all server model states. + * + * @param[in] model: Server model which is going to update the state. + * @param[in] type: Server model state type. + * @param[in] value: Server model state value. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_server_model_update_state(esp_ble_mesh_model_t *model, + esp_ble_mesh_server_state_type_t type, + esp_ble_mesh_server_state_value_t *value); + +/** + * @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. + * + * @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(uint16_t 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(uint16_t 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 an invalid value (0xFFFF) on failure. + * + */ +uint16_t esp_ble_mesh_provisioner_get_node_index(const char *name); + +/** + * @brief This function is called to store the Composition Data of the node. + * + * @param[in] unicast_addr: Element address of the node + * @param[in] data: Pointer of Composition Data + * @param[in] length: Length of Composition Data + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_store_node_comp_data(uint16_t unicast_addr, uint8_t *data, uint16_t length); + +/** + * @brief This function is called to get the provisioned node information + * with the node device uuid. + * + * @param[in] uuid: Device UUID of the node + * + * @return Pointer of the node info struct or NULL on failure. + * + */ +esp_ble_mesh_node_t *esp_ble_mesh_provisioner_get_node_with_uuid(const uint8_t uuid[16]); + +/** + * @brief This function is called to get the provisioned node information + * with the node unicast address. + * + * @param[in] unicast_addr: Unicast address of the node + * + * @return Pointer of the node info struct or NULL on failure. + * + */ +esp_ble_mesh_node_t *esp_ble_mesh_provisioner_get_node_with_addr(uint16_t unicast_addr); + +/** + * @brief This function is called to delete the provisioned node information + * with the node device uuid. + * + * @param[in] uuid: Device UUID of the node + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_delete_node_with_uuid(const uint8_t uuid[16]); + +/** + * @brief This function is called to delete the provisioned node information + * with the node unicast address. + * + * @param[in] unicast_addr: Unicast address of the node + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_delete_node_with_addr(uint16_t unicast_addr); + +/** + * @brief This function is called to add a local AppKey for Provisioner. + * + * @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 used to update a local AppKey for Provisioner. + * + * @param[in] app_key: Value of the AppKey. + * @param[in] net_idx: Corresponding NetKey Index. + * @param[in] app_idx: The AppKey Index + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_update_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 update a local network key. + * + * @param[in] net_key: Value of the NetKey. + * @param[in] net_idx: The NetKey Index. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_update_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 by Provisioner to get provisioned node count. + * + * @return Number of the provisioned nodes. + * + */ +uint16_t esp_ble_mesh_provisioner_get_prov_node_count(void); + +/** + * @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/esp_ble_mesh/api/core/include/esp_ble_mesh_provisioning_api.h b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_provisioning_api.h new file mode 100644 index 0000000000..b2ebe07c9a --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_provisioning_api.h @@ -0,0 +1,370 @@ +// Copyright 2017-2019 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. + * + * @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 Provision an unprovisioned device with fixed unicast address. + * + * @param[in] uuid: Device UUID of the unprovisioned device + * @param[in] addr: Device address of the unprovisioned device + * @param[in] addr_type: Device address type of the unprovisioned device + * @param[in] bearer: Provisioning bearer going to be used by Provisioner + * @param[in] oob_info: OOB info of the unprovisioned device + * @param[in] unicast_addr: Unicast address going to be allocated for the unprovisioned device + * + * @return Zero on success or (negative) error code otherwise. + * + * @note: 1. Currently address type only supports public address and static random address. + * 2. Bearer must be equal to ESP_BLE_MESH_PROV_ADV or ESP_BLE_MESH_PROV_GATT, since + * Provisioner will start to provision a device immediately once this function is + * invked. And the input bearer must be identical with the one within the parameters + * of the ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT event. + * 3. If this function is used by a Provisioner to provision devices, the application + * should take care of the assigned unicast address and avoid overlap of the unicast + * addresses of different nodes. + * 4. Recommend to use only one of the functions "esp_ble_mesh_provisioner_add_unprov_dev" + * and "esp_ble_mesh_provisioner_prov_device_with_addr" by a Provisioner. + */ +esp_err_t esp_ble_mesh_provisioner_prov_device_with_addr(const uint8_t uuid[16], + esp_ble_mesh_bd_addr_t addr, esp_ble_mesh_addr_type_t addr_type, + esp_ble_mesh_prov_bearer_t bearer, uint16_t oob_info, uint16_t unicast_addr); + +/** + * @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_ble_mesh_bd_addr_t addr, const esp_ble_mesh_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 by Provisioner to set static oob value used for provisioning. + * + * @param[in] value: Pointer to the static oob value. + * @param[in] length: Length of the static oob value. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_set_static_oob_value(const uint8_t *value, uint8_t length); + +/** + * @brief This function is called by Provisioner to set own Primary element address. + * + * @note This API must be invoked when BLE Mesh initialization is completed successfully, + * and can be invoked before Provisioner functionality is enabled. + * Once this API is invoked successfully, the prov_unicast_addr value in the struct + * esp_ble_mesh_prov_t will be ignored, and Provisioner will use this address as its + * own primary element address. + * And if the unicast address going to assigned for the next unprovisioned device is + * smaller than the input address + element number of Provisioner, then the address + * for the next unprovisioned device will be recalculated internally. + * + * @param[in] addr: Unicast address of the Primary element of Provisioner. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_provisioner_set_primary_elem_addr(uint16_t addr); + +/** + * @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/esp_ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h new file mode 100644 index 0000000000..216a597ff4 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h @@ -0,0 +1,118 @@ +// Copyright 2017-2019 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. + * + * @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. + * + * @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. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_proxy_gatt_disable(void); + +/** + * @brief Proxy Client creates a connection with the Proxy Server. + * + * @param[in] addr: Device address of the Proxy Server. + * @param[in] addr_type: Device address type(public or static random). + * @param[in] net_idx: NetKey Index related with Network ID in the Mesh Proxy + * advertising packet. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_proxy_client_connect(esp_ble_mesh_bd_addr_t addr, + esp_ble_mesh_addr_type_t addr_type, uint16_t net_idx); + +/** + * @brief Proxy Client terminates a connection with the Proxy Server. + * + * @param[in] conn_handle: Proxy connection handle. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_proxy_client_disconnect(uint8_t conn_handle); + +/** + * @brief Proxy Client sets the filter type of the Proxy Server. + * + * @param[in] conn_handle: Proxy connection handle. + * @param[in] net_idx: Corresponding NetKey Index. + * @param[in] filter_type: whitelist or blacklist. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_proxy_client_set_filter_type(uint8_t conn_handle, + uint16_t net_idx, esp_ble_mesh_proxy_filter_type_t filter_type); + +/** + * @brief Proxy Client adds address to the Proxy Server filter list. + * + * @param[in] conn_handle: Proxy connection handle. + * @param[in] net_idx: Corresponding NetKey Index. + * @param[in] addr: Pointer to the filter address. + * @param[in] addr_num: Number of the filter address. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_proxy_client_add_filter_addr(uint8_t conn_handle, + uint16_t net_idx, uint16_t *addr, uint16_t addr_num); + +/** + * @brief Proxy Client removes address from the Proxy Server filter list. + * + * @param[in] conn_handle: Proxy connection handle. + * @param[in] net_idx: Corresponding NetKey Index. + * @param[in] addr: Pointer to the filter address. + * @param[in] addr_num: Number of the filter address. + * + * @return ESP_OK on success or error code otherwise. + * + */ +esp_err_t esp_ble_mesh_proxy_client_remove_filter_addr(uint8_t conn_handle, + uint16_t net_idx, uint16_t *addr, uint16_t addr_num); + +#endif /* _ESP_BLE_MESH_PROXY_API_H_ */ + diff --git a/components/bt/esp_ble_mesh/api/esp_ble_mesh_defs.h b/components/bt/esp_ble_mesh/api/esp_ble_mesh_defs.h new file mode 100644 index 0000000000..bdc1c7fdb7 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/esp_ble_mesh_defs.h @@ -0,0 +1,2024 @@ +// Copyright 2017-2019 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 "mesh_common.h" +#include "proxy_server.h" +#include "provisioner_main.h" + +#ifdef CONFIG_BLUEDROID_ENABLED +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#define ESP_BLE_HOST_STATUS_ENABLED ESP_BLUEDROID_STATUS_ENABLED +#define ESP_BLE_HOST_STATUS_CHECK(status) ESP_BLUEDROID_STATUS_CHECK(status) +#else +#define ESP_BLE_HOST_STATUS_ENABLED 0 +#define ESP_BLE_HOST_STATUS_CHECK(status) do {} while (0) +#endif + +/*!< The maximum length of a BLE Mesh message, including Opcode, Payload and TransMIC */ +#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 0x0000 +#define ESP_BLE_MESH_ADDR_ALL_NODES 0xFFFF +#define ESP_BLE_MESH_ADDR_PROXIES 0xFFFC +#define ESP_BLE_MESH_ADDR_FRIENDS 0xFFFD +#define ESP_BLE_MESH_ADDR_RELAYS 0xFFFE + +#define ESP_BLE_MESH_KEY_UNUSED 0xFFFF +#define ESP_BLE_MESH_KEY_DEV 0xFFFE + +#define ESP_BLE_MESH_KEY_PRIMARY 0x0000 +#define ESP_BLE_MESH_KEY_ANY 0xFFFF + +/*!< Primary Network Key index */ +#define ESP_BLE_MESH_NET_PRIMARY 0x000 + +/*!< Relay state value */ +#define ESP_BLE_MESH_RELAY_DISABLED 0x00 +#define ESP_BLE_MESH_RELAY_ENABLED 0x01 +#define ESP_BLE_MESH_RELAY_NOT_SUPPORTED 0x02 + +/*!< Beacon state value */ +#define ESP_BLE_MESH_BEACON_DISABLED 0x00 +#define ESP_BLE_MESH_BEACON_ENABLED 0x01 + +/*!< GATT Proxy state value */ +#define ESP_BLE_MESH_GATT_PROXY_DISABLED 0x00 +#define ESP_BLE_MESH_GATT_PROXY_ENABLED 0x01 +#define ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED 0x02 + +/*!< Friend state value */ +#define ESP_BLE_MESH_FRIEND_DISABLED 0x00 +#define ESP_BLE_MESH_FRIEND_ENABLED 0x01 +#define ESP_BLE_MESH_FRIEND_NOT_SUPPORTED 0x02 + +/*!< Node identity state value */ +#define ESP_BLE_MESH_NODE_IDENTITY_STOPPED 0x00 +#define ESP_BLE_MESH_NODE_IDENTITY_RUNNING 0x01 +#define ESP_BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED 0x02 + +/*!< Supported features */ +#define ESP_BLE_MESH_FEATURE_RELAY BIT(0) +#define ESP_BLE_MESH_FEATURE_PROXY BIT(1) +#define ESP_BLE_MESH_FEATURE_FRIEND BIT(2) +#define ESP_BLE_MESH_FEATURE_LOW_POWER BIT(3) +#define ESP_BLE_MESH_FEATURE_ALL_SUPPORTED (ESP_BLE_MESH_FEATURE_RELAY | \ + ESP_BLE_MESH_FEATURE_PROXY | \ + ESP_BLE_MESH_FEATURE_FRIEND | \ + ESP_BLE_MESH_FEATURE_LOW_POWER) + +#define ESP_BLE_MESH_ADDR_IS_UNICAST(addr) ((addr) && (addr) < 0x8000) +#define ESP_BLE_MESH_ADDR_IS_GROUP(addr) ((addr) >= 0xC000 && (addr) <= 0xFF00) +#define ESP_BLE_MESH_ADDR_IS_VIRTUAL(addr) ((addr) >= 0x8000 && (addr) < 0xC000) +#define ESP_BLE_MESH_ADDR_IS_RFU(addr) ((addr) >= 0xFF00 && (addr) <= 0xFFFB) + +#define ESP_BLE_MESH_INVALID_NODE_INDEX 0xFFFF + +/** @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) ((count) | (((int_ms / 10) - 1) << 3)) + +/** @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) (((transmit) & (uint8_t)BIT_MASK(3))) + +/** @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) ((((transmit) >> 3) + 1) * 10) + +/** @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) ESP_BLE_MESH_TRANSMIT(count, (int_ms) / 5) + +/** @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) ESP_BLE_MESH_GET_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) ((((transmit) >> 3) + 1) * 50) + +/*!< Callbacks which are 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; + +/*!< Macros used to define message opcode */ +#define ESP_BLE_MESH_MODEL_OP_1(b0) (b0) +#define ESP_BLE_MESH_MODEL_OP_2(b0, b1) (((b0) << 8) | (b1)) +#define ESP_BLE_MESH_MODEL_OP_3(b0, cid) ((((b0) << 16) | 0xC00000) | (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 uint8_t UINT8; +typedef uint16_t UINT16; +typedef uint32_t UINT32; +typedef uint64_t UINT64; + +#define BT_OCTET32_LEN 32 +typedef UINT8 BT_OCTET32[BT_OCTET32_LEN]; /* octet array: size 32 */ + + +#ifndef BD_ADDR_LEN +#define BD_ADDR_LEN 6 +typedef uint8_t BD_ADDR[BD_ADDR_LEN]; +#endif + +typedef uint8_t esp_ble_mesh_bd_addr_t[BD_ADDR_LEN]; + +#define ESP_BLE_MESH_ADDR_TYPE_PUBLIC 0x00 +#define ESP_BLE_MESH_ADDR_TYPE_RANDOM 0x01 +#define ESP_BLE_MESH_ADDR_TYPE_RPA_PUBLIC 0x02 +#define ESP_BLE_MESH_ADDR_TYPE_RPA_RANDOM 0x03 +/// BLE device address type +typedef uint8_t esp_ble_mesh_addr_type_t; + +/** BLE Mesh deinit parameters */ +typedef struct { + bool erase_flash; /*!< Indicate if erasing flash when deinit mesh stack */ +} esp_ble_mesh_deinit_param_t; + +typedef struct esp_ble_mesh_model esp_ble_mesh_model_t; + +/** Abstraction that describes a BLE Mesh Element. + * This structure is associated with struct 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; + + const uint8_t sig_model_count; /*!< SIG Model count */ + const uint8_t vnd_model_count; /*!< Vendor Model count */ + + esp_ble_mesh_model_t *sig_models; /*!< SIG Models */ + esp_ble_mesh_model_t *vnd_models; /*!< Vendor Models */ +} esp_ble_mesh_elem_t; + +/** Abstraction that describes a model publication context. + * This structure is associated with struct bt_mesh_model_pub in mesh_access.h + */ +typedef struct { + /** Pointer to 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:12, /*!< Publish AppKey Index. */ + cred:1; /*!< Friendship Credentials Flag. */ + + uint8_t ttl; /*!< Publish Time to Live. */ + uint8_t retransmit; /*!< Retransmit Count & Interval Steps. */ + + uint8_t period; /*!< Publish Period. */ + uint8_t period_div:4, /*!< Divisor for the Period. */ + 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; + + /** Callback used to update publish message. Initialized by the stack. */ + esp_ble_mesh_cb_t update; + + /** Role of the device that is going to publish messages */ + uint8_t dev_role; + + /** Publish Period Timer. Initialized by the stack. */ + 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, \ + } + +/** @def ESP_BLE_MESH_MODEL_OP + * + * Define a model operation context. + * + * @param _opcode Message opcode. + * @param _min_len Message minimum length. + */ +#define ESP_BLE_MESH_MODEL_OP(_opcode, _min_len) \ +{ \ + .opcode = _opcode, \ + .min_len = _min_len, \ + .param_cb = (uint32_t)NULL, \ +} + +/** Abstraction that describes a model operation context. + * This structure is associated with struct bt_mesh_model_op in mesh_access.h + */ +typedef struct { + const uint32_t opcode; /*!< Message opcode */ + const size_t min_len; /*!< Message minimum length */ + esp_ble_mesh_cb_t param_cb; /*!< Callback used to handle message. Initialized by the stack. */ +} 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 struct 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 struct 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; + + /** RSSI of received packet. Not used for sending. */ + int8_t recv_rssi; + + /** 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 struct 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; + + /** Callback used to notify to set OOB Public Key. Initialized by the stack. */ + 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; + + /** Callback used to output the number. Initialized by the stack. */ + esp_ble_mesh_cb_t output_num_cb; + /** Callback used to output the string. Initialized by the stack. */ + esp_ble_mesh_cb_t output_str_cb; + /** Callback used to notify to input number/string. Initialized by the stack. */ + esp_ble_mesh_cb_t input_cb; + /** Callback used to indicate that link is opened. Initialized by the stack. */ + esp_ble_mesh_cb_t link_open_cb; + /** Callback used to indicate that link is closed. Initialized by the stack. */ + esp_ble_mesh_cb_t link_close_cb; + /** Callback used to indicate that provisioning is completed. Initialized by the stack. */ + esp_ble_mesh_cb_t complete_cb; + /** Callback used to indicate that node has been reset. Initialized by the stack. */ + 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; + + /** Callback used to notify to set device OOB Public Key. Initialized by the stack. */ + 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; + + /** Callback used to notify to input number/string. Initialized by the stack. */ + esp_ble_mesh_cb_t provisioner_prov_input; + /** Callback used to output number/string. Initialized by the stack. */ + esp_ble_mesh_cb_t provisioner_prov_output; + + /** Key refresh and IV update flag */ + uint8_t flags; + + /** IV index */ + uint32_t iv_index; + + /** Callback used to indicate that link is opened. Initialized by the stack. */ + esp_ble_mesh_cb_t provisioner_link_open; + /** Callback used to indicate that link is closed. Initialized by the stack. */ + esp_ble_mesh_cb_t provisioner_link_close; + /** Callback used to indicate that a device is provisioned. Initialized by the stack. */ + esp_ble_mesh_cb_t provisioner_prov_comp; +#endif /* CONFIG_BLE_MESH_PROVISIONER */ +} esp_ble_mesh_prov_t; + +/** Node Composition data context. + * This structure is associated with struct bt_mesh_comp in mesh_access.h + */ +typedef struct { + uint16_t cid; /*!< 16-bit SIG-assigned company identifier */ + uint16_t pid; /*!< 16-bit vendor-assigned product identifier */ + uint16_t vid; /*!< 16-bit vendor-assigned product version identifier */ + + size_t element_count; /*!< Element count */ + esp_ble_mesh_elem_t *elements; /*!< A sequence of elements */ +} esp_ble_mesh_comp_t; + +/*!< This enum value is the role of the device */ +typedef enum { + ROLE_NODE = 0, + ROLE_PROVISIONER, + ROLE_FAST_PROV, +} esp_ble_mesh_dev_role_t; + +/*!< Flag which will be set when device is going to be added. */ +typedef uint8_t esp_ble_mesh_dev_add_flag_t; +#define ADD_DEV_RM_AFTER_PROV_FLAG BIT(0) /*!< Device will be removed from queue after provisioned successfully */ +#define ADD_DEV_START_PROV_NOW_FLAG BIT(1) /*!< Start provisioning device immediately */ +#define ADD_DEV_FLUSHABLE_DEV_FLAG BIT(2) /*!< Device can be remove when queue is full and new device is going to added */ + +/** Information of the device which is going to be added for provisioning. */ +typedef struct { + esp_ble_mesh_bd_addr_t addr; /*!< Device address */ + esp_ble_mesh_addr_type_t addr_type; /*!< Device address type */ + uint8_t uuid[16]; /*!< Device UUID */ + uint16_t oob_info; /*!< Device 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; /*!< Provisioning Bearer */ +} esp_ble_mesh_unprov_dev_add_t; + +#define DEL_DEV_ADDR_FLAG BIT(0) +#define DEL_DEV_UUID_FLAG BIT(1) +/** Information of the device which is going to be deleted. */ +typedef struct { + union { + struct { + esp_ble_mesh_bd_addr_t addr; /*!< Device address */ + esp_ble_mesh_addr_type_t addr_type; /*!< Device address type */ + }; + uint8_t uuid[16]; /*!< Device UUID */ + }; + 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) +/** Information of the provisioner which is going to be updated. */ +typedef struct { + union { + uint16_t net_idx; /*!< NetKey Index */ + uint8_t flags; /*!< Flags */ + uint32_t iv_index; /*!< IV Index */ + }; + uint8_t flag; /*!< BIT0: net_idx; BIT1: flags; BIT2: iv_index */ +} esp_ble_mesh_prov_data_info_t; + +/** Information of the provisioned node */ +typedef struct { + /* Device information */ + esp_ble_mesh_bd_addr_t addr; /*!< Node device address */ + esp_ble_mesh_addr_type_t addr_type; /*!< Node device address type */ + uint8_t dev_uuid[16]; /*!< Device UUID */ + uint16_t oob_info; /*!< Node OOB information */ + + /* Provisioning information */ + uint16_t unicast_addr; /*!< Node unicast address */ + uint8_t element_num; /*!< Node element number */ + uint16_t net_idx; /*!< Node NetKey Index */ + uint8_t flags; /*!< Node key refresh flag and iv update flag */ + uint32_t iv_index; /*!< Node IV Index */ + uint8_t dev_key[16]; /*!< Node device key */ + + /* Additional information */ + char name[ESP_BLE_MESH_NODE_NAME_MAX_LEN]; /*!< Node name */ + uint16_t comp_length; /*!< Length of Composition Data */ + uint8_t *comp_data; /*!< Value of Composition Data */ +} __attribute__((packed)) esp_ble_mesh_node_t; + +/** Context of fast provisioning which need to be set. */ +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; + +/*!< This enum value is the action of fast provisioning */ +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; + +/*!< This enum value is the type of proxy filter */ +typedef enum { + PROXY_FILTER_WHITELIST, + PROXY_FILTER_BLACKLIST, +} esp_ble_mesh_proxy_filter_type_t; + +/*!< This enum value is the event of node/provisioner/fast provisioning */ +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_PROV_DEV_WITH_ADDR_COMP_EVT, /*!< Provisioner start to provision an unprovisioned device 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_SET_STATIC_OOB_VALUE_COMP_EVT, /*!< Provisioner set static oob value used for provisioning completion event */ + ESP_BLE_MESH_PROVISIONER_SET_PRIMARY_ELEM_ADDR_COMP_EVT, /*!< Provisioner set unicast address of primary element 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_UPDATE_LOCAL_APP_KEY_COMP_EVT, /*!< Provisioner update 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_PROVISIONER_UPDATE_LOCAL_NET_KEY_COMP_EVT, /*!< Provisioner update local network key completion event */ + ESP_BLE_MESH_PROVISIONER_STORE_NODE_COMP_DATA_COMP_EVT, /*!< Provisioner store node composition data completion event */ + ESP_BLE_MESH_PROVISIONER_DELETE_NODE_WITH_UUID_COMP_EVT, /*!< Provisioner delete node with uuid completion event */ + ESP_BLE_MESH_PROVISIONER_DELETE_NODE_WITH_ADDR_COMP_EVT, /*!< Provisioner delete node with unicast address 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_HEARTBEAT_MESSAGE_RECV_EVT, /*!< Receive Heartbeat message event */ + ESP_BLE_MESH_LPN_ENABLE_COMP_EVT, /*!< Enable Low Power Node completion event */ + ESP_BLE_MESH_LPN_DISABLE_COMP_EVT, /*!< Disable Low Power Node completion event */ + ESP_BLE_MESH_LPN_POLL_COMP_EVT, /*!< Low Power Node send Friend Poll completion event */ + ESP_BLE_MESH_LPN_FRIENDSHIP_ESTABLISH_EVT, /*!< Low Power Node establishes friendship event */ + ESP_BLE_MESH_LPN_FRIENDSHIP_TERMINATE_EVT, /*!< Low Power Node terminates friendship event */ + ESP_BLE_MESH_FRIEND_FRIENDSHIP_ESTABLISH_EVT, /*!< Friend Node establishes friendship event */ + ESP_BLE_MESH_FRIEND_FRIENDSHIP_TERMINATE_EVT, /*!< Friend Node terminates friendship event */ + ESP_BLE_MESH_PROXY_CLIENT_RECV_ADV_PKT_EVT, /*!< Proxy Client receives Network ID advertising packet event */ + ESP_BLE_MESH_PROXY_CLIENT_CONNECTED_EVT, /*!< Proxy Client establishes connection successfully event */ + ESP_BLE_MESH_PROXY_CLIENT_DISCONNECTED_EVT, /*!< Proxy Client terminates connection successfully event */ + ESP_BLE_MESH_PROXY_CLIENT_RECV_FILTER_STATUS_EVT, /*!< Proxy Client receives Proxy Filter Status event */ + ESP_BLE_MESH_PROXY_CLIENT_CONNECT_COMP_EVT, /*!< Proxy Client connect completion event */ + ESP_BLE_MESH_PROXY_CLIENT_DISCONNECT_COMP_EVT, /*!< Proxy Client disconnect completion event */ + ESP_BLE_MESH_PROXY_CLIENT_SET_FILTER_TYPE_COMP_EVT, /*!< Proxy Client set filter type completion event */ + ESP_BLE_MESH_PROXY_CLIENT_ADD_FILTER_ADDR_COMP_EVT, /*!< Proxy Client add filter address completion event */ + ESP_BLE_MESH_PROXY_CLIENT_REMOVE_FILTER_ADDR_COMP_EVT, /*!< Proxy Client remove filter address completion event */ + ESP_BLE_MESH_PROV_EVT_MAX, +} esp_ble_mesh_prov_cb_event_t; + +/** + * @brief BLE Mesh Node/Provisioner callback parameters union + */ +typedef union { + /** + * @brief ESP_BLE_MESH_PROV_REGISTER_COMP_EVT + */ + struct ble_mesh_prov_register_comp_param { + int err_code; /*!< Indicate the result of BLE Mesh initialization */ + } prov_register_comp; /*!< Event parameter of ESP_BLE_MESH_PROV_REGISTER_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT + */ + struct ble_mesh_set_unprov_dev_name_comp_param { + int err_code; /*!< Indicate the result of setting BLE Mesh device name */ + } node_set_unprov_dev_name_comp; /*!< Event parameter of ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT + */ + struct ble_mesh_prov_enable_comp_param { + int err_code; /*!< Indicate the result of enabling BLE Mesh device */ + } node_prov_enable_comp; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_ENABLE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_DISABLE_COMP_EVT + */ + struct ble_mesh_prov_disable_comp_param { + int err_code; /*!< Indicate the result of disabling BLE Mesh device */ + } node_prov_disable_comp; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_DISABLE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT + */ + struct ble_mesh_link_open_evt_param { + esp_ble_mesh_prov_bearer_t bearer; /*!< Type of the bearer used when device link is open */ + } node_prov_link_open; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT + */ + struct ble_mesh_link_close_evt_param { + esp_ble_mesh_prov_bearer_t bearer; /*!< Type of the bearer used when device link is closed */ + } node_prov_link_close; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT + */ + struct ble_mesh_output_num_evt_param { + esp_ble_mesh_output_action_t action; /*!< Action of Output OOB Authentication */ + uint32_t number; /*!< Number of Output OOB Authentication */ + } node_prov_output_num; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT + */ + struct ble_mesh_output_str_evt_param { + char string[8]; /*!< String of Output OOB Authentication */ + } node_prov_output_str; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_INPUT_EVT + */ + struct ble_mesh_input_evt_param { + esp_ble_mesh_input_action_t action; /*!< Action of Input OOB Authentication */ + uint8_t size; /*!< Size of Input OOB Authentication */ + } node_prov_input; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_INPUT_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT + */ + struct ble_mesh_provision_complete_evt_param { + uint16_t net_idx; /*!< NetKey Index */ + uint8_t net_key[16]; /*!< NetKey */ + uint16_t addr; /*!< Primary address */ + uint8_t flags; /*!< Flags */ + uint32_t iv_index; /*!< IV Index */ + } node_prov_complete; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_RESET_EVT + */ + struct ble_mesh_provision_reset_param { + + } node_prov_reset; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_RESET_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_SET_OOB_PUB_KEY_COMP_EVT + */ + struct ble_mesh_set_oob_pub_key_comp_param { + int err_code; /*!< Indicate the result of setting OOB Public Key */ + } node_prov_set_oob_pub_key_comp; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_SET_OOB_PUB_KEY_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_INPUT_NUM_COMP_EVT + */ + struct ble_mesh_input_number_comp_param { + int err_code; /*!< Indicate the result of inputting number */ + } node_prov_input_num_comp; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_INPUT_NUM_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROV_INPUT_STR_COMP_EVT + */ + struct ble_mesh_input_string_comp_param { + int err_code; /*!< Indicate the result of inputting string */ + } node_prov_input_str_comp; /*!< Event parameter of ESP_BLE_MESH_NODE_PROV_INPUT_STR_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROXY_IDENTITY_ENABLE_COMP_EVT + */ + struct ble_mesh_proxy_identity_enable_comp_param { + int err_code; /*!< Indicate the result of enabling Mesh Proxy advertising */ + } node_proxy_identity_enable_comp; /*!< Event parameter of ESP_BLE_MESH_NODE_PROXY_IDENTITY_ENABLE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROXY_GATT_ENABLE_COMP_EVT + */ + struct ble_mesh_proxy_gatt_enable_comp_param { + int err_code; /*!< Indicate the result of enabling Mesh Proxy Service */ + } node_proxy_gatt_enable_comp; /*!< Event parameter of ESP_BLE_MESH_NODE_PROXY_GATT_ENABLE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_NODE_PROXY_GATT_DISABLE_COMP_EVT + */ + struct ble_mesh_proxy_gatt_disable_comp_param { + int err_code; /*!< Indicate the result of disabling Mesh Proxy Service */ + } node_proxy_gatt_disable_comp; /*!< Event parameter of ESP_BLE_MESH_NODE_PROXY_GATT_DISABLE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT + */ + struct ble_mesh_provisioner_recv_unprov_adv_pkt_param { + uint8_t dev_uuid[16]; /*!< Device UUID of the unprovisoned device */ + esp_ble_mesh_bd_addr_t addr; /*!< Device address of the unprovisoned device */ + esp_ble_mesh_addr_type_t addr_type; /*!< Device address type */ + uint16_t oob_info; /*!< OOB Info of the unprovisoned device */ + uint8_t adv_type; /*!< Avertising type of the unprovisoned device */ + esp_ble_mesh_prov_bearer_t bearer; /*!< Bearer of the unprovisoned device */ + int8_t rssi; /*!< RSSI of the received advertising packet */ + } provisioner_recv_unprov_adv_pkt; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT + */ + struct ble_mesh_provisioner_prov_enable_comp_param { + int err_code; /*!< Indicate the result of enabling BLE Mesh Provisioner */ + } provisioner_prov_enable_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT + */ + struct ble_mesh_provisioner_prov_disable_comp_param { + int err_code; /*!< Indicate the result of disabling BLE Mesh Provisioner */ + } provisioner_prov_disable_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT + */ + struct ble_mesh_provisioner_link_open_evt_param { + esp_ble_mesh_prov_bearer_t bearer; /*!< Type of the bearer used when Provisioner link is opened */ + } provisioner_prov_link_open; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT */ + /** + * @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; /*!< Index of the provisioning link */ + } provisioner_prov_read_oob_pub_key; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_READ_OOB_PUB_KEY_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_INPUT_EVT + */ + struct ble_mesh_provisioner_prov_input_evt_param { + esp_ble_mesh_oob_method_t method; /*!< Method of device Output OOB Authentication */ + esp_ble_mesh_output_action_t action; /*!< Action of device Output OOB Authentication */ + uint8_t size; /*!< Size of device Output OOB Authentication */ + uint8_t link_idx; /*!< Index of the provisioning link */ + } provisioner_prov_input; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_INPUT_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_OUTPUT_EVT + */ + struct ble_mesh_provisioner_prov_output_evt_param { + esp_ble_mesh_oob_method_t method; /*!< Method of device Input OOB Authentication */ + esp_ble_mesh_input_action_t action; /*!< Action of device Input OOB Authentication */ + uint8_t size; /*!< Size of device Input OOB Authentication */ + uint8_t link_idx; /*!< Index of the provisioning link */ + union { + char string[8]; /*!< String output by the Provisioner */ + uint32_t number; /*!< Number output by the Provisioner */ + }; + } provisioner_prov_output; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_OUTPUT_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT + */ + struct ble_mesh_provisioner_link_close_evt_param { + esp_ble_mesh_prov_bearer_t bearer; /*!< Type of the bearer used when Provisioner link is closed */ + uint8_t reason; /*!< Reason of the closed provisioning link */ + } provisioner_prov_link_close; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT + */ + struct ble_mesh_provisioner_prov_comp_param { + uint16_t node_idx; /*!< Index of the provisioned device */ + esp_ble_mesh_octet16_t device_uuid; /*!< Device UUID of the provisioned device */ + uint16_t unicast_addr; /*!< Primary address of the provisioned device */ + uint8_t element_num; /*!< Element count of the provisioned device */ + uint16_t netkey_idx; /*!< NetKey Index of the provisioned device */ + } provisioner_prov_complete; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT + */ + struct ble_mesh_provisioner_add_unprov_dev_comp_param { + int err_code; /*!< Indicate the result of adding device into queue by the Provisioner */ + } provisioner_add_unprov_dev_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_DEV_WITH_ADDR_COMP_EVT + */ + struct ble_mesh_provisioner_prov_dev_with_addr_comp_param { + int err_code; /*!< Indicate the result of Provisioner starting to provision a device */ + } provisioner_prov_dev_with_addr_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_DEV_WITH_ADDR_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_DELETE_DEV_COMP_EVT + */ + struct ble_mesh_provisioner_delete_dev_comp_param { + int err_code; /*!< Indicate the result of deleting device by the Provisioner */ + } provisioner_delete_dev_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_DELETE_DEV_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT + */ + struct ble_mesh_provisioner_set_dev_uuid_match_comp_param { + int err_code; /*!< Indicate the result of setting Device UUID match value by the Provisioner */ + } provisioner_set_dev_uuid_match_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_SET_PROV_DATA_INFO_COMP_EVT + */ + struct ble_mesh_provisioner_set_prov_data_info_comp_param { + int err_code; /*!< Indicate the result of setting provisioning info by the Provisioner */ + } provisioner_set_prov_data_info_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_SET_PROV_DATA_INFO_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_SET_STATIC_OOB_VALUE_COMP_EVT + */ + struct ble_mesh_provisioner_set_static_oob_val_comp_param { + int err_code; /*!< Indicate the result of setting static oob value by the Provisioner */ + } provisioner_set_static_oob_val_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_SET_STATIC_OOB_VALUE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_SET_PRIMARY_ELEM_ADDR_COMP_EVT + */ + struct ble_mesh_provisioner_set_primary_elem_addr_comp_param { + int err_code; /*!< Indicate the result of setting unicast address of primary element by the Provisioner */ + } provisioner_set_primary_elem_addr_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_SET_PRIMARY_ELEM_ADDR_COMP_EVT */ + /** + * @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; /*!< Indicate the result of setting OOB Public Key by the Provisioner */ + } provisioner_prov_read_oob_pub_key_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_READ_OOB_PUB_KEY_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_INPUT_NUMBER_COMP_EVT + */ + struct ble_mesh_provisioner_prov_input_num_comp_param { + int err_code; /*!< Indicate the result of inputting number by the Provisioner */ + } provisioner_prov_input_num_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_INPUT_NUMBER_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_PROV_INPUT_STRING_COMP_EVT + */ + struct ble_mesh_provisioner_prov_input_str_comp_param { + int err_code; /*!< Indicate the result of inputting string by the Provisioner */ + } provisioner_prov_input_str_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_PROV_INPUT_STRING_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT + */ + struct ble_mesh_provisioner_set_node_name_comp_param { + int err_code; /*!< Indicate the result of setting provisioned device name by the Provisioner */ + uint16_t node_index; /*!< Index of the provisioned device */ + } provisioner_set_node_name_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT + */ + struct ble_mesh_provisioner_add_local_app_key_comp_param { + int err_code; /*!< Indicate the result of adding local AppKey by the Provisioner */ + uint16_t app_idx; /*!< AppKey Index */ + } provisioner_add_app_key_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_UPDATE_LOCAL_APP_KEY_COMP_EVT + */ + struct ble_mesh_provisioner_update_local_app_key_comp_param { + int err_code; /*!< Indicate the result of updating local AppKey by the Provisioner */ + uint16_t net_idx; /*!< NetKey Index */ + uint16_t app_idx; /*!< AppKey Index */ + } provisioner_update_app_key_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_UPDATE_LOCAL_APP_KEY_COMP_EVT */ + /** + * @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; /*!< Indicate the result of binding AppKey with model by the Provisioner */ + uint16_t element_addr; /*!< Element address */ + uint16_t app_idx; /*!< AppKey Index */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ + } provisioner_bind_app_key_to_model_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_NET_KEY_COMP_EVT + */ + struct ble_mesh_provisioner_add_local_net_key_comp_param { + int err_code; /*!< Indicate the result of adding local NetKey by the Provisioner */ + uint16_t net_idx; /*!< NetKey Index */ + } provisioner_add_net_key_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_NET_KEY_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_UPDATE_LOCAL_NET_KEY_COMP_EVT + */ + struct ble_mesh_provisioner_update_local_net_key_comp_param { + int err_code; /*!< Indicate the result of updating local NetKey by the Provisioner */ + uint16_t net_idx; /*!< NetKey Index */ + } provisioner_update_net_key_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_UPDATE_LOCAL_NET_KEY_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_STORE_NODE_COMP_DATA_COMP_EVT + */ + struct ble_mesh_provisioner_store_node_comp_data_comp_param { + int err_code; /*!< Indicate the result of storing node composition data by the Provisioner */ + uint16_t addr; /*!< Node element address */ + } provisioner_store_node_comp_data_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_STORE_NODE_COMP_DATA_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_DELETE_NODE_WITH_UUID_COMP_EVT + */ + struct ble_mesh_provisioner_delete_node_with_uuid_comp_data_comp_param { + int err_code; /*!< Indicate the result of deleting node with uuid by the Provisioner */ + uint8_t uuid[16]; /*!< Node device uuid */ + } provisioner_delete_node_with_uuid_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_DELETE_NODE_WITH_UUID_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROVISIONER_DELETE_NODE_WITH_ADDR_COMP_EVT + */ + struct ble_mesh_provisioner_delete_node_with_addr_comp_data_comp_param { + int err_code; /*!< Indicate the result of deleting node with unicast address by the Provisioner */ + uint16_t unicast_addr; /*!< Node unicast address */ + } provisioner_delete_node_with_addr_comp; /*!< Event parameter of ESP_BLE_MESH_PROVISIONER_DELETE_NODE_WITH_ADDR_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_SET_FAST_PROV_INFO_COMP_EVT + */ + struct ble_mesh_set_fast_prov_info_comp_param { + uint8_t status_unicast; /*!< Indicate the result of setting unicast address range of fast provisioning */ + uint8_t status_net_idx; /*!< Indicate the result of setting NetKey Index of fast provisioning */ + uint8_t status_match; /*!< Indicate the result of setting matching Device UUID of fast provisioning */ + } set_fast_prov_info_comp; /*!< Event parameter of ESP_BLE_MESH_SET_FAST_PROV_INFO_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_SET_FAST_PROV_ACTION_COMP_EVT + */ + struct ble_mesh_set_fast_prov_action_comp_param { + uint8_t status_action; /*!< Indicate the result of setting action of fast provisioning */ + } set_fast_prov_action_comp; /*!< Event parameter of ESP_BLE_MESH_SET_FAST_PROV_ACTION_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_HEARTBEAT_MESSAGE_RECV_EVT + */ + struct ble_mesh_heartbeat_msg_recv_param { + uint8_t hops; /*!< Heartbeat hops (InitTTL - RxTTL + 1) */ + uint16_t feature; /*!< Bit field of currently active features of the node */ + } heartbeat_msg_recv; /*!< Event parameter of ESP_BLE_MESH_HEARTBEAT_MESSAGE_RECV_EVT */ + /** + * @brief ESP_BLE_MESH_LPN_ENABLE_COMP_EVT + */ + struct ble_mesh_lpn_enable_comp_param { + int err_code; /*!< Indicate the result of enabling LPN functionality */ + } lpn_enable_comp; /*!< Event parameter of ESP_BLE_MESH_LPN_ENABLE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_LPN_DISABLE_COMP_EVT + */ + struct ble_mesh_lpn_disable_comp_param { + int err_code; /*!< Indicate the result of disabling LPN functionality */ + } lpn_disable_comp; /*!< Event parameter of ESP_BLE_MESH_LPN_DISABLE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_LPN_POLL_COMP_EVT + */ + struct ble_mesh_lpn_poll_comp_param { + int err_code; /*!< Indicate the result of sending Friend Poll */ + } lpn_poll_comp; /*!< Event parameter of ESP_BLE_MESH_LPN_POLL_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_LPN_FRIENDSHIP_ESTABLISH_EVT + */ + struct ble_mesh_lpn_friendship_establish_param { + uint16_t friend_addr; /*!< Friend Node unicast address */ + } lpn_friendship_establish; /*!< Event parameter of ESP_BLE_MESH_LPN_FRIENDSHIP_ESTABLISH_EVT */ + /** + * @brief ESP_BLE_MESH_LPN_FRIENDSHIP_TERMINATE_EVT + */ + struct ble_mesh_lpn_friendship_terminate_param { + uint16_t friend_addr; /*!< Friend Node unicast address */ + } lpn_friendship_terminate; /*!< Event parameter of ESP_BLE_MESH_LPN_FRIENDSHIP_TERMINATE_EVT */ + /** + * @brief ESP_BLE_MESH_FRIEND_FRIENDSHIP_ESTABLISH_EVT + */ + struct ble_mesh_friend_friendship_establish_param { + uint16_t lpn_addr; /*!< Low Power Node unciast address */ + } frnd_friendship_establish; /*!< Event parameter of ESP_BLE_MESH_FRIEND_FRIENDSHIP_ESTABLISH_EVT */ + /** + * @brief ESP_BLE_MESH_FRIEND_FRIENDSHIP_TERMINATE_EVT + */ + struct ble_mesh_friend_friendship_terminate_param { + uint16_t lpn_addr; /*!< Low Power Node unicast address */ + /** This enum value is the reason of friendship termination on the friend node side */ + enum { + ESP_BLE_MESH_FRND_FRIENDSHIP_TERMINATE_ESTABLISH_FAIL, /*!< Friend Offer has been sent, but Friend Offer is not received within 1 second, friendship fails to be established */ + ESP_BLE_MESH_FRND_FRIENDSHIP_TERMINATE_POLL_TIMEOUT, /*!< Friendship is established, PollTimeout timer expires and no Friend Poll/Sub Add/Sub Remove is received */ + ESP_BLE_MESH_FRND_FRIENDSHIP_TERMINATE_RECV_FRND_REQ, /*!< Receive Friend Request from existing Low Power Node */ + ESP_BLE_MESH_FRND_FRIENDSHIP_TERMINATE_RECV_FRND_CLEAR, /*!< Receive Friend Clear from other friend node */ + ESP_BLE_MESH_FRND_FRIENDSHIP_TERMINATE_DISABLE, /*!< Friend feature disabled or corresponding NetKey is deleted */ + } reason; /*!< Friendship terminated reason */ + } frnd_friendship_terminate; /*!< Event parameter of ESP_BLE_MESH_FRIEND_FRIENDSHIP_TERMINATE_EVT */ + /** + * @brief ESP_BLE_MESH_PROXY_CLIENT_RECV_ADV_PKT_EVT + */ + struct ble_mesh_proxy_client_recv_adv_pkt_param { + esp_ble_mesh_bd_addr_t addr; /*!< Device address */ + esp_ble_mesh_addr_type_t addr_type; /*!< Device address type */ + uint16_t net_idx; /*!< Network ID related NetKey Index */ + uint8_t net_id[8]; /*!< Network ID contained in the advertising packet */ + int8_t rssi; /*!< RSSI of the received advertising packet */ + } proxy_client_recv_adv_pkt; /*!< Event parameter of ESP_BLE_MESH_PROXY_CLIENT_RECV_ADV_PKT_EVT */ + /** + * @brief ESP_BLE_MESH_PROXY_CLIENT_CONNECTED_EVT + */ + struct ble_mesh_proxy_client_connected_param { + esp_ble_mesh_bd_addr_t addr; /*!< Device address of the Proxy Server */ + esp_ble_mesh_addr_type_t addr_type; /*!< Device address type */ + uint8_t conn_handle; /*!< Proxy connection handle */ + uint16_t net_idx; /*!< Corresponding NetKey Index */ + } proxy_client_connected; /*!< Event parameter of ESP_BLE_MESH_PROXY_CLIENT_CONNECTED_EVT */ + /** + * @brief ESP_BLE_MESH_PROXY_CLIENT_DISCONNECTED_EVT + */ + struct ble_mesh_proxy_client_disconnected_param { + esp_ble_mesh_bd_addr_t addr; /*!< Device address of the Proxy Server */ + esp_ble_mesh_addr_type_t addr_type; /*!< Device address type */ + uint8_t conn_handle; /*!< Proxy connection handle */ + uint16_t net_idx; /*!< Corresponding NetKey Index */ + uint8_t reason; /*!< Proxy disconnect reason */ + } proxy_client_disconnected; /*!< Event parameter of ESP_BLE_MESH_PROXY_CLIENT_DISCONNECTED_EVT */ + /** + * @brief ESP_BLE_MESH_PROXY_CLIENT_RECV_FILTER_STATUS_EVT + */ + struct ble_mesh_proxy_client_recv_filter_status_param { + uint8_t conn_handle; /*!< Proxy connection handle */ + uint16_t server_addr; /*!< Proxy Server primary element address */ + uint16_t net_idx; /*!< Corresponding NetKey Index */ + uint8_t filter_type; /*!< Proxy Server filter type(whitelist or blacklist) */ + uint16_t list_size; /*!< Number of addresses in the Proxy Server filter list */ + } proxy_client_recv_filter_status; /*!< Event parameter of ESP_BLE_MESH_PROXY_CLIENT_RECV_FILTER_STATUS_EVT */ + /** + * @brief ESP_BLE_MESH_PROXY_CLIENT_CONNECT_COMP_EVT + */ + struct ble_mesh_proxy_client_connect_comp_param { + int err_code; /*!< Indicate the result of Proxy Client connect */ + esp_ble_mesh_bd_addr_t addr; /*!< Device address of the Proxy Server */ + esp_ble_mesh_addr_type_t addr_type; /*!< Device address type */ + uint16_t net_idx; /*!< Corresponding NetKey Index */ + } proxy_client_connect_comp; /*!< Event parameter of ESP_BLE_MESH_PROXY_CLIENT_CONNECT_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROXY_CLIENT_DISCONNECT_COMP_EVT + */ + struct ble_mesh_proxy_client_disconnect_comp_param { + int err_code; /*!< Indicate the result of Proxy Client disconnect */ + uint8_t conn_handle; /*!< Proxy connection handle */ + } proxy_client_disconnect_comp; /*!< Event parameter of ESP_BLE_MESH_PROXY_CLIENT_DISCONNECT_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROXY_CLIENT_SET_FILTER_TYPE_COMP_EVT + */ + struct ble_mesh_proxy_client_set_filter_type_comp_param { + int err_code; /*!< Indicate the result of Proxy Client set filter type */ + uint8_t conn_handle; /*!< Proxy connection handle */ + uint16_t net_idx; /*!< Corresponding NetKey Index */ + } proxy_client_set_filter_type_comp; /*!< Event parameter of ESP_BLE_MESH_PROXY_CLIENT_SET_FILTER_TYPE_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROXY_CLIENT_ADD_FILTER_ADDR_COMP_EVT + */ + struct ble_mesh_proxy_client_add_filter_addr_comp_param { + int err_code; /*!< Indicate the result of Proxy Client add filter address */ + uint8_t conn_handle; /*!< Proxy connection handle */ + uint16_t net_idx; /*!< Corresponding NetKey Index */ + } proxy_client_add_filter_addr_comp; /*!< Event parameter of ESP_BLE_MESH_PROXY_CLIENT_ADD_FILTER_ADDR_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_PROXY_CLIENT_REMOVE_FILTER_ADDR_COMP_EVT + */ + struct ble_mesh_proxy_client_remove_filter_addr_comp_param { + int err_code; /*!< Indicate the result of Proxy Client remove filter address */ + uint8_t conn_handle; /*!< Proxy connection handle */ + uint16_t net_idx; /*!< Corresponding NetKey Index */ + } proxy_client_remove_filter_addr_comp; /*!< Event parameter of ESP_BLE_MESH_PROXY_CLIENT_REMOVE_FILTER_ADDR_COMP_EVT */ +} esp_ble_mesh_prov_cb_param_t; + +/** + * @brief BLE Mesh models related Model ID and Opcode definitions + */ + +/*!< Foundation Models */ +#define ESP_BLE_MESH_MODEL_ID_CONFIG_SRV 0x0000 +#define ESP_BLE_MESH_MODEL_ID_CONFIG_CLI 0x0001 +#define ESP_BLE_MESH_MODEL_ID_HEALTH_SRV 0x0002 +#define ESP_BLE_MESH_MODEL_ID_HEALTH_CLI 0x0003 + +/*!< Models from the Mesh Model Specification */ +#define ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV 0x1000 +#define ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI 0x1001 +#define ESP_BLE_MESH_MODEL_ID_GEN_LEVEL_SRV 0x1002 +#define ESP_BLE_MESH_MODEL_ID_GEN_LEVEL_CLI 0x1003 +#define ESP_BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV 0x1004 +#define ESP_BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI 0x1005 +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV 0x1006 +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV 0x1007 +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI 0x1008 +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV 0x1009 +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV 0x100a +#define ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI 0x100b +#define ESP_BLE_MESH_MODEL_ID_GEN_BATTERY_SRV 0x100c +#define ESP_BLE_MESH_MODEL_ID_GEN_BATTERY_CLI 0x100d +#define ESP_BLE_MESH_MODEL_ID_GEN_LOCATION_SRV 0x100e +#define ESP_BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV 0x100f +#define ESP_BLE_MESH_MODEL_ID_GEN_LOCATION_CLI 0x1010 +#define ESP_BLE_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV 0x1011 +#define ESP_BLE_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV 0x1012 +#define ESP_BLE_MESH_MODEL_ID_GEN_USER_PROP_SRV 0x1013 +#define ESP_BLE_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV 0x1014 +#define ESP_BLE_MESH_MODEL_ID_GEN_PROP_CLI 0x1015 +#define ESP_BLE_MESH_MODEL_ID_SENSOR_SRV 0x1100 +#define ESP_BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV 0x1101 +#define ESP_BLE_MESH_MODEL_ID_SENSOR_CLI 0x1102 +#define ESP_BLE_MESH_MODEL_ID_TIME_SRV 0x1200 +#define ESP_BLE_MESH_MODEL_ID_TIME_SETUP_SRV 0x1201 +#define ESP_BLE_MESH_MODEL_ID_TIME_CLI 0x1202 +#define ESP_BLE_MESH_MODEL_ID_SCENE_SRV 0x1203 +#define ESP_BLE_MESH_MODEL_ID_SCENE_SETUP_SRV 0x1204 +#define ESP_BLE_MESH_MODEL_ID_SCENE_CLI 0x1205 +#define ESP_BLE_MESH_MODEL_ID_SCHEDULER_SRV 0x1206 +#define ESP_BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV 0x1207 +#define ESP_BLE_MESH_MODEL_ID_SCHEDULER_CLI 0x1208 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV 0x1300 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV 0x1301 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI 0x1302 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SRV 0x1303 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV 0x1304 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_CLI 0x1305 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV 0x1306 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SRV 0x1307 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV 0x1308 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_CLI 0x1309 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV 0x130a +#define ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV 0x130b +#define ESP_BLE_MESH_MODEL_ID_LIGHT_XYL_SRV 0x130c +#define ESP_BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV 0x130d +#define ESP_BLE_MESH_MODEL_ID_LIGHT_XYL_CLI 0x130e +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LC_SRV 0x130f +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LC_SETUP_SRV 0x1310 +#define ESP_BLE_MESH_MODEL_ID_LIGHT_LC_CLI 0x1311 + +/** + * 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. + * 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; + +#define ESP_BLE_MESH_MODEL_OP_BEACON_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x09) /*!< Config Beacon Get */ +#define ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x08) /*!< Config Composition Data Get */ +#define ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x0C) /*!< Config Default TTL Get */ +#define ESP_BLE_MESH_MODEL_OP_GATT_PROXY_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x12) /*!< Config GATT Proxy Get */ +#define ESP_BLE_MESH_MODEL_OP_RELAY_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x26) /*!< Config Relay Get */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x18) /*!< Config Model Publication Get */ +#define ESP_BLE_MESH_MODEL_OP_FRIEND_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x0F) /*!< Config Friend Get */ +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x38) /*!< Config Heartbeat Publication Get */ +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x3a) /*!< Config Heartbeat Subscription Get */ +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x42) /*!< Config NetKey Get */ +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x01) /*!< Config AppKey Get */ +#define ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x46) /*!< Config Node Identity Get */ +#define ESP_BLE_MESH_MODEL_OP_SIG_MODEL_SUB_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x29) /*!< Config SIG Model Subscription Get */ +#define ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_SUB_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x2B) /*!< Config Vendor Model Subscription Get */ +#define ESP_BLE_MESH_MODEL_OP_SIG_MODEL_APP_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x4B) /*!< Config SIG Model App Get */ +#define ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_APP_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x4D) /*!< Config Vendor Model App Get */ +#define ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x15) /*!< Config Key Refresh Phase Get */ +#define ESP_BLE_MESH_MODEL_OP_LPN_POLLTIMEOUT_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x2D) /*!< Config Low Power Node PollTimeout Get */ +#define ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x23) /*!< Config Network Transmit Get */ + +/** + * 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. + * 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; + +#define ESP_BLE_MESH_MODEL_OP_BEACON_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x0A) /*!< Config Beacon Set */ +#define ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x0D) /*!< Config Default TTL Set */ +#define ESP_BLE_MESH_MODEL_OP_GATT_PROXY_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x13) /*!< Config GATT Proxy Set */ +#define ESP_BLE_MESH_MODEL_OP_RELAY_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x27) /*!< Config Relay Set */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_SET ESP_BLE_MESH_MODEL_OP_1(0x03) /*!< Config Model Publication Set */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1B) /*!< Config Model Subscription Add */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_ADD ESP_BLE_MESH_MODEL_OP_2(0x80, 0x20) /*!< Config Model Subscription Vritual Address Add */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1C) /*!< Config Model Subscription Delete */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_DELETE ESP_BLE_MESH_MODEL_OP_2(0x80, 0x21) /*!< Config Model Subscription Virtual Address Delete */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_OVERWRITE ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1E) /*!< Config Model Subscription Overwrite */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_OVERWRITE ESP_BLE_MESH_MODEL_OP_2(0x80, 0x22) /*!< Config Model Subscription Virtual Address Overwrite */ +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD ESP_BLE_MESH_MODEL_OP_2(0x80, 0x40) /*!< Config NetKey Add */ +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD ESP_BLE_MESH_MODEL_OP_1(0x00) /*!< Config AppKey Add */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND ESP_BLE_MESH_MODEL_OP_2(0x80, 0x3D) /*!< Config Model App Bind */ +#define ESP_BLE_MESH_MODEL_OP_NODE_RESET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x49) /*!< Config Node Reset */ +#define ESP_BLE_MESH_MODEL_OP_FRIEND_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x10) /*!< Config Friend Set */ +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x39) /*!< Config Heartbeat Publication Set */ +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x3B) /*!< Config Heartbeat Subscription Set */ +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_UPDATE ESP_BLE_MESH_MODEL_OP_2(0x80, 0x45) /*!< Config NetKey Update */ +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_DELETE ESP_BLE_MESH_MODEL_OP_2(0x80, 0x41) /*!< Config NetKey Delete */ +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_UPDATE ESP_BLE_MESH_MODEL_OP_1(0x01) /*!< Config AppKey Update */ +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_DELETE ESP_BLE_MESH_MODEL_OP_2(0x80, 0x00) /*!< Config AppKey Delete */ +#define ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x47) /*!< Config Node Identity Set */ +#define ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x16) /*!< Config Key Refresh Phase Set */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_VIRTUAL_ADDR_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1A) /*!< Config Model Publication Virtual Address Set */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE_ALL ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1D) /*!< Config Model Subscription Delete All */ +#define ESP_BLE_MESH_MODEL_OP_MODEL_APP_UNBIND ESP_BLE_MESH_MODEL_OP_2(0x80, 0x3F) /*!< Config Model App Unbind */ +#define ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x24) /*!< Config Network Transmit Set */ + +/** + * esp_ble_mesh_opcode_config_status_t belongs to esp_ble_mesh_opcode_t, this typedef is only + * used to locate the opcodes used by the Config Model messages + * 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_opcode_config_status_t; + +#define ESP_BLE_MESH_MODEL_OP_BEACON_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x0B) +#define ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_STATUS ESP_BLE_MESH_MODEL_OP_1(0x02) +#define ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x0E) +#define ESP_BLE_MESH_MODEL_OP_GATT_PROXY_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x14) +#define ESP_BLE_MESH_MODEL_OP_RELAY_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x28) +#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x19) +#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1F) +#define ESP_BLE_MESH_MODEL_OP_SIG_MODEL_SUB_LIST ESP_BLE_MESH_MODEL_OP_2(0x80, 0x2A) +#define ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_SUB_LIST ESP_BLE_MESH_MODEL_OP_2(0x80, 0x2C) +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x44) +#define ESP_BLE_MESH_MODEL_OP_NET_KEY_LIST ESP_BLE_MESH_MODEL_OP_2(0x80, 0x43) +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x03) +#define ESP_BLE_MESH_MODEL_OP_APP_KEY_LIST ESP_BLE_MESH_MODEL_OP_2(0x80, 0x02) +#define ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x48) +#define ESP_BLE_MESH_MODEL_OP_MODEL_APP_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x3E) +#define ESP_BLE_MESH_MODEL_OP_SIG_MODEL_APP_LIST ESP_BLE_MESH_MODEL_OP_2(0x80, 0x4C) +#define ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_APP_LIST ESP_BLE_MESH_MODEL_OP_2(0x80, 0x4E) +#define ESP_BLE_MESH_MODEL_OP_NODE_RESET_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x4A) +#define ESP_BLE_MESH_MODEL_OP_FRIEND_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x11) +#define ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x17) +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_STATUS ESP_BLE_MESH_MODEL_OP_1(0x06) +#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x3C) +#define ESP_BLE_MESH_MODEL_OP_LPN_POLLTIMEOUT_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x2E) +#define ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x25) + +/** + * This typedef is only used to indicate the status code contained in some of + * the Configuration Server Model status message. + */ +typedef uint8_t esp_ble_mesh_cfg_status_t; + +#define ESP_BLE_MESH_CFG_STATUS_SUCCESS 0x00 +#define ESP_BLE_MESH_CFG_STATUS_INVALID_ADDRESS 0x01 +#define ESP_BLE_MESH_CFG_STATUS_INVALID_MODEL 0x02 +#define ESP_BLE_MESH_CFG_STATUS_INVALID_APPKEY 0x03 +#define ESP_BLE_MESH_CFG_STATUS_INVALID_NETKEY 0x04 +#define ESP_BLE_MESH_CFG_STATUS_INSUFFICIENT_RESOURCES 0x05 +#define ESP_BLE_MESH_CFG_STATUS_KEY_INDEX_ALREADY_STORED 0x06 +#define ESP_BLE_MESH_CFG_STATUS_INVALID_PUBLISH_PARAMETERS 0x07 +#define ESP_BLE_MESH_CFG_STATUS_NOT_A_SUBSCRIBE_MODEL 0x08 +#define ESP_BLE_MESH_CFG_STATUS_STORAGE_FAILURE 0x09 +#define ESP_BLE_MESH_CFG_STATUS_FEATURE_NOT_SUPPORTED 0x0A +#define ESP_BLE_MESH_CFG_STATUS_CANNOT_UPDATE 0x0B +#define ESP_BLE_MESH_CFG_STATUS_CANNOT_REMOVE 0x0C +#define ESP_BLE_MESH_CFG_STATUS_CANNOT_BIND 0x0D +#define ESP_BLE_MESH_CFG_STATUS_TEMP_UNABLE_TO_CHANGE_STATE 0x0E +#define ESP_BLE_MESH_CFG_STATUS_CANNOT_SET 0x0F +#define ESP_BLE_MESH_CFG_STATUS_UNSPECIFIED_ERROR 0x10 +#define ESP_BLE_MESH_CFG_STATUS_INVALID_BINDING 0x11 + +/** + * 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. + * 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; + +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x31) /*!< Health Fault Get */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x34) /*!< Health Period Get */ +#define ESP_BLE_MESH_MODEL_OP_ATTENTION_GET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x04) /*!< Health Attention Get */ + +/** + * 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. + * 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; + +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR ESP_BLE_MESH_MODEL_OP_2(0x80, 0x2F) /*!< Health Fault Clear */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR_UNACK ESP_BLE_MESH_MODEL_OP_2(0x80, 0x30) /*!< Health Fault Clear Unacknowledged */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST ESP_BLE_MESH_MODEL_OP_2(0x80, 0x32) /*!< Health Fault Test */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST_UNACK ESP_BLE_MESH_MODEL_OP_2(0x80, 0x33) /*!< Health Fault Test Unacknowledged */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x35) /*!< Health Period Set */ +#define ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x80, 0x36) /*!< Health Period Set Unacknowledged */ +#define ESP_BLE_MESH_MODEL_OP_ATTENTION_SET ESP_BLE_MESH_MODEL_OP_2(0x80, 0x05) /*!< Health Attention Set */ +#define ESP_BLE_MESH_MODEL_OP_ATTENTION_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x80, 0x06) /*!< Health Attention Set Unacknowledged */ + +/** + * 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. + * 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; + +#define ESP_BLE_MESH_MODEL_OP_HEALTH_CURRENT_STATUS ESP_BLE_MESH_MODEL_OP_1(0x04) +#define ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_STATUS ESP_BLE_MESH_MODEL_OP_1(0x05) +#define ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x37) +#define ESP_BLE_MESH_MODEL_OP_ATTENTION_STATUS ESP_BLE_MESH_MODEL_OP_2(0x80, 0x07) + +/** + * 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. + */ +typedef uint32_t esp_ble_mesh_generic_message_opcode_t; + +/*!< Generic OnOff Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x01) +#define ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x02) +#define ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x03) +#define ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x04) + +/*!< Generic Level Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x05) +#define ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x06) +#define ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x07) +#define ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x08) +#define ESP_BLE_MESH_MODEL_OP_GEN_DELTA_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x09) +#define ESP_BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x0A) +#define ESP_BLE_MESH_MODEL_OP_GEN_MOVE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x0B) +#define ESP_BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x0C) + +/*!< Generic Default Transition Time Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x0D) +#define ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x0E) +#define ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x0F) +#define ESP_BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x10) + +/*!< Generic Power OnOff Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x11) +#define ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x12) + +/*!< Generic Power OnOff Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x13) +#define ESP_BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x14) + +/*!< Generic Power Level Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x15) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x16) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x17) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x18) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LAST_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x19) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x1A) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x1B) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x1C) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x1D) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x1E) + +/*!< Generic Power Level Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x1F) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x20) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x21) +#define ESP_BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x22) + +/*!< Generic Battery Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_BATTERY_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x23) +#define ESP_BLE_MESH_MODEL_OP_GEN_BATTERY_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x24) + +/*!< Generic Location Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x25) +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS ESP_BLE_MESH_MODEL_OP_1(0x40) +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x26) +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x27) + +/*!< Generic Location Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET ESP_BLE_MESH_MODEL_OP_1(0x41) +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET_UNACK ESP_BLE_MESH_MODEL_OP_1(0x42) +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x28) +#define ESP_BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x29) + +/*!< Generic Manufacturer Property Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTIES_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x2A) +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTIES_STATUS ESP_BLE_MESH_MODEL_OP_1(0x43) +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x2B) +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_SET ESP_BLE_MESH_MODEL_OP_1(0x44) +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_SET_UNACK ESP_BLE_MESH_MODEL_OP_1(0x45) +#define ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_STATUS ESP_BLE_MESH_MODEL_OP_1(0x46) + +/*!< Generic Admin Property Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x2C) +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS ESP_BLE_MESH_MODEL_OP_1(0x47) +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x2D) +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET ESP_BLE_MESH_MODEL_OP_1(0x48) +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK ESP_BLE_MESH_MODEL_OP_1(0x49) +#define ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS ESP_BLE_MESH_MODEL_OP_1(0x4A) + +/*!< Generic User Property Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x2E) +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS ESP_BLE_MESH_MODEL_OP_1(0x4B) +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x2F) +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET ESP_BLE_MESH_MODEL_OP_1(0x4C) +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK ESP_BLE_MESH_MODEL_OP_1(0x4D) +#define ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS ESP_BLE_MESH_MODEL_OP_1(0x4E) + +/*!< Generic Client Property Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET ESP_BLE_MESH_MODEL_OP_1(0x4F) +#define ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS ESP_BLE_MESH_MODEL_OP_1(0x50) + +/** + * 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. + */ +typedef uint32_t esp_ble_mesh_sensor_message_opcode_t; + +/*!< Sensor Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x30) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS ESP_BLE_MESH_MODEL_OP_1(0x51) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x31) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_STATUS ESP_BLE_MESH_MODEL_OP_1(0x52) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x32) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS ESP_BLE_MESH_MODEL_OP_1(0x53) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x33) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS ESP_BLE_MESH_MODEL_OP_1(0x54) + +/*!< Sensor Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x34) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET ESP_BLE_MESH_MODEL_OP_1(0x55) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK ESP_BLE_MESH_MODEL_OP_1(0x56) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS ESP_BLE_MESH_MODEL_OP_1(0x57) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x35) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS ESP_BLE_MESH_MODEL_OP_1(0x58) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x36) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET ESP_BLE_MESH_MODEL_OP_1(0x59) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK ESP_BLE_MESH_MODEL_OP_1(0x5A) +#define ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS ESP_BLE_MESH_MODEL_OP_1(0x5B) + +/** + * 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. + */ +typedef uint32_t esp_ble_mesh_time_scene_message_opcode_t; + +/*!< Time Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_TIME_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x37) +#define ESP_BLE_MESH_MODEL_OP_TIME_SET ESP_BLE_MESH_MODEL_OP_1(0x5C) +#define ESP_BLE_MESH_MODEL_OP_TIME_STATUS ESP_BLE_MESH_MODEL_OP_1(0x5D) +#define ESP_BLE_MESH_MODEL_OP_TIME_ROLE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x38) +#define ESP_BLE_MESH_MODEL_OP_TIME_ROLE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x39) +#define ESP_BLE_MESH_MODEL_OP_TIME_ROLE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x3A) +#define ESP_BLE_MESH_MODEL_OP_TIME_ZONE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x3B) +#define ESP_BLE_MESH_MODEL_OP_TIME_ZONE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x3C) +#define ESP_BLE_MESH_MODEL_OP_TIME_ZONE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x3D) +#define ESP_BLE_MESH_MODEL_OP_TAI_UTC_DELTA_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x3E) +#define ESP_BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x3F) +#define ESP_BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x40) + +/*!< Scene Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SCENE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x41) +#define ESP_BLE_MESH_MODEL_OP_SCENE_RECALL ESP_BLE_MESH_MODEL_OP_2(0x82, 0x42) +#define ESP_BLE_MESH_MODEL_OP_SCENE_RECALL_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x43) +#define ESP_BLE_MESH_MODEL_OP_SCENE_STATUS ESP_BLE_MESH_MODEL_OP_1(0x5E) +#define ESP_BLE_MESH_MODEL_OP_SCENE_REGISTER_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x44) +#define ESP_BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x45) + +/*!< Scene Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SCENE_STORE ESP_BLE_MESH_MODEL_OP_2(0x82, 0x46) +#define ESP_BLE_MESH_MODEL_OP_SCENE_STORE_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x47) +#define ESP_BLE_MESH_MODEL_OP_SCENE_DELETE ESP_BLE_MESH_MODEL_OP_2(0x82, 0x9E) +#define ESP_BLE_MESH_MODEL_OP_SCENE_DELETE_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x9F) + +/*!< Scheduler Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x48) +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS ESP_BLE_MESH_MODEL_OP_1(0x5F) +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x49) +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x4A) + +/*!< Scheduler Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET ESP_BLE_MESH_MODEL_OP_1(0x60) +#define ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET_UNACK ESP_BLE_MESH_MODEL_OP_1(0x61) + +/** + * 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. + */ +typedef uint32_t esp_ble_mesh_light_message_opcode_t; + +/*!< Light Lightness Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x4B) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x4C) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x4D) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x4E) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x4F) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x50) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x51) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x52) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x53) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x54) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x55) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x56) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x57) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x58) + +/*!< Light Lightness Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x59) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x5A) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x5B) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x5C) + +/*!< Light CTL Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x5D) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x5E) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x5F) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x60) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x61) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x62) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x63) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x64) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x65) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x66) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x67) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x68) + +/*!< Light CTL Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x69) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x6A) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x6B) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x6C) + +/*!< Light HSL Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x6D) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x6E) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x6F) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x70) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x71) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x72) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x73) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x74) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x75) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x76) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x77) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x78) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x79) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x7A) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x7B) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x7C) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x7D) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x7E) + +/*!< Light HSL Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x7F) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x80) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x81) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x82) + +/*!< Light xyL Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x83) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x84) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x85) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x86) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x87) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x88) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x89) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x8A) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x8B) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x8C) + +/*!< Light xyL Setup Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x8D) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x8E) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x8F) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x90) + +/*!< Light Control Message Opcode */ +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x91) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x92) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x93) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x94) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x95) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x96) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x97) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x98) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x99) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x9A) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET_UNACK ESP_BLE_MESH_MODEL_OP_2(0x82, 0x9B) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS ESP_BLE_MESH_MODEL_OP_2(0x82, 0x9C) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET ESP_BLE_MESH_MODEL_OP_2(0x82, 0x9D) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET ESP_BLE_MESH_MODEL_OP_1(0x62) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK ESP_BLE_MESH_MODEL_OP_1(0x63) +#define ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS ESP_BLE_MESH_MODEL_OP_1(0x64) + +typedef uint32_t esp_ble_mesh_opcode_t; +/*!< End of defines of esp_ble_mesh_opcode_t */ + +/** + * This typedef is only used to indicate the status code contained in some of the + * server models (e.g. Generic Server Model) status message. + */ +typedef uint8_t esp_ble_mesh_model_status_t; + +#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 + +/** + * @brief BLE Mesh client models related definitions + */ + +/** Client model Get/Set message opcode and corresponding Status message opcode */ +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; + +/** Client Model user data context. */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the client model. Initialized by the stack. */ + 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; /*!< Callback used to handle the received unsoliciated message. Initialized by the stack. */ + void *internal_data; /*!< Pointer to the internal data of client model */ + uint8_t msg_role; /*!< Role of the device (Node/Provisioner) that is going to send messages */ +} esp_ble_mesh_client_t; + +/** Common parameters of the messages sent by Client Model. */ +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 */ +} esp_ble_mesh_client_common_param_t; + +/** + * @brief BLE Mesh server models related definitions + */ + +/** This enum value is the flag of transition timer operation */ +enum { + ESP_BLE_MESH_SERVER_TRANS_TIMER_START, /* Proper transition timer has been started */ + ESP_BLE_MESH_SERVER_FLAG_MAX, +}; + +/** Parameters of the server model state transition */ +typedef struct { + bool just_started; /*!< Indicate if the state transition has just started */ + + uint8_t trans_time; /*!< State transition time */ + uint8_t remain_time; /*!< Remaining time of state transition */ + uint8_t delay; /*!< Delay before starting state transition */ + uint32_t quo_tt; /*!< Duration of each divided transition step */ + uint32_t counter; /*!< Number of steps which the transition duration is divided */ + uint32_t total_duration; /*!< State transition total duration */ + int64_t start_timestamp; /*!< Time when the state transition is started */ + + /** + * Flag used to indicate if the transition timer has been started internally. + * + * If the model which contains esp_ble_mesh_state_transition_t sets "set_auto_rsp" + * to ESP_BLE_MESH_SERVER_RSP_BY_APP, the handler of the timer shall be initialized + * by the users. + * + * And users can use this flag to indicate whether the timer is started or not. + */ + BLE_MESH_ATOMIC_DEFINE(flag, ESP_BLE_MESH_SERVER_FLAG_MAX); + struct k_delayed_work timer; /*!< Timer used for state transition */ +} esp_ble_mesh_state_transition_t; + +/** Parameters of the server model received last same set message. */ +typedef struct { + uint8_t tid; /*!< Transaction number of the last message */ + uint16_t src; /*!< Source address of the last message */ + uint16_t dst; /*!< Destination address of the last messgae */ + int64_t timestamp; /*!< Time when the last message is received */ +} esp_ble_mesh_last_msg_info_t; + +#define ESP_BLE_MESH_SERVER_RSP_BY_APP 0 /*!< Response will be sent internally */ +#define ESP_BLE_MESH_SERVER_AUTO_RSP 1 /*!< Response need to be sent in the application */ + +/** Parameters of the Server Model response control */ +typedef struct { + /** + * @brief BLE Mesh Server Response Option + * 1. If get_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, then the + * response of Client Get messages need to be replied by the application; + * 2. If get_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, then the + * response of Client Get messages will be replied by the server models; + * 3. If set_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, then the + * response of Client Set messages need to be replied by the application; + * 4. If set_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, then the + * response of Client Set messages will be replied by the server models; + * 5. If status_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, then the + * response of Server Status messages need to be replied by the application; + * 6. If status_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, then the + * response of Server Status messages will be replied by the server models; + */ + uint8_t get_auto_rsp : 1, /*!< Response control for Client Get messages */ + set_auto_rsp : 1, /*!< Response control for Client Set messages */ + status_auto_rsp : 1; /*!< Response control for Server Status messages */ +} esp_ble_mesh_server_rsp_ctrl_t; + +/** + * @brief Server model state value union + */ +typedef union { + struct { + uint8_t onoff; /*!< The value of the Generic OnOff state */ + } gen_onoff; /*!< The Generic OnOff state */ + struct { + int16_t level; /*!< The value of the Generic Level state */ + } gen_level; /*!< The Generic Level state */ + struct { + uint8_t onpowerup; /*!< The value of the Generic OnPowerUp state */ + } gen_onpowerup; /*!< The Generic OnPowerUp state */ + struct { + uint16_t power; /*!< The value of the Generic Power Actual state */ + } gen_power_actual; /*!< The Generic Power Actual state */ + struct { + uint16_t lightness; /*!< The value of the Light Lightness Actual state */ + } light_lightness_actual; /*!< The Light Lightness Actual state */ + struct { + uint16_t lightness; /*!< The value of the Light Lightness Linear state */ + } light_lightness_linear; /*!< The Light Lightness Linear state */ + struct { + uint16_t lightness; /*!< The value of the Light CTL Lightness state */ + } light_ctl_lightness; /*!< The Light CTL Lightness state */ + struct { + uint16_t temperature; /*!< The value of the Light CTL Temperature state */ + int16_t delta_uv; /*!< The value of the Light CTL Delta UV state */ + } light_ctl_temp_delta_uv; /*!< The Light CTL Temperature & Delta UV states */ + struct { + uint16_t lightness; /*!< The value of the Light HSL Lightness state */ + } light_hsl_lightness; /*!< The Light HSL Lightness state */ + struct { + uint16_t hue; /*!< The value of the Light HSL Hue state */ + } light_hsl_hue; /*!< The Light HSL Hue state */ + struct { + uint16_t saturation; /*!< The value of the Light HSL Saturation state */ + } light_hsl_saturation; /*!< The Light HSL Saturation state */ + struct { + uint16_t lightness; /*!< The value of the Light xyL Lightness state */ + } light_xyl_lightness; /*!< The Light xyL Lightness state */ + struct { + uint8_t onoff; /*!< The value of the Light LC Light OnOff state */ + } light_lc_light_onoff; /*!< The Light LC Light OnOff state */ +} esp_ble_mesh_server_state_value_t; + +/** This enum value is the type of server model states */ +typedef enum { + ESP_BLE_MESH_GENERIC_ONOFF_STATE, + ESP_BLE_MESH_GENERIC_LEVEL_STATE, + ESP_BLE_MESH_GENERIC_ONPOWERUP_STATE, + ESP_BLE_MESH_GENERIC_POWER_ACTUAL_STATE, + ESP_BLE_MESH_LIGHT_LIGHTNESS_ACTUAL_STATE, + ESP_BLE_MESH_LIGHT_LIGHTNESS_LINEAR_STATE, + ESP_BLE_MESH_LIGHT_CTL_LIGHTNESS_STATE, + ESP_BLE_MESH_LIGHT_CTL_TEMP_DELTA_UV_STATE, + ESP_BLE_MESH_LIGHT_HSL_LIGHTNESS_STATE, + ESP_BLE_MESH_LIGHT_HSL_HUE_STATE, + ESP_BLE_MESH_LIGHT_HSL_SATURATION_STATE, + ESP_BLE_MESH_LIGHT_XYL_LIGHTNESS_STATE, + ESP_BLE_MESH_LIGHT_LC_LIGHT_ONOFF_STATE, + ESP_BLE_MESH_SERVER_MODEL_STATE_MAX, +} esp_ble_mesh_server_state_type_t; + +/*!< This enum value is the event of undefined SIG models and vendor models */ +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_SERVER_MODEL_UPDATE_STATE_COMP_EVT, /*!< Server models update state value completion event */ + ESP_BLE_MESH_MODEL_EVT_MAX, +} esp_ble_mesh_model_cb_event_t; + +/** + * @brief BLE Mesh model callback parameters union + */ +typedef union { + /** + * @brief ESP_BLE_MESH_MODEL_OPERATION_EVT + */ + struct ble_mesh_model_operation_evt_param { + uint32_t opcode; /*!< Opcode of the recieved message */ + esp_ble_mesh_model_t *model; /*!< Pointer to the model which receives the message */ + esp_ble_mesh_msg_ctx_t *ctx; /*!< Pointer to the context of the received message */ + uint16_t length; /*!< Length of the received message */ + uint8_t *msg; /*!< Value of the received message */ + } model_operation; /*!< Event parameter of ESP_BLE_MESH_MODEL_OPERATION_EVT */ + /** + * @brief ESP_BLE_MESH_MODEL_SEND_COMP_EVT + */ + struct ble_mesh_model_send_comp_param { + int err_code; /*!< Indicate the result of sending a message */ + uint32_t opcode; /*!< Opcode of the message */ + esp_ble_mesh_model_t *model; /*!< Pointer to the model which sends the message */ + esp_ble_mesh_msg_ctx_t *ctx; /*!< Context of the message */ + } model_send_comp; /*!< Event parameter of ESP_BLE_MESH_MODEL_SEND_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT + */ + struct ble_mesh_model_publish_comp_param { + int err_code; /*!< Indicate the result of publishing a message */ + esp_ble_mesh_model_t *model; /*!< Pointer to the model which publishes the message */ + } model_publish_comp; /*!< Event parameter of ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT */ + /** + * @brief ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT + */ + struct ble_mesh_mod_recv_publish_msg_param { + uint32_t opcode; /*!< Opcode of the unsoliciated received message */ + esp_ble_mesh_model_t *model; /*!< Pointer to the model which receives the message */ + esp_ble_mesh_msg_ctx_t *ctx; /*!< Pointer to the context of the message */ + uint16_t length; /*!< Length of the received message */ + uint8_t *msg; /*!< Value of the received message */ + } client_recv_publish_msg; /*!< Event parameter of ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT */ + /** + * @brief ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT + */ + struct ble_mesh_client_model_send_timeout_param { + uint32_t opcode; /*!< Opcode of the previously sent message */ + esp_ble_mesh_model_t *model; /*!< Pointer to the model which sends the previous message */ + esp_ble_mesh_msg_ctx_t *ctx; /*!< Pointer to the context of the previous message */ + } client_send_timeout; /*!< Event parameter of ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT */ + /** + * @brief ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT + */ + struct ble_mesh_model_publish_update_evt_param { + esp_ble_mesh_model_t *model; /*!< Pointer to the model which is going to update its publish message */ + } model_publish_update; /*!< Event parameter of ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT */ + /** + * @brief ESP_BLE_MESH_SERVER_MODEL_UPDATE_STATE_COMP_EVT + */ + struct ble_mesh_server_model_update_state_comp_param { + int err_code; /*!< Indicate the result of updating server model state */ + esp_ble_mesh_model_t *model; /*!< Pointer to the server model which state value is updated */ + esp_ble_mesh_server_state_type_t type; /*!< Type of the updated server state */ + } server_model_update_state; /*!< Event parameter of ESP_BLE_MESH_SERVER_MODEL_UPDATE_STATE_COMP_EVT */ +} esp_ble_mesh_model_cb_param_t; + +#endif /* _ESP_BLE_MESH_DEFS_H_ */ diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c new file mode 100644 index 0000000000..e6870d0864 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c @@ -0,0 +1,99 @@ +// Copyright 2017-2019 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_manage.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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_CONFIG_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_CONFIG_SERVER, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +static bool config_client_get_need_param(esp_ble_mesh_opcode_t opcode) +{ + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: + case ESP_BLE_MESH_MODEL_OP_MODEL_PUB_GET: + case ESP_BLE_MESH_MODEL_OP_SIG_MODEL_SUB_GET: + case ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_SUB_GET: + case ESP_BLE_MESH_MODEL_OP_APP_KEY_GET: + case ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_GET: + case ESP_BLE_MESH_MODEL_OP_SIG_MODEL_APP_GET: + case ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_APP_GET: + case ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_GET: + case ESP_BLE_MESH_MODEL_OP_LPN_POLLTIMEOUT_GET: + return true; + default: + return false; + } +} + +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_config_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !ESP_BLE_MESH_ADDR_IS_UNICAST(params->ctx.addr) || + (config_client_get_need_param(params->opcode) && !get_state)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_CONFIG_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_config_client_args_t), btc_ble_mesh_config_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_config_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !ESP_BLE_MESH_ADDR_IS_UNICAST(params->ctx.addr) || + (params->opcode != ESP_BLE_MESH_MODEL_OP_NODE_RESET && !set_state)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_CONFIG_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_config_client_args_t), btc_ble_mesh_config_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c new file mode 100644 index 0000000000..cb2b22a95d --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c @@ -0,0 +1,92 @@ +// Copyright 2017-2019 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_manage.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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_GENERIC_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); +} + +static bool generic_client_get_need_param(esp_ble_mesh_opcode_t opcode) +{ + switch (opcode) { + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_MANUFACTURER_PROPERTY_GET: + case ESP_BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET: + return true; + default: + return false; + } +} + +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 || + (generic_client_get_need_param(params->opcode) && !get_state)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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); +} + +esp_err_t esp_ble_mesh_register_generic_server_callback(esp_ble_mesh_generic_server_cb_t callback) +{ + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_GENERIC_SERVER, callback) == 0 ? ESP_OK : ESP_FAIL); +} diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c new file mode 100644 index 0000000000..506e906c17 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c @@ -0,0 +1,99 @@ +// Copyright 2017-2019 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_manage.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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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 && + params->opcode == ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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}; + + if (element == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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.health_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/esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c new file mode 100644 index 0000000000..c7847cc266 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c @@ -0,0 +1,79 @@ +// Copyright 2017-2019 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_manage.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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_LIGHTING_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_lighting_client_args_t arg = {0}; + btc_msg_t msg = {0}; + + if (!params || !params->model || !params->ctx.addr || (!get_state && + params->opcode == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_LIGHTING_CLIENT; + msg.act = BTC_BLE_MESH_ACT_LIGHTING_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_lighting_client_args_t), btc_ble_mesh_lighting_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_lighting_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_LIGHTING_CLIENT; + msg.act = BTC_BLE_MESH_ACT_LIGHTING_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_lighting_client_args_t), btc_ble_mesh_lighting_client_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_mesh_register_lighting_server_callback(esp_ble_mesh_lighting_server_cb_t callback) +{ + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_LIGHTING_SERVER, callback) == 0 ? ESP_OK : ESP_FAIL); +} diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c new file mode 100644 index 0000000000..2d23d37db4 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c @@ -0,0 +1,80 @@ +// Copyright 2017-2019 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_manage.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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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); +} + +esp_err_t esp_ble_mesh_register_sensor_server_callback(esp_ble_mesh_sensor_server_cb_t callback) +{ + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_SENSOR_SERVER, callback) == 0 ? ESP_OK : ESP_FAIL); +} + + diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c new file mode 100644 index 0000000000..80347934d6 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c @@ -0,0 +1,80 @@ +// Copyright 2017-2019 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_manage.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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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 && + params->opcode == ESP_BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET)) { + return ESP_ERR_INVALID_ARG; + } + + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_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); +} + +esp_err_t esp_ble_mesh_register_time_scene_server_callback(esp_ble_mesh_time_scene_server_cb_t callback) +{ + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return (btc_profile_cb_set(BTC_PID_TIME_SCENE_SERVER, callback) == 0 ? ESP_OK : ESP_FAIL); +} + diff --git a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_config_model_api.h b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_config_model_api.h new file mode 100644 index 0000000000..9d1785cffc --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_config_model_api.h @@ -0,0 +1,818 @@ +// Copyright 2017-2019 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) + +/** Configuration Server Model context */ +typedef struct esp_ble_mesh_cfg_srv { + esp_ble_mesh_model_t *model; /*!< Pointer to Configuration Server 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; /*!< Heartbeat Publication timer */ + + uint16_t dst; /*!< Destination address for Heartbeat messages */ + uint16_t count; /*!< Number of Heartbeat messages to be sent */ + uint8_t period; /*!< Period for sending Heartbeat messages */ + uint8_t ttl; /*!< TTL to be used when sending Heartbeat messages */ + uint16_t feature; /*!< Bit field indicating features that trigger Heartbeat messages when changed */ + uint16_t net_idx; /*!< NetKey Index used by Heartbeat Publication */ + } heartbeat_pub; + + /** Heartbeat Subscription */ + struct { + int64_t expiry; /*!< Timestamp when Heartbeat subscription period is expired */ + + uint16_t src; /*!< Source address for Heartbeat messages */ + uint16_t dst; /*!< Destination address for Heartbeat messages */ + uint16_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 */ + + /** Optional heartbeat subscription tracking function */ + esp_ble_mesh_cb_t heartbeat_recv_cb; + } heartbeat_sub; +} esp_ble_mesh_cfg_srv_t; + +/** Parameters of Config Composition Data Get. */ +typedef struct { + uint8_t page; /*!< Page number of the Composition Data. */ +} esp_ble_mesh_cfg_composition_data_get_t; + +/** Parameters of Config 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 Config 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 Config 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 Config AppKey Get. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ +} esp_ble_mesh_cfg_app_key_get_t; + +/** Parameters of Config Node Identity Get. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ +} esp_ble_mesh_cfg_node_identity_get_t; + +/** Parameters of Config 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 Config 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 Config Key Refresh Phase Get. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ +} esp_ble_mesh_cfg_kr_phase_get_t; + +/** Parameters of Config 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 Config Beacon Set. */ +typedef struct { + uint8_t beacon; /*!< New Secure Network Beacon state */ +} esp_ble_mesh_cfg_beacon_set_t; + +/** Parameters of Config Default TTL Set. */ +typedef struct { + uint8_t ttl; /*!< The default TTL state value */ +} esp_ble_mesh_cfg_default_ttl_set_t; + +/** Parameters of Config Friend Set. */ +typedef struct { + uint8_t friend_state; /*!< The friend state value */ +} esp_ble_mesh_cfg_friend_set_t; + +/** Parameters of Config GATT Proxy Set. */ +typedef struct { + uint8_t gatt_proxy; /*!< The GATT Proxy state value */ +} esp_ble_mesh_cfg_gatt_proxy_set_t; + +/** Parameters of Config 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 Config NetKey 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 Config AppKey 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 Config Model App 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 Config 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 Config 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 Config 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 Config 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 Config 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 Config 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 Config 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 Config 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 Config 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 Config NetKey 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 Config NetKey Delete. */ +typedef struct { + uint16_t net_idx; /*!< The network key index */ +} esp_ble_mesh_cfg_net_key_delete_t; + +/** Parameters of Config AppKey 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 Config AppKey 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 Config 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 Config Model App 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 Config 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 Config Network Transmit Set. */ +typedef struct { + uint8_t net_transmit; /*!< Network Transmit State */ +} esp_ble_mesh_cfg_net_transmit_set_t; + +/** Parameters of Config Model Heartbeat Publication Set. */ +typedef struct { + uint16_t dst; /*!< Destination address for Heartbeat messages */ + uint8_t count; /*!< Number of Heartbeat messages to be sent */ + uint8_t period; /*!< Period for sending Heartbeat messages */ + uint8_t ttl; /*!< TTL to be used when sending Heartbeat messages */ + uint16_t feature; /*!< Bit field indicating features that trigger Heartbeat messages when changed */ + uint16_t net_idx; /*!< NetKey Index */ +} esp_ble_mesh_cfg_heartbeat_pub_set_t; + +/** Parameters of Config Model Heartbeat Subscription Set. */ +typedef struct { + uint16_t src; /*!< Source address for Heartbeat messages */ + uint16_t dst; /*!< Destination address for Heartbeat messages */ + uint8_t period; /*!< Period for receiving Heartbeat messages */ +} 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; + +/** Parameter of Config Beacon Status */ +typedef struct { + uint8_t beacon; /*!< Secure Network Beacon state value */ +} esp_ble_mesh_cfg_beacon_status_cb_t; + +/** Parameters of Config Composition Data Status */ +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; + +/** Parameter of Config Default TTL Status */ +typedef struct { + uint8_t default_ttl; /*!< Default TTL state value */ +} esp_ble_mesh_cfg_default_ttl_status_cb_t; + +/** Parameter of Config GATT Proxy Status */ +typedef struct { + uint8_t gatt_proxy; /*!< GATT Proxy state value */ +} esp_ble_mesh_cfg_gatt_proxy_status_cb_t; + +/** Parameters of Config Relay Status */ +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; + +/** Parameters of Config Model Publication Status */ +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; + +/** Parameters of Config Model Subscription Status */ +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; + +/** Parameters of Config NetKey Status */ +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; + +/** Parameters of Config AppKey Status */ +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; + +/** Parameters of Config Model App Status */ +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; + +/** Parameter of Config Friend Status */ +typedef struct { + uint8_t friend_state; /*!< Friend state value */ +} esp_ble_mesh_cfg_friend_status_cb_t; + +/** Parameters of Config Heartbeat Publication Status */ +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; + +/** Parameters of Config Heartbeat Subscription Status */ +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; + +/** Parameters of Config Network Transmit Status */ +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; + +/** Parameters of Config SIG/Vendor Subscription List */ +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; + +/** Parameter of Config NetKey List */ +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; + +/** Parameters of Config AppKey List */ +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; + +/** Parameters of Config Node Identity Status */ +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; + +/** Parameters of Config SIG/Vendor Model App List */ +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; + +/** Parameters of Config Key Refresh Phase Status */ +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; + +/** Parameters of Config Low Power Node PollTimeout Status */ +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; + +/** + * @brief Configuration Client Model received message union + */ +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; + +/** Configuration Client Model callback parameters */ +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; + +/** This enum value is the event of Configuration Client Model */ +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; + +/** + * @brief Configuration Server model related context. + */ + +typedef struct { + uint16_t element_addr; /*!< Element Address */ + uint16_t pub_addr; /*!< Publish Address */ + uint16_t app_idx; /*!< AppKey Index */ + bool cred_flag; /*!< Friendship Credential Flag */ + uint8_t pub_ttl; /*!< Publish TTL */ + uint8_t pub_period; /*!< Publish Period */ + uint8_t pub_retransmit; /*!< Publish Retransmit */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ +} esp_ble_mesh_state_change_cfg_mod_pub_set_t; + +/** Parameters of Config Model Subscription Add */ +typedef struct { + uint16_t element_addr; /*!< Element Address */ + uint16_t sub_addr; /*!< Subscription Address */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ +} esp_ble_mesh_state_change_cfg_model_sub_add_t; + +/** Parameters of Config Model Subscription Delete */ +typedef struct { + uint16_t element_addr; /*!< Element Address */ + uint16_t sub_addr; /*!< Subscription Address */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ +} esp_ble_mesh_state_change_cfg_model_sub_delete_t; + +/** Parameters of Config NetKey Add */ +typedef struct { + uint16_t net_idx; /*!< NetKey Index */ + uint8_t net_key[16]; /*!< NetKey */ +} esp_ble_mesh_state_change_cfg_netkey_add_t; + +/** Parameters of Config NetKey Update */ +typedef struct { + uint16_t net_idx; /*!< NetKey Index */ + uint8_t net_key[16]; /*!< NetKey */ +} esp_ble_mesh_state_change_cfg_netkey_update_t; + +/** Parameter of Config NetKey Delete */ +typedef struct { + uint16_t net_idx; /*!< NetKey Index */ +} esp_ble_mesh_state_change_cfg_netkey_delete_t; + +/** Parameters of Config AppKey Add */ +typedef struct { + uint16_t net_idx; /*!< NetKey Index */ + uint16_t app_idx; /*!< AppKey Index */ + uint8_t app_key[16]; /*!< AppKey */ +} esp_ble_mesh_state_change_cfg_appkey_add_t; + +/** Parameters of Config AppKey Update */ +typedef struct { + uint16_t net_idx; /*!< NetKey Index */ + uint16_t app_idx; /*!< AppKey Index */ + uint8_t app_key[16]; /*!< AppKey */ +} esp_ble_mesh_state_change_cfg_appkey_update_t; + +/** Parameters of Config AppKey Delete */ +typedef struct { + uint16_t net_idx; /*!< NetKey Index */ + uint16_t app_idx; /*!< AppKey Index */ +} esp_ble_mesh_state_change_cfg_appkey_delete_t; + +/** Parameters of Config Model App Bind */ +typedef struct { + uint16_t element_addr; /*!< Element Address */ + uint16_t app_idx; /*!< AppKey Index */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ +} esp_ble_mesh_state_change_cfg_model_app_bind_t; + +/** Parameters of Config Model App Unbind */ +typedef struct { + uint16_t element_addr; /*!< Element Address */ + uint16_t app_idx; /*!< AppKey Index */ + uint16_t company_id; /*!< Company ID */ + uint16_t model_id; /*!< Model ID */ +} esp_ble_mesh_state_change_cfg_model_app_unbind_t; + +/** Parameters of Config Key Refresh Phase Set */ +typedef struct { + uint16_t net_idx; /*!< NetKey Index */ + uint8_t kr_phase; /*!< New Key Refresh Phase Transition */ +} esp_ble_mesh_state_change_cfg_kr_phase_set_t; + +/** + * @brief Configuration Server model state change value union + */ +typedef union { + /** + * The recv_op in ctx can be used to decide which state is changed. + */ + esp_ble_mesh_state_change_cfg_mod_pub_set_t mod_pub_set; /*!< Config Model Publication Set */ + esp_ble_mesh_state_change_cfg_model_sub_add_t mod_sub_add; /*!< Config Model Subscription Add */ + esp_ble_mesh_state_change_cfg_model_sub_delete_t mod_sub_delete; /*!< Config Model Subscription Delete */ + esp_ble_mesh_state_change_cfg_netkey_add_t netkey_add; /*!< Config NetKey Add */ + esp_ble_mesh_state_change_cfg_netkey_update_t netkey_update; /*!< Config NetKey Update */ + esp_ble_mesh_state_change_cfg_netkey_delete_t netkey_delete; /*!< Config NetKey Delete */ + esp_ble_mesh_state_change_cfg_appkey_add_t appkey_add; /*!< Config AppKey Add */ + esp_ble_mesh_state_change_cfg_appkey_update_t appkey_update; /*!< Config AppKey Update */ + esp_ble_mesh_state_change_cfg_appkey_delete_t appkey_delete; /*!< Config AppKey Delete */ + esp_ble_mesh_state_change_cfg_model_app_bind_t mod_app_bind; /*!< Config Model App Bind */ + esp_ble_mesh_state_change_cfg_model_app_unbind_t mod_app_unbind; /*!< Config Model App Unbind */ + esp_ble_mesh_state_change_cfg_kr_phase_set_t kr_phase_set; /*!< Config Key Refresh Phase Set */ +} esp_ble_mesh_cfg_server_state_change_t; + +/** + * @brief Configuration Server model callback value union + */ +typedef union { + esp_ble_mesh_cfg_server_state_change_t state_change; /*!< ESP_BLE_MESH_CFG_SERVER_STATE_CHANGE_EVT */ +} esp_ble_mesh_cfg_server_cb_value_t; + +/** Configuration Server model callback parameters */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the server model structure */ + esp_ble_mesh_msg_ctx_t ctx; /*!< Context of the received message */ + esp_ble_mesh_cfg_server_cb_value_t value; /*!< Value of the received configuration messages */ +} esp_ble_mesh_cfg_server_cb_param_t; + +/** This enum value is the event of Configuration Server model */ +typedef enum { + ESP_BLE_MESH_CFG_SERVER_STATE_CHANGE_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 Configuration Client Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +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 Configuration Server Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +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 esp_ble_mesh_opcode_config_client_get_t in esp_ble_mesh_defs.h + * + * @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 esp_ble_mesh_opcode_config_client_set_t in esp_ble_mesh_defs.h + * + * @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/esp_ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h new file mode 100644 index 0000000000..42c90714c1 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h @@ -0,0 +1,1297 @@ +// Copyright 2017-2019 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 "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. + */ + +/** Parameters of Generic OnOff Set. */ +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; + +/** Parameters of Generic Level Set. */ +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; + +/** Parameters of Generic Delta Set. */ +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; + +/** Parameters of Generic Move Set. */ +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; + +/** Parameter of Generic Default Transition Time Set. */ +typedef struct { + uint8_t trans_time; /*!< The value of the Generic Default Transition Time state */ +} esp_ble_mesh_gen_def_trans_time_set_t; + +/** Parameter of Generic OnPowerUp Set. */ +typedef struct { + uint8_t onpowerup; /*!< The value of the Generic OnPowerUp state */ +} esp_ble_mesh_gen_onpowerup_set_t; + +/** Parameters of Generic Power Level Set. */ +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; + +/** Parameter of Generic Power Default Set. */ +typedef struct { + uint16_t power; /*!< The value of the Generic Power Default state */ +} esp_ble_mesh_gen_power_default_set_t; + +/** Parameters of Generic Power Range Set. */ +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; + +/** Parameters of Generic Location Global Set. */ +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; + +/** Parameters of Generic Location Local Set. */ +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; + +/** Parameter of Generic User Property Get. */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Generic User Property */ +} esp_ble_mesh_gen_user_property_get_t; + +/** Parameters of Generic User Property Set. */ +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; + +/** Parameter of Generic Admin Property Get. */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Generic Admin Property */ +} esp_ble_mesh_gen_admin_property_get_t; + +/** Parameters of Generic Admin Property Set. */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Generic Admin Property */ + uint8_t user_access; /*!< Enumeration indicating user accessn */ + struct net_buf_simple *property_value; /*!< Raw value for the Admin Property */ +} esp_ble_mesh_gen_admin_property_set_t; + +/** Parameter of Generic Manufacturer Property Get. */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Generic Manufacturer Property */ +} esp_ble_mesh_gen_manufacturer_property_get_t; + +/** Parameters of Generic Manufacturer Property Set. */ +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; + +/** Parameter of Generic Client Properties Get. */ +typedef struct { + uint16_t property_id; /*!< A starting Client Property ID present within an element */ +} esp_ble_mesh_gen_client_properties_get_t; + +/** + * @brief Generic Client Model get message union + */ +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; + +/** + * @brief Generic Client Model set message union + */ +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. + */ + +/** Parameters of Generic OnOff Status. */ +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; + +/** Parameters of Generic Level Status. */ +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; + +/** Parameter of Generic Default Transition Time Status. */ +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; + +/** Parameter of Generic OnPowerUp Status. */ +typedef struct { + uint8_t onpowerup; /*!< The value of the Generic OnPowerUp state */ +} esp_ble_mesh_gen_onpowerup_status_cb_t; + +/** Parameters of Generic Power Level Status. */ +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; + +/** Parameter of Generic Power Last Status. */ +typedef struct { + uint16_t power; /*!< The value of the Generic Power Last state */ +} esp_ble_mesh_gen_power_last_status_cb_t; + +/** Parameter of Generic Power Default Status. */ +typedef struct { + uint16_t power; /*!< The value of the Generic Default Last state */ +} esp_ble_mesh_gen_power_default_status_cb_t; + +/** Parameters of Generic Power Range Status. */ +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; + +/** Parameters of Generic Battery Status. */ +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; + +/** Parameters of Generic Location Global Status. */ +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; + +/** Parameters of Generic Location Local Status. */ +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; + +/** Parameter of Generic User Properties Status. */ +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; + +/** Parameters of Generic User Property Status. */ +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; + +/** Parameter of Generic Admin Properties Status. */ +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; + +/** Parameters of Generic Admin Property Status. */ +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; + +/** Parameter of Generic Manufacturer Properties Status. */ +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; + +/** Parameters of Generic Manufacturer Property Status. */ +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; + +/** Parameter of Generic Client Properties Status. */ +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; + +/** + * @brief Generic Client Model received message union + */ +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; + +/** Generic Client Model callback parameters */ +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; + +/** This enum value is the event of Generic Client Model */ +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 Generic Client Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +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 esp_ble_mesh_generic_message_opcode_t in esp_ble_mesh_defs.h + * + * @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 esp_ble_mesh_generic_message_opcode_t in esp_ble_mesh_defs.h + * + * @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); + +/** + * @brief Generic Server Models related context. + */ + +/** @def ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV + * + * @brief Define a new Generic OnOff Server Model. + * + * @note 1. The Generic OnOff Server Model is a root model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_onoff_srv_t. + * + * @return New Generic OnOff Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_LEVEL_SRV + * + * @brief Define a new Generic Level Server Model. + * + * @note 1. The Generic Level Server Model is a root model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_level_srv_t. + * + * @return New Generic Level Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_LEVEL_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_LEVEL_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_DEF_TRANS_TIME_SRV + * + * @brief Define a new Generic Default Transition Time Server Model. + * + * @note 1. The Generic Default Transition Time Server Model is a root model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_def_trans_time_srv_t. + * + * @return New Generic Default Transition Time Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_DEF_TRANS_TIME_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_POWER_ONOFF_SRV + * + * @brief Define a new Generic Power OnOff Server Model. + * + * @note 1. The Generic Power OnOff Server model extends the Generic OnOff Server + * model. When this model is present on an element, the corresponding + * Generic Power OnOff Setup Server model shall also be present. + * 2. This model may be used to represent a variety of devices that do not + * fit any of the model descriptions that have been defined but support + * the generic properties of On/Off. + * 3. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_power_onoff_srv_t. + * + * @return New Generic Power OnOff Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_POWER_ONOFF_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_POWER_ONOFF_SETUP_SRV + * + * @brief Define a new Generic Power OnOff Setup Server Model. + * + * @note 1. The Generic Power OnOff Setup Server model extends the Generic Power + * OnOff Server model and the Generic Default Transition Time Server model. + * 2. This model shall support model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_power_onoff_setup_srv_t. + * + * @return New Generic Power OnOff Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_POWER_ONOFF_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_POWER_LEVEL_SRV + * + * @brief Define a new Generic Power Level Server Model. + * + * @note 1. The Generic Power Level Server model extends the Generic Power OnOff + * Server model and the Generic Level Server model. When this model is + * present on an Element, the corresponding Generic Power Level Setup + * Server model shall also be present. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_power_level_srv_t. + * + * @return New Generic Power Level Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_POWER_LEVEL_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_POWER_LEVEL_SETUP_SRV + * + * @brief Define a new Generic Power Level Setup Server Model. + * + * @note 1. The Generic Power Level Setup Server model extends the Generic Power + * Level Server model and the Generic Power OnOff Setup Server model. + * 2. This model shall support model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_power_level_setup_srv_t. + * + * @return New Generic Power Level Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_POWER_LEVEL_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_BATTERY_SRV + * + * @brief Define a new Generic Battery Server Model. + * + * @note 1. The Generic Battery Server Model is a root model. + * 2. This model shall support model publication and model subscription. + * 3. The model may be used to represent an element that is powered by a battery. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_battery_srv_t. + * + * @return New Generic Battery Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_BATTERY_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_BATTERY_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_LOCATION_SRV + * + * @brief Define a new Generic Location Server Model. + * + * @note 1. The Generic Location Server model is a root model. When this model + * is present on an Element, the corresponding Generic Location Setup + * Server model shall also be present. + * 2. This model shall support model publication and model subscription. + * 3. The model may be used to represent an element that knows its + * location (global or local). + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_location_srv_t. + * + * @return New Generic Location Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_LOCATION_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_LOCATION_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_LOCATION_SETUP_SRV + * + * @brief Define a new Generic Location Setup Server Model. + * + * @note 1. The Generic Location Setup Server model extends the Generic Location + * Server model. + * 2. This model shall support model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_location_setup_srv_t. + * + * @return New Generic Location Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_LOCATION_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_USER_PROP_SRV + * + * @brief Define a new Generic User Property Server Model. + * + * @note 1. The Generic User Property Server model is a root model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_user_prop_srv_t. + * + * @return New Generic User Property Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_USER_PROP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_USER_PROP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_ADMIN_PROP_SRV + * + * @brief Define a new Generic Admin Property Server Model. + * + * @note 1. The Generic Admin Property Server model extends the Generic User + * Property Server model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_admin_prop_srv_t. + * + * @return New Generic Admin Property Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_ADMIN_PROP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_MANUFACTURER_PROP_SRV + * + * @brief Define a new Generic Manufacturer Property Server Model. + * + * @note 1. The Generic Manufacturer Property Server model extends the Generic + * User Property Server model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_manu_prop_srv_t. + * + * @return New Generic Manufacturer Property Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_MANUFACTURER_PROP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_GEN_CLIENT_PROP_SRV + * + * @brief Define a new Generic User Property Server Model. + * + * @note 1. The Generic Client Property Server model is a root model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_gen_client_prop_srv_t. + * + * @return New Generic Client Property Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_GEN_CLIENT_PROP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV, \ + NULL, srv_pub, srv_data) + +/** Parameters of Generic OnOff state */ +typedef struct { + uint8_t onoff; /*!< The present value of the Generic OnOff state */ + uint8_t target_onoff; /*!< The target value of the Generic OnOff state */ +} esp_ble_mesh_gen_onoff_state_t; + +/** User data of Generic OnOff Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic OnOff Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_onoff_state_t state; /*!< Parameters of the Generic OnOff state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ +} esp_ble_mesh_gen_onoff_srv_t; + +/** Parameters of Generic Level state */ +typedef struct { + int16_t level; /*!< The present value of the Generic Level state */ + int16_t target_level; /*!< The target value of the Generic Level state */ + + /** + * When a new transaction starts, level should be set to last_last, and use + * "level + incoming delta" to calculate the target level. In another word, + * "last_level" is used to record "level" of the last transaction, and + * "last_delta" is used to record the previously received delta_level value. + */ + int16_t last_level; /*!< The last value of the Generic Level state */ + int32_t last_delta; /*!< The last delta change of the Generic Level state */ + + bool move_start; /*!< Indicate if the transition of the Generic Level state has been started */ + bool positive; /*!< Indicate if the transition is positive or negative */ +} esp_ble_mesh_gen_level_state_t; + +/** User data of Generic Level Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Level Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_level_state_t state; /*!< Parameters of the Generic Level state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ + int32_t tt_delta_level; /*!< Delta change value of level state transition */ +} esp_ble_mesh_gen_level_srv_t; + +/** Parameter of Generic Default Transition Time state */ +typedef struct { + uint8_t trans_time; /*!< The value of the Generic Default Transition Time state */ +} esp_ble_mesh_gen_def_trans_time_state_t; + +/** User data of Generic Default Transition Time Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Default Transition Time Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_def_trans_time_state_t state; /*!< Parameters of the Generic Default Transition Time state */ +} esp_ble_mesh_gen_def_trans_time_srv_t; + +/** Parameter of Generic OnPowerUp state */ +typedef struct { + uint8_t onpowerup; /*!< The value of the Generic OnPowerUp state */ +} esp_ble_mesh_gen_onpowerup_state_t; + +/** User data of Generic Power OnOff Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Power OnOff Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_onpowerup_state_t *state; /*!< Parameters of the Generic OnPowerUp state */ +} esp_ble_mesh_gen_power_onoff_srv_t; + +/** User data of Generic Power OnOff Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Power OnOff Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_onpowerup_state_t *state; /*!< Parameters of the Generic OnPowerUp state */ +} esp_ble_mesh_gen_power_onoff_setup_srv_t; + +/** Parameters of Generic Power Level state */ +typedef struct { + uint16_t power_actual; /*!< The present value of the Generic Power Actual state */ + uint16_t target_power_actual; /*!< The target value of the Generic Power Actual state */ + + uint16_t power_last; /*!< The value of the Generic Power Last state */ + uint16_t power_default; /*!< The value of the Generic Power Default state */ + + uint8_t status_code; /*!< The status code of setting Generic Power Range state */ + uint16_t power_range_min; /*!< The minimum value of the Generic Power Range state */ + uint16_t power_range_max; /*!< The maximum value of the Generic Power Range state */ +} esp_ble_mesh_gen_power_level_state_t; + +/** User data of Generic Power Level Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Power Level Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_power_level_state_t *state; /*!< Parameters of the Generic Power Level state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ + int32_t tt_delta_level; /*!< Delta change value of level state transition */ +} esp_ble_mesh_gen_power_level_srv_t; + +/** User data of Generic Power Level Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Power Level Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_power_level_state_t *state; /*!< Parameters of the Generic Power Level state */ +} esp_ble_mesh_gen_power_level_setup_srv_t; + +/** Parameters of Generic Battery state */ +typedef struct { + uint32_t battery_level : 8, /*!< The value of the Generic Battery Level state */ + time_to_discharge : 24; /*!< The value of the Generic Battery Time to Discharge state */ + uint32_t time_to_charge : 24, /*!< The value of the Generic Battery Time to Charge state */ + battery_flags : 8; /*!< The value of the Generic Battery Flags state */ +} esp_ble_mesh_gen_battery_state_t; + +/** User data of Generic Battery Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Battery Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_battery_state_t state; /*!< Parameters of the Generic Battery state */ +} esp_ble_mesh_gen_battery_srv_t; + +/** Parameters of Generic Location state */ +typedef struct { + int32_t global_latitude; /*!< The value of the Global Latitude field */ + int32_t global_longitude; /*!< The value of the Global Longitude field */ + int16_t global_altitude; /*!< The value of the Global Altitude field */ + int16_t local_north; /*!< The value of the Local North field */ + int16_t local_east; /*!< The value of the Local East field */ + int16_t local_altitude; /*!< The value of the Local Altitude field */ + uint8_t floor_number; /*!< The value of the Floor Number field */ + uint16_t uncertainty; /*!< The value of the Uncertainty field */ +} esp_ble_mesh_gen_location_state_t; + +/** User data of Generic Location Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Location Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_location_state_t *state; /*!< Parameters of the Generic Location state */ +} esp_ble_mesh_gen_location_srv_t; + +/** User data of Generic Location Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Location Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_gen_location_state_t *state; /*!< Parameters of the Generic Location state */ +} esp_ble_mesh_gen_location_setup_srv_t; + +/** This enum value is the access value of Generic User Property */ +typedef enum { + ESP_BLE_MESH_GEN_USER_ACCESS_PROHIBIT, + ESP_BLE_MESH_GEN_USER_ACCESS_READ, + ESP_BLE_MESH_GEN_USER_ACCESS_WRITE, + ESP_BLE_MESH_GEN_USER_ACCESS_READ_WRITE, +} esp_ble_mesh_gen_user_prop_access_t; + +/** This enum value is the access value of Generic Admin Property */ +typedef enum { + ESP_BLE_MESH_GEN_ADMIN_NOT_USER_PROP, + ESP_BLE_MESH_GEN_ADMIN_ACCESS_READ, + ESP_BLE_MESH_GEN_ADMIN_ACCESS_WRITE, + ESP_BLE_MESH_GEN_ADMIN_ACCESS_READ_WRITE, +} esp_ble_mesh_gen_admin_prop_access_t; + +/** This enum value is the access value of Generic Manufacturer Property */ +typedef enum { + ESP_BLE_MESH_GEN_MANU_NOT_USER_PROP, + ESP_BLE_MESH_GEN_MANU_ACCESS_READ, +} esp_ble_mesh_gen_manu_prop_access_t; + +/** Parameters of Generic Property states */ +typedef struct { + uint16_t id; /*!< The value of User/Admin/Manufacturer Property ID */ + uint8_t user_access; /*!< The value of User Access field */ + uint8_t admin_access; /*!< The value of Admin Access field */ + uint8_t manu_access; /*!< The value of Manufacturer Access field */ + struct net_buf_simple *val; /*!< The value of User/Admin/Manufacturer Property */ +} esp_ble_mesh_generic_property_t; + +/** User data of Generic User Property Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic User Property Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + uint8_t property_count; /*!< Generic User Property count */ + esp_ble_mesh_generic_property_t *properties; /*!< Parameters of the Generic User Property state */ +} esp_ble_mesh_gen_user_prop_srv_t; + +/** User data of Generic Admin Property Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Admin Property Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + uint8_t property_count; /*!< Generic Admin Property count */ + esp_ble_mesh_generic_property_t *properties; /*!< Parameters of the Generic Admin Property state */ +} esp_ble_mesh_gen_admin_prop_srv_t; + +/** User data of Generic Manufacturer Property Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Manufacturer Property Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + uint8_t property_count; /*!< Generic Manufacturer Property count */ + esp_ble_mesh_generic_property_t *properties; /*!< Parameters of the Generic Manufacturer Property state */ +} esp_ble_mesh_gen_manu_prop_srv_t; + +/** User data of Generic Client Property Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Generic Client Property Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + uint8_t id_count; /*!< Generic Client Property ID count */ + uint16_t *property_ids; /*!< Parameters of the Generic Client Property state */ +} esp_ble_mesh_gen_client_prop_srv_t; + +/** Parameter of Generic OnOff Set state change event */ +typedef struct { + uint8_t onoff; /*!< The value of Generic OnOff state */ +} esp_ble_mesh_state_change_gen_onoff_set_t; + +/** Parameter of Generic Level Set state change event */ +typedef struct { + int16_t level; /*!< The value of Generic Level state */ +} esp_ble_mesh_state_change_gen_level_set_t; + +/** Parameter of Generic Delta Set state change event */ +typedef struct { + int16_t level; /*!< The value of Generic Level state */ +} esp_ble_mesh_state_change_gen_delta_set_t; + +/** Parameter of Generic Move Set state change event */ +typedef struct { + int16_t level; /*!< The value of Generic Level state */ +} esp_ble_mesh_state_change_gen_move_set_t; + +/** Parameter of Generic Default Transition Time Set state change event */ +typedef struct { + uint8_t trans_time; /*!< The value of Generic Default Transition Time state */ +} esp_ble_mesh_state_change_gen_def_trans_time_set_t; + +/** Parameter of Generic OnPowerUp Set state change event */ +typedef struct { + uint8_t onpowerup; /*!< The value of Generic OnPowerUp state */ +} esp_ble_mesh_state_change_gen_onpowerup_set_t; + +/** Parameter of Generic Power Level Set state change event */ +typedef struct { + uint16_t power; /*!< The value of Generic Power Actual state */ +} esp_ble_mesh_state_change_gen_power_level_set_t; + +/** Parameter of Generic Power Default Set state change event */ +typedef struct { + uint16_t power; /*!< The value of Generic Power Default state */ +} esp_ble_mesh_state_change_gen_power_default_set_t; + +/** Parameters of Generic Power Range Set state change event */ +typedef struct { + uint16_t range_min; /*!< The minimum value of Generic Power Range state */ + uint16_t range_max; /*!< The maximum value of Generic Power Range state */ +} esp_ble_mesh_state_change_gen_power_range_set_t; + +/** Parameters of Generic Location Global Set state change event */ +typedef struct { + int32_t latitude; /*!< The Global Latitude value of Generic Location state */ + int32_t longitude; /*!< The Global Longitude value of Generic Location state */ + int16_t altitude; /*!< The Global Altitude value of Generic Location state */ +} esp_ble_mesh_state_change_gen_loc_global_set_t; + +/** Parameters of Generic Location Local Set state change event */ +typedef struct { + int16_t north; /*!< The Local North value of Generic Location state */ + int16_t east; /*!< The Local East value of Generic Location state */ + int16_t altitude; /*!< The Local Altitude value of Generic Location state */ + uint8_t floor_number; /*!< The Floor Number value of Generic Location state */ + uint16_t uncertainty; /*!< The Uncertainty value of Generic Location state */ +} esp_ble_mesh_state_change_gen_loc_local_set_t; + +/** Parameters of Generic User Property Set state change event */ +typedef struct { + uint16_t id; /*!< The property id of Generic User Property state */ + struct net_buf_simple *value; /*!< The property value of Generic User Property state */ +} esp_ble_mesh_state_change_gen_user_property_set_t; + +/** Parameters of Generic Admin Property Set state change event */ +typedef struct { + uint16_t id; /*!< The property id of Generic Admin Property state */ + uint8_t access; /*!< The property access of Generic Admin Property state */ + struct net_buf_simple *value; /*!< The property value of Generic Admin Property state */ +} esp_ble_mesh_state_change_gen_admin_property_set_t; + +/** Parameters of Generic Manufacturer Property Set state change event */ +typedef struct { + uint16_t id; /*!< The property id of Generic Manufacturer Property state */ + uint8_t access; /*!< The property value of Generic Manufacturer Property state */ +} esp_ble_mesh_state_change_gen_manu_property_set_t; + +/** + * @brief Generic Server Model state change value union + */ +typedef union { + /** + * The recv_op in ctx can be used to decide which state is changed. + */ + esp_ble_mesh_state_change_gen_onoff_set_t onoff_set; /*!< Generic OnOff Set */ + esp_ble_mesh_state_change_gen_level_set_t level_set; /*!< Generic Level Set */ + esp_ble_mesh_state_change_gen_delta_set_t delta_set; /*!< Generic Delta Set */ + esp_ble_mesh_state_change_gen_move_set_t move_set; /*!< Generic Move Set */ + esp_ble_mesh_state_change_gen_def_trans_time_set_t def_trans_time_set; /*!< Generic Default Transition Time Set */ + esp_ble_mesh_state_change_gen_onpowerup_set_t onpowerup_set; /*!< Generic OnPowerUp Set */ + esp_ble_mesh_state_change_gen_power_level_set_t power_level_set; /*!< Generic Power Level Set */ + esp_ble_mesh_state_change_gen_power_default_set_t power_default_set; /*!< Generic Power Default Set */ + esp_ble_mesh_state_change_gen_power_range_set_t power_range_set; /*!< Generic Power Range Set */ + esp_ble_mesh_state_change_gen_loc_global_set_t loc_global_set; /*!< Generic Location Global Set */ + esp_ble_mesh_state_change_gen_loc_local_set_t loc_local_set; /*!< Generic Location Local Set */ + esp_ble_mesh_state_change_gen_user_property_set_t user_property_set; /*!< Generic User Property Set */ + esp_ble_mesh_state_change_gen_admin_property_set_t admin_property_set; /*!< Generic Admin Property Set */ + esp_ble_mesh_state_change_gen_manu_property_set_t manu_property_set; /*!< Generic Manufacturer Property Set */ +} esp_ble_mesh_generic_server_state_change_t; + +/** Context of the received Generic User Property Get message */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Generic User Property */ +} esp_ble_mesh_server_recv_gen_user_property_get_t; + +/** Context of the received Generic Admin Property Get message */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Generic Admin Property */ +} esp_ble_mesh_server_recv_gen_admin_property_get_t; + +/** Context of the received Generic Manufacturer Property message */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Generic Manufacturer Property */ +} esp_ble_mesh_server_recv_gen_manufacturer_property_get_t; + +/** Context of the received Generic Client Properties Get message */ +typedef struct { + uint16_t property_id; /*!< A starting Client Property ID present within an element */ +} esp_ble_mesh_server_recv_gen_client_properties_get_t; + +/** + * @brief Generic Server Model received get message union + */ +typedef union { + esp_ble_mesh_server_recv_gen_user_property_get_t user_property; /*!< Generic User Property Get */ + esp_ble_mesh_server_recv_gen_admin_property_get_t admin_property; /*!< Generic Admin Property Get */ + esp_ble_mesh_server_recv_gen_manufacturer_property_get_t manu_property; /*!< Generic Manufacturer Property Get */ + esp_ble_mesh_server_recv_gen_client_properties_get_t client_properties; /*!< Generic Client Properties Get */ +} esp_ble_mesh_generic_server_recv_get_msg_t; + +/** Context of the received Generic OnOff Set message */ +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_server_recv_gen_onoff_set_t; + +/** Context of the received Generic Level Set message */ +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_server_recv_gen_level_set_t; + +/** Context of the received Generic Delta Set message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + int32_t delta_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_server_recv_gen_delta_set_t; + +/** Context of the received Generic Move Set message */ +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_server_recv_gen_move_set_t; + +/** Context of the received Generic Default Transition Time Set message */ +typedef struct { + uint8_t trans_time; /*!< The value of the Generic Default Transition Time state */ +} esp_ble_mesh_server_recv_gen_def_trans_time_set_t; + +/** Context of the received Generic OnPowerUp Set message */ +typedef struct { + uint8_t onpowerup; /*!< The value of the Generic OnPowerUp state */ +} esp_ble_mesh_server_recv_gen_onpowerup_set_t; + +/** Context of the received Generic Power Level Set message */ +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_server_recv_gen_power_level_set_t; + +/** Context of the received Generic Power Default Set message */ +typedef struct { + uint16_t power; /*!< The value of the Generic Power Default state */ +} esp_ble_mesh_server_recv_gen_power_default_set_t; + +/** Context of the received Generic Power Range Set message */ +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_server_recv_gen_power_range_set_t; + +/** Context of the received Generic Location Global Set message */ +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_server_recv_gen_loc_global_set_t; + +/** Context of the received Generic Location Local Set message */ +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_server_recv_gen_loc_local_set_t; + +/** Context of the received Generic User Property Set message */ +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_server_recv_gen_user_property_set_t; + +/** Context of the received Generic Admin Property Set message */ +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_server_recv_gen_admin_property_set_t; + +/** Context of the received Generic Manufacturer Property Set message */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Generic Manufacturer Property */ + uint8_t user_access; /*!< Enumeration indicating user access */ +} esp_ble_mesh_server_recv_gen_manufacturer_property_set_t; + +/** + * @brief Generic Server Model received set message union + */ +typedef union { + esp_ble_mesh_server_recv_gen_onoff_set_t onoff; /*!< Generic OnOff Set/Generic OnOff Set Unack */ + esp_ble_mesh_server_recv_gen_level_set_t level; /*!< Generic Level Set/Generic Level Set Unack */ + esp_ble_mesh_server_recv_gen_delta_set_t delta; /*!< Generic Delta Set/Generic Delta Set Unack */ + esp_ble_mesh_server_recv_gen_move_set_t move; /*!< Generic Move Set/Generic Move Set Unack */ + esp_ble_mesh_server_recv_gen_def_trans_time_set_t def_trans_time; /*!< Generic Default Transition Time Set/Generic Default Transition Time Set Unack */ + esp_ble_mesh_server_recv_gen_onpowerup_set_t onpowerup; /*!< Generic OnPowerUp Set/Generic OnPowerUp Set Unack */ + esp_ble_mesh_server_recv_gen_power_level_set_t power_level; /*!< Generic Power Level Set/Generic Power Level Set Unack */ + esp_ble_mesh_server_recv_gen_power_default_set_t power_default; /*!< Generic Power Default Set/Generic Power Default Set Unack */ + esp_ble_mesh_server_recv_gen_power_range_set_t power_range; /*!< Generic Power Range Set/Generic Power Range Set Unack */ + esp_ble_mesh_server_recv_gen_loc_global_set_t location_global; /*!< Generic Location Global Set/Generic Location Global Set Unack */ + esp_ble_mesh_server_recv_gen_loc_local_set_t location_local; /*!< Generic Location Local Set/Generic Location Local Set Unack */ + esp_ble_mesh_server_recv_gen_user_property_set_t user_property; /*!< Generic User Property Set/Generic User Property Set Unack */ + esp_ble_mesh_server_recv_gen_admin_property_set_t admin_property; /*!< Generic Admin Property Set/Generic Admin Property Set Unack */ + esp_ble_mesh_server_recv_gen_manufacturer_property_set_t manu_property; /*!< Generic Manufacturer Property Set/Generic Manufacturer Property Set Unack */ +} esp_ble_mesh_generic_server_recv_set_msg_t; + +/** + * @brief Generic Server Model callback value union + */ +typedef union { + esp_ble_mesh_generic_server_state_change_t state_change; /*!< ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT */ + esp_ble_mesh_generic_server_recv_get_msg_t get; /*!< ESP_BLE_MESH_GENERIC_SERVER_RECV_GET_MSG_EVT */ + esp_ble_mesh_generic_server_recv_set_msg_t set; /*!< ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT */ +} esp_ble_mesh_generic_server_cb_value_t; + +/** Generic Server Model callback parameters */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to Generic Server Models */ + esp_ble_mesh_msg_ctx_t ctx; /*!< Context of the received messages */ + esp_ble_mesh_generic_server_cb_value_t value; /*!< Value of the received Generic Messages */ +} esp_ble_mesh_generic_server_cb_param_t; + +/** This enum value is the event of Generic Server Model */ +typedef enum { + /** + * 1. When get_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, no event will be + * callback to the application layer when Generic Get messages are received. + * 2. When set_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, this event will + * be callback to the application layer when Generic Set/Set Unack messages + * are received. + */ + ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT, + /** + * When get_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will be + * callback to the application layer when Generic Get messages are received. + */ + ESP_BLE_MESH_GENERIC_SERVER_RECV_GET_MSG_EVT, + /** + * When set_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will be + * callback to the application layer when Generic Set/Set Unack messages are received. + */ + ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT, + ESP_BLE_MESH_GENERIC_SERVER_EVT_MAX, +} esp_ble_mesh_generic_server_cb_event_t; + +/** + * @brief Bluetooth Mesh Generic Server Model function. + */ + +/** + * @brief Generic Server Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +typedef void (* esp_ble_mesh_generic_server_cb_t)(esp_ble_mesh_generic_server_cb_event_t event, + esp_ble_mesh_generic_server_cb_param_t *param); + +/** + * @brief Register BLE Mesh Generic 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_generic_server_callback(esp_ble_mesh_generic_server_cb_t callback); + +#endif /* _ESP_BLE_MESH_GENERIC_MODEL_API_H_ */ + diff --git a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_health_model_api.h b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_health_model_api.h new file mode 100644 index 0000000000..f0836756ec --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_health_model_api.h @@ -0,0 +1,406 @@ +// Copyright 2017-2019 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) + +/** @def ESP_BLE_MESH_HEALTH_PUB_DEFINE + * + * A helper to define a health publication context + * + * @param _name Name given to the publication context variable. + * @param _max Maximum number of faults the element can have. + * @param _role Role of the device which contains the model. + */ +#define ESP_BLE_MESH_HEALTH_PUB_DEFINE(_name, _max, _role) \ + ESP_BLE_MESH_MODEL_PUB_DEFINE(_name, (1 + 3 + (_max)), _role) + +/** + * SIG identifier of Health Fault Test. + * 0x01 ~ 0xFF: Vendor Specific Test. + */ +#define ESP_BLE_MESH_HEALTH_STANDARD_TEST 0x00 + +/** + * Fault values of Health Fault Test. + * 0x33 ~ 0x7F: Reserved for Future Use. + * 0x80 ~ 0xFF: Vendor Specific Warning/Error. + */ +#define ESP_BLE_MESH_NO_FAULT 0x00 +#define ESP_BLE_MESH_BATTERY_LOW_WARNING 0x01 +#define ESP_BLE_MESH_BATTERY_LOW_ERROR 0x02 +#define ESP_BLE_MESH_SUPPLY_VOLTAGE_TOO_LOW_WARNING 0x03 +#define ESP_BLE_MESH_SUPPLY_VOLTAGE_TOO_LOW_ERROR 0x04 +#define ESP_BLE_MESH_SUPPLY_VOLTAGE_TOO_HIGH_WARNING 0x05 +#define ESP_BLE_MESH_SUPPLY_VOLTAGE_TOO_HIGH_ERROR 0x06 +#define ESP_BLE_MESH_POWER_SUPPLY_INTERRUPTED_WARNING 0x07 +#define ESP_BLE_MESH_POWER_SUPPLY_INTERRUPTED_ERROR 0x08 +#define ESP_BLE_MESH_NO_LOAD_WARNING 0x09 +#define ESP_BLE_MESH_NO_LOAD_ERROR 0x0A +#define ESP_BLE_MESH_OVERLOAD_WARNING 0x0B +#define ESP_BLE_MESH_OVERLOAD_ERROR 0x0C +#define ESP_BLE_MESH_OVERHEAT_WARNING 0x0D +#define ESP_BLE_MESH_OVERHEAT_ERROR 0x0E +#define ESP_BLE_MESH_CONDENSATION_WARNING 0x0F +#define ESP_BLE_MESH_CONDENSATION_ERROR 0x10 +#define ESP_BLE_MESH_VIBRATION_WARNING 0x11 +#define ESP_BLE_MESH_VIBRATION_ERROR 0x12 +#define ESP_BLE_MESH_CONFIGURATION_WARNING 0x13 +#define ESP_BLE_MESH_CONFIGURATION_ERROR 0x14 +#define ESP_BLE_MESH_ELEMENT_NOT_CALIBRATED_WARNING 0x15 +#define ESP_BLE_MESH_ELEMENT_NOT_CALIBRATED_ERROR 0x16 +#define ESP_BLE_MESH_MEMORY_WARNING 0x17 +#define ESP_BLE_MESH_MEMORY_ERROR 0x18 +#define ESP_BLE_MESH_SELF_TEST_WARNING 0x19 +#define ESP_BLE_MESH_SELF_TEST_ERROR 0x1A +#define ESP_BLE_MESH_INPUT_TOO_LOW_WARNING 0x1B +#define ESP_BLE_MESH_INPUT_TOO_LOW_ERROR 0x1C +#define ESP_BLE_MESH_INPUT_TOO_HIGH_WARNING 0x1D +#define ESP_BLE_MESH_INPUT_TOO_HIGH_ERROR 0x1E +#define ESP_BLE_MESH_INPUT_NO_CHANGE_WARNING 0x1F +#define ESP_BLE_MESH_INPUT_NO_CHANGE_ERROR 0x20 +#define ESP_BLE_MESH_ACTUATOR_BLOCKED_WARNING 0x21 +#define ESP_BLE_MESH_ACTUATOR_BLOCKED_ERROR 0x22 +#define ESP_BLE_MESH_HOUSING_OPENED_WARNING 0x23 +#define ESP_BLE_MESH_HOUSING_OPENED_ERROR 0x24 +#define ESP_BLE_MESH_TAMPER_WARNING 0x25 +#define ESP_BLE_MESH_TAMPER_ERROR 0x26 +#define ESP_BLE_MESH_DEVICE_MOVED_WARNING 0x27 +#define ESP_BLE_MESH_DEVICE_MOVED_ERROR 0x28 +#define ESP_BLE_MESH_DEVICE_DROPPED_WARNING 0x29 +#define ESP_BLE_MESH_DEVICE_DROPPED_ERROR 0x2A +#define ESP_BLE_MESH_OVERFLOW_WARNING 0x2B +#define ESP_BLE_MESH_OVERFLOW_ERROR 0x2C +#define ESP_BLE_MESH_EMPTY_WARNING 0x2D +#define ESP_BLE_MESH_EMPTY_ERROR 0x2E +#define ESP_BLE_MESH_INTERNAL_BUS_WARNING 0x2F +#define ESP_BLE_MESH_INTERNAL_BUS_ERROR 0x30 +#define ESP_BLE_MESH_MECHANISM_JAMMED_WARNING 0x31 +#define ESP_BLE_MESH_MECHANISM_JAMMED_ERROR 0x32 + +/** ESP BLE Mesh Health Server callback */ +typedef struct { + /** Clear health registered faults. Initialized by the stack. */ + esp_ble_mesh_cb_t fault_clear; + + /** Run a specific health test. Initialized by the stack. */ + esp_ble_mesh_cb_t fault_test; + + /** Health attention on callback. Initialized by the stack. */ + esp_ble_mesh_cb_t attention_on; + + /** Health attention off callback. Initialized by the stack. */ + esp_ble_mesh_cb_t attention_off; +} esp_ble_mesh_health_srv_cb_t; + +#define ESP_BLE_MESH_HEALTH_FAULT_ARRAY_SIZE 32 + +/** ESP BLE Mesh Health Server test Context */ +typedef struct { + uint8_t id_count; /*!< Number of Health self-test ID */ + const uint8_t *test_ids; /*!< Array of Health self-test IDs */ + uint16_t company_id; /*!< Company ID used to identify the Health Fault state */ + uint8_t prev_test_id; /*!< Current test ID of the health fault test */ + uint8_t current_faults[ESP_BLE_MESH_HEALTH_FAULT_ARRAY_SIZE]; /*!< Array of current faults */ + uint8_t registered_faults[ESP_BLE_MESH_HEALTH_FAULT_ARRAY_SIZE]; /*!< Array of registered faults */ +} __attribute__((packed)) esp_ble_mesh_health_test_t; + +/** ESP BLE Mesh Health Server Model Context */ +typedef struct { + /** Pointer to Health Server Model */ + esp_ble_mesh_model_t *model; + + /** Health callback struct */ + esp_ble_mesh_health_srv_cb_t health_cb; + + /** Attention Timer state */ + struct k_delayed_work attention_timer; + + /** Attention Timer start flag */ + bool attention_timer_start; + + /** Health Server fault test */ + esp_ble_mesh_health_test_t health_test; +} esp_ble_mesh_health_srv_t; + +/** Parameter of Health Fault Get */ +typedef struct { + uint16_t company_id; /*!< Bluetooth assigned 16-bit Company ID */ +} esp_ble_mesh_health_fault_get_t; + +/** Parameter of Health Attention Set */ +typedef struct { + uint8_t attention; /*!< Value of the Attention Timer state */ +} esp_ble_mesh_health_attention_set_t; + +/** Parameter of Health Period Set */ +typedef struct { + uint8_t fast_period_divisor; /*!< Divider for the Publish Period */ +} esp_ble_mesh_health_period_set_t; + +/** Parameter of Health Fault Test */ +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; + +/** Parameter of Health Fault Clear */ +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; + +/** Parameters of Health Current Status */ +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; + +/** Parameters of Health Fault Status */ +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; + +/** Parameter of Health Period Status */ +typedef struct { + uint8_t fast_period_divisor; /*!< Divider for the Publish Period */ +} esp_ble_mesh_health_period_status_cb_t; + +/** Parameter of Health Attention Status */ +typedef struct { + uint8_t attention; /*!< Value of the Attention Timer state */ +} esp_ble_mesh_health_attention_status_cb_t; + +/** + * @brief Health Client Model received message union + */ +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; + +/** Health Client Model callback parameters */ +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; + +/** This enum value is the event of Health Client Model */ +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; + +/** Parameter of publishing Health Current Status completion event */ +typedef struct { + int error_code; /*!< The result of publishing Health Current Status */ + esp_ble_mesh_elem_t *element; /*!< Pointer to the element which contains the Health Server Model */ +} esp_ble_mesh_health_fault_update_comp_cb_t; + +/** Parameters of Health Fault Clear event */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Health Server Model */ + uint16_t company_id; /*!< Bluetooth assigned 16-bit Company ID */ +} esp_ble_mesh_health_fault_clear_cb_t; + +/** Parameters of Health Fault Test event */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Health Server Model */ + uint8_t test_id; /*!< ID of a specific test to be performed */ + uint16_t company_id; /*!< Bluetooth assigned 16-bit Company ID */ +} esp_ble_mesh_health_fault_test_cb_t; + +/** Parameter of Health Attention On event */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Health Server Model */ + uint8_t time; /*!< Duration of attention timer on (in seconds) */ +} esp_ble_mesh_health_attention_on_cb_t; + +/** Parameter of Health Attention Off event */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Health Server Model */ +} esp_ble_mesh_health_attention_off_cb_t; + +/** + * @brief Health Server Model callback parameters union + */ +typedef union { + esp_ble_mesh_health_fault_update_comp_cb_t fault_update_comp; /*!< ESP_BLE_MESH_HEALTH_SERVER_FAULT_UPDATE_COMP_EVT */ + esp_ble_mesh_health_fault_clear_cb_t fault_clear; /*!< ESP_BLE_MESH_HEALTH_SERVER_FAULT_CLEAR_EVT */ + esp_ble_mesh_health_fault_test_cb_t fault_test; /*!< ESP_BLE_MESH_HEALTH_SERVER_FAULT_TEST_EVT */ + esp_ble_mesh_health_attention_on_cb_t attention_on; /*!< ESP_BLE_MESH_HEALTH_SERVER_ATTENTION_ON_EVT */ + esp_ble_mesh_health_attention_off_cb_t attention_off; /*!< ESP_BLE_MESH_HEALTH_SERVER_ATTENTION_OFF_EVT */ +} esp_ble_mesh_health_server_cb_param_t; + +/** This enum value is the event of Health Server Model */ +typedef enum { + ESP_BLE_MESH_HEALTH_SERVER_FAULT_UPDATE_COMP_EVT, + ESP_BLE_MESH_HEALTH_SERVER_FAULT_CLEAR_EVT, + ESP_BLE_MESH_HEALTH_SERVER_FAULT_TEST_EVT, + ESP_BLE_MESH_HEALTH_SERVER_ATTENTION_ON_EVT, + ESP_BLE_MESH_HEALTH_SERVER_ATTENTION_OFF_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 Health Client Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +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 Health Server Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +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 esp_ble_mesh_opcode_health_client_get_t in esp_ble_mesh_defs.h + * + * @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 esp_ble_mesh_opcode_health_client_set_t in esp_ble_mesh_defs.h + * + * @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 update the context of its Health Current status. + * + * @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/esp_ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h new file mode 100644 index 0000000000..ff08cfcc07 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h @@ -0,0 +1,1675 @@ +// Copyright 2017-2019 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 "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. + */ + +/** Parameters of Light Lightness Set */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t lightness; /*!< Target value of light lightness 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_light_lightness_set_t; + +/** Parameters of Light Lightness Linear Set */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t lightness; /*!< Target value of light lightness linear 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_light_lightness_linear_set_t; + +/** Parameter of Light Lightness Default Set */ +typedef struct { + uint16_t lightness; /*!< The value of the Light Lightness Default state */ +} esp_ble_mesh_light_lightness_default_set_t; + +/** Parameters of Light Lightness Range Set */ +typedef struct { + uint16_t range_min; /*!< Value of range min field of light lightness range state */ + uint16_t range_max; /*!< Value of range max field of light lightness range state */ +} esp_ble_mesh_light_lightness_range_set_t; + +/** Parameters of Light CTL Set */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t ctl_lightness; /*!< Target value of light ctl lightness state */ + uint16_t ctl_temperatrue; /*!< Target value of light ctl temperature state */ + int16_t ctl_delta_uv; /*!< Target value of light ctl delta UV 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_light_ctl_set_t; + +/** Parameters of Light CTL Temperature Set */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t ctl_temperatrue; /*!< Target value of light ctl temperature state */ + int16_t ctl_delta_uv; /*!< Target value of light ctl delta UV 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_light_ctl_temperature_set_t; + +/** Parameters of Light CTL Temperature Range Set */ +typedef struct { + uint16_t range_min; /*!< Value of temperature range min field of light ctl temperature range state */ + uint16_t range_max; /*!< Value of temperature range max field of light ctl temperature range state */ +} esp_ble_mesh_light_ctl_temperature_range_set_t; + +/** Parameters of Light CTL Default Set */ +typedef struct { + uint16_t lightness; /*!< Value of light lightness default state */ + uint16_t temperature; /*!< Value of light temperature default state */ + int16_t delta_uv; /*!< Value of light delta UV default state */ +} esp_ble_mesh_light_ctl_default_set_t; + +/** Parameters of Light HSL Set */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t hsl_lightness; /*!< Target value of light hsl lightness state */ + uint16_t hsl_hue; /*!< Target value of light hsl hue state */ + uint16_t hsl_saturation; /*!< Target value of light hsl saturation 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_light_hsl_set_t; + +/** Parameters of Light HSL Hue Set */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t hue; /*!< Target value of light hsl hue 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_light_hsl_hue_set_t; + +/** Parameters of Light HSL Saturation Set */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t saturation; /*!< Target value of light hsl hue 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_light_hsl_saturation_set_t; + +/** Parameters of Light HSL Default Set */ +typedef struct { + uint16_t lightness; /*!< Value of light lightness default state */ + uint16_t hue; /*!< Value of light hue default state */ + uint16_t saturation; /*!< Value of light saturation default state */ +} esp_ble_mesh_light_hsl_default_set_t; + +/** Parameters of Light HSL Range Set */ +typedef struct { + uint16_t hue_range_min; /*!< Value of hue range min field of light hsl hue range state */ + uint16_t hue_range_max; /*!< Value of hue range max field of light hsl hue range state */ + uint16_t saturation_range_min; /*!< Value of saturation range min field of light hsl saturation range state */ + uint16_t saturation_range_max; /*!< Value of saturation range max field of light hsl saturation range state */ +} esp_ble_mesh_light_hsl_range_set_t; + +/** Parameters of Light xyL Set */ +typedef struct { + bool op_en; /*!< Indicate whether optional parameters included */ + uint16_t xyl_lightness; /*!< The target value of the Light xyL Lightness state */ + uint16_t xyl_x; /*!< The target value of the Light xyL x state */ + uint16_t xyl_y; /*!< The target value of the Light xyL y state */ + uint8_t tid; /*!< Transaction Identifier */ + uint8_t trans_time; /*!< Time to complete state transition (optional) */ + uint8_t delay; /*!< Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_xyl_set_t; + +/** Parameters of Light xyL Default Set */ +typedef struct { + uint16_t lightness; /*!< The value of the Light Lightness Default state */ + uint16_t xyl_x; /*!< The value of the Light xyL x Default state */ + uint16_t xyl_y; /*!< The value of the Light xyL y Default state */ +} esp_ble_mesh_light_xyl_default_set_t; + +/** Parameters of Light xyL Range Set */ +typedef struct { + uint16_t xyl_x_range_min; /*!< The value of the xyL x Range Min field of the Light xyL x Range state */ + uint16_t xyl_x_range_max; /*!< The value of the xyL x Range Max field of the Light xyL x Range state */ + uint16_t xyl_y_range_min; /*!< The value of the xyL y Range Min field of the Light xyL y Range state */ + uint16_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; + +/** Parameter of Light LC Mode Set */ +typedef struct { + uint8_t mode; /*!< The target value of the Light LC Mode state */ +} esp_ble_mesh_light_lc_mode_set_t; + +/** Parameter of Light LC OM Set */ +typedef struct { + uint8_t mode; /*!< The target value of the Light LC Occupancy Mode state */ +} esp_ble_mesh_light_lc_om_set_t; + +/** Parameters of Light LC Light OnOff Set */ +typedef struct { + bool op_en; /*!< Indicate whether optional parameters included */ + uint8_t light_onoff; /*!< The target value of the Light LC Light OnOff state */ + uint8_t tid; /*!< Transaction Identifier */ + uint8_t trans_time; /*!< Time to complete state transition (optional) */ + uint8_t delay; /*!< Indicate message execution delay (C.1) */ +} esp_ble_mesh_light_lc_light_onoff_set_t; + +/** Parameter of Light LC Property Get */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Light LC Property */ +} esp_ble_mesh_light_lc_property_get_t; + +/** Parameters of Light LC Property Set */ +typedef struct { + uint16_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; + +/** + * @brief Lighting Client Model get message union + */ +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; + +/** + * @brief Lighting Client Model set message union + */ +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. + */ + +/** Parameters of Light Lightness Status */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t present_lightness; /*!< Current value of light lightness actual state */ + uint16_t target_lightness; /*!< Target value of light lightness actual state (optional) */ + uint8_t remain_time; /*!< Time to complete state transition (C.1) */ +} esp_ble_mesh_light_lightness_status_cb_t; + +/** Parameters of Light Lightness Linear Status */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t present_lightness; /*!< Current value of light lightness linear state */ + uint16_t target_lightness; /*!< Target value of light lightness linear state (optional) */ + uint8_t remain_time; /*!< Time to complete state transition (C.1) */ +} esp_ble_mesh_light_lightness_linear_status_cb_t; + +/** Parameter of Light Lightness Last Status */ +typedef struct { + uint16_t lightness; /*!< The value of the Light Lightness Last state */ +} esp_ble_mesh_light_lightness_last_status_cb_t; + +/** Parameter of Light Lightness Default Status */ +typedef struct { + uint16_t lightness; /*!< The value of the Light Lightness default State */ +} esp_ble_mesh_light_lightness_default_status_cb_t; + +/** Parameters of Light Lightness Range Status */ +typedef struct { + uint8_t status_code; /*!< Status Code for the request message */ + uint16_t range_min; /*!< Value of range min field of light lightness range state */ + uint16_t range_max; /*!< Value of range max field of light lightness range state */ +} esp_ble_mesh_light_lightness_range_status_cb_t; + +/** Parameters of Light CTL Status */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t present_ctl_lightness; /*!< Current value of light ctl lightness state */ + uint16_t present_ctl_temperature; /*!< Current value of light ctl temperature state */ + uint16_t target_ctl_lightness; /*!< Target value of light ctl lightness state (optional) */ + uint16_t target_ctl_temperature; /*!< Target value of light ctl temperature state (C.1) */ + uint8_t remain_time; /*!< Time to complete state transition (C.1) */ +} esp_ble_mesh_light_ctl_status_cb_t; + +/** Parameters of Light CTL Temperature Status */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t present_ctl_temperature; /*!< Current value of light ctl temperature state */ + uint16_t present_ctl_delta_uv; /*!< Current value of light ctl delta UV state */ + uint16_t target_ctl_temperature; /*!< Target value of light ctl temperature state (optional) */ + uint16_t target_ctl_delta_uv; /*!< Target value of light ctl delta UV state (C.1) */ + uint8_t remain_time; /*!< Time to complete state transition (C.1) */ +} esp_ble_mesh_light_ctl_temperature_status_cb_t; + +/** Parameters of Light CTL Temperature Range Status */ +typedef struct { + uint8_t status_code; /*!< Status code for the request message */ + uint16_t range_min; /*!< Value of temperature range min field of light ctl temperature range state */ + uint16_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; + +/** Parameters of Light CTL Default Status */ +typedef struct { + uint16_t lightness; /*!< Value of light lightness default state */ + uint16_t temperature; /*!< Value of light temperature default state */ + int16_t delta_uv; /*!< Value of light delta UV default state */ +} esp_ble_mesh_light_ctl_default_status_cb_t; + +/** Parameters of Light HSL Status */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t hsl_lightness; /*!< Current value of light hsl lightness state */ + uint16_t hsl_hue; /*!< Current value of light hsl hue state */ + uint16_t hsl_saturation; /*!< Current value of light hsl saturation state */ + uint8_t remain_time; /*!< Time to complete state transition (optional) */ +} esp_ble_mesh_light_hsl_status_cb_t; + +/** Parameters of Light HSL Target Status */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t hsl_lightness_target; /*!< Target value of light hsl lightness state */ + uint16_t hsl_hue_target; /*!< Target value of light hsl hue state */ + uint16_t hsl_saturation_target; /*!< Target value of light hsl saturation state */ + uint8_t remain_time; /*!< Time to complete state transition (optional) */ +} esp_ble_mesh_light_hsl_target_status_cb_t; + +/** Parameters of Light HSL Hue Status */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t present_hue; /*!< Current value of light hsl hue state */ + uint16_t target_hue; /*!< Target value of light hsl hue state (optional) */ + uint8_t remain_time; /*!< Time to complete state transition (C.1) */ +} esp_ble_mesh_light_hsl_hue_status_cb_t; + +/** Parameters of Light HSL Saturation Status */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t present_saturation; /*!< Current value of light hsl saturation state */ + uint16_t target_saturation; /*!< Target value of light hsl saturation state (optional) */ + uint8_t remain_time; /*!< Time to complete state transition (C.1) */ +} esp_ble_mesh_light_hsl_saturation_status_cb_t; + +/** Parameters of Light HSL Default Status */ +typedef struct { + uint16_t lightness; /*!< Value of light lightness default state */ + uint16_t hue; /*!< Value of light hue default state */ + uint16_t saturation; /*!< Value of light saturation default state */ +} esp_ble_mesh_light_hsl_default_status_cb_t; + +/** Parameters of Light HSL Range Status */ +typedef struct { + uint8_t status_code; /*!< Status code for the request message */ + uint16_t hue_range_min; /*!< Value of hue range min field of light hsl hue range state */ + uint16_t hue_range_max; /*!< Value of hue range max field of light hsl hue range state */ + uint16_t saturation_range_min; /*!< Value of saturation range min field of light hsl saturation range state */ + uint16_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; + +/** Parameters of Light xyL Status */ +typedef struct { + bool op_en; /*!< Indicate whether optional parameters included */ + uint16_t xyl_lightness; /*!< The present value of the Light xyL Lightness state */ + uint16_t xyl_x; /*!< The present value of the Light xyL x state */ + uint16_t xyl_y; /*!< The present value of the Light xyL y state */ + uint8_t remain_time; /*!< Time to complete state transition (optional) */ +} esp_ble_mesh_light_xyl_status_cb_t; + +/** Parameters of Light xyL Target Status */ +typedef struct { + bool op_en; /*!< Indicate whether optional parameters included */ + uint16_t target_xyl_lightness; /*!< The target value of the Light xyL Lightness state */ + uint16_t target_xyl_x; /*!< The target value of the Light xyL x state */ + uint16_t target_xyl_y; /*!< The target value of the Light xyL y state */ + uint8_t remain_time; /*!< Time to complete state transition (optional) */ +} esp_ble_mesh_light_xyl_target_status_cb_t; + +/** Parameters of Light xyL Default Status */ +typedef struct { + uint16_t lightness; /*!< The value of the Light Lightness Default state */ + uint16_t xyl_x; /*!< The value of the Light xyL x Default state */ + uint16_t xyl_y; /*!< The value of the Light xyL y Default state */ +} esp_ble_mesh_light_xyl_default_status_cb_t; + +/** Parameters of Light xyL Range Status */ +typedef struct { + uint8_t status_code; /*!< Status Code for the requesting message */ + uint16_t xyl_x_range_min; /*!< The value of the xyL x Range Min field of the Light xyL x Range state */ + uint16_t xyl_x_range_max; /*!< The value of the xyL x Range Max field of the Light xyL x Range state */ + uint16_t xyl_y_range_min; /*!< The value of the xyL y Range Min field of the Light xyL y Range state */ + uint16_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; + +/** Parameter of Light LC Mode Status */ +typedef struct { + uint8_t mode; /*!< The present value of the Light LC Mode state */ +} esp_ble_mesh_light_lc_mode_status_cb_t; + +/** Parameter of Light LC OM Status */ +typedef struct { + uint8_t mode; /*!< The present value of the Light LC Occupancy Mode state */ +} esp_ble_mesh_light_lc_om_status_cb_t; + +/** Parameters of Light LC Light OnOff Status */ +typedef struct { + bool op_en; /*!< Indicate whether optional parameters included */ + uint8_t present_light_onoff; /*!< The present value of the Light LC Light OnOff state */ + uint8_t target_light_onoff; /*!< The target value of the Light LC Light OnOff state (Optional) */ + uint8_t remain_time; /*!< Time to complete state transition (C.1) */ +} esp_ble_mesh_light_lc_light_onoff_status_cb_t; + +/** Parameters of Light LC Property Status */ +typedef struct { + uint16_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; + +/** + * @brief Lighting Client Model received message union + */ +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; + +/** Lighting Client Model callback parameters */ +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; + +/** This enum value is the event of Lighting Client Model */ +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 Lighting Client Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +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 esp_ble_mesh_light_message_opcode_t in esp_ble_mesh_defs.h + * + * @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 esp_ble_mesh_light_message_opcode_t in esp_ble_mesh_defs.h + * + * @param[in] params: Pointer to BLE Mesh common client parameters. + * @param[in] set_state: Pointer of light 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); + +/** + * @brief Lighting Server Models related context. + */ + +/** @def ESP_BLE_MESH_MODEL_LIGHT_LIGHTNESS_SRV + * + * @brief Define a new Light Lightness Server Model. + * + * @note 1. The Light Lightness Server model extends the Generic Power OnOff + * Server model and the Generic Level Server model. When this model + * is present on an Element, the corresponding Light Lightness Setup + * Server model shall also be present. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_lightness_srv_t. + * + * @return New Light Lightness Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_LIGHTNESS_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_LIGHTNESS_SETUP_SRV + * + * @brief Define a new Light Lightness Setup Server Model. + * + * @note 1. The Light Lightness Setup Server model extends the Light Lightness + * Server model and the Generic Power OnOff Setup Server model. + * 2. This model shall support model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_lightness_setup_srv_t. + * + * @return New Light Lightness Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_LIGHTNESS_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_CTL_SRV + * + * @brief Define a new Light CTL Server Model. + * + * @note 1. The Light CTL Server model extends the Light Lightness Server model. + * When this model is present on an Element, the corresponding Light + * CTL Temperature Server model and the corresponding Light CTL Setup + * Server model shall also be present. + * 2. This model shall support model publication and model subscription. + * 3. The model requires two elements: the main element and the Temperature + * element. The Temperature element contains the corresponding Light CTL + * Temperature Server model and an instance of a Generic Level state + * bound to the Light CTL Temperature state on the Temperature element. + * The Light CTL Temperature state on the Temperature element is bound to + * the Light CTL state on the main element. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_ctl_srv_t. + * + * @return New Light CTL Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_CTL_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_CTL_SETUP_SRV + * + * @brief Define a new Light CTL Setup Server Model. + * + * @note 1. The Light CTL Setup Server model extends the Light CTL Server and + * the Light Lightness Setup Server. + * 2. This model shall support model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_ctl_setup_srv_t. + * + * @return New Light CTL Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_CTL_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_CTL_TEMP_SRV + * + * @brief Define a new Light CTL Temperature Server Model. + * + * @note 1. The Light CTL Temperature Server model extends the Generic Level + * Server model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_ctl_temp_srv_t. + * + * @return New Light CTL Temperature Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_CTL_TEMP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_HSL_SRV + * + * @brief Define a new Light HSL Server Model. + * + * @note 1. The Light HSL Server model extends the Light Lightness Server model. When + * this model is present on an Element, the corresponding Light HSL Hue + * Server model and the corresponding Light HSL Saturation Server model and + * the corresponding Light HSL Setup Server model shall also be present. + * 2. This model shall support model publication and model subscription. + * 3. The model requires three elements: the main element and the Hue element + * and the Saturation element. The Hue element contains the corresponding + * Light HSL Hue Server model and an instance of a Generic Level state bound + * to the Light HSL Hue state on the Hue element. The Saturation element + * contains the corresponding Light HSL Saturation Server model and an + * instance of a Generic Level state bound to the Light HSL Saturation state + * on the Saturation element. The Light HSL Hue state on the Hue element is + * bound to the Light HSL state on the main element and the Light HSL + * Saturation state on the Saturation element is bound to the Light HSL state + * on the main element. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_hsl_srv_t. + * + * @return New Light HSL Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_HSL_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_HSL_SETUP_SRV + * + * @brief Define a new Light HSL Setup Server Model. + * + * @note 1. The Light HSL Setup Server model extends the Light HSL Server and + * the Light Lightness Setup Server. + * 2. This model shall support model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_hsl_setup_srv_t. + * + * @return New Light HSL Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_HSL_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_HSL_HUE_SRV + * + * @brief Define a new Light HSL Hue Server Model. + * + * @note 1. The Light HSL Hue Server model extends the Generic Level Server model. + * This model is associated with the Light HSL Server model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_hsl_hue_srv_t. + * + * @return New Light HSL Hue Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_HSL_HUE_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_HSL_SAT_SRV + * + * @brief Define a new Light HSL Saturation Server Model. + * + * @note 1. The Light HSL Saturation Server model extends the Generic Level Server + * model. This model is associated with the Light HSL Server model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_hsl_sat_srv_t. + * + * @return New Light HSL Saturation Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_HSL_SAT_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_XYL_SRV + * + * @brief Define a new Light xyL Server Model. + * + * @note 1. The Light xyL Server model extends the Light Lightness Server model. + * When this model is present on an Element, the corresponding Light xyL + * Setup Server model shall also be present. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_xyl_srv_t. + * + * @return New Light xyL Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_XYL_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_XYL_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_XYL_SETUP_SRV + * + * @brief Define a new Light xyL Setup Server Model. + * + * @note 1. The Light xyL Setup Server model extends the Light xyL Server and + * the Light Lightness Setup Server. + * 2. This model shall support model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_xyl_setup_srv_t. + * + * @return New Light xyL Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_XYL_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_LC_SRV + * + * @brief Define a new Light LC Server Model. + * + * @note 1. The Light LC (Lightness Control) Server model extends the Light + * Lightness Server model and the Generic OnOff Server model. When + * this model is present on an Element, the corresponding Light LC + * Setup Server model shall also be present. + * 2. This model shall support model publication and model subscription. + * 3. This model may be used to represent an element that is a client to + * a Sensor Server model and controls the Light Lightness Actual state + * via defined state bindings. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_lc_srv_t. + * + * @return New Light LC Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_LC_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_LC_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_LIGHT_LC_SETUP_SRV + * + * @brief Define a new Light LC Setup Server Model. + * + * @note 1. The Light LC (Lightness Control) Setup model extends the Light LC + * Server model. + * 2. This model shall support model publication and model subscription. + * 3. This model may be used to configure setup parameters for the Light + * LC Server model. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_light_lc_setup_srv_t. + * + * @return New Light LC Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_LIGHT_LC_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_LIGHT_LC_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +/** Parameters of Light Lightness state */ +typedef struct { + uint16_t lightness_linear; /*!< The present value of Light Lightness Linear state */ + uint16_t target_lightness_linear; /*!< The target value of Light Lightness Linear state */ + + uint16_t lightness_actual; /*!< The present value of Light Lightness Actual state */ + uint16_t target_lightness_actual; /*!< The target value of Light Lightness Actual state */ + + uint16_t lightness_last; /*!< The value of Light Lightness Last state */ + uint16_t lightness_default; /*!< The value of Light Lightness Default state */ + + uint8_t status_code; /*!< The status code of setting Light Lightness Range state */ + uint16_t lightness_range_min; /*!< The minimum value of Light Lightness Range state */ + uint16_t lightness_range_max; /*!< The maximum value of Light Lightness Range state */ +} esp_ble_mesh_light_lightness_state_t; + +/** User data of Light Lightness Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting Lightness Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_lightness_state_t *state; /*!< Parameters of the Light Lightness state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t actual_transition; /*!< Parameters of state transition */ + esp_ble_mesh_state_transition_t linear_transition; /*!< Parameters of state transition */ + int32_t tt_delta_lightness_actual; /*!< Delta change value of lightness actual state transition */ + int32_t tt_delta_lightness_linear; /*!< Delta change value of lightness linear state transition */ +} esp_ble_mesh_light_lightness_srv_t; + +/** User data of Light Lightness Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting Lightness Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_lightness_state_t *state; /*!< Parameters of the Light Lightness state */ +} esp_ble_mesh_light_lightness_setup_srv_t; + +/** Parameters of Light CTL state */ +typedef struct { + uint16_t lightness; /*!< The present value of Light CTL Lightness state */ + uint16_t target_lightness; /*!< The target value of Light CTL Lightness state */ + + uint16_t temperature; /*!< The present value of Light CTL Temperature state */ + uint16_t target_temperature; /*!< The target value of Light CTL Temperature state */ + + int16_t delta_uv; /*!< The present value of Light CTL Delta UV state */ + int16_t target_delta_uv; /*!< The target value of Light CTL Delta UV state */ + + uint8_t status_code; /*!< The statue code of setting Light CTL Temperature Range state */ + uint16_t temperature_range_min; /*!< The minimum value of Light CTL Temperature Range state */ + uint16_t temperature_range_max; /*!< The maximum value of Light CTL Temperature Range state */ + + uint16_t lightness_default; /*!< The value of Light Lightness Default state */ + uint16_t temperature_default; /*!< The value of Light CTL Temperature Default state */ + int16_t delta_uv_default; /*!< The value of Light CTL Delta UV Default state */ +} esp_ble_mesh_light_ctl_state_t; + +/** User data of Light CTL Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting CTL Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_ctl_state_t *state; /*!< Parameters of the Light CTL state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ + int32_t tt_delta_lightness; /*!< Delta change value of lightness state transition */ + int32_t tt_delta_temperature; /*!< Delta change value of temperature state transition */ + int32_t tt_delta_delta_uv; /*!< Delta change value of delta uv state transition */ +} esp_ble_mesh_light_ctl_srv_t; + +/** User data of Light CTL Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting CTL Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_ctl_state_t *state; /*!< Parameters of the Light CTL state */ +} esp_ble_mesh_light_ctl_setup_srv_t; + +/** User data of Light CTL Temperature Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting CTL Temperature Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_ctl_state_t *state; /*!< Parameters of the Light CTL state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ + int32_t tt_delta_temperature; /*!< Delta change value of temperature state transition */ + int32_t tt_delta_delta_uv; /*!< Delta change value of delta uv state transition */ +} esp_ble_mesh_light_ctl_temp_srv_t; + +/** Parameters of Light HSL state */ +typedef struct { + uint16_t lightness; /*!< The present value of Light HSL Lightness state */ + uint16_t target_lightness; /*!< The target value of Light HSL Lightness state */ + + uint16_t hue; /*!< The present value of Light HSL Hue state */ + uint16_t target_hue; /*!< The target value of Light HSL Hue state */ + + uint16_t saturation; /*!< The present value of Light HSL Saturation state */ + uint16_t target_saturation; /*!< The target value of Light HSL Saturation state */ + + uint16_t lightness_default; /*!< The value of Light Lightness Default state */ + uint16_t hue_default; /*!< The value of Light HSL Hue Default state */ + uint16_t saturation_default; /*!< The value of Light HSL Saturation Default state */ + + uint8_t status_code; /*!< The status code of setting Light HSL Hue & Saturation Range state */ + uint16_t hue_range_min; /*!< The minimum value of Light HSL Hue Range state */ + uint16_t hue_range_max; /*!< The maximum value of Light HSL Hue Range state */ + uint16_t saturation_range_min; /*!< The minimum value of Light HSL Saturation state */ + uint16_t saturation_range_max; /*!< The maximum value of Light HSL Saturation state */ +} esp_ble_mesh_light_hsl_state_t; + +/** User data of Light HSL Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting HSL Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_hsl_state_t *state; /*!< Parameters of the Light HSL state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ + int32_t tt_delta_lightness; /*!< Delta change value of lightness state transition */ + int32_t tt_delta_hue; /*!< Delta change value of hue state transition */ + int32_t tt_delta_saturation; /*!< Delta change value of saturation state transition */ +} esp_ble_mesh_light_hsl_srv_t; + +/** User data of Light HSL Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting HSL Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_hsl_state_t *state; /*!< Parameters of the Light HSL state */ +} esp_ble_mesh_light_hsl_setup_srv_t; + +/** User data of Light HSL Hue Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting HSL Hue Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_hsl_state_t *state; /*!< Parameters of the Light HSL state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ + int32_t tt_delta_hue; /*!< Delta change value of hue state transition */ +} esp_ble_mesh_light_hsl_hue_srv_t; + +/** User data of Light HSL Saturation Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting HSL Saturation Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_hsl_state_t *state; /*!< Parameters of the Light HSL state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ + int32_t tt_delta_saturation; /*!< Delta change value of saturation state transition */ +} esp_ble_mesh_light_hsl_sat_srv_t; + +/** Parameters of Light xyL state */ +typedef struct { + uint16_t lightness; /*!< The present value of Light xyL Lightness state */ + uint16_t target_lightness; /*!< The target value of Light xyL Lightness state */ + + uint16_t x; /*!< The present value of Light xyL x state */ + uint16_t target_x; /*!< The target value of Light xyL x state */ + + uint16_t y; /*!< The present value of Light xyL y state */ + uint16_t target_y; /*!< The target value of Light xyL y state */ + + uint16_t lightness_default; /*!< The value of Light Lightness Default state */ + uint16_t x_default; /*!< The value of Light xyL x Default state */ + uint16_t y_default; /*!< The value of Light xyL y Default state */ + + uint8_t status_code; /*!< The status code of setting Light xyL x & y Range state */ + uint16_t x_range_min; /*!< The minimum value of Light xyL x Range state */ + uint16_t x_range_max; /*!< The maximum value of Light xyL x Range state */ + uint16_t y_range_min; /*!< The minimum value of Light xyL y Range state */ + uint16_t y_range_max; /*!< The maximum value of Light xyL y Range state */ +} esp_ble_mesh_light_xyl_state_t; + +/** User data of Light xyL Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting xyL Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_xyl_state_t *state; /*!< Parameters of the Light xyL state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ + int32_t tt_delta_lightness; /*!< Delta change value of lightness state transition */ + int32_t tt_delta_x; /*!< Delta change value of x state transition */ + int32_t tt_delta_y; /*!< Delta change value of y state transition */ +} esp_ble_mesh_light_xyl_srv_t; + +/** User data of Light xyL Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting xyL Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_xyl_state_t *state; /*!< Parameters of the Light xyL state */ +} esp_ble_mesh_light_xyl_setup_srv_t; + +/** Parameters of Light LC states */ +typedef struct { + /** + * 0b0 The controller is turned off. + * - The binding with the Light Lightness state is disabled. + * 0b1 The controller is turned on. + * - The binding with the Light Lightness state is enabled. + */ + uint32_t mode : 1, /*!< The value of Light LC Mode state */ + occupancy_mode : 1, /*!< The value of Light LC Occupancy Mode state */ + light_onoff : 1, /*!< The present value of Light LC Light OnOff state */ + target_light_onoff : 1, /*!< The target value of Light LC Light OnOff state */ + occupancy : 1, /*!< The value of Light LC Occupancy state */ + ambient_luxlevel : 24; /*!< The value of Light LC Ambient LuxLevel state */ + + /** + * 1. Light LC Linear Output = max((Lightness Out)^2/65535, Regulator Output) + * 2. If the Light LC Mode state is set to 0b1, the binding is enabled and upon + * a change of the Light LC Linear Output state, the following operation + * shall be performed: + * Light Lightness Linear = Light LC Linear Output + * 3. If the Light LC Mode state is set to 0b0, the binding is disabled (i.e., + * upon a change of the Light LC Linear Output state, no operation on the + * Light Lightness Linear state is performed). + */ + uint16_t linear_output; /*!< The value of Light LC Linear Output state */ +} esp_ble_mesh_light_lc_state_t; + +/** + * Parameters of Light Property states. + * The Light LC Property states are read / write states that determine the + * configuration of a Light Lightness Controller. Each state is represented + * by a device property and is controlled by Light LC Property messages. + */ +typedef struct { + /** + * A timing state that determines the delay for changing the Light LC + * Occupancy state upon receiving a Sensor Status message from an + * occupancy sensor. + */ + uint32_t time_occupancy_delay; /*!< The value of Light LC Time Occupany Delay state */ + /** + * A timing state that determines the time the controlled lights fade + * to the level determined by the Light LC Lightness On state. + */ + uint32_t time_fade_on; /*!< The value of Light LC Time Fade On state */ + /** + * A timing state that determines the time the controlled lights stay + * at the level determined by the Light LC Lightness On state. + */ + uint32_t time_run_on; /*!< The value of Light LC Time Run On state */ + /** + * A timing state that determines the time the controlled lights fade + * from the level determined by the Light LC Lightness On state to the + * level determined by the Light Lightness Prolong state. + */ + uint32_t time_fade; /*!< The value of Light LC Time Fade state */ + /** + * A timing state that determines the time the controlled lights stay at + * the level determined by the Light LC Lightness Prolong state. + */ + uint32_t time_prolong; /*!< The value of Light LC Time Prolong state */ + /** + * A timing state that determines the time the controlled lights fade from + * the level determined by the Light LC Lightness Prolong state to the level + * determined by the Light LC Lightness Standby state when the transition is + * automatic. + */ + uint32_t time_fade_standby_auto; /*!< The value of Light LC Time Fade Standby Auto state */ + /** + * A timing state that determines the time the controlled lights fade from + * the level determined by the Light LC Lightness Prolong state to the level + * determined by the Light LC Lightness Standby state when the transition is + * triggered by a change in the Light LC Light OnOff state. + */ + uint32_t time_fade_standby_manual; /*!< The value of Light LC Time Fade Standby Manual state */ + + /** + * A lightness state that determines the perceptive light lightness at the + * Occupancy and Run internal controller states. + */ + uint16_t lightness_on; /*!< The value of Light LC Lightness On state */ + /** + * A lightness state that determines the light lightness at the Prolong + * internal controller state. + */ + uint16_t lightness_prolong; /*!< The value of Light LC Lightness Prolong state */ + /** + * A lightness state that determines the light lightness at the Standby + * internal controller state. + */ + uint16_t lightness_standby; /*!< The value of Light LC Lightness Standby state */ + + /** + * A uint16 state representing the Ambient LuxLevel level that determines + * if the controller transitions from the Light Control Standby state. + */ + uint16_t ambient_luxlevel_on; /*!< The value of Light LC Ambient LuxLevel On state */ + /** + * A uint16 state representing the required Ambient LuxLevel level in the + * Prolong state. + */ + uint16_t ambient_luxlevel_prolong; /*!< The value of Light LC Ambient LuxLevel Prolong state */ + /** + * A uint16 state representing the required Ambient LuxLevel level in the + * Standby state. + */ + uint16_t ambient_luxlevel_standby; /*!< The value of Light LC Ambient LuxLevel Standby state */ + + /** + * A float32 state representing the integral coefficient that determines the + * integral part of the equation defining the output of the Light LC PI + * Feedback Regulator, when Light LC Ambient LuxLevel is less than LuxLevel + * Out. Valid range: 0.0 ~ 1000.0. The default value is 250.0. + */ + float regulator_kiu; /*!< The value of Light LC Regulator Kiu state */ + /** + * A float32 state representing the integral coefficient that determines the + * integral part of the equation defining the output of the Light LC PI + * Feedback Regulator, when Light LC Ambient LuxLevel is greater than or equal + * to the value of the LuxLevel Out state. Valid range: 0.0 ~ 1000.0. The + * default value is 25.0. + */ + float regulator_kid; /*!< The value of Light LC Regulator Kid state */ + /** + * A float32 state representing the proportional coefficient that determines + * the proportional part of the equation defining the output of the Light LC + * PI Feedback Regulator, when Light LC Ambient LuxLevel is less than the value + * of the LuxLevel Out state. Valid range: 0.0 ~ 1000.0. The default value is 80.0. + */ + float regulator_kpu; /*!< The value of Light LC Regulator Kpu state */ + /** + * A float32 state representing the proportional coefficient that determines + * the proportional part of the equation defining the output of the Light LC PI + * Feedback Regulator, when Light LC Ambient LuxLevel is greater than or equal + * to the value of the LuxLevel Out state. Valid range: 0.0 ~ 1000.0. The default + * value is 80.0. + */ + float regulator_kpd; /*!< The value of Light LC Regulator Kpd state */ + /** + * A int8 state representing the percentage accuracy of the Light LC PI Feedback + * Regulator. Valid range: 0.0 ~ 100.0. The default value is 2.0. + */ + int8_t regulator_accuracy; /*!< The value of Light LC Regulator Accuracy state */ + + /** + * If the message Raw field contains a Raw Value for the Time Since Motion + * Sensed device property, which represents a value less than or equal to + * the value of the Light LC Occupancy Delay state, it shall delay setting + * the Light LC Occupancy state to 0b1 by the difference between the value + * of the Light LC Occupancy Delay state and the received Time Since Motion + * value. + */ + uint32_t set_occupancy_to_1_delay; /*!< The value of the difference between value of the + Light LC Occupancy Delay state and the received + Time Since Motion value */ +} esp_ble_mesh_light_lc_property_state_t; + +/** This enum value is the Light LC State Machine states */ +typedef enum { + ESP_BLE_MESH_LC_OFF, + ESP_BLE_MESH_LC_STANDBY, + ESP_BLE_MESH_LC_FADE_ON, + ESP_BLE_MESH_LC_RUN, + ESP_BLE_MESH_LC_FADE, + ESP_BLE_MESH_LC_PROLONG, + ESP_BLE_MESH_LC_FADE_STANDBY_AUTO, + ESP_BLE_MESH_LC_FADE_STANDBY_MANUAL, +} esp_ble_mesh_lc_state_t; + +/** Parameters of Light LC state machine */ +typedef struct { + /** + * The Fade On, Fade, Fade Standby Auto, and Fade Standby Manual states are + * transition states that define the transition of the Lightness Out and + * LuxLevel Out states. This transition can be started as a result of the + * Light LC State Machine change or as a result of receiving the Light LC + * Light OnOff Set or Light LC Light Set Unacknowledged message. + */ + struct { + uint8_t fade_on; /*!< The value of transition time of Light LC Time Fade On */ + uint8_t fade; /*!< The value of transition time of Light LC Time Fade */ + uint8_t fade_standby_auto; /*!< The value of transition time of Light LC Time Fade Standby Auto */ + uint8_t fade_standby_manual; /*!< The value of transition time of Light LC Time Fade Standby Manual */ + } trans_time; /*!< The value of transition time */ + esp_ble_mesh_lc_state_t state; /*!< The value of Light LC state machine state */ + struct k_delayed_work timer; /*!< Timer of Light LC state machine */ +} esp_ble_mesh_light_lc_state_machine_t; + +/** Parameters of Light Lightness controller */ +typedef struct { + esp_ble_mesh_light_lc_state_t state; /*!< Parameters of Light LC state */ + esp_ble_mesh_light_lc_property_state_t prop_state; /*!< Parameters of Light LC Property state */ + esp_ble_mesh_light_lc_state_machine_t state_machine; /*!< Parameters of Light LC state machine */ +} esp_ble_mesh_light_control_t; + +/** User data of Light LC Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting LC Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_control_t *lc; /*!< Parameters of the Light controller */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ +} esp_ble_mesh_light_lc_srv_t; + +/** User data of Light LC Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Lighting LC Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_light_control_t *lc; /*!< Parameters of the Light controller */ +} esp_ble_mesh_light_lc_setup_srv_t; + +/** Parameter of Light Lightness Actual state change event */ +typedef struct { + uint16_t lightness; /*!< The value of Light Lightness Actual state */ +} esp_ble_mesh_state_change_light_lightness_set_t; + +/** Parameter of Light Lightness Linear state change event */ +typedef struct { + uint16_t lightness; /*!< The value of Light Lightness Linear state */ +} esp_ble_mesh_state_change_light_lightness_linear_set_t; + +/** Parameter of Light Lightness Default state change event */ +typedef struct { + uint16_t lightness; /*!< The value of Light Lightness Default state */ +} esp_ble_mesh_state_change_light_lightness_default_set_t; + +/** Parameters of Light Lightness Range state change event */ +typedef struct { + uint16_t range_min; /*!< The minimum value of Light Lightness Range state */ + uint16_t range_max; /*!< The maximum value of Light Lightness Range state */ +} esp_ble_mesh_state_change_light_lightness_range_set_t; + +/** Parameters of Light CTL state change event */ +typedef struct { + uint16_t lightness; /*!< The value of Light CTL Lightness state */ + uint16_t temperature; /*!< The value of Light CTL Temperature state */ + int16_t delta_uv; /*!< The value of Light CTL Delta UV state */ +} esp_ble_mesh_state_change_light_ctl_set_t; + +/** Parameters of Light CTL Temperature state change event */ +typedef struct { + uint16_t temperature; /*!< The value of Light CTL Temperature state */ + int16_t delta_uv; /*!< The value of Light CTL Delta UV state */ +} esp_ble_mesh_state_change_light_ctl_temperature_set_t; + +/** Parameters of Light CTL Temperature Range state change event */ +typedef struct { + uint16_t range_min; /*!< The minimum value of Light CTL Temperature Range state */ + uint16_t range_max; /*!< The maximum value of Light CTL Temperature Range state */ +} esp_ble_mesh_state_change_light_ctl_temperature_range_set_t; + +/** Parameters of Light CTL Default state change event */ +typedef struct { + uint16_t lightness; /*!< The value of Light Lightness Default state */ + uint16_t temperature; /*!< The value of Light CTL Temperature Default state */ + int16_t delta_uv; /*!< The value of Light CTL Delta UV Default state */ +} esp_ble_mesh_state_change_light_ctl_default_set_t; + +/** Parameters of Light HSL state change event */ +typedef struct { + uint16_t lightness; /*!< The value of Light HSL Lightness state */ + uint16_t hue; /*!< The value of Light HSL Hue state */ + uint16_t saturation; /*!< The value of Light HSL Saturation state */ +} esp_ble_mesh_state_change_light_hsl_set_t; + +/** Parameter of Light HSL Hue state change event */ +typedef struct { + uint16_t hue; /*!< The value of Light HSL Hue state */ +} esp_ble_mesh_state_change_light_hsl_hue_set_t; + +/** Parameter of Light HSL Saturation state change event */ +typedef struct { + uint16_t saturation; /*!< The value of Light HSL Saturation state */ +} esp_ble_mesh_state_change_light_hsl_saturation_set_t; + +/** Parameters of Light HSL Default state change event */ +typedef struct { + uint16_t lightness; /*!< The value of Light HSL Lightness Default state */ + uint16_t hue; /*!< The value of Light HSL Hue Default state */ + uint16_t saturation; /*!< The value of Light HSL Saturation Default state */ +} esp_ble_mesh_state_change_light_hsl_default_set_t; + +/** Parameters of Light HSL Range state change event */ +typedef struct { + uint16_t hue_range_min; /*!< The minimum hue value of Light HSL Range state */ + uint16_t hue_range_max; /*!< The maximum hue value of Light HSL Range state */ + uint16_t saturation_range_min; /*!< The minimum saturation value of Light HSL Range state */ + uint16_t saturation_range_max; /*!< The maximum saturation value of Light HSL Range state */ +} esp_ble_mesh_state_change_light_hsl_range_set_t; + +/** Parameters of Light xyL state change event */ +typedef struct { + uint16_t lightness; /*!< The value of Light xyL Lightness state */ + uint16_t x; /*!< The value of Light xyL x state */ + uint16_t y; /*!< The value of Light xyL y state */ +} esp_ble_mesh_state_change_light_xyl_set_t; + +/** Parameters of Light xyL Default state change event */ +typedef struct { + uint16_t lightness; /*!< The value of Light Lightness Default state */ + uint16_t x; /*!< The value of Light xyL x Default state */ + uint16_t y; /*!< The value of Light xyL y Default state */ +} esp_ble_mesh_state_change_light_xyl_default_set_t; + +/** Parameters of Light xyL Range state change event */ +typedef struct { + uint16_t x_range_min; /*!< The minimum value of Light xyL x Range state */ + uint16_t x_range_max; /*!< The maximum value of Light xyL x Range state */ + uint16_t y_range_min; /*!< The minimum value of Light xyL y Range state */ + uint16_t y_range_max; /*!< The maximum value of Light xyL y Range state */ +} esp_ble_mesh_state_change_light_xyl_range_set_t; + +/** Parameter of Light LC Mode state change event */ +typedef struct { + uint8_t mode; /*!< The value of Light LC Mode state */ +} esp_ble_mesh_state_change_light_lc_mode_set_t; + +/** Parameter of Light LC Occupancy Mode state change event */ +typedef struct { + uint8_t mode; /*!< The value of Light LC Occupany Mode state */ +} esp_ble_mesh_state_change_light_lc_om_set_t; + +/** Parameter of Light LC Light OnOff state change event */ +typedef struct { + uint8_t onoff; /*!< The value of Light LC Light OnOff state */ +} esp_ble_mesh_state_change_light_lc_light_onoff_set_t; + +/** Parameters of Light LC Property state change event */ +typedef struct { + uint16_t property_id; /*!< The property id of Light LC Property state */ + struct net_buf_simple *property_value; /*!< The property value of Light LC Property state */ +} esp_ble_mesh_state_change_light_lc_property_set_t; + +/** Parameters of Sensor Status state change event */ +typedef struct { + uint16_t property_id; /*!< The value of Sensor Property ID */ + /** Parameters of Sensor Status related state */ + union { + uint8_t occupancy; /*!< The value of Light LC Occupancy state */ + uint32_t set_occupancy_to_1_delay; /*!< The value of Light LC Set Occupancy to 1 Delay state */ + uint32_t ambient_luxlevel; /*!< The value of Light LC Ambient Luxlevel state */ + } state; +} esp_ble_mesh_state_change_sensor_status_t; + +/** + * @brief Lighting Server Model state change value union + */ +typedef union { + /** + * The recv_op in ctx can be used to decide which state is changed. + */ + esp_ble_mesh_state_change_light_lightness_set_t lightness_set; /*!< Light Lightness Set */ + esp_ble_mesh_state_change_light_lightness_linear_set_t lightness_linear_set; /*!< Light Lightness Linear Set */ + esp_ble_mesh_state_change_light_lightness_default_set_t lightness_default_set; /*!< Light Lightness Default Set */ + esp_ble_mesh_state_change_light_lightness_range_set_t lightness_range_set; /*!< Light Lightness Range Set */ + esp_ble_mesh_state_change_light_ctl_set_t ctl_set; /*!< Light CTL Set */ + esp_ble_mesh_state_change_light_ctl_temperature_set_t ctl_temp_set; /*!< Light CTL Temperature Set */ + esp_ble_mesh_state_change_light_ctl_temperature_range_set_t ctl_temp_range_set; /*!< Light CTL Temperature Range Set */ + esp_ble_mesh_state_change_light_ctl_default_set_t ctl_default_set; /*!< Light CTL Default Set */ + esp_ble_mesh_state_change_light_hsl_set_t hsl_set; /*!< Light HSL Set */ + esp_ble_mesh_state_change_light_hsl_hue_set_t hsl_hue_set; /*!< Light HSL Hue Set */ + esp_ble_mesh_state_change_light_hsl_saturation_set_t hsl_saturation_set; /*!< Light HSL Saturation Set */ + esp_ble_mesh_state_change_light_hsl_default_set_t hsl_default_set; /*!< Light HSL Default Set */ + esp_ble_mesh_state_change_light_hsl_range_set_t hsl_range_set; /*!< Light HSL Range Set */ + esp_ble_mesh_state_change_light_xyl_set_t xyl_set; /*!< Light xyL Set */ + esp_ble_mesh_state_change_light_xyl_default_set_t xyl_default_set; /*!< Light xyL Default Set */ + esp_ble_mesh_state_change_light_xyl_range_set_t xyl_range_set; /*!< Light xyL Range Set */ + esp_ble_mesh_state_change_light_lc_mode_set_t lc_mode_set; /*!< Light LC Mode Set */ + esp_ble_mesh_state_change_light_lc_om_set_t lc_om_set; /*!< Light LC Occupancy Mode Set */ + esp_ble_mesh_state_change_light_lc_light_onoff_set_t lc_light_onoff_set; /*!< Light LC Light OnOff Set */ + esp_ble_mesh_state_change_light_lc_property_set_t lc_property_set; /*!< Light LC Property Set */ + esp_ble_mesh_state_change_sensor_status_t sensor_status; /*!< Sensor Status */ +} esp_ble_mesh_lighting_server_state_change_t; + +/** Context of the received Light LC Property Get message */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a Light LC Property */ +} esp_ble_mesh_server_recv_light_lc_property_get_t; + +/** + * @brief Lighting Server Model received get message union + */ +typedef union { + esp_ble_mesh_server_recv_light_lc_property_get_t lc_property; /*!< Light LC Property Get */ +} esp_ble_mesh_lighting_server_recv_get_msg_t; + +/** Context of the received Light Lightness Set message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t lightness; /*!< Target value of light lightness 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_server_recv_light_lightness_set_t; + +/** Context of the received Light Lightness Linear Set message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t lightness; /*!< Target value of light lightness linear 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_server_recv_light_lightness_linear_set_t; + +/** Context of the received Light Lightness Default Set message */ +typedef struct { + uint16_t lightness; /*!< The value of the Light Lightness Default state */ +} esp_ble_mesh_server_recv_light_lightness_default_set_t; + +/** Context of the received Light Lightness Range Set message */ +typedef struct { + uint16_t range_min; /*!< Value of range min field of light lightness range state */ + uint16_t range_max; /*!< Value of range max field of light lightness range state */ +} esp_ble_mesh_server_recv_light_lightness_range_set_t; + +/** Context of the received Light CTL Set message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t lightness; /*!< Target value of light ctl lightness state */ + uint16_t temperature; /*!< Target value of light ctl temperature state */ + int16_t delta_uv; /*!< Target value of light ctl delta UV 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_server_recv_light_ctl_set_t; + +/** Context of the received Light CTL Temperature Set message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t temperature; /*!< Target value of light ctl temperature state */ + int16_t delta_uv; /*!< Target value of light ctl delta UV 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_server_recv_light_ctl_temperature_set_t; + +/** Context of the received Light CTL Temperature Range Set message */ +typedef struct { + uint16_t range_min; /*!< Value of temperature range min field of light ctl temperature range state */ + uint16_t range_max; /*!< Value of temperature range max field of light ctl temperature range state */ +} esp_ble_mesh_server_recv_light_ctl_temperature_range_set_t; + +/** Context of the received Light CTL Default Set message */ +typedef struct { + uint16_t lightness; /*!< Value of light lightness default state */ + uint16_t temperature; /*!< Value of light temperature default state */ + int16_t delta_uv; /*!< Value of light delta UV default state */ +} esp_ble_mesh_server_recv_light_ctl_default_set_t; + +/** Context of the received Light HSL Set message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t lightness; /*!< Target value of light hsl lightness state */ + uint16_t hue; /*!< Target value of light hsl hue state */ + uint16_t saturation; /*!< Target value of light hsl saturation 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_server_recv_light_hsl_set_t; + +/** Context of the received Light HSL Hue Set message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t hue; /*!< Target value of light hsl hue 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_server_recv_light_hsl_hue_set_t; + +/** Context of the received Light HSL Saturation Set message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t saturation; /*!< Target value of light hsl hue 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_server_recv_light_hsl_saturation_set_t; + +/** Context of the received Light HSL Default Set message */ +typedef struct { + uint16_t lightness; /*!< Value of light lightness default state */ + uint16_t hue; /*!< Value of light hue default state */ + uint16_t saturation; /*!< Value of light saturation default state */ +} esp_ble_mesh_server_recv_light_hsl_default_set_t; + +/** Context of the received Light HSL Range Set message */ +typedef struct { + uint16_t hue_range_min; /*!< Value of hue range min field of light hsl hue range state */ + uint16_t hue_range_max; /*!< Value of hue range max field of light hsl hue range state */ + uint16_t saturation_range_min; /*!< Value of saturation range min field of light hsl saturation range state */ + uint16_t saturation_range_max; /*!< Value of saturation range max field of light hsl saturation range state */ +} esp_ble_mesh_server_recv_light_hsl_range_set_t; + +/** Context of the received Light xyL Set message */ +typedef struct { + bool op_en; /*!< Indicate whether optional parameters included */ + uint16_t lightness; /*!< The target value of the Light xyL Lightness state */ + uint16_t x; /*!< The target value of the Light xyL x state */ + uint16_t y; /*!< The target value of the Light xyL y state */ + uint8_t tid; /*!< Transaction Identifier */ + uint8_t trans_time; /*!< Time to complete state transition (optional) */ + uint8_t delay; /*!< Indicate message execution delay (C.1) */ +} esp_ble_mesh_server_recv_light_xyl_set_t; + +/** Context of the received Light xyL Default Set message */ +typedef struct { + uint16_t lightness; /*!< The value of the Light Lightness Default state */ + uint16_t x; /*!< The value of the Light xyL x Default state */ + uint16_t y; /*!< The value of the Light xyL y Default state */ +} esp_ble_mesh_server_recv_light_xyl_default_set_t; + +/** Context of the received Light xyl Range Set message */ +typedef struct { + uint16_t x_range_min; /*!< The value of the xyL x Range Min field of the Light xyL x Range state */ + uint16_t x_range_max; /*!< The value of the xyL x Range Max field of the Light xyL x Range state */ + uint16_t y_range_min; /*!< The value of the xyL y Range Min field of the Light xyL y Range state */ + uint16_t y_range_max; /*!< The value of the xyL y Range Max field of the Light xyL y Range state */ +} esp_ble_mesh_server_recv_light_xyl_range_set_t; + +/** Context of the received Light LC Mode Set message */ +typedef struct { + uint8_t mode; /*!< The target value of the Light LC Mode state */ +} esp_ble_mesh_server_recv_light_lc_mode_set_t; + +/** Context of the received Light OM Set message */ +typedef struct { + uint8_t mode; /*!< The target value of the Light LC Occupancy Mode state */ +} esp_ble_mesh_server_recv_light_lc_om_set_t; + +/** Context of the received Light LC Light OnOff Set message */ +typedef struct { + bool op_en; /*!< Indicate whether optional parameters included */ + uint8_t light_onoff; /*!< The target value of the Light LC Light OnOff state */ + uint8_t tid; /*!< Transaction Identifier */ + uint8_t trans_time; /*!< Time to complete state transition (optional) */ + uint8_t delay; /*!< Indicate message execution delay (C.1) */ +} esp_ble_mesh_server_recv_light_lc_light_onoff_set_t; + +/** Context of the received Light LC Property Set message */ +typedef struct { + uint16_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_server_recv_light_lc_property_set_t; + +/** + * @brief Lighting Server Model received set message union + */ +typedef union { + esp_ble_mesh_server_recv_light_lightness_set_t lightness; /*!< Light Lightness Set/Light Lightness Set Unack */ + esp_ble_mesh_server_recv_light_lightness_linear_set_t lightness_linear; /*!< Light Lightness Linear Set/Light Lightness Linear Set Unack */ + esp_ble_mesh_server_recv_light_lightness_default_set_t lightness_default; /*!< Light Lightness Default Set/Light Lightness Default Set Unack */ + esp_ble_mesh_server_recv_light_lightness_range_set_t lightness_range; /*!< Light Lightness Range Set/Light Lightness Range Set Unack */ + esp_ble_mesh_server_recv_light_ctl_set_t ctl; /*!< Light CTL Set/Light CTL Set Unack */ + esp_ble_mesh_server_recv_light_ctl_temperature_set_t ctl_temp; /*!< Light CTL Temperature Set/Light CTL Temperature Set Unack */ + esp_ble_mesh_server_recv_light_ctl_temperature_range_set_t ctl_temp_range; /*!< Light CTL Temperature Range Set/Light CTL Temperature Range Set Unack */ + esp_ble_mesh_server_recv_light_ctl_default_set_t ctl_default; /*!< Light CTL Default Set/Light CTL Default Set Unack */ + esp_ble_mesh_server_recv_light_hsl_set_t hsl; /*!< Light HSL Set/Light HSL Set Unack */ + esp_ble_mesh_server_recv_light_hsl_hue_set_t hsl_hue; /*!< Light HSL Hue Set/Light HSL Hue Set Unack */ + esp_ble_mesh_server_recv_light_hsl_saturation_set_t hsl_saturation; /*!< Light HSL Saturation Set/Light HSL Saturation Set Unack */ + esp_ble_mesh_server_recv_light_hsl_default_set_t hsl_default; /*!< Light HSL Default Set/Light HSL Default Set Unack */ + esp_ble_mesh_server_recv_light_hsl_range_set_t hsl_range; /*!< Light HSL Range Set/Light HSL Range Set Unack */ + esp_ble_mesh_server_recv_light_xyl_set_t xyl; /*!< Light xyL Set/Light xyL Set Unack */ + esp_ble_mesh_server_recv_light_xyl_default_set_t xyl_default; /*!< Light xyL Default Set/Light xyL Default Set Unack */ + esp_ble_mesh_server_recv_light_xyl_range_set_t xyl_range; /*!< Light xyL Range Set/Light xyL Range Set Unack */ + esp_ble_mesh_server_recv_light_lc_mode_set_t lc_mode; /*!< Light LC Mode Set/Light LC Mode Set Unack */ + esp_ble_mesh_server_recv_light_lc_om_set_t lc_om; /*!< Light LC OM Set/Light LC OM Set Unack */ + esp_ble_mesh_server_recv_light_lc_light_onoff_set_t lc_light_onoff; /*!< Light LC Light OnOff Set/Light LC Light OnOff Set Unack */ + esp_ble_mesh_server_recv_light_lc_property_set_t lc_property; /*!< Light LC Property Set/Light LC Property Set Unack */ +} esp_ble_mesh_lighting_server_recv_set_msg_t; + +/** Context of the received Sensor Status message */ +typedef struct { + struct net_buf_simple *data; /*!< Value of sensor data state (optional) */ +} esp_ble_mesh_server_recv_sensor_status_t; + +/** + * @brief Lighting Server Model received status message union + */ +typedef union { + esp_ble_mesh_server_recv_sensor_status_t sensor_status; /*!< Sensor Status */ +} esp_ble_mesh_lighting_server_recv_status_msg_t; + +/** + * @brief Lighting Server Model callback value union + */ +typedef union { + esp_ble_mesh_lighting_server_state_change_t state_change; /*!< ESP_BLE_MESH_LIGHTING_SERVER_STATE_CHANGE_EVT */ + esp_ble_mesh_lighting_server_recv_get_msg_t get; /*!< ESP_BLE_MESH_LIGHTING_SERVER_RECV_GET_MSG_EVT */ + esp_ble_mesh_lighting_server_recv_set_msg_t set; /*!< ESP_BLE_MESH_LIGHTING_SERVER_RECV_SET_MSG_EVT */ + esp_ble_mesh_lighting_server_recv_status_msg_t status; /*!< ESP_BLE_MESH_LIGHTING_SERVER_RECV_STATUS_MSG_EVT */ +} esp_ble_mesh_lighting_server_cb_value_t; + +/** Lighting Server Model callback parameters */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to Lighting Server Models */ + esp_ble_mesh_msg_ctx_t ctx; /*!< Context of the received messages */ + esp_ble_mesh_lighting_server_cb_value_t value; /*!< Value of the received Lighting Messages */ +} esp_ble_mesh_lighting_server_cb_param_t; + +/** This enum value is the event of Lighting Server Model */ +typedef enum { + /** + * 1. When get_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, no event will be + * callback to the application layer when Lighting Get messages are received. + * 2. When set_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, this event will + * be callback to the application layer when Lighting Set/Set Unack messages + * are received. + */ + ESP_BLE_MESH_LIGHTING_SERVER_STATE_CHANGE_EVT, + /** + * When get_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will be + * callback to the application layer when Lighting Get messages are received. + */ + ESP_BLE_MESH_LIGHTING_SERVER_RECV_GET_MSG_EVT, + /** + * When set_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will be + * callback to the application layer when Lighting Set/Set Unack messages are received. + */ + ESP_BLE_MESH_LIGHTING_SERVER_RECV_SET_MSG_EVT, + /** + * When status_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will + * be callback to the application layer when Sensor Status message is received. + */ + ESP_BLE_MESH_LIGHTING_SERVER_RECV_STATUS_MSG_EVT, + ESP_BLE_MESH_LIGHTING_SERVER_EVT_MAX, +} esp_ble_mesh_lighting_server_cb_event_t; + +/** + * @brief Bluetooth Mesh Lighting Server Model function. + */ + +/** + * @brief Lighting Server Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +typedef void (* esp_ble_mesh_lighting_server_cb_t)(esp_ble_mesh_lighting_server_cb_event_t event, + esp_ble_mesh_lighting_server_cb_param_t *param); + +/** + * @brief Register BLE Mesh Lighting 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_lighting_server_callback(esp_ble_mesh_lighting_server_cb_t callback); + +#endif /* _ESP_BLE_MESH_LIGHTING_MODEL_API_H_ */ + diff --git a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h new file mode 100644 index 0000000000..52bc082dd4 --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h @@ -0,0 +1,626 @@ +// Copyright 2017-2019 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 "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. + */ + +/** Parameters of Sensor Descriptor Get */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t property_id; /*!< Property ID of a sensor (optional) */ +} esp_ble_mesh_sensor_descriptor_get_t; + +/** Parameter of Sensor Cadence Get */ +typedef struct { + uint16_t property_id; /*!< Property ID of a sensor */ +} esp_ble_mesh_sensor_cadence_get_t; + +/** Parameters of Sensor Cadence Set */ +typedef struct { + uint16_t property_id; /*!< Property ID for the sensor */ + uint8_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 */ + uint8_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; + +/** Parameter of Sensor Settings Get */ +typedef struct { + uint16_t sensor_property_id; /*!< Property ID of a sensor */ +} esp_ble_mesh_sensor_settings_get_t; + +/** Parameters of Sensor Setting Get */ +typedef struct { + uint16_t sensor_property_id; /*!< Property ID of a sensor */ + uint16_t sensor_setting_property_id; /*!< Setting ID identifying a setting within a sensor */ +} esp_ble_mesh_sensor_setting_get_t; + +/** Parameters of Sensor Setting Set */ +typedef struct { + uint16_t sensor_property_id; /*!< Property ID identifying a sensor */ + uint16_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; + +/** Parameters of Sensor Get */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t property_id; /*!< Property ID for the sensor (optional) */ +} esp_ble_mesh_sensor_get_t; + +/** Parameters of Sensor Column Get */ +typedef struct { + uint16_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; + +/** Parameters of Sensor Series Get */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_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; + +/** + * @brief Sensor Client Model get message union + */ +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; + +/** + * @brief Sensor Client Model set message union + */ +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. + */ + +/** Parameter of Sensor Descriptor Status */ +typedef struct { + struct net_buf_simple *descriptor; /*!< Sequence of 8-octet sensor descriptors (optional) */ +} esp_ble_mesh_sensor_descriptor_status_cb_t; + +/** Parameters of Sensor Cadence Status */ +typedef struct { + uint16_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; + +/** Parameters of Sensor Settings Status */ +typedef struct { + uint16_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; + +/** Parameters of Sensor Setting Status */ +typedef struct { + bool op_en; /*!< Indicate id optional parameters are included */ + uint16_t sensor_property_id; /*!< Property ID identifying a sensor */ + uint16_t sensor_setting_property_id; /*!< Setting ID identifying a setting within a sensor */ + uint8_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; + +/** Parameter of Sensor Status */ +typedef struct { + struct net_buf_simple *marshalled_sensor_data; /*!< Value of sensor data state (optional) */ +} esp_ble_mesh_sensor_status_cb_t; + +/** Parameters of Sensor Column Status */ +typedef struct { + uint16_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; + +/** Parameters of Sensor Series Status */ +typedef struct { + uint16_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; + +/** + * @brief Sensor Client Model received message union + */ +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; + +/** Sensor Client Model callback parameters */ +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; + +/** This enum value is the event of Sensor Client Model */ +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 Sensor Client Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +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 esp_ble_mesh_sensor_message_opcode_t in esp_ble_mesh_defs.h + * + * @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 esp_ble_mesh_sensor_message_opcode_t in esp_ble_mesh_defs.h + * + * @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); + +/** + * @brief Sensor Server Models related context. + */ + +/** @def ESP_BLE_MESH_MODEL_SENSOR_SRV + * + * @brief Define a new Sensor Server Model. + * + * @note 1. The Sensor Server model is a root model. When this model is present + * on an element, the corresponding Sensor Setup Server model shall + * also be present. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_sensor_srv_t. + * + * @return New Sensor Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_SENSOR_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_SENSOR_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_SENSOR_SETUP_SRV + * + * @brief Define a new Sensor Setup Server Model. + * + * @note 1. The Sensor Setup Server model extends the Sensor Server model. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_sensor_setup_srv_t. + * + * @return New Sensor Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_SENSOR_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +#define ESP_BLE_MESH_INVALID_SENSOR_PROPERTY_ID 0x0000 /*!< Invalid Sensor Property ID */ + +#define ESP_BLE_MESH_SENSOR_PROPERTY_ID_LEN 0x02 /*!< Length of Sensor Property ID */ + +#define ESP_BLE_MESH_SENSOR_DESCRIPTOR_LEN 0x08 /*!< Length of Sensor Descriptor state */ + +#define ESP_BLE_MESH_SENSOR_UNSPECIFIED_POS_TOLERANCE 0x000 /*!< Unspecified Sensor Positive Tolerance */ +#define ESP_BLE_MESH_SENSOR_UNSPECIFIED_NEG_TOLERANCE 0x000 /*!< Unspecified Sensor Negative Tolerance */ + +#define ESP_BLE_MESH_SENSOR_NOT_APPL_MEASURE_PERIOD 0x00 /*!< Not applicable Sensor Measurement Period */ + +#define ESP_BLE_MESH_SENSOR_NOT_APPL_UPDATE_INTERVAL 0x00 /*!< Not applicable Sensor Update Interval */ + +#define ESP_BLE_MESH_INVALID_SENSOR_SETTING_PROPERTY_ID 0x0000 /*!< Invalid Sensor Setting Property ID */ + +#define ESP_BLE_MESH_SENSOR_SETTING_PROPERTY_ID_LEN 0x02 /*!< Length of Sensor Setting Property ID */ +#define ESP_BLE_MESH_SENSOR_SETTING_ACCESS_LEN 0x01 /*!< Length of Sensor Setting Access */ + +#define ESP_BLE_MESH_SENSOR_SETTING_ACCESS_READ 0x01 /*!< Sensor Setting Access - Read */ +#define ESP_BLE_MESH_SENSOR_SETTING_ACCESS_READ_WRITE 0x03 /*!< Sensor Setting Access - Read & Write */ + +#define ESP_BLE_MESH_SENSOR_DIVISOR_TRIGGER_TYPE_LEN 0x01 /*!< Length of Sensor Divisor Trigger Type */ +#define ESP_BLE_MESH_SENSOR_STATUS_MIN_INTERVAL_LEN 0x01 /*!< Length of Sensor Status Min Interval */ + +#define ESP_BLE_MESH_SENSOR_PERIOD_DIVISOR_MAX_VALUE 15 /*!< Maximum value of Sensor Period Divisor */ + +#define ESP_BLE_MESH_SENSOR_STATUS_MIN_INTERVAL_MAX 26 /*!< Maximum value of Sensor Status Min Interval */ + +/** + * Sensor Status Trigger Type - Format Type of the characteristic + * that the Sensor Property ID state references + */ +#define ESP_BLE_MESH_SENSOR_STATUS_TRIGGER_TYPE_CHAR 0 +/** Sensor Status Trigger Type - Format Type "uint16" */ +#define ESP_BLE_MESH_SENSOR_STATUS_TRIGGER_TYPE_UINT16 1 + +#define ESP_BLE_MESH_SENSOR_DATA_FORMAT_A 0x00 /*!< Sensor Data Format A */ +#define ESP_BLE_MESH_SENSOR_DATA_FORMAT_B 0x01 /*!< Sensor Data Format B */ + +#define ESP_BLE_MESH_SENSOR_DATA_FORMAT_A_MPID_LEN 0x02 /*!< MPID length of Sensor Data Format A */ +#define ESP_BLE_MESH_SENSOR_DATA_FORMAT_B_MPID_LEN 0x03 /*!< MPID length of Sensor Data Format B */ + +/** + * Zero length of Sensor Data. + * + * Note: + * The Length field is a 1-based uint7 value (valid range 0x0–0x7F, + * representing range of 1–127). The value 0x7F represents a length + * of zero. + */ +#define ESP_BLE_MESH_SENSOR_DATA_ZERO_LEN 0x7F + +/** This enum value is value of Sensor Sampling Function */ +enum esp_ble_mesh_sensor_sample_func { + ESP_BLE_MESH_SAMPLE_FUNC_UNSPECIFIED, + ESP_BLE_MESH_SAMPLE_FUNC_INSTANTANEOUS, + ESP_BLE_MESH_SAMPLE_FUNC_ARITHMETIC_MEAN, + ESP_BLE_MESH_SAMPLE_FUNC_RMS, + ESP_BLE_MESH_SAMPLE_FUNC_MAXIMUM, + ESP_BLE_MESH_SAMPLE_FUNC_MINIMUM, + ESP_BLE_MESH_SAMPLE_FUNC_ACCUMULATED, + ESP_BLE_MESH_SAMPLE_FUNC_COUNT, +}; + +/** Parameters of Sensor Descriptor state */ +typedef struct { + uint32_t positive_tolerance : 12, /*!< The value of Sensor Positive Tolerance field */ + negative_tolerance : 12, /*!< The value of Sensor Negative Tolerance field */ + sampling_function : 8; /*!< The value of Sensor Sampling Function field */ + uint8_t measure_period; /*!< The value of Sensor Measurement Period field */ + uint8_t update_interval; /*!< The value of Sensor Update Interval field */ +} esp_ble_mesh_sensor_descriptor_t; + +/** Parameters of Sensor Setting state */ +typedef struct { + uint16_t property_id; /*!< The value of Sensor Setting Property ID field */ + uint8_t access; /*!< The value of Sensor Setting Access field */ + struct net_buf_simple *raw; /*!< The value of Sensor Setting Raw field */ +} esp_ble_mesh_sensor_setting_t; + +/** Parameters of Sensor Cadence state */ +typedef struct { + uint8_t period_divisor : 7, /*!< The value of Fast Cadence Period Divisor field */ + trigger_type : 1; /*!< The value of Status Trigger Type field */ + /** + * Note: + * The parameter "size" in trigger_delta_down, trigger_delta_up, fast_cadence_low & + * fast_cadence_high indicates the exact length of these four parameters, and they + * are associated with the Sensor Property ID. Users need to initialize the "size" + * precisely. + */ + struct net_buf_simple *trigger_delta_down; /*!< The value of Status Trigger Delta Down field */ + struct net_buf_simple *trigger_delta_up; /*!< The value of Status Trigger Delta Up field */ + uint8_t min_interval; /*!< The value of Status Min Interval field */ + struct net_buf_simple *fast_cadence_low; /*!< The value of Fast Cadence Low field */ + struct net_buf_simple *fast_cadence_high; /*!< The value of Fast Cadence High field */ +} esp_ble_mesh_sensor_cadence_t; + +/** Parameters of Sensor Data state */ +typedef struct { + /** + * Format A: The Length field is a 1-based uint4 value (valid range 0x0–0xF, + * representing range of 1 – 16). + * Format B: The Length field is a 1-based uint7 value (valid range 0x0–0x7F, + * representing range of 1 – 127). The value 0x7F represents a + * length of zero. + */ + uint8_t format : 1, /*!< The value of the Sensor Data format */ + length : 7; /*!< The value of the Sensor Data length */ + struct net_buf_simple *raw_value; /*!< The value of Sensor Data raw value */ +} esp_ble_mesh_sensor_data_t; + +/** Parameters of Sensor Series Column state */ +typedef struct { + struct net_buf_simple *raw_value_x; /*!< The value of Sensor Raw Value X field */ + struct net_buf_simple *column_width; /*!< The value of Sensor Column Width field */ + struct net_buf_simple *raw_value_y; /*!< The value of Sensor Raw Value Y field */ +} esp_ble_mesh_sensor_series_column_t; + +/** Parameters of Sensor states */ +typedef struct { + uint16_t sensor_property_id; /*!< The value of Sensor Property ID field */ + + /* Constant throughout the lifetime of an element */ + esp_ble_mesh_sensor_descriptor_t descriptor; /*!< Parameters of the Sensor Descriptor state */ + + /** + * Multiple Sensor Setting states may be present for each sensor. + * The Sensor Setting Property ID values shall be unique for each + * Sensor Property ID that identifies a sensor within an element. + */ + const uint8_t setting_count; /*!< */ + esp_ble_mesh_sensor_setting_t *settings; /*!< Parameters of the Sensor Setting state */ + + /** + * The Sensor Cadence state may be not supported by sensors based + * on device properties referencing "non-scalar characteristics" + * such as "histograms" or "composite characteristics". + */ + esp_ble_mesh_sensor_cadence_t *cadence; /*!< Parameters of the Sensor Cadence state */ + + esp_ble_mesh_sensor_data_t sensor_data; /*!< Parameters of the Sensor Data state */ + + esp_ble_mesh_sensor_series_column_t series_column; /*!< Parameters of the Sensor Series Column state */ +} esp_ble_mesh_sensor_state_t; + +/** User data of Sensor Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Sensor Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + const uint8_t state_count; /*!< Sensor state count */ + esp_ble_mesh_sensor_state_t *states; /*!< Parameters of the Sensor states */ +} esp_ble_mesh_sensor_srv_t; + +/** User data of Sensor Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Sensor Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + const uint8_t state_count; /*!< Sensor state count */ + esp_ble_mesh_sensor_state_t *states; /*!< Parameters of the Sensor states */ +} esp_ble_mesh_sensor_setup_srv_t; + +/** Parameters of Sensor Cadence Set state change event */ +typedef struct { + uint16_t property_id; /*!< The value of Sensor Property ID state */ + uint8_t period_divisor : 7, /*!< The value of Fast Cadence Period Divisor state */ + trigger_type : 1; /*!< The value of Status Trigger Type state */ + struct net_buf_simple *trigger_delta_down; /*!< The value of Status Trigger Delta Down state */ + struct net_buf_simple *trigger_delta_up; /*!< The value of Status Trigger Delta Up state */ + uint8_t min_interval; /*!< The value of Status Min Interval state */ + struct net_buf_simple *fast_cadence_low; /*!< The value of Fast Cadence Low state */ + struct net_buf_simple *fast_cadence_high; /*!< The value of Fast Cadence High state */ +} esp_ble_mesh_state_change_sensor_cadence_set_t; + +/** Parameters of Sensor Setting Set state change event */ +typedef struct { + uint16_t property_id; /*!< The value of Sensor Property ID state */ + uint16_t setting_property_id; /*!< The value of Sensor Setting Property ID state */ + struct net_buf_simple *setting_value; /*!< The value of Sensor Property Value state */ +} esp_ble_mesh_state_change_sensor_setting_set_t; + +/** + * @brief Sensor Server Model state change value union + */ +typedef union { + /** + * The recv_op in ctx can be used to decide which state is changed. + */ + esp_ble_mesh_state_change_sensor_cadence_set_t sensor_cadence_set; /*!< Sensor Cadence Set */ + esp_ble_mesh_state_change_sensor_setting_set_t sensor_setting_set; /*!< Sensor Setting Set */ +} esp_ble_mesh_sensor_server_state_change_t; + +/** Context of the received Sensor Descriptor Get message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t property_id; /*!< Property ID of a sensor (optional) */ +} esp_ble_mesh_server_recv_sensor_descriptor_get_t; + +/** Context of the received Sensor Cadence Get message */ +typedef struct { + uint16_t property_id; /*!< Property ID of a sensor */ +} esp_ble_mesh_server_recv_sensor_cadence_get_t; + +/** Context of the received Sensor Settings Get message */ +typedef struct { + uint16_t property_id; /*!< Property ID of a sensor */ +} esp_ble_mesh_server_recv_sensor_settings_get_t; + +/** Context of the received Sensor Setting Get message */ +typedef struct { + uint16_t property_id; /*!< Property ID of a sensor */ + uint16_t setting_property_id; /*!< Setting ID identifying a setting within a sensor */ +} esp_ble_mesh_server_recv_sensor_setting_get_t; + +/** Context of the received Sensor Get message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t property_id; /*!< Property ID for the sensor (optional) */ +} esp_ble_mesh_server_recv_sensor_get_t; + +/** Context of the received Sensor Column Get message */ +typedef struct { + uint16_t property_id; /*!< Property identifying a sensor */ + struct net_buf_simple *raw_value_x; /*!< Raw value identifying a column */ +} esp_ble_mesh_server_recv_sensor_column_get_t; + +/** Context of the received Sensor Series Get message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t property_id; /*!< Property identifying a sensor */ + struct net_buf_simple *raw_value; /*!< Raw value containg X1 and X2 (optional) */ +} esp_ble_mesh_server_recv_sensor_series_get_t; + +/** + * @brief Sensor Server Model received get message union + */ +typedef union { + esp_ble_mesh_server_recv_sensor_descriptor_get_t sensor_descriptor; /*!< Sensor Descriptor Get */ + esp_ble_mesh_server_recv_sensor_cadence_get_t sensor_cadence; /*!< Sensor Cadence Get */ + esp_ble_mesh_server_recv_sensor_settings_get_t sensor_settings; /*!< Sensor Settings Get */ + esp_ble_mesh_server_recv_sensor_setting_get_t sensor_setting; /*!< Sensor Setting Get */ + esp_ble_mesh_server_recv_sensor_get_t sensor_data; /*!< Sensor Get */ + esp_ble_mesh_server_recv_sensor_column_get_t sensor_column; /*!< Sensor Column Get */ + esp_ble_mesh_server_recv_sensor_series_get_t sensor_series; /*!< Sensor Series Get */ +} esp_ble_mesh_sensor_server_recv_get_msg_t; + +/** Context of the received Sensor Cadence Set message */ +typedef struct { + uint16_t property_id; /*!< Property ID for the sensor */ + struct net_buf_simple *cadence; /*!< Value of Sensor Cadence state */ +} esp_ble_mesh_server_recv_sensor_cadence_set_t; + +/** Context of the received Sensor Setting Set message */ +typedef struct { + uint16_t property_id; /*!< Property ID identifying a sensor */ + uint16_t setting_property_id; /*!< Setting ID identifying a setting within a sensor */ + struct net_buf_simple *setting_raw; /*!< Raw value for the setting */ +} esp_ble_mesh_server_recv_sensor_setting_set_t; + +/** + * @brief Sensor Server Model received set message union + */ +typedef union { + esp_ble_mesh_server_recv_sensor_cadence_set_t sensor_cadence; /*!< Sensor Cadence Set */ + esp_ble_mesh_server_recv_sensor_setting_set_t sensor_setting; /*!< Sensor Setting Set */ +} esp_ble_mesh_sensor_server_recv_set_msg_t; + +/** + * @brief Sensor Server Model callback value union + */ +typedef union { + esp_ble_mesh_sensor_server_state_change_t state_change; /*!< ESP_BLE_MESH_SENSOR_SERVER_STATE_CHANGE_EVT */ + esp_ble_mesh_sensor_server_recv_get_msg_t get; /*!< ESP_BLE_MESH_SENSOR_SERVER_RECV_GET_MSG_EVT */ + esp_ble_mesh_sensor_server_recv_set_msg_t set; /*!< ESP_BLE_MESH_SENSOR_SERVER_RECV_SET_MSG_EVT */ +} esp_ble_mesh_sensor_server_cb_value_t; + +/** Sensor Server Model callback parameters */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to Sensor Server Models */ + esp_ble_mesh_msg_ctx_t ctx; /*!< Context of the received messages */ + esp_ble_mesh_sensor_server_cb_value_t value; /*!< Value of the received Sensor Messages */ +} esp_ble_mesh_sensor_server_cb_param_t; + +/** This enum value is the event of Sensor Server Model */ +typedef enum { + /** + * 1. When get_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, no event will be + * callback to the application layer when Sensor Get messages are received. + * 2. When set_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, this event will + * be callback to the application layer when Sensor Set/Set Unack messages + * are received. + */ + ESP_BLE_MESH_SENSOR_SERVER_STATE_CHANGE_EVT, + /** + * When get_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will be + * callback to the application layer when Sensor Get messages are received. + */ + ESP_BLE_MESH_SENSOR_SERVER_RECV_GET_MSG_EVT, + /** + * When set_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will be + * callback to the application layer when Sensor Set/Set Unack messages are received. + */ + ESP_BLE_MESH_SENSOR_SERVER_RECV_SET_MSG_EVT, + ESP_BLE_MESH_SENSOR_SERVER_EVT_MAX, +} esp_ble_mesh_sensor_server_cb_event_t; + +/** + * @brief Bluetooth Mesh Sensor Server Model function. + */ + +/** + * @brief Sensor Server Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +typedef void (* esp_ble_mesh_sensor_server_cb_t)(esp_ble_mesh_sensor_server_cb_event_t event, + esp_ble_mesh_sensor_server_cb_param_t *param); + +/** + * @brief Register BLE Mesh Sensor 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_sensor_server_callback(esp_ble_mesh_sensor_server_cb_t callback); + +#endif /* _ESP_BLE_MESH_SENSOR_MODEL_API_H_ */ + + diff --git a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_time_scene_model_api.h b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_time_scene_model_api.h new file mode 100644 index 0000000000..525fe3a88a --- /dev/null +++ b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_time_scene_model_api.h @@ -0,0 +1,912 @@ +// Copyright 2017-2019 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 "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. + */ + +/** Parameters of Time Set */ +typedef struct { + uint8_t tai_seconds[5]; /*!< The current TAI time in seconds */ + uint8_t sub_second; /*!< The sub-second time in units of 1/256 second */ + uint8_t uncertainty; /*!< The estimated uncertainty in 10-millisecond steps */ + uint16_t time_authority : 1; /*!< 0 = No Time Authority, 1 = Time Authority */ + uint16_t tai_utc_delta : 15; /*!< Current difference between TAI and UTC in seconds */ + uint8_t time_zone_offset; /*!< The local time zone offset in 15-minute increments */ +} esp_ble_mesh_time_set_t; + +/** Parameters of Time Zone Set */ +typedef struct { + uint8_t time_zone_offset_new; /*!< Upcoming local time zone offset */ + uint8_t tai_zone_change[5]; /*!< TAI Seconds time of the upcoming Time Zone Offset change */ +} esp_ble_mesh_time_zone_set_t; + +/** Parameters of TAI-UTC Delta Set */ +typedef struct { + uint16_t tai_utc_delta_new : 15; /*!< Upcoming difference between TAI and UTC in seconds */ + uint16_t padding : 1; /*!< Always 0b0. Other values are Prohibited. */ + uint8_t tai_delta_change[5]; /*!< TAI Seconds time of the upcoming TAI-UTC Delta change */ +} esp_ble_mesh_tai_utc_delta_set_t; + +/** Parameter of Time Role Set */ +typedef struct { + uint8_t time_role; /*!< The Time Role for the element */ +} esp_ble_mesh_time_role_set_t; + +/** Parameter of Scene Store */ +typedef struct { + uint16_t scene_number; /*!< The number of scenes to be stored */ +} esp_ble_mesh_scene_store_t; + +/** Parameters of Scene Recall */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t scene_number; /*!< The number of scenes to be recalled */ + 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_scene_recall_t; + +/** Parameter of Scene Delete */ +typedef struct { + uint16_t scene_number; /*!< The number of scenes to be deleted */ +} esp_ble_mesh_scene_delete_t; + +/** Parameter of Scheduler Action Get */ +typedef struct { + uint8_t index; /*!< Index of the Schedule Register entry to get */ +} esp_ble_mesh_scheduler_act_get_t; + +/** Parameters of Scheduler Action Set */ +typedef struct { + uint64_t index : 4; /*!< Index of the Schedule Register entry to set */ + uint64_t year : 7; /*!< Scheduled year for the action */ + uint64_t month : 12; /*!< Scheduled month for the action */ + uint64_t day : 5; /*!< Scheduled day of the month for the action */ + uint64_t hour : 5; /*!< Scheduled hour for the action */ + uint64_t minute : 6; /*!< Scheduled minute for the action */ + uint64_t second : 6; /*!< Scheduled second for the action */ + uint64_t day_of_week : 7; /*!< Schedule days of the week for the action */ + uint64_t action : 4; /*!< Action to be performed at the scheduled time */ + uint64_t trans_time : 8; /*!< Transition time for this action */ + uint16_t scene_number; /*!< Transition time for this action */ +} esp_ble_mesh_scheduler_act_set_t; + +/** + * @brief Time Scene Client Model get message union + */ +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; + +/** + * @brief Time Scene Client Model set message union + */ +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. + */ + +/** Parameters of Time Status */ +typedef struct { + uint8_t tai_seconds[5]; /*!< The current TAI time in seconds */ + uint8_t sub_second; /*!< The sub-second time in units of 1/256 second */ + uint8_t uncertainty; /*!< The estimated uncertainty in 10-millisecond steps */ + uint16_t time_authority : 1; /*!< 0 = No Time Authority, 1 = Time Authority */ + uint16_t tai_utc_delta : 15; /*!< Current difference between TAI and UTC in seconds */ + uint8_t time_zone_offset; /*!< The local time zone offset in 15-minute increments */ +} esp_ble_mesh_time_status_cb_t; + +/** Parameters of Time Zone Status */ +typedef struct { + uint8_t time_zone_offset_curr; /*!< Current local time zone offset */ + uint8_t time_zone_offset_new; /*!< Upcoming local time zone offset */ + uint8_t tai_zone_change[5]; /*!< TAI Seconds time of the upcoming Time Zone Offset change */ +} esp_ble_mesh_time_zone_status_cb_t; + +/** Parameters of TAI-UTC Delta Status */ +typedef struct { + uint16_t tai_utc_delta_curr : 15; /*!< Current difference between TAI and UTC in seconds */ + uint16_t padding_1 : 1; /*!< Always 0b0. Other values are Prohibited. */ + uint16_t tai_utc_delta_new : 15; /*!< Upcoming difference between TAI and UTC in seconds */ + uint16_t padding_2 : 1; /*!< Always 0b0. Other values are Prohibited. */ + uint8_t tai_delta_change[5]; /*!< TAI Seconds time of the upcoming TAI-UTC Delta change */ +} esp_ble_mesh_tai_utc_delta_status_cb_t; + +/** Parameter of Time Role Status */ +typedef struct { + uint8_t time_role; /*!< The Time Role for the element */ +} esp_ble_mesh_time_role_status_cb_t; + +/** Parameters of Scene Status */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint8_t status_code; /*!< Status code of the last operation */ + uint16_t current_scene; /*!< Scene Number of the current scene */ + uint16_t target_scene; /*!< Scene Number of the target scene (optional) */ + uint8_t remain_time; /*!< Time to complete state transition (C.1) */ +} esp_ble_mesh_scene_status_cb_t; + +/** Parameters of Scene Register Status */ +typedef struct { + uint8_t status_code; /*!< Status code for the previous operation */ + uint16_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; + +/** Parameter of Scheduler Status */ +typedef struct { + uint16_t schedules; /*!< Bit field indicating defined Actions in the Schedule Register */ +} esp_ble_mesh_scheduler_status_cb_t; + +/** Parameters of Scheduler Action Status */ +typedef struct { + uint64_t index : 4; /*!< Enumerates (selects) a Schedule Register entry */ + uint64_t year : 7; /*!< Scheduled year for the action */ + uint64_t month : 12; /*!< Scheduled month for the action */ + uint64_t day : 5; /*!< Scheduled day of the month for the action */ + uint64_t hour : 5; /*!< Scheduled hour for the action */ + uint64_t minute : 6; /*!< Scheduled minute for the action */ + uint64_t second : 6; /*!< Scheduled second for the action */ + uint64_t day_of_week : 7; /*!< Schedule days of the week for the action */ + uint64_t action : 4; /*!< Action to be performed at the scheduled time */ + uint64_t trans_time : 8; /*!< Transition time for this action */ + uint16_t scene_number; /*!< Transition time for this action */ +} esp_ble_mesh_scheduler_act_status_cb_t; + +/** + * @brief Time Scene Client Model received message union + */ +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; + +/** Time Scene Client Model callback parameters */ +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; + +/** This enum value is the event of Time Scene Client Model */ +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 Time Scene Client Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +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 esp_ble_mesh_time_scene_message_opcode_t in esp_ble_mesh_defs.h + * + * @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 esp_ble_mesh_time_scene_message_opcode_t in esp_ble_mesh_defs.h + * + * @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); + +/** + * @brief Time Scene Server Models related context. + */ + +/** @def ESP_BLE_MESH_MODEL_TIME_SRV + * + * @brief Define a new Time Server Model. + * + * @note 1. The Time Server model is a root model. When this model is present on an + * Element, the corresponding Time Setup Server model shall also be present. + * 2. This model shall support model publication and model subscription. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_time_srv_t. + * + * @return New Time Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_TIME_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_TIME_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_TIME_SETUP_SRV + * + * @brief Define a new Time Setup Server Model. + * + * @note 1. The Time Setup Server model extends the Time Server model. Time is + * sensitive information that is propagated across a mesh network. + * 2. Only an authorized Time Client should be allowed to change the Time + * and Time Role states. A dedicated application key Bluetooth SIG + * Proprietary should be used on the Time Setup Server to restrict + * access to the server to only authorized Time Clients. + * 3. This model does not support subscribing nor publishing. + * + * @param srv_data Pointer to the unique struct esp_ble_mesh_time_setup_srv_t. + * + * @return New Time Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_TIME_SETUP_SRV(srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_TIME_SETUP_SRV, \ + NULL, NULL, srv_data) + +/** @def ESP_BLE_MESH_MODEL_SCENE_SRV + * + * @brief Define a new Scene Server Model. + * + * @note 1. The Scene Server model is a root model. When this model is present + * on an Element, the corresponding Scene Setup Server model shall + * also be present. + * 2. This model shall support model publication and model subscription. + * 3. The model may be present only on the Primary element of a node. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_scene_srv_t. + * + * @return New Scene Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_SCENE_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_SCENE_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_SCENE_SETUP_SRV + * + * @brief Define a new Scene Setup Server Model. + * + * @note 1. The Scene Setup Server model extends the Scene Server model and + * the Generic Default Transition Time Server model. + * 2. This model shall support model subscription. + * 3. The model may be present only on the Primary element of a node. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_scene_setup_srv_t. + * + * @return New Scene Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_SCENE_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_SCENE_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_SCHEDULER_SRV + * + * @brief Define a new Scheduler Server Model. + * + * @note 1. The Scheduler Server model extends the Scene Server model. When + * this model is present on an Element, the corresponding Scheduler + * Setup Server model shall also be present. + * 2. This model shall support model publication and model subscription. + * 3. The model may be present only on the Primary element of a node. + * 4. The model requires the Time Server model shall be present on the element. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_scheduler_srv_t. + * + * @return New Scheduler Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_SCHEDULER_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_SCHEDULER_SRV, \ + NULL, srv_pub, srv_data) + +/** @def ESP_BLE_MESH_MODEL_SCHEDULER_SETUP_SRV + * + * @brief Define a new Scheduler Setup Server Model. + * + * @note 1. The Scheduler Setup Server model extends the Scheduler Server and + * the Scene Setup Server models. + * 2. This model shall support model subscription. + * 3. The model may be present only on the Primary element of a node. + * + * @param srv_pub Pointer to the unique struct esp_ble_mesh_model_pub_t. + * @param srv_data Pointer to the unique struct esp_ble_mesh_scheduler_setup_srv_t. + * + * @return New Scheduler Setup Server Model instance. + */ +#define ESP_BLE_MESH_MODEL_SCHEDULER_SETUP_SRV(srv_pub, srv_data) \ + ESP_BLE_MESH_SIG_MODEL(ESP_BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV, \ + NULL, srv_pub, srv_data) + +#define ESP_BLE_MESH_UNKNOWN_TAI_SECONDS 0x0000000000 /*!< Unknown TAI Seconds */ +#define ESP_BLE_MESH_UNKNOWN_TAI_ZONE_CHANGE 0x0000000000 /*!< Unknown TAI of Zone Change */ +#define ESP_BLE_MESH_UNKNOWN_TAI_DELTA_CHANGE 0x0000000000 /*!< Unknown TAI of Delta Change */ + +#define ESP_BLE_MESH_TAI_UTC_DELAT_MAX_VALUE 0x7FFF /*!< Maximum TAI-UTC Delta value */ + +#define ESP_BLE_MESH_TAI_SECONDS_LEN 0x05 /*!< Length of TAI Seconds */ +#define ESP_BLE_MESH_TAI_OF_ZONE_CHANGE_LEN 0x05 /*!< Length of TAI of Zone Change */ +#define ESP_BLE_MESH_TAI_OF_DELAT_CHANGE_LEN 0x05 /*!< Length of TAI of Delta Change */ + +#define ESP_BLE_MESH_INVALID_SCENE_NUMBER 0x0000 /*!< Invalid Scene Number */ +#define ESP_BLE_MESH_SCENE_NUMBER_LEN 0x02 /*!< Length of the Scene Number */ + +#define ESP_BLE_MESH_SCHEDULE_YEAR_ANY_YEAR 0x64 /*!< Any year of the Scheduled year */ + +#define ESP_BLE_MESH_SCHEDULE_DAY_ANY_DAY 0x00 /*!< Any day of the Scheduled day */ + +#define ESP_BLE_MESH_SCHEDULE_HOUR_ANY_HOUR 0x18 /*!< Any hour of the Scheduled hour */ +#define ESP_BLE_MESH_SCHEDULE_HOUR_ONCE_A_DAY 0x19 /*!< Any hour of the Scheduled Day */ + +#define ESP_BLE_MESH_SCHEDULE_SEC_ANY_OF_HOUR 0x3C /*!< Any minute of the Scheduled hour */ +#define ESP_BLE_MESH_SCHEDULE_SEC_EVERY_15_MIN 0x3D /*!< Every 15 minutes of the Scheduled hour */ +#define ESP_BLE_MESH_SCHEDULE_SEC_EVERY_20_MIN 0x3E /*!< Every 20 minutes of the Scheduled hour */ +#define ESP_BLE_MESH_SCHEDULE_SEC_ONCE_AN_HOUR 0x3F /*!< Once of the Scheduled hour */ + +#define ESP_BLE_MESH_SCHEDULE_SEC_ANY_OF_MIN 0x3C /*!< Any second of the Scheduled minute */ +#define ESP_BLE_MESH_SCHEDULE_SEC_EVERY_15_SEC 0x3D /*!< Every 15 seconds of the Scheduled minute */ +#define ESP_BLE_MESH_SCHEDULE_SEC_EVERY_20_SEC 0x3E /*!< Every 20 seconds of the Scheduled minute */ +#define ESP_BLE_MESH_SCHEDULE_SEC_ONCE_AN_MIN 0x3F /*!< Once of the Scheduled minute */ + +#define ESP_BLE_MESH_SCHEDULE_ACT_TURN_OFF 0x00 /*!< Scheduled Action - Turn Off */ +#define ESP_BLE_MESH_SCHEDULE_ACT_TURN_ON 0x01 /*!< Scheduled Action - Turn On */ +#define ESP_BLE_MESH_SCHEDULE_ACT_SCENE_RECALL 0x02 /*!< Scheduled Action - Scene Recall */ +#define ESP_BLE_MESH_SCHEDULE_ACT_NO_ACTION 0x0F /*!< Scheduled Action - No Action */ + +#define ESP_BLE_MESH_SCHEDULE_SCENE_NO_SCENE 0x0000 /*!< Scheduled Scene - No Scene */ + +#define ESP_BLE_MESH_SCHEDULE_ENTRY_MAX_INDEX 0x0F /*!< Maximum number of Scheduled entries */ + +#define ESP_BLE_MESH_TIME_NONE 0x00 /*!< Time Role - None */ +#define ESP_BLE_MESH_TIME_AUTHORITY 0x01 /*!< Time Role - Mesh Time Authority */ +#define ESP_BLE_MESH_TIME_RELAY 0x02 /*!< Time Role - Mesh Time Relay */ +#define ESP_BLE_MESH_TIME_CLINET 0x03 /*!< Time Role - Mesh Time Client */ + +#define ESP_BLE_MESH_SCENE_SUCCESS 0x00 /*!< Scene operation - Success */ +#define ESP_BLE_MESH_SCENE_REG_FULL 0x01 /*!< Scene operation - Scene Register Full */ +#define ESP_BLE_MESH_SCENE_NOT_FOUND 0x02 /*!< Scene operation - Scene Not Found */ + +/** Parameters of Time state */ +typedef struct { + struct { + uint8_t tai_seconds[5]; /*!< The value of the TAI Seconds state */ + uint8_t subsecond; /*!< The value of the Subsecond field */ + uint8_t uncertainty; /*!< The value of the Uncertainty field */ + uint8_t time_zone_offset_curr; /*!< The value of the Time Zone Offset Current field */ + uint8_t time_zone_offset_new; /*!< The value of the Time Zone Offset New state */ + uint8_t tai_zone_change[5]; /*!< The value of the TAI of Zone Chaneg field */ + uint16_t time_authority : 1, /*!< The value of the Time Authority bit */ + tai_utc_delta_curr : 15; /*!< The value of the TAI-UTC Delta Current state */ + uint16_t tai_utc_delta_new : 15; /*!< The value of the TAI-UTC Delta New state */ + uint8_t tai_delta_change[5]; /*!< The value of the TAI of Delta Change field */ + } time; /*!< Parameters of the Time state */ + uint8_t time_role; /*!< The value of the Time Role state */ +} esp_ble_mesh_time_state_t; + +/** User data of Time Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Time Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_time_state_t *state; /*!< Parameters of the Time state */ +} esp_ble_mesh_time_srv_t; + +/** User data of Time Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Time Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_time_state_t *state; /*!< Parameters of the Time state */ +} esp_ble_mesh_time_setup_srv_t; + +/** + * 1. Scene Store is an operation of storing values of a present state of an element. + * 2. The structure and meaning of the stored state is determined by a model. States + * to be stored are specified by each model. + * 3. The Scene Store operation shall persistently store all values of all states + * marked as Stored with Scene for all models present on all elements of a node. + * 4. If a model is extending another model, the extending model shall determine the + * Stored with Scene behavior of that model. + */ + +/** Parameters of Scene Register state */ +typedef struct { + uint16_t scene_number; /*!< The value of the Scene Number */ + uint8_t scene_type; /*!< The value of the Scene Type */ + /** + * Scene value may use a union to represent later, the union contains + * structures of all the model states which can be stored in a scene. + */ + struct net_buf_simple *scene_value; /*!< The value of the Scene Value */ +} esp_ble_mesh_scene_register_t; + +/** + * Parameters of Scenes state. + * + * Scenes serve as memory banks for storage of states (e.g., a power level + * or a light level/color). Values of states of an element can be stored + * as a scene and can be recalled later from the scene memory. + * + * A scene is represented by a Scene Number, which is a 16-bit non-zero, + * mesh-wide value. (There can be a maximum of 65535 scenes in a mesh + * network.) The meaning of a scene, as well as the state storage container + * associated with it, are determined by a model. + * + * The Scenes state change may start numerous parallel model transitions. + * In that case, each individual model handles the transition internally. + * + * The scene transition is defined as a group of individual model transitions + * started by a Scene Recall operation. The scene transition is in progress + * when at least one transition from the group of individual model transitions + * is in progress. + */ +typedef struct { + const uint16_t scene_count; /*!< The Scenes state's scene count */ + esp_ble_mesh_scene_register_t *scenes; /*!< Parameters of the Scenes state */ + + /** + * The Current Scene state is a 16-bit value that contains either the Scene + * Number of the currently active scene or a value of 0x0000 when no scene + * is active. + * + * When a Scene Store operation or a Scene Recall operation completes with + * success, the Current Scene state value shall be to the Scene Number used + * during that operation. + * + * When the Current Scene Number is deleted from a Scene Register state as a + * result of Scene Delete operation, the Current Scene state shall be set to + * 0x0000. + * + * When any of the element's state that is marked as “Stored with Scene” has + * changed not as a result of a Scene Recall operation, the value of the + * Current Scene state shall be set to 0x0000. + * + * When a scene transition is in progress, the value of the Current Scene + * state shall be set to 0x0000. + */ + uint16_t current_scene; /*!< The value of the Current Scene state */ + + /** + * The Target Scene state is a 16-bit value that contains the target Scene + * Number when a scene transition is in progress. + * + * When the scene transition is in progress and the target Scene Number is + * deleted from a Scene Register state as a result of Scene Delete operation, + * the Target Scene state shall be set to 0x0000. + * + * When the scene transition is in progress and a new Scene Number is stored + * in the Scene Register as a result of Scene Store operation, the Target + * Scene state shall be set to the new Scene Number. + * + * When the scene transition is not in progress, the value of the Target Scene + * state shall be set to 0x0000. + */ + uint16_t target_scene; /*!< The value of the Target Scene state */ + + /* Indicate the status code for the last operation */ + uint8_t status_code; /*!< The status code of the last scene operation */ + + /* Indicate if scene transition is in progress */ + bool in_progress; /*!< Indicate if the scene transition is in progress */ +} esp_ble_mesh_scenes_state_t; + +/** User data of Scene Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Scene Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_scenes_state_t *state; /*!< Parameters of the Scenes state */ + esp_ble_mesh_last_msg_info_t last; /*!< Parameters of the last received set message */ + esp_ble_mesh_state_transition_t transition; /*!< Parameters of state transition */ +} esp_ble_mesh_scene_srv_t; + +/** User data of Scene Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Scene Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_scenes_state_t *state; /*!< Parameters of the Scenes state */ +} esp_ble_mesh_scene_setup_srv_t; + +/** Parameters of Scheduler Register state */ +typedef struct { + bool in_use; /*!< Indicate if the registered schedule is in use */ + uint64_t year : 7, /*!< The value of Scheduled year for the action */ + month : 12, /*!< The value of Scheduled month for the action */ + day : 5, /*!< The value of Scheduled day of the month for the action */ + hour : 5, /*!< The value of Scheduled hour for the action */ + minute : 6, /*!< The value of Scheduled minute for the action */ + second : 6, /*!< The value of Scheduled second for the action */ + day_of_week : 7, /*!< The value of Schedule days of the week for the action */ + action : 4, /*!< The value of Action to be performed at the scheduled time */ + trans_time : 8; /*!< The value of Transition time for this action */ + uint16_t scene_number; /*!< The value of Scene Number to be used for some actions */ +} esp_ble_mesh_schedule_register_t; + +/** Parameters of Scheduler state */ +typedef struct { + const uint8_t schedule_count; /*!< Scheduler count */ + esp_ble_mesh_schedule_register_t *schedules; /*!< Up to 16 scheduled entries */ +} esp_ble_mesh_scheduler_state_t; + +/** User data of Scheduler Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Scheduler Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_scheduler_state_t *state; /*!< Parameters of the Scheduler state */ +} esp_ble_mesh_scheduler_srv_t; + +/** User data of Scheduler Setup Server Model */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to the Scheduler Setup Server Model. Initialized internally. */ + esp_ble_mesh_server_rsp_ctrl_t rsp_ctrl; /*!< Response control of the server model received messages */ + esp_ble_mesh_scheduler_state_t *state; /*!< Parameters of the Scheduler state */ +} esp_ble_mesh_scheduler_setup_srv_t; + +/** Parameters of Time Set state change event */ +typedef struct { + uint8_t tai_seconds[5]; /*!< The current TAI time in seconds */ + uint8_t subsecond; /*!< The sub-second time in units of 1/256 second */ + uint8_t uncertainty; /*!< The estimated uncertainty in 10-millisecond steps */ + uint16_t time_authority : 1; /*!< 0 = No Time Authority, 1 = Time Authority */ + uint16_t tai_utc_delta_curr : 15; /*!< Current difference between TAI and UTC in seconds */ + uint8_t time_zone_offset_curr; /*!< The local time zone offset in 15-minute increments */ +} esp_ble_mesh_state_change_time_set_t; + +/** Parameters of Time Status state change event */ +typedef struct { + uint8_t tai_seconds[5]; /*!< The current TAI time in seconds */ + uint8_t subsecond; /*!< The sub-second time in units of 1/256 second */ + uint8_t uncertainty; /*!< The estimated uncertainty in 10-millisecond steps */ + uint16_t time_authority : 1; /*!< 0 = No Time Authority, 1 = Time Authority */ + uint16_t tai_utc_delta_curr : 15; /*!< Current difference between TAI and UTC in seconds */ + uint8_t time_zone_offset_curr; /*!< The local time zone offset in 15-minute increments */ +} esp_ble_mesh_state_change_time_status_t; + +/** Parameters of Time Zone Set state change event */ +typedef struct { + uint8_t time_zone_offset_new; /*!< Upcoming local time zone offset */ + uint8_t tai_zone_change[5]; /*!< TAI Seconds time of the upcoming Time Zone Offset change */ +} esp_ble_mesh_state_change_time_zone_set_t; + +/** Parameters of TAI UTC Delta Set state change event */ +typedef struct { + uint16_t tai_utc_delta_new : 15; /*!< Upcoming difference between TAI and UTC in seconds */ + uint8_t tai_delta_change[5]; /*!< TAI Seconds time of the upcoming TAI-UTC Delta change */ +} esp_ble_mesh_state_change_tai_utc_delta_set_t; + +/** Parameter of Time Role Set state change event */ +typedef struct { + uint8_t time_role; /*!< The Time Role for the element */ +} esp_ble_mesh_state_change_time_role_set_t; + +/** Parameter of Scene Store state change event */ +typedef struct { + uint16_t scene_number; /*!< The number of scenes to be stored */ +} esp_ble_mesh_state_change_scene_store_t; + +/** Parameter of Scene Recall state change event */ +typedef struct { + uint16_t scene_number; /*!< The number of scenes to be recalled */ +} esp_ble_mesh_state_change_scene_recall_t; + +/** Parameter of Scene Delete state change event */ +typedef struct { + uint16_t scene_number; /*!< The number of scenes to be deleted */ +} esp_ble_mesh_state_change_scene_delete_t; + +/** Parameter of Scheduler Action Set state change event */ +typedef struct { + uint64_t index : 4; /*!< Index of the Schedule Register entry to set */ + uint64_t year : 7; /*!< Scheduled year for the action */ + uint64_t month : 12; /*!< Scheduled month for the action */ + uint64_t day : 5; /*!< Scheduled day of the month for the action */ + uint64_t hour : 5; /*!< Scheduled hour for the action */ + uint64_t minute : 6; /*!< Scheduled minute for the action */ + uint64_t second : 6; /*!< Scheduled second for the action */ + uint64_t day_of_week : 7; /*!< Schedule days of the week for the action */ + uint64_t action : 4; /*!< Action to be performed at the scheduled time */ + uint64_t trans_time : 8; /*!< Transition time for this action */ + uint16_t scene_number; /*!< Scene number to be used for some actions */ +} esp_ble_mesh_state_change_scheduler_act_set_t; + +/** + * @brief Time Scene Server Model state change value union + */ +typedef union { + /** + * The recv_op in ctx can be used to decide which state is changed. + */ + esp_ble_mesh_state_change_time_set_t time_set; /*!< Time Set */ + esp_ble_mesh_state_change_time_status_t time_status; /*!< Time Status */ + esp_ble_mesh_state_change_time_zone_set_t time_zone_set; /*!< Time Zone Set */ + esp_ble_mesh_state_change_tai_utc_delta_set_t tai_utc_delta_set; /*!< TAI UTC Delta Set */ + esp_ble_mesh_state_change_time_role_set_t time_role_set; /*!< Time Role Set */ + esp_ble_mesh_state_change_scene_store_t scene_store; /*!< Scene Store */ + esp_ble_mesh_state_change_scene_recall_t scene_recall; /*!< Scene Recall */ + esp_ble_mesh_state_change_scene_delete_t scene_delete; /*!< Scene Delete */ + esp_ble_mesh_state_change_scheduler_act_set_t scheduler_act_set; /*!< Scheduler Action Set */ +} esp_ble_mesh_time_scene_server_state_change_t; + +/** Context of the received Scheduler Action Get message */ +typedef struct { + uint8_t index; /*!< Index of the Schedule Register entry to get */ +} esp_ble_mesh_server_recv_scheduler_act_get_t; + +/** + * @brief Time Scene Server Model received get message union + */ +typedef union { + esp_ble_mesh_server_recv_scheduler_act_get_t scheduler_act; /*!< Scheduler Action Get */ +} esp_ble_mesh_time_scene_server_recv_get_msg_t; + +/** Context of the received Time Set message */ +typedef struct { + uint8_t tai_seconds[5]; /*!< The current TAI time in seconds */ + uint8_t subsecond; /*!< The sub-second time in units of 1/256 second */ + uint8_t uncertainty; /*!< The estimated uncertainty in 10-millisecond steps */ + uint16_t time_authority : 1; /*!< 0 = No Time Authority, 1 = Time Authority */ + uint16_t tai_utc_delta : 15; /*!< Current difference between TAI and UTC in seconds */ + uint8_t time_zone_offset; /*!< The local time zone offset in 15-minute increments */ +} esp_ble_mesh_server_recv_time_set_t; + +/** Context of the received Time Zone Set message */ +typedef struct { + uint8_t time_zone_offset_new; /*!< Upcoming local time zone offset */ + uint8_t tai_zone_change[5]; /*!< TAI Seconds time of the upcoming Time Zone Offset change */ +} esp_ble_mesh_server_recv_time_zone_set_t; + +/** Context of the received TAI UTC Delta Set message */ +typedef struct { + uint16_t tai_utc_delta_new : 15; /*!< Upcoming difference between TAI and UTC in seconds */ + uint16_t padding : 1; /*!< Always 0b0. Other values are Prohibited. */ + uint8_t tai_delta_change[5]; /*!< TAI Seconds time of the upcoming TAI-UTC Delta change */ +} esp_ble_mesh_server_recv_tai_utc_delta_set_t; + +/** Context of the received Time Role Set message */ +typedef struct { + uint8_t time_role; /*!< The Time Role for the element */ +} esp_ble_mesh_server_recv_time_role_set_t; + +/** Context of the received Scene Store message */ +typedef struct { + uint16_t scene_number; /*!< The number of scenes to be stored */ +} esp_ble_mesh_server_recv_scene_store_t; + +/** Context of the received Scene Recall message */ +typedef struct { + bool op_en; /*!< Indicate if optional parameters are included */ + uint16_t scene_number; /*!< The number of scenes to be recalled */ + 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_server_recv_scene_recall_t; + +/** Context of the received Scene Delete message */ +typedef struct { + uint16_t scene_number; /*!< The number of scenes to be deleted */ +} esp_ble_mesh_server_recv_scene_delete_t; + +/** Context of the received Scheduler Action Set message */ +typedef struct { + uint64_t index : 4; /*!< Index of the Schedule Register entry to set */ + uint64_t year : 7; /*!< Scheduled year for the action */ + uint64_t month : 12; /*!< Scheduled month for the action */ + uint64_t day : 5; /*!< Scheduled day of the month for the action */ + uint64_t hour : 5; /*!< Scheduled hour for the action */ + uint64_t minute : 6; /*!< Scheduled minute for the action */ + uint64_t second : 6; /*!< Scheduled second for the action */ + uint64_t day_of_week : 7; /*!< Schedule days of the week for the action */ + uint64_t action : 4; /*!< Action to be performed at the scheduled time */ + uint64_t trans_time : 8; /*!< Transition time for this action */ + uint16_t scene_number; /*!< Scene number to be used for some actions */ +} esp_ble_mesh_server_recv_scheduler_act_set_t; + +/** + * @brief Time Scene Server Model received set message union + */ +typedef union { + esp_ble_mesh_server_recv_time_set_t time; /*!< Time Set */ + esp_ble_mesh_server_recv_time_zone_set_t time_zone; /*!< Time Zone Set */ + esp_ble_mesh_server_recv_tai_utc_delta_set_t tai_utc_delta; /*!< TAI-UTC Delta Set */ + esp_ble_mesh_server_recv_time_role_set_t time_role; /*!< Time Role Set */ + esp_ble_mesh_server_recv_scene_store_t scene_store; /*!< Scene Store/Scene Store Unack */ + esp_ble_mesh_server_recv_scene_recall_t scene_recall; /*!< Scene Recall/Scene Recall Unack */ + esp_ble_mesh_server_recv_scene_delete_t scene_delete; /*!< Scene Delete/Scene Delete Unack */ + esp_ble_mesh_server_recv_scheduler_act_set_t scheduler_act; /*!< Scheduler Action Set/Scheduler Action Set Unack */ +} esp_ble_mesh_time_scene_server_recv_set_msg_t; + +/** Context of the received Time Status message */ +typedef struct { + uint8_t tai_seconds[5]; /*!< The current TAI time in seconds */ + uint8_t subsecond; /*!< The sub-second time in units of 1/256 second */ + uint8_t uncertainty; /*!< The estimated uncertainty in 10-millisecond steps */ + uint16_t time_authority : 1; /*!< 0 = No Time Authority, 1 = Time Authority */ + uint16_t tai_utc_delta : 15; /*!< Current difference between TAI and UTC in seconds */ + uint8_t time_zone_offset; /*!< The local time zone offset in 15-minute increments */ +} esp_ble_mesh_server_recv_time_status_t; + +/** + * @brief Time Scene Server Model received status message union + */ +typedef union { + esp_ble_mesh_server_recv_time_status_t time_status; /*!< Time Status */ +} esp_ble_mesh_time_scene_server_recv_status_msg_t; + +/** + * @brief Time Scene Server Model callback value union + */ +typedef union { + esp_ble_mesh_time_scene_server_state_change_t state_change; /*!< ESP_BLE_MESH_TIME_SCENE_SERVER_STATE_CHANGE_EVT */ + esp_ble_mesh_time_scene_server_recv_get_msg_t get; /*!< ESP_BLE_MESH_TIME_SCENE_SERVER_RECV_GET_MSG_EVT */ + esp_ble_mesh_time_scene_server_recv_set_msg_t set; /*!< ESP_BLE_MESH_TIME_SCENE_SERVER_RECV_SET_MSG_EVT */ + esp_ble_mesh_time_scene_server_recv_status_msg_t status; /*!< ESP_BLE_MESH_TIME_SCENE_SERVER_RECV_STATUS_MSG_EVT */ +} esp_ble_mesh_time_scene_server_cb_value_t; + +/** Time Scene Server Model callback parameters */ +typedef struct { + esp_ble_mesh_model_t *model; /*!< Pointer to Time and Scenes Server Models */ + esp_ble_mesh_msg_ctx_t ctx; /*!< Context of the received messages */ + esp_ble_mesh_time_scene_server_cb_value_t value; /*!< Value of the received Time and Scenes Messages */ +} esp_ble_mesh_time_scene_server_cb_param_t; + +/** This enum value is the event of Time Scene Server Model */ +typedef enum { + /** + * 1. When get_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, no event will be + * callback to the application layer when Time Scene Get messages are received. + * 2. When set_auto_rsp is set to ESP_BLE_MESH_SERVER_AUTO_RSP, this event will + * be callback to the application layer when Time Scene Set/Set Unack messages + * are received. + */ + ESP_BLE_MESH_TIME_SCENE_SERVER_STATE_CHANGE_EVT, + /** + * When get_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will be + * callback to the application layer when Time Scene Get messages are received. + */ + ESP_BLE_MESH_TIME_SCENE_SERVER_RECV_GET_MSG_EVT, + /** + * When set_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will be + * callback to the application layer when Time Scene Set/Set Unack messages are received. + */ + ESP_BLE_MESH_TIME_SCENE_SERVER_RECV_SET_MSG_EVT, + /** + * When status_auto_rsp is set to ESP_BLE_MESH_SERVER_RSP_BY_APP, this event will + * be callback to the application layer when TIme Status message is received. + */ + ESP_BLE_MESH_TIME_SCENE_SERVER_RECV_STATUS_MSG_EVT, + ESP_BLE_MESH_TIME_SCENE_SERVER_EVT_MAX, +} esp_ble_mesh_time_scene_server_cb_event_t; + +/** + * @brief Bluetooth Mesh Time and Scenes Server Model function. + */ + +/** + * @brief Time Scene Server Model callback function type + * @param event: Event type + * @param param: Pointer to callback parameter + */ +typedef void (* esp_ble_mesh_time_scene_server_cb_t)(esp_ble_mesh_time_scene_server_cb_event_t event, + esp_ble_mesh_time_scene_server_cb_param_t *param); + +/** + * @brief Register BLE Mesh Time and Scenes 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_time_scene_server_callback(esp_ble_mesh_time_scene_server_cb_t callback); + +#endif /* _ESP_BLE_MESH_TIME_SCENE_MODEL_API_H_ */ + diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_config_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_config_model.c new file mode 100644 index 0000000000..20a86c8fa6 --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_config_model.c @@ -0,0 +1,801 @@ +// Copyright 2017-2019 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 "foundation.h" +#include "mesh_common.h" +#include "cfg_cli.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; + +/* Configuration Client Model related functions */ + +static inline void btc_ble_mesh_config_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_ble_mesh_cb = + (esp_ble_mesh_cfg_client_cb_t)btc_profile_cb_get(BTC_PID_CONFIG_CLIENT); + if (btc_ble_mesh_cb) { + btc_ble_mesh_cb(event, param); + } +} + +void btc_ble_mesh_config_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_config_client_args_t *dst = (btc_ble_mesh_config_client_args_t *)p_dest; + btc_ble_mesh_config_client_args_t *src = (btc_ble_mesh_config_client_args_t *)p_src; + + if (!msg || !dst || !src) { + BT_ERR("%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 *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (dst->cfg_client_get_state.params) { + memcpy(dst->cfg_client_get_state.params, src->cfg_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + break; + } + if (src->cfg_client_get_state.get_state) { + dst->cfg_client_get_state.get_state = (esp_ble_mesh_cfg_client_get_state_t *)bt_mesh_malloc(sizeof(esp_ble_mesh_cfg_client_get_state_t)); + if (dst->cfg_client_get_state.get_state) { + 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 { + BT_ERR("%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 *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (dst->cfg_client_set_state.params) { + memcpy(dst->cfg_client_set_state.params, src->cfg_client_set_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + break; + } + if (src->cfg_client_set_state.set_state) { + dst->cfg_client_set_state.set_state = (esp_ble_mesh_cfg_client_set_state_t *)bt_mesh_malloc(sizeof(esp_ble_mesh_cfg_client_set_state_t)); + if (dst->cfg_client_set_state.set_state) { + 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 { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + } + default: + BT_DBG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_config_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_config_client_args_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_config_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_CONFIG_CLIENT_GET_STATE: + if (arg->cfg_client_get_state.params) { + bt_mesh_free(arg->cfg_client_get_state.params); + } + if (arg->cfg_client_get_state.get_state) { + bt_mesh_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) { + bt_mesh_free(arg->cfg_client_set_state.params); + } + if (arg->cfg_client_set_state.set_state) { + bt_mesh_free(arg->cfg_client_set_state.set_state); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_config_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; + u16_t length = 0U; + + if (!msg || !p_src_data || !p_dest_data) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (p_src_data->params) { + p_dest_data->params = bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (!p_dest_data->params) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } + + 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) { + switch (p_src_data->params->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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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: + break; + default: + break; + } +} + +static void btc_ble_mesh_config_client_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_cfg_client_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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) { + switch (arg->params->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) { + bt_mesh_free(arg->params); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_config_client_callback(esp_ble_mesh_cfg_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_CONFIG_CLIENT)) { + return; + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_CONFIG_CLIENT; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_cfg_client_cb_param_t), btc_ble_mesh_config_client_copy_req_data); +} + +void bt_mesh_config_client_cb_evt_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 = 0U; + uint8_t act = 0U; + + if (!model || !ctx) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_CONFIG_CLIENT_GET_STATE: + act = ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_CONFIG_CLIENT_SET_STATE: + act = ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_CONFIG_CLIENT_PUBLISH: + act = ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT; + break; + case BTC_BLE_MESH_EVT_CONFIG_CLIENT_TIMEOUT: + act = ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT; + break; + default: + BT_ERR("%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_ble_mesh_config_client_callback(&cb_params, act); + return; +} + +void btc_ble_mesh_config_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) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_config_client_cb_evt_to_btc(opcode, + BTC_BLE_MESH_EVT_CONFIG_CLIENT_PUBLISH, model, ctx, buf->data, buf->len); + return; +} + +static 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, + esp_ble_mesh_cfg_client_cb_param_t *cb) +{ + struct bt_mesh_msg_ctx ctx = {0}; + + if (!params || !cb) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + switch (params->opcode) { + case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: + case ESP_BLE_MESH_MODEL_OP_MODEL_PUB_GET: + case ESP_BLE_MESH_MODEL_OP_SIG_MODEL_SUB_GET: + case ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_SUB_GET: + case ESP_BLE_MESH_MODEL_OP_APP_KEY_GET: + case ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_GET: + case ESP_BLE_MESH_MODEL_OP_SIG_MODEL_APP_GET: + case ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_APP_GET: + case ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_GET: + case ESP_BLE_MESH_MODEL_OP_LPN_POLLTIMEOUT_GET: + if (get == NULL) { + BT_ERR("%s, Invalid config client get", __func__); + return -EINVAL; + } + break; + default: + break; + } + + 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 (cb->error_code = bt_mesh_cfg_beacon_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_GET: + return (cb->error_code = bt_mesh_cfg_ttl_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_FRIEND_GET: + return (cb->error_code = bt_mesh_cfg_friend_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_GATT_PROXY_GET: + return (cb->error_code = bt_mesh_cfg_gatt_proxy_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_RELAY_GET: + return (cb->error_code = bt_mesh_cfg_relay_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_MODEL_PUB_GET: + return (cb->error_code = + bt_mesh_cfg_mod_pub_get(&ctx, get->model_pub_get.element_addr, + get->model_pub_get.model_id, get->model_pub_get.company_id)); + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_GET: + return (cb->error_code = bt_mesh_cfg_hb_pub_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_GET: + return (cb->error_code = bt_mesh_cfg_hb_sub_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: + return (cb->error_code = bt_mesh_cfg_comp_data_get(&ctx, get->comp_data_get.page)); + case ESP_BLE_MESH_MODEL_OP_SIG_MODEL_SUB_GET: + return (cb->error_code = + bt_mesh_cfg_mod_sub_get(&ctx, get->sig_model_sub_get.element_addr, + get->sig_model_sub_get.model_id)); + case ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_SUB_GET: + return (cb->error_code = + bt_mesh_cfg_mod_sub_get_vnd(&ctx, get->vnd_model_sub_get.element_addr, + get->vnd_model_sub_get.model_id, get->vnd_model_sub_get.company_id)); + case ESP_BLE_MESH_MODEL_OP_NET_KEY_GET: + return (cb->error_code = bt_mesh_cfg_net_key_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_APP_KEY_GET: + return (cb->error_code = bt_mesh_cfg_app_key_get(&ctx, get->app_key_get.net_idx)); + case ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_GET: + return (cb->error_code = bt_mesh_cfg_node_identity_get(&ctx, get->node_identity_get.net_idx)); + case ESP_BLE_MESH_MODEL_OP_SIG_MODEL_APP_GET: + return (cb->error_code = + bt_mesh_cfg_mod_app_get(&ctx, get->sig_model_app_get.element_addr, + get->sig_model_app_get.model_id)); + case ESP_BLE_MESH_MODEL_OP_VENDOR_MODEL_APP_GET: + return (cb->error_code = + bt_mesh_cfg_mod_app_get_vnd(&ctx, get->vnd_model_app_get.element_addr, + get->vnd_model_app_get.model_id, get->vnd_model_app_get.company_id)); + case ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_GET: + return (cb->error_code = bt_mesh_cfg_kr_phase_get(&ctx, get->kr_phase_get.net_idx)); + case ESP_BLE_MESH_MODEL_OP_LPN_POLLTIMEOUT_GET: + return (cb->error_code = bt_mesh_cfg_lpn_timeout_get(&ctx, get->lpn_pollto_get.lpn_addr)); + case ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_GET: + return (cb->error_code = bt_mesh_cfg_net_transmit_get(&ctx)); + default: + BT_ERR("%s, Invalid opcode 0x%x", __func__, params->opcode); + return (cb->error_code = -EINVAL); + } + + return 0; +} + +static 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, + esp_ble_mesh_cfg_client_cb_param_t *cb) +{ + struct bt_mesh_msg_ctx ctx = {0}; + + if (!params || !cb) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (params->opcode != ESP_BLE_MESH_MODEL_OP_NODE_RESET && set == NULL) { + BT_ERR("%s, Invalid config client set", __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 (cb->error_code = bt_mesh_cfg_beacon_set(&ctx, set->beacon_set.beacon)); + case ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_SET: + return (cb->error_code = bt_mesh_cfg_ttl_set(&ctx, set->default_ttl_set.ttl)); + case ESP_BLE_MESH_MODEL_OP_FRIEND_SET: + return (cb->error_code = bt_mesh_cfg_friend_set(&ctx, set->friend_set.friend_state)); + case ESP_BLE_MESH_MODEL_OP_GATT_PROXY_SET: + return (cb->error_code = bt_mesh_cfg_gatt_proxy_set(&ctx, set->gatt_proxy_set.gatt_proxy)); + case ESP_BLE_MESH_MODEL_OP_RELAY_SET: + return (cb->error_code = + bt_mesh_cfg_relay_set(&ctx, set->relay_set.relay, set->relay_set.relay_retransmit)); + case ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD: + return (cb->error_code = + bt_mesh_cfg_net_key_add(&ctx, set->net_key_add.net_idx, + &set->net_key_add.net_key[0])); + case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: + return (cb->error_code = + bt_mesh_cfg_app_key_add(&ctx, set->app_key_add.net_idx, + set->app_key_add.app_idx, &set->app_key_add.app_key[0])); + case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND: + return (cb->error_code = + bt_mesh_cfg_mod_app_bind(&ctx, set->model_app_bind.element_addr, + set->model_app_bind.model_app_idx, set->model_app_bind.model_id, + set->model_app_bind.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_PUB_SET: { + struct bt_mesh_cfg_mod_pub model_pub = { + .addr = set->model_pub_set.publish_addr, + .app_idx = set->model_pub_set.publish_app_idx, + .cred_flag = set->model_pub_set.cred_flag, + .ttl = set->model_pub_set.publish_ttl, + .period = set->model_pub_set.publish_period, + .transmit = set->model_pub_set.publish_retransmit, + }; + return (cb->error_code = + bt_mesh_cfg_mod_pub_set(&ctx, set->model_pub_set.element_addr, + set->model_pub_set.model_id, set->model_pub_set.company_id, &model_pub)); + } + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD: + return (cb->error_code = + bt_mesh_cfg_mod_sub_add(&ctx, set->model_sub_add.element_addr, + set->model_sub_add.sub_addr, set->model_sub_add.model_id, + set->model_sub_add.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE: + return (cb->error_code = + bt_mesh_cfg_mod_sub_del(&ctx, set->model_sub_delete.element_addr, + set->model_sub_delete.sub_addr, set->model_sub_delete.model_id, + set->model_sub_delete.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_OVERWRITE: + return (cb->error_code = + bt_mesh_cfg_mod_sub_overwrite(&ctx, set->model_sub_overwrite.element_addr, + set->model_sub_overwrite.sub_addr, set->model_sub_overwrite.model_id, + set->model_sub_overwrite.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_ADD: + return (cb->error_code = + bt_mesh_cfg_mod_sub_va_add(&ctx, set->model_sub_va_add.element_addr, + &set->model_sub_va_add.label_uuid[0], set->model_sub_va_add.model_id, + set->model_sub_va_add.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_OVERWRITE: + return (cb->error_code = + bt_mesh_cfg_mod_sub_va_overwrite(&ctx, set->model_sub_va_overwrite.element_addr, + &set->model_sub_va_overwrite.label_uuid[0], set->model_sub_va_overwrite.model_id, + set->model_sub_va_overwrite.company_id)); + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_DELETE: + return (cb->error_code = + bt_mesh_cfg_mod_sub_va_del(&ctx, set->model_sub_va_delete.element_addr, + &set->model_sub_va_delete.label_uuid[0], set->model_sub_va_delete.model_id, + set->model_sub_va_delete.company_id)); + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET: + return (cb->error_code = + bt_mesh_cfg_hb_sub_set(&ctx, + (struct bt_mesh_cfg_hb_sub *)&set->heartbeat_sub_set)); + case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET: + return (cb->error_code = + bt_mesh_cfg_hb_pub_set(&ctx, + (const struct bt_mesh_cfg_hb_pub *)&set->heartbeat_pub_set)); + case ESP_BLE_MESH_MODEL_OP_NODE_RESET: + return (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->model_pub_va_set.publish_app_idx, + .cred_flag = set->model_pub_va_set.cred_flag, + .ttl = set->model_pub_va_set.publish_ttl, + .period = set->model_pub_va_set.publish_period, + .transmit = set->model_pub_va_set.publish_retransmit, + }; + return (cb->error_code = + bt_mesh_cfg_mod_pub_va_set(&ctx, set->model_pub_va_set.element_addr, + set->model_pub_va_set.model_id, set->model_pub_va_set.company_id, + set->model_pub_va_set.label_uuid, &model_pub)); + } + case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE_ALL: + return (cb->error_code = + bt_mesh_cfg_mod_sub_del_all(&ctx, set->model_sub_delete_all.element_addr, + set->model_sub_delete_all.model_id, set->model_sub_delete_all.company_id)); + case ESP_BLE_MESH_MODEL_OP_NET_KEY_UPDATE: + return (cb->error_code = + bt_mesh_cfg_net_key_update(&ctx, set->net_key_update.net_idx, + set->net_key_update.net_key)); + case ESP_BLE_MESH_MODEL_OP_NET_KEY_DELETE: + return (cb->error_code = + bt_mesh_cfg_net_key_delete(&ctx, set->net_key_delete.net_idx)); + case ESP_BLE_MESH_MODEL_OP_APP_KEY_UPDATE: + return (cb->error_code = + bt_mesh_cfg_app_key_update(&ctx, set->app_key_update.net_idx, + set->app_key_update.app_idx, set->app_key_update.app_key)); + case ESP_BLE_MESH_MODEL_OP_APP_KEY_DELETE: + return (cb->error_code = + bt_mesh_cfg_app_key_delete(&ctx, set->app_key_delete.net_idx, + set->app_key_delete.app_idx)); + case ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_SET: + return (cb->error_code = + bt_mesh_cfg_node_identity_set(&ctx, set->node_identity_set.net_idx, + set->node_identity_set.identity)); + case ESP_BLE_MESH_MODEL_OP_MODEL_APP_UNBIND: + return (cb->error_code = + bt_mesh_cfg_mod_app_unbind(&ctx, set->model_app_unbind.element_addr, + set->model_app_unbind.model_app_idx, set->model_app_unbind.model_id, + set->model_app_unbind.company_id)); + case ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_SET: + return (cb->error_code = + bt_mesh_cfg_kr_phase_set(&ctx, set->kr_phase_set.net_idx, + set->kr_phase_set.transition)); + case ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_SET: + return (cb->error_code = + bt_mesh_cfg_net_transmit_set(&ctx, set->net_transmit_set.net_transmit)); + default: + BT_ERR("%s, Invalid opcode 0x%x", __func__, params->opcode); + return (cb->error_code = -EINVAL); + } + + return 0; +} + +void btc_ble_mesh_config_client_call_handler(btc_msg_t *msg) +{ + btc_ble_mesh_config_client_args_t *arg = NULL; + esp_ble_mesh_cfg_client_cb_param_t cb = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_config_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_CONFIG_CLIENT_GET_STATE: { + cb.params = arg->cfg_client_get_state.params; + role_param.model = (struct bt_mesh_model *)cb.params->model; + role_param.role = cb.params->msg_role; + if (bt_mesh_set_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + btc_ble_mesh_config_client_get_state(arg->cfg_client_get_state.params, + arg->cfg_client_get_state.get_state, + &cb); + if (cb.error_code) { + btc_ble_mesh_config_client_callback(&cb, ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT); + } + break; + } + case BTC_BLE_MESH_ACT_CONFIG_CLIENT_SET_STATE: { + cb.params = arg->cfg_client_set_state.params; + role_param.model = (struct bt_mesh_model *)cb.params->model; + role_param.role = cb.params->msg_role; + if (bt_mesh_set_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + btc_ble_mesh_config_client_set_state(arg->cfg_client_set_state.params, + arg->cfg_client_set_state.set_state, + &cb); + if (cb.error_code) { + btc_ble_mesh_config_client_callback(&cb, ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_config_client_arg_deep_free(msg); + return; +} + +void btc_ble_mesh_config_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_cfg_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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_config_client_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_config_client_free_req_data(msg); + return; +} + +/* Configuration Server Model related functions */ + +static inline void btc_ble_mesh_config_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_ble_mesh_cb = + (esp_ble_mesh_cfg_server_cb_t)btc_profile_cb_get(BTC_PID_CONFIG_SERVER); + if (btc_ble_mesh_cb) { + btc_ble_mesh_cb(event, param); + } +} + +static void btc_ble_mesh_config_server_callback(esp_ble_mesh_cfg_server_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_CONFIG_SERVER)) { + return; + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_CONFIG_SERVER; + msg.act = act; + + btc_transfer_context(&msg, cb_params, sizeof(esp_ble_mesh_cfg_server_cb_param_t), NULL); +} + +void bt_mesh_config_server_cb_evt_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 = 0U; + uint8_t act = 0U; + + if (!model || !ctx) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE: + act = ESP_BLE_MESH_CFG_SERVER_STATE_CHANGE_EVT; + break; + default: + BT_ERR("%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.value)) ? len : sizeof(cb_params.value); + memcpy(&cb_params.value, val, length); + } + + btc_ble_mesh_config_server_callback(&cb_params, act); + return; +} + +void btc_ble_mesh_config_server_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_cfg_server_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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_config_server_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } +} diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_generic_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_generic_model.c new file mode 100644 index 0000000000..a9e0d90493 --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_generic_model.c @@ -0,0 +1,776 @@ +// Copyright 2017-2019 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" +#include "generic_client.h" + +#include "btc_ble_mesh_generic_model.h" +#include "esp_ble_mesh_generic_model_api.h" + +/* Generic Client Models related functions */ + +static inline void btc_ble_mesh_generic_client_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_ble_mesh_cb = + (esp_ble_mesh_generic_client_cb_t)btc_profile_cb_get(BTC_PID_GENERIC_CLIENT); + if (btc_ble_mesh_cb) { + btc_ble_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; + u16_t length = 0U; + + if (!msg || !dst || !src) { + BT_ERR("%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 *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (dst->generic_client_get_state.params) { + memcpy(dst->generic_client_get_state.params, src->generic_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + break; + } + if (src->generic_client_get_state.get_state) { + dst->generic_client_get_state.get_state = (esp_ble_mesh_generic_client_get_state_t *)bt_mesh_malloc(sizeof(esp_ble_mesh_generic_client_get_state_t)); + if (dst->generic_client_get_state.get_state) { + 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 { + BT_ERR("%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 *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->generic_client_set_state.set_state = (esp_ble_mesh_generic_client_set_state_t *)bt_mesh_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)); + + switch (src->generic_client_set_state.params->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) { + BT_ERR("%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) { + BT_ERR("%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 { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + BT_DBG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_generic_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_generic_client_args_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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) { + bt_mesh_free(arg->generic_client_get_state.params); + } + if (arg->generic_client_get_state.get_state) { + bt_mesh_free(arg->generic_client_get_state.get_state); + } + break; + case BTC_BLE_MESH_ACT_GENERIC_CLIENT_SET_STATE: + if (arg->generic_client_set_state.set_state) { + if (arg->generic_client_set_state.params) { + switch (arg->generic_client_set_state.params->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; + } + } + bt_mesh_free(arg->generic_client_set_state.set_state); + } + if (arg->generic_client_set_state.params) { + bt_mesh_free(arg->generic_client_set_state.params); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_generic_client_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; + u16_t length = 0U; + + if (!msg || !p_src_data || !p_dest_data) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (p_src_data->params) { + p_dest_data->params = bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (!p_dest_data->params) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } + + 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) { + switch (p_src_data->params->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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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: + break; + default: + break; + } +} + +static void btc_ble_mesh_generic_client_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_generic_client_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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) { + switch (arg->params->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) { + bt_mesh_free(arg->params); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_generic_client_callback(esp_ble_mesh_generic_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_GENERIC_CLIENT)) { + return; + } + + 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_generic_client_copy_req_data); +} + +void bt_mesh_generic_client_cb_evt_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 = 0U; + uint8_t act = 0U; + + if (!model || !ctx) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_GENERIC_CLIENT_GET_STATE: + act = ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_GENERIC_CLIENT_SET_STATE: + act = ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_GENERIC_CLIENT_PUBLISH: + act = ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT; + break; + case BTC_BLE_MESH_EVT_GENERIC_CLIENT_TIMEOUT: + act = ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT; + break; + default: + BT_ERR("%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_ble_mesh_generic_client_callback(&cb_params, act); + return; +} + +void btc_ble_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) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_generic_client_cb_evt_to_btc(opcode, + BTC_BLE_MESH_EVT_GENERIC_CLIENT_PUBLISH, model, ctx, buf->data, buf->len); + return; +} + +void btc_ble_mesh_generic_client_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_client_common_param_t *params = NULL; + btc_ble_mesh_generic_client_args_t *arg = NULL; + esp_ble_mesh_generic_client_cb_param_t cb = {0}; + bt_mesh_client_common_param_t common = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + BT_ERR("%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_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + 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; + + cb.params = arg->generic_client_get_state.params; + cb.error_code = bt_mesh_generic_client_get_state(&common, + (void *)arg->generic_client_get_state.get_state, (void *)&cb.status_cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_generic_client_callback(&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_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + 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; + + cb.params = arg->generic_client_set_state.params; + cb.error_code = bt_mesh_generic_client_set_state(&common, + (void *)arg->generic_client_set_state.set_state, (void *)&cb.status_cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_generic_client_callback(&cb, ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_generic_client_arg_deep_free(msg); + return; +} + +void btc_ble_mesh_generic_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_generic_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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_generic_client_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_generic_client_free_req_data(msg); + return; +} + +/* Generic Server Models related functions */ + +static inline void btc_ble_mesh_generic_server_cb_to_app( + esp_ble_mesh_generic_server_cb_event_t event, + esp_ble_mesh_generic_server_cb_param_t *param) +{ + esp_ble_mesh_generic_server_cb_t btc_ble_mesh_cb = + (esp_ble_mesh_generic_server_cb_t)btc_profile_cb_get(BTC_PID_GENERIC_SERVER); + if (btc_ble_mesh_cb) { + btc_ble_mesh_cb(event, param); + } +} + +static void btc_ble_mesh_generic_server_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_generic_server_cb_param_t *p_dest_data = (esp_ble_mesh_generic_server_cb_param_t *)p_dest; + esp_ble_mesh_generic_server_cb_param_t *p_src_data = (esp_ble_mesh_generic_server_cb_param_t *)p_src; + u16_t length = 0U; + + if (!msg || !p_src_data || !p_dest_data) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT: + switch (p_src_data->ctx.recv_op) { + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK: + if (p_src_data->value.state_change.user_property_set.value) { + length = p_src_data->value.state_change.user_property_set.value->len; + p_dest_data->value.state_change.user_property_set.value = bt_mesh_alloc_buf(length); + if (p_dest_data->value.state_change.user_property_set.value == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.state_change.user_property_set.value, + p_src_data->value.state_change.user_property_set.value->data, + p_src_data->value.state_change.user_property_set.value->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK: + if (p_src_data->value.state_change.admin_property_set.value) { + length = p_src_data->value.state_change.admin_property_set.value->len; + p_dest_data->value.state_change.admin_property_set.value = bt_mesh_alloc_buf(length); + if (p_dest_data->value.state_change.admin_property_set.value == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.state_change.admin_property_set.value, + p_src_data->value.state_change.admin_property_set.value->data, + p_src_data->value.state_change.admin_property_set.value->len); + } + break; + default: + break; + } + break; + case ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT: + switch (p_src_data->ctx.recv_op) { + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK: + if (p_src_data->value.set.user_property.property_value) { + length = p_src_data->value.set.user_property.property_value->len; + p_dest_data->value.set.user_property.property_value = bt_mesh_alloc_buf(length); + if (p_dest_data->value.set.user_property.property_value == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.set.user_property.property_value, + p_src_data->value.set.user_property.property_value->data, + p_src_data->value.set.user_property.property_value->len); + } + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK: + if (p_src_data->value.set.admin_property.property_value) { + length = p_src_data->value.set.admin_property.property_value->len; + p_dest_data->value.set.admin_property.property_value = bt_mesh_alloc_buf(length); + if (p_dest_data->value.set.admin_property.property_value == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.set.admin_property.property_value, + p_src_data->value.set.admin_property.property_value->data, + p_src_data->value.set.admin_property.property_value->len); + } + break; + default: + break; + } + break; + default: + break; + } +} + +static void btc_ble_mesh_generic_server_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_generic_server_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_generic_server_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT: + switch (arg->ctx.recv_op) { + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK: + bt_mesh_free_buf(arg->value.state_change.user_property_set.value); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK: + bt_mesh_free_buf(arg->value.state_change.admin_property_set.value); + break; + default: + break; + } + break; + case ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT: + switch (arg->ctx.recv_op) { + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK: + bt_mesh_free_buf(arg->value.set.user_property.property_value); + break; + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET: + case ESP_BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK: + bt_mesh_free_buf(arg->value.set.admin_property.property_value); + break; + default: + break; + } + break; + default: + break; + } +} + +static void btc_ble_mesh_generic_server_callback(esp_ble_mesh_generic_server_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_GENERIC_SERVER)) { + return; + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_GENERIC_SERVER; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_generic_server_cb_param_t), btc_ble_mesh_generic_server_copy_req_data); +} + +void bt_mesh_generic_server_cb_evt_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_generic_server_cb_param_t cb_params = {0}; + size_t length = 0U; + uint8_t act = 0U; + + if (model == NULL || ctx == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE: + act = ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT; + break; + case BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG: + act = ESP_BLE_MESH_GENERIC_SERVER_RECV_GET_MSG_EVT; + break; + case BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG: + act = ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT; + break; + default: + BT_ERR("%s, Unknown Generic Server event type", __func__); + 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.value)) ? len : sizeof(cb_params.value); + memcpy(&cb_params.value, val, length); + } + + btc_ble_mesh_generic_server_callback(&cb_params, act); + return; +} + +void btc_ble_mesh_generic_server_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_generic_server_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_generic_server_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_GENERIC_SERVER_EVT_MAX) { + btc_ble_mesh_generic_server_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_generic_server_free_req_data(msg); + return; +} diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_health_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_health_model.c new file mode 100644 index 0000000000..769d2e3f65 --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_health_model.c @@ -0,0 +1,661 @@ +// Copyright 2017-2019 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 "foundation.h" +#include "mesh_common.h" +#include "health_srv.h" +#include "health_cli.h" + +#include "btc_ble_mesh_health_model.h" +#include "esp_ble_mesh_defs.h" + +extern s32_t health_msg_timeout; + +/* Health Client Model related functions */ + +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_ble_mesh_cb = + (esp_ble_mesh_health_client_cb_t)btc_profile_cb_get(BTC_PID_HEALTH_CLIENT); + if (btc_ble_mesh_cb) { + btc_ble_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) { + BT_ERR("%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 *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (dst->health_client_get_state.params) { + memcpy(dst->health_client_get_state.params, src->health_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + break; + } + if (src->health_client_get_state.get_state) { + dst->health_client_get_state.get_state = (esp_ble_mesh_health_client_get_state_t *)bt_mesh_malloc(sizeof(esp_ble_mesh_health_client_get_state_t)); + if (dst->health_client_get_state.get_state) { + 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 { + BT_ERR("%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 *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->health_client_set_state.set_state = (esp_ble_mesh_health_client_set_state_t *)bt_mesh_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 { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + BT_DBG("%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) { + BT_ERR("%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) { + bt_mesh_free(arg->health_client_get_state.params); + } + if (arg->health_client_get_state.get_state) { + bt_mesh_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) { + bt_mesh_free(arg->health_client_set_state.params); + } + if (arg->health_client_set_state.set_state) { + bt_mesh_free(arg->health_client_set_state.set_state); + } + 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; + u16_t length = 0U; + + if (!msg || !p_src_data || !p_dest_data) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (p_src_data->params) { + p_dest_data->params = bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (!p_dest_data->params) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } + + 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) { + switch (p_src_data->params->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) { + BT_ERR("%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) { + BT_ERR("%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: + 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; + + if (!msg || !msg->arg) { + BT_ERR("%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) { + switch (arg->params->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) { + bt_mesh_free(arg->params); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_health_client_callback(esp_ble_mesh_health_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_HEALTH_CLIENT)) { + return; + } + + 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); +} + +void bt_mesh_health_client_cb_evt_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 = 0U; + uint8_t act = 0U; + + if (!model || !ctx) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_HEALTH_CLIENT_GET_STATE: + act = ESP_BLE_MESH_HEALTH_CLIENT_GET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_HEALTH_CLIENT_SET_STATE: + act = ESP_BLE_MESH_HEALTH_CLIENT_SET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_HEALTH_CLIENT_PUBLISH: + act = ESP_BLE_MESH_HEALTH_CLIENT_PUBLISH_EVT; + break; + case BTC_BLE_MESH_EVT_HEALTH_CLIENT_TIMEOUT: + act = ESP_BLE_MESH_HEALTH_CLIENT_TIMEOUT_EVT; + break; + default: + BT_ERR("%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_ble_mesh_health_client_callback(&cb_params, act); + return; +} + +void btc_ble_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) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_health_client_cb_evt_to_btc(opcode, + BTC_BLE_MESH_EVT_HEALTH_CLIENT_PUBLISH, model, ctx, buf->data, buf->len); + return; +} + +static 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, + esp_ble_mesh_health_client_cb_param_t *cb) +{ + struct bt_mesh_msg_ctx ctx = {0}; + + if (!params || !cb) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (params->opcode == ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET && get == NULL) { + BT_ERR("%s, Invalid health client get", __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 (cb->error_code = bt_mesh_health_attention_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_GET: + return (cb->error_code = bt_mesh_health_period_get(&ctx)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET: + return (cb->error_code = bt_mesh_health_fault_get(&ctx, get->fault_get.company_id)); + default: + BT_ERR("%s, Invalid opcode 0x%x", __func__, params->opcode); + return (cb->error_code = -EINVAL); + } + + return 0; +} + +static 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, + esp_ble_mesh_health_client_cb_param_t *cb) +{ + struct bt_mesh_msg_ctx ctx = {0}; + + if (!params || !set || !cb) { + BT_ERR("%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 (cb->error_code = + bt_mesh_health_attention_set(&ctx, set->attention_set.attention, true)); + case ESP_BLE_MESH_MODEL_OP_ATTENTION_SET_UNACK: + return (cb->error_code = + bt_mesh_health_attention_set(&ctx, set->attention_set.attention, false)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET: + return (cb->error_code = + bt_mesh_health_period_set(&ctx, set->period_set.fast_period_divisor, true)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_SET_UNACK: + return (cb->error_code = + bt_mesh_health_period_set(&ctx, set->period_set.fast_period_divisor, false)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST: + return (cb->error_code = + bt_mesh_health_fault_test(&ctx, set->fault_test.company_id, set->fault_test.test_id, true)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_TEST_UNACK: + return (cb->error_code = + bt_mesh_health_fault_test(&ctx, set->fault_test.company_id, set->fault_test.test_id, false)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR: + return (cb->error_code = + bt_mesh_health_fault_clear(&ctx, set->fault_clear.company_id, true)); + case ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_CLEAR_UNACK: + return (cb->error_code = + bt_mesh_health_fault_clear(&ctx, set->fault_clear.company_id, false)); + default: + BT_ERR("%s, Invalid opcode 0x%x", __func__, params->opcode); + return (cb->error_code = -EINVAL); + } + + return 0; +} + +void btc_ble_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 cb = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + BT_ERR("%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: { + cb.params = arg->health_client_get_state.params; + role_param.model = (struct bt_mesh_model *)cb.params->model; + role_param.role = cb.params->msg_role; + if (bt_mesh_set_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + btc_ble_mesh_health_client_get_state(arg->health_client_get_state.params, + arg->health_client_get_state.get_state, &cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_health_client_callback(&cb, ESP_BLE_MESH_HEALTH_CLIENT_GET_STATE_EVT); + } + break; + } + case BTC_BLE_MESH_ACT_HEALTH_CLIENT_SET_STATE: { + cb.params = arg->health_client_set_state.params; + role_param.model = (struct bt_mesh_model *)cb.params->model; + role_param.role = cb.params->msg_role; + if (bt_mesh_set_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + btc_ble_mesh_health_client_set_state(arg->health_client_set_state.params, + arg->health_client_set_state.set_state, &cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_health_client_callback(&cb, ESP_BLE_MESH_HEALTH_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_health_client_arg_deep_free(msg); + return; +} + +void btc_ble_mesh_health_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_health_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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 { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_health_client_free_req_data(msg); + return; +} + +/* Health Server Model related functions */ + +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_ble_mesh_cb = + (esp_ble_mesh_health_server_cb_t)btc_profile_cb_get(BTC_PID_HEALTH_SERVER); + if (btc_ble_mesh_cb) { + btc_ble_mesh_cb(event, param); + } +} + +void btc_ble_mesh_health_server_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + if (!msg) { + BT_ERR("%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) { + BT_ERR("%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_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + if (!msg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_HEALTH_SERVER_FAULT_UPDATE_COMP_EVT: + break; + default: + break; + } +} + +static void btc_ble_mesh_health_server_free_req_data(btc_msg_t *msg) +{ + if (!msg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_HEALTH_SERVER_FAULT_UPDATE_COMP_EVT: + break; + default: + break; + } +} + +static void btc_ble_mesh_health_server_callback(esp_ble_mesh_health_server_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_HEALTH_SERVER)) { + return; + } + + 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); +} + +void btc_ble_mesh_health_server_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_health_server_cb_param_t param = {0}; + btc_ble_mesh_health_server_args_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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: + param.fault_update_comp.element = arg->health_fault_update.element; + param.fault_update_comp.error_code = + bt_mesh_fault_update((struct bt_mesh_elem *)arg->health_fault_update.element); + btc_ble_mesh_health_server_callback(¶m, ESP_BLE_MESH_HEALTH_SERVER_FAULT_UPDATE_COMP_EVT); + break; + default: + break; + } + + btc_ble_mesh_health_server_arg_deep_free(msg); + return; +} + +void btc_ble_mesh_health_server_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_health_server_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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 { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_health_server_free_req_data(msg); + return; +} + +void btc_ble_mesh_health_server_fault_clear(struct bt_mesh_model *model, u16_t company_id) +{ + esp_ble_mesh_health_server_cb_param_t param = {0}; + + param.fault_clear.model = (esp_ble_mesh_model_t *)model; + param.fault_clear.company_id = company_id; + + btc_ble_mesh_health_server_callback(¶m, ESP_BLE_MESH_HEALTH_SERVER_FAULT_CLEAR_EVT); +} + +void btc_ble_mesh_health_server_fault_test(struct bt_mesh_model *model, u8_t test_id, u16_t company_id) +{ + esp_ble_mesh_health_server_cb_param_t param = {0}; + + param.fault_test.model = (esp_ble_mesh_model_t *)model; + param.fault_test.test_id = test_id; + param.fault_test.company_id = company_id; + + btc_ble_mesh_health_server_callback(¶m, ESP_BLE_MESH_HEALTH_SERVER_FAULT_TEST_EVT); +} + +void btc_ble_mesh_health_server_attention_on(struct bt_mesh_model *model, u8_t time) +{ + esp_ble_mesh_health_server_cb_param_t param = {0}; + + param.attention_on.model = (esp_ble_mesh_model_t *)model; + param.attention_on.time = time; + + btc_ble_mesh_health_server_callback(¶m, ESP_BLE_MESH_HEALTH_SERVER_ATTENTION_ON_EVT); +} + +void btc_ble_mesh_health_server_attention_off(struct bt_mesh_model *model) +{ + esp_ble_mesh_health_server_cb_param_t param = {0}; + + param.attention_off.model = (esp_ble_mesh_model_t *)model; + + btc_ble_mesh_health_server_callback(¶m, ESP_BLE_MESH_HEALTH_SERVER_ATTENTION_OFF_EVT); +} diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c new file mode 100644 index 0000000000..7737f2bfdd --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c @@ -0,0 +1,591 @@ +// Copyright 2017-2019 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" +#include "lighting_client.h" + +#include "btc_ble_mesh_lighting_model.h" +#include "esp_ble_mesh_lighting_model_api.h" + +/* Lighting Client Models related functions */ + +static inline void btc_ble_mesh_lighting_client_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_ble_mesh_cb = + (esp_ble_mesh_light_client_cb_t)btc_profile_cb_get(BTC_PID_LIGHTING_CLIENT); + if (btc_ble_mesh_cb) { + btc_ble_mesh_cb(event, param); + } +} + +void btc_ble_mesh_lighting_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_lighting_client_args_t *dst = (btc_ble_mesh_lighting_client_args_t *)p_dest; + btc_ble_mesh_lighting_client_args_t *src = (btc_ble_mesh_lighting_client_args_t *)p_src; + + if (!msg || !dst || !src) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_LIGHTING_CLIENT_GET_STATE: { + dst->light_client_get_state.params = (esp_ble_mesh_client_common_param_t *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (dst->light_client_get_state.params) { + memcpy(dst->light_client_get_state.params, src->light_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + break; + } + if (src->light_client_get_state.get_state) { + dst->light_client_get_state.get_state = (esp_ble_mesh_light_client_get_state_t *)bt_mesh_malloc(sizeof(esp_ble_mesh_light_client_get_state_t)); + if (dst->light_client_get_state.get_state) { + 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 { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + } + case BTC_BLE_MESH_ACT_LIGHTING_CLIENT_SET_STATE: { + dst->light_client_set_state.params = (esp_ble_mesh_client_common_param_t *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->light_client_set_state.set_state = (esp_ble_mesh_light_client_set_state_t *)bt_mesh_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 { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + BT_DBG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_lighting_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_lighting_client_args_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_lighting_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_LIGHTING_CLIENT_GET_STATE: + if (arg->light_client_get_state.params) { + bt_mesh_free(arg->light_client_get_state.params); + } + if (arg->light_client_get_state.get_state) { + bt_mesh_free(arg->light_client_get_state.get_state); + } + break; + case BTC_BLE_MESH_ACT_LIGHTING_CLIENT_SET_STATE: + if (arg->light_client_set_state.params) { + bt_mesh_free(arg->light_client_set_state.params); + } + if (arg->light_client_set_state.set_state) { + bt_mesh_free(arg->light_client_set_state.set_state); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_lighting_client_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; + u16_t length = 0U; + + if (!msg || !p_src_data || !p_dest_data) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (p_src_data->params) { + p_dest_data->params = bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (!p_dest_data->params) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } + + 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) { + switch (p_src_data->params->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) { + BT_ERR("%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: + break; + default: + break; + } +} + +static void btc_ble_mesh_lighting_client_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_light_client_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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) { + switch (arg->params->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) { + bt_mesh_free(arg->params); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_lighting_client_callback(esp_ble_mesh_light_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_LIGHTING_CLIENT)) { + return; + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_LIGHTING_CLIENT; + msg.act = act; + + btc_transfer_context(&msg, cb_params, + sizeof(esp_ble_mesh_light_client_cb_param_t), btc_ble_mesh_lighting_client_copy_req_data); +} + +void bt_mesh_lighting_client_cb_evt_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 = 0U; + uint8_t act = 0U; + + if (!model || !ctx) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_LIGHTING_CLIENT_GET_STATE: + act = ESP_BLE_MESH_LIGHT_CLIENT_GET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_LIGHTING_CLIENT_SET_STATE: + act = ESP_BLE_MESH_LIGHT_CLIENT_SET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_LIGHTING_CLIENT_PUBLISH: + act = ESP_BLE_MESH_LIGHT_CLIENT_PUBLISH_EVT; + break; + case BTC_BLE_MESH_EVT_LIGHTING_CLIENT_TIMEOUT: + act = ESP_BLE_MESH_LIGHT_CLIENT_TIMEOUT_EVT; + break; + default: + BT_ERR("%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_ble_mesh_lighting_client_callback(&cb_params, act); + return; +} + +void btc_ble_mesh_lighting_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) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_lighting_client_cb_evt_to_btc(opcode, + BTC_BLE_MESH_EVT_LIGHTING_CLIENT_PUBLISH, model, ctx, buf->data, buf->len); + return; +} + +void btc_ble_mesh_lighting_client_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_client_common_param_t *params = NULL; + btc_ble_mesh_lighting_client_args_t *arg = NULL; + esp_ble_mesh_light_client_cb_param_t cb = {0}; + bt_mesh_client_common_param_t common = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_lighting_client_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_LIGHTING_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_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + 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; + + cb.params = arg->light_client_get_state.params; + cb.error_code = bt_mesh_light_client_get_state(&common, + (void *)arg->light_client_get_state.get_state, (void *)&cb.status_cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_lighting_client_callback(&cb, ESP_BLE_MESH_LIGHT_CLIENT_GET_STATE_EVT); + } + break; + } + case BTC_BLE_MESH_ACT_LIGHTING_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_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + 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; + + cb.params = arg->light_client_set_state.params; + cb.error_code = bt_mesh_light_client_set_state(&common, + (void *)arg->light_client_set_state.set_state, (void *)&cb.status_cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_lighting_client_callback(&cb, ESP_BLE_MESH_LIGHT_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_lighting_client_arg_deep_free(msg); + return; +} + +void btc_ble_mesh_lighting_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_light_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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_lighting_client_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_lighting_client_free_req_data(msg); + return; +} + +/* Lighting Server Models related functions */ + +static inline void btc_ble_mesh_lighting_server_cb_to_app( + esp_ble_mesh_lighting_server_cb_event_t event, + esp_ble_mesh_lighting_server_cb_param_t *param) +{ + esp_ble_mesh_lighting_server_cb_t btc_ble_mesh_cb = + (esp_ble_mesh_lighting_server_cb_t)btc_profile_cb_get(BTC_PID_LIGHTING_SERVER); + if (btc_ble_mesh_cb) { + btc_ble_mesh_cb(event, param); + } +} + +static void btc_ble_mesh_lighting_server_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_lighting_server_cb_param_t *p_dest_data = (esp_ble_mesh_lighting_server_cb_param_t *)p_dest; + esp_ble_mesh_lighting_server_cb_param_t *p_src_data = (esp_ble_mesh_lighting_server_cb_param_t *)p_src; + u16_t length = 0U; + + if (!msg || !p_src_data || !p_dest_data) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_LIGHTING_SERVER_STATE_CHANGE_EVT: + if (p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET || + p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK) { + if (p_src_data->value.state_change.lc_property_set.property_value) { + length = p_src_data->value.state_change.lc_property_set.property_value->len; + p_dest_data->value.state_change.lc_property_set.property_value = bt_mesh_alloc_buf(length); + if (p_dest_data->value.state_change.lc_property_set.property_value == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.state_change.lc_property_set.property_value, + p_src_data->value.state_change.lc_property_set.property_value->data, + p_src_data->value.state_change.lc_property_set.property_value->len); + } + } + break; + case ESP_BLE_MESH_LIGHTING_SERVER_RECV_SET_MSG_EVT: + if (p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET || + p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK) { + if (p_src_data->value.set.lc_property.property_value) { + length = p_src_data->value.set.lc_property.property_value->len; + p_dest_data->value.set.lc_property.property_value = bt_mesh_alloc_buf(length); + if (p_dest_data->value.set.lc_property.property_value == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.set.lc_property.property_value, + p_src_data->value.set.lc_property.property_value->data, + p_src_data->value.set.lc_property.property_value->len); + } + } + break; + case ESP_BLE_MESH_LIGHTING_SERVER_RECV_STATUS_MSG_EVT: + if (p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_STATUS) { + if (p_src_data->value.status.sensor_status.data) { + length = p_src_data->value.status.sensor_status.data->len; + p_dest_data->value.status.sensor_status.data = bt_mesh_alloc_buf(length); + if (p_dest_data->value.status.sensor_status.data == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.status.sensor_status.data, + p_src_data->value.status.sensor_status.data->data, + p_src_data->value.status.sensor_status.data->len); + } + } + break; + default: + break; + } +} + +static void btc_ble_mesh_lighting_server_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_lighting_server_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_lighting_server_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_LIGHTING_SERVER_STATE_CHANGE_EVT: + if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET || + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK) { + bt_mesh_free_buf(arg->value.state_change.lc_property_set.property_value); + } + break; + case ESP_BLE_MESH_LIGHTING_SERVER_RECV_SET_MSG_EVT: + if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET || + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK) { + bt_mesh_free_buf(arg->value.set.lc_property.property_value); + } + break; + case ESP_BLE_MESH_LIGHTING_SERVER_RECV_STATUS_MSG_EVT: + if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_STATUS) { + bt_mesh_free_buf(arg->value.status.sensor_status.data); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_lighting_server_callback(esp_ble_mesh_lighting_server_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_LIGHTING_SERVER)) { + return; + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_LIGHTING_SERVER; + msg.act = act; + + btc_transfer_context( + &msg, cb_params, sizeof(esp_ble_mesh_lighting_server_cb_param_t), btc_ble_mesh_lighting_server_copy_req_data); +} + +void bt_mesh_lighting_server_cb_evt_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_lighting_server_cb_param_t cb_params = {0}; + size_t length = 0U; + uint8_t act = 0U; + + if (model == NULL || ctx == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE: + act = ESP_BLE_MESH_LIGHTING_SERVER_STATE_CHANGE_EVT; + break; + case BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_GET_MSG: + act = ESP_BLE_MESH_LIGHTING_SERVER_RECV_GET_MSG_EVT; + break; + case BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG: + act = ESP_BLE_MESH_LIGHTING_SERVER_RECV_SET_MSG_EVT; + break; + case BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_STATUS_MSG: + act = ESP_BLE_MESH_LIGHTING_SERVER_RECV_STATUS_MSG_EVT; + break; + default: + BT_ERR("%s, Unknown Lighting Server event type", __func__); + 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.value)) ? len : sizeof(cb_params.value); + memcpy(&cb_params.value, val, length); + } + + btc_ble_mesh_lighting_server_callback(&cb_params, act); + return; +} + +void btc_ble_mesh_lighting_server_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_lighting_server_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_lighting_server_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_LIGHTING_SERVER_EVT_MAX) { + btc_ble_mesh_lighting_server_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_lighting_server_free_req_data(msg); + return; +} \ No newline at end of file diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_prov.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_prov.c new file mode 100644 index 0000000000..28e64b767e --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_prov.c @@ -0,0 +1,2024 @@ +// Copyright 2017-2019 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/semphr.h" + +#include "mesh_proxy.h" +#include "mesh.h" +#include "access.h" +#include "prov.h" +#include "proxy_server.h" +#include "proxy_client.h" +#include "provisioner_prov.h" +#include "provisioner_main.h" + +#include "cfg_cli.h" +#include "health_cli.h" +#include "cfg_srv.h" +#include "health_srv.h" +#include "generic_client.h" +#include "lighting_client.h" +#include "sensor_client.h" +#include "time_scene_client.h" +#include "client_common.h" +#include "state_binding.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" + +static inline void btc_ble_mesh_prov_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_ble_mesh_cb = + (esp_ble_mesh_prov_cb_t)btc_profile_cb_get(BTC_PID_PROV); + if (btc_ble_mesh_cb) { + btc_ble_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_ble_mesh_cb = + (esp_ble_mesh_model_cb_t)btc_profile_cb_get(BTC_PID_MODEL); + if (btc_ble_mesh_cb) { + btc_ble_mesh_cb(event, param); + } +} + +void btc_ble_mesh_prov_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_mesh_prov_args_t *dst = (btc_ble_mesh_prov_args_t *)p_dest; + btc_ble_mesh_prov_args_t *src = (btc_ble_mesh_prov_args_t *)p_src; + + if (!msg || !dst || !src) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_PROXY_CLIENT_ADD_FILTER_ADDR: + BT_DBG("%s, BTC_BLE_MESH_ACT_PROXY_CLIENT_ADD_FILTER_ADDR", __func__); + dst->proxy_client_add_filter_addr.addr = (uint16_t *)bt_mesh_calloc(src->proxy_client_add_filter_addr.addr_num << 1); + if (dst->proxy_client_add_filter_addr.addr) { + memcpy(dst->proxy_client_add_filter_addr.addr, src->proxy_client_add_filter_addr.addr, + src->proxy_client_add_filter_addr.addr_num << 1); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + case BTC_BLE_MESH_ACT_PROXY_CLIENT_REMOVE_FILTER_ADDR: + BT_DBG("%s, BTC_BLE_MESH_ACT_PROXY_CLIENT_REMOVE_FILTER_ADDR", __func__); + dst->proxy_client_remove_filter_addr.addr = bt_mesh_calloc(src->proxy_client_remove_filter_addr.addr_num << 1); + if (dst->proxy_client_remove_filter_addr.addr) { + memcpy(dst->proxy_client_remove_filter_addr.addr, src->proxy_client_remove_filter_addr.addr, + src->proxy_client_remove_filter_addr.addr_num << 1); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + case BTC_BLE_MESH_ACT_PROVISIONER_STORE_NODE_COMP_DATA: + BT_DBG("%s, BTC_BLE_MESH_ACT_PROVISIONER_STORE_NODE_COMP_DATA", __func__); + dst->store_node_comp_data.data = bt_mesh_calloc(src->store_node_comp_data.length); + if (dst->store_node_comp_data.data) { + memcpy(dst->store_node_comp_data.data, src->store_node_comp_data.data, src->store_node_comp_data.length); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + default: + BT_DBG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_prov_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_prov_args_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + arg = (btc_ble_mesh_prov_args_t *)(msg->arg); + + switch (msg->act) { + case BTC_BLE_MESH_ACT_PROXY_CLIENT_ADD_FILTER_ADDR: + if (arg->proxy_client_add_filter_addr.addr) { + bt_mesh_free(arg->proxy_client_add_filter_addr.addr); + } + break; + case BTC_BLE_MESH_ACT_PROXY_CLIENT_REMOVE_FILTER_ADDR: + if (arg->proxy_client_remove_filter_addr.addr) { + bt_mesh_free(arg->proxy_client_remove_filter_addr.addr); + } + break; + case BTC_BLE_MESH_ACT_PROVISIONER_STORE_NODE_COMP_DATA: + if (arg->store_node_comp_data.data) { + bt_mesh_free(arg->store_node_comp_data.data); + } + break; + default: + break; + } +} + +void btc_ble_mesh_model_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) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case BTC_BLE_MESH_ACT_SERVER_MODEL_SEND: + case BTC_BLE_MESH_ACT_CLIENT_MODEL_SEND: { + BT_DBG("%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 *)bt_mesh_malloc(src->model_send.length) : NULL; + dst->model_send.ctx = bt_mesh_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 { + BT_ERR("%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 { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + case BTC_BLE_MESH_ACT_SERVER_MODEL_UPDATE_STATE: + BT_DBG("%s, BTC_BLE_MESH_ACT_SERVER_MODEL_UPDATE_STATE", __func__); + dst->model_update_state.value = bt_mesh_malloc(sizeof(esp_ble_mesh_server_state_value_t)); + if (dst->model_update_state.value) { + memcpy(dst->model_update_state.value, src->model_update_state.value, + sizeof(esp_ble_mesh_server_state_value_t)); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + default: + BT_DBG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +static void btc_ble_mesh_model_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_model_args_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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) { + bt_mesh_free(arg->model_send.data); + } + if (arg->model_send.ctx) { + bt_mesh_free(arg->model_send.ctx); + } + break; + case BTC_BLE_MESH_ACT_SERVER_MODEL_UPDATE_STATE: + if (arg->model_update_state.value) { + bt_mesh_free(arg->model_update_state.value); + } + break; + default: + break; + } + + return; +} + +static void btc_ble_mesh_model_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) { + BT_ERR("%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 = bt_mesh_malloc(sizeof(esp_ble_mesh_msg_ctx_t)); + p_dest_data->model_operation.msg = p_src_data->model_operation.length ? (uint8_t *)bt_mesh_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 { + BT_ERR("%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 { + BT_ERR("%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 = bt_mesh_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 *)bt_mesh_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 { + BT_ERR("%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 { + BT_ERR("%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 = bt_mesh_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 { + BT_ERR("%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 = bt_mesh_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 { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + } + break; + } + default: + break; + } +} + +static void btc_ble_mesh_model_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_model_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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) { + bt_mesh_free(arg->model_operation.msg); + } + if (arg->model_operation.ctx) { + bt_mesh_free(arg->model_operation.ctx); + } + break; + } + case ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT: { + if (arg->client_recv_publish_msg.msg) { + bt_mesh_free(arg->client_recv_publish_msg.msg); + } + if (arg->client_recv_publish_msg.ctx) { + bt_mesh_free(arg->client_recv_publish_msg.ctx); + } + break; + } + case ESP_BLE_MESH_MODEL_SEND_COMP_EVT: { + if (arg->model_send_comp.ctx) { + bt_mesh_free(arg->model_send_comp.ctx); + } + break; + } + case ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT: { + if (arg->client_send_timeout.ctx) { + bt_mesh_free(arg->client_send_timeout.ctx); + } + break; + } + default: + break; + } +} + +static bt_status_t btc_ble_mesh_model_callback(esp_ble_mesh_model_cb_param_t *param, uint8_t act) +{ + btc_msg_t msg = {0}; + bt_status_t ret = BT_STATUS_SUCCESS; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_MODEL)) { + return BT_STATUS_SUCCESS; + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_MODEL; + msg.act = act; + + ret = btc_transfer_context(&msg, param, + sizeof(esp_ble_mesh_model_cb_param_t), btc_ble_mesh_model_copy_req_data); + if (ret != BT_STATUS_SUCCESS) { + BT_ERR("%s, btc_transfer_context failed", __func__); + } + return ret; +} + +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}; + + mesh_param.model_operation.opcode = ctx->recv_op; + 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; + + btc_ble_mesh_model_callback(&mesh_param, ESP_BLE_MESH_MODEL_OPERATION_EVT); + 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_node_t *node = NULL; + + if (!model || !model->user_data || !ctx || !buf) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_client_model_lock(); + + node = bt_mesh_is_client_recv_publish_msg(model, ctx, buf, false); + if (node == NULL) { + mesh_param.client_recv_publish_msg.opcode = ctx->recv_op; + 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; + btc_ble_mesh_model_callback(&mesh_param, ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT); + } else { + mesh_param.model_operation.opcode = ctx->recv_op; + 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; + if (!k_delayed_work_free(&node->timer)) { + bt_mesh_client_free_node(node); + btc_ble_mesh_model_callback(&mesh_param, ESP_BLE_MESH_MODEL_OPERATION_EVT); + } + } + + bt_mesh_client_model_unlock(); + return; +} + +static void btc_ble_mesh_client_model_timeout_cb(struct k_work *work) +{ + esp_ble_mesh_model_cb_param_t mesh_param = {0}; + struct k_delayed_work *timer = NULL; + bt_mesh_client_node_t *node = NULL; + struct bt_mesh_msg_ctx ctx = {0}; + + bt_mesh_client_model_lock(); + + timer = CONTAINER_OF(work, struct k_delayed_work, work); + + if (timer && !k_delayed_work_free(timer)) { + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (node) { + memcpy(&ctx, &node->ctx, sizeof(ctx)); + mesh_param.client_send_timeout.opcode = node->opcode; + mesh_param.client_send_timeout.model = (esp_ble_mesh_model_t *)ctx.model; + mesh_param.client_send_timeout.ctx = (esp_ble_mesh_msg_ctx_t *)&ctx; + bt_mesh_client_free_node(node); + btc_ble_mesh_model_callback(&mesh_param, ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT); + } + } + + bt_mesh_client_model_unlock(); + 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}; + + 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; + + btc_ble_mesh_model_callback(&mesh_param, ESP_BLE_MESH_MODEL_SEND_COMP_EVT); + 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}; + + mesh_param.model_publish_comp.err_code = err; + mesh_param.model_publish_comp.model = model; + + btc_ble_mesh_model_callback(&mesh_param, ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT); + return; +} + +static int btc_ble_mesh_model_publish_update(struct bt_mesh_model *mod) +{ + esp_ble_mesh_model_cb_param_t mesh_param = {0}; + bt_status_t ret = BT_STATUS_SUCCESS; + + BT_DBG("%s", __func__); + + mesh_param.model_publish_update.model = (esp_ble_mesh_model_t *)mod; + + ret = btc_ble_mesh_model_callback(&mesh_param, ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT); + return (ret == BT_STATUS_SUCCESS) ? 0 : -1; +} + +static void btc_ble_mesh_server_model_update_state_comp_cb(esp_ble_mesh_model_t *model, + esp_ble_mesh_server_state_type_t type, int err) +{ + esp_ble_mesh_model_cb_param_t mesh_param = {0}; + + mesh_param.server_model_update_state.err_code = err; + mesh_param.server_model_update_state.model = model; + mesh_param.server_model_update_state.type = type; + + btc_ble_mesh_model_callback(&mesh_param, ESP_BLE_MESH_SERVER_MODEL_UPDATE_STATE_COMP_EVT); + return; +} + +static bt_status_t btc_ble_mesh_prov_callback(esp_ble_mesh_prov_cb_param_t *param, uint8_t act) +{ + btc_msg_t msg = {0}; + bt_status_t ret = BT_STATUS_SUCCESS; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_PROV)) { + return BT_STATUS_SUCCESS; + } + + 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) { + BT_ERR("%s, btc_transfer_context failed", __func__); + } + return ret; +} + +#if CONFIG_BLE_MESH_NODE +static void btc_ble_mesh_oob_pub_key_cb(void) +{ + BT_DBG("%s", __func__); + + btc_ble_mesh_prov_callback(NULL, ESP_BLE_MESH_NODE_PROV_OOB_PUB_KEY_EVT); + return; +} + +static int btc_ble_mesh_output_number_cb(bt_mesh_output_action_t act, u32_t num) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + bt_status_t ret = BT_STATUS_SUCCESS; + + BT_DBG("%s", __func__); + + mesh_param.node_prov_output_num.action = (esp_ble_mesh_output_action_t)act; + mesh_param.node_prov_output_num.number = num; + + ret = btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_NODE_PROV_OUTPUT_NUMBER_EVT); + return (ret == BT_STATUS_SUCCESS) ? 0 : -1; +} + +static int btc_ble_mesh_output_string_cb(const char *str) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + bt_status_t ret = BT_STATUS_SUCCESS; + + BT_DBG("%s", __func__); + + strncpy(mesh_param.node_prov_output_str.string, str, strlen(str)); + + ret = btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT); + return (ret == BT_STATUS_SUCCESS) ? 0 : -1; +} + +static int btc_ble_mesh_input_cb(bt_mesh_input_action_t act, u8_t size) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + bt_status_t ret = BT_STATUS_SUCCESS; + + BT_DBG("%s", __func__); + + mesh_param.node_prov_input.action = (esp_ble_mesh_input_action_t)act; + mesh_param.node_prov_input.size = size; + + ret = btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_NODE_PROV_INPUT_EVT); + return (ret == BT_STATUS_SUCCESS) ? 0 : -1; +} + +static void btc_ble_mesh_link_open_cb(bt_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + BT_DBG("%s", __func__); + + mesh_param.node_prov_link_open.bearer = (esp_ble_mesh_prov_bearer_t)bearer; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_NODE_PROV_LINK_OPEN_EVT); + return; +} + +static void btc_ble_mesh_link_close_cb(bt_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + BT_DBG("%s", __func__); + + mesh_param.node_prov_link_close.bearer = (esp_ble_mesh_prov_bearer_t)bearer; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_NODE_PROV_LINK_CLOSE_EVT); + return; +} + +static void btc_ble_mesh_complete_cb(u16_t net_idx, const u8_t net_key[16], u16_t addr, u8_t flags, u32_t iv_index) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + BT_DBG("%s", __func__); + + mesh_param.node_prov_complete.net_idx = net_idx; + memcpy(mesh_param.node_prov_complete.net_key, net_key, 16); + mesh_param.node_prov_complete.addr = addr; + mesh_param.node_prov_complete.flags = flags; + mesh_param.node_prov_complete.iv_index = iv_index; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT); + return; +} + +static void btc_ble_mesh_reset_cb(void) +{ + BT_DBG("%s", __func__); + + btc_ble_mesh_prov_callback(NULL, ESP_BLE_MESH_NODE_PROV_RESET_EVT); + return; +} +#endif /* CONFIG_BLE_MESH_NODE */ + +static void btc_ble_mesh_prov_register_complete_cb(int err_code) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + BT_DBG("%s", __func__); + + mesh_param.prov_register_comp.err_code = err_code; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROV_REGISTER_COMP_EVT); + return; +} + +static void btc_ble_mesh_prov_set_complete_cb(esp_ble_mesh_prov_cb_param_t *param, uint8_t act) +{ + BT_DBG("%s", __func__); + + btc_ble_mesh_prov_callback(param, act); + return; +} + +#if CONFIG_BLE_MESH_PROVISIONER +static void btc_ble_mesh_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, s8_t rssi) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + BT_DBG("%s", __func__); + + if (addr == NULL || dev_uuid == NULL || + (bearer != BLE_MESH_PROV_ADV && bearer != BLE_MESH_PROV_GATT)) { + BT_ERR("%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, BLE_MESH_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; + mesh_param.provisioner_recv_unprov_adv_pkt.rssi = rssi; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT); + return; +} + +static int btc_ble_mesh_provisioner_prov_read_oob_pub_key_cb(u8_t link_idx) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + bt_status_t ret = BT_STATUS_SUCCESS; + + BT_DBG("%s", __func__); + + mesh_param.provisioner_prov_read_oob_pub_key.link_idx = link_idx; + + ret = btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROVISIONER_PROV_READ_OOB_PUB_KEY_EVT); + return (ret == BT_STATUS_SUCCESS) ? 0 : -1; +} + +static int btc_ble_mesh_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}; + bt_status_t ret = BT_STATUS_SUCCESS; + + BT_DBG("%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; + + ret = btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROVISIONER_PROV_INPUT_EVT); + return (ret == BT_STATUS_SUCCESS) ? 0 : -1; +} + +static int btc_ble_mesh_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}; + bt_status_t ret = BT_STATUS_SUCCESS; + + BT_DBG("%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); + } + + ret = btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROVISIONER_PROV_OUTPUT_EVT); + return (ret == BT_STATUS_SUCCESS) ? 0 : -1; +} + +static void btc_ble_mesh_provisioner_link_open_cb(bt_mesh_prov_bearer_t bearer) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + BT_DBG("%s", __func__); + + mesh_param.provisioner_prov_link_open.bearer = (esp_ble_mesh_prov_bearer_t)bearer; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT); + return; +} + +static void btc_ble_mesh_provisioner_link_close_cb(bt_mesh_prov_bearer_t bearer, u8_t reason) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + BT_DBG("%s", __func__); + + mesh_param.provisioner_prov_link_close.bearer = (esp_ble_mesh_prov_bearer_t)bearer; + mesh_param.provisioner_prov_link_close.reason = reason; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT); + return; +} + +static void btc_ble_mesh_provisioner_prov_complete_cb( + u16_t 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}; + + BT_DBG("%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)); + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT); + return; +} + +esp_ble_mesh_node_t *btc_ble_mesh_provisioner_get_node_with_uuid(const uint8_t uuid[16]) +{ + return (esp_ble_mesh_node_t *)bt_mesh_provisioner_get_node_with_uuid(uuid); +} + +esp_ble_mesh_node_t *btc_ble_mesh_provisioner_get_node_with_addr(uint16_t unicast_addr) +{ + return (esp_ble_mesh_node_t *)bt_mesh_provisioner_get_node_with_addr(unicast_addr); +} + +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +static void btc_ble_mesh_heartbeat_msg_recv_cb(u8_t hops, u16_t feature) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + BT_DBG("%s", __func__); + + mesh_param.heartbeat_msg_recv.hops = hops; + mesh_param.heartbeat_msg_recv.feature = feature; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_HEARTBEAT_MESSAGE_RECV_EVT); + return; +} + +#if CONFIG_BLE_MESH_LOW_POWER +static void btc_ble_mesh_lpn_cb(u16_t friend_addr, bool established) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + u8_t act = 0U; + + BT_DBG("%s", __func__); + + if (established) { + mesh_param.lpn_friendship_establish.friend_addr = friend_addr; + act = ESP_BLE_MESH_LPN_FRIENDSHIP_ESTABLISH_EVT; + } else { + mesh_param.lpn_friendship_terminate.friend_addr = friend_addr; + act = ESP_BLE_MESH_LPN_FRIENDSHIP_TERMINATE_EVT; + } + + btc_ble_mesh_prov_callback(&mesh_param, act); + return; +} +#endif /* CONFIG_BLE_MESH_LOW_POWER */ + +#if CONFIG_BLE_MESH_FRIEND +void btc_ble_mesh_friend_cb(bool establish, u16_t lpn_addr, u8_t reason) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + u8_t act = 0U; + + BT_DBG("%s", __func__); + + if (!BLE_MESH_ADDR_IS_UNICAST(lpn_addr)) { + BT_ERR("%s, Not a unicast address", __func__); + return; + } + + if (establish) { + mesh_param.frnd_friendship_establish.lpn_addr = lpn_addr; + act = ESP_BLE_MESH_FRIEND_FRIENDSHIP_ESTABLISH_EVT; + } else { + mesh_param.frnd_friendship_terminate.lpn_addr = lpn_addr; + mesh_param.frnd_friendship_terminate.reason = reason; + act = ESP_BLE_MESH_FRIEND_FRIENDSHIP_TERMINATE_EVT; + } + + btc_ble_mesh_prov_callback(&mesh_param, act); + return; +} +#endif /* CONFIG_BLE_MESH_FRIEND */ + +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT +static void btc_ble_mesh_proxy_client_adv_recv_cb(const bt_mesh_addr_t *addr, + u8_t type, bt_mesh_proxy_adv_ctx_t *ctx, s8_t rssi) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + if (!addr || !ctx || type != BLE_MESH_PROXY_ADV_NET_ID) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + BT_DBG("%s", __func__); + + mesh_param.proxy_client_recv_adv_pkt.addr_type = addr->type; + memcpy(mesh_param.proxy_client_recv_adv_pkt.addr, addr->val, BD_ADDR_LEN); + mesh_param.proxy_client_recv_adv_pkt.net_idx = ctx->net_id.net_idx; + memcpy(mesh_param.proxy_client_recv_adv_pkt.net_id, ctx->net_id.net_id, 8); + mesh_param.proxy_client_recv_adv_pkt.rssi = rssi; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROXY_CLIENT_RECV_ADV_PKT_EVT); + return; +} + +static void btc_ble_mesh_proxy_client_connect_cb(const bt_mesh_addr_t *addr, + u8_t conn_handle, u16_t net_idx) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + if (!addr || conn_handle >= BLE_MESH_MAX_CONN) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + BT_DBG("%s", __func__); + + mesh_param.proxy_client_connected.addr_type = addr->type; + memcpy(mesh_param.proxy_client_connected.addr, addr->val, BD_ADDR_LEN); + mesh_param.proxy_client_connected.conn_handle = conn_handle; + mesh_param.proxy_client_connected.net_idx = net_idx; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROXY_CLIENT_CONNECTED_EVT); + return; +} + +static void btc_ble_mesh_proxy_client_disconnect_cb(const bt_mesh_addr_t *addr, + u8_t conn_handle, u16_t net_idx, u8_t reason) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + if (!addr || conn_handle >= BLE_MESH_MAX_CONN) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + BT_DBG("%s", __func__); + + mesh_param.proxy_client_disconnected.addr_type = addr->type; + memcpy(mesh_param.proxy_client_disconnected.addr, addr->val, BD_ADDR_LEN); + mesh_param.proxy_client_disconnected.conn_handle = conn_handle; + mesh_param.proxy_client_disconnected.net_idx = net_idx; + mesh_param.proxy_client_disconnected.reason = reason; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROXY_CLIENT_DISCONNECTED_EVT); + return; +} + +static void btc_ble_mesh_proxy_client_filter_status_recv_cb(u8_t conn_handle, + u16_t src, u16_t net_idx, u8_t filter_type, u16_t list_size) +{ + esp_ble_mesh_prov_cb_param_t mesh_param = {0}; + + if (conn_handle >= BLE_MESH_MAX_CONN) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + BT_DBG("%s", __func__); + + mesh_param.proxy_client_recv_filter_status.conn_handle = conn_handle; + mesh_param.proxy_client_recv_filter_status.server_addr = src; + mesh_param.proxy_client_recv_filter_status.net_idx = net_idx; + mesh_param.proxy_client_recv_filter_status.filter_type = filter_type; + mesh_param.proxy_client_recv_filter_status.list_size = list_size; + + btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_PROXY_CLIENT_RECV_FILTER_STATUS_EVT); + return; +} +#endif /* CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ + +int btc_ble_mesh_deinit(esp_ble_mesh_deinit_param_t *param) +{ + return bt_mesh_deinit((struct bt_mesh_deinit_param *)param); +} + +int btc_ble_mesh_client_model_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); +} + +int btc_ble_mesh_client_model_deinit(esp_ble_mesh_model_t *model) +{ + return bt_mesh_client_deinit((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(); +} + +u16_t btc_ble_mesh_provisioner_get_prov_node_count(void) +{ + return bt_mesh_provisioner_get_prov_node_count(); +} + +/* 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[]; +/* Generic Server Models */ +extern const struct bt_mesh_model_op gen_onoff_srv_op[]; +extern const struct bt_mesh_model_op gen_level_srv_op[]; +extern const struct bt_mesh_model_op gen_def_trans_time_srv_op[]; +extern const struct bt_mesh_model_op gen_power_onoff_srv_op[]; +extern const struct bt_mesh_model_op gen_power_onoff_setup_srv_op[]; +extern const struct bt_mesh_model_op gen_power_level_srv_op[]; +extern const struct bt_mesh_model_op gen_power_level_setup_srv_op[]; +extern const struct bt_mesh_model_op gen_battery_srv_op[]; +extern const struct bt_mesh_model_op gen_location_srv_op[]; +extern const struct bt_mesh_model_op gen_location_setup_srv_op[]; +extern const struct bt_mesh_model_op gen_user_prop_srv_op[]; +extern const struct bt_mesh_model_op gen_admin_prop_srv_op[]; +extern const struct bt_mesh_model_op gen_manu_prop_srv_op[]; +extern const struct bt_mesh_model_op gen_client_prop_srv_op[]; +/* Lighting Server Models */ +extern const struct bt_mesh_model_op light_lightness_srv_op[]; +extern const struct bt_mesh_model_op light_lightness_setup_srv_op[]; +extern const struct bt_mesh_model_op light_ctl_srv_op[]; +extern const struct bt_mesh_model_op light_ctl_setup_srv_op[]; +extern const struct bt_mesh_model_op light_ctl_temp_srv_op[]; +extern const struct bt_mesh_model_op light_hsl_srv_op[]; +extern const struct bt_mesh_model_op light_hsl_hue_srv_op[]; +extern const struct bt_mesh_model_op light_hsl_sat_srv_op[]; +extern const struct bt_mesh_model_op light_hsl_setup_srv_op[]; +extern const struct bt_mesh_model_op light_xyl_srv_op[]; +extern const struct bt_mesh_model_op light_xyl_setup_srv_op[]; +extern const struct bt_mesh_model_op light_lc_srv_op[]; +extern const struct bt_mesh_model_op light_lc_setup_srv_op[]; +/* Time and Scenes Server Models */ +extern const struct bt_mesh_model_op time_srv_op[]; +extern const struct bt_mesh_model_op time_setup_srv_op[]; +extern const struct bt_mesh_model_op scene_srv_op[]; +extern const struct bt_mesh_model_op scene_setup_srv_op[]; +extern const struct bt_mesh_model_op scheduler_srv_op[]; +extern const struct bt_mesh_model_op scheduler_setup_srv_op[]; +/* Sensor Server Models */ +extern const struct bt_mesh_model_op sensor_srv_op[]; +extern const struct bt_mesh_model_op sensor_setup_srv_op[]; + +static void btc_ble_mesh_model_op_add(esp_ble_mesh_model_t *model) +{ + esp_ble_mesh_model_op_t *op = NULL; + + if (!model) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + /* For SIG client and server models, model->op will be NULL and initialized here. + * For vendor models whose opcode is 3 bytes, model->op will be initialized here. + */ + 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; + struct bt_mesh_cfg_srv *srv = (struct bt_mesh_cfg_srv *)model->user_data; + if (srv) { + srv->hb_sub.func = btc_ble_mesh_heartbeat_msg_recv_cb; + } + 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_ble_mesh_config_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_HEALTH_SRV: { + model->op = (esp_ble_mesh_model_op_t *)bt_mesh_health_srv_op; + struct bt_mesh_health_srv *srv = (struct bt_mesh_health_srv *)model->user_data; + if (srv) { + srv->cb.fault_clear = btc_ble_mesh_health_server_fault_clear; + srv->cb.fault_test = btc_ble_mesh_health_server_fault_test; + srv->cb.attn_on = btc_ble_mesh_health_server_attention_on; + srv->cb.attn_off = btc_ble_mesh_health_server_attention_off; + } + 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_ble_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_client_t *cli = (bt_mesh_gen_onoff_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_client_t *cli = (bt_mesh_gen_level_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_client_t *cli = (bt_mesh_gen_def_trans_time_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_client_t *cli = (bt_mesh_gen_power_onoff_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_client_t *cli = (bt_mesh_gen_power_level_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_client_t *cli = (bt_mesh_gen_battery_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_client_t *cli = (bt_mesh_gen_location_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_client_t *cli = (bt_mesh_gen_property_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_client_t *cli = (bt_mesh_light_lightness_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_mesh_lighting_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_client_t *cli = (bt_mesh_light_ctl_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_mesh_lighting_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_client_t *cli = (bt_mesh_light_hsl_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_mesh_lighting_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_client_t *cli = (bt_mesh_light_xyl_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_mesh_lighting_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_client_t *cli = (bt_mesh_light_lc_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_mesh_lighting_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_ble_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_client_t *cli = (bt_mesh_time_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_scene_client_t *cli = (bt_mesh_scene_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_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_scheduler_client_t *cli = (bt_mesh_scheduler_client_t *)model->user_data; + if (cli != NULL) { + cli->publish_status = btc_ble_mesh_time_scene_client_publish_callback; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_ONOFF_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_onoff_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_LEVEL_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_level_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_def_trans_time_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_power_onoff_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_power_onoff_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_power_level_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_power_level_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_BATTERY_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_battery_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_LOCATION_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_location_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_USER_PROP_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_user_prop_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_admin_prop_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_manu_prop_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_client_prop_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)gen_location_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_lightness_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_lightness_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_CTL_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_ctl_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_ctl_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_ctl_temp_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_HSL_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_hsl_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_hsl_hue_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_hsl_sat_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_hsl_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_XYL_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_xyl_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_xyl_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_LC_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_lc_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_LIGHT_LC_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)light_lc_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_TIME_SRV: + model->op = (esp_ble_mesh_model_op_t *)time_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_TIME_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)time_setup_srv_op; + if (model->pub) { + /* Time Setup Server model does not support subscribing nor publishing. */ + BT_ERR("%s, Time Setup Server shall not support publication", __func__); + return; + } + break; + case BLE_MESH_MODEL_ID_SCENE_SRV: + model->op = (esp_ble_mesh_model_op_t *)scene_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_SCENE_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)scene_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_SCHEDULER_SRV: + model->op = (esp_ble_mesh_model_op_t *)scheduler_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)scheduler_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_SENSOR_SRV: + model->op = (esp_ble_mesh_model_op_t *)sensor_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + case BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV: + model->op = (esp_ble_mesh_model_op_t *)sensor_setup_srv_op; + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; + } + break; + default: + goto add_model_op; + } + return; + +add_model_op: + if (model->pub) { + model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_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_ble_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 = 0U; + + if (!msg) { + BT_ERR("%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 = 0; + 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_ble_mesh_prov_register_complete_cb(err_code); + return; + } + btc_ble_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_ble_mesh_prov_register_complete_cb(err_code); + return; + } + btc_ble_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_ble_mesh_oob_pub_key_cb; + arg->mesh_init.prov->output_num_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_output_number_cb; + arg->mesh_init.prov->output_str_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_output_string_cb; + arg->mesh_init.prov->input_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_input_cb; + arg->mesh_init.prov->link_open_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_link_open_cb; + arg->mesh_init.prov->link_close_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_link_close_cb; + arg->mesh_init.prov->complete_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_complete_cb; + arg->mesh_init.prov->reset_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_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_ble_mesh_provisioner_prov_read_oob_pub_key_cb; + arg->mesh_init.prov->provisioner_prov_input = (esp_ble_mesh_cb_t)btc_ble_mesh_provisioner_prov_input_cb; + arg->mesh_init.prov->provisioner_prov_output = (esp_ble_mesh_cb_t)btc_ble_mesh_provisioner_prov_output_cb; + arg->mesh_init.prov->provisioner_link_open = (esp_ble_mesh_cb_t)btc_ble_mesh_provisioner_link_open_cb; + arg->mesh_init.prov->provisioner_link_close = (esp_ble_mesh_cb_t)btc_ble_mesh_provisioner_link_close_cb; + arg->mesh_init.prov->provisioner_prov_comp = (esp_ble_mesh_cb_t)btc_ble_mesh_provisioner_prov_complete_cb; + bt_mesh_provisioner_adv_pkt_cb_register(btc_ble_mesh_provisioner_recv_unprov_adv_pkt_cb); +#endif /* CONFIG_BLE_MESH_PROVISIONER */ +#if CONFIG_BLE_MESH_LOW_POWER + bt_mesh_lpn_set_cb(btc_ble_mesh_lpn_cb); +#endif /* CONFIG_BLE_MESH_LOW_POWER */ +#if CONFIG_BLE_MESH_FRIEND + bt_mesh_friend_set_cb(btc_ble_mesh_friend_cb); +#endif /* CONFIG_BLE_MESH_FRIEND */ +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT + bt_mesh_proxy_client_set_adv_recv_cb(btc_ble_mesh_proxy_client_adv_recv_cb); + bt_mesh_proxy_client_set_conn_cb(btc_ble_mesh_proxy_client_connect_cb); + bt_mesh_proxy_client_set_disconn_cb(btc_ble_mesh_proxy_client_disconnect_cb); + bt_mesh_proxy_client_set_filter_status_cb(btc_ble_mesh_proxy_client_filter_status_recv_cb); +#endif /* CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ + 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_ble_mesh_prov_register_complete_cb(err_code); + return; + } +#if CONFIG_BLE_MESH_NODE + case BTC_BLE_MESH_ACT_PROV_ENABLE: + BT_DBG("%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: + BT_DBG("%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: + BT_DBG("%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; +#endif /* CONFIG_BLE_MESH_NODE */ +#if (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_SERVER + 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_SERVER) + 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_SERVER */ +#endif /* (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || CONFIG_BLE_MESH_GATT_PROXY_SERVER */ +#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_provisioner_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_provisioner_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_provisioner_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_PROV_DEV_WITH_ADDR: + act = ESP_BLE_MESH_PROVISIONER_PROV_DEV_WITH_ADDR_COMP_EVT; + param.provisioner_prov_dev_with_addr_comp.err_code = + bt_mesh_provisioner_prov_device_with_addr(arg->provisioner_prov_dev_with_addr.uuid, + arg->provisioner_prov_dev_with_addr.addr, arg->provisioner_prov_dev_with_addr.addr_type, + arg->provisioner_prov_dev_with_addr.bearer, arg->provisioner_prov_dev_with_addr.oob_info, + arg->provisioner_prov_dev_with_addr.unicast_addr); + 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_STATIC_OOB_VAL: + act = ESP_BLE_MESH_PROVISIONER_SET_STATIC_OOB_VALUE_COMP_EVT; + param.provisioner_set_static_oob_val_comp.err_code = + bt_mesh_provisioner_set_static_oob_value( + arg->set_static_oob_val.value, arg->set_static_oob_val.length); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_SET_PRIMARY_ELEM_ADDR: + act = ESP_BLE_MESH_PROVISIONER_SET_PRIMARY_ELEM_ADDR_COMP_EVT; + param.provisioner_set_primary_elem_addr_comp.err_code = + bt_mesh_provisioner_set_primary_elem_addr(arg->set_primary_elem_addr.addr); + 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.node_index = arg->set_node_name.index; + param.provisioner_set_node_name_comp.err_code = + bt_mesh_provisioner_set_node_name(arg->set_node_name.index, arg->set_node_name.name); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_ADD_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.app_idx = arg->add_local_app_key.app_idx; + 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); + break; + } + case BTC_BLE_MESH_ACT_PROVISIONER_UPDATE_LOCAL_APP_KEY: + act = ESP_BLE_MESH_PROVISIONER_UPDATE_LOCAL_APP_KEY_COMP_EVT; + param.provisioner_update_app_key_comp.net_idx = arg->update_local_app_key.net_idx; + param.provisioner_update_app_key_comp.app_idx = arg->update_local_app_key.app_idx; + param.provisioner_update_app_key_comp.err_code = + bt_mesh_provisioner_local_app_key_update(arg->update_local_app_key.app_key, + arg->update_local_app_key.net_idx, arg->update_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.element_addr = arg->local_mod_app_bind.elem_addr; + param.provisioner_bind_app_key_to_model_comp.app_idx = arg->local_mod_app_bind.app_idx; + param.provisioner_bind_app_key_to_model_comp.company_id = arg->local_mod_app_bind.cid; + param.provisioner_bind_app_key_to_model_comp.model_id = arg->local_mod_app_bind.model_id; + 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.net_idx = arg->add_local_net_key.net_idx; + param.provisioner_add_net_key_comp.err_code = + bt_mesh_provisioner_local_net_key_add(net_key, &arg->add_local_net_key.net_idx); + break; + } + case BTC_BLE_MESH_ACT_PROVISIONER_UPDATE_LOCAL_NET_KEY: + act = ESP_BLE_MESH_PROVISIONER_UPDATE_LOCAL_NET_KEY_COMP_EVT; + param.provisioner_update_net_key_comp.net_idx = arg->update_local_net_key.net_idx; + param.provisioner_update_net_key_comp.err_code = + bt_mesh_provisioner_local_net_key_update(arg->update_local_net_key.net_key, + arg->update_local_net_key.net_idx); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_STORE_NODE_COMP_DATA: + act = ESP_BLE_MESH_PROVISIONER_STORE_NODE_COMP_DATA_COMP_EVT; + param.provisioner_store_node_comp_data_comp.addr = arg->store_node_comp_data.unicast_addr; + param.provisioner_store_node_comp_data_comp.err_code = + bt_mesh_provisioner_store_node_comp_data(arg->store_node_comp_data.unicast_addr, + arg->store_node_comp_data.data, arg->store_node_comp_data.length); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_DELETE_NODE_WITH_UUID: + act = ESP_BLE_MESH_PROVISIONER_DELETE_NODE_WITH_UUID_COMP_EVT; + memcpy(param.provisioner_delete_node_with_uuid_comp.uuid, arg->delete_node_with_uuid.uuid, 16); + param.provisioner_delete_node_with_uuid_comp.err_code = + bt_mesh_provisioner_delete_node_with_uuid(arg->delete_node_with_uuid.uuid); + break; + case BTC_BLE_MESH_ACT_PROVISIONER_DELETE_NODE_WITH_ADDR: + act = ESP_BLE_MESH_PROVISIONER_DELETE_NODE_WITH_ADDR_COMP_EVT; + param.provisioner_delete_node_with_addr_comp.unicast_addr = arg->delete_node_with_addr.unicast_addr; + param.provisioner_delete_node_with_addr_comp.err_code = + bt_mesh_provisioner_delete_node_with_addr(arg->delete_node_with_addr.unicast_addr); + 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 */ +#if CONFIG_BLE_MESH_LOW_POWER + case BTC_BLE_MESH_ACT_LPN_ENABLE: + act = ESP_BLE_MESH_LPN_ENABLE_COMP_EVT; + param.lpn_enable_comp.err_code = bt_mesh_lpn_set(true, false); + break; + case BTC_BLE_MESH_ACT_LPN_DISABLE: + act = ESP_BLE_MESH_LPN_DISABLE_COMP_EVT; + param.lpn_disable_comp.err_code = bt_mesh_lpn_set(false, arg->lpn_disable.force); + break; + case BTC_BLE_MESH_ACT_LPN_POLL: + act = ESP_BLE_MESH_LPN_POLL_COMP_EVT; + param.lpn_poll_comp.err_code = bt_mesh_lpn_poll(); + break; +#endif /* CONFIG_BLE_MESH_LOW_POWER */ +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT + case BTC_BLE_MESH_ACT_PROXY_CLIENT_CONNECT: + act = ESP_BLE_MESH_PROXY_CLIENT_CONNECT_COMP_EVT; + memcpy(param.proxy_client_connect_comp.addr, arg->proxy_client_connect.addr, BD_ADDR_LEN); + param.proxy_client_connect_comp.addr_type = arg->proxy_client_connect.addr_type; + param.proxy_client_connect_comp.net_idx = arg->proxy_client_connect.net_idx; + param.proxy_client_connect_comp.err_code = + bt_mesh_proxy_client_connect(arg->proxy_client_connect.addr, + arg->proxy_client_connect.addr_type, + arg->proxy_client_connect.net_idx); + break; + case BTC_BLE_MESH_ACT_PROXY_CLIENT_DISCONNECT: + act = ESP_BLE_MESH_PROXY_CLIENT_DISCONNECT_COMP_EVT; + param.proxy_client_disconnect_comp.conn_handle = arg->proxy_client_disconnect.conn_handle; + param.proxy_client_disconnect_comp.err_code = + bt_mesh_proxy_client_disconnect(arg->proxy_client_disconnect.conn_handle); + break; + case BTC_BLE_MESH_ACT_PROXY_CLIENT_SET_FILTER_TYPE: { + struct bt_mesh_proxy_cfg_pdu pdu = { + .opcode = BLE_MESH_PROXY_CFG_FILTER_SET, + .set.filter_type = arg->proxy_client_set_filter_type.filter_type, + }; + act = ESP_BLE_MESH_PROXY_CLIENT_SET_FILTER_TYPE_COMP_EVT; + param.proxy_client_set_filter_type_comp.conn_handle = arg->proxy_client_set_filter_type.conn_handle; + param.proxy_client_set_filter_type_comp.net_idx = arg->proxy_client_set_filter_type.net_idx; + param.proxy_client_set_filter_type_comp.err_code = + bt_mesh_proxy_client_send_cfg(arg->proxy_client_set_filter_type.conn_handle, + arg->proxy_client_set_filter_type.net_idx, &pdu); + break; + } + case BTC_BLE_MESH_ACT_PROXY_CLIENT_ADD_FILTER_ADDR: { + struct bt_mesh_proxy_cfg_pdu pdu = { + .opcode = BLE_MESH_PROXY_CFG_FILTER_ADD, + .add.addr = arg->proxy_client_add_filter_addr.addr, + .add.addr_num = arg->proxy_client_add_filter_addr.addr_num, + }; + act = ESP_BLE_MESH_PROXY_CLIENT_ADD_FILTER_ADDR_COMP_EVT; + param.proxy_client_add_filter_addr_comp.conn_handle = arg->proxy_client_add_filter_addr.conn_handle; + param.proxy_client_add_filter_addr_comp.net_idx = arg->proxy_client_add_filter_addr.net_idx; + param.proxy_client_add_filter_addr_comp.err_code = + bt_mesh_proxy_client_send_cfg(arg->proxy_client_add_filter_addr.conn_handle, + arg->proxy_client_add_filter_addr.net_idx, &pdu); + break; + } + case BTC_BLE_MESH_ACT_PROXY_CLIENT_REMOVE_FILTER_ADDR: { + struct bt_mesh_proxy_cfg_pdu pdu = { + .opcode = BLE_MESH_PROXY_CFG_FILTER_REMOVE, + .remove.addr = arg->proxy_client_remove_filter_addr.addr, + .remove.addr_num = arg->proxy_client_remove_filter_addr.addr_num, + }; + act = ESP_BLE_MESH_PROXY_CLIENT_REMOVE_FILTER_ADDR_COMP_EVT; + param.proxy_client_remove_filter_addr_comp.conn_handle = arg->proxy_client_remove_filter_addr.conn_handle; + param.proxy_client_remove_filter_addr_comp.net_idx = arg->proxy_client_remove_filter_addr.net_idx; + param.proxy_client_remove_filter_addr_comp.err_code = + bt_mesh_proxy_client_send_cfg(arg->proxy_client_remove_filter_addr.conn_handle, + arg->proxy_client_remove_filter_addr.net_idx, &pdu); + break; + } +#endif /* CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ + default: + BT_WARN("%s, Invalid msg->act %d", __func__, msg->act); + return; + } + + /* Callback operation completion events */ + btc_ble_mesh_prov_set_complete_cb(¶m, act); + + if (msg->arg) { + btc_ble_mesh_prov_arg_deep_free(msg); + } + return; +} + +void btc_ble_mesh_prov_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_prov_cb_param_t *param = NULL; + + if (!msg) { + BT_ERR("%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_prov_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } +} + +void btc_ble_mesh_model_call_handler(btc_msg_t *msg) +{ + btc_ble_mesh_model_args_t *arg = NULL; + int err = 0; + + if (!msg || !msg->arg) { + BT_ERR("%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_client_model_role(&common)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + } + 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) { + BT_ERR("%s, Failed to allocate memory", __func__); + break; + } + net_buf_simple_add_mem(buf, arg->model_send.data, arg->model_send.length); + arg->model_send.ctx->srv_send = true; + 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) { + BT_ERR("%s, Failed to allocate memory", __func__); + break; + } + 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_client_model_role(&common)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + 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_ble_mesh_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; + } + case BTC_BLE_MESH_ACT_SERVER_MODEL_UPDATE_STATE: + err = bt_mesh_update_binding_state( + (struct bt_mesh_model *)arg->model_update_state.model, arg->model_update_state.type, + (bt_mesh_server_state_value_t *)arg->model_update_state.value); + btc_ble_mesh_server_model_update_state_comp_cb(arg->model_update_state.model, + arg->model_update_state.type, err); + break; + default: + BT_WARN("%s, Unknown msg->act %d", __func__, msg->act); + break; + } + + btc_ble_mesh_model_arg_deep_free(msg); + return; +} + +void btc_ble_mesh_model_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_model_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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 { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_model_free_req_data(msg); + return; +} diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c new file mode 100644 index 0000000000..cc906d76dd --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c @@ -0,0 +1,908 @@ +// Copyright 2017-2019 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" +#include "sensor_client.h" + +#include "btc_ble_mesh_sensor_model.h" +#include "esp_ble_mesh_sensor_model_api.h" + +/* Sensor Client Models related functions */ + +static inline void btc_ble_mesh_sensor_client_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_ble_mesh_cb = + (esp_ble_mesh_sensor_client_cb_t)btc_profile_cb_get(BTC_PID_SENSOR_CLIENT); + if (btc_ble_mesh_cb) { + btc_ble_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; + u16_t length = 0U; + + if (!msg || !dst || !src) { + BT_ERR("%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 *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->sensor_client_get_state.get_state = (esp_ble_mesh_sensor_client_get_state_t *)bt_mesh_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)); + + switch (src->sensor_client_get_state.params->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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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 { + BT_ERR("%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 *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + dst->sensor_client_set_state.set_state = (esp_ble_mesh_sensor_client_set_state_t *)bt_mesh_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)); + + switch (src->sensor_client_set_state.params->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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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 { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + BT_DBG("%s, Unknown deep copy act %d", __func__, msg->act); + break; + } +} + +void btc_ble_mesh_sensor_client_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_mesh_sensor_client_args_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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.get_state) { + if (arg->sensor_client_get_state.params) { + switch (arg->sensor_client_get_state.params->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; + } + } + bt_mesh_free(arg->sensor_client_get_state.get_state); + } + if (arg->sensor_client_get_state.params) { + bt_mesh_free(arg->sensor_client_get_state.params); + } + break; + case BTC_BLE_MESH_ACT_SENSOR_CLIENT_SET_STATE: + if (arg->sensor_client_set_state.set_state) { + if (arg->sensor_client_set_state.params) { + switch (arg->sensor_client_set_state.params->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; + } + } + bt_mesh_free(arg->sensor_client_set_state.set_state); + } + if (arg->sensor_client_set_state.params) { + bt_mesh_free(arg->sensor_client_set_state.params); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_sensor_client_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; + u16_t length = 0U; + + if (!msg || !p_src_data || !p_dest_data) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (p_src_data->params) { + p_dest_data->params = bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (!p_dest_data->params) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } + + 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) { + switch (p_src_data->params->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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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) { + BT_ERR("%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: + break; + default: + break; + } +} + +static void btc_ble_mesh_sensor_client_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_sensor_client_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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) { + switch (arg->params->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) { + bt_mesh_free(arg->params); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_sensor_client_callback(esp_ble_mesh_sensor_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_SENSOR_CLIENT)) { + return; + } + + 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_sensor_client_copy_req_data); +} + +void bt_mesh_sensor_client_cb_evt_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 = 0U; + uint8_t act = 0U; + + if (!model || !ctx) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_SENSOR_CLIENT_GET_STATE: + act = ESP_BLE_MESH_SENSOR_CLIENT_GET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_SENSOR_CLIENT_SET_STATE: + act = ESP_BLE_MESH_SENSOR_CLIENT_SET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_SENSOR_CLIENT_PUBLISH: + act = ESP_BLE_MESH_SENSOR_CLIENT_PUBLISH_EVT; + break; + case BTC_BLE_MESH_EVT_SENSOR_CLIENT_TIMEOUT: + act = ESP_BLE_MESH_SENSOR_CLIENT_TIMEOUT_EVT; + break; + default: + BT_ERR("%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_ble_mesh_sensor_client_callback(&cb_params, act); + return; +} + +void btc_ble_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) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_sensor_client_cb_evt_to_btc(opcode, + BTC_BLE_MESH_EVT_SENSOR_CLIENT_PUBLISH, model, ctx, buf->data, buf->len); + return; +} + +void btc_ble_mesh_sensor_client_call_handler(btc_msg_t *msg) +{ + esp_ble_mesh_client_common_param_t *params = NULL; + btc_ble_mesh_sensor_client_args_t *arg = NULL; + esp_ble_mesh_sensor_client_cb_param_t cb = {0}; + bt_mesh_client_common_param_t common = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + BT_ERR("%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_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + 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; + + cb.params = arg->sensor_client_get_state.params; + cb.error_code = bt_mesh_sensor_client_get_state(&common, + (void *)arg->sensor_client_get_state.get_state, (void *)&cb.status_cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_sensor_client_callback(&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_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + 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; + + cb.params = arg->sensor_client_set_state.params; + cb.error_code = bt_mesh_sensor_client_set_state(&common, + (void *)arg->sensor_client_set_state.set_state, (void *)&cb.status_cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_sensor_client_callback(&cb, ESP_BLE_MESH_SENSOR_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_sensor_client_arg_deep_free(msg); + return; +} + +void btc_ble_mesh_sensor_client_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_sensor_client_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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_sensor_client_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_sensor_client_free_req_data(msg); + return; +} + +/* Sensor Server Models related functions */ + +static inline void btc_ble_mesh_sensor_server_cb_to_app( + esp_ble_mesh_sensor_server_cb_event_t event, + esp_ble_mesh_sensor_server_cb_param_t *param) +{ + esp_ble_mesh_sensor_server_cb_t btc_ble_mesh_cb = + (esp_ble_mesh_sensor_server_cb_t)btc_profile_cb_get(BTC_PID_SENSOR_SERVER); + if (btc_ble_mesh_cb) { + btc_ble_mesh_cb(event, param); + } +} + +static void btc_ble_mesh_sensor_server_copy_req_data(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_mesh_sensor_server_cb_param_t *p_dest_data = (esp_ble_mesh_sensor_server_cb_param_t *)p_dest; + esp_ble_mesh_sensor_server_cb_param_t *p_src_data = (esp_ble_mesh_sensor_server_cb_param_t *)p_src; + u16_t length = 0U; + + if (!msg || !p_src_data || !p_dest_data) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (msg->act) { + case ESP_BLE_MESH_SENSOR_SERVER_STATE_CHANGE_EVT: + if (p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET || + p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK) { + if (p_src_data->value.state_change.sensor_cadence_set.trigger_delta_down) { + length = p_src_data->value.state_change.sensor_cadence_set.trigger_delta_down->len; + p_dest_data->value.state_change.sensor_cadence_set.trigger_delta_down = bt_mesh_alloc_buf(length); + if (p_dest_data->value.state_change.sensor_cadence_set.trigger_delta_down == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.state_change.sensor_cadence_set.trigger_delta_down, + p_src_data->value.state_change.sensor_cadence_set.trigger_delta_down->data, + p_src_data->value.state_change.sensor_cadence_set.trigger_delta_down->len); + } + if (p_src_data->value.state_change.sensor_cadence_set.trigger_delta_up) { + length = p_src_data->value.state_change.sensor_cadence_set.trigger_delta_up->len; + p_dest_data->value.state_change.sensor_cadence_set.trigger_delta_up = bt_mesh_alloc_buf(length); + if (p_dest_data->value.state_change.sensor_cadence_set.trigger_delta_up == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.state_change.sensor_cadence_set.trigger_delta_up, + p_src_data->value.state_change.sensor_cadence_set.trigger_delta_up->data, + p_src_data->value.state_change.sensor_cadence_set.trigger_delta_up->len); + } + if (p_src_data->value.state_change.sensor_cadence_set.fast_cadence_low) { + length = p_src_data->value.state_change.sensor_cadence_set.fast_cadence_low->len; + p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_low = bt_mesh_alloc_buf(length); + if (p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_low == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_low, + p_src_data->value.state_change.sensor_cadence_set.fast_cadence_low->data, + p_src_data->value.state_change.sensor_cadence_set.fast_cadence_low->len); + } + if (p_src_data->value.state_change.sensor_cadence_set.fast_cadence_high) { + length = p_src_data->value.state_change.sensor_cadence_set.fast_cadence_high->len; + p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_high = bt_mesh_alloc_buf(length); + if (p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_high == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_high, + p_src_data->value.state_change.sensor_cadence_set.fast_cadence_high->data, + p_src_data->value.state_change.sensor_cadence_set.fast_cadence_high->len); + } + } else if (p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET || + p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK) { + if (p_src_data->value.state_change.sensor_setting_set.setting_value) { + length = p_src_data->value.state_change.sensor_setting_set.setting_value->len; + p_dest_data->value.state_change.sensor_setting_set.setting_value = bt_mesh_alloc_buf(length); + if (p_dest_data->value.state_change.sensor_setting_set.setting_value == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.state_change.sensor_setting_set.setting_value, + p_src_data->value.state_change.sensor_setting_set.setting_value->data, + p_src_data->value.state_change.sensor_setting_set.setting_value->len); + } + } + break; + case ESP_BLE_MESH_SENSOR_SERVER_RECV_GET_MSG_EVT: + if (p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET) { + if (p_src_data->value.get.sensor_column.raw_value_x) { + length = p_src_data->value.get.sensor_column.raw_value_x->len; + p_dest_data->value.get.sensor_column.raw_value_x = bt_mesh_alloc_buf(length); + if (p_dest_data->value.get.sensor_column.raw_value_x == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.get.sensor_column.raw_value_x, + p_src_data->value.get.sensor_column.raw_value_x->data, + p_src_data->value.get.sensor_column.raw_value_x->len); + } + } else if (p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_GET) { + if (p_src_data->value.get.sensor_series.raw_value) { + length = p_src_data->value.get.sensor_series.raw_value->len; + p_dest_data->value.get.sensor_series.raw_value = bt_mesh_alloc_buf(length); + if (p_dest_data->value.get.sensor_series.raw_value == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.get.sensor_series.raw_value, + p_src_data->value.get.sensor_series.raw_value->data, + p_src_data->value.get.sensor_series.raw_value->len); + } + } + break; + case ESP_BLE_MESH_SENSOR_SERVER_RECV_SET_MSG_EVT: + if (p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET || + p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK) { + if (p_src_data->value.set.sensor_cadence.cadence) { + length = p_src_data->value.set.sensor_cadence.cadence->len; + p_dest_data->value.set.sensor_cadence.cadence = bt_mesh_alloc_buf(length); + if (p_dest_data->value.set.sensor_cadence.cadence == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.set.sensor_cadence.cadence, + p_src_data->value.set.sensor_cadence.cadence->data, + p_src_data->value.set.sensor_cadence.cadence->len); + } + } else if (p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET || + p_src_data->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK) { + if (p_src_data->value.set.sensor_setting.setting_raw) { + length = p_src_data->value.set.sensor_setting.setting_raw->len; + p_dest_data->value.set.sensor_setting.setting_raw = bt_mesh_alloc_buf(length); + if (p_dest_data->value.set.sensor_setting.setting_raw == NULL) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + net_buf_simple_add_mem(p_dest_data->value.set.sensor_setting.setting_raw, + p_src_data->value.set.sensor_setting.setting_raw->data, + p_src_data->value.set.sensor_setting.setting_raw->len); + } + } + break; + default: + break; + } +} + +static void btc_ble_mesh_sensor_server_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_sensor_server_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + arg = (esp_ble_mesh_sensor_server_cb_param_t *)(msg->arg); + + switch (msg->act) { + case ESP_BLE_MESH_SENSOR_SERVER_STATE_CHANGE_EVT: + if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET || + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK) { + bt_mesh_free_buf(arg->value.state_change.sensor_cadence_set.trigger_delta_down); + bt_mesh_free_buf(arg->value.state_change.sensor_cadence_set.trigger_delta_up); + bt_mesh_free_buf(arg->value.state_change.sensor_cadence_set.fast_cadence_low); + bt_mesh_free_buf(arg->value.state_change.sensor_cadence_set.fast_cadence_high); + } else if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET || + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK) { + bt_mesh_free_buf(arg->value.state_change.sensor_setting_set.setting_value); + } + break; + case ESP_BLE_MESH_SENSOR_SERVER_RECV_GET_MSG_EVT: + if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET) { + bt_mesh_free_buf(arg->value.get.sensor_column.raw_value_x); + } else if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SERIES_GET) { + bt_mesh_free_buf(arg->value.get.sensor_series.raw_value); + } + break; + case ESP_BLE_MESH_SENSOR_SERVER_RECV_SET_MSG_EVT: + if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET || + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK) { + bt_mesh_free_buf(arg->value.set.sensor_cadence.cadence); + } else if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET || + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK) { + bt_mesh_free_buf(arg->value.set.sensor_setting.setting_raw); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_sensor_server_callback(esp_ble_mesh_sensor_server_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_SENSOR_SERVER)) { + return; + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_SENSOR_SERVER; + msg.act = act; + + btc_transfer_context( + &msg, cb_params, sizeof(esp_ble_mesh_sensor_server_cb_param_t), btc_ble_mesh_sensor_server_copy_req_data); +} + +void bt_mesh_sensor_server_cb_evt_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_sensor_server_cb_param_t cb_params = {0}; + size_t length = 0U; + uint8_t act = 0U; + + if (model == NULL || ctx == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_SENSOR_SERVER_STATE_CHANGE: + act = ESP_BLE_MESH_SENSOR_SERVER_STATE_CHANGE_EVT; + break; + case BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_GET_MSG: + act = ESP_BLE_MESH_SENSOR_SERVER_RECV_GET_MSG_EVT; + break; + case BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_SET_MSG: + act = ESP_BLE_MESH_SENSOR_SERVER_RECV_SET_MSG_EVT; + break; + default: + BT_ERR("%s, Unknown Sensor Server event type", __func__); + 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.value)) ? len : sizeof(cb_params.value); + memcpy(&cb_params.value, val, length); + } + + btc_ble_mesh_sensor_server_callback(&cb_params, act); + return; +} + +void btc_ble_mesh_sensor_server_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_sensor_server_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_sensor_server_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_SENSOR_SERVER_EVT_MAX) { + btc_ble_mesh_sensor_server_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_sensor_server_free_req_data(msg); + return; +} diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c new file mode 100644 index 0000000000..d33702edd5 --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c @@ -0,0 +1,495 @@ +// Copyright 2017-2019 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" +#include "time_scene_client.h" + +#include "btc_ble_mesh_time_scene_model.h" +#include "esp_ble_mesh_time_scene_model_api.h" + +/* Time and Scenes Client Models related functions */ + +static inline void btc_ble_mesh_time_scene_client_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_ble_mesh_cb = + (esp_ble_mesh_time_scene_client_cb_t)btc_profile_cb_get(BTC_PID_TIME_SCENE_CLIENT); + if (btc_ble_mesh_cb) { + btc_ble_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) { + BT_ERR("%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 *)bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (dst->time_scene_client_get_state.params) { + memcpy(dst->time_scene_client_get_state.params, src->time_scene_client_get_state.params, + sizeof(esp_ble_mesh_client_common_param_t)); + } else { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + break; + } + if (src->time_scene_client_get_state.get_state) { + dst->time_scene_client_get_state.get_state = (esp_ble_mesh_time_scene_client_get_state_t *)bt_mesh_malloc(sizeof(esp_ble_mesh_time_scene_client_get_state_t)); + if (dst->time_scene_client_get_state.get_state) { + 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 { + BT_ERR("%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 *)bt_mesh_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 *)bt_mesh_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 { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + } + break; + } + default: + BT_DBG("%s, Unknown deep copy act %d", __func__, msg->act); + 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) { + BT_ERR("%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) { + bt_mesh_free(arg->time_scene_client_get_state.params); + } + if (arg->time_scene_client_get_state.get_state) { + bt_mesh_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) { + bt_mesh_free(arg->time_scene_client_set_state.params); + } + if (arg->time_scene_client_set_state.set_state) { + bt_mesh_free(arg->time_scene_client_set_state.set_state); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_time_scene_client_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; + u16_t length = 0U; + + if (!msg || !p_src_data || !p_dest_data) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (p_src_data->params) { + p_dest_data->params = bt_mesh_malloc(sizeof(esp_ble_mesh_client_common_param_t)); + if (!p_dest_data->params) { + BT_ERR("%s, Failed to allocate memory, act %d", __func__, msg->act); + return; + } + + memcpy(p_dest_data->params, p_src_data->params, sizeof(esp_ble_mesh_client_common_param_t)); + } + + 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) { + switch (p_src_data->params->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) { + BT_ERR("%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: + break; + default: + break; + } +} + +static void btc_ble_mesh_time_scene_client_free_req_data(btc_msg_t *msg) +{ + esp_ble_mesh_time_scene_client_cb_param_t *arg = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%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) { + switch (arg->params->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) { + bt_mesh_free(arg->params); + } + break; + default: + break; + } +} + +static void btc_ble_mesh_time_scene_client_callback(esp_ble_mesh_time_scene_client_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_TIME_SCENE_CLIENT)) { + return; + } + + 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_time_scene_client_copy_req_data); +} + +void bt_mesh_time_scene_client_cb_evt_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 = 0U; + uint8_t act = 0U; + + if (!model || !ctx) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_GET_STATE: + act = ESP_BLE_MESH_TIME_SCENE_CLIENT_GET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_SET_STATE: + act = ESP_BLE_MESH_TIME_SCENE_CLIENT_SET_STATE_EVT; + break; + case BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_PUBLISH: + act = ESP_BLE_MESH_TIME_SCENE_CLIENT_PUBLISH_EVT; + break; + case BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_TIMEOUT: + act = ESP_BLE_MESH_TIME_SCENE_CLIENT_TIMEOUT_EVT; + break; + default: + BT_ERR("%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_ble_mesh_time_scene_client_callback(&cb_params, act); + return; +} + +void btc_ble_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) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_time_scene_client_cb_evt_to_btc(opcode, + BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_PUBLISH, model, ctx, buf->data, buf->len); + return; +} + +void btc_ble_mesh_time_scene_client_call_handler(btc_msg_t *msg) +{ + btc_ble_mesh_time_scene_client_args_t *arg = NULL; + esp_ble_mesh_client_common_param_t *params = NULL; + esp_ble_mesh_time_scene_client_cb_param_t cb = {0}; + bt_mesh_client_common_param_t common = {0}; + bt_mesh_role_param_t role_param = {0}; + + if (!msg || !msg->arg) { + BT_ERR("%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_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + 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; + + cb.params = arg->time_scene_client_get_state.params; + cb.error_code = bt_mesh_time_scene_client_get_state(&common, + (void *)arg->time_scene_client_get_state.get_state, (void *)&cb.status_cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_time_scene_client_callback(&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_client_model_role(&role_param)) { + BT_ERR("%s, Failed to set model role", __func__); + break; + } + 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; + + cb.params = arg->time_scene_client_set_state.params; + cb.error_code = bt_mesh_time_scene_client_set_state(&common, + (void *)arg->time_scene_client_set_state.set_state, (void *)&cb.status_cb); + if (cb.error_code) { + /* If send failed, callback error_code to app layer immediately */ + btc_ble_mesh_time_scene_client_callback(&cb, ESP_BLE_MESH_TIME_SCENE_CLIENT_SET_STATE_EVT); + } + break; + } + default: + break; + } + + btc_ble_mesh_time_scene_client_arg_deep_free(msg); + return; +} + +void btc_ble_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) { + BT_ERR("%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_time_scene_client_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + btc_ble_mesh_time_scene_client_free_req_data(msg); + return; +} + +/* Time and Scenes Server Models related functions */ + +static inline void btc_ble_mesh_time_scene_server_cb_to_app( + esp_ble_mesh_time_scene_server_cb_event_t event, + esp_ble_mesh_time_scene_server_cb_param_t *param) +{ + esp_ble_mesh_time_scene_server_cb_t btc_ble_mesh_cb = + (esp_ble_mesh_time_scene_server_cb_t)btc_profile_cb_get(BTC_PID_TIME_SCENE_SERVER); + if (btc_ble_mesh_cb) { + btc_ble_mesh_cb(event, param); + } +} + +static void btc_ble_mesh_time_scene_server_callback(esp_ble_mesh_time_scene_server_cb_param_t *cb_params, uint8_t act) +{ + btc_msg_t msg = {0}; + + BT_DBG("%s", __func__); + + /* If corresponding callback is not registered, event will not be posted. */ + if (!btc_profile_cb_get(BTC_PID_TIME_SCENE_SERVER)) { + return; + } + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_TIME_SCENE_SERVER; + msg.act = act; + + btc_transfer_context( + &msg, cb_params, sizeof(esp_ble_mesh_time_scene_server_cb_param_t), NULL); +} + +void bt_mesh_time_scene_server_cb_evt_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_time_scene_server_cb_param_t cb_params = {0}; + size_t length = 0U; + uint8_t act = 0U; + + if (model == NULL || ctx == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (evt_type) { + case BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE: + act = ESP_BLE_MESH_TIME_SCENE_SERVER_STATE_CHANGE_EVT; + break; + case BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_GET_MSG: + act = ESP_BLE_MESH_TIME_SCENE_SERVER_RECV_GET_MSG_EVT; + break; + case BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG: + act = ESP_BLE_MESH_TIME_SCENE_SERVER_RECV_SET_MSG_EVT; + break; + case BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_STATUS_MSG: + act = ESP_BLE_MESH_TIME_SCENE_SERVER_RECV_STATUS_MSG_EVT; + break; + default: + BT_ERR("%s, Unknown Time Scene Server event type", __func__); + 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.value)) ? len : sizeof(cb_params.value); + memcpy(&cb_params.value, val, length); + } + + btc_ble_mesh_time_scene_server_callback(&cb_params, act); + return; +} + +void btc_ble_mesh_time_scene_server_cb_handler(btc_msg_t *msg) +{ + esp_ble_mesh_time_scene_server_cb_param_t *param = NULL; + + if (!msg || !msg->arg) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + param = (esp_ble_mesh_time_scene_server_cb_param_t *)(msg->arg); + + if (msg->act < ESP_BLE_MESH_TIME_SCENE_SERVER_EVT_MAX) { + btc_ble_mesh_time_scene_server_cb_to_app(msg->act, param); + } else { + BT_ERR("%s, Unknown msg->act = %d", __func__, msg->act); + } + + return; +} + diff --git a/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_config_model.h b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_config_model.h new file mode 100644 index 0000000000..f9920edbe6 --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_config_model.h @@ -0,0 +1,74 @@ +// Copyright 2017-2019 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 "btc/btc_manage.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_ACT_CONFIG_CLIENT_MAX, +} btc_ble_mesh_config_client_act_t; + +typedef union { + struct ble_mesh_cfg_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_cfg_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_config_client_args_t; + +typedef enum { + BTC_BLE_MESH_EVT_CONFIG_CLIENT_GET_STATE, + BTC_BLE_MESH_EVT_CONFIG_CLIENT_SET_STATE, + BTC_BLE_MESH_EVT_CONFIG_CLIENT_PUBLISH, + BTC_BLE_MESH_EVT_CONFIG_CLIENT_TIMEOUT, + BTC_BLE_MESH_EVT_CONFIG_CLIENT_MAX, +} btc_ble_mesh_config_client_evt_t; + +void btc_ble_mesh_config_client_call_handler(btc_msg_t *msg); + +void btc_ble_mesh_config_client_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_config_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_ble_mesh_config_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_config_client_cb_evt_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_ble_mesh_config_server_cb_handler(btc_msg_t *msg); + +typedef enum { + BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + BTC_BLE_MESH_EVT_CONFIG_SERVER_MAX, +} btc_ble_mesh_config_server_evt_t; + +void bt_mesh_config_server_cb_evt_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/esp_ble_mesh/btc/include/btc_ble_mesh_generic_model.h b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_generic_model.h new file mode 100644 index 0000000000..ca615a0fca --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_generic_model.h @@ -0,0 +1,76 @@ +// Copyright 2017-2019 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 "btc/btc_manage.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_ACT_GENERIC_CLIENT_MAX, +} 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; + +typedef enum { + BTC_BLE_MESH_EVT_GENERIC_CLIENT_GET_STATE, + BTC_BLE_MESH_EVT_GENERIC_CLIENT_SET_STATE, + BTC_BLE_MESH_EVT_GENERIC_CLIENT_PUBLISH, + BTC_BLE_MESH_EVT_GENERIC_CLIENT_TIMEOUT, + BTC_BLE_MESH_EVT_GENERIC_CLIENT_MAX, +} btc_ble_mesh_generic_client_evt_t; + +void btc_ble_mesh_generic_client_call_handler(btc_msg_t *msg); + +void btc_ble_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_ble_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_generic_client_cb_evt_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); + +typedef enum { + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, + BTC_BLE_MESH_EVT_GENERIC_SERVER_MAX, +} btc_ble_mesh_generic_server_evt_t; + +void bt_mesh_generic_server_cb_evt_to_btc(u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +void btc_ble_mesh_generic_server_cb_handler(btc_msg_t *msg); + +#endif /* _BTC_BLE_MESH_GENERIC_MODEL_H_ */ diff --git a/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_health_model.h b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_health_model.h new file mode 100644 index 0000000000..90aba889a4 --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_health_model.h @@ -0,0 +1,87 @@ +// Copyright 2017-2019 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 "btc/btc_manage.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 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 enum { + BTC_BLE_MESH_EVT_HEALTH_CLIENT_GET_STATE, + BTC_BLE_MESH_EVT_HEALTH_CLIENT_SET_STATE, + BTC_BLE_MESH_EVT_HEALTH_CLIENT_PUBLISH, + BTC_BLE_MESH_EVT_HEALTH_CLIENT_TIMEOUT, + BTC_BLE_MESH_EVT_HEALTH_CLIENT_MAX, +} btc_ble_mesh_health_client_evt_t; + +void btc_ble_mesh_health_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_ble_mesh_health_client_call_handler(btc_msg_t *msg); + +void btc_ble_mesh_health_client_cb_handler(btc_msg_t *msg); + +void btc_ble_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_health_client_cb_evt_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); + +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_server_fault_update_args { + esp_ble_mesh_elem_t *element; + } health_fault_update; +} btc_ble_mesh_health_server_args_t; + +void btc_ble_mesh_health_server_call_handler(btc_msg_t *msg); + +void btc_ble_mesh_health_server_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_health_server_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_ble_mesh_health_server_fault_clear(struct bt_mesh_model *model, u16_t company_id); + +void btc_ble_mesh_health_server_fault_test(struct bt_mesh_model *model, u8_t test_id, u16_t company_id); + +void btc_ble_mesh_health_server_attention_on(struct bt_mesh_model *model, u8_t time); + +void btc_ble_mesh_health_server_attention_off(struct bt_mesh_model *model); + +#endif /* _BTC_BLE_MESH_HEALTH_MODEL_H_ */ diff --git a/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_lighting_model.h b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_lighting_model.h new file mode 100644 index 0000000000..97e67001ae --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_lighting_model.h @@ -0,0 +1,78 @@ +// Copyright 2017-2019 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 "btc/btc_manage.h" +#include "esp_ble_mesh_lighting_model_api.h" + +typedef enum { + BTC_BLE_MESH_ACT_LIGHTING_CLIENT_GET_STATE, + BTC_BLE_MESH_ACT_LIGHTING_CLIENT_SET_STATE, + BTC_BLE_MESH_ACT_LIGHTING_CLIENT_MAX, +} btc_ble_mesh_lighting_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_lighting_client_args_t; + +typedef enum { + BTC_BLE_MESH_EVT_LIGHTING_CLIENT_GET_STATE, + BTC_BLE_MESH_EVT_LIGHTING_CLIENT_SET_STATE, + BTC_BLE_MESH_EVT_LIGHTING_CLIENT_PUBLISH, + BTC_BLE_MESH_EVT_LIGHTING_CLIENT_TIMEOUT, + BTC_BLE_MESH_EVT_LIGHTING_CLIENT_MAX, +} btc_ble_mesh_lighting_client_evt_t; + +void btc_ble_mesh_lighting_client_call_handler(btc_msg_t *msg); + +void btc_ble_mesh_lighting_client_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_lighting_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_ble_mesh_lighting_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_lighting_client_cb_evt_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); + +typedef enum { + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_GET_MSG, + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_STATUS_MSG, + BTC_BLE_MESH_EVT_LIGHTING_SERVER_MAX, +} btc_ble_mesh_lighting_server_evt_t; + +void bt_mesh_lighting_server_cb_evt_to_btc(u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +void btc_ble_mesh_lighting_server_cb_handler(btc_msg_t *msg); + +#endif /* _BTC_BLE_MESH_LIGHTING_MODEL_H_ */ + diff --git a/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_prov.h b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_prov.h new file mode 100644 index 0000000000..c55de21356 --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_prov.h @@ -0,0 +1,301 @@ +// Copyright 2017-2019 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 "btc/btc_manage.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_PROV_DEV_WITH_ADDR, + 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_STATIC_OOB_VAL, + BTC_BLE_MESH_ACT_PROVISIONER_SET_PRIMARY_ELEM_ADDR, + BTC_BLE_MESH_ACT_PROVISIONER_SET_NODE_NAME, + BTC_BLE_MESH_ACT_PROVISIONER_ADD_LOCAL_APP_KEY, + BTC_BLE_MESH_ACT_PROVISIONER_UPDATE_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_PROVISIONER_UPDATE_LOCAL_NET_KEY, + BTC_BLE_MESH_ACT_PROVISIONER_STORE_NODE_COMP_DATA, + BTC_BLE_MESH_ACT_PROVISIONER_DELETE_NODE_WITH_UUID, + BTC_BLE_MESH_ACT_PROVISIONER_DELETE_NODE_WITH_ADDR, + BTC_BLE_MESH_ACT_SET_FAST_PROV_INFO, + BTC_BLE_MESH_ACT_SET_FAST_PROV_ACTION, + BTC_BLE_MESH_ACT_LPN_ENABLE, + BTC_BLE_MESH_ACT_LPN_DISABLE, + BTC_BLE_MESH_ACT_LPN_POLL, + BTC_BLE_MESH_ACT_PROXY_CLIENT_CONNECT, + BTC_BLE_MESH_ACT_PROXY_CLIENT_DISCONNECT, + BTC_BLE_MESH_ACT_PROXY_CLIENT_SET_FILTER_TYPE, + BTC_BLE_MESH_ACT_PROXY_CLIENT_ADD_FILTER_ADDR, + BTC_BLE_MESH_ACT_PROXY_CLIENT_REMOVE_FILTER_ADDR, +} 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_ACT_SERVER_MODEL_UPDATE_STATE, +} 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_prov_dev_with_addr_args { + uint8_t uuid[16]; + esp_ble_mesh_bd_addr_t addr; + esp_ble_mesh_addr_type_t addr_type; + esp_ble_mesh_prov_bearer_t bearer; + uint16_t oob_info; + uint16_t unicast_addr; + } provisioner_prov_dev_with_addr; + 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_static_oob_val_args { + uint8_t value[16]; + uint8_t length; + } set_static_oob_val; + struct ble_mesh_provisioner_set_primary_elem_addr_args { + uint16_t addr; + } set_primary_elem_addr; + struct ble_mesh_provisioner_set_node_name_args { + uint16_t 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_update_local_app_key_args { + uint8_t app_key[16]; + uint16_t net_idx; + uint16_t app_idx; + } update_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_provisioner_update_local_net_key_args { + uint8_t net_key[16]; + uint16_t net_idx; + } update_local_net_key; + struct ble_mesh_provisioner_store_node_comp_data_args { + uint16_t unicast_addr; + uint16_t length; + uint8_t *data; + } store_node_comp_data; + struct ble_mesh_provisioner_delete_node_with_uuid_args { + uint8_t uuid[16]; + } delete_node_with_uuid; + struct ble_mesh_provisioner_delete_node_with_addr_args { + uint16_t unicast_addr; + } delete_node_with_addr; + 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; + struct ble_mesh_lpn_enable_args { + /* RFU */ + } lpn_enable; + struct ble_mesh_lpn_disable_args { + bool force; + } lpn_disable; + struct ble_mesh_lpn_poll_args { + /* RFU */ + } lpn_poll; + struct ble_mesh_proxy_client_connect_args { + uint8_t addr[6]; + uint8_t addr_type; + uint16_t net_idx; + } proxy_client_connect; + struct ble_mesh_proxy_client_disconnect_args { + uint8_t conn_handle; + } proxy_client_disconnect; + struct ble_mesh_proxy_client_set_filter_type_args { + uint8_t conn_handle; + uint16_t net_idx; + uint8_t filter_type; + } proxy_client_set_filter_type; + struct ble_mesh_proxy_client_add_filter_addr_args { + uint8_t conn_handle; + uint16_t net_idx; + uint16_t addr_num; + uint16_t *addr; + } proxy_client_add_filter_addr; + struct ble_mesh_proxy_client_remove_filter_addr_args { + uint8_t conn_handle; + uint16_t net_idx; + uint16_t addr_num; + uint16_t *addr; + } proxy_client_remove_filter_addr; +} 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; + struct ble_mesh_server_model_update_state_args { + esp_ble_mesh_model_t *model; + esp_ble_mesh_server_state_type_t type; + esp_ble_mesh_server_state_value_t *value; + } model_update_state; +} btc_ble_mesh_model_args_t; + +void btc_ble_mesh_prov_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +void btc_ble_mesh_model_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); + +esp_ble_mesh_node_t *btc_ble_mesh_provisioner_get_node_with_uuid(const uint8_t uuid[16]); + +esp_ble_mesh_node_t *btc_ble_mesh_provisioner_get_node_with_addr(uint16_t unicast_addr); + +int btc_ble_mesh_deinit(esp_ble_mesh_deinit_param_t *param); + +int btc_ble_mesh_client_model_init(esp_ble_mesh_model_t *model); + +int btc_ble_mesh_client_model_deinit(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); + +u16_t btc_ble_mesh_provisioner_get_prov_node_count(void); + +void btc_ble_mesh_model_call_handler(btc_msg_t *msg); +void btc_ble_mesh_model_cb_handler(btc_msg_t *msg); + +void btc_ble_mesh_prov_call_handler(btc_msg_t *msg); +void btc_ble_mesh_prov_cb_handler(btc_msg_t *msg); + +#endif /* _BTC_BLE_MESH_PROV_H_ */ diff --git a/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_sensor_model.h b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_sensor_model.h new file mode 100644 index 0000000000..307cd562d4 --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_sensor_model.h @@ -0,0 +1,77 @@ +// Copyright 2017-2019 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 "btc/btc_manage.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_ACT_SENSOR_CLIENT_MAX, +} 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; + +typedef enum { + BTC_BLE_MESH_EVT_SENSOR_CLIENT_GET_STATE, + BTC_BLE_MESH_EVT_SENSOR_CLIENT_SET_STATE, + BTC_BLE_MESH_EVT_SENSOR_CLIENT_PUBLISH, + BTC_BLE_MESH_EVT_SENSOR_CLIENT_TIMEOUT, + BTC_BLE_MESH_EVT_SENSOR_CLIENT_MAX, +} btc_ble_mesh_sensor_client_evt_t; + +void btc_ble_mesh_sensor_client_call_handler(btc_msg_t *msg); + +void btc_ble_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_ble_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_sensor_client_cb_evt_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); + +typedef enum { + BTC_BLE_MESH_EVT_SENSOR_SERVER_STATE_CHANGE, + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_GET_MSG, + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_SET_MSG, + BTC_BLE_MESH_EVT_SENSOR_SERVER_MAX, +} btc_ble_mesh_sensor_server_evt_t; + +void bt_mesh_sensor_server_cb_evt_to_btc(u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +void btc_ble_mesh_sensor_server_cb_handler(btc_msg_t *msg); + +#endif /* _BTC_BLE_MESH_SENSOR_MODEL_H_ */ + diff --git a/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_time_scene_model.h b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_time_scene_model.h new file mode 100644 index 0000000000..a2df94501a --- /dev/null +++ b/components/bt/esp_ble_mesh/btc/include/btc_ble_mesh_time_scene_model.h @@ -0,0 +1,78 @@ +// Copyright 2017-2019 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 "btc/btc_manage.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_ACT_TIME_SCENE_CLIENT_MAX, +} 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; + +typedef enum { + BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_GET_STATE, + BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_SET_STATE, + BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_PUBLISH, + BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_TIMEOUT, + BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_MAX, +} btc_ble_mesh_time_scene_client_evt_t; + +void btc_ble_mesh_time_scene_client_call_handler(btc_msg_t *msg); + +void btc_ble_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_ble_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_time_scene_client_cb_evt_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); + +typedef enum { + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE, + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_GET_MSG, + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG, + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_STATUS_MSG, + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_MAX, +} btc_ble_mesh_time_scene_server_evt_t; + +void bt_mesh_time_scene_server_cb_evt_to_btc(u8_t evt_type, + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + const u8_t *val, size_t len); + +void btc_ble_mesh_time_scene_server_cb_handler(btc_msg_t *msg); + +#endif /* _BTC_BLE_MESH_TIME_SCENE_MODEL_H_ */ + diff --git a/components/bt/esp_ble_mesh/mesh_common/include/mesh_aes_encrypt.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_aes_encrypt.h new file mode 100644 index 0000000000..afaa6b27dd --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/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/esp_ble_mesh/mesh_common/include/mesh_atomic.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_atomic.h new file mode 100644 index 0000000000..5c8bf17b89 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/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/esp_ble_mesh/mesh_common/include/mesh_buf.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_buf.h new file mode 100644 index 0000000000..9609698bef --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/include/mesh_buf.h @@ -0,0 +1,1260 @@ +/** @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 "mesh_slist.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; +} + +/** + * Clone buffer state, using the same data buffer. + * + * Initializes a buffer to point to the same data as an existing buffer. + * Allows operations on the same data without altering the length and + * offset of the original. + * + * @param original Buffer to clone. + * @param clone The new clone. + */ +void net_buf_simple_clone(const struct net_buf_simple *original, + struct net_buf_simple *clone); + +/** + * @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_mem + * @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 beginning of the buffer data. + */ +#define net_buf_pull_mem(buf, len) net_buf_simple_pull_mem(&(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) + +/** + * @def net_buf_tail + * @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. + */ +#define net_buf_tail(buf) net_buf_simple_tail(&(buf)->b) + +/** + * @brief Find the last fragment in the fragment list. + * + * @return Pointer to last fragment in the list. + */ +struct net_buf *net_buf_frag_last(struct net_buf *frags); + +/** + * @brief Insert a new fragment to a chain of bufs. + * + * Insert a new fragment into the buffer fragments list after the parent. + * + * Note: This function takes ownership of the fragment reference so the + * caller is not required to unref. + * + * @param parent Parent buffer/fragment. + * @param frag Fragment to insert. + */ +void net_buf_frag_insert(struct net_buf *parent, struct net_buf *frag); + +/** + * @brief Add a new fragment to the end of a chain of bufs. + * + * Append a new fragment into the buffer fragments list. + * + * Note: This function takes ownership of the fragment reference so the + * caller is not required to unref. + * + * @param head Head of the fragment chain. + * @param frag Fragment to add. + * + * @return New head of the fragment chain. Either head (if head + * was non-NULL) or frag (if head was NULL). + */ +struct net_buf *net_buf_frag_add(struct net_buf *head, struct net_buf *frag); + +/** + * @brief Delete existing fragment from a chain of bufs. + * + * @param parent Parent buffer/fragment, or NULL if there is no parent. + * @param frag Fragment to delete. + * + * @return Pointer to the buffer following the fragment, or NULL if it + * had no further fragments. + */ +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) +struct net_buf *net_buf_frag_del_debug(struct net_buf *parent, + struct net_buf *frag, + const char *func, int line); +#define net_buf_frag_del(_parent, _frag) \ + net_buf_frag_del_debug(_parent, _frag, __func__, __LINE__) +#else +struct net_buf *net_buf_frag_del(struct net_buf *parent, struct net_buf *frag); +#endif + +/** + * @brief Copy bytes from net_buf chain starting at offset to linear buffer + * + * Copy (extract) @a len bytes from @a src net_buf chain, starting from @a + * offset in it, to a linear buffer @a dst. Return number of bytes actually + * copied, which may be less than requested, if net_buf chain doesn't have + * enough data, or destination buffer is too small. + * + * @param dst Destination buffer + * @param dst_len Destination buffer length + * @param src Source net_buf chain + * @param offset Starting offset to copy from + * @param len Number of bytes to copy + * @return number of bytes actually copied + */ +size_t net_buf_linearize(void *dst, size_t dst_len, + struct net_buf *src, size_t offset, size_t len); + +/** + * @typedef net_buf_allocator_cb + * @brief Network buffer allocator callback. + * + * @details The allocator callback is called when net_buf_append_bytes + * needs to allocate a new net_buf. + * + * @param timeout Affects the action taken should the net buf 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. + * @param user_data The user data given in net_buf_append_bytes call. + * @return pointer to allocated net_buf or NULL on error. + */ +typedef struct net_buf *(*net_buf_allocator_cb)(s32_t timeout, void *user_data); + +/** + * @brief Append data to a list of net_buf + * + * @details Append data to a net_buf. If there is not enough space in the + * net_buf then more net_buf will be added, unless there are no free net_buf + * and timeout occurs. + * + * @param buf Network buffer. + * @param len Total length of input data + * @param value Data to be added + * @param timeout Timeout is passed to the net_buf allocator callback. + * @param allocate_cb When a new net_buf is required, use this callback. + * @param user_data A user data pointer to be supplied to the allocate_cb. + * This pointer is can be anything from a mem_pool or a net_pkt, the + * logic is left up to the allocate_cb function. + * + * @return Length of data actually added. This may be less than input + * length if other timeout than K_FOREVER was used, and there + * were no free fragments in a pool to accommodate all data. + */ +size_t net_buf_append_bytes(struct net_buf *buf, size_t len, + const void *value, s32_t timeout, + net_buf_allocator_cb allocate_cb, void *user_data); + +/** + * @brief Skip N number of bytes in a net_buf + * + * @details Skip N number of bytes starting from fragment's offset. If the total + * length of data is placed in multiple fragments, this function will skip from + * all fragments until it reaches N number of bytes. Any fully skipped buffers + * are removed from the net_buf list. + * + * @param buf Network buffer. + * @param len Total length of data to be skipped. + * + * @return Pointer to the fragment or + * NULL and pos is 0 after successful skip, + * NULL and pos is 0xffff otherwise. + */ +static inline struct net_buf *net_buf_skip(struct net_buf *buf, size_t len) +{ + while (buf && len--) { + net_buf_pull_u8(buf); + if (!buf->len) { + buf = net_buf_frag_del(NULL, buf); + } + } + + return buf; +} + +/** + * @brief Calculate amount of bytes stored in fragments. + * + * Calculates the total amount of data stored in the given buffer and the + * fragments linked to it. + * + * @param buf Buffer to start off with. + * + * @return Number of bytes in the buffer and its fragments. + */ +static inline size_t net_buf_frags_len(struct net_buf *buf) +{ + size_t bytes = 0; + + while (buf) { + bytes += buf->len; + buf = buf->frags; + } + + return bytes; +} + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* _BLE_MESH_BUF_H_ */ + diff --git a/components/bt/esp_ble_mesh/mesh_common/include/mesh_common.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_common.h new file mode 100644 index 0000000000..737b8394e7 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/include/mesh_common.h @@ -0,0 +1,90 @@ +// Copyright 2017-2019 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 _BLE_MESH_COMMON_H_ +#define _BLE_MESH_COMMON_H_ + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" + +#include "esp_heap_caps.h" + +#include "mesh_access.h" + +#if CONFIG_BLE_MESH_ALLOC_FROM_PSRAM_FIRST +#define bt_mesh_malloc(size) heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL) +#define bt_mesh_calloc(size) heap_caps_calloc_prefer(1, size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL) +#else +#define bt_mesh_malloc(size) malloc((size)) +#define bt_mesh_calloc(size) calloc(1, (size)) +#endif /* CONFIG_BLE_MESH_ALLOC_FROM_PSRAM_FIRST */ +#define bt_mesh_free(p) free((p)) + +/** + * @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); + +/** + * @brief This function gets device role for stack internal use. + * + * @Note Currently Provisioner only support client models, Node supports + * client models and server models. Hence if srv_send is set to be + * TRUE, then role NODE will be returned. + * + * @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_device_role(struct bt_mesh_model *model, bool srv_send); + +typedef struct { + SemaphoreHandle_t mutex; +#if CONFIG_SPIRAM_USE_MALLOC + StaticQueue_t *buffer; +#endif +} bt_mesh_mutex_t; + +void bt_mesh_mutex_create(bt_mesh_mutex_t *mutex); + +void bt_mesh_mutex_free(bt_mesh_mutex_t *mutex); + +void bt_mesh_mutex_lock(bt_mesh_mutex_t *mutex); + +void bt_mesh_mutex_unlock(bt_mesh_mutex_t *mutex); + +#endif /* _BLE_MESH_COMMON_H_ */ \ No newline at end of file diff --git a/components/bt/esp_ble_mesh/mesh_common/include/mesh_dlist.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_dlist.h new file mode 100644 index 0000000000..31eef746ec --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/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/esp_ble_mesh/mesh_common/include/mesh_kernel.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_kernel.h new file mode 100644 index 0000000000..e0052e5289 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/include/mesh_kernel.h @@ -0,0 +1,280 @@ +/* + * 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 "mesh_types.h" +#include "mesh_slist.h" +#include "mesh_atomic.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); + +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); + +int k_delayed_work_submit_periodic(struct k_delayed_work *work, s32_t period); + +/** + * @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); + +void bt_mesh_list_lock(void); +void bt_mesh_list_unlock(void); + +void bt_mesh_buf_lock(void); +void bt_mesh_buf_unlock(void); + +void bt_mesh_atomic_lock(void); +void bt_mesh_atomic_unlock(void); + +void bt_mesh_k_init(void); +void bt_mesh_k_deinit(void); + +#endif /* _BLE_MESH_KERNEL_H_ */ + diff --git a/components/bt/esp_ble_mesh/mesh_common/include/mesh_slist.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_slist.h new file mode 100644 index 0000000000..4ddf427e6a --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/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/esp_ble_mesh/mesh_common/include/mesh_trace.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_trace.h new file mode 100644 index 0000000000..821f5716cd --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/include/mesh_trace.h @@ -0,0 +1,130 @@ +/* + * 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" + +/* 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/esp_ble_mesh/mesh_common/include/mesh_types.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_types.h new file mode 100644 index 0000000000..66df1ec75e --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/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/esp_ble_mesh/mesh_common/include/mesh_util.h b/components/bt/esp_ble_mesh/mesh_common/include/mesh_util.h new file mode 100644 index 0000000000..ec92ad39a5 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/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 "soc/soc.h" +#include "mesh_types.h" +#include "mesh_trace.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/esp_ble_mesh/mesh_common/mesh_aes_encrypt.c b/components/bt/esp_ble_mesh/mesh_common/mesh_aes_encrypt.c new file mode 100644 index 0000000000..544bbea721 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/mesh_aes_encrypt.c @@ -0,0 +1,408 @@ +/* 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_util.h" +#include "mesh_aes_encrypt.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] = {0}; + + 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] = {0}; + + 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] = {0}; + 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 = NULL; + 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/esp_ble_mesh/mesh_common/mesh_atomic.c b/components/bt/esp_ble_mesh/mesh_common/mesh_atomic.c new file mode 100644 index 0000000000..5dce0b53b2 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/mesh_atomic.c @@ -0,0 +1,173 @@ +/** + * @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" + +#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) +{ + bt_mesh_atomic_val_t ret = 0; + + bt_mesh_atomic_lock(); + + ret = *target; + *target = value; + + bt_mesh_atomic_unlock(); + + 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) +{ + bt_mesh_atomic_val_t ret = 0; + + bt_mesh_atomic_lock(); + + ret = *target; + *target |= value; + + bt_mesh_atomic_unlock(); + + 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) +{ + bt_mesh_atomic_val_t ret = 0; + + bt_mesh_atomic_lock(); + + ret = *target; + *target &= value; + + bt_mesh_atomic_unlock(); + + 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) +{ + bt_mesh_atomic_val_t ret = 0; + + bt_mesh_atomic_lock(); + + ret = *target; + (*target)--; + + bt_mesh_atomic_unlock(); + + 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) +{ + bt_mesh_atomic_val_t ret = 0; + + bt_mesh_atomic_lock(); + + ret = *target; + (*target)++; + + bt_mesh_atomic_unlock(); + + return ret; +} + +#endif /* #ifndef CONFIG_ATOMIC_OPERATIONS_BUILTIN */ diff --git a/components/bt/esp_ble_mesh/mesh_common/mesh_buf.c b/components/bt/esp_ble_mesh/mesh_common/mesh_buf.c new file mode 100644 index 0000000000..2fe8da1da5 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/mesh_buf.c @@ -0,0 +1,594 @@ +/* + * Copyright (c) 2015 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "mesh_buf.h" +#include "mesh_trace.h" +#include "mesh_kernel.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 = NULL; + + buf = &pool->__bufs[pool->buf_count - uninit_count]; + + buf->pool = pool; + + return buf; +} + +void net_buf_simple_clone(const struct net_buf_simple *original, + struct net_buf_simple *clone) +{ + memcpy(clone, original, sizeof(struct net_buf_simple)); +} + +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 = NULL; + + 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 = 0U; + + 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 = 0U; + + 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 = 0U; + + 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 = 0U; + + 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 = 0U; + + 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 = NULL; + + NET_BUF_ASSERT(list); + NET_BUF_ASSERT(buf); + + for (tail = buf; tail->frags; tail = tail->frags) { + tail->flags |= NET_BUF_FRAGS; + } + + bt_mesh_list_lock(); + sys_slist_append_list(list, &buf->node, &tail->node); + bt_mesh_list_unlock(); +} + +struct net_buf *net_buf_slist_get(sys_slist_t *list) +{ + struct net_buf *buf = NULL, *frag = NULL; + + NET_BUF_ASSERT(list); + + bt_mesh_list_lock(); + buf = (void *)sys_slist_get(list); + bt_mesh_list_unlock(); + + if (!buf) { + return NULL; + } + + /* Get any fragments belonging to this buffer */ + for (frag = buf; (frag->flags & NET_BUF_FRAGS); frag = frag->frags) { + bt_mesh_list_lock(); + frag->frags = (void *)sys_slist_get(list); + bt_mesh_list_unlock(); + + 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 = NULL; + +#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; + 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. + */ + bt_mesh_buf_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_buf_unlock(); + goto success; + } + } + } + + bt_mesh_buf_unlock(); + + 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 + +struct net_buf *net_buf_frag_last(struct net_buf *buf) +{ + NET_BUF_ASSERT(buf); + + while (buf->frags) { + buf = buf->frags; + } + + return buf; +} + +void net_buf_frag_insert(struct net_buf *parent, struct net_buf *frag) +{ + NET_BUF_ASSERT(parent); + NET_BUF_ASSERT(frag); + + if (parent->frags) { + net_buf_frag_last(frag)->frags = parent->frags; + } + /* Take ownership of the fragment reference */ + parent->frags = frag; +} + +struct net_buf *net_buf_frag_add(struct net_buf *head, struct net_buf *frag) +{ + NET_BUF_ASSERT(frag); + + if (!head) { + return net_buf_ref(frag); + } + + net_buf_frag_insert(net_buf_frag_last(head), frag); + + return head; +} + +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) +struct net_buf *net_buf_frag_del_debug(struct net_buf *parent, + struct net_buf *frag, + const char *func, int line) +#else +struct net_buf *net_buf_frag_del(struct net_buf *parent, struct net_buf *frag) +#endif +{ + struct net_buf *next_frag = NULL; + + NET_BUF_ASSERT(frag); + + if (parent) { + NET_BUF_ASSERT(parent->frags); + NET_BUF_ASSERT(parent->frags == frag); + parent->frags = frag->frags; + } + + next_frag = frag->frags; + + frag->frags = NULL; + +#if defined(CONFIG_BLE_MESH_NET_BUF_LOG) + net_buf_unref_debug(frag, func, line); +#else + net_buf_unref(frag); +#endif + + return next_frag; +} + +size_t net_buf_linearize(void *dst, size_t dst_len, struct net_buf *src, + size_t offset, size_t len) +{ + struct net_buf *frag = NULL; + size_t to_copy = 0U; + size_t copied = 0U; + + len = MIN(len, dst_len); + + frag = src; + + /* find the right fragment to start copying from */ + while (frag && offset >= frag->len) { + offset -= frag->len; + frag = frag->frags; + } + + /* traverse the fragment chain until len bytes are copied */ + copied = 0; + while (frag && len > 0) { + to_copy = MIN(len, frag->len - offset); + memcpy((u8_t *)dst + copied, frag->data + offset, to_copy); + + copied += to_copy; + + /* to_copy is always <= len */ + len -= to_copy; + frag = frag->frags; + + /* after the first iteration, this value will be 0 */ + offset = 0; + } + + return copied; +} + +/* This helper routine will append multiple bytes, if there is no place for + * the data in current fragment then create new fragment and add it to + * the buffer. It assumes that the buffer has at least one fragment. + */ +size_t net_buf_append_bytes(struct net_buf *buf, size_t len, + const void *value, s32_t timeout, + net_buf_allocator_cb allocate_cb, void *user_data) +{ + struct net_buf *frag = net_buf_frag_last(buf); + size_t added_len = 0U; + const u8_t *value8 = value; + + do { + u16_t count = MIN(len, net_buf_tailroom(frag)); + + net_buf_add_mem(frag, value8, count); + len -= count; + added_len += count; + value8 += count; + + if (len == 0) { + return added_len; + } + + frag = allocate_cb(timeout, user_data); + if (!frag) { + return added_len; + } + + net_buf_frag_add(buf, frag); + } while (1); + + /* Unreachable */ + return 0; +} \ No newline at end of file diff --git a/components/bt/esp_ble_mesh/mesh_common/mesh_common.c b/components/bt/esp_ble_mesh/mesh_common/mesh_common.c new file mode 100644 index 0000000000..5aa4a13253 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/mesh_common.c @@ -0,0 +1,126 @@ +// Copyright 2017-2019 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_main.h" +#include "client_common.h" +#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 *)bt_mesh_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) { + bt_mesh_free(buf); + } +} + +u8_t bt_mesh_get_device_role(struct bt_mesh_model *model, bool srv_send) +{ + bt_mesh_client_user_data_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_user_data_t *)model->user_data; + + return client->msg_role; +} + +void bt_mesh_mutex_create(bt_mesh_mutex_t *mutex) +{ + if (!mutex) { + BT_ERR("%s, Invalid mutex", __func__); + return; + } + +#if CONFIG_SPIRAM_USE_MALLOC + mutex->buffer = heap_caps_calloc(1, sizeof(StaticQueue_t), MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM); + __ASSERT(mutex->buffer, "%s, Failed to create queue buffer", __func__); + mutex->mutex = xSemaphoreCreateMutexStatic(mutex->buffer); + __ASSERT(mutex->mutex, "%s, Failed to create static mutex", __func__); +#else + mutex->mutex = xSemaphoreCreateMutex(); + __ASSERT(mutex->mutex, "%s, Failed to create mutex", __func__); +#endif +} + +void bt_mesh_mutex_free(bt_mesh_mutex_t *mutex) +{ + if (!mutex) { + BT_ERR("%s, Invalid mutex", __func__); + return; + } + + if (mutex->mutex) { + vSemaphoreDelete(mutex->mutex); + mutex->mutex = NULL; +#if CONFIG_SPIRAM_USE_MALLOC + heap_caps_free(mutex->buffer); + mutex->buffer = NULL; +#endif + } +} + +void bt_mesh_mutex_lock(bt_mesh_mutex_t *mutex) +{ + if (!mutex) { + BT_ERR("%s, Invalid mutex", __func__); + return; + } + + if (mutex->mutex) { + xSemaphoreTake(mutex->mutex, portMAX_DELAY); + } +} + +void bt_mesh_mutex_unlock(bt_mesh_mutex_t *mutex) +{ + if (!mutex) { + BT_ERR("%s, Invalid mutex", __func__); + return; + } + + if (mutex->mutex) { + xSemaphoreGive(mutex->mutex); + } +} diff --git a/components/bt/esp_ble_mesh/mesh_common/mesh_kernel.c b/components/bt/esp_ble_mesh/mesh_common/mesh_kernel.c new file mode 100644 index 0000000000..d4597d8184 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/mesh_kernel.c @@ -0,0 +1,293 @@ +/* + * 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 "osi/hash_map.h" +#include "osi/alarm.h" +#include "osi/hash_functions.h" + +#include "mesh_common.h" +#include "provisioner_prov.h" + +static bt_mesh_mutex_t bm_alarm_lock; +static bt_mesh_mutex_t bm_list_lock; +static bt_mesh_mutex_t bm_buf_lock; +static bt_mesh_mutex_t bm_atomic_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_mutex_new(void) +{ + if (!bm_alarm_lock.mutex) { + bt_mesh_mutex_create(&bm_alarm_lock); + } +} + +static void bt_mesh_alarm_mutex_free(void) +{ + bt_mesh_mutex_free(&bm_alarm_lock); +} + +static void bt_mesh_alarm_lock(void) +{ + bt_mesh_mutex_lock(&bm_alarm_lock); +} + +static void bt_mesh_alarm_unlock(void) +{ + bt_mesh_mutex_unlock(&bm_alarm_lock); +} + +static void bt_mesh_list_mutex_new(void) +{ + if (!bm_list_lock.mutex) { + bt_mesh_mutex_create(&bm_list_lock); + } +} + +static void bt_mesh_list_mutex_free(void) +{ + bt_mesh_mutex_free(&bm_list_lock); +} + +void bt_mesh_list_lock(void) +{ + bt_mesh_mutex_lock(&bm_list_lock); +} + +void bt_mesh_list_unlock(void) +{ + bt_mesh_mutex_unlock(&bm_list_lock); +} + +static void bt_mesh_buf_mutex_new(void) +{ + if (!bm_buf_lock.mutex) { + bt_mesh_mutex_create(&bm_buf_lock); + } +} + +static void bt_mesh_buf_mutex_free(void) +{ + bt_mesh_mutex_free(&bm_buf_lock); +} + +void bt_mesh_buf_lock(void) +{ + bt_mesh_mutex_lock(&bm_buf_lock); +} + +void bt_mesh_buf_unlock(void) +{ + bt_mesh_mutex_unlock(&bm_buf_lock); +} + +static void bt_mesh_atomic_mutex_new(void) +{ + if (!bm_atomic_lock.mutex) { + bt_mesh_mutex_create(&bm_atomic_lock); + } +} + +static void bt_mesh_atomic_mutex_free(void) +{ + bt_mesh_mutex_free(&bm_atomic_lock); +} + +void bt_mesh_atomic_lock(void) +{ + bt_mesh_mutex_lock(&bm_atomic_lock); +} + +void bt_mesh_atomic_unlock(void) +{ + bt_mesh_mutex_unlock(&bm_atomic_lock); +} + +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) +{ + bt_mesh_alarm_mutex_new(); + bt_mesh_list_mutex_new(); + bt_mesh_buf_mutex_new(); + bt_mesh_atomic_mutex_new(); + 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, "%s, Failed to create hash map", __func__); +} + +void bt_mesh_k_deinit(void) +{ + bt_mesh_alarm_mutex_free(); + bt_mesh_list_mutex_free(); + bt_mesh_buf_mutex_free(); + bt_mesh_atomic_mutex_free(); + if (bm_alarm_hash_map) { + hash_map_free(bm_alarm_hash_map); + 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; + + if (!work || !bm_alarm_hash_map) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + k_work_init(&work->work, handler); + + bt_mesh_alarm_lock(); + if (!hash_map_has_key(bm_alarm_hash_map, (void *)work)) { + alarm = osi_alarm_new("bt_mesh", (osi_alarm_callback_t)handler, (void *)&work->work, 0); + if (alarm == NULL) { + BT_ERR("%s, Unable to create alarm", __func__); + bt_mesh_alarm_unlock(); + return; + } + if (!hash_map_set(bm_alarm_hash_map, work, (void *)alarm)) { + BT_ERR("%s Unable to add the timer to hash map.", __func__); + } + } + bt_mesh_alarm_unlock(); + + 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) +{ + if (!work || !bm_alarm_hash_map) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + 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_submit_periodic(struct k_delayed_work *work, s32_t period) +{ + if (!work || !bm_alarm_hash_map) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + 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 starting it. */ + osi_alarm_cancel(alarm); + osi_alarm_set_periodic(alarm, period); + + return 0; +} + +int k_delayed_work_cancel(struct k_delayed_work *work) +{ + if (!work || !bm_alarm_hash_map) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + 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) +{ + if (!work || !bm_alarm_hash_map) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + 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; + } + + osi_alarm_cancel(alarm); + hash_map_erase(bm_alarm_hash_map, work); + return 0; +} + +s32_t k_delayed_work_remaining_get(struct k_delayed_work *work) +{ + if (!work || !bm_alarm_hash_map) { + BT_ERR("%s, Invalid parameter", __func__); + return 0; + } + + 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; + } + + return osi_alarm_get_remaining_ms(alarm); +} diff --git a/components/bt/esp_ble_mesh/mesh_common/mesh_util.c b/components/bt/esp_ble_mesh/mesh_common/mesh_util.c new file mode 100644 index 0000000000..83949594b5 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_common/mesh_util.c @@ -0,0 +1,78 @@ +/* + * 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 "mesh_types.h" +#include "mesh_util.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 str[129]; + const u8_t *b = buf; + int i; + + len = MIN(len, (sizeof(str) - 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/esp_ble_mesh/mesh_core/access.c b/components/bt/esp_ble_mesh/mesh_core/access.c new file mode 100644 index 0000000000..8438bc736f --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/access.c @@ -0,0 +1,1326 @@ +/* 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 + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_ACCESS) + +#include "mesh.h" +#include "adv.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "mesh_main.h" +#include "mesh_common.h" +#include "provisioner_main.h" + +#include "generic_client.h" +#include "sensor_client.h" +#include "time_scene_client.h" +#include "lighting_client.h" + +#include "generic_server.h" +#include "sensor_server.h" +#include "time_scene_server.h" +#include "lighting_server.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 + { BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, bt_mesh_gen_onoff_srv_init }, + { BLE_MESH_MODEL_ID_GEN_LEVEL_SRV, bt_mesh_gen_level_srv_init }, + { BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV, bt_mesh_gen_def_trans_time_srv_init }, + { BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV, bt_mesh_gen_power_onoff_srv_init }, + { BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV, bt_mesh_gen_power_onoff_setup_srv_init }, + { BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV, bt_mesh_gen_power_level_srv_init }, + { BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV, bt_mesh_gen_power_level_setup_srv_init }, + { BLE_MESH_MODEL_ID_GEN_BATTERY_SRV, bt_mesh_gen_battery_srv_init }, + { BLE_MESH_MODEL_ID_GEN_LOCATION_SRV, bt_mesh_gen_location_srv_init }, + { BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV, bt_mesh_gen_location_setup_srv_init }, + { BLE_MESH_MODEL_ID_GEN_USER_PROP_SRV, bt_mesh_gen_user_prop_srv_init }, + { BLE_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV, bt_mesh_gen_admin_prop_srv_init }, + { BLE_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV, bt_mesh_gen_manu_prop_srv_init }, + { BLE_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV, bt_mesh_gen_client_prop_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV, bt_mesh_light_lightness_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV, bt_mesh_light_lightness_setup_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_CTL_SRV, bt_mesh_light_ctl_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV, bt_mesh_light_ctl_setup_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV, bt_mesh_light_ctl_temp_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_HSL_SRV, bt_mesh_light_hsl_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV, bt_mesh_light_hsl_hue_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV, bt_mesh_light_hsl_sat_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV, bt_mesh_light_hsl_setup_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_XYL_SRV, bt_mesh_light_xyl_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV, bt_mesh_light_xyl_setup_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_LC_SRV, bt_mesh_light_lc_srv_init }, + { BLE_MESH_MODEL_ID_LIGHT_LC_SETUP_SRV, bt_mesh_light_lc_setup_srv_init }, + { BLE_MESH_MODEL_ID_TIME_SRV, bt_mesh_time_srv_init }, + { BLE_MESH_MODEL_ID_TIME_SETUP_SRV, bt_mesh_time_setup_srv_init }, + { BLE_MESH_MODEL_ID_SCENE_SRV, bt_mesh_scene_srv_init }, + { BLE_MESH_MODEL_ID_SCENE_SETUP_SRV, bt_mesh_scene_setup_srv_init }, + { BLE_MESH_MODEL_ID_SCHEDULER_SRV, bt_mesh_scheduler_srv_init }, + { BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV, bt_mesh_scheduler_setup_srv_init }, + { BLE_MESH_MODEL_ID_SENSOR_SRV, bt_mesh_sensor_srv_init }, + { BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV, bt_mesh_sensor_setup_srv_init }, +}; + +static const struct { + const u16_t id; + int (*const deinit)(struct bt_mesh_model *model, bool primary); +} model_deinit[] = { + { BLE_MESH_MODEL_ID_CFG_SRV, bt_mesh_cfg_srv_deinit }, + { BLE_MESH_MODEL_ID_HEALTH_SRV, bt_mesh_health_srv_deinit }, +#if defined(CONFIG_BLE_MESH_CFG_CLI) + { BLE_MESH_MODEL_ID_CFG_CLI, bt_mesh_cfg_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_HEALTH_CLI) + { BLE_MESH_MODEL_ID_HEALTH_CLI, bt_mesh_health_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) + { BLE_MESH_MODEL_ID_GEN_ONOFF_CLI, bt_mesh_gen_onoff_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_LEVEL_CLI) + { BLE_MESH_MODEL_ID_GEN_LEVEL_CLI, bt_mesh_gen_level_cli_deinit }, +#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_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_POWER_ONOFF_CLI) + { BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI, bt_mesh_gen_pwr_onoff_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_POWER_LEVEL_CLI) + { BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI, bt_mesh_gen_pwr_level_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_BATTERY_CLI) + { BLE_MESH_MODEL_ID_GEN_BATTERY_CLI, bt_mesh_gen_battery_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_LOCATION_CLI) + { BLE_MESH_MODEL_ID_GEN_LOCATION_CLI, bt_mesh_gen_location_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_GENERIC_PROPERTY_CLI) + { BLE_MESH_MODEL_ID_GEN_PROP_CLI, bt_mesh_gen_property_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_SENSOR_CLI) + { BLE_MESH_MODEL_ID_SENSOR_CLI, bt_mesh_sensor_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_TIME_CLI) + { BLE_MESH_MODEL_ID_TIME_CLI, bt_mesh_time_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_SCENE_CLI) + { BLE_MESH_MODEL_ID_SCENE_CLI, bt_mesh_scene_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_SCHEDULER_CLI) + { BLE_MESH_MODEL_ID_SCHEDULER_CLI, bt_mesh_scheduler_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_LIGHTNESS_CLI) + { BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI, bt_mesh_light_lightness_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_CTL_CLI) + { BLE_MESH_MODEL_ID_LIGHT_CTL_CLI, bt_mesh_light_ctl_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_HSL_CLI) + { BLE_MESH_MODEL_ID_LIGHT_HSL_CLI, bt_mesh_light_hsl_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_XYL_CLI) + { BLE_MESH_MODEL_ID_LIGHT_XYL_CLI, bt_mesh_light_xyl_cli_deinit }, +#endif +#if defined(CONFIG_BLE_MESH_LIGHT_LC_CLI) + { BLE_MESH_MODEL_ID_LIGHT_LC_CLI, bt_mesh_light_lc_cli_deinit }, +#endif + { BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, bt_mesh_gen_onoff_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_LEVEL_SRV, bt_mesh_gen_level_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV, bt_mesh_gen_def_trans_time_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV, bt_mesh_gen_power_onoff_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV, bt_mesh_gen_power_onoff_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV, bt_mesh_gen_power_level_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV, bt_mesh_gen_power_level_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_BATTERY_SRV, bt_mesh_gen_battery_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_LOCATION_SRV, bt_mesh_gen_location_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV, bt_mesh_gen_location_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_USER_PROP_SRV, bt_mesh_gen_user_prop_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV, bt_mesh_gen_admin_prop_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV, bt_mesh_gen_manu_prop_srv_deinit }, + { BLE_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV, bt_mesh_gen_client_prop_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV, bt_mesh_light_lightness_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV, bt_mesh_light_lightness_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_CTL_SRV, bt_mesh_light_ctl_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV, bt_mesh_light_ctl_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV, bt_mesh_light_ctl_temp_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_HSL_SRV, bt_mesh_light_hsl_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV, bt_mesh_light_hsl_hue_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV, bt_mesh_light_hsl_sat_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV, bt_mesh_light_hsl_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_XYL_SRV, bt_mesh_light_xyl_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV, bt_mesh_light_xyl_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_LC_SRV, bt_mesh_light_lc_srv_deinit }, + { BLE_MESH_MODEL_ID_LIGHT_LC_SETUP_SRV, bt_mesh_light_lc_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_TIME_SRV, bt_mesh_time_srv_deinit }, + { BLE_MESH_MODEL_ID_TIME_SETUP_SRV, bt_mesh_time_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_SCENE_SRV, bt_mesh_scene_srv_deinit }, + { BLE_MESH_MODEL_ID_SCENE_SETUP_SRV, bt_mesh_scene_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_SCHEDULER_SRV, bt_mesh_scheduler_srv_deinit }, + { BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV, bt_mesh_scheduler_setup_srv_deinit }, + { BLE_MESH_MODEL_ID_SENSOR_SRV, bt_mesh_sensor_srv_deinit }, + { BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV, bt_mesh_sensor_setup_srv_deinit }, +}; + +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 = 0U, period = 0U; + + 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_INFO("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 = 0; + + 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_INFO("Publishing next time in %dms", delay); + k_delayed_work_submit(&mod->pub->timer, delay); + } +} + +static void publish_start(u16_t duration, int err, void *user_data) +{ + struct bt_mesh_model *mod = user_data; + struct bt_mesh_model_pub *pub = mod->pub; + + if (err) { + BT_ERR("Failed to publish: err %d", err); + return; + } + + /* Initialize the timestamp for the beginning of a new period */ + if (pub->count == BLE_MESH_PUB_TRANSMIT_COUNT(pub->retransmit)) { + pub->period_start = k_uptime_get_32(); + } +} + +static const struct bt_mesh_send_cb pub_sent_cb = { + .start = publish_start, + .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 = 0; + + 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 = 0; + int err = 0; + + BT_DBG("%s", __func__); + + period_ms = bt_mesh_model_pub_period_get(pub->mod); + BT_INFO("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); + + /* 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); + } +} + +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 = NULL; + + 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); + } + } +} + +static void mod_deinit(struct bt_mesh_model *mod, struct bt_mesh_elem *elem, + bool vnd, bool primary, void *user_data) +{ + int i; + + mod->elem = NULL; + + if (mod->pub) { + mod->pub->mod = NULL; + k_delayed_work_free(&mod->pub->timer); + } + + for (i = 0; i < ARRAY_SIZE(mod->keys); i++) { + mod->keys[i] = BLE_MESH_KEY_UNUSED; + } + + mod->flags = 0U; + mod->elem_idx = 0U; + mod->model_idx = 0U; + + if (vnd) { + return; + } + + for (i = 0; i < ARRAY_SIZE(model_deinit); i++) { + if (model_deinit[i].id == mod->id) { + model_deinit[i].deinit(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; +} + +int bt_mesh_comp_deregister(void) +{ + if (dev_comp == NULL) { + return -EINVAL; + } + + bt_mesh_model_foreach(mod_deinit, NULL); + + dev_primary_addr = BLE_MESH_ADDR_UNASSIGNED; + dev_comp = NULL; + + return 0; +} + +void bt_mesh_comp_provision(u16_t addr) +{ + int i; + + dev_primary_addr = addr; + + BT_INFO("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 = NULL; + u16_t *match = NULL; + 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) +{ + u16_t index = 0U; + + if (BLE_MESH_ADDR_IS_UNICAST(addr)) { + index = (addr - dev_comp->elem[0].addr); + if (index < dev_comp->elem_count) { + return &dev_comp->elem[index]; + } else { + return NULL; + } + } + + for (index = 0; index < dev_comp->elem_count; index++) { + struct bt_mesh_elem *elem = &dev_comp->elem[index]; + + if (bt_mesh_elem_find_group(elem, 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) +{ + int i; + + for (i = 0; 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; + } +} + +void bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf) +{ + struct bt_mesh_model *models = NULL, *model = NULL; + const struct bt_mesh_model_op *op = NULL; + u32_t opcode = 0U; + u8_t count = 0U; + int i; + + BT_INFO("app_idx 0x%04x src 0x%04x dst 0x%04x", rx->ctx.app_idx, + rx->ctx.addr, rx->ctx.recv_dst); + BT_INFO("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_INFO("OpCode 0x%08x", 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); + + switch (BLE_MESH_MODEL_OP_LEN(opcode)) { + case 1: + net_buf_simple_add_u8(msg, opcode); + break; + case 2: + net_buf_simple_add_be16(msg, opcode); + break; + case 3: + net_buf_simple_add_u8(msg, ((opcode >> 16) & 0xff)); + net_buf_simple_add_le16(msg, opcode & 0xffff); + break; + default: + BT_WARN("Unknown opcode format"); + break; + } +} + +static bool ready_to_send(u8_t role, u16_t dst) +{ + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned() && role == NODE) { + return true; + } else if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en() && role == PROVISIONER) { + if (!bt_mesh_provisioner_check_msg_dst(dst)) { + BT_ERR("%s, Failed to find DST 0x%04x", __func__, dst); + return false; + } + return true; + } else if (IS_ENABLED(CONFIG_BLE_MESH_FAST_PROV) && bt_mesh_is_provisioned() && role == FAST_PROV) { + return true; + } + + return false; +} + +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) +{ + u8_t role = 0U; + + role = bt_mesh_get_device_role(model, tx->ctx->srv_send); + if (role == ROLE_NVAL) { + BT_ERR("%s, Failed to get model role", __func__); + return -EINVAL; + } + + BT_INFO("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", tx->ctx->net_idx, + tx->ctx->app_idx, tx->ctx->addr); + BT_INFO("len %u: %s", msg->len, bt_hex(msg->data, msg->len)); + + if (!ready_to_send(role, tx->ctx->addr)) { + 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 = 0U; + + role = bt_mesh_get_device_role(model, ctx->srv_send); + if (role == ROLE_NVAL) { + BT_ERR("%s, Failed to get model role", __func__); + return -EINVAL; + } + + sub = bt_mesh_tx_netkey_get(role, ctx->net_idx); + 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 = 0; + + BT_DBG("%s", __func__); + + if (!pub || !pub->msg) { + 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; + } + + key = bt_mesh_tx_appkey_get(pub->dev_role, pub->key); + 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; + + tx.sub = bt_mesh_tx_netkey_get(pub->dev_role, ctx.net_idx); + if (!tx.sub) { + BT_ERR("%s, Failed to get subnet", __func__); + return -EADDRNOTAVAIL; + } + + pub->count = BLE_MESH_PUB_TRANSMIT_COUNT(pub->retransmit); + + BT_INFO("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) +{ + int i; + + for (i = 0; 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) +{ + int i; + + for (i = 0; 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; +} + +/* APIs used by messages encryption in upper transport layer & network layer */ +struct bt_mesh_subnet *bt_mesh_tx_netkey_get(u8_t role, u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned() && role == NODE) { + sub = bt_mesh_subnet_get(net_idx); + } else if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en() && role == PROVISIONER) { + sub = bt_mesh_provisioner_subnet_get(net_idx); + } else if (IS_ENABLED(CONFIG_BLE_MESH_FAST_PROV) && bt_mesh_is_provisioned() && role == FAST_PROV) { + sub = bt_mesh_fast_prov_subnet_get(net_idx); + } + + return sub; +} + +const u8_t *bt_mesh_tx_devkey_get(u8_t role, u16_t dst) +{ + const u8_t *key = NULL; + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned() && role == NODE) { + key = bt_mesh.dev_key; + } else if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en() && role == PROVISIONER) { + key = bt_mesh_provisioner_dev_key_get(dst); + } else if (IS_ENABLED(CONFIG_BLE_MESH_FAST_PROV) && bt_mesh_is_provisioned() && role == FAST_PROV) { + key = bt_mesh_fast_prov_dev_key_get(dst); + } + + return key; +} + +struct bt_mesh_app_key *bt_mesh_tx_appkey_get(u8_t role, u16_t app_idx) +{ + struct bt_mesh_app_key *key = NULL; + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned() && role == NODE) { + key = bt_mesh_app_key_find(app_idx); + } else if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en() && role == PROVISIONER) { + key = bt_mesh_provisioner_app_key_find(app_idx); + } else if (IS_ENABLED(CONFIG_BLE_MESH_FAST_PROV) && bt_mesh_is_provisioned() && role == FAST_PROV) { + key = bt_mesh_fast_prov_app_key_find(app_idx); + } + + return key; +} + +/* APIs used by messages decryption in network layer & upper transport layer */ +size_t bt_mesh_rx_netkey_size(void) +{ + size_t size = 0U; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioned()) { + size = ARRAY_SIZE(bt_mesh.sub); + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + size = ARRAY_SIZE(bt_mesh.p_sub); + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + size = ARRAY_SIZE(bt_mesh.sub); + if (bt_mesh_is_provisioner_en()) { + size += ARRAY_SIZE(bt_mesh.p_sub); + } +#endif + + return size; +} + +struct bt_mesh_subnet *bt_mesh_rx_netkey_get(size_t index) +{ + struct bt_mesh_subnet *sub = NULL; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioned()) { + sub = &bt_mesh.sub[index]; + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + sub = bt_mesh.p_sub[index]; + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (index < ARRAY_SIZE(bt_mesh.sub)) { + sub = &bt_mesh.sub[index]; + } else { + sub = bt_mesh.p_sub[index - ARRAY_SIZE(bt_mesh.sub)]; + } +#endif + + return sub; +} + +size_t bt_mesh_rx_devkey_size(void) +{ + size_t size = 0U; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioned()) { + size = 1; + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + size = 1; + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + size = 1; + if (bt_mesh_is_provisioner_en()) { + size += 1; + } +#endif + + return size; +} + +const u8_t *bt_mesh_rx_devkey_get(size_t index, u16_t src) +{ + const u8_t *key = NULL; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioned()) { + key = bt_mesh.dev_key; + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + key = bt_mesh_provisioner_dev_key_get(src); + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (index < 1) { + key = bt_mesh.dev_key; + } else { + key = bt_mesh_provisioner_dev_key_get(src); + } +#endif + + return key; +} + +size_t bt_mesh_rx_appkey_size(void) +{ + size_t size = 0U; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioned()) { + size = ARRAY_SIZE(bt_mesh.app_keys); + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + size = ARRAY_SIZE(bt_mesh.p_app_keys); + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + size = ARRAY_SIZE(bt_mesh.app_keys); + if (bt_mesh_is_provisioner_en()) { + size += ARRAY_SIZE(bt_mesh.p_app_keys); + } +#endif + + return size; +} + +struct bt_mesh_app_key *bt_mesh_rx_appkey_get(size_t index) +{ + struct bt_mesh_app_key *key = NULL; + +#if CONFIG_BLE_MESH_NODE && !CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioned()) { + key = &bt_mesh.app_keys[index]; + } +#endif + +#if !CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + key = bt_mesh.p_app_keys[index]; + } +#endif + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PROVISIONER + if (index < ARRAY_SIZE(bt_mesh.app_keys)) { + key = &bt_mesh.app_keys[index]; + } else { + key = bt_mesh.p_app_keys[index - ARRAY_SIZE(bt_mesh.app_keys)]; + } +#endif + + return key; +} diff --git a/components/bt/esp_ble_mesh/mesh_core/access.h b/components/bt/esp_ble_mesh/mesh_core/access.h new file mode 100644 index 0000000000..944548c568 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/access.h @@ -0,0 +1,77 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ACCESS_H_ +#define _ACCESS_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); +int bt_mesh_comp_deregister(void); + +struct bt_mesh_subnet *bt_mesh_tx_netkey_get(u8_t role, u16_t net_idx); + +const u8_t *bt_mesh_tx_devkey_get(u8_t role, u16_t dst); + +struct bt_mesh_app_key *bt_mesh_tx_appkey_get(u8_t role, u16_t app_idx); + +size_t bt_mesh_rx_netkey_size(void); + +struct bt_mesh_subnet *bt_mesh_rx_netkey_get(size_t index); + +size_t bt_mesh_rx_devkey_size(void); + +const u8_t *bt_mesh_rx_devkey_get(size_t index, u16_t src); + +size_t bt_mesh_rx_appkey_size(void); + +struct bt_mesh_app_key *bt_mesh_rx_appkey_get(size_t index); + +#endif /* _ACCESS_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/adv.c b/components/bt/esp_ble_mesh/mesh_core/adv.c new file mode 100644 index 0000000000..ea3672e3ca --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/adv.c @@ -0,0 +1,866 @@ +/* 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 "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" + +#include "osi/thread.h" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_ADV) + +#include "mesh.h" +#include "mesh_hci.h" +#include "adv.h" +#include "beacon.h" +#include "prov.h" +#include "foundation.h" +#include "proxy_server.h" +#include "proxy_client.h" +#include "provisioner_prov.h" +#include "mesh_bearer_adapt.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 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, + 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]; + +struct bt_mesh_queue { + QueueHandle_t queue; +#if CONFIG_SPIRAM_USE_MALLOC + StaticQueue_t *buffer; + u8_t *storage; +#endif +}; + +static struct bt_mesh_queue xBleMeshQueue; +/* We reserve one queue for bt_mesh_adv_update() */ +#define BLE_MESH_QUEUE_SIZE (CONFIG_BLE_MESH_ADV_BUF_COUNT + 1) + +#if defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) +NET_BUF_POOL_DEFINE(relay_adv_buf_pool, CONFIG_BLE_MESH_RELAY_ADV_BUF_COUNT, + BLE_MESH_ADV_DATA_SIZE, BLE_MESH_ADV_USER_DATA_SIZE, NULL); + +static struct bt_mesh_adv relay_adv_pool[CONFIG_BLE_MESH_RELAY_ADV_BUF_COUNT]; + +static struct bt_mesh_queue xBleMeshRelayQueue; +#define BLE_MESH_RELAY_QUEUE_SIZE CONFIG_BLE_MESH_RELAY_ADV_BUF_COUNT + +static QueueSetHandle_t xBleMeshQueueSet; +#define BLE_MESH_QUEUE_SET_SIZE (BLE_MESH_QUEUE_SIZE + BLE_MESH_RELAY_QUEUE_SIZE) + +#define BLE_MESH_RELAY_TIME_INTERVAL K_SECONDS(6) +#define BLE_MESH_MAX_TIME_INTERVAL 0xFFFFFFFF + +static bool ignore_relay_packet(u32_t timestamp); +#endif /* defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) */ + +struct bt_mesh_adv_task { + TaskHandle_t handle; +#if CONFIG_SPIRAM_USE_MALLOC + StaticTask_t *task; + StackType_t *stack; +#endif +}; + +static struct bt_mesh_adv_task adv_task; + +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 = 0U, adv_int = 0U; + struct bt_mesh_adv_data ad = {0}; + int err = 0; + + 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; + + bt_mesh_adv_buf_ref_debug(__func__, buf, 4U, BLE_MESH_BUF_REF_SMALL); + + 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) +{ +#if defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) + QueueSetMemberHandle_t handle = NULL; +#endif + bt_mesh_msg_t msg = {0}; + struct net_buf **buf = NULL; + + buf = (struct net_buf **)(&msg.arg); + + BT_DBG("%s, starts", __func__); + + while (1) { + *buf = NULL; +#if !defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) +#if (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_SERVER + xQueueReceive(xBleMeshQueue.queue, &msg, K_NO_WAIT); + while (!(*buf)) { + s32_t timeout; + BT_DBG("Mesh Proxy Advertising start"); + timeout = bt_mesh_proxy_adv_start(); + BT_DBG("Mesh Proxy Advertising up to %d ms", timeout); + xQueueReceive(xBleMeshQueue.queue, &msg, timeout); + BT_DBG("Mesh Proxy Advertising stop"); + bt_mesh_proxy_adv_stop(); + } +#else + xQueueReceive(xBleMeshQueue.queue, &msg, portMAX_DELAY); +#endif /* (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || CONFIG_BLE_MESH_GATT_PROXY_SERVER */ +#else /* !defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) */ +#if (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_SERVER + handle = xQueueSelectFromSet(xBleMeshQueueSet, K_NO_WAIT); + if (handle) { + if (uxQueueMessagesWaiting(xBleMeshQueue.queue)) { + xQueueReceive(xBleMeshQueue.queue, &msg, K_NO_WAIT); + } else if (uxQueueMessagesWaiting(xBleMeshRelayQueue.queue)) { + xQueueReceive(xBleMeshRelayQueue.queue, &msg, K_NO_WAIT); + } + } else { + while (!(*buf)) { + s32_t timeout = 0; + BT_DBG("Mesh Proxy Advertising start"); + timeout = bt_mesh_proxy_adv_start(); + BT_DBG("Mesh Proxy Advertising up to %d ms", timeout); + handle = xQueueSelectFromSet(xBleMeshQueueSet, timeout); + BT_DBG("Mesh Proxy Advertising stop"); + bt_mesh_proxy_adv_stop(); + if (handle) { + if (uxQueueMessagesWaiting(xBleMeshQueue.queue)) { + xQueueReceive(xBleMeshQueue.queue, &msg, K_NO_WAIT); + } else if (uxQueueMessagesWaiting(xBleMeshRelayQueue.queue)) { + xQueueReceive(xBleMeshRelayQueue.queue, &msg, K_NO_WAIT); + } + } + } + } +#else + handle = xQueueSelectFromSet(xBleMeshQueueSet, portMAX_DELAY); + if (handle) { + if (uxQueueMessagesWaiting(xBleMeshQueue.queue)) { + xQueueReceive(xBleMeshQueue.queue, &msg, K_NO_WAIT); + } else if (uxQueueMessagesWaiting(xBleMeshRelayQueue.queue)) { + xQueueReceive(xBleMeshRelayQueue.queue, &msg, K_NO_WAIT); + } + } +#endif /* (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || CONFIG_BLE_MESH_GATT_PROXY_SERVER */ +#endif /* !defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) */ + + if (*buf == NULL) { + continue; + } + + /* busy == 0 means this was canceled */ + if (BLE_MESH_ADV(*buf)->busy) { + BLE_MESH_ADV(*buf)->busy = 0U; +#if !defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) + if (adv_send(*buf)) { + BT_WARN("%s, Failed to send adv packet", __func__); + } +#else /* !defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) */ + if (msg.relay && ignore_relay_packet(msg.timestamp)) { + /* If the interval between "current time - msg.timestamp" is bigger than + * BLE_MESH_RELAY_TIME_INTERVAL, this relay packet will not be sent. + */ + BT_INFO("%s, Ignore relay packet", __func__); + net_buf_unref(*buf); + } else { + if (adv_send(*buf)) { + BT_WARN("%s, Failed to send adv packet", __func__); + } + } +#endif + } else { + bt_mesh_adv_buf_ref_debug(__func__, *buf, 1U, BLE_MESH_BUF_REF_EQUAL); + net_buf_unref(*buf); + } + + /* Give other threads a chance to run */ + taskYIELD(); + } +} + +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 = NULL; + struct net_buf *buf = NULL; + + 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; +} + +void bt_mesh_unref_buf_from_pool(struct net_buf_pool *pool) +{ + int i; + + if (pool == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + for (i = 0; i < pool->buf_count; i++) { + struct net_buf *buf = &pool->__bufs[i]; + if (buf->ref > 1U) { + buf->ref = 1U; + net_buf_unref(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_adv_buf_ref_debug(const char *func, struct net_buf *buf, + u8_t ref_cmp, bt_mesh_buf_ref_flag_t flag) +{ + if (buf == NULL || func == NULL || flag >= BLE_MESH_BUF_REF_MAX) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + switch (flag) { + case BLE_MESH_BUF_REF_EQUAL: + if (buf->ref != ref_cmp) { + BT_ERR("Unexpected ref %d in %s, expect to equal to %d", buf->ref, func, ref_cmp); + } + break; + case BLE_MESH_BUF_REF_SMALL: + if (buf->ref >= ref_cmp) { + BT_ERR("Unexpected ref %d in %s, expect to smaller than %d", buf->ref, func, ref_cmp); + } + break; + default: + break; + } +} + +static void bt_mesh_unref_buf(bt_mesh_msg_t *msg) +{ + struct net_buf *buf = NULL; + + if (msg->arg) { + buf = (struct net_buf *)msg->arg; + BLE_MESH_ADV(buf)->busy = 0U; + net_buf_unref(buf); + } + + return; +} + +static void bt_mesh_task_post(bt_mesh_msg_t *msg, uint32_t timeout) +{ + BT_DBG("%s", __func__); + + if (xBleMeshQueue.queue == NULL) { + BT_ERR("%s, Invalid queue", __func__); + return; + } + + if (xQueueSend(xBleMeshQueue.queue, msg, timeout) != pdTRUE) { + BT_ERR("%s, Failed to send item to queue", __func__); + bt_mesh_unref_buf(msg); + } +} + +void bt_mesh_adv_send(struct net_buf *buf, const struct bt_mesh_send_cb *cb, + void *cb_data) +{ + bt_mesh_msg_t msg = { + .relay = false, + }; + + 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_adv_buf_ref_debug(__func__, buf, 3U, BLE_MESH_BUF_REF_SMALL); + + msg.arg = (void *)net_buf_ref(buf); + bt_mesh_task_post(&msg, portMAX_DELAY); +} + +void bt_mesh_adv_update(void) +{ + bt_mesh_msg_t msg = { + .relay = false, + .arg = NULL, + }; + + BT_DBG("%s", __func__); + + bt_mesh_task_post(&msg, K_NO_WAIT); +} + +#if defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) +static bool ignore_relay_packet(u32_t timestamp) +{ + u32_t now = k_uptime_get_32(); + u32_t interval = 0U; + + if (now >= timestamp) { + interval = now - timestamp; + } else { + interval = BLE_MESH_MAX_TIME_INTERVAL - (timestamp - now) + 1; + } + + return (interval >= BLE_MESH_RELAY_TIME_INTERVAL) ? true : false; +} + +static struct bt_mesh_adv *relay_adv_alloc(int id) +{ + return &relay_adv_pool[id]; +} + +struct net_buf *bt_mesh_relay_adv_create(enum bt_mesh_adv_type type, u8_t xmit, + s32_t timeout) +{ + return bt_mesh_adv_create_from_pool(&relay_adv_buf_pool, relay_adv_alloc, type, + xmit, timeout); +} + +static void ble_mesh_relay_task_post(bt_mesh_msg_t *msg, uint32_t timeout) +{ + QueueSetMemberHandle_t handle = NULL; + bt_mesh_msg_t old_msg = {0}; + + BT_DBG("%s", __func__); + + if (xBleMeshRelayQueue.queue == NULL) { + BT_ERR("%s, Invalid relay queue", __func__); + return; + } + + if (xQueueSend(xBleMeshRelayQueue.queue, msg, timeout) == pdTRUE) { + return; + } + + /** + * If failed to send packet to the relay queue(queue is full), we will + * remove the oldest packet in the queue and put the new one into it. + */ + handle = xQueueSelectFromSet(xBleMeshQueueSet, K_NO_WAIT); + if (handle && uxQueueMessagesWaiting(xBleMeshRelayQueue.queue)) { + BT_INFO("%s, Full queue, remove the oldest relay packet", __func__); + /* Remove the oldest relay packet from queue */ + if (xQueueReceive(xBleMeshRelayQueue.queue, &old_msg, K_NO_WAIT) != pdTRUE) { + BT_ERR("%s, Failed to remove item from queue", __func__); + bt_mesh_unref_buf(msg); + return; + } + /* Unref buf used for the oldest relay packet */ + bt_mesh_unref_buf(&old_msg); + /* Send the latest relay packet to queue */ + if (xQueueSend(xBleMeshRelayQueue.queue, msg, K_NO_WAIT) != pdTRUE) { + BT_ERR("%s, Failed to send item to relay queue", __func__); + bt_mesh_unref_buf(msg); + return; + } + } else { + BT_WARN("%s, Empty queue, but failed to send the relay packet", __func__); + bt_mesh_unref_buf(msg); + } +} + +void bt_mesh_relay_adv_send(struct net_buf *buf, const struct bt_mesh_send_cb *cb, + void *cb_data, u16_t src, u16_t dst) +{ + bt_mesh_msg_t msg = { + .relay = true, + }; + + 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; + + msg.arg = (void *)net_buf_ref(buf); + msg.src = src; + msg.dst = dst; + msg.timestamp = k_uptime_get_32(); + /* Use K_NO_WAIT here, if xBleMeshRelayQueue is full return immediately */ + ble_mesh_relay_task_post(&msg, K_NO_WAIT); +} + +u16_t bt_mesh_get_stored_relay_count(void) +{ + return (u16_t)uxQueueMessagesWaiting(xBleMeshRelayQueue.queue); +} +#endif /* #if defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) */ + +const bt_mesh_addr_t *bt_mesh_pba_get_addr(void) +{ + return dev_addr; +} + +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT +static bool bt_mesh_is_adv_flags_valid(struct net_buf_simple *buf) +{ + u8_t flags = 0U; + + if (buf->len != 1U) { + 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 */ + ((void) flags); + + return true; +} + +static bool bt_mesh_is_adv_srv_uuid_valid(struct net_buf_simple *buf, u16_t *uuid) +{ + if (buf->len != 2U) { + 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; + } + + if (*uuid == BLE_MESH_UUID_MESH_PROV_VAL && + bt_mesh_is_provisioner_en() == false) { + return false; + } + + if (*uuid == BLE_MESH_UUID_MESH_PROXY_VAL && + !IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY_CLIENT)) { + return false; + } + + return true; +} + +#define BLE_MESH_PROV_SRV_DATA_LEN 0x12 +#define BLE_MESH_PROXY_SRV_DATA_LEN1 0x09 +#define BLE_MESH_PROXY_SRV_DATA_LEN2 0x11 + +static void bt_mesh_adv_srv_data_recv(struct net_buf_simple *buf, const bt_mesh_addr_t *addr, u16_t uuid, s8_t rssi) +{ + u16_t type = 0U; + + if (!buf || !addr) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + type = net_buf_simple_pull_le16(buf); + if (type != uuid) { + BT_DBG("%s, Invalid Mesh Service Data UUID 0x%04x", __func__, type); + return; + } + + switch (type) { +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + case BLE_MESH_UUID_MESH_PROV_VAL: + if (bt_mesh_is_provisioner_en()) { + 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 handle Mesh Prov Service Data"); + bt_mesh_provisioner_prov_adv_ind_recv(buf, addr, rssi); + } + break; +#endif +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT + 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_WARN("%s, Invalid Mesh Proxy Service Data length %d", __func__, buf->len); + return; + } + + BT_DBG("Start to handle Mesh Proxy Service Data"); + bt_mesh_proxy_client_adv_ind_recv(buf, addr, rssi); + break; +#endif + default: + break; + } +} +#endif + +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) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT + u16_t uuid = 0U; +#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 (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node()) { + bt_mesh_pb_adv_recv(buf); + } + if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en()) { + bt_mesh_provisioner_pb_adv_recv(buf); + } + break; +#endif /* CONFIG_BLE_MESH_PB_ADV */ + case BLE_MESH_DATA_MESH_BEACON: + bt_mesh_beacon_recv(buf, rssi); + break; +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT + case BLE_MESH_DATA_FLAGS: + if (!bt_mesh_is_adv_flags_valid(buf)) { + BT_DBG("Adv Flags mismatch, ignore this adv pkt"); + return; + } + break; + case BLE_MESH_DATA_UUID16_ALL: + if (!bt_mesh_is_adv_srv_uuid_valid(buf, &uuid)) { + BT_DBG("Adv Service UUID mismatch, ignore this adv pkt"); + return; + } + break; + case BLE_MESH_DATA_SVC_DATA16: + bt_mesh_adv_srv_data_recv(buf, addr, uuid, rssi); + break; +#endif + default: + break; + } + + net_buf_simple_restore(buf, &state); + net_buf_simple_pull(buf, len); + } + + return; +} + +void bt_mesh_adv_init(void) +{ +#if !CONFIG_SPIRAM_USE_MALLOC + xBleMeshQueue.queue = xQueueCreate(BLE_MESH_QUEUE_SIZE, sizeof(bt_mesh_msg_t)); + __ASSERT(xBleMeshQueue.queue, "%s, Failed to create queue", __func__); +#else + xBleMeshQueue.buffer = heap_caps_calloc(1, sizeof(StaticQueue_t), MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM); + __ASSERT(xBleMeshQueue.buffer, "%s, Failed to create queue buffer", __func__); + xBleMeshQueue.storage = heap_caps_calloc(1, (BLE_MESH_QUEUE_SIZE * sizeof(bt_mesh_msg_t)), MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM); + __ASSERT(xBleMeshQueue.storage, "%s, Failed to create queue storage", __func__); + xBleMeshQueue.queue = xQueueCreateStatic(BLE_MESH_QUEUE_SIZE, sizeof(bt_mesh_msg_t), (uint8_t*)xBleMeshQueue.storage, xBleMeshQueue.buffer); + __ASSERT(xBleMeshQueue.queue, "%s, Failed to create static queue", __func__); +#endif + +#if defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) +#if !CONFIG_SPIRAM_USE_MALLOC + xBleMeshRelayQueue.queue = xQueueCreate(BLE_MESH_RELAY_QUEUE_SIZE, sizeof(bt_mesh_msg_t)); + __ASSERT(xBleMeshRelayQueue.queue, "%s, Failed to create relay queue", __func__); +#else + xBleMeshRelayQueue.buffer = heap_caps_calloc(1, sizeof(StaticQueue_t), MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM); + __ASSERT(xBleMeshRelayQueue.buffer, "%s, Failed to create relay queue buffer", __func__); + xBleMeshRelayQueue.storage = heap_caps_calloc(1, (BLE_MESH_RELAY_QUEUE_SIZE * sizeof(bt_mesh_msg_t)), MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM); + __ASSERT(xBleMeshRelayQueue.storage, "%s, Failed to create relay queue storage", __func__); + xBleMeshRelayQueue.queue = xQueueCreateStatic(BLE_MESH_RELAY_QUEUE_SIZE, sizeof(bt_mesh_msg_t), (uint8_t*)xBleMeshRelayQueue.storage, xBleMeshRelayQueue.buffer); + __ASSERT(xBleMeshRelayQueue.queue, "%s, Failed to create static relay queue", __func__); +#endif + + xBleMeshQueueSet = xQueueCreateSet(BLE_MESH_QUEUE_SET_SIZE); + __ASSERT(xBleMeshQueueSet, "%s, Failed to create queue set", __func__); + xQueueAddToSet(xBleMeshQueue.queue, xBleMeshQueueSet); + xQueueAddToSet(xBleMeshRelayQueue.queue, xBleMeshQueueSet); +#endif /* defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) */ + +#if !CONFIG_SPIRAM_USE_MALLOC + int ret = xTaskCreatePinnedToCore(adv_thread, "BLE_Mesh_ADV_Task", BLE_MESH_ADV_TASK_STACK_SIZE, NULL, + configMAX_PRIORITIES - 5, &adv_task.handle, BLE_MESH_ADV_TASK_CORE); + __ASSERT(ret == pdTRUE, "%s, Failed to create adv thread", __func__); +#else + adv_task.task = heap_caps_calloc(1, sizeof(StaticTask_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT); + __ASSERT(adv_task.task, "%s, Failed to create adv thread task", __func__); +#if CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY + adv_task.stack = heap_caps_calloc(1, BLE_MESH_ADV_TASK_STACK_SIZE * sizeof(StackType_t), MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM); +#else + adv_task.stack = heap_caps_calloc(1, BLE_MESH_ADV_TASK_STACK_SIZE * sizeof(StackType_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT); +#endif + __ASSERT(adv_task.stack, "%s, Failed to create adv thread stack", __func__); + adv_task.handle = xTaskCreateStaticPinnedToCore(adv_thread, "BLE_Mesh_ADV_Task", BLE_MESH_ADV_TASK_STACK_SIZE, NULL, + configMAX_PRIORITIES - 5, adv_task.stack, adv_task.task, BLE_MESH_ADV_TASK_CORE); + __ASSERT(adv_task.stack, "%s, Failed to create static adv thread stack", __func__); +#endif +} + +void bt_mesh_adv_deinit(void) +{ + if (xBleMeshQueue.queue == NULL) { + return; + } + +#if defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) + xQueueRemoveFromSet(xBleMeshQueue.queue, xBleMeshQueueSet); + xQueueRemoveFromSet(xBleMeshRelayQueue.queue, xBleMeshQueueSet); + + vQueueDelete(xBleMeshRelayQueue.queue); + xBleMeshRelayQueue.queue = NULL; +#if CONFIG_SPIRAM_USE_MALLOC + heap_caps_free(xBleMeshRelayQueue.buffer); + xBleMeshRelayQueue.buffer = NULL; + heap_caps_free(xBleMeshRelayQueue.storage); + xBleMeshRelayQueue.storage = NULL; +#endif + + bt_mesh_unref_buf_from_pool(&relay_adv_buf_pool); + memset(relay_adv_pool, 0, sizeof(relay_adv_pool)); + + vQueueDelete(xBleMeshQueueSet); + xBleMeshQueueSet = NULL; +#endif /* defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) */ + + vQueueDelete(xBleMeshQueue.queue); + xBleMeshQueue.queue = NULL; +#if CONFIG_SPIRAM_USE_MALLOC + heap_caps_free(xBleMeshQueue.buffer); + xBleMeshQueue.buffer = NULL; + heap_caps_free(xBleMeshQueue.storage); + xBleMeshQueue.storage = NULL; +#endif + + bt_mesh_unref_buf_from_pool(&adv_buf_pool); + memset(adv_pool, 0, sizeof(adv_pool)); + + vTaskDelete(adv_task.handle); + adv_task.handle = NULL; +#if CONFIG_SPIRAM_USE_MALLOC + heap_caps_free(adv_task.stack); + adv_task.stack = NULL; + /* Delay certain period for free adv_task.task */ + vTaskDelay(10 / portTICK_PERIOD_MS); + heap_caps_free(adv_task.task); + adv_task.task = NULL; +#endif +} + +int bt_mesh_scan_enable(void) +{ + int err = 0; + + 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__); + + err = bt_le_scan_start(&scan_param, bt_mesh_scan_cb); + if (err && err != -EALREADY) { + BT_ERR("starting scan failed (err %d)", err); + return err; + } + + return 0; +} + +int bt_mesh_scan_disable(void) +{ + int err = 0; + + BT_DBG("%s", __func__); + + err = bt_le_scan_stop(); + if (err && err != -EALREADY) { + BT_ERR("stopping scan failed (err %d)", err); + return err; + } + + return 0; +} diff --git a/components/bt/esp_ble_mesh/mesh_core/adv.h b/components/bt/esp_ble_mesh/mesh_core/adv.h new file mode 100644 index 0000000000..8d2c05656c --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/adv.h @@ -0,0 +1,100 @@ +/* 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_access.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 { + bool relay; /* Flag indicates if the packet is a relayed one */ + void *arg; /* Pointer to the struct net_buf */ + u16_t src; /* Source address for relay packets */ + u16_t dst; /* Destination address for relay packets */ + u32_t timestamp; /* Timestamp recorded when the relay packet is posted to queue */ +} 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; + + /* 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); + +typedef enum { + BLE_MESH_BUF_REF_EQUAL, + BLE_MESH_BUF_REF_SMALL, + BLE_MESH_BUF_REF_MAX, +} bt_mesh_buf_ref_flag_t; + +void bt_mesh_adv_buf_ref_debug(const char *func, struct net_buf *buf, + u8_t ref_cmp, bt_mesh_buf_ref_flag_t flag); + +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_unref_buf_from_pool(struct net_buf_pool *pool); + +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); + +struct net_buf *bt_mesh_relay_adv_create(enum bt_mesh_adv_type type, u8_t xmit, + s32_t timeout); + +void bt_mesh_relay_adv_send(struct net_buf *buf, const struct bt_mesh_send_cb *cb, + void *cb_data, u16_t src, u16_t dst); + +u16_t bt_mesh_get_stored_relay_count(void); + +void bt_mesh_adv_update(void); + +void bt_mesh_adv_init(void); +void bt_mesh_adv_deinit(void); + +int bt_mesh_scan_enable(void); + +int bt_mesh_scan_disable(void); + +#endif /* _ADV_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/beacon.c b/components/bt/esp_ble_mesh/mesh_core/beacon.c new file mode 100644 index 0000000000..1e720eb08c --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/beacon.c @@ -0,0 +1,475 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_BEACON) + +#include "adv.h" +#include "mesh.h" +#include "prov.h" +#include "crypto.h" +#include "beacon.h" +#include "access.h" +#include "foundation.h" +#include "proxy_client.h" +#include "mesh_main.h" +#include "provisioner_prov.h" +#include "provisioner_main.h" + +#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]) +{ + size_t subnet_size = 0U; + int i = 0; + + subnet_size = bt_mesh_rx_netkey_size(); + + for (i = 0; i < subnet_size; i++) { + struct bt_mesh_subnet *sub = bt_mesh_rx_netkey_get(i); + + if (sub == NULL || 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 = NULL; + + 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_INFO("net_idx 0x%04x flags 0x%02x NetID %s", sub->net_idx, + flags, bt_hex(keys->net_id, 8)); + BT_INFO("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(); + size_t subnet_size = 0U; + int i = 0; + + BT_DBG("%s", __func__); + + subnet_size = bt_mesh_rx_netkey_size(); + + for (i = 0; i < subnet_size; i++) { + struct bt_mesh_subnet *sub = bt_mesh_rx_netkey_get(i); + struct net_buf *buf; + u32_t time_diff; + + if (sub == NULL || 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; + } + + /** + * If a node enables the Proxy Client functionality, and it + * succeeds to send Secure Network Beacon with GATT bearer, + * here we will continue to send Secure Network Beacon of + * other subnets. + */ +#if defined(CONFIG_BLE_MESH_GATT_PROXY_CLIENT) + if (bt_mesh_proxy_client_beacon_send(sub)) { + continue; + } +#endif + + 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; +} + +#if defined(CONFIG_BLE_MESH_NODE) +static int unprovisioned_beacon_send(void) +{ +#if defined(CONFIG_BLE_MESH_PB_ADV) + const struct bt_mesh_prov *prov = NULL; + u8_t uri_hash[16] = { 0 }; + struct net_buf *buf = NULL; + u16_t oob_info = 0U; + + 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; +} +#else /* CONFIG_BLE_MESH_NODE */ +static int unprovisioned_beacon_send(void) +{ + return 0; +} +#endif /* CONFIG_BLE_MESH_NODE */ + +static void update_beacon_observation(void) +{ + static bool first_half; + size_t subnet_size = 0U; + int i = 0; + + /* 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; + } + + subnet_size = bt_mesh_rx_netkey_size(); + + for (i = 0; i < subnet_size; i++) { + struct bt_mesh_subnet *sub = bt_mesh_rx_netkey_get(i); + + if (sub == NULL || sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + + sub->beacons_last = sub->beacons_cur; + sub->beacons_cur = 0U; + } +} + +static bool ready_to_send(void) +{ + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + return true; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en()) { + if (bt_mesh_provisioner_get_all_node_count()) { + return true; + } + } + + return false; +} + +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_NODE) && bt_mesh_is_node() && + IS_ENABLED(CONFIG_BLE_MESH_PROV) && bt_prov_active()) { + k_delayed_work_submit(&beacon_timer, UNPROVISIONED_INTERVAL); + return; + } + + BT_DBG("%s", __func__); + + if (ready_to_send()) { + 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 { + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node()) { + unprovisioned_beacon_send(); + k_delayed_work_submit(&beacon_timer, UNPROVISIONED_INTERVAL); + } + } + +} + +static void secure_beacon_recv(struct net_buf_simple *buf) +{ + u8_t *data = NULL, *net_id = NULL, *auth = NULL; + struct bt_mesh_subnet *sub = NULL; + u32_t iv_index = 0U; + bool new_key = false, kr_change = false, iv_change = false; + u8_t flags = 0U; + + 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_primary_subnet_exist() && + sub->net_idx != BLE_MESH_KEY_PRIMARY) { + BT_WARN("Ignoring secure beacon on non-primary subnet"); + goto update_stats; + } + + BT_INFO("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, s8_t rssi) +{ + u8_t type = 0U; + + 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"); + if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && + bt_mesh_is_provisioner_en()) { + bt_mesh_provisioner_unprov_beacon_recv(buf, rssi); + } + 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_deinit(void) +{ + k_delayed_work_free(&beacon_timer); +} + +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) +{ + size_t subnet_size = 0U; + int i = 0; + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node() && + !bt_mesh_is_provisioned()) { + k_work_submit(&beacon_timer.work); + return; + } + + subnet_size = bt_mesh_rx_netkey_size(); + + for (i = 0; i < subnet_size; i++) { + struct bt_mesh_subnet *sub = bt_mesh_rx_netkey_get(i); + + if (sub == NULL || 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); + } +} diff --git a/components/bt/esp_ble_mesh/mesh_core/beacon.h b/components/bt/esp_ble_mesh/mesh_core/beacon.h new file mode 100644 index 0000000000..4977942e78 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/beacon.h @@ -0,0 +1,27 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BEACON_H_ +#define _BEACON_H_ + +#include "net.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, s8_t rssi); + +void bt_mesh_beacon_create(struct bt_mesh_subnet *sub, + struct net_buf_simple *buf); + +void bt_mesh_beacon_init(void); +void bt_mesh_beacon_deinit(void); + +#endif /* _BEACON_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/bluedroid_host/mesh_bearer_adapt.c b/components/bt/esp_ble_mesh/mesh_core/bluedroid_host/mesh_bearer_adapt.c new file mode 100644 index 0000000000..80998d2024 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/bluedroid_host/mesh_bearer_adapt.c @@ -0,0 +1,1972 @@ +/* + * 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 + +#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 "osi/future.h" +#include "device/controller.h" + +#include "mbedtls/aes.h" + +#include "mesh_hci.h" +#include "mesh_aes_encrypt.h" +#include "mesh_bearer_adapt.h" +#include "mesh_common.h" +#include "provisioner_prov.h" + +struct bt_mesh_dev bt_mesh_dev; + +#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 (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_SERVER +/* Using UUID with a fixed pattern 0x96 for BLE Mesh GATT Proxy Server */ +#define BLE_MESH_GATTS_APP_UUID_BYTE 0x96 +/* 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 + +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT +/* Using UUID with a fixed pattern 0x97 for BLE Mesh GATT Proxy Client */ +#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; + bt_mesh_addr_t addr; + 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 + +int bt_mesh_host_init(void) +{ + return 0; +} + +int bt_mesh_host_deinit(void) +{ + return 0; +} + +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; +} + +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 = NULL; + tBTA_DM_SEARCH result = {0}; + + 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 < 0x0010 || 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}; + u8_t adv_type = 0U; + s8_t rssi = 0; + + 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); + } + bt_mesh_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 = NULL; + tBTM_BLE_ADV_CHNL_MAP channel_map = 0U; + tBLE_ADDR_TYPE addr_type_own = 0U; + tBLE_BD_ADDR p_dir_bda = {0}; + tBTM_BLE_AFP adv_fil_pol = 0U; + u8_t adv_type = 0U; + int err = 0; + +#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 = 0; + +#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 (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_SERVER +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 = {0}; + 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; +} + +void bt_mesh_gatts_conn_cb_deregister(void) +{ + bt_mesh_gatts_conn_cb = NULL; +} + +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 = 0U; + + 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 = 0U; + + /* 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 = 0U; + + 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 = NULL; + u16_t handle = 0U; + + 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); + + ((void) handle); + +populate: + sys_slist_append(&bt_mesh_gatts_db, &svc->node); + return 0; +} + +static int gatts_deregister(struct bt_mesh_gatt_service *svc) +{ + if (sys_slist_is_empty(&bt_mesh_gatts_db)) { + return 0; + } + + sys_slist_find_and_remove(&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_service_deregister(struct bt_mesh_gatt_service *svc) +{ + assert(svc != NULL); + + gatts_deregister(svc); + + BTA_GATTS_DeleteService(svc->attrs[0].handle); + + 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; +} + +int bt_mesh_gatts_set_local_device_name(const char *name) +{ + BLE_MESH_BTM_CHECK_STATUS(BTM_SetLocalDeviceName((char *)name)); + + return 0; +} + +#endif /* (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || CONFIG_BLE_MESH_GATT_PROXY_SERVER */ + +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT +void bt_mesh_gattc_conn_cb_register(struct bt_mesh_prov_conn_cb *cb) +{ + bt_mesh_gattc_conn_cb = cb; +} + +void bt_mesh_gattc_conn_cb_deregister(void) +{ + bt_mesh_gattc_conn_cb = NULL; +} + +u8_t bt_mesh_gattc_get_free_conn_count(void) +{ + u8_t count = 0U; + int i; + + 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) { + ++count; + } + } + + return count; +} + +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) { + return bt_mesh_gattc_info[i].service_uuid; + } + } + + BT_ERR("%s, Conn is not found", __func__); + return 0; +} + +/** 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.val, 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.val, 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.val, 0xC8, 0xC8, 0x00, 0xC80); + + BTA_GATTC_Open(bt_mesh_gattc_if, bt_mesh_gattc_info[i].addr.val, + bt_mesh_gattc_info[i].addr.type, true, BTA_GATT_TRANSPORT_LE); + + return i; +} + +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 = 0U; + + 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 = 0U; + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (conn == &bt_mesh_gattc_info[i].conn) { + 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; + } + } + + BT_ERR("%s, Conn is not found", __func__); + return -EEXIST; +} + +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 = 0U; + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (conn == &bt_mesh_gattc_info[i].conn) { + conn_id = BLE_MESH_GATT_CREATE_CONN_ID(bt_mesh_gattc_if, bt_mesh_gattc_info[i].conn.handle); + BTA_GATTC_Close(conn_id); + return; + } + } + + BT_ERR("%s, Conn is not found", __func__); + return; +} + +/** 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 = 0U; + 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; + + /* Search Mesh Provisioning Service or Mesh Proxy Service */ + tBT_UUID service_uuid = { + .len = sizeof(bt_mesh_gattc_info[i].service_uuid), + .uu.uuid16 = bt_mesh_gattc_info[i].service_uuid, + }; + BTA_GATTC_ServiceSearchRequest(p_data->cfg_mtu.conn_id, &service_uuid); + break; + } + } + } + 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) { + 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; + } + } + 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) { + conn = &bt_mesh_gattc_info[i].conn; + break; + } + } + + if (conn == NULL) { + BT_ERR("%s, Conn handle is not found", __func__); + return; + } + + 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; + } + + u16_t notify_en = BLE_MESH_GATT_CCC_NOTIFY; + btgatt_db_element_t *result = NULL; + tBT_UUID char_uuid = {0}; + tBTA_GATT_STATUS status = 0U; + tBTA_GATT_UNFMT write = {0}; + int count = 0; + int num = 0; + + /* Get the characteristic num within Mesh Provisioning/Proxy Service */ + BTA_GATTC_GetDBSizeByType(p_data->search_cmpl.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(p_data->search_cmpl.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) { + bt_mesh_free(result); + bt_mesh_gattc_disconnect(conn); + return; + } + + if (!j) { + if (!(result[0].properties & BLE_MESH_GATT_CHRC_WRITE_WITHOUT_RESP)) { + bt_mesh_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)) { + bt_mesh_free(result); + bt_mesh_gattc_disconnect(conn); + return; + } + bt_mesh_gattc_info[i].data_out_handle = result[0].attribute_handle; + } + + bt_mesh_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.val, + 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(p_data->search_cmpl.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(p_data->search_cmpl.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) { + bt_mesh_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(p_data->search_cmpl.conn_id, result[0].attribute_handle, + BTA_GATTC_TYPE_WRITE, &write, BTA_GATT_AUTH_REQ_NONE); + + bt_mesh_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) { + conn = &bt_mesh_gattc_info[i].conn; + break; + } + } + + if (conn == NULL) { + BT_ERR("%s, Conn handle is not found", __func__); + return; + } + + 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].addr, &bt_mesh_gattc_info[i].conn); + 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].addr, &bt_mesh_gattc_info[i].conn); + if (len < 0) { + BT_ERR("%s, proxy_write_descr failed", __func__); + bt_mesh_gattc_disconnect(conn); + return; + } + bt_mesh_gattc_info[i].wr_desc_done = true; + } + } + } + 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) { + if (bt_mesh_gattc_info[i].wr_desc_done == false) { + BT_DBG("Receive notification before finishing to write ccc"); + return; + } + + conn = &bt_mesh_gattc_info[i].conn; + break; + } + } + + if (conn == NULL) { + BT_ERR("%s, Conn handle is not found", __func__); + return; + } + + if (memcmp(bt_mesh_gattc_info[i].addr.val, 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.val, 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.val, 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].addr, &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. + */ +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROV_VAL) { + bt_mesh_provisioner_clear_link_info(bt_mesh_gattc_info[i].addr.val); + } +#endif + } + } 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. + */ +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROV_VAL) { + bt_mesh_provisioner_clear_link_info(bt_mesh_gattc_info[i].addr.val); + } +#endif + } +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROV_VAL) { + /* Decrease provisioner pbg_count */ + bt_mesh_provisioner_pbg_count_dec(); + } +#endif + /* 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 /* (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ + +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) +{ + BTA_GATT_SetLocalMTU(GATT_DEF_BLE_MTU_SIZE); + +#if (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_SERVER + tBT_UUID gatts_app_uuid = {LEN_UUID_128, {0}}; + memset(&gatts_app_uuid.uu.uuid128, BLE_MESH_GATTS_APP_UUID_BYTE, LEN_UUID_128); + BTA_GATTS_AppRegister(&gatts_app_uuid, bt_mesh_bta_gatts_cb); +#endif + +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT + tBT_UUID gattc_app_uuid = {LEN_UUID_128, {0}}; + 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(&gattc_app_uuid.uu.uuid128, BLE_MESH_GATTC_APP_UUID_BYTE, LEN_UUID_128); + BTA_GATTC_AppRegister(&gattc_app_uuid, bt_mesh_bta_gattc_cb); +#endif +} + +void bt_mesh_gatt_deinit(void) +{ +#if (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_SERVER + BTA_GATTS_AppDeregister(bt_mesh_gatts_if); + memset(bt_mesh_gatts_addr, 0, BLE_MESH_ADDR_LEN); + bt_mesh_gatts_if = 0U; + svc_handle = 0U; + char_handle = 0U; +#endif + +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT + BTA_GATTC_AppDeregister(bt_mesh_gattc_if); + bt_mesh_gattc_if = 0U; + for (int i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + bt_mesh_gattc_info[i].conn.handle = 0xFFFF; + memset(&bt_mesh_gattc_info[i].addr, 0, sizeof(bt_mesh_addr_t)); + bt_mesh_gattc_info[i].service_uuid = 0U; + bt_mesh_gattc_info[i].mtu = GATT_DEF_BLE_MTU_SIZE; /* Default MTU_SIZE 23 */ + bt_mesh_gattc_info[i].wr_desc_done = false; + bt_mesh_gattc_info[i].start_handle = 0U; + bt_mesh_gattc_info[i].end_handle = 0U; + bt_mesh_gattc_info[i].data_in_handle = 0U; + bt_mesh_gattc_info[i].data_out_handle = 0U; + bt_mesh_gattc_info[i].ccc_handle = 0U; + } +#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) +{ + BT_OCTET32 private_key = {0}; + Point public_key = {0}; + + if (bt_mesh_atomic_test_bit(bt_mesh_dev.flags, BLE_MESH_DEV_HAS_PUB_KEY)) { + return bt_mesh_public_key; + } + + /* 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. + * + * Use the following code for generating different private key + * for each provisioning procedure. + * + * if (bt_mesh_rand(bt_mesh_private_key, BT_OCTET32_LEN)) { + * BT_ERR("%s, Unable to generate bt_mesh_private_key", __func__); + * return NULL; + * } + */ + + memcpy(private_key, bt_mesh_private_key, BT_OCTET32_LEN); + ECC_PointMult(&public_key, &(curve_p256.G), (DWORD *)private_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 = {0}; + Point peer_pub_key = {0}; + Point new_pub_key = {0}; + + BT_DBG("private key = %s", bt_hex(bt_mesh_private_key, BT_OCTET32_LEN)); + + memcpy(private_key, bt_mesh_private_key, BT_OCTET32_LEN); + memcpy(peer_pub_key.x, remote_pk, BT_OCTET32_LEN); + memcpy(peer_pub_key.y, &remote_pk[BT_OCTET32_LEN], BT_OCTET32_LEN); + + BT_DBG("remote public key x = %s", bt_hex(peer_pub_key.x, BT_OCTET32_LEN)); + BT_DBG("remote public key y = %s", bt_hex(peer_pub_key.y, BT_OCTET32_LEN)); + + ECC_PointMult(&new_pub_key, &peer_pub_key, (DWORD *)private_key, KEY_LENGTH_DWORDS_P256); + + BT_DBG("new public key x = %s", bt_hex(new_pub_key.x, 32)); + BT_DBG("new public key y = %s", bt_hex(new_pub_key.y, 32)); + + if (cb != NULL) { + cb((const u8_t *)new_pub_key.x, 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 = {0}; + 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 = {0}; + 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 = {0}; + u8_t tmp[16] = {0}; + + 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 = {0}; + + 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; + } + sys_memcpy_swap(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/esp_ble_mesh/mesh_core/cfg_cli.c b/components/bt/esp_ble_mesh/mesh_core/cfg_cli.c new file mode 100644 index 0000000000..9ca4c2e32d --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/cfg_cli.c @@ -0,0 +1,1714 @@ +/* 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 + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_MODEL) + +#include "mesh.h" +#include "foundation.h" +#include "mesh_common.h" +#include "cfg_cli.h" + +#include "btc_ble_mesh_config_model.h" + +#define CID_NVAL 0xffff + +/* 2 byte dummy opcode for getting compile time buffer sizes. */ +#define DUMMY_2_BYTE_OP BLE_MESH_MODEL_OP_2(0xff, 0xff) + +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 bt_mesh_mutex_t cfg_client_lock; + +static void bt_mesh_cfg_client_mutex_new(void) +{ + if (!cfg_client_lock.mutex) { + bt_mesh_mutex_create(&cfg_client_lock); + } +} + +static void bt_mesh_cfg_client_mutex_free(void) +{ + bt_mesh_mutex_free(&cfg_client_lock); +} + +static void bt_mesh_cfg_client_lock(void) +{ + bt_mesh_mutex_lock(&cfg_client_lock); +} + +static void bt_mesh_cfg_client_unlock(void) +{ + bt_mesh_mutex_unlock(&cfg_client_lock); +} + +static void timeout_handler(struct k_work *work) +{ + struct k_delayed_work *timer = NULL; + bt_mesh_client_node_t *node = NULL; + struct bt_mesh_msg_ctx ctx = {0}; + u32_t opcode = 0U; + + BT_WARN("Receive configuration status message timeout"); + + bt_mesh_cfg_client_lock(); + + timer = CONTAINER_OF(work, struct k_delayed_work, work); + + if (timer && !k_delayed_work_free(timer)) { + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (node) { + memcpy(&ctx, &node->ctx, sizeof(ctx)); + opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_config_client_cb_evt_to_btc( + opcode, BTC_BLE_MESH_EVT_CONFIG_CLIENT_TIMEOUT, ctx.model, &ctx, NULL, 0); + } + } + + bt_mesh_cfg_client_unlock(); + + return; +} + +static void cfg_client_cancel(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + void *status, size_t len) +{ + 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; + } + + /* If it is a publish message, sent to the user directly. */ + buf.data = (u8_t *)status; + buf.len = (u16_t)len; + + bt_mesh_cfg_client_lock(); + + node = bt_mesh_is_client_recv_publish_msg(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 = BTC_BLE_MESH_EVT_CONFIG_CLIENT_GET_STATE; + 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 = BTC_BLE_MESH_EVT_CONFIG_CLIENT_SET_STATE; + break; + default: + break; + } + + if (!k_delayed_work_free(&node->timer)) { + u32_t opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_config_client_cb_evt_to_btc( + opcode, evt_type, model, ctx, (const u8_t *)status, len); + } + } + + bt_mesh_cfg_client_unlock(); + + 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 = 0U; + + 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 = 0U; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_DEV_COMP_DATA_GET, 1); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 0); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 1); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_RELAY_GET, 0); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_RELAY_SET, 2); + int err = 0; + + 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]) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_ADD, 18); + int err = 0; + + 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]) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_ADD, 19); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_APP_BIND, 8); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 8); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 22); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_PUB_GET, 6); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_PUB_SET, 13); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_SUB_SET, 5); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_SUB_GET, 0); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_PUB_SET, 9); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_PUB_GET, 0); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NODE_RESET, 0); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_PUB_VA_SET, 27); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_SUB_DEL_ALL, 6); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 6); + int err = 0; + + 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]) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_UPDATE, 18); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_DEL, 2); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_GET, 0); + int err = 0; + + 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]) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_UPDATE, 19); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_DEL, 3); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_GET, 2); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 3); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_APP_UNBIND, 8); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 6); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 3); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_LPN_TIMEOUT_GET, 2); + int err = 0; + + 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; + } + + if (!client->internal_data) { + internal = bt_mesh_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; + } else { + bt_mesh_client_clear_list(client->internal_data); + } + + cli = client; + + /* Configuration Model security is device-key based */ + model->keys[0] = BLE_MESH_KEY_DEV; + + bt_mesh_cfg_client_mutex_new(); + + return 0; +} + +int bt_mesh_cfg_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + bt_mesh_config_client_t *client = NULL; + + 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; + } + + if (client->internal_data) { + /* Remove items from the list */ + bt_mesh_client_clear_list(client->internal_data); + + /* Free the allocated internal data */ + bt_mesh_free(client->internal_data); + cli->internal_data = NULL; + } + + client = NULL; + + bt_mesh_cfg_client_mutex_free(); + + return 0; +} diff --git a/components/bt/esp_ble_mesh/mesh_core/cfg_srv.c b/components/bt/esp_ble_mesh/mesh_core/cfg_srv.c new file mode 100644 index 0000000000..dbd31dd297 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/cfg_srv.c @@ -0,0 +1,3659 @@ +/* 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 + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_MODEL) + +#include "mesh.h" +#include "adv.h" +#include "lpn.h" +#include "transport.h" +#include "crypto.h" +#include "access.h" +#include "beacon.h" +#include "foundation.h" +#include "friend.h" +#include "settings.h" +#include "cfg_srv.h" +#include "proxy_server.h" +#include "mesh_main.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 labels[CONFIG_BLE_MESH_LABEL_COUNT]; + +static int comp_add_elem(struct net_buf_simple *buf, struct bt_mesh_elem *elem, + bool primary) +{ + struct bt_mesh_model *mod = NULL; + 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 = NULL; + 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_SERVER)) { + 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 = 0U; + + 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 = 0U; + + 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 = 0U, id = 0U; + + 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 = NULL; + struct bt_mesh_app_key *key = NULL; + struct bt_mesh_subnet *sub = NULL; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_STATUS, 4); + u16_t key_net_idx = 0U, key_app_idx = 0U; + u8_t status = 0U; + + 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 (status == STATUS_SUCCESS) { + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_appkey_add.net_idx = key_net_idx; + change.cfg_appkey_add.app_idx = key_app_idx; + memcpy(change.cfg_appkey_add.app_key, buf->data, 16); + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); + } +} + +static void app_key_update(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_STATUS, 4); + u16_t key_net_idx = 0U, key_app_idx = 0U; + u8_t status = 0U; + + 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__); + } + + if (status == STATUS_SUCCESS) { + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_appkey_update.net_idx = key_net_idx; + change.cfg_appkey_update.app_idx = key_app_idx; + memcpy(change.cfg_appkey_update.app_key, buf->data, 16); + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); + } +} + +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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_STATUS, 4); + u16_t key_net_idx = 0U, key_app_idx = 0U; + struct bt_mesh_app_key *key = NULL; + u8_t status = 0U; + + 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__); + } + + if (status == STATUS_SUCCESS) { + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_appkey_delete.net_idx = key_net_idx; + change.cfg_appkey_delete.app_idx = key_app_idx; + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); + } +} + +/* 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_LIST, + 3 + IDX_LEN(CONFIG_BLE_MESH_APP_KEY_COUNT)); + u16_t get_idx = 0U, i = 0U, prev = 0U; + u8_t status = 0U; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_BEACON_STATUS, 1); + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_BEACON_STATUS, 1); + 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 (cfg->beacon) { + bt_mesh_beacon_enable(); + } else { + bt_mesh_beacon_disable(); + } + } + } 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_DEFAULT_TTL_STATUS, 1); + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_DEFAULT_TTL_STATUS, 1); + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_GATT_PROXY_STATUS, 1); + + 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; + + 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_SERVER) || + 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 (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(); + + if (cfg->hb_pub.feat & BLE_MESH_FEAT_PROXY) { + bt_mesh_heartbeat_send(); + } + +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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NET_TRANSMIT_STATUS, 1); + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NET_TRANSMIT_STATUS, 1); + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_RELAY_STATUS, 2); + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_RELAY_STATUS, 2); + 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) { + 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)); + + if ((cfg->hb_pub.feat & BLE_MESH_FEAT_RELAY) && change) { + bt_mesh_heartbeat_send(); + } + } 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_PUB_STATUS, 14); + + 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 = 0U, pub_addr = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *mod_id = NULL, status = 0U; + bool vnd = false; + + 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 = 0U, status = 0U, pub_ttl = 0U, pub_period = 0U, cred_flag = 0U; + u16_t elem_addr = 0U, pub_addr = 0U, pub_app_idx = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *mod_id = NULL; + bool vnd = false; + + 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 (status == STATUS_SUCCESS && mod->pub) { + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_mod_pub_set.elem_addr = elem_addr; + change.cfg_mod_pub_set.pub_addr = mod->pub->addr; + change.cfg_mod_pub_set.app_idx = mod->pub->key; + change.cfg_mod_pub_set.cred_flag = mod->pub->cred; + change.cfg_mod_pub_set.ttl = mod->pub->ttl; + change.cfg_mod_pub_set.period = mod->pub->period; + change.cfg_mod_pub_set.transmit = mod->pub->retransmit; + change.cfg_mod_pub_set.cid = vnd ? mod->vnd.company : 0xFFFF; + change.cfg_mod_pub_set.mod_id = vnd ? mod->vnd.id : mod->id; + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); + } +} + +struct label *get_label(u16_t index) +{ + if (index >= ARRAY_SIZE(labels)) { + return NULL; + } + + return &labels[index]; +} + +#if CONFIG_BLE_MESH_LABEL_COUNT > 0 +static inline void va_store(struct label *store) +{ + bt_mesh_atomic_set_bit(store->flags, BLE_MESH_VA_CHANGED); + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_label(); + } +} + +static struct label *va_find(const u8_t *label_uuid, + struct label **free_slot) +{ + struct label *match = NULL; + int i; + + if (free_slot != NULL) { + *free_slot = NULL; + } + + for (i = 0; i < ARRAY_SIZE(labels); i++) { + if (labels[i].ref == 0) { + if (free_slot != NULL) { + *free_slot = &labels[i]; + } + continue; + } + + if (!memcmp(labels[i].uuid, label_uuid, 16)) { + match = &labels[i]; + } + } + + return match; +} + +static u8_t va_add(u8_t *label_uuid, u16_t *addr) +{ + struct label *update = NULL, *free_slot = NULL; + + update = va_find(label_uuid, &free_slot); + if (update) { + update->ref++; + va_store(update); + 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); + va_store(free_slot); + + return STATUS_SUCCESS; +} + +static u8_t va_del(u8_t *label_uuid, u16_t *addr) +{ + struct label *update = NULL; + + update = va_find(label_uuid, NULL); + if (update) { + update->ref--; + + if (addr) { + *addr = update->addr; + } + + va_store(update); + 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 = NULL; + size_t clear_count = 0U; + 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 = 0U, status = 0U, pub_ttl = 0U, pub_period = 0U, cred_flag = 0U; + u16_t elem_addr = 0U, pub_addr = 0U, pub_app_idx = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *label_uuid = NULL; + u8_t *mod_id = NULL; + bool vnd = false; + + 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 = 0U; + 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 = NULL, status = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u16_t elem_addr = 0U, pub_addr = 0U; + bool vnd = false; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_SUB_STATUS, 9); + + 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 = 0U, sub_addr = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *mod_id = NULL; + u8_t status = 0U; + bool vnd = false; + 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); + + if (status == STATUS_SUCCESS) { + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_mod_sub_add.elem_addr = elem_addr; + change.cfg_mod_sub_add.sub_addr = sub_addr; + change.cfg_mod_sub_add.cid = vnd ? mod->vnd.company : 0xFFFF; + change.cfg_mod_sub_add.mod_id = vnd ? mod->vnd.id : mod->id; + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); + } +} + +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 = 0U, sub_addr = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *mod_id = NULL; + u16_t *match = NULL; + u8_t status = 0U; + bool vnd = false; + + 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); + + if (status == STATUS_SUCCESS) { + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_mod_sub_delete.elem_addr = elem_addr; + change.cfg_mod_sub_delete.sub_addr = sub_addr; + change.cfg_mod_sub_delete.cid = vnd ? mod->vnd.company : 0xFFFF; + change.cfg_mod_sub_delete.mod_id = vnd ? mod->vnd.id : mod->id; + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); + } +} + +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 = 0U, sub_addr = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *mod_id = NULL; + u8_t status = 0U; + bool vnd = false; + + 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 = NULL; + struct bt_mesh_elem *elem = NULL; + u16_t elem_addr = 0U; + u8_t *mod_id = NULL; + u8_t status = 0U; + bool vnd = false; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_SUB_LIST, + 5 + CONFIG_BLE_MESH_MODEL_GROUP_COUNT * 2); + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u16_t addr = 0U, id = 0U; + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_SUB_LIST_VND, + 7 + CONFIG_BLE_MESH_MODEL_GROUP_COUNT * 2); + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u16_t company = 0U, addr = 0U, id = 0U; + 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 = 0U, sub_addr = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *label_uuid = NULL; + u8_t *mod_id = NULL; + u8_t status = 0U; + bool vnd = false; + 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 = 0U, sub_addr = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *label_uuid = NULL; + u8_t *mod_id = NULL; + u16_t *match = NULL; + u8_t status = 0U; + bool vnd = false; + + 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 = 0U, sub_addr = BLE_MESH_ADDR_UNASSIGNED; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *label_uuid = NULL; + u8_t *mod_id = NULL; + u8_t status = 0U; + bool vnd = false; + + 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 = NULL; + struct bt_mesh_elem *elem = NULL; + u16_t elem_addr = 0U; + u8_t *mod_id = NULL; + u8_t status = 0U; + bool vnd = false; + + 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 = NULL; + u16_t elem_addr = 0U; + u8_t *mod_id = NULL; + u8_t status = 0U; + bool vnd = false; + + 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 = NULL; + u16_t elem_addr = 0U; + u8_t *mod_id = NULL; + u8_t status = 0U; + bool vnd = false; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_STATUS, 3); + + 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 = NULL; + u16_t idx = 0U; + int err = 0; + + 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 = 0U; + + 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_SERVER)) { + sub->node_id = BLE_MESH_NODE_IDENTITY_STOPPED; + bt_mesh_proxy_beacon_send(sub); + bt_mesh_adv_update(); + } else { + sub->node_id = BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED; + } + + send_net_key_status(model, ctx, idx, STATUS_SUCCESS); + + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_netkey_add.net_idx = sub->net_idx; + memcpy(change.cfg_netkey_add.net_key, sub->keys[0].net, 16); + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); +} + +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 = NULL; + u16_t idx = 0U; + int err = 0; + + 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); + + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_netkey_update.net_idx = sub->net_idx; + memcpy(change.cfg_netkey_update.net_key, sub->keys[1].net, 16); + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); +} + +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 = NULL; + u16_t del_idx = 0U; + u8_t status = 0U; + + 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); + + if (status == STATUS_SUCCESS) { + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_netkey_delete.net_idx = sub->net_idx; + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); + } +} + +static void net_key_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_LIST, + IDX_LEN(CONFIG_BLE_MESH_SUBNET_COUNT)); + u16_t prev = 0U, i = 0U; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NODE_IDENTITY_STATUS, 4); + struct bt_mesh_subnet *sub = NULL; + u8_t node_id = 0U; + u16_t idx = 0U; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NODE_IDENTITY_STATUS, 4); + struct bt_mesh_subnet *sub = NULL; + u8_t node_id = 0U; + u16_t idx = 0U; + + 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); + /* 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_SERVER) && + 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(); + } + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_APP_STATUS, 9); + u16_t elem_addr = 0U, key_app_idx = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *mod_id = NULL, status = 0U; + bool vnd = false; + + 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__); + } + + if (status == STATUS_SUCCESS) { + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_mod_app_bind.elem_addr = elem_addr; + change.cfg_mod_app_bind.app_idx = key_app_idx; + change.cfg_mod_app_bind.cid = vnd ? mod->vnd.company : 0xFFFF; + change.cfg_mod_app_bind.mod_id = vnd ? mod->vnd.id : mod->id; + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); + } +} + +static void mod_app_unbind(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_APP_STATUS, 9); + u16_t elem_addr = 0U, key_app_idx = 0U; + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *mod_id = NULL, status = 0U; + bool vnd = false; + + 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__); + } + + if (status == STATUS_SUCCESS) { + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_mod_app_unbind.elem_addr = elem_addr; + change.cfg_mod_app_unbind.app_idx = key_app_idx; + change.cfg_mod_app_unbind.cid = vnd ? mod->vnd.company : 0xFFFF; + change.cfg_mod_app_unbind.mod_id = vnd ? mod->vnd.id : mod->id; + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); + } +} + +#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, + MAX(BLE_MESH_MODEL_BUF_LEN(OP_VND_MOD_APP_LIST, + 9 + KEY_LIST_LEN), + BLE_MESH_MODEL_BUF_LEN(OP_SIG_MOD_APP_LIST, + 9 + KEY_LIST_LEN))); + struct bt_mesh_model *mod = NULL; + struct bt_mesh_elem *elem = NULL; + u8_t *mod_id = NULL, status = 0U; + u16_t elem_addr = 0U; + bool vnd = false; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_NODE_RESET_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)); + + + 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 (IS_ENABLED(CONFIG_BLE_MESH_NODE)) { + bt_mesh_reset(); + } +} + +static void send_friend_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_FRIEND_STATUS, 1); + 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; + + 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); + } + } + + if (cfg->hb_pub.feat & BLE_MESH_FEAT_FRIEND) { + bt_mesh_heartbeat_send(); + } + +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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_LPN_TIMEOUT_STATUS, 5); + struct bt_mesh_friend *frnd = NULL; + u16_t lpn_addr = 0U; + s32_t timeout = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_KRP_STATUS, 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 = NULL; + u16_t idx = 0U; + + 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 = NULL; + u8_t phase = 0U; + u16_t idx = 0U; + + 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); + + bt_mesh_cfg_server_state_change_t change = {0}; + change.cfg_kr_phase_set.net_idx = idx; + change.cfg_kr_phase_set.kr_phase = phase; + bt_mesh_config_server_cb_evt_to_btc(BTC_BLE_MESH_EVT_CONFIG_SERVER_STATE_CHANGE, + model, ctx, (const u8_t *)&change, sizeof(change)); +} + +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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_PUB_STATUS, 10); + 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 = 0U, feat = 0U, idx = 0U; + u8_t status = 0U; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_SUB_STATUS, 9); + struct bt_mesh_cfg_srv *cfg = model->user_data; + u16_t period = 0U; + s64_t uptime = 0; + + 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); + 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 = 0U, sub_dst = 0U; + u8_t sub_period = 0U; + s32_t period_ms = 0; + + 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 (sub_src == BLE_MESH_ADDR_UNASSIGNED || + 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.min_hops = BLE_MESH_TTL_MAX; + cfg->hb_sub.max_hops = 0U; + cfg->hb_sub.count = 0U; + } + + 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_subnet *sub = NULL; + u16_t period_ms = 0U; + + 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); + } + + bt_mesh_heartbeat_send(); + + 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_SERVER)) { + 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; +} + +int bt_mesh_cfg_srv_deinit(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; + } + + bt_mesh_cfg_reset(); + + k_delayed_work_free(&cfg->hb_pub.timer); + cfg->hb_pub.dst = BLE_MESH_ADDR_UNASSIGNED; + + conf = NULL; + + 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 = 0U; + + /* 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/esp_ble_mesh/mesh_core/crypto.c b/components/bt/esp_ble_mesh/mesh_core/crypto.c new file mode 100644 index 0000000000..48d3a60980 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/crypto.c @@ -0,0 +1,873 @@ +/* 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 + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_CRYPTO) + +#include "crypto.h" +#include "mesh_aes_encrypt.h" +#include "mesh_bearer_adapt.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 = {0}; + struct tc_cmac_struct state = {0}; + + 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 = 0; + + 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] = {0}; + u8_t salt[16] = {0}; + u8_t out[16] = {0}; + u8_t t[16] = {0}; + u8_t pad = 0U; + int err = 0; + + 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] = {0}; + u8_t t[16] = {0}; + int err = 0; + + 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] = {0}; + u8_t t[16] = {0}; + int err = 0; + + 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] = {0}; + int err = 0; + + 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] = {0}, pmsg[16] = {0}, cmic[16] = {0}, + cmsg[16] = {0}, Xn[16] = {0}, mic[16] = {0}; + u16_t last_blk = 0U, blk_cnt = 0U; + size_t i = 0U, j = 0U; + int err = 0; + + 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 < 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] = {0}, cmic[16] = {0}, cmsg[16] = {0}, + mic[16] = {0}, Xn[16] = {0}; + u16_t blk_cnt = 0U, last_blk = 0U; + size_t i = 0U, j = 0U; + int err = 0; + + 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 < 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] = {0}; + int err = 0, 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] = {0}; + int err = 0; + + 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] = {0}; + + 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] = {0}; + int err = 0; + + 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] = {0}; + int err = 0; + + 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] = {0}; + u8_t tmp[16] = {0}; + int err = 0; + + 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] = {0}, tmp[16] = {0}; + int err = 0; + + 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/esp_ble_mesh/mesh_core/crypto.h b/components/bt/esp_ble_mesh/mesh_core/crypto.h new file mode 100644 index 0000000000..df98c0e95e --- /dev/null +++ b/components/bt/esp_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 +#include "mesh_buf.h" + +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/esp_ble_mesh/mesh_core/foundation.h b/components/bt/esp_ble_mesh/mesh_core/foundation.h new file mode 100644 index 0000000000..5e1148144f --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/foundation.h @@ -0,0 +1,184 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _FOUNDATION_H_ +#define _FOUNDATION_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 + +enum { + BLE_MESH_VA_CHANGED, /* Label information changed */ +}; + +struct label { + u16_t ref; + u16_t addr; + u8_t uuid[16]; + bt_mesh_atomic_t flags[1]; +}; + +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_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_health_srv_deinit(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); + +int bt_mesh_cfg_cli_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_health_cli_deinit(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); + +struct label *get_label(u16_t index); + +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/esp_ble_mesh/mesh_core/friend.c b/components/bt/esp_ble_mesh/mesh_core/friend.c new file mode 100644 index 0000000000..02649b21f6 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/friend.c @@ -0,0 +1,1708 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_FRIEND) + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "transport.h" +#include "access.h" +#include "friend.h" +#include "foundation.h" +#include "mesh_main.h" +#include "provisioner_main.h" + +#ifdef CONFIG_BLE_MESH_FRIEND + +/* 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; + u16_t app_idx; +} adv_pool[FRIEND_BUF_COUNT]; + +enum { + BLE_MESH_FRIENDSHIP_TERMINATE_ESTABLISH_FAIL, + BLE_MESH_FRIENDSHIP_TERMINATE_POLL_TIMEOUT, + BLE_MESH_FRIENDSHIP_TERMINATE_RECV_FRND_REQ, + BLE_MESH_FRIENDSHIP_TERMINATE_RECV_FRND_CLEAR, + BLE_MESH_FRIENDSHIP_TERMINATE_DISABLE, +}; + +static void (*friend_cb)(bool establish, u16_t lpn_addr, u8_t reason); + +static struct bt_mesh_subnet *friend_subnet_get(u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + sub = bt_mesh_subnet_get(net_idx); + } else if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en()) { + sub = bt_mesh_provisioner_subnet_get(net_idx); + } + + return sub; +} + +static struct bt_mesh_adv *adv_alloc(int id) +{ + adv_pool[id].app_idx = BLE_MESH_KEY_UNUSED; + return &adv_pool[id].adv; +} + +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; +} + +static void purge_buffers(sys_slist_t *list) +{ + while (!sys_slist_is_empty(list)) { + struct net_buf *buf = NULL; + + buf = (void *)sys_slist_get_not_empty(list); + + buf->frags = NULL; + buf->flags &= ~NET_BUF_FRAGS; + + net_buf_unref(buf); + } +} + +/* 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, u8_t reason) +{ + int i; + + BT_DBG("LPN 0x%04x", frnd->lpn); + + k_delayed_work_cancel(&frnd->timer); + + if (frnd->established) { + if (friend_cb) { + friend_cb(false, frnd->lpn, reason); + } + } + + friend_cred_del(frnd->net_idx, frnd->lpn); + + if (frnd->last) { + /* Cancel the sending if necessary */ + if (frnd->pending_buf) { + bt_mesh_adv_buf_ref_debug(__func__, frnd->last, 2U, BLE_MESH_BUF_REF_EQUAL); + BLE_MESH_ADV(frnd->last)->busy = 0U; + } else { + bt_mesh_adv_buf_ref_debug(__func__, frnd->last, 1U, BLE_MESH_BUF_REF_EQUAL); + } + + net_buf_unref(frnd->last); + frnd->last = NULL; + } + + purge_buffers(&frnd->queue); + + for (i = 0; i < ARRAY_SIZE(frnd->seg); i++) { + struct bt_mesh_friend_seg *seg = &frnd->seg[i]; + + purge_buffers(&seg->queue); + seg->seg_count = 0U; + } + + 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, BLE_MESH_FRIENDSHIP_TERMINATE_DISABLE); + } + } +} + +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 = NULL; + u16_t lpn_addr = 0U, lpn_counter = 0U; + 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 = {0}; + + 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, BLE_MESH_FRIENDSHIP_TERMINATE_RECV_FRND_CLEAR); + + 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 net_buf *buf = NULL; + + buf = bt_mesh_adv_create_from_pool(&friend_buf_pool, adv_alloc, + BLE_MESH_ADV_DATA, + FRIEND_XMIT, K_NO_WAIT); + if (!buf) { + return NULL; + } + + net_buf_add_u8(buf, (info->iv_index & 1) << 7); /* Will be reset in encryption */ + + 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); + + return buf; +} + +struct unseg_app_sdu_meta { + struct bt_mesh_net_rx net; + const u8_t *key; + struct bt_mesh_subnet *subnet; + bool is_dev_key; + u8_t aid; + u8_t *ad; +}; + +static int unseg_app_sdu_unpack(struct bt_mesh_friend *frnd, + struct net_buf *buf, + struct unseg_app_sdu_meta *meta) +{ + u16_t app_idx = FRIEND_ADV(buf)->app_idx; + u8_t role = 0U; + int err = 0; + + meta->subnet = friend_subnet_get(frnd->net_idx); + if (!meta->subnet) { + BT_ERR("Invalid subnet for unseg app sdu"); + return -EINVAL; + } + + role = (IS_ENABLED(CONFIG_BLE_MESH_NODE) && + bt_mesh_is_provisioned()) ? NODE : PROVISIONER; + + meta->is_dev_key = (app_idx == BLE_MESH_KEY_DEV); + bt_mesh_net_header_parse(&buf->b, &meta->net); + err = bt_mesh_app_key_get(meta->subnet, app_idx, &meta->key, + &meta->aid, role, meta->net.ctx.addr); + if (err) { + BT_ERR("Failed to get AppKey"); + return err; + } + + if (BLE_MESH_ADDR_IS_VIRTUAL(meta->net.ctx.recv_dst)) { + meta->ad = bt_mesh_label_uuid_get(meta->net.ctx.recv_dst); + if (!meta->ad) { + BT_ERR("Failed to get label uuid"); + return -ENOENT; + } + } else { + meta->ad = NULL; + } + + return 0; +} + +static int unseg_app_sdu_decrypt(struct bt_mesh_friend *frnd, + struct net_buf *buf, + const struct unseg_app_sdu_meta *meta) +{ + struct net_buf_simple sdu = {0}; + + net_buf_simple_clone(&buf->b, &sdu); + net_buf_simple_pull(&sdu, 10); + sdu.len -= 4; + + return bt_mesh_app_decrypt(meta->key, meta->is_dev_key, 0, &sdu, &sdu, + meta->ad, meta->net.ctx.addr, + meta->net.ctx.recv_dst, meta->net.seq, + BLE_MESH_NET_IVI_TX); +} + +static int unseg_app_sdu_encrypt(struct bt_mesh_friend *frnd, + struct net_buf *buf, + const struct unseg_app_sdu_meta *meta) +{ + struct net_buf_simple sdu = {0}; + + net_buf_simple_clone(&buf->b, &sdu); + net_buf_simple_pull(&sdu, 10); + sdu.len -= 4; + + return bt_mesh_app_encrypt(meta->key, meta->is_dev_key, 0, &sdu, + meta->ad, meta->net.ctx.addr, + meta->net.ctx.recv_dst, bt_mesh.seq, + BLE_MESH_NET_IVI_TX); +} + +static int unseg_app_sdu_prepare(struct bt_mesh_friend *frnd, + struct net_buf *buf) +{ + struct unseg_app_sdu_meta meta = {0}; + int err = 0; + + if (FRIEND_ADV(buf)->app_idx == BLE_MESH_KEY_UNUSED) { + return 0; + } + + err = unseg_app_sdu_unpack(frnd, buf, &meta); + if (err) { + return err; + } + + /* No need to reencrypt the message if the sequence number is + * unchanged. + */ + if (meta.net.seq == bt_mesh.seq) { + return 0; + } + + err = unseg_app_sdu_decrypt(frnd, buf, &meta); + if (err) { + BT_WARN("Decryption failed! %d", err); + return err; + } + + err = unseg_app_sdu_encrypt(frnd, buf, &meta); + if (err) { + BT_WARN("Re-encryption failed! %d", err); + } + + return err; +} + +static int encrypt_friend_pdu(struct bt_mesh_friend *frnd, struct net_buf *buf, + bool master_cred) +{ + struct bt_mesh_subnet *sub = friend_subnet_get(frnd->net_idx); + const u8_t *enc = NULL, *priv = NULL; + u32_t iv_index = 0U; + u16_t src = 0U; + u8_t nid = 0U; + int err = 0; + + if (!sub) { + BT_ERR("Invalid subnet to encrypt friend pdu"); + return -EINVAL; + } + + if (master_cred) { + 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("friend_cred_get failed"); + return -ENOENT; + } + } + + src = sys_get_be16(&buf->data[5]); + + if (bt_mesh_elem_find(src)) { + u32_t seq; + + if (FRIEND_ADV(buf)->app_idx != BLE_MESH_KEY_UNUSED) { + err = unseg_app_sdu_prepare(frnd, buf); + if (err) { + return err; + } + } + + seq = bt_mesh_next_seq(); + buf->data[2] = seq >> 16; + buf->data[3] = seq >> 8; + buf->data[4] = seq; + iv_index = BLE_MESH_NET_IVI_TX; + FRIEND_ADV(buf)->app_idx = BLE_MESH_KEY_UNUSED; + } else { + u8_t ivi = (buf->data[0] >> 7); + iv_index = (bt_mesh.iv_index - ((bt_mesh.iv_index & 1) != ivi)); + } + + buf->data[0] = (nid | (iv_index & 1) << 7); + + if (bt_mesh_net_encrypt(enc, &buf->b, iv_index, false)) { + BT_ERR("Encrypting failed"); + return -EINVAL; + } + + if (bt_mesh_net_obfuscate(buf->data, iv_index, priv)) { + BT_ERR("Obfuscating failed"); + return -EINVAL; + } + + return 0; +} + +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 = {0}; + + 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; + + memset(info.seq, 0, sizeof(info.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 = NULL; + NET_BUF_SIMPLE_DEFINE(sdu, 1 + sizeof(*upd)); + struct bt_mesh_subnet *sub = friend_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 = NULL; + NET_BUF_SIMPLE_DEFINE(sdu, 1 + sizeof(*cfm)); + struct net_buf *buf = NULL; + + 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 (encrypt_friend_pdu(frnd, buf, false)) { + 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_INFO("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 = NULL; + u8_t xact = 0U; + + 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 = NULL; + u8_t xact = 0U; + + 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 = NULL; + + 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 = NULL; + + 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_INFO("Friendship established with 0x%04x", frnd->lpn); + frnd->established = 1U; + if (friend_cb) { + friend_cb(true, frnd->lpn, 0); + } + } + + 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 = friend_subnet_get(frnd->net_idx), + .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__); + + if (!tx.sub) { + BT_ERR("Invalid subnet for Friend Clear"); + return; + } + + 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 = 0U; + + 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(); + 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 = NULL; + u16_t lpn_addr = 0U, lpn_counter = 0U; + + 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 = NULL; + NET_BUF_SIMPLE_DEFINE(sdu, 1 + sizeof(*off)); + struct net_buf *buf = NULL; + + 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; + } + + if (encrypt_friend_pdu(frnd, buf, true)) { + 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 = 0; + + BT_INFO("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 = 0U; + 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, BLE_MESH_FRIENDSHIP_TERMINATE_RECV_FRND_REQ); + 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_INFO("LPN 0x%04x rssi %d recv_delay %u poll_to %ums", + frnd->lpn, rx->ctx.recv_rssi, frnd->recv_delay, frnd->poll_to); + + /** + * Spec says: + * After a friendship has been established, if the PreviousAddress field + * of the Friend Request message contains a valid unicast address that is + * not the Friend node’s own unicast address, then the Friend node shall + * begin sending Friend Clear messages to that unicast address. + */ + 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->ctx.recv_rssi, + msg->criteria)); + + friend_cred_create(rx->sub, frnd->lpn, frnd->lpn_counter, + frnd->counter); + + enqueue_offer(frnd, rx->ctx.recv_rssi); + + return 0; +} + +static bool is_seg(struct bt_mesh_friend_seg *seg, u16_t src, u16_t seq_zero) +{ + struct net_buf *buf = (void *)sys_slist_peek_head(&seg->queue); + struct net_buf_simple_state state = {0}; + u16_t buf_seq_zero = 0U; + u16_t buf_src = 0U; + + if (!buf) { + return false; + } + + net_buf_simple_save(&buf->b, &state); + net_buf_skip(buf, 5); /* skip IVI, NID, CTL, TTL, SEQ */ + buf_src = net_buf_pull_be16(buf); + net_buf_skip(buf, 3); /* skip DST, OP/AID */ + buf_seq_zero = ((net_buf_pull_be16(buf) >> 2) & TRANS_SEQ_ZERO_MASK); + net_buf_simple_restore(&buf->b, &state); + + return ((src == buf_src) && (seq_zero == buf_seq_zero)); +} + +static struct bt_mesh_friend_seg *get_seg(struct bt_mesh_friend *frnd, + u16_t src, u16_t seq_zero, + u8_t seg_count) +{ + 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]; + + if (is_seg(seg, src, seq_zero)) { + return seg; + } + + if (!unassigned && !sys_slist_peek_head(&seg->queue)) { + unassigned = seg; + } + } + + if (unassigned) { + unassigned->seg_count = seg_count; + } + + return unassigned; +} + +static void enqueue_friend_pdu(struct bt_mesh_friend *frnd, + enum bt_mesh_friend_pdu_type type, + u16_t src, u8_t seg_count, + struct net_buf *buf) +{ + struct bt_mesh_friend_seg *seg = NULL; + + 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; + } + + u16_t seq_zero = (((buf->data[10] << 8 | buf->data[11]) >> 2) & TRANS_SEQ_ZERO_MASK); + + seg = get_seg(frnd, src, seq_zero, seg_count); + if (!seg) { + BT_ERR("No free friend segment RX contexts for 0x%04x", src); + 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); + } + + sys_slist_merge_slist(&frnd->queue, &seg->queue); + + frnd->queue_size += seg->seg_count; + seg->seg_count = 0U; + } else { + /* Mark the buffer as having more to come after it */ + buf->flags |= NET_BUF_FRAGS; + } +} + +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, BLE_MESH_FRIENDSHIP_TERMINATE_POLL_TIMEOUT); + return; + } + + frnd->last = (void *)sys_slist_get(&frnd->queue); + if (!frnd->last) { + BT_WARN("%s, Friendship not established with 0x%04x", __func__, frnd->lpn); + friend_clear(frnd, BLE_MESH_FRIENDSHIP_TERMINATE_ESTABLISH_FAIL); + return; + } + + if (encrypt_friend_pdu(frnd, frnd->last, false)) { + return; + } + + /* Clear the flag we use for segment tracking */ + frnd->last->flags &= ~NET_BUF_FRAGS; + frnd->last->frags = NULL; + + 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); +} + +void bt_mesh_friend_set_cb(void (*cb)(bool establish, u16_t lpn_addr, u8_t reason)) +{ + friend_cb = cb; +} + +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; +} + +int bt_mesh_friend_deinit(void) +{ + int i; + + bt_mesh_friend_clear_net_idx(BLE_MESH_KEY_ANY); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + frnd->net_idx = BLE_MESH_KEY_UNUSED; + + k_delayed_work_free(&frnd->timer); + k_delayed_work_free(&frnd->clear.timer); + } + + bt_mesh_unref_buf_from_pool(&friend_buf_pool); + memset(adv_pool, 0, sizeof(adv_pool)); + + return 0; +} + +static bool is_segack(struct net_buf *buf, u64_t *seqauth, u16_t src) +{ + struct net_buf_simple_state state = {0}; + bool found = false; + + if (buf->len != 16) { + return false; + } + + net_buf_simple_save(&buf->b, &state); + + net_buf_skip(buf, 1); /* skip IVI, NID */ + + if (!(net_buf_pull_u8(buf) >> 7)) { + goto end; + } + + net_buf_pull(buf, 3); /* skip SEQNUM */ + + if (src != net_buf_pull_be16(buf)) { + goto end; + } + + net_buf_skip(buf, 2); /* skip dst */ + + if (TRANS_CTL_OP((u8_t *) net_buf_pull_mem(buf, 1)) != TRANS_CTL_OP_ACK) { + goto end; + } + + found = ((net_buf_pull_be16(buf) >> 2) & TRANS_SEQ_ZERO_MASK) == + (*seqauth & TRANS_SEQ_ZERO_MASK); +end: + net_buf_simple_restore(&buf->b, &state); + return found; +} + +static void friend_purge_old_ack(struct bt_mesh_friend *frnd, u64_t *seq_auth, + u16_t src) +{ + sys_snode_t *cur = NULL, *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 (is_segack(buf, seq_auth, src)) { + 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, u8_t seg_count, + struct net_buf_simple *sbuf) +{ + struct friend_pdu_info info = {0}; + struct net_buf *buf = NULL; + + /* Because of network loopback, tx packets will also be passed into + * this rx function. These packets have already been added to the + * queue, and should be ignored. + */ + if (bt_mesh_elem_find(rx->ctx.addr)) { + return; + } + + 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; + } + + enqueue_friend_pdu(frnd, type, info.src, seg_count, 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, u8_t seg_count, + struct net_buf_simple *sbuf) +{ + struct friend_pdu_info info = {0}; + struct net_buf *buf = NULL; + + 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); + + info.seq[0] = (bt_mesh.seq >> 16); + info.seq[1] = (bt_mesh.seq >> 8); + info.seq[2] = bt_mesh.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 (type == BLE_MESH_FRIEND_PDU_SINGLE && !info.ctl) { + /* Unsegmented application packets may be reencrypted later, + * as they depend on the the sequence number being the same + * when encrypting in transport and network. + */ + FRIEND_ADV(buf)->app_idx = tx->ctx->app_idx; + } + + enqueue_friend_pdu(frnd, type, info.src, seg_count, 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; +} + +static bool friend_queue_has_space(struct bt_mesh_friend *frnd, u16_t addr, + u64_t *seq_auth, u8_t seg_count) +{ + u32_t total = 0U; + int i; + + if (seg_count > CONFIG_BLE_MESH_FRIEND_QUEUE_SIZE) { + return false; + } + + for (i = 0; i < ARRAY_SIZE(frnd->seg); i++) { + struct bt_mesh_friend_seg *seg = &frnd->seg[i]; + + if (seq_auth && is_seg(seg, addr, *seq_auth & TRANS_SEQ_ZERO_MASK)) { + /* If there's a segment queue for this message then the + * space verification has already happened. + */ + return true; + } + + total += seg->seg_count; + } + + /* If currently pending segments combined with this segmented message + * are more than the Friend Queue Size, then there's no space. This + * is because we don't have a mechanism of aborting already pending + * segmented messages to free up buffers. + */ + return (CONFIG_BLE_MESH_FRIEND_QUEUE_SIZE - total) > seg_count; +} + +bool bt_mesh_friend_queue_has_space(u16_t net_idx, u16_t src, u16_t dst, + u64_t *seq_auth, u8_t seg_count) +{ + bool someone_has_space = false, friend_match = false; + 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, dst)) { + continue; + } + + friend_match = true; + + if (friend_queue_has_space(frnd, src, seq_auth, seg_count)) { + someone_has_space = true; + } + } + + /* If there were no matched LPNs treat this as success, so the + * transport layer can continue its work. + */ + if (!friend_match) { + return true; + } + + /* From the transport layers perspective it's good enough that at + * least one Friend Queue has space. If there were multiple Friend + * matches then the destination must be a group address, in which + * case e.g. segment acks are not sent. + */ + return someone_has_space; +} + +static bool friend_queue_prepare_space(struct bt_mesh_friend *frnd, u16_t addr, + u64_t *seq_auth, u8_t seg_count) +{ + bool pending_segments = false; + u8_t avail_space = 0U; + + if (!friend_queue_has_space(frnd, addr, seq_auth, seg_count)) { + return false; + } + + avail_space = CONFIG_BLE_MESH_FRIEND_QUEUE_SIZE - frnd->queue_size; + pending_segments = false; + + while (pending_segments || avail_space < seg_count) { + struct net_buf *buf = (void *)sys_slist_get(&frnd->queue); + + if (!buf) { + BT_ERR("Unable to free up enough buffers"); + return false; + } + + frnd->queue_size--; + avail_space++; + + pending_segments = (buf->flags & NET_BUF_FRAGS); + + /* Make sure old slist entry state doesn't remain */ + buf->frags = NULL; + buf->flags &= ~NET_BUF_FRAGS; + + net_buf_unref(buf); + } + + return true; +} + +void bt_mesh_friend_enqueue_rx(struct bt_mesh_net_rx *rx, + enum bt_mesh_friend_pdu_type type, + u64_t *seq_auth, u8_t seg_count, + 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)) { + continue; + } + + if (!friend_queue_prepare_space(frnd, rx->ctx.addr, seq_auth, + seg_count)) { + continue; + } + + friend_lpn_enqueue_rx(frnd, rx, type, seq_auth, seg_count, + sbuf); + } +} + +bool bt_mesh_friend_enqueue_tx(struct bt_mesh_net_tx *tx, + enum bt_mesh_friend_pdu_type type, + u64_t *seq_auth, u8_t seg_count, + 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)) { + continue; + } + + if (!friend_queue_prepare_space(frnd, tx->src, seq_auth, + seg_count)) { + continue; + } + + friend_lpn_enqueue_tx(frnd, tx, type, seq_auth, seg_count, + 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]; + + if (!is_seg(seg, src, *seq_auth & TRANS_SEQ_ZERO_MASK)) { + continue; + } + + BT_WARN("%s, Clearing incomplete segments for 0x%04x", __func__, src); + + purge_buffers(&seg->queue); + seg->seg_count = 0U; + break; + } + } +} + +void bt_mesh_friend_remove_lpn(u16_t lpn_addr) +{ + struct bt_mesh_friend *frnd = NULL; + + frnd = bt_mesh_friend_find(BLE_MESH_KEY_ANY, lpn_addr, false, false); + if (frnd) { + friend_clear(frnd, BLE_MESH_FRIENDSHIP_TERMINATE_DISABLE); + } +} + +#endif /* CONFIG_BLE_MESH_FRIEND */ diff --git a/components/bt/esp_ble_mesh/mesh_core/friend.h b/components/bt/esp_ble_mesh/mesh_core/friend.h new file mode 100644 index 0000000000..6193e09241 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/friend.h @@ -0,0 +1,59 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _FRIEND_H_ +#define _FRIEND_H_ + +#include "net.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); + +bool bt_mesh_friend_queue_has_space(u16_t net_idx, u16_t src, u16_t dst, + u64_t *seq_auth, u8_t seg_count); + +void bt_mesh_friend_enqueue_rx(struct bt_mesh_net_rx *rx, + enum bt_mesh_friend_pdu_type type, + u64_t *seq_auth, u8_t seg_count, + 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, u8_t seg_count, + 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); +int bt_mesh_friend_deinit(void); + +void bt_mesh_friend_remove_lpn(u16_t lpn_addr); + +#endif /* _FRIEND_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/health_cli.c b/components/bt/esp_ble_mesh/mesh_core/health_cli.c new file mode 100644 index 0000000000..75a025bfda --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/health_cli.c @@ -0,0 +1,516 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_MODEL) + +#include "foundation.h" +#include "mesh_common.h" +#include "health_cli.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 bt_mesh_mutex_t health_client_lock; + +static void bt_mesh_health_client_mutex_new(void) +{ + if (!health_client_lock.mutex) { + bt_mesh_mutex_create(&health_client_lock); + } +} + +static void bt_mesh_health_client_mutex_free(void) +{ + bt_mesh_mutex_free(&health_client_lock); +} + +static void bt_mesh_health_client_lock(void) +{ + bt_mesh_mutex_lock(&health_client_lock); +} + +static void bt_mesh_health_client_unlock(void) +{ + bt_mesh_mutex_unlock(&health_client_lock); +} + +static void timeout_handler(struct k_work *work) +{ + struct k_delayed_work *timer = NULL; + bt_mesh_client_node_t *node = NULL; + struct bt_mesh_msg_ctx ctx = {0}; + u32_t opcode = 0U; + + BT_WARN("Receive health status message timeout"); + + bt_mesh_health_client_lock(); + + timer = CONTAINER_OF(work, struct k_delayed_work, work); + + if (timer && !k_delayed_work_free(timer)) { + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (node) { + memcpy(&ctx, &node->ctx, sizeof(ctx)); + opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_health_client_cb_evt_to_btc( + opcode, BTC_BLE_MESH_EVT_HEALTH_CLIENT_TIMEOUT, ctx.model, &ctx, NULL, 0); + } + } + + bt_mesh_health_client_unlock(); + + return; +} + +static void health_client_cancel(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + void *status, size_t len) +{ + 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; + } + + /* If it is a publish message, sent to the user directly. */ + buf.data = (u8_t *)status; + buf.len = (u16_t)len; + + bt_mesh_health_client_lock(); + + node = bt_mesh_is_client_recv_publish_msg(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 = BTC_BLE_MESH_EVT_HEALTH_CLIENT_GET_STATE; + break; + case OP_HEALTH_FAULT_CLEAR: + case OP_HEALTH_FAULT_TEST: + case OP_HEALTH_PERIOD_SET: + case OP_ATTENTION_SET: + evt_type = BTC_BLE_MESH_EVT_HEALTH_CLIENT_SET_STATE; + break; + default: + break; + } + + if (!k_delayed_work_free(&node->timer)) { + u32_t opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_health_client_cb_evt_to_btc( + opcode, evt_type, model, ctx, (const u8_t *)status, len); + } + } + + bt_mesh_health_client_unlock(); + + 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 = 0U; + u16_t cid = 0U; + + 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_client_recv_publish_msg(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); + + ((void) test_id); + ((void) cid); +} + +static void health_period_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t status = 0U; + + 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 = 0U; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_ATTENTION_GET, 0); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_ATTENTION_SET, 1); + u32_t opcode = 0U; + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_PERIOD_GET, 0); + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_PERIOD_SET, 1); + u32_t opcode = 0U; + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_FAULT_TEST, 3); + u32_t opcode = 0U; + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_FAULT_CLEAR, 2); + u32_t opcode = 0U; + int err = 0; + + 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) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_FAULT_GET, 2); + int err = 0; + + 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; + } + + if (!client->internal_data) { + internal = bt_mesh_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; + } else { + bt_mesh_client_clear_list(client->internal_data); + } + + bt_mesh_health_client_mutex_new(); + + /* Set the default health client pointer */ + if (!health_cli) { + health_cli = client; + } + + return 0; +} + +int bt_mesh_health_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + bt_mesh_health_client_t *client = NULL; + + 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; + } + + if (client->internal_data) { + /* Remove items from the list */ + bt_mesh_client_clear_list(client->internal_data); + + /* Free the allocated internal data */ + bt_mesh_free(client->internal_data); + client->internal_data = NULL; + } + + bt_mesh_health_client_mutex_free(); + + if (health_cli) { + health_cli = NULL; + } + + return 0; +} diff --git a/components/bt/esp_ble_mesh/mesh_core/health_srv.c b/components/bt/esp_ble_mesh/mesh_core/health_srv.c new file mode 100644 index 0000000000..fc1d0366ca --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/health_srv.c @@ -0,0 +1,550 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_MODEL) + +#include "access.h" +#include "foundation.h" +#include "mesh_common.h" +#include "health_srv.h" + +#include "btc_ble_mesh_health_model.h" + +#define HEALTH_TEST_STANDARD 0x00 + +#define HEALTH_NO_FAULT 0x00 + +/* Health Server context of the primary element */ +struct bt_mesh_health_srv *health_srv; + +/** + * When an Element receives a Health Fault Get, or a Health Fault Test, or + * a Health Fault Test Unacknowledged, or a Health Fault Clear, or a Health + * Fault Clear Unacknowledged message that is not successfully processed + * (i.e. the Company ID field that does not identify any Health Fault state + * present in the node), it shall ignore the message. + * The Health Fault state is identified by Company ID and may be present in + * the node for more than one Company ID. + */ + +static u8_t health_get_curr_fault_count(struct bt_mesh_model *model) +{ + struct bt_mesh_health_srv *srv = model->user_data; + u8_t count = 0U; + size_t i = 0U; + + for (i = 0U; i < ARRAY_SIZE(srv->test.curr_faults); i++) { + if (srv->test.curr_faults[i] != HEALTH_NO_FAULT) { + count++; + } + } + + return count; +} + +static void health_get_fault_value(struct bt_mesh_model *model, + struct net_buf_simple *msg, + bool current) +{ + struct bt_mesh_health_srv *srv = model->user_data; + size_t array_size = 0U; + size_t i = 0U; + + array_size = current ? ARRAY_SIZE(srv->test.curr_faults) : ARRAY_SIZE(srv->test.reg_faults); + + for (i = 0U; i < array_size; i++) { + if (net_buf_simple_tailroom(msg) == 0) { + return; + } + + u8_t fault = current ? srv->test.curr_faults[i] : srv->test.reg_faults[i]; + if (fault != HEALTH_NO_FAULT) { + net_buf_simple_add_u8(msg, fault); + } + } +} + +static bool health_is_test_id_exist(struct bt_mesh_model *model, u8_t test_id) +{ + struct bt_mesh_health_srv *srv = model->user_data; + int i; + + for (i = 0; i < srv->test.id_count; i++) { + if (srv->test.test_ids[i] == test_id) { + return true; + } + } + + return false; +} + +static int health_send_fault_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + struct bt_mesh_health_srv *srv = model->user_data; + struct net_buf_simple *msg = NULL; + int err = 0; + + msg = bt_mesh_alloc_buf(4 + ARRAY_SIZE(srv->test.reg_faults) + 4); + if (!msg) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_STATUS); + net_buf_simple_add_u8(msg, srv->test.prev_test_id); + net_buf_simple_add_le16(msg, srv->test.company_id); + if (ctx->recv_op != OP_HEALTH_FAULT_CLEAR) { + /** + * For Health Fault Clear, the FaultArray field in Health Fault Status + * shall be empty. + */ + health_get_fault_value(model, msg, false); + } + + err = bt_mesh_model_send(model, ctx, msg, NULL, NULL); + if (err) { + BT_ERR("%s, Failed to send Health Fault Status response", __func__); + } + + bt_mesh_free_buf(msg); + return err; +} + +static void health_fault_get(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 = 0U; + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + company_id = net_buf_simple_pull_le16(buf); + if (company_id != srv->test.company_id) { + BT_ERR("%s, Unknown Company ID 0x%04x", __func__, company_id); + return; + } + + BT_DBG("company_id 0x%04x", company_id); + + health_send_fault_status(model, ctx); +} + +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; + u16_t company_id = 0U; + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + company_id = net_buf_simple_pull_le16(buf); + if (company_id != srv->test.company_id) { + BT_ERR("%s, Unknown Company ID 0x%04x", __func__, company_id); + return; + } + + BT_DBG("company_id 0x%04x", company_id); + + memset(srv->test.reg_faults, HEALTH_NO_FAULT, ARRAY_SIZE(srv->test.reg_faults)); + + if (srv->cb.fault_clear) { + srv->cb.fault_clear(model, company_id); + } + + if (ctx->recv_op == OP_HEALTH_FAULT_CLEAR) { + health_send_fault_status(model, ctx); + } +} + +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; + u16_t company_id = 0U; + u8_t test_id = 0U; + + BT_DBG("%s", __func__); + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return; + } + + test_id = net_buf_simple_pull_u8(buf); + if (health_is_test_id_exist(model, test_id) == false) { + BT_ERR("%s, Unknown Test ID 0x%02x", __func__, test_id); + return; + } + + company_id = net_buf_simple_pull_le16(buf); + if (company_id != srv->test.company_id) { + BT_ERR("%s, Unknown Company ID 0x%04x", __func__, company_id); + return; + } + + BT_DBG("test 0x%02x company 0x%04x", test_id, company_id); + + srv->test.prev_test_id = test_id; + + if (srv->cb.fault_test) { + srv->cb.fault_test(model, test_id, company_id); + } + + if (ctx->recv_op == OP_HEALTH_FAULT_TEST) { + health_send_fault_status(model, ctx); + } +} + +static void send_attention_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_ATTENTION_STATUS, 1); + struct bt_mesh_health_srv *srv = model->user_data; + u8_t time = 0U; + + 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 health_set_attention(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t time = 0U; + + 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__); + + health_set_attention(model, ctx, buf); + + if (ctx->recv_op == OP_ATTENTION_SET) { + send_attention_status(model, ctx); + } +} + +static void send_health_period_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_PERIOD_STATUS, 1); + + 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_set_period(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u8_t period = 0U; + + 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_set_period(model, ctx, buf); + + if (ctx->recv_op == OP_HEALTH_PERIOD_SET) { + 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 }, + { OP_HEALTH_FAULT_TEST, 3, health_fault_test }, + { OP_HEALTH_FAULT_TEST_UNREL, 3, health_fault_test }, + { 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 }, + { OP_ATTENTION_GET, 0, attention_get }, + { OP_ATTENTION_SET, 1, attention_set }, + { OP_ATTENTION_SET_UNREL, 1, attention_set }, + BLE_MESH_MODEL_OP_END, +}; + +static size_t health_get_current(struct bt_mesh_model *model, + struct net_buf_simple *msg) +{ + struct bt_mesh_health_srv *srv = model->user_data; + + if (!srv) { + BT_ERR("%s, No Health Server context provided", __func__); + return 0; + } + + if (msg->size < 4) { + BT_ERR("%s, Too small health publication msg size %d", __func__, msg->size); + return 0; + } + + bt_mesh_model_msg_init(msg, OP_HEALTH_CURRENT_STATUS); + net_buf_simple_add_u8(msg, srv->test.prev_test_id); + net_buf_simple_add_le16(msg, srv->test.company_id); + health_get_fault_value(model, msg, true); + + return health_get_curr_fault_count(model); +} + +static int health_pub_update(struct bt_mesh_model *model) +{ + struct bt_mesh_model_pub *pub = model->pub; + size_t count = 0U; + + BT_DBG("%s", __func__); + + if (!pub || !pub->msg) { + BT_ERR("%s, Invalid health publication context", __func__); + return -EINVAL; + } + + count = health_get_current(model, 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 *model = NULL; + + model = bt_mesh_model_find(elem, BLE_MESH_MODEL_ID_HEALTH_SRV); + if (!model) { + BT_ERR("%s, Health Server does not exist", __func__); + return -EINVAL; + } + + if (!model->pub) { + BT_ERR("%s, Health Server has no publication support", __func__); + return -EINVAL; + } + + /* Let periodic publishing, if enabled, take care of sending the + * Health Current Status. + */ + if (bt_mesh_model_pub_period_get(model)) { + return 0; + } + + health_pub_update(model); + + return bt_mesh_model_publish(model); +} + +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.attn_off) { + srv->cb.attn_off(srv->model); + } + srv->attn_timer_start = false; +} + +int bt_mesh_health_srv_init(struct bt_mesh_model *model, bool primary) +{ + struct bt_mesh_health_srv *srv = model->user_data; + + /* Health Server Model shall be supported by a primary element and may be + * supported by any secondary elements. + */ + + if (!srv) { + if (!primary) { + /* If Health Server is in the secondary element with NULL user_data. */ + return 0; + } + + BT_ERR("%s, No Health Server context provided", __func__); + return -EINVAL; + } + + if (srv->test.id_count == 0 || !srv->test.test_ids) { + BT_ERR("%s, No Health Test ID 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; + srv->attn_timer_start = false; + + memset(srv->test.curr_faults, HEALTH_NO_FAULT, ARRAY_SIZE(srv->test.curr_faults)); + memset(srv->test.reg_faults, HEALTH_NO_FAULT, ARRAY_SIZE(srv->test.reg_faults)); + + if (primary) { + health_srv = srv; + } + + return 0; +} + +int bt_mesh_health_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + struct bt_mesh_health_srv *srv = model->user_data; + + if (!srv) { + if (!primary) { + /* If Health Server is in the secondary element with NULL user_data. */ + return 0; + } + + BT_ERR("%s, No Health Server context provided", __func__); + return -EINVAL; + } + + if (srv->test.id_count == 0 || !srv->test.test_ids) { + BT_ERR("%s, No Health Test ID provided", __func__); + return -EINVAL; + } + + if (!model->pub) { + BT_ERR("%s, Health Server has no publication support", __func__); + return -EINVAL; + } + + model->pub->addr = BLE_MESH_ADDR_UNASSIGNED; + model->pub->update = NULL; + + k_delayed_work_free(&srv->attn_timer); + + if (primary) { + health_srv = NULL; + } + + return 0; +} + +void bt_mesh_attention(struct bt_mesh_model *model, u8_t time) +{ + struct bt_mesh_health_srv *srv = NULL; + + 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.attn_on) { + srv->cb.attn_on(model, time); + } + + k_delayed_work_submit(&srv->attn_timer, time * 1000U); + srv->attn_timer_start = true; + } else { + k_delayed_work_cancel(&srv->attn_timer); + + if (srv->attn_timer_start == true) { + if (srv->cb.attn_off) { + srv->cb.attn_off(model); + } + srv->attn_timer_start = false; + } + } +} diff --git a/components/bt/esp_ble_mesh/mesh_core/include/cfg_cli.h b/components/bt/esp_ble_mesh/mesh_core/include/cfg_cli.h new file mode 100644 index 0000000000..4d4adc6bcf --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/include/cfg_cli.h @@ -0,0 +1,295 @@ +/** @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 "client_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_user_data_t bt_mesh_config_client_t; +typedef bt_mesh_client_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/esp_ble_mesh/mesh_core/include/cfg_srv.h b/components/bt/esp_ble_mesh/mesh_core/include/cfg_srv.h new file mode 100644 index 0000000000..69b60c2d19 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/include/cfg_srv.h @@ -0,0 +1,214 @@ +/** @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" + +/** + * @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) + +typedef union { + struct { + u8_t beacon; + } cfg_beacon_set; + struct { + u8_t ttl; + } cfg_default_ttl_set; + struct { + u8_t gatt_proxy; + } cfg_gatt_proxy_set; + struct { + u8_t relay; + u8_t retransmit; + } cfg_relay_set; + struct { + u16_t elem_addr; + u16_t pub_addr; + u16_t app_idx; + bool cred_flag; + u8_t ttl; + u8_t period; + u8_t transmit; + u16_t cid; + u16_t mod_id; + } cfg_mod_pub_set; + struct { + u16_t elem_addr; + u8_t pub_addr[16]; + u16_t app_idx; + bool cred_flag; + u8_t ttl; + u8_t period; + u8_t transmit; + u16_t cid; + u16_t mod_id; + } cfg_mod_pub_va_set; + struct { + u16_t elem_addr; + u16_t sub_addr; + u16_t cid; + u16_t mod_id; + } cfg_mod_sub_add; + struct { + u16_t elem_addr; + u8_t sub_addr[16]; + u16_t cid; + u16_t mod_id; + } cfg_mod_sub_va_add; + struct { + u16_t elem_addr; + u16_t sub_addr; + u16_t cid; + u16_t mod_id; + } cfg_mod_sub_delete; + struct { + u16_t elem_addr; + u8_t sub_addr[16]; + u16_t cid; + u16_t mod_id; + } cfg_mod_sub_va_delete; + struct { + u16_t elem_addr; + u16_t sub_addr; + u16_t cid; + u16_t mod_id; + } cfg_mod_sub_overwrite; + struct { + u16_t elem_addr; + u8_t sub_addr[16]; + u16_t cid; + u16_t mod_id; + } cfg_mod_sub_va_overwrite; + struct { + u16_t elem_addr; + u16_t cid; + u16_t mod_id; + } cfg_mod_sub_delete_all; + struct { + u16_t net_idx; + u8_t net_key[16]; + } cfg_netkey_add; + struct { + u16_t net_idx; + u8_t net_key[16]; + } cfg_netkey_update; + struct { + u16_t net_idx; + } cfg_netkey_delete; + struct { + u16_t net_idx; + u16_t app_idx; + u8_t app_key[16]; + } cfg_appkey_add; + struct { + u16_t net_idx; + u16_t app_idx; + u8_t app_key[16]; + } cfg_appkey_update; + struct { + u16_t net_idx; + u16_t app_idx; + } cfg_appkey_delete; + struct { + u16_t net_idx; + u8_t identity; + } cfg_node_identity_set; + struct { + u16_t elem_addr; + u16_t app_idx; + u16_t cid; + u16_t mod_id; + } cfg_mod_app_bind; + struct { + u16_t elem_addr; + u16_t app_idx; + u16_t cid; + u16_t mod_id; + } cfg_mod_app_unbind; + struct { + u8_t frnd; + } cfg_friend_set; + struct { + u16_t net_idx; + u8_t kr_phase; + } cfg_kr_phase_set; + struct { + u16_t dst; + u8_t count; + u8_t period; + u8_t ttl; + u16_t feat; + u16_t net_idx; + } cfg_hb_pub_set; + struct { + u16_t src; + u16_t dst; + u8_t period; + } cfg_hb_sub_set; + struct { + u8_t transmit; + } cfg_net_transmit_set; +} bt_mesh_cfg_server_state_change_t; + +/** + * @} + */ + +#endif /* __BLE_MESH_CFG_SRV_H */ diff --git a/components/bt/esp_ble_mesh/mesh_core/include/health_cli.h b/components/bt/esp_ble_mesh/mesh_core/include/health_cli.h new file mode 100644 index 0000000000..b2127c57ac --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/include/health_cli.h @@ -0,0 +1,74 @@ +/** @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 "client_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_user_data_t bt_mesh_health_client_t; +typedef bt_mesh_client_internal_data_t health_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/esp_ble_mesh/mesh_core/include/health_srv.h b/components/bt/esp_ble_mesh/mesh_core/include/health_srv.h new file mode 100644 index 0000000000..71ccabef7c --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/include/health_srv.h @@ -0,0 +1,97 @@ +/** @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" + +/** + * @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 { + /* Clear registered faults */ + void (*fault_clear)(struct bt_mesh_model *model, u16_t company_id); + + /* Run a specific test */ + void (*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, u8_t time); + + /* 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))) + +struct bt_mesh_health_test { + u8_t id_count; /* Number of Health self-test ID */ + const u8_t *test_ids; /* Array of Health self-test IDs */ + u16_t company_id; /* Company ID used to identify the Health Fault state */ + u8_t prev_test_id; /* Most currently performed test id */ + u8_t curr_faults[32]; /* Array of current faults */ + u8_t reg_faults[32]; /* Array of registered faults */ +} __attribute__((packed)); + +/** Mesh Health Server Model Context */ +struct bt_mesh_health_srv { + struct bt_mesh_model *model; + + /* Optional callback struct */ + struct bt_mesh_health_srv_cb cb; + + /* Attention Timer state */ + struct k_delayed_work attn_timer; + + /* Attention Timer start flag */ + bool attn_timer_start; + + /* Health Server fault test */ + struct bt_mesh_health_test test; +}; + +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/esp_ble_mesh/mesh_core/include/mesh_access.h b/components/bt/esp_ble_mesh/mesh_core/include/mesh_access.h new file mode 100644 index 0000000000..19ba515622 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/include/mesh_access.h @@ -0,0 +1,496 @@ +/** @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 "mesh_types.h" +#include "mesh_util.h" +#include "mesh_buf.h" +#include "mesh_kernel.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_SETUP_SRV 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_SETUP_SRV 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; + + /** RSSI of received packet. Not used for sending. */ + s8_t recv_rssi; + + /** 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 []){}) + +/** Length of a short Mesh MIC. */ +#define BLE_MESH_MIC_SHORT 4 +/** Length of a long Mesh MIC. */ +#define BLE_MESH_MIC_LONG 8 + +/** @def BLE_MESH_MODEL_OP_LEN + * + * @brief Helper to determine the length of an opcode. + * + * @param _op Opcode. + */ +#define BLE_MESH_MODEL_OP_LEN(_op) ((_op) <= 0xff ? 1 : (_op) <= 0xffff ? 2 : 3) + +/** @def BLE_MESH_MODEL_BUF_LEN + * + * @brief Helper for model message buffer length. + * + * Returns the length of a Mesh model message buffer, including the opcode + * length and a short MIC. + * + * @param _op Opcode of the message. + * @param _payload_len Length of the model payload. + */ +#define BLE_MESH_MODEL_BUF_LEN(_op, _payload_len) \ + (BLE_MESH_MODEL_OP_LEN(_op) + (_payload_len) + BLE_MESH_MIC_SHORT) + +/** @def BLE_MESH_MODEL_BUF_LEN_LONG_MIC + * + * @brief Helper for model message buffer length. + * + * Returns the length of a Mesh model message buffer, including the opcode + * length and a long MIC. + * + * @param _op Opcode of the message. + * @param _payload_len Length of the model payload. + */ +#define BLE_MESH_MODEL_BUF_LEN_LONG_MIC(_op, _payload_len) \ + (BLE_MESH_MODEL_OP_LEN(_op) + (_payload_len) + BLE_MESH_MIC_LONG) + +/** @def BLE_MESH_MODEL_BUF_DEFINE + * + * @brief Define a Mesh model message buffer using @ref NET_BUF_SIMPLE_DEFINE. + * + * @param _buf Buffer name. + * @param _op Opcode of the message. + * @param _payload_len Length of the model message payload. + */ +#define BLE_MESH_MODEL_BUF_DEFINE(_buf, _op, _payload_len) \ + NET_BUF_SIMPLE_DEFINE(_buf, BLE_MESH_MODEL_BUF_LEN(_op, (_payload_len))) + +#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:12, /**< Publish AppKey Index. */ + cred:1; /**< Friendship Credentials Flag. */ + + u8_t ttl; /**< Publish Time to Live. */ + u8_t retransmit; /**< Retransmit Count & Interval Steps. */ + u8_t period; /**< Publish Period. */ + u8_t period_div:4, /**< Divisor for the Period. */ + 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/esp_ble_mesh/mesh_core/include/mesh_bearer_adapt.h b/components/bt/esp_ble_mesh/mesh_core/include/mesh_bearer_adapt.h new file mode 100644 index 0000000000..7c3ead1d0a --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/include/mesh_bearer_adapt.h @@ -0,0 +1,766 @@ +/* + * 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_uuid.h" +#include "mesh_buf.h" + +/* BLE Mesh Max Connection Count */ +#ifdef CONFIG_BLUEDROID_ENABLED +#define BLE_MESH_MAX_CONN \ + MIN(CONFIG_BT_ACL_CONNECTIONS, CONFIG_BTDM_CONTROLLER_BLE_MAX_CONN) + +#define BLE_MESH_ADV_TASK_CORE TASK_PINNED_TO_CORE +#endif + +#ifdef CONFIG_BT_NIMBLE_ENABLED +#define BLE_MESH_MAX_CONN CONFIG_BT_NIMBLE_MAX_CONNECTIONS + +#ifdef CONFIG_BT_NIMBLE_PINNED_TO_CORE +#define BLE_MESH_ADV_TASK_CORE (CONFIG_BT_NIMBLE_PINNED_TO_CORE < portNUM_PROCESSORS ? CONFIG_BT_NIMBLE_PINNED_TO_CORE : tskNO_AFFINITY) +#else +#define BLE_MESH_ADV_TASK_CORE (0) +#endif + +#endif + +#define BLE_MESH_ADV_TASK_STACK_SIZE 3072 + +#define BLE_MESH_GAP_ADV_MAX_LEN 31 + +#define BLE_MESH_GATT_DEF_MTU_SIZE 23 + +/* 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)(bt_mesh_addr_t *addr, struct bt_mesh_conn *conn, int id); + + void (*disconnected)(bt_mesh_addr_t *addr, struct bt_mesh_conn *conn, u8_t reason); + + ssize_t (*prov_write_descr)(bt_mesh_addr_t *addr, struct bt_mesh_conn *conn); + + ssize_t (*prov_notify)(struct bt_mesh_conn *conn, u8_t *data, u16_t len); + + ssize_t (*proxy_write_descr)(bt_mesh_addr_t *addr, 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_mesh_host_init(void); +int bt_mesh_host_deinit(void); + +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); +void bt_mesh_gatts_conn_cb_deregister(void); + +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_deregister(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); + +int bt_mesh_gatts_set_local_device_name(const char *name); + +void bt_mesh_gattc_conn_cb_register(struct bt_mesh_prov_conn_cb *cb); +void bt_mesh_gattc_conn_cb_deregister(void); + +u8_t bt_mesh_gattc_get_free_conn_count(void); + +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_gatt_deinit(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/esp_ble_mesh/mesh_core/include/mesh_hci.h b/components/bt/esp_ble_mesh/mesh_core/include/mesh_hci.h new file mode 100644 index 0000000000..75ff8ccf3a --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/include/mesh_hci.h @@ -0,0 +1,133 @@ +/* + * 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_atomic.h" +#include "mesh_bearer_adapt.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/esp_ble_mesh/mesh_core/include/mesh_main.h b/components/bt/esp_ble_mesh/mesh_core/include/mesh_main.h new file mode 100644 index 0000000000..688c3a8a68 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/include/mesh_main.h @@ -0,0 +1,636 @@ +/** @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_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 net_key NetKey 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, const u8_t net_key[16], 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) + */ + 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)(u16_t 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 */ +}; + +enum ble_mesh_role { + NODE = 0, + PROVISIONER, + FAST_PROV, + ROLE_NVAL, +}; + +/* 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); + +/* 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 Provisioner corresponding functionalities, e.g. scan, etc. + * + * @param bearers Bit-wise OR of provisioning bearers. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_provisioner_net_start(bt_mesh_prov_bearer_t bearers); + +/** @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); + +/* BLE Mesh deinit parameters */ +struct bt_mesh_deinit_param { + bool erase; /* Indicate if erasing flash when deinit mesh stack */ +}; + +/** @brief De-initialize Mesh support + * + * @param param BLE Mesh deinit parameters. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_deinit(struct bt_mesh_deinit_param *param); + +/** @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 device is an unprovisioned device + * and will act as a node once provisioned. + * + * @return true - yes, false - no. + */ +bool bt_mesh_is_node(void); + +/** @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 Check if the device is a Provisioner. + * + * @return true - yes, false - no. + */ +bool bt_mesh_is_provisioner(void); + +/** @brief Check if the Provisioner is enabled + * + * @return true - enabled, false - disabled. + */ +bool bt_mesh_is_provisioner_en(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. + * @param force when disable LPN functionality, use this flag to indicate + * whether directly clear corresponding information or sending + * friend clear to disable it. + * + * @return Zero on success or (negative) error code otherwise. + */ +int bt_mesh_lpn_set(bool enable, bool force); + +/** @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)); + +/** @brief Register a callback for Friendship changes of friend node. + * + * Registers a callback that will be called whenever Friendship gets + * established or is terminated. + * + * @param cb Function to call when the Friendship status of friend node changes. + */ +void bt_mesh_friend_set_cb(void (*cb)(bool establish, u16_t lpn_addr, u8_t reason)); + +/** + * @} + */ + +#endif /* _BLE_MESH_MAIN_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/include/mesh_proxy.h b/components/bt/esp_ble_mesh/mesh_core/include/mesh_proxy.h new file mode 100644 index 0000000000..0b14d9e184 --- /dev/null +++ b/components/bt/esp_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/esp_ble_mesh/mesh_core/include/mesh_uuid.h b/components/bt/esp_ble_mesh/mesh_core/include/mesh_uuid.h new file mode 100644 index 0000000000..de03df5c32 --- /dev/null +++ b/components/bt/esp_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/esp_ble_mesh/mesh_core/lpn.c b/components/bt/esp_ble_mesh/mesh_core/lpn.c new file mode 100644 index 0000000000..69d428ad40 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/lpn.c @@ -0,0 +1,1104 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_LOW_POWER) + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "transport.h" +#include "access.h" +#include "beacon.h" +#include "lpn.h" +#include "foundation.h" +#include "mesh_main.h" +#include "cfg_srv.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)) + +/** + * 1. Should use 20 attempts for BQB test case MESH/NODE/FRND/LPM/BI-02-C. + * 2. We should use more specific value for each PollTimeout range. + */ +#define REQ_ATTEMPTS(lpn) (POLL_TIMEOUT_MAX(lpn) < K_SECONDS(3) ? 2 : 6) + +#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) + +#define FIRST_POLL_ATTEMPTS 6 + +static void (*lpn_cb)(u16_t friend_addr, bool established); + +#if !CONFIG_BLE_MESH_NO_LOG +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"; + case BLE_MESH_LPN_OFFER_RECV: + return "offer recv"; + default: + return "(unknown)"; + } +} +#endif + +static inline void lpn_set_state(int state) +{ + BT_DBG("%s -> %s", state2str(bt_mesh.lpn.state), state2str(state)); + 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 bool scan_after_clear; + +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. + */ + if (scan_after_clear == false) { + bt_mesh_scan_enable(); + scan_after_clear = true; + } + + 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_cfg_srv *cfg = bt_mesh_cfg_get(); + 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 (cfg->hb_pub.feat & BLE_MESH_FEAT_LOW_POWER) { + bt_mesh_heartbeat_send(); + } + + 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); + + scan_after_clear = false; + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } +} + +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); + + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_enable(); + } + 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 = sys_cpu_to_be16(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; + + BT_DBG("req 0x%02x duration %u err %d state %s", + lpn->sent_req, duration, err, state2str(lpn->state)); + + 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); + /** + * Friend Update is replied by Friend Node with TTL set to 0 and Network + * Transmit set to 30ms which will cause the packet easy to be missed. + * Regarding this situation, here we can reduce the duration of receiving + * the first Friend Update. + */ + 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 = 0; + + 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, bool force) +{ + 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(force); + } + } + + 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 = NULL; + u16_t frnd_counter = 0U; + int err = 0; + + 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_INFO("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++; + + if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } + + 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 = 0U, counter = 0U; + + 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 = {0}; + size_t i = 0U, g = 0U; + + BT_DBG("op 0x%02x sent_req 0x%02x", op, lpn->sent_req); + + if (lpn->sent_req) { + return false; + } + + for (i = 0U, g = 0U; 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 == 0U) { + 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 < FIRST_POLL_ATTEMPTS) { + 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; + + BT_DBG("state: %s", state2str(lpn->state)); + + 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); + lpn->sent_req = 0U; + k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT); + break; + case BLE_MESH_LPN_OFFER_RECV: + if (lpn->req_attempts < FIRST_POLL_ATTEMPTS) { + BT_WARN("Retrying the first Friend Poll, %d attempts", lpn->req_attempts); + lpn->sent_req = 0U; + send_friend_poll(); + break; + } + + BT_ERR("Timeout waiting for the first Friend Update"); + clear_friendship(true, false); + 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 = 0U; + + 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) { + struct bt_mesh_cfg_srv *cfg = bt_mesh_cfg_get(); + + /* 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 (cfg->hb_pub.feat & BLE_MESH_FEAT_LOW_POWER) { + bt_mesh_heartbeat_send(); + } + + 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_INFO("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_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; +} + +int bt_mesh_lpn_deinit(void) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + bt_mesh_lpn_disable(true); + + k_delayed_work_free(&lpn->timer); + + return 0; +} + +#endif /* CONFIG_BLE_MESH_LOW_POWER */ diff --git a/components/bt/esp_ble_mesh/mesh_core/lpn.h b/components/bt/esp_ble_mesh/mesh_core/lpn.h new file mode 100644 index 0000000000..45573661a0 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/lpn.h @@ -0,0 +1,70 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _LPN_H_ +#define _LPN_H_ + +#include "net.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); +int bt_mesh_lpn_deinit(void); + +#endif /* _LPN_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/main.c b/components/bt/esp_ble_mesh/mesh_core/main.c new file mode 100644 index 0000000000..dcec933db3 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/main.c @@ -0,0 +1,672 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG) + +#include "adv.h" +#include "prov.h" +#include "beacon.h" +#include "lpn.h" +#include "friend.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "settings.h" +#include "mesh.h" +#include "mesh_hci.h" +#include "mesh_common.h" +#include "proxy_client.h" +#include "proxy_server.h" +#include "provisioner_prov.h" +#include "provisioner_main.h" + +#define ACTION_ENTER 0x01 +#define ACTION_SUSPEND 0x02 +#define ACTION_EXIT 0x03 + +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 = false; + int err = 0; + + BT_INFO("Primary Element: 0x%04x", addr); + BT_INFO("net_idx 0x%04x flags 0x%02x iv_index 0x%04x", + net_idx, flags, iv_index); + BT_INFO("dev_key %s", bt_hex(dev_key, 16)); + + 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_SERVER)) { + 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_SETTINGS)) { + bt_mesh_clear_role(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROV)) { + bt_mesh_prov_reset(); + } +} + +bool bt_mesh_is_node(void) +{ + return bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_NODE); +} + +bool bt_mesh_is_provisioned(void) +{ + if (bt_mesh_is_node()) { + return bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID); + } else { + return false; + } +} + +bool bt_mesh_is_provisioner(void) +{ + return bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_PROVISIONER); +} + +bool bt_mesh_is_provisioner_en(void) +{ + if (bt_mesh_is_provisioner()) { + return bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_VALID_PROV); + } else { + return false; + } +} + +int bt_mesh_prov_enable(bt_mesh_prov_bearer_t bearers) +{ + if (bt_mesh_is_provisioned()) { + return -EALREADY; + } + + bt_mesh_atomic_set_bit(bt_mesh.flags, BLE_MESH_NODE); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_role(); + } + + 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; + } + + bt_mesh_atomic_clear_bit(bt_mesh.flags, BLE_MESH_NODE); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_role(); + } + + 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; +} + +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 = 0; + + 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 = 0; + + 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 = 0; + + bt_mesh_k_init(); + + bt_mesh_hci_init(); + + bt_mesh_adapt_init(); + + err = bt_mesh_comp_register(comp); + if (err) { + return err; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROXY)) { + bt_mesh_gatt_init(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROV)) { + if (IS_ENABLED(CONFIG_BLE_MESH_NODE)) { + err = bt_mesh_prov_init(prov); + if (err) { + return err; + } + } + if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER)) { + err = bt_mesh_provisioner_prov_init(prov); + if (err) { + return err; + } + } + } + + bt_mesh_net_init(); + bt_mesh_trans_init(); + + /* Changed by Espressif, add a random delay (0 ~ 3s) */ + if (IS_ENABLED(CONFIG_BLE_MESH_FAST_PROV)) { + u32_t delay = 0; + bt_mesh_rand(&delay, sizeof(u32_t)); + vTaskDelay((delay % 3000) / portTICK_PERIOD_MS); + } + + bt_mesh_beacon_init(); + + bt_mesh_adv_init(); + + if (IS_ENABLED(CONFIG_BLE_MESH_PROXY)) { + if ((IS_ENABLED(CONFIG_BLE_MESH_NODE) && + IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) || + IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY_SERVER)) { + bt_mesh_proxy_init(); + } + if ((IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && + IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) || + IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY_CLIENT)) { + bt_mesh_proxy_prov_client_init(); + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER)) { + bt_mesh_provisioner_init(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_settings_init(); + } + + return 0; +} + +int bt_mesh_deinit(struct bt_mesh_deinit_param *param) +{ + int err = 0; + + if (param == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + if (IS_ENABLED(CONFIG_BLE_MESH_PB_ADV)) { + bt_mesh_beacon_disable(); + bt_mesh_scan_disable(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) { + bt_mesh_proxy_prov_disable(true); + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en()) { + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) { + bt_mesh_provisioner_pb_gatt_disable(); + } + + bt_mesh_scan_disable(); + bt_mesh_atomic_clear_bit(bt_mesh.flags, BLE_MESH_VALID_PROV); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROV)) { + if (IS_ENABLED(CONFIG_BLE_MESH_NODE)) { + err = bt_mesh_prov_deinit(); + if (err) { + return err; + } + } + if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER)) { + err = bt_mesh_provisioner_prov_deinit(param->erase); + if (err) { + return err; + } + } + } + + bt_mesh_trans_deinit(param->erase); + bt_mesh_net_deinit(param->erase); + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE)) { + bt_mesh_beacon_deinit(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_PROXY)) { + if (IS_ENABLED(CONFIG_BLE_MESH_NODE)) { + bt_mesh_proxy_deinit(); + } + } + + if ((IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && + IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) || + IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY_CLIENT)) { + bt_mesh_proxy_prov_client_deinit(); + } + + bt_mesh_gatt_deinit(); + + if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER)) { + err = bt_mesh_provisioner_deinit(param->erase); + if (err) { + return err; + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + bt_mesh_friend_deinit(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_LOW_POWER)) { + bt_mesh_lpn_deinit(); + } + + bt_mesh_adv_deinit(); + + err = bt_mesh_comp_deregister(); + if (err) { + return err; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + if (param->erase) { + bt_mesh_clear_role(); + } + bt_mesh_settings_deinit(); + } + + bt_mesh_k_deinit(); + + return 0; +} + +#if defined(CONFIG_BLE_MESH_PROVISIONER) +int bt_mesh_provisioner_net_start(bt_mesh_prov_bearer_t bearers) +{ + bt_mesh_provisioner_set_prov_bearer(bearers, false); + +#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); + } +#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)) { + bt_mesh_provisioner_pb_gatt_enable(); + } + + bt_mesh_atomic_set_bit(bt_mesh.flags, BLE_MESH_VALID_PROV); + + if (bt_mesh_beacon_get() == BLE_MESH_BEACON_ENABLED) { + bt_mesh_beacon_enable(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + bt_mesh_friend_init(); + } + + return 0; +} + +int bt_mesh_provisioner_enable(bt_mesh_prov_bearer_t bearers) +{ + int err = 0; + + if (bt_mesh_is_provisioner_en()) { + BT_WARN("%s, Already", __func__); + return -EALREADY; + } + + err = bt_mesh_provisioner_set_prov_info(); + if (err) { + BT_ERR("%s, Failed to set provisioning info", __func__); + return err; + } + + err = bt_mesh_provisioner_net_create(); + if (err) { + BT_ERR("%s, Failed to create network", __func__); + return err; + } + + bt_mesh_atomic_set_bit(bt_mesh.flags, BLE_MESH_PROVISIONER); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_role(); + } + + return bt_mesh_provisioner_net_start(bearers); +} + +int bt_mesh_provisioner_disable(bt_mesh_prov_bearer_t bearers) +{ + bt_mesh_prov_bearer_t enable = 0U; + + if (!bt_mesh_is_provisioner_en()) { + BT_WARN("%s, Already", __func__); + return -EALREADY; + } + + enable = bt_mesh_provisioner_get_prov_bearer(); + if (!(enable & bearers)) { + BT_ERR("%s, Bearers mismatch", __func__); + return -EINVAL; + } + + bt_mesh_provisioner_set_prov_bearer(bearers, true); + + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && + (enable & BLE_MESH_PROV_GATT) && + (bearers & BLE_MESH_PROV_GATT)) { + bt_mesh_provisioner_pb_gatt_disable(); +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_REMOVE, + BLE_MESH_EXCEP_INFO_MESH_PROV_ADV, NULL); +#endif + } + + if (!(enable & (~bearers))) { + /* Provisioner is disabled completely, disable scan here */ + bt_mesh_scan_disable(); + +#if defined(CONFIG_BLE_MESH_USE_DUPLICATE_SCAN) + if (IS_ENABLED(CONFIG_BLE_MESH_PB_ADV) && + (enable & BLE_MESH_PROV_ADV)) { + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_REMOVE, + BLE_MESH_EXCEP_INFO_MESH_BEACON, NULL); + } +#endif + + /* Clear corresponding flags */ + bt_mesh_atomic_and(bt_mesh.flags, ~(BIT(BLE_MESH_PROVISIONER) | BIT(BLE_MESH_VALID_PROV))); + + /* When Provisioner is disabled, the device role indicated by bt_mesh.flags + * will not be cleared, because when Provisioner is restarted after disabled, + * its previous information can be recovered from flash properly. + */ + + if (bt_mesh_beacon_get() == BLE_MESH_BEACON_ENABLED) { + bt_mesh_beacon_disable(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + bt_mesh_friend_clear_net_idx(BLE_MESH_KEY_ANY); + } + } + + 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 ((!bt_mesh_is_provisioner_en() && (action == ACTION_SUSPEND || action == ACTION_EXIT)) || + (bt_mesh_is_provisioner_en() && (action == ACTION_ENTER))) { + BT_WARN("%s, Already", __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_SERVER)) { + 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)) { + bt_mesh_provisioner_pb_gatt_enable(); + } + bt_mesh_provisioner_set_prov_bearer(BLE_MESH_PROV_ADV, false); + bt_mesh_provisioner_fast_prov_enable(true); + bt_mesh_atomic_or(bt_mesh.flags, BIT(BLE_MESH_PROVISIONER) | BIT(BLE_MESH_VALID_PROV)); + } else { + if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) { + bt_mesh_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_SERVER) && + bt_mesh_gatt_proxy_get() != BLE_MESH_GATT_PROXY_NOT_SUPPORTED) { + bt_mesh_proxy_gatt_enable(); + bt_mesh_adv_update(); + } +#endif + bt_mesh_atomic_and(bt_mesh.flags, ~(BIT(BLE_MESH_PROVISIONER) | BIT(BLE_MESH_VALID_PROV))); + bt_mesh_provisioner_fast_prov_enable(false); + if (action == ACTION_EXIT) { + bt_mesh_provisioner_remove_node(NULL); + } + } + + return 0x0; +} +#endif /* CONFIG_BLE_MESH_FAST_PROV */ diff --git a/components/bt/esp_ble_mesh/mesh_core/mesh.h b/components/bt/esp_ble_mesh/mesh_core/mesh.h new file mode 100644 index 0000000000..75c89584de --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/mesh.h @@ -0,0 +1,24 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MESH_H_ +#define _MESH_H_ + +#include "net.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/esp_ble_mesh/mesh_core/net.c b/components/bt/esp_ble_mesh/mesh_core/net.c new file mode 100644 index 0000000000..a06d62e5ae --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/net.c @@ -0,0 +1,1557 @@ +/* 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 + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_NET) + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "lpn.h" +#include "friend.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "beacon.h" +#include "settings.h" +#include "prov.h" +#include "proxy_client.h" +#include "proxy_server.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; + +#if defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) +#define BLE_MESH_MAX_STORED_RELAY_COUNT (CONFIG_BLE_MESH_RELAY_ADV_BUF_COUNT / 2) +#endif + +static bool check_dup(struct net_buf_simple *data) +{ + const u8_t *tail = net_buf_simple_tail(data); + u32_t val = 0U; + 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 = 0U, hash2 = 0U; + + /* 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); + int i; + + for (i = 0; i < ARRAY_SIZE(msg_cache); i++) { + if (msg_cache[i] == hash) { + return true; + } + } + + /* Add to the cache */ + rx->msg_cache_idx = msg_cache_next++; + msg_cache[rx->msg_cache_idx] = hash; + msg_cache_next %= ARRAY_SIZE(msg_cache); + + return false; +} + +#if CONFIG_BLE_MESH_PROVISIONER +void bt_mesh_msg_cache_clear(u16_t unicast_addr, u8_t elem_num) +{ + u16_t src = 0U; + int i; + + for (i = 0; i < ARRAY_SIZE(msg_cache); i++) { + src = (((u8_t)(msg_cache[i] >> 16)) << 8) | (u8_t)(msg_cache[i] >> 24); + if (src >= unicast_addr && src < unicast_addr + elem_num) { + memset(&msg_cache[i], 0x0, sizeof(msg_cache[i])); + } + } +} +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +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 = 0U; + int err = 0; + + 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_SERVER) + 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 = 0U, frnd_addr = 0U; + int err = 0; + u8_t p[9] = {0}; + +#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 = 0, 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 = NULL; + int i, err = 0; + + 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 = NULL; + + 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 = NULL; + int err = 0; + + 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_SERVER)) { + 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_INFO("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_INFO("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_SERVER) && + bt_mesh_gatt_proxy_get() == BLE_MESH_GATT_PROXY_ENABLED) { + bt_mesh_proxy_beacon_send(sub); + } +} + +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_INFO("IV Update state entered. New index 0x%08x", + bt_mesh.iv_index); + + bt_mesh_rpl_reset(); + } else { + BT_INFO("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; +} + +bool bt_mesh_primary_subnet_exist(void) +{ + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + if (bt_mesh_subnet_get(BLE_MESH_KEY_PRIMARY)) { + return true; + } + } else if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en()) { + if (bt_mesh_provisioner_subnet_get(BLE_MESH_KEY_PRIMARY)) { + return true; + } + } + + return false; +} + +u32_t bt_mesh_next_seq(void) +{ + u32_t seq = bt_mesh.seq++; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_seq(); + } + + if (!bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS) && + bt_mesh.seq > IV_UPDATE_SEQ_LIMIT && + bt_mesh_primary_subnet_exist()) { + bt_mesh_beacon_ivu_initiator(true); + bt_mesh_net_iv_update(bt_mesh.iv_index + 1, true); + bt_mesh_net_sec_update(NULL); + } + + 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 = NULL, *priv = NULL; + u32_t seq = 0U; + u16_t dst = 0U; + int err = 0; + + 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; + + /* Get destination, in case it's a proxy client */ + dst = DST(buf->data); + + 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; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY_SERVER) && + bt_mesh_proxy_relay(&buf->b, dst)) { + send_cb_finalize(cb, cb_data); + return 0; + } + + bt_mesh_adv_send(buf, cb, cb_data); + return 0; +} + +static void bt_mesh_net_local(struct k_work *work) +{ + struct net_buf *buf = NULL; + + 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 = 0U; + u8_t nid = 0U; + const u8_t *enc = NULL, *priv = NULL; + u8_t *seq = NULL; + int err = 0; + + 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 = 0; + + 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 (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY_SERVER) && + 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. + */ + send_cb_finalize(cb, cb_data); + + err = 0; + goto done; + } + } + +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT + if (tx->ctx->send_ttl != 1U) { + if (bt_mesh_proxy_client_send(&buf->b, tx->ctx->addr)) { + /* If Proxy Client succeeds to send messages with GATT bearer, + * we can directly finish here. And if not, which means no + * connection has been created with Proxy Client, here we will + * use advertising bearer for the messages. + */ + send_cb_finalize(cb, cb_data); + + err = 0; + goto done; + } + } +#endif + + /* Deliver to local network interface if necessary */ + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned() && + (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] = {0}; + + 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) +{ + size_t subnet_size = 0U; + int i; + + subnet_size = bt_mesh_rx_netkey_size(); + + for (i = 0; i < subnet_size; i++) { + struct bt_mesh_subnet *sub = bt_mesh_rx_netkey_get(i); + + if (sub == NULL || 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; + } + + 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 (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); + } + + 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; + size_t array_size = 0U; + int i; + + BT_DBG("%s", __func__); + + array_size = bt_mesh_rx_netkey_size(); + + for (i = 0; i < array_size; i++) { + sub = bt_mesh_rx_netkey_get(i); + if (!sub) { + BT_DBG("%s, NULL subnet", __func__); + continue; + } + + if (sub->net_idx == BLE_MESH_KEY_UNUSED) { + continue; + } + +#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 + + 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. + */ +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 = NULL, *priv = NULL; + struct net_buf *buf = NULL; + u8_t nid = 0U, transmit = 0U; + + 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(); + } + + /** + * When the node tries to relay a Segment ACK message, in this case + * the corresponding segment packets (if exist) can be removed from + * the relay queue. + */ + +#if !defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) + buf = bt_mesh_adv_create(BLE_MESH_ADV_DATA, transmit, K_NO_WAIT); +#else + /** + * Check if the number of relay packets in queue is too large, if so + * use minimum relay retransmit value for later relay packets. + */ + if (bt_mesh_get_stored_relay_count() >= BLE_MESH_MAX_STORED_RELAY_COUNT) { + transmit = BLE_MESH_TRANSMIT(0, 20); + } + buf = bt_mesh_relay_adv_create(BLE_MESH_ADV_DATA, transmit, K_NO_WAIT); +#endif + + 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_SERVER) && + (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)) { +#if !defined(CONFIG_BLE_MESH_RELAY_ADV_BUF) + bt_mesh_adv_send(buf, NULL, NULL); +#else + bt_mesh_relay_adv_send(buf, NULL, NULL, rx->ctx.addr, rx->ctx.recv_dst); +#endif + } + +done: + net_buf_unref(buf); +} + +void bt_mesh_net_header_parse(struct net_buf_simple *buf, + struct bt_mesh_net_rx *rx) +{ + rx->old_iv = (IVI(buf->data) != (bt_mesh.iv_index & 0x01)); + rx->ctl = CTL(buf->data); + rx->ctx.recv_ttl = TTL(buf->data); + rx->seq = SEQ(buf->data); + rx->ctx.addr = SRC(buf->data); + rx->ctx.recv_dst = DST(buf->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) +{ + 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; +} + +static bool ready_to_recv(void) +{ + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + return true; + } else if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en()) { + if (bt_mesh_provisioner_get_all_node_count()) { + return true; + } + } + + return false; +} + +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 = { .ctx.recv_rssi = rssi }; + struct net_buf_simple_state state = {0}; + + BT_DBG("rssi %d net_if %u", rssi, net_if); + + if (!ready_to_recv()) { + return; + } + + 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 (IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY_SERVER) && + net_if == BLE_MESH_NET_IF_PROXY) { + bt_mesh_proxy_addr_add(data, rx.ctx.addr); + } + + rx.local_match = (bt_mesh_fixed_group_match(rx.ctx.recv_dst) || + bt_mesh_elem_find(rx.ctx.recv_dst)); + + /* The transport layer has indicated that it has rejected the message, + * but would like to see it again if it is received in the future. + * This can happen if a message is received when the device is in + * Low Power mode, but the message was not encrypted with the friend + * credentials. Remove it from the message cache so that we accept + * it again in the future. + */ + if (bt_mesh_trans_recv(&buf, &rx) == -EAGAIN) { + BT_WARN("Removing rejected message from Network Message Cache"); + msg_cache[rx.msg_cache_idx] = 0ULL; + /* Rewind the next index now that we're not using this entry */ + msg_cache_next = rx.msg_cache_idx; + } + + /* 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 (IS_ENABLED(CONFIG_BLE_MESH_RELAY) && + (!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); + } +} + +static void ivu_refresh(struct k_work *work) +{ + bt_mesh.ivu_duration += BLE_MESH_IVU_HOURS; + + BT_INFO("%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)) { + bt_mesh_beacon_ivu_initiator(true); + bt_mesh_net_iv_update(bt_mesh.iv_index, false); + } else if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_iv(true); + } +} + +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_SERVER) && + 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; + const u8_t *net_key = bt_mesh.sub[0].keys[flags].net; + 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, net_key, addr, flags, iv_index); + } +} + +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); +} + +void bt_mesh_net_deinit(bool erase) +{ + k_delayed_work_free(&bt_mesh.ivu_timer); + + k_work_init(&bt_mesh.local_work, NULL); + + /* Local queue uses a while loop, currently no need + * to handle this. + */ + +#if FRIEND_CRED_COUNT > 0 + memset(friend_cred, 0, sizeof(friend_cred)); +#endif + + memset(msg_cache, 0, sizeof(msg_cache)); + msg_cache_next = 0U; + + memset(dup_cache, 0, sizeof(dup_cache)); + dup_cache_next = 0U; + + bt_mesh.iv_index = 0U; + bt_mesh.seq = 0U; + + memset(bt_mesh.flags, 0, sizeof(bt_mesh.flags)); + + if (erase && IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_seq(); + bt_mesh_clear_iv(); + } +} diff --git a/components/bt/esp_ble_mesh/mesh_core/net.h b/components/bt/esp_ble_mesh/mesh_core/net.h new file mode 100644 index 0000000000..abdb57450f --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/net.h @@ -0,0 +1,420 @@ +/* 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_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_SERVER) + 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; + + /* The target number of segments, i.e. not necessarily + * the current number of segments, in the queue. This is + * used for Friend Queue free space calculations. + */ + u8_t seg_count; + } 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_NODE, /* Device is a node */ + BLE_MESH_PROVISIONER, /* Device is a Provisioner */ + BLE_MESH_VALID, /* We have been provisioned */ + BLE_MESH_VALID_PROV, /* Provisioner has been enabled */ + 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, must reside within first 32 flags */ + 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, + BLE_MESH_VA_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 */ + u16_t msg_cache_idx; /* Index of entry in message cache */ +}; + +/* 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 + +void bt_mesh_msg_cache_clear(u16_t unicast_addr, u8_t elem_num); + +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); + +bool bt_mesh_primary_subnet_exist(void); + +u32_t bt_mesh_next_seq(void); + +void bt_mesh_net_start(void); + +void bt_mesh_net_init(void); +void bt_mesh_net_deinit(bool erase); + +void bt_mesh_net_header_parse(struct net_buf_simple *buf, + struct bt_mesh_net_rx *rx); + +/* 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); + +static inline void send_cb_finalize(const struct bt_mesh_send_cb *cb, + void *cb_data) +{ + if (!cb) { + return; + } + + if (cb->start) { + cb->start(0, 0, cb_data); + } + + if (cb->end) { + cb->end(0, cb_data); + } +} + +#endif /* _NET_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/nimble_host/mesh_bearer_adapt.c b/components/bt/esp_ble_mesh/mesh_core/nimble_host/mesh_bearer_adapt.c new file mode 100644 index 0000000000..e997be4db1 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/nimble_host/mesh_bearer_adapt.c @@ -0,0 +1,1824 @@ +/* + * 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 "btc/btc_task.h" +#include "osi/alarm.h" + +#include "mbedtls/aes.h" +#include "mbedtls/ecp.h" + +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "host/ble_att.h" +#include "host/ble_gatt.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" + +#include "mesh_hci.h" +#include "mesh_common.h" +#include "mesh_aes_encrypt.h" +#include "provisioner_prov.h" + +/** @def BT_UUID_MESH_PROV + * @brief Mesh Provisioning Service + */ +#define BT_UUID_MESH_PROV_VAL 0x1827 +/** @def BT_UUID_MESH_PROXY + * @brief Mesh Proxy Service + */ +#define BT_UUID_MESH_PROXY_VAL 0x1828 +/** @def BT_UUID_GATT_CCC + * @brief GATT Client Characteristic Configuration + */ +#define BT_UUID_GATT_CCC_VAL 0x2902 +/** @def BT_UUID_MESH_PROV_DATA_IN + * @brief Mesh Provisioning Data In + */ +#define BT_UUID_MESH_PROV_DATA_IN_VAL 0x2adb +/** @def BT_UUID_MESH_PROV_DATA_OUT + * @brief Mesh Provisioning Data Out + */ +#define BT_UUID_MESH_PROV_DATA_OUT_VAL 0x2adc +/** @def BT_UUID_MESH_PROXY_DATA_IN + * @brief Mesh Proxy Data In + */ +#define BT_UUID_MESH_PROXY_DATA_IN_VAL 0x2add +/** @def BT_UUID_MESH_PROXY_DATA_OUT + * @brief Mesh Proxy Data Out + */ +#define BT_UUID_MESH_PROXY_DATA_OUT_VAL 0x2ade + +#define BLE_MESH_GATT_GET_CONN_ID(conn_id) ((u16_t)(conn_id)) +#define BLE_MESH_GATT_CREATE_CONN_ID(conn_id) ((u16_t)(conn_id)) + +static uint16_t proxy_svc_start_handle, prov_svc_start_handle; +struct bt_mesh_dev bt_mesh_dev; + +/* P-256 Variables */ +static u8_t bt_mesh_public_key[64]; +static u8_t bt_mesh_private_key[32] = { + 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; + +#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 u8_t bt_mesh_gatts_addr[6]; + +#endif /* defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE */ + +int bt_mesh_host_init(void) +{ + int rc; + rc = btc_init(); + if (rc != 0) { + return -1; + } + + rc = osi_alarm_create_mux(); + if (rc != 0) { + return -1; + } + + osi_alarm_init(); + return 0; +} + +uint8_t ble_hs_hci_get_hci_version(void); + +void bt_mesh_hci_init(void) +{ + /** + * 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 = ble_hs_hci_get_hci_version(); +#endif + return; +} + +static struct ble_gap_disc_params scan_param; +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT +static struct gattc_prov_info { + /* Service to be found depends on the type of adv pkt received */ + struct bt_mesh_conn conn; + bt_mesh_addr_t addr; + 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 int ble_on_subscribe(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + struct bt_mesh_conn *conn = NULL; + uint8_t value[2] = {0x01, 0x00}; + int i = (int)arg, j, len; + MODLOG_DFLT(INFO, "Subscribe complete; status=%d conn_handle=%d " + "attr_handle=%d\n", + error->status, conn_handle, attr->handle); + + for (j = i + 1; j < ARRAY_SIZE(bt_mesh_gattc_info); j++) { + if ((bt_mesh_gattc_info[j].conn.handle == conn_handle) && bt_mesh_gattc_info[j].ccc_handle) { + break; + } + } + if (j == ARRAY_SIZE(bt_mesh_gattc_info)) { + + conn = &bt_mesh_gattc_info[i].conn; + + if (bt_mesh_gattc_info[i].ccc_handle != attr->handle) { + BT_WARN("%s, gattc ccc_handle is not matched", __func__); + bt_mesh_gattc_disconnect(conn); + return 0; + } + + 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].addr, &bt_mesh_gattc_info[i].conn); + if (len < 0) { + BT_ERR("%s, prov_write_descr failed", __func__); + bt_mesh_gattc_disconnect(conn); + return 0; + } + 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].addr, &bt_mesh_gattc_info[i].conn); + if (len < 0) { + BT_ERR("%s, proxy_write_descr failed", __func__); + bt_mesh_gattc_disconnect(conn); + return 0; + } + bt_mesh_gattc_info[i].wr_desc_done = true; + } + } + + + return 0; + } + + ble_gattc_write_flat(conn_handle, bt_mesh_gattc_info[i].ccc_handle, value, sizeof(value), ble_on_subscribe, (void *)j); + return 0; +} + +static int dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc, + void *arg) +{ + int rc = 0, j, i = (int)arg; /* char index */ + uint8_t value[2] = {0x01, 0x00}; + + switch (error->status) { + case 0: + bt_mesh_gattc_info[i].ccc_handle = dsc->handle; + break; + + case BLE_HS_EDONE: + /* All descriptors in this characteristic discovered; start discovering + * descriptors in the next characteristic. + */ + for (j = i + 1; j < ARRAY_SIZE(bt_mesh_gattc_info); j++) { + if ((bt_mesh_gattc_info[j].conn.handle == conn_handle) && bt_mesh_gattc_info[j].data_out_handle) { + break; + } + } + if (j == ARRAY_SIZE(bt_mesh_gattc_info)) { + /* Register Notification for Mesh Provisioning/Proxy Data Out Characteristic */ + for (j = 0; j < ARRAY_SIZE(bt_mesh_gattc_info); j++) { + if ((bt_mesh_gattc_info[j].conn.handle == conn_handle) && bt_mesh_gattc_info[j].ccc_handle) { + break; + } + } + if (j == ARRAY_SIZE(bt_mesh_gattc_info)) { + return 0; + } + ble_gattc_write_flat(conn_handle, bt_mesh_gattc_info[i].ccc_handle, value, sizeof(value), ble_on_subscribe, (void *)j); + } else { + ble_gattc_disc_all_dscs(conn_handle, bt_mesh_gattc_info[j].data_out_handle, 0xffff, dsc_disced, (void *)j); + } + rc = 0; + break; + + default: + /* Error; abort discovery. */ + rc = error->status; + break; + } + + return rc; +} + + +static int chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_chr *chr, void *arg) +{ + int rc = 0, j; + uint16_t uuid16 = 0; + int i = (int)arg; /* service index */ + struct bt_mesh_conn *conn = &bt_mesh_gattc_info[i].conn; + const ble_uuid_any_t *uuid = &chr->uuid; + if (chr) { + uuid16 = (uint16_t) BLE_UUID16(uuid)->value; + } + switch (error->status) { + case 0: + /* Get Mesh Provisioning/Proxy Data In/Out Characteristic */ + if ((uuid16 == BLE_MESH_UUID_MESH_PROV_DATA_IN_VAL) || (uuid16 == BLE_MESH_UUID_MESH_PROXY_DATA_IN_VAL)) { + if (!(chr->properties & BLE_MESH_GATT_CHRC_WRITE_WITHOUT_RESP)) { + bt_mesh_gattc_disconnect(conn); + BT_ERR("Write without response is not set for Data In characteristic"); + return 0; + } + bt_mesh_gattc_info[i].data_in_handle = chr->val_handle; + } else if ((uuid16 == BLE_MESH_UUID_MESH_PROV_DATA_OUT_VAL) || (uuid16 == BLE_MESH_UUID_MESH_PROXY_DATA_OUT_VAL)) { + if (!(chr->properties & BLE_MESH_GATT_CHRC_NOTIFY)) { + bt_mesh_gattc_disconnect(conn); + BT_ERR("Notify is not set for Data Out characteristic"); + return 0; + } + bt_mesh_gattc_info[i].data_out_handle = chr->val_handle; + } + break; + case BLE_HS_EDONE: + /* All characteristics in this service discovered; start discovering + * characteristics in the next service. + */ + for (j = i + 1; j < ARRAY_SIZE(bt_mesh_gattc_info); j++) { + if ((bt_mesh_gattc_info[j].conn.handle == conn_handle) && (bt_mesh_gattc_info[j].start_handle > bt_mesh_gattc_info[j].end_handle)) { + break; + } + } + if (j == ARRAY_SIZE(bt_mesh_gattc_info)) { + for (j = 0; j < ARRAY_SIZE(bt_mesh_gattc_info); j++) { + if ((bt_mesh_gattc_info[j].conn.handle == conn_handle) && bt_mesh_gattc_info[j].data_out_handle) { + break; + } + } + ble_gattc_disc_all_dscs(conn_handle, bt_mesh_gattc_info[j].data_out_handle, 0xffff, dsc_disced, (void *)j); + } else { + ble_gattc_disc_all_chrs(conn_handle, bt_mesh_gattc_info[j].start_handle, bt_mesh_gattc_info[j].end_handle, + chr_disced, (void *)j); + } + break; + + default: + rc = error->status; + break; + } + + return rc; +} + + +static int svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_svc *service, void *arg) +{ + struct bt_mesh_conn *conn = NULL; + int rc = 0, i; + const ble_uuid_any_t *uuid; + uint8_t uuid_length; + switch (error->status) { + case 0: + if (!service) { + return 0; + } + uuid = &service->uuid; + uuid_length = (uint8_t) (uuid->u.type == BLE_UUID_TYPE_16 ? 2 : 16); + if (uuid_length != 2) { + return 0; + } + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].service_uuid == (uint16_t)BLE_UUID16(uuid)->value) { + bt_mesh_gattc_info[i].start_handle = service->start_handle; + bt_mesh_gattc_info[i].end_handle = service->end_handle; + break; + } + } + + break; + case BLE_HS_EDONE: + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].conn.handle == conn_handle) { + break; + } + } + + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + BT_ERR("%s, Conn handle is not found", __func__); + return 0; + } + 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 0; + } + + /* Get the characteristic num within Mesh Provisioning/Proxy Service */ + ble_gattc_disc_all_chrs(conn_handle, bt_mesh_gattc_info[i].start_handle, bt_mesh_gattc_info[i].end_handle, + chr_disced, (void *)i); + break; + + default: + rc = error->status; + break; + } + + return rc; +} + + +#endif /* (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ + +static int disc_cb(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_disc_desc *desc; + +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT + int rc, i; + uint8_t notif_data[100]; + uint16_t notif_len; + ssize_t len; + struct ble_gap_conn_desc conn_desc; + struct bt_mesh_conn *conn = NULL; +#endif + + switch (event->type) { + case BLE_GAP_EVENT_DISC: + desc = &event->disc; + + struct net_buf_simple *buf = bt_mesh_alloc_buf(desc->length_data); + if (!buf) { + BT_ERR("%s, Failed to allocate memory", __func__); + return 0; + } + net_buf_simple_add_mem(buf, desc->data, desc->length_data); + + if (bt_mesh_scan_dev_found_cb) { + bt_mesh_scan_dev_found_cb((bt_mesh_addr_t *)&desc->addr, desc->rssi, desc->event_type, buf); + } + bt_mesh_free(buf); + break; +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT + case BLE_GAP_EVENT_CONNECT: + if (event->connect.status == 0) { + /* Connection successfully established. */ + MODLOG_DFLT(INFO, "Connection established "); + + rc = ble_gap_conn_find(event->connect.conn_handle, &conn_desc); + assert(rc == 0); + + 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.val, conn_desc.peer_id_addr.val, BLE_MESH_ADDR_LEN)) { + bt_mesh_gattc_info[i].conn.handle = event->connect.conn_handle; + (bt_mesh_gattc_conn_cb->connected)(&bt_mesh_gattc_info[i].addr, &bt_mesh_gattc_info[i].conn, i); + break; + } + } + } + } +#if BLE_MESH_DEV + if (!bt_mesh_atomic_test_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCANNING)) { + rc = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, &scan_param, disc_cb, NULL); + if (rc != 0) { + BT_ERR("%s, Invalid status %d", __func__, rc); + break; + } + bt_mesh_atomic_set_bit(bt_mesh_dev.flags, BLE_MESH_DEV_SCANNING); + } +#else + rc = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, &scan_param, disc_cb, NULL); + if (rc != 0) { + BT_ERR("%s, Invalid status %d", __func__, rc); + break; + } +#endif /* BLE_MESH_DEV */ + break; + case BLE_GAP_EVENT_DISCONNECT: + 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++) { + memcpy(&conn_desc, &event->disconnect.conn, sizeof(conn_desc)); + if (!memcmp(bt_mesh_gattc_info[i].addr.val, conn_desc.peer_ota_addr.val, BLE_MESH_ADDR_LEN)) { + if (bt_mesh_gattc_info[i].conn.handle == event->disconnect.conn.conn_handle) { + (bt_mesh_gattc_conn_cb->disconnected)(&bt_mesh_gattc_info[i].addr, &bt_mesh_gattc_info[i].conn, event->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. + */ +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROV_VAL) { + bt_mesh_provisioner_clear_link_info(bt_mesh_gattc_info[i].addr.val); + } +#endif + } + } 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. + */ +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROV_VAL) { + bt_mesh_provisioner_clear_link_info(bt_mesh_gattc_info[i].addr.val); + } +#endif + } +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + if (bt_mesh_gattc_info[i].service_uuid == BLE_MESH_UUID_MESH_PROV_VAL) { + /* Decrease prov pbg_count */ + bt_mesh_provisioner_pbg_count_dec(); + } +#endif + /* 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 = BLE_ATT_MTU_DFLT; + bt_mesh_gattc_info[i].wr_desc_done = false; + break; + } + } + } + break; + case BLE_GAP_EVENT_MTU: + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].conn.handle == event->mtu.conn_handle) { + bt_mesh_gattc_info[i].mtu = event->mtu.value; + 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) + */ + ble_uuid_any_t bt_uuid; + if (i != ARRAY_SIZE(bt_mesh_gattc_info)) { + //service_uuid.len = sizeof(bt_mesh_gattc_info[i].service_uuid); + if (sizeof(bt_mesh_gattc_info[i].service_uuid) == 0x02) { + bt_uuid.u16.u.type = BLE_UUID_TYPE_16; + bt_uuid.u16.value = bt_mesh_gattc_info[i].service_uuid; + + } else if (sizeof(bt_mesh_gattc_info[i].service_uuid) == 0x10) { + bt_uuid.u128.u.type = BLE_UUID_TYPE_128; + memcpy(bt_uuid.u128.value, &bt_mesh_gattc_info[i].service_uuid, 16); + } + /* Search Mesh Provisioning Service or Mesh Proxy Service */ + ble_gattc_disc_all_svcs(bt_mesh_gattc_info[i].conn.handle, svc_disced, NULL); + } + break; + case BLE_GAP_EVENT_NOTIFY_RX: + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].conn.handle == event->notify_rx.conn_handle) { + break; + } + } + + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + BT_ERR("%s, Conn handle is not found", __func__); + return 0; + } + + conn = &bt_mesh_gattc_info[i].conn; + ble_gap_conn_find(event->notify_rx.conn_handle, &conn_desc); + + if (bt_mesh_gattc_info[i].data_out_handle != event->notify_rx.attr_handle) { + /* Data isn't populated yet */ + return 0; + } + + if (memcmp(bt_mesh_gattc_info[i].addr.val, conn_desc.peer_id_addr.val, BLE_MESH_ADDR_LEN) || + (bt_mesh_gattc_info[i].data_out_handle != event->notify_rx.attr_handle) || + (event->notify_rx.indication != 0)) { + BT_ERR("%s, Notification error", __func__); + bt_mesh_gattc_disconnect(conn); + return 0; + } + + notif_len = OS_MBUF_PKTLEN(event->notify_rx.om); + rc = os_mbuf_copydata(event->notify_rx.om, 0, notif_len, notif_data); + + 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, + notif_data, notif_len); + if (len < 0) { + BT_ERR("%s, prov_notify failed", __func__); + bt_mesh_gattc_disconnect(conn); + return 0; + } + } + } 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, + notif_data, notif_len); + if (len < 0) { + BT_ERR("%s, proxy_notify failed", __func__); + bt_mesh_gattc_disconnect(conn); + return 0; + } + } + } + break; +#endif + default: + break; + } + + return 0; +} + +static int start_le_scan(u8_t scan_type, u16_t interval, u16_t window, u8_t filter_dup) +{ + + scan_param.filter_duplicates = filter_dup; + scan_param.itvl = interval; + scan_param.window = window; + + if (scan_type == BLE_MESH_SCAN_PASSIVE) { + scan_param.passive = 1; + } else { + scan_param.passive = 0; + } + ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, &scan_param, disc_cb, 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 int set_ad(const struct bt_mesh_adv_data *ad, size_t ad_len, u8_t *buf, u8_t *buf_len) +{ + int i; + + for (i = 0; i < ad_len; i++) { + buf[(*buf_len)++] = ad[i].data_len + 1; + buf[(*buf_len)++] = ad[i].type; + + memcpy(&buf[*buf_len], ad[i].data, + ad[i].data_len); + *buf_len += ad[i].data_len; + } + + return 0; +} + +#if defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE +static struct bt_mesh_gatt_attr *bt_mesh_gatts_find_attr_by_handle(u16_t handle); + +static int gap_event_cb(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + MODLOG_DFLT(INFO, "connection %s; status=%d ", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + if (event->connect.status == 0) { + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + } + MODLOG_DFLT(INFO, "\n"); +#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(event->connect.conn_handle); + if (index < BLE_MESH_MAX_CONN) { + bt_mesh_gatts_conn[index].handle = BLE_MESH_GATT_GET_CONN_ID(event->connect.conn_handle); + (bt_mesh_gatts_conn_cb->connected)(&bt_mesh_gatts_conn[index], 0); + } + memcpy(bt_mesh_gatts_addr, desc.peer_id_addr.val, 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. + */ + ble_svc_gatt_changed(prov_svc_start_handle, 0xffff); + + } + + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); + MODLOG_DFLT(INFO, "\n"); +#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(event->disconnect.conn.conn_handle); + if (index < BLE_MESH_MAX_CONN) { + bt_mesh_gatts_conn[index].handle = BLE_MESH_GATT_GET_CONN_ID(event->disconnect.conn.conn_handle); + (bt_mesh_gatts_conn_cb->disconnected)(&bt_mesh_gatts_conn[index], event->disconnect.reason); + } + memset(bt_mesh_gatts_addr, 0x0, BLE_MESH_ADDR_LEN); + } + + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + MODLOG_DFLT(INFO, "connection updated; status=%d ", + event->conn_update.status); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + MODLOG_DFLT(INFO, "\n"); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + MODLOG_DFLT(INFO, "advertise complete; reason=%d", + event->adv_complete.reason); + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + MODLOG_DFLT(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + MODLOG_DFLT(INFO, "\n"); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + MODLOG_DFLT(INFO, "subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", + event->subscribe.conn_handle, + event->subscribe.attr_handle, + event->subscribe.reason, + event->subscribe.prev_notify, + event->subscribe.cur_notify, + event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + struct bt_mesh_gatt_attr *attr = bt_mesh_gatts_find_attr_by_handle(event->subscribe.attr_handle + 1); + u8_t index = BLE_MESH_GATT_GET_CONN_ID(event->subscribe.conn_handle); + u16_t len = 0; + uint16_t ccc_val = 0; + + if (event->subscribe.prev_notify != event->subscribe.cur_notify) { + ccc_val = event->subscribe.cur_notify; + } else if (event->subscribe.prev_indicate != event->subscribe.cur_indicate) { + if (event->subscribe.cur_indicate) { + ccc_val = 2; + } else { + ccc_val = 0; + } + } + + if (attr != NULL && attr->write != NULL) { + if ((len = attr->write(&bt_mesh_gatts_conn[index], attr, + &ccc_val, + sizeof(ccc_val), + 0 /* offset */, 0)) > 0) { + } + } + + return 0; + + case BLE_GAP_EVENT_MTU: + MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + assert(rc == 0); + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + MODLOG_DFLT(INFO, "PASSKEY_ACTION_EVENT started \n"); + return 0; + } + + return 0; +} +#else + +static int gap_event_cb(struct ble_gap_event *event, void *arg) +{ + return 0; +} +#endif + +/* 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) +{ +#if BLE_MESH_DEV + if (bt_mesh_atomic_test_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ADVERTISING)) { + return -EALREADY; + } +#endif + uint8_t buf[BLE_HS_ADV_MAX_SZ]; + uint8_t buf_len = 0; + int err; + struct ble_gap_adv_params adv_params; + + err = set_ad(ad, ad_len, buf, &buf_len); + if (err) { + BT_ERR("set_ad failed: err %d", err); + return err; + } + + err = ble_gap_adv_set_data(buf, buf_len); + if (err != 0) { + BT_ERR("Advertising set failed: err %d", err); + return err; + } + + if (sd && (param->options & BLE_MESH_ADV_OPT_CONNECTABLE)) { + buf_len = 0; + + err = set_ad(sd, sd_len, buf, &buf_len); + if (err) { + BT_ERR("set_ad failed: err %d", err); + return err; + } + + err = ble_gap_adv_rsp_set_data(buf, buf_len); + if (err != 0) { + BT_ERR("Scan rsp failed: err %d", err); + return err; + } + } + + memset(&adv_params, 0, sizeof adv_params); + adv_params.itvl_min = param->interval_min; + adv_params.itvl_max = param->interval_max; + + if (param->options & BLE_MESH_ADV_OPT_CONNECTABLE) { + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + } else if (sd != NULL) { + adv_params.conn_mode = BLE_GAP_CONN_MODE_NON; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + } else { + adv_params.conn_mode = BLE_GAP_CONN_MODE_NON; + adv_params.disc_mode = BLE_GAP_DISC_MODE_NON; + } + + err = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params, + gap_event_cb, NULL); + if (err) { + BT_ERR("Advertising start failed: err %d", err); + return err; + } + +#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_gap_adv_stop(); + +#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 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_gap_disc_cancel(); + } +#else + ble_gap_disc_cancel(); +#endif + + bt_mesh_scan_dev_found_cb = NULL; + return 0; +} +#if defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE + +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 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; +} + +int bt_mesh_gatts_service_register(struct bt_mesh_gatt_service *svc) +{ + uint16_t offset = 0; + int i; + if (BLE_MESH_UUID_16(svc->attrs[0].user_data)->val == BT_UUID_MESH_PROXY_VAL) { + offset = proxy_svc_start_handle; + } else if (BLE_MESH_UUID_16(svc->attrs[0].user_data)->val == BT_UUID_MESH_PROV_VAL) { + offset = prov_svc_start_handle; + } + + for (i = 0; i < svc->attr_count; i++) { + svc->attrs[i].handle = offset + i; + } + gatts_register(svc); + return 0; +} + +int bt_mesh_gatts_disconnect(struct bt_mesh_conn *conn, u8_t reason) +{ + u16_t conn_id = BLE_MESH_GATT_CREATE_CONN_ID(conn->handle); + ble_gap_terminate(conn_id, reason); + return 0; +} + +int bt_mesh_gatts_service_unregister(struct bt_mesh_gatt_service *svc) +{ + assert(svc != NULL); + BT_ERR("%s, Unsupported for NimBLE host", __func__); + 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) +{ + struct os_mbuf *om; + u16_t conn_id = BLE_MESH_GATT_CREATE_CONN_ID(conn->handle); + + om = ble_hs_mbuf_from_flat(data, len); + assert(om); + ble_gattc_notify_custom(conn_id, attr->handle, om); + + return 0; +} + +u16_t bt_mesh_gatt_get_mtu(struct bt_mesh_conn *conn) +{ + return ble_att_preferred_mtu(); +} + +/* APIs added by Espressif */ +int bt_mesh_gatts_service_stop(struct bt_mesh_gatt_service *svc) +{ + int rc; + uint16_t handle; + if (!svc) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + const ble_uuid_t *uuid; + if (BLE_MESH_UUID_16(svc->attrs[0].user_data)->val == BT_UUID_MESH_PROXY_VAL) { + uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL); + } else { + uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL); + } + + rc = ble_gatts_find_svc(uuid, &handle); + assert(rc == 0); + ble_gatts_svc_set_visibility(handle, 0); + + /* FIXME: figure out end handle */ + ble_svc_gatt_changed(handle, 0xffff); + + return 0; +} + +int bt_mesh_gatts_service_start(struct bt_mesh_gatt_service *svc) +{ + int rc; + uint16_t handle; + const ble_uuid_t *uuid; + if (BLE_MESH_UUID_16(svc->attrs[0].user_data)->val == BT_UUID_MESH_PROXY_VAL) { + uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL); + } else { + uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL); + } + + rc = ble_gatts_find_svc(uuid, &handle); + assert(rc == 0); + ble_gatts_svc_set_visibility(handle, 1); + + /* FIXME: figure out end handle */ + ble_svc_gatt_changed(handle, 0xffff); + + return 0; +} + +int bt_mesh_gatts_set_local_device_name(const char *name) +{ + return ble_svc_gap_device_name_set(name); +} +#endif /* defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE */ + +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT +void bt_mesh_gattc_conn_cb_register(struct bt_mesh_prov_conn_cb *cb) +{ + bt_mesh_gattc_conn_cb = cb; +} + +u8_t bt_mesh_gattc_get_free_conn_count(void) +{ + u8_t count = 0; + u8_t i; + + for (i = 0U; 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) { + ++count; + } + } + + return count; +} + +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, rc; + + 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.val, 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.val, 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)) { + rc = ble_gap_disc_cancel(); + if (rc != 0) { + return -1; + } + } +#else + rc = ble_gap_disc_cancel(); + if (rc != 0) { + return -1; + } +#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 + */ + struct ble_gap_conn_params conn_params = {0}; + conn_params.itvl_min = 0xC8; /* (250 * 1000) / 1250 = 200 = 0xC8 */ + conn_params.itvl_max = 0xC8; /* (250 * 1000) / 1250 = 200 = 0xC8 */ + conn_params.latency = 0; + conn_params.supervision_timeout = 0xC80; + conn_params.scan_itvl = 0x0020; //0x0010 + conn_params.scan_window = 0x0020; //0x0010 + conn_params.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN; + conn_params.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN; + + + ble_addr_t peer_addr; + memcpy(peer_addr.val, addr->val, 6); + peer_addr.type = addr->type; + + rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &peer_addr, BLE_HS_FOREVER, &conn_params, + disc_cb, NULL); + + return i; +} + +static int mtu_cb(uint16_t conn_handle, + const struct ble_gatt_error *error, + uint16_t mtu, void *arg) +{ + int i; + if (error->status == 0) { + + for (i = 0; i < ARRAY_SIZE(bt_mesh_gattc_info); i++) { + if (bt_mesh_gattc_info[i].conn.handle == conn_handle) { + bt_mesh_gattc_info[i].mtu = mtu; + break; + } + } + } + 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 + */ + + ble_gattc_exchange_mtu(bt_mesh_gattc_info[index].conn.handle, mtu_cb, NULL); +} + +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_info[i].conn.handle); + + struct os_mbuf *om; + int rc; + + om = ble_hs_mbuf_from_flat(data, len); + if (om == NULL) { + return -1; + } + + rc = ble_gattc_write_no_rsp(conn_id, bt_mesh_gattc_info[i].data_in_handle, om); + if (rc != 0) { + return -1; + } + + 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. + */ + 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; + } + ble_gap_terminate(bt_mesh_gattc_info[i].conn.handle, BLE_ERR_REM_USER_CONN_TERM); +} + +/** 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 + */ +#endif /* (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ + +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)); +} + +#if defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE +static int proxy_char_access_cb(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR || ctxt->op == BLE_GATT_ACCESS_OP_WRITE_DSC) { + struct bt_mesh_gatt_attr *attr = bt_mesh_gatts_find_attr_by_handle(attr_handle); + u8_t index = BLE_MESH_GATT_GET_CONN_ID(conn_handle); + u16_t len = 0; + + BT_DBG("%s, write: handle = %d, len = %d, data = %s", __func__, attr_handle, + ctxt->om->om_len, + bt_hex(ctxt->om->om_data, ctxt->om->om_len)); + + if (attr != NULL && attr->write != NULL) { + if ((len = attr->write(&bt_mesh_gatts_conn[index], attr, + ctxt->om->om_data, + ctxt->om->om_len, + 0 /* offset */, 0)) > 0) { + } + } + } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR || ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) { + BT_ERR("%s, Unhandled read request for chr and dsc: opcode - %d", __func__, ctxt->op); + } + return 0; +} + +static int dummy_access_cb(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + /* + * We should never never enter this callback - it's attached to notify-only + * characteristic which are notified directly from mbuf. And we can't pass + * NULL as access_cb because gatts will assert on init... + */ + assert(0); + return 0; +} + +static const struct ble_gatt_svc_def svc_defs [] = { +#ifdef CONFIG_BLE_MESH_GATT_PROXY_SERVER + { + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL), + .includes = NULL, + .characteristics = (struct ble_gatt_chr_def[]) + { { + .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_IN_VAL), + .access_cb = proxy_char_access_cb, + .flags = BLE_GATT_CHR_F_WRITE_NO_RSP, + }, { + .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_OUT_VAL), + .access_cb = dummy_access_cb, + .flags = BLE_GATT_CHR_F_NOTIFY, + }, { + 0, /* No more characteristics in this service. */ + } + }, + }, +#endif +#ifdef CONFIG_BLE_MESH_PB_GATT + { + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL), + .includes = NULL, + .characteristics = (struct ble_gatt_chr_def[]) + { { + .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_IN_VAL), + .access_cb = proxy_char_access_cb, + .flags = BLE_GATT_CHR_F_WRITE_NO_RSP, + }, { + .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_OUT_VAL), + .access_cb = dummy_access_cb, + .flags = BLE_GATT_CHR_F_NOTIFY, + }, { + 0, /* No more characteristics in this service. */ + } + }, + }, +#endif + { + 0, /* No more services. */ + }, +}; +#endif + +void gatt_register_cb(struct ble_gatt_register_ctxt *ctxt, + void *arg ) +{ + if (ctxt->op == BLE_GATT_REGISTER_OP_SVC) { + if (ble_uuid_cmp(ctxt->svc.svc_def->uuid, BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL)) == 0) { + proxy_svc_start_handle = ctxt->svc.handle; + } else if (ble_uuid_cmp(ctxt->svc.svc_def->uuid, BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL)) == 0) { + prov_svc_start_handle = ctxt->svc.handle; + } + } +} + +void bt_mesh_gatt_init(void) +{ + ble_att_set_preferred_mtu(BLE_ATT_MTU_DFLT); + + ble_hs_cfg.gatts_register_cb = gatt_register_cb; + +#if defined(CONFIG_BLE_MESH_NODE) && CONFIG_BLE_MESH_NODE + int rc; + ble_svc_gap_init(); + ble_svc_gatt_init(); + + rc = ble_gatts_count_cfg(svc_defs); + assert(rc == 0); + + rc = ble_gatts_add_svcs(svc_defs); + assert(rc == 0); + + ble_gatts_start(); + + ble_gatts_svc_set_visibility(prov_svc_start_handle, 1); + ble_gatts_svc_set_visibility(proxy_svc_start_handle, 0); +#endif + +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT + 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 = BLE_ATT_MTU_DFLT; + bt_mesh_gattc_info[i].wr_desc_done = false; + } +#endif +} + +void ble_sm_alg_ecc_init(void); + +void bt_mesh_adapt_init(void) +{ + BT_DBG("%s", __func__); + /* initialization of P-256 parameters */ + ble_sm_alg_ecc_init(); +} + +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); +} + +int ble_sm_alg_gen_key_pair(uint8_t *pub, uint8_t *priv); + +const u8_t *bt_mesh_pub_key_get(void) +{ + uint8_t pri_key[32] = {0}; + +#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 + + int rc = ble_sm_alg_gen_key_pair(bt_mesh_public_key, pri_key); + if (rc != 0) { + BT_ERR("%s, Failed to generate the key pair", __func__); + return NULL; + } + memcpy(bt_mesh_private_key, pri_key, 32); + + 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 mbedtls_ecp_point pt = {0}; + mbedtls_ecp_group grp = {0}; + bool rc = false; + + uint8_t pub[65] = {0}; + /* Hardcoded first byte of pub key for MBEDTLS_ECP_PF_UNCOMPRESSED */ + pub[0] = 0x04; + memcpy(&pub[1], key, 64); + + /* Initialize the required structures here */ + mbedtls_ecp_point_init(&pt); + mbedtls_ecp_group_init(&grp); + + /* Below 3 steps are to validate public key on curve secp256r1 */ + if (mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) != 0) { + goto exit; + } + + if (mbedtls_ecp_point_read_binary(&grp, &pt, pub, 65) != 0) { + goto exit; + } + + if (mbedtls_ecp_check_pubkey(&grp, &pt) != 0) { + goto exit; + } + + rc = true; + +exit: + mbedtls_ecp_point_free(&pt); + mbedtls_ecp_group_free(&grp); + return rc; + +} + +int ble_sm_alg_gen_dhkey(uint8_t *peer_pub_key_x, uint8_t *peer_pub_key_y, + uint8_t *our_priv_key, uint8_t *out_dhkey); + +int bt_mesh_dh_key_gen(const u8_t remote_pk[64], bt_mesh_dh_key_cb_t cb, const u8_t idx) +{ + uint8_t dhkey[32]; + + BT_DBG("private key = %s", bt_hex(bt_mesh_private_key, 32)); + + ble_sm_alg_gen_dhkey((uint8_t *)&remote_pk[0], (uint8_t *)&remote_pk[32], bt_mesh_private_key, dhkey); + + 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) +{ + BT_ERR("%s, Unsupported for NimBLE host", __func__); + return 0; +} +#endif diff --git a/components/bt/esp_ble_mesh/mesh_core/prov.c b/components/bt/esp_ble_mesh/mesh_core/prov.c new file mode 100644 index 0000000000..6f95087c03 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/prov.c @@ -0,0 +1,1814 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_PROV) + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "access.h" +#include "foundation.h" +#include "mesh_common.h" +#include "mesh_proxy.h" +#include "proxy_server.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 PUB_KEY_NO_OOB 0x00 +#define PUB_KEY_OOB 0x01 + +#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 */ + LINK_INVALID, /* Error occurred during provisioning */ + + 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 + + struct k_delayed_work prot_timer; +}; + +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 PROTOCOL_TIMEOUT K_SECONDS(6) +#else +#define RETRANSMIT_TIMEOUT K_MSEC(500) +#define TRANSACTION_TIMEOUT K_SECONDS(30) +#define PROTOCOL_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; + +#if defined(CONFIG_BLE_MESH_PB_ADV) +static bt_mesh_mutex_t pb_buf_lock; + +static void bt_mesh_pb_buf_mutex_new(void) +{ + if (!pb_buf_lock.mutex) { + bt_mesh_mutex_create(&pb_buf_lock); + } +} + +static void bt_mesh_pb_buf_mutex_free(void) +{ + bt_mesh_mutex_free(&pb_buf_lock); +} + +static void bt_mesh_pb_buf_lock(void) +{ + bt_mesh_mutex_lock(&pb_buf_lock); +} + +static void bt_mesh_pb_buf_unlock(void) +{ + bt_mesh_mutex_unlock(&pb_buf_lock); +} +#endif /* CONFIG_BLE_MESH_PB_ADV */ + +static void reset_state(void) +{ + k_delayed_work_cancel(&link.prot_timer); + + /* 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 and protocol timer + * delayed work objects. + */ + (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 /* !PB_ADV */ + /* Clear everything except the protocol timer (k_delayed_work) */ + (void)memset(&link, 0, offsetof(struct prov_link, prot_timer)); +#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; + + bt_mesh_pb_buf_lock(); + + 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; + bt_mesh_adv_buf_ref_debug(__func__, buf, 3U, BLE_MESH_BUF_REF_SMALL); + /* Mark as canceled */ + BLE_MESH_ADV(buf)->busy = 0U; + net_buf_unref(buf); + } + + bt_mesh_pb_buf_unlock(); +} + +static void prov_clear_tx(void) +{ + BT_DBG("%s", __func__); + + k_delayed_work_cancel(&link.tx.retransmit); + + free_segments(); +} + +static void reset_adv_link(void) +{ + prov_clear_tx(); + + 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 = NULL; + + 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 = NULL; + struct net_buf *buf = NULL; + + 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 = NULL; + + 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 = NULL, *buf = NULL; + u8_t seg_len = 0U, seg_id = 0U; + u8_t xact_id = 0U; + s32_t timeout = PROTOCOL_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 defined(CONFIG_BLE_MESH_FAST_PROV) + if (link.tx_pdu_type >= PROV_COMPLETE) { + timeout = K_SECONDS(60); + } +#endif + k_delayed_work_submit(&link.prot_timer, 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; + } + + k_delayed_work_submit(&link.prot_timer, PROTOCOL_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); + + if (prov_send(&buf)) { + BT_ERR("Failed to send Provisioning Failed message"); + } + + bt_mesh_atomic_set_bit(link.flags, LINK_INVALID); +} + +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__); + return; + } + + link.expect = PROV_START; +} + +static void prov_capabilities(const u8_t *data) +{ + u16_t algorithms = 0U, output_action = 0U, input_action = 0U; + + 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); + + ((void) algorithms); + ((void) output_action); + ((void) 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 = 0U; + bt_mesh_input_action_t input = 0U; + + 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] = {'\0'}; + u8_t i = 0U; + + 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 = 0U; + + 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_INFO("Algorithm: 0x%02x", data[0]); + BT_INFO("Public Key: 0x%02x", data[1]); + BT_INFO("Auth Method: 0x%02x", data[2]); + BT_INFO("Auth Action: 0x%02x", data[3]); + BT_INFO("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] != prov->oob_pub_key) { + BT_ERR("%s, Invalid public key type: 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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + return; + } + + if (prov_send(&cfm)) { + BT_ERR("%s, Unable to send Provisioning Confirm", __func__); + return; + } + + link.expect = PROV_RANDOM; +} + +static void send_input_complete(void) +{ + PROV_BUF(buf, 1); + + prov_buf_init(&buf, PROV_INPUT_COMPLETE); + if (prov_send(&buf)) { + BT_ERR("Failed to send Provisioning Input Complete"); + } +} + +int bt_mesh_input_number(u32_t num) +{ + BT_INFO("%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_INFO("%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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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 = NULL; + + /* Copy remote key in little-endian for bt_mesh_dh_key_gen(). + * X and Y halves are swapped independently. Use response + * buffer as a temporary storage location. The validating of + * the remote public key is finished when it is received. + */ + 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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + return; + } + + key = bt_mesh_pub_key_get(); + if (!key) { + BT_ERR("%s, No public key available", __func__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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); + + if (prov_send(&buf)) { + BT_ERR("Failed to send Public Key"); + 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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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] = {0}; + + 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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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)); + prov_send_fail_msg(PROV_ERR_CFM_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__); + return; + } + + if (bt_mesh_prov_salt(link.conf_salt, data, link.rand, + link.prov_salt)) { + BT_ERR("%s, Failed to generate provisioning salt", __func__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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] = {0}; + u8_t nonce[13] = {0}; + u8_t dev_key[16] = {0}; + u8_t pdu[25] = {0}; + u8_t flags = 0U; + u32_t iv_index = 0U; + u16_t addr = 0U; + u16_t net_idx = 0U; + int err = 0; + bool identity_enable = false; + + 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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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__); + prov_send_fail_msg(PROV_ERR_DECRYPT); + return; + } + + err = bt_mesh_dev_key(link.dhkey, link.prov_salt, dev_key); + if (err) { + BT_ERR("%s, Unable to generate device key", __func__); + prov_send_fail_msg(PROV_ERR_UNEXP_ERR); + 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); + if (prov_send(&msg)) { + BT_ERR("Failed to send Provisioning Complete"); + return; + } + + /* 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_SERVER)) { + 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_SERVER) && 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 }, +}; + +#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_adv_link(); + return; + } + + bt_mesh_pb_buf_lock(); + + 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); + } + + } + + bt_mesh_pb_buf_unlock(); +} + +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_INFO("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_adv_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 (bt_mesh_atomic_test_bit(link.flags, LINK_INVALID)) { + BT_WARN("Unexpected msg 0x%02x on invalidated link", type); + prov_send_fail_msg(PROV_ERR_UNEXP_PDU); + return; + } + + 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); + prov_send_fail_msg(PROV_ERR_NVAL_PDU); + 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); + prov_send_fail_msg(PROV_ERR_NVAL_FMT); + return; + } + + /* Changed by Espressif, add provisioning timeout timer operations. + * When received a provisioning PDU, restart the 60s timer. + */ + k_delayed_work_submit(&link.prot_timer, PROTOCOL_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_INFO("Resending ack"); + gen_prov_ack_send(rx->xact_id); + return; + } + + 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); + prov_send_fail_msg(PROV_ERR_NVAL_FMT); + return; + } else if (seg == link.rx.last_seg) { + u8_t expect_len = 0U; + + 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); + prov_send_fail_msg(PROV_ERR_NVAL_FMT); + return; + } + } + + if (!(link.rx.seg & BIT(seg))) { + BT_INFO("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_INFO("Got Start while there are unreceived segments"); + return; + } + + if (link.rx.prev_id == rx->xact_id) { + BT_INFO("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__); + prov_send_fail_msg(PROV_ERR_NVAL_FMT); + return; + } + + if (link.rx.buf->len > link.rx.buf->size) { + BT_ERR("%s, Too large provisioning PDU (%u bytes)", + __func__, link.rx.buf->len); + /* Zephyr uses prov_send_fail_msg() here */ + 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__); + prov_send_fail_msg(PROV_ERR_NVAL_FMT); + 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 = {0}; + + 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 = 0U; + + 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. + */ + k_delayed_work_submit(&link.prot_timer, PROTOCOL_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); +} + +static void protocol_timeout(struct k_work *work) +{ + BT_WARN("Protocol timeout"); + +#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) + u8_t reason = CLOSE_REASON_TIMEOUT; + + link.rx.seg = 0U; + bearer_ctl_send(LINK_CLOSE, &reason, sizeof(reason)); + + reset_state(); +#endif +} + +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; + } + + k_delayed_work_init(&link.prot_timer, protocol_timeout); + + prov = prov_info; + +#if defined(CONFIG_BLE_MESH_PB_ADV) + k_delayed_work_init(&link.tx.retransmit, prov_retransmit); +#endif + + reset_state(); + +#if defined(CONFIG_BLE_MESH_PB_ADV) + bt_mesh_pb_buf_mutex_new(); +#endif + + return 0; +} + +int bt_mesh_prov_deinit(void) +{ + if (prov == NULL) { + BT_ERR("%s, No provisioning context provided", __func__); + return -EINVAL; + } + + k_delayed_work_free(&link.prot_timer); + +#if defined(CONFIG_BLE_MESH_PB_ADV) + prov_clear_tx(); + k_delayed_work_free(&link.tx.retransmit); +#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 /* CONFIG_BLE_MESH_USE_DUPLICATE_SCAN */ +#endif /* CONFIG_BLE_MESH_PB_ADV */ + + (void)memset(&link, 0, sizeof(link)); + +#if defined(CONFIG_BLE_MESH_PB_ADV) + bt_mesh_pb_buf_mutex_free(); +#endif + + prov = NULL; + + return 0; +} + +void bt_mesh_prov_complete(u16_t net_idx, const u8_t net_key[16], u16_t addr, u8_t flags, u32_t iv_index) +{ + if (prov->complete) { + prov->complete(net_idx, net_key, 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/esp_ble_mesh/mesh_core/prov.h b/components/bt/esp_ble_mesh/mesh_core/prov.h new file mode 100644 index 0000000000..782d869214 --- /dev/null +++ b/components/bt/esp_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_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); +int bt_mesh_prov_deinit(void); + +void bt_mesh_prov_complete(u16_t net_idx, const u8_t net_key[16], u16_t addr, u8_t flags, u32_t iv_index); +void bt_mesh_prov_reset(void); + +#endif /* _PROV_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/provisioner_main.c b/components/bt/esp_ble_mesh/mesh_core/provisioner_main.c new file mode 100644 index 0000000000..dcf3421f45 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/provisioner_main.c @@ -0,0 +1,1706 @@ +// Copyright 2017-2019 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.h" +#include "crypto.h" +#include "adv.h" +#include "access.h" +#include "settings.h" +#include "friend.h" +#include "mesh_common.h" +#include "proxy_client.h" +#include "provisioner_prov.h" +#include "provisioner_main.h" + +#if CONFIG_BLE_MESH_PROVISIONER + +static struct bt_mesh_node *mesh_nodes[CONFIG_BLE_MESH_MAX_STORED_NODES]; +static bt_mesh_mutex_t provisioner_lock; +static u16_t all_node_count; +static u16_t prov_node_count; + +static int provisioner_remove_node(u16_t index, bool erase); + +static void bt_mesh_provisioner_mutex_new(void) +{ + if (!provisioner_lock.mutex) { + bt_mesh_mutex_create(&provisioner_lock); + } +} + +static void bt_mesh_provisioner_mutex_free(void) +{ + bt_mesh_mutex_free(&provisioner_lock); +} + +static void bt_mesh_provisioner_lock(void) +{ + bt_mesh_mutex_lock(&provisioner_lock); +} + +static void bt_mesh_provisioner_unlock(void) +{ + bt_mesh_mutex_unlock(&provisioner_lock); +} + +int bt_mesh_provisioner_init(void) +{ + bt_mesh_provisioner_mutex_new(); + + return 0; +} + +/** + * When a Provisioner tries to create a network, it will check the + * status of the restored network keys firstly, and try to create + * one if they are not existed. + */ +int bt_mesh_provisioner_net_create(void) +{ + const struct bt_mesh_prov *prov = NULL; + struct bt_mesh_subnet *sub = NULL; + u8_t p_key[16] = {0}; + + BT_DBG("%s", __func__); + + prov = bt_mesh_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(bt_mesh_provisioner_get_primary_elem_addr()); + + if (bt_mesh.p_sub[0]) { + /* Provisioner is already enabled (enable -> disable -> enable), + * or Provisioner is restored from flash. + */ + BT_INFO("Provisioner already created network"); + sub = bt_mesh.p_sub[0]; + goto done; + } + + /* Generate the primary netkey */ + if (bt_mesh_rand(p_key, 16)) { + BT_ERR("%s, Failed to generate Primary NetKey", __func__); + return -EIO; + } + + sub = bt_mesh_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__); + bt_mesh_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__); + bt_mesh_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; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_p_net_idx(); + bt_mesh_store_p_app_idx(); + bt_mesh_store_p_subnet(bt_mesh.p_sub[0]); + } + + 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; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_iv(true); + } + +done: + BT_INFO("net_idx 0x%03x, netkey %s, nid 0x%02x", + sub->net_idx, bt_hex(sub->keys[0].net, 16), sub->keys[0].nid); + + return 0; +} + +int bt_mesh_provisioner_deinit(bool erase) +{ + int i; + + for (i = 0; i < CONFIG_BLE_MESH_PROVISIONER_SUBNET_COUNT; i++) { + if (bt_mesh.p_sub[i]) { + if (erase && IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_p_subnet(bt_mesh.p_sub[i]); + } + bt_mesh_free(bt_mesh.p_sub[i]); + bt_mesh.p_sub[i] = NULL; + } + } + + for (i = 0; i < CONFIG_BLE_MESH_PROVISIONER_APP_KEY_COUNT; i++) { + if (bt_mesh.p_app_keys[i]) { + if (erase && IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_p_app_key(bt_mesh.p_app_keys[i]); + } + bt_mesh_free(bt_mesh.p_app_keys[i]); + bt_mesh.p_app_keys[i] = NULL; + } + } + + bt_mesh.p_net_idx_next = 0U; + bt_mesh.p_app_idx_next = 0U; + if (erase && IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_p_net_idx(); + bt_mesh_clear_p_app_idx(); + } + + for (i = 0; i < CONFIG_BLE_MESH_MAX_STORED_NODES; i++) { + provisioner_remove_node(i, erase); + } + + all_node_count = 0U; + prov_node_count = 0U; + + bt_mesh_provisioner_mutex_free(); + + return 0; +} + +bool bt_mesh_provisioner_check_is_addr_dup(u16_t addr, u8_t elem_num, bool comp_with_own) +{ + const struct bt_mesh_comp *comp = NULL; + struct bt_mesh_node *node = NULL; + u16_t primary_addr = BLE_MESH_ADDR_UNASSIGNED; + u16_t comp_addr = BLE_MESH_ADDR_UNASSIGNED; + int i; + + if (comp_with_own) { + comp = bt_mesh_comp_get(); + if (!comp) { + BT_ERR("NULL composition data"); + return true; + } + + primary_addr = bt_mesh_provisioner_get_primary_elem_addr(); + if (!BLE_MESH_ADDR_IS_UNICAST(primary_addr)) { + BT_ERR("%s, Not a unicast address 0x%04x", __func__, primary_addr); + return true; + } + } + + for (comp_addr = addr; comp_addr < addr + elem_num; comp_addr++) { + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (node && comp_addr >= node->unicast_addr && + comp_addr < node->unicast_addr + node->element_num) { + BT_ERR("Duplicate with node address 0x%04x", comp_addr); + return true; + } + + if (comp_with_own && comp_addr >= primary_addr && + comp_addr < primary_addr + comp->elem_count) { + BT_ERR("Duplicate with Provisioner address 0x%04x", comp_addr); + return true; + } + } + } + + return false; +} + +static void provisioner_node_count_inc(bool prov) +{ + all_node_count++; + if (prov) { + prov_node_count++; + } +} + +static void provisioner_node_count_dec(bool prov) +{ + if (all_node_count) { + all_node_count--; + } + if (prov) { + if (prov_node_count) { + prov_node_count--; + } + } +} + +u16_t bt_mesh_provisioner_get_prov_node_count(void) +{ + return prov_node_count; +} + +u16_t bt_mesh_provisioner_get_all_node_count(void) +{ + return all_node_count; +} + +static int provisioner_store_node(struct bt_mesh_node *node, bool prov, bool store, u16_t *index) +{ + u16_t min = 0U, max = 0U; + size_t i = 0U; + + bt_mesh_provisioner_lock(); + + /* Check if the node already exists */ + for (i = 0U; i < ARRAY_SIZE(mesh_nodes); i++) { + if (mesh_nodes[i] && !memcmp(mesh_nodes[i]->dev_uuid, node->dev_uuid, 16)) { + BT_WARN("Node already exists, uuid %s", bt_hex(node->dev_uuid, 16)); + bt_mesh_provisioner_unlock(); + return -EEXIST; + } + } + + /** + * 0 ~ (CONFIG_BLE_MESH_MAX_PROV_NODES - 1) are used to store + * the information of self-provisioned nodes. + */ + if (prov) { + min = 0U; + max = CONFIG_BLE_MESH_MAX_PROV_NODES; + } else { + min = CONFIG_BLE_MESH_MAX_PROV_NODES; + max = ARRAY_SIZE(mesh_nodes); + } + + for (i = min; i < max; i++) { + if (mesh_nodes[i] == NULL) { + mesh_nodes[i] = bt_mesh_calloc(sizeof(struct bt_mesh_node)); + if (!mesh_nodes[i]) { + BT_ERR("%s, Failed to allocate memory", __func__); + bt_mesh_provisioner_unlock(); + return -ENOMEM; + } + + memcpy(mesh_nodes[i], node, sizeof(struct bt_mesh_node)); + provisioner_node_count_inc(prov); + if (index) { + *index = i; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS) && store) { + bt_mesh_store_node_info(mesh_nodes[i], prov); + } + + bt_mesh_provisioner_unlock(); + return 0; + } + } + + BT_ERR("%s, Node queue is full", __func__); + bt_mesh_provisioner_unlock(); + return -ENOMEM; +} + +int bt_mesh_provisioner_restore_node_info(struct bt_mesh_node *node, bool prov) +{ + if (!node) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + return provisioner_store_node(node, prov, false, NULL); +} + +int bt_mesh_provisioner_provision(const bt_mesh_addr_t *addr, 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], u16_t *index) +{ + struct bt_mesh_node node = {0}; + + BT_DBG("%s", __func__); + + if (!addr || !uuid || !dev_key || !index) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + BT_INFO("unicast_addr 0x%04x, elem_num %d, net_idx 0x%04x", + unicast_addr, element_num, net_idx); + BT_INFO("dev_uuid %s", bt_hex(uuid, 16)); + BT_INFO("dev_key %s", bt_hex(dev_key, 16)); + + memcpy(node.addr, addr->val, BLE_MESH_ADDR_LEN); + node.addr_type = addr->type; + 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); + + return provisioner_store_node(&node, true, true, index); +} + +static int provisioner_remove_node(u16_t index, bool erase) +{ + struct bt_mesh_node *node = NULL; + struct bt_mesh_rpl *rpl = NULL; + bool is_prov = false; + int i; + + BT_DBG("%s, reset node %d", __func__, index); + + bt_mesh_provisioner_lock(); + + if (mesh_nodes[index] == NULL) { + bt_mesh_provisioner_unlock(); + return 0; + } + + node = mesh_nodes[index]; + + /* Reset corresponding network cache when reset the node */ + bt_mesh_msg_cache_clear(node->unicast_addr, node->element_num); + + /* Reset corresponding rpl when removing 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)); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_rpl_single(node->unicast_addr); + } + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + bt_mesh_friend_remove_lpn(node->unicast_addr); + } + + is_prov = index < CONFIG_BLE_MESH_MAX_PROV_NODES ? true : false; + + if (erase && IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_node_info(node->unicast_addr, is_prov); + } + + if (mesh_nodes[index]->comp_data) { + bt_mesh_free(mesh_nodes[index]->comp_data); + } + bt_mesh_free(mesh_nodes[index]); + mesh_nodes[index] = NULL; + + provisioner_node_count_dec(is_prov); + + bt_mesh_provisioner_unlock(); + return 0; +} + +static struct bt_mesh_node *provisioner_find_node_with_uuid(const u8_t uuid[16], u16_t *index) +{ + int i; + + BT_DBG("%s", __func__); + + if (uuid == NULL) { + BT_ERR("%s, Invalid device uuid", __func__); + return NULL; + } + + bt_mesh_provisioner_lock(); + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + if (mesh_nodes[i] && !memcmp(mesh_nodes[i]->dev_uuid, uuid, 16)) { + if (index) { + *index = i; + } + bt_mesh_provisioner_unlock(); + return mesh_nodes[i]; + } + } + + bt_mesh_provisioner_unlock(); + return NULL; +} + +bool bt_mesh_provisioner_find_node_with_uuid(const u8_t uuid[16], bool reset) +{ + struct bt_mesh_node *node = NULL; + u16_t index = 0U; + + node = provisioner_find_node_with_uuid(uuid, &index); + if (!node) { + return false; + } + + if (reset) { + provisioner_remove_node(index, true); + } + return true; +} + +bool bt_mesh_provisioner_find_node_with_addr(const bt_mesh_addr_t *addr, bool reset) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + if (mesh_nodes[i]) { + if (!memcmp(mesh_nodes[i]->addr, addr->val, BLE_MESH_ADDR_LEN) && + mesh_nodes[i]->addr_type == addr->type) { + if (reset) { + provisioner_remove_node(i, true); + } + return true; + } + } + } + + return false; +} + +int bt_mesh_provisioner_remove_node(const u8_t uuid[16]) +{ + struct bt_mesh_node *node = NULL; + u16_t index = 0U; + int i; + + if (uuid == NULL) { + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + if (mesh_nodes[i]) { + provisioner_remove_node(i, true); + } + } + return 0; + } + + node = provisioner_find_node_with_uuid(uuid, &index); + if (!node) { + BT_WARN("Node %s not exist", bt_hex(uuid, 16)); + return -ENODEV; + } + + provisioner_remove_node(index, true); + return 0; +} + +static struct bt_mesh_node *provisioner_find_node_with_addr(u16_t addr, u16_t *index) +{ + struct bt_mesh_node *node = NULL; + int i; + + BT_DBG("%s", __func__); + + if (!BLE_MESH_ADDR_IS_UNICAST(addr)) { + BT_ERR("%s, Not a unicast address 0x%04x", __func__, addr); + return NULL; + } + + bt_mesh_provisioner_lock(); + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (node && addr >= node->unicast_addr && + addr < (node->unicast_addr + node->element_num)) { + if (index) { + *index = i; + } + bt_mesh_provisioner_unlock(); + return node; + } + } + + bt_mesh_provisioner_unlock(); + return NULL; +} + +int bt_mesh_provisioner_restore_node_name(u16_t addr, const char *name) +{ + struct bt_mesh_node *node = NULL; + + node = provisioner_find_node_with_addr(addr, NULL); + if (node == NULL) { + BT_ERR("%s, Node 0x%04x not exist", __func__, addr); + return -ENODEV; + } + + strncpy(node->name, name, BLE_MESH_NODE_NAME_SIZE); + return 0; +} + +int bt_mesh_provisioner_restore_node_comp_data(u16_t addr, const u8_t *data, u16_t length, bool prov) +{ + struct bt_mesh_node *node = NULL; + + if (!data || length == 0U) { + BT_ERR("%s, Invalid comp data info", __func__); + return -EINVAL; + } + + node = provisioner_find_node_with_addr(addr, NULL); + if (node == NULL) { + BT_ERR("%s, Node 0x%04x not exist", __func__, addr); + return -ENODEV; + } + + node->comp_data = bt_mesh_calloc(length); + if (!node->comp_data) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + node->comp_length = length; + memcpy(node->comp_data, data, length); + + return 0; +} + +int bt_mesh_provisioner_store_node_info(struct bt_mesh_node *node) +{ + if (!node) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + return provisioner_store_node(node, false, true, NULL); +} + +struct bt_mesh_node *bt_mesh_provisioner_get_node_with_uuid(const u8_t uuid[16]) +{ + return provisioner_find_node_with_uuid(uuid, NULL); +} + +struct bt_mesh_node *bt_mesh_provisioner_get_node_with_addr(u16_t unicast_addr) +{ + return provisioner_find_node_with_addr(unicast_addr, NULL); +} + +int bt_mesh_provisioner_delete_node_with_uuid(const u8_t uuid[16]) +{ + struct bt_mesh_node *node = NULL; + u16_t index = 0U; + + node = provisioner_find_node_with_uuid(uuid, &index); + if (!node) { + BT_WARN("Node %s not exist", bt_hex(uuid, 16)); + return -ENODEV; + } + + provisioner_remove_node(index, true); + return 0; +} + +int bt_mesh_provisioner_delete_node_with_addr(u16_t unicast_addr) +{ + struct bt_mesh_node *node = NULL; + u16_t index = 0U; + + node = provisioner_find_node_with_addr(unicast_addr, &index); + if (!node) { + BT_WARN("Node 0x%04x not exist", unicast_addr); + return -ENODEV; + } + + provisioner_remove_node(index, true); + return 0; +} + +static int provisioner_check_node_index(u16_t index) +{ + BT_DBG("%s", __func__); + + if (index >= ARRAY_SIZE(mesh_nodes)) { + BT_ERR("%s, Too big node index %d", __func__, index); + return -EINVAL; + } + + if (mesh_nodes[index] == NULL) { + BT_ERR("%s, Node %d is not found", __func__, index); + return -ENODEV; + } + + return 0; +} + +int bt_mesh_provisioner_set_node_name(u16_t index, const char *name) +{ + size_t length = 0U, name_len = 0U; + int i; + + BT_DBG("%s", __func__); + + if (!name) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (provisioner_check_node_index(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) <= BLE_MESH_NODE_NAME_SIZE) ? strlen(name) : BLE_MESH_NODE_NAME_SIZE; + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + if (mesh_nodes[i]) { + name_len = strlen(mesh_nodes[i]->name); + if (length != name_len) { + continue; + } + if (!strncmp(mesh_nodes[i]->name, name, length)) { + BT_WARN("Node name %s already exists", name); + return -EEXIST; + } + } + } + + memset(mesh_nodes[index]->name, 0, BLE_MESH_NODE_NAME_SIZE); + + strncpy(mesh_nodes[index]->name, name, length); + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_node_name(mesh_nodes[index], + index < CONFIG_BLE_MESH_MAX_PROV_NODES ? true : false); + } + + return 0; +} + +const char *bt_mesh_provisioner_get_node_name(u16_t index) +{ + BT_DBG("%s", __func__); + + if (provisioner_check_node_index(index)) { + BT_ERR("%s, Failed to check node index", __func__); + return NULL; + } + + return mesh_nodes[index]->name; +} + +u16_t bt_mesh_provisioner_get_node_index(const char *name) +{ + size_t length = 0U, name_len = 0U; + int i; + + BT_DBG("%s", __func__); + + if (!name) { + BT_ERR("%s, Invalid parameter", __func__); + return BLE_MESH_INVALID_NODE_INDEX; + } + + length = (strlen(name) <= BLE_MESH_NODE_NAME_SIZE) ? strlen(name) : BLE_MESH_NODE_NAME_SIZE; + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + if (mesh_nodes[i]) { + name_len = strlen(mesh_nodes[i]->name); + if (length != name_len) { + continue; + } + if (!strncmp(mesh_nodes[i]->name, name, length)) { + return (u16_t)i; + } + } + } + + BT_ERR("%s, Node name %s not exist", __func__, name); + return BLE_MESH_INVALID_NODE_INDEX; +} + +int bt_mesh_provisioner_store_node_comp_data(u16_t addr, const u8_t *data, u16_t length) +{ + struct bt_mesh_node *node = NULL; + u16_t index = 0U; + + if (!BLE_MESH_ADDR_IS_UNICAST(addr) || !data || + (length % 2) || length <= 14) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + node = provisioner_find_node_with_addr(addr, &index); + if (node == NULL) { + BT_ERR("%s, Node 0x%04x not exist", __func__, addr); + return -ENODEV; + } + + node->comp_data = bt_mesh_calloc(length); + if (node->comp_data == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return -ENOMEM; + } + + BT_DBG("%s, index %d", __func__, index); + + memcpy(node->comp_data, data, length); + node->comp_length = length; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_node_comp_data(node, + index < CONFIG_BLE_MESH_MAX_PROV_NODES ? true : false); + } + + return 0; +} + +/* Provisioner DevKey, NetKey and AppKey related functions */ + +const u8_t *bt_mesh_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) { + if (sub->kr_flag) { + return sub->keys[1].net; + } else { + return sub->keys[0].net; + } + } + } + + return NULL; +} + +struct bt_mesh_subnet *bt_mesh_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) { + return sub; + } + } + + return NULL; +} + +bool bt_mesh_provisioner_check_msg_dst(u16_t dst) +{ + struct bt_mesh_node *node = NULL; + int i; + + BT_DBG("%s", __func__); + + if (!BLE_MESH_ADDR_IS_UNICAST(dst)) { + return true; + } + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (node && dst >= node->unicast_addr && + dst < node->unicast_addr + node->element_num) { + return true; + } + } + + return false; +} + +const u8_t *bt_mesh_provisioner_dev_key_get(u16_t dst) +{ + /* 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 *node = NULL; + int i; + + BT_DBG("%s", __func__); + + if (!BLE_MESH_ADDR_IS_UNICAST(dst)) { + BT_ERR("%s, Not a unicast address 0x%04x", __func__, dst); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (node && node->unicast_addr == dst) { + return node->dev_key; + } + } + + return NULL; +} + +struct bt_mesh_app_key *bt_mesh_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 && key->net_idx != BLE_MESH_KEY_UNUSED && + key->app_idx == app_idx) { + return key; + } + } + + return NULL; +} + +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_keys *keys = NULL; + struct bt_mesh_app_key *key = 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("AppKey already exists, AppKey Index updated"); + 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 = bt_mesh_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__); + bt_mesh_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__); + bt_mesh_free(key); + return -EIO; + } + } else { + break; + } + } + *app_idx = key->app_idx; + } + key->updated = false; + + bt_mesh.p_app_keys[add] = key; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_p_app_idx(); + bt_mesh_store_p_app_key(key); + } + + return 0; +} + +int bt_mesh_provisioner_local_app_key_update(const u8_t app_key[16], u16_t net_idx, u16_t app_idx) +{ + struct bt_mesh_app_keys *keys = NULL; + struct bt_mesh_app_key *key = NULL; + + if (app_key == NULL) { + BT_ERR("%s, Invalid AppKey", __func__); + return -EINVAL; + } + + BT_INFO("AppKey %s, net_idx 0x%03x, app_idx 0x%03x", bt_hex(app_key, 16), net_idx, app_idx); + + /* Check if the net_idx exists */ + if (provisioner_check_net_idx(net_idx, false)) { + BT_ERR("%s, NetKey Index 0x%03x not exist", __func__, net_idx); + return -ENODEV; + } + + key = bt_mesh_provisioner_app_key_find(app_idx); + if (key == NULL) { + BT_ERR("%s, AppKey 0x%03x not exist", __func__, app_idx); + return -ENODEV; + } + + keys = &key->keys[0]; + if (bt_mesh_app_id(app_key, &keys->id)) { + BT_ERR("%s, Failed to generate AID", __func__); + return -EIO; + } + + memset(keys->val, 0, 16); + memcpy(keys->val, app_key, 16); + + key->updated = false; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_p_app_idx(); + bt_mesh_store_p_app_key(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; +} + +static void model_pub_clear(struct bt_mesh_model *model) +{ + if (!model->pub) { + return; + } + + if (model->pub->addr == BLE_MESH_ADDR_UNASSIGNED) { + return; + } + + 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)) { + bt_mesh_store_mod_pub(model); + } + + return; +} + +static void model_unbind(struct bt_mesh_model *model, u16_t app_idx) +{ + int i; + + BT_DBG("model %p key_idx 0x%03x", model, app_idx); + + for (i = 0; i < ARRAY_SIZE(model->keys); i++) { + if (model->keys[i] != app_idx) { + continue; + } + + model->keys[i] = BLE_MESH_KEY_UNUSED; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_bind(model); + } + + model_pub_clear(model); + } +} + +static void _model_unbind(struct bt_mesh_model *mod, struct bt_mesh_elem *elem, + bool vnd, bool primary, void *user_data) +{ + u16_t app_idx = *(u16_t *)user_data; + + model_unbind(mod, app_idx); +} + +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) { + /* Remove the AppKey from the models if they are bound with it */ + bt_mesh_model_foreach(_model_unbind, &app_idx); + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_p_app_key(key); + } + + bt_mesh_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("NetKey already exists, NetKey Index updated"); + 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 = bt_mesh_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__); + bt_mesh_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__); + bt_mesh_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; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_p_net_idx(); + bt_mesh_store_p_subnet(sub); + } + + return 0; +} + +int bt_mesh_provisioner_local_net_key_update(const u8_t net_key[16], u16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + int err = 0; + + if (net_key == NULL) { + BT_ERR("%s, Invalid NetKey", __func__); + return -EINVAL; + } + + BT_INFO("NetKey %s, net_idx 0x%03x", bt_hex(net_key, 16), net_idx); + + sub = bt_mesh_provisioner_subnet_get(net_idx); + if (sub == NULL) { + BT_ERR("%s, NetKey 0x%03x not exist", __func__, net_idx); + return -ENODEV; + } + + err = bt_mesh_net_keys_create(&sub->keys[0], net_key); + if (err) { + BT_ERR("%s, Failed to generate NID", __func__); + return -EIO; + } + + memset(sub->keys[0].net, 0, 16); + memcpy(sub->keys[0].net, net_key, 16); + + sub->kr_phase = BLE_MESH_KR_NORMAL; + sub->kr_flag = false; + sub->node_id = BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED; + + err = bt_mesh_net_beacon_update(sub); + if (err) { + BT_ERR("%s, Failed to update secure beacon", __func__); + return -EIO; + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_p_subnet(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, j; + + 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) { + /* Delete any app keys bound to this NetKey index */ + for (j = 0; j < ARRAY_SIZE(bt_mesh.p_app_keys); j++) { + struct bt_mesh_app_key *key = bt_mesh.p_app_keys[j]; + if (key->net_idx == sub->net_idx) { + bt_mesh_provisioner_local_app_key_delete(key->net_idx, key->app_idx); + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_p_subnet(sub); + } + + bt_mesh_free(bt_mesh.p_sub[i]); + bt_mesh.p_sub[i] = NULL; + return 0; + } + } + + /* Shall never reach here */ + return -ENODEV; +} + +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_model *model = NULL; + struct bt_mesh_elem *elem = NULL; + int i; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) { + 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("AppKey 0x%03x is already bound to model", app_idx); + return 0; + } + } + + for (i = 0; i < ARRAY_SIZE(model->keys); i++) { + if (model->keys[i] == BLE_MESH_KEY_UNUSED) { + model->keys[i] = app_idx; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_mod_bind(model); + } + return 0; + } + } + + BT_ERR("%s, Model AppKey queue is full", __func__); + return -ENOMEM; +} + +int bt_mesh_print_local_composition_data(void) +{ + const struct bt_mesh_comp *comp = NULL; + struct bt_mesh_model *model = NULL; + struct bt_mesh_elem *elem = NULL; + int i, j; + + comp = bt_mesh_comp_get(); + if (!comp) { + BT_ERR("%s, NULL composition data", __func__); + return -EINVAL; + } + + BT_INFO("************************************************"); + BT_INFO("* cid: 0x%04x pid: 0x%04x vid: 0x%04x *", comp->cid, comp->pid, comp->vid); + BT_INFO("* Element Number: 0x%02x *", comp->elem_count); + for (i = 0; i < comp->elem_count; i++) { + elem = &comp->elem[i]; + BT_INFO("* Element %d: 0x%04x *", i, elem->addr); + BT_INFO("* 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_INFO("* sig_model %d: id - 0x%04x *", j, model->id); + } + for (j = 0; j < elem->vnd_model_count; j++) { + model = &elem->vnd_models[j]; + BT_INFO("* vnd_model %d: id - 0x%04x, cid - 0x%04x *", j, model->vnd.id, model->vnd.company); + } + } + BT_INFO("************************************************"); + + ((void) model); + + return 0; +} + +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +/* The following APIs are for fast provisioning */ + +#if CONFIG_BLE_MESH_FAST_PROV + +const u8_t *bt_mesh_fast_prov_dev_key_get(u16_t dst) +{ + struct bt_mesh_node *node = NULL; + int i; + + BT_DBG("%s", __func__); + + if (!BLE_MESH_ADDR_IS_UNICAST(dst)) { + BT_ERR("%s, Not a unicast address 0x%04x", __func__, dst); + return NULL; + } + + if (dst == bt_mesh_primary_addr()) { + return bt_mesh.dev_key; + } + + for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { + node = mesh_nodes[i]; + if (node && node->unicast_addr == dst) { + return node->dev_key; + } + } + + return NULL; +} + +struct bt_mesh_subnet *bt_mesh_fast_prov_subnet_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.sub); i++) { + sub = &bt_mesh.sub[i]; + if (sub->net_idx == net_idx) { + return sub; + } + } + + 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 sub; + } + } + + return NULL; +} + +struct bt_mesh_app_key *bt_mesh_fast_prov_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.app_keys); i++) { + key = &bt_mesh.app_keys[i]; + if (key->net_idx != BLE_MESH_KEY_UNUSED && + key->app_idx == app_idx) { + return key; + } + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + key = bt_mesh.p_app_keys[i]; + if (key && key->net_idx != BLE_MESH_KEY_UNUSED && + 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_keys *key = NULL; + struct bt_mesh_subnet *sub = NULL; + + sub = bt_mesh_fast_prov_subnet_get(net_idx); + if (sub) { + key = BLE_MESH_KEY_REFRESH(sub->kr_flag) ? &sub->keys[1] : &sub->keys[0]; + return bt_mesh_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 bt_mesh_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 = 0U; + int err = 0; + + net_idx = bt_mesh_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 bt_mesh_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 = bt_mesh_fast_prov_subnet_get(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 = bt_mesh_fast_prov_app_key_find(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/esp_ble_mesh/mesh_core/provisioner_main.h b/components/bt/esp_ble_mesh/mesh_core/provisioner_main.h new file mode 100644 index 0000000000..fabe9399eb --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/provisioner_main.h @@ -0,0 +1,141 @@ +// Copyright 2017-2019 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 "net.h" +#include "mesh_bearer_adapt.h" + +#define BLE_MESH_INVALID_NODE_INDEX 0xFFFF +#define BLE_MESH_NODE_NAME_SIZE 31 + +/* Each node information stored by provisioner */ +struct bt_mesh_node { + /* Device information */ + u8_t addr[6]; /* Node device address */ + u8_t addr_type; /* Node device address type */ + u8_t dev_uuid[16]; /* Node Device UUID */ + u16_t oob_info; /* Node OOB information */ + + /* Provisioning information */ + u16_t unicast_addr; /* Node unicast address */ + u8_t element_num; /* Node element number */ + u16_t net_idx; /* Node NetKey Index */ + 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 */ + + /* Additional information */ + char name[BLE_MESH_NODE_NAME_SIZE]; /* Node name */ + u16_t comp_length; /* Length of Composition Data */ + u8_t *comp_data; /* Value of Composition Data */ +} __packed; + +int bt_mesh_provisioner_init(void); + +int bt_mesh_provisioner_net_create(void); + +int bt_mesh_provisioner_deinit(bool erase); + +bool bt_mesh_provisioner_check_is_addr_dup(u16_t addr, u8_t elem_num, bool comp_with_own); + +u16_t bt_mesh_provisioner_get_prov_node_count(void); + +u16_t bt_mesh_provisioner_get_all_node_count(void); + +int bt_mesh_provisioner_restore_node_info(struct bt_mesh_node *node, bool prov); + +int bt_mesh_provisioner_provision(const bt_mesh_addr_t *addr, 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], u16_t *index); + +bool bt_mesh_provisioner_find_node_with_uuid(const u8_t uuid[16], bool reset); + +bool bt_mesh_provisioner_find_node_with_addr(const bt_mesh_addr_t *addr, bool reset); + +int bt_mesh_provisioner_remove_node(const u8_t uuid[16]); + +int bt_mesh_provisioner_restore_node_name(u16_t addr, const char *name); + +int bt_mesh_provisioner_restore_node_comp_data(u16_t addr, const u8_t *data, u16_t length, bool prov); + +int bt_mesh_provisioner_store_node_info(struct bt_mesh_node *node); + +struct bt_mesh_node *bt_mesh_provisioner_get_node_with_uuid(const u8_t uuid[16]); + +struct bt_mesh_node *bt_mesh_provisioner_get_node_with_addr(u16_t unicast_addr); + +int bt_mesh_provisioner_delete_node_with_uuid(const u8_t uuid[16]); + +int bt_mesh_provisioner_delete_node_with_addr(u16_t unicast_addr); + +int bt_mesh_provisioner_set_node_name(u16_t index, const char *name); + +const char *bt_mesh_provisioner_get_node_name(u16_t index); + +u16_t bt_mesh_provisioner_get_node_index(const char *name); + +int bt_mesh_provisioner_store_node_comp_data(u16_t addr, const u8_t *data, u16_t length); + +const u8_t *bt_mesh_provisioner_net_key_get(u16_t net_idx); + +struct bt_mesh_subnet *bt_mesh_provisioner_subnet_get(u16_t net_idx); + +bool bt_mesh_provisioner_check_msg_dst(u16_t dst); + +const u8_t *bt_mesh_provisioner_dev_key_get(u16_t dst); + +struct bt_mesh_app_key *bt_mesh_provisioner_app_key_find(u16_t app_idx); + +int bt_mesh_provisioner_local_app_key_add(const u8_t app_key[16], u16_t net_idx, u16_t *app_idx); + +int bt_mesh_provisioner_local_app_key_update(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); + +int bt_mesh_provisioner_local_net_key_update(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); + +/* 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); + +/* Provisioner print own element information */ +int bt_mesh_print_local_composition_data(void); + +/* The following APIs are for fast provisioning */ + +const u8_t *bt_mesh_fast_prov_dev_key_get(u16_t dst); + +struct bt_mesh_subnet *bt_mesh_fast_prov_subnet_get(u16_t net_idx); + +struct bt_mesh_app_key *bt_mesh_fast_prov_app_key_find(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/esp_ble_mesh/mesh_core/provisioner_prov.c b/components/bt/esp_ble_mesh/mesh_core/provisioner_prov.c new file mode 100644 index 0000000000..8df45ed4cd --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/provisioner_prov.c @@ -0,0 +1,3497 @@ +// Copyright 2017-2019 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 "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "access.h" +#include "settings.h" +#include "mesh_common.h" +#include "proxy_client.h" +#include "provisioner_prov.h" +#include "provisioner_main.h" + +#if CONFIG_BLE_MESH_PROVISIONER + +_Static_assert(BLE_MESH_MAX_CONN >= CONFIG_BLE_MESH_PBG_SAME_TIME, + "Too large BLE Mesh PB-GATT count"); + +/* 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 assign_addr; /* Application assigned address for the device */ + u16_t unicast_addr; /* unicast address allocated 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) + +#define PROV_MAX_ADDR_TO_ASSIGN 0x7FFF + +static struct prov_link link[BLE_MESH_PROV_SAME_TIME]; + +struct prov_rx { + u32_t link_id; + u8_t xact_id; + u8_t gpc; +}; + +struct bt_mesh_prov_ctx { + /* Primary element address of Provisioner */ + u16_t primary_addr; + + /* Provisioning bearers used by Provisioner */ + bt_mesh_prov_bearer_t bearers; + + /* If provisioning random have been generated, set BIT0 to 1 */ + u8_t rand_gen_done; + + /* Provisioner random */ + u8_t random[16]; + + /* 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 allocated */ + u16_t curr_alloc_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; + + /* Length of Static OOB value */ + u8_t static_oob_len; + + /* Static OOB value */ + u8_t static_oob_val[16]; + + /* 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[16]; + + /* Indicate when received uuid_match adv_pkts, can provision it at once */ + bool prov_after_match; + +#if defined(CONFIG_BLE_MESH_PB_ADV) + /* Mutex used to protect the PB-ADV procedure */ + bt_mesh_mutex_t pb_adv_lock; + + /* Mutex used to protect the adv buf during PB-ADV procedure */ + bt_mesh_mutex_t pb_buf_lock; +#endif + +#if defined(CONFIG_BLE_MESH_PB_GATT) + /* Mutex used to protect the PB-GATT procedure */ + bt_mesh_mutex_t pb_gatt_lock; +#endif + + /* Fast provisioning related information */ + struct { + bool enable; + 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; +}; + +static struct bt_mesh_prov_ctx prov_ctx; + +#define FAST_PROV_ENABLE() (prov_ctx.fast_prov.enable) + +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 /* CONFIG_BLE_MESH_PB_ADV */ + +#define PROV_FREE_MEM(_idx, member) \ +{ \ + if (link[_idx].member) { \ + bt_mesh_free(link[_idx].member); \ + link[_idx].member = NULL; \ + } \ +} + +#if defined(CONFIG_BLE_MESH_PB_ADV) +static void bt_mesh_pb_adv_mutex_new(void) +{ + if (!prov_ctx.pb_adv_lock.mutex) { + bt_mesh_mutex_create(&prov_ctx.pb_adv_lock); + } +} + +static void bt_mesh_pb_adv_mutex_free(void) +{ + bt_mesh_mutex_free(&prov_ctx.pb_adv_lock); +} + +static void bt_mesh_pb_adv_lock(void) +{ + bt_mesh_mutex_lock(&prov_ctx.pb_adv_lock); +} + +static void bt_mesh_pb_adv_unlock(void) +{ + bt_mesh_mutex_unlock(&prov_ctx.pb_adv_lock); +} + +static void bt_mesh_pb_buf_mutex_new(void) +{ + if (!prov_ctx.pb_buf_lock.mutex) { + bt_mesh_mutex_create(&prov_ctx.pb_buf_lock); + } +} + +static void bt_mesh_pb_buf_mutex_free(void) +{ + bt_mesh_mutex_free(&prov_ctx.pb_buf_lock); +} + +static void bt_mesh_pb_buf_lock(void) +{ + bt_mesh_mutex_lock(&prov_ctx.pb_buf_lock); +} + +static void bt_mesh_pb_buf_unlock(void) +{ + bt_mesh_mutex_unlock(&prov_ctx.pb_buf_lock); +} +#endif /* CONFIG_BLE_MESH_PB_ADV */ + +#if defined(CONFIG_BLE_MESH_PB_GATT) +static void bt_mesh_pb_gatt_mutex_new(void) +{ + if (!prov_ctx.pb_gatt_lock.mutex) { + bt_mesh_mutex_create(&prov_ctx.pb_gatt_lock); + } +} + +static void bt_mesh_pb_gatt_mutex_free(void) +{ + bt_mesh_mutex_free(&prov_ctx.pb_gatt_lock); +} + +static void bt_mesh_pb_gatt_lock(void) +{ + bt_mesh_mutex_lock(&prov_ctx.pb_gatt_lock); +} + +static void bt_mesh_pb_gatt_unlock(void) +{ + bt_mesh_mutex_unlock(&prov_ctx.pb_gatt_lock); +} +#endif /* CONFIG_BLE_MESH_PB_GATT */ + +void bt_mesh_provisioner_pbg_count_dec(void) +{ + if (prov_ctx.pbg_count) { + prov_ctx.pbg_count--; + } +} + +static inline void provisioner_pbg_count_inc(void) +{ + prov_ctx.pbg_count++; +} + +#if defined(CONFIG_BLE_MESH_PB_GATT) +void bt_mesh_provisioner_clear_link_info(const u8_t addr[6]) +{ + int 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("Device address %s is not found", bt_hex(addr, BLE_MESH_ADDR_LEN)); + return; +} +#endif + +const struct bt_mesh_prov *bt_mesh_provisioner_get_prov_info(void) +{ + return prov; +} + +void bt_mesh_provisoner_restore_prov_info(u16_t primary_addr, u16_t alloc_addr) +{ + prov_ctx.primary_addr = primary_addr; + prov_ctx.curr_alloc_addr = alloc_addr; +} + +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 = 0U, j = 0U; + 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]) +{ + int 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. + */ + if (bt_mesh_provisioner_find_node_with_uuid(uuid, false)) { + BT_WARN("Device has already been provisioned"); + return true; + } +#endif + + for (i = 0; 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) { + 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], bt_mesh_prov_bearer_t bearer) +{ + 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 mismatch", __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 the current PB-ADV link is full */ + if (bearer == BLE_MESH_PROV_ADV && prov_ctx.pba_count == CONFIG_BLE_MESH_PBA_SAME_TIME) { + BT_INFO("Current PB-ADV links reach max limit"); + return -ENOMEM; + } + + /* Check if the current PB-GATT link is full */ + if (bearer == BLE_MESH_PROV_GATT && prov_ctx.pbg_count == CONFIG_BLE_MESH_PBG_SAME_TIME) { + BT_INFO("Current PB-GATT links reach max limit"); + return -ENOMEM; + } + + /* Check if the device has already been provisioned */ + if (bt_mesh_provisioner_find_node_with_uuid(uuid, false)) { + BT_WARN("Provisioned before, start to provision again"); + return 0; + } + + /* Check if the provisioned nodes queue is full */ + if (bt_mesh_provisioner_get_prov_node_count() == CONFIG_BLE_MESH_MAX_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, u16_t assign_addr) +{ + u8_t zero[6] = {0}; + int addr_cmp = 0; + int i; + + if (!uuid || !addr) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + bt_mesh_pb_adv_lock(); + + /* If the unicast address of the node is going to be allocated internally, + * then we need to check if there are addresses can be allocated. + */ + if (assign_addr == BLE_MESH_ADDR_UNASSIGNED && + prov_ctx.curr_alloc_addr == BLE_MESH_ADDR_UNASSIGNED) { + BT_ERR("No available unicast address to assign"); + return -EIO; + } + + if (is_unprov_dev_being_provision(uuid)) { + bt_mesh_pb_adv_unlock(); + return -EALREADY; + } + + addr_cmp = memcmp(addr->val, zero, BLE_MESH_ADDR_LEN); + + for (i = 0; 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); + /* If the application layer assigned a specific unicast address for the device, + * then Provisioner will use this address in the Provisoning Data PDU. + */ + if (BLE_MESH_ADDR_IS_UNICAST(assign_addr)) { + link[i].assign_addr = assign_addr; + } + bt_mesh_pb_adv_unlock(); + return 0; + } + } + + BT_ERR("%s, No PB-ADV link is available", __func__); + bt_mesh_pb_adv_unlock(); + + 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, u16_t assign_addr) +{ + u8_t zero[6] = {0}; + int addr_cmp = 0; + int i; + + if (!uuid || !addr) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + bt_mesh_pb_gatt_lock(); + + /* If the unicast address of the node is going to be allocated internally, + * then we need to check if there are addresses can be allocated. + */ + if (assign_addr == BLE_MESH_ADDR_UNASSIGNED && + prov_ctx.curr_alloc_addr == BLE_MESH_ADDR_UNASSIGNED) { + BT_ERR("No available unicast address to assign"); + return -EIO; + } + + if (is_unprov_dev_being_provision(uuid)) { + bt_mesh_pb_gatt_unlock(); + 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) < 0) { + memset(link[i].uuid, 0, 16); + link[i].oob_info = 0x0; + memset(&link[i].addr, 0, sizeof(bt_mesh_addr_t)); + bt_mesh_pb_gatt_unlock(); + return -EIO; + } + /* If the application layer assigned a specific unicast address for the device, + * then Provisioner will use this address in the Provisoning Data PDU. + */ + if (BLE_MESH_ADDR_IS_UNICAST(assign_addr)) { + link[i].assign_addr = assign_addr; + } + /* If creating connection successfully, set connecting flag to 1 */ + link[i].connecting = true; + provisioner_pbg_count_inc(); + bt_mesh_pb_gatt_unlock(); + return 0; + } + } + + BT_ERR("%s, No PB-GATT link is available", __func__); + bt_mesh_pb_gatt_unlock(); + + 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 = 0U; + int err = 0; + + 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 (bt_mesh_provisioner_get_prov_node_count() + prov_ctx.pba_count + \ + prov_ctx.pbg_count >= CONFIG_BLE_MESH_MAX_PROV_NODES) { + BT_ERR("Node count + active link count reach max limit"); + return -EIO; + } + + if ((err = provisioner_check_unprov_dev_info(add_dev->uuid, add_dev->bearer))) { + return err; + } + + if (add_dev->bearer == BLE_MESH_PROV_ADV) { +#if defined(CONFIG_BLE_MESH_PB_ADV) + if ((err = provisioner_start_prov_pb_adv( + add_dev->uuid, &add_addr, add_dev->oob_info, BLE_MESH_ADDR_UNASSIGNED))) { + return err; + } +#endif + } else if (add_dev->bearer == BLE_MESH_PROV_GATT) { +#if defined(CONFIG_BLE_MESH_PB_GATT) + if ((err = provisioner_start_prov_pb_gatt( + add_dev->uuid, &add_addr, add_dev->oob_info, BLE_MESH_ADDR_UNASSIGNED))) { + return err; + } +#endif + } + + return 0; +} + +int bt_mesh_provisioner_prov_device_with_addr(const u8_t uuid[16], const u8_t addr[6], + u8_t addr_type, bt_mesh_prov_bearer_t bearer, + u16_t oob_info, u16_t unicast_addr) +{ + bt_mesh_addr_t dev_addr = {0}; + int err = 0; + + if (uuid == NULL) { + BT_ERR("%s, NULL device uuid", __func__); + return -EINVAL; + } + + if (bearer != BLE_MESH_PROV_ADV && bearer != BLE_MESH_PROV_GATT) { + BT_ERR("%s, Invalid provisioning bearer 0x%02x", __func__, bearer); + return -EINVAL; + } + + if (!IS_ENABLED(CONFIG_BLE_MESH_PB_ADV) && bearer == BLE_MESH_PROV_ADV) { + BT_ERR("%s, Not support PB-ADV", __func__); + return -ENOTSUP; + } + + if (!IS_ENABLED(CONFIG_BLE_MESH_PB_GATT) && bearer == BLE_MESH_PROV_GATT) { + BT_ERR("%s, Not support PB-GATT", __func__); + return -ENOTSUP; + } + + if (bearer == BLE_MESH_PROV_GATT && (addr == NULL || addr_type > BLE_MESH_ADDR_RANDOM)) { + BT_ERR("%s, Invalid device address info", __func__); + return -EINVAL; + } + + if (!BLE_MESH_ADDR_IS_UNICAST(unicast_addr)) { + BT_ERR("%s, Invalid unicast address 0x%04x", __func__, unicast_addr); + return -EINVAL; + } + + /* Here we will not check if the assigned unicast address is overlapped + * with the unicast addresses of other nodes or Provisioner, beacuse: + * 1. At this moment, the element number of the device is unknown + * 2. If the node is a reprovisioned device, then the original allocated + * unicast address will be used. + * 3. Some other devices may be just being provisioning, and currently we + * can not know the exactly allocated addresses of them. + */ + + /* Check if current provisioned node count + active link reach max limit */ + if (bt_mesh_provisioner_get_prov_node_count() + prov_ctx.pba_count + \ + prov_ctx.pbg_count >= CONFIG_BLE_MESH_MAX_PROV_NODES) { + BT_ERR("Node count + active link count reach max limit"); + return -EIO; + } + + if ((err = provisioner_check_unprov_dev_info(uuid, bearer))) { + return err; + } + + if (addr) { + dev_addr.type = addr_type; + memcpy(dev_addr.val, addr, BLE_MESH_ADDR_LEN); + } + + if (bearer == BLE_MESH_PROV_ADV) { +#if defined(CONFIG_BLE_MESH_PB_ADV) + if ((err = provisioner_start_prov_pb_adv(uuid, &dev_addr, oob_info, unicast_addr))) { + return err; + } +#endif + } else if (bearer == BLE_MESH_PROV_GATT) { +#if defined(CONFIG_BLE_MESH_PB_GATT) + if ((err = provisioner_start_prov_pb_gatt(uuid, &dev_addr, oob_info, unicast_addr))) { + 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_reset_node() 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 = 0U; + int err = 0; + + 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 */ + if (addr_cmp && (del_dev->addr_type <= BLE_MESH_ADDR_RANDOM)) { + if (bt_mesh_provisioner_find_node_with_addr(&del_addr, true)) { + return 0; + } + } + + if (uuid_cmp) { + if (bt_mesh_provisioner_find_node_with_uuid(del_dev->uuid, true)) { + return 0; + } + } + + 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; + } + + (void)memset(prov_ctx.match_value, 0, 16); + + 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_provisioner_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 = bt_mesh_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; +} + +int bt_mesh_provisioner_set_prov_info(void) +{ + const struct bt_mesh_comp *comp = NULL; + + if (prov_ctx.primary_addr == BLE_MESH_ADDR_UNASSIGNED) { + /* If unicast address of primary element of Provisioner has not been set + * before, then the following initilization procedure will be used. + */ + if (!BLE_MESH_ADDR_IS_UNICAST(prov->prov_unicast_addr) || + !BLE_MESH_ADDR_IS_UNICAST(prov->prov_start_address)) { + BT_ERR("%s, Invalid address, own 0x%04x, start 0x%04x", + __func__, prov->prov_unicast_addr, prov->prov_start_address); + return -EINVAL; + } + + comp = bt_mesh_comp_get(); + if (!comp) { + BT_ERR("%s, NULL composition data", __func__); + return -EINVAL; + } + + if (prov->prov_unicast_addr + comp->elem_count > prov->prov_start_address) { + BT_WARN("Too small start address 0x%04x, update to 0x%04x", + prov->prov_start_address, prov->prov_unicast_addr + comp->elem_count); + prov_ctx.curr_alloc_addr = prov->prov_unicast_addr + comp->elem_count; + } else { + prov_ctx.curr_alloc_addr = prov->prov_start_address; + } + prov_ctx.primary_addr = prov->prov_unicast_addr; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_prov_info(prov_ctx.primary_addr, prov_ctx.curr_alloc_addr); + } + } + prov_ctx.curr_net_idx = BLE_MESH_KEY_PRIMARY; + prov_ctx.curr_flags = prov->flags; + prov_ctx.curr_iv_index = prov->iv_index; + + return 0; +} + +void bt_mesh_provisioner_set_prov_bearer(bt_mesh_prov_bearer_t bearers, bool clear) +{ + if (clear == false) { + prov_ctx.bearers |= bearers; + } else { + prov_ctx.bearers &= ~bearers; + } +} + +bt_mesh_prov_bearer_t bt_mesh_provisioner_get_prov_bearer(void) +{ + return prov_ctx.bearers; +} + +int bt_mesh_provisioner_set_static_oob_value(const u8_t *value, u8_t length) +{ + int i; + + if (value == NULL || length == 0U || length > 16U) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + /* Make sure Static OOB is not being used. */ + for (i = 0; i < BLE_MESH_PROV_SAME_TIME; i++) { + if (link[i].auth_method == AUTH_METHOD_STATIC) { + BT_ERR("%s, Static OOB is being used", __func__); + return -EINVAL; + } + } + + (void)memset(prov_ctx.static_oob_val, 0, 16); + + prov_ctx.static_oob_len = MIN(16, length); + memcpy(prov_ctx.static_oob_val, value, prov_ctx.static_oob_len); + + return 0; +} + +u16_t bt_mesh_provisioner_get_primary_elem_addr(void) +{ + return prov_ctx.primary_addr; +} + +int bt_mesh_provisioner_set_primary_elem_addr(u16_t addr) +{ + const struct bt_mesh_comp *comp = NULL; + + if (!BLE_MESH_ADDR_IS_UNICAST(addr)) { + BT_ERR("Invalid primary address 0x%04x", addr); + return -EINVAL; + } + + comp = bt_mesh_comp_get(); + if (!comp) { + BT_ERR("NULL composition data"); + return -EINVAL; + } + + /* Make sure Provisioner address is not identical with the addresses of nodes */ + if (bt_mesh_provisioner_check_is_addr_dup(addr, comp->elem_count, false)) { + BT_ERR("Address 0x%04x is duplicated with node address", addr); + return -EINVAL; + } + + /* If the current can-be allocated address is bigger than primary address + + * element number, then the curr_alloc_addr will not be changed, and only + * the Provisioner related addresses will be updated. + */ + if (addr + comp->elem_count > prov_ctx.curr_alloc_addr) { + prov_ctx.curr_alloc_addr = addr + comp->elem_count; + } + BT_INFO("Provisioner primary address updated, old 0x%04x, new 0x%04x", prov_ctx.primary_addr, addr); + prov_ctx.primary_addr = addr; + + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_prov_info(prov_ctx.primary_addr, prov_ctx.curr_alloc_addr); + } + + bt_mesh_comp_provision(addr); + + return 0; +} + +/* The following APIs are for fast provisioning */ + +void bt_mesh_provisioner_fast_prov_enable(bool enable) +{ + prov_ctx.fast_prov.enable = enable; +} + +u8_t bt_mesh_provisioner_set_fast_prov_net_idx(const u8_t *net_key, u16_t net_idx) +{ + prov_ctx.fast_prov.net_idx = net_idx; + prov_ctx.fast_prov.net_key = net_key; + + if (!net_key) { + BT_WARN("Wait for NetKey for fast provisioning"); + return 0x01; /*status: wait for net_key */ + } + + return 0x0; /* status: success */ +} + +u16_t bt_mesh_provisioner_get_fast_prov_net_idx(void) +{ + return prov_ctx.fast_prov.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 <= prov_ctx.fast_prov.unicast_addr_max) { + BT_ERR("%s, Address overlap", __func__); + return 0x03; /* status: address overlaps with current value */ + } + + prov_ctx.fast_prov.unicast_addr_min = min; + prov_ctx.fast_prov.unicast_addr_max = max; + + prov_ctx.curr_alloc_addr = prov_ctx.fast_prov.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 */ + prov_ctx.fast_prov.flags = flags & BIT_MASK(2); + prov_ctx.fast_prov.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) +{ + int i; + + bt_mesh_pb_buf_lock(); + + for (i = 0; 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; + bt_mesh_adv_buf_ref_debug(__func__, buf, 3U, BLE_MESH_BUF_REF_SMALL); + /* Mark as canceled */ + BLE_MESH_ADV(buf)->busy = 0U; + net_buf_unref(buf); + } + + bt_mesh_pb_buf_unlock(); +} + +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 = NULL; + + 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 = NULL; + struct net_buf *buf = NULL; + + 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) +{ + int i; + + link[idx].tx.start = k_uptime_get(); + + for (i = 0; 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 = NULL; + + 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) +{ + int 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 = 0; 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 = NULL, *buf = NULL; + u8_t seg_len = 0U, seg_id = 0U; + u8_t xact_id = 0U; + 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 = 0; + + if (!link[idx].conn) { + return -ENOTCONN; + } + + err = bt_mesh_proxy_prov_client_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 = 0U, output_action = 0U, input_action = 0U; + u8_t element_num = 0U, pub_key_oob = 0U, static_oob = 0U, + output_size = 0U, input_size = 0U; + u8_t auth_method = 0U, auth_action = 0U, auth_size = 0U; + + element_num = data[0]; + BT_INFO("Elements: 0x%02x", 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_INFO("Algorithms: 0x%04x", algorithms); + if (algorithms != BIT(PROV_ALG_P256)) { + BT_ERR("%s, Invalid algorithms", __func__); + goto fail; + } + + pub_key_oob = data[3]; + BT_INFO("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_INFO("Static OOB Type: 0x%02x", static_oob); + if (static_oob > 0x01) { + BT_ERR("%s, Invalid Static OOB type", __func__); + goto fail; + } + static_oob = (prov_ctx.static_oob_len ? static_oob : 0x00); + + output_size = data[5]; + BT_INFO("Output OOB Size: 0x%02x", output_size); + if (output_size > 0x08) { + BT_ERR("%s, Invalid Output OOB size", __func__); + goto fail; + } + + output_action = sys_get_be16(&data[6]); + BT_INFO("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_INFO("Input OOB Size: 0x%02x", input_size); + if (input_size > 0x08) { + BT_ERR("%s, Invalid Input OOB size", __func__); + goto fail; + } + + input_action = sys_get_be16(&data[9]); + BT_INFO("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 = 0U; + bt_mesh_input_action_t input = 0U; + + link[idx].auth = (u8_t *)bt_mesh_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_ctx.static_oob_len, + prov_ctx.static_oob_val, prov_ctx.static_oob_len); + memset(link[idx].auth, 0, 16 - prov_ctx.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] = {'\0'}; + u8_t j = 0U; + + bt_mesh_rand(str, size); + + /* Normalize to '0' .. '9' & 'A' .. 'Z' */ + for (j = 0U; 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, PROV_AUTH_VAL_SIZE - 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 = 0U; + + 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 *)bt_mesh_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 *)bt_mesh_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_provisioner_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; + } + + BT_INFO("Link idx %d, type %s", idx, num_flag ? "number" : "string"); + + 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; +} + +int bt_mesh_provisioner_set_oob_output_data(const u8_t idx, const 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; + } + + BT_INFO("Link idx %d, type %s", idx, num_flag ? "number" : "string"); + + 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; +} + +int bt_mesh_provisioner_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 *)bt_mesh_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] = {0}; + + /* 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("Waiting for local public key"); + 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 *)bt_mesh_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); + u16_t prev_addr = BLE_MESH_ADDR_UNASSIGNED; + u16_t max_addr = BLE_MESH_ADDR_UNASSIGNED; + struct bt_mesh_node *node = NULL; + const u8_t *netkey = NULL; + u8_t session_key[16] = {0}; + u8_t nonce[13] = {0}; + u8_t pdu[25] = {0}; + int err = 0; + + 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_ENABLE()) { + netkey = prov_ctx.fast_prov.net_key; + if (!netkey) { + BT_ERR("%s, Failed to get NetKey for fast provisioning", __func__); + goto fail; + } + memcpy(pdu, netkey, 16); + sys_put_be16(prov_ctx.fast_prov.net_idx, &pdu[16]); + pdu[18] = prov_ctx.fast_prov.flags; + sys_put_be32(prov_ctx.fast_prov.iv_index, &pdu[19]); + } else { + netkey = bt_mesh_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]); + } + + /** + * 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. + */ + + /* Check if this device is a re-provisioned device */ + node = bt_mesh_provisioner_get_node_with_uuid(link[idx].uuid); + if (node) { + if (link[idx].element_num <= node->element_num) { + /** + * If the device is provisioned before, but the element number of + * the device is bigger now, then we treate it as a new device. + */ + prev_addr = node->unicast_addr; + } + bt_mesh_provisioner_remove_node(link[idx].uuid); + } + + max_addr = FAST_PROV_ENABLE() ? prov_ctx.fast_prov.unicast_addr_max : PROV_MAX_ADDR_TO_ASSIGN; + + if (BLE_MESH_ADDR_IS_UNICAST(prev_addr)) { + sys_put_be16(prev_addr, &pdu[23]); + link[idx].unicast_addr = prev_addr; + } else { + if (BLE_MESH_ADDR_IS_UNICAST(link[idx].assign_addr)) { + if (link[idx].assign_addr + link[idx].element_num - 1 > max_addr) { + BT_ERR("%s, Too large assigned address for the device", __func__); + goto fail; + } + + /* Make sure the assigned unicast address is not identical with any unicast address + * of other nodes. Also need to make sure the address is not identical with any + * address of Provisioner. + */ + if (bt_mesh_provisioner_check_is_addr_dup(link[idx].assign_addr, link[idx].element_num, true)) { + BT_ERR("%s, Assigned address 0x%04x is duplicated", __func__, link[idx].assign_addr); + goto fail; + } + + sys_put_be16(link[idx].assign_addr, &pdu[23]); + link[idx].unicast_addr = link[idx].assign_addr; + } else { + /* If this device to be provisioned is a new device */ + if (prov_ctx.curr_alloc_addr == BLE_MESH_ADDR_UNASSIGNED) { + BT_ERR("%s, No unicast address can be allocated", __func__); + goto fail; + } + + if (prov_ctx.curr_alloc_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.curr_alloc_addr, &pdu[23]); + link[idx].unicast_addr = prov_ctx.curr_alloc_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; + } + + /** + * We update the next unicast address to be allocated 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.curr_alloc_addr after the proper provisioning + * complete pdu is received. + */ + if (!BLE_MESH_ADDR_IS_UNICAST(prev_addr)) { + if (BLE_MESH_ADDR_IS_UNICAST(link[idx].assign_addr)) { + /* Even if the unicast address of the node is assigned by the + * application, we will also update the prov_ctx.curr_alloc_addr + * here, in case Users use the two methods together (i.e. allocate + * the unicast address for the node internally and assign the + * unicast address for the node from application). + */ + if (prov_ctx.curr_alloc_addr < link[idx].assign_addr + link[idx].element_num) { + prov_ctx.curr_alloc_addr = link[idx].assign_addr + link[idx].element_num; + } + } else { + prov_ctx.curr_alloc_addr += link[idx].element_num; + if (prov_ctx.curr_alloc_addr > max_addr) { + /* No unicast address will be used for further provisioning */ + prov_ctx.curr_alloc_addr = BLE_MESH_ADDR_UNASSIGNED; + } + } + /* Store the available unicast address range to flash */ + if (IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_store_prov_info(prov_ctx.primary_addr, prov_ctx.curr_alloc_addr); + } + } + + if (FAST_PROV_ENABLE()) { + link[idx].ki_flags = prov_ctx.fast_prov.flags; + link[idx].iv_index = prov_ctx.fast_prov.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] = {0}; + + 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. bt_mesh_calloc memory for prov_salt + * 2. calculate prov_salt + * 3. prepare provisioning data and send + */ + link[idx].prov_salt = (u8_t *)bt_mesh_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] = {0}; + u16_t net_idx = 0U; + u16_t index = 0U; + u16_t rm = 0U; + int err = 0; + + /* 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); + } + + 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; + } + + if (FAST_PROV_ENABLE()) { + net_idx = prov_ctx.fast_prov.net_idx; + } else { + net_idx = prov_ctx.curr_net_idx; + } + err = bt_mesh_provisioner_provision(&link[idx].addr, link[idx].uuid, link[idx].oob_info, + link[idx].unicast_addr, link[idx].element_num, net_idx, + link[idx].ki_flags, link[idx].iv_index, device_key, &index); + 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(index, link[idx].uuid, link[idx].unicast_addr, + link[idx].element_num, 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("Device is not found in queue"); + } else { + BT_ERR("Failed to remove device from queue"); + } + + 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_WARN("%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; + int i; + + BT_DBG("%s", __func__); + + if (!bt_mesh_atomic_test_bit(link[idx].flags, LINK_ACTIVE)) { + BT_WARN("Link is not active"); + 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); + } + + bt_mesh_pb_buf_lock(); + + for (i = 0; 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); + } + } + + bt_mesh_pb_buf_unlock(); +} + +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_INFO("%s, Link ACK is already received", __func__); + return; + } + + link[idx].conf_inputs = (u8_t *)bt_mesh_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 = 0U; + + 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_INFO("%s, Resending ack", __func__); + gen_prov_ack_send(idx, rx->xact_id); + return; + } + + if (rx->xact_id != link[idx].rx.trans_id) { + BT_WARN("Data for unknown transaction (%u != %u)", + rx->xact_id, link[idx].rx.trans_id); + 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 = 0U; + + 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_INFO("%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 = 0U, pub_key_oob = 0U; + + 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_INFO("%s, Get Start while there are unreceived segments", __func__); + return; + } + + if (link[idx].rx.prev_id == rx->xact_id) { + BT_INFO("%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) +{ + int i; + + /* link for PB-ADV is from 0 to CONFIG_BLE_MESH_PBA_SAME_TIME */ + for (i = 0; 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 bt_mesh_provisioner_pb_adv_recv(struct net_buf_simple *buf) +{ + struct prov_rx rx = {0}; + u8_t idx = 0U; + + 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) +{ + int 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 bt_mesh_provisioner_pb_gatt_recv(struct bt_mesh_conn *conn, struct net_buf_simple *buf) +{ + u8_t type = 0U; + u8_t idx = 0U; + + 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 bt_mesh_provisioner_set_prov_conn(const u8_t addr[6], struct bt_mesh_conn *conn) +{ + int 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 bt_mesh_provisioner_pb_gatt_open(struct bt_mesh_conn *conn, u8_t *addr) +{ + u8_t idx = 0U; + int 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 = 0; 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("Provision using PB-GATT & PB-ADV same time"); + 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 *)bt_mesh_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 bt_mesh_provisioner_pb_gatt_close(struct bt_mesh_conn *conn, u8_t reason) +{ + u8_t idx = 0U; + + 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 bt_mesh_provisioner_prov_init(const struct bt_mesh_prov *prov_info) +{ + const u8_t *key = NULL; + int i; + + if (!prov_info) { + BT_ERR("%s, No provisioning context provided", __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; + + prov_ctx.primary_addr = BLE_MESH_ADDR_UNASSIGNED; + + if (prov->prov_static_oob_val && prov->prov_static_oob_len) { + prov_ctx.static_oob_len = MIN(16, prov->prov_static_oob_len); + memcpy(prov_ctx.static_oob_val, prov->prov_static_oob_val, prov_ctx.static_oob_len); + } + +#if defined(CONFIG_BLE_MESH_PB_ADV) + for (i = 0; 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 = 0; i < BLE_MESH_PROV_SAME_TIME; i++) { + k_delayed_work_init(&link[i].timeout, prov_timeout); + link[i].timeout.work.index = (int)i; + } + +#if defined(CONFIG_BLE_MESH_PB_ADV) + bt_mesh_pb_adv_mutex_new(); + bt_mesh_pb_buf_mutex_new(); +#endif +#if defined(CONFIG_BLE_MESH_PB_GATT) + bt_mesh_pb_gatt_mutex_new(); +#endif + + return 0; +} + +int bt_mesh_provisioner_prov_deinit(bool erase) +{ + int i; + + if (prov == NULL) { + BT_ERR("%s, No provisioning context provided", __func__); + return -EINVAL; + } + +#if defined(CONFIG_BLE_MESH_PB_ADV) + for (i = 0; i < CONFIG_BLE_MESH_PBA_SAME_TIME; i++) { + prov_clear_tx(i); + k_delayed_work_free(&link[i].tx.retransmit); +#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[i].link_id); +#endif /* CONFIG_BLE_MESH_USE_DUPLICATE_SCAN */ + } +#endif /* CONFIG_BLE_MESH_PB_ADV */ + + for (i = 0; i < BLE_MESH_PROV_SAME_TIME; i++) { + prov_memory_free(i); + k_delayed_work_free(&link[i].timeout); + memset(&link[i], 0, sizeof(link[i])); + } + +#if defined(CONFIG_BLE_MESH_PB_ADV) + bt_mesh_pb_adv_mutex_free(); + bt_mesh_pb_buf_mutex_free(); +#endif +#if defined(CONFIG_BLE_MESH_PB_GATT) + bt_mesh_pb_gatt_mutex_free(); +#endif + memset(&prov_ctx, 0, sizeof(prov_ctx)); + +#if defined(CONFIG_BLE_MESH_PB_ADV) + memset(adv_buf, 0, sizeof(adv_buf)); + memset(adv_buf_data, 0, sizeof(adv_buf_data)); +#endif + memset(unprov_dev, 0, sizeof(unprov_dev)); + + if (erase && IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_prov_info(); + } + + prov = NULL; + + 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, s8_t rssi) +{ + u16_t index = 0U; + + 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, rssi); + } + 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, rssi); + } + return true; + } + } + + return false; +} + +void bt_mesh_provisioner_unprov_beacon_recv(struct net_buf_simple *buf, s8_t rssi) +{ +#if defined(CONFIG_BLE_MESH_PB_ADV) + const bt_mesh_addr_t *addr = NULL; + const u8_t *uuid = NULL; + u16_t oob_info = 0U; + + if (!(prov_ctx.bearers & BLE_MESH_PROV_ADV)) { + BT_WARN("Provisioner not support PB-ADV bearer"); + return; + } + + if (buf->len != 0x12 && buf->len != 0x16) { + BT_ERR("%s, Invalid Unprovisioned Device Beacon length", __func__); + 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, BLE_MESH_PROV_ADV)) { + return; + } + + if (is_unprov_dev_info_callback_to_app( + BLE_MESH_PROV_ADV, uuid, addr, oob_info, rssi)) { + return; + } + + provisioner_start_prov_pb_adv(uuid, addr, oob_info, BLE_MESH_ADDR_UNASSIGNED); +#endif /* CONFIG_BLE_MESH_PB_ADV */ +} + +void bt_mesh_provisioner_prov_adv_ind_recv(struct net_buf_simple *buf, const bt_mesh_addr_t *addr, s8_t rssi) +{ +#if defined(CONFIG_BLE_MESH_PB_GATT) + const u8_t *uuid = NULL; + u16_t oob_info = 0U; + + if (!(prov_ctx.bearers & BLE_MESH_PROV_GATT)) { + BT_WARN("Provisioner not support PB-GATT bearer"); + return; + } + + if (bt_mesh_gattc_get_free_conn_count() == 0) { + BT_INFO("BLE connections for mesh 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, BLE_MESH_PROV_GATT)) { + return; + } + + if (is_unprov_dev_info_callback_to_app( + BLE_MESH_PROV_GATT, uuid, addr, oob_info, rssi)) { + 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, BLE_MESH_ADDR_UNASSIGNED); +#endif /* CONFIG_BLE_MESH_PB_GATT */ +} + +#endif /* CONFIG_BLE_MESH_PROVISIONER */ diff --git a/components/bt/esp_ble_mesh/mesh_core/provisioner_prov.h b/components/bt/esp_ble_mesh/mesh_core/provisioner_prov.h new file mode 100644 index 0000000000..7642e2c8ef --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/provisioner_prov.h @@ -0,0 +1,416 @@ +// Copyright 2017-2019 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_main.h" +#include "mesh_bearer_adapt.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 bt_mesh_provisioner_pbg_count_dec(void); + +/** + * @brief This function clears the part of the link info of the proper device. + * + * @param[in] addr: Remote device address + * + * @return None + */ +void bt_mesh_provisioner_clear_link_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 bt_mesh_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 bt_mesh_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 bt_mesh_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 bt_mesh_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 bt_mesh_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 bt_mesh_provisioner_prov_init(const struct bt_mesh_prov *prov_info); + +/** + * @brief This function deinitializes provisioner's PB-GATT and PB-ADV + * related information. + * + * @param[in] erase: Indicate if erasing provisioning information from flash. + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_provisioner_prov_deinit(bool erase); + +/** + * @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 + * @param[in] rssi: RSSI of the received unprovisioned device beacon + * + * @return None + */ +void bt_mesh_provisioner_unprov_beacon_recv(struct net_buf_simple *buf, s8_t rssi); + +void bt_mesh_provisioner_prov_adv_ind_recv(struct net_buf_simple *buf, const bt_mesh_addr_t *addr, s8_t rssi); + +/** + * @brief This function gets the bt_mesh_prov pointer. + * + * @return bt_mesh_prov pointer(prov) + */ +const struct bt_mesh_prov *bt_mesh_provisioner_get_prov_info(void); + +void bt_mesh_provisoner_restore_prov_info(u16_t primary_addr, u16_t alloc_addr); + +/* 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 Provision an unprovisioned device with fixed unicast address. + * + * @param[in] uuid: Device UUID of the unprovisioned device + * @param[in] addr: Device address of the unprovisioned device + * @param[in] addr_type: Device address type of the unprovisioned device + * @param[in] bearer: Provisioning bearer going to be used + * @param[in] oob_info: OOB info of the unprovisioned device + * @param[in] unicast_addr: Unicast address going to be allocated for the unprovisioned device + * + * @return Zero on success or (negative) error code otherwise. + * + * @note 1. Currently address type only supports public address and static random address. + * 2. Bearer must be equal to BLE_MESH_PROV_ADV or BLE_MESH_PROV_GATT + */ +int bt_mesh_provisioner_prov_device_with_addr(const u8_t uuid[16], const u8_t addr[6], + u8_t addr_type, bt_mesh_prov_bearer_t bearer, + u16_t oob_info, u16_t unicast_addr); + +/** @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. + * @param rssi RSSI of the received advertising packet + * + */ +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, s8_t rssi); + +/** + * @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_provisioner_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 sets the provisioning information needed by Provisioner, + * including unicast address, IV Index, etc. + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_provisioner_set_prov_info(void); + +/** + * @brief This function sets the provisioning bearer type used by Provisioner. + * + * @param[in] bearers: Provisioning bearer type + * @param[in] clear: Indicate if the corresponding bearer type will be cleared + * + * @return None + */ +void bt_mesh_provisioner_set_prov_bearer(bt_mesh_prov_bearer_t bearers, bool clear); + +/** + * @brief This function gets the provisioning bearer type used by Provisioner. + * + * @return Currently supported provisioning bearer type + */ +bt_mesh_prov_bearer_t bt_mesh_provisioner_get_prov_bearer(void); + +/** + * @brief This function sets the Static OOB value used by Provisioner. + * + * @param[in] value: Static OOB value + * @param[in] length: Static OOB value length + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_provisioner_set_static_oob_value(const u8_t *value, u8_t length); + +/** + * @brief This function gets the unicast address of primary element of Provisioner. + * + * @return Unicast address of primary element of Provisioner. + */ +u16_t bt_mesh_provisioner_get_primary_elem_addr(void); + +/** + * @brief This function sets the unicast address of primary element of Provisioner. + * + * @param[in] addr: unicast address of primary element + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_provisioner_set_primary_elem_addr(u16_t addr); + +/** + * @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_provisioner_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 + */ +int bt_mesh_provisioner_set_oob_output_data(const u8_t idx, const u8_t *num, u8_t size, bool num_flag); + +/** + * @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_provisioner_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] enable: Enable or disable fast provisioning + * + * @return None + */ +void bt_mesh_provisioner_fast_prov_enable(bool enable); + +/** + * @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 bt_mesh_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 bt_mesh_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/esp_ble_mesh/mesh_core/proxy_client.c b/components/bt/esp_ble_mesh/mesh_core/proxy_client.c new file mode 100644 index 0000000000..fc34726676 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/proxy_client.c @@ -0,0 +1,999 @@ +// Copyright 2017-2019 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.h" +#include "access.h" +#include "beacon.h" +#include "mesh_common.h" +#include "foundation.h" +#include "proxy_client.h" +#include "provisioner_prov.h" +#include "provisioner_main.h" +#include "mesh_bearer_adapt.h" + +#define PDU_TYPE(data) (data[0] & BIT_MASK(6)) +#define PDU_SAR(data) (data[0] >> 6) + +#define PROXY_SAR_TIMEOUT K_SECONDS(20) + +#define SAR_COMPLETE 0x00 +#define SAR_FIRST 0x01 +#define SAR_CONT 0x02 +#define SAR_LAST 0x03 + +#define PDU_HDR(sar, type) (sar << 6 | (type & BIT_MASK(6))) + +#define SERVER_BUF_SIZE 68 + +#if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_CLIENT + +static struct bt_mesh_proxy_server { + struct bt_mesh_conn *conn; + enum __packed { + NONE, + PROV, + PROXY, + } conn_type; +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT + u16_t net_idx; +#endif + u8_t msg_type; + struct k_delayed_work sar_timer; + 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 void proxy_sar_timeout(struct k_work *work) +{ + struct bt_mesh_proxy_server *server = NULL; + + BT_WARN("%s", __func__); + + server = CONTAINER_OF(work, struct bt_mesh_proxy_server, sar_timer.work); + if (!server || !server->conn) { + BT_ERR("%s, Invalid proxy server parameter", __func__); + return; + } + + net_buf_simple_reset(&server->buf); + bt_mesh_gattc_disconnect(server->conn); +} + +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT +/** + * The following callbacks are used to notify proper information + * to the application layer. + */ +static proxy_client_recv_adv_cb_t proxy_client_adv_recv_cb; +static proxy_client_connect_cb_t proxy_client_connect_cb; +static proxy_client_disconnect_cb_t proxy_client_disconnect_cb; +static proxy_client_recv_filter_status_cb_t proxy_client_filter_status_recv_cb; + +void bt_mesh_proxy_client_set_adv_recv_cb(proxy_client_recv_adv_cb_t cb) +{ + proxy_client_adv_recv_cb = cb; +} + +void bt_mesh_proxy_client_set_conn_cb(proxy_client_connect_cb_t cb) +{ + proxy_client_connect_cb = cb; +} + +void bt_mesh_proxy_client_set_disconn_cb(proxy_client_disconnect_cb_t cb) +{ + proxy_client_disconnect_cb = cb; +} + +void bt_mesh_proxy_client_set_filter_status_cb(proxy_client_recv_filter_status_cb_t cb) +{ + proxy_client_filter_status_recv_cb = cb; +} + +static void filter_status(struct bt_mesh_proxy_server *server, + struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + u8_t filter_type = 0U; + u16_t list_size = 0U; + + if (buf->len != 3) { + BT_ERR("%s, Invalid Proxy Filter Status length %d", __func__, buf->len); + return; + } + + filter_type = net_buf_simple_pull_u8(buf); + if (filter_type > 0x01) { + BT_ERR("%s, Invalid filter type 0x%02x", __func__, filter_type); + return; + } + + list_size = net_buf_simple_pull_be16(buf); + + BT_INFO("%s, filter_type 0x%02x list_size %d", __func__, filter_type, list_size); + + if (proxy_client_filter_status_recv_cb) { + proxy_client_filter_status_recv_cb(server - servers, rx->ctx.addr, server->net_idx, filter_type, list_size); + } + + return; +} + +static void proxy_cfg(struct bt_mesh_proxy_server *server) +{ + NET_BUF_SIMPLE_DEFINE(buf, 29); + struct bt_mesh_net_rx rx = {0}; + u8_t opcode = 0U; + int err = 0; + + 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; + } + + if (!BLE_MESH_ADDR_IS_UNICAST(rx.ctx.addr)) { + BT_ERR("%s, Proxy Configuration from non-unicast addr 0x%04x", __func__, rx.ctx.addr); + 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 < 3) { + BT_WARN("Too short proxy configuration PDU"); + return; + } + + opcode = net_buf_simple_pull_u8(&buf); + + switch (opcode) { + case BLE_MESH_PROXY_CFG_FILTER_STATUS: + filter_status(server, &rx, &buf); + break; + default: + BT_WARN("Unknown Proxy Configuration OpCode 0x%02x", opcode); + break; + } +} +#endif /* CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ + +static void proxy_complete_pdu(struct bt_mesh_proxy_server *server) +{ + switch (server->msg_type) { +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT + 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"); + bt_mesh_beacon_recv(&server->buf, 0); + break; + case BLE_MESH_PROXY_CONFIG: + BT_DBG("Mesh Configuration PDU"); + proxy_cfg(server); + break; +#endif /* CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + case BLE_MESH_PROXY_PROV: + BT_DBG("Mesh Provisioning PDU"); + bt_mesh_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 = 0U; + + if (!server) { + BT_ERR("%s, No Proxy Server object found", __func__); + 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; + } + + k_delayed_work_submit(&server->sar_timer, PROXY_SAR_TIMEOUT); + 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; + } + + k_delayed_work_submit(&server->sar_timer, PROXY_SAR_TIMEOUT); + 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; + } + + k_delayed_work_cancel(&server->sar_timer); + net_buf_simple_add_mem(&server->buf, data + 1, len - 1); + proxy_complete_pdu(server); + break; + } + + return len; +} + + +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)); + + return bt_mesh_gattc_write_no_rsp(conn, NULL, data, len); +} + +static int proxy_segment_and_send(struct bt_mesh_conn *conn, u8_t type, + struct net_buf_simple *msg) +{ + u16_t mtu = 0U; + int err = 0; + + 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)); + err = 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)); + err = proxy_send(conn, msg->data, msg->len); + break; + } + + net_buf_simple_push_u8(msg, PDU_HDR(SAR_CONT, type)); + err = proxy_send(conn, msg->data, mtu); + net_buf_simple_pull(msg, mtu); + } + + return err; +} + +int bt_mesh_proxy_prov_client_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 object found", __func__); + return -ENOTCONN; + } + + if ((server->conn_type == PROV) != (type == BLE_MESH_PROXY_PROV)) { + BT_ERR("%s, Invalid PDU type for Proxy Server", __func__); + return -EINVAL; + } + + return proxy_segment_and_send(conn, type, msg); +} + +static void proxy_connected(bt_mesh_addr_t *addr, struct bt_mesh_conn *conn, int id) +{ + struct bt_mesh_proxy_server *server = NULL; + + if (!servers[id].conn) { + server = &servers[id]; + } + + if (!server) { + BT_ERR("%s, No free Proxy Server objects", __func__); + /** Disconnect current connection, clear part of prov_link + * information, like uuid, dev_addr, linking flag, etc. + */ + bt_mesh_gattc_disconnect(conn); + return; + } + + server->conn = bt_mesh_conn_ref(conn); + server->conn_type = NONE; + net_buf_simple_reset(&server->buf); + + bt_mesh_gattc_exchange_mtu(id); + return; +} + +static void proxy_disconnected(bt_mesh_addr_t *addr, struct bt_mesh_conn *conn, u8_t reason) +{ + struct bt_mesh_proxy_server *server = find_server(conn); + + BT_DBG("conn %p, handle is %d, reason 0x%02x", conn, conn->handle, reason); + + if (!server) { + BT_ERR("%s, No Proxy Server object found", __func__); + return; + } + +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + if (server->conn_type == PROV) { + bt_mesh_provisioner_pb_gatt_close(conn, reason); + } +#endif + +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT + if (server->conn_type == PROXY) { + if (proxy_client_disconnect_cb) { + proxy_client_disconnect_cb(addr, server - servers, server->net_idx, reason); + } + } +#endif + + k_delayed_work_cancel(&server->sar_timer); + server->conn = NULL; + server->conn_type = NONE; +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT + server->net_idx = BLE_MESH_KEY_UNUSED; +#endif + + return; +} + +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT +static ssize_t prov_write_ccc(bt_mesh_addr_t *addr, struct bt_mesh_conn *conn) +{ + struct bt_mesh_proxy_server *server = find_server(conn); + + if (!server) { + BT_ERR("%s, No Proxy Server object found", __func__); + return -ENOTCONN; + } + + if (server->conn_type == NONE) { + server->conn_type = PROV; + + if (bt_mesh_provisioner_set_prov_conn(addr->val, server->conn)) { + BT_ERR("%s, bt_mesh_provisioner_set_prov_conn failed", __func__); + bt_mesh_gattc_disconnect(server->conn); + return -EIO; + } + return bt_mesh_provisioner_pb_gatt_open(conn, addr->val); + } + + return -ENOMEM; +} + +static ssize_t prov_recv_ntf(struct bt_mesh_conn *conn, u8_t *data, u16_t len) +{ + struct bt_mesh_proxy_server *server = find_server(conn); + + if (!server) { + BT_ERR("%s, No Proxy Server object found", __func__); + return -ENOTCONN; + } + + if (server->conn_type == PROV) { + return proxy_recv(conn, NULL, data, len, 0, 0); + } + + return -EINVAL; +} + +int bt_mesh_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].conn_type = PROV; + } + } + + return 0; +} + +int bt_mesh_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->conn_type == PROV) { + bt_mesh_gattc_disconnect(server->conn); + server->conn_type = NONE; + } + } + + return 0; +} +#endif /* CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT */ + +#if defined(CONFIG_BLE_MESH_GATT_PROXY_CLIENT) +static ssize_t proxy_write_ccc(bt_mesh_addr_t *addr, struct bt_mesh_conn *conn) +{ + struct bt_mesh_proxy_server *server = find_server(conn); + + if (!server) { + BT_ERR("%s, No Proxy Server object found", __func__); + return -ENOTCONN; + } + + if (server->conn_type == NONE) { + server->conn_type = PROXY; + + if (proxy_client_connect_cb) { + proxy_client_connect_cb(addr, server - servers, server->net_idx); + } + return 0; + } + + return -EINVAL; +} + +static ssize_t proxy_recv_ntf(struct bt_mesh_conn *conn, u8_t *data, u16_t len) +{ + struct bt_mesh_proxy_server *server = find_server(conn); + + if (!server) { + BT_ERR("%s, No Proxy Server object found", __func__); + return -ENOTCONN; + } + + if (server->conn_type == PROXY) { + return proxy_recv(conn, NULL, data, len, 0, 0); + } + + return -EINVAL; +} + +/** + * Currently proxy client does't need bt_mesh_proxy_client_enable() and + * bt_mesh_proxy_client_disable() functions, and once they are used, + * proxy client 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, proxy client can stop handling these + * two kinds of connectable advertising packets. + */ +int bt_mesh_proxy_client_enable(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + if (servers[i].conn) { + servers[i].conn_type = PROXY; + } + } + + /** + * TODO: + * Once at leat one device has been provisioned, proxy client 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; +} + +int bt_mesh_proxy_client_disable(void) +{ + int i; + + BT_DBG("%s", __func__); + + /** + * TODO: + * Once this function is invoked, proxy client shall stop handling + * node_id & net_id adv packets, and if proxy connection exists, + * it should be disconnected. + */ + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + struct bt_mesh_proxy_server *server = &servers[i]; + + if (server->conn && server->conn_type == PROXY) { + bt_mesh_gattc_disconnect(server->conn); + server->conn_type = NONE; + } + } + + return 0; +} +#endif /* CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ + +static struct bt_mesh_prov_conn_cb conn_callbacks = { + .connected = proxy_connected, + .disconnected = proxy_disconnected, +#if CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT + .prov_write_descr = prov_write_ccc, + .prov_notify = prov_recv_ntf, +#endif /* CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT */ +#if defined(CONFIG_BLE_MESH_GATT_PROXY_CLIENT) + .proxy_write_descr = proxy_write_ccc, + .proxy_notify = proxy_recv_ntf, +#endif /* CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ +}; + +#if defined(CONFIG_BLE_MESH_GATT_PROXY_CLIENT) +static struct bt_mesh_subnet *bt_mesh_is_net_id_exist(const u8_t net_id[8]) +{ + struct bt_mesh_subnet *sub = NULL; + size_t size = 0U, i = 0U; + + size = bt_mesh_rx_netkey_size(); + + for (i = 0U; i < size; i++) { + sub = bt_mesh_rx_netkey_get(i); + if (sub && !memcmp(sub->keys[sub->kr_flag].net_id, net_id, 8)) { + return sub; + } + } + + return NULL; +} + +void bt_mesh_proxy_client_adv_ind_recv(struct net_buf_simple *buf, const bt_mesh_addr_t *addr, s8_t rssi) +{ + bt_mesh_proxy_adv_ctx_t ctx = {0}; + u8_t type = 0U; + + /* Check if connection reaches the maximum limitation */ + if (bt_mesh_gattc_get_free_conn_count() == 0) { + BT_INFO("BLE connections for mesh reach max limit"); + return; + } + + type = net_buf_simple_pull_u8(buf); + + switch (type) { + case BLE_MESH_PROXY_ADV_NET_ID: { + struct bt_mesh_subnet *sub = NULL; + sub = bt_mesh_is_net_id_exist(buf->data); + if (!sub) { + return; + } + + memcpy(ctx.net_id.net_id, buf->data, buf->len); + ctx.net_id.net_idx = sub->net_idx; + break; + } + case BLE_MESH_PROXY_ADV_NODE_ID: + /* Gets node identity information. + * hash = aes-ecb(identity key, 16 octets(padding + random + src)) mod 2^64, + * If Proxy Client wants to get src, it may encrypts multiple times and compare + * the hash value (8 octets) with the received one. + */ + return; + default: + BT_DBG("%s, Unknwon Mesh Proxy adv type 0x%02x", __func__, type); + return; + } + + if (proxy_client_adv_recv_cb) { + proxy_client_adv_recv_cb(addr, type, &ctx, rssi); + } +} + +int bt_mesh_proxy_client_connect(const u8_t addr[6], u8_t addr_type, u16_t net_idx) +{ + bt_mesh_addr_t remote_addr = {0}; + int result = 0; + + if (!addr || addr_type > BLE_MESH_ADDR_RANDOM) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + memcpy(remote_addr.val, addr, BLE_MESH_ADDR_LEN); + remote_addr.type = addr_type; + + result = bt_mesh_gattc_conn_create(&remote_addr, BLE_MESH_UUID_MESH_PROXY_VAL); + if (result < 0) { + return result; + } + + /* Store corresponding net_idx which can be used for sending Proxy Configuration */ + servers[result].net_idx = net_idx; + return 0; +} + +int bt_mesh_proxy_client_disconnect(u8_t conn_handle) +{ + struct bt_mesh_conn *conn = NULL; + + if (conn_handle >= BLE_MESH_MAX_CONN) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + BT_DBG("conn_handle %d", conn_handle); + + conn = servers[conn_handle].conn; + if (!conn) { + BT_ERR("%s, Not connected, conn_handle %d", __func__, conn_handle); + return -ENOTCONN; + } + + bt_mesh_gattc_disconnect(conn); + return 0; +} + +bool bt_mesh_proxy_client_send(struct net_buf_simple *buf, u16_t dst) +{ + bool send = false; + int err = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + struct bt_mesh_proxy_server *server = &servers[i]; + NET_BUF_SIMPLE_DEFINE(msg, 32); + + if (!server->conn || server->conn_type != PROXY) { + continue; + } + + /* Proxy PDU sending modifies the original buffer, + * so we need to make a copy. + */ + net_buf_simple_init(&msg, 1); + net_buf_simple_add_mem(&msg, buf->data, buf->len); + + err = bt_mesh_proxy_prov_client_send(server->conn, BLE_MESH_PROXY_NET_PDU, &msg); + if (err) { + BT_ERR("%s, Failed to send proxy net message (err %d)", __func__, err); + } else { + BT_INFO("%u bytes to dst 0x%04x", buf->len, dst); + send = true; + } + } + + return send; +} + +static int beacon_send(struct bt_mesh_conn *conn, struct bt_mesh_subnet *sub) +{ + NET_BUF_SIMPLE_DEFINE(buf, 23); + + net_buf_simple_init(&buf, 1); + bt_mesh_beacon_create(sub, &buf); + + return bt_mesh_proxy_prov_client_send(conn, BLE_MESH_PROXY_BEACON, &buf); +} + +bool bt_mesh_proxy_client_beacon_send(struct bt_mesh_subnet *sub) +{ + bool send = false; + int err = 0; + int i; + + /* NULL means we send Secure Network Beacon on all subnets */ + if (!sub) { +#if CONFIG_BLE_MESH_NODE + if (bt_mesh_is_provisioned()) { + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + if (bt_mesh.sub[i].net_idx != BLE_MESH_KEY_UNUSED) { + send = bt_mesh_proxy_client_beacon_send(&bt_mesh.sub[i]); + } + } + return send; + } +#endif /* CONFIG_BLE_MESH_NODE */ +#if CONFIG_BLE_MESH_PROVISIONER + if (bt_mesh_is_provisioner_en()) { + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + if (bt_mesh.p_sub[i] && bt_mesh.p_sub[i]->net_idx != BLE_MESH_KEY_UNUSED) { + send = bt_mesh_proxy_client_beacon_send(bt_mesh.p_sub[i]); + } + } + return send; + } +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + return send; + } + + for (i = 0; i < ARRAY_SIZE(servers); i++) { + if (servers[i].conn && servers[i].conn_type == PROXY) { + err = beacon_send(servers[i].conn, sub); + if (err) { + BT_ERR("%s, Failed to send proxy beacon message (err %d)", __func__, err); + } else { + send = true; + } + } + } + + return send; +} + +static int send_proxy_cfg(struct bt_mesh_conn *conn, u16_t net_idx, struct bt_mesh_proxy_cfg_pdu *cfg) +{ + struct bt_mesh_msg_ctx ctx = { + .net_idx = net_idx, + .app_idx = BLE_MESH_KEY_UNUSED, /* CTL shall be set to 1 */ + .addr = BLE_MESH_ADDR_UNASSIGNED, /* DST shall be set to the unassigned address */ + .send_ttl = 0U, /* TTL shall be set to 0 */ + }; + struct bt_mesh_net_tx tx = { + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + }; + struct net_buf_simple *buf = NULL; + u16_t alloc_len = 0U; + int err = 0; + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_provisioned()) { + tx.sub = bt_mesh_subnet_get(net_idx); + } else if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en()) { + tx.sub = bt_mesh_provisioner_subnet_get(net_idx); + } + if (!tx.sub) { + BT_ERR("%s, Failed to find subnet", __func__); + return -EIO; + } + + switch (cfg->opcode) { + case BLE_MESH_PROXY_CFG_FILTER_SET: + if (cfg->set.filter_type > 0x01) { + BT_ERR("%s, Invalid filter type 0x%02x", __func__, cfg->set.filter_type); + return -EINVAL; + } + + alloc_len = sizeof(cfg->opcode) + sizeof(cfg->set.filter_type); + break; + case BLE_MESH_PROXY_CFG_FILTER_ADD: + if (cfg->add.addr == NULL || cfg->add.addr_num == 0) { + BT_ERR("%s, Add address list is NULL", __func__); + return -EINVAL; + } + + alloc_len = sizeof(cfg->opcode) + (cfg->add.addr_num << 1); + break; + case BLE_MESH_PROXY_CFG_FILTER_REMOVE: + if (cfg->remove.addr == NULL || cfg->remove.addr_num == 0) { + BT_ERR("%s, Remove address list is NULL", __func__); + return -EINVAL; + } + + alloc_len = sizeof(cfg->opcode) + (cfg->remove.addr_num << 1); + break; + default: + BT_ERR("%s, Unknown Proxy Configuration opcode 0x%02x", __func__, cfg->opcode); + return -EINVAL; + } + + /** + * For Proxy Configuration PDU: + * 1 octet Proxy PDU type + 9 octets network pdu header + Tranport PDU + 8 octets NetMIC + */ + buf = bt_mesh_alloc_buf(1 + BLE_MESH_NET_HDR_LEN + alloc_len + 8); + if (!buf) { + return -ENOMEM; + } + + net_buf_simple_reset(buf); + net_buf_simple_reserve(buf, 10); + + net_buf_simple_add_u8(buf, cfg->opcode); + switch (cfg->opcode) { + case BLE_MESH_PROXY_CFG_FILTER_SET: + net_buf_simple_add_u8(buf, cfg->set.filter_type); + break; + case BLE_MESH_PROXY_CFG_FILTER_ADD: + for (u16_t i = 0U; i < cfg->add.addr_num; i++) { + net_buf_simple_add_le16(buf, cfg->add.addr[i]); + } + break; + case BLE_MESH_PROXY_CFG_FILTER_REMOVE: + for (u16_t i = 0U; i < cfg->remove.addr_num; i++) { + net_buf_simple_add_le16(buf, cfg->remove.addr[i]); + } + break; + } + + BT_DBG("%s, len %u bytes: %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + err = bt_mesh_net_encode(&tx, buf, true); + if (err) { + BT_ERR("%s, Encoding Proxy message failed (err %d)", __func__, err); + bt_mesh_free_buf(buf); + return err; + } + + err = bt_mesh_proxy_prov_client_send(conn, BLE_MESH_PROXY_CONFIG, buf); + if (err) { + BT_ERR("%s, Failed to send proxy cfg message (err %d)", __func__, err); + } + + bt_mesh_free_buf(buf); + return err; +} + +int bt_mesh_proxy_client_send_cfg(u8_t conn_handle, u16_t net_idx, struct bt_mesh_proxy_cfg_pdu *pdu) +{ + struct bt_mesh_conn *conn = NULL; + + if (conn_handle >= BLE_MESH_MAX_CONN || !pdu || pdu->opcode > BLE_MESH_PROXY_CFG_FILTER_REMOVE) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + BT_DBG("conn_handle %d, net_idx 0x%04x", conn_handle, net_idx); + + conn = servers[conn_handle].conn; + if (!conn) { + BT_ERR("%s, Not connected, conn_handle %d", __func__, conn_handle); + return -ENOTCONN; + } + + /** + * Check if net_idx used to encrypt Proxy Configuration are the same + * with the one added when creating proxy connection. + */ + if (servers[conn_handle].net_idx != net_idx) { + BT_ERR("%s, NetKey Index 0x%04x mismatch, expect 0x%04x", + __func__, net_idx, servers[conn_handle].net_idx); + return -EIO; + } + + return send_proxy_cfg(conn, net_idx, pdu); +} +#endif /* CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ + +int bt_mesh_proxy_prov_client_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]; + + k_delayed_work_init(&server->sar_timer, proxy_sar_timeout); + server->buf.size = SERVER_BUF_SIZE; + server->buf.__buf = server_buf_data + (i * SERVER_BUF_SIZE); +#if CONFIG_BLE_MESH_GATT_PROXY_CLIENT + server->net_idx = BLE_MESH_KEY_UNUSED; +#endif + } + + bt_mesh_gattc_conn_cb_register(&conn_callbacks); + +#if CONFIG_BLE_MESH_USE_DUPLICATE_SCAN && CONFIG_BLE_MESH_GATT_PROXY_CLIENT + bt_mesh_update_exceptional_list(BLE_MESH_EXCEP_LIST_ADD, + BLE_MESH_EXCEP_INFO_MESH_PROXY_ADV, NULL); +#endif + + return 0; +} + +int bt_mesh_proxy_prov_client_deinit(void) +{ + int i; + + /* Initialize the server receive buffers */ + for (i = 0; i < ARRAY_SIZE(servers); i++) { + struct bt_mesh_proxy_server *server = &servers[i]; + k_delayed_work_free(&server->sar_timer); + memset(server, 0, sizeof(struct bt_mesh_proxy_server)); + } + + memset(server_buf_data, 0, sizeof(server_buf_data)); + + bt_mesh_gattc_conn_cb_deregister(); + + return 0; +} + +#endif /* (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_PB_GATT) || CONFIG_BLE_MESH_GATT_PROXY_CLIENT */ diff --git a/components/bt/esp_ble_mesh/mesh_core/proxy_client.h b/components/bt/esp_ble_mesh/mesh_core/proxy_client.h new file mode 100644 index 0000000000..18943f8243 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/proxy_client.h @@ -0,0 +1,103 @@ +// Copyright 2017-2019 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 _PROXY_CLIENT_H_ +#define _PROXY_CLIENT_H_ + +#include "net.h" +#include "mesh_bearer_adapt.h" + +#define BLE_MESH_PROXY_ADV_NET_ID 0x00 +#define BLE_MESH_PROXY_ADV_NODE_ID 0x01 + +#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 BLE_MESH_PROXY_CFG_FILTER_SET 0x00 +#define BLE_MESH_PROXY_CFG_FILTER_ADD 0x01 +#define BLE_MESH_PROXY_CFG_FILTER_REMOVE 0x02 +#define BLE_MESH_PROXY_CFG_FILTER_STATUS 0x03 + +typedef union { + struct { + u8_t net_id[8]; + u16_t net_idx; + } net_id; + struct { + u16_t src; + } node_id; +} bt_mesh_proxy_adv_ctx_t; + +struct bt_mesh_proxy_net_pdu { + struct net_buf_simple *val; +}; + +struct bt_mesh_proxy_cfg_pdu { + u8_t opcode; + union { + struct cfg_filter_set { + u8_t filter_type; + } set; + struct cfg_addr_add { + u16_t *addr; + u16_t addr_num; + } add; + struct cfg_addr_remove { + u16_t *addr; + u16_t addr_num; + } remove; + }; +}; + +typedef struct { + u8_t type; + union { + struct bt_mesh_proxy_net_pdu net; + struct bt_mesh_proxy_cfg_pdu cfg; + }; +} bt_mesh_proxy_client_pdu_t; + +int bt_mesh_proxy_prov_client_send(struct bt_mesh_conn *conn, u8_t type, struct net_buf_simple *msg); + +int bt_mesh_provisioner_pb_gatt_enable(void); +int bt_mesh_provisioner_pb_gatt_disable(void); + +int bt_mesh_proxy_client_enable(void); +int bt_mesh_proxy_client_disable(void); + +typedef void (*proxy_client_recv_adv_cb_t)(const bt_mesh_addr_t *addr, u8_t type, bt_mesh_proxy_adv_ctx_t *ctx, s8_t rssi); +typedef void (*proxy_client_connect_cb_t)(const bt_mesh_addr_t *addr, u8_t conn_handle, u16_t net_idx); +typedef void (*proxy_client_disconnect_cb_t)(const bt_mesh_addr_t *addr, u8_t conn_handle, u16_t net_idx, u8_t reason); +typedef void (*proxy_client_recv_filter_status_cb_t)(u8_t conn_handle, u16_t src, u16_t net_idx, u8_t filter_type, u16_t list_size); + +void bt_mesh_proxy_client_set_adv_recv_cb(proxy_client_recv_adv_cb_t cb); +void bt_mesh_proxy_client_set_conn_cb(proxy_client_connect_cb_t cb); +void bt_mesh_proxy_client_set_disconn_cb(proxy_client_disconnect_cb_t cb); +void bt_mesh_proxy_client_set_filter_status_cb(proxy_client_recv_filter_status_cb_t cb); + +void bt_mesh_proxy_client_adv_ind_recv(struct net_buf_simple *buf, const bt_mesh_addr_t *addr, s8_t rssi); + +int bt_mesh_proxy_client_connect(const u8_t addr[6], u8_t addr_type, u16_t net_idx); +int bt_mesh_proxy_client_disconnect(u8_t conn_handle); + +bool bt_mesh_proxy_client_beacon_send(struct bt_mesh_subnet *sub); +bool bt_mesh_proxy_client_send(struct net_buf_simple *buf, u16_t dst); +int bt_mesh_proxy_client_send_cfg(u8_t conn_handle, u16_t net_idx, struct bt_mesh_proxy_cfg_pdu *pdu); + +int bt_mesh_proxy_prov_client_init(void); +int bt_mesh_proxy_prov_client_deinit(void); + +#endif /* _PROXY_CLIENT_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/proxy_server.c b/components/bt/esp_ble_mesh/mesh_core/proxy_server.c new file mode 100644 index 0000000000..676b533888 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/proxy_server.c @@ -0,0 +1,1460 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_PROXY) + +#include "mesh.h" +#include "adv.h" +#include "prov.h" +#include "beacon.h" +#include "access.h" +#include "foundation.h" +#include "proxy_server.h" + +#if (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || \ + CONFIG_BLE_MESH_GATT_PROXY_SERVER + +/* Not support enabling Proxy Client and Proxy Server simultaneously */ +_Static_assert(!(IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY_SERVER) &&IS_ENABLED(CONFIG_BLE_MESH_GATT_PROXY_CLIENT)), + "Not support Proxy Server and Proxy Client simultaneously"); + +#define PDU_TYPE(data) (data[0] & BIT_MASK(6)) +#define PDU_SAR(data) (data[0] >> 6) + +/* Mesh Profile 1.0 Section 6.6: + * "The timeout for the SAR transfer is 20 seconds. When the timeout + * expires, the Proxy Server shall disconnect." + */ +#define PROXY_SAR_TIMEOUT K_SECONDS(20) + +#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_SERVER) +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 + +static struct bt_mesh_proxy_client { + struct bt_mesh_conn *conn; +#if defined(CONFIG_BLE_MESH_GATT_PROXY_SERVER) + u16_t filter[CONFIG_BLE_MESH_PROXY_FILTER_SIZE]; +#endif + enum __packed { + NONE, + WHITELIST, + BLACKLIST, + PROV, + } filter_type; + u8_t msg_type; +#if defined(CONFIG_BLE_MESH_GATT_PROXY_SERVER) + struct k_work send_beacons; +#endif + struct k_delayed_work sar_timer; + struct net_buf_simple buf; +} clients[BLE_MESH_MAX_CONN] = { + [0 ... (BLE_MESH_MAX_CONN - 1)] = { +#if defined(CONFIG_BLE_MESH_GATT_PROXY_SERVER) + .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 bt_mesh_gatts_set_local_device_name(device_name); +} + +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; +} + +static void proxy_sar_timeout(struct k_work *work) +{ + struct bt_mesh_proxy_client *client = NULL; + + BT_WARN("Proxy SAR timeout"); + + 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; + } + + net_buf_simple_reset(&client->buf); + bt_mesh_gatts_disconnect(client->conn, 0x13); +} + +#if defined(CONFIG_BLE_MESH_GATT_PROXY_SERVER) +/* 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 = 0U; + + 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 = 0U; + int i, err = 0; + + /* 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 = {0}; + u8_t opcode = 0U; + int err = 0; + + 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 = 0U; + + 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 = 0U; + + 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 = NULL; + 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_SERVER) + 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, 0); + 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; + } + + k_delayed_work_submit(&client->sar_timer, PROXY_SAR_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; + } + + k_delayed_work_submit(&client->sar_timer, PROXY_SAR_TIMEOUT); + 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; + } + + 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 = NULL; + 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; +#if defined(CONFIG_BLE_MESH_GATT_PROXY_SERVER) + (void)memset(client->filter, 0, sizeof(client->filter)); +#endif + 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); + } + + 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 = NULL; + 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_SERVER) +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 = NULL; + u16_t value = 0U; + + 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_INFO("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 == BLACKLIST) { + for (i = 0; i < ARRAY_SIZE(client->filter); i++) { + if (client->filter[i] == addr) { + return false; + } + } + + return true; + } + + if (addr == BLE_MESH_ADDR_ALL_NODES) { + return true; + } + + if (client->filter_type == WHITELIST) { + for (i = 0; i < ARRAY_SIZE(client->filter); i++) { + if (client->filter[i] == addr) { + 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_SERVER */ + +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_SERVER) + 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 = 0U; + + 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_SERVER) + +#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 size_t gatt_proxy_adv_create(struct bt_mesh_adv_data *proxy_sd) +{ + const char *name = device_name; + size_t name_len = strlen(name); + /* One octet for Length, and another octet for AD type */ + size_t sd_space = 29; + + if (name_len > sd_space) { + proxy_sd->type = BLE_MESH_DATA_NAME_SHORTENED; + proxy_sd->data_len = sd_space; + } else { + proxy_sd->type = BLE_MESH_DATA_NAME_COMPLETE; + proxy_sd->data_len = name_len; + } + + proxy_sd->data = (const u8_t *)name; + + return 1; +} + +static int node_id_adv(struct bt_mesh_subnet *sub) +{ + struct bt_mesh_adv_data proxy_sd = {0}; + size_t proxy_sd_len = 0U; + u8_t tmp[16] = {0}; + int err = 0; + + 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); + proxy_sd_len = gatt_proxy_adv_create(&proxy_sd); + + err = bt_le_adv_start(&fast_adv_param, node_id_ad, + ARRAY_SIZE(node_id_ad), &proxy_sd, proxy_sd_len); + 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) +{ + struct bt_mesh_adv_data proxy_sd = {0}; + size_t proxy_sd_len = 0U; + int err = 0; + + 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); + proxy_sd_len = gatt_proxy_adv_create(&proxy_sd); + + err = bt_le_adv_start(&slow_adv_param, net_id_ad, + ARRAY_SIZE(net_id_ad), &proxy_sd, proxy_sd_len); + 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) +{ + struct bt_mesh_subnet *sub = NULL; + int i; + + for (i = next_idx; i < ARRAY_SIZE(bt_mesh.sub); i++) { + sub = &bt_mesh.sub[i]; + if (advertise_subnet(sub)) { + next_idx = (i + 1) % ARRAY_SIZE(bt_mesh.sub); + return sub; + } + } + + /** + * If among [next_idx, ARRAY_SIZE(bt_mesh.sub)], there is no subnet + * to advertise, then try to start advertising from Primary subnet. + */ + for (i = 0; i < next_idx; i++) { + sub = &bt_mesh.sub[i]; + if (advertise_subnet(sub)) { + next_idx = (i + 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 = 0; + + 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 = 0; + + /* 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 = 0U; + size_t sd_space = 31U; + + 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_SERVER) + 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 = 0; + + 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, +}; + +int bt_mesh_proxy_init(void) +{ + int i; + +#if defined(CONFIG_BLE_MESH_GATT_PROXY_SERVER) + bt_mesh_gatts_service_register(&proxy_svc); +#endif + +#if defined(CONFIG_BLE_MESH_PB_GATT) + bt_mesh_gatts_service_register(&prov_svc); +#endif + + /* 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_sar_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 bt_mesh_gatts_set_local_device_name(device_name); +} + +int bt_mesh_proxy_deinit(void) +{ + int i; + + proxy_adv_enabled = false; + gatt_svc = MESH_GATT_NONE; + +#if defined(CONFIG_BLE_MESH_GATT_PROXY_SERVER) + bt_mesh_gatts_service_deregister(&proxy_svc); +#endif + +#if defined(CONFIG_BLE_MESH_PB_GATT) + bt_mesh_gatts_service_deregister(&prov_svc); +#endif + + for (i = 0; i < ARRAY_SIZE(clients); i++) { + struct bt_mesh_proxy_client *client = &clients[i]; + k_delayed_work_free(&client->sar_timer); + memset(client, 0, sizeof(struct bt_mesh_proxy_client)); + } + + memset(client_buf_data, 0, sizeof(client_buf_data)); + memset(device_name, 0, sizeof(device_name)); + + bt_mesh_gatts_conn_cb_deregister(); + + return 0; +} + +#endif /* (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_GATT) || CONFIG_BLE_MESH_GATT_PROXY_SERVER */ diff --git a/components/bt/esp_ble_mesh/mesh_core/proxy_server.h b/components/bt/esp_ble_mesh/mesh_core/proxy_server.h new file mode 100644 index 0000000000..df4a73eda7 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/proxy_server.h @@ -0,0 +1,65 @@ +/* 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_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 + +#if CONFIG_BLE_MESH_PROXY +/** + * Device Name Characteristic: + * 1. For iOS, when it tries to get the value of Device Name Characteristic, the PDU + * "Read By Type Request" will be used, and the valid length of corresponding + * response is 19 (23 - 1 - 1 - 2). + * 2. For Android, when it tries to get the value of Device Name Characteristic, the + * PDU "Read Request" will be used, and the valid length of corresponding response + * is 22 (23 - 1). + */ +#define DEVICE_NAME_SIZE MIN((BLE_MESH_GATT_DEF_MTU_SIZE - 4), (BLE_MESH_GAP_ADV_MAX_LEN - 2)) +#else +/* For Scan Response Data, the maximum length is 29 (31 - 1 - 1) currently. */ +#define DEVICE_NAME_SIZE (BLE_MESH_GAP_ADV_MAX_LEN - 2) +#endif + +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); +int bt_mesh_proxy_deinit(void); + +#endif /* _PROXY_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/settings.c b/components/bt/esp_ble_mesh/mesh_core/settings.c new file mode 100644 index 0000000000..2792d893d0 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/settings.c @@ -0,0 +1,2592 @@ +/* + * Copyright (c) 2018 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_SETTINGS) + +#include "mesh.h" +#include "crypto.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "proxy_server.h" +#include "cfg_srv.h" +#include "mesh_common.h" +#include "settings_nvs.h" +#include "provisioner_main.h" +#include "provisioner_prov.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 + * key: "mesh/vaddr" -> write/read to set/get all virtual addresses + * key: "mesh/va/xxxx" -> write/read to set/get the "xxxx" virtual address + */ + +#if CONFIG_BLE_MESH_SETTINGS + +/* 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; +}; + +/* Virtual Address information */ +struct va_val { + u16_t ref; + u16_t addr; + u8_t uuid[16]; +} __packed; + +/* 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; + +struct prov_info { + u16_t primary_addr; + u16_t alloc_addr; +}; + +struct node_info { + u8_t addr[6]; + u8_t addr_type; + u8_t dev_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; + u8_t dev_key[16]; +} __packed; + +#define DEVICE_ROLE_BITS (BIT(BLE_MESH_NODE) | BIT(BLE_MESH_PROVISIONER)) + +static int role_set(const char *name) +{ + bool exist = false; + int err = 0; + + BT_DBG("%s", __func__); + + err = bt_mesh_load_core_settings(name, (u8_t *)bt_mesh.flags, sizeof(bt_mesh.flags), &exist); + if (err) { + return err; + } + + if (exist == false) { + return 0; + } + + BT_INFO("Restored mesh device role %lu", bt_mesh_atomic_get(bt_mesh.flags) & DEVICE_ROLE_BITS); + + return 0; +} + +static int net_set(const char *name) +{ + struct net_val net = {0}; + bool exist = false; + int err = 0; + + 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_INFO("Restored primary address 0x%04x", net.primary_addr); + BT_INFO("Restored 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 = false; + int err = 0; + + 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_INFO("Restored 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 = false; + int err = 0; + + 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--; +#endif + + BT_INFO("Restored 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'}; + bool exist = false; + size_t length = 0U; + 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_INFO("Restored 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'}; + bool exist = false; + size_t length = 0U; + 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_INFO("Restored NetKey Index 0x%03x", 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'}; + bool exist = false; + size_t length = 0U; + 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_INFO("Restored AppKey Index 0x%03x", 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 = false; + int err = 0; + + 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_INFO("Restored heartbeat publication, dst 0x%04x", hb_pub->dst); + + 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 = false; + int err = 0; + + 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; + } + + memcpy(&stored_cfg.cfg, &val, sizeof(val)); + stored_cfg.valid = true; + BT_INFO("Restored 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 = false; + int err = 0; + int i; + + /* 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 = false; + int err = 0; + int i; + + /* 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 = false; + int err = 0; + + 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_INFO("Restored 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 = 0U, model_idx = 0U; + size_t length = 0U; + 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 = BLE_MESH_GET_ELEM_IDX(model_key); + model_idx = BLE_MESH_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); +} + +#if CONFIG_BLE_MESH_LABEL_COUNT > 0 +static int va_set(const char *name) +{ + struct net_buf_simple *buf = NULL; + struct va_val va = {0}; + char get[16] = {'\0'}; + struct label *lab = NULL; + size_t length = 0U; + bool exist = false; + 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 index = net_buf_simple_pull_le16(buf); + sprintf(get, "mesh/va/%04x", index); + + err = bt_mesh_load_core_settings(get, (u8_t *)&va, sizeof(va), &exist); + if (err) { + BT_ERR("%s, Failed to load virtual address %s", __func__, get); + goto free; + } + + if (exist == false) { + continue; + } + + if (va.ref == 0) { + BT_DBG("Ignore virtual address %s with ref = 0", get); + goto free; + } + + lab = get_label(index); + if (lab == NULL) { + BT_WARN("%s, Out of labels buffers", __func__); + err = -ENOBUFS; + goto free; + } + + memcpy(lab->uuid, va.uuid, 16); + lab->addr = va.addr; + lab->ref = va.ref; + + BT_INFO("Restored virtual address 0x%04x ref 0x%04x", index, lab->ref); + } + +free: + bt_mesh_free_buf(buf); + return err; +} +#endif + +#if CONFIG_BLE_MESH_PROVISIONER +static int p_prov_set(const char *name) +{ + struct prov_info val = {0}; + bool exist = false; + int err = 0; + + BT_DBG("%s", __func__); + + err = bt_mesh_load_core_settings(name, (u8_t *)&val, sizeof(val), &exist); + if (err) { + BT_ERR("%s, Failed to load Provisioner prov info", __func__); + return 0; + } + + if (exist == false) { + return 0; + } + + bt_mesh_provisoner_restore_prov_info(val.primary_addr, val.alloc_addr); + + BT_INFO("Restored primary_addr 0x%04x, alloc_addr 0x%04x", + val.primary_addr, val.alloc_addr); + + return 0; +} + +static int p_net_idx_set(const char *name) +{ + u16_t net_idx = 0U; + bool exist = false; + int err = 0; + + BT_DBG("%s", __func__); + + err = bt_mesh_load_core_settings(name, (u8_t *)&net_idx, sizeof(net_idx), &exist); + if (err) { + BT_ERR("%s, Failed to load Provisioner keys", __func__); + return 0; + } + + if (exist == false) { + return 0; + } + + bt_mesh.p_net_idx_next = net_idx; + + BT_INFO("Restored p_net_idx_next 0x%04x", bt_mesh.p_net_idx_next); + + return 0; +} + +static int p_app_idx_set(const char *name) +{ + u16_t app_idx = 0U; + bool exist = false; + int err = 0; + + BT_DBG("%s", __func__); + + err = bt_mesh_load_core_settings(name, (u8_t *)&app_idx, sizeof(app_idx), &exist); + if (err) { + return 0; + } + + if (exist == false) { + return 0; + } + + bt_mesh.p_app_idx_next = app_idx; + + BT_INFO("Restored p_app_idx_next 0x%04x", bt_mesh.p_app_idx_next); + + return 0; +} + +static struct bt_mesh_subnet *p_subnet_alloc(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + if (bt_mesh.p_sub[i] == NULL) { + bt_mesh.p_sub[i] = bt_mesh_calloc(sizeof(struct bt_mesh_subnet)); + if (!bt_mesh.p_sub[i]) { + BT_ERR("%s, Failed to allocate memory", __func__); + return NULL; + } + + return bt_mesh.p_sub[i]; + } + } + + return NULL; +} + +static struct bt_mesh_app_key *p_appkey_alloc(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_app_keys); i++) { + if (bt_mesh.p_app_keys[i] == NULL) { + bt_mesh.p_app_keys[i] = bt_mesh_calloc(sizeof(struct bt_mesh_app_key)); + if (!bt_mesh.p_app_keys[i]) { + BT_ERR("%s, Failed to allocate memory", __func__); + return NULL; + } + + return bt_mesh.p_app_keys[i]; + } + } + + return NULL; +} + +static int p_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 = 0U; + bool exist = false; + 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/pnk/%04x", net_idx); + + err = bt_mesh_load_core_settings(get, (u8_t *)&key, sizeof(key), &exist); + if (err) { + BT_ERR("%s, Failed to load Provisioner NetKey %s", __func__, get); + goto free; + } + + if (exist == false) { + continue; + } + + sub = bt_mesh_provisioner_subnet_get(net_idx); + if (!sub) { + sub = p_subnet_alloc(); + if (!sub) { + BT_ERR("%s, No space for a new subnet 0x%03x", __func__, net_idx); + err = -ENOMEM; + goto free; + } + } + + 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); + + BT_INFO("Restored NetIdx 0x%03x", sub->net_idx); + BT_INFO("Restored NetKey %s", bt_hex(sub->keys[0].net, 16)); + } + +free: + bt_mesh_free_buf(buf); + return err; +} + +static int p_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 = 0U; + bool exist = false; + 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/pak/%04x", app_idx); + + err = bt_mesh_load_core_settings(get, (u8_t *)&key, sizeof(key), &exist); + if (err) { + BT_ERR("%s, Failed to load Provisioner AppKey %s", __func__, get); + goto free; + } + + if (exist == false) { + continue; + } + + sub = bt_mesh_provisioner_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_provisioner_app_key_find(app_idx); + if (!app) { + app = p_appkey_alloc(); + if (!app) { + BT_ERR("%s, No space for a new app key 0x%03x", __func__, app_idx); + err = -ENOMEM; + goto free; + } + } + + 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); + + BT_INFO("Restored AppIdx %03x, NetIdx 0x%03x", app->app_idx, app->net_idx); + BT_INFO("Restored AppKey %s", bt_hex(app->keys[0].val, 16)); + } + +free: + bt_mesh_free_buf(buf); + return err; +} + +static int node_info_set(u16_t addr, bool prov, bool *exist) +{ + struct bt_mesh_node node = {0}; + struct node_info info = {0}; + char get[16] = {'\0'}; + int err = 0; + + sprintf(get, prov ? "mesh/pn/%04x/i" : "mesh/sn/%04x/i", addr); + err = bt_mesh_load_core_settings(get, (u8_t *)&info, sizeof(info), exist); + if (err) { + BT_ERR("%s, Failed to load node %s", __func__, get); + return -EIO; + } + + if (*exist == false) { + return 0; + } + + memcpy(node.addr, info.addr, BLE_MESH_ADDR_LEN); + node.addr_type = info.addr_type; + memcpy(node.dev_uuid, info.dev_uuid, 16); + node.oob_info = info.oob_info; + node.unicast_addr = info.unicast_addr; + node.element_num = info.element_num; + node.net_idx = info.net_idx; + node.flags = info.flags; + node.iv_index = info.iv_index; + memcpy(node.dev_key, info.dev_key, 16); + + err = bt_mesh_provisioner_restore_node_info(&node, prov); + if (err) { + BT_ERR("%s, Failed to restore node 0x%04x", __func__, addr); + return -EIO; + } + + BT_INFO("Restored node 0x%04x, uuid %s", addr, bt_hex(node.dev_uuid, 16)); + + return 0; +} + +static int node_name_set(u16_t addr, bool prov) +{ + char name[BLE_MESH_NODE_NAME_SIZE] = {0}; + char get[16] = {'\0'}; + bool exist = false; + int err = 0; + + sprintf(get, prov ? "mesh/pn/%04x/n" : "mesh/sn/%04x/n", addr); + err = bt_mesh_load_core_settings(get, (u8_t *)name, BLE_MESH_NODE_NAME_SIZE, &exist); + if (err) { + BT_ERR("%s, Failed to load node name %s", __func__, get); + return -EIO; + } + + if (exist == false) { + return 0; + } + + err = bt_mesh_provisioner_restore_node_name(addr, name); + if (err) { + BT_ERR("%s, Failed to restore node name 0x%04x", __func__, addr); + return -EIO; + } + + BT_INFO("Restored node 0x%04x, name %s", addr, name); + + return 0; +} + +static int node_comp_data_set(u16_t addr, bool prov) +{ + struct net_buf_simple *buf = NULL; + char get[16] = {'\0'}; + int err = 0; + + sprintf(get, prov ? "mesh/pn/%04x/c" : "mesh/sn/%04x/c", addr); + buf = bt_mesh_get_core_settings_item(get); + if (!buf) { + return 0; + } + + err = bt_mesh_provisioner_restore_node_comp_data(addr, buf->data, buf->len, prov); + if (err) { + BT_ERR("%s, Failed to restore node comp data 0x%04x", __func__, addr); + } + + BT_INFO("Restored node 0x%04x, comp data %s", addr, bt_hex(buf->data, buf->len)); + + bt_mesh_free_buf(buf); + return err; +} + +static int p_node_set(const char *name) +{ + struct net_buf_simple *buf = NULL; + bool exist = false, prov = false; + size_t length = 0U; + int err = 0; + int i; + + buf = bt_mesh_get_core_settings_item(name); + if (!buf) { + return 0; + } + + prov = strcmp(name, "mesh/p_pnode") == 0 ? true : false; + length = buf->len; + + for (i = 0; i < length / SETTINGS_ITEM_SIZE; i++) { + u16_t addr = net_buf_simple_pull_le16(buf); + if (!BLE_MESH_ADDR_IS_UNICAST(addr)) { + BT_ERR("%s, 0x%04x is not a unicast address", __func__, addr); + goto free; + } + + err = node_info_set(addr, prov, &exist); + if (err) { + BT_ERR("%s, Failed to load node 0x%04x info", __func__, addr); + goto free; + } + + if (exist == false) { + continue; + } + + err = node_name_set(addr, prov); + if (err) { + BT_ERR("%s, Failed to load node 0x%04x name", __func__, addr); + goto free; + } + + err = node_comp_data_set(addr, prov); + if (err) { + BT_ERR("%s, Failed to load node 0x%04x comp data", __func__, addr); + goto free; + } + } + +free: + bt_mesh_free_buf(buf); + return err; +} +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +const struct bt_mesh_setting { + const char *name; + int (*func)(const char *name); +} settings[] = { + { "mesh/role", role_set }, + { "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 }, +#if CONFIG_BLE_MESH_LABEL_COUNT > 0 + { "mesh/vaddr", va_set }, +#endif +#if CONFIG_BLE_MESH_PROVISIONER + { "mesh/p_prov", p_prov_set }, + { "mesh/p_netidx", p_net_idx_set }, + { "mesh/p_appidx", p_app_idx_set }, + { "mesh/p_netkey", p_net_key_set }, + { "mesh/p_appkey", p_app_key_set }, + { "mesh/p_pnode", p_node_set }, + { "mesh/p_snode", p_node_set }, +#endif +}; + +/** + * For Provisioner, the load operation needs the following actions: + * net_set: Not needed + * iv_set: Need, although Provisioner will do some initialization of IV Index + * during startup, but we need to restore the last IV Index status + * seq_set: Need, restore the previous sequence number + * rpl_set: Need, restore the previous Replay Protection List + * net_key_set: Need, restore the previous network keys + * app_key_set: Need, restore the previous application keys + * hb_pub_set: Not needed currently + * cfg_set: Not needed currently + * sig_mod_set: Need, restore SIG models related info (app, sub, pub) + * vnd_mod_set: Need, restore vendor models related info (app, sub, pub) + */ +int settings_core_load(void) +{ + int i; + + BT_DBG("%s", __func__); + + for (i = 0; i < ARRAY_SIZE(settings); i++) { + if ((!strcmp(settings[i].name, "mesh/net") || + !strcmp(settings[i].name, "mesh/netkey") || + !strcmp(settings[i].name, "mesh/appkey") || + !strcmp(settings[i].name, "mesh/hb_pub") || + !strcmp(settings[i].name, "mesh/cfg")) && + (!IS_ENABLED(CONFIG_BLE_MESH_NODE) || bt_mesh_is_provisioner())) { + BT_DBG("Not restoring %s for Provisioner", settings[i].name); + continue; + } + + if ((!strcmp(settings[i].name, "mesh/p_prov") || + !strcmp(settings[i].name, "mesh/p_netidx") || + !strcmp(settings[i].name, "mesh/p_appidx") || + !strcmp(settings[i].name, "mesh/p_netkey") || + !strcmp(settings[i].name, "mesh/p_appkey") || + !strcmp(settings[i].name, "mesh/p_pnode") || + !strcmp(settings[i].name, "mesh/p_snode")) && + (!IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) || bt_mesh_is_node())) { + BT_DBG("Not restoring %s for node", settings[i].name); + continue; + } + + settings[i].func(settings[i].name); + + if (!strcmp(settings[i].name, "mesh/role")) { + u8_t role = bt_mesh_atomic_get(bt_mesh.flags) & DEVICE_ROLE_BITS; + if (role == 0U) { + BT_INFO("Device just starts up, nothing restored"); + return 0; + } + if (role != BIT(BLE_MESH_NODE) && role != BIT(BLE_MESH_PROVISIONER)) { + BT_ERR("Invalid restored device role %d", role); + return 0; + } + } + } + + return 0; +} + +static int subnet_init(struct bt_mesh_subnet *sub) +{ + int err = 0; + + 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_SERVER)) { + 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_subnet *sub = NULL; + int err = 0; + int i; + +#if defined(CONFIG_BLE_MESH_NODE) + if (bt_mesh_is_node()) { + BT_INFO("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)) { + bt_mesh_proxy_prov_disable(true); + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { + sub = &bt_mesh.sub[i]; + + 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); + } + } + } +#endif /* CONFIG_BLE_MESH_NODE */ + +#if defined(CONFIG_BLE_MESH_PROVISIONER) + if (bt_mesh_is_provisioner()) { + if (bt_mesh.p_sub[0] == NULL || + bt_mesh.p_sub[0]->net_idx == BLE_MESH_KEY_UNUSED) { + return 0; + } + + BT_INFO("p_sub[0]->net_idx 0x%03x", bt_mesh.p_sub[0]->net_idx); + + bt_mesh_comp_provision(bt_mesh_provisioner_get_primary_elem_addr()); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.p_sub); i++) { + sub = bt_mesh.p_sub[i]; + + if (sub == NULL || 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); + } + sub->node_id = BLE_MESH_NODE_IDENTITY_NOT_SUPPORTED; + } + } +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + + 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); + +#if defined(CONFIG_BLE_MESH_NODE) + if (bt_mesh_is_node()) { + struct bt_mesh_hb_pub *hb_pub = NULL; + struct bt_mesh_cfg_srv *cfg = 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); + bt_mesh_net_start(); + } +#endif /* CONFIG_BLE_MESH_NODE */ + +#if defined(CONFIG_BLE_MESH_PROVISIONER) + if (bt_mesh_is_provisioner()) { + bt_mesh_provisioner_net_start(BLE_MESH_PROV_ADV | BLE_MESH_PROV_GATT); + } +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + + return 0; +} + +/* Pending flags that use K_NO_WAIT as the storage timeout */ +#define NO_WAIT_PENDING_BITS (BIT(BLE_MESH_NET_PENDING) | \ + BIT(BLE_MESH_IV_PENDING) | \ + BIT(BLE_MESH_SEQ_PENDING)) + +/* Pending flags that use CONFIG_BLE_MESH_STORE_TIMEOUT */ +#define GENERIC_PENDING_BITS (BIT(BLE_MESH_KEYS_PENDING) | \ + BIT(BLE_MESH_HB_PUB_PENDING) | \ + BIT(BLE_MESH_CFG_PENDING) | \ + BIT(BLE_MESH_MOD_PENDING)) + +static void schedule_store(int flag) +{ + s32_t timeout = 0, remaining = 0; + + bt_mesh_atomic_set_bit(bt_mesh.flags, flag); + + if (bt_mesh_atomic_get(bt_mesh.flags) & NO_WAIT_PENDING_BITS) { + timeout = K_NO_WAIT; + } else if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_RPL_PENDING) && + (!(bt_mesh_atomic_get(bt_mesh.flags) & GENERIC_PENDING_BITS) || + (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); + } + + remaining = k_delayed_work_remaining_get(&pending_store); + if (remaining && remaining < timeout) { + BT_DBG("Not rescheduling due to existing earlier deadline"); + return; + } + + 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_role(void) +{ + BT_DBG("Store, device role %lu", bt_mesh_atomic_get(bt_mesh.flags) & DEVICE_ROLE_BITS); + + bt_mesh_save_core_settings("mesh/role", (const u8_t *)bt_mesh.flags, sizeof(bt_mesh.flags)); +} + +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); + } +} + +void bt_mesh_clear_iv(void) +{ + clear_iv(); +} + +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); +} + +void bt_mesh_clear_seq(void) +{ + bt_mesh_save_core_settings("mesh/seq", NULL, 0); +} + +static void store_rpl(struct bt_mesh_rpl *entry) +{ + struct rpl_val rpl = {0}; + char name[16] = {'\0'}; + int err = 0; + + 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 = 0U; + u16_t src = 0U; + 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 = 0; + + 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 = 0; + + 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 = 0; + + 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 = 0; + + 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 = 0U; + int err = 0; + + model_key = BLE_MESH_GET_MODEL_KEY(model->elem_idx, model->model_idx); + + sprintf(name, "mesh/%s/%04x/b", vnd ? "v" : "s", model_key); + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node() && + !bt_mesh_is_provisioned()) { + 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 = 0U; + int err = 0; + + model_key = BLE_MESH_GET_MODEL_KEY(model->elem_idx, model->model_idx); + + sprintf(name, "mesh/%s/%04x/s", vnd ? "v" : "s", model_key); + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node() && + !bt_mesh_is_provisioned()) { + 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 = 0U; + int err = 0; + + 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 = BLE_MESH_GET_MODEL_KEY(model->elem_idx, model->model_idx); + + sprintf(name, "mesh/%s/%04x/p", vnd ? "v" : "s", model_key); + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node() && + !bt_mesh_is_provisioned()) { + 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); + } +} + +#define IS_VA_DEL(_label) ((_label)->ref == 0) +static void store_pending_va(void) +{ + struct va_val va = {0}; + char name[16] = {'\0'}; + struct label *lab = NULL; + u16_t i = 0U; + int err = 0; + + for (i = 0U; (lab = get_label(i)) != NULL; i++) { + if (!bt_mesh_atomic_test_and_clear_bit(lab->flags, + BLE_MESH_VA_CHANGED)) { + continue; + } + + sprintf(name, "mesh/va/%04x", i); + + if (IS_VA_DEL(lab)) { + err = bt_mesh_save_core_settings(name, NULL, 0); + } else { + va.ref = lab->ref; + va.addr = lab->addr; + memcpy(va.uuid, lab->uuid, 16); + err = bt_mesh_save_core_settings(name, (const u8_t *)&va, sizeof(va)); + } + if (err) { + BT_ERR("%s, Failed to %s virtual address %s", __func__, + IS_VA_DEL(lab) ? "delete" : "store", name); + return; + } + + if (IS_VA_DEL(lab)) { + err = bt_mesh_remove_core_settings_item("mesh/vaddr", i); + } else { + err = bt_mesh_add_core_settings_item("mesh/vaddr", i); + } + if (err) { + BT_ERR("%s, Failed to %s 0x%04x in mesh/vaddr", __func__, + IS_VA_DEL(lab) ? "delete" : "store", i); + return; + } + + BT_DBG("%s virtual address 0x%04x", IS_VA_DEL(lab) ? "Delete" : "Store", i); + } +} + +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 (!IS_ENABLED(CONFIG_BLE_MESH_NODE) || bt_mesh_is_provisioned()) { + store_pending_rpl(); + } else { + clear_rpl(); + } + } + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node() && + bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_KEYS_PENDING)) { + store_pending_keys(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node() && + bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_NET_PENDING)) { + if (bt_mesh_is_provisioned()) { + store_pending_net(); + } else { + clear_net(); + } + } + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_IV_PENDING)) { + if (!IS_ENABLED(CONFIG_BLE_MESH_NODE) || bt_mesh_is_provisioned()) { + 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 (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node() && + bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_HB_PUB_PENDING)) { + store_pending_hb_pub(); + } + + if (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node() && + bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_CFG_PENDING)) { + if (bt_mesh_is_provisioned()) { + 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 (IS_ENABLED(CONFIG_BLE_MESH_NODE) && bt_mesh_is_node() && + !bt_mesh_is_provisioned()) { + bt_mesh_save_core_settings("mesh/sig", NULL, 0); + bt_mesh_save_core_settings("mesh/vnd", NULL, 0); + } + } + + if (bt_mesh_atomic_test_and_clear_bit(bt_mesh.flags, BLE_MESH_VA_PENDING)) { + store_pending_va(); + } +} + +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_role(void) +{ + BT_DBG("Clear device role"); + bt_mesh_save_core_settings("mesh/role", NULL, 0); +} + +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); +} + +void bt_mesh_store_label(void) +{ + schedule_store(BLE_MESH_VA_PENDING); +} + +#if CONFIG_BLE_MESH_PROVISIONER +/** + * key: "mesh/p_prov" -> write/read to set/get prov_ctx.curr_addr + * key: "mesh/p_netidx" -> write/read to set/get bt_mesh.p_net_idx_next + * key: "mesh/p_appidx" -> write/read to set/get bt_mesh.p_app_idx_next + * key: "mesh/p_netkey" -> write/read to set/get all Provisioner NetKey Index + * key: "mesh/pnk/xxxx" -> write/read to set/get the "xxxx" NetKey + * key: "mesh/p_appkey" -> write/read to set/get all Provisioner AppKey Index + * key: "mesh/pak/xxxx" -> write/read to set/get the "xxxx" AppKey + * key: "mesh/p_pnode" -> write/read to set/get all self-provisioned nodes info + * key: "mesh/pn/xxxx/i" -> write/read to set/get the "xxxx" provisioned node info + * key: "mesh/pn/xxxx/n" -> write/read to set/get the "xxxx" provisioned node name + * key: "mesh/pn/xxxx/c" -> write/read to set/get the "xxxx" provisioned node composition data + * key: "mesh/p_snode" -> write/read to set/get all locally stored nodes info + * key: "mesh/sn/xxxx/i" -> write/read to set/get the "xxxx" stored node info + * key: "mesh/sn/xxxx/n" -> write/read to set/get the "xxxx" stored node name + * key: "mesh/sn/xxxx/c" -> write/read to set/get the "xxxx" stored node composition data + */ +void bt_mesh_store_prov_info(u16_t primary_addr, u16_t alloc_addr) +{ + struct prov_info val = {0}; + + BT_DBG("primary_addr 0x%04x, alloc_addr 0x%04x", primary_addr, alloc_addr); + + val.primary_addr = primary_addr; + val.alloc_addr = alloc_addr; + + bt_mesh_save_core_settings("mesh/p_prov", (const u8_t *)&val, sizeof(val)); +} + +void bt_mesh_clear_prov_info(void) +{ + bt_mesh_save_core_settings("mesh/p_prov", NULL, 0); +} + +static void clear_p_net_key(u16_t net_idx) +{ + char name[16] = {'\0'}; + int err = 0; + + sprintf(name, "mesh/pnk/%04x", net_idx); + bt_mesh_save_core_settings(name, NULL, 0); + + err = bt_mesh_remove_core_settings_item("mesh/p_netkey", net_idx); + if (err) { + BT_ERR("%s, Failed to remove 0x%04x from mesh/p_netkey", __func__, net_idx); + } +} + +static void clear_p_app_key(u16_t app_idx) +{ + char name[16] = {'\0'}; + int err = 0; + + sprintf(name, "mesh/pak/%04x", app_idx); + bt_mesh_save_core_settings(name, NULL, 0); + + err = bt_mesh_remove_core_settings_item("mesh/p_appkey", app_idx); + if (err) { + BT_ERR("%s, Failed to remove 0x%03x from mesh/p_appkey", __func__, app_idx); + } +} + +static void store_p_net_key(struct bt_mesh_subnet *sub) +{ + struct net_key_val key = {0}; + char name[16] = {'\0'}; + int err = 0; + + 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/pnk/%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/p_netkey", sub->net_idx); + if (err) { + BT_ERR("%s, Failed to add 0x%04x to mesh/p_netkey", __func__, sub->net_idx); + } +} + +static void store_p_app_key(struct bt_mesh_app_key *app) +{ + struct app_key_val key = {0}; + char name[16] = {'\0'}; + int err = 0; + + 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/pak/%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/p_appkey", app->app_idx); + if (err) { + BT_ERR("%s, Failed to add 0x%04x to mesh/p_appkey", __func__, app->app_idx); + } +} + +void bt_mesh_store_p_net_idx(void) +{ + BT_DBG("p_net_idx_next 0x%03x", bt_mesh.p_net_idx_next); + + bt_mesh_save_core_settings("mesh/p_netidx", + (const u8_t *)&bt_mesh.p_net_idx_next, sizeof(bt_mesh.p_net_idx_next)); +} + +void bt_mesh_clear_p_net_idx(void) +{ + bt_mesh_save_core_settings("mesh/p_netidx", NULL, 0); +} + +void bt_mesh_store_p_app_idx(void) +{ + BT_DBG("p_app_idx_next 0x%03x", bt_mesh.p_app_idx_next); + + bt_mesh_save_core_settings("mesh/p_appidx", + (const u8_t *)&bt_mesh.p_app_idx_next, sizeof(bt_mesh.p_app_idx_next)); +} + +void bt_mesh_clear_p_app_idx(void) +{ + bt_mesh_save_core_settings("mesh/p_appidx", NULL, 0); +} + +void bt_mesh_store_p_subnet(struct bt_mesh_subnet *sub) +{ + if (sub == NULL) { + BT_ERR("%s, Invalid subnet",__func__); + return; + } + + BT_DBG("NetKeyIndex 0x%03x NetKey %s", sub->net_idx, + bt_hex(sub->keys[0].net, 16)); + + store_p_net_key(sub); +} + +void bt_mesh_store_p_app_key(struct bt_mesh_app_key *key) +{ + if (key == NULL) { + BT_ERR("%s, Invalid AppKey",__func__); + return; + } + + BT_DBG("AppKeyIndex 0x%03x AppKey %s", key->app_idx, + bt_hex(key->keys[0].val, 16)); + + store_p_app_key(key); +} + +void bt_mesh_clear_p_subnet(struct bt_mesh_subnet *sub) +{ + if (sub == NULL) { + BT_ERR("%s, Invalid subnet",__func__); + return; + } + + BT_DBG("NetKeyIndex 0x%03x", sub->net_idx); + + clear_p_net_key(sub->net_idx); +} + +void bt_mesh_clear_p_app_key(struct bt_mesh_app_key *key) +{ + if (key == NULL) { + BT_ERR("%s, Invalid AppKey",__func__); + return; + } + + BT_DBG("AppKeyIndex 0x%03x", key->app_idx); + + clear_p_app_key(key->app_idx); +} + +void bt_mesh_clear_rpl_single(u16_t src) +{ + char name[16] = {'\0'}; + int err = 0; + + if (!BLE_MESH_ADDR_IS_UNICAST(src)) { + BT_ERR("%s, Invalid source address 0x%04x", __func__, src); + return; + } + + sprintf(name, "mesh/rpl/%04x", src); + bt_mesh_save_core_settings(name, NULL, 0); + + err = bt_mesh_remove_core_settings_item("mesh/rpl", src); + if (err) { + BT_ERR("%s, Failed to remove 0x%04x from mesh/rpl", __func__, src); + } +} + +void bt_mesh_store_node_info(struct bt_mesh_node *node, bool prov) +{ + struct node_info val = {0}; + char name[16] = {'\0'}; + int err = 0; + + if (node == NULL) { + BT_ERR("%s, Invalid node", __func__); + return; + } + + memcpy(val.addr, node->addr, BLE_MESH_ADDR_LEN); + val.addr_type = node->addr_type; + memcpy(val.dev_uuid, node->dev_uuid, 16); + val.oob_info = node->oob_info; + val.unicast_addr = node->unicast_addr; + val.element_num = node->element_num; + val.net_idx = node->net_idx; + val.flags = node->flags; + val.iv_index = node->iv_index; + memcpy(val.dev_key, node->dev_key, 16); + + sprintf(name, prov ? "mesh/pn/%04x/i" : "mesh/sn/%04x/i", node->unicast_addr); + err = bt_mesh_save_core_settings(name, (const u8_t *)&val, sizeof(val)); + if (err) { + BT_ERR("%s, Failed to save node %s", __func__, name); + return; + } + + err = bt_mesh_add_core_settings_item(prov ? "mesh/p_pnode" : "mesh/p_snode", node->unicast_addr); + if (err) { + BT_ERR("%s, Failed to add node 0x%04x", __func__, node->unicast_addr); + } +} + +static void clear_node(u16_t addr, bool prov) +{ + char name[16] = {'\0'}; + int err = 0; + + /* Clear node information */ + sprintf(name, prov ? "mesh/pn/%04x/i" : "mesh/sn/%04x/i", addr); + bt_mesh_save_core_settings(name, NULL, 0); + + /* Clear node name */ + sprintf(name, prov ? "mesh/pn/%04x/n" : "mesh/sn/%04x/n", addr); + bt_mesh_save_core_settings(name, NULL, 0); + + /* Clear node composition data */ + sprintf(name, prov ? "mesh/pn/%04x/c" : "mesh/sn/%04x/c", addr); + bt_mesh_save_core_settings(name, NULL, 0); + + err = bt_mesh_remove_core_settings_item(prov ? "mesh/p_pnode" : "mesh/p_snode", addr); + if (err) { + BT_ERR("%s, Failed to remove node 0x%04x", __func__, addr); + } +} + +void bt_mesh_clear_node_info(u16_t unicast_addr, bool prov) +{ + if (!BLE_MESH_ADDR_IS_UNICAST(unicast_addr)) { + BT_ERR("%s, Invalid unicast address 0x%04x", __func__, unicast_addr); + return; + } + + BT_DBG("Unicast address 0x%04x", unicast_addr); + + clear_node(unicast_addr, prov); +} + +void bt_mesh_store_node_name(struct bt_mesh_node *node, bool prov) +{ + char node_name[BLE_MESH_NODE_NAME_SIZE] = {0}; + char name[16] = {'\0'}; + int err = 0; + + if (node == NULL) { + BT_ERR("%s, Invalid node", __func__); + return; + } + + strncpy(node_name, node->name, BLE_MESH_NODE_NAME_SIZE); + + sprintf(name, prov ? "mesh/pn/%04x/n" : "mesh/sn/%04x/n", node->unicast_addr); + err = bt_mesh_save_core_settings(name, (const u8_t *)node_name, BLE_MESH_NODE_NAME_SIZE); + if (err) { + BT_ERR("%s, Failed to save node name %s", __func__, name); + } +} + +void bt_mesh_store_node_comp_data(struct bt_mesh_node *node, bool prov) +{ + char name[16] = {'\0'}; + int err = 0; + + if (!node || !node->comp_data || node->comp_length == 0U) { + BT_ERR("%s, Invalid node info", __func__); + return; + } + + sprintf(name, prov ? "mesh/pn/%04x/c" : "mesh/sn/%04x/c", node->unicast_addr); + err = bt_mesh_save_core_settings(name, (const u8_t *)node->comp_data, node->comp_length); + if (err) { + BT_ERR("%s, Failed to save node comp data %s", __func__, name); + } +} +#endif /* CONFIG_BLE_MESH_PROVISIONER */ + +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; +} + +int settings_core_deinit(void) +{ + k_delayed_work_free(&pending_store); + + return 0; +} + +int bt_mesh_settings_deinit(void) +{ + bt_mesh_settings_deforeach(); + + return 0; +} + +#endif /* CONFIG_BLE_MESH_SETTINGS */ diff --git a/components/bt/esp_ble_mesh/mesh_core/settings.h b/components/bt/esp_ble_mesh/mesh_core/settings.h new file mode 100644 index 0000000000..a02db9e550 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/settings.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ + +#include "net.h" +#include "provisioner_main.h" + +int settings_core_init(void); +int settings_core_load(void); +int settings_core_commit(void); +int settings_core_deinit(void); + +void bt_mesh_store_role(void); +void bt_mesh_store_net(void); +void bt_mesh_store_iv(bool only_duration); +void bt_mesh_clear_iv(void); +void bt_mesh_store_seq(void); +void bt_mesh_clear_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_store_label(void); + +void bt_mesh_clear_role(void); +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); + +#if CONFIG_BLE_MESH_PROVISIONER +void bt_mesh_store_prov_info(u16_t primary_addr, u16_t alloc_addr); +void bt_mesh_clear_prov_info(void); +void bt_mesh_store_p_net_idx(void); +void bt_mesh_clear_p_net_idx(void); +void bt_mesh_store_p_app_idx(void); +void bt_mesh_clear_p_app_idx(void); +void bt_mesh_store_p_subnet(struct bt_mesh_subnet *sub); +void bt_mesh_store_p_app_key(struct bt_mesh_app_key *key); +void bt_mesh_clear_p_subnet(struct bt_mesh_subnet *sub); +void bt_mesh_clear_p_app_key(struct bt_mesh_app_key *key); +void bt_mesh_clear_rpl_single(u16_t src); +void bt_mesh_store_node_info(struct bt_mesh_node *node, bool prov); +void bt_mesh_clear_node_info(u16_t unicast_addr, bool prov); +void bt_mesh_store_node_name(struct bt_mesh_node *node, bool prov); +void bt_mesh_store_node_comp_data(struct bt_mesh_node *node, bool prov); +#endif + +int bt_mesh_settings_init(void); +int bt_mesh_settings_deinit(void); + +#endif /* _SETTINGS_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_core/storage/settings_nvs.c b/components/bt/esp_ble_mesh/mesh_core/storage/settings_nvs.c new file mode 100644 index 0000000000..400eac9ce2 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/storage/settings_nvs.c @@ -0,0 +1,406 @@ +// Copyright 2017-2019 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 "nvs.h" +#include "nvs_flash.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); + int (*settings_deinit)(void); + int (*settings_erase)(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_deinit = settings_core_deinit, + }, + [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 err = 0; + int i; + +#if CONFIG_BLE_MESH_SPECIFIC_PARTITION + err = nvs_flash_init_partition(CONFIG_BLE_MESH_PARTITION_NAME); + if (err != ESP_OK) { + BT_ERR("Failed to init mesh partition, name %s, err %d", CONFIG_BLE_MESH_PARTITION_NAME, err); + return; + } +#endif + + for (i = 0; i < ARRAY_SIZE(settings_ctx); i++) { + struct settings_context *ctx = &settings_ctx[i]; + +#if CONFIG_BLE_MESH_SPECIFIC_PARTITION + err = nvs_open_from_partition(CONFIG_BLE_MESH_PARTITION_NAME, ctx->nvs_name, NVS_READWRITE, &ctx->handle); +#else + err = nvs_open(ctx->nvs_name, NVS_READWRITE, &ctx->handle); +#endif + 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; + } + } +} + +void bt_mesh_settings_deforeach(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(settings_ctx); i++) { + struct settings_context *ctx = &settings_ctx[i]; + + if (ctx->settings_deinit && ctx->settings_deinit()) { + BT_ERR("%s, Deinit settings failed, name %s", __func__, ctx->nvs_name); + continue; + } + + nvs_close(ctx->handle); + } + +#if CONFIG_BLE_MESH_SPECIFIC_PARTITION + nvs_flash_deinit_partition(CONFIG_BLE_MESH_PARTITION_NAME); +#endif +} + +/* 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 = 0; + + 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 = 0; + + 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 = 0U; + int err = 0; + + 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 = 0U; + bool exist = false; + int err = 0; + + 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 = 0U; + 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 = 0U; + int err = 0; + + 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 = 0U; + size_t buf_len = 0U; + int err = 0; + int i; + + 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/esp_ble_mesh/mesh_core/storage/settings_nvs.h b/components/bt/esp_ble_mesh/mesh_core/storage/settings_nvs.h new file mode 100644 index 0000000000..099efa54d8 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/storage/settings_nvs.h @@ -0,0 +1,47 @@ +// Copyright 2017-2019 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 "mesh_buf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SETTINGS_ITEM_SIZE sizeof(u16_t) + +#define BLE_MESH_GET_ELEM_IDX(x) ((u8_t)((x) >> 8)) +#define BLE_MESH_GET_MODEL_IDX(x) ((u8_t)(x)) +#define BLE_MESH_GET_MODEL_KEY(a, b) ((u16_t)(((u16_t)((a) << 8)) | b)) + +void bt_mesh_settings_foreach(void); +void bt_mesh_settings_deforeach(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/esp_ble_mesh/mesh_core/test.c b/components/bt/esp_ble_mesh/mesh_core/test.c new file mode 100644 index 0000000000..384336ebfa --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/test.c @@ -0,0 +1,127 @@ +/* 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.h" +#include "test.h" +#include "crypto.h" +#include "access.h" +#include "foundation.h" +#include "mesh_main.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 = 0; + + 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/esp_ble_mesh/mesh_core/test.h b/components/bt/esp_ble_mesh/mesh_core/test.h new file mode 100644 index 0000000000..477ca105f3 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/test.h @@ -0,0 +1,38 @@ +/* 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 "mesh_types.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/esp_ble_mesh/mesh_core/transport.c b/components/bt/esp_ble_mesh/mesh_core/transport.c new file mode 100644 index 0000000000..3d78e4365c --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/transport.c @@ -0,0 +1,1726 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_TRANS) + +#include "crypto.h" +#include "adv.h" +#include "mesh.h" +#include "lpn.h" +#include "friend.h" +#include "access.h" +#include "foundation.h" +#include "settings.h" +#include "transport.h" +#include "mesh_main.h" +#include "mesh_common.h" +#include "cfg_srv.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; + +static bt_mesh_mutex_t tx_seg_lock; + +static void bt_mesh_tx_seg_mutex_new(void) +{ + if (!tx_seg_lock.mutex) { + bt_mesh_mutex_create(&tx_seg_lock); + } +} + +static void bt_mesh_tx_seg_mutex_free(void) +{ + bt_mesh_mutex_free(&tx_seg_lock); +} + +static void bt_mesh_tx_seg_lock(void) +{ + bt_mesh_mutex_lock(&tx_seg_lock); +} + +static void bt_mesh_tx_seg_unlock(void) +{ + bt_mesh_mutex_unlock(&tx_seg_lock); +} + +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 = NULL; + + 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_FRIEND)) { + if (!bt_mesh_friend_queue_has_space(tx->sub->net_idx, + tx->src, tx->ctx->addr, + NULL, 1)) { + if (BLE_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) { + BT_ERR("Not enough space in Friend Queue"); + net_buf_unref(buf); + return -ENOBUFS; + } else { + BT_WARN("No space in Friend Queue"); + goto send; + } + } + + if (bt_mesh_friend_enqueue_tx(tx, BLE_MESH_FRIEND_PDU_SINGLE, + NULL, 1, &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); + send_cb_finalize(cb, cb_data); + return 0; + } + } + +send: + 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; + } + + bt_mesh_tx_seg_lock(); + + for (i = 0; i <= tx->seg_n; i++) { + if (!tx->seg[i]) { + continue; + } + + bt_mesh_adv_buf_ref_debug(__func__, tx->seg[i], 3U, BLE_MESH_BUF_REF_SMALL); + BLE_MESH_ADV(tx->seg[i])->busy = 0U; + net_buf_unref(tx->seg[i]); + tx->seg[i] = NULL; + } + + tx->nack_count = 0U; + + bt_mesh_tx_seg_unlock(); + + 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 = 0; + + bt_mesh_tx_seg_lock(); + + 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"); + bt_mesh_tx_seg_unlock(); + 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__); + bt_mesh_tx_seg_unlock(); + seg_tx_complete(tx, -EIO); + return; + } + } + + bt_mesh_tx_seg_unlock(); +} + +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 = 0U, seg_o = 0U; + u16_t seq_zero = 0U; + struct seg_tx *tx = NULL; + 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 & TRANS_SEQ_ZERO_MASK; + + BT_DBG("SeqZero 0x%04x", seq_zero); + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND) && + !bt_mesh_friend_queue_has_space(tx->sub->net_idx, net_tx->src, + tx->dst, &tx->seq_auth, + tx->seg_n + 1) && + BLE_MESH_ADDR_IS_UNICAST(tx->dst)) { + BT_ERR("Not enough space in Friend Queue for %u segments", + tx->seg_n + 1); + seg_tx_reset(tx); + return -ENOBUFS; + } + + for (seg_o = 0U; sdu->len; seg_o++) { + struct net_buf *seg = NULL; + u16_t len = 0U; + int err = 0; + + 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); + + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND)) { + enum bt_mesh_friend_pdu_type type = BLE_MESH_FRIEND_PDU_PARTIAL; + + 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, + tx->seg_n + 1, + &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); + continue; + } + } + + tx->seg[seg_o] = net_buf_ref(seg); + + 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; + } + } + + /* This can happen if segments only went into the Friend Queue */ + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND) && !tx->seg[0]) { + seg_tx_reset(tx); + /* If there was a callback notify sending immediately since + * there's no other way to track this (at least currently) + * with the Friend Queue. + */ + send_cb_finalize(cb, cb_data); + } + + 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 = NULL, role = 0U; + u8_t aid = 0U; + int err = 0; + + 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_device_role(tx->ctx->model, tx->ctx->srv_send); + if (role == ROLE_NVAL) { + BT_ERR("%s, Failed to get model role", __func__); + return -EINVAL; + } + + err = bt_mesh_app_key_get(tx->sub, tx->ctx->app_idx, &key, + &aid, role, tx->ctx->addr); + if (err) { + return err; + } + + tx->aid = aid; + + 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; +} + +static void update_rpl(struct bt_mesh_rpl *rpl, struct bt_mesh_net_rx *rx) +{ + 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); + } +} + +/* Check the Replay Protection List for a replay attempt. If non-NULL match + * parameter is given the RPL slot is returned but it is not immediately + * updated (needed for segmented messages), whereas if a NULL match is given + * the RPL is immediately updated (used for unsegmented messages). + */ +static bool is_replay(struct bt_mesh_net_rx *rx, struct bt_mesh_rpl **match) +{ + int i; + + /* Don't bother checking messages from ourselves */ + if (rx->net_if == BLE_MESH_NET_IF_LOCAL) { + return false; + } + + /* The RPL is used only for the local node */ + if (!rx->local_match) { + 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) { + if (match) { + *match = rpl; + } else { + update_rpl(rpl, rx); + } + + 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 */ + if (match) { + *match = rpl; + } else { + update_rpl(rpl, rx); + } + + 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; + size_t array_size = 0U; + size_t i = 0U; + u8_t *ad = NULL; + int err = 0; + + 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)) { + array_size = bt_mesh_rx_devkey_size(); + + for (i = 0U; i < array_size; i++) { + const u8_t *dev_key = NULL; + + dev_key = bt_mesh_rx_devkey_get(i, rx->ctx.addr); + 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; + } + + array_size = bt_mesh_rx_appkey_size(); + + for (i = 0U; i < array_size; i++) { + struct bt_mesh_app_keys *keys = NULL; + struct bt_mesh_app_key *key = NULL; + + key = bt_mesh_rx_appkey_get(i); + 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 = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) { + tx = &seg_tx[i]; + + if ((tx->seq_auth & TRANS_SEQ_ZERO_MASK) != 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 = NULL; + unsigned int bit = 0; + u32_t ack = 0U; + u16_t seq_zero = 0U; + u8_t obo = 0U; + + 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) & TRANS_SEQ_ZERO_MASK; + + 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 = 0U, hops = 0U; + u16_t feat = 0U; + + 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_INFO("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_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 = 0U; + + 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 (is_replay(rx, NULL)) { + 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 = 0; + u8_t ttl = 0U; + + 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 = NULL; + + 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, 1, &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 & TRANS_SEQ_ZERO_MASK; + u8_t buf[6] = {0}; + + 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, + u8_t *seg_count) +{ + struct bt_mesh_rpl *rpl = NULL; + struct seg_rx *rx = NULL; + u8_t *hdr = buf->data; + u16_t seq_zero = 0U; + u8_t seg_n = 0U; + u8_t seg_o = 0U; + int err = 0; + + if (buf->len < 5) { + BT_ERR("%s, Too short segmented message (len %u)", __func__, buf->len); + return -EINVAL; + } + + if (is_replay(net_rx, &rpl)) { + BT_WARN("Replay: src 0x%04x dst 0x%04x seq 0x%06x", + net_rx->ctx.addr, net_rx->ctx.recv_dst, net_rx->seq); + 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) & TRANS_SEQ_ZERO_MASK; + 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)))); + + *seg_count = seg_n + 1; + + /* 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); + + if (rpl) { + update_rpl(rpl, net_rx); + } + + 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; + } + + /* Verify early that there will be space in the Friend Queue(s) in + * case this message is destined to an LPN of ours. + */ + if (IS_ENABLED(CONFIG_BLE_MESH_FRIEND) && + net_rx->friend_match && !net_rx->local_match && + !bt_mesh_friend_queue_has_space(net_rx->sub->net_idx, + net_rx->ctx.addr, + net_rx->ctx.recv_dst, seq_auth, + *seg_count)) { + BT_ERR("No space in Friend Queue for %u segments", *seg_count); + 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 -ENOBUFS; + } + + /* 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_INFO("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 (rpl) { + update_rpl(rpl, net_rx); + } + + *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 = {0}; + u8_t seg_count = 0U; + int err = 0; + + 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_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, &seg_count); + } else { + seg_count = 1U; + 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_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_FRIEND) && rx->friend_match && !err) { + if (seq_auth == TRANS_SEQ_AUTH_NVAL) { + bt_mesh_friend_enqueue_rx(rx, pdu_type, NULL, + seg_count, buf); + } else { + bt_mesh_friend_enqueue_rx(rx, pdu_type, &seq_auth, + seg_count, 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; + } + + bt_mesh_tx_seg_mutex_new(); +} + +void bt_mesh_trans_deinit(bool erase) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) { + seg_rx_reset(&seg_rx[i], true); + } + + if (erase && IS_ENABLED(CONFIG_BLE_MESH_SETTINGS)) { + bt_mesh_clear_rpl(); + } else { + bt_mesh_rpl_clear(); + } + + bt_mesh_tx_reset(); + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) { + k_delayed_work_free(&seg_tx[i].retransmit); + } + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) { + k_delayed_work_free(&seg_rx[i].ack); + } + + bt_mesh_tx_seg_mutex_free(); +} + +void bt_mesh_rpl_clear(void) +{ + BT_DBG("%s", __func__); + (void)memset(bt_mesh.rpl, 0, sizeof(bt_mesh.rpl)); +} + +void bt_mesh_heartbeat_send(void) +{ + struct bt_mesh_cfg_srv *cfg = bt_mesh_cfg_get(); + 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(cfg->model)->addr, + .xmit = bt_mesh_net_transmit_get(), + }; + + /* Do nothing if heartbeat publication is not enabled */ + if (cfg->hb_pub.dst == BLE_MESH_ADDR_UNASSIGNED) { + return; + } + + 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 (bt_mesh_lpn_established()) { + feat |= BLE_MESH_FEAT_LOW_POWER; + } + + hb.feat = sys_cpu_to_be16(feat); + + BT_INFO("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); +} + +int bt_mesh_app_key_get(const struct bt_mesh_subnet *subnet, u16_t app_idx, + const u8_t **key, u8_t *aid, u8_t role, u16_t dst) +{ + struct bt_mesh_app_key *app_key = NULL; + + if (app_idx == BLE_MESH_KEY_DEV) { + *key = bt_mesh_tx_devkey_get(role, dst); + if (!*key) { + BT_ERR("%s, Failed to get Device Key", __func__); + return -EINVAL; + } + + *aid = 0U; + return 0; + } + + if (!subnet) { + BT_ERR("%s, Invalid subnet", __func__); + return -EINVAL; + } + + app_key = bt_mesh_tx_appkey_get(role, app_idx); + if (!app_key) { + BT_ERR("%s, Failed to get AppKey", __func__); + return -ENOENT; + } + + if (subnet->kr_phase == BLE_MESH_KR_PHASE_2 && app_key->updated) { + *key = app_key->keys[1].val; + *aid = app_key->keys[1].id; + } else { + *key = app_key->keys[0].val; + *aid = app_key->keys[0].id; + } + + return 0; +} diff --git a/components/bt/esp_ble_mesh/mesh_core/transport.h b/components/bt/esp_ble_mesh/mesh_core/transport.h new file mode 100644 index 0000000000..028b62be85 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_core/transport.h @@ -0,0 +1,111 @@ +/* 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_ + +#include "net.h" + +#define TRANS_SEQ_AUTH_NVAL 0xffffffffffffffff + +#define BLE_MESH_TX_SDU_MAX (CONFIG_BLE_MESH_TX_SEG_MAX * 12) + +#define TRANS_SEQ_ZERO_MASK ((u16_t)BIT_MASK(13)) +#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_trans_deinit(bool erase); + +void bt_mesh_rpl_clear(void); + +void bt_mesh_heartbeat_send(void); + +int bt_mesh_app_key_get(const struct bt_mesh_subnet *subnet, u16_t app_idx, + const u8_t **key, u8_t *aid, u8_t role, u16_t dst); + +#endif /* _TRANSPORT_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/client/client_common.c b/components/bt/esp_ble_mesh/mesh_models/client/client_common.c new file mode 100644 index 0000000000..13b2896cb9 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/client_common.c @@ -0,0 +1,408 @@ +// Copyright 2017-2019 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.h" +#include "mesh_main.h" +#include "client_common.h" +#include "mesh_common.h" + +static bt_mesh_client_node_t *bt_mesh_client_pick_node(sys_slist_t *list, u16_t tx_dst) +{ + bt_mesh_client_node_t *node = NULL; + sys_snode_t *cur = NULL; + + bt_mesh_list_lock(); + if (sys_slist_is_empty(list)) { + bt_mesh_list_unlock(); + return 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) { + bt_mesh_list_unlock(); + return node; + } + } + + bt_mesh_list_unlock(); + return NULL; +} + +bt_mesh_client_node_t *bt_mesh_is_client_recv_publish_msg( + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, bool need_pub) +{ + bt_mesh_client_internal_data_t *data = NULL; + bt_mesh_client_user_data_t *cli = NULL; + bt_mesh_client_node_t *node = NULL; + + if (!model || !ctx || !buf) { + BT_ERR("%s, Invalid parameter", __func__); + return NULL; + } + + cli = (bt_mesh_client_user_data_t *)model->user_data; + if (!cli) { + BT_ERR("%s, Clinet user_data is NULL", __func__); + return NULL; + } + + /** 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", ctx->recv_op); + if (cli->publish_status && need_pub) { + cli->publish_status(ctx->recv_op, 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_client_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", ctx->recv_op); + if (cli->publish_status && need_pub) { + cli->publish_status(ctx->recv_op, model, ctx, buf); + } + return NULL; + } + + if (node->op_pending != ctx->recv_op) { + BT_DBG("Unexpected status message 0x%x", ctx->recv_op); + if (cli->publish_status && need_pub) { + cli->publish_status(ctx->recv_op, model, ctx, buf); + } + return NULL; + } + + if (k_delayed_work_remaining_get(&node->timer) == 0) { + BT_DBG("Unexpected status message 0x%x", ctx->recv_op); + if (cli->publish_status && need_pub) { + cli->publish_status(ctx->recv_op, model, ctx, buf); + } + return NULL; + } + + return node; +} + +static bool bt_mesh_client_check_node_in_list(sys_slist_t *list, u16_t tx_dst) +{ + bt_mesh_client_node_t *node = NULL; + sys_snode_t *cur = NULL; + + bt_mesh_list_lock(); + if (sys_slist_is_empty(list)) { + bt_mesh_list_unlock(); + return false; + } + + 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) { + bt_mesh_list_unlock(); + return true; + } + } + + bt_mesh_list_unlock(); + return false; +} + +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_client_internal_data_t *internal = NULL; + bt_mesh_client_user_data_t *cli = NULL; + bt_mesh_client_node_t *node = NULL; + int err = 0; + + if (!model || !ctx || !msg) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + cli = (bt_mesh_client_user_data_t *)model->user_data; + __ASSERT(cli, "Invalid client value when sent client msg."); + internal = (bt_mesh_client_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 *)bt_mesh_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__); + bt_mesh_free(node); + return -EINVAL; + } + if ((err = bt_mesh_model_send(model, ctx, msg, cb, cb_data)) != 0) { + bt_mesh_free(node); + } else { + bt_mesh_list_lock(); + sys_slist_append(&internal->queue, &node->client_node); + bt_mesh_list_unlock(); + k_delayed_work_init(&node->timer, timer_handler); + k_delayed_work_submit(&node->timer, timeout ? timeout : CONFIG_BLE_MESH_CLIENT_MSG_TIMEOUT); + } + } + + return err; +} + +static bt_mesh_mutex_t client_model_lock; + +static void bt_mesh_client_model_mutex_new(void) +{ + if (!client_model_lock.mutex) { + bt_mesh_mutex_create(&client_model_lock); + } +} + +static void bt_mesh_client_model_mutex_free(void) +{ + bt_mesh_mutex_free(&client_model_lock); +} + +void bt_mesh_client_model_lock(void) +{ + bt_mesh_mutex_lock(&client_model_lock); +} + +void bt_mesh_client_model_unlock(void) +{ + bt_mesh_mutex_unlock(&client_model_lock); +} + +int bt_mesh_client_init(struct bt_mesh_model *model) +{ + bt_mesh_client_internal_data_t *data = NULL; + bt_mesh_client_user_data_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; + } + + if (!cli->internal_data) { + data = bt_mesh_calloc(sizeof(bt_mesh_client_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; + } else { + bt_mesh_client_clear_list(cli->internal_data); + } + + bt_mesh_client_model_mutex_new(); + + return 0; +} + +int bt_mesh_client_deinit(struct bt_mesh_model *model) +{ + bt_mesh_client_user_data_t *client = NULL; + + if (!model) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_client_user_data_t *)model->user_data; + if (!client) { + BT_ERR("%s, Client user_data is NULL", __func__); + return -EINVAL; + } + + if (client->internal_data) { + /* Remove items from the list */ + bt_mesh_client_clear_list(client->internal_data); + + /* Free the allocated internal data */ + bt_mesh_free(client->internal_data); + client->internal_data = NULL; + } + + bt_mesh_client_model_mutex_free(); + + return 0; +} + +int bt_mesh_client_free_node(bt_mesh_client_node_t *node) +{ + bt_mesh_client_internal_data_t *internal = NULL; + bt_mesh_client_user_data_t *client = NULL; + + if (!node || !node->ctx.model) { + BT_ERR("%s, Client model list item is NULL", __func__); + return -EINVAL; + } + + client = (bt_mesh_client_user_data_t *)node->ctx.model->user_data; + if (!client) { + BT_ERR("%s, Client model user data is NULL", __func__); + return -EINVAL; + } + + internal = (bt_mesh_client_internal_data_t *)client->internal_data; + if (!internal) { + BT_ERR("%s, Client model internal data is NULL", __func__); + return -EINVAL; + } + + // Release the client node from the queue + bt_mesh_list_lock(); + sys_slist_find_and_remove(&internal->queue, &node->client_node); + bt_mesh_list_unlock(); + // Free the node + bt_mesh_free(node); + + return 0; +} + +int bt_mesh_client_clear_list(void *data) +{ + bt_mesh_client_internal_data_t *internal = NULL; + bt_mesh_client_node_t *node = NULL; + + if (!data) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + internal = (bt_mesh_client_internal_data_t *)data; + + bt_mesh_list_lock(); + while (!sys_slist_is_empty(&internal->queue)) { + node = (void *)sys_slist_get_not_empty(&internal->queue); + bt_mesh_free(node); + } + bt_mesh_list_unlock(); + + return 0; +} + +int bt_mesh_set_client_model_role(bt_mesh_role_param_t *common) +{ + bt_mesh_client_user_data_t *client = NULL; + + if (!common || !common->model || !common->model->user_data) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + client = (bt_mesh_client_user_data_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; +} diff --git a/components/bt/esp_ble_mesh/mesh_models/client/generic_client.c b/components/bt/esp_ble_mesh/mesh_models/client/generic_client.c new file mode 100644 index 0000000000..3ccb43a79c --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/generic_client.c @@ -0,0 +1,1298 @@ +// Copyright 2017-2019 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 "model_opcode.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 bt_mesh_mutex_t generic_client_lock; + +static void bt_mesh_generic_client_mutex_new(void) +{ + if (!generic_client_lock.mutex) { + bt_mesh_mutex_create(&generic_client_lock); + } +} + +static void bt_mesh_generic_client_mutex_free(void) +{ + bt_mesh_mutex_free(&generic_client_lock); +} + +static void bt_mesh_generic_client_lock(void) +{ + bt_mesh_mutex_lock(&generic_client_lock); +} + +static void bt_mesh_generic_client_unlock(void) +{ + bt_mesh_mutex_unlock(&generic_client_lock); +} + +static void timeout_handler(struct k_work *work) +{ + struct k_delayed_work *timer = NULL; + bt_mesh_client_node_t *node = NULL; + struct bt_mesh_msg_ctx ctx = {0}; + u32_t opcode = 0U; + + BT_WARN("Receive generic status message timeout"); + + bt_mesh_generic_client_lock(); + + timer = CONTAINER_OF(work, struct k_delayed_work, work); + + if (timer && !k_delayed_work_free(timer)) { + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (node) { + memcpy(&ctx, &node->ctx, sizeof(ctx)); + opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_generic_client_cb_evt_to_btc( + opcode, BTC_BLE_MESH_EVT_GENERIC_CLIENT_TIMEOUT, ctx.model, &ctx, NULL, 0); + } + } + + bt_mesh_generic_client_unlock(); + + return; +} + +static void generic_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 *val = NULL; + u8_t evt = 0xFF; + size_t len = 0U; + + BT_DBG("%s, len %d, bytes %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + switch (ctx->recv_op) { + 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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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; + + bt_mesh_generic_client_lock(); + + node = bt_mesh_is_client_recv_publish_msg(model, ctx, buf, true); + if (!node) { + BT_DBG("Unexpected generic status message 0x%x", ctx->recv_op); + } 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 = BTC_BLE_MESH_EVT_GENERIC_CLIENT_GET_STATE; + 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 = BTC_BLE_MESH_EVT_GENERIC_CLIENT_SET_STATE; + break; + default: + break; + } + + if (!k_delayed_work_free(&node->timer)) { + u32_t opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_generic_client_cb_evt_to_btc(opcode, evt, model, ctx, val, len); + } + } + + bt_mesh_generic_client_unlock(); + + switch (ctx->recv_op) { + 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; + } + + bt_mesh_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(bt_mesh_client_common_param_t *common, void *value) +{ + NET_BUF_SIMPLE_DEFINE(msg, BLE_MESH_GEN_GET_STATE_MSG_LEN); + int err = 0; + + 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(bt_mesh_client_common_param_t *common, + void *value, u16_t value_len, bool need_ack) +{ + struct net_buf_simple *msg = NULL; + int err = 0; + + 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->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_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(bt_mesh_client_common_param_t *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(bt_mesh_client_common_param_t *common, void *set, void *status) +{ + bt_mesh_generic_client_t *client = NULL; + u16_t length = 0U; + 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; + } + + if (!client->internal_data) { + internal = bt_mesh_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; + } else { + bt_mesh_client_clear_list(client->internal_data); + } + + bt_mesh_generic_client_mutex_new(); + + 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); +} + +static int generic_client_deinit(struct bt_mesh_model *model, bool primary) +{ + bt_mesh_generic_client_t *client = NULL; + + 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; + } + + if (client->internal_data) { + /* Remove items from the list */ + bt_mesh_client_clear_list(client->internal_data); + + /* Free the allocated internal data */ + bt_mesh_free(client->internal_data); + client->internal_data = NULL; + } + + bt_mesh_generic_client_mutex_free(); + + return 0; +} + +int bt_mesh_gen_onoff_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_client_deinit(model, primary); +} + +int bt_mesh_gen_level_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_client_deinit(model, primary); +} + +int bt_mesh_gen_def_trans_time_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_client_deinit(model, primary); +} + +int bt_mesh_gen_pwr_onoff_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_client_deinit(model, primary); +} + +int bt_mesh_gen_pwr_level_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_client_deinit(model, primary); +} + +int bt_mesh_gen_battery_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_client_deinit(model, primary); +} + +int bt_mesh_gen_location_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_client_deinit(model, primary); +} + +int bt_mesh_gen_property_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_client_deinit(model, primary); +} diff --git a/components/bt/esp_ble_mesh/mesh_models/client/include/client_common.h b/components/bt/esp_ble_mesh/mesh_models/client/include/client_common.h new file mode 100644 index 0000000000..0e337d34bc --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/include/client_common.h @@ -0,0 +1,130 @@ +// Copyright 2017-2019 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 _CLIENT_COMMON_H_ +#define _CLIENT_COMMON_H_ + +#include "mesh_access.h" + +/** Client model opcode pair table */ +typedef struct { + u32_t cli_op; /* Client message opcode */ + u32_t status_op; /* Corresponding status message opcode */ +} bt_mesh_client_op_pair_t; + +/** Client model user data context */ +typedef struct { + /** Pointer to the client model */ + struct bt_mesh_model *model; + + /** Size of the opcode pair table */ + int op_pair_size; + + /** Pointer to the opcode pair table */ + 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); + + /** Pointer to the internal data of client model */ + void *internal_data; + + /** Role of the device to which the client model belongs */ + u8_t msg_role; +} bt_mesh_client_user_data_t; + +/** Client model internal data context */ +typedef struct { + sys_slist_t queue; +} bt_mesh_client_internal_data_t; + +/** Client model sending message related context */ +typedef struct { + sys_snode_t client_node; + struct bt_mesh_msg_ctx ctx; /* Message context */ + u32_t opcode; /* Message opcode */ + u32_t op_pending; /* Expected status message opcode */ + struct k_delayed_work timer; /* Time used to get response. Only for internal use. */ +} bt_mesh_client_node_t; + +/** Client model sending message parameters */ +typedef struct { + u32_t opcode; /* Message opcode */ + struct bt_mesh_model *model; /* Pointer to the client model */ + struct bt_mesh_msg_ctx ctx; /* Message context */ + s32_t msg_timeout; /* Time to get corresponding response */ + const struct bt_mesh_send_cb *cb; /* User defined callback function */ + void *cb_data; /* User defined callback value */ +} bt_mesh_client_common_param_t; + +void bt_mesh_client_model_lock(void); + +void bt_mesh_client_model_unlock(void); + +int bt_mesh_client_init(struct bt_mesh_model *model); + +int bt_mesh_client_deinit(struct bt_mesh_model *model); + +/** + * @brief Check if the msg received by client model 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_client_recv_publish_msg( + struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, bool need_pub); + +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(bt_mesh_client_node_t *node); + +int bt_mesh_client_clear_list(void *data); + +typedef struct { + 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 bt_mesh_role_param_t structure + * + * @return Zero - success, otherwise - fail + */ +int bt_mesh_set_client_model_role(bt_mesh_role_param_t *common); + +#endif /* _CLIENT_COMMON_H_ */ + diff --git a/components/bt/esp_ble_mesh/mesh_models/client/include/generic_client.h b/components/bt/esp_ble_mesh/mesh_models/client/include/generic_client.h new file mode 100644 index 0000000000..ddee425ac8 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/include/generic_client.h @@ -0,0 +1,569 @@ +// Copyright 2017-2019 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 "client_common.h" + +/* Generic client model common structure */ +typedef bt_mesh_client_user_data_t bt_mesh_generic_client_t; +typedef bt_mesh_client_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_user_data_t bt_mesh_gen_onoff_client_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_user_data_t bt_mesh_gen_level_client_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 delta_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_user_data_t bt_mesh_gen_def_trans_time_client_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_user_data_t bt_mesh_gen_power_onoff_client_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_user_data_t bt_mesh_gen_power_level_client_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_user_data_t bt_mesh_gen_battery_client_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_user_data_t bt_mesh_gen_location_client_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_user_data_t bt_mesh_gen_property_client_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 de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(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(bt_mesh_client_common_param_t *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(bt_mesh_client_common_param_t *common, void *set, void *status); + +#endif /* _GENERIC_CLIENT_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/client/include/lighting_client.h b/components/bt/esp_ble_mesh/mesh_models/client/include/lighting_client.h new file mode 100644 index 0000000000..309a7c57ea --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/include/lighting_client.h @@ -0,0 +1,539 @@ +// Copyright 2017-2019 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 "client_common.h" + +/* Light client model common structure */ +typedef bt_mesh_client_user_data_t bt_mesh_light_client_t; +typedef bt_mesh_client_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_user_data_t bt_mesh_light_lightness_client_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_user_data_t bt_mesh_light_ctl_client_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_user_data_t bt_mesh_light_hsl_client_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_user_data_t bt_mesh_light_xyl_client_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_user_data_t bt_mesh_light_lc_client_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 de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(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(bt_mesh_client_common_param_t *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(bt_mesh_client_common_param_t *common, void *set, void *status); + +#endif /* _LIGHTING_CLIENT_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/client/include/sensor_client.h b/components/bt/esp_ble_mesh/mesh_models/client/include/sensor_client.h new file mode 100644 index 0000000000..046e099166 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/include/sensor_client.h @@ -0,0 +1,174 @@ +// Copyright 2017-2019 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 "client_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_user_data_t bt_mesh_sensor_client_t; +typedef bt_mesh_client_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 de-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_deinit(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(bt_mesh_client_common_param_t *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(bt_mesh_client_common_param_t *common, void *set, void *status); + +#endif /* _SENSOR_CLIENT_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/client/include/time_scene_client.h b/components/bt/esp_ble_mesh/mesh_models/client/include/time_scene_client.h new file mode 100644 index 0000000000..de978d0119 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/include/time_scene_client.h @@ -0,0 +1,284 @@ +// Copyright 2017-2019 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 "client_common.h" + +/* Time scene client model common structure */ +typedef bt_mesh_client_user_data_t bt_mesh_time_scene_client_t; +typedef bt_mesh_client_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_user_data_t bt_mesh_time_client_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_user_data_t bt_mesh_scene_client_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_user_data_t bt_mesh_scheduler_client_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 de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(struct bt_mesh_model *model, bool primary); + +/** + * @brief This function is called to de-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_deinit(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(bt_mesh_client_common_param_t *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(bt_mesh_client_common_param_t *common, void *set, void *status); + +#endif /* _TIME_SCENE_CLIENT_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/client/lighting_client.c b/components/bt/esp_ble_mesh/mesh_models/client/lighting_client.c new file mode 100644 index 0000000000..ff693cf882 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/lighting_client.c @@ -0,0 +1,1458 @@ +// Copyright 2017-2019 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 "model_opcode.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 bt_mesh_mutex_t light_client_lock; + +static void bt_mesh_light_client_mutex_new(void) +{ + if (!light_client_lock.mutex) { + bt_mesh_mutex_create(&light_client_lock); + } +} + +static void bt_mesh_light_client_mutex_free(void) +{ + bt_mesh_mutex_free(&light_client_lock); +} + +static void bt_mesh_light_client_lock(void) +{ + bt_mesh_mutex_lock(&light_client_lock); +} + +static void bt_mesh_light_client_unlock(void) +{ + bt_mesh_mutex_unlock(&light_client_lock); +} + +static void timeout_handler(struct k_work *work) +{ + struct k_delayed_work *timer = NULL; + bt_mesh_client_node_t *node = NULL; + struct bt_mesh_msg_ctx ctx = {0}; + u32_t opcode = 0U; + + BT_WARN("Receive light status message timeout"); + + bt_mesh_light_client_lock(); + + timer = CONTAINER_OF(work, struct k_delayed_work, work); + + if (timer && !k_delayed_work_free(timer)) { + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (node) { + memcpy(&ctx, &node->ctx, sizeof(ctx)); + opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_lighting_client_cb_evt_to_btc( + opcode, BTC_BLE_MESH_EVT_LIGHTING_CLIENT_TIMEOUT, ctx.model, &ctx, NULL, 0); + } + } + + bt_mesh_light_client_unlock(); + + return; +} + +static void light_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 *val = NULL; + u8_t evt = 0xFF; + size_t len = 0U; + + BT_DBG("%s, len %d, bytes %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + switch (ctx->recv_op) { + 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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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__); + bt_mesh_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; + + bt_mesh_light_client_lock(); + + node = bt_mesh_is_client_recv_publish_msg(model, ctx, buf, true); + if (!node) { + BT_DBG("Unexpected light status message 0x%x", ctx->recv_op); + } 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 = BTC_BLE_MESH_EVT_LIGHTING_CLIENT_GET_STATE; + 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 = BTC_BLE_MESH_EVT_LIGHTING_CLIENT_SET_STATE; + break; + default: + break; + } + + if (!k_delayed_work_free(&node->timer)) { + u32_t opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_lighting_client_cb_evt_to_btc(opcode, evt, model, ctx, val, len); + } + } + + bt_mesh_light_client_unlock(); + + switch (ctx->recv_op) { + 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; + } + + bt_mesh_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(bt_mesh_client_common_param_t *common, void *value) +{ + NET_BUF_SIMPLE_DEFINE(msg, BLE_MESH_LIGHT_GET_STATE_MSG_LEN); + int err = 0; + + 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(bt_mesh_client_common_param_t *common, + void *value, u16_t value_len, bool need_ack) +{ + struct net_buf_simple *msg = NULL; + int err = 0; + + 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(bt_mesh_client_common_param_t *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(bt_mesh_client_common_param_t *common, void *set, void *status) +{ + bt_mesh_light_client_t *client = NULL; + u16_t length = 0U; + 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; + } + + if (!client->internal_data) { + internal = bt_mesh_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; + } else { + bt_mesh_client_clear_list(client->internal_data); + } + + bt_mesh_light_client_mutex_new(); + + 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); +} + +static int light_client_deinit(struct bt_mesh_model *model, bool primary) +{ + bt_mesh_light_client_t *client = NULL; + + 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; + } + + if (client->internal_data) { + /* Remove items from the list */ + bt_mesh_client_clear_list(client->internal_data); + + /* Free the allocated internal data */ + bt_mesh_free(client->internal_data); + client->internal_data = NULL; + } + + bt_mesh_light_client_mutex_free(); + + return 0; +} + +int bt_mesh_light_lightness_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return light_client_deinit(model, primary); +} + +int bt_mesh_light_ctl_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return light_client_deinit(model, primary); +} + +int bt_mesh_light_hsl_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return light_client_deinit(model, primary); +} + +int bt_mesh_light_xyl_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return light_client_deinit(model, primary); +} + +int bt_mesh_light_lc_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return light_client_deinit(model, primary); +} \ No newline at end of file diff --git a/components/bt/esp_ble_mesh/mesh_models/client/sensor_client.c b/components/bt/esp_ble_mesh/mesh_models/client/sensor_client.c new file mode 100644 index 0000000000..9df2e25ae6 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/sensor_client.c @@ -0,0 +1,650 @@ +// Copyright 2017-2019 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 "model_opcode.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 bt_mesh_mutex_t sensor_client_lock; + +static void bt_mesh_sensor_client_mutex_new(void) +{ + if (!sensor_client_lock.mutex) { + bt_mesh_mutex_create(&sensor_client_lock); + } +} + +static void bt_mesh_sensor_client_mutex_free(void) +{ + bt_mesh_mutex_free(&sensor_client_lock); +} + +static void bt_mesh_sensor_client_lock(void) +{ + bt_mesh_mutex_lock(&sensor_client_lock); +} + +static void bt_mesh_sensor_client_unlock(void) +{ + bt_mesh_mutex_unlock(&sensor_client_lock); +} + +static void timeout_handler(struct k_work *work) +{ + struct k_delayed_work *timer = NULL; + bt_mesh_client_node_t *node = NULL; + struct bt_mesh_msg_ctx ctx = {0}; + u32_t opcode = 0U; + + BT_WARN("Receive sensor status message timeout"); + + bt_mesh_sensor_client_lock(); + + timer = CONTAINER_OF(work, struct k_delayed_work, work); + + if (timer && !k_delayed_work_free(timer)) { + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (node) { + memcpy(&ctx, &node->ctx, sizeof(ctx)); + opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_sensor_client_cb_evt_to_btc( + opcode, BTC_BLE_MESH_EVT_SENSOR_CLIENT_TIMEOUT, ctx.model, &ctx, NULL, 0); + } + } + + bt_mesh_sensor_client_unlock(); + + return; +} + +static void sensor_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 *val = NULL; + u8_t evt = 0xFF; + size_t len = 0U; + + BT_DBG("%s, len %d, bytes %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS: { + struct bt_mesh_sensor_descriptor_status *status = NULL; + status = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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__); + bt_mesh_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; + + bt_mesh_sensor_client_lock(); + + node = bt_mesh_is_client_recv_publish_msg(model, ctx, buf, true); + if (!node) { + BT_DBG("Unexpected sensor status message 0x%x", ctx->recv_op); + } 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 = BTC_BLE_MESH_EVT_SENSOR_CLIENT_GET_STATE; + break; + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET: + case BLE_MESH_MODEL_OP_SENSOR_SETTING_SET: + evt = BTC_BLE_MESH_EVT_SENSOR_CLIENT_SET_STATE; + break; + default: + break; + } + + if (!k_delayed_work_free(&node->timer)) { + u32_t opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_sensor_client_cb_evt_to_btc(opcode, evt, model, ctx, val, len); + } + } + + bt_mesh_sensor_client_unlock(); + + switch (ctx->recv_op) { + 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; + } + + bt_mesh_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(bt_mesh_client_common_param_t *common, + void *value, u16_t value_len, bool need_ack) +{ + struct net_buf_simple *msg = NULL; + int err = 0; + + 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(bt_mesh_client_common_param_t *common, void *get, void *status) +{ + bt_mesh_sensor_client_t *client = NULL; + u16_t length = 0U; + + 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(bt_mesh_client_common_param_t *common, void *set, void *status) +{ + bt_mesh_sensor_client_t *client = NULL; + u16_t length = 0U; + 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; + } + + if (!client->internal_data) { + internal = bt_mesh_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; + } else { + bt_mesh_client_clear_list(client->internal_data); + } + + bt_mesh_sensor_client_mutex_new(); + + return 0; +} + +int bt_mesh_sensor_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + bt_mesh_sensor_client_t *client = NULL; + + 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; + } + + if (client->internal_data) { + /* Remove items from the list */ + bt_mesh_client_clear_list(client->internal_data); + + /* Free the allocated internal data */ + bt_mesh_free(client->internal_data); + client->internal_data = NULL; + } + + bt_mesh_sensor_client_mutex_free(); + + return 0; +} \ No newline at end of file diff --git a/components/bt/esp_ble_mesh/mesh_models/client/time_scene_client.c b/components/bt/esp_ble_mesh/mesh_models/client/time_scene_client.c new file mode 100644 index 0000000000..15961d1eb2 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/client/time_scene_client.c @@ -0,0 +1,743 @@ +// Copyright 2017-2019 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 "model_opcode.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 bt_mesh_mutex_t time_scene_client_lock; + +static void bt_mesh_time_scene_client_mutex_new(void) +{ + if (!time_scene_client_lock.mutex) { + bt_mesh_mutex_create(&time_scene_client_lock); + } +} + +static void bt_mesh_time_scene_client_mutex_free(void) +{ + bt_mesh_mutex_free(&time_scene_client_lock); +} + +static void bt_mesh_time_scene_client_lock(void) +{ + bt_mesh_mutex_lock(&time_scene_client_lock); +} + +static void bt_mesh_time_scene_client_unlock(void) +{ + bt_mesh_mutex_unlock(&time_scene_client_lock); +} + +static void timeout_handler(struct k_work *work) +{ + struct k_delayed_work *timer = NULL; + bt_mesh_client_node_t *node = NULL; + struct bt_mesh_msg_ctx ctx = {0}; + u32_t opcode = 0U; + + BT_WARN("Receive time scene status message timeout"); + + bt_mesh_time_scene_client_lock(); + + timer = CONTAINER_OF(work, struct k_delayed_work, work); + + if (timer && !k_delayed_work_free(timer)) { + node = CONTAINER_OF(work, bt_mesh_client_node_t, timer.work); + if (node) { + memcpy(&ctx, &node->ctx, sizeof(ctx)); + opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_time_scene_client_cb_evt_to_btc( + opcode, BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_TIMEOUT, ctx.model, &ctx, NULL, 0); + } + } + + bt_mesh_time_scene_client_unlock(); + + return; +} + +static void time_scene_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 *val = NULL; + u8_t evt = 0xFF; + size_t len = 0U; + + BT_DBG("%s, len %d, bytes %s", __func__, buf->len, bt_hex(buf->data, buf->len)); + + switch (ctx->recv_op) { + 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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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 = bt_mesh_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__); + bt_mesh_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 = bt_mesh_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 = bt_mesh_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; + + bt_mesh_time_scene_client_lock(); + + node = bt_mesh_is_client_recv_publish_msg(model, ctx, buf, true); + if (!node) { + BT_DBG("Unexpected time scene status message 0x%x", ctx->recv_op); + } 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 = BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_GET_STATE; + 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 = BTC_BLE_MESH_EVT_TIME_SCENE_CLIENT_SET_STATE; + break; + default: + break; + } + + if (!k_delayed_work_free(&node->timer)) { + u32_t opcode = node->opcode; + bt_mesh_client_free_node(node); + bt_mesh_time_scene_client_cb_evt_to_btc(opcode, evt, model, ctx, val, len); + } + } + + bt_mesh_time_scene_client_unlock(); + + switch (ctx->recv_op) { + 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; + } + + bt_mesh_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(bt_mesh_client_common_param_t *common, void *value) +{ + NET_BUF_SIMPLE_DEFINE(msg, BLE_MESH_SCENE_GET_STATE_MSG_LEN); + int err = 0; + + 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(bt_mesh_client_common_param_t *common, + void *value, u16_t value_len, bool need_ack) +{ + struct net_buf_simple *msg = NULL; + int err = 0; + + 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(bt_mesh_client_common_param_t *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(bt_mesh_client_common_param_t *common, void *set, void *status) +{ + bt_mesh_time_scene_client_t *client = NULL; + u16_t length = 0U; + 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; + } + + if (!client->internal_data) { + internal = bt_mesh_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; + } else { + bt_mesh_client_clear_list(client->internal_data); + } + + bt_mesh_time_scene_client_mutex_new(); + + 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); +} + +static int time_scene_client_deinit(struct bt_mesh_model *model, bool primary) +{ + bt_mesh_time_scene_client_t *client = NULL; + + 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; + } + + if (client->internal_data) { + /* Remove items from the list */ + bt_mesh_client_clear_list(client->internal_data); + + /* Free the allocated internal data */ + bt_mesh_free(client->internal_data); + client->internal_data = NULL; + } + + bt_mesh_time_scene_client_mutex_free(); + + return 0; +} + +int bt_mesh_time_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return time_scene_client_deinit(model, primary); +} + +int bt_mesh_scene_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return time_scene_client_deinit(model, primary); +} + +int bt_mesh_scheduler_cli_deinit(struct bt_mesh_model *model, bool primary) +{ + return time_scene_client_deinit(model, primary); +} \ No newline at end of file diff --git a/components/bt/esp_ble_mesh/mesh_models/common/include/model_opcode.h b/components/bt/esp_ble_mesh/mesh_models/common/include/model_opcode.h new file mode 100644 index 0000000000..95def6c2c4 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/common/include/model_opcode.h @@ -0,0 +1,276 @@ +// Copyright 2017-2019 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_access.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) + +/* 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/esp_ble_mesh/mesh_models/server/device_property.c b/components/bt/esp_ble_mesh/mesh_models/server/device_property.c new file mode 100644 index 0000000000..9e8afffcd1 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/device_property.c @@ -0,0 +1,145 @@ +// Copyright 2017-2019 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 "server_common.h" +#include "device_property.h" + +static struct bt_mesh_dev_prop { + u16_t prop_id; + u8_t len; +} device_properties [] = { + { BLE_MESH_INVALID_DEVICE_PROPERTY_ID, 0xFF }, + { BLE_MESH_AVERAGE_AMBIENT_TEMPERATURE_IN_A_PERIOD_OF_DAY, 0x03 }, + { BLE_MESH_AVERAGE_INPUT_CURRENT, 0x03 }, + { BLE_MESH_AVERAGE_INPUT_VOLTAGE, 0x03 }, + { BLE_MESH_AVERAGE_OUTPUT_CURRENT, 0x03 }, + { BLE_MESH_AVERAGE_OUTPUT_VOLTAGE, 0x03 }, + { BLE_MESH_CENTER_BEAM_INTENSITY_AT_FULL_POWER, 0x02 }, + { BLE_MESH_CHROMATICITY_TOLERANCE, 0x01 }, + { BLE_MESH_COLOR_RENDERING_INDEX_R9, 0x01 }, + { BLE_MESH_COLOR_RENDERING_INDEX_RA, 0x01 }, + { BLE_MESH_DEVICE_APPEARANCE, 0x02 }, + { BLE_MESH_DEVICE_COUNTRY_OF_ORIGIN, 0x02 }, + { BLE_MESH_DEVICE_DATE_OF_MANUFACTURE, 0x04 }, + { BLE_MESH_DEVICE_ENERGY_USE_SINCE_TURN_ON, 0x04 }, + { BLE_MESH_DEVICE_FIRMWARE_REVISION, 0x08 }, + { BLE_MESH_DEVICE_GLOBAL_TRADE_ITEM_NUMBER, 0x08 }, + { BLE_MESH_DEVICE_HARDWARE_REVISION, 0x16 }, + { BLE_MESH_DEVICE_MANUFACTURER_NAME, 0x36 }, + { BLE_MESH_DEVICE_MODEL_NUMBER, 0x24 }, + { BLE_MESH_DEVICE_OPERATING_TEMPERATURE_RANGE_SPECIFICATION, 0x04 }, + { BLE_MESH_DEVICE_OPERATING_TEMPERATURE_STATISTICAL_VALUES, 0x09 }, + { BLE_MESH_DEVICE_OVER_TEMPERATURE_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_DEVICE_POWER_RANGE_SPECIFICATION, 0x12 }, + { BLE_MESH_DEVICE_RUNTIME_SINCE_TURN_ON, 0x04 }, + { BLE_MESH_DEVICE_RUNTIME_WARRANTY, 0x04 }, + { BLE_MESH_DEVICE_SERIAL_NUMBER, 0x16 }, + { BLE_MESH_DEVICE_SOFTWARE_REVISION, 0x08 }, + { BLE_MESH_DEVICE_UNDER_TEMPERATURE_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_INDOOR_AMBIENT_TEMPERATURE_STATISTICAL_VALUES, 0x05 }, + { BLE_MESH_INITIAL_CIE_1931_CHROMATICITY_COORDINATES, 0x04 }, + { BLE_MESH_INITIAL_CORRELATED_COLOR_TEMPERATURE, 0x02 }, + { BLE_MESH_INITIAL_LUMINOUS_FLUX, 0x02 }, + { BLE_MESH_INITIAL_PLANCKIAN_DISTANCE, 0x02 }, + { BLE_MESH_INPUT_CURRENT_RANGE_SPECIFICATION, 0x06 }, + { BLE_MESH_INPUT_CURRENT_STATISTICS, 0x09 }, + { BLE_MESH_INPUT_OVER_CURRENT_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_INPUT_OVER_RIPPLE_VOLTAGE_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_INPUT_OVER_VOLTAGE_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_INPUT_UNDER_CURRENT_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_INPUT_UNDER_VOLTAGE_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_INPUT_VOLTAGE_RANGE_SPECIFICATION, 0x06 }, + { BLE_MESH_INPUT_VOLTAGE_RIPPLE_SPECIFICATION, 0x01 }, + { BLE_MESH_INPUT_VOLTAGE_STATISTICS, 0x09 }, + { BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_ON, 0x04 }, + { BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_PROLONG, 0x04 }, + { BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_STANDBY, 0x04 }, + { BLE_MESH_LIGHT_CONTROL_LIGHTNESS_ON, 0x02 }, + { BLE_MESH_LIGHT_CONTROL_LIGHTNESS_PROLONG, 0x02 }, + { BLE_MESH_LIGHT_CONTROL_LIGHTNESS_STANDBY, 0x02 }, + { BLE_MESH_LIGHT_CONTROL_REGULATOR_ACCURACY, 0x01 }, + { BLE_MESH_LIGHT_CONTROL_REGULATOR_KID, 0x04 }, + { BLE_MESH_LIGHT_CONTROL_REGULATOR_KIU, 0x04 }, + { BLE_MESH_LIGHT_CONTROL_REGULATOR_KPD, 0x04 }, + { BLE_MESH_LIGHT_CONTROL_REGULATOR_KPU, 0x04 }, + { BLE_MESH_LIGHT_CONTROL_TIME_FADE, 0x03 }, + { BLE_MESH_LIGHT_CONTROL_TIME_FADE_ON, 0x03 }, + { BLE_MESH_LIGHT_CONTROL_TIME_FADE_STANDBY_AUTO, 0x03 }, + { BLE_MESH_LIGHT_CONTROL_TIME_FADE_STANDBY_MANUAL, 0x03 }, + { BLE_MESH_LIGHT_CONTROL_TIME_OCCUPANCY_DELAY, 0x03 }, + { BLE_MESH_LIGHT_CONTROL_TIME_PROLONG, 0x03 }, + { BLE_MESH_LIGHT_CONTROL_TIME_RUN_ON, 0x03 }, + { BLE_MESH_LUMEN_MAINTENANCE_FACTOR, 0x01 }, + { BLE_MESH_LUMINOUS_EFFICACY, 0x02 }, + { BLE_MESH_LUMINOUS_ENERGY_SINCE_TURN_ON, 0x04 }, + { BLE_MESH_LUMINOUS_EXPOSURE, 0x04 }, + { BLE_MESH_LUMINOUS_FLUX_RANGE, 0x04 }, + { BLE_MESH_MOTION_SENSED, 0x01 }, + { BLE_MESH_MOTION_THRESHOLD, 0x01 }, + { BLE_MESH_OPEN_CIRCUIT_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_OUTDOOR_STATISTICAL_VALUES, 0x05 }, + { BLE_MESH_OUTPUT_CURRENT_RANGE, 0x04 }, + { BLE_MESH_OUTPUT_CURRENT_STATISTICS, 0x09 }, + { BLE_MESH_OUTPUT_RIPPLE_VOLTAGE_SPECIFICATION, 0x01 }, + { BLE_MESH_OUTPUT_VOLTAGE_RANGE, 0x06 }, + { BLE_MESH_OUTPUT_VOLTAGE_STATISTICS, 0x09 }, + { BLE_MESH_OVER_OUTPUT_RIPPLE_VOLTAGE_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_PEOPLE_COUNT, 0x02 }, + { BLE_MESH_PRESENCE_DETECTED, 0x01 }, + { BLE_MESH_PRESENT_AMBIENT_LIGHT_LEVEL, 0x04 }, + { BLE_MESH_PRESENT_AMBIENT_TEMPERATURE, 0x01 }, + { BLE_MESH_PRESENT_CIE_1931_CHROMATICITY, 0x04 }, + { BLE_MESH_PRESENT_CORRELATED_COLOR_TEMPERATURE, 0x02 }, + { BLE_MESH_PRESENT_DEVICE_INPUT_POWER, 0x04 }, + { BLE_MESH_PRESENT_DEVICE_OPERATING_EFFICIENCY, 0x01 }, + { BLE_MESH_PRESENT_DEVICE_OPERATING_TEMPERATURE, 0x02 }, + { BLE_MESH_PRESENT_ILLUMINANCE, 0x04 }, + { BLE_MESH_PRESENT_INDOOR_AMBIENT_TEMPERATURE, 0x01 }, + { BLE_MESH_PRESENT_INPUT_CURRENT, 0x02 }, + { BLE_MESH_PRESENT_INPUT_RIPPLE_VOLTAGE, 0x01 }, + { BLE_MESH_PRESENT_INPUT_VOLTAGE, 0x02 }, + { BLE_MESH_PRESENT_LUMINOUS_FLUX, 0x02 }, + { BLE_MESH_PRESENT_OUTDOOR_AMBIENT_TEMPERATURE, 0x01 }, + { BLE_MESH_PRESENT_OUTPUT_CURRENT, 0x02 }, + { BLE_MESH_PRESENT_OUTPUT_VOLTAGE, 0x02 }, + { BLE_MESH_PRESENT_PLANCKIAN_DISTANCE, 0x02 }, + { BLE_MESH_PRESENT_RELATIVE_OUTPUT_RIPPLE_VOLTAGE, 0x01 }, + { BLE_MESH_RELATIVE_DEVICE_ENERGY_USE_IN_A_PERIOD_OF_DAY, 0x06 }, + { BLE_MESH_RELATIVE_DEVICE_RUNTIME_IN_A_GENERIC_LEVEL_RANGE, 0x05 }, + { BLE_MESH_RELATIVE_EXPOSURE_TIME_IN_AN_ILLUMINANCE_RANGE, 0x09 }, + { BLE_MESH_RELATIVE_RUNTIME_IN_A_CORRELATED_COLOR_TEMPERATURE_RANGE, 0x04 }, + { BLE_MESH_RELATIVE_RUNTIME_IN_A_DEVICE_OPERATING_TEMPERATURE_RANGE, 0x05 }, + { BLE_MESH_RELATIVE_RUNTIME_IN_AN_INPUT_CURRENT_RANGE, 0x05 }, + { BLE_MESH_RELATIVE_RUNTIME_IN_AN_INPUT_VOLTAGE_RANGE, 0x05 }, + { BLE_MESH_SHORT_CIRCUIT_EVENT_STATISTICS, 0x06 }, + { BLE_MESH_TIME_SINCE_MOTION_SENSED, 0x02 }, + { BLE_MESH_TIME_SINCE_PRESENCE_DETECTED, 0x02 }, + { BLE_MESH_TOTAL_DEVICE_ENERGY_USE, 0x04 }, + { BLE_MESH_TOTAL_DEVICE_OFF_ON_CYCLES, 0x04 }, + { BLE_MESH_TOTAL_DEVICE_POWER_ON_CYCLES, 0x04 }, + { BLE_MESH_TOTAL_DEVICE_POWER_ON_TIME, 0x04 }, + { BLE_MESH_TOTAL_DEVICE_RUNTIME, 0x04 }, + { BLE_MESH_TOTAL_LIGHT_EXPOSURE_TIME, 0x04 }, + { BLE_MESH_TOTAL_LUMINOUS_ENERGY, 0x04 }, +}; + +u8_t bt_mesh_get_dev_prop_len(u16_t prop_id) +{ + if (prop_id > BLE_MESH_TOTAL_LUMINOUS_ENERGY) { + BT_ERR("%s, Unknown Device Property ID 0x%04x", __func__, prop_id); + return UINT8_MAX; + } + + return device_properties[prop_id].len; +} diff --git a/components/bt/esp_ble_mesh/mesh_models/server/generic_server.c b/components/bt/esp_ble_mesh/mesh_models/server/generic_server.c new file mode 100644 index 0000000000..79f7c08ebb --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/generic_server.c @@ -0,0 +1,2795 @@ +/* Bluetooth: Mesh Generic Server Models + * + * Copyright (c) 2018 Vikrant More + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "access.h" +#include "transport.h" +#include "model_opcode.h" +#include "state_transition.h" +#include "device_property.h" + +#include "btc_ble_mesh_generic_model.h" + +static bt_mesh_mutex_t generic_server_lock; + +static void bt_mesh_generic_server_mutex_new(void) +{ + if (!generic_server_lock.mutex) { + bt_mesh_mutex_create(&generic_server_lock); + } +} + +static void bt_mesh_generic_server_mutex_free(void) +{ + bt_mesh_mutex_free(&generic_server_lock); +} + +void bt_mesh_generic_server_lock(void) +{ + bt_mesh_mutex_lock(&generic_server_lock); +} + +void bt_mesh_generic_server_unlock(void) +{ + bt_mesh_mutex_unlock(&generic_server_lock); +} + +/* message handlers (Start) */ + +/* Generic OnOff Server message handlers */ +static void send_gen_onoff_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish) +{ + struct bt_mesh_gen_onoff_srv *srv = model->user_data; + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 3; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS); + net_buf_simple_add_u8(msg, srv->state.onoff); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_u8(msg, srv->state.target_onoff); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void gen_onoff_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_onoff_srv *srv = model->user_data; + + if (srv == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + send_gen_onoff_status(model, ctx, false); + return; +} + +void gen_onoff_publish(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + send_gen_onoff_status(model, NULL, true); + return; +} + +static void gen_onoff_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_onoff_srv *srv = model->user_data; + u8_t tid = 0U, onoff = 0U, trans_time = 0U, delay = 0U; + bool optional = false; + s64_t now = 0; + + if (srv == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + onoff = net_buf_simple_pull_u8(buf); + if (onoff > BLE_MESH_STATE_ON) { + BT_ERR("%s, Invalid OnOff value 0x%02x", __func__, onoff); + return; + } + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .onoff_set.op_en = optional, + .onoff_set.onoff = onoff, + .onoff_set.tid = tid, + .onoff_set.trans_time = trans_time, + .onoff_set.delay = delay, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_ONOFF_SET) { + send_gen_onoff_status(model, ctx, false); + } + send_gen_onoff_status(model, NULL, true); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_generic_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->state.target_onoff = onoff; + + if (srv->state.target_onoff != srv->state.onoff) { + generic_onoff_tt_values(srv, trans_time, delay); + } else { + bt_mesh_gen_server_state_change_t change = { + .gen_onoff_set.onoff = srv->state.onoff, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_ONOFF_SET) { + send_gen_onoff_status(model, ctx, false); + } + send_gen_onoff_status(model, NULL, true); + + bt_mesh_generic_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state.onoff = srv->state.target_onoff; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_ONOFF_SET) { + send_gen_onoff_status(model, ctx, false); + } + send_gen_onoff_status(model, NULL, true); + + bt_mesh_generic_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +/* Generic Level Server message handlers */ +static void send_gen_level_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish) +{ + struct bt_mesh_gen_level_srv *srv = model->user_data; + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 5; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_LEVEL_STATUS); + net_buf_simple_add_le16(msg, srv->state.level); + if (srv->transition.counter) { + if (srv->state.move_start) { + if (srv->state.positive) { + net_buf_simple_add_le16(msg, INT16_MAX); + } else { /* 0 should not be possible */ + net_buf_simple_add_le16(msg, INT16_MIN); + } + net_buf_simple_add_u8(msg, BLE_MESH_UNKNOWN_REMAIN_TIME); + } else { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_le16(msg, srv->state.target_level); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void gen_level_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_level_srv *srv = model->user_data; + + if (srv == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + send_gen_level_status(model, ctx, false); + return; +} + +void gen_level_publish(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + send_gen_level_status(model, NULL, true); + return; +} + +static void gen_level_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_level_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + bool optional = false; + s16_t level = 0; + s64_t now = 0; + + if (srv == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + level = (s16_t) net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .level_set.op_en = optional, + .level_set.level = level, + .level_set.tid = tid, + .level_set.trans_time = trans_time, + .level_set.delay = delay, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_LEVEL_SET) { + send_gen_level_status(model, ctx, false); + } + send_gen_level_status(model, NULL, true); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_generic_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->state.target_level = level; + + /** + * If the target state is equal to the current state, the transition + * shall not be started and is considered complete. + */ + if (srv->state.target_level != srv->state.level) { + generic_level_tt_values(srv, trans_time, delay); + } else { + bt_mesh_gen_server_state_change_t change = { + .gen_level_set.level = srv->state.level, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_LEVEL_SET) { + send_gen_level_status(model, ctx, false); + } + send_gen_level_status(model, NULL, true); + + bt_mesh_generic_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state.level = srv->state.target_level; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_LEVEL_SET) { + send_gen_level_status(model, ctx, false); + } + send_gen_level_status(model, NULL, true); + + bt_mesh_generic_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +static void gen_delta_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_level_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + s32_t tmp32 = 0, delta = 0; + bool optional = false; + s64_t now = 0; + + if (srv == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + delta = (s32_t)net_buf_simple_pull_le32(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .delta_set.op_en = optional, + .delta_set.delta_level = delta, + .delta_set.tid = tid, + .delta_set.trans_time = trans_time, + .delta_set.delay = delay, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + /** + * A number of Generic Delta Set and Generic Delta Set Unacknowledged + * messages with the same transaction identifier set in the TID field + * may be sent. + * + * A new transaction starts when the TID field value in the received + * message is different from the TID field value in the previously + * received message that was using the same source and destination + * addresses or from the most recently received message with the same + * TID field value that was received 6 or more seconds earlier. + */ + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (srv->state.last_delta == delta) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_DELTA_SET) { + send_gen_level_status(model, ctx, false); + } + send_gen_level_status(model, NULL, true); + /* In this condition, no event will be callback to application layer */ + return; + } + + tmp32 = srv->state.last_level + delta; + } else { + /* Starts a new transaction */ + srv->state.last_level = srv->state.level; + tmp32 = srv->state.level + delta; + } + + bt_mesh_generic_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->state.last_delta = delta; + if (tmp32 < INT16_MIN) { + tmp32 = INT16_MIN; + } else if (tmp32 > INT16_MAX) { + tmp32 = INT16_MAX; + } + srv->state.target_level = tmp32; + + /** + * If the target state is equal to the current state, the transition + * shall not be started and is considered complete. + */ + if (srv->state.target_level != srv->state.level) { + generic_level_tt_values(srv, trans_time, delay); + } else { + bt_mesh_gen_server_state_change_t change = { + .gen_delta_set.level = srv->state.level, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_DELTA_SET) { + send_gen_level_status(model, ctx, false); + } + send_gen_level_status(model, NULL, true); + + bt_mesh_generic_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state.level = srv->state.target_level; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_DELTA_SET) { + send_gen_level_status(model, ctx, false); + } + send_gen_level_status(model, NULL, true); + + bt_mesh_generic_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +static void gen_move_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_level_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + bool optional = false; + s16_t delta = 0; + s32_t tmp32 = 0; + s64_t now = 0; + + if (srv == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + delta = (s16_t) net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .move_set.op_en = optional, + .move_set.delta_level = delta, + .move_set.tid = tid, + .move_set.trans_time = trans_time, + .move_set.delay = delay, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_MOVE_SET) { + send_gen_level_status(model, ctx, false); + } + send_gen_level_status(model, NULL, true); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_generic_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->state.last_delta = delta; + + tmp32 = srv->state.level + delta; + if (tmp32 < INT16_MIN) { + tmp32 = INT16_MIN; + } else if (tmp32 > INT16_MAX) { + tmp32 = INT16_MAX; + } + srv->state.target_level = tmp32; + + /** + * If the target state is equal to the current state, the transition + * shall not be started and is considered complete. + */ + if (srv->state.target_level != srv->state.level) { + generic_level_tt_values(srv, trans_time, delay); + } else { + bt_mesh_gen_server_state_change_t change = { + .gen_move_set.level = srv->state.level, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_MOVE_SET) { + send_gen_level_status(model, ctx, false); + } + send_gen_level_status(model, NULL, true); + srv->state.move_start = false; + + bt_mesh_generic_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + if (delta) { + srv->state.move_start = true; + srv->state.positive = (delta > 0) ? true : false; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_MOVE_SET) { + send_gen_level_status(model, ctx, false); + } + send_gen_level_status(model, NULL, true); + + bt_mesh_generic_server_unlock(); + + /** + * If (trans_time == 0) OR (delta == 0) + * 1. If the resulting Transition Time is equal to 0 or is undefined, + * the Generic Move Set command will not initiate any Generic Level + * state change. + * 2. When a Generic Level Server receives the message with a value of + * the Delta Level field equal to 0, it shall stop changing the + * Generic Level state. (if delta == 0, srv->state.target_level will + * equal to srv->state.level) + */ + if (srv->transition.counter == 0U) { + srv->state.move_start = false; + bt_mesh_gen_server_state_change_t change = { + .gen_move_set.level = srv->state.level, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + return; + } + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +/* Generic Default Transition Time Server message handlers */ +static void send_gen_def_trans_time_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish) +{ + struct bt_mesh_gen_def_trans_time_srv *srv = model->user_data; + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 1; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_STATUS); + net_buf_simple_add_u8(msg, srv->state.trans_time); + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void gen_def_trans_time_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_def_trans_time_srv *srv = model->user_data; + + if (srv == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + send_gen_def_trans_time_status(model, ctx, false); + return; +} + +static void gen_def_trans_time_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_def_trans_time_srv *srv = model->user_data; + u8_t trans_time = 0U; + + if (srv == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + trans_time = net_buf_simple_pull_u8(buf); + if ((trans_time & 0x3F) == 0x3F) { + BT_WARN("%s, Invalid Transaction Number of Steps 0x3F", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .def_trans_time_set.trans_time = trans_time, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (srv->state.trans_time != trans_time) { + srv->state.trans_time = trans_time; + } + + bt_mesh_gen_server_state_change_t change = { + .gen_def_trans_time_set.trans_time = trans_time, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET) { + send_gen_def_trans_time_status(model, ctx, false); + } + send_gen_def_trans_time_status(model, NULL, true); + + return; +} + +/* Generic Power OnOff Server message handlers */ +static void send_gen_onpowerup_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish) +{ + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 1; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_ONPOWERUP_STATUS); + switch (model->id) { + case BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV: { + struct bt_mesh_gen_power_onoff_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->onpowerup); + break; + } + case BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV: { + struct bt_mesh_gen_power_onoff_setup_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->onpowerup); + break; + } + default: + BT_ERR("%s, Invalid Generic Power OnOff Server 0x%04x", __func__, model->id); + if (publish == false) { + bt_mesh_free_buf(msg); + } + return; + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void gen_onpowerup_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_power_onoff_srv *srv = model->user_data; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + send_gen_onpowerup_status(model, ctx, false); + return; +} + +/* Generic Power OnOff Setup Server message handlers */ +void gen_onpowerup_publish(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV: { + struct bt_mesh_gen_power_onoff_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Generic Power OnOff Server state", __func__); + return; + } + break; + } + case BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV: { + struct bt_mesh_gen_power_onoff_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Generic Power OnOff Setup Server state", __func__); + return; + } + break; + } + default: + BT_ERR("%s, Invalid Generic Power OnOff Server 0x%04x", __func__, model->id); + return; + } + + send_gen_onpowerup_status(model, NULL, true); + return; +} + +static void gen_onpowerup_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_power_onoff_setup_srv *srv = model->user_data; + u8_t onpowerup = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + onpowerup = net_buf_simple_pull_u8(buf); + if (onpowerup > BLE_MESH_STATE_RESTORE) { + BT_WARN("%s, Invalid OnPowerUp value 0x%02x", __func__, onpowerup); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .onpowerup_set.onpowerup = onpowerup, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (srv->state->onpowerup != onpowerup) { + srv->state->onpowerup = onpowerup; + } + + bt_mesh_gen_server_state_change_t change = { + .gen_onpowerup_set.onpowerup = onpowerup, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET) { + send_gen_onpowerup_status(model, ctx, false); + } + send_gen_onpowerup_status(model, NULL, true); + + return; +} + +/* Generic Power Level Server message handlers */ +static void send_gen_power_level_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish, u16_t opcode) +{ + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 5; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, opcode); + switch (opcode) { + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS: + case BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS: { + struct bt_mesh_gen_power_level_srv *srv = model->user_data; + if (opcode == BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS) { + net_buf_simple_add_le16(msg, srv->state->power_actual); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_le16(msg, srv->state->target_power_actual); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + } else if (opcode == BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS) { + net_buf_simple_add_le16(msg, srv->state->power_last); + } + break; + } + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS: + if (model->id == BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV) { + struct bt_mesh_gen_power_level_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->power_default); + } else if (model->id == BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV) { + struct bt_mesh_gen_power_level_setup_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->power_default); + } + break; + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS: + if (model->id == BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV) { + struct bt_mesh_gen_power_level_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->power_range_min); + net_buf_simple_add_le16(msg, srv->state->power_range_max); + } else if (model->id == BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV) { + struct bt_mesh_gen_power_level_setup_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->power_range_min); + net_buf_simple_add_le16(msg, srv->state->power_range_max); + } + break; + default: + BT_WARN("%s, Unknown Generic Power status opcode 0x%04x", __func__, opcode); + if (publish == false) { + bt_mesh_free_buf(msg); + } + return; + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void gen_power_level_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_power_level_srv *srv = model->user_data; + u16_t opcode = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_GET: + opcode = BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS; + break; + case BLE_MESH_MODEL_OP_GEN_POWER_LAST_GET: + opcode = BLE_MESH_MODEL_OP_GEN_POWER_LAST_STATUS; + break; + case BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_GET: + opcode = BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS; + break; + case BLE_MESH_MODEL_OP_GEN_POWER_RANGE_GET: + opcode = BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS; + break; + default: + BT_WARN("%s, Unknown Generic Power Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + send_gen_power_level_status(model, ctx, false, opcode); + return; +} + +void gen_power_level_publish(struct bt_mesh_model *model, u16_t opcode) +{ + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV: { + struct bt_mesh_gen_power_level_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Generic Power Level Server state", __func__); + return; + } + break; + } + case ESP_BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV: { + struct bt_mesh_gen_power_level_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Generic Power Level Setup Server state", __func__); + return; + } + break; + } + default: + BT_ERR("%s, Invalid Generic Power Level Server 0x%04x", __func__, model->id); + return; + } + + send_gen_power_level_status(model, NULL, true, opcode); + return; +} + +static void gen_power_level_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_power_level_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + bool optional = false; + u16_t power = 0U; + s64_t now = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + power = net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .power_level_set.op_en = optional, + .power_level_set.power = power, + .power_level_set.tid = tid, + .power_level_set.trans_time = trans_time, + .power_level_set.delay = delay, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET) { + send_gen_power_level_status(model, ctx, false, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS); + } + send_gen_power_level_status(model, NULL, true, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_generic_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + if (power) { + if (srv->state->power_range_min && power < srv->state->power_range_min) { + power = srv->state->power_range_min; + } else if (srv->state->power_range_max && power > srv->state->power_range_max) { + power = srv->state->power_range_max; + } + } + srv->state->target_power_actual = power; + + /* If the target state is equal to the current state, the transition + * shall not be started and is considered complete. + */ + if (srv->state->target_power_actual != srv->state->power_actual) { + generic_power_level_tt_values(srv, trans_time, delay); + } else { + bt_mesh_gen_server_state_change_t change = { + .gen_power_level_set.power = srv->state->power_actual, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET) { + send_gen_power_level_status(model, ctx, false, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS); + } + send_gen_power_level_status(model, NULL, true, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS); + + bt_mesh_generic_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state->power_actual = srv->state->target_power_actual; + /* Whenever the Generic Power Actual state is changed to a non-zero value + * as a result of a non-transactional message or a completed sequence of + * transactional messages, the value of the Generic Power Last state shall + * be set to the value of the Generic Power Actual state. + */ + if (srv->state->power_actual) { + srv->state->power_last = srv->state->power_actual; + } + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET) { + send_gen_power_level_status(model, ctx, false, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS); + } + send_gen_power_level_status(model, NULL, true, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS); + + bt_mesh_generic_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +/* Generic Power Level Setup Server message handlers */ +static void gen_power_default_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_power_level_setup_srv *srv = model->user_data; + u16_t power = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + power = net_buf_simple_pull_le16(buf); + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .power_default_set.power = power, /* Just callback the actual recived value */ + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + /** + * Value 0x0000 has a special meaning defined: use the value of the + * Generic Power Last state as the default value. + */ + if (power == 0x0000) { + power = srv->state->power_last; + } + + if (srv->state->power_default != power) { + srv->state->power_default = power; + } + + bt_mesh_gen_server_state_change_t change = { + .gen_power_default_set.power = power, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET) { + send_gen_power_level_status(model, ctx, false, BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS); + } + send_gen_power_level_status(model, NULL, true, BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_STATUS); + + return; +} + +static void gen_power_range_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_power_level_setup_srv *srv = model->user_data; + u16_t range_min = 0U, range_max = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + range_min = net_buf_simple_pull_le16(buf); + range_max = net_buf_simple_pull_le16(buf); + + if (range_min > range_max) { + BT_ERR("%s, Range Min 0x%04x is greater than Range Max 0x%04x", + __func__, range_min, range_max); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .power_range_set.range_min = range_min, + .power_range_set.range_max = range_max, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (range_min == 0x0000) { + srv->state->status_code = BLE_MESH_CANNOT_SET_RANGE_MIN; + } else if (range_max == 0x0000) { + srv->state->status_code = BLE_MESH_CANNOT_SET_RANGE_MAX; + } else { + srv->state->status_code = BLE_MESH_RANGE_UPDATE_SUCCESS; + } + + if (range_min && srv->state->power_range_min != range_min) { + srv->state->power_range_min = range_min; + } + + if (range_max && srv->state->power_range_max != range_max) { + srv->state->power_range_max = range_max; + } + + bt_mesh_gen_server_state_change_t change = { + .gen_power_range_set.range_min = srv->state->power_range_min, + .gen_power_range_set.range_max = srv->state->power_range_max, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET) { + send_gen_power_level_status(model, ctx, false, BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS); + } + send_gen_power_level_status(model, NULL, true, BLE_MESH_MODEL_OP_GEN_POWER_RANGE_STATUS); + + return; +} + +/* Generic Battery Server message handlers */ +static void gen_battery_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_battery_srv *srv = model->user_data; + NET_BUF_SIMPLE_DEFINE(msg, 2 + 8 + BLE_MESH_SERVER_TRANS_MIC_SIZE); + + if (srv == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + bt_mesh_model_msg_init(&msg, BLE_MESH_MODEL_OP_GEN_BATTERY_STATUS); + net_buf_simple_add_le32(&msg, srv->state.time_to_discharge << 8 | srv->state.battery_level); + net_buf_simple_add_le32(&msg, srv->state.battery_flags << 24 | srv->state.time_to_charge); + + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, &msg, NULL, NULL)); + return; +} + +/* Generic Location Server message handlers */ +static void send_gen_location_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish, u16_t opcode) +{ + struct net_buf_simple *msg = NULL; + u8_t length = 1 + 10; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, opcode); + switch (opcode) { + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS: + if (model->id == BLE_MESH_MODEL_ID_GEN_LOCATION_SRV) { + struct bt_mesh_gen_location_srv *srv = model->user_data; + net_buf_simple_add_le32(msg, srv->state->global_latitude); + net_buf_simple_add_le32(msg, srv->state->global_longitude); + net_buf_simple_add_le16(msg, srv->state->global_altitude); + } else if (model->id == BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV) { + struct bt_mesh_gen_location_setup_srv *srv = model->user_data; + net_buf_simple_add_le32(msg, srv->state->global_latitude); + net_buf_simple_add_le32(msg, srv->state->global_longitude); + net_buf_simple_add_le16(msg, srv->state->global_altitude); + } + break; + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS: + if (model->id == BLE_MESH_MODEL_ID_GEN_LOCATION_SRV) { + struct bt_mesh_gen_location_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->local_north); + net_buf_simple_add_le16(msg, srv->state->local_east); + net_buf_simple_add_le16(msg, srv->state->local_altitude); + net_buf_simple_add_u8(msg, srv->state->floor_number); + net_buf_simple_add_le16(msg, srv->state->uncertainty); + } else if (model->id == BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV) { + struct bt_mesh_gen_location_setup_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->local_north); + net_buf_simple_add_le16(msg, srv->state->local_east); + net_buf_simple_add_le16(msg, srv->state->local_altitude); + net_buf_simple_add_u8(msg, srv->state->floor_number); + net_buf_simple_add_le16(msg, srv->state->uncertainty); + } + break; + default: + BT_WARN("%s, Unknown Generic Location status opcode 0x%04x", __func__, opcode); + if (publish == false) { + bt_mesh_free_buf(msg); + } + return; + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void gen_location_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_location_srv *srv = model->user_data; + u16_t opcode = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_GET: + opcode = BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS; + break; + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_GET: + opcode = BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS; + break; + default: + BT_WARN("%s, Unknown Generic Location Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + send_gen_location_status(model, ctx, false, opcode); + return; +} + +/* Generic Location Setup Server message handlers */ +static void gen_location_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_location_setup_srv *srv = model->user_data; + u16_t opcode = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET: + case BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET_UNACK: { + opcode = BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_STATUS; + s32_t latitude = net_buf_simple_pull_le32(buf); + s32_t longitude = net_buf_simple_pull_le32(buf); + s16_t altitude = net_buf_simple_pull_le16(buf); + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .loc_global_set.latitude = latitude, + .loc_global_set.longitude = longitude, + .loc_global_set.altitude = altitude, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (latitude != 0x80000000) { + srv->state->global_latitude = latitude; + } + if (longitude != 0x80000000) { + srv->state->global_longitude = longitude; + } + if (altitude != 0x7FFF) { + srv->state->global_altitude = altitude; + } + + bt_mesh_gen_server_state_change_t change = { + .gen_loc_global_set.latitude = srv->state->global_latitude, + .gen_loc_global_set.longitude = srv->state->global_longitude, + .gen_loc_global_set.altitude = srv->state->global_altitude, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + break; + } + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET: + case BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET_UNACK: { + opcode = BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_STATUS; + u16_t north = net_buf_simple_pull_le16(buf); + u16_t east = net_buf_simple_pull_le16(buf); + u16_t altitude = net_buf_simple_pull_le16(buf); + u8_t floor = net_buf_simple_pull_u8(buf); + u16_t uncertainty = net_buf_simple_pull_le16(buf); + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .loc_local_set.north = north, + .loc_local_set.east = east, + .loc_local_set.altitude = altitude, + .loc_local_set.floor_number = floor, + .loc_local_set.uncertainty = uncertainty, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (north != 0x8000) { + srv->state->local_north = north; + } + if (east != 0x8000) { + srv->state->local_east = east; + } + if (altitude != 0x7FFF) { + srv->state->local_altitude = altitude; + } + if (floor != 0xFF) { + srv->state->floor_number = floor; + } + srv->state->uncertainty = uncertainty; + + bt_mesh_gen_server_state_change_t change = { + .gen_loc_local_set.north = srv->state->local_north, + .gen_loc_local_set.east = srv->state->local_east, + .gen_loc_local_set.altitude = srv->state->local_altitude, + .gen_loc_local_set.floor_number = srv->state->floor_number, + .gen_loc_local_set.uncertainty = srv->state->uncertainty, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + break; + } + default: + BT_WARN("%s, Unknown Generic Location Set opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET || + ctx->recv_op == BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET) { + send_gen_location_status(model, ctx, false, opcode); + } + send_gen_location_status(model, NULL, true, opcode); + + return; +} + +/* Generic User Property Server message handlers */ +struct bt_mesh_generic_property *gen_get_user_property(struct bt_mesh_model *model, + u16_t property_id) +{ + struct bt_mesh_gen_user_prop_srv *srv = model->user_data; + int i; + + for (i = 0; i < srv->property_count; i++) { + if (srv->properties[i].id == property_id) { + return &srv->properties[i]; + } + } + + return NULL; +} + +static void send_gen_user_prop_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t property_id, bool publish) +{ + struct bt_mesh_generic_property *property = NULL; + struct net_buf_simple *msg = NULL; + u16_t length = 0U; + + if (property_id == BLE_MESH_INVALID_DEVICE_PROPERTY_ID) { + BT_ERR("%s, Invalid User Property ID 0x%04x", __func__, property_id); + return; + } + + property = gen_get_user_property(model, property_id); + if (property == NULL) { + BT_WARN("%s, User property 0x%04x not exist", __func__, property_id); + length = sizeof(property_id); + } else { + length = sizeof(property->id) + sizeof(property->user_access) + property->val->len; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(1 + length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, 1 + length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_STATUS); + if (property == NULL) { + net_buf_simple_add_le16(msg, property_id); + } else { + net_buf_simple_add_le16(msg, property->id); + net_buf_simple_add_u8(msg, property->user_access); + if ((ctx->recv_op == BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET && + property->user_access != USER_ACCESS_PROHIBIT && + property->user_access != USER_ACCESS_WRITE) || + ((ctx->recv_op == BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET || + ctx->recv_op == BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK) && + property->user_access != USER_ACCESS_PROHIBIT && + property->user_access != USER_ACCESS_READ)) { + net_buf_simple_add_mem(msg, property->val->data, property->val->len); + } + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void gen_user_prop_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_user_prop_srv *srv = model->user_data; + + if (srv == NULL || srv->property_count == 0U || srv->properties == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + /** + * Also we can use __packed for esp_ble_mesh_gen_user_property_get_t, + * and directly callback with buf->data & buf->len. + */ + bt_mesh_gen_server_recv_get_msg_t get = {0}; + const u8_t *param = NULL; + size_t len = 0; + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET) { + get.user_property_get.id = net_buf_simple_pull_le16(buf); + param = (const u8_t *)&get; + len = sizeof(get); + } + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, param, len); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET: { + struct net_buf_simple *msg = NULL; + u8_t i; + msg = bt_mesh_alloc_buf(1 + srv->property_count * 2 + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_STATUS); + for (i = 0U; i < srv->property_count; i++) { + if (srv->properties[i].admin_access != ADMIN_NOT_USER_PROP && + srv->properties[i].manu_access != MANU_NOT_USER_PROP) { + net_buf_simple_add_le16(msg, srv->properties[i].id); + } + } + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + return; + } + case BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET: { + u16_t property_id = net_buf_simple_pull_le16(buf); + send_gen_user_prop_status(model, ctx, property_id, false); + return; + } + default: + BT_WARN("%s, Unknown Generic User Property Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } +} + +static void gen_user_prop_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_user_prop_srv *srv = model->user_data; + struct bt_mesh_generic_property *property = NULL; + u16_t property_id = 0U; + u8_t expect_len = 0U; + + if (srv == NULL || srv->property_count == 0U || srv->properties == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + property_id = net_buf_simple_pull_le16(buf); + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .user_property_set.id = property_id, + .user_property_set.value = buf, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + property = gen_get_user_property(model, property_id); + if (property == NULL || property->user_access == USER_ACCESS_PROHIBIT || + property->user_access == USER_ACCESS_READ) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET) { + send_gen_user_prop_status(model, ctx, property_id, false); + } + send_gen_user_prop_status(model, ctx, property_id, true); + return; + } + + /* For BLE Mesh Model BQB test: + * PTS will send Generic User Property Set/Set Unack messages with + * invalid device property value length, these messages need to be + * ignored, otherwise the test case will fail. + */ + expect_len = bt_mesh_get_dev_prop_len(property_id); + if (buf->len != expect_len) { + BT_ERR("%s, Invalid User Property length, ID 0x%04x, expect %d, actual %d", + __func__, property_id, expect_len, buf->len); + return; + } + + net_buf_simple_reset(property->val); + net_buf_simple_add_mem(property->val, buf->data, MIN(buf->len, property->val->size)); + + bt_mesh_gen_server_state_change_t change = { + .gen_user_prop_set.id = property_id, + .gen_user_prop_set.value = property->val, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET) { + send_gen_user_prop_status(model, ctx, property_id, false); + } + send_gen_user_prop_status(model, ctx, property_id, true); + + return; +} + +/* Generic Admin Property Server message handlers */ +struct bt_mesh_generic_property *gen_get_admin_property(struct bt_mesh_model *model, + u16_t property_id) +{ + struct bt_mesh_gen_admin_prop_srv *srv = model->user_data; + int i; + + for (i = 0; i < srv->property_count; i++) { + if (srv->properties[i].id == property_id) { + return &srv->properties[i]; + } + } + + return NULL; +} + +static void send_gen_admin_prop_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t property_id, bool publish) +{ + struct bt_mesh_generic_property *property = NULL; + struct net_buf_simple *msg = NULL; + u16_t length = 0U; + + if (property_id == BLE_MESH_INVALID_DEVICE_PROPERTY_ID) { + BT_ERR("%s, Invalid User Property ID 0x%04x", __func__, property_id); + return; + } + + property = gen_get_admin_property(model, property_id); + if (property == NULL) { + BT_WARN("%s, Admin property 0x%04x not exist", __func__, property_id); + length = sizeof(property_id); + } else { + length = sizeof(property->id) + sizeof(property->admin_access) + property->val->len; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(1 + length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, 1 + length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_STATUS); + if (property == NULL) { + net_buf_simple_add_le16(msg, property_id); + } else { + net_buf_simple_add_le16(msg, property->id); + net_buf_simple_add_u8(msg, property->admin_access); + if (ctx->recv_op != BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET || + property->admin_access != ADMIN_ACCESS_WRITE) { + net_buf_simple_add_mem(msg, property->val->data, property->val->len); + } + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void gen_admin_prop_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_admin_prop_srv *srv = model->user_data; + + if (srv == NULL || srv->property_count == 0U || srv->properties == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_get_msg_t get = {0}; + const u8_t *param = NULL; + size_t len = 0U; + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET) { + get.admin_property_get.id = net_buf_simple_pull_le16(buf); + param = (const u8_t *)&get; + len = sizeof(get); + } + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, param, len); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET: { + struct net_buf_simple *msg = NULL; + u8_t i = 0U; + msg = bt_mesh_alloc_buf(1 + srv->property_count * 2 + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_STATUS); + for (i = 0U; i < srv->property_count; i++) { + net_buf_simple_add_le16(msg, srv->properties[i].id); + } + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + return; + } + case BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET: { + u16_t property_id = net_buf_simple_pull_le16(buf); + send_gen_admin_prop_status(model, ctx, property_id, false); + return; + } + default: + BT_WARN("%s, Unknown Generic Admin Property Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } +} + +static void gen_admin_prop_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_admin_prop_srv *srv = model->user_data; + struct bt_mesh_generic_property *property = NULL; + u16_t property_id = 0U; + u8_t access = 0U; + + if (srv == NULL || srv->property_count == 0U || srv->properties == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + property_id = net_buf_simple_pull_le16(buf); + access = net_buf_simple_pull_u8(buf); + if (access > ADMIN_ACCESS_READ_WRITE) { + BT_ERR("%s, Invalid Admin Access 0x%02x", __func__, access); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .admin_property_set.id = property_id, + .admin_property_set.access = access, + .admin_property_set.value = buf, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + property = gen_get_admin_property(model, property_id); + if (property == NULL) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET) { + send_gen_admin_prop_status(model, ctx, property_id, false); + } + send_gen_admin_prop_status(model, ctx, property_id, true); + return; + } + + property->admin_access = access; + + net_buf_simple_reset(property->val); + net_buf_simple_add_mem(property->val, buf->data, MIN(buf->len, property->val->size)); + + bt_mesh_gen_server_state_change_t change = { + .gen_admin_prop_set.id = property_id, + .gen_admin_prop_set.access = property->admin_access, + .gen_admin_prop_set.value = property->val, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET) { + send_gen_admin_prop_status(model, ctx, property_id, false); + } + send_gen_admin_prop_status(model, ctx, property_id, true); + + return; +} + +/* Generic Manufacturer Property Server message handlers */ +struct bt_mesh_generic_property *gen_get_manu_property(struct bt_mesh_model *model, + u16_t property_id) +{ + struct bt_mesh_gen_manu_prop_srv *srv = model->user_data; + int i; + + for (i = 0; i < srv->property_count; i++) { + if (srv->properties[i].id == property_id) { + return &srv->properties[i]; + } + } + + return NULL; +} + +static void send_gen_manu_prop_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t property_id, bool publish) +{ + struct bt_mesh_generic_property *property = NULL; + struct net_buf_simple *msg = NULL; + u16_t length = 0U; + + if (property_id == BLE_MESH_INVALID_DEVICE_PROPERTY_ID) { + BT_ERR("%s, Invalid User Property ID 0x%04x", __func__, property_id); + return; + } + + property = gen_get_manu_property(model, property_id); + if (property == NULL) { + BT_WARN("%s, Manufacturer property 0x%04x not exist", __func__, property_id); + length = sizeof(property_id); + } else { + length = sizeof(property->id) + sizeof(property->manu_access) + property->val->len; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(1 + length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, 1 + length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_STATUS); + if (property == NULL) { + net_buf_simple_add_le16(msg, property_id); + } else { + net_buf_simple_add_le16(msg, property->id); + net_buf_simple_add_u8(msg, property->manu_access); + net_buf_simple_add_mem(msg, property->val->data, property->val->len); + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void gen_manu_prop_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_manu_prop_srv *srv = model->user_data; + + if (srv == NULL || srv->property_count == 0U || srv->properties == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_get_msg_t get = {0}; + const u8_t *param = NULL; + size_t len = 0U; + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_GET) { + get.manu_property_get.id = net_buf_simple_pull_le16(buf); + param = (const u8_t *)&get; + len = sizeof(get); + } + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, param, len); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_GET: { + struct net_buf_simple *msg = NULL; + u8_t i = 0U; + msg = bt_mesh_alloc_buf(1 + srv->property_count * 2 + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_STATUS); + for (i = 0U; i < srv->property_count; i++) { + net_buf_simple_add_le16(msg, srv->properties[i].id); + } + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + return; + } + case BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_GET: { + u16_t property_id = net_buf_simple_pull_le16(buf); + send_gen_manu_prop_status(model, ctx, property_id, false); + return; + } + default: + BT_WARN("%s, Unknown Generic Manufacturer Property Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } +} + +static void gen_manu_prop_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_manu_prop_srv *srv = model->user_data; + struct bt_mesh_generic_property *property = NULL; + u16_t property_id = 0U; + u8_t access = 0U; + + if (srv == NULL || srv->property_count == 0U || srv->properties == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + property_id = net_buf_simple_pull_le16(buf); + access = net_buf_simple_pull_u8(buf); + if (access > MANU_ACCESS_READ) { + BT_ERR("%s, Invalid Manufacturer Access 0x%02x", __func__, access); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_set_msg_t set = { + .manu_property_set.id = property_id, + .manu_property_set.access = access, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + property = gen_get_manu_property(model, property_id); + if (property == NULL) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET) { + send_gen_manu_prop_status(model, ctx, property_id, false); + } + send_gen_manu_prop_status(model, ctx, property_id, true); + return; + } + + property->manu_access = access; + + bt_mesh_gen_server_state_change_t change = { + .gen_manu_prop_set.id = property_id, + .gen_manu_prop_set.access = property->manu_access, + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET) { + send_gen_manu_prop_status(model, ctx, property_id, false); + } + send_gen_manu_prop_status(model, ctx, property_id, true); + + return; +} + +/* Generic Client Property Server message handlers */ +static int search_prop_id_index(const u16_t *array, u8_t array_idx, u16_t id) +{ + static const u16_t *start = NULL; + u8_t index = 0U; + u16_t temp = 0U; + + if (start == NULL) { + start = array; + } + + if (array_idx == 0U) { + if (*array >= id) { + return array - start; + } else { + return -1; + } + } + + index = array_idx / 2; + temp = array[index]; + + if (temp == id) { + return array + index - start; + } else if (temp > id) { + return search_prop_id_index(array, index, id); + } else { + return search_prop_id_index(array + index + 1, array_idx - 1 - index, id); + } +} + +static void gen_client_prop_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_gen_client_prop_srv *srv = model->user_data; + struct net_buf_simple *sdu = NULL; + u16_t total_len = 5U; + u16_t property_id = 0U; + int i, index = 0; + + if (srv == NULL || srv->id_count == 0U || srv->property_ids == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_gen_server_recv_get_msg_t get = { + .client_properties_get.id = net_buf_simple_pull_le16(buf), + }; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + return; + } + + /* The sequence shall be in an ascending order of Property ID values and shall + * start with a smallest Property ID that is greater than or equal to the value + * of the Generic Client Property field of the Generic Client Properities Get + * message that it is responding to. + */ + + property_id = net_buf_simple_pull_le16(buf); + index = search_prop_id_index(srv->property_ids, srv->id_count - 1, property_id); + if (index < 0) { + NET_BUF_SIMPLE_DEFINE(msg, 1 + BLE_MESH_SERVER_TRANS_MIC_SIZE); + bt_mesh_model_msg_init(&msg, BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS); + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, &msg, NULL, NULL)); + return; + } + + sdu = bt_mesh_alloc_buf(MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)); + if (sdu == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + bt_mesh_model_msg_init(sdu, BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_STATUS); + for (i = index; i < srv->id_count; i++) { + total_len += sizeof(u16_t); + if (total_len > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)) { + /* Add this in case the message is too long */ + break; + } + net_buf_simple_add_le16(sdu, srv->property_ids[i]); + } + + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, sdu, NULL, NULL)); + bt_mesh_free_buf(sdu); + return; +} + +/* message handlers (End) */ + +/* Mapping of message handlers for Generic OnOff Server (0x1000) */ +const struct bt_mesh_model_op gen_onoff_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_ONOFF_GET, 0, gen_onoff_get }, + { BLE_MESH_MODEL_OP_GEN_ONOFF_SET, 2, gen_onoff_set }, + { BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2, gen_onoff_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Levl Server (0x1002) */ +const struct bt_mesh_model_op gen_level_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_LEVEL_GET, 0, gen_level_get }, + { BLE_MESH_MODEL_OP_GEN_LEVEL_SET, 3, gen_level_set }, + { BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK, 3, gen_level_set }, + { BLE_MESH_MODEL_OP_GEN_DELTA_SET, 5, gen_delta_set }, + { BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK, 5, gen_delta_set }, + { BLE_MESH_MODEL_OP_GEN_MOVE_SET, 3, gen_move_set }, + { BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK, 3, gen_move_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Default TT Server (0x1004) */ +const struct bt_mesh_model_op gen_def_trans_time_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_GET, 0, gen_def_trans_time_get }, + { BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET, 1, gen_def_trans_time_set }, + { BLE_MESH_MODEL_OP_GEN_DEF_TRANS_TIME_SET_UNACK, 1, gen_def_trans_time_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Power OnOff Server (0x1006) */ +const struct bt_mesh_model_op gen_power_onoff_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_ONPOWERUP_GET, 0, gen_onpowerup_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Power OnOff Setup Server (0x1007) */ +const struct bt_mesh_model_op gen_power_onoff_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET, 1, gen_onpowerup_set }, + { BLE_MESH_MODEL_OP_GEN_ONPOWERUP_SET_UNACK, 1, gen_onpowerup_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Power Level Server (0x1009) */ +const struct bt_mesh_model_op gen_power_level_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_GET, 0, gen_power_level_get }, + { BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET, 3, gen_power_level_set }, + { BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_SET_UNACK, 3, gen_power_level_set }, + { BLE_MESH_MODEL_OP_GEN_POWER_LAST_GET, 0, gen_power_level_get }, + { BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_GET, 0, gen_power_level_get }, + { BLE_MESH_MODEL_OP_GEN_POWER_RANGE_GET, 0, gen_power_level_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Power Level Setup Server (0x100A) */ +const struct bt_mesh_model_op gen_power_level_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET, 2, gen_power_default_set }, + { BLE_MESH_MODEL_OP_GEN_POWER_DEFAULT_SET_UNACK, 2, gen_power_default_set }, + { BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET, 4, gen_power_range_set }, + { BLE_MESH_MODEL_OP_GEN_POWER_RANGE_SET_UNACK, 4, gen_power_range_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Battery Server (0x100C) */ +const struct bt_mesh_model_op gen_battery_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_BATTERY_GET, 0, gen_battery_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Location Server (0x100E) */ +const struct bt_mesh_model_op gen_location_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_GET, 0, gen_location_get }, + { BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_GET, 0, gen_location_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Location Setup Server (0x100F) */ +const struct bt_mesh_model_op gen_location_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET, 10, gen_location_set }, + { BLE_MESH_MODEL_OP_GEN_LOC_GLOBAL_SET_UNACK, 10, gen_location_set }, + { BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET, 9, gen_location_set }, + { BLE_MESH_MODEL_OP_GEN_LOC_LOCAL_SET_UNACK, 9, gen_location_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic User Property Server (0x1013) */ +const struct bt_mesh_model_op gen_user_prop_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_USER_PROPERTIES_GET, 0, gen_user_prop_get }, + { BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_GET, 2, gen_user_prop_get }, + { BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET, 3, gen_user_prop_set }, + { BLE_MESH_MODEL_OP_GEN_USER_PROPERTY_SET_UNACK, 3, gen_user_prop_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Admin Property Server (0x1011) */ +const struct bt_mesh_model_op gen_admin_prop_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTIES_GET, 0, gen_admin_prop_get }, + { BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_GET, 2, gen_admin_prop_get }, + { BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET, 4, gen_admin_prop_set }, + { BLE_MESH_MODEL_OP_GEN_ADMIN_PROPERTY_SET_UNACK, 4, gen_admin_prop_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Manufacturer Property Server (0x1012) */ +const struct bt_mesh_model_op gen_manu_prop_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_MANU_PROPERTIES_GET, 0, gen_manu_prop_get }, + { BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_GET, 2, gen_manu_prop_get }, + { BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET, 3, gen_manu_prop_set }, + { BLE_MESH_MODEL_OP_GEN_MANU_PROPERTY_SET_UNACK, 3, gen_manu_prop_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Generic Client Property Server (0x1014) */ +const struct bt_mesh_model_op gen_client_prop_srv_op[] = { + { BLE_MESH_MODEL_OP_GEN_CLIENT_PROPERTIES_GET, 2, gen_client_prop_get }, + BLE_MESH_MODEL_OP_END, +}; + +static inline int property_id_compare(const void *p1, const void *p2) +{ + if (*(u16_t *)p1 < * (u16_t *)p2) { + return -1; + } + if (*(u16_t *)p1 > *(u16_t *)p2) { + return 1; + } + return 0; +} + +static int generic_server_init(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, No Generic Server context provided, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_GEN_ONOFF_SRV: { + struct bt_mesh_gen_onoff_srv *srv = model->user_data; + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, generic_onoff_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_LEVEL_SRV: { + struct bt_mesh_gen_level_srv *srv = model->user_data; + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, generic_level_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV: { + struct bt_mesh_gen_def_trans_time_srv *srv = model->user_data; + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV: { + struct bt_mesh_gen_power_onoff_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Generic OnPowerUp State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV: { + struct bt_mesh_gen_power_onoff_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Generic OnPowerUp State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV: { + struct bt_mesh_gen_power_level_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Generic Power Level State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, generic_power_level_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV: { + struct bt_mesh_gen_power_level_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Generic Power Level State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_BATTERY_SRV: { + struct bt_mesh_gen_battery_srv *srv = model->user_data; + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_LOCATION_SRV: { + struct bt_mesh_gen_location_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Generic Location State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV: { + struct bt_mesh_gen_location_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Generic Location State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_USER_PROP_SRV: { + struct bt_mesh_gen_user_prop_srv *srv = model->user_data; + if (srv->property_count == 0U || srv->properties == NULL) { + BT_ERR("%s, NULL Generic User Property State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV: { + struct bt_mesh_gen_admin_prop_srv *srv = model->user_data; + if (srv->property_count == 0U || srv->properties == NULL) { + BT_ERR("%s, NULL Generic Admin Property State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV: { + struct bt_mesh_gen_manu_prop_srv *srv = model->user_data; + if (srv->property_count == 0U || srv->properties == NULL) { + BT_ERR("%s, NULL Generic Manufacturer Property State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV: { + struct bt_mesh_gen_client_prop_srv *srv = model->user_data; + if (srv->id_count == 0U || srv->property_ids == NULL) { + BT_ERR("%s, NULL Generic Client Property State", __func__); + return -EINVAL; + } + /* Quick sort the Client Property IDs in ascending order */ + qsort(srv->property_ids, srv->id_count, sizeof(u16_t), property_id_compare); + srv->model = model; + break; + } + default: + BT_WARN("%s, Unknown Generic Server Model, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + bt_mesh_generic_server_mutex_new(); + + return 0; +} + +int bt_mesh_gen_onoff_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic OnOff Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_init(model); +} + +int bt_mesh_gen_level_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Level Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_init(model); +} + +int bt_mesh_gen_def_trans_time_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Default Trans Time Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_init(model); +} + +int bt_mesh_gen_power_onoff_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Power OnOff Server has no publication support", __func__); + return -EINVAL; + } + + /* When this model is present on an element, the corresponding Generic + * Power OnOff Setup Server model shall also be present. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV) == NULL) { + BT_WARN("%s, Generic Power OnOff Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return generic_server_init(model); +} + +int bt_mesh_gen_power_onoff_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + return generic_server_init(model); +} + +int bt_mesh_gen_power_level_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Power Level Server has no publication support", __func__); + return -EINVAL; + } + + /* When this model is present on an Element, the corresponding Generic + * Power Level Setup Server model shall also be present. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV) == NULL) { + BT_WARN("%s, Generic Power Level Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return generic_server_init(model); +} + +int bt_mesh_gen_power_level_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + return generic_server_init(model); +} + +int bt_mesh_gen_battery_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Battery Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_init(model); +} + +int bt_mesh_gen_location_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Location Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_init(model); +} + +int bt_mesh_gen_location_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + /* When this model is present on an Element, the corresponding Generic + * Location Setup Server model shall also be present. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_GEN_LOCATION_SETUP_SRV) == NULL) { + BT_WARN("%s, Generic Location Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return generic_server_init(model); +} + +int bt_mesh_gen_user_prop_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic User Property has no publication support", __func__); + return -EINVAL; + } + + return generic_server_init(model); +} + +int bt_mesh_gen_admin_prop_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Admin Property has no publication support", __func__); + return -EINVAL; + } + + return generic_server_init(model); +} + +int bt_mesh_gen_manu_prop_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Manufacturer Property has no publication support", __func__); + return -EINVAL; + } + + return generic_server_init(model); +} + +int bt_mesh_gen_client_prop_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Client Property has no publication support", __func__); + return -EINVAL; + } + + return generic_server_init(model); +} + +static int generic_server_deinit(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, No Generic Server context provided, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_GEN_ONOFF_SRV: { + struct bt_mesh_gen_onoff_srv *srv = model->user_data; + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + case BLE_MESH_MODEL_ID_GEN_LEVEL_SRV: { + struct bt_mesh_gen_level_srv *srv = model->user_data; + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + case BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV: { + struct bt_mesh_gen_power_level_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Generic Power Level State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + default: + BT_WARN("%s, Unknown Generic Server Model, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + bt_mesh_generic_server_mutex_free(); + + return 0; +} + +int bt_mesh_gen_onoff_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic OnOff Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_level_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Level Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_def_trans_time_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Default Trans Time Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_power_onoff_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Power OnOff Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_power_onoff_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_server_deinit(model); +} + +int bt_mesh_gen_power_level_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Power Level Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_power_level_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_server_deinit(model); +} + +int bt_mesh_gen_battery_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Battery Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_location_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Location Server has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_location_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + return generic_server_deinit(model); +} + +int bt_mesh_gen_user_prop_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic User Property has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_admin_prop_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Admin Property has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_manu_prop_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Manufacturer Property has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} + +int bt_mesh_gen_client_prop_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Generic Client Property has no publication support", __func__); + return -EINVAL; + } + + return generic_server_deinit(model); +} \ No newline at end of file diff --git a/components/bt/esp_ble_mesh/mesh_models/server/include/device_property.h b/components/bt/esp_ble_mesh/mesh_models/server/include/device_property.h new file mode 100644 index 0000000000..4f69a44287 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/include/device_property.h @@ -0,0 +1,1045 @@ +// Copyright 2017-2019 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 _DEVICE_PROPERTY_H_ +#define _DEVICE_PROPERTY_H_ + +#include "mesh_types.h" + +/** + * BLE Mesh Device Properties. + * + * Name Type ID Characteristic Type Size + * Average Ambient Temperature In A Period Of Day org.bluetooth.property.average_ambient_temperature_in_a_period_of_day 0x0001 Temperature 8 In A Period Of Day 3 + * Average Input Current org.bluetooth.property.average_input_current 0x0002 Average Current 3 + * Average Input Voltage org.bluetooth.property.average_input_voltage 0x0003 Average Voltage 3 + * Average Output Current org.bluetooth.property.average_output_current 0x0004 Average Current 3 + * Average Output Voltage org.bluetooth.property.average_output_voltage 0x0005 Average Voltage 3 + * Center Beam Intensity At Full Power org.bluetooth.property.center_beam_intensity_at_full_power 0x0006 Luminous Intensity 2 + * Chromaticity Tolerance org.bluetooth.property.chromaticity_tolerance 0x0007 Chromaticity Tolerance 1 + * Color Rendering Index R9 org.bluetooth.property.color_rendering_index_r9 0x0008 Cie 13.3-1995 Color Rendering Index 1 + * Color Rendering Index Ra org.bluetooth.property.color_rendering_index_ra 0x0009 Cie 13.3-1995 Color Rendering Index 1 + * Device Appearance org.bluetooth.property.device_appearance 0x000A Gap.Appearance 2 + * Device Country Of Origin org.bluetooth.property.device_country_of_origin 0x000B Country Code 2 + * Device Date Of Manufacture org.bluetooth.property.device_date_of_manufacture 0x000C Date Utc 4 + * Device Energy Use Since Turn On org.bluetooth.property.device_energy_use_since_turn_on 0x000D Energy 4 + * Device Firmware Revision org.bluetooth.property.device_firmware_revision 0x000E Fixed String 8 8 + * Device Global Trade Item Number org.bluetooth.property.device_global_trade_item_number 0x000F Global Trade Item Number 8 + * Device Hardware Revision org.bluetooth.property.device_hardware_revision 0x0010 Fixed String 16 16 + * Device Manufacturer Name org.bluetooth.property.device_manufacturer_name 0x0011 Fixed String 36 36 + * Device Model Number org.bluetooth.property.device_model_number 0x0012 Fixed String 24 24 + * Device Operating Temperature Range Specification org.bluetooth.property.device_operating_temperature_range_specification 0x0013 Temperature Range 4 + * Device Operating Temperature Statistical Values org.bluetooth.property.device_operating_temperature_statistical_values 0x0014 Temperature Statistics 9 + * Device Over Temperature Event Statistics org.bluetooth.property.device_over_temperature_event_statistics 0x0015 Event Statistics 6 + * Device Power Range Specification org.bluetooth.property.device_power_range_specification 0x0016 Power Specification 12 + * Device Runtime Since Turn On org.bluetooth.property.device_runtime_since_turn_on 0x0017 Time Hour 24 4 + * Device Runtime Warranty org.bluetooth.property.device_runtime_warranty 0x0018 Time Hour 24 4 + * Device Serial Number org.bluetooth.property.device_serial_number 0x0019 Fixed String 16 16 + * Device Software Revision org.bluetooth.property.device_software_revision 0x001A Fixed String 8 8 + * Device Under Temperature Event Statistics org.bluetooth.property.device_under_temperature_event_statistics 0x001B Event Statistics 6 + * Indoor Ambient Temperature Statistical Values org.bluetooth.property.indoor_ambient_temperature_statistical_values 0x001C Temperature 8 Statistics 5 + * Initial CIE 1931 Chromaticity Coordinates org.bluetooth.property.initial_cie_1931_chromaticity_coordinates 0x001D Chromaticity Coordinates 4 + * Initial Correlated Color Temperature org.bluetooth.property.initial_correlated_color_temperature 0x001E Correlated Color Temperature 2 + * Initial Luminous Flux org.bluetooth.property.initial_luminous_flux 0x001F Luminous Flux 2 + * Initial Planckian Distance org.bluetooth.property.initial_planckian_distance 0x0020 Chromatic Distance From Planckian 2 + * Input Current Range Specification org.bluetooth.property.input_current_range_specification 0x0021 Electric Current Specification 6 + * Input Current Statistics org.bluetooth.property.input_current_statistics 0x0022 Electric Current Statistics 9 + * Input Over Current Event Statistics org.bluetooth.property.input_over_current_event_statistics 0x0023 Event Statistics 6 + * Input Over Ripple Voltage Event Statistics org.bluetooth.property.input_over_ripple_voltage_event_statistics 0x0024 Event Statistics 6 + * Input Over Voltage Event Statistics org.bluetooth.property.input_over_voltage_event_statistics 0x0025 Event Statistics 6 + * Input Under Current Event Statistics org.bluetooth.property.input_under_current_event_statistics 0x0026 Event Statistics 6 + * Input Under Voltage Event Statistics org.bluetooth.property.input_under_voltage_event_statistics 0x0027 Event Statistics 6 + * Input Voltage Range Specification org.bluetooth.property.input_voltage_range_specification 0x0028 Voltage Specification 6 + * Input Voltage Ripple Specification org.bluetooth.property.input_voltage_ripple_specification 0x0029 Percentage 8 1 + * Input Voltage Statistics org.bluetooth.property.input_voltage_statistics 0x002A Voltage Statistics 9 + * Light Control Ambient LuxLevel On org.bluetooth.property.light_control_ambient_luxlevel_on 0x002B Illuminance 4 + * Light Control Ambient LuxLevel Prolong org.bluetooth.property.light_control_ambient_luxlevel_prolong 0x002C Illuminance 4 + * Light Control Ambient LuxLevel Standby org.bluetooth.property.light_control_ambient_luxlevel_standby 0x002D Illuminance 4 + * Light Control Lightness On org.bluetooth.property.light_control_lightness_on 0x002E Perceived Lightness 2 + * Light Control Lightness Prolong org.bluetooth.property.light_control_lightness_prolong 0x002F Perceived Lightness 2 + * Light Control Lightness Standby org.bluetooth.property.light_control_lightness_standby 0x0030 Perceived Lightness 2 + * Light Control Regulator Accuracy org.bluetooth.property.light_control_regulator_accuracy 0x0031 Percentage 8 1 + * Light Control Regulator Kid org.bluetooth.property.light_control_regulator_kid 0x0032 Coefficient 4 + * Light Control Regulator Kiu org.bluetooth.property.light_control_regulator_kiu 0x0033 Coefficient 4 + * Light Control Regulator Kpd org.bluetooth.property.light_control_regulator_kpd 0x0034 Coefficient 4 + * Light Control Regulator Kpu org.bluetooth.property.light_control_regulator_kpu 0x0035 Coefficient 4 + * Light Control Time Fade org.bluetooth.property.light_control_time_fade 0x0036 Time Millisecond 24 4 + * Light Control Time Fade On org.bluetooth.property.light_control_time_fade_on 0x0037 Time Millisecond 24 4 + * Light Control Time Fade Standby Auto org.bluetooth.property.light_control_time_fade_standby_auto 0x0038 Time Millisecond 24 4 + * Light Control Time Fade Standby Manual org.bluetooth.property.light_control_time_fade_standby_manual 0x0039 Time Millisecond 24 4 + * Light Control Time Occupancy Delay org.bluetooth.property.light_control_time_occupancy_delay 0x003A Time Millisecond 24 4 + * Light Control Time Prolong org.bluetooth.property.light_control_time_prolong 0x003B Time Millisecond 24 4 + * Light Control Time Run On org.bluetooth.property.light_control_time_run_on 0x003C Time Millisecond 24 4 + * Lumen Maintenance Factor org.bluetooth.property.lumen_maintenance_factor 0x003D Percentage 8 1 + * Luminous Efficacy org.bluetooth.property.luminous_efficacy 0x003E Luminous Efficacy 2 + * Luminous Energy Since Turn On org.bluetooth.property.luminous_energy_since_turn_on 0x003F Luminous Energy 4 + * Luminous Exposure org.bluetooth.property.luminous_exposure 0x0040 Luminous Exposure 4 + * Luminous Flux Range org.bluetooth.property.luminous_flux_range 0x0041 Luminous Flux Range 4 + * Motion Sensed org.bluetooth.property.motion_sensed 0x0042 Percentage 8 1 + * Motion Threshold org.bluetooth.property.motion_threshold 0x0043 Percentage 8 1 + * Open Circuit Event Statistics org.bluetooth.property.open_circuit_event_statistics 0x0044 Event Statistics 6 + * Outdoor Statistical Values org.bluetooth.property.outdoor_statistical_values 0x0045 Temperature 8 Statistics 5 + * Output Current Range org.bluetooth.property.output_current_range 0x0046 Electric Current Range 4 + * Output Current Statistics org.bluetooth.property.output_current_statistics 0x0047 Electric Current Statistics 9 + * Output Ripple Voltage Specification org.bluetooth.property.output_ripple_voltage_specification 0x0048 Percentage 8 1 + * Output Voltage Range org.bluetooth.property.output_voltage_range 0x0049 Voltage Specification 6 + * Output Voltage Statistics org.bluetooth.property.output_voltage_statistics 0x004A Voltage Statistics 9 + * Over Output Ripple Voltage Event Statistics org.bluetooth.property.over_output_ripple_voltage_event_statistics 0x004B Event Statistics 6 + * People Count org.bluetooth.property.people_count 0x004C Count 16 2 + * Presence Detected org.bluetooth.property.presence_detected 0x004D Boolean 1 + * Present Ambient Light Level org.bluetooth.property.present_ambient_light_level 0x004E Illuminance 4 + * Present Ambient Temperature org.bluetooth.property.present_ambient_temperature 0x004F Temperature 8 1 + * Present CIE 1931 Chromaticity Coordinates org.bluetooth.property.present_cie_1931_chromaticity 0x0050 Chromaticity Coordinates 4 + * Present Correlated Color Temperature org.bluetooth.property.present_correlated_color_temperature 0x0051 Correlated Color Temperature 2 + * Present Device Input Power org.bluetooth.property.present_device_input_power 0x0052 Power 4 + * Present Device Operating Efficiency org.bluetooth.property.present_device_operating_efficiency 0x0053 Percentage 8 1 + * Present Device Operating Temperature org.bluetooth.property.present_device_operating_temperature 0x0054 Temperature 2 + * Present Illuminance org.bluetooth.property.present_illuminance 0x0055 Illuminance 4 + * Present Indoor Ambient Temperature org.bluetooth.property.present_indoor_ambient_temperature 0x0056 Temperature 8 1 + * Present Input Current org.bluetooth.property.present_input_current 0x0057 Electric Current 2 + * Present Input Ripple Voltage org.bluetooth.property.present_input_ripple_voltage 0x0058 Percentage 8 1 + * Present Input Voltage org.bluetooth.property.present_input_voltage 0x0059 Voltage 2 + * Present Luminous Flux org.bluetooth.property.present_luminous_flux 0x005A Luminous Flux 2 + * Present Outdoor Ambient Temperature org.bluetooth.property.present_outdoor_ambient_temperature 0x005B Temperature 8 1 + * Present Output Current org.bluetooth.property.present_output_current 0x005C Electric Current 2 + * Present Output Voltage org.bluetooth.property.present_output_voltage 0x005D Voltage 2 + * Present Planckian Distance org.bluetooth.property.present_planckian_distance 0x005E Chromatic Distance From Planckian 2 + * Present Relative Output Ripple Voltage org.bluetooth.property.present_relative_output_ripple_voltage 0x005F Percentage 8 1 + * Relative Device Energy Use In A Period Of Day org.bluetooth.property.relative_device_energy_use_in_a_period_of_day 0x0060 Energy In A Period Of Day 6 + * Relative Device Runtime In A Generic Level Range org.bluetooth.property.relative_device_runtime_in_a_generic_level_range 0x0061 Relative Runtime In A Generic Level Range 5 + * Relative Exposure Time In An Illuminance Range org.bluetooth.property.relative_exposure_time_in_an_illuminance_range 0x0062 Relative Value In An Illuminance Range 9 + * Relative Runtime In A Correlated Color Temperature Range org.bluetooth.property.relative_runtime_in_a_correlated_color_temperature_range 0x0063 Luminous Energy 4 + * Relative Runtime In A Device Operating Temperature Range org.bluetooth.property.relative_runtime_in_a_device_operating_temperature_range 0x0064 Relative Value In A Temperature Range 5 + * Relative Runtime In An Input Current Range org.bluetooth.property.relative_runtime_in_an_input_current_range 0x0065 Relative Runtime In A Current Range 5 + * Relative Runtime In An Input Voltage Range org.bluetooth.property.relative_runtime_in_an_input_voltage_range 0x0066 Relative Value In A Voltage Range 5 + * Short Circuit Event Statistics org.bluetooth.property.short_circuit_event_statistics 0x0067 Event Statistics 6 + * Time Since Motion Sensed org.bluetooth.property.time_since_motion_sensed 0x0068 Time Second 16 2 + * Time Since Presence Detected org.bluetooth.property.time_since_presence_detected 0x0069 Time Second 16 2 + * Total Device Energy Use org.bluetooth.property.total_device_energy_use 0x006A Energy 4 + * Total Device Off On Cycles org.bluetooth.property.total_device_off_on_cycles 0x006B Count 24 4 + * Total Device Power On Cycles org.bluetooth.property.total_device_power_on_cycles 0x006C Count 24 4 + * Total Device Power On Time org.bluetooth.property.total_device_power_on_time 0x006D Time Hour 24 4 + * Total Device Runtime org.bluetooth.property.total_device_runtime 0x006E Time Hour 24 4 + * Total Light Exposure Time org.bluetooth.property.total_light_exposure_time 0x006F Time Hour 24 4 + * Total Luminous Energy org.bluetooth.property.total_luminous_energy 0x0070 Luminous Energy 4 + */ + +/** + * Characteristics referenced by BLE Mesh Device Properties. + * + * Name Uniform Type Identifier Assigned Number Specification Level + * Average Current org.bluetooth.characteristic.average_current 2AE0 Adopted + * Average Voltage org.bluetooth.characteristic.average_voltage 2AE1 Adopted + * Boolean org.bluetooth.characteristic.boolean 2AE2 Adopted + * Chromatic Distance From Planckian org.bluetooth.characteristic.chromatic_distance_from_planckian 2AE3 Adopted + * Chromaticity Coordinate org.bluetooth.characteristic.chromaticity_coordinate 2B1C Adopted + * Chromaticity Coordinates org.bluetooth.characteristic.chromaticity_coordinates 2AE4 Adopted + * Chromaticity In CCT And Duv Values org.bluetooth.characteristic.chromaticity_in_cct_and_duv_values 2AE5 Adopted + * Chromaticity Tolerance org.bluetooth.characteristic.chromaticity_tolerance 2AE6 Adopted + * CIE 13.3-1995 Color Rendering Index org.bluetooth.characteristic.cie_13.3-1995_color_rendering_index 2AE7 Adopted + * Coefficient org.bluetooth.characteristic.coefficient 2AE8 Adopted + * Correlated Color Temperature org.bluetooth.characteristic.correlated_color_temperature 2AE9 Adopted + * Count 16 org.bluetooth.characteristic.count_16 2AEA Adopted + * Count 24 org.bluetooth.characteristic.count_24 2AEB Adopted + * Country Code org.bluetooth.characteristic.country_code 2AEC Adopted + * Date UTC org.bluetooth.characteristic.date_utc 2AED Adopted + * Electric Current org.bluetooth.characteristic.electric_current 2AEE Adopted + * Electric Current Range org.bluetooth.characteristic.electric_current_range 2AEF Adopted + * Electric Current Specification org.bluetooth.characteristic.electric_current_specification 2AF0 Adopted + * Electric Current Statistics org.bluetooth.characteristic.electric_current_statistics 2AF1 Adopted + * Energy org.bluetooth.characteristic.energy 2AF2 Adopted + * Energy In A Period Of Day org.bluetooth.characteristic.energy_in_a_period_of_day 2AF3 Adopted + * Event Statistics org.bluetooth.characteristic.event_statistics 2AF4 Adopted + * Fixed String 16 org.bluetooth.characteristic.fixed_string_16 2AF5 Adopted + * Fixed String 24 org.bluetooth.characteristic.fixed_string_24 2AF6 Adopted + * Fixed String 36 org.bluetooth.characteristic.fixed_string_36 2AF7 Adopted + * Fixed String 8 org.bluetooth.characteristic.fixed_string_8 2AF8 Adopted + * Generic Level org.bluetooth.characteristic.generic_level 2AF9 Adopted + * Global Trade Item Number org.bluetooth.characteristic.global_trade_item_number 2AFA Adopted + * Illuminance org.bluetooth.characteristic.illuminance 2AFB Adopted + * Luminous Efficacy org.bluetooth.characteristic.luminous_efficacy 2AFC Adopted + * Luminous Energy org.bluetooth.characteristic.luminous_energy 2AFD Adopted + * Luminous Exposure org.bluetooth.characteristic.luminous_exposure 2AFE Adopted + * Luminous Flux org.bluetooth.characteristic.luminous_flux 2AFF Adopted + * Luminous Flux Range org.bluetooth.characteristic.luminous_flux_range 2B00 Adopted + * Luminous Intensity org.bluetooth.characteristic.luminous_intensity 2B01 Adopted + * Mass Flow org.bluetooth.characteristic.mass_flow 2B02 Adopted + * Mesh Provisioning Data In org.bluetooth.characteristic.mesh_provisioning_data_in 2ADB Adopted + * Mesh Provisioning Data Out org.bluetooth.characteristic.mesh_provisioning_data_out 2ADC Adopted + * Mesh Proxy Data In org.bluetooth.characteristic.mesh_proxy_data_in 2ADD Adopted + * Mesh Proxy Data Out org.bluetooth.characteristic.mesh_proxy_data_out 2ADE Adopted + * Perceived Lightness org.bluetooth.characteristic.perceived_lightness 2B03 Adopted + * Percentage 8 org.bluetooth.characteristic.percentage_8 2B04 Adopted + * Power org.bluetooth.characteristic.power 2B05 Adopted + * Power Specification org.bluetooth.characteristic.power_specification 2B06 Adopted + * Relative Runtime In A Current Range org.bluetooth.characteristic.relative_runtime_in_a_current_range 2B07 Adopted + * Relative Runtime In A Generic Level Range org.bluetooth.characteristic.relative_runtime_in_a_generic_level_range 2B08 Adopted + * Relative Value In A Period of Day org.bluetooth.characteristic.relative_value_in_a_period_of_day 2B0B Adopted + * Relative Value In A Temperature Range org.bluetooth.characteristic.relative_value_in_a_temperature_range 2B0C Adopted + * Relative Value In A Voltage Range org.bluetooth.characteristic.relative_value_in_a_voltage_range 2B09 Adopted + * Relative Value In An Illuminance Range org.bluetooth.characteristic.relative_value_in_an_illuminance_range 2B0A Adopted + * Temperature 8 org.bluetooth.characteristic.temperature_8 2B0D Adopted + * Temperature 8 In A Period Of Day org.bluetooth.characteristic.temperature_8_in_a_period_of_day 2B0E Adopted + * Temperature 8 Statistics org.bluetooth.characteristic.temperature_8_statistics 2B0F Adopted + * Temperature Range org.bluetooth.characteristic.temperature_range 2B10 Adopted + * Temperature Statistics org.bluetooth.characteristic.temperature_statistics 2B11 Adopted + * Time Decihour 8 org.bluetooth.characteristic.time_decihour_8 2B12 Adopted + * Time Exponential 8 org.bluetooth.characteristic.time_exponential_8 2B13 Adopted + * Time Hour 24 org.bluetooth.characteristic.time_hour_24 2B14 Adopted + * Time Millisecond 24 org.bluetooth.characteristic.time_millisecond_24 2B15 Adopted + * Time Second 16 org.bluetooth.characteristic.time_second_16 2B16 Adopted + * Time Second 8 org.bluetooth.characteristic.time_second_8 2B17 Adopted + * Voltage org.bluetooth.characteristic.voltage 2B18 Adopted + * Voltage Specification org.bluetooth.characteristic.voltage_specification 2B19 Adopted + * Voltage Statistics org.bluetooth.characteristic.voltage_statistics 2B1A Adopted + * Volume Flow org.bluetooth.characteristic.volume_flow 2B1B Adopted + */ + +/** + * @brief BLE Mesh Device Property IDs + */ +#define BLE_MESH_AVERAGE_AMBIENT_TEMPERATURE_IN_A_PERIOD_OF_DAY 0x0001 +#define BLE_MESH_AVERAGE_INPUT_CURRENT 0x0002 +#define BLE_MESH_AVERAGE_INPUT_VOLTAGE 0x0003 +#define BLE_MESH_AVERAGE_OUTPUT_CURRENT 0x0004 +#define BLE_MESH_AVERAGE_OUTPUT_VOLTAGE 0x0005 +#define BLE_MESH_CENTER_BEAM_INTENSITY_AT_FULL_POWER 0x0006 +#define BLE_MESH_CHROMATICITY_TOLERANCE 0x0007 +#define BLE_MESH_COLOR_RENDERING_INDEX_R9 0x0008 +#define BLE_MESH_COLOR_RENDERING_INDEX_RA 0x0009 +#define BLE_MESH_DEVICE_APPEARANCE 0x000A +#define BLE_MESH_DEVICE_COUNTRY_OF_ORIGIN 0x000B +#define BLE_MESH_DEVICE_DATE_OF_MANUFACTURE 0x000C +#define BLE_MESH_DEVICE_ENERGY_USE_SINCE_TURN_ON 0x000D +#define BLE_MESH_DEVICE_FIRMWARE_REVISION 0x000E +#define BLE_MESH_DEVICE_GLOBAL_TRADE_ITEM_NUMBER 0x000F +#define BLE_MESH_DEVICE_HARDWARE_REVISION 0x0010 +#define BLE_MESH_DEVICE_MANUFACTURER_NAME 0x0011 +#define BLE_MESH_DEVICE_MODEL_NUMBER 0x0012 +#define BLE_MESH_DEVICE_OPERATING_TEMPERATURE_RANGE_SPECIFICATION 0x0013 +#define BLE_MESH_DEVICE_OPERATING_TEMPERATURE_STATISTICAL_VALUES 0x0014 +#define BLE_MESH_DEVICE_OVER_TEMPERATURE_EVENT_STATISTICS 0x0015 +#define BLE_MESH_DEVICE_POWER_RANGE_SPECIFICATION 0x0016 +#define BLE_MESH_DEVICE_RUNTIME_SINCE_TURN_ON 0x0017 +#define BLE_MESH_DEVICE_RUNTIME_WARRANTY 0x0018 +#define BLE_MESH_DEVICE_SERIAL_NUMBER 0x0019 +#define BLE_MESH_DEVICE_SOFTWARE_REVISION 0x001A +#define BLE_MESH_DEVICE_UNDER_TEMPERATURE_EVENT_STATISTICS 0x001B +#define BLE_MESH_INDOOR_AMBIENT_TEMPERATURE_STATISTICAL_VALUES 0x001C +#define BLE_MESH_INITIAL_CIE_1931_CHROMATICITY_COORDINATES 0x001D +#define BLE_MESH_INITIAL_CORRELATED_COLOR_TEMPERATURE 0x001E +#define BLE_MESH_INITIAL_LUMINOUS_FLUX 0x001F +#define BLE_MESH_INITIAL_PLANCKIAN_DISTANCE 0x0020 +#define BLE_MESH_INPUT_CURRENT_RANGE_SPECIFICATION 0x0021 +#define BLE_MESH_INPUT_CURRENT_STATISTICS 0x0022 +#define BLE_MESH_INPUT_OVER_CURRENT_EVENT_STATISTICS 0x0023 +#define BLE_MESH_INPUT_OVER_RIPPLE_VOLTAGE_EVENT_STATISTICS 0x0024 +#define BLE_MESH_INPUT_OVER_VOLTAGE_EVENT_STATISTICS 0x0025 +#define BLE_MESH_INPUT_UNDER_CURRENT_EVENT_STATISTICS 0x0026 +#define BLE_MESH_INPUT_UNDER_VOLTAGE_EVENT_STATISTICS 0x0027 +#define BLE_MESH_INPUT_VOLTAGE_RANGE_SPECIFICATION 0x0028 +#define BLE_MESH_INPUT_VOLTAGE_RIPPLE_SPECIFICATION 0x0029 +#define BLE_MESH_INPUT_VOLTAGE_STATISTICS 0x002A +#define BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_ON 0x002B +#define BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_PROLONG 0x002C +#define BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_STANDBY 0x002D +#define BLE_MESH_LIGHT_CONTROL_LIGHTNESS_ON 0x002E +#define BLE_MESH_LIGHT_CONTROL_LIGHTNESS_PROLONG 0x002F +#define BLE_MESH_LIGHT_CONTROL_LIGHTNESS_STANDBY 0x0030 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_ACCURACY 0x0031 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_KID 0x0032 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_KIU 0x0033 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_KPD 0x0034 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_KPU 0x0035 +#define BLE_MESH_LIGHT_CONTROL_TIME_FADE 0x0036 +#define BLE_MESH_LIGHT_CONTROL_TIME_FADE_ON 0x0037 +#define BLE_MESH_LIGHT_CONTROL_TIME_FADE_STANDBY_AUTO 0x0038 +#define BLE_MESH_LIGHT_CONTROL_TIME_FADE_STANDBY_MANUAL 0x0039 +#define BLE_MESH_LIGHT_CONTROL_TIME_OCCUPANCY_DELAY 0x003A +#define BLE_MESH_LIGHT_CONTROL_TIME_PROLONG 0x003B +#define BLE_MESH_LIGHT_CONTROL_TIME_RUN_ON 0x003C +#define BLE_MESH_LUMEN_MAINTENANCE_FACTOR 0x003D +#define BLE_MESH_LUMINOUS_EFFICACY 0x003E +#define BLE_MESH_LUMINOUS_ENERGY_SINCE_TURN_ON 0x003F +#define BLE_MESH_LUMINOUS_EXPOSURE 0x0040 +#define BLE_MESH_LUMINOUS_FLUX_RANGE 0x0041 +#define BLE_MESH_MOTION_SENSED 0x0042 +#define BLE_MESH_MOTION_THRESHOLD 0x0043 +#define BLE_MESH_OPEN_CIRCUIT_EVENT_STATISTICS 0x0044 +#define BLE_MESH_OUTDOOR_STATISTICAL_VALUES 0x0045 +#define BLE_MESH_OUTPUT_CURRENT_RANGE 0x0046 +#define BLE_MESH_OUTPUT_CURRENT_STATISTICS 0x0047 +#define BLE_MESH_OUTPUT_RIPPLE_VOLTAGE_SPECIFICATION 0x0048 +#define BLE_MESH_OUTPUT_VOLTAGE_RANGE 0x0049 +#define BLE_MESH_OUTPUT_VOLTAGE_STATISTICS 0x004A +#define BLE_MESH_OVER_OUTPUT_RIPPLE_VOLTAGE_EVENT_STATISTICS 0x004B +#define BLE_MESH_PEOPLE_COUNT 0x004C +#define BLE_MESH_PRESENCE_DETECTED 0x004D +#define BLE_MESH_PRESENT_AMBIENT_LIGHT_LEVEL 0x004E +#define BLE_MESH_PRESENT_AMBIENT_TEMPERATURE 0x004F +#define BLE_MESH_PRESENT_CIE_1931_CHROMATICITY 0x0050 +#define BLE_MESH_PRESENT_CORRELATED_COLOR_TEMPERATURE 0x0051 +#define BLE_MESH_PRESENT_DEVICE_INPUT_POWER 0x0052 +#define BLE_MESH_PRESENT_DEVICE_OPERATING_EFFICIENCY 0x0053 +#define BLE_MESH_PRESENT_DEVICE_OPERATING_TEMPERATURE 0x0054 +#define BLE_MESH_PRESENT_ILLUMINANCE 0x0055 +#define BLE_MESH_PRESENT_INDOOR_AMBIENT_TEMPERATURE 0x0056 +#define BLE_MESH_PRESENT_INPUT_CURRENT 0x0057 +#define BLE_MESH_PRESENT_INPUT_RIPPLE_VOLTAGE 0x0058 +#define BLE_MESH_PRESENT_INPUT_VOLTAGE 0x0059 +#define BLE_MESH_PRESENT_LUMINOUS_FLUX 0x005A +#define BLE_MESH_PRESENT_OUTDOOR_AMBIENT_TEMPERATURE 0x005B +#define BLE_MESH_PRESENT_OUTPUT_CURRENT 0x005C +#define BLE_MESH_PRESENT_OUTPUT_VOLTAGE 0x005D +#define BLE_MESH_PRESENT_PLANCKIAN_DISTANCE 0x005E +#define BLE_MESH_PRESENT_RELATIVE_OUTPUT_RIPPLE_VOLTAGE 0x005F +#define BLE_MESH_RELATIVE_DEVICE_ENERGY_USE_IN_A_PERIOD_OF_DAY 0x0060 +#define BLE_MESH_RELATIVE_DEVICE_RUNTIME_IN_A_GENERIC_LEVEL_RANGE 0x0061 +#define BLE_MESH_RELATIVE_EXPOSURE_TIME_IN_AN_ILLUMINANCE_RANGE 0x0062 +#define BLE_MESH_RELATIVE_RUNTIME_IN_A_CORRELATED_COLOR_TEMPERATURE_RANGE 0x0063 +#define BLE_MESH_RELATIVE_RUNTIME_IN_A_DEVICE_OPERATING_TEMPERATURE_RANGE 0x0064 +#define BLE_MESH_RELATIVE_RUNTIME_IN_AN_INPUT_CURRENT_RANGE 0x0065 +#define BLE_MESH_RELATIVE_RUNTIME_IN_AN_INPUT_VOLTAGE_RANGE 0x0066 +#define BLE_MESH_SHORT_CIRCUIT_EVENT_STATISTICS 0x0067 +#define BLE_MESH_TIME_SINCE_MOTION_SENSED 0x0068 +#define BLE_MESH_TIME_SINCE_PRESENCE_DETECTED 0x0069 +#define BLE_MESH_TOTAL_DEVICE_ENERGY_USE 0x006A +#define BLE_MESH_TOTAL_DEVICE_OFF_ON_CYCLES 0x006B +#define BLE_MESH_TOTAL_DEVICE_POWER_ON_CYCLES 0x006C +#define BLE_MESH_TOTAL_DEVICE_POWER_ON_TIME 0x006D +#define BLE_MESH_TOTAL_DEVICE_RUNTIME 0x006E +#define BLE_MESH_TOTAL_LIGHT_EXPOSURE_TIME 0x006F +#define BLE_MESH_TOTAL_LUMINOUS_ENERGY 0x0070 + +/** + * @brief BLE Mesh Device Property value length + */ +#define BLE_MESH_AVERAGE_AMBIENT_TEMPERATURE_IN_A_PERIOD_OF_DAY_LEN 0x03 +#define BLE_MESH_AVERAGE_INPUT_CURRENT_LEN 0x03 +#define BLE_MESH_AVERAGE_INPUT_VOLTAGE_LEN 0x03 +#define BLE_MESH_AVERAGE_OUTPUT_CURRENT_LEN 0x03 +#define BLE_MESH_AVERAGE_OUTPUT_VOLTAGE_LEN 0x03 +#define BLE_MESH_CENTER_BEAM_INTENSITY_AT_FULL_POWER_LEN 0x02 +#define BLE_MESH_CHROMATICITY_TOLERANCE_LEN 0x01 +#define BLE_MESH_COLOR_RENDERING_INDEX_R9_LEN 0x01 +#define BLE_MESH_COLOR_RENDERING_INDEX_RA_LEN 0x01 +#define BLE_MESH_DEVICE_APPEARANCE_LEN 0x02 +#define BLE_MESH_DEVICE_COUNTRY_OF_ORIGIN_LEN 0x02 +#define BLE_MESH_DEVICE_DATE_OF_MANUFACTURE_LEN 0x04 +#define BLE_MESH_DEVICE_ENERGY_USE_SINCE_TURN_ON_LEN 0x04 +#define BLE_MESH_DEVICE_FIRMWARE_REVISION_LEN 0x08 +#define BLE_MESH_DEVICE_GLOBAL_TRADE_ITEM_NUMBER_LEN 0x08 +#define BLE_MESH_DEVICE_HARDWARE_REVISION_LEN 0x16 +#define BLE_MESH_DEVICE_MANUFACTURER_NAME_LEN 0x36 +#define BLE_MESH_DEVICE_MODEL_NUMBER_LEN 0x24 +#define BLE_MESH_DEVICE_OPERATING_TEMPERATURE_RANGE_SPECIFICATION_LEN 0x04 +#define BLE_MESH_DEVICE_OPERATING_TEMPERATURE_STATISTICAL_VALUES_LEN 0x09 +#define BLE_MESH_DEVICE_OVER_TEMPERATURE_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_DEVICE_POWER_RANGE_SPECIFICATION_LEN 0x12 +#define BLE_MESH_DEVICE_RUNTIME_SINCE_TURN_ON_LEN 0x04 +#define BLE_MESH_DEVICE_RUNTIME_WARRANTY_LEN 0x04 +#define BLE_MESH_DEVICE_SERIAL_NUMBER_LEN 0x16 +#define BLE_MESH_DEVICE_SOFTWARE_REVISION_LEN 0x08 +#define BLE_MESH_DEVICE_UNDER_TEMPERATURE_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_INDOOR_AMBIENT_TEMPERATURE_STATISTICAL_VALUES_LEN 0x05 +#define BLE_MESH_INITIAL_CIE_1931_CHROMATICITY_COORDINATES_LEN 0x04 +#define BLE_MESH_INITIAL_CORRELATED_COLOR_TEMPERATURE_LEN 0x02 +#define BLE_MESH_INITIAL_LUMINOUS_FLUX_LEN 0x02 +#define BLE_MESH_INITIAL_PLANCKIAN_DISTANCE_LEN 0x02 +#define BLE_MESH_INPUT_CURRENT_RANGE_SPECIFICATION_LEN 0x06 +#define BLE_MESH_INPUT_CURRENT_STATISTICS_LEN 0x09 +#define BLE_MESH_INPUT_OVER_CURRENT_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_INPUT_OVER_RIPPLE_VOLTAGE_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_INPUT_OVER_VOLTAGE_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_INPUT_UNDER_CURRENT_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_INPUT_UNDER_VOLTAGE_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_INPUT_VOLTAGE_RANGE_SPECIFICATION_LEN 0x06 +#define BLE_MESH_INPUT_VOLTAGE_RIPPLE_SPECIFICATION_LEN 0x01 +#define BLE_MESH_INPUT_VOLTAGE_STATISTICS_LEN 0x09 +#define BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_ON_LEN 0x04 +#define BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_PROLONG_LEN 0x04 +#define BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_STANDBY_LEN 0x04 +#define BLE_MESH_LIGHT_CONTROL_LIGHTNESS_ON_LEN 0x02 +#define BLE_MESH_LIGHT_CONTROL_LIGHTNESS_PROLONG_LEN 0x02 +#define BLE_MESH_LIGHT_CONTROL_LIGHTNESS_STANDBY_LEN 0x02 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_ACCURACY_LEN 0x01 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_KID_LEN 0x04 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_KIU_LEN 0x04 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_KPD_LEN 0x04 +#define BLE_MESH_LIGHT_CONTROL_REGULATOR_KPU_LEN 0x04 +#define BLE_MESH_LIGHT_CONTROL_TIME_FADE_LEN 0x03 +#define BLE_MESH_LIGHT_CONTROL_TIME_FADE_ON_LEN 0x03 +#define BLE_MESH_LIGHT_CONTROL_TIME_FADE_STANDBY_AUTO_LEN 0x03 +#define BLE_MESH_LIGHT_CONTROL_TIME_FADE_STANDBY_MANUAL_LEN 0x03 +#define BLE_MESH_LIGHT_CONTROL_TIME_OCCUPANCY_DELAY_LEN 0x03 +#define BLE_MESH_LIGHT_CONTROL_TIME_PROLONG_LEN 0x03 +#define BLE_MESH_LIGHT_CONTROL_TIME_RUN_ON_LEN 0x03 +#define BLE_MESH_LUMEN_MAINTENANCE_FACTOR_LEN 0x01 +#define BLE_MESH_LUMINOUS_EFFICACY_LEN 0x02 +#define BLE_MESH_LUMINOUS_ENERGY_SINCE_TURN_ON_LEN 0x04 +#define BLE_MESH_LUMINOUS_EXPOSURE_LEN 0x04 +#define BLE_MESH_LUMINOUS_FLUX_RANGE_LEN 0x04 +#define BLE_MESH_MOTION_SENSED_LEN 0x01 +#define BLE_MESH_MOTION_THRESHOLD_LEN 0x01 +#define BLE_MESH_OPEN_CIRCUIT_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_OUTDOOR_STATISTICAL_VALUES_LEN 0x05 +#define BLE_MESH_OUTPUT_CURRENT_RANGE_LEN 0x04 +#define BLE_MESH_OUTPUT_CURRENT_STATISTICS_LEN 0x09 +#define BLE_MESH_OUTPUT_RIPPLE_VOLTAGE_SPECIFICATION_LEN 0x01 +#define BLE_MESH_OUTPUT_VOLTAGE_RANGE_LEN 0x06 +#define BLE_MESH_OUTPUT_VOLTAGE_STATISTICS_LEN 0x09 +#define BLE_MESH_OVER_OUTPUT_RIPPLE_VOLTAGE_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_PEOPLE_COUNT_LEN 0x02 +#define BLE_MESH_PRESENCE_DETECTED_LEN 0x01 +#define BLE_MESH_PRESENT_AMBIENT_LIGHT_LEVEL_LEN 0x04 +#define BLE_MESH_PRESENT_AMBIENT_TEMPERATURE_LEN 0x01 +#define BLE_MESH_PRESENT_CIE_1931_CHROMATICITY_LEN 0x04 +#define BLE_MESH_PRESENT_CORRELATED_COLOR_TEMPERATURE_LEN 0x02 +#define BLE_MESH_PRESENT_DEVICE_INPUT_POWER_LEN 0x04 +#define BLE_MESH_PRESENT_DEVICE_OPERATING_EFFICIENCY_LEN 0x01 +#define BLE_MESH_PRESENT_DEVICE_OPERATING_TEMPERATURE_LEN 0x02 +#define BLE_MESH_PRESENT_ILLUMINANCE_LEN 0x04 +#define BLE_MESH_PRESENT_INDOOR_AMBIENT_TEMPERATURE_LEN 0x01 +#define BLE_MESH_PRESENT_INPUT_CURRENT_LEN 0x02 +#define BLE_MESH_PRESENT_INPUT_RIPPLE_VOLTAGE_LEN 0x01 +#define BLE_MESH_PRESENT_INPUT_VOLTAGE_LEN 0x02 +#define BLE_MESH_PRESENT_LUMINOUS_FLUX_LEN 0x02 +#define BLE_MESH_PRESENT_OUTDOOR_AMBIENT_TEMPERATURE_LEN 0x01 +#define BLE_MESH_PRESENT_OUTPUT_CURRENT_LEN 0x02 +#define BLE_MESH_PRESENT_OUTPUT_VOLTAGE_LEN 0x02 +#define BLE_MESH_PRESENT_PLANCKIAN_DISTANCE_LEN 0x02 +#define BLE_MESH_PRESENT_RELATIVE_OUTPUT_RIPPLE_VOLTAGE_LEN 0x01 +#define BLE_MESH_RELATIVE_DEVICE_ENERGY_USE_IN_A_PERIOD_OF_DAY_LEN 0x06 +#define BLE_MESH_RELATIVE_DEVICE_RUNTIME_IN_A_GENERIC_LEVEL_RANGE_LEN 0x05 +#define BLE_MESH_RELATIVE_EXPOSURE_TIME_IN_AN_ILLUMINANCE_RANGE_LEN 0x09 +#define BLE_MESH_RELATIVE_RUNTIME_IN_A_CORRELATED_COLOR_TEMPERATURE_RANGE_LEN 0x04 +#define BLE_MESH_RELATIVE_RUNTIME_IN_A_DEVICE_OPERATING_TEMPERATURE_RANGE_LEN 0x05 +#define BLE_MESH_RELATIVE_RUNTIME_IN_AN_INPUT_CURRENT_RANGE_LEN 0x05 +#define BLE_MESH_RELATIVE_RUNTIME_IN_AN_INPUT_VOLTAGE_RANGE_LEN 0x05 +#define BLE_MESH_SHORT_CIRCUIT_EVENT_STATISTICS_LEN 0x06 +#define BLE_MESH_TIME_SINCE_MOTION_SENSED_LEN 0x02 +#define BLE_MESH_TIME_SINCE_PRESENCE_DETECTED_LEN 0x02 +#define BLE_MESH_TOTAL_DEVICE_ENERGY_USE_LEN 0x04 +#define BLE_MESH_TOTAL_DEVICE_OFF_ON_CYCLES_LEN 0x04 +#define BLE_MESH_TOTAL_DEVICE_POWER_ON_CYCLES_LEN 0x04 +#define BLE_MESH_TOTAL_DEVICE_POWER_ON_TIME_LEN 0x04 +#define BLE_MESH_TOTAL_DEVICE_RUNTIME_LEN 0x04 +#define BLE_MESH_TOTAL_LIGHT_EXPOSURE_TIME_LEN 0x04 +#define BLE_MESH_TOTAL_LUMINOUS_ENERGY_LEN 0x04 + +/** + * @brief BLE Mesh Device Property referenced Characteristic UUIDs + */ +#define BLE_MESH_UUID_AVERAGE_CURRENT_VAL 0x2AE0 +#define BLE_MESH_UUID_AVERAGE_VOLTAGE_VAL 0x2AE1 +#define BLE_MESH_UUID_BOOLEAN_VAL 0x2AE2 +#define BLE_MESH_UUID_CHROMATIC_DISTANCE_FROM_PLANCKIAN_VAL 0x2AE3 +#define BLE_MESH_UUID_CHROMATICITY_COORDINATE_VAL 0x2B1C +#define BLE_MESH_UUID_CHROMATICITY_COORDINATES_VAL 0x2AE4 +#define BLE_MESH_UUID_CHROMATICITY_IN_CCT_AND_DUV_VALUES_VAL 0x2AE5 +#define BLE_MESH_UUID_CHROMATICITY_TOLERANCE_VAL 0x2AE6 +#define BLE_MESH_UUID_CIE_13_3_1995_COLOR_RENDERING_INDEX_VAL 0x2AE7 +#define BLE_MESH_UUID_COEFFICIENT_VAL 0x2AE8 +#define BLE_MESH_UUID_CORRELATED_COLOR_TEMPERATURE_VAL 0x2AE9 +#define BLE_MESH_UUID_COUNT_16_VAL 0x2AEA +#define BLE_MESH_UUID_COUNT_24_VAL 0x2AEB +#define BLE_MESH_UUID_COUNTRY_CODE_VAL 0x2AEC +#define BLE_MESH_UUID_DATE_UTC_VAL 0x2AED +#define BLE_MESH_UUID_ELECTRIC_CURRENT_VAL 0x2AEE +#define BLE_MESH_UUID_ELECTRIC_CURRENT_RANGE_VAL 0x2AEF +#define BLE_MESH_UUID_ELECTRIC_CURRENT_SPECIFICATION_VAL 0x2AF0 +#define BLE_MESH_UUID_ELECTRIC_CURRENT_STATISTICS_VAL 0x2AF1 +#define BLE_MESH_UUID_ENERGY_VAL 0x2AF2 +#define BLE_MESH_UUID_ENERGY_IN_A_PERIOD_OF_DAY_VAL 0x2AF3 +#define BLE_MESH_UUID_EVENT_STATISTICS_VAL 0x2AF4 +#define BLE_MESH_UUID_FIXED_STRING_16_VAL 0x2AF5 +#define BLE_MESH_UUID_FIXED_STRING_24_VAL 0x2AF6 +#define BLE_MESH_UUID_FIXED_STRING_36_VAL 0x2AF7 +#define BLE_MESH_UUID_FIXED_STRING_8_VAL 0x2AF8 +#define BLE_MESH_UUID_GENERIC_LEVEL_VAL 0x2AF9 +#define BLE_MESH_UUID_GLOBAL_TRADE_ITEM_NUMBER_VAL 0x2AFA +#define BLE_MESH_UUID_ILLUMINANCE_VAL 0x2AFB +#define BLE_MESH_UUID_LUMINOUS_EFFICACY_VAL 0x2AFC +#define BLE_MESH_UUID_LUMINOUS_ENERGY_VAL 0x2AFD +#define BLE_MESH_UUID_LUMINOUS_EXPOSURE_VAL 0x2AFE +#define BLE_MESH_UUID_LUMINOUS_FLUX_VAL 0x2AFF +#define BLE_MESH_UUID_LUMINOUS_FLUX_RANGE_VAL 0x2B00 +#define BLE_MESH_UUID_LUMINOUS_INTENSITY_VAL 0x2B01 +#define BLE_MESH_UUID_MASS_FLOW_VAL 0x2B02 +/** + * The following four have been defined in mesh_uuid.h + * #define BLE_MESH_UUID_MESH_PROV_DATA_IN_VAL 0x2ADB + * #define BLE_MESH_UUID_MESH_PROV_DATA_OUT_VAL 0x2ADC + * #define BLE_MESH_UUID_MESH_PROXY_DATA_IN_VAL 0x2ADD + * #define BLE_MESH_UUID_MESH_PROXY_DATA_OUT_VAL 0x2ADE + */ +#define BLE_MESH_UUID_PERCEIVED_LIGHTNESS_VAL 0x2B03 +#define BLE_MESH_UUID_PERCENTAGE_8_VAL 0x2B04 +#define BLE_MESH_UUID_POWER_VAL 0x2B05 +#define BLE_MESH_UUID_POWER_SPECIFICATION_VAL 0x2B06 +#define BLE_MESH_UUID_RELATIVE_RUNTIME_IN_A_CURRENT_RANGE_VAL 0x2B07 +#define BLE_MESH_UUID_RELATIVE_RUNTIME_IN_A_GENERIC_LEVEL_RANGE_VAL 0x2B08 +#define BLE_MESH_UUID_RELATIVE_VALUE_IN_A_PERIOD_OF_DAY_VAL 0x2B0B +#define BLE_MESH_UUID_RELATIVE_VALUE_IN_A_TEMPERATURE_RANGE_VAL 0x2B0C +#define BLE_MESH_UUID_RELATIVE_VALUE_IN_A_VOLTAGE_RANGE_VAL 0x2B09 +#define BLE_MESH_UUID_RELATIVE_VALUE_IN_AN_ILLUMINANCE_RANGE_VAL 0x2B0A +#define BLE_MESH_UUID_TEMPERATURE_8_VAL 0x2B0D +#define BLE_MESH_UUID_TEMPERATURE_8_IN_A_PERIOD_OF_DAY_VAL 0x2B0E +#define BLE_MESH_UUID_TEMPERATURE_8_STATISTICS_VAL 0x2B0F +#define BLE_MESH_UUID_TEMPERATURE_RANGE_VAL 0x2B10 +#define BLE_MESH_UUID_TEMPERATURE_STATISTICS_VAL 0x2B11 +#define BLE_MESH_UUID_TIME_DECIHOUR_8_VAL 0x2B12 +#define BLE_MESH_UUID_TIME_EXPONENTIAL_8_VAL 0x2B13 +#define BLE_MESH_UUID_TIME_HOUR_24_VAL 0x2B14 +#define BLE_MESH_UUID_TIME_MILLISECOND_24_VAL 0x2B15 +#define BLE_MESH_UUID_TIME_SECOND_16_VAL 0x2B16 +#define BLE_MESH_UUID_TIME_SECOND_8_VAL 0x2B17 +#define BLE_MESH_UUID_VOLTAGE_VAL 0x2B18 +#define BLE_MESH_UUID_VOLTAGE_SPECIFICATION_VAL 0x2B19 +#define BLE_MESH_UUID_VOLTAGE_STATISTICS_VAL 0x2B1A +#define BLE_MESH_UUID_VOLUME_FLOW_VAL 0x2B1B + +/** + * @brief BLE Mesh Device Property referenced Characteristic Type Definitions + */ + +/* Unit is in degrees Celsius with a resolution of 0.01 degrees Celsius. */ +typedef s16_t bt_mesh_temperature_t; + +typedef u16_t bt_mesh_gap_appearance_t; + +/* Mesh Characteristics Type Definitions */ + +/* This characteristic represents an electric current. + * Note: Unit is ampere with a resolution of 0.01. + * Minimum value: 0, maximum value: 655.34; + * A value of 0xFFFF represents 'value is not known'. + */ +typedef u16_t bt_mesh_electric_current_t; + +/* The Time Exponential 8 characteristic is used to represent a measure of period of + * time in seconds. + * Note: The time duration is given by the value 1.1^(N-64) in seconds, with N being + * the raw 8-bit value; + * Minimum value: 0.0, maximum value: 73216705; + * A raw value of 0x00 represents 0 seconds, and a raw value of 0xFF represents + * the total life of the device. + */ +typedef u8_t bt_mesh_time_exponential_8_t; + +/* The Voltage characteristic is used to represent a measure of positive electric + * potential difference in units of volts. + * Note: Unit is volt with a resolution of 1/64V; + * Minimum value: 0.0, maximum value: 1022.0; + * A value of 0xFFFF represents 'value is not known'. The minimum representable + * value represents the minimum value or lower, the maximum representable value + * represents the maximum value or higher. + */ +typedef u16_t bt_mesh_voltage_t; + +/* This characteristic aggregates the Electric Current characteristic and instance of + * the Time Exponential 8 characteristic. + */ +typedef struct __packed average_current { + bt_mesh_electric_current_t electric_current; + bt_mesh_time_exponential_8_t sensing_duration; +} bt_mesh_average_current_t; + +/* This characteristic aggregates the Voltage characteristic and instance of the Time + * Exponential 8 characateristic. + */ +typedef struct __packed average_voltage { + bt_mesh_voltage_t voltage; + bt_mesh_time_exponential_8_t sensing_duration; +} bt_mesh_average_voltage_t; + +/* The Boolean characteristic defines the predefined Boolean values as an enumeration. + * Key | Value + * 0 | False + * 1 | True + * 2 to 255 | Prohibited + */ +typedef u8_t bt_mesh_boolean_t; + +/* The Chromatic Distance From Planckian characteristic represents a distance of a + * chromaticity coordinate from the Planckian locus in the (u',2/3 v') diagram as + * defined by ANSI standard C78.377-2008. The distance is positive if the chromaticity + * coordinate is located above the Planckian locus (i.e. has as higher y value than the + * Planckian), and negative if it is located below. The distance is only valid within + * the range from -0.05 to 0.05. + * Note: Unit is unitless with a resolution of 0.00001; + * Minimum value: -0.05, maximum value: 0.05; + * A value of 0xFFFF represents 'value is not known'; + * A value of 0xFFFE represents 'value is not valid'. + */ +typedef s16_t bt_mesh_chromatic_distance_from_planckian_t; + +/* This characteristic represents a chromaticity coordinate in a color diagram such as + * the CIE1931 diagram. It can represent an x or y coordinate. + * Note: Unit is unitless with a resolution of 1/65535; + * Minimum value: 0, maximum value: 1.0. + */ +typedef u16_t bt_mesh_chromaticity_coordinate_t; + +/* This characteristic represents a chromaticity coordinate as a tuple with an x and + * y coordinate. + */ +typedef struct __packed chromaticity_coordinates { + bt_mesh_chromaticity_coordinate_t chromaticity_x_coordinate; + bt_mesh_chromaticity_coordinate_t chromaticity_y_coordinate; +} bt_mesh_chromaticity_coordinates_t; + +/* The Correlated Color Temperature characteristic is used to represent correlated color + * temperature in a range from 800 to 65534 Kelvin with a resolution of 1 Kelvin. + * Note: Unit is Kelvin with a resolution of 1; + * Minimum value: 800, maximum value: 65534; + * A value of 0xFFFF represents 'value is not known'. + */ +typedef u16_t bt_mesh_correlated_color_temperature_t; + +/* The Chromaticity In CCT And Duv Values characteristic is a composite characteristic + * consisting of the Correlated Color Temperature characteristic and the Chromatic + * Distance From Planckian characteristic. + */ +typedef struct __packed chromaticity_in_cct_and_duv_values { + bt_mesh_correlated_color_temperature_t correlated_color_temperature; + bt_mesh_chromatic_distance_from_planckian_t chromaticity_distance_from_planckian; +} bt_mesh_chromaticity_in_cct_and_duv_values_t; + +/* The Chromaticity Tolerance characteristic is a tolerance of a tuple of chromaticity + * values represented as a value of a radius of a circle in the CIE 1976 (u',v') diagram; + * value corresponding to the 3-sigma values of the expected chromaticity deviations. + * Note: Unit is unitless with a resolution of 0.0001; + * Minimum value: 0, maximum value: 0.0255. + */ +typedef u8_t bt_mesh_chromaticity_tolerance_t; + +/* The CIE 13.3-1995 Color Rendering Index characteristic is a color rendition index value + * for a color patch as calculated in accordance with the CIE 13.3-1995 standard. + * Note: Unit is unitless with a resolution of 1; + * Minimum value: -128, maximum value: 100. + */ +typedef s8_t bt_mesh_cie_13_3_1995_color_rendering_index_t; + +/* The Coefficient characteristic is used to represent a general coefficient value. */ +typedef float bt_mesh_coefficient_t; + +/* The Count 16 characteristic is used to represent a general count value. + * Note: Unit is unitless with a resolution of 1; + * Minimum value: 0, maximum value 65534; + * A value of 0xFFFF represents 'value is not known'. + */ +typedef u16_t bt_mesh_count_16_t; + +/* The Count 24 characteristic is used to represent a general count value. + * Note: Unit is unitless with a resolution of 1; + * Minimum value: 0, maximum value 16777214; + * A value of 0xFFFFFF represents 'value is not known'. + */ +typedef u8_t bt_mesh_count_24_t[3]; + +/* This characteristic represents a country or dependent areas in accordance with + * the ISO 3166-1 Numeric standard. + * Note: Unit is unitless with a resolution of 1; + * Minimum value: 0, maximum value: 4095; + * A value of 0xFFFF represents 'value is not known'. + */ +typedef u16_t bt_mesh_country_code_t; + +/* Date as days elapsed since the Epoch (Jan 1, 1970) in the Coordinated Universal + * Time (UTC) time zone. + * Note: Unit is a day with a resolution of 1; + * Minimum value: 1, maximum value: 16777214; + * A value of 0x000000 represents 'value is not known'. + */ +typedef u8_t bt_mesh_date_utc_t[3]; + +/* This characteristic aggregates two instances of the Electric Current characteristic + * to represent a range of Electric Current values. + */ +typedef struct __packed electric_current_range { + bt_mesh_electric_current_t minimum_electric_current_value; + bt_mesh_electric_current_t maximum_electric_current_value; +} bt_mesh_electric_current_range_t; + +/* This characteristic aggregates three instances of the Electric Current characteristic + * to represent a specification of electric current values. + */ +typedef struct __packed electric_current_specification { + bt_mesh_electric_current_t minimum_electric_current_value; + bt_mesh_electric_current_t typical_electric_current_value; + bt_mesh_electric_current_t maximum_electric_current_value; +} bt_mesh_electric_current_specification_t; + +/* This characteristic aggregates four instances of the Electric Current characteristic + * with a Sensing Duration to represent a set of statistical electric current values. + */ +typedef struct __packed electric_current_statistics { + bt_mesh_electric_current_t average_electric_current_value; + bt_mesh_electric_current_t standard_electric_current_value; + bt_mesh_electric_current_t minimum_electric_current_value; + bt_mesh_electric_current_t maximum_electric_current_value; + bt_mesh_time_exponential_8_t sensing_duration; +} bt_mesh_electric_current_statistics_t; + +/* The Energy characteristic is used to represent a measure of energy in units of + * kilowatt hours. + * Note: Unit is kilowatt-hour with a resolution of 1; + * Minimum value: 0, maximum value: 16777214; + * A value of 0xFFFFFF represents ‘value is not known’. + */ +typedef u8_t bt_mesh_energy_t[3]; + +/* The Time Decihour 8 characteristic is used to represent a period of time in + * tenths of an hour. + * Note: Unit is hour with a resolution of 0.1; + * Minimum value: 0.0, maximum value: 24.0; + * A value of 0xFF represents 'value is not known'. All other values are Prohibited. + */ +typedef u8_t bt_mesh_time_decihour_8_t; + +/* This characteristic aggregates the Energy characteristic, and two instances of + * the Time Decihour 8 characteristic, to represent energy use in a period of day. + */ +typedef struct __packed energy_in_a_period_of_day { + bt_mesh_energy_t energy_value; + bt_mesh_time_decihour_8_t start_time; + bt_mesh_time_decihour_8_t end_time; +} bt_mesh_energy_in_a_period_of_day_t; + +/* The Time Second 16 characteristic is used to represent a period of time with a + * unit of 1 second. + * Note: Unit is second with a resolution of 1; + * Minimum value: 0, maximum value: 65534; + * A value of 0xFFFF represents 'value is not known'. + */ +typedef u16_t bt_mesh_time_second_16_t; + +/* This characteristic aggregates the Count 16 characteristic, two instances of the + * Time Decihour 8 characteristic and an instance of the Sensing Duration characteristic, + * to represent statistical values of events. + */ +typedef struct __packed event_statistics { + bt_mesh_count_16_t number_of_events; + bt_mesh_time_second_16_t average_event_duration; + bt_mesh_time_exponential_8_t time_elapsed_since_last_event; + bt_mesh_time_exponential_8_t sensing_duration; +} bt_mesh_event_statistics_t; + +/* The Fixed String 16 characteristic represents a 16-octet UTF-8 string. */ +typedef char bt_mesh_fixed_string_16_t[16]; + +/* The Fixed String 24 characteristic represents a 24-octet UTF-8 string. */ +typedef char bt_mesh_fixed_string_24_t[24]; + +/* The Fixed String 36 characteristic represents a 36-octet UTF-8 string. */ +typedef char bt_mesh_fixed_string_36_t[36]; + +/* The Fixed String 8 characteristic represents an 8-octet UTF-8 string. */ +typedef char bt_mesh_fixed_string_8_t[8]; + +/* The Generic Level characteristic represents a general level value of a + * setting of a device. + * Note: Unit is unitless with a resolution of 1; + * Minimum value: 0, maximum value: 65535. + */ +typedef u16_t bt_mesh_generic_level_t; + +/* The Global Trade Item Number characteristic represents an identifier as + * issued by GS1 General Specifications, which may consist up to 14 digits, + * and is here represented as a 48-bit unsigned integer. + */ +typedef u8_t bt_mesh_global_trade_item_number_t[6]; + +/* The Illuminance characteristic is used to represent a measure of illuminance + * in units of lux. + * Note: Unit is lux with a resolution of 0.01; + * Minimum value: 0, maximum value: 167772.14; + * A value of 0xFFFFFF represents 'value is not known'. + */ +typedef u8_t bt_mesh_illuminance_t[3]; + +/* The Luminous Efficacy characteristic is used to represent a measure of luminous + * efficacy in units of lumen per watt. + * Note: Unit is lumen per watt with a resolution of 0.1; + * Minimum value: 0, maximum value: 1800; + * A value of 0xFFFF represents 'value is not known'. All other values are Prohibited. + */ +typedef u16_t bt_mesh_luminous_efficacy_t; + +/* The Luminous Energy characteristic is used to represent a measure of luminous + * energy in units of lumen hour. + * Note: Unit is lumen hour with a resolution of 1000; + * Minimum value: 0, maximum value: 16777214000; + * A value of 0xFFFFFF represents 'value is not known'. + */ +typedef u8_t bt_mesh_luminous_energy_t[3]; + +/* The Luminous Exposure characteristic is used to represent a measure of luminous + * exposure in units of lux-hour. + * Note: Unit is lux hour with a resolution of 1000; + * Minimum value: 0, maximum value: 16777214000; + * A value of 0xFFFFFF represents 'value is not known'. + */ +typedef u8_t bt_mesh_luminous_exposure_t[3]; + +/* The Luminous Flux characteristic is used to represent a measure of luminous flux + * in units of lumen. + * Note: Unit is lumen with a resolution of 1; + * Minimum value: 0, maximum value: 65534; + * A value of 0xFFFF represents 'value is not known'. + */ +typedef u16_t bt_mesh_luminous_flux_t; + +/* This characteristic aggregates two instances of the Luminous Flux characteristic + * to represent a luminous flux range. + */ +typedef struct __packed luminous_flux_range { + bt_mesh_luminous_flux_t minimum_luminous_flux; + bt_mesh_luminous_flux_t maximum_luminous_flux; +} bt_mesh_luminous_flux_range_t; + +/* The Luminous Intensity characteristic is used to represent a luminous intensity of + * a beam of light in units of candela. + * Note: Unit is candela with a resolution of 1; + * Minimum value: 0, maximum value: 65534; + * A value of 0xFFFF represents 'value is not known'. + */ +typedef u16_t bt_mesh_luminous_intensity_t; + +/* The Mass Flow characteristic is used to represent a flow of mass. + * Note: Unit is gram/second with a resolution of 1; + * Minimum value: 0, maximum value: 65534; + * A value of 0xFFFF represents 'value is not known'. + */ +typedef u16_t bt_mesh_mass_flow_t; + +/* The Mesh Provisioning Data In characteristic can be written to send a Proxy PDU + * message containing Provisioning PDU to the Provisioning Server. + */ +struct mesh_provisioning_data_in { + +}; + +/* The Mesh Provisioning Data Out characteristic can be notified to send a Proxy PDU + * message containing Provisioning PDU from a Provisioning Server to a Provisioning Client. + */ +struct mesh_provisioning_data_out { + +}; + +/* The Mesh Proxy Data In characteristic is used by the client to send Proxy PDUs to + * the server. + */ +struct mesh_proxy_data_in { + +}; + +/* The Mesh Proxy Data Out characteristic is used by the server to send Proxy PDUs to + * the client. + */ +struct mesh_proxy_data_out { + +}; + +/* The Perceived Lightness characteristic is used to represent the perceived lightness + * of a light. + * Note: Unit is unitless with a resolution of 1; + * Minimum value: 0, maximum value: 65535. + */ +typedef u16_t bt_mesh_perceived_lightness_t; + +/* The Percentage 8 characteristic is used to represent a measure of percentage. + * Note: Unit is a percentage with a resolution of 0.5; + * Minimum value: 0, maximum value: 100; + * A value of 0xFF represents 'value is not known'. All other values are Prohibited. + */ +typedef u8_t bt_mesh_percentage_8_t; + +/* The Power characteristic is used to represent a measure of power in units of watts. + * Note: Unit is watt with a resolution of 0.1; + * Minimum value: 0, maximum value: 1677721.4; + * A value of 0xFFFFFF represents 'value is not known'. + */ +typedef u8_t bt_mesh_power_t[3]; + +/* This characteristic aggregates three instances of the Power characteristic to + * represent a specification of Power values. + */ +typedef struct __packed power_specification { + bt_mesh_power_t minimum_power_value; + bt_mesh_power_t typical_power_value; + bt_mesh_power_t maximum_power_value; +} bt_mesh_power_specification_t; + +/* This characteristic aggregates the Percentage 8 characteristic and two instances of + * the Electric Current characteristic to represent a relative value in an electric + * current range. + */ +typedef struct __packed relative_runtime_in_a_current_range { + bt_mesh_percentage_8_t relative_runtime_value; + bt_mesh_electric_current_t minimum_current; + bt_mesh_electric_current_t maximum_current; +} bt_mesh_relative_runtime_in_a_current_range_t; + +/* This characteristic aggregates the Percentage 8 characteristic and two instances of + * the Generic Level characteristic to represent a runtime in a generic level range. + */ +typedef struct __packed relative_runtime_in_a_generic_level_range { + bt_mesh_percentage_8_t relative_value; + bt_mesh_generic_level_t minimum_generic_level; + bt_mesh_generic_level_t maximum_generic_level; +} bt_mesh_relative_runtime_in_a_generic_level_range_t; + +/* This characteristic aggregates the Percentage 8 characteristic, and two instances of + * the Time Decihour 8 characteristic. + */ +typedef struct __packed relative_value_in_a_period_of_day { + bt_mesh_percentage_8_t relative_value; + bt_mesh_time_decihour_8_t start_time; + bt_mesh_time_decihour_8_t end_time; +} bt_mesh_relative_value_in_a_period_of_day_t; + +/* This characteristic aggregates the Percentage 8 characteristic, and two instances of + * the Temperature characteristic. + */ +typedef struct __packed relative_value_in_a_temperature_range { + bt_mesh_percentage_8_t relative_value; + bt_mesh_temperature_t minimum_temperature_value; + bt_mesh_temperature_t maximum_temperature_value; +} bt_mesh_relative_value_in_a_temperature_range_t; + +/* This characteristic aggregates the Percentage 8 characteristic and two instances of + * the Voltage characteristic to represent a relative value in a voltage range. + */ +typedef struct __packed relative_value_in_a_voltage_range { + bt_mesh_percentage_8_t relative_value; + bt_mesh_voltage_t minimum_voltage; + bt_mesh_voltage_t maximum_voltage; +} bt_mesh_relative_value_in_a_voltage_range_t; + +/* This characteristic aggregates the Percentage 8 characteristic and two instances of + * the Illuminance characteristic to represent a relative value in a illuminance range. + */ +typedef struct __packed relative_value_in_an_illuminance_range { + bt_mesh_percentage_8_t relative_value; + bt_mesh_illuminance_t minimum_illuminance; + bt_mesh_illuminance_t maximum_illuminance; +} bt_mesh_relative_value_in_an_illuminance_range_t; + +/* The Temperature 8 characteristic is used to represent a measure of temperature with + * a unit of 0.5 degree Celsius. + * Note: Unit is degree Celsius with a resolution of 0.5; + * Minimum value: -64.0, maximum value: 63.5; + * A value of 0xFF represents 'value is not known'. + */ +typedef s8_t bt_mesh_temperature_8_t; + +/* This characteristic aggregates the Temperature 8 characteristic, and two instances + * of the Time Decihour 8 characteristic, to represent a temperature value in a period + * of day. + */ +typedef struct __packed temperature_8_in_a_period_of_day { + bt_mesh_temperature_8_t temperature; + bt_mesh_time_decihour_8_t start_time; + bt_mesh_time_decihour_8_t end_time; +} bt_mesh_temperature_8_in_a_period_of_day_t; + +/* This characteristic aggregates four instances of the Temperature 8 characteristic, + * and one instance of the Time Exponential 8 characteristic. + */ +typedef struct __packed temperature_8_statistics { + bt_mesh_temperature_8_t average; + bt_mesh_temperature_8_t standard_deviation_value; + bt_mesh_temperature_8_t minimum_value; + bt_mesh_temperature_8_t maximum_value; + bt_mesh_time_exponential_8_t sensing_duration; +} bt_mesh_temperature_8_statistics_t; + +/* This characteristic aggregates two instances of the Temperature characteristic to + * represent a temperature range. + */ +typedef struct __packed temperature_range { + bt_mesh_temperature_t minimum_temperature; + bt_mesh_temperature_t maximum_temperature; +} bt_mesh_temperature_range_t; + +/* This characteristic aggregates four instances of the Temperature characteristic, + * and one instance of the Time Exponential 8 characteristic. + */ +typedef struct __packed temperature_statistics { + bt_mesh_temperature_t average_temperature; + bt_mesh_temperature_t standard_deviation_temperature; + bt_mesh_temperature_t minimum_temperature; + bt_mesh_temperature_t maximum_temperature; + bt_mesh_time_exponential_8_t sensing_duration; +} bt_mesh_temperature_statistics_t; + +/* The Time Hour 24 characteristic is used to represent a period of time in hours. + * Note: Unit is hour with a resolution of 1; + * Minimum value: 0, maximum value: 16777214; + * A value of 0xFFFFFF represents 'value is not known'. + */ +typedef u8_t bt_mesh_time_hour_24_t[3]; + +/* The Time Millisecond 24 characteristic is used to represent a period of time with + * a resolution of 1 millisecond. + * Note: Unit is second with a resolution of 0.001; + * Minimum value: 0, maximum value: 16777.214; + * A value of 0xFFFFFF represents 'value is not known'. + */ +typedef u8_t bt_mesh_time_millisecond_24_t[3]; + +/* The Time Second 8 characteristic is used to represent a period of time with a unit + * of 1 second. + * Note: Unit is second with a resolution of 1; + * Minimum value: 0, maximum value: 254; + * A value of 0xFF represents 'value is not known'. + */ +typedef u8_t bt_mesh_time_second_8_t; + +/* This characteristic aggregates three instances of the Voltage characteristic to + * represent a specification of voltage values. + */ +typedef struct __packed voltage_specification { + bt_mesh_voltage_t minimum_voltage_value; + bt_mesh_voltage_t typical_voltage_value; + bt_mesh_voltage_t maximum_voltage_value; +} bt_mesh_voltage_specification_t; + +/* This characteristic aggregates four instances of the Voltage characteristic and an + * instance of the Time Exponential 8 characteristic to represent a set of statistical + * voltage values over a period of time. + */ +typedef struct __packed voltage_statistics { + bt_mesh_voltage_t average_voltage_value; + bt_mesh_voltage_t standard_deviation_voltage_value; + bt_mesh_voltage_t minimum_voltage_value; + bt_mesh_voltage_t maximum_voltage_value; + bt_mesh_time_exponential_8_t sensing_duration; +} bt_mesh_voltage_statistics_t; + +/* The Volume Flow characteristic is used to represent a flow of a general volume such + * as a volume of material or gas. + * Note: Unit is liter/second with a resolution of 0.001 (1 milliliter); + * Minimum value: 0, maximum value: 65534; + * A value of 0xFFFF represents 'value is not known'. + */ +typedef u16_t bt_mesh_volume_flow_t; + +/* Mesh Device Property related function */ + +u8_t bt_mesh_get_dev_prop_len(u16_t prop_id); + +#endif /* _DEVICE_PROPERTY_H_ */ \ No newline at end of file diff --git a/components/bt/esp_ble_mesh/mesh_models/server/include/generic_server.h b/components/bt/esp_ble_mesh/mesh_models/server/include/generic_server.h new file mode 100644 index 0000000000..4ab53eddab --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/include/generic_server.h @@ -0,0 +1,389 @@ +/* Bluetooth: Mesh Generic OnOff, Generic Level, Lighting & Vendor Models + * + * Copyright (c) 2018 Vikrant More + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _GENERIC_SERVER_H_ +#define _GENERIC_SERVER_H_ + +#include "server_common.h" + +struct bt_mesh_gen_onoff_state { + u8_t onoff; + u8_t target_onoff; +}; + +struct bt_mesh_gen_onoff_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_onoff_state state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; +}; + +struct bt_mesh_gen_level_state { + s16_t level; + s16_t target_level; + + s16_t last_level; + s32_t last_delta; + + bool move_start; + bool positive; +}; + +struct bt_mesh_gen_level_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_level_state state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; + s32_t tt_delta_level; +}; + +struct bt_mesh_gen_def_trans_time_state { + u8_t trans_time; +}; + +struct bt_mesh_gen_def_trans_time_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_def_trans_time_state state; +}; + +struct bt_mesh_gen_onpowerup_state { + u8_t onpowerup; +}; + +struct bt_mesh_gen_power_onoff_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_onpowerup_state *state; +}; + +struct bt_mesh_gen_power_onoff_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_onpowerup_state *state; +}; + +struct bt_mesh_gen_power_level_state { + u16_t power_actual; + u16_t target_power_actual; + + u16_t power_last; + u16_t power_default; + + u8_t status_code; + u16_t power_range_min; + u16_t power_range_max; +}; + +struct bt_mesh_gen_power_level_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_power_level_state *state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; + s32_t tt_delta_level; +}; + +struct bt_mesh_gen_power_level_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_power_level_state *state; +}; + +struct bt_mesh_gen_battery_state { + u32_t battery_level : 8, + time_to_discharge : 24; + u32_t time_to_charge : 24, + battery_flags : 8; +}; + +struct bt_mesh_gen_battery_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_battery_state state; +}; + +struct bt_mesh_gen_location_state { + s32_t global_latitude; + s32_t global_longitude; + s16_t global_altitude; + s16_t local_north; + s16_t local_east; + s16_t local_altitude; + u8_t floor_number; + u16_t uncertainty; +}; + +struct bt_mesh_gen_location_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_location_state *state; +}; + +struct bt_mesh_gen_location_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_gen_location_state *state; +}; + +/** + * According to the hierarchy of Generic Property states (Model Spec section 3.1.8), + * the Manufacturer Properties and Admin Properties may contain multiple Property + * states. User Properties just a collection of which can be accessed. + * + * property_count: Number of the properties contained in the table + * properties: Table of the properties + * + * These variables need to be initialized in the application layer, the precise + * number of the properties should be set and memories used to store the property + * values should be allocated. + */ + +enum bt_mesh_gen_user_prop_access { + USER_ACCESS_PROHIBIT, + USER_ACCESS_READ, + USER_ACCESS_WRITE, + USER_ACCESS_READ_WRITE, +}; + +enum bt_mesh_gen_admin_prop_access { + ADMIN_NOT_USER_PROP, + ADMIN_ACCESS_READ, + ADMIN_ACCESS_WRITE, + ADMIN_ACCESS_READ_WRITE, +}; + +enum bt_mesh_gen_manu_prop_access { + MANU_NOT_USER_PROP, + MANU_ACCESS_READ, +}; + +struct bt_mesh_generic_property { + u16_t id; + u8_t user_access; + u8_t admin_access; + u8_t manu_access; + struct net_buf_simple *val; +}; + +struct bt_mesh_gen_user_prop_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + u8_t property_count; + struct bt_mesh_generic_property *properties; +}; + +struct bt_mesh_gen_admin_prop_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + u8_t property_count; + struct bt_mesh_generic_property *properties; +}; + +struct bt_mesh_gen_manu_prop_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + u8_t property_count; + struct bt_mesh_generic_property *properties; +}; + +struct bt_mesh_gen_client_prop_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + u8_t id_count; + u16_t *property_ids; +}; + +typedef union { + struct { + u8_t onoff; + } gen_onoff_set; + struct { + s16_t level; + } gen_level_set; + struct { + s16_t level; + } gen_delta_set; + struct { + s16_t level; + } gen_move_set; + struct { + u8_t trans_time; + } gen_def_trans_time_set; + struct { + u8_t onpowerup; + } gen_onpowerup_set; + struct { + u16_t power; + } gen_power_level_set; + struct { + u16_t power; + } gen_power_default_set; + struct { + u16_t range_min; + u16_t range_max; + } gen_power_range_set; + struct { + s32_t latitude; + s32_t longitude; + s16_t altitude; + } gen_loc_global_set; + struct { + s16_t north; + s16_t east; + s16_t altitude; + u8_t floor_number; + u16_t uncertainty; + } gen_loc_local_set; + struct { + u16_t id; + struct net_buf_simple *value; + } gen_user_prop_set; + struct { + u16_t id; + u8_t access; + struct net_buf_simple *value; + } gen_admin_prop_set; + struct { + u16_t id; + u8_t access; + } gen_manu_prop_set; +} bt_mesh_gen_server_state_change_t; + +typedef union { + struct { + u16_t id; + } user_property_get; + struct { + u16_t id; + } admin_property_get; + struct { + u16_t id; + } manu_property_get; + struct { + u16_t id; + } client_properties_get; +} bt_mesh_gen_server_recv_get_msg_t; + +typedef union { + struct { + bool op_en; + u8_t onoff; + u8_t tid; + u8_t trans_time; + u8_t delay; + } onoff_set; + struct { + bool op_en; + s16_t level; + u8_t tid; + u8_t trans_time; + u8_t delay; + } level_set; + struct { + bool op_en; + s32_t delta_level; + u8_t tid; + u8_t trans_time; + u8_t delay; + } delta_set; + struct { + bool op_en; + s16_t delta_level; + u8_t tid; + u8_t trans_time; + u8_t delay; + } move_set; + struct { + u8_t trans_time; + } def_trans_time_set; + struct { + u8_t onpowerup; + } onpowerup_set; + struct { + bool op_en; + u16_t power; + u8_t tid; + u8_t trans_time; + u8_t delay; + } power_level_set; + struct { + u16_t power; + } power_default_set; + struct { + u16_t range_min; + u16_t range_max; + } power_range_set; + struct { + s32_t latitude; + s32_t longitude; + s16_t altitude; + } loc_global_set; + struct { + s16_t north; + s16_t east; + s16_t altitude; + u8_t floor_number; + u16_t uncertainty; + } loc_local_set; + struct { + u16_t id; + struct net_buf_simple *value; + } user_property_set; + struct { + u16_t id; + u8_t access; + struct net_buf_simple *value; + } admin_property_set; + struct { + u16_t id; + u8_t access; + } manu_property_set; +} bt_mesh_gen_server_recv_set_msg_t; + +void bt_mesh_generic_server_lock(void); +void bt_mesh_generic_server_unlock(void); + +void gen_onoff_publish(struct bt_mesh_model *model); +void gen_level_publish(struct bt_mesh_model *model); +void gen_onpowerup_publish(struct bt_mesh_model *model); +void gen_power_level_publish(struct bt_mesh_model *model, u16_t opcode); + +int bt_mesh_gen_onoff_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_level_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_def_trans_time_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_power_onoff_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_power_onoff_setup_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_power_level_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_power_level_setup_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_battery_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_location_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_location_setup_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_user_prop_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_admin_prop_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_manu_prop_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_client_prop_srv_init(struct bt_mesh_model *model, bool primary); + +int bt_mesh_gen_onoff_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_level_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_def_trans_time_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_power_onoff_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_power_onoff_setup_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_power_level_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_power_level_setup_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_battery_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_location_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_location_setup_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_user_prop_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_admin_prop_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_manu_prop_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_gen_client_prop_srv_deinit(struct bt_mesh_model *model, bool primary); + +#endif /* _GENERIC_SERVER_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/server/include/lighting_server.h b/components/bt/esp_ble_mesh/mesh_models/server/include/lighting_server.h new file mode 100644 index 0000000000..dd39ce0068 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/include/lighting_server.h @@ -0,0 +1,530 @@ +/* Bluetooth: Mesh Generic OnOff, Generic Level, Lighting & Vendor Models + * + * Copyright (c) 2018 Vikrant More + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _LIGHTING_SERVER_H_ +#define _LIGHTING_SERVER_H_ + +#include "server_common.h" + +struct bt_mesh_light_lightness_state { + u16_t lightness_linear; + u16_t target_lightness_linear; + + u16_t lightness_actual; + u16_t target_lightness_actual; + + u16_t lightness_last; + u16_t lightness_default; + + u8_t status_code; + u16_t lightness_range_min; + u16_t lightness_range_max; +}; + +struct bt_mesh_light_lightness_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_lightness_state *state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition actual_transition; + struct bt_mesh_state_transition linear_transition; + s32_t tt_delta_lightness_actual; + s32_t tt_delta_lightness_linear; +}; + +struct bt_mesh_light_lightness_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_lightness_state *state; +}; + +struct bt_mesh_light_ctl_state { + u16_t lightness; + u16_t target_lightness; + + u16_t temperature; + u16_t target_temperature; + + s16_t delta_uv; + s16_t target_delta_uv; + + u8_t status_code; + u16_t temperature_range_min; + u16_t temperature_range_max; + + u16_t lightness_default; + u16_t temperature_default; + s16_t delta_uv_default; +}; + +struct bt_mesh_light_ctl_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_ctl_state *state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; + s32_t tt_delta_lightness; + s32_t tt_delta_temperature; + s32_t tt_delta_delta_uv; +}; + +struct bt_mesh_light_ctl_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_ctl_state *state; +}; + +struct bt_mesh_light_ctl_temp_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_ctl_state *state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; + s32_t tt_delta_temperature; + s32_t tt_delta_delta_uv; +}; + +struct bt_mesh_light_hsl_state { + u16_t lightness; + u16_t target_lightness; + + u16_t hue; + u16_t target_hue; + + u16_t saturation; + u16_t target_saturation; + + u16_t lightness_default; + u16_t hue_default; + u16_t saturation_default; + + u8_t status_code; + u16_t hue_range_min; + u16_t hue_range_max; + u16_t saturation_range_min; + u16_t saturation_range_max; +}; + +struct bt_mesh_light_hsl_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_hsl_state *state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; + s32_t tt_delta_lightness; + s32_t tt_delta_hue; + s32_t tt_delta_saturation; +}; + +struct bt_mesh_light_hsl_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_hsl_state *state; +}; + +struct bt_mesh_light_hsl_hue_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_hsl_state *state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; + s32_t tt_delta_hue; +}; + +struct bt_mesh_light_hsl_sat_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_hsl_state *state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; + s32_t tt_delta_saturation; +}; + +struct bt_mesh_light_xyl_state { + u16_t lightness; + u16_t target_lightness; + + u16_t x; + u16_t target_x; + + u16_t y; + u16_t target_y; + + u16_t lightness_default; + u16_t x_default; + u16_t y_default; + + u8_t status_code; + u16_t x_range_min; + u16_t x_range_max; + u16_t y_range_min; + u16_t y_range_max; +}; + +struct bt_mesh_light_xyl_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_xyl_state *state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; + s32_t tt_delta_lightness; + s32_t tt_delta_x; + s32_t tt_delta_y; +}; + +struct bt_mesh_light_xyl_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_xyl_state *state; +}; + +struct bt_mesh_light_lc_state { + u32_t mode : 1, /* default 0 */ + occupancy_mode : 1, /* default 1 */ + light_onoff : 1, + target_light_onoff : 1, + occupancy : 1, + ambient_luxlevel : 24; /* 0x000000 ~ 0xFFFFFF */ + + u16_t linear_output; /* 0x0000 ~ 0xFFFF */ +}; + +struct bt_mesh_light_lc_property_state { + u32_t time_occupancy_delay; /* 0x003A */ + u32_t time_fade_on; /* 0x0037 */ + u32_t time_run_on; /* 0x003C */ + u32_t time_fade; /* 0x0036 */ + u32_t time_prolong; /* 0x003B */ + u32_t time_fade_standby_auto; /* 0x0038 */ + u32_t time_fade_standby_manual; /* 0x0039 */ + + u16_t lightness_on; /* 0x002E */ + u16_t lightness_prolong; /* 0x002F */ + u16_t lightness_standby; /* 0x0030 */ + + u16_t ambient_luxlevel_on; /* 0x002B, 0x0000 ~ 0xFFFF */ + u16_t ambient_luxlevel_prolong; /* 0x002C, 0x0000 ~ 0xFFFF */ + u16_t ambient_luxlevel_standby; /* 0x002D, 0x0000 ~ 0xFFFF */ + + float regulator_kiu; /* 0x0033, 0.0 ~ 1000.0, default 250.0 */ + float regulator_kid; /* 0x0032, 0.0 ~ 1000.0, default 25.0 */ + float regulator_kpu; /* 0x0035, 0.0 ~ 1000.0, default 80.0 */ + float regulator_kpd; /* 0x0034, 0.0 ~ 1000.0, default 80.0 */ + s8_t regulator_accuracy; /* 0x0031, 0.0 ~ 100.0, default 2.0 */ + + u32_t set_occupancy_to_1_delay; +}; + +typedef enum { + LC_OFF, + LC_STANDBY, + LC_FADE_ON, + LC_RUN, + LC_FADE, + LC_PROLONG, + LC_FADE_STANDBY_AUTO, + LC_FADE_STANDBY_MANUAL, +} bt_mesh_lc_state; + +struct bt_mesh_light_lc_state_machine { + struct { + u8_t fade_on; + u8_t fade; + u8_t fade_standby_auto; + u8_t fade_standby_manual; + } trans_time; + bt_mesh_lc_state state; + struct k_delayed_work timer; +}; + +struct bt_mesh_light_control { + struct bt_mesh_light_lc_state state; + struct bt_mesh_light_lc_property_state prop_state; + struct bt_mesh_light_lc_state_machine state_machine; +}; + +struct bt_mesh_light_lc_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_control *lc; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; +}; + +struct bt_mesh_light_lc_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_light_control *lc; +}; + +typedef union { + struct { + u16_t lightness; + } lightness_set; + struct { + u16_t lightness; + } lightness_linear_set; + struct { + u16_t lightness; + } lightness_default_set; + struct { + u16_t range_min; + u16_t range_max; + } lightness_range_set; + struct { + u16_t lightness; + u16_t temperature; + s16_t delta_uv; + } ctl_set; + struct { + u16_t temperature; + s16_t delta_uv; + } ctl_temp_set; + struct { + u16_t range_min; + u16_t range_max; + } ctl_temp_range_set; + struct { + u16_t lightness; + u16_t temperature; + s16_t delta_uv; + } ctl_default_set; + struct { + u16_t lightness; + u16_t hue; + u16_t saturation; + } hsl_set; + struct { + u16_t hue; + } hsl_hue_set; + struct { + u16_t saturation; + } hsl_saturation_set; + struct { + u16_t lightness; + u16_t hue; + u16_t saturation; + } hsl_default_set; + struct { + u16_t hue_range_min; + u16_t hue_range_max; + u16_t sat_range_min; + u16_t sat_range_max; + } hsl_range_set; + struct { + u16_t lightness; + u16_t x; + u16_t y; + } xyl_set; + struct { + u16_t lightness; + u16_t x; + u16_t y; + } xyl_default_set; + struct { + u16_t x_range_min; + u16_t x_range_max; + u16_t y_range_min; + u16_t y_range_max; + } xyl_range_set; + struct { + u8_t mode; + } lc_mode_set; + struct { + u8_t mode; + } lc_om_set; + struct { + u8_t onoff; + } lc_light_onoff_set; + struct { + u16_t id; + struct net_buf_simple *value; + } lc_property_set; + struct { + u16_t property_id; + union { + u8_t occupancy; + u32_t set_occupancy_to_1_delay; + u32_t ambient_luxlevel; + } state; + } sensor_status; +} bt_mesh_light_server_state_change_t; + +typedef union { + struct { + u16_t id; + } lc_property_get; +} bt_mesh_light_server_recv_get_msg_t; + +typedef union { + struct { + bool op_en; + u16_t lightness; + u8_t tid; + u8_t trans_time; + u8_t delay; + } lightness_set; + struct { + bool op_en; + u16_t lightness; + u8_t tid; + u8_t trans_time; + u8_t delay; + } lightness_linear_set; + struct { + u16_t lightness; + } lightness_default_set; + struct { + u16_t range_min; + u16_t range_max; + } lightness_range_set; + struct { + bool op_en; + u16_t lightness; + u16_t temperature; + s16_t delta_uv; + u8_t tid; + u8_t trans_time; + u8_t delay; + } ctl_set; + struct { + bool op_en; + u16_t temperature; + s16_t delta_uv; + u8_t tid; + u8_t trans_time; + u8_t delay; + } ctl_temp_set; + struct { + u16_t range_min; + u16_t range_max; + } ctl_temp_range_set; + struct { + u16_t lightness; + u16_t temperature; + s16_t delta_uv; + } ctl_default_set; + struct { + bool op_en; + u16_t lightness; + u16_t hue; + u16_t saturation; + u8_t tid; + u8_t trans_time; + u8_t delay; + } hsl_set; + struct { + bool op_en; + u16_t hue; + u8_t tid; + u8_t trans_time; + u8_t delay; + } hsl_hue_set; + struct { + bool op_en; + u16_t saturation; + u8_t tid; + u8_t trans_time; + u8_t delay; + } hsl_saturation_set; + struct { + u16_t lightness; + u16_t hue; + u16_t saturation; + } hsl_default_set; + struct { + u16_t hue_range_min; + u16_t hue_range_max; + u16_t sat_range_min; + u16_t sat_range_max; + } hsl_range_set; + struct { + bool op_en; + u16_t lightness; + u16_t x; + u16_t y; + u8_t tid; + u8_t trans_time; + u8_t delay; + } xyl_set; + struct { + u16_t lightness; + u16_t x; + u16_t y; + } xyl_default_set; + struct { + u16_t x_range_min; + u16_t x_range_max; + u16_t y_range_min; + u16_t y_range_max; + } xyl_range_set; + struct { + u8_t mode; + } lc_mode_set; + struct { + u8_t mode; + } lc_om_set; + struct { + bool op_en; + u8_t light_onoff; + u8_t tid; + u8_t trans_time; + u8_t delay; + } lc_light_onoff_set; + struct { + u16_t id; + struct net_buf_simple *value; + } lc_property_set; +} bt_mesh_light_server_recv_set_msg_t; + +typedef union { + struct { + struct net_buf_simple *data; + } sensor_status; +} bt_mesh_light_server_recv_status_msg_t; + +void bt_mesh_light_server_lock(void); +void bt_mesh_light_server_unlock(void); + +u8_t *bt_mesh_get_lc_prop_value(struct bt_mesh_model *model, u16_t prop_id); + +void light_lightness_publish(struct bt_mesh_model *model, u16_t opcode); +void light_ctl_publish(struct bt_mesh_model *model, u16_t opcode); +void light_hsl_publish(struct bt_mesh_model *model, u16_t opcode); +void light_xyl_publish(struct bt_mesh_model *model, u16_t opcode); +void light_lc_publish(struct bt_mesh_model *model, u16_t opcode); + +int bt_mesh_light_lightness_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_lightness_setup_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_ctl_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_ctl_setup_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_ctl_temp_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_hsl_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_hsl_setup_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_hsl_hue_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_hsl_sat_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_xyl_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_xyl_setup_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_lc_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_lc_setup_srv_init(struct bt_mesh_model *model, bool primary); + +int bt_mesh_light_lightness_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_lightness_setup_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_ctl_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_ctl_setup_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_ctl_temp_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_hsl_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_hsl_setup_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_hsl_hue_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_hsl_sat_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_xyl_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_xyl_setup_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_lc_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_light_lc_setup_srv_deinit(struct bt_mesh_model *model, bool primary); + +#endif /* _LIGHTING_SERVER_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/server/include/sensor_server.h b/components/bt/esp_ble_mesh/mesh_models/server/include/sensor_server.h new file mode 100644 index 0000000000..79be44bb7a --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/include/sensor_server.h @@ -0,0 +1,252 @@ +// Copyright 2017-2019 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 _SENSOR_SERVER_H_ +#define _SENSOR_SERVER_H_ + +#include "server_common.h" + +/* Sensor Property ID related */ +#define INVALID_SENSOR_PROPERTY_ID 0x0000 + +#define SENSOR_PROPERTY_ID_LEN 0x02 + +/* Sensor Descriptor state related */ +#define SENSOR_DESCRIPTOR_LEN 0x08 + +#define SENSOR_UNSPECIFIED_POS_TOLERANCE 0x000 +#define SENSOR_UNSPECIFIED_NEG_TOLERANCE 0x000 + +#define SENSOR_NOT_APPL_MEASURE_PERIOD 0x00 + +#define SENSOR_NOT_APPL_UPDATE_INTERVAL 0x00 + +/* Sensor Setting state related */ +#define INVALID_SENSOR_SETTING_PROPERTY_ID 0x0000 + +#define SENSOR_SETTING_PROPERTY_ID_LEN 0x02 +#define SENSOR_SETTING_ACCESS_LEN 0x01 + +#define SENSOR_SETTING_ACCESS_READ 0x01 +#define SENSOR_SETTING_ACCESS_READ_WRITE 0x03 + +/* Sensor Cadence state related */ +#define SENSOR_DIVISOR_TRIGGER_TYPE_LEN 0x01 +#define SENSOR_STATUS_MIN_INTERVAL_LEN 0x01 + +#define SENSOR_PERIOD_DIVISOR_MAX_VALUE 15 + +#define SENSOR_STATUS_MIN_INTERVAL_MAX 26 + +#define SENSOR_STATUS_TRIGGER_TYPE_CHAR 0 +#define SENSOR_STATUS_TRIGGER_TYPE_UINT16 1 + +#define SENSOR_STATUS_TRIGGER_UINT16_LEN 0x02 + +/* Sensor Data state related */ +#define SENSOR_DATA_FORMAT_A 0x00 +#define SENSOR_DATA_FORMAT_B 0x01 + +#define SENSOR_DATA_FORMAT_A_MPID_LEN 0x02 +#define SENSOR_DATA_FORMAT_B_MPID_LEN 0x03 + +#define SENSOR_DATA_ZERO_LEN 0x7F + +enum bt_mesh_sensor_sample_func { + UNSPECIFIED, + INSTANTANEOUS, + ARITHMETIC_MEAN, + RMS, + MAXIMUM, + MINIMUM, + ACCUMULATED, + COUNT, +}; + +struct sensor_descriptor { + u32_t positive_tolerance : 12, + negative_tolerance : 12, + sample_function : 8; + u8_t measure_period; + u8_t update_interval; +}; + +struct sensor_setting { + u16_t property_id; + u8_t access; + /* Or use union to include all possible types */ + struct net_buf_simple *raw; +}; + +struct sensor_cadence { + u8_t period_divisor : 7, + trigger_type : 1; + struct net_buf_simple *trigger_delta_down; + struct net_buf_simple *trigger_delta_up; + u8_t min_interval; + struct net_buf_simple *fast_cadence_low; + struct net_buf_simple *fast_cadence_high; +}; + +struct sensor_data { + /** + * Format A: The Length field is a 1-based uint4 value (valid range 0x0–0xF, + * representing range of 1 – 16). + * Format B: The Length field is a 1-based uint7 value (valid range 0x0–0x7F, + * representing range of 1 – 127). The value 0x7F represents a + * length of zero. + */ + u8_t format : 1, + length : 7; + struct net_buf_simple *raw_value; +}; + +struct sensor_series_column { + struct net_buf_simple *raw_value_x; + struct net_buf_simple *column_width; + struct net_buf_simple *raw_value_y; +}; + +struct bt_mesh_sensor_state { + u16_t sensor_property_id; + + /* Constant throughout the lifetime of an element */ + struct sensor_descriptor descriptor; + + /* Multiple Sensor Setting states may be present for each sensor. + * The Sensor Setting Property ID values shall be unique for each + * Sensor Property ID that identifies a sensor within an element. + */ + const u8_t setting_count; + struct sensor_setting *settings; + + /* The Sensor Cadence state may be not supported by sensors based + * on device properties referencing "non-scalar characteristics" + * such as "histograms" or "composite characteristics". + */ + struct sensor_cadence *cadence; + + struct sensor_data sensor_data; + + /* Values measured by sensors may be organized as arrays (and + * represented as series of columns, such as histograms). + * 1. The Sensor Raw Value X field has a size and representation + * defined by the Sensor Property ID and represents the left + * corner of the column on the X axis. + * 2. The Sensor Column Width field has a size and representation + * defined by the Sensor Property ID and represents the width + * of the column on the X axis. + * 3. The Sensor Raw Value Y field has a size and representation + * defined by the Sensor Property ID and represents the height + * of the column on the Y axis. + * Note: Values outside the bins defined by a Sensor Property are + * not included. For example, if the histogram is defined as 3 bins + * representing “lamp operating hours in a given temperature range” + * and the bins are [40,60), [60, 80), and [80,100], then any hours + * outside that [40, 100] range would not be included. + */ + struct sensor_series_column series_column; +}; + +/* 1. Multiple instances of the Sensor states may be present within the + * same model, provided that each instance has a unique value of the + * Sensor Property ID to allow the instances to be differentiated. + * 2. Note: The number of sensors within a multisensor is limited by the + * size of the message payload for the Sensor Descriptor Status message. + * A single Sensor Descriptor may be sent using a single Unsegmented + * Access message. Using Segmentation and Reassembly (SAR), up to 38 + * Sensor Descriptor states may be sent. + */ + +struct bt_mesh_sensor_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + const u8_t state_count; + struct bt_mesh_sensor_state *states; +}; + +struct bt_mesh_sensor_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + const u8_t state_count; + struct bt_mesh_sensor_state *states; +}; + +typedef union { + struct { + u16_t id; + u8_t period_divisor : 7, + trigger_type : 1; + struct net_buf_simple *trigger_delta_down; + struct net_buf_simple *trigger_delta_up; + u8_t min_interval; + struct net_buf_simple *fast_cadence_low; + struct net_buf_simple *fast_cadence_high; + } sensor_cadence_set; + struct { + u16_t id; + u16_t setting_id; + struct net_buf_simple *value; + } sensor_setting_set; +} bt_mesh_sensor_server_state_change_t; + +typedef union { + struct { + bool op_en; + u16_t id; + } sensor_descriptor_get; + struct { + u16_t id; + } sensor_cadence_get; + struct { + u16_t id; + } sensor_settings_get; + struct { + u16_t id; + u16_t setting_id; + } sensor_setting_get; + struct { + bool op_en; + u16_t id; + } sensor_get; + struct { + u16_t id; + struct net_buf_simple *raw_x; + } sensor_column_get; + struct { + bool op_en; + u16_t id; + struct net_buf_simple *raw; + } sensor_series_get; +} bt_mesh_sensor_server_recv_get_msg_t; + +typedef union { + struct { + u16_t id; + struct net_buf_simple *cadence; + } sensor_cadence_set; + struct { + u16_t id; + u16_t setting_id; + struct net_buf_simple *raw; + } sensor_setting_set; +} bt_mesh_sensor_server_recv_set_msg_t; + +int bt_mesh_sensor_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_sensor_setup_srv_init(struct bt_mesh_model *model, bool primary); + +int bt_mesh_sensor_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_sensor_setup_srv_deinit(struct bt_mesh_model *model, bool primary); + +#endif /* _SENSOR_SERVER_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/server/include/server_common.h b/components/bt/esp_ble_mesh/mesh_models/server/include/server_common.h new file mode 100644 index 0000000000..5647c0ea5b --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/include/server_common.h @@ -0,0 +1,126 @@ +// Copyright 2017-2019 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 _SERVER_COMMON_H_ +#define _SERVER_COMMON_H_ + +#include +#include +#include "mesh_access.h" + +#define BLE_MESH_SERVER_RSP_MAX_LEN 384 + +#define BLE_MESH_SERVER_TRANS_MIC_SIZE 4 + +#define BLE_MESH_CHECK_SEND_STATUS(_func) do { \ + int __status = (_func); \ + if (__status) { \ + BT_ERR("%s, Send failed, err %d", __func__, __status); \ + } \ + } while(0); + +#define BLE_MESH_STATE_OFF 0x00 +#define BLE_MESH_STATE_ON 0x01 +#define BLE_MESH_STATE_RESTORE 0x02 + +/* Following 4 values are as per Mesh Model specification */ +#define BLE_MESH_LIGHTNESS_MIN 0x0001 +#define BLE_MESH_LIGHTNESS_MAX 0xFFFF +#define BLE_MESH_TEMPERATURE_MIN 0x0320 +#define BLE_MESH_TEMPERATURE_MAX 0x4E20 +#define BLE_MESH_TEMPERATURE_UNKNOWN 0xFFFF + +/* Refer 7.2 of Mesh Model Specification */ +#define BLE_MESH_RANGE_UPDATE_SUCCESS 0x00 +#define BLE_MESH_CANNOT_SET_RANGE_MIN 0x01 +#define BLE_MESH_CANNOT_SET_RANGE_MAX 0x02 + +#define BLE_MESH_UNKNOWN_REMAIN_TIME 0x3F +#define BLE_MESH_DEVICE_SPECIFIC_RESOLUTION 10 + +#define BLE_MESH_INVALID_DEVICE_PROPERTY_ID 0x0000 + +enum { + BLE_MESH_TRANS_TIMER_START, /* Proper transition timer has been started */ + BLE_MESH_TRANS_FLAG_MAX, +}; + +struct bt_mesh_state_transition { + bool just_started; + + u8_t trans_time; + u8_t remain_time; + u8_t delay; + u32_t quo_tt; + u32_t counter; + u32_t total_duration; + s64_t start_timestamp; + + BLE_MESH_ATOMIC_DEFINE(flag, BLE_MESH_TRANS_FLAG_MAX); + struct k_delayed_work timer; +}; + +struct bt_mesh_last_msg_info { + u8_t tid; + u16_t src; + u16_t dst; + s64_t timestamp; +}; + +#define BLE_MESH_SERVER_RSP_BY_APP 0 +#define BLE_MESH_SERVER_AUTO_RSP 1 + +struct bt_mesh_server_rsp_ctrl { + /** + * @brief BLE Mesh Server Response Option + * 1. If get_auto_rsp is set to BLE_MESH_SERVER_RSP_BY_APP, then the response + * of Client Get messages need to be replied by the application; + * 2. If get_auto_rsp is set to BLE_MESH_SERVER_AUTO_RSP, then the response + * of Client Get messages will be replied by the server models; + * 3. If set_auto_rsp is set to BLE_MESH_SERVER_RSP_BY_APP, then the response + * of Client Set messages need to be replied by the application; + * 4. If set_auto_rsp is set to BLE_MESH_SERVER_AUTO_RSP, then the response + * of Client Set messages will be replied by the server models; + * 5. If status_auto_rsp is set to BLE_MESH_SERVER_RSP_BY_APP, then the response + * of Server Status messages need to be replied by the application; + * 6. If status_auto_rsp is set to BLE_MESH_SERVER_AUTO_RSP, then the response + * of Server status messages will be replied by the server models; + */ + u8_t get_auto_rsp : 1, /* Response for Client Get messages */ + set_auto_rsp : 1, /* Response for Client Set messages */ + status_auto_rsp : 1; /* Response for Server Status messages */ +}; + +u8_t bt_mesh_get_default_trans_time(struct bt_mesh_model *model); + +int bt_mesh_get_light_lc_trans_time(struct bt_mesh_model *model, u8_t *trans_time); + +int bt_mesh_server_get_optional(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, + u8_t *trans_time, u8_t *delay, + bool *optional); + +void bt_mesh_server_alloc_ctx(struct k_work *work); +void bt_mesh_server_free_ctx(struct k_work *work); + +bool bt_mesh_is_server_recv_last_msg(struct bt_mesh_last_msg_info *last, + u8_t tid, u16_t src, u16_t dst, s64_t *now); + +void bt_mesh_server_update_last_msg(struct bt_mesh_last_msg_info *last, + u8_t tid, u16_t src, u16_t dst, s64_t *now); + +struct net_buf_simple *bt_mesh_server_get_pub_msg(struct bt_mesh_model *model, u16_t msg_len); + +#endif /* _SERVER_COMMON_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/server/include/state_binding.h b/components/bt/esp_ble_mesh/mesh_models/server/include/state_binding.h new file mode 100644 index 0000000000..18ee04ec71 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/include/state_binding.h @@ -0,0 +1,94 @@ +/* Bluetooth: Mesh Generic OnOff, Generic Level, Lighting & Vendor Models + * + * Copyright (c) 2018 Vikrant More + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _STATE_BINDING_H_ +#define _STATE_BINDING_H_ + +#include "mesh_access.h" + +typedef enum { + GENERIC_ONOFF_STATE, + GENERIC_LEVEL_STATE, + GENERIC_ONPOWERUP_STATE, + GENERIC_POWER_ACTUAL_STATE, + LIGHT_LIGHTNESS_ACTUAL_STATE, + LIGHT_LIGHTNESS_LINEAR_STATE, + LIGHT_CTL_LIGHTNESS_STATE, + LIGHT_CTL_TEMP_DELTA_UV_STATE, + LIGHT_HSL_LIGHTNESS_STATE, + LIGHT_HSL_HUE_STATE, + LIGHT_HSL_SATURATION_STATE, + LIGHT_XYL_LIGHTNESS_STATE, + LIGHT_LC_LIGHT_ONOFF_STATE, + BIND_STATE_MAX, +} bt_mesh_server_state_type_t; + +typedef union { + struct { + u8_t onoff; + } gen_onoff; + struct { + s16_t level; + } gen_level; + struct { + u8_t onpowerup; + } gen_onpowerup; + struct { + u16_t power; + } gen_power_actual; + struct { + u16_t lightness; + } light_lightness_actual; + struct { + u16_t lightness; + } light_lightness_linear; + struct { + u16_t lightness; + } light_ctl_lightness; + struct { + u16_t temperature; + s16_t delta_uv; + } light_ctl_temp_delta_uv; + struct { + u16_t lightness; + } light_hsl_lightness; + struct { + u16_t hue; + } light_hsl_hue; + struct { + u16_t saturation; + } light_hsl_saturation; + struct { + u16_t lightness; + } light_xyl_lightness; + struct { + u8_t onoff; + } light_lc_light_onoff; +} bt_mesh_server_state_value_t; + +u16_t bt_mesh_convert_lightness_actual_to_linear(u16_t actual); + +u16_t bt_mesh_convert_lightness_linear_to_actual(u16_t linear); + +s16_t bt_mesh_convert_temperature_to_gen_level(u16_t temp, u16_t min, u16_t max); + +u16_t bt_mesh_covert_gen_level_to_temperature(s16_t level, u16_t min, u16_t max); + +s16_t bt_mesh_convert_hue_to_level(u16_t hue); + +u16_t bt_mesh_convert_level_to_hue(s16_t level); + +s16_t bt_mesh_convert_saturation_to_level(u16_t saturation); + +u16_t bt_mesh_convert_level_to_saturation(s16_t level); + +int bt_mesh_update_binding_state(struct bt_mesh_model *model, + bt_mesh_server_state_type_t type, + bt_mesh_server_state_value_t *value); + +#endif /* _STATE_BINDING_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/server/include/state_transition.h b/components/bt/esp_ble_mesh/mesh_models/server/include/state_transition.h new file mode 100644 index 0000000000..055192d992 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/include/state_transition.h @@ -0,0 +1,92 @@ +/* Bluetooth: Mesh Generic OnOff, Generic Level, Lighting & Vendor Models + * + * Copyright (c) 2018 Vikrant More + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _STATE_TRANSITION_H_ +#define _STATE_TRANSITION_H_ + +#include "server_common.h" +#include "generic_server.h" +#include "sensor_server.h" +#include "lighting_server.h" +#include "time_scene_server.h" + +void bt_mesh_server_calc_remain_time(struct bt_mesh_state_transition *transition); + +/* APIs used to get server model transtion time values */ + +void generic_onoff_tt_values(struct bt_mesh_gen_onoff_srv *srv, + u8_t trans_time, u8_t delay); + +void generic_level_tt_values(struct bt_mesh_gen_level_srv *srv, + u8_t trans_time, u8_t delay); + +void generic_power_level_tt_values(struct bt_mesh_gen_power_level_srv *srv, + u8_t trans_time, u8_t delay); + +void light_lightness_actual_tt_values(struct bt_mesh_light_lightness_srv *srv, + u8_t trans_time, u8_t delay); + +void light_lightness_linear_tt_values(struct bt_mesh_light_lightness_srv *srv, + u8_t trans_time, u8_t delay); + +void light_ctl_tt_values(struct bt_mesh_light_ctl_srv *srv, + u8_t trans_time, u8_t delay); + +void light_ctl_temp_tt_values(struct bt_mesh_light_ctl_temp_srv *srv, + u8_t trans_time, u8_t delay); + +void light_hsl_tt_values(struct bt_mesh_light_hsl_srv *srv, + u8_t trans_time, u8_t delay); + +void light_hsl_hue_tt_values(struct bt_mesh_light_hsl_hue_srv *srv, + u8_t trans_time, u8_t delay); + +void light_hsl_sat_tt_values(struct bt_mesh_light_hsl_sat_srv *srv, + u8_t trans_time, u8_t delay); + +void light_xyl_tt_values(struct bt_mesh_light_xyl_srv *srv, + u8_t trans_time, u8_t delay); + +void light_lc_tt_values(struct bt_mesh_light_lc_srv *srv, + u8_t trans_time, u8_t delay); + +void scene_tt_values(struct bt_mesh_scene_srv *srv, u8_t trans_time, u8_t delay); + +/* Server model transtion timer handlers */ + +void generic_onoff_work_handler(struct k_work *work); + +void generic_level_work_handler(struct k_work *work); + +void generic_power_level_work_handler(struct k_work *work); + +void light_lightness_actual_work_handler(struct k_work *work); + +void light_lightness_linear_work_handler(struct k_work *work); + +void light_ctl_work_handler(struct k_work *work); + +void light_ctl_temp_work_handler(struct k_work *work); + +void light_hsl_work_handler(struct k_work *work); + +void light_hsl_hue_work_handler(struct k_work *work); + +void light_hsl_sat_work_handler(struct k_work *work); + +void light_xyl_work_handler(struct k_work *work); + +void light_lc_work_handler(struct k_work *work); + +void scene_recall_work_handler(struct k_work *work); + +void bt_mesh_server_stop_transition(struct bt_mesh_state_transition *transition); + +void bt_mesh_server_start_transition(struct bt_mesh_state_transition *transition); + +#endif /* _STATE_TRANSITION_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/server/include/time_scene_server.h b/components/bt/esp_ble_mesh/mesh_models/server/include/time_scene_server.h new file mode 100644 index 0000000000..c311da65fd --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/include/time_scene_server.h @@ -0,0 +1,398 @@ +// Copyright 2017-2019 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 _TIME_SCENE_SERVER_H_ +#define _TIME_SCENE_SERVER_H_ + +#include "server_common.h" + +/** + * 1. Mesh defines times based on International Atomic Time (TAI). The base + * representation of times is the number of seconds after 00:00:00 TAI + * on 2000-01-01 (that is, 1999-12-31 T23:59:28 UTC). + * 2. UTC: Coordinated Universal Time. For more information, please refer + * to https://time.is/zh/UTC + * 3. For the algorithm used for the transfer between TAI and UTC, please + * refer to Mesh Model Spec Section 5.1.1 + */ + +#define UNKNOWN_TAI_SECONDS 0x0000000000 +#define UNKNOWN_TAI_ZONE_CHANGE 0x0000000000 +#define UNKNOWN_TAI_DELTA_CHANGE 0x0000000000 +#define TAI_UTC_DELAT_MAX_VALUE 0x7FFF +#define TAI_SECONDS_LEN 0x05 +#define TAI_OF_ZONE_CHANGE_LEN 0x05 +#define TAI_OF_DELTA_CHANGE_LEN 0x05 + +#define INVALID_SCENE_NUMBER 0x0000 +#define SCENE_NUMBER_LEN 0x02 + +#define SCHEDULE_YEAR_ANY_YEAR 0x64 + +#define SCHEDULE_DAY_ANY_DAY 0x00 + +#define SCHEDULE_HOUR_ANY_HOUR 0x18 +#define SCHEDULE_HOUR_ONCE_A_DAY 0x19 + +#define SCHEDULE_SEC_ANY_OF_HOUR 0x3C +#define SCHEDULE_SEC_EVERY_15_MIN 0x3D +#define SCHEDULE_SEC_EVERY_20_MIN 0x3E +#define SCHEDULE_SEC_ONCE_AN_HOUR 0x3F + +#define SCHEDULE_SEC_ANY_OF_MIN 0x3C +#define SCHEDULE_SEC_EVERY_15_SEC 0x3D +#define SCHEDULE_SEC_EVERY_20_SEC 0x3E +#define SCHEDULE_SEC_ONCE_AN_MIN 0x3F + +#define SCHEDULE_ACT_TURN_OFF 0x00 +#define SCHEDULE_ACT_TURN_ON 0x01 +#define SCHEDULE_ACT_SCENE_RECALL 0x02 +#define SCHEDULE_ACT_NO_ACTION 0x0F + +#define SCHEDULE_SCENE_NO_SCENE 0x0000 + +#define SCHEDULE_ENTRY_MAX_INDEX 0x0F + +#define TIME_NONE 0x00 +#define TIME_AUTHORITY 0x01 +#define TIME_RELAY 0x02 +#define TIME_CLINET 0x03 + +#define SCENE_SUCCESS 0x00 +#define SCENE_REG_FULL 0x01 +#define SCENE_NOT_FOUND 0x02 + +/** + * The Time state represents the present TAI time, the current TAI-UTC Delta + * and local time zone offset, and the next change to each of the latter + * (e.g., because of a switch from winter to summer time or an announced leap + * second). It consists of 10 fields with a total size of 183 bits. + */ +struct bt_mesh_time_state { + struct { + u8_t tai_seconds[5]; + u8_t subsecond; + u8_t uncertainty; + u8_t time_zone_offset_curr; + u8_t time_zone_offset_new; + u8_t tai_zone_change[5]; + u16_t time_authority : 1, + tai_utc_delta_curr : 15; + u16_t tai_utc_delta_new : 15; + u8_t tai_delta_change[5]; + } time; + u8_t time_role; +}; + +struct bt_mesh_time_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_time_state *state; +}; + +struct bt_mesh_time_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_time_state *state; +}; + +struct scene_register { + u16_t scene_number; + u8_t scene_type; /* Indicate the type of scene value */ + /** + * Scene value may use a union to represent later, the union contains + * structures of all the model states which can be stored in a scene. + */ + struct net_buf_simple *scene_value; +}; + +/** + * Scenes serve as memory banks for storage of states (e.g., a power level + * or a light level/color). Values of states of an element can be stored + * as a scene and can be recalled later from the scene memory. + * + * A scene is represented by a Scene Number, which is a 16-bit non-zero, + * mesh-wide value. (There can be a maximum of 65535 scenes in a mesh + * network.) The meaning of a scene, as well as the state storage container + * associated with it, are determined by a model. + * + * The Scenes state change may start numerous parallel model transitions. + * In that case, each individual model handles the transition internally. + * + * The scene transition is defined as a group of individual model transitions + * started by a Scene Recall operation. The scene transition is in progress + * when at least one transition from the group of individual model transitions + * is in progress. + */ +struct bt_mesh_scenes_state { + const u16_t scene_count; + struct scene_register *scenes; + + /** + * The Current Scene state is a 16-bit value that contains either the Scene + * Number of the currently active scene or a value of 0x0000 when no scene + * is active. + * + * When a Scene Store operation or a Scene Recall operation completes with + * success, the Current Scene state value shall be to the Scene Number used + * during that operation. + * + * When the Current Scene Number is deleted from a Scene Register state as a + * result of Scene Delete operation, the Current Scene state shall be set to + * 0x0000. + * + * When any of the element's state that is marked as “Stored with Scene” has + * changed not as a result of a Scene Recall operation, the value of the + * Current Scene state shall be set to 0x0000. + * + * When a scene transition is in progress, the value of the Current Scene + * state shall be set to 0x0000. + */ + u16_t current_scene; + + /** + * The Target Scene state is a 16-bit value that contains the target Scene + * Number when a scene transition is in progress. + * + * When the scene transition is in progress and the target Scene Number is + * deleted from a Scene Register state as a result of Scene Delete operation, + * the Target Scene state shall be set to 0x0000. + * + * When the scene transition is in progress and a new Scene Number is stored + * in the Scene Register as a result of Scene Store operation, the Target + * Scene state shall be set to the new Scene Number. + * + * When the scene transition is not in progress, the value of the Target Scene + * state shall be set to 0x0000. + */ + u16_t target_scene; + + /* Indicate the status code for the last operation */ + u8_t status_code; + + /* Indicate if scene transition is in progress */ + bool in_progress; +}; + +struct bt_mesh_scene_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_scenes_state *state; + struct bt_mesh_last_msg_info last; + struct bt_mesh_state_transition transition; +}; + +struct bt_mesh_scene_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_scenes_state *state; +}; + +struct schedule_register { + bool in_use; + u64_t year : 7, + month : 12, + day : 5, + hour : 5, + minute : 6, + second : 6, + day_of_week : 7, + action : 4, + trans_time : 8; + u16_t scene_number; +}; + +struct bt_mesh_scheduler_state { + const u8_t schedule_count; + struct schedule_register *schedules; /* Up to 16 scheduled entries */ + + /** + * A recommended implementation of the Scheduler should calculate the + * value of the TAI Seconds of the next scheduled event and put it in + * a queue of scheduled events sorted by time. + * + * Every second, the first event in the queue is compared with the value + * of the Time state. The first event is executed if it is less than or + * equal to the Time state and then removed from the queue. After + * execution, the Repeat Flag shall be checked, and the next occurrence + * of the scheduled event is calculated and put in the queue. + * + * One second timeout value, and compare the first event in queue with the + * Time state. If it is satisfied, then execute the first event. Also the + * Repeat Flag need to be checked, if it is set then the event needs to + * be put into the end of queue. + * + * sys_slist_t event_queue; + * + * For each event_queue item, it can use the following struct: + * struct schedule_event { + * sys_snode_t node; + * u8_t event_index; + * }; + * + * Also we need a "struct k_delayed_work track_timer" which can be used to + * track the schedule timer and handle proper scheduled events. + */ +}; + +struct bt_mesh_scheduler_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_scheduler_state *state; +}; + +struct bt_mesh_scheduler_setup_srv { + struct bt_mesh_model *model; + struct bt_mesh_server_rsp_ctrl rsp_ctrl; + struct bt_mesh_scheduler_state *state; +}; + +typedef union { + struct { + u8_t tai_seconds[5]; + u8_t subsecond; + u8_t uncertainty; + u16_t time_authority : 1; + u16_t tai_utc_delta_curr : 15; + u8_t time_zone_offset_curr; + } time_set; + struct { + u8_t tai_seconds[5]; + u8_t subsecond; + u8_t uncertainty; + u16_t time_authority : 1; + u16_t tai_utc_delta_curr : 15; + u8_t time_zone_offset_curr; + } time_status; + struct { + u8_t time_zone_offset_new; + u8_t tai_zone_change[5]; + } time_zone_set; + struct { + u16_t tai_utc_delta_new : 15; + u8_t tai_delta_change[5]; + } tai_utc_delta_set; + struct { + u8_t role; + } time_role_set; + struct { + u16_t scene_number; + } scene_store; + struct { + u16_t scene_number; + } scene_recall; + struct { + u16_t scene_number; + } scene_delete; + struct { + u64_t index : 4, + year : 7, + month : 12, + day : 5, + hour : 5, + minute : 6, + second : 6, + day_of_week : 7, + action : 4, + trans_time : 8; + u16_t scene_number; + } scheduler_act_set; +} bt_mesh_time_scene_server_state_change_t; + +typedef union { + struct { + u8_t index; + } scheduler_act_get; +} bt_mesh_time_scene_server_recv_get_msg_t; + +typedef union { + struct { + u8_t tai_seconds[5]; + u8_t subsecond; + u8_t uncertainty; + u16_t time_authority : 1; + u16_t tai_utc_delta : 15; + u8_t time_zone_offset; + } time_set; + struct { + u8_t time_zone_offset_new; + u8_t tai_zone_change[5]; + } time_zone_set; + struct { + u16_t tai_utc_delta_new : 15; + u16_t padding : 1; + u8_t tai_delta_change[5]; + } tai_utc_delta_set; + struct { + u8_t time_role; + } time_role_set; + struct { + u16_t scene_number; + } scene_store; + struct { + bool op_en; + u16_t scene_number; + u8_t tid; + u8_t trans_time; + u8_t delay; + } scene_recall; + struct { + u16_t scene_number; + } scene_delete; + struct { + u64_t index : 4, + year : 7, + month : 12, + day : 5, + hour : 5, + minute : 6, + second : 6, + day_of_week : 7, + action : 4, + trans_time : 8; + u16_t scene_number; + } scheduler_act_set; +} bt_mesh_time_scene_server_recv_set_msg_t; + +typedef union { + struct { + u8_t tai_seconds[5]; + u8_t subsecond; + u8_t uncertainty; + u16_t time_authority : 1; + u16_t tai_utc_delta : 15; + u8_t time_zone_offset; + } time_status; +} bt_mesh_time_scene_server_recv_status_msg_t; + +void bt_mesh_time_scene_server_lock(void); +void bt_mesh_time_scene_server_unlock(void); + +void scene_publish(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, u16_t opcode); + +int bt_mesh_time_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_time_setup_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_scene_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_scene_setup_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_scheduler_srv_init(struct bt_mesh_model *model, bool primary); +int bt_mesh_scheduler_setup_srv_init(struct bt_mesh_model *model, bool primary); + +int bt_mesh_time_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_time_setup_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_scene_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_scene_setup_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_scheduler_srv_deinit(struct bt_mesh_model *model, bool primary); +int bt_mesh_scheduler_setup_srv_deinit(struct bt_mesh_model *model, bool primary); + +#endif /* _TIME_SCENE_SERVER_H_ */ diff --git a/components/bt/esp_ble_mesh/mesh_models/server/lighting_server.c b/components/bt/esp_ble_mesh/mesh_models/server/lighting_server.c new file mode 100644 index 0000000000..a63de218ca --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/lighting_server.c @@ -0,0 +1,3553 @@ +/* Bluetooth: Mesh Lighting Server Models + * + * Copyright (c) 2018 Vikrant More + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "access.h" +#include "transport.h" +#include "model_opcode.h" +#include "state_transition.h" +#include "device_property.h" + +#include "btc_ble_mesh_lighting_model.h" + +static bt_mesh_mutex_t light_server_lock; + +static void bt_mesh_light_server_mutex_new(void) +{ + if (!light_server_lock.mutex) { + bt_mesh_mutex_create(&light_server_lock); + } +} + +static void bt_mesh_light_server_mutex_free(void) +{ + bt_mesh_mutex_free(&light_server_lock); +} + +void bt_mesh_light_server_lock(void) +{ + bt_mesh_mutex_lock(&light_server_lock); +} + +void bt_mesh_light_server_unlock(void) +{ + bt_mesh_mutex_unlock(&light_server_lock); +} + +/* message handlers (Start) */ + +/* Light Lightness Server/Setup Server message handlers */ + +static void send_light_lightness_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish, u16_t opcode) +{ + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 5; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, opcode); + switch (opcode) { + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS: { + struct bt_mesh_light_lightness_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_actual); + if (srv->actual_transition.counter) { + bt_mesh_server_calc_remain_time(&srv->actual_transition); + net_buf_simple_add_le16(msg, srv->state->target_lightness_actual); + net_buf_simple_add_u8(msg, srv->actual_transition.remain_time); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS: { + struct bt_mesh_light_lightness_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_linear); + if (srv->linear_transition.counter) { + bt_mesh_server_calc_remain_time(&srv->linear_transition); + net_buf_simple_add_le16(msg, srv->state->target_lightness_linear); + net_buf_simple_add_u8(msg, srv->linear_transition.remain_time); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS: { + struct bt_mesh_light_lightness_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_last); + break; + } + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS: + if (model->id == BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV) { + struct bt_mesh_light_lightness_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_default); + } else if (model->id == BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV) { + struct bt_mesh_light_lightness_setup_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_default); + } + break; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS: + if (model->id == BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV) { + struct bt_mesh_light_lightness_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->lightness_range_min); + net_buf_simple_add_le16(msg, srv->state->lightness_range_max); + } else if (model->id == BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV) { + struct bt_mesh_light_lightness_setup_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->lightness_range_min); + net_buf_simple_add_le16(msg, srv->state->lightness_range_max); + } + break; + default: + BT_WARN("%s, Unknown Light Lightness status opcode 0x%04x", __func__, opcode); + if (publish == false) { + bt_mesh_free_buf(msg); + } + return; + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void light_lightness_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lightness_srv *srv = model->user_data; + u16_t opcode = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS; + break; + default: + BT_WARN("%s, Unknown Light Lightness Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + send_light_lightness_status(model, ctx, false, opcode); + return; +} + +void light_lightness_publish(struct bt_mesh_model *model, u16_t opcode) +{ + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV: { + struct bt_mesh_light_lightness_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light Lightness Server state", __func__); + return; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV: { + struct bt_mesh_light_lightness_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light Lightness Setup Server state", __func__); + return; + } + break; + } + default: + BT_ERR("%s, Invalid Light Lightness Server Model 0x%04x", __func__, model->id); + return; + } + + send_light_lightness_status(model, NULL, true, opcode); + return; +} + +static void light_lightness_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lightness_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + bool optional = false; + u16_t actual = 0U; + s64_t now = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + actual = net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .lightness_set.op_en = optional, + .lightness_set.lightness = actual, + .lightness_set.tid = tid, + .lightness_set.trans_time = trans_time, + .lightness_set.delay = delay, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET) { + send_light_lightness_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS); + } + send_light_lightness_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_light_server_lock(); + + bt_mesh_server_stop_transition(&srv->actual_transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + if (actual) { + if (srv->state->lightness_range_min && actual < srv->state->lightness_range_min) { + actual = srv->state->lightness_range_min; + } else if (srv->state->lightness_range_max && actual > srv->state->lightness_range_max) { + actual = srv->state->lightness_range_max; + } + } + srv->state->target_lightness_actual = actual; + + /** + * If the target state is equal to the current state, the transition shall not be + * started and is considered complete. + */ + if (srv->state->target_lightness_actual != srv->state->lightness_actual) { + light_lightness_actual_tt_values(srv, trans_time, delay); + } else { + bt_mesh_light_server_state_change_t change = { + .lightness_set.lightness = srv->state->lightness_actual, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET) { + send_light_lightness_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS); + } + send_light_lightness_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS); + + bt_mesh_light_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->actual_transition.timer.work._reserved) { + memcpy(srv->actual_transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->actual_transition.counter == 0U) { + srv->state->lightness_actual = srv->state->target_lightness_actual; + /** + * Whenever the Light Lightness Actual state is changed with a non-transactional + * message or a completed sequence of transactional messages to a non-zero value, + * the value of the Light Lightness Last shall be set to the value of the Light + * Lightness Actual. + */ + if (srv->state->lightness_actual) { + srv->state->lightness_last = srv->state->lightness_actual; + } + } + + srv->actual_transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET) { + send_light_lightness_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS); + } + send_light_lightness_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS); + + bt_mesh_light_server_unlock(); + + bt_mesh_server_start_transition(&srv->actual_transition); + return; +} + +static void light_lightness_linear_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lightness_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + bool optional = false; + u16_t linear = 0U; + s64_t now = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + linear = net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .lightness_linear_set.op_en = optional, + .lightness_linear_set.lightness = linear, + .lightness_linear_set.tid = tid, + .lightness_linear_set.trans_time = trans_time, + .lightness_linear_set.delay = delay, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET) { + send_light_lightness_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS); + } + send_light_lightness_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_light_server_lock(); + + bt_mesh_server_stop_transition(&srv->linear_transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->state->target_lightness_linear = linear; + + /** + * If the target state is equal to the current state, the transition shall not + * be started and is considered complete. + */ + if (srv->state->target_lightness_linear != srv->state->lightness_linear) { + light_lightness_linear_tt_values(srv, trans_time, delay); + } else { + bt_mesh_light_server_state_change_t change = { + .lightness_linear_set.lightness = srv->state->lightness_actual, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET) { + send_light_lightness_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS); + } + send_light_lightness_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS); + + bt_mesh_light_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->linear_transition.timer.work._reserved) { + memcpy(srv->linear_transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->linear_transition.counter == 0U) { + srv->state->lightness_linear = srv->state->target_lightness_linear; + } + + srv->linear_transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET) { + send_light_lightness_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS); + } + send_light_lightness_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS); + + bt_mesh_light_server_unlock(); + + bt_mesh_server_start_transition(&srv->linear_transition); + return; +} + +static void light_lightness_default_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lightness_setup_srv *srv = model->user_data; + u16_t lightness = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + lightness = net_buf_simple_pull_le16(buf); + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .lightness_default_set.lightness = lightness, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (srv->state->lightness_default != lightness) { + srv->state->lightness_default = lightness; + + bt_mesh_light_server_state_change_t change = { + .lightness_default_set.lightness = lightness, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + } + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET) { + send_light_lightness_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS); + } + send_light_lightness_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_STATUS); + + return; +} + +static void light_lightness_range_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lightness_setup_srv *srv = model->user_data; + u16_t range_min = 0U, range_max = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + range_min = net_buf_simple_pull_le16(buf); + range_max = net_buf_simple_pull_le16(buf); + + if (range_min > range_max) { + BT_ERR("%s, Range Min 0x%04x is greater than Range Max 0x%04x", + __func__, range_min, range_max); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .lightness_range_set.range_min = range_min, + .lightness_range_set.range_max = range_max, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + /** + * When a Light Lightness Setup Server receives a Light Lightness Range Set + * message or a Light Lightness Range Set Unacknowledged message with values + * that cannot be accepted, it shall set the status of the operation to a + * value representing the reason why the values cannot be accepted. + * + * TODO: 0x0000 for Light Range Min/Max is prohibited, but BQB test case + * MMDL/SR/LLNS/BI-01-C requires 'SUCCESS' when it sends a set message with + * Light Range Min set to 0x0000. + */ +#if 0 + srv->state->status_code = BLE_MESH_RANGE_UPDATE_SUCCESS; +#else + if (range_min == 0x0000) { + srv->state->status_code = BLE_MESH_CANNOT_SET_RANGE_MIN; + } else if (range_max == 0x0000) { + srv->state->status_code = BLE_MESH_CANNOT_SET_RANGE_MAX; + } else { + srv->state->status_code = BLE_MESH_RANGE_UPDATE_SUCCESS; + } +#endif + + if (range_min && srv->state->lightness_range_min != range_min) { + srv->state->lightness_range_min = range_min; + } + + if (range_max && srv->state->lightness_range_max != range_max) { + srv->state->lightness_range_max = range_max; + } + + bt_mesh_light_server_state_change_t change = { + .lightness_range_set.range_min = srv->state->lightness_range_min, + .lightness_range_set.range_max = srv->state->lightness_range_max, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET) { + send_light_lightness_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS); + } + send_light_lightness_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_STATUS); + + return; +} + +/* Light CTL Server/Temperature Server/Setup Server message handlers */ + +static void send_light_ctl_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish, u16_t opcode) +{ + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 9; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, opcode); + switch (opcode) { + case BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS: { + struct bt_mesh_light_ctl_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness); + net_buf_simple_add_le16(msg, srv->state->temperature); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_le16(msg, srv->state->target_lightness); + net_buf_simple_add_le16(msg, srv->state->target_temperature); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS: + if (model->id == BLE_MESH_MODEL_ID_LIGHT_CTL_SRV) { + struct bt_mesh_light_ctl_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->temperature_range_min); + net_buf_simple_add_le16(msg, srv->state->temperature_range_max); + } else if (model->id == BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV) { + struct bt_mesh_light_ctl_setup_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->temperature_range_min); + net_buf_simple_add_le16(msg, srv->state->temperature_range_max); + } + break; + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS: { + if (model->id == BLE_MESH_MODEL_ID_LIGHT_CTL_SRV) { + struct bt_mesh_light_ctl_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_default); + net_buf_simple_add_le16(msg, srv->state->temperature_default); + net_buf_simple_add_le16(msg, srv->state->delta_uv_default); + } else if (model->id == BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV) { + struct bt_mesh_light_ctl_setup_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_default); + net_buf_simple_add_le16(msg, srv->state->temperature_default); + net_buf_simple_add_le16(msg, srv->state->delta_uv_default); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS: { + struct bt_mesh_light_ctl_temp_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->temperature); + net_buf_simple_add_le16(msg, srv->state->delta_uv); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_le16(msg, srv->state->target_temperature); + net_buf_simple_add_le16(msg, srv->state->target_delta_uv); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + break; + } + default: + BT_WARN("%s, Unknown Light CTL status opcode 0x%04x", __func__, opcode); + if (publish == false) { + bt_mesh_free_buf(msg); + } + return; + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void light_ctl_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_server_rsp_ctrl *rsp_ctrl = NULL; + u16_t opcode = 0U; + + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_LIGHT_CTL_SRV: { + struct bt_mesh_light_ctl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light CTL Server state", __func__); + return; + } + rsp_ctrl = &srv->rsp_ctrl; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV: { + struct bt_mesh_light_ctl_temp_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light CTL Temperature Server state", __func__); + return; + } + rsp_ctrl = &srv->rsp_ctrl; + break; + } + default: + BT_ERR("%s, Invalid Light CTL Server Model 0x%04x", __func__, model->id); + return; + } + + /* Callback the received message to the application layer */ + if (rsp_ctrl->get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_LIGHT_CTL_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS; + break; + default: + BT_WARN("%s, Unknown Light CTL Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + send_light_ctl_status(model, ctx, false, opcode); + return; +} + +void light_ctl_publish(struct bt_mesh_model *model, u16_t opcode) +{ + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_LIGHT_CTL_SRV: { + struct bt_mesh_light_ctl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light CTL Server state", __func__); + return; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV: { + struct bt_mesh_light_ctl_temp_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light CTL Temperature Server state", __func__); + return; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV: { + struct bt_mesh_light_ctl_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light CTL Setup Server state", __func__); + return; + } + break; + } + default: + BT_ERR("%s, Invalid Light CTL Server Model 0x%04x", __func__, model->id); + return; + } + + send_light_ctl_status(model, NULL, true, opcode); + return; +} + +static void light_ctl_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_ctl_srv *srv = model->user_data; + u16_t lightness = 0U, temperature = 0U; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + s16_t delta_uv = 0; + bool optional = false; + s64_t now = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + lightness = net_buf_simple_pull_le16(buf); + temperature = net_buf_simple_pull_le16(buf); + delta_uv = (s16_t) net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (temperature < BLE_MESH_TEMPERATURE_MIN || temperature > BLE_MESH_TEMPERATURE_MAX) { + BT_ERR("%s, Invalid temperature 0x%04x", __func__, temperature); + return; + } + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .ctl_set.op_en = optional, + .ctl_set.lightness = lightness, + .ctl_set.temperature = temperature, + .ctl_set.delta_uv = delta_uv, + .ctl_set.tid = tid, + .ctl_set.trans_time = trans_time, + .ctl_set.delay = delay, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_CTL_SET) { + send_light_ctl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS); + } + send_light_ctl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_light_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->state->target_lightness = lightness; + if (srv->state->temperature_range_min && + srv->state->temperature_range_min != BLE_MESH_TEMPERATURE_UNKNOWN && + temperature < srv->state->temperature_range_min) { + temperature = srv->state->temperature_range_min; + } else if (srv->state->temperature_range_max && + srv->state->temperature_range_max != BLE_MESH_TEMPERATURE_UNKNOWN && + temperature > srv->state->temperature_range_max) { + temperature = srv->state->temperature_range_max; + } + srv->state->target_temperature = temperature; + srv->state->target_delta_uv = delta_uv; + + if (srv->state->target_lightness != srv->state->lightness || + srv->state->target_temperature != srv->state->temperature || + srv->state->target_delta_uv != srv->state->delta_uv) { + light_ctl_tt_values(srv, trans_time, delay); + } else { + bt_mesh_light_server_state_change_t change = { + .ctl_set.lightness = srv->state->lightness, + .ctl_set.temperature = srv->state->temperature, + .ctl_set.delta_uv = srv->state->delta_uv, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_CTL_SET) { + send_light_ctl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS); + } + send_light_ctl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS); + + bt_mesh_light_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state->lightness = srv->state->target_lightness; + srv->state->temperature = srv->state->target_temperature; + srv->state->delta_uv = srv->state->target_delta_uv; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_CTL_SET) { + send_light_ctl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS); + } + send_light_ctl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS); + + bt_mesh_light_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +static void light_ctl_default_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_ctl_setup_srv *srv = model->user_data; + u16_t lightness = 0U, temperature = 0U; + s16_t delta_uv = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + lightness = net_buf_simple_pull_le16(buf); + temperature = net_buf_simple_pull_le16(buf); + delta_uv = (s16_t) net_buf_simple_pull_le16(buf); + + if (temperature < BLE_MESH_TEMPERATURE_MIN || temperature > BLE_MESH_TEMPERATURE_MAX) { + BT_ERR("%s, Invalid temperature 0x%04x", __func__, temperature); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .ctl_default_set.lightness = lightness, + .ctl_default_set.temperature = temperature, + .ctl_default_set.delta_uv = delta_uv, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (srv->state->temperature_range_min && + srv->state->temperature_range_min != BLE_MESH_TEMPERATURE_UNKNOWN && + temperature < srv->state->temperature_range_min) { + temperature = srv->state->temperature_range_min; + } else if (srv->state->temperature_range_max && + srv->state->temperature_range_max != BLE_MESH_TEMPERATURE_UNKNOWN && + temperature > srv->state->temperature_range_max) { + temperature = srv->state->temperature_range_max; + } + + srv->state->lightness_default = lightness; + srv->state->temperature_default = temperature; + srv->state->delta_uv_default = delta_uv; + + bt_mesh_light_server_state_change_t change = { + .ctl_default_set.lightness = srv->state->lightness_default, + .ctl_default_set.temperature = srv->state->temperature_default, + .ctl_default_set.delta_uv = srv->state->delta_uv_default, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET) { + send_light_ctl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS); + } + send_light_ctl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_STATUS); + + return; +} + +static void light_ctl_temp_range_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_ctl_setup_srv *srv = model->user_data; + u16_t min = 0U, max = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + min = net_buf_simple_pull_le16(buf); + max = net_buf_simple_pull_le16(buf); + + /* This is as per 6.1.3.1 in Mesh Model Specification */ + if (min > max || + min < BLE_MESH_TEMPERATURE_MIN || (min != BLE_MESH_TEMPERATURE_UNKNOWN && min > BLE_MESH_TEMPERATURE_MAX) || + max < BLE_MESH_TEMPERATURE_MIN || (max != BLE_MESH_TEMPERATURE_UNKNOWN && max > BLE_MESH_TEMPERATURE_MAX)) { + BT_ERR("%s, Invalid parameter, range Min 0x%04x, range max 0x%04x", + __func__, min, max); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .ctl_temp_range_set.range_min = min, + .ctl_temp_range_set.range_max = max, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (min == BLE_MESH_TEMPERATURE_UNKNOWN) { + srv->state->status_code = BLE_MESH_CANNOT_SET_RANGE_MIN; + } else if (max == BLE_MESH_TEMPERATURE_UNKNOWN ) { + srv->state->status_code = BLE_MESH_CANNOT_SET_RANGE_MAX; + } else { + srv->state->status_code = BLE_MESH_RANGE_UPDATE_SUCCESS; + } + + if (min != BLE_MESH_TEMPERATURE_UNKNOWN && srv->state->temperature_range_min != min) { + srv->state->temperature_range_min = min; + } + + if (max != BLE_MESH_TEMPERATURE_UNKNOWN && srv->state->temperature_range_max != max) { + srv->state->temperature_range_max = max; + } + + bt_mesh_light_server_state_change_t change = { + .ctl_temp_range_set.range_min = srv->state->temperature_range_min, + .ctl_temp_range_set.range_max = srv->state->temperature_range_max, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET) { + send_light_ctl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS); + } + send_light_ctl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_STATUS); + + return; +} + +static void light_ctl_temp_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_ctl_temp_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + u16_t temperature = 0U; + s16_t delta_uv = 0; + bool optional = false; + s64_t now = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + temperature = net_buf_simple_pull_le16(buf); + delta_uv = (s16_t) net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (temperature < BLE_MESH_TEMPERATURE_MIN || temperature > BLE_MESH_TEMPERATURE_MAX) { + BT_ERR("%s, Invalid temperature 0x%04x", __func__, temperature); + return; + } + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .ctl_temp_set.op_en = optional, + .ctl_temp_set.temperature = temperature, + .ctl_temp_set.delta_uv = delta_uv, + .ctl_temp_set.tid = tid, + .ctl_temp_set.trans_time = trans_time, + .ctl_temp_set.delay = delay, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET) { + send_light_ctl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS); + } + send_light_ctl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_light_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + if (srv->state->temperature_range_min && + srv->state->temperature_range_min != BLE_MESH_TEMPERATURE_UNKNOWN && + temperature < srv->state->temperature_range_min) { + temperature = srv->state->temperature_range_min; + } else if (srv->state->temperature_range_max && + srv->state->temperature_range_max != BLE_MESH_TEMPERATURE_UNKNOWN && + temperature > srv->state->temperature_range_max) { + temperature = srv->state->temperature_range_max; + } + srv->state->target_temperature = temperature; + srv->state->target_delta_uv = delta_uv; + + if (srv->state->target_temperature != srv->state->temperature || + srv->state->target_delta_uv != srv->state->delta_uv) { + light_ctl_temp_tt_values(srv, trans_time, delay); + } else { + bt_mesh_light_server_state_change_t change = { + .ctl_temp_set.temperature = srv->state->temperature, + .ctl_temp_set.delta_uv = srv->state->delta_uv, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET) { + send_light_ctl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS); + } + send_light_ctl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS); + + bt_mesh_light_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state->temperature = srv->state->target_temperature; + srv->state->delta_uv = srv->state->target_delta_uv; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET) { + send_light_ctl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS); + } + send_light_ctl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS); + + bt_mesh_light_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +/* Light HSL Server/Hue Server/Saturation Server/Setup Server message handlers */ + +static void send_light_hsl_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish, u16_t opcode) +{ + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 9; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, opcode); + switch (opcode) { + case BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS: + case BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS: { + struct bt_mesh_light_hsl_srv *srv = model->user_data; + if (opcode == BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS) { + net_buf_simple_add_le16(msg, srv->state->lightness); + net_buf_simple_add_le16(msg, srv->state->hue); + net_buf_simple_add_le16(msg, srv->state->saturation); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + } else if (opcode == BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS) { + net_buf_simple_add_le16(msg, srv->state->target_lightness); + net_buf_simple_add_le16(msg, srv->state->target_hue); + net_buf_simple_add_le16(msg, srv->state->target_saturation); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS: + if (model->id == BLE_MESH_MODEL_ID_LIGHT_HSL_SRV) { + struct bt_mesh_light_hsl_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_default); + net_buf_simple_add_le16(msg, srv->state->hue_default); + net_buf_simple_add_le16(msg, srv->state->saturation_default); + } else if (model->id == BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV) { + struct bt_mesh_light_hsl_setup_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_default); + net_buf_simple_add_le16(msg, srv->state->hue_default); + net_buf_simple_add_le16(msg, srv->state->saturation_default); + } + break; + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS: + if (model->id == BLE_MESH_MODEL_ID_LIGHT_HSL_SRV) { + struct bt_mesh_light_hsl_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->hue_range_min); + net_buf_simple_add_le16(msg, srv->state->hue_range_max); + net_buf_simple_add_le16(msg, srv->state->saturation_range_min); + net_buf_simple_add_le16(msg, srv->state->saturation_range_max); + } else if (model->id == BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV) { + struct bt_mesh_light_hsl_setup_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->hue_range_min); + net_buf_simple_add_le16(msg, srv->state->hue_range_max); + net_buf_simple_add_le16(msg, srv->state->saturation_range_min); + net_buf_simple_add_le16(msg, srv->state->saturation_range_max); + } + break; + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS: { + struct bt_mesh_light_hsl_hue_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->hue); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_le16(msg, srv->state->target_hue); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS: { + struct bt_mesh_light_hsl_sat_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->saturation); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_le16(msg, srv->state->target_saturation); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + break; + } + default: + BT_WARN("%s, Unknown Light HSL status opcode 0x%04x", __func__, opcode); + if (publish == false) { + bt_mesh_free_buf(msg); + } + return; + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void light_hsl_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_server_rsp_ctrl *rsp_ctrl = NULL; + u16_t opcode = 0U; + + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_LIGHT_HSL_SRV: { + struct bt_mesh_light_hsl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Server state", __func__); + return; + } + rsp_ctrl = &srv->rsp_ctrl; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV: { + struct bt_mesh_light_hsl_hue_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Hue Server state", __func__); + return; + } + rsp_ctrl = &srv->rsp_ctrl; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV: { + struct bt_mesh_light_hsl_sat_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Saturation Server state", __func__); + return; + } + rsp_ctrl = &srv->rsp_ctrl; + break; + } + default: + BT_ERR("%s, Invalid Light HSL Server Model 0x%04x", __func__, model->id); + return; + } + + /* Callback the received message to the application layer */ + if (rsp_ctrl->get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_LIGHT_HSL_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS; + break; + default: + BT_WARN("%s, Unknown Light HSL Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + send_light_hsl_status(model, ctx, false, opcode); + return; +} + +void light_hsl_publish(struct bt_mesh_model *model, u16_t opcode) +{ + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_LIGHT_HSL_SRV: { + struct bt_mesh_light_hsl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Server state", __func__); + return; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV: { + struct bt_mesh_light_hsl_hue_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Hue Server state", __func__); + return; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV: { + struct bt_mesh_light_hsl_sat_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Saturation Server state", __func__); + return; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV: { + struct bt_mesh_light_hsl_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Setup Server state", __func__); + return; + } + break; + } + default: + BT_ERR("%s, Invalid Light HSL Server Model 0x%04x", __func__, model->id); + return; + } + + send_light_hsl_status(model, NULL, true, opcode); + return; +} + +static void light_hsl_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_hsl_srv *srv = model->user_data; + u16_t lightness = 0U, hue = 0U, saturation = 0U; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + bool optional = false; + s64_t now = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + lightness = net_buf_simple_pull_le16(buf); + hue = net_buf_simple_pull_le16(buf); + saturation = net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .hsl_set.op_en = optional, + .hsl_set.lightness = lightness, + .hsl_set.hue = hue, + .hsl_set.saturation = saturation, + .hsl_set.tid = tid, + .hsl_set.trans_time = trans_time, + .hsl_set.delay = delay, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_light_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->state->target_lightness = lightness; + if (srv->state->hue_range_min && hue < srv->state->hue_range_min) { + hue = srv->state->hue_range_min; + } else if (srv->state->hue_range_max && hue > srv->state->hue_range_max) { + hue = srv->state->hue_range_max; + } + srv->state->target_hue = hue; + if (srv->state->saturation_range_min && saturation < srv->state->saturation_range_min) { + saturation = srv->state->saturation_range_min; + } else if (srv->state->saturation_range_max && saturation > srv->state->saturation_range_max) { + saturation = srv->state->saturation_range_max; + } + srv->state->target_saturation = saturation; + + /** + * If the target state is equal to the current state, the transition shall not + * be started and is considered complete. + */ + if (srv->state->target_lightness != srv->state->lightness || + srv->state->target_hue != srv->state->hue || + srv->state->target_saturation != srv->state->saturation) { + light_hsl_tt_values(srv, trans_time, delay); + } else { + bt_mesh_light_server_state_change_t change = { + .hsl_set.lightness = srv->state->lightness, + .hsl_set.hue = srv->state->hue, + .hsl_set.saturation = srv->state->saturation, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS); + + bt_mesh_light_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state->lightness = srv->state->target_lightness; + srv->state->hue = srv->state->target_hue; + srv->state->saturation = srv->state->target_saturation; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS); + + bt_mesh_light_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +static void light_hsl_default_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_hsl_setup_srv *srv = model->user_data; + u16_t lightness = 0U, hue = 0U, saturation = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + lightness = net_buf_simple_pull_le16(buf); + hue = net_buf_simple_pull_le16(buf); + saturation = net_buf_simple_pull_le16(buf); + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .hsl_default_set.lightness = lightness, + .hsl_default_set.hue = hue, + .hsl_default_set.saturation = saturation, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (srv->state->hue_range_min && hue < srv->state->hue_range_min) { + hue = srv->state->hue_range_min; + } else if (srv->state->hue_range_max && hue > srv->state->hue_range_max) { + hue = srv->state->hue_range_max; + } + + if (srv->state->saturation_range_min && saturation < srv->state->saturation_range_min) { + saturation = srv->state->saturation_range_min; + } else if (srv->state->saturation_range_max && saturation > srv->state->saturation_range_max) { + saturation = srv->state->saturation_range_max; + } + + srv->state->lightness_default = lightness; + srv->state->hue_default = hue; + srv->state->saturation_default = saturation; + + bt_mesh_light_server_state_change_t change = { + .hsl_default_set.lightness = srv->state->lightness_default, + .hsl_default_set.hue = srv->state->hue_default, + .hsl_default_set.saturation = srv->state->saturation_default, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_STATUS); + + return; +} + +static void light_hsl_range_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_hsl_setup_srv *srv = model->user_data; + u16_t hue_min = 0U, hue_max = 0U, saturation_min = 0U, saturation_max = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + hue_min = net_buf_simple_pull_le16(buf); + hue_max = net_buf_simple_pull_le16(buf); + saturation_min = net_buf_simple_pull_le16(buf); + saturation_max = net_buf_simple_pull_le16(buf); + + if (hue_min > hue_max) { + BT_ERR("%s, Invalid parameter, Hue min 0x%04x, Hue max 0x%04x", + __func__, hue_min, hue_max); + return; + } + + if (saturation_min > saturation_max) { + BT_ERR("%s, Invalid parameter, Saturation min 0x%04x, Saturation max 0x%04x", + __func__, hue_min, hue_max); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .hsl_range_set.hue_range_min = hue_min, + .hsl_range_set.hue_range_max = hue_max, + .hsl_range_set.sat_range_min = saturation_min, + .hsl_range_set.sat_range_max = saturation_max, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + srv->state->status_code = BLE_MESH_RANGE_UPDATE_SUCCESS; + srv->state->hue_range_min = hue_min; + srv->state->hue_range_max = hue_max; + srv->state->saturation_range_min = saturation_min; + srv->state->saturation_range_max = saturation_max; + + bt_mesh_light_server_state_change_t change = { + .hsl_range_set.hue_range_min = srv->state->hue_range_min, + .hsl_range_set.hue_range_max = srv->state->hue_range_max, + .hsl_range_set.sat_range_min = srv->state->saturation_range_min, + .hsl_range_set.sat_range_max = srv->state->saturation_range_max, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_STATUS); + + return; +} + +static void light_hsl_hue_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_hsl_hue_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + bool optional = false; + u16_t hue = 0U; + s64_t now = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + hue = net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .hsl_hue_set.op_en = optional, + .hsl_hue_set.hue = hue, + .hsl_hue_set.tid = tid, + .hsl_hue_set.trans_time = trans_time, + .hsl_hue_set.delay = delay, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_light_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + if (srv->state->hue_range_min && hue < srv->state->hue_range_min) { + hue = srv->state->hue_range_min; + } else if (srv->state->hue_range_max && hue > srv->state->hue_range_max) { + hue = srv->state->hue_range_max; + } + srv->state->target_hue = hue; + + /** + * If the target state is equal to the current state, the transition shall not + * be started and is considered complete. + */ + if (srv->state->target_hue != srv->state->hue) { + light_hsl_hue_tt_values(srv, trans_time, delay); + } else { + bt_mesh_light_server_state_change_t change = { + .hsl_hue_set.hue = srv->state->hue, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS); + + bt_mesh_light_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state->hue = srv->state->target_hue; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS); + + bt_mesh_light_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +static void light_hsl_sat_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_hsl_sat_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + u16_t saturation = 0U; + bool optional = false; + s64_t now = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + saturation = net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .hsl_saturation_set.op_en = optional, + .hsl_saturation_set.saturation = saturation, + .hsl_saturation_set.tid = tid, + .hsl_saturation_set.trans_time = trans_time, + .hsl_saturation_set.delay = delay, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_light_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + if (srv->state->saturation_range_min && saturation < srv->state->saturation_range_min) { + saturation = srv->state->saturation_range_min; + } else if (srv->state->saturation_range_max && saturation > srv->state->saturation_range_max) { + saturation = srv->state->saturation_range_max; + } + srv->state->target_saturation = saturation; + + /** + * If the target state is equal to the current state, the transition shall not + * be started and is considered complete. + */ + if (srv->state->target_saturation != srv->state->saturation) { + light_hsl_sat_tt_values(srv, trans_time, delay); + } else { + bt_mesh_light_server_state_change_t change = { + .hsl_saturation_set.saturation = srv->state->saturation, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS); + + bt_mesh_light_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state->saturation = srv->state->target_saturation; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET) { + send_light_hsl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS); + } + send_light_hsl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS); + + bt_mesh_light_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +/* Light xyL Server/Setup Server message handlers */ + +static void send_light_xyl_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish, u16_t opcode) +{ + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 9; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, opcode); + switch (opcode) { + case BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS: + case BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS: { + struct bt_mesh_light_xyl_srv *srv = model->user_data; + if (opcode == BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS) { + net_buf_simple_add_le16(msg, srv->state->lightness); + net_buf_simple_add_le16(msg, srv->state->x); + net_buf_simple_add_le16(msg, srv->state->y); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + } else if (opcode == BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS) { + net_buf_simple_add_le16(msg, srv->state->target_lightness); + net_buf_simple_add_le16(msg, srv->state->target_x); + net_buf_simple_add_le16(msg, srv->state->target_y); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + } + break; + } + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS: + if (model->id == BLE_MESH_MODEL_ID_LIGHT_XYL_SRV) { + struct bt_mesh_light_xyl_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_default); + net_buf_simple_add_le16(msg, srv->state->x_default); + net_buf_simple_add_le16(msg, srv->state->y_default); + } else if (model->id == BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV) { + struct bt_mesh_light_xyl_setup_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->lightness_default); + net_buf_simple_add_le16(msg, srv->state->x_default); + net_buf_simple_add_le16(msg, srv->state->y_default); + } + break; + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS: + if (model->id == BLE_MESH_MODEL_ID_LIGHT_XYL_SRV) { + struct bt_mesh_light_xyl_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->x_range_min); + net_buf_simple_add_le16(msg, srv->state->x_range_max); + net_buf_simple_add_le16(msg, srv->state->y_range_min); + net_buf_simple_add_le16(msg, srv->state->y_range_max); + } else if (model->id == BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV) { + struct bt_mesh_light_xyl_setup_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->status_code); + net_buf_simple_add_le16(msg, srv->state->x_range_min); + net_buf_simple_add_le16(msg, srv->state->x_range_max); + net_buf_simple_add_le16(msg, srv->state->y_range_min); + net_buf_simple_add_le16(msg, srv->state->y_range_max); + } + break; + default: + BT_WARN("%s, Unknown Light xyL status opcode 0x%04x", __func__, opcode); + if (publish == false) { + bt_mesh_free_buf(msg); + } + return; + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void light_xyl_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_xyl_srv *srv = model->user_data; + u16_t opcode = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_LIGHT_XYL_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS; + break; + default: + BT_WARN("%s, Unknown Light xyL Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + send_light_xyl_status(model, ctx, false, opcode); + return; +} + +void light_xyl_publish(struct bt_mesh_model *model, u16_t opcode) +{ + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_LIGHT_XYL_SRV: { + struct bt_mesh_light_xyl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light xyL Server state", __func__); + return; + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV: { + struct bt_mesh_light_xyl_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light xyL Setup Server state", __func__); + return; + } + break; + } + default: + BT_ERR("%s, Invalid Light xyL Server Model 0x%04x", __func__, model->id); + return; + } + + send_light_xyl_status(model, NULL, true, opcode); + return; +} + +static void light_xyl_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_xyl_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + u16_t lightness = 0U, x = 0U, y = 0U; + bool optional = false; + s64_t now = 0; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + lightness = net_buf_simple_pull_le16(buf); + x = net_buf_simple_pull_le16(buf); + y = net_buf_simple_pull_le16(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .xyl_set.op_en = optional, + .xyl_set.lightness = lightness, + .xyl_set.x = x, + .xyl_set.y = y, + .xyl_set.tid = tid, + .xyl_set.trans_time = trans_time, + .xyl_set.delay = delay, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_XYL_SET) { + send_light_xyl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS); + } + send_light_xyl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_light_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->state->target_lightness = lightness; + if (srv->state->x_range_min && x < srv->state->x_range_min) { + x = srv->state->x_range_min; + } else if (srv->state->x_range_max && x > srv->state->x_range_max) { + x = srv->state->x_range_max; + } + srv->state->target_x = x; + if (srv->state->y_range_min && y < srv->state->y_range_min) { + y = srv->state->y_range_min; + } else if (srv->state->y_range_max && y > srv->state->y_range_max) { + y = srv->state->y_range_max; + } + srv->state->target_y = y; + + /** + * If the target state is equal to the current state, the transition shall not + * be started and is considered complete. + */ + if (srv->state->target_lightness != srv->state->lightness || + srv->state->target_x != srv->state->x || + srv->state->target_y != srv->state->y) { + light_xyl_tt_values(srv, trans_time, delay); + } else { + bt_mesh_light_server_state_change_t change = { + .xyl_set.lightness = srv->state->lightness, + .xyl_set.x = srv->state->x, + .xyl_set.y = srv->state->y, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_XYL_SET) { + send_light_xyl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS); + } + send_light_xyl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS); + + bt_mesh_light_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state->lightness = srv->state->target_lightness; + srv->state->x = srv->state->target_x; + srv->state->y = srv->state->target_y; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_XYL_SET) { + send_light_xyl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS); + } + send_light_xyl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS); + + bt_mesh_light_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +static void light_xyl_default_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_xyl_setup_srv *srv = model->user_data; + u16_t lightness = 0U, x = 0U, y = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + lightness = net_buf_simple_pull_le16(buf); + x = net_buf_simple_pull_le16(buf); + y = net_buf_simple_pull_le16(buf); + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .xyl_default_set.lightness = lightness, + .xyl_default_set.x = x, + .xyl_default_set.y = y, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (srv->state->x_range_min && x < srv->state->x_range_min) { + x = srv->state->x_range_min; + } else if (srv->state->x_range_max && x > srv->state->x_range_max) { + x = srv->state->x_range_max; + } + + if (srv->state->y_range_min && y < srv->state->y_range_min) { + y = srv->state->y_range_min; + } else if (srv->state->y_range_max && y > srv->state->y_range_max) { + y = srv->state->y_range_max; + } + + srv->state->lightness_default = lightness; + srv->state->x_default = x; + srv->state->y_default = y; + + bt_mesh_light_server_state_change_t change = { + .xyl_default_set.lightness = srv->state->lightness_default, + .xyl_default_set.x = srv->state->x_default, + .xyl_default_set.y = srv->state->y_default, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET) { + send_light_xyl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS); + } + send_light_xyl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_STATUS); + + return; +} + +static void light_xyl_range_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_xyl_setup_srv *srv = model->user_data; + u16_t x_min = 0U, x_max = 0U, y_min = 0U, y_max = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + x_min = net_buf_simple_pull_le16(buf); + x_max = net_buf_simple_pull_le16(buf); + y_min = net_buf_simple_pull_le16(buf); + y_max = net_buf_simple_pull_le16(buf); + + if (x_min > x_max) { + BT_ERR("%s, Invalid parameter, xyL x min 0x%04x, xyL x max 0x%04x", + __func__, x_min, x_max); + return; + } + + if (y_min > y_max) { + BT_ERR("%s, Invalid parameter, xyL y min 0x%04x, xyL y max 0x%04x", + __func__, y_min, y_max); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .xyl_range_set.x_range_min = x_min, + .xyl_range_set.x_range_max = x_max, + .xyl_range_set.y_range_min = y_min, + .xyl_range_set.y_range_max = y_max, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + srv->state->status_code = BLE_MESH_RANGE_UPDATE_SUCCESS; + srv->state->x_range_min = x_min; + srv->state->x_range_max = x_max; + srv->state->y_range_min = y_min; + srv->state->y_range_max = y_max; + + bt_mesh_light_server_state_change_t change = { + .xyl_range_set.x_range_min = srv->state->x_range_min, + .xyl_range_set.x_range_max = srv->state->x_range_max, + .xyl_range_set.y_range_min = srv->state->y_range_min, + .xyl_range_set.y_range_max = srv->state->y_range_max, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET) { + send_light_xyl_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS); + } + send_light_xyl_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_STATUS); + + return; +} + +/* Light LC Server/Setup Server message handlers */ +static void send_light_lc_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish, u16_t opcode) +{ + struct bt_mesh_light_lc_srv *srv = model->user_data; + struct net_buf_simple *msg = NULL; + u8_t length = 2 + 3; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, opcode); + switch (opcode) { + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS: + net_buf_simple_add_u8(msg, srv->lc->state.mode); + break; + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS: + net_buf_simple_add_u8(msg, srv->lc->state.occupancy_mode); + break; + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS: + net_buf_simple_add_u8(msg, srv->lc->state.light_onoff); + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_u8(msg, srv->lc->state.target_light_onoff); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + break; + default: + BT_WARN("%s, Unknown Light LC status opcode 0x%04x", __func__, opcode); + if (publish == false) { + bt_mesh_free_buf(msg); + } + return; + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void light_lc_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lc_srv *srv = model->user_data; + u16_t opcode = 0U; + + if (srv == NULL || srv->lc == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_LIGHT_LC_MODE_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_LC_OM_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS; + break; + case BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_GET: + opcode = BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS; + break; + default: + BT_WARN("%s, Unknown Light LC Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + send_light_lc_status(model, ctx, false, opcode); + return; +} + +void light_lc_publish(struct bt_mesh_model *model, u16_t opcode) +{ + struct bt_mesh_light_lc_srv *srv = model->user_data; + + if (srv == NULL || srv->lc == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + send_light_lc_status(model, NULL, true, opcode); + return; +} + +static void light_lc_mode_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lc_srv *srv = model->user_data; + u8_t mode = 0U; + + if (srv == NULL || srv->lc == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + mode = net_buf_simple_pull_u8(buf); + if (mode > BLE_MESH_STATE_ON) { + BT_ERR("%s, Invalid LC Mode 0x%02x", __func__, mode); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .lc_mode_set.mode = mode, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + srv->lc->state.mode = mode; + + bt_mesh_light_server_state_change_t change = { + .lc_mode_set.mode = srv->lc->state.mode, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET) { + send_light_lc_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS); + } + send_light_lc_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LC_MODE_STATUS); + + return; +} + +static void light_lc_om_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lc_srv *srv = model->user_data; + u8_t om = 0U; + + if (srv == NULL || srv->lc == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + om = net_buf_simple_pull_u8(buf); + if (om > BLE_MESH_STATE_ON) { + BT_ERR("%s, Invalid LC Occupancy Mode 0x%02x", __func__, om); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .lc_om_set.mode = om, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + srv->lc->state.occupancy_mode = om; + + bt_mesh_light_server_state_change_t change = { + .lc_om_set.mode = srv->lc->state.occupancy_mode, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET) { + send_light_lc_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS); + } + send_light_lc_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LC_OM_STATUS); + + return; +} + +static void light_lc_light_onoff_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lc_srv *srv = model->user_data; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + bool optional = false; + u8_t onoff = 0U; + s64_t now = 0; + + if (srv == NULL || srv->lc == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + onoff = net_buf_simple_pull_u8(buf); + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .lc_light_onoff_set.op_en = optional, + .lc_light_onoff_set.light_onoff = onoff, + .lc_light_onoff_set.tid = tid, + .lc_light_onoff_set.trans_time = trans_time, + .lc_light_onoff_set.delay = delay, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET) { + send_light_lc_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS); + } + send_light_lc_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_light_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->lc->state.target_light_onoff = onoff; + + if (srv->lc->state.target_light_onoff != srv->lc->state.light_onoff) { + light_lc_tt_values(srv, trans_time, delay); + } else { + bt_mesh_light_server_state_change_t change = { + .lc_light_onoff_set.onoff = srv->lc->state.light_onoff, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET) { + send_light_lc_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS); + } + send_light_lc_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS); + + bt_mesh_light_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->lc->state.light_onoff = srv->lc->state.target_light_onoff; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET) { + send_light_lc_status(model, ctx, false, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS); + } + send_light_lc_status(model, NULL, true, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS); + + bt_mesh_light_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +static void light_lc_sensor_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /** + * When a Light LC Server receives a Sensor Status message, and if the message + * Raw field contains a Raw Value for the Motion Sensed Property, and the value + * is greater than 0, or a Raw Value for the People Count Property, and the + * value is greater than 0, or a Raw Value for the Presence Detected Property, + * and the value is greater than 0, then it shall set the Light LC Occupancy + * state to 0b1. + * If the message Raw field contains a Raw Value for the Time Since Motion Sensed + * device property, which represents a value less than or equal to the value of + * the Light LC Occupancy Delay state, it shall delay setting the Light LC Occupancy + * state to 0b1 by the difference between the value of the Light LC Occupancy Delay + * state and the received Time Since Motion value. + * When a Light LC Server receives a Sensor Status message, and if the message Raw + * field contains a Raw Value for the Present Ambient Light Level device property, + * it shall set the Light LC Ambient LuxLevel state to the Represented Value of the + * received Present Ambient Light Level. + * + * Motion Sensed: 1 octet, 0x0042 + * People Count: 2 octets, 0x004C + * Presence Detected: 1 octet, 0x004D + * + * Time Since Motion Sensed: 2 octets, 0x0068 + * + * Present Ambient Light Level: 4 octets, 0x004E + */ + struct bt_mesh_light_lc_srv *srv = model->user_data; + bt_mesh_light_server_state_change_t change = {0}; + u16_t mpid = 0U, prop_id = 0U; + u8_t length = 0U; + + if (srv == NULL || srv->lc == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + if (srv->rsp_ctrl.status_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_status_msg_t status = { + .sensor_status.data = buf, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_STATUS_MSG, model, ctx, (const u8_t *)&status, sizeof(status)); + return; + } + + mpid = net_buf_simple_pull_le16(buf); + if (mpid & BIT(0)) { + length = (u8_t)((mpid & 0xff) >> 1); + u8_t msb = net_buf_simple_pull_u8(buf); + prop_id = (u16_t)(msb << 8) | (u16_t)(mpid >> 8); + } else { + length = (u8_t)((mpid & 0x1f) >> 1); + prop_id = (u16_t)(mpid >> 5); + } + + change.sensor_status.property_id = prop_id; + + switch (prop_id) { + case BLE_MESH_MOTION_SENSED: { + if (length != BLE_MESH_MOTION_SENSED_LEN || length != buf->len) { + BT_WARN("%s, Invalid Motion Sensed Property length", __func__); + return; + } + u8_t val = net_buf_simple_pull_u8(buf); + if (val > 0) { + srv->lc->state.occupancy = BLE_MESH_STATE_ON; + + change.sensor_status.state.occupancy = srv->lc->state.occupancy; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + } + break; + } + case BLE_MESH_PEOPLE_COUNT: { + if (length != BLE_MESH_PEOPLE_COUNT_LEN || length != buf->len) { + BT_WARN("%s, Invalid Motion Sensed Property length", __func__); + return; + } + u16_t val = net_buf_simple_pull_le16(buf); + if (val > 0) { + srv->lc->state.occupancy = BLE_MESH_STATE_ON; + + change.sensor_status.state.occupancy = srv->lc->state.occupancy; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + } + break; + } + case BLE_MESH_PRESENCE_DETECTED: { + if (length != BLE_MESH_PRESENCE_DETECTED_LEN || length != buf->len) { + BT_WARN("%s, Invalid Motion Sensed Property length", __func__); + return; + } + u8_t val = net_buf_simple_pull_u8(buf); + if (val > 0) { + srv->lc->state.occupancy = BLE_MESH_STATE_ON; + + change.sensor_status.state.occupancy = srv->lc->state.occupancy; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + } + break; + } + case BLE_MESH_TIME_SINCE_MOTION_SENSED: { + if (length != BLE_MESH_TIME_SINCE_MOTION_SENSED_LEN || length != buf->len) { + BT_WARN("%s, Invalid Motion Sensed Property length", __func__); + return; + } + u16_t val = net_buf_simple_pull_le16(buf); + if (val <= srv->lc->prop_state.time_occupancy_delay) { + srv->lc->prop_state.set_occupancy_to_1_delay = + srv->lc->prop_state.time_occupancy_delay - val; + + change.sensor_status.state.set_occupancy_to_1_delay = srv->lc->prop_state.set_occupancy_to_1_delay; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + } + break; + } + case BLE_MESH_PRESENT_AMBIENT_LIGHT_LEVEL: { + /** + * Present Ambient Light Level device property is 4 octets, but ambient + * luxlevel length is 3 octets, and other devices may send Sensor Status + * which only contains 3 octets just for Light LC Server. + * Here we just check if the length is larger than 3. + */ + if (buf->len < 3) { + BT_WARN("%s, Invalid Motion Sensed Property length", __func__); + return; + } + u16_t lsb = net_buf_simple_pull_le16(buf); + u8_t msb = net_buf_simple_pull_u8(buf); + srv->lc->state.ambient_luxlevel = (msb << 16) | lsb; + + change.sensor_status.state.ambient_luxlevel = srv->lc->state.ambient_luxlevel; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + break; + } + default: + break; + } +} + +static u8_t *get_light_lc_prop_val(struct bt_mesh_model *model, u16_t prop_id) +{ + struct bt_mesh_light_lc_setup_srv *srv = model->user_data; + u8_t *val = NULL; + + switch (prop_id) { + case BLE_MESH_LIGHT_CONTROL_TIME_OCCUPANCY_DELAY: + val = (u8_t *)&srv->lc->prop_state.time_occupancy_delay; + break; + case BLE_MESH_LIGHT_CONTROL_TIME_FADE_ON: + val = (u8_t *)&srv->lc->prop_state.time_fade_on; + break; + case BLE_MESH_LIGHT_CONTROL_TIME_RUN_ON: + val = (u8_t *)&srv->lc->prop_state.time_run_on; + break; + case BLE_MESH_LIGHT_CONTROL_TIME_FADE: + val = (u8_t *)&srv->lc->prop_state.time_fade; + break; + case BLE_MESH_LIGHT_CONTROL_TIME_PROLONG: + val = (u8_t *)&srv->lc->prop_state.time_prolong; + break; + case BLE_MESH_LIGHT_CONTROL_TIME_FADE_STANDBY_AUTO: + val = (u8_t *)&srv->lc->prop_state.time_fade_standby_auto; + break; + case BLE_MESH_LIGHT_CONTROL_TIME_FADE_STANDBY_MANUAL: + val = (u8_t *)&srv->lc->prop_state.time_fade_standby_manual; + break; + case BLE_MESH_LIGHT_CONTROL_LIGHTNESS_ON: + val = (u8_t *)&srv->lc->prop_state.lightness_on; + break; + case BLE_MESH_LIGHT_CONTROL_LIGHTNESS_PROLONG: + val = (u8_t *)&srv->lc->prop_state.lightness_prolong; + break; + case BLE_MESH_LIGHT_CONTROL_LIGHTNESS_STANDBY: + val = (u8_t *)&srv->lc->prop_state.lightness_standby; + break; + case BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_ON: + val = (u8_t *)&srv->lc->prop_state.ambient_luxlevel_on; + break; + case BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_PROLONG: + val = (u8_t *)&srv->lc->prop_state.ambient_luxlevel_prolong; + break; + case BLE_MESH_LIGHT_CONTROL_AMBIENT_LUXLEVEL_STANDBY: + val = (u8_t *)&srv->lc->prop_state.ambient_luxlevel_standby; + break; + case BLE_MESH_LIGHT_CONTROL_REGULATOR_KIU: + val = (u8_t *)&srv->lc->prop_state.regulator_kiu; + break; + case BLE_MESH_LIGHT_CONTROL_REGULATOR_KID: + val = (u8_t *)&srv->lc->prop_state.regulator_kid; + break; + case BLE_MESH_LIGHT_CONTROL_REGULATOR_KPU: + val = (u8_t *)&srv->lc->prop_state.regulator_kpu; + break; + case BLE_MESH_LIGHT_CONTROL_REGULATOR_KPD: + val = (u8_t *)&srv->lc->prop_state.regulator_kpd; + break; + case BLE_MESH_LIGHT_CONTROL_REGULATOR_ACCURACY: + val = (u8_t *)&srv->lc->prop_state.regulator_accuracy; + break; + } + + return val; +} + +u8_t *bt_mesh_get_lc_prop_value(struct bt_mesh_model *model, u16_t prop_id) +{ + if (model == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return NULL; + } + + return get_light_lc_prop_val(model, prop_id); +} + +static void send_light_lc_prop_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t prop_id, bool publish) +{ + struct net_buf_simple *msg = NULL; + u8_t length = 1 + 2 + 4; + u8_t *prop_val = NULL; + + prop_val = get_light_lc_prop_val(model, prop_id); + if (prop_val == NULL) { + BT_ERR("%s, Failed to get Light LC Property value", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_STATUS); + net_buf_simple_add_le16(msg, prop_id); + net_buf_simple_add_mem(msg, prop_val, bt_mesh_get_dev_prop_len(prop_id)); + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void light_lc_prop_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lc_setup_srv *srv = model->user_data; + u16_t prop_id = 0U; + + if (srv == NULL || srv->lc == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + prop_id = net_buf_simple_pull_le16(buf); + if (prop_id < 0x002B || prop_id > 0x003C) { + BT_ERR("%s, Invalid Light LC Property ID 0x%04x", __func__, prop_id); + return; + } + + /* Callback the received message to the application layer */ + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_get_msg_t get = { + .lc_property_get.id = net_buf_simple_pull_le16(buf), + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + return; + } + + send_light_lc_prop_status(model, ctx, prop_id, false); + return; +} + +static void light_lc_prop_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_light_lc_setup_srv *srv = model->user_data; + u8_t *prop_val = NULL, expect_len = 0U; + u16_t prop_id = 0U; + + if (srv == NULL || srv->lc == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + prop_id = net_buf_simple_pull_le16(buf); + if (prop_id < 0x002B || prop_id > 0x003C) { + BT_ERR("%s, Invalid Light LC Property ID 0x%04x", __func__, prop_id); + return; + } + + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_light_server_recv_set_msg_t set = { + .lc_property_set.id = net_buf_simple_pull_le16(buf), + .lc_property_set.value = buf, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + expect_len = bt_mesh_get_dev_prop_len(prop_id); + if (buf->len != expect_len) { + BT_ERR("%s, Invalid Light LC Property length, ID 0x%04x, expect %d, actual %d", + __func__, prop_id, expect_len, buf->len); + return; + } + + prop_val = get_light_lc_prop_val(model, prop_id); + if (prop_val == NULL) { + BT_ERR("%s, Failed to get Light LC Property value", __func__); + return; + } + + memcpy(prop_val, buf->data, buf->len); + + bt_mesh_light_server_state_change_t change = { + .lc_property_set.id = prop_id, + .lc_property_set.value = buf, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET) { + send_light_lc_prop_status(model, ctx, prop_id, false); + } + send_light_lc_prop_status(model, ctx, prop_id, true); + + return; +} + +/* message handlers (End) */ + +/* Mapping of message handlers for Light Lightness Server (0x1300) */ +const struct bt_mesh_model_op light_lightness_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_GET, 0, light_lightness_get }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET, 3, light_lightness_set }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET_UNACK, 3, light_lightness_set }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_GET, 0, light_lightness_get }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET, 3, light_lightness_linear_set }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET_UNACK, 3, light_lightness_linear_set }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LAST_GET, 0, light_lightness_get }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_GET, 0, light_lightness_get }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_GET, 0, light_lightness_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light Lightness Setup Server (0x1301) */ +const struct bt_mesh_model_op light_lightness_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET, 2, light_lightness_default_set }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET_UNACK, 2, light_lightness_default_set }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET, 4, light_lightness_range_set }, + { BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET_UNACK, 4, light_lightness_range_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light CTL Server (0x1303) */ +const struct bt_mesh_model_op light_ctl_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_CTL_GET, 0, light_ctl_get }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_SET, 7, light_ctl_set }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_SET_UNACK, 7, light_ctl_set }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_GET, 0, light_ctl_get }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_GET, 0, light_ctl_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light CTL Setup Server (0x1304) */ +const struct bt_mesh_model_op light_ctl_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET, 6, light_ctl_default_set }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET_UNACK, 6, light_ctl_default_set }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET, 4, light_ctl_temp_range_set }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET_UNACK, 4, light_ctl_temp_range_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light CTL Temperature Server (0x1306) */ +const struct bt_mesh_model_op light_ctl_temp_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_GET, 0, light_ctl_get }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET, 5, light_ctl_temp_set }, + { BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET_UNACK, 5, light_ctl_temp_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light HSL Server (0x1307) */ +const struct bt_mesh_model_op light_hsl_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_HSL_GET, 0, light_hsl_get }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_SET, 7, light_hsl_set }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_SET_UNACK, 7, light_hsl_set }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_TARGET_GET, 0, light_hsl_get }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_GET, 0, light_hsl_get }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_GET, 0, light_hsl_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light HSL Setup Server (0x1308) */ +const struct bt_mesh_model_op light_hsl_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET, 6, light_hsl_default_set }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_DEFAULT_SET_UNACK, 6, light_hsl_default_set }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET, 8, light_hsl_range_set }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_RANGE_SET_UNACK, 8, light_hsl_range_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light HSL Hue Server (0x130A) */ +const struct bt_mesh_model_op light_hsl_hue_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_GET, 0, light_hsl_get }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET, 3, light_hsl_hue_set }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_SET_UNACK, 3, light_hsl_hue_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light HSL Saturation Server (0x130B) */ +const struct bt_mesh_model_op light_hsl_sat_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_GET, 0, light_hsl_get }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET, 3, light_hsl_sat_set }, + { BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_SET_UNACK, 3, light_hsl_sat_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light xyL Server (0x130C) */ +const struct bt_mesh_model_op light_xyl_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_XYL_GET, 0, light_xyl_get }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_SET, 7, light_xyl_set }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_SET_UNACK, 7, light_xyl_set }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_TARGET_GET, 0, light_xyl_get }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_GET, 0, light_xyl_get }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_GET, 0, light_xyl_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light xyL Setup Server (0x130D) */ +const struct bt_mesh_model_op light_xyl_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET, 6, light_xyl_default_set }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_DEFAULT_SET_UNACK, 6, light_xyl_default_set }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET, 8, light_xyl_range_set }, + { BLE_MESH_MODEL_OP_LIGHT_XYL_RANGE_SET_UNACK, 8, light_xyl_range_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light LC Server (0x130F) */ +const struct bt_mesh_model_op light_lc_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_LC_MODE_GET, 0, light_lc_get }, + { BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET, 1, light_lc_mode_set }, + { BLE_MESH_MODEL_OP_LIGHT_LC_MODE_SET_UNACK, 1, light_lc_mode_set }, + { BLE_MESH_MODEL_OP_LIGHT_LC_OM_GET, 0, light_lc_get }, + { BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET, 1, light_lc_om_set }, + { BLE_MESH_MODEL_OP_LIGHT_LC_OM_SET_UNACK, 1, light_lc_om_set }, + { BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_GET, 0, light_lc_get }, + { BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET, 2, light_lc_light_onoff_set }, + { BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_SET_UNACK, 2, light_lc_light_onoff_set }, + { BLE_MESH_MODEL_OP_SENSOR_STATUS, 3, light_lc_sensor_status }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Light LC Setup Server (0x1310) */ +const struct bt_mesh_model_op light_lc_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET, 2, light_lc_prop_get }, + { BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET, 3, light_lc_prop_set }, + { BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK, 3, light_lc_prop_set }, + BLE_MESH_MODEL_OP_END, +}; + +static int light_server_init(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, No Light Server context provided, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV: { + struct bt_mesh_light_lightness_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light Lightness State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->actual_transition.timer.work); + bt_mesh_server_alloc_ctx(&srv->linear_transition.timer.work); + k_delayed_work_init(&srv->actual_transition.timer, light_lightness_actual_work_handler); + k_delayed_work_init(&srv->linear_transition.timer, light_lightness_linear_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV: { + struct bt_mesh_light_lightness_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light Lightness State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_CTL_SRV: { + struct bt_mesh_light_ctl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light CTL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, light_ctl_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV: { + struct bt_mesh_light_ctl_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light CTL State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV: { + struct bt_mesh_light_ctl_temp_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light CTL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, light_ctl_temp_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_SRV: { + struct bt_mesh_light_hsl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light HSL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, light_hsl_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV: { + struct bt_mesh_light_hsl_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light HSL State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV: { + struct bt_mesh_light_hsl_hue_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light HSL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, light_hsl_hue_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV: { + struct bt_mesh_light_hsl_sat_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light HSL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, light_hsl_sat_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_XYL_SRV: { + struct bt_mesh_light_xyl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light xyL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, light_xyl_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV: { + struct bt_mesh_light_xyl_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light xyL State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_LC_SRV: { + struct bt_mesh_light_lc_srv *srv = model->user_data; + if (srv->lc == NULL) { + BT_ERR("%s, NULL Light LC State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, light_lc_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_LIGHT_LC_SETUP_SRV: { + struct bt_mesh_light_lc_setup_srv *srv = model->user_data; + if (srv->lc == NULL) { + BT_ERR("%s, NULL Light LC State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + default: + BT_WARN("%s, Unknown Light Server Model, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + bt_mesh_light_server_mutex_new(); + + return 0; +} + +int bt_mesh_light_lightness_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light Lightness Server has no publication support", __func__); + return -EINVAL; + } + + /* When this model is present on an Element, the corresponding Light Lightness + * Setup Server model shall also be present. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV) == NULL) { + BT_WARN("%s, Light Lightness Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return light_server_init(model); +} + +int bt_mesh_light_lightness_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + return light_server_init(model); +} + +int bt_mesh_light_ctl_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light CTL Server has no publication support", __func__); + return -EINVAL; + } + + /** + * When this model is present on an Element, the corresponding Light CTL + * Temperature Server model and the corresponding Light CTL Setup Server + * model shall also be present. + * The model requires two elements: the main element and the Temperature + * element. The Temperature element contains the corresponding Light CTL + * Temperature Server model. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV) == NULL) { + BT_WARN("%s, Light CTL Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + if (bt_mesh_elem_count() < 2) { + BT_WARN("%s, Light CTL Server requires two elements", __func__); + /* Just give a warning here, continue with the initialization */ + } + return light_server_init(model); +} + +int bt_mesh_light_ctl_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + return light_server_init(model); +} + +int bt_mesh_light_ctl_temp_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light CTL Temperature Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_init(model); +} + +int bt_mesh_light_hsl_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light HSL Server has no publication support", __func__); + return -EINVAL; + } + + /** + * When this model is present on an Element, the corresponding Light HSL Hue + * Server model and the corresponding Light HSL Saturation Server model and + * the corresponding Light HSL Setup Server model shall also be present. + * The model requires three elements: the main element and the Hue element + * and the Saturation element. The Hue element contains the corresponding + * Light HSL Hue Server model, and the Saturation element contains the corr- + * esponding Light HSL Saturation Server model. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV) == NULL) { + BT_WARN("%s, Light HSL Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + if (bt_mesh_elem_count() < 3) { + BT_WARN("%s, Light HSL Server requires three elements", __func__); + /* Just give a warning here, continue with the initialization */ + } + return light_server_init(model); +} + +int bt_mesh_light_hsl_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + return light_server_init(model); +} + +int bt_mesh_light_hsl_hue_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light HSL Hue Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_init(model); +} + +int bt_mesh_light_hsl_sat_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light HSL Saturation Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_init(model); +} + +int bt_mesh_light_xyl_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light xyL Server has no publication support", __func__); + return -EINVAL; + } + + /** + * When this model is present on an Element, the corresponding Light xyL + * Setup Server model shall also be present. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV) == NULL) { + BT_WARN("%s, Light xyL Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return light_server_init(model); +} + +int bt_mesh_light_xyl_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + return light_server_init(model); +} + +int bt_mesh_light_lc_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light LC Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_init(model); +} + +int bt_mesh_light_lc_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light LC Setup Server has no publication support", __func__); + return -EINVAL; + } + + /** + * When this model is present on an Element, the corresponding Light LC + * Setup Server model shall also be present. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_LIGHT_LC_SETUP_SRV) == NULL) { + BT_WARN("%s, Light LC Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return light_server_init(model); +} + +static int light_server_deinit(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, No Light Server context provided, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV: { + struct bt_mesh_light_lightness_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light Lightness State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->actual_transition.timer.work); + bt_mesh_server_free_ctx(&srv->linear_transition.timer.work); + k_delayed_work_free(&srv->actual_transition.timer); + k_delayed_work_free(&srv->linear_transition.timer); + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_CTL_SRV: { + struct bt_mesh_light_ctl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light CTL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV: { + struct bt_mesh_light_ctl_temp_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light CTL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_SRV: { + struct bt_mesh_light_hsl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light HSL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV: { + struct bt_mesh_light_hsl_hue_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light HSL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV: { + struct bt_mesh_light_hsl_sat_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light HSL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_XYL_SRV: { + struct bt_mesh_light_xyl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Light xyL State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + case BLE_MESH_MODEL_ID_LIGHT_LC_SRV: { + struct bt_mesh_light_lc_srv *srv = model->user_data; + if (srv->lc == NULL) { + BT_ERR("%s, NULL Light LC State", __func__); + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + default: + BT_WARN("%s, Unknown Light Server Model, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + bt_mesh_light_server_mutex_free(); + + return 0; +} + +int bt_mesh_light_lightness_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light Lightness Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_deinit(model); +} + +int bt_mesh_light_lightness_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + return light_server_deinit(model); +} + +int bt_mesh_light_ctl_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light CTL Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_deinit(model); +} + +int bt_mesh_light_ctl_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + return light_server_deinit(model); +} + +int bt_mesh_light_ctl_temp_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light CTL Temperature Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_deinit(model); +} + +int bt_mesh_light_hsl_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light HSL Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_deinit(model); +} + +int bt_mesh_light_hsl_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + return light_server_deinit(model); +} + +int bt_mesh_light_hsl_hue_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light HSL Hue Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_deinit(model); +} + +int bt_mesh_light_hsl_sat_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light HSL Saturation Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_deinit(model); +} + +int bt_mesh_light_xyl_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light xyL Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_deinit(model); +} + +int bt_mesh_light_xyl_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + return light_server_deinit(model); +} + +int bt_mesh_light_lc_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light LC Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_deinit(model); +} + +int bt_mesh_light_lc_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Light LC Setup Server has no publication support", __func__); + return -EINVAL; + } + + return light_server_deinit(model); +} diff --git a/components/bt/esp_ble_mesh/mesh_models/server/sensor_server.c b/components/bt/esp_ble_mesh/mesh_models/server/sensor_server.c new file mode 100644 index 0000000000..d4b0a432a4 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/sensor_server.c @@ -0,0 +1,1108 @@ +// Copyright 2017-2019 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 "access.h" +#include "transport.h" +#include "model_opcode.h" +#include "state_transition.h" +#include "device_property.h" + +#include "btc_ble_mesh_sensor_model.h" + +static void update_sensor_periodic_pub(struct bt_mesh_model *model, u16_t prop_id); + +/* message handlers (Start) */ + +/* Sensor Server & Sensor Setup Server message handlers */ +static void send_sensor_descriptor_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t prop_id, bool get_all) +{ + struct bt_mesh_sensor_srv *srv = model->user_data; + struct bt_mesh_sensor_state *state = NULL; + struct net_buf_simple *msg = NULL; + u16_t total_len = 5U; + int i; + + msg = bt_mesh_alloc_buf(MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_STATUS); + + if (get_all == true) { + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID) { + total_len += SENSOR_DESCRIPTOR_LEN; + if (total_len > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)) { + /* Add this in case the message is too long */ + break; + } + net_buf_simple_add_le16(msg, state->sensor_property_id); + net_buf_simple_add_le32(msg, (state->descriptor.sample_function << 24) | + (state->descriptor.negative_tolerance << 12) | + (state->descriptor.positive_tolerance)); + net_buf_simple_add_u8(msg, state->descriptor.measure_period); + net_buf_simple_add_u8(msg, state->descriptor.update_interval); + } + } + } else { + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && + state->sensor_property_id == prop_id) { + net_buf_simple_add_le16(msg, state->sensor_property_id); + net_buf_simple_add_le32(msg, (state->descriptor.sample_function << 24) | + (state->descriptor.negative_tolerance << 12) | + (state->descriptor.positive_tolerance)); + net_buf_simple_add_u8(msg, state->descriptor.measure_period); + net_buf_simple_add_u8(msg, state->descriptor.update_interval); + break; + } + } + if (i == srv->state_count) { + BT_WARN("%s, Sensor Property ID 0x%04x does not exist", __func__, prop_id); + net_buf_simple_add_le16(msg, prop_id); + } + } + + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + return; +} + +static void send_sensor_data_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t prop_id, bool get_all) +{ + struct bt_mesh_sensor_srv *srv = model->user_data; + struct bt_mesh_sensor_state *state = NULL; + struct net_buf_simple *msg = NULL; + u16_t total_len = 5U; + int i; + + msg = bt_mesh_alloc_buf(MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SENSOR_STATUS); + + if (get_all == true) { + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID) { + u8_t mpid_len = (state->sensor_data.format == SENSOR_DATA_FORMAT_A) ? + SENSOR_DATA_FORMAT_A_MPID_LEN : SENSOR_DATA_FORMAT_B_MPID_LEN; + total_len += (mpid_len + (state->sensor_data.raw_value ? + state->sensor_data.raw_value->len : 0)); + if (total_len > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)) { + /* Add this in case the message is too long */ + break; + } + if (state->sensor_data.format == SENSOR_DATA_FORMAT_A) { + u16_t mpid = ((state->sensor_property_id & BIT_MASK(11)) << 5) | + ((state->sensor_data.length & BIT_MASK(4)) << 1) | state->sensor_data.format; + net_buf_simple_add_le16(msg, mpid); + } else if (state->sensor_data.format == SENSOR_DATA_FORMAT_B) { + u8_t mpid = (state->sensor_data.length << 1) | state->sensor_data.format; + net_buf_simple_add_u8(msg, mpid); + net_buf_simple_add_le16(msg, state->sensor_property_id); + } + if (state->sensor_data.raw_value) { + net_buf_simple_add_mem(msg, state->sensor_data.raw_value->data, state->sensor_data.raw_value->len); + } + } + } + } else { + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && + state->sensor_property_id == prop_id) { + if (state->sensor_data.format == SENSOR_DATA_FORMAT_A) { + u16_t mpid = ((state->sensor_property_id & BIT_MASK(11)) << 5) | + ((state->sensor_data.length & BIT_MASK(4)) << 1) | + state->sensor_data.format; + net_buf_simple_add_le16(msg, mpid); + } else if (state->sensor_data.format == SENSOR_DATA_FORMAT_B) { + u8_t mpid = (state->sensor_data.length << 1) | state->sensor_data.format; + net_buf_simple_add_u8(msg, mpid); + net_buf_simple_add_le16(msg, state->sensor_property_id); + } + if (state->sensor_data.raw_value) { + net_buf_simple_add_mem(msg, state->sensor_data.raw_value->data, + state->sensor_data.raw_value->len); + } + break; + } + } + if (i == srv->state_count) { + BT_WARN("%s, Sensor Property ID 0x%04x does not exist", __func__, prop_id); + u8_t mpid = (SENSOR_DATA_ZERO_LEN << 1) | SENSOR_DATA_FORMAT_B; + net_buf_simple_add_u8(msg, mpid); + net_buf_simple_add_le16(msg, prop_id); + } + } + + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + return; +} + +static void send_sensor_cadence_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t prop_id, bool publish) +{ + struct bt_mesh_sensor_setup_srv *srv = model->user_data; + struct bt_mesh_sensor_state *state = NULL; + struct net_buf_simple *msg = NULL; + u16_t length = 0U; + int i; + + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && + state->sensor_property_id == prop_id && state->cadence) { + length = SENSOR_PROPERTY_ID_LEN + 1 + 1; + if (state->cadence->trigger_delta_down) { + if (state->cadence->trigger_type == SENSOR_STATUS_TRIGGER_TYPE_CHAR) { + length += state->cadence->trigger_delta_down->len; + } else { + length += SENSOR_STATUS_TRIGGER_UINT16_LEN; + } + } + if (state->cadence->trigger_delta_up) { + if (state->cadence->trigger_type == SENSOR_STATUS_TRIGGER_TYPE_CHAR) { + length += state->cadence->trigger_delta_up->len; + } else { + length += SENSOR_STATUS_TRIGGER_UINT16_LEN; + } + } + if (state->cadence->fast_cadence_low) { + length += state->cadence->fast_cadence_low->len; + } + if (state->cadence->fast_cadence_high) { + length += state->cadence->fast_cadence_high->len; + } + break; + } + } + if (i == srv->state_count) { + BT_WARN("%s, Sensor Property ID 0x%04x does not exist", __func__, prop_id); + length = SENSOR_PROPERTY_ID_LEN; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(1 + length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, 1 + length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SENSOR_CADENCE_STATUS); + net_buf_simple_add_le16(msg, prop_id); + if (i != srv->state_count) { + if (state->cadence) { + net_buf_simple_add_u8(msg, (state->cadence->trigger_type << 7) | + state->cadence->period_divisor); + if (state->cadence->trigger_delta_down) { + if (state->cadence->trigger_type == SENSOR_STATUS_TRIGGER_TYPE_CHAR) { + net_buf_simple_add_mem(msg, state->cadence->trigger_delta_down->data, + state->cadence->trigger_delta_down->len); + } else { + net_buf_simple_add_mem(msg, state->cadence->trigger_delta_down->data, + SENSOR_STATUS_TRIGGER_UINT16_LEN); + } + } + if (state->cadence->trigger_delta_up) { + if (state->cadence->trigger_type == SENSOR_STATUS_TRIGGER_TYPE_CHAR) { + net_buf_simple_add_mem(msg, state->cadence->trigger_delta_up->data, + state->cadence->trigger_delta_up->len); + } else { + net_buf_simple_add_mem(msg, state->cadence->trigger_delta_up->data, + SENSOR_STATUS_TRIGGER_UINT16_LEN); + } + } + net_buf_simple_add_u8(msg, state->cadence->min_interval); + if (state->cadence->fast_cadence_low) { + net_buf_simple_add_mem(msg, state->cadence->fast_cadence_low->data, + state->cadence->fast_cadence_low->len); + } + if (state->cadence->fast_cadence_high) { + net_buf_simple_add_mem(msg, state->cadence->fast_cadence_high->data, + state->cadence->fast_cadence_high->len); + } + } + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void send_sensor_settings_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u16_t prop_id) +{ + struct bt_mesh_sensor_setup_srv *srv = model->user_data; + struct bt_mesh_sensor_state *state = NULL; + struct sensor_setting *item = NULL; + struct net_buf_simple *msg = NULL; + u16_t total_len = 7U; + int i, j; + + msg = bt_mesh_alloc_buf(MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SENSOR_SETTINGS_STATUS); + net_buf_simple_add_le16(msg, prop_id); + + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && + state->sensor_property_id == prop_id) { + for (j = 0; j < state->setting_count; j++) { + item = &state->settings[j]; + if (item->property_id != INVALID_SENSOR_SETTING_PROPERTY_ID) { + total_len += SENSOR_SETTING_PROPERTY_ID_LEN; + if (total_len > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)) { + /* Add this in case the message is too long */ + break; + } + net_buf_simple_add_le16(msg, item->property_id); + } + } + break; + } + } + if (i == srv->state_count) { + BT_WARN("%s, Sensor Property ID 0x%04x does not exist", __func__, prop_id); + } + + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + return; +} + +static struct sensor_setting *find_sensor_setting(struct bt_mesh_model *model, + u16_t prop_id, u16_t set_prop_id) +{ + struct bt_mesh_sensor_setup_srv *srv = model->user_data; + struct bt_mesh_sensor_state *state = NULL; + struct sensor_setting *item = NULL; + int i, j; + + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && + state->sensor_property_id == prop_id) { + for (j = 0; j < state->setting_count; j++) { + item = &state->settings[j]; + if (item->property_id != INVALID_SENSOR_SETTING_PROPERTY_ID && + item->property_id == set_prop_id) { + return item; + } + } + } + } + + return NULL; +} + +static void send_sensor_setting_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, u16_t prop_id, + u16_t set_prop_id, bool publish) +{ + struct sensor_setting *item = NULL; + struct net_buf_simple *msg = NULL; + u16_t length = 0U; + + item = find_sensor_setting(model, prop_id, set_prop_id); + if (item) { + length = SENSOR_PROPERTY_ID_LEN + SENSOR_SETTING_PROPERTY_ID_LEN + + SENSOR_SETTING_ACCESS_LEN + (item->raw ? item->raw->len : 0); + } else { + /* If the message is sent as a response to the Sensor Setting Get message or + * a Sensor Setting Set message with an unknown Sensor Property ID field or + * an unknown Sensor Setting Property ID field, the Sensor Setting Access + * field and the Sensor Setting Raw field shall be omitted. + */ + BT_WARN("%s, Sensor Setting not found, 0x%04x, 0x%04x", __func__, prop_id, set_prop_id); + length = SENSOR_PROPERTY_ID_LEN + SENSOR_SETTING_PROPERTY_ID_LEN; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(1 + length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, 1 + length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SENSOR_SETTING_STATUS); + net_buf_simple_add_le16(msg, prop_id); + net_buf_simple_add_le16(msg, set_prop_id); + if (item) { + /** + * If the message is sent as a response to the Sensor Setting Set message with + * a Sensor Setting Property ID field that identifies an existing Sensor Setting, + * and the value of the Sensor Setting Access state is 0x01 (can be read), the + * Sensor Setting Property ID field shall be set to the value of the Sensor + * Setting Property ID field of the incoming message, the Sensor Setting Access + * field shall be set to the value of the Sensor Setting Access state field, and + * the Sensor Setting Raw field shall be omitted. + * + * TODO: What if the Sensor Setting Access is Prohibited? + */ + net_buf_simple_add_u8(msg, item->access); + if (ctx->recv_op != BLE_MESH_MODEL_OP_SENSOR_SETTING_SET || + item->access == SENSOR_SETTING_ACCESS_READ_WRITE) { + if (item->raw) { + net_buf_simple_add_mem(msg, item->raw->data, item->raw->len); + } + } + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void send_sensor_column_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, u16_t prop_id) +{ + struct bt_mesh_sensor_srv *srv = model->user_data; + struct bt_mesh_sensor_state *state = NULL; + struct net_buf_simple *msg = NULL; + bool optional = false; + u16_t length = 0U; + int i; + + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && + state->sensor_property_id == prop_id) { + length = SENSOR_PROPERTY_ID_LEN + state->series_column.raw_value_x->len; + /** + * TODO: column width & raw value y in Sensor Column Status are optional, + * here we need to add some conditions to decide whether put these two + * in the status message. + */ + if (optional) { + length += state->series_column.column_width->len + state->series_column.raw_value_y->len; + } + break; + } + } + if (i == srv->state_count) { + BT_WARN("%s, Sensor Property ID 0x%04x does not exist", __func__, prop_id); + length = SENSOR_PROPERTY_ID_LEN; + } + + msg = bt_mesh_alloc_buf(1 + length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + /** + * TODO: Sensor Column Get contains Raw Value X which identifies a column, + * we need to use this value to decide the column. + */ + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS); + net_buf_simple_add_le16(msg, prop_id); + if (i != srv->state_count) { + net_buf_simple_add_mem(msg, state->series_column.raw_value_x->data, + state->series_column.raw_value_x->len); + if (optional) { + net_buf_simple_add_mem(msg, state->series_column.column_width->data, + state->series_column.column_width->len); + net_buf_simple_add_mem(msg, state->series_column.raw_value_y->data, + state->series_column.raw_value_y->len); + } + } + + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + return; +} + +static void send_sensor_series_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, u16_t prop_id) +{ + struct bt_mesh_sensor_srv *srv = model->user_data; + struct bt_mesh_sensor_state *state = NULL; + struct net_buf_simple *msg = NULL; + bool optional = false; + u16_t length = 0U; + int i; + + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && + state->sensor_property_id == prop_id) { + length = SENSOR_PROPERTY_ID_LEN; + /* TODO: raw value x, column width & raw value y in Sensor Series + * Status are optional, here we need to add some conditions to + * decide whether put these three in the status message. + */ + if (optional) { + length += state->series_column.raw_value_x->len + + state->series_column.column_width->len + + state->series_column.raw_value_y->len; + } + break; + } + } + if (i == srv->state_count) { + BT_WARN("%s, Sensor Property ID 0x%04x does not exist", __func__, prop_id); + length = SENSOR_PROPERTY_ID_LEN; + } + + msg = bt_mesh_alloc_buf(1 + length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + + /** + * TODO: Sensor Series Get may contain Raw Value X1 and Raw Value X2 which + * identifies a starting column and a ending column, we need to use these + * values to decide the columns. + */ + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SENSOR_SERIES_STATUS); + net_buf_simple_add_le16(msg, prop_id); + if (i != srv->state_count) { + if (optional) { + net_buf_simple_add_mem(msg, state->series_column.raw_value_x->data, + state->series_column.raw_value_x->len); + net_buf_simple_add_mem(msg, state->series_column.column_width->data, + state->series_column.column_width->len); + net_buf_simple_add_mem(msg, state->series_column.raw_value_y->data, + state->series_column.raw_value_y->len); + } + } + + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + return; +} + +static void sensor_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + u16_t set_prop_id = INVALID_SENSOR_PROPERTY_ID; + u16_t prop_id = INVALID_SENSOR_PROPERTY_ID; + + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET: + case BLE_MESH_MODEL_OP_SENSOR_GET: + case BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET: + case BLE_MESH_MODEL_OP_SENSOR_SERIES_GET: { + struct bt_mesh_sensor_srv *srv = model->user_data; + if (srv->state_count == 0U || srv->states == NULL) { + BT_ERR("%s, Invalid Sensor Server state", __func__); + return; + } + if (ctx->recv_op == BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET || + ctx->recv_op == BLE_MESH_MODEL_OP_SENSOR_GET) { + bool get_all = buf->len ? false : true; + if (buf->len) { + prop_id = net_buf_simple_pull_le16(buf); + if (prop_id == INVALID_SENSOR_PROPERTY_ID) { + BT_ERR("%s, Prohibited Sensor Property ID 0x0000", __func__); + return; + } + } + if (ctx->recv_op == BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET) { + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_sensor_server_recv_get_msg_t get = { + .sensor_descriptor_get.op_en = !get_all, + .sensor_descriptor_get.id = prop_id, + }; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + } else { + send_sensor_descriptor_status(model, ctx, prop_id, get_all); + } + } else { + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_sensor_server_recv_get_msg_t get = { + .sensor_get.op_en = !get_all, + .sensor_get.id = prop_id, + }; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + } else { + send_sensor_data_status(model, ctx, prop_id, get_all); + } + } + } else { + prop_id = net_buf_simple_pull_le16(buf); + if (prop_id == INVALID_SENSOR_PROPERTY_ID) { + BT_ERR("%s, Prohibited Sensor Property ID 0x0000", __func__); + return; + } + if (ctx->recv_op == BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET) { + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_sensor_server_recv_get_msg_t get = { + .sensor_column_get.id = prop_id, + .sensor_column_get.raw_x = buf, + }; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + } else { + send_sensor_column_status(model, ctx, buf, prop_id); + } + } else { + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_sensor_server_recv_get_msg_t get = { + .sensor_series_get.id = prop_id, + .sensor_series_get.raw = buf, + }; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + } else { + send_sensor_series_status(model, ctx, buf, prop_id); + } + } + } + return; + } + case BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET: + case BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET: + case BLE_MESH_MODEL_OP_SENSOR_SETTING_GET: { + struct bt_mesh_sensor_setup_srv *srv = model->user_data; + if (srv->state_count == 0U || srv->states == NULL) { + BT_ERR("%s, Invalid Sensor Setup Server state", __func__); + return; + } + if (ctx->recv_op == BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET || + ctx->recv_op == BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET) { + prop_id = net_buf_simple_pull_le16(buf); + if (prop_id == INVALID_SENSOR_PROPERTY_ID) { + BT_ERR("%s, Prohibited Sensor Property ID 0x0000", __func__); + return; + } + if (ctx->recv_op == BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET) { + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_sensor_server_recv_get_msg_t get = { + .sensor_cadence_get.id = prop_id, + }; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + } else { + send_sensor_cadence_status(model, ctx, prop_id, false); + } + } else { + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_sensor_server_recv_get_msg_t get = { + .sensor_settings_get.id = prop_id, + }; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + } else { + send_sensor_settings_status(model, ctx, prop_id); + } + } + } else { + prop_id = net_buf_simple_pull_le16(buf); + if (prop_id == INVALID_SENSOR_PROPERTY_ID) { + BT_ERR("%s, Prohibited Sensor Property ID 0x0000", __func__); + return; + } + set_prop_id = net_buf_simple_pull_le16(buf); + if (set_prop_id == INVALID_SENSOR_PROPERTY_ID) { + BT_ERR("%s, Prohibited Sensor Setting Property ID 0x0000", __func__); + return; + } + + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_sensor_server_recv_get_msg_t get = { + .sensor_setting_get.id = prop_id, + .sensor_setting_get.setting_id = set_prop_id, + }; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + } else { + send_sensor_setting_status(model, ctx, prop_id, set_prop_id, false); + } + } + return; + } + default: + BT_WARN("%s, Unknown Sensor Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } +} + +static void sensor_cadence_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_sensor_setup_srv *srv = model->user_data; + bt_mesh_sensor_server_state_change_t change = {0}; + struct bt_mesh_sensor_state *state = NULL; + struct bt_mesh_model *sensor_model = NULL; + struct bt_mesh_elem *element = NULL; + u16_t prop_id = 0U, trigger_len = 0U; + u8_t val = 0U, divisor = 0U; + int i; + + if (srv == NULL || srv->state_count == 0U || srv->states == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + prop_id = net_buf_simple_pull_le16(buf); + if (prop_id == INVALID_SENSOR_PROPERTY_ID) { + BT_ERR("%s, Prohibited Sensor Property ID 0x0000", __func__); + return; + } + + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_sensor_server_recv_set_msg_t set = { + .sensor_cadence_set.id = prop_id, + .sensor_cadence_set.cadence = buf, + }; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && + state->sensor_property_id == prop_id) { + break; + } + } + if (i == srv->state_count || state->cadence == NULL) { + /* When the message is sent as a response to the Sensor Cadence Get message or + * a Sensor Cadence Set message with an unknown Property ID field or the Sensor + * Server does not support the Sensor Cadence state for the sensor referred by + * the Property ID, the following fields shall be omitted: + * • Fast Cadence Period Divisor + * • Status Trigger Type + * • Status Trigger Delta Down + * • Status Trigger Delta Up + * • Status Min Interval + * • Fast Cadence Low + * • Fast Cadence High + */ + send_sensor_cadence_status(model, ctx, prop_id, false); + return; + } + + val = net_buf_simple_pull_u8(buf); + divisor = val & BIT_MASK(7); + if (divisor > SENSOR_PERIOD_DIVISOR_MAX_VALUE) { + BT_ERR("%s, Prohibited Fast Cadence Period Divisor 0x%02x", __func__, divisor); + return; + } + state->cadence->period_divisor = divisor; + state->cadence->trigger_type = (val >> 7) & BIT_MASK(1); + + if (state->cadence->trigger_type == SENSOR_STATUS_TRIGGER_TYPE_CHAR) { + trigger_len = bt_mesh_get_dev_prop_len(prop_id); + } else { + trigger_len = SENSOR_STATUS_TRIGGER_UINT16_LEN; + } + if (buf->len < (trigger_len << 1) + SENSOR_STATUS_MIN_INTERVAL_LEN) { + BT_ERR("%s, Invalid Sensor Cadence Set length %d, trigger type %d", + __func__, buf->len + 3, state->cadence->trigger_type); + return; + } + + if (state->cadence->trigger_delta_down) { + net_buf_simple_reset(state->cadence->trigger_delta_down); + net_buf_simple_add_mem(state->cadence->trigger_delta_down, buf->data, trigger_len); + net_buf_simple_pull_mem(buf, trigger_len); + } + if (state->cadence->trigger_delta_up) { + net_buf_simple_reset(state->cadence->trigger_delta_up); + net_buf_simple_add_mem(state->cadence->trigger_delta_up, buf->data, trigger_len); + net_buf_simple_pull_mem(buf, trigger_len); + } + + /* The valid range for the Status Min Interval is 0–26 and other values are Prohibited. */ + val = net_buf_simple_pull_u8(buf); + if (val > SENSOR_STATUS_MIN_INTERVAL_MAX) { + BT_ERR("%s, Invalid Status Min Interval %d", __func__, val); + return; + } + state->cadence->min_interval = val; + + if (buf->len % 2) { + BT_ERR("%s, Different length of Fast Cadence Low & High, length %d", __func__, buf->len); + return; + } + if (buf->len) { + u8_t range_len = buf->len / 2; + if (state->cadence->fast_cadence_low) { + net_buf_simple_reset(state->cadence->fast_cadence_low); + net_buf_simple_add_mem(state->cadence->fast_cadence_low, buf->data, range_len); + net_buf_simple_pull_mem(buf, range_len); + } + if (state->cadence->fast_cadence_high) { + net_buf_simple_reset(state->cadence->fast_cadence_high); + net_buf_simple_add_mem(state->cadence->fast_cadence_high, buf->data, range_len); + net_buf_simple_pull_mem(buf, range_len); + } + } + + change.sensor_cadence_set.id = prop_id; + change.sensor_cadence_set.period_divisor = state->cadence->period_divisor; + change.sensor_cadence_set.trigger_type = state->cadence->trigger_type; + change.sensor_cadence_set.trigger_delta_down = state->cadence->trigger_delta_down; + change.sensor_cadence_set.trigger_delta_up = state->cadence->trigger_delta_up; + change.sensor_cadence_set.min_interval = state->cadence->min_interval; + change.sensor_cadence_set.fast_cadence_low = state->cadence->fast_cadence_low; + change.sensor_cadence_set.fast_cadence_high = state->cadence->fast_cadence_high; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET) { + send_sensor_cadence_status(model, ctx, prop_id, false); + } + send_sensor_cadence_status(model, ctx, prop_id, true); + + /* Try to find the corresponding Sensor Server Model */ + element = bt_mesh_model_elem(model); + sensor_model = bt_mesh_model_find(element, BLE_MESH_MODEL_ID_SENSOR_SRV); + if (sensor_model == NULL) { + BT_WARN("%s, Sensor Server Model does not exist in the element", __func__); + return; + } + + /** + * Based on the configured Sensor Cadence state, change Periodic Sensor + * status publication mechanism. + */ + update_sensor_periodic_pub(sensor_model, prop_id); + return; +} + +static void update_sensor_periodic_pub(struct bt_mesh_model *model, u16_t prop_id) +{ + struct bt_mesh_sensor_state *state = NULL; + struct bt_mesh_sensor_srv *srv = NULL; + int i; + + if (model->id != BLE_MESH_MODEL_ID_SENSOR_SRV) { + BT_ERR("%s, Not a Sensor Server Model", __func__); + return; + } + + srv = (struct bt_mesh_sensor_srv *)model->user_data; + if (srv == NULL || srv->state_count == 0U || srv->states == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + for (i = 0; i < srv->state_count; i++) { + state = &srv->states[i]; + if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && + state->sensor_property_id == prop_id) { + break; + } + } + if (i == srv->state_count) { + BT_ERR("%s, Sensor Property ID 0x%04x does not exist", __func__, prop_id); + return; + } + + if (state->cadence == NULL) { + BT_WARN("%s, Sensor Cadence state does not exist", __func__); + return; + } + + /** + * Currently when the device receives a Sensor Cadence Set message, + * a event will be callback to the application layer, and users can + * change the Sensor Data publication period in the event. And this + * is exactly what we do for the BQB test. + */ +} + +static void sensor_setting_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_sensor_setup_srv *srv = model->user_data; + bt_mesh_sensor_server_state_change_t change = {0}; + struct sensor_setting *item = NULL; + u16_t prop_id = 0U, set_prop_id = 0U; + + if (srv == NULL || srv->state_count == 0U || srv->states == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + prop_id = net_buf_simple_pull_le16(buf); + if (prop_id == INVALID_SENSOR_PROPERTY_ID) { + BT_ERR("%s, Prohibited Sensor Property ID 0x0000", __func__); + return; + } + + set_prop_id = net_buf_simple_pull_le16(buf); + if (set_prop_id == INVALID_SENSOR_PROPERTY_ID) { + BT_ERR("%s, Prohibited Sensor Setting Property ID 0x0000", __func__); + return; + } + + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_sensor_server_recv_set_msg_t set = { + .sensor_setting_set.id = prop_id, + .sensor_setting_set.setting_id = set_prop_id, + .sensor_setting_set.raw = buf, + }; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + item = find_sensor_setting(model, prop_id, set_prop_id); + if (item) { + if (item->access == SENSOR_SETTING_ACCESS_READ_WRITE && item->raw) { + net_buf_simple_reset(item->raw); + net_buf_simple_add_mem(item->raw, buf->data, + MIN(buf->len, item->raw->size)); + + change.sensor_setting_set.id = prop_id; + change.sensor_setting_set.setting_id = set_prop_id; + change.sensor_setting_set.value = item->raw; + bt_mesh_sensor_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_SENSOR_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + } + } + + if (ctx->recv_op == BLE_MESH_MODEL_OP_SENSOR_SETTING_SET) { + send_sensor_setting_status(model, ctx, prop_id, set_prop_id, false); + } + if (item) { + send_sensor_setting_status(model, ctx, prop_id, set_prop_id, true); + } + + return; +} + +/* message handlers (End) */ + +/* Mapping of message handlers for Sensor Server (0x1100) */ +const struct bt_mesh_model_op sensor_srv_op[] = { + { BLE_MESH_MODEL_OP_SENSOR_DESCRIPTOR_GET, 0, sensor_get }, + { BLE_MESH_MODEL_OP_SENSOR_GET, 0, sensor_get }, + { BLE_MESH_MODEL_OP_SENSOR_COLUMN_GET, 2, sensor_get }, + { BLE_MESH_MODEL_OP_SENSOR_SERIES_GET, 2, sensor_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Sensor Setup Server (0x1101) */ +const struct bt_mesh_model_op sensor_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_SENSOR_CADENCE_GET, 2, sensor_get }, + { BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET, 4, sensor_cadence_set }, + { BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK, 4, sensor_cadence_set }, + { BLE_MESH_MODEL_OP_SENSOR_SETTINGS_GET, 2, sensor_get }, + { BLE_MESH_MODEL_OP_SENSOR_SETTING_GET, 4, sensor_get }, + { BLE_MESH_MODEL_OP_SENSOR_SETTING_SET, 4, sensor_setting_set }, + { BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK, 4, sensor_setting_set }, + BLE_MESH_MODEL_OP_END, +}; + +static int check_sensor_server_init(struct bt_mesh_sensor_state *state_start, + const u8_t state_count) +{ + struct bt_mesh_sensor_state *state = NULL; + struct sensor_setting *setting = NULL; + int i, j; + + for (i = 0; i < state_count; i++) { + state = &state_start[i]; + if (state->sensor_property_id == INVALID_SENSOR_PROPERTY_ID) { + BT_ERR("%s, Invalid Sensor Property ID 0x%04x", __func__, state->sensor_property_id); + return -EINVAL; + } + if (state->setting_count == 0U || state->settings == NULL) { + BT_ERR("%s, Invalid Sensor Setting state", __func__); + return -EINVAL; + } + for (j = 0; j < state->setting_count; j++) { + setting = &state->settings[j]; + if (setting->property_id == INVALID_SENSOR_SETTING_PROPERTY_ID || setting->raw == NULL) { + BT_ERR("%s, Invalid Sensor Setting state internal parameter", __func__); + return -EINVAL; + } + } + if (state->cadence) { + if (state->cadence->trigger_delta_down == NULL || + state->cadence->trigger_delta_up == NULL || + state->cadence->fast_cadence_low == NULL || + state->cadence->fast_cadence_high == NULL) { + BT_ERR("%s, Invalid Sensor Cadence state", __func__); + return -EINVAL; + } + } + if (state->sensor_data.raw_value == NULL) { + BT_ERR("%s, Invalid Sensor Data state", __func__); + return -EINVAL; + } + if (state->series_column.raw_value_x == NULL || + state->series_column.column_width == NULL || + state->series_column.raw_value_y == NULL) { + BT_ERR("%s, Invalid Sensor Series column state", __func__); + return -EINVAL; + } + } + + return 0; +} + +static int sensor_server_init(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, No Sensor Server context provided, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_SENSOR_SRV: { + struct bt_mesh_sensor_srv *srv = model->user_data; + if (srv->state_count == 0U || srv->states == NULL) { + BT_ERR("%s, Invalid Sensor state parameter, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + if (check_sensor_server_init(srv->states, srv->state_count)) { + BT_ERR("%s, Invalid Sensor Server init value", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV: { + struct bt_mesh_sensor_setup_srv *srv = model->user_data; + if (srv->state_count == 0U || srv->states == NULL) { + BT_ERR("%s, Invalid parameter, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + if (check_sensor_server_init(srv->states, srv->state_count)) { + BT_ERR("%s, Invalid Sensor Setup Server init value", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + default: + BT_WARN("%s, Unknown Sensor Server Model, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + return 0; +} + +int bt_mesh_sensor_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Sensor Server has no publication support", __func__); + return -EINVAL; + } + + /* When this model is present on an element, the corresponding Sensor Setup + * Server model shall also be present. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV) == NULL) { + BT_WARN("%s, Sensor Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return sensor_server_init(model); +} + +int bt_mesh_sensor_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Sensor Setup Server has no publication support", __func__); + return -EINVAL; + } + + return sensor_server_init(model); +} + +static int sensor_server_deinit(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, No Sensor Server context provided, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + return 0; +} + +int bt_mesh_sensor_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Sensor Server has no publication support", __func__); + return -EINVAL; + } + + return sensor_server_deinit(model); +} + +int bt_mesh_sensor_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Sensor Setup Server has no publication support", __func__); + return -EINVAL; + } + + return sensor_server_deinit(model); +} diff --git a/components/bt/esp_ble_mesh/mesh_models/server/server_common.c b/components/bt/esp_ble_mesh/mesh_models/server/server_common.c new file mode 100644 index 0000000000..ee698e2d74 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/server_common.c @@ -0,0 +1,258 @@ +// Copyright 2017-2019 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 "mesh.h" +#include "access.h" +#include "mesh_common.h" +#include "generic_server.h" +#include "lighting_server.h" + +/** + * According to Mesh Model Spec: + * If the Transition Time field is not present and the Generic Default Transition + * Time state is supported, the Generic Default Transition Time state shall be + * used. Otherwise the transition shall be instantaneous. + */ +#define INSTANTANEOUS_TRANS_TIME 0 + +u8_t bt_mesh_get_default_trans_time(struct bt_mesh_model *model) +{ + /** + * 1. If a Generic Default Transition Time Server model is present on the + * main element of the model, that model instance shall be used. + * 2. If a Generic Default Transition Time Server model is not present on + * the main element of the model, then the Generic Default Transition + * Time Server model instance that is present on the element with the + * largest address that is smaller than the address of the main element + * of the node shall be used; if no model instance is present on any + * element with an address smaller than the address of the main element, + * then the Generic Default Transition Time Server is not supported. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + struct bt_mesh_gen_def_trans_time_srv *state = NULL; + u16_t primary_addr = bt_mesh_primary_addr(); + struct bt_mesh_model *srv = NULL; + + for (u16_t addr = element->addr; addr >= primary_addr; addr--) { + element = bt_mesh_elem_find(addr); + if (element) { + srv = bt_mesh_model_find(element, BLE_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV); + if (srv) { + state = (struct bt_mesh_gen_def_trans_time_srv *)srv->user_data; + if (state) { + return state->state.trans_time; + } + } + } + } + + return INSTANTANEOUS_TRANS_TIME; +} + +int bt_mesh_get_light_lc_trans_time(struct bt_mesh_model *model, u8_t *trans_time) +{ + struct bt_mesh_light_lc_srv *srv = NULL; + u32_t value = 0U; + + if (model == NULL || trans_time == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (model->id != BLE_MESH_MODEL_ID_LIGHT_LC_SRV) { + BT_ERR("%s, Not a Light LC Server", __func__); + return -EINVAL; + } + + srv = (struct bt_mesh_light_lc_srv *)model->user_data; + if (srv == NULL) { + BT_ERR("%s, Invalid Light LC Server user_data", __func__); + return -EINVAL; + } + + /** + * 1. Set transition time to 0x54 for BQB test case MESH/SR/LLC/BV-04-C. + * Light LC Property Set: 0x3C, 0x004E20 -> Light LC Time Run On + * Light LC Property Set: 0x37, 0x004E20 -> Light LC Time Fade On + * Light LC Property Set: 0x39, 0x004E20 -> Light LC Time Fade Standby Manual + * + * 2. Set transition time to 0x0 for BQB test case MESH/SR/LLC/BV-08-C. + * + * TODO: Based on Light LC state and choose property property value as the + * transition time. Currently directly use Light LC Time Run On property value. + * Unit: Millisecond, range: [0, 16777214(0xFFFFFE)] + */ + value = srv->lc->prop_state.time_run_on & 0xFFFFFF; + + /** + * Convert value into Default Transition Time state format. + * 0b00: 0 ~ 6.2s, 100 millisecond step resolution + * 0b01: 0 ~ 62s, 1 second step resolution + * 0b10: 0 ~ 620s, 10 seconds step resolution + * 0b11: 0 ~ 620m, 10 minutes step resolution + */ + if (value <= 6200) { + *trans_time = (0 << 6) | (value / 100); + } else if (value <= 62000) { + *trans_time = (1 << 6) | (value / 1000); + } else if (value <= 620000) { + *trans_time = (2 << 6) | (value / 10000); + } else { + *trans_time = (3 << 6) | (value / 600000); + } + + return 0; +} + +int bt_mesh_server_get_optional(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, + u8_t *trans_time, u8_t *delay, + bool *optional) +{ + if (model == NULL || buf == NULL || trans_time == NULL || + delay == NULL || optional == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + if (buf->len != 0x00 && buf->len != 0x02) { + BT_ERR("%s, Invalid optional message length %d", __func__, buf->len); + return -EINVAL; + } + + /* Currently we only get optional msg info which dst is set to a unicast address */ + if (!BLE_MESH_ADDR_IS_UNICAST(ctx->recv_dst)) { + *trans_time = 0U; + *delay = 0U; + *optional = false; + return 0; + } + + /* No optional fields are available */ + if (buf->len == 0x00) { + if (model->id == BLE_MESH_MODEL_ID_LIGHT_LC_SRV) { + /** + * Both messages(i.e. Light LC OnOff Set/Set Unack) may optionally include + * a Transition Time field indicating the transition time to the target state. + * If the Transition Time is not included, the Light LC Server shall use + * its appropriate transition times defined by the Light LC Property states. + */ + if (bt_mesh_get_light_lc_trans_time(model, trans_time)) { + BT_ERR("%s, Failed to get Light LC transition time", __func__); + return -EIO; + } + } else { + *trans_time = bt_mesh_get_default_trans_time(model); + } + *delay = 0U; + *optional = false; + return 0; + } + + /* Optional fields are available */ + *trans_time = net_buf_simple_pull_u8(buf); + if ((*trans_time & 0x3F) == 0x3F) { + BT_ERR("%s, Invalid Transaction Number of Steps 0x3F", __func__); + return -EINVAL; + } + + *delay = net_buf_simple_pull_u8(buf); + *optional = true; + return 0; +} + +void bt_mesh_server_alloc_ctx(struct k_work *work) +{ + /** + * This function is used to allocate memory for storing "struct bt_mesh_msg_ctx" + * of the received messages, because some server models will callback the "struct + * bt_mesh_msg_ctx" info to the application layer after a certain delay. + * Here we use the allocated heap memory to store the "struct bt_mesh_msg_ctx". + */ + __ASSERT(work, "%s, Invalid parameter", __func__); + if (!work->_reserved) { + work->_reserved = bt_mesh_calloc(sizeof(struct bt_mesh_msg_ctx)); + __ASSERT(work->_reserved, "%s, Failed to allocate memory", __func__); + } +} + +void bt_mesh_server_free_ctx(struct k_work *work) +{ + __ASSERT(work, "%s, Invalid parameter", __func__); + if (work->_reserved) { + bt_mesh_free(work->_reserved); + work->_reserved = NULL; + } +} + +bool bt_mesh_is_server_recv_last_msg(struct bt_mesh_last_msg_info *last, + u8_t tid, u16_t src, u16_t dst, s64_t *now) +{ + *now = k_uptime_get(); + + /* Currently we only compare msg info which dst is set to a unicast address */ + if (!BLE_MESH_ADDR_IS_UNICAST(dst)) { + return false; + } + + if (last->tid == tid && last->src == src && last->dst == dst && + (*now - last->timestamp <= K_SECONDS(6))) { + return true; + } + + return false; +} + +void bt_mesh_server_update_last_msg(struct bt_mesh_last_msg_info *last, + u8_t tid, u16_t src, u16_t dst, s64_t *now) +{ + /* Currently we only update msg info which dst is set to a unicast address */ + if (!BLE_MESH_ADDR_IS_UNICAST(dst)) { + return; + } + + last->tid = tid; + last->src = src; + last->dst = dst; + last->timestamp = *now; + return; +} + +struct net_buf_simple *bt_mesh_server_get_pub_msg(struct bt_mesh_model *model, u16_t msg_len) +{ + struct net_buf_simple *buf = NULL; + + if (model == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return NULL; + } + + if (model->pub == NULL || model->pub->msg == NULL || + model->pub->addr == BLE_MESH_ADDR_UNASSIGNED) { + BT_DBG("%s, Model 0x%04x has no publication support", __func__, model->id); + return NULL; + } + + buf = model->pub->msg; + if (buf->size < msg_len) { + BT_ERR("%s, Too small publication msg size %d, model 0x%04x", + __func__, buf->size, model->id); + return NULL; + } + + return buf; +} \ No newline at end of file diff --git a/components/bt/esp_ble_mesh/mesh_models/server/state_binding.c b/components/bt/esp_ble_mesh/mesh_models/server/state_binding.c new file mode 100644 index 0000000000..45dbadffd8 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/state_binding.c @@ -0,0 +1,340 @@ +/* Bluetooth: Mesh Generic OnOff, Generic Level, Lighting & Vendor Models + * + * Copyright (c) 2018 Vikrant More + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "model_opcode.h" +#include "state_binding.h" +#include "state_transition.h" + +#define MINDIFF (2.25e-308) + +static float bt_mesh_sqrt(float square) +{ + float root = 0.0, last = 0.0, diff = 0.0; + + root = square / 3.0; + diff = 1; + + if (square <= 0) { + return 0; + } + + do { + last = root; + root = (root + square / root) / 2.0; + diff = root - last; + } while (diff > MINDIFF || diff < -MINDIFF); + + return root; +} + +static s32_t bt_mesh_ceiling(float num) +{ + s32_t inum = (s32_t)num; + + if (num == (float)inum) { + return inum; + } + + return inum + 1; +} + +u16_t bt_mesh_convert_lightness_actual_to_linear(u16_t actual) +{ + float tmp = ((float) actual / UINT16_MAX); + + return bt_mesh_ceiling(UINT16_MAX * tmp * tmp); +} + +u16_t bt_mesh_convert_lightness_linear_to_actual(u16_t linear) +{ + return (u16_t) (UINT16_MAX * bt_mesh_sqrt(((float) linear / UINT16_MAX))); +} + +s16_t bt_mesh_convert_temperature_to_gen_level(u16_t temp, u16_t min, u16_t max) +{ + float tmp = (temp - min) * UINT16_MAX / (max - min); + return (s16_t) (tmp + INT16_MIN); +} + +u16_t bt_mesh_covert_gen_level_to_temperature(s16_t level, u16_t min, u16_t max) +{ + float diff = (float) (max - min) / UINT16_MAX; + u16_t tmp = (u16_t) ((level - INT16_MIN) * diff); + return (u16_t) (min + tmp); +} + +s16_t bt_mesh_convert_hue_to_level(u16_t hue) +{ + return (s16_t) (hue + INT16_MIN); +} + +u16_t bt_mesh_convert_level_to_hue(s16_t level) +{ + return (u16_t) (level - INT16_MIN); +} + +s16_t bt_mesh_convert_saturation_to_level(u16_t saturation) +{ + return (s16_t) (saturation + INT16_MIN); +} + +u16_t bt_mesh_convert_level_to_saturation(s16_t level) +{ + return (u16_t) (level - INT16_MIN); +} + +int bt_mesh_update_binding_state(struct bt_mesh_model *model, + bt_mesh_server_state_type_t type, + bt_mesh_server_state_value_t *value) +{ + if (model == NULL || model->user_data == NULL || + value == NULL || type > BIND_STATE_MAX) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } + + switch (type) { + case GENERIC_ONOFF_STATE: { + if (model->id != BLE_MESH_MODEL_ID_GEN_ONOFF_SRV) { + BT_ERR("%s, Not a Generic OnOff Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_gen_onoff_srv *srv = model->user_data; + bt_mesh_server_stop_transition(&srv->transition); + srv->state.onoff = value->gen_onoff.onoff; + gen_onoff_publish(model); + break; + } + case GENERIC_LEVEL_STATE: { + if (model->id != BLE_MESH_MODEL_ID_GEN_LEVEL_SRV) { + BT_ERR("%s, Not a Generic Level Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_gen_level_srv *srv = model->user_data; + bt_mesh_server_stop_transition(&srv->transition); + srv->state.level = value->gen_level.level; + gen_level_publish(model); + break; + } + case GENERIC_ONPOWERUP_STATE: { + if (model->id != BLE_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV) { + BT_ERR("%s, Not a Generic Power OnOff Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_gen_power_onoff_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Generic Power OnOff Server state", __func__); + return -EINVAL; + } + + srv->state->onpowerup = value->gen_onpowerup.onpowerup; + gen_onpowerup_publish(model); + break; + } + case GENERIC_POWER_ACTUAL_STATE: { + if (model->id != BLE_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV) { + BT_ERR("%s, Not a Generic Power Level Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_gen_power_level_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Generic Power Level Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->transition); + srv->state->power_actual = value->gen_power_actual.power; + /** + * Whenever the Generic Power Actual state is changed to a non-zero value + * as a result of a non-transactional message or a completed sequence of + * transactional messages, the value of the Generic Power Last state shall + * be set to the value of the Generic Power Actual state. + */ + if (srv->state->power_actual) { + srv->state->power_last = srv->state->power_actual; + } + gen_power_level_publish(model, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS); + break; + } + case LIGHT_LIGHTNESS_ACTUAL_STATE: { + if (model->id != BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV) { + BT_ERR("%s, Not a Light Lightness Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_light_lightness_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light Lightness Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->actual_transition); + srv->state->lightness_actual = value->light_lightness_actual.lightness; + /** + * Whenever the Light Lightness Actual state is changed with a non-transactional + * message or a completed sequence of transactional messages to a non-zero value, + * the value of the Light Lightness Last shall be set to the value of the Light + * Lightness Actual. + */ + if (srv->state->lightness_actual) { + srv->state->lightness_last = srv->state->lightness_actual; + } + light_lightness_publish(model, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS); + break; + } + case LIGHT_LIGHTNESS_LINEAR_STATE: { + if (model->id != BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV) { + BT_ERR("%s, Not a Light Lightness Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_light_lightness_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light Lightness Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->linear_transition); + srv->state->lightness_linear = value->light_lightness_linear.lightness; + light_lightness_publish(model, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS); + break; + } + case LIGHT_CTL_LIGHTNESS_STATE: { + if (model->id != BLE_MESH_MODEL_ID_LIGHT_CTL_SRV) { + BT_ERR("%s, Not a Light CTL Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_light_ctl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light CTL Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->transition); + srv->state->lightness = value->light_ctl_lightness.lightness; + light_ctl_publish(model, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS); + break; + } + case LIGHT_CTL_TEMP_DELTA_UV_STATE: { + if (model->id != BLE_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV) { + BT_ERR("%s, Not a Light CTL Temperature Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_light_ctl_temp_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light CTL Temperature Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->transition); + srv->state->temperature = value->light_ctl_temp_delta_uv.temperature; + srv->state->delta_uv = value->light_ctl_temp_delta_uv.delta_uv; + light_ctl_publish(model, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS); + break; + } + case LIGHT_HSL_LIGHTNESS_STATE: { + if (model->id != BLE_MESH_MODEL_ID_LIGHT_HSL_SRV) { + BT_ERR("%s, Not a Light HSL Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_light_hsl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->transition); + srv->state->lightness = value->light_hsl_lightness.lightness; + light_hsl_publish(model, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS); + break; + } + case LIGHT_HSL_HUE_STATE: { + if (model->id != BLE_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV) { + BT_ERR("%s, Not a Light HSL Hue Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_light_hsl_hue_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Hue Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->transition); + srv->state->hue = value->light_hsl_hue.hue; + light_hsl_publish(model, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS); + break; + } + case LIGHT_HSL_SATURATION_STATE: { + if (model->id != BLE_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV) { + BT_ERR("%s, Not a Light HSL Saturation Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_light_hsl_sat_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light HSL Saturation Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->transition); + srv->state->saturation = value->light_hsl_saturation.saturation; + light_hsl_publish(model, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS); + break; + } + case LIGHT_XYL_LIGHTNESS_STATE: { + if (model->id != BLE_MESH_MODEL_ID_LIGHT_XYL_SRV) { + BT_ERR("%s, Not a Light xyL Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_light_xyl_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Light xyL Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->transition); + srv->state->lightness = value->light_xyl_lightness.lightness; + light_xyl_publish(model, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS); + break; + } + case LIGHT_LC_LIGHT_ONOFF_STATE: { + if (model->id != BLE_MESH_MODEL_ID_LIGHT_LC_SRV) { + BT_ERR("%s, Not a Light LC Server Model, id 0x%04x", __func__, model->id); + return -EINVAL; + } + + struct bt_mesh_light_lc_srv *srv = model->user_data; + if (srv->lc == NULL) { + BT_ERR("%s, Invalid Light LC Server state", __func__); + return -EINVAL; + } + + bt_mesh_server_stop_transition(&srv->transition); + srv->lc->state.light_onoff = value->light_lc_light_onoff.onoff; + light_lc_publish(model, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS); + break; + } + default: + BT_WARN("%s, Unknown binding state type 0x%02x", __func__, type); + return -EINVAL; + } + + return 0; +} + diff --git a/components/bt/esp_ble_mesh/mesh_models/server/state_transition.c b/components/bt/esp_ble_mesh/mesh_models/server/state_transition.c new file mode 100644 index 0000000000..38c9f94fe2 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/state_transition.c @@ -0,0 +1,1020 @@ +/* Bluetooth: Mesh Generic OnOff, Generic Level, Lighting & Vendor Models + * + * Copyright (c) 2018 Vikrant More + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "model_opcode.h" +#include "state_transition.h" + +#include "btc_ble_mesh_generic_model.h" +#include "btc_ble_mesh_lighting_model.h" +#include "btc_ble_mesh_time_scene_model.h" +#include "btc_ble_mesh_sensor_model.h" + +/* Function to calculate Remaining Time (Start) */ + +void bt_mesh_server_calc_remain_time(struct bt_mesh_state_transition *transition) +{ + u8_t steps = 0U, resolution = 0U; + s32_t duration_remainder = 0; + s64_t now = 0; + + if (transition->just_started) { + transition->remain_time = transition->trans_time; + } else { + now = k_uptime_get(); + duration_remainder = transition->total_duration - + (now - transition->start_timestamp); + if (duration_remainder > 620000) { + /* > 620 seconds -> resolution = 0b11 [10 minutes] */ + resolution = 0x03; + steps = duration_remainder / 600000; + } else if (duration_remainder > 62000) { + /* > 62 seconds -> resolution = 0b10 [10 seconds] */ + resolution = 0x02; + steps = duration_remainder / 10000; + } else if (duration_remainder > 6200) { + /* > 6.2 seconds -> resolution = 0b01 [1 seconds] */ + resolution = 0x01; + steps = duration_remainder / 1000; + } else if (duration_remainder > 0) { + /* <= 6.2 seconds -> resolution = 0b00 [100 ms] */ + resolution = 0x00; + steps = duration_remainder / 100; + } else { + resolution = 0x00; + steps = 0x00; + } + + transition->remain_time = (resolution << 6) | steps; + } +} + +/* Function to calculate Remaining Time (End) */ + +static void tt_values_calculator(struct bt_mesh_state_transition *transition) +{ + u8_t steps_multiplier = 0U, resolution = 0U; + + resolution = (transition->trans_time >> 6); + steps_multiplier = (transition->trans_time & 0x3F); + + switch (resolution) { + case 0: /* 100ms */ + transition->total_duration = steps_multiplier * 100; + break; + case 1: /* 1 second */ + transition->total_duration = steps_multiplier * 1000; + break; + case 2: /* 10 seconds */ + transition->total_duration = steps_multiplier * 10000; + break; + case 3: /* 10 minutes */ + transition->total_duration = steps_multiplier * 600000; + break; + } + + transition->counter = ((float) transition->total_duration / 100); + + if (transition->counter > BLE_MESH_DEVICE_SPECIFIC_RESOLUTION) { + transition->counter = BLE_MESH_DEVICE_SPECIFIC_RESOLUTION; + } +} + +static void transition_time_values(struct bt_mesh_state_transition *transition, + u8_t trans_time, u8_t delay) +{ + transition->trans_time = trans_time; + transition->delay = delay; + + if (trans_time == 0U) { + return; + } + + tt_values_calculator(transition); + transition->quo_tt = transition->total_duration / transition->counter; +} + +void generic_onoff_tt_values(struct bt_mesh_gen_onoff_srv *srv, + u8_t trans_time, u8_t delay) +{ + return transition_time_values(&srv->transition, trans_time, delay); +} + +void generic_level_tt_values(struct bt_mesh_gen_level_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); + srv->tt_delta_level = + ((float) (srv->state.level - srv->state.target_level) / srv->transition.counter); +} + +void generic_power_level_tt_values(struct bt_mesh_gen_power_level_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); + srv->tt_delta_level = + ((float) (srv->state->power_actual - srv->state->target_power_actual) / srv->transition.counter); +} + +void light_lightness_actual_tt_values(struct bt_mesh_light_lightness_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->actual_transition, trans_time, delay); + srv->tt_delta_lightness_actual = + ((float) (srv->state->lightness_actual - srv->state->target_lightness_actual) / srv->actual_transition.counter); +} + +void light_lightness_linear_tt_values(struct bt_mesh_light_lightness_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->linear_transition, trans_time, delay); + srv->tt_delta_lightness_linear = + ((float) (srv->state->lightness_linear - srv->state->target_lightness_linear) / srv->linear_transition.counter); +} + +void light_ctl_tt_values(struct bt_mesh_light_ctl_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); + srv->tt_delta_lightness = + ((float) (srv->state->lightness - srv->state->target_lightness) / srv->transition.counter); + srv->tt_delta_temperature = + ((float) (srv->state->temperature - srv->state->target_temperature) / srv->transition.counter); + srv->tt_delta_delta_uv = + ((float) (srv->state->delta_uv - srv->state->target_delta_uv) / srv->transition.counter); +} + +void light_ctl_temp_tt_values(struct bt_mesh_light_ctl_temp_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); + srv->tt_delta_temperature = + ((float) (srv->state->temperature - srv->state->target_temperature) / srv->transition.counter); + srv->tt_delta_delta_uv = + ((float) (srv->state->delta_uv - srv->state->target_delta_uv) / srv->transition.counter); +} + +void light_hsl_tt_values(struct bt_mesh_light_hsl_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); + srv->tt_delta_lightness = + ((float) (srv->state->lightness - srv->state->target_lightness) / srv->transition.counter); + srv->tt_delta_hue = + ((float) (srv->state->hue - srv->state->target_hue) / srv->transition.counter); + srv->tt_delta_saturation = + ((float) (srv->state->saturation - srv->state->target_saturation) / srv->transition.counter); +} + +void light_hsl_hue_tt_values(struct bt_mesh_light_hsl_hue_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); + srv->tt_delta_hue = + ((float) (srv->state->hue - srv->state->target_hue) / srv->transition.counter); +} + +void light_hsl_sat_tt_values(struct bt_mesh_light_hsl_sat_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); + srv->tt_delta_saturation = + ((float) (srv->state->saturation - srv->state->target_saturation) / srv->transition.counter); +} + +void light_xyl_tt_values(struct bt_mesh_light_xyl_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); + srv->tt_delta_lightness = + ((float) (srv->state->lightness - srv->state->target_lightness) / srv->transition.counter); + srv->tt_delta_x = + ((float) (srv->state->x - srv->state->target_x) / srv->transition.counter); + srv->tt_delta_y = + ((float) (srv->state->y - srv->state->target_y) / srv->transition.counter); +} + +void light_lc_tt_values(struct bt_mesh_light_lc_srv *srv, + u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); +} + +void scene_tt_values(struct bt_mesh_scene_srv *srv, u8_t trans_time, u8_t delay) +{ + transition_time_values(&srv->transition, trans_time, delay); +} + +static void transition_timer_start(struct bt_mesh_state_transition *transition) +{ + transition->start_timestamp = k_uptime_get(); + k_delayed_work_submit_periodic(&transition->timer, K_MSEC(transition->quo_tt)); + bt_mesh_atomic_set_bit(transition->flag, BLE_MESH_TRANS_TIMER_START); +} + +static void transition_timer_stop(struct bt_mesh_state_transition *transition) +{ + k_delayed_work_cancel(&transition->timer); + bt_mesh_atomic_clear_bit(transition->flag, BLE_MESH_TRANS_TIMER_START); +} + +/* Timers related handlers & threads (Start) */ +void generic_onoff_work_handler(struct k_work *work) +{ + struct bt_mesh_gen_onoff_srv *srv = + CONTAINER_OF(work, struct bt_mesh_gen_onoff_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_gen_server_state_change_t change = {0}; + + if (srv == NULL || srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_generic_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.gen_onoff_set.onoff = srv->state.onoff; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + /** + * Because binary states cannot support transitions, when changing to + * 0x01 (On), the Generic OnOff state shall change immediately when + * the transition starts, and when changing to 0x00, the state shall + * change when the transition finishes. + */ + if (srv->state.target_onoff == BLE_MESH_STATE_ON) { + srv->state.onoff = BLE_MESH_STATE_ON; + change.gen_onoff_set.onoff = srv->state.onoff; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + } + transition_timer_start(&srv->transition); + } + + bt_mesh_generic_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->state.onoff = srv->state.target_onoff; + if (srv->state.target_onoff != BLE_MESH_STATE_ON) { + change.gen_onoff_set.onoff = srv->state.onoff; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + } + } + + gen_onoff_publish(srv->model); + + bt_mesh_generic_server_unlock(); + return; +} + +void generic_level_work_handler(struct k_work *work) +{ + struct bt_mesh_gen_level_srv *srv = + CONTAINER_OF(work, struct bt_mesh_gen_level_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_gen_server_state_change_t change = {0}; + + if (srv == NULL || srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_generic_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_GEN_LEVEL_SET: + case BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK: + change.gen_level_set.level = srv->state.level; + break; + case BLE_MESH_MODEL_OP_GEN_DELTA_SET: + case BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK: + change.gen_delta_set.level = srv->state.level; + break; + case BLE_MESH_MODEL_OP_GEN_MOVE_SET: + case BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK: + change.gen_move_set.level = srv->state.level; + break; + } + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->transition); + } + + bt_mesh_generic_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + srv->state.level -= srv->tt_delta_level; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->state.level = srv->state.target_level; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_GEN_LEVEL_SET: + case BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK: + change.gen_level_set.level = srv->state.level; + break; + case BLE_MESH_MODEL_OP_GEN_DELTA_SET: + case BLE_MESH_MODEL_OP_GEN_DELTA_SET_UNACK: + change.gen_delta_set.level = srv->state.level; + break; + case BLE_MESH_MODEL_OP_GEN_MOVE_SET: + case BLE_MESH_MODEL_OP_GEN_MOVE_SET_UNACK: + change.gen_move_set.level = srv->state.level; + break; + } + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + gen_level_publish(srv->model); + + bt_mesh_generic_server_unlock(); + return; +} + +void generic_power_level_work_handler(struct k_work *work) +{ + struct bt_mesh_gen_power_level_srv *srv = + CONTAINER_OF(work, struct bt_mesh_gen_power_level_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_gen_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_generic_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.gen_power_level_set.power = srv->state->power_actual; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->transition); + } + + bt_mesh_generic_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + srv->state->power_actual -= srv->tt_delta_level; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + + srv->state->power_actual = srv->state->target_power_actual; + /** + * Whenever the Generic Power Actual state is changed to a non-zero value + * as a result of a non-transactional message or a completed sequence of + * transactional messages, the value of the Generic Power Last state shall + * be set to the value of the Generic Power Actual state. + */ + if (srv->state->power_actual) { + srv->state->power_last = srv->state->power_actual; + } + } + + change.gen_power_level_set.power = srv->state->power_actual; + bt_mesh_generic_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_GENERIC_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + gen_power_level_publish(srv->model, BLE_MESH_MODEL_OP_GEN_POWER_LEVEL_STATUS); + + bt_mesh_generic_server_unlock(); + return; +} + +void light_lightness_actual_work_handler(struct k_work *work) +{ + struct bt_mesh_light_lightness_srv *srv = + CONTAINER_OF(work, struct bt_mesh_light_lightness_srv, actual_transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_light_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->actual_transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_light_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->actual_transition.timer.work._reserved; + + if (srv->actual_transition.just_started) { + srv->actual_transition.just_started = false; + if (srv->actual_transition.counter == 0U) { + change.lightness_set.lightness = srv->state->lightness_actual; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->actual_transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->actual_transition); + } + + bt_mesh_light_server_unlock(); + return; + } + + if (srv->actual_transition.counter != 0U) { + srv->actual_transition.counter--; + srv->state->lightness_actual -= srv->tt_delta_lightness_actual; + } + + if (srv->actual_transition.counter == 0U) { + transition_timer_stop(&srv->actual_transition); + + srv->state->lightness_actual = srv->state->target_lightness_actual; + /** + * Whenever the Light Lightness Actual state is changed with a non- + * transactional message or a completed sequence of transactional + * messages to a non-zero value, the value of the Light Lightness + * Last shall be set to the value of the Light Lightness Actual. + */ + if (srv->state->lightness_actual) { + srv->state->lightness_last = srv->state->lightness_actual; + } + } + + change.lightness_set.lightness = srv->state->lightness_actual; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + light_lightness_publish(srv->model, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS); + + bt_mesh_light_server_unlock(); + return; +} + +void light_lightness_linear_work_handler(struct k_work *work) +{ + struct bt_mesh_light_lightness_srv *srv = + CONTAINER_OF(work, struct bt_mesh_light_lightness_srv, linear_transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_light_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->linear_transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_light_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->linear_transition.timer.work._reserved; + + if (srv->linear_transition.just_started) { + srv->linear_transition.just_started = false; + if (srv->linear_transition.counter == 0U) { + change.lightness_linear_set.lightness = srv->state->lightness_linear; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->linear_transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->linear_transition); + } + + bt_mesh_light_server_unlock(); + return; + } + + if (srv->linear_transition.counter != 0U) { + srv->linear_transition.counter--; + srv->state->lightness_linear -= srv->tt_delta_lightness_linear; + } + + if (srv->linear_transition.counter == 0U) { + transition_timer_stop(&srv->linear_transition); + srv->state->lightness_linear = srv->state->target_lightness_linear; + } + + change.lightness_linear_set.lightness = srv->state->lightness_linear; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + light_lightness_publish(srv->model, BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS); + + bt_mesh_light_server_unlock(); + return; +} + +void light_ctl_work_handler(struct k_work *work) +{ + struct bt_mesh_light_ctl_srv *srv = + CONTAINER_OF(work, struct bt_mesh_light_ctl_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_light_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_light_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.ctl_set.lightness = srv->state->lightness; + change.ctl_set.temperature = srv->state->temperature; + change.ctl_set.delta_uv = srv->state->delta_uv; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->transition); + } + + bt_mesh_light_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + srv->state->lightness -= srv->tt_delta_lightness; + srv->state->temperature -= srv->tt_delta_temperature; + srv->state->delta_uv -= srv->tt_delta_delta_uv; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->state->lightness = srv->state->target_lightness; + srv->state->temperature = srv->state->target_temperature; + srv->state->delta_uv = srv->state->target_delta_uv; + } + + change.ctl_set.lightness = srv->state->lightness; + change.ctl_set.temperature = srv->state->temperature; + change.ctl_set.delta_uv = srv->state->delta_uv; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + light_ctl_publish(srv->model, BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS); + + bt_mesh_light_server_unlock(); + return; +} + +void light_ctl_temp_work_handler(struct k_work *work) +{ + struct bt_mesh_light_ctl_temp_srv *srv = + CONTAINER_OF(work, struct bt_mesh_light_ctl_temp_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_light_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_light_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.ctl_temp_set.temperature = srv->state->temperature; + change.ctl_temp_set.delta_uv = srv->state->delta_uv; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->transition); + } + + bt_mesh_light_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + srv->state->temperature -= srv->tt_delta_temperature; + srv->state->delta_uv -= srv->tt_delta_delta_uv; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->state->temperature = srv->state->target_temperature; + srv->state->delta_uv = srv->state->target_delta_uv; + } + + change.ctl_temp_set.temperature = srv->state->temperature; + change.ctl_temp_set.delta_uv = srv->state->delta_uv; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + light_ctl_publish(srv->model, BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_STATUS); + + bt_mesh_light_server_unlock(); + return; +} + +void light_hsl_work_handler(struct k_work *work) +{ + struct bt_mesh_light_hsl_srv *srv = + CONTAINER_OF(work, struct bt_mesh_light_hsl_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_light_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_light_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.hsl_set.lightness = srv->state->lightness; + change.hsl_set.hue = srv->state->hue; + change.hsl_set.saturation = srv->state->saturation; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->transition); + } + + bt_mesh_light_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + srv->state->lightness -= srv->tt_delta_lightness; + srv->state->hue -= srv->tt_delta_hue; + srv->state->saturation -= srv->tt_delta_saturation; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->state->lightness = srv->state->target_lightness; + srv->state->hue = srv->state->target_hue; + srv->state->saturation = srv->state->target_saturation; + } + + change.hsl_set.lightness = srv->state->lightness; + change.hsl_set.hue = srv->state->hue; + change.hsl_set.saturation = srv->state->saturation; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + light_hsl_publish(srv->model, BLE_MESH_MODEL_OP_LIGHT_HSL_STATUS); + + bt_mesh_light_server_unlock(); + return; +} + +void light_hsl_hue_work_handler(struct k_work *work) +{ + struct bt_mesh_light_hsl_hue_srv *srv = + CONTAINER_OF(work, struct bt_mesh_light_hsl_hue_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_light_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_light_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.hsl_hue_set.hue = srv->state->hue; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->transition); + } + + bt_mesh_light_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + srv->state->hue -= srv->tt_delta_hue; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->state->hue = srv->state->target_hue; + } + + change.hsl_hue_set.hue = srv->state->hue; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + light_hsl_publish(srv->model, BLE_MESH_MODEL_OP_LIGHT_HSL_HUE_STATUS); + + bt_mesh_light_server_unlock(); + return; +} + +void light_hsl_sat_work_handler(struct k_work *work) +{ + struct bt_mesh_light_hsl_sat_srv *srv = + CONTAINER_OF(work, struct bt_mesh_light_hsl_sat_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_light_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_light_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.hsl_saturation_set.saturation = srv->state->saturation; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->transition); + } + + bt_mesh_light_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + srv->state->saturation -= srv->tt_delta_saturation; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->state->saturation = srv->state->target_saturation; + } + + change.hsl_saturation_set.saturation = srv->state->saturation; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + light_hsl_publish(srv->model, BLE_MESH_MODEL_OP_LIGHT_HSL_SATURATION_STATUS); + + bt_mesh_light_server_unlock(); + return; +} + +void light_xyl_work_handler(struct k_work *work) +{ + struct bt_mesh_light_xyl_srv *srv = + CONTAINER_OF(work, struct bt_mesh_light_xyl_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_light_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_light_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.xyl_set.lightness = srv->state->lightness; + change.xyl_set.x = srv->state->x; + change.xyl_set.y = srv->state->y; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->transition); + } + + bt_mesh_light_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + srv->state->lightness -= srv->tt_delta_lightness; + srv->state->x -= srv->tt_delta_x; + srv->state->y -= srv->tt_delta_y; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->state->lightness = srv->state->target_lightness; + srv->state->x = srv->state->target_x; + srv->state->y = srv->state->target_y; + } + + change.xyl_set.lightness = srv->state->lightness; + change.xyl_set.x = srv->state->x; + change.xyl_set.y = srv->state->y; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + light_xyl_publish(srv->model, BLE_MESH_MODEL_OP_LIGHT_XYL_STATUS); + + bt_mesh_light_server_unlock(); + return; +} + +void light_lc_work_handler(struct k_work *work) +{ + struct bt_mesh_light_lc_srv *srv = + CONTAINER_OF(work, struct bt_mesh_light_lc_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_light_server_state_change_t change = {0}; + + if (srv == NULL || srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_light_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.lc_light_onoff_set.onoff = srv->lc->state.light_onoff; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + /** + * Because binary states cannot support transitions, when changing to + * 0x01 (On), the Generic OnOff state shall change immediately when + * the transition starts, and when changing to 0x00, the state shall + * change when the transition finishes. + */ + if (srv->lc->state.target_light_onoff == BLE_MESH_STATE_ON) { + srv->lc->state.light_onoff = BLE_MESH_STATE_ON; + bt_mesh_light_server_state_change_t change = { + .lc_light_onoff_set.onoff = srv->lc->state.light_onoff, + }; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + } + transition_timer_start(&srv->transition); + } + + bt_mesh_light_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->lc->state.light_onoff = srv->lc->state.target_light_onoff; + if (srv->lc->state.light_onoff != BLE_MESH_STATE_ON) { + change.lc_light_onoff_set.onoff = srv->lc->state.light_onoff; + bt_mesh_lighting_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_LIGHTING_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + } + } + + light_lc_publish(srv->model, BLE_MESH_MODEL_OP_LIGHT_LC_LIGHT_ONOFF_STATUS); + + bt_mesh_light_server_unlock(); + return; +} + +void scene_recall_work_handler(struct k_work *work) +{ + struct bt_mesh_scene_srv *srv = + CONTAINER_OF(work, struct bt_mesh_scene_srv, transition.timer.work); + struct bt_mesh_msg_ctx *ctx = NULL; + bt_mesh_time_scene_server_state_change_t change = {0}; + + if (srv == NULL || srv->state == NULL || + srv->transition.timer.work._reserved == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + bt_mesh_time_scene_server_lock(); + + ctx = (struct bt_mesh_msg_ctx *)srv->transition.timer.work._reserved; + + if (srv->transition.just_started) { + srv->transition.just_started = false; + if (srv->transition.counter == 0U) { + change.scene_recall.scene_number = srv->state->current_scene; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + bt_mesh_atomic_clear_bit(srv->transition.flag, BLE_MESH_TRANS_TIMER_START); + } else { + transition_timer_start(&srv->transition); + } + + bt_mesh_time_scene_server_unlock(); + return; + } + + if (srv->transition.counter != 0U) { + srv->transition.counter--; + } + + if (srv->transition.counter == 0U) { + transition_timer_stop(&srv->transition); + srv->state->current_scene = srv->state->target_scene; + srv->state->in_progress = false; + srv->state->target_scene = INVALID_SCENE_NUMBER; + } + + change.scene_recall.scene_number = srv->state->current_scene; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE, srv->model, ctx, (const u8_t *)&change, sizeof(change)); + + scene_publish(srv->model, ctx, BLE_MESH_MODEL_OP_SCENE_STATUS); + + bt_mesh_time_scene_server_unlock(); + return; +} + +/* Timers related handlers & threads (End) */ + +void bt_mesh_server_stop_transition(struct bt_mesh_state_transition *transition) +{ + memset(transition, 0x0, offsetof(struct bt_mesh_state_transition, flag)); + if (bt_mesh_atomic_test_and_clear_bit(transition->flag, BLE_MESH_TRANS_TIMER_START)) { + k_delayed_work_cancel(&transition->timer); + } +} + +void bt_mesh_server_start_transition(struct bt_mesh_state_transition *transition) +{ + if (transition->delay) { + k_delayed_work_submit(&transition->timer, K_MSEC(5 * transition->delay)); + bt_mesh_atomic_set_bit(transition->flag, BLE_MESH_TRANS_TIMER_START); + } else { + k_work_submit(&transition->timer.work); + } +} + +/* Messages handlers (End) */ diff --git a/components/bt/esp_ble_mesh/mesh_models/server/time_scene_server.c b/components/bt/esp_ble_mesh/mesh_models/server/time_scene_server.c new file mode 100644 index 0000000000..78caa680f8 --- /dev/null +++ b/components/bt/esp_ble_mesh/mesh_models/server/time_scene_server.c @@ -0,0 +1,1500 @@ +// Copyright 2017-2019 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 "access.h" +#include "transport.h" +#include "model_opcode.h" +#include "state_transition.h" + +#include "btc_ble_mesh_time_scene_model.h" + +static bt_mesh_mutex_t time_scene_server_lock; + +static void bt_mesh_time_scene_server_mutex_new(void) +{ + if (!time_scene_server_lock.mutex) { + bt_mesh_mutex_create(&time_scene_server_lock); + } +} + +static void bt_mesh_time_scene_server_mutex_free(void) +{ + bt_mesh_mutex_free(&time_scene_server_lock); +} + +void bt_mesh_time_scene_server_lock(void) +{ + bt_mesh_mutex_lock(&time_scene_server_lock); +} + +void bt_mesh_time_scene_server_unlock(void) +{ + bt_mesh_mutex_unlock(&time_scene_server_lock); +} + +/* message handlers (Start) */ + +/* Time Server & Time Setup Server message handlers */ +static void send_time_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish, u16_t opcode) +{ + struct net_buf_simple *msg = NULL; + u8_t zero[5] = {0}; + u8_t length = 1 + 10; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, opcode); + switch (opcode) { + case BLE_MESH_MODEL_OP_TIME_STATUS: + if (model->id == BLE_MESH_MODEL_ID_TIME_SRV) { + struct bt_mesh_time_srv *srv = model->user_data; + net_buf_simple_add_mem(msg, srv->state->time.tai_seconds, TAI_SECONDS_LEN); + if (memcmp(srv->state->time.tai_seconds, zero, TAI_SECONDS_LEN)) { + net_buf_simple_add_u8(msg, srv->state->time.subsecond); + /** + * Set the Uncertainty field to a value that is a sum of the value of + * the Uncertainty state and an estimated time it will take the message + * to be processed before being sent on the radio interface. + * + * TODO: how to estimate the processing time? + */ + net_buf_simple_add_u8(msg, srv->state->time.uncertainty); + net_buf_simple_add_le16(msg, + (srv->state->time.tai_utc_delta_curr << 1) | srv->state->time.time_authority); + net_buf_simple_add_u8(msg, srv->state->time.time_zone_offset_curr); + } + } else if (model->id == BLE_MESH_MODEL_ID_TIME_SETUP_SRV) { + struct bt_mesh_time_setup_srv *srv = model->user_data; + net_buf_simple_add_mem(msg, srv->state->time.tai_seconds, TAI_SECONDS_LEN); + if (memcmp(srv->state->time.tai_seconds, zero, TAI_SECONDS_LEN)) { + net_buf_simple_add_u8(msg, srv->state->time.subsecond); + net_buf_simple_add_u8(msg, srv->state->time.uncertainty); + net_buf_simple_add_le16(msg, + (srv->state->time.tai_utc_delta_curr << 1) | srv->state->time.time_authority); + net_buf_simple_add_u8(msg, srv->state->time.time_zone_offset_curr); + } + } + break; + case BLE_MESH_MODEL_OP_TIME_ZONE_STATUS: + if (model->id == BLE_MESH_MODEL_ID_TIME_SRV) { + struct bt_mesh_time_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->time.time_zone_offset_curr); + net_buf_simple_add_u8(msg, srv->state->time.time_zone_offset_new); + net_buf_simple_add_mem(msg, srv->state->time.tai_zone_change, TAI_OF_ZONE_CHANGE_LEN); + } else if (model->id == BLE_MESH_MODEL_ID_TIME_SETUP_SRV) { + struct bt_mesh_time_setup_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->time.time_zone_offset_curr); + net_buf_simple_add_u8(msg, srv->state->time.time_zone_offset_new); + net_buf_simple_add_mem(msg, srv->state->time.tai_zone_change, TAI_OF_ZONE_CHANGE_LEN); + } + break; + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS: + if (model->id == BLE_MESH_MODEL_ID_TIME_SRV) { + struct bt_mesh_time_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->time.tai_utc_delta_curr); + net_buf_simple_add_le16(msg, srv->state->time.tai_utc_delta_new); + net_buf_simple_add_mem(msg, srv->state->time.tai_delta_change, TAI_OF_DELTA_CHANGE_LEN); + } else if (model->id == BLE_MESH_MODEL_ID_TIME_SETUP_SRV) { + struct bt_mesh_time_setup_srv *srv = model->user_data; + net_buf_simple_add_le16(msg, srv->state->time.tai_utc_delta_curr); + net_buf_simple_add_le16(msg, srv->state->time.tai_utc_delta_new); + net_buf_simple_add_mem(msg, srv->state->time.tai_delta_change, TAI_OF_DELTA_CHANGE_LEN); + } + break; + case BLE_MESH_MODEL_OP_TIME_ROLE_STATUS: { + struct bt_mesh_time_setup_srv *srv = model->user_data; + net_buf_simple_add_u8(msg, srv->state->time_role); + break; + } + default: + BT_WARN("%s, Unknown Time status opcode 0x%04x", __func__, opcode); + if (publish == false) { + bt_mesh_free_buf(msg); + } + return; + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void time_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_server_rsp_ctrl *rsp_ctrl = NULL; + u8_t zero[5] = {0}; + u16_t opcode = 0U, val = 0U; + u8_t prev_ttl = 0U; + + if (model->user_data == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_TIME_SRV: { + struct bt_mesh_time_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Time Server state", __func__); + return; + } + rsp_ctrl = &srv->rsp_ctrl; + break; + } + case BLE_MESH_MODEL_ID_TIME_SETUP_SRV: { + struct bt_mesh_time_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Time Setup Server state", __func__); + return; + } + rsp_ctrl = &srv->rsp_ctrl; + break; + } + default: + BT_ERR("%s, Invalid Time Server 0x%04x", __func__, model->id); + return; + } + + if (rsp_ctrl->get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + if (ctx->recv_op != BLE_MESH_MODEL_OP_TIME_STATUS) { + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_TIME_GET: + opcode = BLE_MESH_MODEL_OP_TIME_STATUS; + break; + case BLE_MESH_MODEL_OP_TIME_STATUS: { + struct bt_mesh_time_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, Invalid Time Server state", __func__); + return; + } + if (srv->state->time_role != TIME_RELAY && + srv->state->time_role != TIME_CLINET) { + /** + * If the value of the Time Role state of the element is 0x00 (None) or + * 0x01 (Time Authority), the message shall be ignored. + */ + return; + } + if (rsp_ctrl->status_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_status_msg_t status = {0}; + memcpy(status.time_status.tai_seconds, buf->data, TAI_SECONDS_LEN); + net_buf_simple_pull(buf, TAI_SECONDS_LEN); + if (memcmp(status.time_status.tai_seconds, zero, TAI_SECONDS_LEN)) { + if (buf->len != TAI_SECONDS_LEN) { + BT_ERR("%s, Invalid Time Status length %d", __func__, buf->len + TAI_SECONDS_LEN); + return; + } + status.time_status.subsecond = net_buf_simple_pull_u8(buf); + status.time_status.uncertainty = net_buf_simple_pull_u8(buf); + val = net_buf_simple_pull_le16(buf); + status.time_status.time_authority = val & BIT(0); + status.time_status.tai_utc_delta = (val >> 1) & BIT_MASK(15); + status.time_status.time_zone_offset = net_buf_simple_pull_u8(buf); + } + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_STATUS_MSG, model, ctx, (const u8_t *)&status, sizeof(status)); + return; + } + memcpy(srv->state->time.tai_seconds, buf->data, TAI_SECONDS_LEN); + net_buf_simple_pull(buf, TAI_SECONDS_LEN); + /** + * If the TAI Seconds field is 0x0000000000 the Subsecond, Uncertainty, + * Time Authority, TAI-UTC Delta and Time Zone Offset fields shall be + * omitted; otherwise these fields shall be present. + */ + if (memcmp(srv->state->time.tai_seconds, zero, TAI_SECONDS_LEN)) { + if (buf->len != TAI_SECONDS_LEN) { + BT_ERR("%s, Invalid Time Status length %d", __func__, buf->len + TAI_SECONDS_LEN); + return; + } + srv->state->time.subsecond = net_buf_simple_pull_u8(buf); + srv->state->time.uncertainty = net_buf_simple_pull_u8(buf); + val = net_buf_simple_pull_le16(buf); + srv->state->time.tai_utc_delta_curr = (val >> 1) & BIT_MASK(15); + srv->state->time.time_zone_offset_curr = net_buf_simple_pull_u8(buf); + } + + bt_mesh_time_scene_server_state_change_t change = {0}; + memcpy(change.time_status.tai_seconds, srv->state->time.tai_seconds, TAI_SECONDS_LEN); + change.time_status.subsecond = srv->state->time.subsecond; + change.time_status.uncertainty = srv->state->time.uncertainty; + change.time_status.time_authority = srv->state->time.time_authority; + change.time_status.tai_utc_delta_curr = srv->state->time.subsecond; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (model->pub == NULL || model->pub->msg == NULL || + model->pub->addr == BLE_MESH_ADDR_UNASSIGNED) { + return; + } + prev_ttl = model->pub->ttl; + if (srv->state->time_role == TIME_RELAY) { + /** + * Shall publish a Time Status message using TTL = 0 if the value of the + * Time Role state is 0x02 (Time Relay) and the Publish Address for the + * Time Server model is not set to unassigned address. + */ + model->pub->ttl = 0U; + } + send_time_status(model, NULL, true, BLE_MESH_MODEL_OP_TIME_STATUS); + /* Restore model publication ttl value */ + model->pub->ttl = prev_ttl; + return; + } + case BLE_MESH_MODEL_OP_TIME_ZONE_GET: + opcode = BLE_MESH_MODEL_OP_TIME_ZONE_STATUS; + break; + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_GET: + opcode = BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS; + break; + case BLE_MESH_MODEL_OP_TIME_ROLE_GET: + opcode = BLE_MESH_MODEL_OP_TIME_ROLE_STATUS; + break; + default: + BT_WARN("%s, Unknown Time Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + send_time_status(model, ctx, false, opcode); + return; +} + +static void time_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_time_setup_srv *srv = model->user_data; + bt_mesh_time_scene_server_state_change_t change = {0}; + u16_t opcode = 0U, val = 0U; + u8_t role = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_TIME_SET: + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_set_msg_t set = {0}; + memcpy(set.time_set.tai_seconds, buf->data, TAI_SECONDS_LEN); + net_buf_simple_pull(buf, TAI_SECONDS_LEN); + set.time_set.subsecond = net_buf_simple_pull_u8(buf); + set.time_set.uncertainty = net_buf_simple_pull_u8(buf); + val = net_buf_simple_pull_le16(buf); + set.time_set.time_authority = val & BIT(0); + set.time_set.tai_utc_delta = (val >> 1) & BIT_MASK(15); + set.time_set.time_zone_offset = net_buf_simple_pull_u8(buf); + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + memcpy(srv->state->time.tai_seconds, buf->data, TAI_SECONDS_LEN); + net_buf_simple_pull(buf, TAI_SECONDS_LEN); + srv->state->time.subsecond = net_buf_simple_pull_u8(buf); + srv->state->time.uncertainty = net_buf_simple_pull_u8(buf); + val = net_buf_simple_pull_le16(buf); + srv->state->time.time_authority = val & BIT(0); + srv->state->time.tai_utc_delta_curr = (val >> 1) & BIT_MASK(15); + srv->state->time.time_zone_offset_curr = net_buf_simple_pull_u8(buf); + opcode = BLE_MESH_MODEL_OP_TIME_STATUS; + break; + case BLE_MESH_MODEL_OP_TIME_ZONE_SET: + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_set_msg_t set = {0}; + set.time_zone_set.time_zone_offset_new = net_buf_simple_pull_u8(buf); + memcpy(set.time_zone_set.tai_zone_change, buf->data, TAI_OF_ZONE_CHANGE_LEN); + net_buf_simple_pull(buf, TAI_OF_ZONE_CHANGE_LEN); + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + srv->state->time.time_zone_offset_new = net_buf_simple_pull_u8(buf); + memcpy(srv->state->time.tai_zone_change, buf->data, TAI_OF_ZONE_CHANGE_LEN); + net_buf_simple_pull(buf, TAI_OF_ZONE_CHANGE_LEN); + opcode = BLE_MESH_MODEL_OP_TIME_ZONE_STATUS; + break; + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET: + val = net_buf_simple_pull_le16(buf); + if ((val >> 15) & BIT(0)) { + BT_ERR("%s, Invalid Padding value 1", __func__); + return; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_set_msg_t set = {0}; + set.tai_utc_delta_set.tai_utc_delta_new = val & BIT_MASK(15); + memcpy(set.tai_utc_delta_set.tai_delta_change, buf->data, TAI_OF_DELTA_CHANGE_LEN); + net_buf_simple_pull(buf, TAI_OF_DELTA_CHANGE_LEN); + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + srv->state->time.tai_utc_delta_new = val & BIT_MASK(15); + memcpy(srv->state->time.tai_delta_change, buf->data, TAI_OF_DELTA_CHANGE_LEN); + net_buf_simple_pull(buf, TAI_OF_DELTA_CHANGE_LEN); + opcode = BLE_MESH_MODEL_OP_TAI_UTC_DELTA_STATUS; + break; + case BLE_MESH_MODEL_OP_TIME_ROLE_SET: + role = net_buf_simple_pull_u8(buf); + if (role > TIME_CLINET) { + BT_ERR("%s, Invalid Time Role 0x%02x", __func__, role); + return; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_set_msg_t set = { + .time_role_set.time_role = role, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + srv->state->time_role = role; + opcode = BLE_MESH_MODEL_OP_TIME_ROLE_STATUS; + break; + default: + BT_ERR("%s, Unknown Time Set opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_TIME_SET: + memcpy(change.time_set.tai_seconds, srv->state->time.tai_seconds, TAI_SECONDS_LEN); + change.time_set.subsecond = srv->state->time.subsecond; + change.time_set.uncertainty = srv->state->time.uncertainty; + change.time_set.time_authority = srv->state->time.time_authority; + change.time_set.tai_utc_delta_curr = srv->state->time.subsecond; + break; + case BLE_MESH_MODEL_OP_TIME_ZONE_SET: + change.time_zone_set.time_zone_offset_new = srv->state->time.time_zone_offset_new; + memcpy(change.time_zone_set.tai_zone_change, srv->state->time.tai_zone_change, TAI_OF_ZONE_CHANGE_LEN); + break; + case BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET: + change.tai_utc_delta_set.tai_utc_delta_new = srv->state->time.tai_utc_delta_new; + memcpy(change.tai_utc_delta_set.tai_delta_change, srv->state->time.tai_delta_change, TAI_OF_DELTA_CHANGE_LEN); + break; + case BLE_MESH_MODEL_OP_TIME_ROLE_SET: + change.time_role_set.role = srv->state->time_role; + break; + default: + return; + } + + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + /* Send corresponding time status message */ + send_time_status(model, ctx, false, opcode); + return; +} + +/* Scene Server & Scene Setup Server message handlers */ +static void send_scene_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + bool publish) +{ + struct bt_mesh_scene_srv *srv = model->user_data; + struct net_buf_simple *msg = NULL; + u8_t length = 1 + 6; + + if (publish == false) { + msg = bt_mesh_alloc_buf(length + BLE_MESH_SERVER_TRANS_MIC_SIZE); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, length); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SCENE_STATUS); + /** + * If the message is sent as a reply to the Scene Recall message, the + * Status Code field identifies the result of the related operation; + * otherwise, the Status Code field shall be set to Success. + */ + if (ctx->recv_op == BLE_MESH_MODEL_OP_SCENE_GET) { + net_buf_simple_add_u8(msg, SCENE_SUCCESS); + } else { + net_buf_simple_add_u8(msg, srv->state->status_code); + } + net_buf_simple_add_le16(msg, srv->state->current_scene); + /** + * When an element is in the process of changing the Scene state, the + * Target Scene field identifies the target Scene Number of the target + * Scene state the element is to reach. + * When an element is not in the process of changing the Scene state, + * the Target Scene field shall be omitted. + */ + if (srv->transition.counter) { + bt_mesh_server_calc_remain_time(&srv->transition); + net_buf_simple_add_le16(msg, srv->state->target_scene); + net_buf_simple_add_u8(msg, srv->transition.remain_time); + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void send_scene_register_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u8_t status_code, bool publish) +{ + struct bt_mesh_scene_setup_srv *srv = model->user_data; + struct scene_register *scene = NULL; + struct net_buf_simple *msg = NULL; + u16_t total_len = 9U; + int i; + + if (ctx == NULL && publish == false) { + BT_ERR("%s, Invalid parameter", __func__); + return; + } + + if (publish == false) { + msg = bt_mesh_alloc_buf(MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)); + if (msg == NULL) { + BT_ERR("%s, Failed to allocate memory", __func__); + return; + } + } else { + msg = bt_mesh_server_get_pub_msg(model, 5); + if (msg == NULL) { + return; + } + } + + bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SCENE_REGISTER_STATUS); + net_buf_simple_add_u8(msg, status_code); + net_buf_simple_add_le16(msg, srv->state->current_scene); + + for (i = 0; i < srv->state->scene_count; i++) { + scene = &srv->state->scenes[i]; + if (scene->scene_number != INVALID_SCENE_NUMBER) { + total_len += SCENE_NUMBER_LEN; + if ((publish == false && total_len > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SERVER_RSP_MAX_LEN)) || + (publish == true && total_len > msg->size + BLE_MESH_SERVER_TRANS_MIC_SIZE)) { + /* Add this in case the message is too long */ + break; + } + net_buf_simple_add_le16(msg, scene->scene_number); + } + } + + if (publish == false) { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, msg, NULL, NULL)); + bt_mesh_free_buf(msg); + } else { + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_publish(model)); + } + return; +} + +static void scene_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_scene_srv *srv = model->user_data; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_SCENE_GET: + send_scene_status(model, ctx, false); + return; + case BLE_MESH_MODEL_OP_SCENE_REGISTER_GET: + /** + * When a Scene Server receives a Scene Register Get message, it shall + * respond with a Scene Register Status message, setting the Status + * Code field to Success. + */ + send_scene_register_status(model, ctx, SCENE_SUCCESS, false); + return; + default: + BT_WARN("%s, Unknown Scene Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } +} + +void scene_publish(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, u16_t opcode) +{ + struct bt_mesh_scene_srv *srv = model->user_data; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + send_scene_status(model, ctx, true); + return; +} + +static void scene_recall(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_scene_srv *srv = model->user_data; + struct scene_register *scene = NULL; + u8_t tid = 0U, trans_time = 0U, delay = 0U; + u16_t scene_number = 0U; + bool optional = false; + s64_t now = 0; + int i; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + scene_number = net_buf_simple_pull_le16(buf); + if (scene_number == INVALID_SCENE_NUMBER) { + BT_ERR("%s, Invalid Scene Number 0x0000", __func__); + return; + } + tid = net_buf_simple_pull_u8(buf); + + if (bt_mesh_server_get_optional(model, ctx, buf, &trans_time, &delay, &optional)) { + return; + } + + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_set_msg_t set = { + .scene_recall.op_en = optional, + .scene_recall.scene_number = scene_number, + .scene_recall.tid = tid, + .scene_recall.trans_time = trans_time, + .scene_recall.delay = delay, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + for (i = 0; i < srv->state->scene_count; i++) { + scene = &srv->state->scenes[i]; + if (scene->scene_number == scene_number) { + break; + } + } + if (i == srv->state->scene_count) { + BT_WARN("%s, Scene Number 0x%04x not exist", __func__, scene_number); + srv->state->status_code = SCENE_NOT_FOUND; + if (ctx->recv_op == BLE_MESH_MODEL_OP_SCENE_RECALL) { + send_scene_status(model, ctx, false); + } + send_scene_status(model, ctx, true); + return; + } + srv->state->status_code = SCENE_SUCCESS; + + /* Mesh Model Spec doesn't mention about this operation. */ + if (bt_mesh_is_server_recv_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now)) { + if (ctx->recv_op == BLE_MESH_MODEL_OP_SCENE_RECALL) { + send_scene_status(model, ctx, false); + } + send_scene_status(model, ctx, true); + /* In this condition, no event will be callback to application layer */ + return; + } + + bt_mesh_time_scene_server_lock(); + + bt_mesh_server_stop_transition(&srv->transition); + bt_mesh_server_update_last_msg(&srv->last, tid, ctx->addr, ctx->recv_dst, &now); + + srv->state->in_progress = false; + /** + * When the scene transition is not in progress, the value of the Target + * Scene state shall be set to 0x0000. + */ + srv->state->target_scene = INVALID_SCENE_NUMBER; + + /** + * If the target state is equal to the current state, the transition + * shall not be started and is considered complete. + */ + if (srv->state->current_scene != scene_number) { + scene_tt_values(srv, trans_time, delay); + } else { + if (ctx->recv_op == BLE_MESH_MODEL_OP_SCENE_RECALL) { + send_scene_status(model, ctx, false); + } + send_scene_status(model, ctx, true); + + bt_mesh_time_scene_server_state_change_t change = { + .scene_recall.scene_number = scene_number, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + bt_mesh_time_scene_server_unlock(); + return; + } + + /* Copy the ctx of the received message */ + if (srv->transition.timer.work._reserved) { + memcpy(srv->transition.timer.work._reserved, ctx, sizeof(struct bt_mesh_msg_ctx)); + } + + /* For Instantaneous Transition */ + if (srv->transition.counter == 0U) { + srv->state->current_scene = scene_number; + } else { + /** + * When a scene transition is in progress, the value of the Current + * Scene state shall be set to 0x0000. + */ + srv->state->in_progress = true; + srv->state->current_scene = INVALID_SCENE_NUMBER; + srv->state->target_scene = scene_number; + } + + srv->transition.just_started = true; + if (ctx->recv_op == BLE_MESH_MODEL_OP_SCENE_RECALL) { + send_scene_status(model, ctx, false); + } + send_scene_status(model, ctx, true); + + bt_mesh_time_scene_server_unlock(); + + bt_mesh_server_start_transition(&srv->transition); + return; +} + +static void scene_action(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_scene_setup_srv *srv = model->user_data; + struct scene_register *scene = NULL; + u16_t scene_number = 0U; + int i; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + scene_number = net_buf_simple_pull_le16(buf); + if (scene_number == INVALID_SCENE_NUMBER) { + BT_ERR("%s, Invalid Scene number 0x0000", __func__); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_SCENE_STORE: + case BLE_MESH_MODEL_OP_SCENE_STORE_UNACK: { + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_set_msg_t set = { + .scene_store.scene_number = scene_number, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + /* Try to find a matching Scene Number */ + for (i = 0; i < srv->state->scene_count; i++) { + scene = &srv->state->scenes[i]; + if (scene->scene_number == scene_number) { + srv->state->status_code = SCENE_SUCCESS; + srv->state->current_scene = scene_number; + break; + } + } + /* Try to find a unset entry if no matching Scene Number is found */ + if (i == srv->state->scene_count) { + BT_DBG("%s, No matching Scene Number 0x%04x found", __func__, scene_number); + for (i = 0; i < srv->state->scene_count; i++) { + scene = &srv->state->scenes[i]; + if (scene->scene_number == INVALID_SCENE_NUMBER) { + scene->scene_number = scene_number; + srv->state->status_code = SCENE_SUCCESS; + srv->state->current_scene = scene_number; + break; + } + } + if (i == srv->state->scene_count) { + BT_WARN("%s, Scene Register full", __func__); + srv->state->status_code = SCENE_REG_FULL; + /* Get the Scene Number of the currently active scene */ + for (i = 0; i < srv->state->scene_count; i++) { + scene = &srv->state->scenes[i]; + if (scene->scene_number != INVALID_SCENE_NUMBER) { + srv->state->current_scene = scene->scene_number; + break; + } + } + if (i == srv->state->scene_count) { + /* A value of 0x0000 when no scene is active */ + srv->state->current_scene = INVALID_SCENE_NUMBER; + } + } + } + + if (srv->state->in_progress == true) { + /** + * When the scene transition is in progress and a new Scene Number is + * stored in the Scene Register as a result of Scene Store operation, + * the Target Scene state shall be set to the new Scene Number. + */ + srv->state->target_scene = scene_number; + } + if (srv->state->status_code == SCENE_SUCCESS) { + bt_mesh_time_scene_server_state_change_t change = { + .scene_store.scene_number = scene_number, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + } + break; + } + case BLE_MESH_MODEL_OP_SCENE_DELETE: + case BLE_MESH_MODEL_OP_SCENE_DELETE_UNACK: { + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_set_msg_t set = { + .scene_delete.scene_number = scene_number, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + for (i = 0; i < srv->state->scene_count; i++) { + scene = &srv->state->scenes[i]; + if (scene->scene_number == scene_number) { + scene->scene_number = INVALID_SCENE_NUMBER; + break; + } + } + if (i == srv->state->scene_count) { + BT_WARN("%s, Scene Number 0x%04x not exist", __func__, scene_number); + /** + * When a Scene Server receives a Scene Delete message with the Scene + * Number value that does not match a Scene Number stored within the + * Scene Register state, it shall respond with the Scene Register + * Status message, setting the Status Code field to Success. + */ + } + srv->state->status_code = SCENE_SUCCESS; + if (srv->state->current_scene == scene_number) { + /** + * When the Current Scene Number is deleted from a Scene Register state + * as a result of Scene Delete operation, the Current Scene state shall + * be set to 0x0000. + */ + srv->state->current_scene = INVALID_SCENE_NUMBER; + } else { + /** + * MMDL/SR/SCES/BV-02-C requires response with Current Scene set to the + * latest Scene Number, but this is not mentioned in the spec. + * + * TODO: Do we need a timestamp for each newly added scene? + */ + for (i = srv->state->scene_count; i > 0; i--) { + scene = &srv->state->scenes[i - 1]; + if (scene->scene_number != INVALID_SCENE_NUMBER) { + srv->state->current_scene = scene->scene_number; + break; + } + } + if (i == 0) { + /* A value of 0x0000 when no scene is active */ + srv->state->current_scene = INVALID_SCENE_NUMBER; + } + } + + if (srv->state->target_scene == scene_number && + srv->state->in_progress == true) { + /** + * When the scene transition is in progress and the target Scene Number + * is deleted from a Scene Register state as a result of Scene Delete + * operation, the Target Scene state shall be set to 0x0000. + */ + srv->state->target_scene = INVALID_SCENE_NUMBER; + + /** + * When a scene is deleted when a scene transition to the deleted Scene + * Number is in progress, the scene transition shall be terminated, but + * individual model transitions shall not be terminated. + */ + struct bt_mesh_scene_srv *scene_srv = NULL; + struct bt_mesh_model *scene_model = NULL; + + scene_model = bt_mesh_model_find(bt_mesh_model_elem(model), BLE_MESH_MODEL_ID_SCENE_SRV); + if (scene_model == NULL) { + BT_ERR("%s, Scene Server is not present in the element", __func__); + break; + } + + scene_srv = scene_model->user_data; + if (scene_srv == NULL || scene_srv->state == NULL) { + BT_ERR("%s, Invalid Scene Server parameter", __func__); + break; + } + + if (srv->state != scene_srv->state) { + /** + * Add this in case the Scene Setup Server is extending the Scene + * Server in another element. + */ + BT_WARN("%s, Different Scene state in Scene Server & Scene Setup Server", __func__); + break; + } + + scene_srv->state->in_progress = false; + bt_mesh_server_stop_transition(&scene_srv->transition); + } + + bt_mesh_time_scene_server_state_change_t change = { + .scene_delete.scene_number = scene_number, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + break; + } + default: + BT_ERR("%s, Unknown Scene setup action opcode 0x%04x", __func__, ctx->recv_op); + return; + } + + if (ctx->recv_op == BLE_MESH_MODEL_OP_SCENE_STORE || + ctx->recv_op == BLE_MESH_MODEL_OP_SCENE_DELETE) { + send_scene_register_status(model, ctx, srv->state->status_code, false); + } + send_scene_register_status(model, NULL, srv->state->status_code, true); + + return; +} + +static u16_t get_schedule_reg_bit(struct bt_mesh_scheduler_state *state) +{ + u16_t val = 0U; + int i; + + for (i = 0; i < state->schedule_count; i++) { + if (state->schedules[i].in_use) { + val |= (1 << i); + } + } + + return val; +} + +static u64_t get_schedule_reg_state(struct bt_mesh_scheduler_state *state, u8_t index) +{ + struct schedule_register *reg = &state->schedules[index]; + u64_t val = 0U; + + val = ((u64_t)(reg->year) << 4) | index; + val |= ((u64_t)(reg->day) << 23) | ((u64_t)(reg->month) << 11); + val |= ((u64_t)(reg->minute) << 33) | ((u64_t)(reg->hour) << 28); + val |= ((u64_t)(reg->day_of_week) << 45) | ((u64_t)(reg->second) << 39); + val |= ((u64_t)(reg->trans_time) << 56) | ((u64_t)(reg->action) << 52); + + return val; +} + +static void send_scheduler_act_status(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + u8_t index) +{ + NET_BUF_SIMPLE_DEFINE(msg, 1 + 10 + BLE_MESH_SERVER_TRANS_MIC_SIZE); + u64_t value = 0U; + + bt_mesh_model_msg_init(&msg, BLE_MESH_MODEL_OP_SCHEDULER_ACT_STATUS); + switch (model->id) { + case BLE_MESH_MODEL_ID_SCHEDULER_SRV: { + struct bt_mesh_scheduler_srv *srv = model->user_data; + value = get_schedule_reg_state(srv->state, index); + net_buf_simple_add_le32(&msg, (u32_t)value); + net_buf_simple_add_le32(&msg, (u32_t)(value >> 32)); + net_buf_simple_add_le16(&msg, srv->state->schedules[index].scene_number); + break; + } + case BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV: { + struct bt_mesh_scheduler_setup_srv *srv = model->user_data; + value = get_schedule_reg_state(srv->state, index); + net_buf_simple_add_le32(&msg, (u32_t)value); + net_buf_simple_add_le32(&msg, (u32_t)(value >> 32)); + net_buf_simple_add_le16(&msg, srv->state->schedules[index].scene_number); + break; + } + default: + BT_ERR("%s, Invalid Scheduler Server 0x%04x", __func__, model->id); + return; + } + + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, &msg, NULL, NULL)); + return; +} + +static void scheduler_get(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_scheduler_srv *srv = model->user_data; + NET_BUF_SIMPLE_DEFINE(msg, 2 + 2 + BLE_MESH_SERVER_TRANS_MIC_SIZE); + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + switch (ctx->recv_op) { + case BLE_MESH_MODEL_OP_SCHEDULER_GET: { + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_GET_MSG, model, ctx, NULL, 0); + return; + } + + bt_mesh_model_msg_init(&msg, BLE_MESH_MODEL_OP_SCHEDULER_STATUS); + net_buf_simple_add_le16(&msg, get_schedule_reg_bit(srv->state)); + BLE_MESH_CHECK_SEND_STATUS(bt_mesh_model_send(model, ctx, &msg, NULL, NULL)); + return; + } + case BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET: { + u8_t index = net_buf_simple_pull_u8(buf); + if (index > SCHEDULE_ENTRY_MAX_INDEX) { + BT_ERR("%s, Invalid Scheduler Register Entry index 0x%02x", __func__, index); + return; + } + + if (srv->rsp_ctrl.get_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_get_msg_t get = { + .scheduler_act_get.index = index, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_GET_MSG, model, ctx, (const u8_t *)&get, sizeof(get)); + return; + } + + send_scheduler_act_status(model, ctx, index); + return; + } + default: + BT_WARN("%s, Unknown Scheduler Get opcode 0x%04x", __func__, ctx->recv_op); + return; + } +} + +static void scheduler_act_set(struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + /** + * A recommended implementation of the Scheduler should calculate the value + * of the TAI Seconds of the next scheduled event and put it in a queue of + * scheduled events sorted by time. Every second, the first event in the + * queue is compared with the value of the Time state. The first event is + * executed if it is less than or equal to the Time state and then removed + * from the queue. After execution, the Repeat Flag shall be checked, and + * the next occurrence of the scheduled event is calculated and put in the + * queue. + */ + struct bt_mesh_scheduler_setup_srv *srv = model->user_data; + u8_t index = 0U, year = 0U, day = 0U, hour = 0U, minute = 0U, + second = 0U, day_of_week = 0U, action = 0U, trans_time = 0U; + u16_t month = 0U, scene_number = 0U; + u64_t value = 0U; + + if (srv == NULL || srv->state == NULL) { + BT_ERR("%s, Invalid model user_data", __func__); + return; + } + + value = net_buf_simple_pull_le32(buf); + value |= ((u64_t)net_buf_simple_pull_le32(buf) << 32); + + index = value & BIT_MASK(4); + year = (value >> 4) & BIT_MASK(7); + month = (value >> 11) & BIT_MASK(12); + day = (value >> 23) & BIT_MASK(5); + hour = (value >> 28) & BIT_MASK(5); + minute = (value >> 33) & BIT_MASK(6); + second = (value >> 39) & BIT_MASK(6); + day_of_week = (value >> 45) & BIT_MASK(7); + action = (value >> 52) & BIT_MASK(4); + trans_time = (value >> 56) & BIT_MASK(8); + + if (index > SCHEDULE_ENTRY_MAX_INDEX) { + BT_ERR("%s, Invalid Scheduler Register Entry index 0x%02x", __func__, index); + return; + } + + if (year > SCHEDULE_YEAR_ANY_YEAR) { + BT_ERR("%s, Invalid Scheduler Register year 0x%02x", __func__, year); + return; + } + + if (hour > SCHEDULE_HOUR_ONCE_A_DAY) { + BT_ERR("%s, Invalid Scheduler Register hour 0x%02x", __func__, hour); + return; + } + + if (action > SCHEDULE_ACT_SCENE_RECALL && action != SCHEDULE_ACT_NO_ACTION) { + BT_ERR("%s, Invalid Scheduler Register action 0x%02x", __func__, action); + return; + } + + scene_number = net_buf_simple_pull_le16(buf); + + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_RSP_BY_APP) { + bt_mesh_time_scene_server_recv_set_msg_t set = { + .scheduler_act_set.index = index, + .scheduler_act_set.year = year, + .scheduler_act_set.month = month, + .scheduler_act_set.day = day, + .scheduler_act_set.hour = hour, + .scheduler_act_set.minute = minute, + .scheduler_act_set.second = second, + .scheduler_act_set.day_of_week = day_of_week, + .scheduler_act_set.action = action, + .scheduler_act_set.trans_time = trans_time, + .scheduler_act_set.scene_number = scene_number, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_RECV_SET_MSG, model, ctx, (const u8_t *)&set, sizeof(set)); + return; + } + + srv->state->schedules[index].in_use = true; + srv->state->schedules[index].year = year; + srv->state->schedules[index].month = month; + srv->state->schedules[index].day = day; + srv->state->schedules[index].hour = hour; + srv->state->schedules[index].minute = minute; + srv->state->schedules[index].second = second; + srv->state->schedules[index].day_of_week = day_of_week; + srv->state->schedules[index].action = action; + srv->state->schedules[index].trans_time = trans_time; + srv->state->schedules[index].scene_number = scene_number; + + bt_mesh_time_scene_server_state_change_t change = { + .scheduler_act_set.index = index, + .scheduler_act_set.year = year, + .scheduler_act_set.month = month, + .scheduler_act_set.day = day, + .scheduler_act_set.hour = hour, + .scheduler_act_set.minute = minute, + .scheduler_act_set.second = second, + .scheduler_act_set.day_of_week = day_of_week, + .scheduler_act_set.action = action, + .scheduler_act_set.trans_time = trans_time, + .scheduler_act_set.scene_number = scene_number, + }; + bt_mesh_time_scene_server_cb_evt_to_btc( + BTC_BLE_MESH_EVT_TIME_SCENE_SERVER_STATE_CHANGE, model, ctx, (const u8_t *)&change, sizeof(change)); + + if (ctx->recv_op == BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET) { + send_scheduler_act_status(model, ctx, index); + } + + return; +} + +/* message handlers (End) */ + +/* Mapping of message handlers for Time Server (0x1200) */ +const struct bt_mesh_model_op time_srv_op[] = { + { BLE_MESH_MODEL_OP_TIME_GET, 0, time_get }, + { BLE_MESH_MODEL_OP_TIME_STATUS, 5, time_get }, + { BLE_MESH_MODEL_OP_TIME_ZONE_GET, 0, time_get }, + { BLE_MESH_MODEL_OP_TAI_UTC_DELTA_GET, 0, time_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Time Setup Server (0x1201) */ +const struct bt_mesh_model_op time_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_TIME_SET, 10, time_set }, + { BLE_MESH_MODEL_OP_TIME_ZONE_SET, 6, time_set }, + { BLE_MESH_MODEL_OP_TAI_UTC_DELTA_SET, 7, time_set }, + { BLE_MESH_MODEL_OP_TIME_ROLE_GET, 0, time_get }, + { BLE_MESH_MODEL_OP_TIME_ROLE_SET, 1, time_set }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Scene Server (0x1203) */ +const struct bt_mesh_model_op scene_srv_op[] = { + { BLE_MESH_MODEL_OP_SCENE_GET, 0, scene_get }, + { BLE_MESH_MODEL_OP_SCENE_RECALL, 3, scene_recall }, + { BLE_MESH_MODEL_OP_SCENE_RECALL_UNACK, 3, scene_recall }, + { BLE_MESH_MODEL_OP_SCENE_REGISTER_GET, 0, scene_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Scene Setup Server (0x1204) */ +const struct bt_mesh_model_op scene_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_SCENE_STORE, 2, scene_action }, + { BLE_MESH_MODEL_OP_SCENE_STORE_UNACK, 2, scene_action }, + { BLE_MESH_MODEL_OP_SCENE_DELETE, 2, scene_action }, + { BLE_MESH_MODEL_OP_SCENE_DELETE_UNACK, 2, scene_action }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Scheduler Server (0x1206) */ +const struct bt_mesh_model_op scheduler_srv_op[] = { + { BLE_MESH_MODEL_OP_SCHEDULER_GET, 0, scheduler_get }, + { BLE_MESH_MODEL_OP_SCHEDULER_ACT_GET, 1, scheduler_get }, + BLE_MESH_MODEL_OP_END, +}; + +/* Mapping of message handlers for Scheduler Setup Server (0x1207) */ +const struct bt_mesh_model_op scheduler_setup_srv_op[] = { + { BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET, 10, scheduler_act_set }, + { BLE_MESH_MODEL_OP_SCHEDULER_ACT_SET_UNACK, 10, scheduler_act_set }, + BLE_MESH_MODEL_OP_END, +}; + +static int check_scene_server_init(struct bt_mesh_scenes_state *state) +{ + int i; + + if (state->scene_count == 0U || state->scenes == NULL) { + BT_ERR("%s, Invalid Scene state", __func__); + return -EINVAL; + } + + for (i = 0; i < state->scene_count; i++) { + if (state->scenes[i].scene_value == NULL) { + BT_ERR("%s, Invalid Scene value, index %d", __func__, i); + return -EINVAL; + } + } + + return 0; +} + +static int time_scene_server_init(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, No Time Scene Server context provided, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_TIME_SRV: { + struct bt_mesh_time_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Time State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_TIME_SETUP_SRV: { + struct bt_mesh_time_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Time State", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_SCENE_SRV: { + struct bt_mesh_scene_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Scene State", __func__); + return -EINVAL; + } + if (check_scene_server_init(srv->state)) { + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_alloc_ctx(&srv->transition.timer.work); + k_delayed_work_init(&srv->transition.timer, scene_recall_work_handler); + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_SCENE_SETUP_SRV: { + struct bt_mesh_scene_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Scene State", __func__); + return -EINVAL; + } + if (check_scene_server_init(srv->state)) { + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_SCHEDULER_SRV: { + struct bt_mesh_scheduler_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Scheduler State", __func__); + return -EINVAL; + } + if (srv->state->schedule_count == 0U || srv->state->schedules == NULL) { + BT_ERR("%s, NULL Register Schedule", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + case BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV: { + struct bt_mesh_scheduler_setup_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Scheduler State", __func__); + return -EINVAL; + } + if (srv->state->schedule_count == 0U || srv->state->schedules == NULL) { + BT_ERR("%s, NULL Register Schedule", __func__); + return -EINVAL; + } + srv->model = model; + break; + } + default: + BT_WARN("%s, Unknown Time Scene Server Model, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + bt_mesh_time_scene_server_mutex_new(); + + return 0; +} + +int bt_mesh_time_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Time Server has no publication support", __func__); + return -EINVAL; + } + + /** + * When this model is present on an Element, the corresponding Time Setup + * Server model shall also be present. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_TIME_SETUP_SRV) == NULL) { + BT_WARN("%s, Time Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return time_scene_server_init(model); +} + +int bt_mesh_time_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + /* This model does not support subscribing nor publishing */ + if (model->pub) { + BT_ERR("%s, Time Setup Server shall not support publication", __func__); + return -EINVAL; + } + + return time_scene_server_init(model); +} + +int bt_mesh_scene_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Scene Server has no publication support", __func__); + return -EINVAL; + } + + /* The model may be present only on the Primary element of a node. */ + if (primary == false) { + BT_WARN("%s, Scene Server is not on the Primary element", __func__); + /* Just give a warning here, continue with the initialization */ + } + /** + * When this model is present on an Element, the corresponding Scene Setup + * Server model shall also be present. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_SCENE_SETUP_SRV) == NULL) { + BT_WARN("%s, Scene Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return time_scene_server_init(model); +} + +int bt_mesh_scene_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + /* The model may be present only on the Primary element of a node. */ + if (primary == false) { + BT_WARN("%s, Scene Setup Server is not on the Primary element", __func__); + /* Just give a warning here, continue with the initialization */ + } + return time_scene_server_init(model); +} + +int bt_mesh_scheduler_srv_init(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Scheduler Server has no publication support", __func__); + return -EINVAL; + } + + /* The model may be present only on the Primary element of a node. */ + if (primary == false) { + BT_WARN("%s, Scheduler Server is not on the Primary element", __func__); + /* Just give a warning here, continue with the initialization */ + } + /** + * When this model is present on an Element, the corresponding Scheduler + * Setup Server model shall also be present. The model requires the Time + * Server model shall be present on the element. + */ + struct bt_mesh_elem *element = bt_mesh_model_elem(model); + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_SCHEDULER_SETUP_SRV) == NULL) { + BT_WARN("%s, Scheduler Setup Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + if (bt_mesh_model_find(element, BLE_MESH_MODEL_ID_TIME_SRV) == NULL) { + BT_WARN("%s, Time Server is not present", __func__); + /* Just give a warning here, continue with the initialization */ + } + return time_scene_server_init(model); +} + +int bt_mesh_scheduler_setup_srv_init(struct bt_mesh_model *model, bool primary) +{ + /* The model may be present only on the Primary element of a node. */ + if (primary == false) { + BT_WARN("%s, Scheduler Setup Server is not on the Primary element", __func__); + /* Just give a warning here, continue with the initialization */ + } + return time_scene_server_init(model); +} + +static int time_scene_server_deinit(struct bt_mesh_model *model) +{ + if (model->user_data == NULL) { + BT_ERR("%s, No Time Scene Server context provided, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + switch (model->id) { + case BLE_MESH_MODEL_ID_SCENE_SRV: { + struct bt_mesh_scene_srv *srv = model->user_data; + if (srv->state == NULL) { + BT_ERR("%s, NULL Scene State", __func__); + return -EINVAL; + } + if (check_scene_server_init(srv->state)) { + return -EINVAL; + } + if (srv->rsp_ctrl.set_auto_rsp == BLE_MESH_SERVER_AUTO_RSP) { + bt_mesh_server_free_ctx(&srv->transition.timer.work); + k_delayed_work_free(&srv->transition.timer); + } + break; + } + default: + BT_WARN("%s, Unknown Time Scene Server Model, model_id 0x%04x", __func__, model->id); + return -EINVAL; + } + + bt_mesh_time_scene_server_mutex_free(); + + return 0; +} + +int bt_mesh_time_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Time Server has no publication support", __func__); + return -EINVAL; + } + + return time_scene_server_deinit(model); +} + +int bt_mesh_time_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub) { + BT_ERR("%s, Time Setup Server shall not support publication", __func__); + return -EINVAL; + } + + return time_scene_server_deinit(model); +} + +int bt_mesh_scene_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Scene Server has no publication support", __func__); + return -EINVAL; + } + + return time_scene_server_deinit(model); +} + +int bt_mesh_scene_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + return time_scene_server_deinit(model); +} + +int bt_mesh_scheduler_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + if (model->pub == NULL) { + BT_ERR("%s, Scheduler Server has no publication support", __func__); + return -EINVAL; + } + + return time_scene_server_deinit(model); +} + +int bt_mesh_scheduler_setup_srv_deinit(struct bt_mesh_model *model, bool primary) +{ + return time_scene_server_deinit(model); +} diff --git a/docs/Doxyfile b/docs/Doxyfile index 8be68c7c55..919358b18e 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -55,6 +55,20 @@ INPUT = \ ../../components/bt/bluedroid/api/include/api/esp_hf_client_api.h \ ## NimBLE related Bluetooth APIs ../../components/nimble/esp-hci/include/esp_nimble_hci.h \ + ## ESP BLE Mesh APIs + ../../components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_common_api.h \ + ../../components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_local_data_operation_api.h \ + ../../components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_low_power_api.h \ + ../../components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_networking_api.h \ + ../../components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_provisioning_api.h \ + ../../components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h \ + ../../components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_config_model_api.h \ + ../../components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h \ + ../../components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_health_model_api.h \ + ../../components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h \ + ../../components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h \ + ../../components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_time_scene_model_api.h \ + ../../components/bt/esp_ble_mesh/api/esp_ble_mesh_defs.h \ ## ## Ethernet - API Reference ## diff --git a/docs/_static/ble-mesh-config-complete.png b/docs/_static/ble-mesh-config-complete.png new file mode 100644 index 0000000000000000000000000000000000000000..9dfc317ab26f1f87052c9255681bcf0ba6a2b5cb GIT binary patch literal 94673 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

tnH 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 YN>YoGn=WuNFfcH9y85}Sb4q9e0EAvy5&!@I literal 0 HcmV?d00001 diff --git a/docs/_static/ble-mesh-device-power-on.png b/docs/_static/ble-mesh-device-power-on.png new file mode 100644 index 0000000000000000000000000000000000000000..c18e1176ab633cba7af68afd2b8f69fa09802baf GIT binary patch literal 381459 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

*eZEUq|!#sq&8N8rQ}}vy!}Bp zcepoH&YHbKv-iN#3A>$wl=iAuItMWYYqf_Z$*!j4;^5soB|x-m3D*+O6&ahe zB39UZGf?%)s+pr0D6;E_a;IT`#NrPc%QZtBIXWLaJ6W1?&)>iCb9(>hOH*xcWj1U2 z%K27uoNMY4IX1!S_^l03?!Nz}SNHXLT{F9U&5UGs{}1>7me;p`-}m*c&94i}E;A2i z-16{Id=bi8e!rT1l~By?P3O)=SNrg%ub9HEH=|tq|7X!Emz4*c_O@tM=j8Rz^l|H$ z@!Ul)<5ruO>$x?0Mw~*@!ii>&_kO>3c1LFAt4qE;o@;dVJ{+rl|LED4)Z3M5_a48VBJxfJNxKErgXox4{Du_vkUx!LfBTi%rZ3m_%78TyS#eh+H}Ad_YRe z6IrE{4-FG`W^q3W=>6W;;O^o%X`YH!PJqjOrS3o07owgPoF0A*Sk z_xQ9-31@fGmhuzP=jSIH$b9(V9e&Vnr8xVY@`pCnb5jqiM=70S@47ePoV4EFqSm*b zCqMhgJX!qS_SmTpezQJqj=26{kNUa!r}d^SP+hi4v)XiWn#-mpt255#o<+&E^B2u1 zJbrVsht2QFoadUwc6RjzJxD#o!@X?X$p-nQoB6iCeX&HH!(T~guJdt^7fgCtTf8dO zE;=o6cqz41=%8<>p}^u(f9e*0xGS>MQH6P-+lrQL3C+qa4Mtk^7IWQq+*$kU+~*lC z^Vr?rXK+NGyj3|xgY(!0mihYYJ*F_}a{VcH>NQi^bLjP*m0X{9ZsDl<7kH+6u9Axi zs~5Y=E02pp(e2z!>xD{pcIad+Il~_8HS2nu_YH;0Z=K>dcH2I?xjgRC$@F*TvpXu#$X!da6JKqpx-HTCof9k1rfr^HwAH z(c=8N2J!ry4I8(*M`oT|qqDK>m&y6JG6KRTbC^i9RdVBfKKew#T+gEmaeP4F$NN9|?!lJb6%8I!8PHwYKiZmxaQ1#U2TPC~sZKnHn%_dy$`;IS<_@rO!8{eDz@l@~ZH}7RH`K2!rcs6~~o~r06qUEtW z4jejuT|?{St%-`!zq?fe14M*kHQKt{s^43j+-dw=L##UFa8vcWowKLM?>m-!zh=_K ze;q2%qFw9fam8wShJM{qTB>~Bu&r(DR@c5YdWt;@xHoLvcXDN`RC1PF{Z}?m$Id6a z-|d~BZJxKOJbQ9~?VH~f&t|6oxW~U|`gyy5Ca=Tugq)9XdH;VhA%E|t^Pm5}xWA#& z;@J_ygTeZ_8$NHHeq;92$j1I@Git)_zii=`4HVbm`Ss(5q4k^vzWrx=YybTyj(-B0 z^Z3PF_rtxuMgGTw*&Y8x@8~Q{Idppucc+lb%xBe0f2yw8dT6Hy2VdZ`ztM^!mm)7n z#a?%L|7nlS*4&qZK5z_H$Re0FzDz*Q6GO zS~qd7)`@oSCDi5y&vja0d*j&U%+@tk88dHp?TC4~M8qRdAgwWgYl&un!zaURwr2IH z`PaikoZLK}CbS$b)s3CZq19NT!{w=>;Id$i$o6mPf42YsAU=_0*|m>n^XExlH{>{t#lTX>O-@j>@ z62W6p|3re{_OsO5*-iX+s-Nk&_sKs$DjxUnvHb5kx%zLC_r5D`_3=g|MZ<}v}7l;1)aOBFv$;+;FrZ`+RHLQ$zE~v=uJmp+pnj))KrP^f9Fs&|w!z>e0Jts*$WNwjM zU=XAnqBKc#l2^mA6sP%IE(-&Kl3n<=9JASTI_8ze2jg~al}&3ddM)bUQ2%_-Vb<~v z>27_YRVE&)o_67Bny!<+-aMoidhZqoQ_7K@Y`0l*E?LNGehQr;VY5`Q#Ob5|Kl%S+ zib`wCYrej|zpQxPKd)~m+66YwJ+9sv-qw58wkov0|L=wGlRRc@I$o^5@65Y@io0F} zh87&Zd)%V1q<2xwqf1wL7k!^&vGdgHcl*1`a&GR~>H1v9HIw16w*DSBLyeWcW<7Qf zkBoRTaZ8FxW~Tcr>ps0r=DsoYU%6Z}SCkxdzxOxpTK)6yJ3s8&ZBhD7rOf2fV)?%s z{~yf%uRY1g;`xkQ&TQ%DKXtOlKQiXud*;|d#r&P+o&NSeOv2a2xn5!QQEz#o@U*A* zg5YbZRjb_B+nY}?HC|QJ_&lzn_k8WY(=u{$o64SW)IU!DXT?8f_vCj|y*<_nJb9!l z;J~@4!*Y+R%F-!QIHX=1v2N7)EF~^$Xr;mBvhc&Ti7{tZz0|ni6v*Wn^kA3fWxk}; zm8L(-<2^iBw@jO&6QrS)+;K>lVI|8cPM)>}iUJxTjgzD%GTrlTIdd?))uC+X0gqh{ zSGioyw6AM+Ik7oSoa5ePtLPe1CY?X7%ELtaW#NC(TH+Ou54mlcHBNDe|sY z!j2sW!sRL_uCISAKglKGj)mNxL&_aNm*eBk=oqZMnlii;IHp9kVI-QaW$f6BKgT_05@}86CXywq*(grSX>v z?5qm3bQY*>pY7MVY*ExJ=jWd1CN|bh)L_}Aqrg&AaHg_qiGt_nZTm!2GcbN{pE z0vDInLJL3VZMv5ux{PouHAs_VuR#~hc$&fIA5#;~}lH*MPU#-If~POG~v z{+KX(`!z}b=%(|WR}Wv5^HS{zoMTx%=lA(|&<|CkO> zO)S0POl5XIR{6^%SNBFT$LPzcS#Ba* zygpyszJKZi>wmBO|FGoO{o=i{*Y3LAch^b2C-y0Mo;>o-xGQ*DnT}KBwZ5>L-;aWK zoaFJ7b=TKh&?R)AWy_imhUbczC2imAZkVray8p+~{0~R<>s9v`8Gd+a|HmZ#-Hvwo zs`IjM{Nh!*Vj`ty9!!QWQqQ5xS`C4qw61CK7cO*mA; zqAcvG-XV6~?8nSy5ps)S4wcDiioS3W;f$Sjg5h9vQ}L6vT|09OkMz8~qQq`L_vmpK z5kXa7wYN7L424u>?wR_`mNaB*37li0D)CMH#_UW!_u#g_la_Gh-MMi#H^Wfh=hduh z=XUJfcW_4`%kvAZ4--l{rtqy*Iqtuv;D2oS#VxYu_xwE5d;ZAAN)arXB=B=x4>?oZ5@mo*k?GM|^_s$fTtDhqMnw{sh+zGBg z5gGgHzW)Cwjz_%U-|_t)`;B`y6ug>eSBC6&kvx3FFY{0-->zxd?8ahYyykq%-`E$; zohdNIJxoATX)=>H-@&ui>esBdgzmT&$e+C}KmT)k{SV2w-Bq&5Jg=p<|Gawt2lK!7 z|Ns6UI(2Kp*T8uvxma2+mB_8x9OBw&sA{D;MRTRELq+?HdTC{W+bssJ^Bz~tH#YY= zTX@1?MOILc&#k@?kqli!qY0nF4cK@4xNb0ZDybK`hl{h`(oO`%b&{W1D{;uNB>*)R6)AR2*$(q&tnEPG%^`!gtzf9MKHp@6&WZhP{Lg~!pEt9rcF4-*H zb~x1Sc6H3oFIn@NUpGnEY+oPyY^M6Wr@8h&^SCxUUw^i9-uHP?;vst{6-rN7xnP~@ zw(O0Y`Bt(tHE{K<5Z$=q!Y+j+EP9R|5%(36j?55hQmOF0?KS1xuYE=L)Oyz)yv^~H zX_<_|QN?wicYc*%cFXQ^JiCS0WBP?CMed2k?23v_XLhhL9^?{HlAjf&oW$~bZs_cv zapmW{6ar=_bnH9Jz@)3F(qp8N`N8V_H1*jRraCTl*&xYbyyn*wX3x9VT^JW+Se=h` z=lTC(|4+-feQA~dKCP}f&u)Jt$@Ibef2aTVMw^=z2bozoT@GBYm*{$O^7(xyKAjS} z|NA2UC1cU{_Vyi_-l1G!eX@?>+&XKs-mTHP_x0QM!uxm2Kc2k*zc#M!snFh5sbH^J zo~Pt_6a$?UN)iee9+!T%fBqBO=jYcJKL1|Rk#Ac#(LKI$n)Us^VtV_kWVi1=o>VWG z$uhlMJ?DMjj~DIx`}J+UsHpeJJ={8b`;TKsz0a&U>L+Y;@Xdn9F7cn5>p$+^S-Io< zlWUCccmB6|{@&`?^7;FYY~5?tKx-Xy^ZHSF<2zv@qWPtmgE4n zZtF?x4;5GNWWguaK$S(p{}--ZXQIV;t+{!H@`|5zn;4cBEq&@d{kXr?`uUkF zFP;+EvgE7m#@C;0f|u-xnzGSWXEmheOT3u&|J(b2EPnH!t?suwdwbvO z+&%vez5emE{(tnIs+W#e7VxXYWN$vWO6tQK@AXbi{WV{lL4}$7{0gD#79n5O$VM-f z(MdXWWGDAT^L_v7?tT2W{nOL+^>f?htEYS}pSRF_#f`Xr4n+Z@pl_<_e)Av9D;A%T z_x|&`{u-8G{vXr#fAp;JnzQ4(UEI#U0^C#GyqdLs*ZjM<{O8I3nh9TTN89{;lPzOk z)>8kydVZRe(9;wbN2RV=AI~w`|9SU2(#G(?vEE$2aMSm-&ue3D**=-vALlYl&MAA( znW70(obmO%sqFe4E{87P(u|rC66By&F!xZT zXJq)4t%qFZyLWCDRLm&Z+hBO1Q}D+cCQ-uxF;xxc+iX53A}2;Xaahff8Rz)?bctLu zhk?{e6N~;5$ui{>mowM*wVX+il~wr3w>WfP%#oIhszH)1JXv_8*KA#)c;fZzUQ5u-G_zW4R6{%>0CZ+Pp5U0gh6Z)%?$_Z+L*S>N|R zzdOb4v-JM2)-k(^dN;){*mB_Kl&49bdoI?0au9#=zv2>Gs@AGjE=`_1v5Mc)_l|ws z{qDiRZt=|gTmdN$vqit!HpO3*Q_G9rb!`8?>;I+Mx1H~e-u>mWgt~f5tLmf=KDQ+e zU6x$9Q~l2G-#7n1rh0GJd}iPORZ}cXui)RENV~sJQj~ zxN3~kBKRXBta-J?3_5 z)v_Mm*yoO3dp^H;^IjqOUbLBJ^`qn-4atj2;c+KtRB8n~#x{$wWUu6&tf&_x)G$Fu zi&cavCGP#{+i!{wh$hT7oM#gMcfyn;zQhBGK~BpW`sLmWb?;gd9{$-(=S$UTi~7l% z-Te#y9bskL%eeO+lVj8T!_Fy7U#7{%|NSFg`8xXDWB0mGk7M@kJhwRhzuNtpCxKHm z16LXnE?h0^PEU}WnZDjjGboaAR@KHsBCL-O_p$H$&0BMQ z_q~IweVr?R8?6*PqSt<5RbA`mzLVz^=l}WsUrt+WXHj)iVS7gDffe>H*Ct)t-f&>X zB;mue_J7+re~Gr}{6F9K_v(aK{H=ZeJX^f{L$15w{#@?2FR$9(itPN_`9L-`@=~u_ zMwsQcvMD0*j#?-G=2tM>Hko6;v#E>oev3?AaMzQ~{4ft9 zM!ljU*#O6&>$XXem&?S&XUEt|H<$KZT%+pdX}aPiP4JFENU7$qWfiz2<0r zn1fNVpkRY{$xLsBPis>6!cJ;eGkJI^2g_-9ZgPkfA9aj_sxkTA5O;~ z3V5)bQ<61xy^33J!K8G#x;fML|K)kz-t>1aGux+zPR!Z<%1RFX*z5)P}-+xsx|u+LkRC6ud&{`c{+8nu4AAQIp!7rhXA@ zdGdKz(xoY-y>FFrGYa?pG&XseVzoF|ch{<-4<t~avsGI2!fRU3^_@<{?o;^mYK~FIgTPA)uO_v)yJ%?5ZT0hdQF{UF&q<3^mYK=C>-%wOe%(C&z3*dXY|Ey!%l|dI|Kn14#iO0#5fL#CCt9|1 zCVO=)I^-8U{Ht!t9eXjl@I5$4rFxyx2 z(P;WE&D=AYQA=4I8+3TsV^dbI_<2Uy|HHd9{-r8MCM0s6oOm&sH8oP;_JK8Dc}_Q7 zli#pu*D?No7x*{q{2tA*w(|Mzdq=DH{hqt$-MYtX_J0#Pc(9<-gF|k<)gJyEkt~Zh zo=Uvkx9fiThJc67XIE!6vpQvbj;rvgv|FQ9<*j|?RN~s?JAHHfj$pigf3hHX$X?R;}Qk;H=sb2HOaysclbk|M_b7^32?sAyn75T5_nGZHtzG@b-CGCORiqf2KejwQQTTX-Wp&Tm@cfUq@)i8{|3BCB2T!&6 zbt8F>O;t;O-T(F(UaDs(7>Uc;%Og>J~Eo5_8^c-n_f@d!1a< z$@VHQAx-aFdPNdZd2i%CRM&rqoLB!)X#U@8$#-t-JbCL7)5YqPNi7N%H|{<@dp!Kd z&iy~P|0u8jlsfOW)q{xr4}RbOX*X}r&XdLFdBUmx60VfajNTVyuyNkqRnM+!Pxs_! zT~qGC>HA^Fl<$9og{Fi$W-<9*PBH9m^USHd-fnkd^ZK~N_X$V#e!qX--R?(ST>X1l zli%B4rZxV1#N_p?Tf{Cw+Itey>*Y7LE!FU@YMv#*opvwiOPhz%56^3b+XPpvm_BVq ziTraLU+bm9qCt(CA}{WUiD}w<@8ji&R0ux95x2l+r`O9lsy|{XA1ySOcyw4r{XwXs zi_qS)q2)}mQ%*eS+~&;LKjq2K$2Ij9YDGVE&qg-|Ic>T5c+Mwxb=LlD{t4$6Re8@? zb%xPb{`0SyJwGdZ6uFJ&EWaS7tg4h*#;-d2wlxo%vl@G{+4k*%R{dIYawDVS9vx}D zoRe>V{BHT(gZ4k&|J%Cz&3SM#ef~nTS^K{{u5XP0{Uv?Jr&pzXW#2YVcK-57>F=s1 zOQ**_I<-_=My@B)x~S!%j(4?-m!y*Eb3PNr4R?DhpMLBvd3<2qSDW8APXD;>Yp_(Y zwWUKSTX^wX=|AT7|2N-Q^kn|uU-B148q4pLHx{S+e|Q~VDG@5xG9%)}RmqF1KCSzn z*AkvCJHO&X@t%+8il;11e5NzEW66sSMTM!$bcFM+J(T&0MT5*NM{&bT%0@68!O(@q2_a{DUvFAm~jdF|<^l-;?V*NlZr zu{ck;B1&I@&E=E!Hp@ZxJ_zDW~3VVVZ9AlYe4n zi_@9w$;k>FQxD(wOvv@l;WJ!xfM@yywPy!5G3b8Ux9cReph`rydU%Q|wgNhP(D|36Bvnv#e`~IFV?%Dq>v@P}E$?J2McgqH5)l6#fbnfcAByy+td+eX< z^>6h*>HoW%zsT)(&4at|AAdUW@zvMal7G7tBG~m7OuVrzF*jFmcfE(B!;w3i_=1$Y zJF{)(_PNLhI0c-zQ*`BNL+$H|@9lO6K3xio+gCDk@oMckw)JzC-A>lBc^agDUt_An zDwkR89g{d*Ok7Q$ocJWj_q|N=+jQeE4jii%xo?V``eN^4R?pZkFTYkzaFgkCvtae+ zwyjC$G`#R$Qp@&;cYk=_1KoYO6IZ!qJI`wWwWK&>;@r=k+)Z=OL@O6>tkJqC)RMp9 z+@+-+o%6%pCbTS2ZaV)&-NoR7OcCoaZA~x zw|5Qqf70H6GQ;HAm6?<8l)s<7I&XTy$ zlHB88y=UJzH))c(YQet6xo#_zHtsY{E6zSN(b}*0;VZHtGC`O)r{SCUT83v*R&H86RtJ?Pq%bmBzWo$trkf3)uZ|GnbZ&Tbwy=X3L{ zTd&CO-pst}DWhgpi-~2-^W?nrQ+J$-IKW<9+;+vSGGpSyCG*_Tsxfv$6kB#m$Oc`P~-qN-^*>C~NS&15?l0T=VL zX@;r-nv;U^`cGeYG-qiy=aF)ez%y4_(DY@qgqtN zYPnJagywvH5tOisDWFBqou%rlQFnK@g7LP<7M6^jIUBcJDEN4ZG5y?~gRy(LR;pZd zbD26ru1Dg1pM>7Ji;)NO|DRiKX)|Zjo?Qo`?|t%Hmmil}v!Q?U#I(wUHEdQ++U}Mcg)(`ldm)_(&F8gebD38E{D@n>beWLSzLC#Nhs4uV&!DZ$x2(7tngZNwqUNu zw*%_-jH;WjnW*(>b%f5BGCVKO*%c=@Rp8)RHIGok2pOXn-)Hnh2c5X>bgyj9x`^~U1%?EXhl z-Tjkp&z>#PYI&hBh*e0|IB_g06!UvK~UOIUpK3MWse5)I{un3xB%?^iW>>+d~w z|MzKo&sdJIY2lLcZyx3}t3O#~n6f03XHx4wslxjH>$0&Ad&}()$lL!@(JPx`w7q9( zh{yVl*aZ@L3w%ARF9hhW3DWtpvggt6d;iSd)xT!$aj%ZqT_mVkyDmz#WHVdmkzX#C z*n&llC}{ldZjqQ#s=LBQUpy$;Sz&_q=ZuhLFL#_sv2yH^#PQref_xH>I`C(^XHhHwkDgmHZ~E$z~;efWxHnm_Jv`i~y$~sZIm7iAsEqBC1hW zXNIYMn&tY-k9DnylBZMlbH?R1`qL{d85WuZdZ(-rxTsjG@N!R?YKnte@3pRDtS;CE=|PnX+&3jFhZPyg<7b02H_+cj@B&Pf#rN;)BNQY7(0yt8jq zSnK1x`PJ&-552X|R&cq&Zr6ujNAu9|1H-P?B@nQUux`8BpY`L{d%_|Dz$Qmmvm z?kt>nckP7ef~ZH!c<))x>EEHUqWUIp>{V_TrnT9Ep21?X7EQP@RNS5>$-@rV$Mf%yN=)IWiYunlB?<723N)&hgx+Ur-6a7@Z?h$+C!z)~GubJ(w zJGuFKe!>e-El$gQzDq%kVp69+_UN7EqR`EzrwQIvPw)aMx_nSvJ^e+D)HZ9CQI82o}^z*;( z`};F9B;MwKes%i1@J5xzpTF;T?cpIMt+6_;ZhmOz%kLYvC0{CAvB*6(q~*oO;*y+n zngT}KpDdkZ@jT)~*RBgHH=lazNeSABpW8q4=&w+{xSEN|+3qi7 zOxT0BoJcv+{MWhcdU@mEj*nBa!_8i# z&3n2v^i9RFlb2VocLQ_1J;&!5}zuy=j6?f&17 z!YdxW@>Y@kew>#nj9jP5 z&ZrY}>)>0d>m<=*qB3#D4$jyuC3#)5Bip!bm*qO&W}Pxc(o#y*QDcjdNUCF?Xol{N zy4PkuI&ww77EIlw*Rim9*Rfq3XJ38(5MJf*!w}vLMM%r`j=F_Ey8ZZiXa|2#2X$OAcAj7haaKZ6kvZ(|Xm5?w1xG zox&9Bd1^^<0vBVkpJ}SEw#dHC75Rn|Qzn_Lbzka`XvDCzShmRO&{pAJ+}smU^}50@pK zILf}4RetE-cjhVgbBi2Z^Le{ZEnOWhc{b{mqc2xvN&v6-y1ZPYe}|lF9>09PW25n- z+WdO<`!#Pq*Szd>_po|m8CSq^wJ0^ezy6o~U!m)|KI*UcykGaZ=JD(EpHDrVt&#ib z{CduWQ+qxoXQf4Bt^804Kvf0X4bZ)&~rnGmm ztXHO$>IARAaM2(yr|GQ@tyfsA4=wr+mp4&N78B%)4lQ~O3sOUxR)%sAQZz9 z`ODQQ+f!O7^U6Z)W=&O5t>7a;Z2YauR5P5@Kh2u9S=Ke=h1cbq4%c_ZY-QFfIdW4m z*7$(O?A&;_-Aq1^ka6N+B*C8o*%dL%v#k5@~KkX-Hs4V^QY-+gCvr^9w z?)~*M?JFN;Z#kIuuk6E=V!u72)0XpJtE~HOHtoQ~t*LI}^H07ytTnOa{H89eG|?rK zmN*Fu%x#Gjs3zfeKSvreVGtv>~)c5yNuK4-i1od58Q6u`}qF9XX-C` zO;2X?mE}1pM#Mxf;f`I_bGET_-DioVKMt_l9awJvHFMvusp~%;PYIZ?s@#N^;Kb0=*?3mQF zN$)djTTP*^*WuH@qmR5TeYELO_IkTh+vEP3$#RMt7oPkv<>Jo7W4#6s%jZ|iyw?4@ z)b3lGLTqjC%55H#9o&*6Il650^>_ohd=ytq#ee6v%to@m~+Ka#JEyUrMBOgU85 zKi|9ff|337o!i|zDyPi|65IS^!L!g!i&wmQ)2_aNYo>C4U`l%snBhG%y2 zy`yD~9j6w_&$QN?`}P^bOr~J3E~WF&YCUGGV!Hk?#@%B=&@qGdDI8`KpH21q=lNXr z+aiqstBby}``#95-DTNwEo5ohwNe$`0Iy4Be6N>XX+5CcX;M3-EAo)zJ06vp_MfgM zaInhwna$W@+|uGFW%tp&-YHbAN5|qu-baFaL)V%Ke}0 z|NH+pUcdjp+^Sti{L@5QResE=zj^ESQoZGOZk>C!Ux4}U8rLH(iC#5l+NTFu6$_S? zTh8dI`0=w_B-K-4^8b*TjY0RGmg*!O$UK-n@1L4sqxy%{@qcO;X=EHO>5DFtP}w&7 z;?#Ni_jkGM`DCvBRgH(F1P0B>4=+n{?ZBNOg(l+ zs&Q^A*X#)s1!Ubk7EQVt{J{C&O!Y?pi6Xi;bgBiF9vyC3wsl8}f$O0R#!~`&&fX4C zI-I)163o38*1jiL?)iNqMUl0+9D-iKN|Tmp8RuQyK5x;a6%Rhf za(fB|o!AjGk;5)2S%bz4q-vZvZ? zF|EThx`e!)yM%01ggv>ssv6dCEqZa|&K|edwpSON-yELz>4x$7O&hEd${tAeuM6Pe zX6vqv*-)(OwJkC#edX$|_dniamYi&t*3X?TA8 z`4Xq>&TGL_*g`Xcgg0&cbUrnj+aW~8zIN88H8V|kH@|E-_x#DqFs;>ZQu1d05GXwN ze3k1{pRFAeBUEnPc=9E6N@R)sdIRo-D<>}x=Uk&1Xu-Gqx*)S_{zr?dy9*}xxV4{Q z)BduW@mlGMMQ$^>Vxyv_2X9e4SGaQBX-AofMTaK+c{nk(F_3MhiDTBTbgJ6Ed5dZAhPjlhO!TFfpbbv%1{e{54;!f+}fgzbwrW2}&i;))VK2kqb|uZ&mDxg;VM zs4>YmBHeJp)+tgoKE?98l5R*}n%=hT_F-@47Rw6<6&AU03AlE0vFR3bzitg*XMA_| zgXq&)`yU*ye`I@uWB1}CrzSq0vh>kxf19J9&&xfT9?8)Xw&mW(pXcjaT-?k{2f0kR@jmMxikR>I$HL&cPHWg?p#UUEf7%u?glPClhmn!elS zv*?9wvRqTY&WQ^CCZfeLY2BvV4x*N~FGO~2S|ayFVD91eKWjWxyu8HPxJ%010wrcG zJAB*Wa#=|0l66^&JXBualCNUKVl%&RB=Pv59#|2^;C*SQZ~d*1(W zTK(P}>*pG!t9MVG;Lsp&L*mSggzB_6GfnT!{8+Wu*fjX*s=2b)-&i%o%H6)RRa3UAxZjeE!q+;pfWw zIDO6MP2C;AQ`wq29#2>|=cvP?Fo}g9dfnR`gjw`Bf^rw_aFJfp!LAhKzG{9a*WFz! z9l9(ouV(7)k11DGsl1hLnHwBlJ;i99y|`A(1P=YjJEB)z>~&-Ey!GtOQQd3n-#u6@ z^;Wn3<8S#zce6KcynNxMzM(`(<4N zclB=`v0s0lEw7*c`F%`sVP)&Z4E{*(IoTOCJnd}^`&J&bUec4A+VS(wnyZOV4>WQ} z3K~fFfAsA&lhS0}!NDRFb@>YKwa6Z$&*oQUN~W?%Su1k5Z`dgGYOedj6HRVGi+4%o zo2;8@u;%*(ZrP&i3ui{=1R6^9Zt%WXe8Gg*b6#?Z+uCCh*6j;eydS@^s4`oxbwlV% zP5zb4CK0x!##!501(|7Eq9t8#3QSox^_j%em8-eZlezA6pNKcGa@yZ?;m59n*Dg+MyqF>B zUeMZL)gUq{WTwQct5%!NE$u13b;xOv(v72)TP6pDv%KBlq_ov#LBu=#2|wEsH@2%B zY&ZIJGgU18?$jCKt-Nm8$5i)Jmh~EBYpAn+zq{#FaKG)LuV*=Xy(4W)W@H*ZT7AE! z@vm&BN}5l}v`wb7K26oHZ{Gjmaed$Nd%xo@nIvXcx7Yvm-tV{ks=8s*&61{^q|!ZQ zp*1dS{u!$z?7qrHhZgStljr-_Ubu@}QNU)+{X1z^vkWA@1UM$<+WPbCTYaiW^YyY- z0$PjY_pVi59>cX~^@q=2N=(*O9te&;o>P9tU9Nh<{9iBSpIKkm+w|=kYu%Sx{)=)v zKknQAP1V~S@onRwJ{E~>jL|JM12)|Z5z4U+4~rO zMr?J-1qD^BlLpJq=9oEkv^;j_3k-WY$8??Gxy4Q;ZriI@e>R+BD7*Gxb<6~br7Ksz z=-U7JT)W2c>pSX-yZ2mn_ikOwwWjz<Hx31LPhwl}LF4uVSDEP?*Mvi_ zb2?_r@I4fDo4KNzM=boh$mR--mnKW^o?Uq?c!@_qr{WJL)yHprGdcGiTIdm#duYcI zl_mwD5D$+@A}eh}Ll?)$#7#~0`k&7I|N8&fVtyN!Y2`Z$zDk9!i+9R&+ zo67YEwmq1YA#gcxmC=$t zBTQ7?@2;!4wq^A^AlXrQ88~<&N5P4e!5}R>>pa!+Y+QX7}xSh z7(X}hm@+3@bM5g;Z_C+_QC=1kw?g+hqadJHfQwfE?v5!s+Rlku8z@zt&SGG zhL*QlEaF}^n&d;~|&-T9ZPhp;QDcASt z{ii>#k2|E%s>BuRGp*p^Gt;_n`Ty-JzBIN+Mp_-2`nB|2d>#91DXY7DcfTo3-rz30 zAX974){5sJ>}IY1V{=jFS@xb;S9!j>nfNZa|9at`4l`@X_-}R`Dnj|PzOl(c!Y?a* zg_o+d{Itq>_Go&<%YuVJS4`__oA>_h+T%42ijNnc zP0@HN+26J$x|VOP-lpx_{d1)+g$Qf4Ph(mieCw{<<)W4_-rTj%7N#3q3J?wMI`#QR z^}GjvZl4eGa=n&!e2-h8!-SGs%ppwYu2w_`i%)Z!Vxd0KwS-}T@0Rtf{bE8(C;wRX zQQ@LQu7I0cs$*;rM`wB8lDnO6uSGVRy|vJNEIp;i-hHk{jP1eMW|g~}=U!2%Z2BPN z;x{d$DMy35n`7Rtl`p?dulz8v{pZ>9^%EZ!OlN<;clP|BFXunGUiY_q%k*iLk3N1k zoYhnL^N#N}vo;Q!L)A_EGyKl2ul;rZ^VQ|^pB}7!w|tFlx_)d*cKTcs^AfArmgypi zK6kI|I3{%Ou5@k3W$#1EHup`}vi?t3IJ$Euk5YE3$YsgH5AC9)ml*zdz~SrQ;d$BO z)F#K57Q004SvL0Qv>vFIx4rp!w#xyf=bbJSWTGrjth`)aw0LFZ*H-VEw`*V96jb{K zPMflCQJH9@)q<;@kH4)BT2pS~w@8J>#;IxZ$L5RgwoKI9cka=R*p7o1f&p%q&zd-8 z`RqDT64lIpSFpn7T3CgS=e&SxrfM3SrX@XFcBV_v>v)j=tfN=nS6m91x=~AjBPZ|v zfl%WsHUVM}e@+a0nyR3%C^@!rjYx&J&U0(Yvq4HmclMTE6qa?k!M1^A`T`%hjqSdT z#f%G07G`cp^^6r4oE%Y|6{(bJ`~8}_pv^<67bPB_^n67Hq-vOMWV}5X?bD!gmLqb( z2hmu~OwC8^9%^sinBTdv$FcgtQA_tw6B9m<;643|{W}_X8n;X}T9sDwt~>r~T0HZoyj>a;DrOPie#S3cY_&utfBU^s$*hwK5 zn~t^2fsfa^y*#0jkt686`O*Vz?rjS;in?X&@m?9*8e;0jI!UGfv6*kDpODY$?yZ73 z!k1R8_@elr$+^lg&*%ip(*6lc{8o9kIIzr^$|-L;8=%|pk(?cQO0M4z&EP|GI zjjl%s{y3a(=Nuaq#wVXvxb^(Mb5@7%>WYPnWq!AMv-bRp?NK^BzeGyU_MV+>viVp4 z^~D`c0^Q56W^T5#*D6X&S8*-bZW8gy&uy0G8g7rt0k^H#b}$tm zO0oH{G-yk;>jrHc9NU0y*xSvi*jc1wjkIQ8{(i0_uvwg=CzcuB6#|NLOxN4|8Xsj6IM zbNZ#)6`m_cJ&4VTIVF;#ntiEN1c=fv|*T*9w0n5v{WaIK1K~Yx;UO6-*1u?$i!9zcJs>qT*SJ z$mJ<^w+t@xJZJqn=eZigWxpL8cOJZX{*PJY>a!1p-Cy$Fdh<3%u;y$+X5%is38^Pf zeG*!0lU`fiJ@@Y1_gPDn=R2Qq^sjH5v|C{T>kTWpFyFWPqszjgT;AtjO_1(3kdn6Yxg7qPHHJ*)oQN{`&kzp%+y+qh)ctiP8QmdwbXCU^aaO!E{$Hzk1%fs=V}>v(+l z%CBG0W`9v8F2T3`#;scimPq#7{W2)OUnPCb`reMBzKh<;YSoh~KAhwB+9;r+^kJRe zhaWTA3&hsS26=`karPdc_OZZh<*{~+!=9UXH^p=2$GWG@>&(ay_SlkCm?$_kvTK!#cJBn!bt_M9)_WijH)%GH zu8Gt0DWRhL`I8^T_D*TC>5TV|j5ZSQ_{`Vnw_RLcZe6FM^2!f~CRIA_-`>HyQRz{M zgH-qI_4!kldN8Cc4C2vI*K2l$}+hD=GX(3N} zRGF<e@ak^T1)=AWH;{I^w9MAPBh zv)r|{$>5|X#*-M09d=tBI|7KEk`ec>-!rQXjKivEMj-4@lTfR|M zo!jLj*MG@8=@DUkDO={ZdG)DTZd_X~YRDRNCYkAeyf<+Pr<0?xetG&@v&}C>ZeKC9 zmEHWRFem89#H%X%N+r&6m8r4z7QNXjwXfo7uFnb%+3qz=zxOezN|sbrTvb}FE%n+U zDrl;vo0`Pr2Q{sNfh*Va9=K*YwP=RQO#59rI}I~bSL|KBPRh#Z@!F+xw+ebCq_ghq z3Edmw)ziPP@!zSyJ>4Is2*^ZT(NN9UZf%jhGKcq2qI$5{Ce|f<6`G!@O?4> zn&lAf?W27-%JtI8lRn3CY&uUwWf@JK%(v^xIu#W*fs##jrz~O&m3IiGn5cFM+|CNh z?1@}(S8U7g*se`3>RR8{>|MI@d5MeQ_hSqk%P&M-h<}xkd-&ctDQ|7|7bU-Xj#-=R zQ#)TgujUK0OkC{J65jijzeU?F8~WZn?v0}wx5rE@@ICsW*28z<`SQzpBK(v{(0~ycT>lm&tcORtVw^RcH*Z8=fPwj zw`-@8F8G{~kl8GjX|iqI%5I~!f+a4TuEw*E9a!Lb@OV(g-H;VJ%BO|8_I3%mUn)8? z>+OwjUR%vttW(8so&xXQIQg4mF}H52a7!v~nP^mWIapQj)}xX` zYceu4GSw@eC|Y<&zIk+~$0qUsORF~5tOUtkZ7a|2Z;Ko`-4_{432LY%+e~2T+|Kw` z);BQlmXzx46vd^2yjM)$My^?QM#}r?@{&zrx3bkzn=edqv5L{mh*{(D;^73JCnmkD zffvpt3lv591YUAi?0A=y#`m79#`cgoYr7Tq=hJg$ZC@r77A7`xmF~U|XI`f)Je`!y zIalS2Rky@-1=j`z4yT9N=T|OXz32PJ_7$^dc~4*e>Dp0k4gJWtn6&Kr?vn*aMERr5 zuW70)XE7dBnW1v{TBywx=QG>32>DKvaI^kB<=iCA-i~C0Cv*Aa4(1p&SH8Cu2$;E0 zqHklddHInkW>t)`n)Ig5_0(c?4<=sHkmyC|$_?V<%+ zcs<&K+SD`**w!*loo}$ttL4&_ZE0aWnbMw-(XWn8RAbX;zPdqeYC!by6tkt5^OCnL zn=!>iFG0XV*JJaXf1eue-p#EEoTS!$WvjB`f~|_aHh#{rnak?B&MjQKD`W1)c+rrn z0;;Av-yN?jOP2KWs!@!Hd9%P+zVxQ2Q{?n1u7cWyPG^}b`jSF~0#tVO7Rj&Z9D zwC#B?({H-7`*N1+3MF0>xI9mN6lPbD{ru{5FkdO(;@h+Qmb+_bFHUi2+wq-6-_Ysf zJ@@xdZ)|)Vv%jwQ^Yrzb_HI7@>5-7vp>y}lO0RzS)#Jl0om7Cb%`9d$G+Z<^E>-Th1*4PhYDgZra^_(x_^$SZ}Gw=8iYQOn$s#H|G09 zBF&q(KdaOocy^6--?y`q_Rg)o z5gF~Cl+1bYR!4u1)V}wf$A8YY|9w5uZim+@;cxEMfBx2g)311zeSYJX4JS4VyG!tX zpLzC}jKvNYx9*E|j=`cIufG4GH`V9I4g2p_il?o3Ce3-fc5TGIFMT?vHQSthrd`TP zZ(-Pc(&Uv) z;{w|^%SC0c%JN!kTNgILgzIjJ;JSmi*m8wJbUBy0@R-iMut@#(ikZuIS}J-6`%E&J zeNMpGJ38%i(FPS?<2Pp>E)(5cCABsuC~=9C$)pD=7XFJIs+)@@eM~v?D525$G`oaY zaMv;w|DdAD8|-)Ytd{-zuHjbP@_APySnf`1Ot>lkZsII4-{hN8c>=52md_K?k=eza z`p6>pv6R{M3TFS10izq78L^#A4iKi_5Si)V#R zyBuP=|HH}n$Hw!2+6rBK{NW+9$k!=*=hp68&CIrXbx%8MY3L%a_EJVcrY-?jKegu< zociCM&6SEiR#chAanShQJ+;iOFUyLhUawfwyD)}jyPjTQ;*RrIOgUKCe{=KPvdquP zacf$-_M}jAKv&hRg13*X;(vbnEaR^t|Nlhw4f(SbFMmq6Tx?;NtK}EI{OJFWSI;w# zU5*es&k|PAGeu=rqlv-fJ~wNxhZ#IiDxG4lo19X#;+*H~c4;+r$_$w#p zDO~pCIl$RCLDO};Vn;ueuq8>gon#|%ngKI{{ zrAW8%?U|L$yF(OeoL67%JGeK}^OlB*;qIx+bz>No1^Bs}DAtv+Bp9$r-<}awv8CN- zt46x&Jxf11yS^!1cavHK8tcT|J=!M=?mTIj>1C;%A=U3^GWU#u-232A(acMU7I%}4 zE;PCo_ti{B|!Oa&Y?S06%+0$*d*>gRc=8dM_-riFl3kL0a5l}Aop5+khzf1l# zP4d4#%ByBSEs2(mt9l&z@BaTE^NT(|vRchPEmJ@3P2Ss})=5W`xm9|T_t-mMab0uj z=MMc-AC6Z)sW%f*XGt)bp8qbWXIsFf&3mjKoQX|eF>BVtb2Gl4Hn+I)I??sq&zB~% zCLdFkuX^RYr|=_J)>|q0ibtOJ>b{!pd+#j&#NOt&?7H>u6P9*en4t5J`+jBL^tjqy z^Y+m;t`fvffK(y-{JmT zbG4N5iwRQBC+_@V6VAUnH_y*Q-gF8?P-AZv3u# zFS1WZesS`yC!U86y)t^c=JdaU`Mq;z^W5SVNj+I&qIi}cj`3!JzD90@9@*RwR-;^%0G&ddYv_8 z%AP&jj{X1j{h!_Yp6?zR68CM+7T>;J@%ES1woD5>cAE>k=dR!PN$L8|F5!6r&0Y)h zbf&I13`#I+Ru1sdF4-=cDr~h**YVB&t)F{Ld?h6NeT%1Pg={w*}5$%N}DVwD(YpSm$T>G(O%OxSCX?Djo4nR-raLv z;k9s$LuSLQU3!)~DrOwLCd3tCwZUuo-J@pHyv_BDGIRq>O(#E8dB*tpM8SroOK144 zoZTjVziR5_)ypFcyzi90{+pBgrr_hJ&?ya`!Jo?iZ?E^7_2s4n|4nW8IoAK97pPvo zI3a9%zd)9r)vAh5weOdH>#P4!Y=8c7zwOa~*UaocHQzt4y?+0>+3WZCnwLaaU3mUu zV=B|6mphI;ov@rUck+h7w}DrWmh&Yk;M;xoIEUJS3BeGT-jGMli&Uc zeYf+giu7T_=gX689$$$2;5`3_#QZ;d^$XrFWY*E=*)Z8q{@)AtpV9y4&p)1=5p%#l z?aE!g{l9MS@0~Jf`?u?nd;Xf8+q>(qcDUXK6T1uUD*;OS^QN7yoe3|4ei!PsB>6Z|O_c>eLFp<|=AyE=Q?&2z++3cHNc_1e!;EjKv*28bnewWSL=KtI%|M=zA&@eu^ zC587}1R|eK4hTHawQ<$W8M%{qqH@Y~_WwBb-f-@;6@GoMmi~Dr?fr#YAA~%Yn{g+hxOno@ zcSf1&?jd}^PUbHIZnPyuJ?N-3ZCu~M%kF5YcfpF3Ci@1yA%+k<@e3%mEVEZBbI z(5f!4BOhXX9iDNX+QMler4xDY;F8z?Q?1U;YcnTpTwpZkX3t7LBPw3&y4CPp1UP;Mxivhe7N z8^?3QQ|``DiA}7J+T8I(W%-VM+s|KLADggnf77b$OS_IqoX>i1vhC`#OV2{9UNX(~ z6JO(gPHNYxR^hIf|2Xu6R$VGgHd-pRL?$LC!R*VO9aU3LPhY3Nv?68l-%}ZwA!Rk!?40=<{&t>8V##lJ?)w&c&8+N(Os}Ww7WsX9%a|3fPdpSbo%NJP z)LJt&!!1#jSB?t(-W|Prw~mhPr_TQW43j;N?*Dh^ebKWsoK|b~QiB8YLUi=XPhJ+A zZNB{g+q7G{YQDW$TR$j0e0DWE;(Z|3673uJ=L;=)ez5)Bvp(bP8@BE2E0|O_@50Xa zX7?&T+wdJs$ZWj(wYFf6MP2%>91vXs`D4i_8`84tzMkHHc;VyYC!%%da85Zlxisj~wgn=hzAI*4 zQ#JAxY>xOj zR|}dCvvhyqD^bYYdg6=F_AbWjGYnT<_Xs(cwasb&#w|5Y+s}E#bd^rKnZi=tSC-DI z9nGVz%6?U`t?bzA{9J)cntS*3YI5~tdqho&uKr$JRxxweiip?--|{D36U%Ok zoZD3OgyS0PxAO9TJNo~nzg)#|oZ zdT>Rzz2e8m>WJGn9?tzdUBKy5*7CsjD|Oy?#Om$;A$cPB)tN_Aol`hpo7*ZDPteYh z;<$8}$4M^su~6eGsTVw)Yq@etUY;`gcJ%_&Y%QlGf!+hFZ|F_9l;HZM|D?d*{!M+# zIV}wz%$7aB%zCcx-V#%tkEKTi5|(**`D?KpVhc&WkjfpXacY+fyK7MD6=mmwX{=Kf z_NL`WCAlXXE&Q?B z*B!RqU6*z9U&+Oo{MYEK5|L@lI7qixHxGeekYW}`+ zzt%lAuX`Lk?e8_?%H`&SDXFD4T-|qw^n@)? z&PowG(eb}FQ*_s>sCe^>}ve(P+)h1Dae$wC8-EraO5L)tKh0BF^ zOWwTWSvf`NWRX^MVy*Kko_%E=3p19=_MH2;=~&R@ZNX99v9DGsO>U}wSJ!ZPyUgR4 z;`5%*HBWz`;#|>wjcekPKNq*pd;VZy@Eps^IqU7e8%h8EaMJ&u+V%W<3W04pmv!{? zls?PN4LdCCZ+9y9`@Lh^_y3ihSNH4onl(H(_U&{FKF-N$vqHh5=#fGB{c`ii+Sxju z2VZ@RuKYIp{HBWIicelOto4qTu`ZO+6gE2-_rUu6{sYJDztx^u^Y~UTFIzM7pKH(S z`n}ASFsc7bU==bDc#H$^T9)K?!Y&=S1McCm57w2$czRW^0>^(L_B87~lBI8E?_vWk=B z9FB?46&SPwT|Uj?;Hz<-kjoK!??%nUZ!40XWu?U_rZv^*Oi&Xwjb zGTkgEE;(crkh@B2gZaEgr-Xz>_O0-2UVb@#$>o=ucJ6K0<-WD@;MGZKF$q&tUTSCqFcU^(T&T6)WjtFZj!{YD8x^90i8s9(o#4<{&Y5NMD^!;momjC}( zTywtN?!@+am2;oR?Q$`dHI{j+*sSC$6ejj-%Wv+&o^Rin_q=cJzr3T({>P)~H>2(+ z7A`zqP;V~9`oDzx@BibC?`)s0InXufaC_K`R8H~lWk00te@?%-LiT!@8qez7P16)l z7cJZEJVj>07Na=JYO#;`_08uJCN7V!Z|)1Rh)(#;8RWI)tA{#gX@cqNPvY}_N$mgo z_IbtYLsKnwNBVrYEV(b2BlmEQW~WVx5=XPaa{Wxn{Z*VEj(RM|t88b57;p(C%2Ym^ znc8L>J&D`#+DU<^htAH)LbFVk7MXf+M5(c^y?7`=Gx=8phtzYAW)~q3CojF?CIKI( zQ+dIQFNt|8E}m`0*V)2)*XbH(exH|KYuDnE%1db-yOz23%S6leKEP1Q-B^6cnW-?)8s&6n5e$?r=Rm1nudFW_XrEvg?EyI3+xZg2I(MfJ_ zJWqc(c1$imPPTZ(@}C>$SLi!k)wt@Waej$j<>xl_RV;gcllkJU}aanO=sYV zV~^CUTka`jCEn&1natTJrOLdz+12ist^A{H*8Uqe?>?Wu|7-gc*Uk{byEC^K-`iO? z`TuqMAJbK=ToWvItci$uyI_5`Ok?XorPF^?&C0)dh90YyubFf8uy)PY)AcQ@W!%(1 z25H`tt>+2ZEF|tFHoZ7Zyz0)5f~oRzfCCJTs>h}!6gWkGEOG136CWUBUu;f-;IGH1? zs)>1-^CguSDbqcITvdvXIqN(2%$Rs-skZ3iQy<@+<-L{`EEgI1V4d}XZxc>5Z<*Bd zdAIYv%CB6ft)8>X$Elh!#_&%#;qvLyR?})B;thJkiBT}#D3cug!5 z%UBkiwpsHQht*y?`I<+Z_x?{jE+740WSfW2|NT@y>C@e??0VbM zt9sR;+}j^!Znu_+iCFlN={`rEeX+Q|?KhSDJ-^Jhm2BCSyQgPy-?qFJU6Mvy=h)Ou zsIp32c>Ce;x*zR7k41}HR)3Yc#PaK}o^{gnllKIeTEdP!-}gf`FD^DY+wAdM?duUC zT@z<7Vvl`t&+ZiGoZt?rym+fK{BKY!wS;HCD-d6gV?IUV7QOC90~+?)Kee)-gM~V#|_kf`@0T zYdT3zcu>>n94Mq{!pYUCpsqUA((HhN|IMO4n-rN(mTT8Mnvr)|RMK@}Wu;Ln`*GW( z&fBjW@|j{Uy0slRdUDG1YuhbVs{VXfX_vcnLtdw!)q-vFmgmPjKXr|*Uncs&wdUAU zL6c7~265*3e6!fS&DE-z|4!wEIKC*|3n!y1lq=s|V%u%5W>b;YxqJV2lQ^wQD#rsi z8_)W1>n5+$?&Bp}ybYx;$3}H1C;2M)iY%Iw=q{R-$T)SIgJfH^aLFQ1l{uDmP5w21 zerLv>U+|BN+f+GP>e#TV_C`90qxFr)8yOVY9C@;j%FJrldXb7x!T z@-xeX@BMyJyJzdpW378-zojXETWxsSoj=$|yDi&!*2A7mkZZXF>&IhggdGPo9Ctcb<7T>kyD+ziRZFT1KW8P!d&n>Rnop>m`eRJyZ zDGLw1m@~0O`lx7>W8v=ydR(D~T%t^kfs)c!X9RBbz5c~gGx|$vd#$9Slxy}e*XT(* zrm#n~S8l51xh#;q;y#$D;34XV3rt?tFdI+rs{>y@Kxi+X}Ky z-2VS1JX1~7{-gN(NBh|C9l!m2*_KT$(fQT~^Zy)NZu$S3`Huhls()zPe~vHud8+hU zeon!C=I;ix-W7cOw0^=i-O$CErO^vayi_?;lbVST-0YRTC?MIpRtTZVNd$~n#sE~ zSJs@G9DbL9p@G5E#WBR$?8J=N`_F%!Rowrpd;hVC%I!P9Tr)j#X7lS~Crq4MemH4& zcSUlXC_A)6YiChu@5>dBo?gGdth89#T3g~>|Atc+BfKwk-7GnklRv%Pf4-B&`RVs7 zrmCEu@%H`48+W&Fl#-ff|J!V{Npkg`v#%yH=j7&Y*lc!Ws%eFPYHW&{wm_8P7MCEw zmS&f8=^c-&c#ID0o~&?h^3q2B`?YMdz4mR{>^;p(F4Fc;@3MsV&sMKdi_NpIGv7UB z%0f3y--W^Ju4SLdIx+K~WXPhM+{QAEAy41jnl<;P)l4soh3soii3Km!h-oes6^~V^ zx;s<$(aCd#Vl}gu>Fiiv-VvUkS5*91wC}Co&)eVYTaxcqSN#2>w%7HA`n37~uZjPB z^vwFt3+L^hKC1s?dLMI9)v1X zBmV!@{i$#N^!V4j5#0B_+V0Fv?#5-ScD}zC{qx-I^B-@$p8x2{BgTKH{Qp@@&OF)r z+1#S|v}V=cp8Aig_n(S1JtX0KuTv&hlQ&3n*0JE;?5dIo4}Pa@4jh_WZziXgOkE{6 zajB#_pIq8X;{|u$KDKF5Q(ADhrswUs$?6sNmH8)aFX~$K->NaRZsim9UD(xdnY#k-}nEG<@aokd+YD>H2(VLigS}0$Lkf+-4`G9K05Zx>;;Q$ zVa?O*`-$uGX7ny@Pcoa;)cN;yk(H`=!GGKBpXV085Af(bv)J)XXS>`}{(1F$>AL3S z<`FiPBAa&|x_56vQMFj3p;_AnrPtyu6FD9V?sHqw;PFMi-L{fpm5 zX}6x0o-Jx8=16bW-7|O7BpwUBBB9Hv*UtwyPEeIJzhfG9MydaK`%Zh?jcHDhDXHh+mD_U9?s4duT1`XdQO3;{qHN^Cztvc{mVL- zbp68ntv7zX_1{#R+uMJyuFb#ZKL4j@KWA;y+WEWhvCa2m=9d3g>TiCdTmM99%DlcZcl!btnb;d^S?5Ok zeR}gvio@~!3&C?OhkMkLf4#SE^biq#xH8uc-i)c;Q7k@dYKU z!S+XguW$*Ka(X1V^NHnjV^i5Mv0o*h`K)gmy-k@K+??#OaHpD3n^$6qn||b?wL)T- z4-3dtzvS9l5RosqsdX}!vUk-ohsP(M7zs0F*L4WY3%gYC-~dA)x76k{QB_llt;KK7 zeOu6fYUlGCwttQsO1!rH{N{-=izGw;ba%%mBwl*_oA3Uy__{akKfe6R-@LnSg8%&8 zN3O4*>w3lXe)S(epZA9|#C&HVV2w`Ms zD00~|{n+%NdVZC>{O=#fEB}7oF7dwKvhWYfD&fepRvy=`UyHndW5M;`Qs(3|$<2iaZ#dPxF<5`5tZ;E`)m*-En-3C; zKX$xpc2^8M6Y?OTR(kJIJz2$zo252yX}NuK@%{YCza-z+{}8b{y+h&no&V46PTeei z{`1b(;u#`&)lV3${+iu&>|d#SWBoA>P0uBVTCLW(E3U{ndBndU@tyxQrui&2dKIx> z3bdB)zOZoilQ*+eW5g^jMjcoXaow^yu_-_^_OiPFE}faonH@NG{=01#wdgu6brECA(Ynu0D@{3Z-ri!;bdU*BmpQ%S5@2Pvt zdhOO~6}_j|*VRl-I{o?J;^jXVy6>BB;Qiro^PhX++kakaojx;6v|nE?apnF)4_ozQ zjUW74D*Ermq0Kda-%fvdW0w8@Bm4@p<=oiQHaFM&l&=1BE_}b!-7`Nf?EHS|W!VX* zwuzkQYhEjz-}^~RdiHLeviT3I?LIiJifS%gqQ!cB`8uzf+>H(SBJRJ~EAH2p7kqjm zdCfd6@O7rn>)ESg_LNOyf48&scK+VpNY$J3!rlvoKI&K_V6yJv!rl4L)Bo$&&kc{O zp8R%gET7K$3$Jw(nq3y(ynp-osjIJb^!X=9-Lt8mmcRFx(z2_Hk#8>AUwHoQa{C8g zh1{(#b#w$`7R(SklhPFxRP*(fWlzwY9q;SXp7!hsFZj~4=vtGYs9VG??e06}zXM;D zsT&!m8Bdz3*DrI{^R{lA(*)W02e$PuAFsK4=ZX3Kx`}^I81c`INZnxKpwe|WY5t|_ zORPH0Y;Imyk#T&^F3qL$U8U#rdBsh4ylhx=dh5;e!hG9Q72j`~&mUpA#i2a)kV)od zL77%h58v4;v5Wc-&pxa%cT=X=io;1MdsoZoi&VZgI24{C;-d&nPgKk9LEx$UWk z%^~!%9TkZe(mUVu?uM3X%{=ca9teM~EDcLKs|6_Fg(`BGZvzxQTsykgFe7Ty*}2>0A8u@ackp6b_5KC7vjfle6@I)`={C7^Ud10r zu`2C7dkZHYvTT{cq51rQwErVr`>&7fz3MY(o)%v(wCDMOSApx^_6s;F>c;G_-TtRUFgB| z`?v6>k5ledoZ1}!U+m^A_jNoGmPO)!s~n2oD$PEt_Fg2rq?|du%KOg8AA5VUE+ij3 zw={dtmO4pQCUvXsl+GpfKNzlQm7R-E6!B6kJ^PlMJ;UX}wB7HYU0Ep{WA&X+-{v!u z@bw3m8@?xWw;gl!c~y~pXma`8)A#@6|JQ!E=e3+vxUt;(%kTAt<~;q>E4`=qqwKD^ za!#r_J$av2-v4JC_v_nho1X`2Yd)-0|FN_F-+9fzn)}uAk8R&q_tgJ=Z+HCY=J3iF zjnO~!>s}rIvHbrd@yOj}owu&Z*p+mAuYHl25^bQHe5-Dc@v}=uXY1T8<1V+WQ*RFA z*HnGG#`W@*E4`{0H9c25U(39lyIpCAM#ZwwR`-b#%>o4X70aLWLuu}M_kfC(D_h+czdY3= z;w${9DrdqIjw$nS^HTQkq*pzZfNXRqDjo%j3M z<~KdZTVftGADA|g{lQ0{HrDNWvX9>X>D_Hn=dM2xzVDBW`n2gkC(8ebtNUEM|M0DIcKr9s*zG@0{?9u97k~Vtm+S4% zuT*CKckcfW`wu72|KZcO`OV`Sd-3hpicik{7h*FFv$IY`9^Eprq)-0AhlR}BInZV8`{UdT$)q1-Cr>4*x4ywR%>zp0+JwL2!Qd-h`8Y2P1S3qEe~^~2qq{OL=o zr*(_#73Nhm_xV_C+tA==((TS?cuQh)eQj@SXmZShFNd`3e0O~Q_gnJI?mfT$bnp56 zt&;1hg5VFU0C7<>6AN*U!(ID-Z0!G-Z2#@KWzicJSHop%xY_=)p4#+eZnyZ(f~PX> zeDf00vhVHuWw!rk^!(?)j!OS|aoo;n-ts@c-v807_#tio+4#kt!}|X|EiVW?sBZU9 zv2TvuVf{a=TpO|H(sR1ZG+GrsKUFSQxpX79VL>wgr4=Fz zRbEdvjNehzy88C-J$uS}7i3>BH8o38{AhB}&25X4m+6@~FSg33=e~Y$kXtBG!0X7T zi^p8|aG7WcgdC~PoUE|*5cAeRi`>8rx7&;K*CJ|CxGeJSAW>tNMJtw7=j7%e*|)pLKwMR2?wX^U*Y7#B^{lSiX2neT zbMe;a_x}msf26>Me-X>sTesNG*F3gfBNy+gbu^>=(6d`&ULNoEzAfALy-_{#)Nciw zd*&9IALU#+YV}nYeNpfb+}h=(FuBD=T_g8~KKH(|$5Q!x?Q8DeIk1u|Q7S8|DMUf# z>{K@SKQTsv2AzE`?>(4kZB_MRue#{_wyQQdaR*;F1V0FMIB`R6l9kl9tfd=nDTg@B z6boa!7QkWNy)#6TBkp0T+|~W-x?<8+1_-H$2{Q@xPM>r)A(OLTc#=W?o}Q+|oUMYA zb}X+HIF(W#>FSvX+DhvjjQk?=I=aIDZjVHTFGu4g^Dl0F>x%E}+~E;>J!hNLq}@XA zEIvr6tE=x=I=A3?aR29*&*wjtp8uDZtv189hWq=SuNr%!HdMs-&uq#6lmEZNAlKr@ z3&oneidSF$%DgBKlozV`)gAxzcKlDR)o)qv{jZgOvd?(CfoxyJlXLtNN^kwSwEwrx z*@BKsA2+XEGH1G`uBz(%zmLp+914$HGL=_AOVv}TE9=PFy|QjsJ8o7;Sl&C^^7bsR z(c%kUZhX7j!zL9zJQZ5^Gu-~wozF(|o+B=dFmUkCY`wXI&*^;R*7@5oFR)o2aD=SyQdu9~SbX<796a2(pe zp0coOg1BH|S!08+;IXAn3pnMpgk~qo)`j~_bq-6e zDn0yAC3R(W!`F*!PrZD%65r{uUCHA7oB;(u;cLCRJ(%TwGM}`ewF=qQT;#f*MBYT@>MPT zwGU2q?7r6Y`~8o?x_8t6@ruV+HHX|h;W_j1#I;*J9$QrZmD>N|^!!DWzfM@y>=`_T zC+A)6CR1snneQIXN*6d~eDD4u3BSj6OcS^3s~CCzIP<=K9`kB;&RqxDk5AwKu{275 zug?U=g)CYg=k^xQJ9YNOS{{W1=YGlUmbMm|oO$G*_s6>vx7bfDx_{$Nis}0gw)}h8 zlX)COZtnMfIm`Is-QAtZEa%#H{Hbx!Y&Dy7`}M5NkDJ!txskSdmF6i~8_`26c64fs zw(3^6OlVPipe=45o_oNQMNuhUTE4Qw%W1~u2TQv$OBv#iZQ11|u+TLvYNGHOkuc9S zVHd4bnLE_{9-cof@6@H?F~RDeYPZMS?&hpXUQ$vbymox=m`z=ZuGG|Y2*h6VlWJPe zap~BLyg!SQcPVmAXgWE^S!YSQzgf=i1D@|Kj4h7n-d!y7>VEG&y&o(4ZO*KW4x1Um zx?Rrd)b^M|f7#zX>Dw*0Jb%}o_w(FOL|H!9i&OUxwyK;y`MBTa*eQ34Uh#atUp0&S zwfT>)`hNvGy(`|oE&u!@mj8lp@F$g}w_ks_u{XT(pY{3;JFR;)&I>GhdD8jT>yL+8 zi}!qZ6*-Olw2xZXvOEc$Qq8VeC)X&fkW}NWPmoB(ux2^vMG4i2b|H^>&+I(2Dy}Q^3QgS6oxt+JEQ2oK_v_p>7tiFE-`KNH zbI}7=*OQ&%X9TxQ{-GFjQ7{daR&)3aqG-T4!(5c{bUWtjYkX?&X<1~Dqv!iCCSR9seA7D*B8#N92eQ;>-X=L zV^q|rD7R(%mg@V+*4y{!+_}tm%q;x2RWKA9KW=}we`@WQ}|RmX0N5PK&!#I%02ukYt5!_+g|bb&HI9cLeXd`x5cws zpYok!ZJR9Vcg91AJt*jmR#oMc<2R1E-Hx0!>BbDf>gTUgThv!gb$;;athP$0qa^2% ztsA!QJ^t;i?v#)hHjfXg|FS7NeRt3EdDV;NC+;)<^`P_9tsUZSzC~B|?K_$`uiA_x1}EM@RC+Xsvu+y;y<6 zSD8o3QC6mFry$RUASVHhnmr#_TBq~LpUUv5nI6vnK$gYPF+u;#e)+_kVZ2Lu?`X2! z*;ga8eT(BNzv5YZ^xbVTv^wrI zDbeRtQ2v3BkGCtVI&k>A&5?ssS@(T=^*PXM$%DV&?a$m*&r4i*xidj3x#GQTdEvV^ ztXbB|EaHMsKVRDB8}#qN&isp0b$7kBv!30ja=B!e`AyYOrx6m)3?Z6o~_fq{<`5mtf zzROviI9NA-g2$|+HjcpfRUNa%EI5Q5Q$Fwe&FHAz6!_!8v%WhVmpX#W%=B!Q{+L`Y z=-4rB=9vqTHmBykWu9{}c1noIrDWHHmZxniGCOvKsq@4gla%vbAT}%J$;Yp%3honY zzi0Py-b}G$w>@@AAoieW;MbQf!gF6O>bv3fRN&mgwdw(lJTnB|EB0=GW2(&ca>J>> ztb~5a><{ADcH!(K$R?PTe@O?FROs-{N z!Bmw{k#m8n##Ww(F0EQ9VYO>>Ppe=5QfJv!s}7#CdA#e{o!ibamnW!58hLVfDai_k z8Om=K^qRCi>f_nB(^sB^mW%N+7PcwH|hrCO-7z|Q6DN_Hvk zrC&vSn>PorYUqR%C*AyhXVa12=jS}|$Zzlz&hl!~zuF zup}?Ks2lTIVdzWXkvcV7JDb5BnTBxlg$z zFQ_iJt?FS}`sir-ecwmj_oBUYX9O5(a`oxzeK--jz39~st-Frxk&&({`K$c{&DYG` ze`Kce@|RzJZMh}eWAbFJob>J4Id9&6UU+=olQXXrbKVM^Q_$5=T)-*wQh#E~tgzLt zvz|qyXKWP{{G^sR@77nnFrPV_*L6clsfp>D?(FO7mRw*B*s^7j`@>z5m{}zF$ zAUsWp%039s2=ty5ZU zqLnb4Qz^n^B7>x|M%h2(zW0oxOjc}09A1X~p@K_#_^0mNDr73ZZN9xtlR})Cv%?eJ zX=46``P+^N&V9@x!nEc?PG_61)B(YkL!Q0In+r^w9<(lW?VGq!@3zwqQVcFAQWg#W_toHA$W9BC=IB~gO5PQkg#iqXK>sgk@fPf!ew)Ts}`n)`~cwK`H z)``5gFO_~DZ*}xjk@!RjnM*N#2mJ;phe@e(;}BSF4fm}e3O@b(d;-sGIXQe>L0zI z%_|;061)76uX5Rm9wyBVdJ~>%PCm79uko>^nnBz>9tNRjbGh4o^U3x&e^7KOUsq7^ zl%v-@LNfMvlv3}(wjVd9S*e@rZn<{ml_9U3=9ikuy-b`Xd*-qT{&y1STOiaDrm|P` z(>bN`(|Y!kWm6NPKHuJ$E+dowvv!+PtMDYY^C1;lGFt^a5+f$G*>UwATi`Jv<%ngO zu8L-p&~dwYToPSvliFBZ*Vsu@KOzClpzO&il!L`NZAHQ7HRA1icsJ~&`Zq|dF?z}R%d*_+gzLVPd^VQ=c)t1O^ z5IuEm3-`gsZw+gvZZw_u;nY!W9`}tpn|E42J9Nuzd7tcpHL}&4C2#)@-d28`Lsjs| zbhi(M`*v!j-6{L6d*|PQ*q9xAj^)%Jx%N$OQPb{oa~`g%e*fUWrv@AU$QR!%iar?B zzZb9X43DoiK6k9D-G}EM`>b7^qBBqAwLTD3a?F%C3!l&hN>o}2e~h) zsGg1LS=Dwv{`Qm8`Rk4E+&uKcVcv|6C0pjHcxSYv)E3Jx7Y>;CQ-B|yi<79VSn{@i<#dxGRn;JVmY@{(EQJoC@z`AeR8%_1y~eccDJxD^bF82 zNxP@te|hF4p3bA2`z$=p57N@X_Ol1(f3MEcKrvZ4>ND3 z+U4Irc#MT1PW#y8T0eQuiZ2U)SA2Fmd-Zx=(eF+>>&cv@p#={fB>s8V(Vf>DF0Owm zE@nZ9?iBu(O)7IO`L>I__`d7Sbk^tV)^FTt?DXj7P5IB)o|?Yf_nK#G>))bN_m(77 zmpeUXc5{8IdgN3>aHvdF?9+|v@(*u4(|f$O_S?6%kgZQ2ymEZ^@55Xfn+-0zXJ39_ zVp06WV*jU`_n$5`zjrKL@}N~ur|#xu>n?gJs@%U?pk(4TyW$6@>$L40mE5nHujX`l z2Hm==7nK=zQ}Ppce$1m!TYYC``DkBOaeL_WM0v^Dp0^LD1SWoTJ1%%G>cfMe+3J03 zH(YvmF^K7@?YeIkFFz{2N=%j2ZoibVcck0-uRQ9XZ)C4Z)A@SdKlnhzg8oqVLZ zOT1AbX|;x3or#gRAvb$Qy6wD4%ykCVK8tvK=iE#%4%zec+l)saoq{;r8y5>Dw5kZs zQccVdzPP&T-wn@arng-*cp5uD^l*q+v@JT+=^!K$EaNN~-M_M@dhwLLzAYN>U!E3g zPwnF@xGDPQ(uY#LTfGq}0ZpeOzvLX5tlod|mE800^|?j=XS#N&ZP>ZhQ~Pvg^~qyX zP47kS^L#7%_GxIzB;{9=mbAqsN4({8&|H_7_xaY**CwG~Dr0WT6s3h+dY|Isb-H(M zZCQKpx8uTF`h=P9?{#6FaP89x+wGHe%XU^(&do|ab?uv7XQ<7`AIfv=YMZ7W)yte$ zSW?+~srujY{r`(ie0y_OS**+Tne$}UeYT}F*H5ib@{8cwqnIxl5fS1_ z^Y$slCYMJyGQRDM3bmQ1Uo=1RiS;9;Fbglu6*0cyOP9P>{p&pM`fS0M1#PRd*X8c< z4b8tCu6e)wxpPjRd(6&#Zf0{o{))~wnDcg%2Gh?aDUPZkTT_e6XJ>{Q&GFl~?%4&e z>5-<-o<;R?1z2~_%~5t~b0mXL{m*Iv%>`uu*IFU#h@E0@wT zPo0UHvpQo=YMA)OD@zln3(mgE#^&uVDHbgoq87juCbs(vyV|V(1u-{egnQDwJRdLW zGjdj1tN4R&7TZ*gJu5bZvYwkXQ|wl1$zJ!9EK;6oDwQWRU#Wfbb?s2jI`=q>+sJW- z$kZ7-1lt{UZ*GaQIOC$_bGN|zUh3vGKeBe74RJg4(1q(n~Y=0uOq+Rg-?T<qx6Gi|Ev-*v?E_MKg`gYPTX{p#C)bNr9R?5dl) zbkn0tzj*WBtV_&oyf=H(s-QhzV{Yy__b=sGhS9<`X)+P7l$Iv{ea36mxo}1HtVEgG zrWe!M#r5AfUbrHIx61k?8^-r1K2KS|dU@B3@U{gPOlO}JD>YWR z+;miB`P6id?9A;^I%`tiybD)x+h``UDcaP;%dJ+YF-EIr`h_PFsuND$iH?aRBu#xHm{jN8 ztjX8m2vG_U)L3$$wLwj5YAcV%TLldl4U72R6>}dv%xOG7+e}>BUpGO_UUZALM>+ST zxT9ZRnk{deBprQy$IkkBn{GZox72x#Rb|`i>}-id4(s20N+0HGSs(0m(+Qim&g>0e z`x{@bWu-^|r5sXJjd-*4&6|AZsYY|pE?BoxeYeAzD;*CU&rDr=&3LZg3w^zh!S+9= z-?%Mrc=vXCd9ieEP#B-Qf~RrI_a~W$9yYUAzT0Xp`QChm%F^Z!P!+BHh(pf&9ms#d6>v{cxqp_lq=loV&8z21N7Qg=L?^b6~gUmvoq?_ZZFu3BI6(K=^cZ1Uzgg#|Aa7IE&`R@b@g zYQVL=`~Qzk|8cEbKj1oJzDb&5@|Nvwav626`R$H}@B8Pw%IjQi!o069<%F%3INTr0 ztXr8};c;wzW&M=^AKiYt0`_kwIGa9%q;fi5y3wX{Qe(ByQq|{rvCAiyd8At1SK!^Z zMlkNt-j2V@l2Md!Xz>)d;4S6;xq&o`XE7rwgUd2H*`uiMWT7QYozyV~(H`_%Q| z47V>VOZa$m%?zh0Y9uRnL~M@#e3 z5am`^FaHCZqR-FJ^QilK_58;#OU*aLZ4lzwZ*}i!>t632tS7mn(?8DtC90-);T=!J z72UY_-6zuadQM}XSN&Uc-JiYx)q90Q_^jA}$Ly?|n3M41)#>}s?zDPO`TTW7NeVJ?CK9vcqb^HXwVS(dpUdKZ}n>tt~L|A7YLsDe&@&S|DE;k+wML){WbNTR@xug zTItRX=g&dx%X}NJ_7%UJ@c--gf2y9_Jl;Ksu76>jdE1Jsgo^l{y>-s)@1Emr zQ!_+E_mu`J#w^&zD6bQw*(4s)tLn)T#oZ)uCXID(*tyB?>zl4l6}{#z99RF?_gmSu znP-<7P7+XWORV46E4 z=ly2mxtBOiT{UmskqkEHjm@=~mv*P}wB|dzD~m6mySXmw--~0Zakt{ri>)5tJLmXs zb+w%8iI%&Y4*l{ls4joAV*XdlIa?%yHa|X9sc=|1XMy{BiG><_+|a%O^oeyAst_1BKGHT|cXV_hnnmoTL9mc=NoRX!WM zE_8Ryy`7y~8EyA^%&>40&Mn$}|g7@s|d$+#M z4mmh+*<1@X&D1?LRp*0Nx5_QinKi5bUJ-Zt`FZJ^_8!-5*5i)4Jj--S=*e4e)G}?A zH*eTznqs1TLGAGZuOBxqI^QY(QN}h`?6Sa*YiD2EY%gxker)Jo+Wr66J9oiRr_muxHuikg;$^1-PE+GZS3;*7=owE7%O@4iJ z=Yx(*GM>EmOLo85bc9QgdzH|)*MiQQ&mVoddXL3zr%)l&%{M$dS4dwnNsaIS`RSh8 zHqH}?O*%|vQIoa_NinYvf4#?QSEoMHu`MT->;Bq1W!jk?TehBC+AG_VbH09?n0#8w zPB+d1-{#F_|AP2`tV-KCwYZ{^QBb5*c&D)5ktN%&847Q^@P4ui=hW?6ZgU>=XqqxZ zr#rLm^~Jb?qEyL5p}ckZ#kW@~_eh<;RUjWPZsL3RRHJI&-LkgT(z|2!mPpQDcBkZzNnUo*=IA!ED&88}$v03f;>ipW-%0ZhiWM9r&gQKD{l6TuL(gt0n{v-+wO3nd zr+C}qw{uvhZg|M8_IbmGBg(QmEBV-?b^EpisVRN<^Do*azURlJpq}ND)Bniv?%sGw zrFzM(vko)t+j9SPnu!b_$;_;OJ> z=H{K{YgyRCrtR2by=29*E2XUy3O;=ce&N?29#`Fd_O0xi7L(UWvm1(e@40iw{pin6 z_~`KcoM6nC)vrvR-w65YJXJ2!vB+$%X^ zO*eDeK2MA(zP;cm|J_pUd1VXEt7vX`TpzS{$~I8>$_A3=HQOn6F>hye}4S8?XIKk zljew@s@<|BB`El^LaT!EG&fHXua*@Ld{iUvxrzIln6P@jPMCl59g8B9r}oiaMtP37 z1wqZ{OsW*8Z4qAS5hbZ-vFX<3E7M%gUeH>m+-wly>KYn3Ytgc+o)4>QvW*tpU!i=xFj37k3Sywe{4Tr*Zu$R{6F#`!tV7KQjK(fo(kXZw|njdX_Xaj zo3_V4mg>*Xa!{D^@XW>JEj)8}SM}-Jf9KkJ($4>0A-A@-cZc+R`ObAbMaHxBGvU7YsJ6UOrCz-tk;{x&P-QcxzprYp zi>t}vDf;=jANuE2%dS+JIOE}G!N@rq?$0kWkeN^-C9zh*M_s2(YEIm&#R6ShE?oEH=8Wzw5?z@Z38yJu?aQ4kcfa z<1;orSlO=sIsD(F|GfuOPMwY3{_{h--HEqntN$$h|1rPl`88=1X-&Da;`=^`UJALG z>)LT5`IJofc9*qf?d{L1@7&vUxH~_m_;td3?!A1|&&9cD-*egZUR}A(^`v9g#;J)` zvy=8Zb06Qn_WZ}=_r7WrzZK&_7x3kX|?SFH*FIr{_YyI=t`k#LNn~}zr zcTIgFU$UsQ^>2%p9lLY+x+N=)w@$5$I?HCf(qj9C$Nlp5r>lzPmRVlw`Keoe#JvCA z#hfjbM!7**=L1r8u53)YFXqhOb!tnW>R)+@>U{e}=T`3e_wVv{+qU(eQ`AJ{FZr5% zonZZ~V;Ya$L$+?W2`n3!mBbqT)NQpcFNtI~)c!ctdAo4P=96CvJY>8>rX{F}9%6mg za6-g0Sg2D%H~CS-WD}!Di;hV4Z)fe*cwoAuZv)?|OwXCpb5>@2m9pS|d{}Y$^=p+s z4q48t{HL0iCu=y<=VIqE5!K886ek~Sc{bL-F9b zANv2l%zsq9|9N%Avz_7*k#UbMEp7jC`~Q>qAN=dTJr9t3_Uze?^78K2;rW+yk8i1( zm6%l3na7f*V_ObwzLvf5&TGT1(-v*oGPz^(Uefm>f<~u zW>-PudYjKY#dr1m_rI_G^J#tktj*8QZM+hD*gjIo%|TLF!=1Hm{S|Kii&M3`|;^@ zbxKg;9OaMmj+Py}^~lD}Lv3@DPFF;J>5e^n4m~=kcS&N-_ny5E%S>A?_Iy3@@!-@- zzq6$~=6>5LeOUVFlw}P+!iVgd3BTijEz+xGlKOV%L$Q z&$#~I+H-KBfXS(sEx5i6z20}ZrwA0K3~*1d6W&*_5a9Fdaqs^0H5@cLB$|NTFy$9E=~ zS)cF-j`SDebo;%#=Nq4glx);mOT9UkXVeeVg!d!2BezZ&X}><5a@~CAUPG>(2daZqEp}SCufCcR zFpY!1*P}Qyai*|#x6Ci0KVOY3z*lb1+H-HF{9pX)RB_UHf5-KS>Ky~k8iDF650 z1;*k34IRH%KRL#JKmA1X^eTZh6W6?Cd*2r%ca`(}PR8v|^|<_%gfA^||L{nc*)*!@ z!`EkVjmGkJc{V)HXRiEF;BA!ckOouHWRpy|%yDH*XhB{4e)At6cPb<-M0*XF5dvrkoSa%*05S51A- zgX`bzESWZA>pY;@_*upHMXa z5nn&k-hyB2l9_>0%&UF>pKa+}xx-#|ldbe*70#$D6IE3^7*^XT zC+nRQ3x8!%^LvYjK-VGrj?;e*c2;C>x2*l$9`B%Kx^jxoMvbM8l}lDQq$e^iSJ7zx z$`;tD&w8zeMRVzq8C;6ClOJ}Rd}#3?=+HerO_ft@x0bB*TEp@2)!gOvJI<%rJeeo> zgW=?{oetb<(^!l5%6;ZOZBzJBsc=%k)2XZX{Co2{B(Ue4)QW?349T4Cb2N6H6%*H+ zyTD}9+`D%1zfLXBFf-o55%cj*n1J@44`)6xsoQXOe}2G|z1n+=RqmX^jHIqizul8B zyiB>XYv0Kf17*+PbMxvfHWcr9zVWH*JKy52iyfcWw)ETmQjs`$?yv5QFb`hu*PplS zDn7H;WP+Arw#>!1r4C$GcjXS>>^W8WYxAVH7iSc6#|nuSKDuF5U2J$Zk6V{j`?+}J zJD(Z5H=o_T+jrmNFRa_tl=XB)V{C0wa+cd{tD7Tk_Wr@0o}UpHJB|5Pb^X3zl7;2)8wO;-P_GX znD0$KzwAwJp23}4oUSWY+~nd|W9Z^>Yp&XI|2Yp91~1>ScjwXknpeeBS{>7PkF4-t zBD{xl_j|W#8#Y~9SXtG%_wXF^ck$29N$W4Z)G$row>6MyQNoI#5YgAiuV~>Qy}%0s8xk}lV&|tD%_{o;t)oWf#kSYWuUQoSVp(P&)634EH*;3n-)k4|vF|?Y zk`*Q{e)Gig85dhxxqpaZ~WPBY;vkT>2xQhb|HIQP!w~ZkFmEv zMUtka>Gg$zu4m??3VDADYhKoIa*JBntR=VI&TW0+W4l&m=_Nz+HupDAEV=LUEuH`A z#3w8Ft25#*DRe2BrMo|gSR=9|NXWK8T!?9P)R)``7tCF|KE678@p1F>xgXfeXYT*2 z^3~gB1@m?11*bJV6IqSJAGUBj&IfTO*;$4by<4d5`=vA zJ`IllBc<*)|M98S`k!9!|EK!zvivVQyZ=Y^i+7katnXBgu&p0Um7+N2pS$5qxTWtyybcH3mllG&yiYLc@rbDFcc z>y%dc_{`t>&W2y!-rGFac>Wekjc+r539VL`G|8h&;n^aV4mD-|speZ;x>S?AUIfV< zNj@Y0CiUMl_SpulMpq^)^YeMCiYyKA$!VO-v|eTB`hpO~hIC&8`%8>3UN;|RUr}KA z`&P)I4ykHJjimum3afUNd~;mmab}Dp&hQ$UN_EYJI(f%+WRL))ti9NWUUKX*%oOpM0l}_ieQMZt=am?B|>G zy2&9fCeo5|6K!z`f7X`#=7F-2eB1{KK!|>xv$q@@?U63P0{A=f0{# z=Vd|BQup-4zOBO92X`;J$+EWM>oeVu$fBCR5^tAu8?~wXufNb>{PQ2bx zvEftY?=2tsKXE81m%LbP^-YjTS;Z~0C_W&eFzA8EnKwNiTuau!Y>jeIDd4~ImSM`8 zQk6fmPxT#YjS5*MP#<(<=f#`m^A`v>GM*0${jlr%qI<6FY~1IP=A>I$Jh<_TYj=D5 zox67qWgjiCdDN`HdOiQn!ZkXdi%+LSHqTuft2UW4()ihpmwf&<8{M=sKew*0m#tr| z)TykbJ27fwy~$JY`lp|3?yK)_p8qS|{`{{~T=IV}{QhbF|CzB!aCcBs-ujcLeiQ2 z<(^mbgmc$iy?b79yS_<%|6P5?$MYr2PQ%7kB6mtQR`issI2m2{=$w+2Y_dp8Tj}u( zZ;{SRTP~)AC3oJG;+-iv?WkLK-W;K`<`V;tT-)%Vz-sYwX_Hbn%?As#I!iD3h6M?& z+~OzIbv2{W`=X6(lF`e8PiwjFUhOn~Y|m5h<+mmOvCxN4`95_UE3Q~_q>3}>acYma zoZ|syg_IXNwziAZsD3#2P`jJ!v!(!(Xtx4?M)QW|tzz*KA!`esd!1&M*}vV9(XV5J z_ZPE0KPw8hiFrG2*k6C(++W@l<->kledW9UJW#hkxVF3e#vR$B3Ja!(l`o!TN_jeM zp1Wz+zC*3T$6tP%_Vcv;N8yaCD!x)*q$D>Ar$jbq=jVK=|F!?$_Z>`gdVf4KK419i zjNz=iW}E-Yiiu6LD0?Kaes5K`@$qXvx&I%RKc0Eq|Ign2hYj=tCnkl6$n+g^>Fp_; zxHzeI%YmbLx|km&%Or54O?^x2;~>j`Ue$+MMYZ z82aMWCE>2NzKqaWS6oBv9w!(*DVSAUx7JhLFJk`ebQ;swm zRw#B(T{daLDi)Uwv);Ut>v!!wGx4I3sM8Av-d|$s7ulaXF2C1)pwxX5x1%PDADgO` zK2w)V(ks8{R(Tz+ZmoF1E%R0@G>Yv~?(A_AIjQhR;6+F3gxR4%!Bf>b8az4u9`!Ik zzc|sYqJOjFjpJckjh1}(Ni&{T`^~aMD)?#Nysn>({xvonBfDY0{h~EqdNTH| zyC+Umn{@T@>N#=o&)>W({_}VK&q%xPH}fZ)nz7Su@(s&J8qW$$p8fxO|8H*0yA5iO zwBvuV`P-LHyjIix*K>ZH@#8BuFW=d_+go|kpBwXkOfI=DUit3oYL0}}_tO_%P74c} zVv#gAVVUwd+4#p7z4<4r@v=SEJResnn9RF#<)Kp-BUY`d+Wf*y@5wO_?Pr%c9wr1$ z>o_$@^2Le{A6JuFmp4g!dT+@2Ow6eAZ2q~$clF)me50i5sEb{qyi+e-(EOQm=tR`C zO$=YWLRuzpp5W39(o^xCGcA|R!swx7CqtZYsg2pIh(FDGIxF|5H*eP~?LQz8A?enk zpsH;WcI;sE-$t)_6BJ~X*yeNYVJ!-e-{;uqr`6ffTo8C8sH$aiNUJMnZ+U`tJI|$_ zgL&5~mQ3rHfK)i@ttId5(KpUZFF`4uwN zng4pYb}?^*$eFG=tF5hXEb$a^cS&;#J>xNT*-R6|Yeo|OJBlx)r|_)OxT&SHo!{N& z4)1~aofASfOWtCR`*db*Y}jqd=VH5TEM-6U_U7^}7TR7g>q^C)a~+>HJh}C-MgQ*Z z^NZsO_S!!^SI~1oNZmGE{kn&e=if|08o?_4_)*mSm=!RwRbu>fsX6_r*)$92~@l>`Gt zEi*TJ+Lg+@d&8ElEid=`($UXz_EpSGR@X27^@TMjQ<8uAnJ&(=X_Lb?J4a4?vboEm z^QNVZ%TEqn({r;H+))>iJN5V;p0?E7O0}62 z5N7=CS8d(r?)db~yuRkMf=T;&JA``H?%2BD{n||T6&)^|r;@ymFwI@l{JF`~?&-OE zitRfcpRp?I@7Q&J`P+rU>Ml-!XNz(sEsUP@{QZ03%!89(d)rLh{`2fx*&5Q4F!yjZ=TzrkRevJcjvkGA#>XI;vLnFnqL;Lbuj``Vn$U+m&J~T9q?U>b zay@QJ+F2&2tK<_Yq{?~E^-cc!0H5?9CI>B#pG%?B(5)(yB8Nb4?g`rI=S4o{&=AgN&cZR>sUzP0F$4~SwoXWZH`R;v__kWyh zfAs6@aE;GukN-`Hv^!>TGJM}Rne=n>mw!;aF5l<&=9y*kV-F?XZHK~G&swPV8GD6q zG`2{}Hc`&fxa3h#JYlo>j1TvYrY5ZnxHidOP381y86WR;Z)HCo+32cgVr_HJ{>i&% z4bB%Gw>=A-JSnTZLo#BXKAX+1LizQ|aW~%^-?KY%UHQ(;^%2=>XZ+Ro7)WoJ*k6(L z&cdlGNOE!LWUt6uyN>UEHW8)gUj2#Ska(Ek zeb(@zg4nE?0aK!k7ahtzIy-2o+X9~%A&Z)f77HkyP_sWVu|ePVtc9wlN};}^ck5a1 zf=yv7doBhk?paV`wkENcf01>l=dBE58wq0zn*~CXlF~Y-KD*eizhOt&tnTUa9xVLa zpP+Sq!TsYins)B2oTQr%Hd^j+Qe)t5!XF zsjhE$iZ{OUA?uo%hO(kP?aDp6I}4x7#s52W{f528gN*d1uZ3qP7Ja?t^=IA8-RtHo zT^O{ZU{+sfCYb=$vK} zILfJZbjr)PzH5!8@AvTU`W7i%Z1J?(?xSMepY#7D%f9nV@cv%2PA&HCK-&-{G>eAG<%5x`Mk1h~fo?K+(I`eY&QW@8-wVNKS5ZP1nx-39X zNA`SvUSL3y+pOxtO}op@e*8GPS!dgB3GV9MyXRaGy!pvUKacmvv#-^Ep1!Z0Reo0g z!^XF@`zrpk?KC`c?qM0frIl4m>bj%6$vUoRr>ZZA~pV2*kIa9Z=xc+e7P zmH+!KYST3%slJW(F1{@LsP@L~^P+_MDwaiDK|5Dn6VKnmaP+bGxsU&UdDk}{iYzNH zi<}Xkl9$$SoW1AYCEmJU^8btdVr|a5|2t8>c zzVzw*{=(+H-OT?!FPDG1`&n4U(_^uHeC-97Ci@k;eJ}4Qyb;$Ozpq$am-Ty^@Se3E zlMPzg;ymPl-4+|zGR@M_JU&zk8pOfj$P)1 z$!bX!kB|TVGXKfm_w}=?zrSy8xV-%Q$L#t4g+kx{EKE=CzPfp}j?R}Kzpl@Jdg`Q`NceADgT`xfBAkoDWT}--gPvr zJNfhHlqr8c-4pv>bUK^ex$N^Ki)%iog!FB>-|Fe-zWsb@t@WC+Dx1PzGCR5NF&a<* z^!0c9of)Auzpk;JzjNqV*VXfiuSnI?iQzF!VZ1GN14O)_OOY{7%N!L@36z$;a z{P8zft=_(K3!_g#j@J!=X|}~je6{9u6&$;GS+**(+cuEpsJ&)$+Fo7;AAQzy6>~V} z?$WVcwC4D;Lq!Dz2M)fIJH@pmIC1&Prj3&ej9z?{?iLcfR~-|o*m7k0v{jq7h+A)& z#k=qCVfm#7efqVpxaZ8Ve{!Y&A7`JMvD#$G!X=V^$8W4!v}S>pS6?5WMfoe1?7gmY zG*9Mw>O59hA1VD!My7Y?{+)-{|38-gbE|)CukdT`KjHtc_AAvk3N~)4{{PQ*Ud5-t zeV?tTd0d)e)b;WV>y`TDH=pb%UURl|=kCh(xpQsT@uyX6^|!J(!fkP?c-rpVwqqau ze3h2@dc5Y-%5uZ|uMIb+BFi}bpAG$vS}?6Jx-_dW^TwSU&wh%p|6u;_+5N*(C)Mq~$%$Qm z9=K}JB!d_77wlhrez#oi`9<%8#);X*{jbCG3!dI8T{hY9)%Mkaf441;?Ug$7XvW?< z6J+>)G#;P-Aj2y0`#-+z?7z3CrOU?_HR`y@v zS}A(y+JZ&5{`Pq&`RE>N?@ysvVtpW!IGrU_tf@((68^DobGpF?t4SY{++dN#k!AQ zJ6I_sYgjxf{5#)|zRlt{=KEB<{QCONJ9&c*oT~&|Tf{T-Wcz;ndL91aWruv#ug7nC z+H~a8=EOuLX^7f?-nyFS_v|l685Ya_|CM!geQc;prqjy@>cMBX&$aYsQHd%JcxoYd zr?+5|&HRE8QJGNVW&2njc6<==3OIW3*%#x|* zLLej4tRwLBmVF;qCQI~R7fNLAx0TrA*}~DfqKEnV5$S%9l7gFo0Z^9cnpgu}8pT zZJ}$cm+86CGi}M+();%IG9G@|QntQVps)Um{y)*{W_%fDLTlD%9OLY@Q1cYtR?dIo zcksD&XB3l;Km4aqG1)`1XzSV!QJ?t#|JeDNap*ZTdzd;O!Ie>fLBMRc~iYW(h6=D+i} z|NL;bKepcPyUzU`--H(bRo-&tNTyTXGM;v2yMlj#bx)Y@A1&U^{O<1F!{u}KAMkNH zIOT0pcTm1kl(4|GOP+k$6DEad+JC*HxbY2(!;CGAf->AKT%0=a4 zAWu|t*N2IUQ5lybcQj0%WWP!*qIdS6sV`6W)NtxK{zw)nWvW$o^UAk3Y?3&5LtN)% z%e=e|k#`O!b%u(V?5=&AtkYb!ce32VSFsa6FVN}rlR03sw7qJJ^4`#)#(7;ffw-p{ z%aU)M6-iW`9O-J@HKR?8}Va^m@(PyT4oDgYx@&{nu;N=GXs>jN4T%tf}B< z%p=*TB5KHkjVci^ymybDL?MAquG_QoCk8GMeR66+;)JkV2p)DmCu zw-dx`+@_;$l-LrAR!$;rO-K#yN;HEs)?ON!z zFGBBGAHS54F?lX^=g!TC>;J9Y-z_+6=hLejPL~JtEL+m45X{+rn6ua7vANy%RJE%; zCYeg2DPDrV9cCBbD|mZO_L9lso1BYp_5KsQXT8YeXo%>&!zpHupH7ckuI2UYT3E%4 zhwcm#K1F$N&g>}vU}gX7-|-*C^8YRNe|Z!B^VVPK8EYjAr!1Jj&x7nXLfyJMfieb$60Ap zgqydsoQ@Xl@_b$)94xZnpXpKWRlk(J=RcafX5)j{9ZzZ(O!HJp4Snd-ca2Fl{m<(7 zpQUVvInVx7m0Ih!Vg5O7AIs!*cT+58D5ada-jZY@mAJ^kx^L?;#gKy8-6Fpi+qqxn zjMP3_>8vQbTe-P|S>V#mgoC>`ZaqG?I(o;cE36y&`Eu6P>6Ny>tAF0QZi&LR#>Sn3~{J#7(V;F<|R ziW6U`TegHiv(wMVNQ#3ylWxf0Tvn3~!7^Qhe6lN`EU%N`qgWo`;F(p8@_ zxvMqp*r_CGV{tvb14Z(4R?HutBT|31Nvd2B1xTpB_e0F!G8SWePl=2K8 zh*tA!>N>e`te&uxRru0w0kMUy1a=JpU)`m+38u}#&6?V$ z_9zIk>8eD|ns7wWi=}*5VdMtOIUinLU;jZ}zH-j{y6e?HZ{3y7k$F4u7-#WbJ-5hd z)34pk{`w=|{&VHKyB3fBf4ZnXF-U;3H%!z>>fsZf7+?MnMwad`5o$h1bdF3BQM^{d zF#A#V^FzBPb5~y6zf@MiVnRRj@MTiukkWP__7tN2mVYD)46x5cUuw1>kDxWO*`|tgT0qmzCHDIg7+hh2>IO( zt7{${e0Q{Yndj9lTOa&-`8sfS7?-#R{p9=PrWD4CKbxxyz`|$qt0=;G@4{eO!LlU1w~cB~BBr*UryZg|rBp{mAXEYIs!p;lF9q-RRj* zF3T(p<#qTrW0O-=;}@enC8irAT^{mo3uWd1b1wJpOm5fETML-q=xozydalo;$$Ru- z%jD_uk9P0>uWa-A)#^Jti&^p}M!1OxcTa13`crSZer!_ZzT=lJ+5CU;{J-w{xSfJ_ zI$KZnuW^reViD*L(0Y>~>#_2JRmh>hOG|=;mn~L4lQ`{2SdPZ!Q5y)(W(?YZ>q z&+OUeKePT%&~BDp!Vy|l7wNs3;^Ni1GG;-%RrlIe zzh1MrNu^z$WW1NP?v1}&d&Biiu($%+?;Sp8(}&&8}i`+VOw zp7Z-FM5it*6?`2S)yR8~A*l7GD2ub0qDr91x3`6>9c8Bau(u~D`YGM&Rk`7weezRH z+nmrC>7F0f;c(PI%_M>#Rh3TD95Z;_&!`(Z16>-Hx`bx^}NDPGQfH*}t4u zGe+(Yi@WyoJj<@`DfyRHwuqGO==|j*z98nT<>ZALALR5+S8aE(+TIr;xb*BxarZwZ zrQOT&K6zYo);c@U=VU|Wy`S%7nx;QcUQ)B$r_yzr%&L}H#R)8Beou8dTBk_q+icq> z&hoHNAk$}}^|p2Qk4|NiJ*mK*({yLuH~v}9jm3PLuQ%@6`a>(`(L#>W7amvN)#-^; zZaX9vDzIT9)1D)~yzl?d>s~WI{?Y35`~wJmc$#bEKZ@!ZOio+(;4E+6ydGE|t~=Pbx1@%+H)sNxo> zxmy2h*Xt(g7n;aUHW6CHWmdL*=1M+u{&yQIE95nuRV@u!A7z|So*Oz%?3v^d!KW!M zLad2Wk7bwEE97JzIvU%}<)!#BG4boPx@g%Aj^CHOyTqBi>BIS^28W9BkPUn}sY_Nb z_q@b9r!{h;cVJHI+#}>ca?S(5o2_+wze=myX zq0rZ`1=e)pjeB^yOZarI zo?VI9Vxdbq&d0fe#Z|6xEh*M$dRMA4xuJc65{p8jJjcNnXRcmduVa51x1D*-ZCbJ? zP-JmS%8c_LqAtBr%gMi!>U8MZUFLWB_m2D$TFA0=gN*Dq%j+{9fBB?zS?AF*?GAng zQA0le$lrUp1^>Ki+;u9n)Mnq?_wxc+IRC3DAK9&N;o;I1!mC$Ly!WtZdZf#qqDf1( zwteWzEIgvKpI@RSukfVqhx`9+qaNRHto!vqcA|ib zme0{8Ar_h+OCLG1K30q5Si$mpGUqApMJi0M3qu8pgyJ5&>YSjkLgLmUy=5|qd|n$C zhXk^$Teb5Q;}6ZXODC#Madi!yx})gSsaH1lzwOI^erG1PO#GfkL&1AaQ>W`}QIf0t zArrW`b-G~p>CZLqc8WV{Y6&+i_iB;NT=MAFOwTJ{PJOUx{eI(A%kxZ|$^RxKNiC^o znWy5XF?Xr`+zWHFH(B0JGm$m;7R<+AE^+=BKdZ`_(0~iE;=u)9EgOGF5iL#tj@nyYIDJce}FS%&Ju@_Z~}3 zaX8AYxbf1pj_*>s@e)F7CvH@k|4btKQirIxoYiS}`!9^oW_9PtKlM=R%Iq{yoqldz z;iog4MXP7bpa1yWwc4Iy4#DKL3PN(4HcpvJE)3nD8ocE7s`6#8RZY1f^0Vh-LdRy& zplk29uCbqO%6dLgPDiNjw)X0!T*<1z5^>d!^}heA?pc0&##{3Zn+4h0Lly>Usau{^ zdU(F}iF3(yv7htfeyB|j^xSmCX{Ey2Y|^a7a%bZ9wuG&B-hHv< zl;CL-A7`1y&)SZ=s!E<5wC*fEIP>L+9b@4gmy;Zcw%g6O?I_xCIAe=<=J6djTTX>9 zj1WlZ;z~3AcUj%;*ztKaUHfaF&bBe%b9#|@M(Fj9bFJ%Qt}1Ia@o&7udG6+iTPxV- zcmMjdysY~3!cX5XU4LBax+eL;yi?L0!H>3XPANVf;3D#KMoo=;zIvnd>ZXR{2c&le zU0f5q?RV|5%knNZZ0%-|(zjm}%(UoO;>0O=Z~G~>36E#Hmff|G>i0=rA{=l{*qAl) z(t_JBn>HR%@dz*f8YH;VAnUst!{j}}7SeTM(N8tf1^tX~y*HNa)l64C{n}ecZ=U-( z-J-|$cqxF!5K;4Y<{mIM4VrH(&oZ%`TaTx|b}}Dmz7IMJzvZ z>gxNgYmd)k%3jGmfosB)$(+4zhdL*Vzc7_H5fkfp%*J&v#pLnP+2%K2=Nq3Z->7!{ ziSV3B9&0}a1<3SS?A~?a|7Ydd{rvEb1?dN&K}RH?^~V3T02Fp zBc@F#nVA1-_5X)4FA_c;(U`*iT!m$loR>$Eir=;0Nx{EYYDeC_e{E+#LqN?S z>|Mf{l4n1kxbJKJm~kv;(&FT!kNz-4al8GT;u#khV)?th+EHt#mSO5S3w;5n$wx97 zx~-&LW@c^HXg?Gx%5fsya-B|OY;?oZ7F~Q^U6Q+&RB%FIaSXLT+ zV0(T*Qq+xKW633nj}7U%ho{}%$ z4SkXEp`&F(kw)!}w+s`zn#8IfYSeUw&04i)!?8y#3f;{2uUl^LOIo|0d#Qrl<28#W zHK}-K=y^ohp8WWDwNIKc-+TFwRfWudox7g?O!dlsalAH2wkN3TQp1E2Y2Rzlo*V9a zFtIyb;Id3)s0WjJBxhrI;Eorm61oo$CKNb5TBCSgea-xchaZ>UKfK0%kxXC5B(}3* zeSNBY$B$H$sR#dhY-XBH7`UZ_8gS#7wt3gJ}h@3s(>l^P|dlk z*XG~z;t^2STziz&Wy+@)FO5aErED>Na_XS(8~K$HkuisuJf|q_JznXa%6raP`((ts za~tjLo*a24bpPkW@5Nho_fJYVt-z?Y^jKy8l$MGqOdl8QEST^-c~^1nysG=vg2xPR zT05+gSb3o0{6C%adXuCWK7%r+&}WH7icHF`5gj3C_TKr>y1RU*txUo9Yiwt^q^2P=<9^dw8YO?c8&%or|W;&i{~sDf>_gVrjgr#f9pmO_^HjAkbf8OE>k5Xb;yQO^!r~T)r z`j2x@o^cLIo%2#}(u)}zbvqaJM3`+@*DNZeYZLXOA?Jc&XV=RYGxC((jwbr##|T=9 z%bV0r?F-CYpqwGOQ0eTeLrQDkX$h;(6bb(3*2gO)qs9K@!QrUPlkPg)M|m>3wlCqm z|MKzcEwW|lqOyrEnXEHb%?er>$g!U3!dbRevqZMBoeU7x%az!A@Wrd8Cm*ja5vx9eB(RD@|&ok9>mF9l@8NTkr&gb*fpV{h7pYu94Y2i&a#S2!dmvpzhZ;`c~qdxSyoj$L06qJD;*uVWUnJzv@9ifZ>|a| zQ)(7vw&8o3(AN>VP%8J}ZkmKvVbam$iD_GKN<{9>6n?-G%PNgYY8A%cZc z#YJx=?A{t}op&+kz&V$K`0X>g4ChJyxDc^M(6Bv$@8E@Y&lmE3I&E&JExJEUvwWCo z)%bl@LGHrQ44wa!g*Xygr~b zk4Hx-AZWpsMLJq7Ax8pP_!hY}f8~BY(IT$?>-G=3neWy9vY8xt^z{1sDONK#ZQbs_ z`s$)pTx^dG@4C%iBvbtD3+Ju5e7%0tJ>O0AsM^a{XuNcWU&j_Vug+pcv87WoM1!Xo zuiEqAO6z40(T`Wp*L4E#u6FcvCSH3!8IPLi} ziO{}q_T;q7bN>H%Oxc-Dk-ohehw8;#4| zf3o(S^!J;$@V(jtl|WYh-E8s`x|jTG5D)aJIIZ(^F5fMt_Y?ro}5Or|7kmE634*O81F!F_xIr<9jZi%{DV zrP-_F{G(&x%tDjdNt;D{n$(=S9W1+reHYF0xc@N3`_sGR>^U)!%RV)&Zsz>{OS$gD z7frjOPa>W&fnMz&*~Ein#l|etJi9_ zax0hR+_;%!yNKhEj1%`B#@Ed!dO8$>I2Ua>;wiM_U{KqQ%~mt^{g|coYKa!&%b-?+p_tK-e3p^i zeEj}@jqK%q2~*YlgpyW9oJ^YbOln8U+?Nk)=V%0kdd6g3+Tpl*uHaG4P`#V;pZFBc z*wQG(S!mlDJaK{Ga*q2;7pwH^mR9^YQW%waEHCcUpL-3HGf(~hb}6;5)H*^{W6!}v zgLBDio!ty0FL6w0u@XyG&0L~soY>jYa>F3Q_K^LIn5Waz&L>`YkbKMSLtd4QQZ&z> z3D!zaj@i%o+MnE&e7Mgt&)e^WSiGQhgvvFC$t>A1D_+I=J~U;Vt@H)^4YA$t%Qn`f7W=#>>9BFCNKMG;i%oN~wG~Grh1?F^FsDt^@1q z{&CklI~||CZ?DU4Tj_K1&MBtIshf9f@u;qwX#Kmo=4*ER^8H)SJFAC% zIH#oC+Y!~19z11Q$mOno_tdnyUg+vQ?p5|P@>#m0^0Cxizazo(|7?4ZS@hkmJnvrf zp~T#^ec|i#>YrZ>Tyw!`QbuP`-(~IWHzCH~*oAl9D_XPn$g4X{+;jB;muU7TZ?SlE z?4Mb_kB#x-8#8+g&Mj_ruzvAt#{1eAO7_3jweQ?hG$-uZq77}&zkU1hN!tA9y>`7z zXK&Ti=FjiF$nlc*^39%8QL{3e=1sF$Im5EqKs3rLNHrkpQRx|{Ssx#^CR>Pp{`^?_ zi)2#>he@n-VUS+I3vHH3b^#yT3l|=iQw`UAF|lBhrONCU)^%&BX?y}f&Pm03tLY+7~iuq{KQME=CMt3Q;xzg!XF>oi^HEjZ6ym7i58x#|Bp zp-C#;JB!|%J$@dSko(eNZ5sbXk6(XlmEO;I@>?al`@}b`^ERK2*6;blrPsD)=Az3w zH?Gf`P%*9AH=t?v_H93ZTz>y-VY}a_o9pe5-;0~?b}MOvwS&j;r{C?*YKZ6TtC{|B zLI1|CojWa+G(DF(a2a4Vz5R|9 z-^XoBbhws5kiS>eBPt(3La86|f-GKmecKa#ZZo5=dBE@W>N2%0pVwVm5H?*8r6WT}bOr{9&d6pFRF zhZpGNeL5~@dA|PB2~|J6HA1Ur)Xn_7^7eMkQb5f7J z-)2&~ypQ$X2TOCq8f)IFErlMZcI`gdvLd|V!CFp-1mP~PY}r$t7jmyp|H(X2_jygO ze`x)-nzM&Kd^@}7($xn(HM;WI*|TP|2TN^WoRd)_ShScYj&1+XKbr%r`gG^3ct(e; zN-EjmK170KF2a5IMp1<09-Sc^r%2}R;!E?eR*sq1niJP5lp=A~XX3-XH_W={RW&)@l|=V#uLe-9a_uKf9&|G?vX z>!|AuJwDm@wr}u}^QPY{(N>(FMBPUzmDGK+x9A9v+`x$ew;qPzU^Sb zv6z{g^}I`DiqkF9BF<^l{#0vF^$yzi{Z6Hv{l0VJ<>rzS-rSw}87785I2sp-OgjIA z@ovrJ>Z~K$t4=$&aPMtn*qeO(!HuO!YQgV1CrSU5eYbxo<6y}NvJfBlER zZN-PL&X#tZbFguPl(FpRyZ3+2{i3cMHp}q;*ZF^gW$Y{G2tW7xEL{J6xuM$v<)crJ zpEr^cxwEgXy*2LG;phHECNm};jhryi_0x|#Hmzbcxke8J&Ymo5R`*|9yyp61#o#F% z3SOd`a<+AI*5BQAxO=v+Ti%%kGmNh1Ef$=UVgBRu`^t{~*s6}-kLT#;zrE;{Bo{s7 z@yVsBR)J@FKQ`?D_33lWee0xkYa$ATcSRn$TX^tcj?cs5JsiqbYfbOh{b8AJQQw#U zjem!g+~b_1!l$*DC^5}%;IHU-KI0SrDc-hS4Ul25Lfc9iDD5}%~N?FEQ``TH)UE<7;8!KB?m1=wG$_$(lfUk-TQi1ewpR{XJ4xZUWMj{cUzbd!CZ<1JD7aJ;;5G1D(6|HIVi-CxqD9Zqsd)twlw zq4?w6=How#-&f1Ob(GhT$y=$ZAWPXe23QE$Pc*LRSLl^VQpVzO-UH3YF z{pgI>i_Y$Lv6Xo;S65FxVq5FN5M8_IUX4|PweKfXaHSS5Q}{S3GFMA&?E&e=FHIVr z8tIh_7To$U`E=A*Ux~)PP~DKP4HLrGSGBlgGzv(`ZO~5n@%~)JsXa$TL<6S?s0SX9 zy&7EmT!-JjW}fi!e#04lJO2GD6_T>~^sYU^Zr?d={n&?(F28^DZmqP*ZPojqJHHpb zO*k+&dbY*$3>8U!#&TYuI)Kr zd4cP}r#toDTkbqoRW6vw;u<;Wai@~!@{12Xel6YQ>64#$;L{x?*nL)XYl)xG)TDi;0u&z;rlHw2!Y zaZBd4*R8W+r#-v**Xw?~amvU!{?pRGweR@8MVy|N%`o{v$eqVNk6F(DW{jV`x1rx= z@)yNxAup7?_I>Sh%C~2ow{QEM4Xm?8A_6z0F3(W$mA|NSeUWhdPT(NBj4i^a#a35NVhwxZudcBxt!H;ZvB1dytaF5 zpUWsQzIOle)7|@DGtaTC?yCQ%UpH~DQhJ!=H?701S4+S7p8WD^it*Yh-{s<;zImi| z%U#6P@u60V%FCmitG#7+ZCrNu?s0e7H3Ak7Gs~0gKHiX=XH`7y?4k$pZ*o4IDRA-k zp0`TPLv^Qusc7H5sL8ID$#v<6_~+W3tM;GsRD9i@V^<3gT@e*Y^HM$?ky%pt|C5q@ zT>RnpoZGrJzqeJ#M8&Mo3}|A#`lR!V%J!v?_0Pq%co_Kk6$Q#3?+i$B+f!_l(4OA% zw9j~tU*mMu^3{fi9J@QF^?SIsxv(p)m?ijN?n2q6Q{035+KeBWtoyn7p{IxNq($H5 z0zdC%jyHO9(^f8hr7Uyvzp5oB>rBoZt7P!g@{AM>Xh{mmIBIg zhg)2~V8`8blXEN6L|6B+b{VBxNqt`aZ?pWeojVS!uYah$r{Hbgi&ZQU&zlQYdsrm0ucZ z&3&AHHny&8*GZi|!K{bA+V{K4!~ShOu<5z(^_+7)Aw4q;3RFL5M7bea?y>RTcHGb!F{aXBfE7Oqff2r|#L=&zz4|w3&)6 zWzOF0%jd?x(7@p7;uxYZ-&fE{ap@7Js~Lyp|NJNa;II5&f%y8jqVsCr7;ZEF9g^C4 z>fxu2#c%H1e>U;6_>ac-fA!3MDZl%7XT3s;^GXG^$35y74L^5#pZUJ4=Dhm;=Jk6@ zx)&d|lmGLL|I^LHVLN|NrsbaNe08%k4jRzj^;=vRdwy z6$b*@a<0BNN&nbVdwtECijNrwUf-6!5qa?CQnS^WledL_k@;osx;JN3!N@ypuEO7-x25^Ri1*Zfr1S+~WH)rKIhB zPW+?&lJ(8G+u6+&CdvmGb2ZKTYMt1T`1<(fa|I95jyPxu=gpYI;yb&;XURbp=S&yT z3rB*oee9C|XK=^=jJoTq7}Byr$Eoe_4&75xk7E^jIHs6#dT^4iocf(TVcp-0hRTjZn>#ohQEuMFE>thee;$6F2syQa9I97?j$g6myvZyDy?&Ih9 zg%eF@W}6D0V=-N0!pImJ_~B%K?Hm)&pI@%WKixTfebLV+On397yH)jzN>(4ec(Gye z?`n_98!p7SScRV!(Bfw>lR*Xrn~%wD(|ehmTBh-{}wc_nzQLv zWb(?pNi)*k>}YX1c*U}6ty;g%xyi>2&wu5YaGB>nFDfz8O(ycN&y`gLOuM{upZ#sQ z9DMZh>f<|i6t#W-%ARxo-R3JZoIBKn-$(RFuQ8EYQh2IpmeKO$rb|0H4#=*q2wL~) z`R)GC*Sodv=)G^c{2=^Z%zp`|i9h{1&Qvtrp7nj&zPX8W5?#~oY-6p+SdiJpvQW9h zBq{3G7tC_< ziX21XvN1|&Pp_~0 zYN@vK;f+U3&2w!J2rt!)t7vOxxlv z)D)?}p0p!x*;J=_an7^ZcIL6R_M33k@YR~>)1qQd&6;v-Q@&Ty+?Ml!%1M=b-A%2Y z#8%Bae(hXAlGW3#e}CVZ<7;8O|J;eE9P%4yEjeQz{p8-Jd8U>cs|N$3??vFPXttoVp$~;u%!Nqy2sY~NDL&M?g!qM+P zToXQOsMWXPlhE5{k*(ofCpdSVH9PJ%KP@+NYJRc7>}MV7s-7aOtI||fu2IZ3zaeq9 z;^R$iozm(%lbIS_Uo21#s_56{s(a)9&v=ttd;Ra|`KeVmo?pqD?YI8$vuAphnHT1o zNwa^S>1#UCKl7+jfX&pyy8|-31Dzk$Ew5cQsVi*Krdg?9);`_F;q*0#Luh@Z-|d)v z1%h)u`rMq=j<$rf8lH=}l%%J5a|M@{%&Evd+fAk(dZ@AXVbYY36W#jz!hGgDKiXWo zC8UC7C$qncbBakwTbN+fQjMt#Q(C@A2Hsi{?{hL_vPWBCjuK0+g;S+}be7TG2cp)` z{=G;od&|&xFq(VcqO;LuAC6j!>|kBk826#z)ZwdHE-$^5@;8KfDjJ7eQJS`hZQIq( zi&8F{8U+h@d)GKFzIgB)&kU|;wwIUQJ+k@yxp>E-LuYw1C$UzaJSCK#dG{r|ZTWK@ zt-vXjCm&uDTzqgsn)c%}JKJA;wR)2)J2B1hvg66s-}!fbKPKlNXVV>N%G2h)<@Q|j ziyL;8J>6aT-tN79X;*ld1pRcK3>q1dioG55=yop}>Z@w_^i=IpK^!^_gU z;viq&e5w7XOt*I*VcEA?JM(epeX;(R)&l*iikaJO=6u_bn6OkUH?mbC?;u8Z#`WmaHj9 z{v484UvxI;{j#p@`_|W?boOSkx@B3-)aJQ$4vNu7kL`H0H2nSp>-*pHp1dmTV{2dH z=_GT!Vr9X#38|X5f3w@{+~rpswBh3^(N8Xmm1P~KWS%jP-+%m_{~wbREzKW$X0H6P zGycEK;>h`Ki%;g5ER*riEDK_5R{Z~E{{QHGe-5qw^HKh9qMY^oqCGPWr${|@`nYJR z+GauZ>C-IA-3>ZIAD5e)n*L)uIi-TeLOTE}!I^ zq&ne{v*?vSd$?pR#Fp)Ia=-Adv}2Kov5w#>uB=v$Y5nUj=9p}_ER?8tc9VnSSAlfR zYf?+PR(5ekdcS+Nc+TImwrQ6oKYM?Usd%AtiM#Jxn@Ke9uiwG*c9%_GE?;f#A73-& zUxr8IQxj?4C6`1(JtwtDy4QVHleej#xBi{{1lJ~wlO0POjyDC~c~JlJW_)5@UgyO# z1s112u6uOLfA6`68~j&KG@Sk9$EE{kmc{I@KNA1zw7sKPcH3mlvun=A)r-IOby)B5 zaku^#_G4UU-A=8VB=OQPDlv^UGVoGtUxsP4LPC(7fpOE?wY7Y1#$vZ+)#3tYE?}7U zV#?u@J$Fma9Omwh@tD1*;>L6D#nm+lrL9lXx-At%wHR{vWQ%kxXGQX*S*lyvYN>dc zON240Cw)4!h*w5bRa@*^cUOe_mPIN733HBUoWFirH)m?`F3aPSUmD$BcmBfRpH>0) z?=7v@p1O;N<;#i>F_R?gr?8!_b5Rv$+joB2OoNGRFIiW83b475wT(l$^TdQqr`@u} zWp73PzHFImZg$a2DY>^+SY_q&nEv+`M?`W$kmvocbcD6Z-b+naa4HhpC3yYjL z|MAS#;WOf`{ldZ$pL88PKIv`b$+ga{rdFo5k*L zs&+Vha4Ijgv5R1>UEQ~Kaz^9x86H3EE-EeUpU4@Xa3a(??FrYM_8Uz)O-ED?DXww( z$ue`v3z2i{=N#ZVdN;a!f|F8GuQ8kP?2Xms+o$ENwJsO`a&+&yjDWM%QlViiwLg+wBqYNi$yNDp_&ln850e zqkhx-_q@M*=f*zoEjxTRU#fV`)D&0kCO2p z)tM#>1IjmClsBAlI?_kz+)+*Q@BAm+mG{-w&aGTo_~XIax!gw@TW6;DPxH}sUZTvy z<#OnP$C?jML}e_`Z_wBfR1`6_WKvIrR!ghI(S=$+zdRB1IQ{r-sM6D$hc3GnO)@vN zoq4tLWAsjM{a1G+I8SarZPsr0ls9mbx~luT4$n}7t8EcSUR+YtI<)7`&I8i|3|HMu z*m^UqHg&G`?As#lmVq*?DQSrkvOE%Rdk(N!=4LvbJJh;PC40*Rr@+wA5AvO&g+iBS zMnAZCJnTf2?z-6BhZ8;vgo~)0opQ3`7`uFBQ?mEzEmg54%Bl7F{honB?glb_GvAj@ zi2D5FC%4Hg&H45JBB%MqbDXHE5%}S0y1m~m=C+*!hv%%UIod08S`FA#St{c`o?3l9 zV#e#DPgkTu6F;y?&E>Ohzx{=Man$jEe{u_Ucubrqh>-@xfk3UcPxK{U+Qu<}ieH#krb8ma_tl-GmX1k?4S9q4IymdRIe|PV>;>XN! z^LRT#M63c7C2uy}`Sx*9iCncwBms{>~_Wq)s1DyWs+z}hH0 zlk4QH{)#z0x$*tpHV<^x%?Uc!C6LZmvvQ&nYq8|>iK|Yg1Z~=0dmtxBkeks*P=p19`9A` zlS$de<|kT_@1fi&93bNOG_$Jj9qO0%9wexM`az5`F z@>WRs#@wFB+$|n0N1m~+dbq&E=l$DYp0+Q0pLkpEzVXaA?zQ|!xB4R&z6%~oWHs5; zdhM4F^ zK0Ef!kq19*Wc!(MV*Q_6`5WxdO7bj!BO_;cJ@3NnZ30n-*WU*A9oH$pedA8S->K#w z+=W+Z1-zJ3;Ic~Pbj;rytmhs+5RJ?3p0++n%4Kyf7y*lPT393oQtEQP4+x2&HQobZuF6j&+et1dflw( z#NA`QLgSf1@6Yt>aVn>R;^y;N+@9s^;hLJ5vTVvXjwQ;Mx{kX}_P%JdJ@%v^C-Sy-(MvlZ$M&~d2xUH;qPnUlUgt-Pe-=xVsuq;1iH%b5jRY#%)P zY`){hYu4&X9?B~`lpDqPG;QW_Y0C7v1V7YFX)K=i`}_BmT8EbwAJ5z>?Q>joef<8j z_a53UTlPNC`shEte6cN4+1i)Ki@h*;yzpu0y&cu;L&hgWKwp2InK^ zW`CZynSb&z!O3rB>;ASz+w8lce3^6V<}c}@^Y?FU_Db6K`IBW|xs}E1oAW|q?!GUs z-hMuL^>K^2X@4$WP36k=+;Z$jQ~GxE4_=>Yr|V6s_TGO&aH5l%;Gr;{)ss%It(-Ec zWRZxb$x_XaX$*@POx<5LoZ6^1JEx`pQvTxm_BX_r-fvn^JHaMQ4HV8uBOVV)CC_lDkBt=+j| z?&_AUCDV4_xMO(hUh^iUuvw?9OE&kK80_8HsOvS&I@e{nDtkry*qr|z^Zygu zcA4w7nom04bC$@Ei&@(;ORHC;wZHv+dro9t!LNy@V|Hygl_hp@?rWt*GgcX_h+ubS z`8X|YzW|e{>yfi9FAX$K`>MBv$JH{gRXHZPtn%QUm!@kr^sH*yqfuFrIdl3f-M(YW z`zHEZ*xXod{N{4b{nRv`%taNi&ZzEM%V*BcUh}JUwuzvLzMV+O0jg)D zTd#20&40UwY-?{nlruA-__j(&-j@Nwuq~!jVEptiXwkNZ1OUq1CmA5SyTdB(a zQ)E4hmS;oP;YCkUu05MF>5Qu8%npq;M?;+_CX-To`YAt3IH6)Q;b`ODM6Fxg-X124-`1W*0_W$SqFWB69@Wz}?Tc)@Pte#W0*>Uq-<+tZHZ!JEu=TzmNm+FME&%bm(Uh}|N zo<;ZO+`h`J%+AZFPv4Q?-H|7gW|5FP>++4;uBx1qjh=tn7%CUN|G)*G9hK$Nr{~{) z`f0IvXGlx2@;iPW`=7npB2k~dbSB5lh);-$S|`)<^VVG5XGsT_njYM3E&Jt{k(1Y! zhO;j_9QQx@QPY3iPv-Foof$KjrmA@8YUZ?-&0B8ey=8LCw!VcTQ?7~}QPl7h(KR$| zn|6H-r|Z#Zk;Cjs?w761|NW2r{XW6_^6!1Zzil4+s^7g+YrXs=CvyT@A_J3)0_Ox4 z&xu#Mrfs?6l^q$Cof@_KTcda zw#<7elYhVBlk~hN_Vd2qk(=<#d1DiL+bN79p_qn?Mqx3)L&5dI9w}&>7BrTA5z6(@$Z132<80%W zGmDS!ENoG6d?pn0WHSG>X`1|LKHJMZovyfTzci_2dSb-J`fBk8;nUtNI=!!!`dzsB z;=`lU>pwi=UT-w-Jm$c1L$ko}Du{^6{IOi@&fj zI|IWEl7)Vx9kR6D%#i--Jc=I!pP-@Q(^#(Z+_k9!bO-)aB($mO*g z9Ixo-)g0(@&{9c$zp}Yod)?8rg-_M96g`(d+3vpmM!uVXR{)=&O@-^DMboOyS)`&O ze}0qxc3{ijuk|NF(|wS-uU)DfI|Tgk|FLJO;VkkJ}xyOJ-O|8Ctd0 zPLbEC*@_Yuj1<*v6I**x7+jF_|0AKQ>axE44%3q#re^uY{ zs=wiV{P!|D|61YeO-4+C!Ewu3|6ZL^WySc~z;{8O&av4mR^MCqbgICcUiX@BjqR1c z8`~=$zOsIE&H33vrQlgfYLR-o)_g6}^DnF1vi;0cx6gmhG0u;PO;UaBvTkM38x__1 z_iMdt{=JKpyZz##r|EV#)$h-yow;W7{Q&1YyP_%b)jtZORxdaa|5Z*o$xz2R;g#&o zLxTAScZxB!E|}1D{gvVNSIi$iznJlq_mC>b>xi(XeeA+aZg>0_ZE+3%BtNY+@s@>o zcLmQyUfqvfDj6;r9!^V6Yb(w$Ycp=XT=Q&Z`wfrjE2O*m_zetyJO=LQ>t*g(Y zsc`M>!q-8|lVUU_U0YmMB~LOEbj_L7rgQp2RPZ&^yEg8glDTSvGp;#p4Sw=v+V3;R zjb|-W)SeV3b$L>%<~GA8k?gBgOYbT~i?8ah`%qi*nNL*n`uls18Bv_CTf1_-6P14P zdd$5L9p|bZ?3BFWSn#!Jt8xmCUGxolR?3@tG+n-CO1*`wvF!W8%1y`Tnkde7);}Bh zQsj3vv372`%bUVlCiFzrcwDMVJ@G9voI_~g2c~_-_6Kfgu8>;d z#M-*OX`ZkI=PmbNPad^rB&Rx=H zk(>RWFSeh1&~!w)`5Tj}@SN!!+>RAOi#UP;gfbV^?C=YDvg&jgzn#lr;i)PSHdhOr zmI=@4UY|HoJmPu5!OQPUq9k)WdG%JU@jjYR*)XB$XGPlu!3LZ8=NEq|o*@+9M9_wS+FQA}Zl`%IV&3Mn(8OM_6 zF8rwG#VQ>2JWwEa>CeZivF3ggUi7q0dHz1;x$yGyANSY%Xa9NqeaViE#l3G|GXD1U zHRniqWvOYh`lwcYL3MxqpSjx&i!G*=zkE4oW_#SCi6`f5y!&kH-%0QJ9%~CUsb(B( zjDNPj?x*yecY3Z#`+MpRd$n9U5o@%`^!u~mtk)-m9T%}0UA^Iw==8-R-0NfP?!((` zJGMtFPLR7+d$2i4@*%G`o6|q_s}pz(*x3H~FVEJ|C|vyQeQZIut3aTaB}bUcLCJ(j zl{;ULbXuI;VQjj`NNUn;mN^HboH_VTNtw$Wf6v*vT~E#X(O=2*gOx4k9-TUBQ8F)S z)ug|FZMOuhk+tny>E-tOc8-j-W29u-WX`{N;uDuzycan4Xzg)f={^OOC~cEfTX%-& zI-fJ185$DmIdA!wU6zw?&N-RmrmoMyw=O@|IA@v0+l_Wsk|(c8dbMwlc~-#34%-?`i6`i1$MjyuK%wY>PY^%1M| zQLcP{-Tfa<&AxMYy=CE^<3(W)mMmIs+L?F5``xE+6~~r#GOXn|D5$bz)r0)Kvp(PW zG|_pvVS9&~xl+^NW#=}8`pqzH^YISi)w;T4$y_sMh45M0iASD3)7$d7pJRiF3IC=e zf%7-KNlCH)u(#^nw%+4%{}a`|y4L8Q^Ij`e9iqd$Yss8RO}v&*WEsz~3JIM$_3_rb z4psT1Hgh(!q;vW;HrAyGuMIBC zyPJ^vS);M#;Xk>#ixf1i3>&Msk3GD#M6~ihC#TUm|Eqt)(}J#^NmI?#lKf&d#rN>R z3?ar!uj1AN=zcIM0uyZEX>|4DgszUZXItCFti3b+b}zRqesXI-&yqEez%(7v+A zX7zt(epkN#U-93ASFi8f-G4UDQZ3?qmzU=r<>{7lb7scZJhzJ9QPTchjdix`^bk$1 zwJ)_A{nhUjSLfU>wb{QoDn8qJLf8F0=Bb}1rCXjZbb1sOpRc-Dy?Oeiw^hgDESVL& zRid~(OxNXh8EI-ReIEVH>e%_k)0;&y)GeRxp0l{$M736y@4Vgne}P;7zx!w}o4rNj z+2e2f#nV+gU06N#Jm`2);AK*>WYIUqmn>E)cO=61WPA$@6NqWvYPK(ZvSgl7_nQUR zGFmUJo4?d$hT1#1khblGTj#29PX0LIVugqP`;B#PRDMT)|FAAC^|8d_X2ok#Re$Xq zSI&@Ld-SHm1TC%ut6N;OQUrDg6fYJCh(4ZSbvV*{`|Qcfx|$!dB>NPlVaIZz=y${`W$Z zn#B8zJr5q8_~5-h{zFKJX>l(ATL zVzY~Dqlm7e;DQ*Qqdrb~f>vqMl#8aG|90+E#l~ZAvpmE^PVUZo(VdrevA6HmfwMoq z?_O#BsH;{{e|N-N_t!~{-}_EoTQjLZDgIgaRmS{+0~^*=={4jyj$QNBvSD{W0mDS7AC2yuRQfbCOdWPdOuGfJosFaIKOxF8Zi%V&KDw= zOjdPW6zjWiYk61e^t~Nfp4&1#N?az+UUqCoL&z+p^PZu7pBr~>zO(wl{5>D}>Yv*7 zf7;A%cXa=xS@9K5`)8cVs)}^deR%k}{DZlce-4$$Jy`o!@b4D?V9hn9FHDrLOD_13 z#B-EIKUzPF@qyu@2j%ZVYCfiQ1#rf3HkEU{U;KAHQ@i?(Wp`t(JzR4xZ*6hecJGSb zzw(Cyo+gs7y6xt_oAY$$JGJ2Opq3rquHDhU)xGFl$K{F2Vqfd$b7mGA&lj3~rhNWv zm4rR%d0AGD1&Y?E%wKcfPv~E{(o*Ty8vXf?^6Ng!7rp=0ks`ApRJ40X*hJ2R*Jv(IaHdj@`hczKBND8#J1#jal1~J{BQ7E?|kp= zL)VUv5!hPZ2;3a1|%OUQl`S^H07 zUFgcmYa=J*@;GS*%}staYggy?TP~_Tv$>>A>PyPoH@}a0wmJR$rd7uO=Ifu`VRCQb zgs#|R%UxT%>R+1t|ImB-&+77ZO0Hc?7G&Of>7A;w>p-%itk9C|w}H1SrgU|0eK@x} zIz!0mb%A8fl7NLu{r7*ZkZaG<&QrQC+I?j9JfXt7lg-~|g@3H5nj$YV=UK|NMow%^vTZ3){HN4@v8?+51Zhq^Q4tsCBp^U!70F z=;4i-#WRZh>`r_v&)ZO{8Py{Gm(l91!M0;lQbj%qX$ECY@p@#r@@kX+r7gJ}qQ0@N zzofs*<~DN}-cw20>F{dD$H_rk_oWoNKfg6YREbO1@^#X^QpV`$o>mjjz=MWc3tJUi zd-`hro4vg5;~6AqG12o}NzlDpcb~`a`DvBTR$G(#^TIRBDz8gtx!EfFUvJi29C?<1 z{*9Cz*_EPm?#ASXPne{mn-s?_X}9r8;#Ny(hvgEiDfJ{uJ7mdfI1p=9IXc=Iq9j=dq1jHC~0zcKIKx z>0(&u#(Cb=eKkkm+sQvtS3i)L+4Va0q=Smht(V8&@i@*}@@85v*UZn$|-X zrsEr_)S_~7)3zq#DQy1@85SGfP(l{&jam1_!%mP_{JTYk?K zk1ZB!+?c&1@Z3!4!>xYeE?>m`OHUm!UDEs9{rukcDg(#x1#dM`>+zHuw|*u+e)bb$?(UbfpSc=ldpTFHKf@7uxN$SD06B~~ZGwiyd+ zD*C@ao;=~L%92N2pFW%sw6|NL>9o=#x9Fe2bPmnMg5IX>0+ZJVtewvIeWl&O>YD7= zQN7F0yi#obDjIfudPH=jnrM>~=eZ}7Zg)P)zMdZ#W^ANsIjiKyk(KT*)j99(Ib3w{ z;oOIewhwPpN)QV%iz0utu$9+K zWa)-_vZTD_4RTdIs}NNkuNtYMD7ff>SAgcO-6s$4O|}VKvvHM?-gIZaIS(Ji8h&}V z`N<-~ExCR%FUtSye&WVI<>SgjGGZS%+FHWwZ6mh5o|K(>(&5|7C3mkS1V5UZl5Y0U zC4K7uA1C=0zVTM-YIy9}x&7>RwP>H$QnEr#rn!GkYPi11c)YCluz)~F$At^=r&oA4 zoARx+)jl4+LOnR`F!QIp1(#n=RGa0nx^w+v<#`UW&)k+y(wN|~`h(`9lDVo@cKlv~ zOf2HFx8A7XTC>tHv^Qjyis$3|4`Xw}#5ZOr|1haIe(;)EWZZ@5mzyW)8_)NXxW=m( z_4CeoTc}Zx$jtW7(a0g%MJ*JH! zo?^~V{8=`rUwE)`gULsu_Nb0d7QW`N76+$<>yi83NxchYUlZ1AHHulcU6gAzp^B7CPh2}I zEpc(eb4ibb%BxrZnSS2R^|5VOg-GM~`4(>Wr!`7;|C`ixY?oN@Le1R6cT9Lb%~3hM zV(#LZygx6i_f6K3eJsBIkKol3p~wC@Qf}A(J&Da%$TjM?wt3N4r{@z?h2Hf`x!6Bj zpyIeI;zp>~#$1FA3S!x{728$!k3HBF)4}t{^7oE zs#V{q1lN0v3s)>FuDWYy)G2r4;P&|K96NR`nXkI%fAD!ji+u;*U1Qv9EfR+ z&f-578ZXaui3wWn^kF6Y`HQ~BMk_CPJ*+vP6AI|L;yexUKzd5r_NgIp2O;#`tV0l(-+F$m*zf%jRZQ)$y-$3(W2%m;SDH zT(JB2i#2oDO|(=hMD%Z3o@YN8xw+}u(Z4n|FAkL1i=8&pPjl!~>Rk1=UWre(QOdn} z>B6nmZVFBn*B6U=2<%AMc}rs76Q24V^>a-EuQ&xvxD?#9`l?=d-u*{8H&xFTh3&3A zwC2h0`VWso;{TQI`|~YZ#p=N%=r2-&s6rZ zuk87`bra(E{1of;P}rjX?V+Lnk*$T%EXi8ge=kHeFxYK*+}*(!5Ok!0SCFMK=;QYm z$0gn+W^#`^-i7Ouzw28GJSe0MTC2O>BYnoM_JTd zcfS)6+@f-M%E1N4fBlj=?Y=vv^0iu!no4Z%1gRFAFKOQ<3iJgDJ+F~IpvA|!wtr{- zeRf44Fm8sh@jZ zzu>K3Ql?^*c;(8Ow=Mp$7H7@VonI~Bbi6yiMvUM2_dVT7X=ixW7BH_`Dz%Nz-QmNd z3x6HA1WgLOcdFsk$!qdYe4g9Pzv#Z>neG3td-Ff9y=iw|eb(2L+iyngKhXKlaMpw9 ztGux@3B3W%iESSqbi0 zkCM0rCTIMK+RM$c@@0vYj(l|5<0D4p`FYJDDRY(lv{rmG?OmVv;fUt(Xo2tPlvS9YbNQ+L+tZ@3tWHqwjlGhoYvV#n_tP?o3_A2 z{N87kV_J33=l|DRxUS;e;`i!-T<%}E1b#$piRM1{z$R3<^#7C%DN#wz3$~apE%kZb z))z46+k~&8)7y8?i9K2QO_YlmCk+|}JrTOdepBY7SHZMA^e>T6cwC2F{)`x7u@6LR`Y5l9h<)MC3;1S;` z;@`Wil>FW(D=~y@53H!){KW8wM`cV0hv1@F@d9gmi~sxzR@`u}SoFN;{SVAXrT&^c zJ^ORcgES7si(L0^)V8mW+3oDa#x^al@27qKADPvwXRGWv80;+_BY!V>`O^1M@=9;b zS(bEdY-oQV%ILYO_}ca9mW4&k6WQFXf4@+1RX2LIn_ckWF~tXSE%pSmzZY~mI_qh4 z`}S-9N(8@uobjIjuL+-dc_QPn5RR2AmKWRoxWK$--I@<)&F>#O$+Gm3=gCNCQ{70* zT_9=`gTKNeSS|9RB> z-h*#7XSPTs`$(pSUi>k^>4Qk8nqbq+Y>m0gSBWz3JTt%ciAw!bX8*tx&1`A)ANBD& z&pcnhx7F}l?EH;u=LI!-JmxZ!(!L*Gd`w;S+#$cFEXl`qF1m5`^;yR?U+7Ghua>xZ z^NF+GWBpS959P^N_1ZR*V27o4sEx*A@3HFx&8d7 zS3>LS9;+VPy?f4%f}Vp4%XN6oDz;4eQgZCs}TZi^#jkKQ{dF!fBg#AF zaqFw+C+hr6wP%KJwqU5Z?&ZJj?75`3Z#$|!I6aV6u#GN%XRunW;`zm>q&-X4Wq;E7 zCE>BsVCvCOUZc~GUv1T$qi`!q^j4ZlTHw-9oBLa?EGn=#$JHcUxL41?^ZaH0`H%Pi z*t!4I)M)dHpI^KG{QG?W*|oWAZ@hTXBAkA{==Ce!ZR_qnJFzk}?0WyCZ^A_@JyTCT zUw{9>{h0m798J@LdOxmnWN#FcG}D;!p`=u&%Q$^S?t*nf=e}S2DSh7V%rocMKK->D zbcO2v?Oy-!(B9h{Gb3|a1Pu43Fe!CbD0OVqsOdgeUie#~qcY%>*vC8TF0AS3%F_B5 zo0U29%Z=caUB~;o?fO$+|M~Op_MVV;Nt0RDvhGjTPTMVRIkV*D3Km5c-g#~;bAl{H zkI#JUc}#HT*1t|UpK=S%INN+SQOt`86%g!|w>i{qAraNjr_iikJi+q3O@-br%jLC) z{J;AZFOb=9yZ^L-hSuvBX{YB{)^$ajuXne+9x~~hilf)2x<((A{80W0l?8OB{WmM{gi?M6DARgH^532L|2X~TSe;YPzyHYle#O-My}ztN zLm$Q_WY#wSTX6b%{rl4uw+kK}3JlVnaV}kBsljI}smm4D?Zh9}J>IVI;@I)fmuDi^ z`5lXqzQ1To`GC z5^EBq4^Q%oyr?zv*PeXIhS zEe$GC0yB&hLJqC}cP#(I-0AVp-R*xLw)y$|+~+^rKA9(5W431t(o->U-opWusnSp^@#u zph*qa_LLetjo(|>`nIU&I+tQN=bZPn(Q}-QDi2Z#0(Zy}c zxg>L~3Nr#Htex+=MmF^sZ|(Ys$(;A6^S#iIP4JI6cF4tUi+IVccXyadJN{j8wJ%$nZ=f>EMWAyq(JD+K^DW6H}sb8Sk)8vd7-R-%okY@ zmwU%OH$0tG{W@CQT#`FAgV9qjLF@RXOIqvqeii%wX8J#OyPp^PKNr{kTVC<>>-C?1 z#Q%4^eJBxpPV()W!fyvZtE^10Tc0uIQ0ETIo2#?+`oG;=ofmj(ZrNVxyL(%*9Vf;K z`D6zvi&*J=ELnbhqm62~;KqO1@9#WljV{kv!g|%UV~PXoLMM&JPnMN07KXNYWSr+; zWSh^jX7%E~>ARD@AG*7|O^Y>1NG9d3vdZ!EfoYtcPbHRUrk9*MFLwM#%BDl}3``C! zbXIVkxQeA^iCRFLgKNfljY0-#Vb^^|EZ#?~KijZqiCJ-d40$PFVqqi_yL6+{JpN4z zCl#I@oA&p!LhZC)>XnQmr+0 z;bf8bcWPC3x@klv-!D?<^S&8Pgjk8WIX>D z%X!o zDNkJXxv1YfzT>jaeUv47u6OCT!ovu(_Q``K#(JG>;L;KyX zt9IX<9yTjXp0w0&dbaIZ@jG(XLi@6_;%4?)%qz8=RCcqp%`M_uh@Y10oL9CV#db2B zWf2#iCbIDaySsbFt`!j{Ql2O*cXTiv7a=XRtqXL=uky&g*)f0E!c>1l_{VplJYRk{ivd73y1CvT28$l55d zHhFgO+m^Q*Qad%Z_q3nlZt>XQs}OOy>0XrGIA6Tb-3z_qAxZGN;GKS=2agDr|B3dBDLbUe!Z#Maan;CMIzSF-;pZ z)vacI^_#zTSJM7DlUa}dT;y#Z@6^(w74hz-tlE?>bDtF-mwWK>>|>QqC$7#*T1DYY zmz43I+rClBCayy3e$&jBBkdK(BF=KR{pH&)%(uhm!LrXawN_6iCLLgBpWU-3)#vQ0 zGcP=90ojsQ)Lh$RK=LaWodG@P#J)5N1s-XJMK&Ut zp<7SOh5x141}UdM+eMlLlxAGs-H`ok#!s2|2X{n#nIazYS#4JSjtYJeE7cVu>AX(r z6aQ%({>#S2Q8aOd@23drlU4 zt`i(+xS3Cy<&Xv28Ow{WW#`qWg`QmICn54$<&^~A(F=Vyzx|GQU$HFWdApzOnXLWp z0t=S(^oXzgaPaaCHg}6Z^LY3k%NlX4>EJC3&+c-ZpPxESf41!$pC4|PPuIwNC`g}b zY_~yNN@ZSY?2&mr9iG>Y{NBUM*M3~n(Rsn4M5hJ%CXz;atSNtf@HC1wUAsBcA)~kJ zQNshl6ptE?Gi#5s`l}@c@+*F*GJV=xbn1?zl#Fon<#W9zW>zo{U2=X z|L*kPu(3|)O7xxGh0|Tw{N~#pes|99@LoeJ;U#esp0m#VsyEemM(*u-egBv2TQW^6 zp(f;w=3$xV-e#BN?pgM%wFv)GQS(zn+WWMI%Hgady(cGl-`<_WpBQ#M=B9K~RDjbX zsk%Q`)aF~)&&r!6XmdwXyRP=P*#7d*QvPMuf2Nn!;D!)chBsMHLJPW_oKObdgbz+$AT^{JQmC?Q0cW$ zWo8fm+-Y4gUP4nZu}tvV99QDGWD!@J7sspzGj7hRESMp%{Go`eKSxMMYy1}1ySm%b zt|_eP58)M``Y?o}Lfla-%ByGXjl6G$X*{bODw;p|y0B-nu44UR(HJ0QuDP+adBeoA z$1c(yW=*#io7e8CuNJ+3!=(3B)YBZN;A!2={QFi#GqO7huxXlXTjRMab`Q4?Uz^9; z6D3!a<3Ai%R`(9xZT%hp!Tg6lB>6w${PN(ZB9G+v% zH}C)T{k`E$CcV#R-wSFqz<=aMyF+PvKK%C`yK|1YnN&PlQD=}GXKzdR+%{Yt^CMX64DtTVkf zI6YXBqWk_*pl0cYDGi*{c4p6Dcb9!sYcsXWZ$b*kss~X&-TOH=COqc3bc{3m9dmK| zqC1VppQ#D&^Z*3NbEw zxFy4<1?}|c44SniI3Tpx$1?x=BO$5o&C7Bbq_&*eklO1Jd_wi}LzWZ;&tippu3M*A zJiqm1mEfVHn>+FyJ4>0P76jjo^HkFFNT1aG{?5aMZ%RKW8p}woF?j#==kNcI*Bj^Y zWX=9^$o}oD_nuGkEOYc?&2qQob22xNSf1Zh@aM;3{!b4lANN>tDC*$r$AbU= zoD03Tqr6?-;-kU;)!*x8%oMmY%b)N1$KKoz2PeO7@mo}QZu5PUGflnE+o$a^eeum= z;`22xTY|6ZO|Lx~{j?+g)<3;?(dizWrdV0{EDDJ9Qe>GKqT=bMq&Qt_r3vo@b;<0? z=?)9HrbRhCm3ns0h~F-6X_JEcR^|YiLn#XLR0IRH7k|}V&+L=S;-JNJP0FR??13$k z#Sh#}&dLNnd63n#cB0eq>K!}@Ijj8_uH;v%NaoiKS}L&ibIKguDM1ewAD9|Gks;~{ ztC!ca%Yxg?bOhAPq9(o)c`Y4aRwgGUe{Ol)Z|AhWpyu%<-*xwIs-7&$v0VH@wd9xP z>n|2tHyt@{_s20t>BnJx`|~R-7u-KR!Pcb;XIbz1r9B zJ<6y*;O;-?@x-NzpJbMCoW5x;s?VSI>FN8p^g9*r&Ky7E+so1PmBGI1mxz7AKgCrK z3eSC96Qc9=cg&r+8?K#xDIytvYwv0OJ%8D_9K%does(CGH;fB9o94m)yY%dpDH(H( zG`RZK@VwC1FWh(e>2bd~iCgUxa+PMjzwv*UejMYh|JG<5FMF!1kdVuw!iqJsFTPB%DgGz;?{d1m>%vJZ7OzdZ{&G+M z=2;x=O3YE2ORn&!3&-vI##i3i&;l+MdSNBdrz9jRSMf{b1JS|c**9m_Wg>9 z%ciYar~mQ5&dE0aelXYkZ@+i^eeK)IeZQZHdHvVBM4YoVQ974NQC-rHQ%6L+uk@|FOfH4k@dN7uaks(oip_?=n3 zH`gvp$(-KDyQloKhW)Px$_q@knm!3COsk!Hdhw)>IVXy?pPqeL(fy`m<0@?ytizHgHKd4{Xh zW9CGjNWJ32VY%f+!7Q1Q#(<5D+*(U#uixCrVy-CIWmHkNDmTlz>x#;KF_(q^B5bmj zTqyJ^=Up^Ysplwr<&7&3qZfqADoq#d-&VY4wPs2q=e+98k`j)ql-~weX>?6FelF%g z`u*>JXWVKso;YiTs@{%{Oaa0DiGqeEczeCY$C~1U8jnle+!*=b_jeE(VfL*}Xa-S(+#_q$w}^#A_T z?wW_^O4*E#`m3qzQ}X71)_ZiS>OtT7LM~E1_n``QP=|e%Lwrx#h%? z7qSw|c9&;7v%XVos-k#ok@tbrwkRYa;`HwDb$btI*d4#d zyKGy^UcPN}KJ80Z|M~EI4cpxGk6Q0K>=^#a?OXZb+(glh8+q<+oh7x(w)RNy#*jsO z+1#`WGqd-7`{t?_x2KOs>NT_HG)32A>;FCzpKz4R{rYa3nm;!FHf4>TmQO#&|B~7r zdh(T9`_-j(ubcI}GY|RKzB9gZYjg2$wfy+Z9afedGvf9)?D@B`DKC~~U8JBu)Q2A{ z+Y8dB_u4#VKW}?(;vROpgVMeWgD<&U^e(z2ta(kO=KHT)Df`7+OdB00zFsjw_`%n? zwx^ao+&kOd^47}@mJw-t7k|80oNo5?QpJ46F5}+k?R|1f@5_Jqc{+UOPU|IF>AI$@ zJjLhczW($#{ait=S=-GyM}FT}`Hpuc|8|8pPFaptEkzT#WhRy7&uj2M=$5DUQt0$w zVI9@Ro%1Jf^&U3L%u9Z?Ti3w9!AHQzdGfX^syB~4wqdHvVZP?FQRwZ$l?S8)BCEfD z{&@Y|0|5(`L)BCI9p*n!;gq@^F=>J2zN%>xi(_uze|~7O|E4`#L|7Bvy`509yJJuD zSGn-;8DdXobWF*zdX{{?R^sp1p7>vP&nr*KSZN|>J@b>Z{XfaNH^=Lzn8)q!xG1-e?*@~yn$G56Dt-}jG8Z(sj;ZFs!uOvepP2VY5r>%~2a z|92|C@a^2_H!-)>?Dxw##xdQ!?qwBCosjKoe*UC+ z@SM{Xj&baPM%z_AZz*1!pypIawY6EEciUpMEhGZb}C<`NB>lEFEd zsb+$lAh&N7J*7!50D_1M0 zJ4^3$h1#a658rh9)_rmI|9t7F_M7~-K~FT}xK34_YZkxu^Gp1Xz&;)R7ipTQ$v+IouW@Kq^`HC&i{W_-%v-}?!!lZBZ)PSLl#U=v8j3_6P~yCSn%uP zKW@Bqf63KrcyZm9#lj&<=Z~b4<=!X6UTHcQL)}q8)9H!E!f3nFBm5Qi{YTGTyV&Bj_0o)GIWv_rS^m0qdN_T! z!C=Nwv|hmRflbE(kESE$)11mI3O@zvy~`^s`@qm|_toU@Q_)u^CAOQZ#6~KrNFI}l z34Xl9{Z_}~6H6aFmj7#T`~MgDqR(fq2SzQBuy`7tNgVw!awCKQSI;s|uRT>Y2-v zx#-1mRxg#B|IV^o!cM3x=IIOK-lcBtvCDGmW}WAj*{cnyP>6~_X zYv1DXe)U%ep37B=%q`oot$O;_q{+vkCW-ypKT)OT)g4y7oV-ux;trm@DB!tZTVHcz zuc_&h(wM`Y6|)aow0vO^6glVc-4QLwc>+<738NE029$?rPz0AQscHeF5vDTdP;7WY}71vn zZdw1!ao@*R*?(T`|6{tOami%&wGSS0Y^<6xVabf2EUVb$MS{Gq+~O*-d9wf4>-wpA z_l`$8u42)e7?Heq&!c03{~q-3J5~REdwt`kz9iW|JO2%Pt}KtZmtxZI>$XUE$%K+i zBGLYtHCHzUv2M%c2nu*7|G{|vPdmHs#`ca%lMgR#VK}ww;gO4v|E!e%FLS@(5y(b++Q=)a?>y)oec8==@>Xa`^jhp53f@ zI}RJaPOm7*9FI~aFV~#AM>zDz z9#{60S9oTce&;Cb;n3?kaDquthUxOD;}&bye#jFleXM+F^VPPpK&2b*(+YZyi=BBI z9W^gt;#%lJ3Rl>&-;IDR&}i|pP6yz9^)Z#IW9`Nt)uR2$9l>-yU%a$Ws7^O8feS{fU#Jy^J{ZRr~C@TWFA<|xRWuK0P2H%nJ}>FW7L7R4;{ zHKg(__gz^&*+u-|lGz{cx%+L{dFA*+i>jnn_TTfqJd=}usrPxOyWG=H6(`QlwQl)( z`N}fIK(2=}&#hwXUQBe?w33~cWAP-=U;A)DlxyqZEq$9i6q+)dyLV0s+u@z{fpKa| z$!9$)&kYORlsBwPyzwUSjNK2(L)=YowwNSIpD$*y;mcNMiQjhnWW_(umN4lBqQ^d7 z)G~>XS1*14=s2hEhe-*}Q(9FInos+==h2s)lVxJ9&aJQc$$jSkOMCa54e!2lZq{75 zsiXI?MgE7&n&(_(ITos}^Qe6E!s`F^|6k*mUynU{%VCvj9A`}Oi_=rCel`8~PXDiR z-Sh4Hdd^K_?4Lf7Z}+BceP_ZY&p-YqsOVYZw9vHF^jMpf-Eq5wo}*RGSvuZ3)!w?P z`Y0Y);mVUK3YRU@t(d1|?XZ;Fck?c z&z!8g{PM8{u{HhYwg>N1{J}Nx+lk-DBVWlW3raOQDN0=b-7~9pkNL)G9yPJz`+53K zvg*<-ic>nwGTwJ9ws(Ary}*$p{Igs4vF4U9(oTDRSERq-a@8v8X@shadLI{vcf59Qg2y;-CdWg zIk0ld%Zjeo&rI&}C}*`8i0tL7ytnitU}=4&%ALSUy9b#l=K62%hPt}d3>IuGjWdemx@0(3fWxyjXbOlOP0PZ$PGNc z{o{-1Ef*Kv{W$SbvpGw+<08*0_ZQ#&C!gdI4PB>sc1ClS_-n0#Ksf`BM=koSdL}zA ze46geo7Z>1?bY1J6DI43mEM+Y`aIG2zwO5xUGWyq>K<0B4%}$CBq?{$JpPCCF5&a@ ztKKbM^L1K-KxX#b9vjUWcW*D#(*E^&waHY~jZN?0<$e5g&g`1ES}3cdQqN&iv59Fb zwj`LzZ7wfxn3);l{;0&Zq(KlQ04oHj*IOr-aU_VCA8-B1jb zbe*?w%9AAlF|jun2zIGOge-bl-aRc?|Am<3HS3xWbKZ%}`F?syrkLi-yHjjplPV*M zR4%J{ZN3@%C};Wk2#vBei|31f5jt6I)>n}C*kWAfy(*k9+>Q{krhqZlKidjZ_)Hl+3mumHvIxThqRkGZUk(57vJ~b zdB+8TnAz;&R${tmjVl=c#qKOrjJnF_qG-`#$}>sNXo&^OUBCX_yT9-FcxZb?#b3sI z|C`@;-}`VaU{_m7DXZaXiF+%SByheEQLvJi%*A|A80M<$Nc0bA?FV)oMNv-O0_l zZr1TT5==@iI}@L0UD7!!YWwf!{(rLX-t_%wt^Xu_MorY;u4WQ{-4DrA8<)<0*0Su; z*Xw$d6n^aUxAUHq`7Y+xqqpVjihm!AeKxuBN{Ybutq=B#i-_B-3|SI1NsaUG9_OI< z$sw-cqI06+9-er`rl2u5xkg~A-jWG35@xU;FPe18+bB!%z>(cMGi2wel^(8|xM|jN z9o88)R=BQi>MGe0WuPw`B|0NPAhTy&)YpPkKnS}h+R4!+oDA?EfG4No7h5H9JwtXL17T$i) zbAbKgy#}X(_d+`vI`T3O9z8d?Rq8gAP}Pq|>@fyUq5_v_&hxl>bN}YtUXgW$ah~wEuT@{_|(2?;o`8Uo9=7 zrSv#9m+$|!`$f`$R})h|%!~hhXu6{sPnE6P zvO}yt-Yw03!{a{3qHfBnl@h$qZK{@dU#gm!9yM8%b*KLZlO?B$vNAi9U%O8XS~y9v z{B%a;iq>On&XS8x91ohcI8Z})(Gj%(>C<(Qh9^vpck!)u+?dLwbmU}6L}!*t>QjM~ z3K>5xmq~_qe}Czw%^i2X?(20INb-x&IM`m%-?dBZVcnr`-0RA;Z#+^m zHxnzm9yH%IC*t=0J6Z?!aUe~O=|J2ugjQ?=$>F$)uKCz3er*Czf^Otg*q?WOE zVyDA0p_Pjd1}qZNdi-Gyf{l1)qQzkIY>@?7=0yLTVP|3CJ= z_|*@!^m#U?mHF*jM4DW4q`F7BzumOUEWu`O(FXI>{CpOU zDIvKxEw>2liN7E`^BBVyriZ4_Cou-AR4+T0GHIgj$=8yd9yj#(4U|?X`ud*zy6WMt zcY6MFcOCG$mzZvpYBfjcxAv2ui=HB_ry{(9RVK~eom_N(L3sb6GYQ*{PdVI{ExM=7 zAmvfkzS8{uyPMKN%xiu>ldZ~R4}Mp?#Xdoa>He;=UgOoTbxOImtvM>&b;)HHS03XH zf!ygLsp+BrHI82LY?&}4EcR8xzOxlYve|A0D=hPFZM(RoIkbBB5qUq8g`%Q@-R+BY zzjEj8h!FX*;>snq)iyp0?2f$i>U2nC{X6qP^YZygPycM5V^`I+eg9`R#JGR zG_}Kg?*rz}u#jHI^T!LLg_+DMf_O8RMZ`rW-YT0F9j%;q_nx}G(Uhj+&MtYg6b$_e z!@hGAo$xbVevakx1%ox}C6oBdpH~NdyW)ECx^?NH7Bo1d;V=o z-8|#jm0^>b&Mi2_KJ2*H9nzo*rNM_ z<>R6g88yAj?sW^Q3OgBGob%W~PEqK>B9*61DJ|k3AK$y0%;vwk@a_BAZaVY+Wb_C; z7Hkw}TB}ev;XKp%JR6t#$ARba=RJG(R61r?N$10gQ$_g)UY-@cb9a~X3$Zzt#c}}) zOsup|eBv{Dzo4|~Q{WK~u2#RIYvI>-Y_?vap|s6uOW4kXuDZE`DjxmUx_0k&4(pdo zPtc9Nefx^ms_B;E*Y^ZzebPBG_N+_vr8{dxtVk9XR&rkiJR@(8NBLC+*wyn;n~FE>g2Ef=KQleH!UCBdBwHq z_Hp?uA?(S2Yzj_V#@)R8taonpo;|e(&RDozwmiHmZNk@r;=`?Tcb{4NwAZ@4^8d=q zPdbmWb&Idf6nIx~;z2;u>>av6nlDdje!9nI=W_kjt;n>?x6drw{;}>}SN%x~nU8vs z?-inA0(;5@4=prhe;>!TefHu4iS~bXj}^C^TBy6$LLmQK6vw7Zg^HGkelroPiv>ooIPuqnN7tn86}o=_jYr-PFI(&V9^ZrFe;fiQIvJtwkMuK=Vv=TYD(I0 zrQc$z%EJ3^8;d8hK4OZT7raEa$6RG+pF>1)#j{5B8RyjAm=$}J++n-9;_AF*nyXGU za^2?WTCEm8txUHo^^=zNEK`kqH6iNZ9OqLV-`#k)d zE~!`vvfaPbdAt6|{!}M^$8BkRlXmIsRIrlyVqRJKn~QzwGxFhEwj+7oEz2ze7k8bj*tcuz(fyUrrRK-VFKBr( zCBP=nX0pxGBd3J&d%n#yZQJTN-%ovU7GMaa9sm$F2w=XVK3KdxUs=tHjzh#r?UG>S5 z!uIK>WFBYjiO$g05A$04W!d+)uQb*@cx?4A_DQ|sBi_5OEd;ANzjg83eA1}@sa`+r z>eAF-bL~=1AAkE{Ry|qKb?uD^+jFme^&6~w7PG%xdiS2LuxYnUPN*zW7oO@M^z%%R zN655Au9G%xy7lU-PpCnv*bw8y{)I_F{R5!Y5w{vR~cw9&(ET& zgjdt#`?fNHgOgU7+*+?>P zdE@#!cb*^HTU+<*X1j!n@p8TCGs%QIu=@QpW-9PDF>yDUFqtG$q ziG%CEx{litH)Su}teBwkUZ|#d(yX5~^MAguT6gbhz*{dV7Oi_0&I-I6rj)!~C0878 z$X6^C?dCYmIdH*^fVNhjS4wiu7JJs;zb|$)MXJe(tyk^f6yE;4d5`KpKmW%i{rdG9 z{;-v^yLLa`opb)1=jj7Q<#WHDJ+IL4KBjW^$&zzk^_@3!+NN`b$c1mq{PK*C?~=~( zRtBrZSyF3Op4=Tf=~R+S^LM7`V@Yo(-m$pxoPXc1(p{OyK0baeW4-Ip?Dczk=N{R; zrd)1iP*~r($z{_gDz!44a-S%ee>-!Nu6C4mM(EK+j}{%7c|zf330Ifj!4xZ3o$5;} zi?6?Rj>wqk{if#FU?D;2=Gt*B6Uy-F{~omX-`KfRdg;p85Ch#^u~yp6 z86A(Kuk7Wnc>IxXNzXq;C5LJ17qbtriZzLxvkF>!BAhK{?wpq;hh_#JpK&(pTiG{{ zk|swNUM3qaRgIr{_m6E`AS|v!Ad6dto~dd5YwP??b6kDnR*FQ2o?JayG5T<|&CWj@ z!p|#n#3_?)Pd(&ql0BxR@?JWhh2JYHY_c4`>gqZE(`K-r zzN@w&)yvJvFud#3l(m&k8E@ln>sjdJy?qq>IAFKZ)IimHj8pSB?CV^Y@Y6_*W!m-Y z8MD|{JHBX&3r}$JRJENO*KZ~9YSkhg?@cz{A)8e4L??x~cdg(2q*KW=NNuv`oO4T5 zu1cBC-KeD!IZ5G^q+?b~P{_)J-wCtt|G0Ypfi1uNnPh*vQ$gDgC)9Ep+b!@Ao)f+! zvUkp##d}>@Ht013U)*e4VIwEnaq9zj>uTqPvnsqlpEI64-D9oCWxpr4s-3kaw}x@= z3=s)hwDifRl>YB)?GG+JXmiH-+WZ2GNUv<2-q?diNs=y@cv?F1$g$*g|M3-tl z{B)-R~s z<-7gTo~2&bHl^;;I(SXkZLy@der&p_^|^L`dk@7$DMgE!?o0UY__ucY%NGudRMiel zEjqQGf7?sb;&(fWD;ECv@=S7G>|GU(6HAw5>SQkR*t+c7w}R6VyED3y*C=0?QeCXy z5Ws1^%8>o6nd8k2RVkk%##`)an0$BDv?RR@krwQGz`XgP?W~u%1(zkXH>H06u_Mtd z^;nwKgNY@*zIPMPPs=f#w$>nhneelNmn1HG1#eZKAI4?2N87xseCFiZD!i^g z*yEUP>(;COS}bL*(xMwv-PCQZVq0gLM=*My);{s}wvQXT$*~DbI5Q^+O#1Omz4O@5 z71|r8lz%wr{xVT;dCXIrmnmne@-44TD3W@))GnZ0CO@uba`gL{M?2H+9Z$%e+N-?! z%(37}%bcEap7WaFFHq_5dqvXAn$uZ=Lh?RWTm>HF*tEKBUf8-SvF2LuMw8ym*qe)P zsjE#`$+G0Q=Az4qub1oH+nT$x-ub!^|CM{2u0$p+W9{_#ac3p}r{5>L@8rHK@O@LT zeOBO&o|*cReamd7l>fYV+})UeSbL-b8v)tKKyl-zQXG-q{O%0B9YA+f*nC9Q# zd+JB%S%s4xEe_o$R-7npx}+PvG1M_w(O`?|vML%?clVFcNRW`qU zw{O~ZmPVt>(~3Z)c%Ln`n{EN6QuuL z>91>2pI_7Ta!bpx4<}l=WA;|IM_)XMN*S2aW zo_w=l*}QWX4_$ou?1f8km|4@QmIdFYDZO9tdCJVqe@=W%|JfuEGdKR>I*E9XL+i?S+jY|pZoiN z>-LH5-4Q6nB+1n3b0JDE#+{?Wq^UD=MU&$ymbH^IjHONQ?6A21c@4Q=bTH&s|V@ zD!u+id&RSv-;HJDH&{zuy3(d{`o}44{ZD7}_nhfGF8}Dv&*YlVOT#N(J}sB%Rjc`x zeEvh{`~MuFsfM9PcM7W;_&(_0_m5ZY@}!V5flbZ|r^+8Ke&2XyRjsyior6bX>;vwW zB{QX0y<~aG8@VdLs>(BhNzLNg?`)opHur2#s{8Nj&wsP;l#0xUX?tDOc>;qBHi@3C zIOpCs=fO(j_vyRm8L!M{+iSOV+MdvNAE&;(?oheJkMqz&=QF*4D$<5L;KH9ddzulY4y(Z^q$hAv+$A8VN*GO&Ox%H^%-q&I(QHxa%CK>hb3}k5(npfGqapfBKYELa=uh*Wp zIzzT?Id||?!=e+9yW&>;2=<;Ll(Ba4gg(FM36)dW`I^FN0~SnmS>0lMy#MpT`M)jC z&9grHHQIc}6xArLWy>B;um5X)b7x(@y4|n1H3fgZ?f+S;7r*!P$FrvYuFU_b+v}^6p2C&F;x~#He^o!)5NdYpMuplZ##0w%jP+k;T&QgG z5jwSMQC`jwZ-4}$k(uJ5UOswMxD?*qp&yMS)C`F##=T;wm0mrEg^mduR~<=Lb^0A<`eL6-%C0Rg-;YG5 zq)W;#(hf1bm%OijZi3p|*pbqypXY7T+qkXWZOiuTBGd1)djf)zPg!o)yT3^C+=^l3&C!DL_EqaVKFKT;PTA}+V^e9O@C%bv8=2s9-fZ5dQzHG6WhY-`$vGT+ z-sW8J`^uJ8a|(Z6i!Cd6UtzPDZ{8}_>lT_n&(7Wc^WRhBJ@2m=?4y!su%MdQym_j{1y{g51}9{d25DkDBE) zugu=%*~Zg!>&A)QQ@d8J+;C38^FmX_Y5oUWl&-R_>Ri>{F=IiRnNsikpC5`Zy!`NV z{U5>dvUf*Uo{Lx)x6`ZEKzZt{f2Y{aPR-qxcPGJY{>Nvtz2Ed}`y_KNd-(W#ZG-&J zGyEUVJuRPcHeF;c)5^^mN=@B-1ukm-6PHfeJn3VB%Xaexm)|CSJd;-Z>g5!6Iji%= z@-9aHszrFtR%EaRp?tT?---UA=61c;Wa^P0ia`X8Q4qi?F zId}E-h}jW!PmavYXn!pecI`(A|B*QhOfw|6GJh~Jdj5KM@xG0J!$i7;xOx0?K2Bs> zDKtSS(Ra$aZ+zl5QA;{hI=39ZAYd>fg)h=~wh7<$TTUEZUwOM0r*l||Ilolh{P(uW z;oAaPVgY6?IV}3xXE?(R(ylqd>idV8a<{a%PegSGi$O-geOG0sp|@yd+1}cTS|51oQG#_ezy6tZ~4z_+V>x> z)~}hpbLGj9Lr)$w{I&kgb7_iSzQe528Smsb)IQd_bnVYU`F#y~RxQUa`ilHNd4ArL zm&@lpxpX^U(8V!Gto~X$Z4=+* zfWYmYn(JJPmUTXke`kOG+FJ)Rof}5H``)IkT`zFx2w&ul-v>B38>N=0s$7tzf2KrW(xc%Q&Vyt=hJw-R82# z-36&4e4KGEJWj!vbgQNmA9tReJ;$oB$2|Yu!-TYztGi5s_xumO?|$!O#pmAbH+Jup zUF#C;JL||X2koOLuI|}f&Z~VaX72vOAJ1|9F7r{o?Yvh+z^G?RP2s29Z`+uz&P&Vw ztfS}hd9Kx&=tZpP1Wq16n_WfrM zuIWzE70_}lT@=~I&t|`RW#0SZgncJp%KtI+x|*1i=1}y%@7}r_{MY9=$(k`gDB#+2 z?q&J(lXKLz&Z{&|udVFW@tkqpxMY^W?d%)7w;y*F|COJoNyYle#j z8~61cywH&`xuTppsHeO zw`FJHK|%3H6O6k1CoR(ncv^VpsNAm)A{WczXDhNmD6>ql3ID9%g%eQTqN^337u(i^Nt?$;aYO( z&W_UQPEY4p>}(QHT6dN6%&IQO@T1eO9JxF>XqHpz5ath!}zBPRLMnIDVS*BVRZdbVai%i-De=R^NL zgT0HSCw|_fI8jJnT)W8ik@v5oR)_yG+kN;TS>^3r^Zl3Zsbj|TeIl$k9jdnf6IU=V zzy5P5-#5Mwo-vQ*Ew}m2%X;_rt?}$LizQ#m$M1aZwb(^rGE?K>Hs%SU)>miD)hase zX~KQmZ%WdWHlDY3E4FHwy?;DAZO>sA;p;ZLVx@)n4h98oyVUd{*D>^a;DfA>TW{V- z)Vz6X$uBcO`D6JhcQOR)yLRw#y)9YuyS7SQe}T1kM6yzDs?nl5JNxu+&)(UgeWi81 zL6E|n2S+{yek;o>e*Ct3#md9ymV2mrSl7xF=4P%r^t)xR`Ms34XCnLN*q{HJC@Q?< z`&)4dRgUHfg{GIpK419c=er@rOHf4OZ9!@8^sYU(k4Ei&!M@>GptQ63Nsp~ZcD>LM z)vQut3f;pn^YPS0lD?VGiZA6!>4w&pw4N?)GVWV@S2k^; znaQz9<-bd!QuViL3EJ(meC)~pUUl)_+O*GBUapS{m$^OKcu#Oum;ApUo3C6eetfC) z-}nDV^xgOMTJ)dt`tQ?k-o5p7qPkpy-K@`##P|Cs$=`+FDx!;Xlz0!+b-JR2weKl0zw1#QIvso+Vh%xnUZ@D0IO!=Vjk{$lp zM-&$&e*S*!@uU9c?u|;fGmq}i{`%>i?V@SzA0@=T<;$hayJ%ZEFXt!6v8XLqc2u6M zdCpe0?Q)jviQf$CF5Fu%Cw=pFN8{NAPuIRzYG*T_@ATnCp2_J>+SoG#Q)^}FDPI4t~q8;W$*ug@_)J3*F5(1n``Sf>6y*{-Sr+TmWjAd_pO(C z$;>&)r9sqm9`k|1(p~bwGyNP^{oWet`R-cz0WA-64PW1vx=jv`WzI<-XquM0uy=B5 zL-uD_TQ+%sZ`1{%8muA;FKi}-xy}Noz zbLzCPYl52&b+HM$a9eCODfRr&`0kUC)3NXV>oNk4o2 z!1+!yC)xkMJpXBH_`cK1$IpE*|JVIrW@c91TmJoL?*Bd-e>nR1PVV%bl`qxI?^jK> z2^G03x9*G2vS1Gr-{tIjUUBN()_-<$S1pli3~yTScy0c}b*)o4M3=WUbniK8rn|sY zL~f?L#A^P)Bl{?#%x_luG{-(+{~UPsh(na*YJgVL<0Yo404 z$nV5aqhnHT)x`%M_9~yTJ(MMNz2N=Y?Gke=yF)Y&?))trxcaHULB7IWpSibMhzIE9 zNk5y+C9l&vCFjfZmrrGLR_jgoYV}iHR=6N^wWv^j?B;tId0D3_ zJ11!#^oU93(7N<@)~QYVHuCXXy)N?h>W!hjy!dFVmHK)ElUh>;CG|GY6Ic zPp|(!oKsdG-)Lr)VtTdb=HBQTH}5HGKc2w0MWVN6&-$+~3~ul3o!1|+PL-9}rthMo ztFX*Lt3x&oo&O$wzgIW=algImq$O8M4=-GB;*qwPQQ6^rPx;s%mASj={m6-Wm>Xlx zzsp-kGi$GBjR2MJ;|(@CM?+A^=&tw(ycd(Zn+t_^uD*~$Z%S<;6wXz18F8zw#7^{%IAi# z7+&KO-G8X5;=(hn!X2J20-qBFawo<;x$Js3pIIYwZ3wgOGz~tfxl@x%`t4h;R8D!x z+jO#EdeM8agJ<49))L$E(e<#j!erK}YZusF9odd?d8gO3XK^+jmDUn$+p+4v;D+QntEyyncQrcvTw*j}pyF&GzJ zo%>w&6UpN}`L|zMc3oyyR1_C+ZTC6)bzb(| zi;uS4+E>Y4EFypP%*2mz+aFwzQ~JuX(O`RP{FbZ$rp8Gh+g>_})O5ZUs+4aLv)Oe! zvEWtUr+W8@=PE9e3?Fh1;%WNd;hmK(|KDy_WG7eOtghmhi(*=)yO?}_%{xI>@nHhd+Dt_A@ zc{P4qld|`=YM{_tTh0sGMVD-jTllXM@#MJpWm(>gv*|8rJG)mW1W#Ndl+JpPC1ugX zw;^g-m9)7Y_VipN9&jix0I3@zW+Nx#i4oxVNt<>$}q zcc=Z|@qT*fl_PFBZXqtAVPR>hb>H_spYz=2*~H&J?f>oF-^^Y&Nj|YMuhD=0e?BLP zvU0XFy4x)Z|9fuBm)o#+YrDANh) zv&A*u-aP$Ha*FEGz??mQ*q&CjWSFfma;-0xFWm2_+~oA6pwh2b;_CM<;qrYqt!92Q~LjTxVwwrynkC%MmpCG3&b7b|$~^&Nc1xN1#l zEMpvZ>#n&bGcI#V9o*C5+39GSJ;O3)QIOkL=75K_Gg_u_?B))4p6tZxpAs*ub7+ci z#B48Zu4tCSVwwV`s~%*(zp9aY$@Zs#XTIorT|qAMnMQZ^Ihx*ezoN*n@HQ_`kv_-e z%@ZoLCj>Q5nlicP%_CDUu3viTHm@FkcR#bTMY(y-R+~iiTmvJ8J#3N>>cZoXG`2W4 zOipvYKDS6jVw*il#Mzihb|GuMD zmr6d94D*`9Rrx~x*G2g!N3+9s)TK6?bZUC_t0?u&TC&!S;maO3j{cR8^IE4XDuoL1 z_5Sd9!@$tM;OXKRl54xw>UFbv>zd6=OXt35O|qDo)FpT=^vOAeMJ$~M!hSZwJ2?3PX0{lZh3epA`s z|KMnJYRZsHDR?fPa&(7d%aYz@PZtYJI~-%tKg~*(TOid# zeyGcGI?47zrTrT%)$x{p!GpLj!Xd23z4pw(jyTmwE#2qHWBap%R!mi^oS5z^E_*5S@{iHm$^46@-hP-> z>AJQ4K+1QkZ8BGX3a`t{xhlJP(ySX^cUcy*On%GU6}^4tk!cT0j+B@v$UP}3IrMUF z)2Y%MQ#6C8U+tK__w=t>yO(4czu6lnE3@cWV!#|J?>M&BmW^!>OAa~h7QJydD56Ej|)O`MxtZ=vKld(Enjwg2q2{0z=*++cLP|IZQj%HQ{Mjb;5j9?!7( zt{|-;dNITNka~HGgM*p7M>m=0W?sl+1mH{q{fFUiW?To44%0pT#XdowWY&i-e{b zX*LB(bJIS{eY)7LK?oii^fc?vb7_xsju2vVtCOOZ$?msUNQ#?zk6p;+AMr z)|K>v-&~dV_&2Osz0klZ`hleuD=V@K`Q&1Dw3pARZ4hstURnEH=JiFJ<6+?z z)o&7~Ez%Pa{r~Mx_>Xtm`pTEY?EE%V9^=s#HkNeL<+gglC?l8EIeFF{t34$lhZbAO zKC`y9D(ouOtC;!wmPsIs+Qvzf4kjF0`*1?=>*FuVsx7o!-o0=WJR5fAb$)D8#ROJm z*5?xs1<#ygF;QoZ#mQ^p@rP%3oD1S!7~R;LlXF6$ob{So;?qF$SJ$}Kl)lgqu)j5z zuln*F|3Ayme>ms?JA#E|M~d&J{QaUC61w2XPMeJ?|6N>_*luO@-XMbboq(a?Wz{~U4HFl%e~G0 zn4|BuEDKf2-qi1u?HebO!_#IWoqKC8-`O;-sf!z-X*C7^fg#gaC z7Db;#*yHP)Yj}!Je}2)%8|`IaUks4AEo zZ8S-2sg~DnX(>)YHX#+$MOm5pYYgjOCHhDeDNbq6d0!?`a%M`^c0s*mQjDCN^a^i8 zJy>YOHt|@^hZEKjZ|#nSv#;N{w|H*#Ep>~^`-Xn&=O%h?*&#%)t>!$$@1zaEZ6s!b(&;doOPY! zDx!vN?e7W*N z&A)E_&tLQ77hKHSa4Xp6&jZb4sR!RiU(Z~w*{hN$H1$sPd#V2q{(p&|tYRqt|A_j} zciihY?AmiIV+N~cg2=V&^$QoAp1C8gE8&%5(zQvddEeJhOq0xhDWm;TsPl?q{cmMT|52aG1W6q8X}n*R`@!;^JvcU9RUti!|q2H{sV!2m7W8rSl$>n#2E0@K|J@o=#Cx>C{VM881C;euVK~ zQajw7o${EebA_kBShCid@|?RjQr|z*<8n2Ad9hEmH{#;kQpsn|n`)e%Fng+865JZa z+P&Bi-r45GImP+lg=sP7%4J2KO-qDXA9=SfR<+)#kN!Y>*Uid^MWc)ul^gm zZ)f4;_FdgJ+xDC>|F-?-m)_Ym2}_S3?(VKhPHryO)hRApC;rHK@ADwDXPd3fD;L&F zo)lThWO(8F6N9zM)B5Zkil4G9-F(E!crKr0Ud%F?oa?LltNv?dzcAUb_?1m&xbwEe zBFU+uvsXUeIWILmX5+nxM;=^fOZtws&rhh#n<^|eY3=onwTnyt+s+8%wzlMWeo<~# z#eLI>*Hg5@C(qw={O6=Ip+^e6Qi7v)E)sO#61Dcj9gho9k8~x1lVUnTFB6?P3;q5I)4jI{R_jg@g3RVRJ}RD@0+}igoPX{_)|h#X|6o+ z;!J5qiIhwa|B6#hE*wI#tn;gF#9B8RoaNfp+u^42^VRnFMf-R5Z)IxaOmh#-z4+!} zYVtelU8hc2U&|H`~BUAtmoHESX=)q`p%A`i5{L-XDi*9+fS;SJZcGB?knPb z>#f^fHqmmAUH?^2$6n+AxhLyFfUMYL%V%>R|4QjL{%G}mYu_c6b^7xDQ(W%IXZuZK zpR-wbNzR5)5u0a#g(&F z+k0F7J&w!M^=HP^cDCQEln>+D_wVTXfU0&cWu>*9O1&HBy-e|mj1mhzd(&85;uK%1y$DI( zlWHJQ{OL(xTXpQer~7{@*S)@9%OzPo@7IZHfuIXt4Rx<3dRFzFV$@xLlHK3=o~o!nnh{_9#L%wjkHp_qNy$cuD=$7!_rG(x z>5@!#c|}6v%<5gAS9CtFUG2RmId`h%gV^lj5pPRQ__R)&^u7Av;=Qi_j+=AG$y65Y z>`#>H=nFjb=c4eE?(8YsToer|k7Ya-O073vQ~OSBzhy$$m6fqu_$;*_KIY3g_Ec-R z)24=|X!aXV^^%LWYz~>4_(0Z2_pDp{F=hTufnpWL(e5`sSk~Hh3SZ;;y6@!#gY@2| z2Gf>>ED7wH>0@wt&cBR9LCa&!Kglh1^631;=$Nsb_gPe~N}q}EK})}R7RQ+R@18&C zIDcoEurBL0FA>4GrcYd6r~ke6{iNh&kndk3Aj=9H5A9zL7 z_8d<>wQ12q8UA^eZX%g+`!6kzcUvQ4+vfG^OoP^eJqPU~9`!u^@^bN?@Bi=KPn-0V zr#*R(@tl3%Rib|G_v>RbLc8SBE9&^s_Mu0rZ<%kL#my~++O^>loAC}ER|$gS$StkS9R_SN#I z*Q#AVp4{`nX5*G)RhOQ4{crfbR(|2+B^8S+yz@R8tG|DoR6l){i1{6H&EKE`dTvsw>ZB7&w+It85ir|DdmY|5lf%%h_9BzGmJs zX^Cf7$%h9we^)+!Wx6WUQM=Fa$A&PmE1x|3wY9h9-*Yin>N;t*ZPteillu#vKI4o3 z`^++B?ylt_6L`fdPnK96oYwZ(TeffATBBFf_Y@U(howtKM+FEa=N3%n36XD^bfe~s z{=YN*pT7LQcX+Mzc7}+^IF-EZfxfL?CYKi}oaX9VtN-DdH1ofA|9{S(bgXHM?t?})c7S_7;V|>FM=Bq{#+GgUB8Cwi*ie3vj61;Y@&8zE?lX>}8?8s3&5_rh# z%A2J`<$`*=wxp09d47oDvm9C zQnC9azg*!YZr){YCL7R`TBvM{i!w$&F(e(G}5c@+*DlK*;rTB*4T<$r!!9PFIatn96wTb7VBo9&7Op6_*(!6Wt*|;sfttid&(bO`nwz8G| zowaXu;l%a(N+#ajt1DOkCh^|)ci#(Ai)UxN6H=SK=rPA!mBi-fMUUK^9u|p-J^S&x z{_pzYpSME){mH(cnjMh95Wb!$A>FdVfE5q|dLN8E5_I?)$0+=iQwjZSaJWA%=2d$(WuU#9TIlG5LcarW9l_b<5TE za~EP~roTRRZDG}-%UK@}%&fL3{-vVcumADZPwzRk4=mpQZ{!cCsCahAlx?ZwaYL(4 zNAH}-MZvzD8reTu=l`w@S@iRe`F`)R-096Z?8}`D0;8Nau(g-6a-BVzZN2Bisn>hT z-YL!hdxv{R)}i%(r}jG)_o>WXUcce+>GkuL-wd$%cO$Y-&idS^jiOO+FPz!(xMS%z zt+z`KrWEzEOLgy2w>ng^lZdKbwJ0sMa|T-4+m=h_ zuC=(8_3`s@|Kh7pYj$4fagl$_l_YyPGNRq$;H?S02Dk1fZYkxS?>nP%o4nYKs(8P- zy@GYJZOhyDn)M}`rk-AQXT7Tb{@t6Gdb=_YlcS=Z5bI)d*lKYwOEKBDcXBWP=W1H*lg+DJ;-#g#T zZ*$_n-iAFD#%um5`HCEO+_zG9*F=MvX*PczIR80x{-25do^M>$_iH;JZaH!_!(GXw zQ$*tF$(uAJ;`bjU)H`T<7KWZ&82;@ptF4MQo6f(S)xBLR)0emXwpRan zFr!gE%HrU}os)e|bA~Ugjg!y6d^vYYNy#k3$MYPHNa8l<`4m~x!@~s}ULJJkl=6J2bN+S{bMj9?#`N2<^Wy9eeeRxDTqN6n z(6ZaO?Uba_!ota#k8jVO5h&KzAzt45^q-&M^v%jK`B!uo2AA%;mwo6&pGEtUiOc4^ zEG_ifsBH9Z?zfvqKfkJ&9L+DQ9L{>PL_enMhxO{7-8biKKJ-t#LtgLA@m|-H9Is_V z!-67(%Urh3-#Sf5(B0*%n?a|eK(?ofh^NTExdqcsOYfH9X*&ORh5Zr9?!H5Hn@=tK zbELn%MgIRvd3M#xmv2AU{3^E2$&3E?=X5-~ZbXj$hJ4d7i5y9tyw(XCD>F@k@^a-q ztpERY|I-_ti`T4OvvF&^Y`(*B#W_n&__SrGp8d%2D6RW!;z9YkU(zCBm*Zj&O;g?f zzdhdVxNo1{noWsX$8OFzU;i&suIhox>DS#JvK#kXC%$}Qbff*g3cP$?DS{y8`m=j>dVP3o^~h!OQT{m=CsSu{ zQZ*9bOXj&27Lo7Svdrd%@x5EeQW?LA{>$S#>XTF>p1a*{xzm+THq!5OydC5FDhs&^ z7oYreM`qcY8@j*e%;2$@-}_^p^rr7OY@eJ8c*cH4sg5}*V*dLNobP{3xYB%Xq2pv> zkLMGu*j}IPo^)tRNT{ohv&i~slfsl$^OlqrEmOM2rOUK^@g~vAz`~cu-e0s)~AHS^o-+uN}{C-Mxc6;#h_ldQsy>rd=76-g=VY_m=%0_gluHe$;7hiw4voqW0 zkNHa-dBsz*(tmsYe>J!FycP4{#UZZQySLxG_44z}^EK1kD+rt9*HsjU9Sx0h+NZtRk< z_U#t)o%x{Ph)2-M*Sk+{%+Pu7DXn8_V%gNaSTTr0%~WMqz+}^7$AmaNbyS73Z?fh^ z9lju?q?);k86hx=DFQjxNhUJ-FGiuxqkP#j)g)6@BOP2 zxjK(-3ibOjtt7@%s^pR7Gwp-5r#>#XeqLdA-#z1~iT2@^M=k{gTnz!W<=5InIzA-G zi2Yo!G2BmKuaci|%a<3Y0{yb9eQY0dM9eB|-f>rTvzTi0A9u~(J|@nKk^XnGSS*%r zS}~!m&tGJt&x%iz9v+-=vA{>wcDmkOm)TA6|EJ17J<%C@tXc5?KjHdw=Mtn!=9b}F1PhAw05fl*gR<`@g6?x+>AG)SEmnE;IOs&}ts_M=YX_YRXAB z(=KM-vU1II4K|OwILmLJr_O@KCOh*l2(Rt@5k(oz!B@<{$5A%AAlmTMuAZvU_G zs`*)N+!4Mjjk=4}g3rC$Bf3>+>AY2qwZ;#B?|d9~D^s36Vj6S!c4hN8emg-M31R?O%r{#N$wV&;yGTQ(4wm&KCZ`-xj>)(&_`%ZoPeok@j@v7nzdE!AyrzZXMJLCWVNBhqU?e!h6 zt!MA~n{Rhe=T`*BgpFyld<$=C%t>CDX(l~qf2FXlna5<7l_rZ1wY+%~VqkvX)$4qU z&BHslr&m5c>wRZ;>4Dw%|MI=t`(7lMv)@Jco7QOu^O@zE#Y#%PO)16eY|l@UG@Ro% zGg#_vvXs#xU7JANSl?E)sgv$Rx0ufPFiSqe%yVnf{!MS51+NkBIW;Ly=jBnw*>+Dh zdT$Z%+56b6Z(*s(y-8ZG!7hsaajZacoguS%)2h$61vNyK zmvHUhqLOF+al7ogB8{f9STmE2JU^fAzWqBQdFRfJC%1ZEx7=)X{Ql4N|Ao4}WzF*L zJ*)pY*Wd8Y3IRKXh#w9qU!59PR{VH)`OmBPU#W66$N&DhR{tkBuJW%?5UXob(7`=9 zMivKr)OMIS9!<3PeeSdV#mud*7z`V~Ov;-gXfFEsbLI^kM|QEWX-=3^k>N&^+2iRS+dE?PdaTsoV4CKP^|Rdjw2S$`dpXX zCad%{oxA`0K+?MX%e1wp@Y`K2vpO0zq0nXXRRwABnuoLXKddXS>&#eqc(Z=&vy|wM zFOXpPpVIq#QqQTSHY74cLVIPKu-&iE_UHWn-tkvdKKSQ${^i`Q zHzy|Nh@($<~_Bn+R_~~>vrg|wzDr!aXq$u?jEex zJZH1I{{)ZT?UuhMYrgIk<6r8_cA4{hZf@WfYnwfLoV;5MrdD-OP>Kwn*q0>~&cE;csecpVw@aLZ2li9-UgN z4?jB)Dwe6!*;Q!f(a;jU#M9|&!JaSIO8>p+|KB73=LG+Ur}=w*njSr5_xpI@rEx@Z z#gm2Ed%ir=H7heKlqeBN_G;eTb@5+Ef6a&3KcC`%u|5-fHgi@_YhJs@@*PGuI*sNk zbakcW&--{#?pTOK_=PMrkB|rA2_4CvPr1%)QSnp~3xDQ$OR#cclCk?{$>gQ!TDtc? z*M5_olY4eyTi@-UEz(sJmwsfNyJgXl;;k~9UUMZGKRZyV_s-X1<*ha5vp84|+oVgV zuQ8czvn===e_Kty#Y6UM*D|$_N@QrA-1%2AcWGroCTF<&Gd9!t3m&reht9j)EA~2b za`UD~cM6W=*(GT#uP{HF7{IY9=SV?1_iW$h$Ft`Ps=W8P|43l(#jP(VShGm-pFI>P z6Lsa$#&s$BpLteyMP!+k9Nc`F*G&1iiS(mb(XyYLEv`w$e!0`w-nzs2mT39Rn=Na) zC+LY?zrQ(WsZ4y_vWY4_-8v>6UI8Jjt2#2wqKZ4eOV8D zf zoXo=o>WmL1QkkcOl~n0YYD==|-O#ESAzSfCMK#y;;Kp;atrD`nN!<;-P`jicXi>E6 zv*g2Zk9OYaOPo>Yol^AR$BRF;XI{^U(E8TZ<^7k@ zyO-^|`Ry%x6YS4Nc+K8%D&VPP;p)?IaUY*X-B3vA<1|)@0on@+0#2E2bwmy=G@zby>AS z&p{|UVpCw_gjtD}j~-psE;*+CLn!RJ*v4y<&1UteDIZ%bnEJ9nV?*eStdt`uF&A=8 zDxW{@XAPPm+Vf34r1C_l-X|v6<7ea}o$lO9u{jYC*zhuSuk6s0v5^={%RHMVI1r3>^QknTKY-y>5E@eLsUE`saZ<8 z&YPIddhYasrQ02UO-~o)oOS&EM~yhIu1gd8y?mY(q|Vq;BD1k1XX=j1M$h`L&kHNB z1hM&AGp%gmUAKBCUl3PLszB7GwJf#kq9SmR+Bp?0eMqEhbNU)t`pP zO3Lr=yLg@qIlN3>dC7wXCNUB`FMNXM*;IA8WXutWk?PxNtNY`|$;UPqXQsQlOxd6*%7I?*Fhl3*_P$=YUk~K2YrsmTQ&!DnfNyUXK2b>+}Oc1 zB~QR&;f!aj+;drPtu6boR^Y*BreF~PcMVO?M{Ge$+fV2?O-xrkCb8t&oqKnm-n#nw z&b=GUFTc1nH(P0`+v^#tOl01?O`dbaO7so`<8#%YJ0ARKK5kj{$)eTp$>gq+3Wr4= z^eCS%zEswI?-tYU?cZnic}vZ0vFLN^&ht^2)iv|=d1m% zUMH@5a=D3Y)8=Sr${wnpVlRlR@z=F_zLhKro{ z@7{ktn_XEde7&%`;B!8=7-?QlY2I0Z6PCwF@>M)d=+}Rr|M6Evv(5cuy8_PG%7wd? z3eF8Qbw2p*UeIQySNmtN23`3!H(>U?E8;6Bl=GQvJv^zrJu1;qxuBwXbLb1R*P8N* zd}j^bMMz)McbK~|Z|};JGLCGISf@yEX(fsF{9abQJ7#~CXg<&5O$j#hCUCuw=gIWp zT2sie@aFCN%g-1;+vWRVnum5xXo7b4)_IGJD%LZW%1l2U!(-{_iQ_A z|NHs=gYNdY{sgv#U3Dw2zITmg&JFMU2HeQm7pUlWAHP`qe)h~%&Glr|CD#f^C8eaLJLbu6xIh0x$SsvSL?`qm^mj7|BhHn9$A|&z6e;Vz^9ttbSpRDhVu%Om@{e-PadsU+uLORzo+LV=b1Q%>*BLY z%i|_X8g3DqH-~GzN#GjI-?e7z?k-rIT$C-@cU+~y?XTeqO`*;p!^t{}>o=VLo9-7p zb5hvi=JiE6nHxBkrLx5}pTD@#rQ+-n$pY22CQ17gZ{C>k$^ZY&{ZH5bdBv!|uc+^N zTm|!E6+L|iudYnVgy*M}OAj5l|7clO#upJA`Ru&?cb(+PN*s)ig(90b>=3x|HTBp^ z&?ewXhIgzTJR^5+U}f=ty<(bvYf|tOw#ic)XHS+ikkc1+PW&Wif>E@Cft?iS_ zjUGz>tX!wAZPMaqA{+)+KW)C}$TKa9X zDp{yKh%{yJA4>6qNUM#pi|NZ7`nIHctzpg3%-mq<&N-xWM8Mcbl{PyQ%wMJH2y3M&1_xuKHurtE}o1CMIJWcKQ~uT`nOhB zFMglb=9>vqo{Rhx$+cx~7Mwa!({MIHEqsqN0>tBQ4+(Pu-l*zc=sXW!I+6J>vFu ze_Zp+Yt~O-;(8zd z1NZ-J-G7Mx--mY3MKgXL?Rk6FciCe@4&Nmr{res#K5{F0u_GiUxJ8HQa6}ttfMT;^ z6Pr-)#t%=dnyZ}6XNL55mY(zaUzFTh@|LYoGb7_;$9tof^On9A z`|<3a_MTbqi;UiGT=Y!h3G=q)%yyTm1dhne-%&dEznWd|s_VM7v7i3F<=wYqabCRW!)ehvm+yV?1YPWbSlL zV{f0n&-FLmh<>tI{vX5SHw|g49XDT9@f6xH?PbY{B^rfhi}r6n zt#Q8c{cm|!&KLdS7S;c2H8{`LzP5g0@$h)vH}w;9A6~tFPr<7>aZ>SC=0$sw-KA`f zDxUV9B4ktX-_uVvR{ga{jL5rV+!AtPzJ5Q&_+N?6TCJ;>zyF;0=F>XA*}kuE-7>pK z`S!x-V>KBsZEGs?PZ+ZMuUXgrS>WL8(D~_l@o7KXl9pa{TKv-5|Gs3>)06r8{r1la zc<|$SIRB6G?dOd*&Rf>dA8#+8ld?4J_OzVPsO#(`4FGOM+{b=K;(u^jmIKjD<7*VM$-Dz0y%1wD$)!X_;9c#vNAhTWq27e~-G z3Etm#_G~=KzvsE;vP$`EZL7O{y;jMGW>0#U;*qMl_+QIw^L+N5k2{k^p0d35|2*%z z?aA)-`%XRGuK(dguz%6BeUtapzU0d2R6A51pI+UPe)HNhD%nlJ8M5;bUlw*4<({E|nlkHprVl;by@CMg=b ztt;2;O~)de1)r|U)a=-zD;9jWGlMriSY}d?5{sR_ zr2H4b2qT@^K!Z2^FXVY9giQ#w^t|)n#>L4M?`KRCUgA-B%ve@5;_Zv)!p9yP-fKH& z+9$k6L_=POdv3YPU9?SKrlC_K%l?We_sXe~2;Ml#d|{!$p5P}>oGhXGUx3{+jF;eo!s-<=A2#; z%PH<9g*|NS73rD*G69o(f91S?uPp1cOtP~pGTxf+Yh_Pu&xc7D6P#pR-}sch{N8R6 zoUm$6*lUhBcJ1VcD(4)hxW(L*{`7nPAEnc$yGx!~_ndAzxFI2Rqm#0PzxJZEe$Qtw z`;Uj5+q8Le|MtGJ?T2ru^W^t=E?ImtWJXZYzdvmC-)822em2`Ya{u?Zeg9wGe)BeW zlA7kB#xI-t-io!`n`<6QjS`jgK3%*<*4pinS6)uc;;45MZq6w+Q5F5)y!$AV?>^(5 zhaS64F6%Gcz3c2Y?Hy5XjdhIAod~VU>ioZD^U0%syZ&e8$wq%}6X{zyZ-Z|6OY8Gf zZIZ-zR9t5qIrKN|!PckSq&H4=U*WuHmdjOb!E7!r>qT3f=a{c>?pm}y@uSt9EVCz9 znEf@bY~8z>qj&C#r5oCV?%i};{oXCDJh?aQ9;fea?Sj|qA`4y7F(+f~kT zip1Mr9F*M=&(R?><+0}Fj~q)99%=KrxD`$aPLo`=#Uk*+>oXTKOAF6Um@XN0ROB+3 zqDrV;+Usv(mtTqm`%Ux{Sjsy`;KS|Y{HL?|_Wpr~3|9Ym zGkss@%A%mTGD>^S=*&;Q9j~a-&7>*&-L7C#-tMynB?l6WQYSrCSv#e2r^oWEi8tgY z^&aDhm%C@(&MGT^rSY10OnGpgrsuP^m>F>oa_d_|4o!S!W5_#elcTbCqR>VO(~9>_ zhrC`kJJ#*}sIiCjsX$UwqSUV9m;4VIUml9PuuJQYwBm;shn;6VQlHktnyuQrSD!E>7VYu+apn2GW8Ulcu^KsP zv`#ZTHznmVW1NagCi8QxmJd^&Hk>wRTaww-XX;tX?icK(xOcgr>m4hJf|t_g8@{e^ zdARm)RQI&5Ju6l$V-1`YB+5E*+6xs&g=X=xDJ(^8#U5)j)n=`+PpjYWzxr-s-Ol3& z@0nezUNUjYGMAGIiXYDf&U#QX{fOYXuqTn{PW@8i{+t_E|IyWKeXMF%@+3E5P1f$? z&kU|Tjy<~Wbfve{68R$S{pWa#r?!R2j<-{lm{&R*DpMWWM$o@>7n$S8?$V zzpwUl4)O7Sdb8%9;+2T!GpnTkT->WJE%Ts@Gs?B*`_Ay2xBsbgNH&LfNVY#RkxkG! z;<@`@oy zG&rrY_9*)~znWz#wzQ_tmee>tZWrXXnQsus(dU-2P080bg}=L-0S>`>%B40LA{V43+~QWHMYv% zcuvVxFwS}*|8xHSC7T#0neF3fcUZ}A>W9FJ$1DFHeP6Zp{e(MNKWe81H>~A8b z*(@JTHPK5FUeX`;|GoC!aJxbL^E?-qpyW@QmtS&-mS2c^Y~aB&Ip@UI>}VPLO)XO# z9iP6I$X|NsyRf_9`k0;PjMvX~*X-<4nYz)@OkMPH;f9E*fi0R`lia$dY!+B{z|^Qx z)T2UZR-sFMdjCt4<>vPOqsV(+_P!Z3p0h^^M-~7{MtQn->wDbofn)wP0;>XtrMiQQ$cj!)>E9e z$2VnN7U3)|iCECSO2t?5Vvk91_6o1VHH$gr)Q(NqIVs_F!l8St8_p@E6=4*r;RaFyH?% zwr1wT(t=Z5_YO}oxoR?d)+&?4|C$FQo4;Rpair(CqOMk^TgfclM;eYEj*-(i-f#44 zoxfE?M}Pf;Rjhqaz1PUw9?LLzaA~PFhrZ_}wM~6rjh7}{X-e?AFNq3V#u>P%SvVo} z=e_Ooj?S9%INfidh{`w5#4{Zv@!;x*^O!=g1eUzPKiyjrB=G~vX>uh-=>)#J>NXsxm)?(9`32LOxcZA-IwQ>*g1VmvhUPrpHwEF*bto(>p51H zYt){eIBcvM$-ll-rDMwS6U7&rj?4Lf@cT6H`@Y$;w{I^zYO+ExOT{iHmSlGMnM8SSxwhxeq18NmWtK%rvTIqhwLj~q=2off zxnJ$qJw?!RdP(!ocZPj0E1!ilFY|~@>Fdi@)OK_~o}nr+T~u&_i{P( z4%qn2*56gea@)B-kA$99bgVA&FunFm;Df5nTQ{d$Q!Io}v;_UQ_1i(voaM63ocm$h z9hYBp%I;O4a9!a^tcGr(Mc*3v)Q=vsRj%pi=xy9uIs0{Z{zMhtNh&Ayw+c_GJX$el zvj1|EM>94}T(D)q-K?Sw*Ps8Is%;`Ic>MevC-+Hi#;GPVHtiRlqphIqRJQ45@{R>O_uK#AY*}UI5 zEKTKJ+x69}3d~CG&6`lWI<&=st+Lx#L#bs&fyJ$$%`ZeIeqHlibottaPm5-`e#o4k zakkIrM!$&p)?{ZdH5N5pEgOZUA{C-e;aTb~d$wo1biA_fww_4w%0(ZF6-z!GYdid| z)uGBg=D4s__NzY`{Be^jyqBCh{8{n@Ps_?3dMuYXw{Td+tQA(8+QixVWQLdJ-8Y4D zj)tZuG!9kmcz!~u{-^$5o9o+uy?MhQcu&aHuyAwa{rB^f^fguqP2F-Kuu1sfw$Gd6 z|Crsc|C}@D`y_GLG)*d$1Jms$zd~8R>>%;xW-+n zVJWbS*d4dKW@4H0vzPKU`n_@O`St&d%AzjDzIwRSovU+|&PoxN!b8gaYd^13mwWv5 z`u!)b-s<*zd6Red*sVEBzpC~a_Ox40PHc23Qt`YgKJWL-Ps&W&Y7Tb2c@X{mf@)Ia zwig$azRj94#l*`|vuFDfyCl2J0E^SDiSzUG@;A=+_b{;FJU1g?#_LBfdmO!F&nT>6 zj20AXJFGL2C4$y(+SGT|*QLlj2$jEa;96u4{}YvmvKtxJid?&OK&^~#j*?JQgqZ2_ zm3*~|GYUL3nHFDbR1+xJ($`rc^~iN^vfq(Aw_iN^GNC9-BX>u}^J?CIpY;E$cv>C5 z9iFdjkg?Kpw(f<8*&>miabhc$bY3ew%fI%=weN8czj|L^>9Zr{wIKgW7CG;E?7cfD zG@Yu5Nu3ZSl<8?GyKu^)i*Fh}Oj+g-ldxUa|*o)udnfLX;Gh7HAngToF_l? zZI6FRa4KKeej!m`vsd|>*iw<@?<|k~zW-b0dS2d#TSvR6+$-F$`EW_ifkUT+oV0)a zX1=snQ~I&y((8M?^(H;u`KHWr;=0RU4D^GhxwOydxZ!T%I(da)`=VDeYb}mi-O$(8 z<7MAtcf)-~(~^7X*1{(?1+AR*Lc24>Ao$}lmtP-#aUP1b(pTE$H$md`$};t$4{EB}-569%|=XqcM-ccGd|0sj12JckO6{nb5j#vpg?dn;0^KH^*!J{b-oA2@(B}KEH z>uF!4k>zkLSh9aollSxcDYdzs<#vB9{x%D)x83inBXTyZs3X`T`cha|+VZoH-`A?U z_pkYUS3EB1^8lFNg~6{ObB{hxQg-!O-gO~v!gID%(Ya~tOOs;+T_&B2%JXs0xxv|= z>AwHNneE{-{G~aPXNraXP-LFFqjQST{9eyjP4=&Y`d6^2uUW3nqVAR)wpP{1(nV7# z>tTwYrfB7ooiB{msd4_@a#Uu#y&|uFRkYrGFmQW z=wB2w>Dr{xX_cEY<#Ux2G1FY&R=~vInL>(9S9DoSGGgBT2(#>V z+H@hO#bg=RH+Sb{p$C&LIfVDEy~~(7PcX7w)9caC8GkQMx&Q0(`Qn$~%vPQhFgua< zD9j*Gg8%qJ=F&+Pmt5|$IYrIU^ela0R{T03sxkN>gSN*j2HE$q35wB;+D@Osl}tY0 zt!(KqxYokyqZyK75Gq;I*J541czN0n;9RMuw&=ZJBMr@*ZECLy`#tf zWAEN#ohjQ_v?vr?PBMI(CmbBMX6me;N{-c@p&=E@8B299E$P&BoMCV@&;R(Iz#fam zI^Fj!`Bdqg-G1@jL{Tr@ zc~)TR;+yX#mRMW7m*91CluUG;AiGVvBjUlTCJ)W1vn>b0IVR6o$rBgx$RsswTgvK$ zI}dx0?qup+bbFq1rpd}vLW+lk_+^s4@0~w)Pi+5ZdHb{F`@T!1PBXloy|FB*_uwWSqAS--A}WvVShB}!PLW6?Q^o8MafF7JhW=kDe;Kp?EMw)OH5CB zui^EZa!o2o@?y5x#Eh8C8?yWdEvGG>^3fu>I9)zmY{!}aaY3zImWPwK z-Z+(xi`PbE1dZF_;t&lYZw2yZU2L3_I9%aQ)PBO*YNU83ZJ-TmQ2of z!9yE2TvEKw`FM>}MX!)?lQl*&ByZfo|Dn=FfUQx|2L@4l07o4uPo$4`BYjOEFr+~JyMCrsAn*4grW$HtAl zZ`5|2R7#{W_8Qk}@8dh` z{^eR_b#Be{%s>Bealei0Q=Z%6dWCCtdOhTJt8|Q$d=k^%BqzPSEMj(ia?`=5mdpF* zvM{<&3ky8(?d{nKYN{rlQ#Q=!-?5>7Zu1%yEtdYd76(s$6~1G4%6Kh%S)tQlsp{;MFUOU8N;4EC{f*4l-E*;MH&;ExdL!JW>sw9x zQYH0Iny!Ht-yAuzQOQVbrO3p-nSAOBJjI`7Q|2TUH)@|wc%u0F0i$%2){+nn&TU$U z)ec>sqN(XIEBBMmYT2_qyZX+4JIDH5Uy_66kh75cnX~5u#oGSGJ;)gToj<~A*Ri*2ZNGgti;Rzb@Z*rp?%ltAl-h+@FZZ54vwq!$ z>~cY+9WriOhkA>|X5~gXZ3_N>K)x%Jz|m;X1YxOOD@q<;iDe*4f>qyj1`vf`h&um51lq0Qp)&n{ifme3VE%i>;8|GHo8 zNo3z7A$1$J;|hFj`bW2<7)S+%h_0HJn|tTRf>T`$30H#Wh+c?zwaMWA(uGnfmwVi_ z6@5jov3i89R7(vD5R;r6*SuqD64wUTPSeM?o@ffJn8U7X64kc9TrOqrw+#-9ud1E? zWbkk%m+5V{itg>AQ7lVNM7xW)mprQy@jO^zvqZ<(YH#JFw64tuZH`(=_{v)4e!AeC z{_~UhKJUlR*s9stW9~~l>|w~ZD10PxI{UOqSI_)^H~Ke}eKWa#f6pQ5{eRUp<`yli zcUYyoq~yar(M7?alz(T>fnxDtx2M1gnF$ zH@v!aUw%{RTdU4#KeSSWQc^qpl&U`Gn)D?}eYzcemqSz4$@7w%jLfID816@~13 z3op0aJ7lwM)0>>TZkev-iJEuRLpdI8iZh%cGRLlX*4$>h^ZWm(1#z5 zHt)GR#Z4y*PW(E#_|Fypx+xC}PIbrsX4`&m*MYvi7r!@fO-(w+vTOAM`N_I{r>@0U zEH9iU{m$(2TUoZlv0{C55WJavoxeWa zPI&(Fr~E(BeHZ`p{eKbv$M{dek^1lD|IH@fw*K=``}~GAQaZAFf?c84Z>1Da`y}Qr%Rfkz@y~nZ6zgKMCiw~{o_a4>C|57-=r*h(Z zoBwM6r*94B`CVP}(%k;s{vYlC%Y$BPl;sy3O#g9}weGX`{inP0V;{Z$^TqvS_oSTP zVz$zk-<4W?yO3;=KEr!<%>_P)KY%p@yDLS=L@%*L(k9sC0Y}Bv` zYFcTtY*o`i_e|z~M~;|Ug*oLOYgeqAq`{S9k?%Vp)Ixkg{Ob+pCR@sLWS_b`^Swd~ zPgwi8wpi(=EjJ$=y{`9>|Nq7PkJ{~jnDpD%>APPO|FPG8kAvCCPLUZl3f6m%@`bOv z%c5NQY3}>9&yPaa)%<2Xl^?HSZQ)S<%;4Oj_nYVZyU7>-qJPKM(wX0G6@D{2UQ_VW zLcaQoY>fDI%N-wdj}=Yk_GS{*-=w7S^yun2yDgr$|NrN|`R?7v`~M&QKhfT{e%}7S z-uoSHz5Xvnv((Ij^ z9(_D6Ci(vT!^LVLx%+?t|1tlMnf%WKmu5?MTk_--*N1bp{ zzQ$T>yQIe?vszX!_w$oqm)Fd*IjDVopVO}LR&%T0J&P-Yr0&>jRy@`|U+}Ei{-nRn zPs4v#wfCRh|MRW=gMWVQ^&h_fkN)@BJnqTYxwCCH>~OO?d(+S6_4j!yncFl^ajgxW zc3dWX}oSf>oT|17hY?pgxU-LThR$+7fmlM{N z|90zdE_iD8{pxy9KOZT@*jMi*UKi|Y z=fv!=64g1u;+zz3@GVg5kfe90NW}AZGYWSmWLAH=5||ger)8^XgtwHt%S5HG^B(@| z@VuA)_lA+zG?CPfIrIA1o-8&@jV#!*z0V}LYmLI`;t-j|OOHu)hbW0eJvK}YUF{H) z`{appP0rb9fuMv{Lb68_=cdLu_qm5GK&YCFq06M5OMf;z>U3!PV)r3t)>*Oo zKQGr8zq}+S|L4HuJ?~d;PTBdecl$b)&61VRE8mt`<=ngV=)|i+IVUxZu4aS8+H|q2luI4TO^czlvuyNbmHy9 z%v$N0<-hQ`dc8A92Urmlj$o+wvjhu}r#_*6RsQlidtmXFlAc z(3e!QMAOULtWe{9rYX~62?fs}p``_Gily9ZgC}ii59-=7`NF*HOR*W7s+M@$t!-AD zq84Pkf=ep<^cvY%Mfs;53Z79Su3_&(WeX<-%zW@?nTy2=KaMGU``x)#E#g?0Yn5_n zx8(1Ta~!3suI+B`58t?KP0^Z<8fycb5?6g_&{+D)@7m=IlN}|=v$N|zNY?#(z;03a z%4~btH;1s64NT|HM&Di|6aDnrS-br|_t!S%*L<%0^_TJRii5{KG-zD&kd)$7U!ddl zXrjAJ#HJ$~ZbUr$ogceE@M}W3!|z+FVtyF~f=jpiXml)LSC!~HKIQ0?{ok|er=I`+ z^SQ;QJzZ}~CGOpN*lFDV!TEii{{5ei?>}znT5|hX$AtZRZ}@8FcJ>7?X}vyg?}0hn zXPDpq(#^U3cQW6%{Rc|QTl84-pIb^FX1r{azVCYS43!$)+s|FDraXLM_$H=XOUP%h zLjSCYLnel^rbQ@9K2!ah$$2j@#Ih>0@%Qy>25J(rOEb?js~?~8zGBMLsiEiM_a9pN zwswzKMrpN3>(QS=$+|&WlQekuOjx9FjWck`$~nttSj;Ibkv^EItQHhX(n#eQwGk zSU2_PCGK09&6_?%tnQqwXXKY*wtI!g#W~pz_NH&Vn4G%v@V$48fhx{-R=qboQdsg* zg4^5HtvdOU#z9NLiU5{mwY5rK!O=yh12!{R>Sw%bo$_R|j)q7{i#SvC>!-~(<8pU7 ze=rRwR45k@jC+InO4}c`~Ao^%FMl zw@+Jl{n^*+`5*rOnP1nOxBhU3L4tPXCR-_iqk<CV2-joNUX!QK zo`u>~yt%ujZ9{p%%&lQcy-AY!{X&pYv%2pe)nWc_4V+IfO8baN}in&q-72|IXfjdjJ2u{t6*73bJosmRUD4 zOca+0ezbPkRV%N^L#=CV^WNQa=&0@b*zxD+XO6Xp?|Ex35sy$5^je(KB)}IuEkt#N zirU*_Gahzmt)38Az`T0T(u=bXFV>j0Y=+I@XRL3Jn#~Opj-D#wIzi;2&Dt#o57nk$ z2=tjJDxn<^*i$q?&G1<0jZDYPLo8w);e4vfCAsSioQ0MIxdgUsea9ys-!*IJN{=Yc z)Ifu?Mvo#Ex2j$e(lptp*3Kar@uPgpdakvs#ooMH*+0L8@AqCKnz`In#Cng(3yY-m z^yxJ=58dnku;09UGc`F`xFz|8m)cU*zQqcMC$adaFFc{;zxZZS_II`KdbZ~doiuBH z$+kMr>3u}E_GDkSxqNe3I|~g1)fa7^Q1tg3Z{2Tx`*X_a@(;rQ-HKm!hRg2XP5+6y zW;-7I&Z&9pFR{93U56xZ*~>Hf#jj56m3h-&@$-?X-M4k>Gkun=S-bvnXw&MywQB25 zhGuj5XZ;o}3&FiOcy=oeE?fTBDk3z1If#=Q_ zuerW-|K5|^^Y6NCjZ*1loB#i^|Ht`uKQotHd2+1Dpu}2slE$I%(6{cqo}0RYG^gji zFD`wrb}Z*%7w64?w;hAuG%NP5T5G&FL}TWnB?o3G-T1EMTfDFFw9%=HA`3;GR!m5^ zktcXmQ`!1pie#OUYVL8JZPghp&5rgS?R(V2x`fsJ7c9Q@+-iM*732GtrAjt!h10If z-L{%=V2^9JMuB%$UHJzC&feD9FDD7M3-1z`m1cf4>hOyMfh1i+mtAGKALU|Nr@ZQW ztFujQk5jqcvZAi{`#!Oq-&fV=w%T=4#j;b2l#XWDq@@>6=CAvq`Ky{eCqMUbZ~7B1 zzAmAqi#JI3iyY%vw8eMo+YMW5Coh+;kl;F4@I3e0b>(9-A0IoXS63+4eCWumpUSa8 z4}YGyzVAq}e$D*&KRf3?a(-VsEB#(o@B4fEd@2_mUmo{MD|&KmhwipnMz`)id$RKP zi?U}s%RX~0Uu^ik=7Wy;x}1Vv|1SHeJAW%W`|pU5u1sR3uBxBu5{e!6s^S!nBS8^~beG9n)uDU$?pPubStT zg)>6dtkt=kQ65luYSvoQY3CNRCazDCQ5O-p_DTJI#k7Y7NBC`P|QaW1C!MRmbr*GNiPqV(_dLJ1 z_)qix|K**R*<3@5eqIS&WRY5zIj`;8pL5Umowyo!Y`56%Ekb9j@BK8mExr4oS-8r< zpS;BnSUBYSKn3X49<(&-D#-my}EeD zo+T%^7HOz;F)fbw5Y=fdY@a;otEiXeB98@1t!s78J=IWo8#jB?nLxi!w@yFzyr&ou z`#P}d@VB!0&5oWj+b4Pnz2w-Er1#SB*6AIkf7O29Wq$N{lKeYa)yvbKA8klCUzcEN z^Hx@itLj%JXZ(+2;g#qKI`b5EB^S%m91Iv?o!ph^}3(Vrtdo~zwd+5rV|>=lI8aOH~ITkw?JrN z(w+d0;A=rUBDeHAbg0F-&9T~eV)FZ4;;Q!=yIc%niprff%;6M@oZB+BqmVC~tA$rf z?ru&}P3vjzM=A~Y@)QRS##Z{C1U6<$T%XhmhGCvuiwh& z_SQ{QzgKBreA-t>a(lVENZ6!hvWu(=gIjIi|CeXITYaRW$D+P>uDL$@jDp?gE1sIg zmv!AuI(4qV;eEmh>n`>Q6PK;g$re8TlXXUnw~Cp|G9a3{K6A%CC_hJaGz=t5j>?S6(x9l z(%a_PF0JJow|I!EPMP@h=j!P*o`_yPwR!V?=SVRQX~hjW4<*XNC3tw}|M_-0hu`Sj zz3NGM>5ST{bN!lEtvRvOA-hqVSC#9fqUby~mCh-PbWC?!S08lL%u|}Xc#W1#*Osdb zw7gH%a8)en`N;9Q>2r<&*Y{Znf`bLpMes!a!O zO_-vxbcs>lzhi41O1hPlWK~MNvtL_C7rwp27Iom$Euo`%ZgD)!+{OF4ueVgV3i_C~ zh)O*6yks$BZsZCh?n9TOBOR1?{`(fXWtxnAX-m$mi}n(1n`TT`tT^JB$+gMmaJ0XO z>lP{5CF__Cdia;{zI0Cf)})eqA#1xNFWZ-P;eG$+PH$=n)&3y3?9-FWP4{YMYfQVL zHDTqM3ZI(d_P@p9H?@!Zl>HKkzHPH!O6Ey$*>T|;Hy__Psl37?(8_%|Q{ii~zbl_7 z_uC!)*~@M7_k;D1&F^dH7MiK#sd}UfMkQ_P`5m?T&ky#xDW7L=-|^vAw9Z!XozE9m z|Gf3o`^)^BX?YuYGg?@mnqKk@TIwNu_TYx|304cXZfWq9p2B-jTiTo{F~}w7?fZ$o z>J_Um9x|TNxAvIQ;*WOm_T|kl_y3c<_wxhu8hKlQkXzPVs9m+gJ^&DRda%l@)2Z9cz5(|eK$pIi!?k0{T<+uWZ`X7lF7?(v%{I>(}z zWu-{C`TEu+7IU3spCzrDq;q=Xu|-ptUCj}`YWI7e+ae{|u08s!R+qNymi_gc-RE=D z`~6?Un7S`;^&bnJ^m507tDYXWG+VctunDu7uiL!8Quco4$3uSe=k84PNxeDS@1`_Q zg@db2uB=h7=6CKecGl_oa~|Jtey=i7*Kg}CnVw}ivAa5Qmi<`e)UEJx@^9g~FDK=n zomd%c^ZAZ(N_FtLc{}H?^!xc%zphu~n~AmToOFgDyhp)XeGrD?&`?E-A zQiNaSzqQ3{O2j3fsqU`6{Xq56+U$t@o$FYy-m$tpUGIbR|Bt~n4`lfZb81Cb2Db#2 zSn;Yw`iXibIo(=3Rq&Ff6xXc6o+HMyBG}lSFZ2q$e`%Wp01&(3uoVH;}|{x{^ZP%E+1LDzZF!j@ zwm)UcXUW_NhZi1Suyy*vHeKyzZ_krg&dBWDcKq5|+f`S&zC{SGzpU8R=5hMa%1Di= zcTX*4Ic;Wic!7vTpX;owfF_Gmt5$72$@%A9!``p5T8~}Drlncl$V}P8Yu+Eml*=(G zMA#+qSh~Xl3%yG9$@>kG;v|wvJiDE@m}!W3A4%-7m)W%Ue#-ls*9~|kUepPme8Jme za!cn+Ew^Whzx?a|JU1_4+^e$a;_KjG%U3J?tvYTP?P>{hxN$~cqLpVxh1}e~72tN|vd9e&O+jVP}jdpI_XjShvW?z;TK}dVk@o3!HKFk3-L`zx(LU$K4hs z6^(!I@;T>wq^_CKDC**+WZKr(`B74hQ^V6SZA`w|<1fPTsKI6Cjco^vj2ln5 z?e?_WXkZv{ct&$tf9}Pj_8CsIl3E0RH_Od4KO$3MvFs!>ck!Nm+m7{Z?-x;gI_Gf8 z$^!=P9v|gWW!1VkMQCz0-~5Ni*52Mz@|DRw?U_a8D~`SU+;?AH7@(VEQ5N<~*h)J4 zij;n*z{Dlu{2ROVCaO+IYg%kNFSWq*#m2JQ{$}glK2KCCk3U^-@raCmqJLlTn{Ni6 zg6~K^+4=eT`6P{2`?47k+9g*Gaxszkm4Ql9A9q=7%0X)bI*&~`_WRE7q_sYoYtB4-%WKsv zcic~R@ze+3&hqjoo_PA1~=%B9reA|!Jocvo9`hq^~ z6#7=uSGT|UedQ8QqfHwc6@88!>vr+OTAnMObltd=^!{9F$rI(Q$0~lTvP)+;?2KA?`_A%>`+PTsr4{XC>pb|) zi&?Z%#?HFi$fqpNY~qZJg&KZ36V9i*oC#6i@_P4=zj~Y7muZT8Rw?|k@2`*d!2_Qo z)=jPW^YZx0kWFXxdTL^}JBS>UdGkK_--l+O^F9yU1?N4V=%y(Bm2Z9AF5eg51(dR1 z9|?4tld@KNs)h<%r`N`q?n^FHO#;{4i(GCMmy%f7_x3Au)K|5iL5!KbHE*H>R(BZi z81-(t#NyeMo1z%<_j*V5`>CHMtGIfdym7=NF7H6Wg~R-ZzOOFUYD!w!5L(o6iQ#%# zf$!&9zZ3O^&eyqe&OcaO%wJ%u^?C2v?GZNBO^Fhl3ZFB~Rm)K9e8jT;sp9XhnU7>9 zbLvdh%2^Y)@SP{m)Pm~#$>(c67{7TdU-0&t>^qZkAyp(g%jH_39`M#A`zPO`aC^LKdx^*A!nyRE9q^39kr>&YG4^osA# z*rIMqq&W$>N-VP!Qcsz^R-Vn{a!OWOmqy2qKi5=?EN4Gf?=yPAf8*92wdtpWHAEaz zPMqp-~lIPCrN1PvX+JYYDYK4>L`ES z#~&>iXMbR$k?R7<*>}=)H||LIboI5qVOGrR$Cg=`1_rm z_mq~LwQGa5pPqQi^T_0gh|=t&fSI9MKEjI(mgIQ9+0$&Ky7JVoP1BM$Y%1#hv*G;z zuktmG)$jMWJ8eC6>sgxHΠ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

CEC$NJ@-`|qjE)=rEv+qH6G7S}#$fgOrl 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 literal 0 HcmV?d00001 diff --git a/docs/_static/ble-mesh-generic-onoff.png b/docs/_static/ble-mesh-generic-onoff.png new file mode 100644 index 0000000000000000000000000000000000000000..69008f43090bdd01bb17ea4b809d60e86832bf72 GIT binary patch literal 42920 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 YOd%TDH-}0xFfcH9y85}Sb4q9e0N)p4H~;_u literal 0 HcmV?d00001 diff --git a/docs/_static/ble-mesh-identify-provision.png b/docs/_static/ble-mesh-identify-provision.png new file mode 100644 index 0000000000000000000000000000000000000000..709bc06fe78f1bf3454438e1a96c8beb8a88c338 GIT binary patch literal 73312 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 z

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 YNl9u^a?=G)1_lNOPgg&ebxsLQ00Pl)hX4Qo literal 0 HcmV?d00001 diff --git a/docs/_static/ble-mesh-initial-config-fail.png b/docs/_static/ble-mesh-initial-config-fail.png new file mode 100644 index 0000000000000000000000000000000000000000..feb6c7f10b0ba149fd5ad22fd750733e86c9ec52 GIT binary patch literal 108974 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

    W}80YmzGw)Uk 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 literal 0 HcmV?d00001 diff --git a/docs/_static/ble-mesh-model-bind-appkey.png b/docs/_static/ble-mesh-model-bind-appkey.png new file mode 100644 index 0000000000000000000000000000000000000000..0a1bf994fea4b96287511881884f4de4c2e5be38 GIT binary patch literal 144581 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
    _O{{GSSHth5gfKRU0xa z_m!S&ea4LW;^LKCT9R${Ep0yR{h9MduI1sv89j+YUTGQqDiR(`zGx|$MaWvntP^H?sh<`wTHvCiV_fx#6;-s1XZrSD6OQh6p9GMbC~ zuG>=d(#K+r%K1~iCqEk*o_AJR`#i_u@CKV4yV=J!_uj3@eKq4~VOi1dCA`fUQR&$R zrgq>KHEylQ)7)jLIRMIj~e zpjnfXCq!>8&eA#_BC^R;LUq=)!c|$9yWC+OKb z54(3W9$YjD``((tcXVdx8}&)2E(rSOl+QbP#D;%k&$&NB?{R_t;AD?>Br~0ycxkuWUz@lU6muEzZD9k!} zL8veIP09K3bYhff6KACyS8Um{DdG#b9G$lB*|=8G`@YxfX@7FMk900IzUi3Led?B3+Qi#d zDPa@mM;>JqEBW&I{aM*a*(*_Bwy$iG=3A-!;6g__PukRHALHsX)?a2-nZ!KN*IDq| zxAq_NB}--EW}Pqgo%Yc@=zV}lYv)fnx$GAU>>fP(!}(Mr;$no{Y`2xScNW{aa>Ql7 zU2H1zT>7)x$JFB~QRRJ>s_fGF_SbOV8X=ejFg$WVQeHCo7H{1`>%y`}D6z zdTaQJtZwRO{GvK3EaGqnyU_U;d#+vc*ICQll%RF?b#Cnv-ewoK<+^hgSkGFJslhd4 z&DY(U-*-jK=arAVWIChvrw4an(T59SeavcmKdRjQsN!!@GC}IG>mRRd{auMuy{?HI z{Nf~k-n4Y{I(wg62Iap$@u?aAsf^Xuz5P_8PieOImzZFFx!JpXAM|_>%aiH(aL=K_ zl>-lY{q+2UUr%^7#+_P1U&Q`PqL*4TDAH}{*UYlLW=`qRpyP!Iii5i_+FJ_i-^ zzy9THsjHPdOQK^=qD9SX--Q+zb52&>HMuF(BXphj&8nwoZ`g=DEWc^++h}`paJF~F zww1HFk4>L^i5Ha5PHbY5%YBh>x;=rxTSZ=zY1U!GSmvNv~xb z%Z+cR_?XF8JWgnNti^qt=lvCtgRAB}*cRi;XuDD6ylH8%M_|LVf}HhETX`<4OgZ%- zFTUel#3$xZ?>AkCS{m5Hw*_Tas~o(-q|fANF*h;Wz%58(XZn@tC)##(=Q>qt%zW1S z?O)D`bqlPgB_y_oZnK|r?t-N61*xvf78x(2dk<=?QhL#$lEHcWW#`fvQ{E>UOiVlM zFyoBe>iI$^8&6+bs4;8N1xeoxD~ylT&#ErF(RAFvS1$X7i@DFIxn?|dkx|?~?K@9osB8P}6Q}N}Z!a;@ zdvI*C_`Jgpn@rwbWO@7WM_&4t2jc#3Zmzk1xHpwGY+_eNl>Fk9Pi2WYx8JjGJ8YNR zbK@RUrnnbh)SH`Hv#KZk$q#1X3ss$V@=Yw~SKGTLmD}BJ`3bSy?=Rf`yz6Fv!+QpO zM)h~^Qg$y|pb?h3VGhU1FfQ-(?_Dy>jy+E@n5nksu9)cUqUmmbawFdIK9}O%u5{v^ ze%g#1n|68}o_D3h?1{IMWyFc%8v!glM=tNKv!AUNx4YMC{p58SlM;+}=iiUho3i4j z>%50Q&4sl8S3ZB6D<73ro57n{@iD`H<08AwMv3RwRZIO){GjyU_F}%WV;=hL0u?!0 z6R$`-yS?vK^zHAu;+s|2CHX=X*GgA!*}lm+^n!?^&f52Pg-$ZA&VL)(*uK}m`n!(< zPd?vn_uQQAr!217_Wb;>I;pwm?mD+#h1?lSdTVdgRQkt%`n`%rroqeQl);U~<_vGH ztlqUZyzI&S+tSs~Iv!^pR?!W=qsngRd{}Ye?%BVb1U%DkSnLTsYj=vP_P5O(L7wL_ z??i8Ew8w7Uz5BJj$hQwaC6@g>_&S<>fy{IdJCAQ_dzDv8t9-S!tCf~7uFZa57BIy_ zWm2=~N)b;LdA*`OPZgn)jQu8jszT6l{Z5xn-+#+^sR*5{J8F_TW2cMK&l!rX(?D|w z3j{Up+)oBi2B?d%o!z$IU2&p^N_F4eMiT*5p_9iMdAFC>_d6<0^!O&TJ-xo)0mKr@ zl<-oS)X#ov-_#eApwkC$B!jL`@=%fIPjQrjTdOqD<8c6VCILiUvX}x@?x7;YiOT!W zdu-v!bDs~Cm*(&XvaE2bF(^IZv&Qz$8Gg&cyDYPn3RZH>5@0Wz_veH0L=P2qIrh_6 zWTx%DeR|fN=fA5PgiapsVJ>}F6Ob&ma((TE?>p3UXHR2#A0qmuOXBNJ--usxwT}FL z?ffaed)t2Y4~a?*QvI^imtBzZ6};Me$8fjOiqAJZrPa=E6gjrfhfmlpHu}KJ)VjKz zJKIzL1`7O6P@DB2fc0w}`%=H&h+_%aCa&>HpBuyXm#yZS9`d{}E>(`TZ0b9gw)SYY z%00C&n%`!`8~Qptc~|W=Z?e(y-tE5^9dWOz7dlzbe2Asr_uyKsB|MV@qPC}8WegCt zKfmlzkm#<-FKZ{yXIQ3c^w&(ZU8y65CrR>g^UnP_x9w(k1&barVw?D-FIW4(94G$I z-ZdFr57zrKZ*iH-@^-uGx>+hMDuPX4F*nZEY&Gg_8yX}h^MDwIe<*XGl`cyOf zm;O{R5n-INE9Xk}+YF}*Lf#jpd>8iHoNTdLKUZ2^#CZLIv$GajY+w6QVWIS#P{{|U zb}@CyO4;AOaX9#`v-OLmOd7>EC7y3wtl4y~V%D;Zt}Ti0zQ6Yrb0|b}5qW)!m%J zHGKPQ*Y;dK{ihr*X^&L_h|3JS_%KKgFjb5y z{bqH{*AFq)h9k&*{8s7!gpc*8NEF-)zqi` z`uTZ}l-FFdsASE!cYK%S{EM&m6y?dpyIeA;TvXfPe!K0&jK#lKe7mu8-@XYym)d=L ztL3aYclz$be-gdrb2n}Y*`o44^oHoc^epZlkB%fAtCrPp2r;kNs(xl}_U;=3xB81t zn^>wmcyiypH~FSR5BI%$UODn&b8J@~;uY@Nv2S08)^^^8ZxOTXO}~A<-FC0`nku7A z*Mn7st#@n|b`;97cQw3oX=9(w{^RE|Ikuo_J|$UA@6WG!uxVD!HMYkoKEiiRW`@6b z*k8Q5a3z;E`{QIQmbt4Aef`>c#{gusEGx&dQn`HoPk&2eOO7qQGfC#jv4sK?@~>+Y zW!$mZIOBS?awN0g0#3(HXXSO$8cAEc>!$SA>LjOBe7(V9{QLjexpOyfHrpg(ZCY0w zA^O?7#^$2jld1CzMD?VvUlIK<`|zrl-f0zk)%UFJ4b;A#^hM>Itgy11N>Tl;(1Wd^ zjF*<3yy`u>a;du0KHsfj-9E)%EVjRI7nIxkFgbay{WFX24~h>zc_JkL{oLw@-%Mry zq|};g-hJ?1w|R|B*Jjm8cYglZR=;m&>TlcMd@7Tedzso#t><6;?9eKP*G`;aJjYg+ z-28Fphf<%$mx7y%+_t#|C7=H<`;7Vax!X4_t8ca?&ibNiT%u~E@>Okzn-@3!u(Tje#9KB zS-zq9#I1+_&KTO=zq(x3V8U9aty%OFX2G-N=2jMqa1?q3-0aoyAp_ z(uX3Wa$lXo|#mK?eKrzV)+@a}^@ zr)>5aZsl3Otl;%5zDv6tTQ8sc=qhK{A~a`ng7(^vB5ip^ISbp3miNA2`})zk@4F0c z2Cm<|L#tn2KIq`Ij4tczWkTm9=0poV`24v00!vL{=xwve?e4#xee1Ev*ZH|cTekXl zy4u%me~&rl%JeO{mHYgf=F*tWTV{Gxy%Ei1?D=eEvFe2LJl}n5<)29_K?vR3nqc8T%T=PK)@b!0m>Do#0YfJ@wV*9m8f zwU=j~DJYpJzFaHflKkVl33D1^pFRnaFO{=4yu08?W~&I#G9#J57t1WCCFL40Huwiy zth?bo_xQPI1(TLVba}?Hzh@IJIdWMh_RY~{9AatCw(Y^&-)4p|meY+OwS&+m0OnbWnr$ zp-Qvn+#?&9vZ5Xe92cq7D8Kc>V@c(1eW&s}Rr^Bqq&I(#&EZkyyb;28x?I@j^`48; z3$-WSyKnE@neu&xnt15b;=ocl{gSpHce=!)k~8etAR`B^q(z+Ge57upsz4=*p zX7J-}lMQ`mE!I4A^Mhx*l15pU%VoCA%S+BZK5%4-;UuZOS$R&4&Wn%bD$mVT-V`gn zv8PduCDl)wccRMv8&{gcgEKpq7QHW8uC?OpjgMJccRNjFKW=nhE5e#LFX9a6(YU(X z7mmxmFEyHRY37^ag7*(iW8&kFNbePt%auKnf1Tq-$gjzgHnW_j-gYxcy&&Y>_w5O< z@`GE2t)LY2aGK-3UAyMKb7_;>-doML`q^bu);%|+AKqL0H)QAAp3gTtU#LktdA0>v ztrGD0YR!2g^jfRXmSe#zmw%NN-FvdaiGTUjpA}23&!kT?xjg+$K*seQ^>MtDyM))mjoi_ zvD`J-sM#d7ey+`IFMlIv_nhb#k&UrO*J|-BD_s>+Ce-FwY_fHK{mn_uGiusTJwGnK zkn6`M>s*_QW-MxdR+n@;XxiF0X=y4v!`BDX+esUh&a{O}4)-A15{{{+t@Nu8u z-oNsB+ecTMdv-^5B{VJW+G`1%u8G^Eid?SjHlb_Qj4Qs~m@)TxTDHl_ z>Vqs?2Bzmv3bM+DFuvzGTA6O(cgv(O_3Mcpc}vC4oGkww<=?V);*yvfxA<|)b$Tn$J^!a$n_ThhK@XRG`T2EAuH~vvt^H|tTkz58_ggN?c?Uk(@^SXz zRXa8q^-oDOni4AUMR4Ack9F+(t+sSc_S&PuE8MPBvDy3(uXa@Ct-Pi2fh_9NDg+NK z&UK!axFUizY2JIDkB@opmZ%%8V5%vbDlZoVY8@E(2>S|tQ8liq&Q-6^m056QW#Rif zQa)=08l$$$?)P12IqT7>rDt0%v)S7$X^Pq|+ps%5^waCJd)M$CtNd}o3Y3echi`qj zef1sN)swVeab578TySfF{nW%g*AI$^ZTWQmn&Z29p`P<%pDwv>c3JGQX7Q~Z<$1ie zCwZ3dxp?4A!{qkqfDt^D91lKD#p2=cDyS-^ovz4fk!`HhJpbz-7;O|9rhC%B)5B zhRDH-t#UpOcAgW6kG|`dZ`90iZY9r$-jz#zK1Js0$aZ`@TW%d;E0FBAZ}$E(tCw0% zQ@Xz{=;5Z1D_wnS@@fP(e~zh1D-?du`uIwvY)!eFrTX+=1zKFSD|zbgTsm&cTf6vk z>t@CI>C@O>bS(dQ_F3F7p-h3MQs=^d7cLx+?_2ToP8S2HUHNiW%|5H0E#cLN&YUYY zzW7>U#fxuDlYpucgvBWnB7}u7DT0Qx_|P1|C;1ljf179qdwdU-DkLUO82TyAF{Om zuH>n|b?dyo`S(c|c=s7@?b)pTXN_D}anQ#8Zal0$aB6}b^=qPS!K#`){_T=ug`HaM64e0qDB;r^0n>HM#*I}=^Cqn$bR z)}MX;I@s%`jHl7j=MU`fCN^cxZSPx?Tv*@Zzi+Ro?2k`Rg!1MFH92p5Wqho(`ThFW zkIZ>%Z=!r9tn`yo z@;p{8!;%x0b>RQL%g5$Rvu`u5@JV~VbH(9FlS^IXc>yE6{JGc4-^MhBroci>>6db(WyTxVu%eifxTMIa3 zCOubsz$56Bn$f*h>obN^+yU>UA>eD|=^;ES8VP%!@Nd0l^72m&`Mh9HxH}97X z^tMQTa8V@3>BISJj_cNGKX^X7YUkg(dGSx~9I?eE*Fr&KsQ{e=lBDxSC7y@{&ubk2f9l&FNK{aP6V{`GfImMLb@pffR)+ z9S&EYZgB7YmOF1Fc`h%xl>7L%v9`vB>^nOa^-jK@@gU!qxn`4A|7>+J<|gk|pLZ;= zn*YC~-Lm+g*uGDG)|_`w{LN`q+EJUOxqQm6A1B-7{AzNI|8HN|?my+%j>MXaeEajK zuL^j&N%%VNEw^Kp+!_7LcjlI|+uy(9>}9@l(Yl87*Tg^7gm+xNf9KFt?^!1$)c)tY zSsH(uGh+hx)W6}c6_)S2c_3fv*NX2}Agj+#RJpE`yG(J?67`7>yHtJulzHo~+VF`b zgfXGgXuVwcflrMR&-ZSfnmyfA_%g3dm&CN@j4sh>)@#%`6GQE+nn_k?wt)juuZyruTYh-0)}39unm3E* zBo@~9Tt4;ZjCR54Q_CkY%k?h{c)ihh#<>l4HdgNG&Kd`&hi@yoKh2KkaEjl)+4s+` zTy`k+@p9?iJJ)ZVpWI~w>R{|C6Wy(};^Pg=y=VOEjSll)FTX70U6`87_&kK=zy^_d z)z3nLA6@vdpZD&cog3#r?%oS3Kk{aGId8pXvGz=Rtl*wYvTBnqt~?=TvO$~4H*iOC zmF4uX=bvNJJFM@$USdA;VYRW=js->=?(Q+xEWf#;;N}e>-%C>dxyHL#_DJ^#`@Ht~ zVp*_~i*fd;iuKKxPyN|*MfTBk-n~0ay(?!vDemDrYNG2Ov-Y-Mw2rNnOf1K>cA+mS zYme&r3jW!d*>HBEisa!Z3!gFmyJ~f$>!1YxcAiOQKHJ=a{v-wao2^P(!XU0A-SP2U zz#QXKfBsa)>CRtsZuzO)^uYKj^82nI-SkhwZuY7E^w1*DQaS%^>_6Y_(kr?AuV5{7{SKXhg{AcV-Y5S>arQgMX#;7=2%q~v{ z4U}$Bobu%2HpN+sG(%h;W(Rv&EI)L9z4`Z|N7=J~-rJ?ur}-&j*R%~KyzNRJl4{rW za-Ust^#zr%AybdDtY=nyux;hiIf2ifKbO{xeck?gx8=qgr+XiFuQA(DBE4JS&eu5c z{rB#8A7k0Q;fpM@#i|R3b3sL%U(vnj=$}sxt=)3`a!qlRWcw4#a*ow9&o_!)H*cT3 zbBfZ2OWkJelNqk;eA|&D-&)o?wYl(I)P~me>t7yNtXZ>c8^iW(i(kdQ-nf_}qeI10 zdhugjGxs&yj#ta-yz2kdD!R7C$m)Ho(47Be%Kvj@pH#ozRk5Lxp&>r+-9uyVSug!^ zrrT#6YCb#d%YHGzA6)E$>o z20y=TF*+4ncIHia^s6fsn`}>nhj0I!@R{N6NgnlS6(4Vh*qF5nd++(8VzlR?@WTyE z;`irA`Wei9_^7pa&gKN}=tZ7+vx814R_gKigd}d=xx@aHc=*TI?`NHWxRqZD2I(gpg+lRDriH65rws%YxN*p#>`T2(B+B4^$_qkfC#0sW+uX>9tro4o_B0& ztS{Sz#+7yI&qIDh-VlFUAHG%4=ar6p2dJgHj6KHsN#75X)qkpVl5?K_6h6ZHdY|pA zCTCGg%fIcqwSOM$)eCVAcwhKol+amF^Qve1vv2Mh6@C z{&4^7;PU_N=>p!TVG~_3a7EGP+(*-~Qq3%2d!;z@AeK z(MpZMkd}+iAit7kK)nDC0E#V}cR)x}?Lm zeDZM@xXdztS&`e$`HQ*tu94{}PU@T<5cT}$I)TS~`Rqa`pP!X52h@sj*E#xXp~ZQ* z`17ryl{VMxrWnuIzjxc@>h0f>XDs_7CAUUK;ny_hzyOvdyu!w-U(}~ZNgfxg^qi@- zEc4UqvwPP_b`_^pHayGda=+d7!ZWR6qxPB7$6S^hk9apapZy#5|B}gpdaI@ts}4Oi z{Ryg@h1%VEZ%2K&CFJ`tqI$x-ls55QPBGK3Ze{wGn+qDh*?;>E_klY~{_i3=A8aXF zJ>kmU=--uFPhTqEv+C4R(@IPKM;>!leT~fO`E=&+{GB1E4MdrKO_HqJyR!YObK$N> znfoj@&M2DByWaQaLW#a?@m&7XhTB&Oc)eW1uX;j4ZDP+s>t@5%9DSLW1h)sJM=jF4 z#(%VU-dPzYv9InsOA?jiXGENtnOav@ZjU@~i)d}q_D-gObTr2aF0xcx8x z39~t$Y}eG{O{SA>8Kza+Co%Awd@D|Cov)q6ozc0>!0pZZptsu;8%hthJPi$Du|3JN zcB9DUKiA5*RQ-Anmb5Lq@xg1mrtGAHTvpEqmLUl@l-U#;iJY zq2tfdp3F%GJSK@p_x8IaNJg-!#rZBgl)7Ad;BF4f5?W0K#}bNFPJTQZrxmm9j#K=tJ7JS5 zE%wDvc5>UqeP@-Eleyi6R|l$lH$00zwn2%p?fF-c`IFCI6MqyS;x$J~zpS-loAQa0 z{lX2Wvp!kwz0%L=^U=C+71z}~ZSw$zg(PZ@m@ zX5H_M4qZ`nGoy2L3oQ(K{{D=hQ*&y>!*w##g0B6x zJo@}ezWWuW4WAlA5+!$-t!7>%+3fq!<(jl&#aeYAf2FgXs&VsP^Ax!(Y=7Du_V0(f zSIy0W+8=-CWSx1Fe|@3Gl^wZ}s+U!AXXNmxUf^{WE3@$Un8~%&PdZxZnb*R1^GdEjG)?0KO(a!S-L?Fdy>=UcN@ zlqD@pjw5lU{ZYPNo7r9)oj;to7$)ttt@oiJkA450rauy!7Mx05Dqza@d@JX{$8Vi& zZVGPsayanwF_k$}=O$PFyAzoxdoHKC{e@as+!mz^8&h^`=^Q`s&8^nL+Hd=+HwF>u z>vCmR2b}JRzNNOpji)@Eb+=C#?~xyWK7}2tmIc)tr*<*r%?W4;5B_nNDLKCidisehbu@`70;A!~g1&;+%=oBixSX2Un~!Zb_VR zB~|?S|K!Fg;GMNdQ@iFw_VcxJMOPivf6M{(-lG?J z*4@A0eriLH_w5hMzAjsOhxd6%oq#@@@^2Xv#e#PykH`dG$eR9lwwn5sUq61n@e1QP zwzJk(`|nDga$o)DYl{BMojaeGUHXWQX^|po5li-ex%A4E8E8O)| zQhhF;`hMFnQ1FY25zo>!7p~aMw#wq);x(Oj|FTPx{{6RRm-)Z+fAwXl(G)8auD4Rm zCw=k^*PmG!m=GQ*?Iqy#`u*##CO6+NIk9k)-i!xd*BPuYcL%kF?w?)xtf$CcefqDW z*#YXj%~KMimcE=U`R}ULiAn!%NEm6(P8adqw88$=oEr1zVb1~|Zu;1+TdQ)3?c17* zv(HsUuo|Siu(_Bgx>{nYdTGL*I#qd-WukTe)@G*833~NBxLf+B$gTaY)>e-+*@CPl z*YJ7CO1=GPVjpq8H*eM*)5UY%`)EstE?%fR?^${DucEk(52e_dt!GbHwv*OVzbxea zxm~x`a_8dNY74g{hAQ3rxJg!G-sG~<+mU8RZ#melleX5$F->uH=jXJzxQY7%!z!g2 zf(48bZB=D9$zS&x+opJ%xSleYDOm96np>3^YkSJ=JPvjDTW!_82bL98<}z~Leem!Z zqgl(N9FAi@{mW-O?~^{iGId`};^v;rPyN#zr50;WSyQ)q_J$M%F9+$B>A9=Z_HQ_U zYOCGmJN{1Q8U4#6FGw@GysNHXXMCjV;Ebn#3=SSSRT$1E5bc=su~D+!OmRn{lVG{8 z{^OhHCWzM8E~vf96!)6*v%_PC__;AguP)q5m@^~qLuZhDebyWGDQ66{XDjSclUQy! zD=FFHWT&cKpXRp>b|S}@YEIdmGb7?yy0?yNv`uSxaOGb0GuD3tTa;!5Cxn)Eh5m@=9XFWaZeaZ2P=0XbO6?=7=Q`Syo&D666JIHHI(bv`Tit|g6;F4YvlCh6j#a)f zX`9^h+1hf?0pDmH-((Zlr984z-|#LwDYC!Y{@mKcImge{M*sf)y`$qb8}~1zGx}c( z3rpqlzq@uUDN#Dfb2YM3xnWA;lU0ZQuB~j?Zx%K6xy`Jp%dZQ17l%wtU;dXPQBpTd zTJGPS8|M$7WmaH`^F5dz{IX}M<+2-_Pk-cEpgd1)&c;=}yFwgok^^3serWAHx<<0= z^Y>?sxxrBpT@N=Mlt`cU+j8@r^R>~x4=}VZ`*8A=P0mfHxVF4^bCpyUn9od1jGvNy zT(rnBRL=hHh2F#&E4m*1S!MIbZ0hzZ<3%9PXsdEyXzgqvvv(Vf>D;M$`WcH~< z+KN2yDFT(|%QCp`nnX(b6ujM(Cba9RrAGP97oI6a#~y!MQqr7m@uU0BbQx_+1`E>G1tjoYCqF4ST^p8U=N&j|iJRhSw8)@qCYgz{OBh|q8G zFZqf?V;R*xyuYS6D>?JPnWwhuC6n!QU#d^Nc(VM~47q}hRR(^pe&?siaMUOqTR6>> zXTSO4Iq6o;HJPE&>*jo5JX~;NDdYZ|cTTH&?U9*wc5!g))~APs-#Y58JM~!qbJk4> zzk;$#hS^Fx%CqecrnJ>>=PN7>l`HqtPcNI-9kjgn^65V>mUSwz-2G?*8W81A3;VEL z`BlX_V~@tz$8)+E_)WeR|DM%%(X4;Ns*Hb&Qzx9x`ds;EM%6UlBP&X7T5LUYye*)q zdi%G@rzGxNzj6Lgki7le(32Ni<-F!fiHAM?Sd~9x`mRGia!zd%XufOPHg{g~r;OJM z)2}}%emzC#SoK>A(W6>16Zh)OGmJ15dEVi~zu#g@S9x=xpT@VKjO#^Le-=kum_Eijz)=)5oU z9lNDd*q>efR2DzwahHwH2df|dcIC}l?CGcRant5#n+bJ&lb9RUB>q@_ID5tSTR;DF zg~h$NwLi`|JLW^e=LMlMtJVql3ff$kbP_En4BaO>Z{9V}S8DxRctLZ~`wX{=gmUZ> z%G6uI6Y#pM;OV7hv3axE7f7r1Z{aJfOO?AnC#)@2@Qa#p%#5`XHBT=+yQ^oT$#jiP z7&J7nL`Xh9(nHs=gpb}3%8tmwb0^ytB}si{=&G_hW9R~E7z)@In&U6I{Wj^ zi|EW=JSo)6t3n; zgT@p89jp7dVQK#U|1GNWa(ST-tuLS6QJG{|f8)~e@@r+!w#~gc|A;XgXpl6sPW=9y zuv3Aqa@X30LU!fM344}qu=KKwui=CfAC9lB1@fyEosW+~SpIKcIQy6fSHJJZ zLr;%;OcYG!@4V;ICcozWGydY$Kd&wfof9vquuNJe{>{=O3k#3m^1CP7)qnHeyi2ps ze7N0aS7)@k={NuShey{LG-u9D?zZvyWcf4tGK23$5q?wtBF9v@?f$x{UH3i9mCkA| zwR0$V7ai@EGuLWY`DK&Jea1&ZOItIT=Dp>KOnl2RFN^=v%UMk8R_iZ0vT~#0oD+G@ zNzV^$-+pQSrSISUBaE89=gK^@X^TAkVcOK(FKU+$^xd1^|7*6nPf=y%wCm?)tvP#9 z#XjQx*|ou5k>Xwn2|}kO?rd8&_g_leD2 zz0O-VXKDC_E6vr;6Fw=SBj2%6bIuWylj{z!pWjMJ|^`Ft*5&TpyTmWvJ~uf3<;#9Ax(?~cWR%+xx&z(dl=Ui>9jIZu%X2kjde|w1P>mr*AKM-ctlBYd_7Z@mUjk zy?#62$N$$NXY2o8u6@S*TFHm2nyP=EZO~dBQt9@bKl&HPvb8b`5~9?n-4v^uXSU}2 zqg_*jKi7nJF6EFZkFc08k;`xY>qCeNzYY(?Zl})owMVm z+qcNvY^BLlldXBPMVrKTJAu6Ye{RsB@@3PX2c_If%G|ecS##^(z)QPMhKFtcyx3U_ zv;-_~UWTZC^;-2q%RHDA*X)@aaO}iVRi6KBvleN_z2*D7a#qc}mmEUpUns2kb0mN5 z+XKrEEMN6`=bmK@%n_y%o9~J5tiE*o9(&NIjYk(p$sL}pCi3~WZtb0qdFkKo?)d*z zmqq7Yf6>oJYI(CF&RlGj<2zd%IZ zm$mI!zEIp}^0tauPniv~=bwL5o71ZjX(pgN|GjZCa~Ck<@2zkizS;ynG07hW4a*WS@^gsY@glMDVw#{ylwt>S@x0p>)n~X z%Z}Bw)#uA4Xsj(h{6ncv>ytvm_a;tI39Wv+`KE|+gt(W$xt!aJEmLQ#k?dMLdxFvN z4qnk|8>@~AHP2QPF;>6dyNqL-+oU@?w@t3!!jo1k$!d5v<>>^`i)KN2TaRASk?(n1 zbb1}T)bE#`X*kl%76a{&rK^KAV*-!d-han9N!y|4Ay= zm;GqMIQ!J6gEOY8@4eght($$3&2`Bm%Q)`6+wA0ZvZ~sC`<~YIewq(&DCxV4Rb})p z-SOJiTTc9j={?CV;~AH>-*$4@WH0ci_^DkzcUWJd;QjfL2R}7R{NKH6?mCyMH-8OV zQaM=5H!=QgZ}kohX#KiLW~IBvp{PbxO-}7? z`n%_k2A-Zb`P8%Z8BPYa z4^kACCMOA9_kA}n^kjM#_l}B0<^JuFv#oZUtTu4-GB1{1*mSjjQm%3GtlMVm&)-X) z=zMss<`)&CCA`AVJN6wsC9hL_tK&7>-jh6hM}Pf$v}>lYgx5Fg54S^{GUxtFQJcD6Zz3ra1&Nz1SwK-2Wep@IT z=2*NudEfgDZ)>HOo8Di}8WXZJC&g#-w2Us_hbd+y7p!~_YBk%PeSc)y-IB^=3&&#{ zv}F0;bKjWp_099!p|5PpswPX`N_rV1{df=GvL7-xL=GM+m~?h`hO=P6`@#p;M9kVJ zU$bjd6R|W+d%Bb3M#!v1n)kSm6&F{he~)-}X@=m2buW9CM@nz`#Mu6R{gsu0FE<@p z@N1Ko|JK`n6P=eDPLc{`topsJTSm(G(;ShTrl2kr?=eZAyzFR($2NR_%v#S*Ows#t zf3c>|2dj=0o^9_|-Du}L>esunPFgYMO`9_O%Tu`#+g{aDL!=V7or=j?HZkL4~5rZ-MePZ9=!EN{6 z%)RaH_rDh2U0^?RV-`~nhq{p3kGIq2O*!?-^W~az#@DQGuhhzDy146FhuNey&-W6! zN8IPEPy4zgac)M%9zEZMM;0bB-R(5-tTpefT_$=f`ppG_n*rZmaJ+x(5vx7#sx9kn zuA4nEYou4_F4xj1yLDlcsQ=;Hm2Jnb#NXtOJ-#K&T*T7k%}u9H=aoHQ=Lz3F_Ar1| z)lWLBOy;oMtGA`6-_}*_Jhl4c+t-OH_0mWEc$X`^@PEm&*;VTJr9{=={k7(NzB|^K z&9^_Z_TX~OFDk|^Mpmc%(x#}f?7w^IH2?Kd*&jazZr429ZI@oYeWUu@H`({AZ@-iK zR_igZyI$V5uXG_x{#Vx-@5(=|oK+LXcWh@yqH?TqvGli6ncdZ8)lzRu8>W1iRGP+ZKyFg#=p5$^c(l>@7B})m=ym{Y-ch!{BZK_ zzd8B0Py9>&{juj$!`sZn)v+HhCi%fIi` z&-MDo{{FVbpIc9Y_StWi@f9pNc3Fj|c)4__Hq*YJdFuRTU*GS)f8brxt?l;j|MC>q zZ~d6~GD*JdcJ<}s?S_5*kM60w@BS_tw##r%O0dPjIW^|K7e(^FyXrhH=+TckShTlZ zr;fMU=m`I-IsL2d+P>LsI1MylwquRj{Qq-foPx|N_t>8JpZh{CS7P7%!Z+`B+2(#v z{t!G#rlP{6m1(!?uQ{unrrzFndHy7=1(wU+-k*PKo<{j@FEzQ>zYOhf-!>~N`WGjj ze8Y9Wwe>!A&TG|H^Y|axeS2Yj>*nUWNB6$@cJs~S(nMx_=g!Z4^YGuXx-MBM&bv#rC!T9v@1-K2&9X+Wr}*zDv3GN$ zo85zVeBSGK?@jNUkc+3@cB)VGQ2E{Yzod0%VkLL|jA-Y5u}|}TnfDkA-2Tec_(jHB zWm2s}Wqf%0>G%K6Z%S(mvig2*?ZYQV_1|tt{8y3Z_E~dk;cO?*s7=-HPrUapx}fmj z?LChFoAGG*jNr(Ak@#$#LY6<)G)10~Q zJTcoh`4?!{MngsYF~ui5M_x}eHJ)+l=i@t4&!)c(_SpNx!FgYb3cI$`27_z`raR>o zyzb(a$L~FiRhsC*$Fhm-(bv<_CpNGA^YVjK+VkBLuUe}Jon$}x+ng_PN~6V;OFtja z$;#$;)ZCyVbh5Eaz>M{PuN*%clJm@mIad9XP^xj#Z><K2pPj;IoNL*dej{an^UtMre`a~bYaQ;6Rg6l0S-Xe3)8*4b zC6@f}t~(#BUCYyVBR;==AxE~iR(n+8q1jf#Vx{mio{o)Wvp{%9QYLgnn%s7Q(q;Z!qdDhV%D>at}pXi zp3b^yV$Lai42Kt8SXUSr+R*0F6<&2{ao^LM zA$qG$EjFF^=;*o+a@#ctYhNGbVu&t%7B-K2z7_YbyCt^AXcd|u;tOL#D-_Yz$j zIBT)yxm6#GFTQp#SDa)RA>H%&TtE%Gq_voxZ{n)^o;xoe^S=A=&c8Z7*MgwV{+W#G zlEU}ib#JIKXiJQ8d#&!Z$A^E1ha?+!)#5cWTekTXt>#jn{%gjg@<~^C@4e{VP^F=l z>vEFuAp3L6Gf9USgKwT+2pV{rV3<&A0jj!rAF9ml1U1fhA9rgMX6(5r4)QE(Xyu%s zXU+;1I`>=eFWY1{XMy6RB^MTcJlc7*=ew0gVFqY|p(XK?m;a2K-N#HHa}{WS!u))% zzS1niimmECjj@k+6}DDv;r|optul$9L->VfTG`&h5AH2Z57rdfZ}-{sT!SyR+ z%-2oGe#P~ne~PA>_4(;%8&>A*pHf@2vVU^d+C66^ z8P&tDi5lE}pAxrkw)8RaLf2SExzgQ+x$XYJpdo?WV6UB}MVeRhqVw+jESswDqmh)i zZ#L8JQ~$4s7ddUz-#+v2S>f)PbDuA|uzQ^Rdt|w1H{2)vFtCY{bPwSZ4?p^veA?QGfe>#JBm; zq86g5Z(eVb&v1Hhb&9N#oJ`jtL)W;jH&PrHI_G^4EZbx_M`58fXd}V$tWVorL*A%p zJ??Pg-|naRbXQ^PoRCMZvKy~|R`;rz6Zb6nWndW1^`p)^nqj6&2TwnSu*;y8&`1SV5>dutn;s z`pU%i!J;!OKQ7m;-Qp6gux8TN;J<$N0+eSQI2JHzx|Nf{n!gwF=7${XX~1S$D2w{n#JH)|t-JlsQ-5S8L&gb%MUjzwHipQkp39V@5>c zOLx(#2zjq3c5}oEf}UOJcyZIfNZ7}- zEO2+QSp4{E2p{K;ioM4#?R>N9!3ih+znZeP*7B?Y?~4tr&$lv!u|GCxnLHz+`t-Ro z#fDekFHxU)aIF^09Bwu1NG@GV~1q-n>#`SY6EgU>~F{PYj+*j{z$Mrdnhoq9V!E8pts{5WBoi`UnfmZ{YxlcM`JZ)1-FbilZr0t{YoKU$3+LOC27ark`(EfS- zx_Hsu9Zq_y&OJU;RByKZ=#iD0pgC;Q($+cgk`J^edr3Usn*6TUaH^>ZudtM|XWVNp}(bHL@IO)jpl|M|U zYE7Seu3}cew|&w|o}Y5G?0grVN?oiwO>LEQ!^vkJlbCk5c~4~uSNgd^z#(S4lWD)s zmxOTE83FGL10G$8koGZ%X?-}sMm&B-fXhT@<@@W39{*5!_GI@#6U${MPJjLC`opC4 zq#b+kAHOqeyz}E0wu5Hx13?=f=g$vtnHPIoJAF1o_J4Q98!~>EWV{PK=5Z?~q#Zcg zVz{ULJ=^0oulfZ~_N-W%*c2@IBUfwhNgj^Vk{9QKc0qi|;)?TKXg=|zmHYzpnF~)W ze6)Gxj!@O|*i$AiebY3`RutuXon$=d_}(&v1r%B>N%~F>7Reu;AFJCXl=&|I&GSR6 zZ1!1h?Rl2bb-h}7ja-+azznr3Rc~ZM-?$1I+PM3PQodgH zS!^83*IxaTQdYU|O6ub?MfU5zxk*?#aDDMmnZzs~dSIHzq^k9EEq~_4z2FIYyDj+X zwvSQ%Et9=#CDdeQy?@lfWWGP^#Ztpbk6M!S#lxO$w40JJ>;B1#_07A@zg?K>@%iK` zn`fds3>y}6+xmR7{Bd(x+^=9syXg26l27aY-Lu(oJ~^*@`Nu3SyV<9#oeQIqUdF_o zGq9{KsQ&BT5G%N(_-hJF=3$qUb$PP4mu+0itIcw49Y;==#8l^*W@Giwu)34O85aF)`nizhc{-w@HxsXH4mXTJ$=0uyLtKdg1W??e^# z>AxZ_h(CPz%<)Z^!u@560u~{yn@|1KdDs6b{I5;rWHl((|#U#Cz?l?Ey(KU z%_j5ue7OxpQ;!SDCLL#1iuO4sp*WjI*d+0!I^zpv9pAfW#2n6SOUcQ;vCHq2&Pm2Y zj`=dP)b`wyPP`f*BYK+IV#>9HEuqbZX04NbK3L8OfAH+rFQ<8LF8uH}OWbuW_xTMa zey50&r$KAqcOR_Ko0I)Cp=Vk|*J5tlIT3H3&Rw|UsUizg+0@mqxiUMKR&K36FT4S? zds(4FI_$<3!PdOEx17Z;jqTqmW__!$n*Q17>iZOLlbPHnqs#=JuHmwOY{U1;me0(6 z&D27l%Ur3a$`>by7HrULdfa6@L+#1!kUwUk%eB5)K22S6SKL$D<(pbhoLjw@ihOm5 z>xG#fmnA$4Qx>-GOKE$_D9*o?$H@A8YUS;aR;Bi1-mwp3mK@rBkmWO%t%$YBpIL{F z6)+ii&K5Wu@ocJlux4r28QlWod#@c5L=T?ap50=!vR3-ThecBof2vLXJ)t=7?x74v zb0gN8-&re_ony)^)SfPQ!tn5cIbY+sidkzHPMrFkCHRjY*Bh;sue{HzIC`OpHD%#w4{^vucv0(R=5R(A_yljE>n&zv?fkW|kV!xT(uWV%gWq zQwASzZ);tV#Z+?nmrd@B8}2PhH$@!PeuH+CzYvHpdhzz3#`Lh~6EFBC?K;>N&}8{j zPaq+!RP#57{>)gx8OuD?vX|W4#=UmSKckZpVe=k5D-d$!__9vm?MIWxHr4xBCtCiA z4}P#&^UBV*d#;M#y%=@7dA4tQ>f~w{j@{9sF2-z}hZW|ojj*cO7ah9kxR23In*swt zj+j+W{zjX>Kc3)W$HMn(jx*y96V~l_yFP!rY5Hc>f(r|aTw@t8Gx_DLYVr;~v3cc@ zQsKiUf=N3Hnmg_0T$mI4ZDsN8orO+Qe`=P{rLV4Sx-#G!H)iQG;9)Wh}&d6K9H8Hg2 zo55q{_P4M@p5YlQzu#AUN$owf>X`Z695EmX>hI zXz7=>X7(G^PqT<1kd`ON>({L`-L5A1-%6$%_Np;&Qh6m(Q?;`!{TZpmuGqyYd<)3a`E+Q zB^jZDqRU5wq!xpA;;xbD*|WYT%!Yk>UkVQYQ2G@t`RvK=pXsj^B%R%IJXGXG+cJgDN=P`^EZ)4s-s`}! zO-TxD971iKzUBr#6^m?7Y<I0;lK5KKq!Xe1KVw3WT|2^7IW(RwFN=z%)T_~Nx z(+rw+4vl5JKO?FuFX|%mcXPf8hPwPuzhy_OU7KOFZ0Z@AtGs;r1;NiZCWo^)YtFUL zd%4(pqN89zV5pq!Nt6Fsl2XoP9WnOGmHUj3lzwfksrAw{W3>{_6#E)I??k$lb4+mf zf!tuP0u3(Tiz27lk`fMGmUUdWO8bd)uy^6gFpamX4(^x|If zCP{M1N(KMn3*$T1na;x{qvbAArLig7#BK5#iJs?N2Saw|$b`N)$Y9n!*&_7dsx|E! zuBqi`kHdp6-U+(#$&IiF-aa`^y%qd4RJ-#M=vj;!P8 zl9j4G$>VwW$Xcx*IbmDa_NNW8q3(G?JNe67}#)KbG*mq+uoSeE+r9-QJAW3Ew{QDLI3VV^nUT7>(O zzacwvmTa2e-|8OxAzSP2$uAntB^5rBms7G;OiwkO*&cm-(k{X!w0U!O`Ss;4N;kv#svZkS#NK6HW6&{i z-8R{Oa)od2omjMS-R*sEV|z2798K!^T(0`P>^gtX-Dmq`CVPC-XWv`M@y6nLxi<5r z|JK(mpXq(sv~k_-$oS;mPQ!KguUFq$Tz&I+ve3!8M+^A0S-`KnO%`Mcb<&FaU?x1GQC?%0;=weJ({<&PM4x_o-5T=x0fx{4?@Xp6a#m^{y$QAk{7!OtaGtr)~b2 z5Nsd(*(PJwEZrHJtkYeTECn`x`+Vkw)#SHodb?#8a_}6k`0wkjB6PC7L4YYYZQpbs zHRZkSAS2uYMNL85nX@!jwXIt9qOC7;)+UdoE=raT zf*E@K?ow?aZSE4R$}%2yM&5RXG(ve zg^Z%QAmg^MFp255JMK?XN9Mr;E}}7vx@j1;7(r^DdmHKff_JNzFMrVg$g zimU&5Ld>k`$k??mdHe09msBTu@MV<5s?YR!^s(aG_W+2z2cMV8_S?P7PQK++6*}42 zn>mX^(SXM}Pfd3s_#m23iVHjr2PjRfkr8lOuJ?%mtsq^PB(Rj@Ng{?`pQHZ}M-v;mh`}gmjpU-Be7)dgO zgsST6_y77;m6etC?RSm0+T@y--tmi;E^TF(t7y1&>(<-)ea$zfoSbwgy=?c~ zS+k_{_Wvnbx98KT{v#)DiLMS`|LouI_v!EM?A&d_xA>xl`TZJWZZVw)$L;?a&i{EP z-R{fAyXE(5|NMDw|6JYvC+GC|I!lJNYuDbXc-*@ta&udL&131Bcg6E>uRrgv^m7A8 z`{5L$nTEE>v(59LmGA$3cgFD+0h#JI8|M_Cv;6%e^!AQIcH84$hf1^^e0FyB%}uGNXS99xxL5UB*Y4{||A!wdD!$!JpJ|+auH9-| zh0VF#?RVY6!^PL{|7Rtv=Hqa^wC?-v`ybEd*G;x8e)i$|{(q(WZLK>~%E5W!&-D$P zH+U+t)YjI1`S$Hy!5I_ZS+i!%FiPdRyxjl(YdhY<3wH0G{qJ8L$g04Q5SRLTd;NVs zltAt(*clU3`E)A#k>Z=N5ieg|U0wNd>2wJhnJ1vMw5VI}(94p#70KLe%_U`JLcMN` z9IZ|@zi;1vHZy(R!&R%-J=%0yZ}P(plbWRzkN9nu?mn87T$d-o1`2fH`9F?m^#7ih zG)wJe$t-7n+bPlcdqrIXZ)`|p-v8sM{*Kq{cF!=+mur`=`@wg;MxeOR|Mtzv#XN@} ztXw`%NvKnx)1@q1=AGyL-PXrVe0dH_Ox^e4MsokhzwhhkpE~7L_v`X}qq;vo_`lpd zxrN~*dnf2XtCRJn%Q6>bO0^{>NF1A?A92~lb7|1~)%@$Pp9Y25GT+&32j18JueSUD z^Ze&q+3TP7I3G61$jC@|dTOeQ5a%+#xu@1dZZ5EzJ7d|&-S78#*Vo&(%a%zrIyCsG z9bOf>y5socGjpxQ8&XeCn>c;Cdt98H_jEngxqihz9=01N9%{L>_h^!#n0}m3ak25` zWxluHp89QY-tM=~$BH?HkB@CEc<6L(UF_`F=95ouNIJ^J;N|Tdxv$3ZWXdGV;%5ex zpPtNN+*kEAYt`!2ooTaG=VfIjy}YzkWwPfo|M}+>+hv-TW$yX?Zg)v}xpDC`pHrt!G2GZys?E#G>odck z@z<{^r-cHir|U0Yxw3Q59-C+9=6aWwZVlO?!Y;Ec^HGV_$DcJWN)rPWUmxZITZzE}A?aq~?Bp5|U@^H10J|C_pO*|Ir>H!fW|q}*?FX!rYl-Nzp%{`>QD zPT8%@9e4A7{rdXuV8oUT!TA5bt_wPS00r^l zJ}a&AJ*!uDyUSI!h-!yD`2O4S@0ZJ#Pp1TTxG33tIKa#hQ+(F+$27RH7WdnEd3&Fhb7y$N+m<-tWQu^(g^L$KRf*mF{3}YQO|Qoso}}t6 zVV)=RuwVwL-dbPxbu}oczkL7x8I%+>gO?SYRGt2$=kWjU|NqH9pHs{?J*J4$dz#L} zbJp*d?Aj$Y{j{svbjlJq?{HCT)lb~6r!Mlxh(MI z*6VSLH*TB=%DZ2_efu=~{+~A2Zn45|H`AFnq@EVr_y6zv$7Q=YS(sL=@(KHdeF@O36vC?nwY%2y(|BGJYMe;&&V_qrMKG)L&TS;#zFxBK0rPW5>S|Ni{6 z`FKS5&)@g;_f!9`-}8y<{=aYA14F0E|9#i3{(ni zI<5Ek>-G5O%kBSa`uh6*`80jMid_Hu`SL4VjM%ys8Pxx)dG-2rha)ct%4 z@=#2{K~@kgQ+6X!zN2x)Dy`GgbRRG3)_b()^SQ@8#^(}lZc4pVbXr%Y^2tO7a1b4A zVy$_b9iM!#i4~L_KuG~qtmwq;v6%jT$7)Q<(ms0I{o``+#XcCc%RQun)skoeV&2L1xXd3w{_3V)ZG8; z>iQ11#dE6P?NpiU334|BsBVsrmtQ`=&MI#G^me%_4n>yw-?#5qJnl9B@veNow~koy z#YL|27uN?CZBViW^{4hvdiaRTZ~0_U$#&)1wW4n~(~IwxUVr*6-l;$eCN|?(KD>e^;UWV-q0T3Vy55#qPmin83|{8r z7&zr`REe|FO*z-V6X(yXOYm%}_?VQHm9=8++NB#d2=pIsKAZMQtImJ+VN)7=^vM-?>o}O%Dv;o zqV8`y)(iXFOym?+V=$=wRT5Y8(Upyz{pFiCDPLb*^_gwf+pV{&qp69>&*CA=tJkkV z?Qy@`(YwoZLESZXxk{DE>i%jIJq)t1>BQCjOqG(BF0PeqJNV}2=9#wDW(=SPe*M4C z^Fgh*i5@E7?>s+0-#;@`bLrBhF*P5L*8F`PpZ@gJ)HCz#>)ZFW3bZ&WhOLX~Jp3@> z>Z;H^f4|+HQ}^p-gJ|Z*Q`+kvlvu4?wJPc7r>8G=`Gtmxc8lvTTez_C=jZ3aVPVH! zuix(%A1@E;2gvoe2m9NK&OYlF7$`W=qXkqkx+opo`F!4Kb^D(uYfl+Y^>9h7=puq;!EgQem@BRi<8 zurj2p{_pGfJEhlSYySVf|NLI{`=u*aa=Hf2uq;k{w`P!uzkd43$;rjf->=(J?d;6Fe%~)GRaI3UUfz$_qVoeIB0RLF zK3cha-lH{}&z+iUeQCj`-+z7P+05L2zixJ8W8mhf%L!ijKs#hyxPLz3ia<1S1@7M2BrZR5VOP`&cecb-vNB$f$=}wm>P?b>q zZfARPzwI<{{k>BbE>z^=;yQ8u{P|dOnRj>Z-ksZBbR)XWHfLGpBoCDix5Ym5Y$~sO zv)?_r9n{!mW@bK;WLWoY^ZdkFYP;55zH{fyY5o0kjQO@N->_kVgMx#Gh|*lYe#v## zHT}DzE%to5bNcyb6P)=RISy{$|2Nm?@{{@h|ESmhJRQGc z)v8CA=l?S?zhCqD_rG_l|L;P2lHrRqxT3e^JiPz^pSinisfewu?VdlMPOpjIKkwz` z<-xJBXMcTt4eC?fb};Z24h>zoa^=Ex>-ZW#-njq&-*=tZT~iVaI_~BjH#r-4G_fRB zee%f%U#sjV_|9t;$e7i(?sau&!gKroKl$IjeXG%Bw5ozH;^DmB-d;=o7vH|m^-&8H zVGRv+Z!O}T?6>@6ic!X_ZKwBM1~)_QzOS2p=~B?X-*?{^U5_pQ_}u>g%s+qr+$jZh zA>3sOS$3^ktijc_=*b>N_4zeMt*xz~Rsc)0qnhumDcrrQSFU{6$S#*~agnQ~{wPeE+-kA1)5a7rwMertXRK(`Ocj)O|!2}@LCG$q}1)7fAHWz{x$Zl zfg5hWy^&+~rmR};c*W~$Yj5P3?TOPrm@py3WY+2b(h?F33l=O$m@;wu-`Y2v%Q7Do zSQJ>zWobSbtEbSrWdHv8>GNyFTmvI^m+68Acvd|2xBV(|J*N2VjK>_UPA-cBy}Z0G znB@96@7}*(K4E$F)mh6t6B8A;->cFNUgnc{cUS43r~35|K|>Vwzi*!3a5HB|jNYF* zd;9-C&#zp+{(NPIsB7Y>DVi%*ow}Jm|LkRd`?LA=zoT#6zP;KQrI| zllS|*YW>;f`Nz^WU%YVPfbI7?&e_?o*>~OiyumS5f-U>{y3~vej``=?4?j$Jc4p>` zWhb-O@11tLqUzL<%cF!%bnj5{Aul?}O4a;p0rfoJ%JtdN~Q6hi; z-?BWNxpU`Q?^P4({P1M5|D&?qJ0JI1?|441`p4Jx^=IGLeYe)z^TA02vgAcTJf$PfpEeM#Js5_g-4Odi{Fi?YDPI zF8j_YI;Cmz`HbbAA2a*PuZk&|r>C@fkw~P_Ib2-)0gk zx7dcNuUXH|&-bsav}{WRHFQ3MTEr(OtADQl|GnPk&xgYyp`jn2&CcKVenL{`lfRqx z78<>=t@(1%9aKfk^kH)ijM$XIslWG&(94ov%rDC;?8?i_FJHZS^w-zdpe8k=_9{ht z`}vFeth%mU3!7K*h;!0O6YAOc-RaU)ey_6K zMQP*l?{QI4r@p+r9JxJDR)7B=qn9PKOtY`O39{3V+R_11blKm2Zg_m{RM0RRE4SE( zOWyj0PbRu=0*ywjiJTnml9iFMVC~x86r+>D{s5y&K`RHnQ_`28M$(IcXogK zzb1U`$;(^geJ&qK+x+ljMS+FPox0y|&&)7v{{4Rcd{D)~!o=8T^U0&>paL&1@0oeF zvnxM86YO-!$jLb)Irq4Hy-jFns7}NNhV8e{YA&C{6cOsDzwbv<-maHwr%s;+wH0>0 z+x6Ojr+M16Y5YIMH7#%4Oh5nd!i9jm(hQZj_2%KX%!N8Z<39^CrJm2P|ECxK2b4S$ z7p=Z6HqTOP{HPGsg?V{qd8V_T_n_B-EhyA5h#NcCR@P`<-%JzKx#A@`h`PbYPvwsd^ISKSZFCcl1FndRT> zS+xq(S-ewlm^UOO1IyYCX$`Ey4vmW9F1ziv*dvBW7KU$I3+-7BYOG ze$<8Aw@+6-pX=`I%xr6GJE!oNr1e)G4;6XV!+J`#n6wt-C~L zg~u1J(mF0%F2is4LjhFxR#sN#+}%|gVt;s3){-+)KWAQk`Q+E@_31A!Ev@&2 z9kyn{rcINy*X>BUQVJ>%D<8IsbFejo#?U^Wv+iHqZ&!6Def!@qrO9f(k3d5!*W;>n z%kP$oZ%#Y===1#lHlTK&{hx>YmH&P|FSMGw=kqyh&}cm=G z%LV7+>bKLTP6cJ6qVu-j3*PN~4jLM{wA5QuPfyO6JxJ-M^cCy(dp@75-TC3|_WRHD z>wj{4PuEL5IZ0I~a#KqyxA-IE`b_(JySBuFS1Xr4o6?^cSMiY5Ece!t ztKsoy3y;g1zlw;9lbdapt0cl@n19b^Uj4tyNhei|)6YG*nLdB%;>FFI&)dzme!pjO zQxlVvlvKr!hwY%YPp37|MJy$a>@`L$*5s*88u{qy_&|7H93 z%>xzpJB7X&r=2X1Jd=yhUEaC-)$inc>rz>I6qKItTX%8ht5w0v zSEbLY@~i0cNlsS2c<~~rb_3PtEiEkzF29tpC{S=)EV%owUr>+`CX{jaDQRg& zWp8fe^;(qep8NOr_si?!?Lkv4xw=;xR;y}qup5EhQy_fCM zH*MO)!OeX*X`_Iw?Aa3&l_hPfOw4BYPMaq7rOMVg?aYIHzu#Rx#cQ%dH)2Brs3&Te ze2gKs@%M(r!yaB5mG2?Xf3LKQ%S=Zqyg%FIBcHSFWu2`E+{C zx0~spA=q2;6BRi2^UL)=_uLz2?>+75(E~H)SJfn}Y@h#YW4eh{ec3gy(9oA}-dwrH z)x7Tf;R80a-WTnRxpL*ogO3#*y}hbBI&U^yXTE7AEF`q0va|7_zpSk6ixR6GGwDex z4?q8W@#f8yHVf8QS8m+!C@wa>c<~|&E9=7oiwzMv?W)T+-hNwQBbQ@#Td?`@fvuG` za%%qbK=TZzr|Y{fzx?oHg@=#N6}!*-|NY7a&0~PZ`;5=qIG2{1n$7Orv17;Lb1O@% z=I)5mTN9SOeOKk)INg^%1qB<*_U+nbmHRI^BqU^e{o=5-QAe+=4Bq+im^7%*#Q*y) z#~tDBqfRj~clgy_3AV@|e*U>u^Yka-O*;BaP5vddeeKTA#pPcXI|Yi^$njV0{U@Hv z@U6n;md%l5(3t+5+HW^wO0R~3GTpJKMGG&#+*O)#r{?q7J>PC+>qKm5I5}DUanVi# zsovu{4BR@*EV2Ku$^4r6w7>#14)NpH>-CX)DmI$6{|acVyzRF55hH$K`|ZqyFbxvwx_@U|9jVs zjg0z!{VI<>`L*hABI7a@PZxK0VOiO=xzzv}t0wPudP9C}?WF z+)&H^_YNNymy){rbN2V`N0TfcRi_z9@a@)He9@z*=u_fvGc&VKH`C{Ty#N27_~MH< zaur`OL{97M?EHDtR=24@YqHAAYT=_NHvL&Fbn?%H(=pkP67><7|cFk{{8*^@ux)_3m>}$1qZ*pzu&%Y|NObu<(q1Mmx;J8^q+6X zBVpjclMf04T?>mD*W;>rT?6}etYJNHVWD&6ww#;m_jweDg^A6t`=yz-dFGrsG8=bs zwL35{G%$F&IEHN4tH8kkN{#+@Kc7^$C*LbL%=_nKfBm1U)z9_z{YYBoH@E7F;@_r% zJ%8TRD>8p}*=POSKt-ix$`p}xJ05X8J2P{!uHT$lvrdVI$2ewXy;@(@2pTE=y1xGD z;(j})Nh*8d^lhuZJ-HelpZej!L7nJrJ)qu>oNZOcT9d||#%_xzPSp-i+J1Xt>dcwO z>7On*^Dn&q`sTh`>+SdJtfRN*T~*WYpKmu8G=y~GgvY$&gXUy zTYlW!|JJ#k&o;@tWG8y!QG%O**Il zfP(I{{{ECRGYo4!om4k5F)4UHx7;ZGoD67aTS!*+EXcp#-rlZ!v+=luZIwy>zMpFA z_kNQ)+{XKO+wHu=@4sKZdDGL$i3v1+GaR9R*) z<_AqK1%!qkz5oATc~VjmXdHS%>P%2O2((Hf^YSvoloJ!;KbJcTusDKHOYn*Fe0qWPx97NR8(v%e0=QU=5&8ht(eEX!f&qC z)XnGZq(PIZljhGqKQ%n=V7nRuU!|0{{Pg-@kI@%AfaGM4J>iAZY)U^L%{VTwF8E^W(NZxbXhF zanTcxqT*sur}y`_x0jC<=HJ^B=^gRGyUE!#@WsW&?8VQ{D5|Kq%ufDsH)sANm5bM| zxz*O%zJ2>PrvC5OD20gV=6G34{=PoGS+izsnQ~;4PIl_aO+9hzK?SSW8eR`?@5kq?-#^*;eBR?fpUvg$OzJ8eF4{iL=J#Ev3)Sb}<5)zLpmt61P^8dVG#C+O6Z^mbQjT=)w78aEKDj0HvWK_#;pd+>rk)0k&~A-8CV%?*XT}X>Z=>c_K9dBMQ=ryv-ky(c zdHa5*-K%)a%O_`}(c}zsM&InodKObsXFhuLC@-^>`L)csdA8B7XYzb80F`$+_x8*L zWz5g#?d2POetvG8azX%q&^_USQ2oR5$9FW<9g zPX4~1;C}q=yxqRm|9&?eIS_DhdVHN^`{9ER4mMjBJYWC~TCuXSMyw4BTN~w?l%$lI znW_Hk8aE_eHGQ!Dwz>K6fjc{$uUm$QxW4ZE{qD{#hXr4PtFG)jonmB|aDd^}>(?t+ ztauSt77-zlw|(}KB`R#ZQZ6MWCJdLC`yYS*9h{|KE}y^X@=FP;5)DwVlC8P%#fuC! zUa2d&aw$bcM$PQ}@3zS$r=_Vev-7EFXmEH=+VOthZw_AGr}}MxoSTPcnP%_!^XYWu<6iTM zUoV$eKA&425IXhD8K1oUf4{wtT@VT?C_u%}hZsis8sCp(nIWf_Gx42KBhz#FxL1rJ%NgJZpR_u+lk>gL>yz_R`QdwD96Em|# zmtS^tb{@zNxtp3Ya=B6mJ826-+ndg>ZCUsT~6NK+-ui@#zhw` zUcB}CzTNv*t+JTwC*pcAYipKcsk=KnFE4LPTU*w_6^9=#Sh$eUHL#YcHxB{? zSRP(i1EnuNGbIZVmWL@y|34{#JPl59EtBVR@UeoKATdE1E@*r!aDY_9Bm@pFgk{pE z24oE`1sc$jr%6EpWDCd?M*$W_WRp5Tu7Dclz|!8NFh}W6oqgA$lH1Qd|Kz*b9yZ-9) z&$_SY861D;3W5Bxzk1cGPj`yXf4ozC{^94J4eJ~pempLp{`J*W^)+#z5!!2OB6q~B z6Rz#9yO2J=_Son1_WxJg+N_P(*wijxC$awe?cD0XK*7?|Qu|$V&Yko7_U>-?{rAiF z?wt#o_PD#dy!ivHEQE*20SefrEyV@+LM+gEm=im>?EnV+GrRXIQ_ z5jGx|JDj(jIkq?Z{hrT!pmD^xbLZ|~@#g92>7U#MoKmPdTw%9&189tvg~= zYbWowJoxv`r$w2+fBvnrvbNsJt}*}ob=`nZRePfrye`|lfBU`0O?%9676{CmH*TV47srSW~YFRQUhz_sR3;pFb)d?~|UcUVf(#lq-*aYvVfP=~BN)TY;nLL-vK&UsKZ3 zKK=Q89@N=Cboemi1QpJP(%08MzOVl;z5A|TRh1P;=f{s9Z+*0z-_LLVXTth@zqsa~ zKmOt2VbF9iXqBIQ?H56-xqX{98MX1ro&rTE7Z=x$>-+z;-ppCHWJ$}MIWq6|{m#>6 z__+==#GZ9k3$$kF(f8k=(K`(d4FwyUIWvvZgCZkOc8O|Nd^pI?Sn=heyQF=cjn&*f z^LrJ{%P)KS`SBU2pL`XquOcoBQJHuQAncxAw?dPZN)?nb_3CbnEtQ(17U6 z7cU-M4Ub>CdGqADzrPB9zujJ5%`qi)=HBY>MZe!}4~~yN-@+;U+7G-*Z&iaoW}opZh%NDXaHVRJ8VP#eLEv#V@c~Wo&3FD#k9lLFo4#@ zfL3ov@USs(adZDXZvXEfsD1hEX8O*T%Vr0JPPHt4_Tg6c`h}~szTYY4&&Xa!i{c^THKAqN=G|!9Ke*WvFOF`T6@1M(FzxUXOhleFC z3KX8ttJeGZ^XH@Y{uZg7^PC+8{5ZQ7eQ3A;)A->-0cgtqPu>1+A1+_GaNy0&&5~9n z8lYuZzP`RTzu#=WTXkGZOA9nPXnv=Fx%^(Gx_tc~!{}`}3)ijdo2ngdlzK`e?cAJ` zO|0AzI|>wYb8|tFm3X+V5VSa>^mW+1+V6J{zf$!S0?n+wum5lT_*k#9l2X(2dDWnC zP0(z?qeqWGD}pC&acZ70`7Y-~Dr z&JVPp1eDW2!#>f`(zmzgFW-Qc!q7YnY4Lc%Dp|6?N_fpEh>Dubb8U} zv*yO>=VY#}k3WB`7d)-D^Vh4@KR?g^f2N6*8?>lHS64UZ))vp#l4s7JU%qG&sNetS zXm@94=fi{S@=I2&>N;=#e@8pGjaRx(?|0kJrh!%n9eP=^Detb;t=qSMp0EE08r1^ztt2HSYkoXzzgx#YYu2oYjY+J^ z?tM?@RlfrbJ%L8rcfZ@!op`wI<0WtXi}&uW+uPo6^NGW`jVJNin#e2Hu6=s7dVNq_ z+&NHpZi1q7!S1^+O04XDJ`o1>UqFkBo}8Rq`D|u-Mo!KZDSc3b!&B(>wY87;{eJft zw0gKpdmY0rrp?pisyf}}Yekk{KItxBd!+XJUH9l{X@A?VQ?5m9E`01JSO4eZ;%_yl zH=XH|wchdX*K1JAEh9VoF{s-BYS1#=uldYtez!#U>C>k08WQDxRRxqn5U|qO+#g z3*PVje(AymhnyT822W2<&?HpO{e5$vJxjB)vf9Gly=IM$TfbcI&78D1H#UM=?4S|* zBS&10^~?LGq^Mk8?w@|Rjd#VWRkj~aZvqvCJ72Hc4PNDKQ)%Su>$@gy?<_`UwhbvK zg>K!xee(QybuF!~HEVRj*2nc8O;S`=Zk}gbJ!#IIGsk+RJNx^egJw-6B_;2!t9*2% zbH>@UeSbcAb8&HHfr>{p-&rgNuB;4>+?2vOJ*LQW`vEn9j@7HYQc_fc zm;0$28&9^Y{dM8W6&FuWPFY#mn8Kr?Dypi6Wp5&$otu06ZPcVmlQ_7!mF?{2J$R5X zO)vJ;j>5+hhDj_8i`{yYPEJwjVEuDnUU(5xYt>LD3H?0!&Rqs}*?fw1Wb*N76YWLZY&=vPZ^p(!Sc?k3Rnd z4Toy#>!;t|mJ3?i@a4tD<{djMKr2*kZOy*9Emt}`rjYgTudm7$7BkB4RkClt4Vt^0 zsM5JL`}(Czmz-Q(S&tq)diR_z$9^%-Nh?;ZN_uu?=8qaX(9C3smFxx6_vPp2Sb`>` zKr`QOZ*MO?YkK{{wQI*-E}wsHk!$yk2Tj}&`)VvfR_N-!UGY3Hbn1*55}@Mh(xpot z9v%<6_4h5Fs`F)W$2_wrUkM!O&XZK$zSsG%SK^fJoKr{k zTQw=nF%ofI7{9*`wA>0bxoKizqHx*>JdCrR*L_Qbjz-s|wa-^=*f0UquI)eW{Quuy zPF~)t&vvjf$un%ec}MqiYQpZLNtQa+t5iHK=K5W_eEH^{N@Kou=h^3%U4LD;Ge$*S zz4)~5b^$@bgG)RoZ<)8M@bNK05s@RH(gKuvHmC7|>f)b2f8Jg9bMtw-$Dm~`o}Qjx zUR~wh`>#H?sgYf2`Q??h@5^@2v?x@%yv)}+hwuKvgYzzCTzSSi>ug%??yFfx-SJLZE_w(j=i(t5!`&o%tWMsQTy6pF8g6 zt=s>v>TQ*ilM~J&oP2y~x3}ebOmc~f zlgrJ`&AGXWwg33xM@PFo{ruFlw7jhM?bso)`|df_=`l`aWoCKXqhGV;WMnK@x2~_P zjm<3gmP=lqp4HsGYuCb7tzP}|#fyZqvrJRc)61(ZKK}j-8cPe0ubm2NOjbS>J#^?0 zXb{BCZeDc$-l?E9gKO64tO?UjpHs-Tbm`JJ_x8@dxY!-kZhYKt?>FDB7Stc_?CZ13 ziFoC-otzmNO$=@V!a%)0E&f<1fYT!Sob`Ty^4XHUq_Jel zk_D@@q|Nh=JS_?g2q=hXn(MH-F5zXnV>8>vyt`IXQc|F`pMQUS<&m@5aof;pE+43; z6M62U(K@ZJL*Dv(U7VenL3<}aOB%Yx_3e@lRBqWM;;Lw5G-=5am1%mhUANz+eSUU! z%eHMsDJKLjUA`Q-vq;r%`Q%5Bl5X9;Z8+2C%gf8`tFNBgdOhwmXbkw%r%xR{J#VHx zSiWS5%a@EfO;Xa*ky|nZ-|zh{_kRC>I}koSU4Qx3tzUP{4Gt99w0U#ogGP2xvlBE^ z16oLZ;cK~sjLaEU@t6al;ju?SQ(2&e7!wqoK?5ve-Jq7VaR2ecCnhS_U;C-*>9qWE zsrR#zjON*K>p^4xpoJWuT}^*J&;S4AR`z<;ReZQC@^l9+4P!knC(EZ*<=d}DjQ zd~R+ocp=Z-yc#?C@AvELw^v234NFQ+_Vn?2a*$pANq+rr>BqNlQwnIa#iYrTLGuoB{qEe$Ky9Sg>vnVS@*aI#R{ZT|db#!SozLf;4)(V_>M9=F zqT&f^I)DDz(bEGOXlmHA#|E?$L9X(Nppvrk#|j%z{eSlqCoivSpa`h@B_$fJZ#JxH*SpL zPm73^**mZ&&6IaNHF=niTqboBOKt$Nqg)YQ?_bLCwA#s~?U3Iou> z$@rR&thu?lF(nsW?^dawO*1w$G(53s&EGiIcZ)A(sHmy6Em)ucYH}~`w>z~h_co}F zbSry3XoI43-j2rV_j{+G*4xd~e9$2zM1-xm@%6gheIUWr>vnNPPJ4NAG5g_mesvuk zp6;VgrKP5zF+)&G@#f8&>c9NGeoQqnG1-uCkZIMbRa+8oUJy07cKy0>>M0Q?XXpN( zm7$?WGfa{UBtS(cXgLXJ615|u~ zHsFQFRjQsmdD8y%9Y;sUCr_V#yc!-4S`Bw6&zzN&b;+`2LQ+y*>*MyWSh+Is$%%=e zDXldTjeg4)@7y`_xP1K_P;uJU#s=D05Lf#(^h=d(YisK*gU=0HwwUCYNo$9#QBYTJ z@0B*cbnl*Dznrb#o!NRE>NA#fiQ2O|T6k92%v1kb^;aiynmBWE-KmYey}dKeroDBH z3lw?v`ZXsX-?6mK3pKb>Q&UaM%qGp7cW>==g9S^Mwmy24q&s0l)LPI+iSPI8!3!IA z-}OsNQ(L$YJiZ88Di6wgM;?Ei04m{T^0hagJLk7(@#4mHPdLQ7TQ6J)m^EvbhMu09 ziOH0n9-c2%wt3rUgGPXj&)ZDy*54=bVwQ1mOpH%gxJXVUv=cp8dJX%E}5fvY?{Ua^XUNju^Mv5fXL#=TDj> z^rp<()6?_TpCiu9F3T^U1a<6tdsX%H_?BPxbaZ5#q%v{t+`G9Q*$$zhqQ1VqPo6xv zaP68~M1+LeIK25EZ?e`z|RqR#sFL)CN^N zZS?TrLkS)>D{E^_9UYfxshc)$-WZ{?Wy=`s9q`~LmP-j;vAPv`WDuT^K#HgEgWxoQ=Uq)|)a;WkG1cY@{; zJa?NG%D;ZxZ~ts+c-+I!=k2c>wcCBWkqnyi0X6J+rOiOw9OkyPu*mSSuUxqj)E{kW zZ{M7DRx0hxjDw&~_RiQ_;o-))w@lvc z`~5D@`2*|25GBx1uAkI?6$_g<&~g>KpHGDERlnbxXWI*!Liu=H{&_RMor03mBk}pm z*R1I|E?3<%ZJJnI<SovF72LRW>s0moz3$oB+HQ*{f@<-pQ;*KGt^V=t zcK*koH4?pUphX(b&#|_(`OUHTsGhzzH#2kLzJ2q~obl21*mV1?h`9LVIdgn6GBl1J zJ^JSE?&%XI2(acT1WwkDUr=q9lH+#Coi{3Ts*QX!vll}3x?$`JC{xLB!uCA^#eAFgQ zo*WoDbN{-nr`&5)B5|*JUH0=^Y;CJ-?Z> zFW*etn)T<$$1B&afo6Q?)qcDA*vRtt@n3nre#swx{1KD}&GPU0l$4kRFY{4URAhXS z)fv67#`5;|e04p&zRlF0SG`_LC-?TY-nG%& zFI~CflANsk^XJcsz4wHpj+@`FnY{g8)oXRx&)1^!KZ17gZrf%irWbR<*Zl61rSbo+ z@BepnP2^@!_4)a{{qt+l`Ac{1lxz@>t7vSOubXl1oF6FHfOcTrx_$fO@4uiiQc(98 zG-3zJ+Uj5Cfjrgp;qr?Tt2RDaubLX0u0;>NR%K>qAAbIM;VP|_kVR-Mt*wGmQeGY& z9Gi5qd#^Y;I!5SBi{Ey=e}p`ofKCR0FJaL$}Ld3#HWpPzes&FWd% zZqO=?-rn9PPoGYlHLL6V^T!VkHs5l|nt%Ry10(YUmCoGT+ZZqGudmP5n6XGhL_{PY zB*dkt$f&Zi^2OJxuI_F_37#$6wgrWTy5{BSX=-Z9SQIp{a*HW=3V~Vy=H}tQCw%$+ z7nCuN966GAQjg%SZ2!J2w-1brJX!gC?(tcs*`V=8`MMwA!V6T9tE#HbssHyA zG=8J#-1gz+^7%#QZND2NALHSZu{hu=9(!b(ZnT75jYakMcd3V3I4$O$%j2#D&qEdz zb#!(n-rAB0+G4e7lM#6SZDvmoPaBVgaDto>K#D`bb?-&f1n(ciDG zs@i(!kW*Q~{+Y_2n;KWPnB~Km$<^3^X)eY@@Zh+3@#2bAUXG58XV0F!RUBDSVR7>0Nzg3!_S?J-aqE}6 z_sd z&6q30$Ii|tbKqskq=^$9b8>WATU$*`O*2#5+S;9<^2Pp|LDkt51+;pgYsX6ENFU$B6|HSogAk|)oe3CYOx+|66QZQHaT zKPuLQX|Gxp6dCDRS-CSzz4@R+aIi2dE9(p&wNIZuZCTm{YLw{es_N+UELpNd_&576 z>o482rpD-rdr#MU3Ob(zG}c-F|M&gM-*2~rCJgnaPF3mLw#^K*klEM#?h#(|I|nwO zx4XSI-Dg@VXd~yNquoD2WzzHc_2*UuE(UFFDzuurW$RYM%u6buS_QPxdRxv-r?fOR z&}L7}0ock|S3Z01alubatl z|3{$t;DJ+9wLxQ*pd2l%=JR0P?su=gu}h1I9Rsb(Io2n8`|T_nIsUFi8NW|$>baP4 zByaoUvfY89Upv;b+8tn8|FXm?ckY@4=NmY9TJOuc1_p$N)^zHBRNdz{Ykj=e^2>r^ zVs1V@J!UmucBe$D85vEwaU&urDXH#b5s$C0FQ^qQYh6}y``*o)Cl9r9gZ3J1|Ml`~ z6%QM;s;a7tO~r)1KE7+~V!Ka2bpjo>bMX1+jdg#kUcG+3HMeg6d{OPNfY{jH(@!rI zdGCnP0|nc@+TQ^ptPP76DS@^fo=i#l{_bwhy*-laub+N&v^z69d-2MZovPl`9(?|J zV_&WH(xpo^x(+=%J3DfF-rTVJ+qRj3LTp>!-Bo}0BpwE}H480d?k>}Kduwa=&f@2n zZroUryL#T^7r?5Wly2qZA?DSwaj>|7^%)YJ%3WBNci*{;gYF@mz|A5GvO`A5w z|0!3Mxq1GbC6CUzT^SdZbdAMIbWLJILqWq7^)ZVon-4m~#mN;G7XJ8KCnPMqTQ`7b zL*ijJ89w&;HJ>`oc)oyYt0>3X-`|dAm`s{B%`GNIhMAr3NQTLzIdgh$<{X0!^6*NV z4NyRYbgg{bKQh-S7jvW?jMSYfER`k4-xOchL+`c_~_B{UA z*5$MI(EHfb)Tf-jR;nCLAA*1U{g=Gy+ROE87c6La@+4)|>eYo-b8r1QI%$%SlCm-= zEo|L7b;AY&BV*&nb#dD!O%kg5{%&omkE~URM%e1m>ey+iRo~zFy1B7kxax85%9SH8 zE-vou>sz*D3Cr@!pwn&qmQNN`cFVY2TVp4`d~VsSYq!O2Y~Q?jGN-WG0u3&1F`WaC zKPq?%-Mo2|N6Lg__0?1RYJYD@s69PRH$rDxUbY1rJNxC;;rdO@&753ZOuOb~et&=8 zKP6>}*pW&bIZ#V0BO_yb`noxDWI%1?zcMd;WlXa~1O)|8Z0flWTA4X<_UzSb#aCW_ z86eWC8@(;TZ1%<1U!(M+7v!!hTlw*0Qjm@DF{Xlpxn z$`lbEHfC2>*OIcbN%Q7`rnT+n_g}gc^y}N(?$b|`j`zuey75Pjxb(OwuUe&a+Guap zS0!z2?)~@uV`F72D=Q^<*w}a^97;+|bai!2OiU)soY{H$>7w-W^Hfw-1;xbLbWU&C zz8%!uUmv#@G-#7y^62wV3F|VQZgKrhCMH>>yZ%Ym`6XIF); z-tpyu{7OrH~*dX7J4+|`t~{kCsFfI!-2NzX}J#B}!G??3+d zVjT0Xz0bZ@ZRIy{{IR#IxA*VWwsqW13V(vsAMdH(e%m)PQWCVc{`R)q#e4S5k+ZFO z@cp-@j!w#l2M4E2n-&xjaz$6V%F~l`)v8Hz=77eM-@bjT6T3@fZ=AoA6Vu+RuZP}$ z-(3AY@71eU8=}@mF>I>&S#&1NSWr-~rL`5b1XoH*>c*Z*4)<@sJ&>#~68Xz$2K$-`~D&XZKQxVbmpehZ4wS+k_x z+Gb^ECtqC^Y9YgS&gQd^kdV-gEt$eQckYz2sR*d9yR*02JZ_*KE#0wWMqeM_ zuWxU?LqkKie+N}bEbQ#UqM}QC7ewzW(L8$eXpgkH-|ER1GgQ>o+4tY?*Et=vxBShG z#)Anjrk&llWsAuE`~F#3T3^3@y>j(xqS{9MI&NVe&ChWN2$I-nUO~^2sBh zl_oAOEOPzJqu)%JGUdoJ-`UXV-X2+NvB@V}Kq;hHmXFSw@6wvgzp`qcHEn6;Jy3}+w z?RVf|YbN=eg9i_SCYV5pe*0}-PfyN|A3xeh9J?6z{>6&}IcDY68+!Zt)O2)uWUb2z ze!W~C931RiS!oG6n5(d`kcE|X;{5sN4>U5bT(xS^u3fXvoB>6i!Azewx3+f2?X3!o zi}S0k-FvM{okKk+VMoaV!N(C?CM!S<$>Z*+sY}J~Xm%aiSNr?L#l`JrRyqPtKt1BR zzrPk(wZm#vqH*c1#Th|8)mSd9X0RaIG=bvAe`ktJWv?YDRfnQT@-n==d_?#uE zKAvk`K562_gHtqv4QBdm*}C=P)9LY(X3jkM>+9>6x3}w;?T%eN`D)hDr$vPy9yqq~ z%dfM|m+o~F5)#@HxBJ6~0?=xS6@iOE=e~hgG8stqwzRZNm^5h-?`PB5XEnMGy}P>` zG;V4&mv8%R--rl_$?E>c^0t4z8XkXfb+~?smF&uuD^*lg7hZm;p{olTINKZN4;di@ zO)7u-RP^Z4Ba7#AiVbF;-LhrNho{ryFRhQaZ{w9dc449O#g`?ZO*c{NKb4!Bn=iip z`sBHD%hs&vnKetQjb9$Lp@~~e$6@*9g{xvOuc@(K``TtqVnhyurIr;goGaWsxx7(*WA|!MwXgkvS{r_fxhJ+V7 zw{J>4Ee3Lg{Qn=vm8bo_n{e*^cjJNw4r;!$j?68;cM>!cSUE+1&xb?buNm6ff|r$F zkFTGby>6!)H%oL>)Q+dqqIW#%(w<>cxoKN{Mur9`*@BKkm^yXpoBR9c|M*ei)+^-- zl87nd1Wk#$y1L$CKfNLGFb6;X^F`fyk9IzvmwbMnZO+|YQ}6AqHm>;aAi3&1>%-+- zI=e63ym_)m(zv6u)AC}d5R0QgT@y1qpMad4Uvjc?*y^dDJ{3hpM{8{Ps)MuI2Td zv_(a)YVSPPZn29uZggDCSh8e^i#JC^VBkVteO_K(RSSz5*RF-#x_$fN>#v}d1G{!v z@yS>;tXdVJBlhjhP3P29)m5v4qNBTYPOGS>lt^Dq+kA72WliG(9Rrsw@%!soSXn{2 zQ`3Lx(xoOQCKqnq@`{X?5*bZJ$aG>QonZX(p|e|ojZ5#@Ve8FKL&(_xp{eU2?`2E z=uBJuZBck=D5$BwzwYmbn>nB(6CNIJH_p3baqHGC(5RGHH|y-PZCA5gC#j@F9!oLG z%&y})e(pRU-{eV?Hbm%H%sr>f)^YOW$>V#j8>p+dr_ZZ&%NA@}$3OqYi;PQ`FM}4a zZH!v$;^Ok**X#A5gLXh0r++-^-a2g-=q!-9y;Y2{@=Tl4&tEfYb#rm~0GdyqXIs7K z@=MTWO3)M>Xk&n+QOnBU2B|vbDdzxvsxnUTwE2YVDC@$2KJ$<l zzkDf4iA+1*Cp%^8)QgMV`MbKiL2K=oE?pWD5(3&iR8(C2@%P^!HFhz2;-Gci*4DH4 z)&5rU6w1D~=HTwBx+0$Pow*afJ}&C~-CJ9;4QHPPop1E(_`DNTL-<=eK2 zt-tOa5+V{97$_nlvLWT9(4j+zHr#%D>%H%TD5m}FjrY!imT$eE8n^y>o9OoK+l{lX zfF>_$e|jSIkB>-lGwZ|h6^j=)gA~eIml;%kN>Nf$`tjwmKWM(ROM9J#8snnl zk1K=LUq3%7wbIbwnM?d;1&%}fAAi=wpW5`NMdx&P!-M#k2Zhhdc4ua0DypfuNl(3c z?b@J=DN7K1;xkvM?^$ypTlwW>eZ+`mdI(H-Q9~fZ=O71!h}5D z>w43>H*GS?m27u#F0ZY%H8nLoa^#4Byu81=d;7GPA)%o?ZpxjVowsB>Jtuki`X0^N zdg;aukL+yi3s)WFrKMM2wGJqse%du8L}b;fpzv__*jU+#6DNYpxQW*;TyQ8T*dX?U z?Oo@pRV}TprPk9|m90G~JL6hNVBkXCoeHOo_}ZOMZ;@P)?T`?W84@zZ%*9Mg%S*a- z*(z|e@Y=PkRJmDGHm+UE>l(7*U$Tg&<|zXDLSV?M<*ubY*6@f&SI|L zz54&P+j4IodvbCzXoVM(9Hs9jroE*>^_vPE> z+pZB&P*G{&*55P1_?$)av14wz56^;*eT;~xxFICm_%)uz@kcG^mS^*d&sqA+FmTL% z%ER;h$Fte_Tf`zA1wf1WA8vms&f0##G|j@!Zr+a{KjgRWk7)N7sH`(uU@?>u#maCjs4onpN4#LHXgOLe+A=LuU)%z!GZ@a_n9CLaTI8I{AdSD zd)FeFZ%@Df-nvz=ew}Ugw+$5^lfJ#Zef-bQ&nwriy&6|+H5WA6z9x2e*zIQ@D?lfw zo}RA%Kk)h8&FTDCuU>VpS-Wagmu>Yonf}|V5SMZ^6&#YCp14#}QnE+N^i{`Aull@G zk*z=SmIKc4jJ-GYB*37v4@=4H&)9&uW877-RXPeo6k1@-;bndJD4I0|vVQUtXxpw{f=Ax%wpfU8sL`Amd#(lNF<@T3*fTF);@~q>JFJ3z< z%;NY%*LtPUC5c|Q8aw&yYiktE%%)wrawTuQ_ax9FVht@VB}2oB&z_|j85@H_W#2xz z?YGZv?US_z9Su8i;zUrp zZ_}ntd7Wt?p+}Q8PM9|B*uiG@lc!HRPf`(aRWvgbYd(13Sg$mwTc@xq0TUteEOHkxT5)mu_lHt)r4B~ZHD z@7cwrH^1rf>Ia`Re~5hDa9!kU{r%tkKh5v1U$LWxYt^lP&W7a>;LhRjr!ZyXGd@Uyz2jU-=9A@zpyw8_=Ty5o=*%25CDy#z5aUX)~&9% z|NCFf_E7^ZB0QR;sISl8ebgx@M@MaP=fA(dlQ%}puqag8UG{d-=FO8kh1Egpj5`FC zZ|tkJ*45SRkuYrf@uNaXSvgRo_0y-KLx&D^^z{RX^Pcvc zY<73%4SQD1-M-!I!=Wo7+rq6Y(n5I|!kW*#=t)>7CE~ke>+j?LKeE;RsjU_26 z59G76Z$>_T@X&bjkCbrXZoVxGFPHX;O=>KzZri+VO|$0C#F%UQwG;2T|2%u<{_R7- zlP7y}i*r6_akSVvC*)wFpC6y4rKNk#*$k6QyUX(#&ZHUF{`$hGU}QAu#0ihLzYUX* zl~|uk+iY0$w(rL6L%E+%M|^STF6%$^>QPyqgjDmQLtTf=zFo1Bn3S^Ef6mldeEQkl)&-TJ zt8UxmtbX%EKUru(!}e_^8s=(*DY2(^c=eo1AXHJo26WX(3TU*@5Fd^q2^>Nilm%KQcgN`$hxtheZ+7n7^lE2ao}sRvyxZI_^)nCb{$?zdGE2s8UUcl?{rYDd zCOJ%ET&ewG^URVnFN7V#mduRXD3&)P`q0w9){^r@7tQsaq?!_$ROl$(ue1KquZ4zx z3JXj&sVwxEAe8v%mE%Q;lK-bo3od_%(3=x{ps7|@!LfGv^zd`tZ@2X;a2#6c1lkhD z(WD?_9=HG0>)Ee<{&LXP?z$~GS<)$POKY?5|I-gvGP%t?vb1^8jQz`7E32dUyt@`K zHLh%|eY7Tm$7uI!nY&HSkxh%#%wIwYs_B$1J0>nw{(kS||Lc!`++AWQB((Hw-X5+gTX)=y zeSRe_m@#sifpm&amE>eUHzpO%b<$GKt55r{zI?vaIeCKX@`VZ<%QY?*gZIrHDonoM z8DniRtMlf*kuuFd`(8GOlcf5mIT?|W-qWVE}jD~gMqw_jIzYaejw(V=&NC%R0^ zZ@UMERvnA1sCHx9EEf9d!6PrbJto}}vl(4m<2H)Tn>VNE#)OM+JdF1y>#it>nAxPj zv0Mml;p6YCWikpj?!9$x`tPWZKSGn5cvdjH`rp>IXoZHBN{htK;uM+Ew{kV@I^GYz zX}(!i_~Gimur%FuOX8cR3T7U>dv56}fo$Uo_VGTva-&v7Y$}6|OYl&0$;p5Y^ ztfRHw=B(QieE%iut}6c0NiA`c1m?CiDa<+LavrjU@ObM{g~=jmPwvlt{qy}cwiM4t z=hKfETiMQdlxF^NqS_ia$<1*}wwHR&UbvcOyEfe-oWC!UF?CrzUDT-Z52cEZ=6fVeKiupcxa_Uj4Stvb}^id2f1& zjriBMr!wT^FNtpOQnC_Zc_^Z`vVEPBK4=5|vyTH5hxu547>>oOyyieQV&6gjA6(UNxRW^20&8EwX z8k`VXC??yc?8B2&{PUx>*28P-jBM+srnUDgEbfVCZ4du>|D0CKjM5wb@-=(Jmh(3h zoZgsg{vEX`zCeNFP@zf{WKX7rUXNAh4TZyk@x~{F4BPjJeV+TbsmH3R$BIFRjkEh< zd-|c4S*7iB?e%UHT)EC)x~AF5QJ`h=#2Jv(1+L1FH(#P`tF#GZ6pBY(MN=8OP zd-uIAJp1_Lg-e&5LPA9RmQOx+&MzrBd19SoS7>NxW=_tMojWBr-}H%zkx`rclD+@+ zn>T-sKF`m~1MRZlku-YZKKHXrc23Td1C7i)l140xFRsY_{e@Tu&_}5*QS>DD{?*u+oCb z(ey$3^0jNn{`~x$nV)}so2iKj=+s~tKK9bm(l<9YGP?%$MY(x;a{m1J^M3Wbx3{-{ z{`q|V=IrZwpaXuV$Jce<&HI1w*VSv+{yq3v?@?WC4O&iOGcUR5eA)YZzK)Iu4K-hT z+`4t^#?E4OyZP&HmrXx?^x4_jFJHX)5%~1S-@3B5w_J;hKNqfE{o(W8y-OR5{}ngP zw$p!j%fD;Jr*C!7-sUns_$%xC=jCQ0nYdQV+tM?CJhR((<2F~x+ReYVr-s_>TO>7q z(WmXHp+5D`70hen4pkQOeR|HCdh6CIM&2_0@cNh|>D9aH#HWdFGyFTZxu$r*)Lne~ z)9WKnq{@pGo!%3a_iIlJyIA<97YW+p`=Sq=dbI9*+pRMF@Vb~I_1n$TBW6>5TTpLY3W%Hw0bJo0vPK+TQqw`W?H>#_66C>R<}+_1sGM@{(b*|QQn zY)#G0FWv%c5UyjT~<0`+?<@8J7V;#tgJdZI~}K`&YC@YqDtq7 z4+RTWhv$Q4+SZ0GTC|9Rpa1yFlAzew-c_rP96z4C`R1;>kGE|ztFe=xIB_Cma;&4H zv zx19fG5`6jH!%P1r{yF654Bnp&Zhl6}^|SXMZ&vZF*c-bSw9+Ch?AW=v)-#P#xjsKX ze>}rPNJhq|wA57Dy-!6)r{~tKs5fQSqN1WNzE)jZ8-09c`aDk;mzJ=;lPQaqEo)n| zMkmKi`tmYg(BTLf7nRuf<$TJ@%(%t%RLsn#fmXBT-rn}GXs3sV2k4lZTR(l(gdH6n zuUxrOa4T~;XgZfq#^S)P($^gw9Sc^j>`Xk|c5!$4`@`!lKL5Ni?X1+RSFb?FzM7hv zg7!H*c#v@G)~ypKPb%u__JYo4OFqWqC=;wTefW3ka1CI(j?GK zlEqvESn`LxjF7w)|3hV-~Z*>le>!R zVxn7=jNQMVxw`%Hr}B6RRLE|SObt5Ro8mI##QMSy^L2U8$Vj>;z4+fI?>)&SbauPF zzK*)pi37otgE`DzuGs#Yfpe}(;ss9+Ar{HhCSR*OOWSF;pHKhv@?F}!yVp+i*iHP> zzy0r$Y#S-i?s3p+yFbS~Cbe9C`DFF_eTQ<)m^1bUPEvXJ{rATTn-#0Njz3O(abe+? z_xI;d)ea8|2mmeFa(RDm@5+@cFEXuod3kyH?Az%^GiO*7DrtwWdvdq@zN(^PqhmAM z#2GU@LPA7DMMYg)TrS+Y)ph^<@x1N9v9Z3O#gcJ8p`jnE_RgvM^%AtXB=`0<&>0#< zMMa?5w3k;`gGLThPEXSXjrQNU6O+rQTJp7RUHtxeyLMTD&O6L8lkOJNRnpP|9nKdN z6?JM;>gf|_&mK)N0`;+rij3B-UAtt-l8mRIBZMYR5^8F0K6&g8p=ph(>JBk4<( z?flwrlF{4qK&#Gf8QRVFmsXP6(ciV`!RMa_QoW$*!{&nrtl#f(j*XRdTP(Qx>Zv83 zlRJ8PQVuq;g65JgE_Tm8UbQzaDJcnbj{nxJTWkLPd=8p2pFiI}AV2^#bx^i@u4VDF zIWOW3A8Mp$0ro-zNu376R-#>S%)207=uZ2!I<^B5gkCbv_yZxuOIxpH;TA?Gn*;#Uy z(2;ntDWLY!@r4&dN@um5LxdAYy3vT`%%XrK=d9F2^PU%q&; zV8sfK?YDiCl9Zl4eHtZcHMeiU0tE&kVd0w_5}AE{eL;C9{k&XPcXy%H+^F~I3KmmT zI;Tw&Gt0SgU`62Kj;^jlk3U|xaG~Ko`2L}ay>V}=HgDdnsik%3^;aQ5LC2JoCxP!{ zV`F6t3kz?VE?=@FAS9$^k;aKlci#S!)!Lw|q2VFTmt`XL@%P^qtGaIH9Lw6Ov+jenzE_9QVn%{9% z;GJ zx9eUQhOM5e=-jrU`g@*{v9YJ8=fMn8HOoKjl`B zOR#t`Aw~M3n60$7Rb^(bt}fsI#J$C0_)VE9n0c^WcCt{K%AG~(#-Bd8 z&5w6V(G~@D&m4d7{>tm0vTD`crkMx6x3lTA=kcpd2A!|HdGq96Y4bAf8E4XrLG?iN zww^RsnOqeyxtF+XVlate9(iPnqpDJuXlj4sa zC0$z|@1K*SvvK3bB}FMf_$+e3Y zE`WA8>@Iuj6et2}Lh$k~UAnaO%a<>Q-%UT6vS{13Y15{O6%`k+T)8sw_O{$8vCDCL zt4eR%YiV`ex)t^9-QCmI)<$P$W?p<%n(?75!#}6E*f@H7Uh4gQwNVb*r#}ha?{@A# zd-CMN2M3!$PJMfOdv-BjbaeF1Et!+e=7(5(Oc7lbWoxr>(X8G3w4QJ6{W@owm`~0I zM%TbVQPHXYxj%@C?&qp~wC0Y9jo5yF$IyT40~u~azJ1VmRP@ioW~Z`iRy(Rom>k{i z{`XtP#1-*Hs8i7`&EF@fHj;h)n)Qd*mrS+K_cz`=Jq*;}oEH`9yZCSY&7x3O+h@GI zyKZhyKW}$2ze$1PQ2)d4zpq!>%oDBJ`;W_W(j~u=TF~@s$EsEAsgZHoo|i-q^v9ji z7kILC*|MyguFQM$SFT*?5NVlc0s~pwyjXmTVvaI@ro=Ht&AW z_A@0lwYIyv%d<}wG&MC@Jnk{x5VclCP3_Xfiw6%jvxD|BzPUO1!Gr%_8(7*ZmMvK_ zVaAM(+i%_0U!OdEI{Sfte}99Pdb;<^xwyJ!Mtb$SDKoS4*{nH!E^TvQWMt>T1kl!@ z3l{=F&3i|r%ZyPRLxbR1f9TzvZp;WJmy87g4)7s+JAI~tEG-XPQif4t5 z+@r^jH%92p@KO6yXMgnQ(UjCw!-5A6xi2!jytp>$2+PU&m6UAxCbw+K5&>!HZolP^ z4>U60GF`rG*`ifiPoF+rvSdlg>~76D7JT!Y9MvCwTm3`C!q~cJ=Hf>j+g}t`gev8- zxh-XX{L#5&L*vBEgA%1ewu{cr=rv*%%H{Fckn~+^29J^6dZ+#j?(hBH2`*U$+wZx% zh8F%1@$=?XvpwWe)zVq8dY;+z`S%KA4^RJU)#=yYba(ZG@Pvvi0Y^94I#!#mQyo_X zCLOM;s68w19pt)yn*xVAXaDifPj;2QPUDp_$=G-{O}bd8J)E;iK_U*~Qn*N2&KZ^hH{>uBy^tAKL z(nI;J8H)s19xj zjvR40llD3A^#7fy@=@QybL-v`elT#&UzrCUJNtff$#knD8BcDEf3ff<^WZAM$pqX;R-eN83 zeZ1}VTZ_4VYuyjN|Ni*%&kq$gH{O=zN<=w3GlTZ(|9}7gul>K@_x~5&ExlfP`peT2 zE6}1!rj?CH1DSuz>?&Aw{q@o3pEI9-{r$J3tn5+Q?ybA(|9-uGW}YqR@SK1Ef#796 z7n^oC?B(|@l+pOV`>WlR-S79E2JNv29eDry-EPqNi~4&$G=1B3`su5(HQ>!KkIHrz zTFtGnk;}b*AZb_Q>v9X3yYG@0*fmyz2i8HQ)cez(SBrO4d`xO;Y64vuu^~byB|ROq zlP)q+($doM&+GXANq>KRJ+bKwXw#_tzYpxwbfb?ov2srUoz0fy)+2GS*Zkgz)$8|x z)@fEepIhG1-=BVRlB&-vlg`V2*1e$9KFaUcPQP#=;LxE%pgoVl%X}W*OrQVsob`Lq zM7_V=&z5uN{PgyI2}(OZ&$qhTdXh@v>$TfKJA;BlLRv0nJZY28b69`P|zXm z^?MGbPLDk{N!9zr$&-%LQvdxtU%x5&I3Euivv_=sA?Q}Oe}8_iiQLRK|Gax;rDgiO zN;UI)70KJL?L2hI>C)xPmT$LQzVNc-PWk=XC(oZh{`@myYnJH$zwhe{cgBGF;h+-+ zK+}rz|Nmj{Kki&qWF*($4%)Hs;zb6i84{x>4m#JS_}Q7nkB^ReOgaMEtpVERVf}v3 z;Wl3BjQsrP+ivG2AL$TO|8*VI#sQCrC%;(o%2G#53$!4#`zTY%(u8@Y*97#ekOiNSoy>*tKg`>gj2P*CNxwA#zfEen3FLfiEvFdwP04T-2?% z$h}_*`+s{8w zK0C|w#n&p(_Ib1Xdp)<`KKpoFKK(!gqs`whmzOMG4%#Dpd9gdc-i`-Mf9mXCUt7C) z@#1FCA&WV8c1#5ARR%4AT0XySmTmR7fT*Zbudc2J-Ia1ZrkEELhMJn1FU#yfr|N=g zbI=m~J3EU(?!A@0e(9DiQ$QP0GcPX#oo(~+YIyv{q@!G*E2*|-iB4AYee|eXKkfg& zzd5(IaP}WRyr=SWM^_i{;83 z`$5xU;ITfLWcASV3-7+KpmE_)l)%z?TZ%+kNjepv_C>e z3=}}1ll`Kiq#izecxJwRe022j6eCdg&clO4Q&aQGwQEYALVn99i)x2~_VxVw`r7^d zy}dvGd_Et!x5|_ObPnd17Z0iRd( zO7l;hJ!CD`r3)7h?5q7fL8TKk{p~qfZSISBW>~uHYo5I7xq+Hm+o@Atf9mWnn1<)y z+fxaOf%VtBuV!8SwlzZMmdrkfQx6>szSm0f*~;;=H9LBj6>fPmzkOP1#!b*^Mxf&} zO--jl)(`bn*|`ROsIZCJn;Lm6$Lw*5)zY8lwT1WS2d^Zm}QY9oSBU$;pwTVpj}*H zVaK-R-j*=QxRBW0Q}_M1^LEWHCqF+v(CFs9y|cf4E9>g+X56xHO=xInNl8gSNJz;M zBhZ$S_xJYBG|QC=TP?c&I(NxeuU&WZbQ3~DMOR<-dVg;(X!qXSxpP6&qJMvbRsbGQ zzV
    )hf`DL7xtB>tDEbtxd%;BL}@~`Qdk^>Elpi|sIS+}L7 z<-)yt=Rk)bbO=CJAY80VZE9k<03K?it%ijCFl z_eo7ZeKazC?$Hwym2Ym(mw)*1p+k6r2o8V52= zCQY5%dNE_k=FP&eB{-KZTyXI4;BZ?!v8|2m(4j*HGkr`C!I9Ae8 zpyhq>CD!)i(dp^x-|yGk^R+WK%$g-NukxAXr%#{!O0Kdzobvo*#k@B!Ud%|{7$LTL zmDMxIiJR>nlT;vEr`4}+xD@Xk5Fh|LGsL>=&4H&y7uUzz3keJRmuzKm{9&J)n=8S? z_ND4?ps*WgVpeR$Dp0rQX;GrlOqV2cM}fM}E(H}Vf`WoQ(&q16!kq+5hdeTZJVN-J;s?6x+x9JATe?)V9?JlyUDno6A$QuXy!Yn;B_E*8fhZzrp8HXn2- zC@`>_-@k2}nTV_9#j8RrjuxvXzvJ`8Pv)z!Fq_FYq_srbfqe&ZOHeNrJw|%h&*TI7a@1B~L8fk6*VR75x%Dr(R zp?~l3ENm?0;omFnZhZ~hBHVR%@_~<4d;ht|?pF9C){z+5q%g-PB=l&4K}XO1CWSd7 zeybjASi82j^!2riH*a>vtxx~(;Gl|{8fbwcXiq>%i3w;=!GZ-0p`oC>`QyXGTkJ*Y z>FV6#dMOVMG(LIqgdsN4cK^?3(n`w8l6EyezR!yf6q%+U@3+71?};;KY~Fm0-Cg#u z#0s>v<$~yvwJ|#Lgz+~85^IBM^4Tg!SlfZ0SJ6D(^R?H#Ztrtperw-R1eSW`nka$@(pyeB(w0r~(QJ zeR?~8|I&T?pCqJbkNz(BGs_fqPy>&11(3dl6iF@*KBq#Xm&6$e>tFL;6 zhKhoACR{Kz2@MSm3J#u_Dj7K~Gdml!wp>L;MZr@@R8&+&MFq5a+{cHfZvXsdcK%Cu z?({6uSh93!qJe~g1doZi`Q=-;PCYo-+zCG5sqNRVDu%T^SFVI?%e~#UNF(XQJkY>8 zN0Y*wMSiP19&EYnr>v#b1v=JCfB&B#>ErzWPjjSgzWL$zUr>sBdwcukTeohl1)Z}} zRAj`?C$nJh-nqMWS#8?9nQ_IcRiHaOG=rCcddX|sbKl?F+aqf|4Rp-hj2RLlu8FU& zt(|c;jg3d5;r{#Q2O62}t=8_-IejC?%*x8j!{0ys#f62pvhC*kb8D!2USc}Z$jrVe z_qLf?{=G8?8krmapPhK+Wyy+FUDsbXi-L9UfTVAwRcYi zinPkvRs{qHAMTU2?&#=H(9z*>4SezD&5@*y31+i7xwsDHm|ebehX+*oRPB{pe6eGZ z#+I#HL7TAa>g>W+Pu;L#gS>3a%2l8PVv{y1oHk0^99gX^@Pc)ph^ylym87Jk3olDf zZ1Ty;(W%-SCoLs?LtQ|Foyj&dbZd%nC&ZFu3Q1FWi2Qu*se3Dv1j4Mix+R?n1MD_o}XtsQH9er zu%oAE$>zic_pUwp075#z4@_C`@j zscG6Yv01ZcuUxa{NY+-+p(!e!Az@)b@$vn~AA_VPOc3z$_I~-|#e#(k8&90@P*PR~ z?ZaBUxcSs6uS16pxf~0E=0uCumuctc+5UD3e{_6;O6P_R2A~po zsu~y{?;jK-)Y;hy-n+`o&iCN^Z%Lz+j%{07&OcwgbZP6GH#s_D+;#isJ2ta{3UC!q z&|sXi8)$9p_4V?Rk&zK=!$7;eQd3o{_Rf3rCg;-S%bI$6p!EWfT|!;QA0JFG$oLu% zI<>8hEhs4H!_OMfF`j{ej_&U4pkZ(`vuT?)8F_hmd3bs(qybhe0ib6r+u~x6MwSI`!i2Zu6Hf zUxIeX)&H;i_V%`WNQlVa-`_#IoPYhQ`tszY8s7BzV|zEuNKM zw=CHiQ?WNrN6dTs*Z)k1O_-g{neVf=C9-X^1}*+Ol(aG9xYb-gh90-YWqR|^w_kpF z1Uz!NW#R00zKb>`a}PUw3*H;I{_W%MzqdY|-(F-jcg3nzS07&ZDp_kc|Jv#21r`@x zf4zGx*5=C%=j(MPyYEJ<4SRd1Yte%O3xk#xX7I{IwK=Dhd? z8g>$xfByT@+i&ZfCM|Jw_@J)`Iw0J_YUQS>PS@w`aTcG+pJP4cX3jgae$c*z-22Y^ z?M-G(R^eQIwd%&kcDAltXD`3ref6ow=Wl$r_a}-Su)P&6sw@`?4F#>~6$-4K@6fdJ{W(rC7RMhOLqd;cZA~(od1O;ylfs{8 zo$7Lq0)CoZi#8NI1Xax+_wr}0S|ubZdi3q~`+gM_KhE3DU$qJ}E)^U2VpjH_U}49V zt4|ho4oz@&j-!?0ijd9^>(t%KI`V{%F5Q<2wL!LbG(gL8Z=Te zS>1nG{{4N&U+W6A1ov_9usT{qf|l2`1b6A2?vXO}ihe9|;hGh1iWr~4?6dAZ0bhf6 z+|7Hp=d<6o{QG<*td)%kZ|4-B0}WtU>72YaamA`r?D93BuAZb-$qT{e!{5G@fo{PQ zaZP-6Wu=LUi9zkJ5+Na>A0H0$bMo^3y;lsX^gvyTjt&mUgx(=d4{(z&2y(>2-EE~M zB^zpff-{Gk+c87i`S;(0_S?O=v9VcD*-b!5sOk7)Q2hWpVz!x`|I>@b{r{H49a+rV zZdQ0%naHwW2N;Pv{&iwBSU?b99~>jiC0(_Xiu>CBlk$)Mv$ zIC*)Wwu;9s@Skr7TF3J4&PmnjF^6X7?>h+^H!CVCTCrx0ijmQz6)Q9tX3d_RnUSHO zrq;G$gTY#x{^N&1BkUO&3Z|x0pFDYTn8_+M6m&H7z3TTzQ;bTZ#kVb5)b!~SXmjh# znKND7+~mq~N=r>$U0q#VT_;YRs(Q@{G%eH;+#tcA&v8goTR z%1-|*$-`D+CF{0$Vk0wq(XGto7q46a?eYR`M*Q;P;+~Jkq;LIuzxTTw=wQ-!cXoPA zYEkk0@wZN{{!ihte);pDt{Vua&#x`Ju`?xd*`h^F-uio|+|J)GyZdh6`R9+HPLEGJ z)WSKZ{9YyKy57vo%Qhw*?aDh|_w8mnXhiVjWOd`ZKNa(;-&xk!-S4d14ap7XK?w=088r*Du?(3%tLntE=m96@RUrd}(Q^iJ6&_r_gNk{IqXxZXP*$ z^y9VYe9!>Y#_I2RpbKVh-MY2qad%hOj#sNzSG-u*9uPVeG|1G<&Oh(Pb{}y3Klb6# z7G-%jv&apznoxYsk(Eo^11A~9gny+ZQ4}#@u!OCp}g&&6TBzSm?06PCk{G1 z`EVQWoZ4?UL6hxWU0w1z?xCS2RWw)ddX)-yAWKmPvvEX?oM7?ho&4Uo^=Iv`y#5MW#IyZg)ob=C50B5FE6nH2kzs%=r+#xS zx!gvx%PA>IsrcEMhoHN5US3+dCUSEdXvdHC`aP5C|9zhCekMqYL&B9U7*w(U_$yq@ z$LeUY^zRM1-SJg?bN$@i+}JEFEuTDjA|Nb$S=WH4=6!+1mfZF_`3K)YXU;P3V%j&? zx?IWglHhe)n<*-uprQek3MMMMe>%u64_fU~_3h0`&<2Y&Yec$BKu7T$()9ZRx+~(t zeua+}Y~A<6%e=g}5)%_adhgtcv9h+V{C>B5_giI8A-nngppm>;v!t@JvwwcM><>Ea z^Tvimjjp0ApHFTAZEp9s|7%kH?af2~`d^?$Ut6|p5s;Mh3<#J|_GjX&pV=%ILBXgn zCjvBc{%}g##t05hPRD7fA)%@o8ZTz>Eu5m#+0ntVXz}8UulxT^yuH7^9(1`1Xx=34 z+#JuE8XHSXOA8skHDTHe2M-=xvV6I)wDjq}-|zcZS6iPvc@lIB>y#;=v-~=HdagY4 zeD$?z(V|753tmD)LF2hx+}xF~*KUv4nk70(W#XJUXVye+1}%5X+A1U}+IlqU;ww=h zAtxUn9<|9YUG|599V?SQNo!WKzn0(f$+KojDJd&+^71a-ym|7jT~?1CJ=$ttZSDN_RC;`%OvcGb7vxDlb^xoGFknQd)sj~+eZ;N@jqux?%7 zwryrYLP8u|TvmSX*C_m{`-wOv&*4Mox_$+YL*Ff5HXVNTDhqT`U{`lH=p6Q6UtWHB zb@la*x%p>jnTCXffUa)(v*KRMqD4y2&dv4C&DFK7`tqQOTTekt>y`Wb^A8I^yP7vu zeog~T&_1723_8v7!qmF||9-!J{@`FU=!8*FIkNxHQ+*Q?lMff1`3+M}2&|oR=#Uf0 z+N|tsPha1shq(1s3=Kb8>@#jE_#@j7TIK$*!baex1u1zRaS+#1?#EA=S z&F5rvEqYL5HBp7L`)JXT=Nga(Scb{JCxTq7Ry``Q3JewPKC1N0m-~=mK@hKYZeCs* z=&I(kX`ms&Z*On!d^#-}bO+UmbLZ^r%stlszbGGe^ytw$Rj=2kq@`WjUtd2>C-TrT z-`N%aem>{q`}eS(|4*GgXu$0CwYAB=zPtqOAbdWz{L_=k{zgehILhALIk_$O_K)xT z|NDZDhT=2?61}T z|9%IZxWDuHyu&F*pd&*6eQ3AeQ1&+J*Z245#p7!lPo{uE>B+NaML(ZT&o1r+ZJ#-t)`#UB+|RQ&(@edX%ak5B9Gf3j-z zx=hG?eB}3xdqjMKM^#j$&eG5;V@W|Wu^I2Zwg7eQ8Z{EB)dhz;It7e>i7H^YZb$?&&nHh%6 z?S~gmRXuGK7#IjTb`I1$xYjnWUVi!IlgE0cJ^lQyF`a#%m6MZlc9v# zw-?;6{r>Sra(`|9&$|8dL8pQQ1$}DXyQ)dyj|C`(tH0+}eR<&sy3r|Z^GtWSN*2#a zA1Z7>w|ZCXjRPGH^6&5adgaqb%xpXlUM`>i>`w9d)B_ESd;WYn4Z1ot`52E})eFTh zRkq2;dLH(g-#d_CFk$Z8v!G$~C7zQ($EJhMEV;csKmE^-kDy^?7k78i4H)+-9`oAB z@q^|bFML(pTm0Nl&1c4fuO`Qi9-TOMu5VqP-Nua@ZGJwP3_9if%iG)2L1$LYm?5#u zfBw4I`}=BtgT@;4_5E*`{d&24bIs49J9*}snwl)^?31TXbxlc80iA&v8ai?6)TOL# z3^QlWY-w-*d_uXu;HYT$h4u0Fpi6j5tYkqK@Vvgh{`scUddctZ>@+bo1%=v&SF6{9 zHevQi8iSU0x3;!|)<#}k9ljZ~1<(F}4Ja$+m`N+U_dWS~J^px(+2jcm4uGzmpQ0K3 z;_mM45!}Aucr~2s=U!2<{8mboKQ;n--m?Xly)rZS;1~ zI?991?4avKc7kR{`uo*wZRZ~C7N0zAnwygo6KJMIQ&Uq!L`31VQT6wCOE+zrls>Pr zt)+!UNJuCnE9=s$Wg1+0+jrkSbaRqQ;^vzhZok#&a;mAZfu6En^ZV^~5pnVI-FC|_ zcizl-H*LeAw9OB{R&jE1f${<9Z~)KAYKa>oKu1V#-MSUD`r&TX>qn0tS3aE@Uh#VE zcF=7dX`8{fqJfSP$iHU;y6-h7N2m7px2K@F z+GQ2CKJM(1PT`d+RwP_r=6k35{a(<@ut}2+Jv!RG^UbEy9)5n$4zkO?G1~&ZDXb+~ zUqGVn=hNvHbNxVvvOb+2?-v$!&Cs@g5$JsJg0rUAGk7n6CO2VhyV5sNrL(Dt>Dk%Y?zOeH&h31upPrn&>z2;h z^x-jRM_WXMMD_P~uA!lmCQSmZ#Rc8F7`-h=(Z*)Zw{K;j#SFVjUw^3D`{n(8`Th6% zS}AykpLMckG@t(8YD2(?U!#pcp=xOR-t?T$&^c*zV=EOE{eWe zVgmaK zR%OJ7hOREIyxng>!#ElF`RNxIxk~V`{i(CxTm9WFa7x~F@K)YK9}6=QUX<;=__8D< zH1y-U-S6e5Gkn}VY4T)FKE7pZ*YYmEe6s%EXL-=EZJ^D{2~TtRb}Nk=$z zJ?phfaj!)tgJ`hZs#9=TL#+O->tvz1nA`Px7+X6ExK>yXz_AR z$mGLrJrbZ%bU_Q*FiyrHtS-aX3J#^-G|OHccG?z7GP ze2&Y@d>`K}zn^+;j^&+#!@L<=Z(VNM_AKE_*4Chy7@h;=_iK+oI@*1;)UEiEnJvz0*$Y$7+Ofvy}ekvhBd-&RmT+Va2BZ~0`57?=twL3s5`M)zwueZcoJ7WB)!^vs?TY@!uPFe)D-d&{E9G zk4ME!^EvAio6cO1ukSsYv~$V)nF?Fn)@JQk9=<*fG(r#Rt(CmJ1sdoCU2J7vXR~qR zM$p{FOyl%Ri{1JAWUY_wD17YU=a*-=YU#y{2`Zi2a&H%Xy&C@ch_L^KwbACAHf`GR z^V#gqzCO3rS69XRo&CK%`+8s9-(Me3DECj8G2_I$ySpo2ENqYZ^8T1~e#-TAv45WG z*Dv&)Z3dd52VI;9x;tjxyt=5}_VfJ&g@q4aSs4tvMhtW(OlW8*=xo79-TF+v@iUgk zb?cl?=Q#&jE4KZ9-RZmK_kCxZrR7Xe$(($QXII(VQx_MzgAOoYWMoXb0^S&`r{o$4$^oyh zt#w{~^-+mcG)o#=bK|XBQJ^NYuld~)$4zBfTcbeJ6AunHPgLmyjn#ho)Y0Aj_UX?7n(k3Z(X&!YOddLXMWow z(3>*d7Ec6SEpp_D3&Xqp|Ldy0zH$u<6LW6oTU!49o{5FIf9rm_23t+MmB-{%zS{W)uX-{*Lr z?9IK^=5cXxpp)A!TnJc@x$b|kzwJ`BJ)pJYvQ{M)Z#APMA|yb^jo;p$|NKa&Fi7RS zz15E&KRygfcOD)I@9*seMJ*#UTY`zyOw;VJ?jnVcA3y5E>=X;#p_YgoK19q)INncmi~0)Yj{9-oL-Sy&Aat^Tjz|We;4wJbA(d zflZq?UoCBYZdCdzgo~Rya!-XJXrG6f*|d+3j~kc1x+2#2bfxmjO*)UucAu|4HdTS+ z|J7AZ&KX=mpu2Xwy{&U+A52I%(7>o`6!Y?HRcWlOLr~D9U+If4o><`63|bG9`PRI< zuP^NXo0l(7hK9$Mp8Ho>UA_9Uh5ysXk1sE}8!R3)-)mZ`RoNSl<9)LJx5XqRJQ5NX z{3doSkyik%^Do9FMJZ#DPbmRcJd8wsNnkM8R=wYBGeM1q2* zsUY#hrZsjN-#&b>$h|gU`gBmkUi`}(z3JWW?(PO1R8(RmJ4t0L$1xGtpT0Al-j-Q^ z{rdGq$u6;(*UqacC^UeI9nh)gU%r%p4i*mfw{1O}_V}W^{9cLt?Fup1qOQ(@-Xptk zX}779nx~Ld@3S87#FWTmIcA`PgHBFXXFPHG^x`#Z*0^5KIBnG0+A3pPCDIVPyR7iV z1;s~?A9M2Z+P>ykqT(5$BL=$k0@OBia(13+RjTE0`&C3vPEN+I=Es_?!mN%z-bUR$ z0ol`g=&2X@;v&82-gCpMHZ~oO&)S}Ue;?@HdUyHSDWIj0Z{Fk-6%}1sYI#BAiKwWk zVc{c}Ns}hs*k5nIb?a82ITnKX`R|R@7<2yBZ@%f1m8At*cL%!C@5QC1pqa0OZ_A7e z9vs-LCMd%4aJw+*c#|dt4kztI1Bn+mHY%5vmU>KTS)`#m?LZ6%s7{eIZaX*EdcJYu znH`0XK|Sf&`TG_wS<(VJy$nWMqG_2qMZ>Ts zW|)9l1(PNTm9AZSGUd^#)$5#AUtMJ>#o}mDKS@b$;~y=)_QyS!wHIifHBs19&UMH& zY5VQx8C!3ur$+u0l&Dzw8+4DW_Z+|HMv>FfPjA}8^kJ7k?!1@R-fmy>-&%b8?X_YX z|1aHVBEBx4?}CVH;QPsbZ?lUtIXB;2v+eer!Yw~z?Emkac{c5E+U7(PspuaS>PV=H@i$1^EODKRn8;qvtR@9yq4oPAbBU48!NKY80{gKo-N7rWcoZmD&} z2L-d)zH75zlvv%|UvHm#dmHHNubv(r6DeLsM#e4K*WdNr^jf&m;myClzYBNYEqQUl zF}CGju~>KOojWl;YQlwug$z@sPv2bsAGD(S?d|RAHa0Sl^G{c-TBT%WHm&UKt%Vv~ zGiT16V^PRtZ*Ol^{_f7B(_!Gs-hR2g(uPuY z)XRy?d%ah{aiPMWvb9=@XV=B<23=I7smZzf?zvM_wKv|(0aX&WZbf-{d;k1+T>j(d z&&pO-v(C;o->$)ty)_E7gUPz=O+ftWHEU9SetP=KDbqDDBO~KL*4Awn+s~#Q&e|Fj z7uR<-jd@nn{$IbUN{?*Vwr$$1SyEpmrk_4~Z*TR=_3NKMIXQXCv}rHgcqQ|h+Q)73R1x7@8#Kg#ePSm%zpa17iP1&0pjx{wltgNhW?(O{@ zRKEv27Rk}15aYBq>&b>?%h>Ys^FfWso*or#ZSL*2*IwIVS@eV>F)^{^{XN-?S=&B* z-g3IGt`0N<8yVTTf4{wZzueIjqm?hGl)t^@T2^MpaPs8Ik~cRdu34i4S_gA6W672+ zBE4?TwY9dO6Zw-iK6v@^Eal2)z$Ul&Q9ap+uJ~ELz|kI7G%z=4+;{> z+di9_oo~harLeik>GQ)xIZIw&>%Da=3UsT*rOTH)!3XB7QqKDN_3MvczZPk59q*Sv zp0;_eS?T4=mnSb+ps;DvCeY>ZM>>VKV@8_R7b!tmX%N7m0{Oz}| zUb%SIalMM?kt0VoRDMna-Mdv%Vv@F5(%=8Q*uj|38>7~WMNYezVR9qKY>uCL)n2)b zRlhT~-`;pL=ap0L-njnDFGVExfoGEhSRNW(Th-)jZ*T9`F9+IGsiCd?`1kw$;mk@I zS|8pOSQPBO3(9D@x6LkHz8t+}p$6C6TU(XQ&8MF^<6~oE!@UY0Dnnq@fChlQ2( z?|0^(H$BpeLG$lNpB6nVu<-EmvPzzvF7-8=PtV_%_pXe^ykE0s%-083hYB3iw}%OX zMkd16Mjh=GR`2NUUL3wYE+j1M(uE5P-U-XeojW;MedEm>&@pa5e*C!1wPe@byhDc% zPfVT3D{VGm%9Iwj#RuP(J%0JJ^J*684%hg2|Kwz4Mn*=DNiMm$x_s@RBd<~-kDWf< zy<>+(8^8Ry4ngHBD*EYM0<+_@*SUrS1UR_5vQC^hamU@fH)Xqbt#5B<*PGsb>sHj7 zFm1o(!kcgUI65{~>HhrrGd<`2;ls?}Q=tN{YgScPCmYSw(AN*&URhcB@a!v2gQ22NxF>MMXuO=xsc% zfgPQlie_fhK7A^>wkEQMn{{uT|MtAQpq0Sc*VY_-SO6No;$d^{P6(N#Vq;@- zA;Uz1hs|%E&B;ls-W#IUg0jMf4F-L()^34;f`5N~WuCQc@3!3A)4+FY8W|cI&N!O} zIz*~p&h|pqR?yv4A3hX(sj{uw8)v)xW{#Sn;l!(}!xvx7=;-NDF)*0GCu_AK^Rk-X zJe$tL4;Ad}P(^T`7D0 z>eZFEXJ5`)8>U^hTUK6P{;SmN^Uuq6bAl(9va+*3e)<%2pIKjD-$d$c=JT9=b<zE7J-4~ejsh*+ zORuuFHz~yIb1Bf^aLQH&tqqNu9JcuxxaR}TwL2<6a!mz-re>hKqt5LreI2nqPj z{<9}1C-1oXF5af>-kwUG=xuL$Zi6oMSw6px>%hgu?v@1)4p`nkb@JrH`~Ux$o0^(F zznVY&^wE8_zcn;9EuYuDeEIUsOylLQMH|whx8*$i`F#HIw9S7N-){vMh5Nxr#R=#w z`t+%2na|8aUtV4Y^+_sTty~T|-XGLQ@LT?Q&hd4zyQkgVT|U3?b{nrWXml2Q2P?O@ zo=v*t+)0y!_Wk*E+L)~{z39yi!(;vO=V#~dt33C2)8@^Qn^I0nG9OTO@7qxFGDwE+ zxcskqpqay_54X3>%30c^z#;Bj{8K8!OKIu#*M_stim&;${Px>r8#hi|KCh|^v}Stu z-E;i*e-2FcxAQzd&-Ue|rQBlOtoHWy;2raftFNw_b}>+-_0%aZEp2U1ZtlZ5W~+0} zzZZh94DtJ#-hI?5Fi?=KxpBh=gDp|IlT;Kvg+RBVJbSk2@=FeGZtLXQkOoohum>ep zk=yfRj~+eR($==>He~s5)}eLJwlT`^vA4FizW7>oCvW?&s^Uu7_QQ>bAA(k`HEh~s zl$Djm!OI&uInVU^+_h`g9Jzl#zE*#h{rYFc?d|QWJ-36S_s3sQ^d3rGy=BW3(B_6k ziwXo_tYyiQPagU3@Gz(&e&X!erAwByx&U}CruKP z;bTAC#`}1pyPV@B70?k0mc?qUtgIp7;l<}|zZV>rEuVAk{;pkC)AZxd)qcNw{L|CZ zpau`Q)b<@NWoJ`th+URe(f~V>}wC+zhB=Ml(jW#-;YP! zVmc8Gx8JInnob2>&3v?5+_?T<&7C~+y1F{h68$at_vhvB|2yq^e7)@S)2)j%Kx6x$ zaSzZ=g_=*Eee(8wmtQUkUms`l@5f`^O}5a4(wVumNg-w%pS<0fhlkrUFE8Vb-BqG! zYC5&6i)&l%?J~#ni#52+X7kQI>sDK9JKH=Tl;>y6kXW^9l||vB7Aw^)KhwA0Udt8> zx&s-szC%@2^~_vr@dZauq!kt#va+%=fJV_Hbi_az>J(DoEbNy!Wo(0LM|2F2%R zXOpk22n3zO8WI{h_uBn0UrMyJv_R+5|NZ?v{pF>luS9Oh*ZpV&`MLO+4``U`(c{O3 zR&!5mI&*2McjT@T&CTiOpMAX^uWn@(C3{gvU;p`wi;F>*3)=j6&rS4za~sdX;^*g}b6TK-XpbgYzBW`;R1^^t z+mv-x%gNapG>hQr=XVTp{q&B)#~mFV3(DW$i_j5!_3D-1>-U$IdcVA}QF-FTi9E7a zQ=HrRCeED62|5_h)7v}w#)iZrM~{LgakoV2=53$-_4W13>*MV~n~Fd^Et3p^sI_9Q zfd#wof~qIOMQ)1|@9Zf2^7ghk__$kTGc&PXx8idOA(NJfWi!>*)^5C+^V}!JIQ5js zpTB=2_tjW}lGWW^rOD6E%*={D`_`0wioJckqa)+$u(gLWOp1QL-JZPprbXc+7VWS# z3Z|x0ckQxbVq%Kga(R#0rKF7l($cpV{a-#QHL`Z?wr$g1y~^76`k>@dGQXevrcS^4Jfa(%Peyo)b(oJ~9Y zwrut9)YR061r`@xmguf~`rOIMsiU*=;nnc?qvy}_&pz9BH0fa8_RUF0xj+LDpveJH z=H%AjbKt|n!=N7K#EBC@BMZsL`wSBgu}ss8J#}Seu>PZb@I_%UcF^)Rc6XW2Oruuk zcD{{SSGBCl-}!uhch}R)YuAG3me*e-Bqb>=UAh!BWY5CNYFPiTrc}jeTIw{NNYFYC zsopkFSzZ6;llg?>S>!PrUbLcw`;zcPh{g=d=Q%jr$$Zn(JE zz4&@;`N#M5|7XvbAt9y{QE*N0RN!K_k3XN!|9m64->~LK0qAtO~%9Eu(u7b|8Yx(av3#9S?t}Al| zSR5_n^`>V});XQ6>bXm_a_U!@`6S{gO~f2 zyuQY}{IciwcXvSxeg6OZ>odckF*@9uz3iu(S2dG+^qGFBxWxwp3o3JDzoEgUT=F}b^|RN2^g zvTpRY2k+mX2W3p`qQSMGd>|T}Uuk z!T4b1ZBr>;(B!DBRmqNw>(}Od{OnW8emBoNZ~O0XR>!lEPNq_4U0>#Z&UiTUR{px$ zifvL|%d_U4x*gN~aeCZ;<#oOSdMe8=@AR4W^yVg=^$dITO6yr11~H6 zQ^FvjAu1^8?X9gXt*sZgW?vVRbWV*_)7S5xYh8Y6cX_^(vvZ)x(xV=?juxyt@IJ@! zaenT5CyVfeZ`n;|o|Dd5C{=H}{=Xwzn8i_`<@D4a;$Z?Ur<+cuEUN$i@5b9QE-o(6 zUU<+ttEEdHzI?foEuNQ`7qtHL?(Xu*6DBw;_nUhq6S{%m|7l2WUA%4EG=KZQQ`W4} z0j=io_gB}}?q2BJZj^CBp-;y0(DV8AewCG$y}iAl>kigLZ|~z2R@4sZ(BCqSk6m zRF2tJch^SMQJ^JE$KHPa-{0Rsv+}0bV-9afJbWQ*YtHR$ytB_9ySKL*w20q-z8z?P z!#vw+(B8~OW_G>O>#U9f{|rE7;YD}()L&m-`pmbRn?A3yEp&C*#Tz#&&OP?>_6DVo znKNfD&(+q~PrtOp6BG=`j=80sp9dO=I(PFPXgB*D%i?pp@3-^GKKlRf{eKN@ZR^)| zdD~}$%-UJ}eBQRRPm4Yt5%xF8zqiN!m#%n=eQ%hkjMZE}&<>zAYjQrue>T~8b#?gv zN8;D#Pd%Hqc)@~(Q>RW{4%!#JHjImlYsIQnR=Ls6&diX8-~4N7xw)YJY)E*x@+Uq{ zr_1(#HtAg6obK<|D|Hk!1Yc5OqBos;`|aM#FOS^5JzG%O%^>TFMjM}O*U1#C^ou_~ zTP{DF7F=Jp`|gb#vybIL0xkAydwO_4Z9u=}hmRj`|MaN{w7B$M-S4->a?eYwG&M9V z9`B9#Y_|Gp)xE!oviMk_U6V$k4Z;1rJk<%{dW7tn>i;ooq2L{vZQ_8pY?uOiX8v5MO+td z*dU;06_8ZL7bjn3#w(?Am1oI*hGTSlz?JV?q7@e_1mX+YcXX5o^OhTW7+;#4cXE$RlGB;1enO?@AQpZqZI*^%=(Le1?XGS#1ZG zEK#|(F1GsCjn#i@+azV+?7>%w)X?wHfB!5(j?J-^&oS*mD=Pl^cdyh#+ zwAb$`iu!*yZ~H8#{WA|gOt`ita>v~~P(fQ&Wwkl|JgARyZm#v`Gsfp1ynJ~Qba273 zUg?>()n>9*C7`1RLG`7Ji_3>Mo6m15dg=w*^l4T4isgW?zs*6=gxCC?7Y$vDd^5m2?-BhUtY+SMhh>$ z1f5p6`f5;Mpkrd9;=h0YTH4#=H_Uc$6o}$?4FpZyt`1upw3%u5-Ml?}_RKI!J>?oJ zEepBq#B6piXf+gQ+IPy7meWrkfmZ65zrP2XbLl>MX!ZJiURhaM?tL;3yY=@yXy&&I z*uPoD6STqXxP1MbmzS3p|NVOX^Owv1ljqIz%gWOF{QP|SlM@rq%&`n!oBgV6_r%nh z;qkTLe&6f$`~PkEw>EyiT(8?<<$fE`pn_%1j}2A+U+$LQ|M=r^|K*jz>SnpOKzArz z`Mpqo{~z#bp>?slHzgis0}WoEpJ!XRGsfoko6Vq073JmqGcq*%mQM!FdUbSgyeYHh z7T0@X|Nkd{)!uoU!OIfNW`F$lE$!W%ouDZq$q2bzxiX?c#UatH0-g*3V>SYG!6;`pmPDTpM;;d;OlK{(k=7 z-`|5)=Y4#9eDTGMAD>R^7h1@GrjPn9-0v~z$m;d`PT79Hv)JzLs@1EL&&{y}nFd;* zmu2tm?F~945R^hSH9KFt$ar>szJFDfm7=0zN?O{dU$56+-j*8;>Uq6b+z&eBLTz&A zTA7-fnjPP6WrLcVM~@y=vJlbBR#a33 z?N0y=kiT?0onSCw!h{AD&o6Io3fI@yXJuz!eQ@FY`S;;xS$%$$}2t-;j9t#QF1}?x%{ndhwqhg_kZ};^5)}H!RxPiXI$b1kH@s z)cjdmXU*#Pqj_hHUQtm|M@L7(mlqdbe68}EV{tG%zSgy{@FOcP-|N?}K}V+V+qVz2 zatGwy#qRt}%*=&Wb9KEKOzqz^Eo*=C{a&@cu$qs8x%qU%gwu^H*dX_V$9>zpCET zE-Z9r?~}JbcRjw|cX!!aQ2hdGTI}054>W#oak2a73(ou-)6dI^>Bsrh*6!utYqei{ zUsfh8EDRKwT-@9rKYUmaySq$9MI~m7RY=+Hxjj8RI%3>;+ok>e&xztOcF6#m9 z4{Bs)2Q{5Q>n7*;sh8g^6$c${xN+mgJ7u?XLC0FI3|S|sX->O)TO*+9*QKzKy z_cX?>cb}FD+T8-`fAX*$w!H3RZePF*8sAn_Y&>(uhmB9B;?};i{14Xcd?xkm+}zWB zveuyWiQn(n+q*3etljbK`Ezy$2Zx5L|HZG~bS`%9-&FlQ4>bGm@$qp_FR!GZpPqtN z8{Wt<>+0&-@&DiNi7K4_{{BtPpw%s_R%x9&bqZu2!@Ya=SlHNBT~|MCq^YSX(d(9# zm3898i31rXMep~1hfGvGzp*hHv`TBqk|iHN=QC~DVgkDQYkl0_3olDz>i>S#iQ6*+ zR6*~ruLtFg(pBpe{z(2^$MNpQ#$-@ydX7zHP;JtdvbRxeJQ4@4hR4?)oZkhRKLIUx zP1`Iv{j}?Hzqu9fc0P}K^ZBg#eFaY;VRgSF*RM~1e7t{i&P}6Z{qp^B>z}W$|Emq^ z4ehP^`sjB4{-Rh~ni(G-9~YLF@7Fo~<>lq&uJ`xu*;DcB<#PYoT-W~W zoBL~*&grbCp5X1K{bqAAqdym(y_`Io`LJ_4-^Wwh>p?>#N=iz4FP+_wNMgqT7)7j&*0Ze4Ec+ zl)h^jyL`=riOTMv3(`QNfpvdAy5Fn$eD<^5@h(wq&{BlI-|s&^%x`}rZS%y*lMjD; zd;8_Bt*fnspX5BZzt565Nd7KxsavBWw`PeRJb3WS+uPrp%C`OFynI@Jzt8P$xrIApUX<**ET$$dA~Ip>)T6cE z@3Nct_I~*I@nGI|<Ty$!i;H7;Rl4JD-l@~4H8nLK&P<=@xccg&0t*AF zUhsCaNmV^Mr$MV$Zt|S}%KD}F?EBj*kMFpfwf)1r$Rpd+^K*Z;Q#H8(eI6x7$(4+;v>`;o&GIn8jU zPu8TF!M`88ed`O^A^hl3Qdd{km1@vBgGOfd7x(sB-@JJfG;%&WZ`aE;-yM$k%Rm2k zT>kk*cloCa+vSvuj3$8wzE7Px^_j~F6s+%dzX$D@105b#urr1Mv=Y1K=hNw+#Zxa{ zWPoNgCn!3vShr5i-hTdv4+Tt2OdQoqksMQqEF%)Y)Z_1c=q9vRC?t5#`&sfZ#_ec(^h`@QgZ3ir7J#x;$1qYfBgKp zGMMMwk;8|ZK~3Oe()la?UVi;G!(^6N@so#d8s}WhP|?u=9XGT3=9UGSXM;YApTGHM zmU`B`d-q;^t(||Givus~Mz0N9wR$xtA79w|{g!t(9Zg!e zZe8Dm2?7f=eLpMr+c-HnF&U?w+3{e0vjWHSz=(@z5dzCyz;;C@_%fU3QeAA#Am6t^?@sn~>1Z!na$m3(3f=*=JYwCL-p*3) zn$L`e{(k<})>hDL321f2&f@1qpU;{L3kV!|c(}dt$HR6`US7~0w4mDk%9W6!q9V}B zf{Pa~g2(>Py)tKS7Znw?n0sz>U13Ogc=G0(dKCeW3M?!N9x#0U`Za5TxVSj`AM*>+ zMkyTL({wnra?pJF)6>&IN0by56@lv7 z?{|v*yK85=f1Y*QK!WGV)2D#}0T+}OCnP5;gVwVoAMeYmXKZS225rQ8+;6X@so4p- zS#$n;&}1|RH@C8;W@o?szlhk$A#rhj&(F>6?CDXlvzy1tEhZo*cdp<5pGR%2?Of|} zP#a6qIIZB*6Hm~z-mdxgxInS z#>~#AVqhSUpP$dhFL%aWzV^twyP(sklob>h7GLBzaAKlzXIEF!{e87tGB2~m$H&{) z+D=TJ8M!%atAxUfOG~*yhmqahmU}pD^TcV>+-`5nov70J^Ye4iy<$3I-28Sw7W{gD zf3m;b$qqr~6X(u7Ym?4<(51c3BG)8L;Ga)jalwnXZ+mr4r^{9v&ps<|WANqECs3hh ze!nJo@5CUH*38Sx9=?C?zq{=11eH!kX0{Ixn)yM^FzD|==K)GjE z$;+m&)lWes_005n5Bsd&IZRRkZMMkD(%Pi+`N?GePj5D#2hGALpEl~Z`NZMr=?Q9F ztXZS8B}(_a?RS~3MGuO0N|UZRt36%ndZsyua?Ri$O8p zB$c`4OzXR+zVH@kv0vWQ#009?x2~CXHtq1?!;6ouS#fr@IrEC_$+NE;wzsuiTIk$< zUqq&@rDcWj#B(mIuYyk$`ueK$);7J^T?})kh2BdrxUjdn{Oj{|vAb8*fBW<)>Bfe{ zT*l$76WsaFplHtL)Nd&Zu!-AQ-scxDN`Oied?N!kkFm`_3PIu)22Oo`_|XNfnk!$ z){6PF=9`%9g{!sN>UOZFh^ybvaIL1G#SP%2^f^Jn@d^HO+VdOnc=i#&2`D%)a zj=HRhoU+u{)Nm;OrV}~#~q;7tBDk^d%xUL z$hqrvb#`@iby=F-r%rkG$=Q0v#>$$__SJo=ut|!$=|l9Nb(UVSyGjn;zYpHi7ZcNS zFkwOM?{A>aVz0FMJToVa(?+&cUv^x%uETMNH7qRb7-*;D`t|zT?^Uhdb>kmsvhU&| zSDnaBEf+2X+}fI5zH+w~N7IE>pe<_p`+hk23T+0hUCY0B=18aT%R4)byT$cEr&+#Q zvt<3gUtJR>2)x_*TrSYxU!W!aZ8>ZEBo#qvX>T8&o>>}>i!TO5M69^>$TGCJP0LZ> z-}V_kYM{#@ii(Q5Cl<%Y$LH<)nRe;&<&WRL@4s?agvBwV^wXzL2D8tC_S%95T_Yn; z9+$88NlaAq^z?l4{5fchY2BVrUMikR@9*seZI)lPN(*#`#fR0g6W*{*Qkgp~Dng*; zbogHf=l|ao zi;0RZyqIz2l7OhF=#H3mVoQ}+9Dm$b3A#V`P}=6XVZqC19)5V>Y0=7+{j82VOzM*P zbU~8~p35%>1_vKDzhBdQFk!*Mg^b-to5EIyrst%lrERMCn55#VWN-gJJk`-|@|Q0q zpFVxs@%38t$4{RgotUT`p(AFxIOFs859iaH|L%Em>}H%W%fs`p7qPUbua>XKvORGB z(94pbprAvbZl$MZrlIfJjTfKUFGl3bqcFnl)RYmE2^f(M%KFQ$mPqE z_tpO1RQTBK+q=8mCagKO)!%Xg=fA%`Nu_hwE~~OPHx5ovbiVTD`2~}+UmqN7*64D| z%F+r9419BIEBETFpi|ZtE?fwj6zo2FXkF~?6{}Y-&b_@Y>FiF0Ekfd|7FS-D=)U@3 z@_VoUCQw^rb2_LQYr$vF$(cNd$f^V;0z4~y9QKE^|#+x};u3z7NBh5t0HIu{fM>8uM zTige!RiLFu-Irhfi9N2aqGFJH%S1|A`s$^S<@f4A3%IvzG1+=%x{BwFv(N7Dy|X-e zd!C4x*rdsm7eDJ+v3`B}-Cd8bunYz*2=1g;p1JxDXLNa_31`VL~=-+m!c!Cy6J}XO3O6ut8dGdPwe!IsjmaJc| z|Mlxv5m8Y=QPHCtlaGJ+@Zms$!2%7g-R18=BmWM;8!v%Ynogd4_}bd&tFv-CJ3CEG zOg5yQm0A;~JxRr|O?u`oouVAJ7-hKBA* zil-9{=6vRyb1*?cNvTQMy-z?$$jQ&IPwGu*SQu#W5@_7yOO-9C9cpAW>C2ZAtFkv9 zK|w+c{7qD=W)Dg6GHY-=L1p_U-0J zj~+EKGZPXMYrB|nB*$#A1{Y|;DkKE7Xje)~>cZ=Vx-ZnPz9)-lki%S8n_5-s#iDdwY9B!o!0hm*pNj zc<{y7Dlc#Ejkn)M=!mI!9=d*g`iBn%ph2eziq0N>eqrYW!@}B5rX)Q%0b1-@_WoY& z%KLnvBSQpQxR+Ibf0tEcZ*Ol8T3G^`rtw=2I*O2mon2T)<_zc-&)H`|lk5{F2&i~2 z@}Fl^|_etmh_d@i2KMJms$RWV z0IEqVRh5*K&i#14WXTfmTFoYfKVFkAado%d%rPsmitWx>(X}XH^GyQ@9)=rl%X-|D z{gw+azub8-uPK?N&vtE3|K)j!bjf8MKQ zENy}nt2`%t0PO>kUmLc1+W85oJ6(TvKYLtLTkD^FKEXghNNCZ&npYl6FJ}CS{n`v# z(gTjEe>qW4VS9WW1y~*)5`yl80j+CNXoBwk0VxHY3Iz!o4yLsxioM{Z=#>4i#41{3 znmCK&kIgWxhg?%OM(CA_gH{Q2!w%)rQ<9gLH!RZNa9RyDHly{89xKCKw?&q33N*Pi zbiYpqoqOkJ9XH9rPBkG=q+tH0`8u8^`DNM*7A&w#t)IR~;eW}qXOGX_cy_)oJCA>g z*za8;tjoO}mrtBnFXpakKX-rS|M}m)Udf)iWLfT}FZaK@GRo+&Ml!wC?A<-}S+?!g zd@hB#*V^xUgS@hZ4RrPks6t(G>eZ9Ib5qxxUdhV(>v-kI>+=p~q;}3+&3$6!hLw#S z(;b)9&oS5T3wvB*&g$58Nu)!Sz52tG2^>AQmfb0d_Pg@&*0iKKc@r8G{B?z`*bEfz z&DQ4++w|2%_P>-==k`En$F*~phl?6)%vLaKpIyFf`XUA?PN8O-9ecACa+!N;ig$FG z9?zO#sUu|H|NmZ5&9?nbds}(W$VfFhs&6*iJ+1O(>Xa+i9Ure2m#-JtHD}Oe2V&A zC7aHV#utoFbx%p)zb3o-=&ORu*2lWPDaz@(eenyq9XT=5ch3CAc^Bu2T({iuxm%`8 zG~$HL)=!ToN=e1$F}%vrn&_qN`-1M> zk301$c`HLpL9mt2bp?1l%cDX`kjO(BkdXBf|yW zGxo4Z`;C5%!a4u*@l`C3ldlLQTnb7nd{EG+N)Mg2_fsx zx@%6YN_;oN!fpHa!!N^DY}{3m?=E?L=?~?T3of7Dvwv|L%HKlXDN^o{&t?oA8K5fzDhp&6I)@8r%pWps1 zW;xTWYrbhyZ)b?7s2sRb;ypGuHnmBYUZ_*16_kAOx*v4P*4O78_~2KF{XbKaPvP6wYCKpuy>dam#79Q98Xl=hO*RIvQ&l))xOUWg zy?+_z5ID=ITdS+vVe_=~^*t)Lt{*sL`_$+EJC*NWxc(jOnQztWvLVtva4qw0Z^!i$ zHS!BSeSBB6&gV;Fd?52p(O#Wf(dWu%UVRdN`={OJS=H+~4&MKAdb`2GIIA^$=B``I z7S()VNC`K4lzYLX*XQ4qIm^?h%(ULc`tjH_AsxQ*ZXHnjdAcyDZUD6?TQ0BtaO+u; z+&{(s#i?c@PnS8Wi#b86O`D7*Xjr_52i zuqIb4dSQc(_n)ol1{e2Loyw9rdSjsi$8=q|Z?`)hKC^J*nV8rEz1^Dv82HzoF+Qwp z_%gN0wp7PH=&kmjqsXpmHa=Kd!i{A){PVW_`|*GmjK5x%*F9FV}lz%f1a~9tE(! zuj!FuI%}1}xoWN7Dd{=(q8sytz61wk6z=H!a823Um-W?V?c9)vIW9k5bbd2UDBh*H zu2|sJ(%{LLH5>}(GH+ib@NVt#hgngtB@!+@O3mP^lAfE#^;)K-;@|fj&n|}W&-J~jbR!m*FefNp=8z1MLYgp9aslRxl zYF4h^*05LpQ>!?>T@lRqm;WOuB76C*XG{*tjuRr=_j`6nH%`9VTk#^r$)=PcByw(u znMLO((}1A&3)XFXY0>Pabj7{MgKJr5{N@Zh__JsJ$9e&Ef&z1)NPTsgXI!@Tv^YEO!)~MOt685&6R34USKMB39 znO`83b4}=HFq1*M_d}Ob<$Z2zf7zK>+!48Qqw~wf?I-U1iQc;F!G*NVpu5zHiiJPg ziOTMvS??S((8^sVW@g3$3mMR&c+l9+?)Up<$L+29_-uAQXp$Xt*3z!Bx2v`V$H$)s z8KCY9}XC>ZT`8r{R!pOciMCu8zPU- z5e)p&^^Ikgk>dR?Ck>VMd_A*&AuogfdcB17I@?~qRyOy`rkN_8k>PF=Llcv4KGfQ` z&s4BE?AcUb*E9D+`GSJC&egPS-nl%S!?lzzFQTHS%+c+hq;UIm$HV&;vHkmXwa`vR z-hVsC=lB2iM1>aIxN+men#j$dmAorgu6*+3$%E(5k9+IyZP~y7zpG`sn;YBlKH0_5 z+w(jw?L7VH+LbGydtN}xd;0o7tFb^6mh5sB3~Rsrx%Wzc|DQ?b_bQrOT3A4b#{K{M zz4G7B=bhc%j~^Ut-t+xl^`Fo4|1WWF=K~$j0Xi!Hw0UXA4vVe5zkXGL*8ls>v2eV- zEqA7Uy&dS}z>mk}-Pd3LUnoBVlD7&XPp{5PId{v+P4)Mue#VD6_L1lBPkU;la8G)( z-?2+ClFoH#ShdeC*m-ctzFK%B8Qzv9rxaCJ-ybTIFxbeS3bL48+p_Ao^d|y_hJ3ipEZ@) zN@;t~9DBSqMI_oX@aBoeM{JiC*;FPKu$V2MUHLM#X4m?r>FYZc??n4=>7VzdhWn|Q zy3mKW54GZMhMY-mR+hMVZo2-raHHy6#ghl+RM@Hl5(_`L1~NC_t}wRLmDugQaLc*I zyQPU-0b5o*C^$V$cTL2`rtkOb=U=%J;^pNv!zh*O_4W1XUtV0akl_Q3-kqGR?&;~d zF!^{NXj|&UiH#RCRE&)$&zK=m_2orl*y^Ks+c($#F8lQPv!}1Gs;TMJTeqT&jEo9E zTbz8nyto7f1wAIU98Ch9Dm-QC)Rn*IOBv|J?U^xWj*Q)p2h4{K9lEhKTO71>3v|@B zsw!wp#=^@lEvmj~i_=*EfrCp zUo+|b{{M3AhZ|3(faY@L>wYlyx-}n6IFMqr@~wc1FebbL2YxZ+h&opIsBCCPHzudb0zf26Eu7r(A3 zDQK|@`hH^q$F=8I9+qfZ+~tawU6T~Ch(Gl!-!!>ZO1h^eThuc%ER3`I5;b#)0{^0# zFRw}&EJP#9cFZa}D0kwJh#z%auNP^5l{wOBe)%gbv-=S$y$jiAL9<+xh#?o|>xd>F4(hv>N&2ad~wM ziy0>;tAmExnV6U=9=3|lFiz)lZs&XY^?H2z;Wl0sRn?CN+2uccx$K{8HhbseKI@MD z{^zr@*D-?DDDL_BZ1$O%#_gb0_cLak03FGdc4o%G3k#ham%UsdZI+Ypg&~ zJB!nAZOa86$YdzN13F0g-QC@f-@ZNDt-tTYY5n~^_xIIaeSLd-zP#JwiHV2X7!%B9 z?|irGb;Z-E;Tjqm3hL_Zvt~(sdvnt{Awgm1&YdA)VVla|$L%VA-*@oB7w_lKpR*qT zZ4&G6=YM-^>toPe#RdiwmU>VBbS*mn;>uw4XXodi_cgzJWJTa&&}t6`&=K-er%qK- zQTcE}xqk!bcD~A|qR-CGK3@HPFKEKo!^7jj-|zR+zrDH1aHsA!=&sR%uUEq_?=H`W zw+0woeo9r;6tEOu6E}LY_b^Y-_h;+2BqaUiNtqI{a{KOz2e*YQq)j}8Y;1KVF@C(K zD{Xmt!3-7CwW)vpIH>wle@?n_^$R`RUCq}1@nR`KHYK;^zhW<(Umcoi zuyW-}MFjN%FNb?~OYT z+GG57>vhm#o84t^mCVhjgG&4AcRSnLXtp6LCO$M&^xnODx^9fjOiUZ1*0Qj$6ddL?pD<<05oUfHhkiL*&`jUUD=URT zduKm>`jsDh_s9{Kv~zP#f_5K+?s4mpG|tG$Ir6sb^EvDHpx6YR4QY^f#{#rcGdEYa zYY})dy#D|9de9k7xwp3^Zj1nJ;?B&>oHBj-=d0oIhPk&)K&OM=+M0d*`TY7m$kqzb z!tBh;%Q}}>K--UAOloDE4{qNUo>}I~!QL%BV}nMwZL3%cXOGm(=v^#EwGI;e-?$g& zKKS|k!~|7~xSJ>L@le+3eV#uhGxjbs$*( z^U;G`bKY&{k(%eEZQiS3b@zYoI~l#!U-O0Ttg(pltzF!$wOZIU&?4^YiD|-LpMQ_@ zTyWXrMl9FajcZS@eNdp8k;=(g!2IxD)jvyvIZxeJ{j}RG^|MIU^2>)03s$Y-%HR^| zm$g3DC8{l9o+krZRI_2jgzxvN`}^(x&FJdlnlx$BhTCsvoJ}kBU34?&*vpcjfB?`k z>Eh?-jwToc#Krj??~}dyz(7I4!FIih=ac8plZ|F}baXI)P82)(wrut96PtSC)-PYT zZk_A=wPD%|7cP9YbGG|eA2ngW<(rT0b6YGp`)u3chXL0$%uGxcT+BFEeOAfj@1cGP z!zR$|RsH`u&?dqynU_Ip2toVOUS3)XYHs}cRrRIHHf*)%-Z=k)g3C|py zl|FIe#Dj_Ma)I~Po8PNo-W%s187XO7^(El``tGgIf%z;oBADbLsE-G15@cX6K1tO*Tq+KE@pd_8jub{J-< zu3D>i$17MMGPyYEf|o?8P#)`|nlC**pN_88sF@ylber&36RW$=ZRR&QEr{%%-`=*j zRcu}U{gU|o3I3L+`$In83B6b|J+fEFd(YOjA+~v*Zlc$>e9pBj{mN4!)%iq$f13Q- zg)7ea?btr=kCgRst(-5Nyo)M%j=c?&nE2xKiL!lr_HX8i{P%Rhx0Vc! z?O>kk=bo9VdAv{d@vhhFtk`tC})!u!sw;353mn>cS@mh5L z!`H99+xcW~>@L>_oj>{QMzV{$d-B#O!`Wwb-TXo0rl8UEkUF`jvL}&0j+d)TPJVU% zVUV`ja;Mo0%nK4W@BUE|&wBf_&W<}=f_7Ew^53_wvz$;g|M9=;YZolc{JX&K^Zn}F z$$n`Si}Z`PPc=x7RZQD^=J@W@l5-w3@Au^9lF`X~u=VUwk?YnG(}RyMdFLpx)o2pa zZ?4(fG#lFe(h8*WRa_4~&C^<5SUla~+0yBWS58W7HOkM`e)viyzHeHzWyqZa+1HO6 zS}e@Wdb8(?(Y-q{K4GoBWsY*b%1!SsC-WX%tH5Yp&Y2RKW_o#_<-rZx?$ykX>YwWS0oT&57wtK8tku)~@~Ni7j?HWvQ%(x~``BL(TJ`km^=r@> za>2`d6crU4L5lyZSVqADUT{`u|p`=8DjpP%z_ zhVfQL(1!4C{e2y$pDrqXelF+sHecOt!<-uic0Zp8+t}EET7OY%r_IjW^>WKL&>UEF z{@$ZKlEwy7y-SuXnJ{6(0g#(PxAE<%GzMKcc>n(YJq~@vQo*z<>{C884(?AMGgHSHh7S z^{cdgen1V6)HM0E2iI!cxn??HV49dps?wj5em^Los7QW``rM-KzzP|qR&*$wy_es6@S_L{4@@jZ|@5PKIJ9f-y zA5*bL7S(R`Yum%Jb&U1D%UgUtj<0%gf7;e*F4XweQy}ZP0lE z%a^NPk1d}&>xrtic6aXWZH9@5SW2v9L50bSi;H{Y?f-45uP`z)irAFG`Sa(`mbNyd zvNsVy!NDt6uTK8>=;)h!duK0Lpa5EnqNJ=0+K4S-Ria^NXy|accM7qVQFb@ z(DiZh^?wZ2{pT$Kxp9t#VE55O{r3M(v~UU=%&xD`6?L=_-+k9E{qeD0|8FlYK6dx_ zKi`|m!?h1u1L3yk>e&$h;PW7iW?$2bJ82mo# z{HBr(M>jsN`jb?=D^SGXRZ8jp;PaYum!6&f{khNI4VHI51mAAhb?#$NuGasvT2;%o z)%}}wdhLZ5K?WJBCX-68{jGax`)g}dY{80soc5)^ef%Ha$@{+EtLIbp_i&M?;`5p2 zowpPJ{3AZ=`S&gVv+wIW?g)5O_Ipn9@jly^PoI8OHrOz=u9mZ@VCDqS9N;upSJ$)C zR6IfRbbnrMU6r|Xxm9?==WOQb@w@;3WC`7GzF~JjaKO21=iX<=v$P#rDk3A(b1>n+ z%aTV$JCAHha}@YDMaA>iRnDg@jskk7AA40foK`Qsm~kP)WdD9eNSDh|wXFd(q6Xo? zThgEr=$xpPi!?+;MFXRvx|T0jH#9WN*c!#f#l^wFap3Ub=3l?6{N~woUVf>fq_oJ@ zuzL4hzqmL#MMcGms;X7FVj?07F2Cg9yXRlbHu{Gh(O3>Ajx8JI$t26s{ zMssmNkI-ZLbDKY%{`}RvgG&T-FHL6!&m1r znV6Xs{rOQCqbI&`#K*Vh8$;`)vzCEnbW3c0BobiByb2A)}` zTtH_{zqzsT@s{gl@9zBUeqO%&E@+br?}-MnlLlN|To#p|mi*cbIYJJ!c=^c*LC^-w zx;ne;YiklOFZ11T_nq;)!TTB)x^VZ-sFG|zqcq*Ff}z@vV3{+_S;t#USC+~{G!APw68KHGVS@fxglX; zLSkZUCagt}8^fx;z3IG}qo%ITzWVB_wD&fZpHw6zB|Rp&9Pg9m1}7YEwZ$WU5ZDk3g!oOVW{#7eeXTpv{SG&M2Z+LFn9;NoI;Pd`7<>cL#;SxmEughi`4o2Cbsj(CE0jIvgahtMoN!iOZ2A3EOW^ zROzh!{cYmx+1^=MtE^6yZ_Au%&%dLlriO!uXUUEoKem`$H=E7d>(+cR!)5v9g%>jl zcHiA`HxINq^H{HRq1D_UzkUgci(gMVweoD5Gvw&7mnAPstaQY<%XZ7^uU~e_OKoyz zTN|5>n0NPeKi*ByR+Qt8KU-EkFfcTnn7w|l*y4*PW|?N+*pTSl-LP!uhTVrBCOkhk z7qpsjxv%(CmChqaTtJ5&cM7W)?2K`7aao|j)!N!B(d!lz91QA{ZPJ-Mcdl=2tZeMA z5=BG9i4Ptm%<)r?jEn@0RZX4RdNgU_k|j^Vy9?#m)F)xPn|k- z%Jk`%7rXbzm(B~mZFBj=0tsEndLaRpg;!bH^`>{fy$!m^6m*c!kq$x7=8s*wtV*qx z@87@QO3FOtgutXplUA%=oqf@-c#`<+v&WA0N_Y169|teYidri+{q)klD?9JzeW|jY zq_Xm|^W8l2Nh%AkW`S1fTUyRkbZ(n4YgW}g<$SU3)~+tDrluyp+i50Jg}d+Gcw4sh z#CGs{K*zVv&d!G=Z|CmoJ9f-%j-R^Ua^cA*TMi~1NZQDdurErk!2rw)o)763}@(pv01ysoB`rSW!{I;OiLg<;8WpPd52zmnf*W`{zgD zq)C(fvQOuj35$vz1?`u3x9jzZQ>UB)ML?&sY|R#beQj+p^Xirs7A`I>P{;M)!Gj{= z;)}0ld3t(+whBCYl%%Dt{qf5eo8*G4Hd^YQLY0-3Id^tUJaowE(BZ?Ky}hdX`u&?W z8SN^2%hfP_y7<3;|3GJd&-H6xzFhs@y?dac`fa(l7hTN)?ebK1@3UFUv19pi^~T1= zoEsY$n-4a$x3fDsI)e7_rJt8uwQ7}yw)SH8emRXUCpR~?+2;9b=H}sR%tfMjKUQmj zi_3ySk5f0F8UEwk8KdVn$HFl%P*80$=)CMCv)Pt~k5~>KJUGKg?bWMSCr+IL72XdX zBs_Zj_^N4bZ*Q-Nn3#~PZ12^qrHd9d&6pvfrLDd3W=>0cdvI*5Z&;YvCLQSce7wB8 zDQRg;z6U`EC#P+mdFM_Hs5;=`QBhQET(wGTj-UF%g$o^m4^%xm!g=V>p$!o_Po6$i z^c1qQvvYBCTXZ#R<%$)ct8{MMi1=!yE5ID0z4tmgI2LH`e$^?g{^9#~bw$O6 zM{l`1{xAluJUVsi)VwbrKmH6{e33__aS^C7VXxX7z3lSKwAWvMiB3|fjD2Ot*`zSX zdo>pytK*J6H{X7xwQ=;kVW zQ^ODy4yV%!yAFgLDxom7~Hr&j)a`o!Ur^`WeSUD=5;DrWjPoKYj zi>pcD&rHp(rjsd$Qj9t$f$r*i3AzXiRET`s-ppU>J8Z>hFjiu89~Mfw482Sq%nOL z=yk>*M#^zxi8e zHFwI?si1Q=+uPZ#%ibjX|MwSkl-7+KH|*EQH(i+3c_2Wb<(gAXFlSRiXpIEi=Lbi1dW&n1PFkd3y+R;zPY#e_nzCkMCzy8+Sq*f z{{8sr)35pMw}YaVqkc`8=~@$O>)Gb_YlK%{UA0fmQwVe-$JD7)LE9B-{(L??H|8{eR1>%HQ!g zWM*o9es=cpfkx(*mKKA|ODaLZ!HgC~PdGk5JFBd(-~Zx8hMMmzm!zaes@3r<56>^z zvSrGlLrx4@+S*qc0w<~Dm`O`YN>)@vdUAlB>!|k^= z|Nnfp`S;^7$O$DcFSTykWF+FMXkZ}F>(+ev<&r&nWP0740|ElN%~r2oee(3_!zo6B zva-E?%Y(OD%J6;q{CVZdl?TtC@9*f~*mCQC-Bs(keq7w#pqpK?tIjx{R46zWbQ@w?YDhBJvl)~7q+wr2nk(Mx=^+=Mnps; zATrX^!GYo7!-xB`q}D=$d{3BZQA9w%fk#KXL5Cb$TFzXuL`6tg_~XZq2eZJ37~g)o zZ%J*<&!^KXpH2-wS6%NKSn=_w_|H$L^(76HSghvufsVZg-AE&KYqr*F64SJT$^=KZJQxoFj@uAiTue}1`qeo%Du@7>F1T7hqFtJ)hU zTYLHSSD!f+6K`%#zq~%)e%0#LKTpU1JJiA{tf8&VJZs+$ur%=YFE4`*n>yUipMGJ1Bj^sJDbuGj`#L@q z_P1H+mn&*KlQ_Y%Lpx(XW3*z^D2Z{2mT z7x&vGeRyyXbZ3?J`aP45c8jl8o_+P|)iZM}Cp$K?t$aSyG<({w50EPr?U#p{7I8dy z`t;}$&&e6Lx8-(UH#Rbgve~Mqum8MVzHY&a6&+i)nEd+ow)^zcq^GB*f|6bR|GGD2 z*5PrLs+)8+*ZeFJaZNnZA^3`8_SLM^v$IS=vtTVPEN%Sqebc9l&o;|-nwHwt)pg?B zIW=9~-g|qiH>aJI0yQ^PJQrnOU&kYF7gHN_@5YT2<@amdm;24lxV+4_yKY^~&PkwC z&wAwT=S@_02ihR^m9*UWv@$oabY3oG^Dq;w}Wmo*iiX7Ew17r zYa5@e*Z+TiB~7zLK%@J1cJs>K-ZD%+#&hWKVNV~QC(CB%J^JzSammX|PlM`PnhH*` zva=Uo_BG#_aFD4_&i0lPL&@&D71hc)W_boElhXy!vc#FXU{I(xG}Kyg{r4eJHI?=cU!`d4nffV_B~%N zd8??Y6@hMB`}h0(=VQ|O1+Uj`H%d7nu>X4C6LEF72xumA5*8jeJ ze?!z-&~YENze<+*&p)>z@o>eXPW2C;J}pW=KM#~3KqK%iEi6gN$(A)g3_#nUF8kZ7 zDkwBOpI6;i`uf_%&FTJe^?yst-roaX!nLRJGw2kcW;WhM8#hkmxBJnsv-o*XRFqe4 zuI?n2z=((&NwpJ>COw?&Z+9@kU<(JIwzf9tHdiYvDF%6Y`9p^eSyX;fNli@!EtQv) znq%HIx-_e19S))XehUu&M2mXA;xUGM2(wySLoiU)J__p7x;$D2wW4Yg43EL`@`!%0=h1LC@ zytug7W73hS;c+KhIE7!_-mZUlS1D*$iio&4sC-|vXc1@z5p?DJl@);@VPTUdPIPp3 zW@fOO+XtGnkBioW{w?`Eb?h zb&IOMzZ18)==^!gdeHHp6;)M_igv!Zu~B(*+S#D*7c)$1{(ij<+KB<0Mf>of0F((q z+nYg0SEzVCTDSY%qifOmpaCyXktC?>_Tl>ee^U>)^Mi62^uXu%+OMMd`+lnZ{q;5Z z>?~88FBhDBW|?qqzwP_}-d@m=DFr)YK*e0n{e5#mw|K6N-mc^+RR8mIeAJh_=g;?p zwy)|kMP@|(U#!t(b?Amo>6(oj17#h5nBLAYoBiw8BG>MS4GD}hXU+s|kk6W+$>B8l z!i50P@ghyE+@LzSuC5L=0$B3;+S{gi>PkvY;&By?o|Dx!*8D8m_xD@0m|o0@W4+Qp zUo7s=xWDgj<<`X-T#Ss2Sp`#P&GL$fkWlxVb0BML(bdrKjX5`s{(alNe`CQzCs2sb zFiiIF^aS<6pFDjEO6rRiEn?{C?S0y75?AhL)o}6)Uai9P>v8e}q&Ohjy1n{{mpq}nSf6$g|(1CP+zg`E8Ki58bTekb; z>C=xd``aJQF`GPTQjw$eY7MS=^XB=?wVDdK8Q0FvPIr=_M%ST*&g~i+8VSF?yo4Mk z1Ul*I`t`+Ev#uWDii?Z;@$1*B#bxv6&3jQ|^`^{v>C&YlqM{#fWv~Bu&iZ}A#)uVb z)_{)P_nZV8#Q=BTKYaTZHh)ow4TnYe~bTBbg_Q z!43K!#Xrv;Ow7yE`~B^$wQj(~)S0tpNtxx}^9c&N#CqOu`SsT?@9q7anax+P2e|=e zb?v9`-;dwE{X2YV)A!GxLDNWwPoL)YoHWC#RO{r)lj0^Xd)wRDrFz|buRJKR+8Ch& z8c#8sJyFGz*G86)otKyQm6>bcl{0J4dg}lA`*-EKb!zJB?R~P=t6uZF_evc-*v#%) zpuv$YASQNfZuz~+K&I)v(&nHY#m0$;SU@Lye5tavv$Om1_O|%iFmF%KPAMNt8NSl6 zhRZK))|&W#{_gnspSs_i1#8yyw6w5@xIR40Z|}1BqCxsO84=gSkB^Rmjwx;B76)G& zBqHLHm!}81AfT-+C^-0VbpGDflPSNhW;6Qx>J|U^khmbTHZ>zdRJ>K9`0US zySM7`m(RtZR_oQ(;ge^~@bL2D0-ag4WC_dOxcX~V4a;lqcZImCUnzl&a6P%J7g z28|%-==3~z06L1zxnIr}bRq#OE9;i*>wPCqc>MYECt`P*uB>%g%K3S=AWJ4qI`r^x z`%JT3srP$6^WEKD4!S@vSfa|x%U3vf4^S8_*x|;Ee$$+ z%hAyhbm#}@Dlbqmm2i+ruKJB(-maHwUf$l7&*zr!`1cEZ6psGa^$p<0diuxN&CYZE z%Fmg$p1nL*FC%?x)SEJEe!CwEYLh+pW`a&iHI(3amA%-lH|h1YwRcLd#~wL$EGQxZ zG|iB4f1hpI=AB2aY&YLrFLGMyq{)+KT9s;TPCJ|Q@zGIGjW%i0p$10g4<9}_ ztiQhcuhrM*d3SdiW?$0*ooffW_x$VERjaf>#py>-t?_#O{y5ufDMlYbM-0t0P6t(p znVFiP5b>XHH^WEm-R}2xX^>+rHMF!Iozh;v#JyjxqM`z{!7{Gqqbn#AJ32C6k1dy# zwJtmIvINwc18w0?N=iC$_H62x7Z=4uY0-H(~;tHtP1r zE8kt0Uk06dx{sZ$nX$gUe$SpgKYri;?^jxC3aU*0d^lY4@)GOXu+yMJ?VX*O({R`wpCxYY&G1taic`98y7dXr;m@z>Z?gxqi$|a=VxVQRZ&s7u+W*Ep-;}% zD zj6v6+Bp3+D$@MMLc=F^)f{9dTN5_Gzt)QcgzEs)P{`wM_ziE!2`tNUVm94CPZCSIh zYte+cbI<EUT=ZU&unRJHj<*>2FPp>{r5B^{j} z(D?_Qaye$wzrVeG{N&{1Bgc+i+L(O&erdBd$8*qOrl5}hrAt92RDGiT0x^6c57t682N9twtrfq$!4YH&qH zM#|V!2yDIyx-f0!iWLsiQbDUzB6LzxQw4>DnvN!c4hsG8|vYierx=I-w4j~*o*I&|p8ot>Mlp8mVBG5N~Xs~6YBT7Rux zwrEk)oH;U(Yur^JW21`}FFtwdl#;%Fzgh0B3mcPB<0cHaeU%`UKzIZ!VgYWf_s0Of~mU2(w*4KC0)&i%UIx_s@< zyiB652NMh?Oq=GGk&)5;wQ`cm#f{1C8{hc~{9`v|Z%a*01)XB}8DGh zKCR+jx@F6hGiQ9Vva&!!o|cx9_4WTh+xib~}In+^O2(|JDU@HYso%ni<>k zTgx@@$FJAxXPRbB{LWU1CV6rLo zH0UY^(A>o9Yikc@ZM}5k#*g-|&H_=Z@9&?`wyZ@*PEKz6uP7GB9WHypUnC z{O$@+7q9Zu6K|`f9s(>2w-)cddty_~xt^D2Z`b7?*pYwV4pfSQsv5&&wv#7Mf{yny zzh5&MG*|<=MPkQ}9q~EKcgoj4Indy@Jo(d;lc4K#KnRpZ;2JqOMmWlt7zftxm-;OF@7<6;-LGbw6(jB9diTC^&EKp;o;#k^X=u=hI!Ao ztL^CR{Tut-+uvVZU%y{7co}E`-m00st5>c1@n-XR&;@Vv=FM9ZwYAGE_tu1I)7Tu! z%FJ$W%U$f;&R6p0M&R4{%1TSn%~00Xv!_fEVdIxuGflDBLT1VG<(qSFo4qNsUb%AR zm)F<*b@x9l+6g-NGJ0Fi!K|&97CN*4`u@Iu`EvEESFf&#+Z*M(Yis%YxOMygRn^qg zboBS(umKbo~QC@9D&AYj6*i^8*>-Iuqmn)2X50uwXy)$7H@#epJE_slC; zx_r5?jEs+u&za2Iee(8xuCAc zuI_G0vz!~OCtp8!{kl7B_0tCjn?YxfKY5a}B}x}`Ao65&e>FS1d0)PifYv*{+wqvs z)FNhkp6q0G|7RbM%ZE2Ry9QP~o0*=Gk+ERmLdN#P2W`LKIsE44=8Lbtf^Mq#^E&>& z)9R~>u4b)VvnJ(G3nyp@L8zmp(SrevhHJ5Mo)l<3K?;h*c-{;}z$Ot+< zev{7Q0*eh%Ye6?Z)c>miomf9jKOVI1XJYEiLx-F|gQm5$wp+Ju1(hGir1K5R-b9GF zK3v>y_h{pBxyAeT$<6g^znk}b-|u&iLBnv}pHiDX?0>iD+~**Xr#=7whV9(Bv*zNJz+*8)=(w{`mL%{ml)D%~c==U0UjWb6f80U$bt%&70`>^~&{6pFXWvwJIpS zd}qw7!l_fIK6(0dZsBZj8|lzd5izl2prhKoyu7-9&v>RYN7=0>;mV3Y@g+G|9~^9! zG*08Wy)8HS$%%=e9+;~4v;-3=Q1+{?5r%%J*}1h|9!;~N9=f$P`{niZ@}N$6^tPOxSLF99{8^}X>O|sny;u$o z4vSo_Wh$O7?(YAemF?ZT7jz2M>hSe;Z}s;s(wO0+1{y$Ku|mPXV8Wd{F*#<^QESCK zCxLFC`2Fqe;kRXzCrxrnN>Y0I^l8rBU7`(>CJ6}%2^H+V3o0|3nwb9l`SarPa{fsw zil>eImQM!VFc%Qea5Cjlm-e~?$B#cxs`mrWwZuGga&iK#^Z|{}%$zyXV^T}l>eL?} z9`?vseroD1`1$+y=V!C?i;jwhZzy=^1X@JBr}8uCfIffQuOiNEJU>I<-YvVG`{l(& z=7#9Jovol#M?p=zvbVPsPaAFBx>d%qs0DPZ?W1me(5-MzPE4So8_=br$9g1rq|JC5 z+~sRU?CtG+)P(c4OHV)T`u*MA%5OK*KYsdj=w-<~G0;t6pp`nOin!5hf<6Zjb>U@ebHcKWCS^~rG-VsbJ4bK(?EBatc^D3VPlpy&jX!} zxi)(Hqo+?@gMx%wTU$LQxwyHp?cBK&)Dc)4y&ZI-tLa5IPfyP3@9$ENbO=6q@?^sF z>FS^DJ&@#xtz`*Wg zhFxWEPwjrckJ}{Gnbkn5cYnoSsb06(_za_&9bH{XcXySBgoaL>KcD}=p;m6t36c%V zBr`9V6#EG1ss9h=%oK4|)YR-e+|Dm7Cf0T}OV!YD;+;D&r%s=qm=beotWOI=-EOG`_^j}M7T%F634?E@#NcurDz z`Cdp&>{v6uokvlT(b}l3yB0PsT(t^xPU?{(E}#zM*;%G9Z*EpEEG)E;;R78b{qOJZ z^_JT|_N%U zu+T!rB=^=7PzUqlBiB9|%c9Nt(IBrb`Jd>-F~=``X}zMN;+E{|e0$^KGpmnoJ`=z6 z@=H)hJb&+3F;FKYY_+Hc|0%6&oF)=HJ9h3&G>|xP^5nubYkW@MEznIrKhKrJF)~u} z_O@K-KoKdkoD&xpyMsz8?eKLeA08Y84SFA!tL|B|M(5l-Tkpkgy&pb&NO*p3?u@f( z4Ab;ty{@l|joerBQ~urF5RukHhn&vMx94{N4LS77soL1gdGR7c$~+IWzvySE^OaiNHI44pifVXuW#!>EUg?hBUR8B#{cwKsThnzqj|ws?hEwOH{Vy-}ifcZSB?tCTePJz0&5NJ{;y}-cbAdn^)4)kX>HI!UA;LJg798tma#1elziCcGsc_)2AN?bt6G{a#wy*>Fn$T4S=r- zU43b9bvdZf5Vj`5F)vT=>+9>!KRi4fv!`O>`~Cmt^~qY_d(+>b^7s0S`xTFS&GX7s z|M9p6a&U4gT3GA|(n@bW=ulB%@$cV17B)5^VPR(v4~}{B=C!o9KYsn%yQIX#YA&C= zynNKwtfhOOJ$>4`e7U;sY%@^e1sxV6DA;&5O<7B;YsCr;&|)Kgesvq0IU6<@yeYHZ zntlD6TCn$|FW-xI#_TS!x*O&iD95(y{+nMOlT`l7g@&Hj?Ar9&et&em=F}BOlN9aj z;$AOY?Q!H$(atUZ*WD14lJa_cYwP;e=N>+Mc;x8OL<0$n;%7YS{_|WyLqX%cPfkvT ztQDUwWtuf%+O%Uki=Tti;e!K>61{Gqv-&ruo%QhaWK8(*;NX$t$0yI3<1^bV7c{B& z_4W1R$B(z0<=zSi2sm(WZ#C!;(yiIopPeaevNZodT;`JbPkzq~2c zdztTSx7A^5LC4cs7Pq_b)&bX{QmZK%G9YJ z4>0q8c(r;xXjw;lJ3Htynf2F;UtQ4zrOAK*f#hR73qi+G?(AEy@^^h;OH9Axq$SZU zck||_8AzzTD%sWRaOdp0eihG4?+dNwW|vsq4VmlrUHHN0@KXX#e;z(u)U_z1Gko3j zRjZ~ciKsQx_jzQm=YbLa0Rb2C=XJ?`q71(#*kDkRe)2&8DBpsZo|6tPgwQIUlUjKA zAUPT&l2V`n*Pz(!3}y)>y5YH_Ajm3eACC%^JKhL zSZF907gvFWjQMA=yG1d2;;gK!7IV*e-TwdXdlbm9e||<5Q&e8o&l6|@o$vnj^>yVI zh6684KK=cEfAWM0A7&q)bv8{$>~!he{adz730&+}_~ODs=`vrj?$&<0Ume}z`k&64 z-~aIM_xt}#i@LhIL38pJb$=?BzU6Cge)K5mZJp=T)bw=YnjZy|CQZ`H)z{SpO{mG( zRD4Ja2iJ4|OhEqnV(-s!5H$1;8mK%uS-tVogMy2TTxG1w-kA5#wXe797Sr9e?(=T& zdVLLD-MtI1FTMOSK&17}n;gCPeSfB(DzKV+ea-DgW_Hlp?6B2e+phhrZ$11_!P4^Q zs^6e-qxye;cxt{=-ksZ&diurn z_3{fBE(9&bUbINbs_ae2A`MW3PS(2Y!Ryzjo%wBDTwGWpBO`0R-z{IVbZOxJx>`1V zxt{IY&DH(qfsPfQaW-vN*;}vg@9y5*QK+1mnHjUMW~TM~J;M6>`d_MSt>*T9`BHMP z_IvE7PoF@ug0j|SEDtU$bY2s;SIRZ;#r^&EXJ?rneps-f>TA}w_xJrhJUGO{ zczSx){Cc?@)Qo21mHP1Gaer_~$dT{&>(^f`zOcZtqPjYK*Ktlx&OaXx^UpY&W>xm) z1f=7_b$ffhd!Ps?_n)4oD``=nAZ?ZdTJ3Q=Z+GvZLr$Q|^Zvftl|f6n0^}~_-`>^> zI^IuCu5bT-d*9h+ss;uVN?%_~+!)c((UEXrf#aVqm;FHp@5#%{gBJR{y0-T8x3{-9 zM(BKbdAa@4r6AC4X|m;a9MjU&+WBM;<(N&LHOp&z-d#}J-0JTC?NhB}_^z#wKR?g5 z`opJBkKXNmzpU=>uPIZff~Mm@o!o>31+&?_u7Lthpg;iK&g|jAv2^Lu7hkJ{goHpl zeOXyqP4e%}*|Ec-?#Dy+J{e1=tyx!h{Qn0U69k?6rRqH`ARqvArTUJ~=d1%oTIbjO z>IB8%+qY*II5w{cUoR)t-8xk}9JC22>)M)=pkuBb92hz~J9T1rffnk3?wNs{+_~pw zyfvsnC#GD@r|S7DT)@d7<%Gbc%a?z?+x>n^f=8f;ju?0Hv7U#ATDd`va_tuT`0=>B zu$Y*eg9C%3qazDDd$?Bq$45s&X$CYU;oQz=Sp6;M)924W|9n1w^4vMLgzM{K*F&r{f=tEBrkKLaS&U5_KLCYs*=kL47RMxQAz27MBjzv>b6Ld5^`FP*NS+h<}(G2$R z^i)()X$f5&W?1~p=hfBK?Pt>-PYsV#R90@DYhAvn?r+rsn+fqd3KVbNya~D+<=gH2 z$3;6q8-li8k4wJ4uXgIa+t6*>7uG}?>xgkDAMXPV_kltXR08fQeGQt2jf<0GYi{h7 zHa9AL6_RyjMdQ^h&^dj3Gid>pyAr$XJ-y3ZTxU6 zd;P@Ov%M#)`Tls&%ztBFt@ZEk@1MWjet%iuVz(J*)71RtczAkpf)){IhphqK9|g+7 zpnF%%W=opq$+Wh%nq*z+h}@jEG5L7k?7vedt9VYzbKLYiVA7I$Rz;SJ7cYVmjb`w& z3yWO2ORQwA=JqXi@1Hbp-nr`cd)*@6wK9CY zT<~%~(80U0yUP|{e=TWSWioFb=q|p6*Ix&QPW|`y_vcru*9S#JtYA%Fwd&H&;`Dj7 z-y&uB*g-w#^Yd&|Qd1L6q%top;S5_n_37#9#UCCxg0g#T`Q4+J{q23r%FLp-<*XEW z(c0dAd1rAtDEES<>}r3REccuH==J*j%c{P?~7ByBdpq`}ToutKt^dyRg`uUraaZ$j!~^H+L4R=ib_)Xkrqw z`tUT}=oQi1^_1QFjyx^eSp3{CB-E9|u~*6z)H>zh;dye9UH-|v>i17Cy2~HU+WP4L zGyjIv)6=|c8%5u{9^IRDV}l}SC`N5^=aMBVpx)E(Z*RBON}FUHSiOE<*XgH=vaYTI zEmi*Vr3B>F!~FJVE-ZAmtp8`TyX@_umnEC>?pjrSe|L8Cc{}gAI=kp?IgX*BA)!kn zH>ZI%oNwA>1iC=AU(R+#*jg#ws4WlLBoQ)CyrNFRyI$cF5cSO+S1<63~51z$1WCdx;<_B^!DAmcZa>#3SIS}Xs3pj)}^Ki zn>HDNwqDKgQ{S3(^-$8rjDvG7W*kWX9cjwe%((or=h<1Not>SDPftw+tzdaRzkc7P zpPxT}=H%sdU4D7ts#Rt#o0Kk?N%c;dIyEpN!XqU`g+WeEuBEMQ(#)AZXLr|bc)`HX zz~JfP7&5=PYnMisQ$~gcD=X{NxcwC$l{TlJS2Hscn|`|W^wUFWn?Z{ZBO+#mwZ*N! zzG>RE)#2+e{av$S_3YWwY|V_DZ~D~L?YlPnRf$#R#YL=kc6Ok_LeQBdfs5S)g@g|6 zD15x*?Y7$|&YypNJAePu1q&Li-|cALumQBQ2DD_Sv$NAehA(PMM&tbX@|TzSK3=_k zALx+HmiBhzygNJg|9%Z>d;aC0bl3}2MeQ*9QTg-fbWr+h=aY3>epygf);BS6;j}BN zc6M^{@$tG6zfvD8S*7**`ugRuyURf1rJ$)2(7na8&$2B@HP_YBx>O}R*H68&vXbFe zK)~XQCw_f>UHN39J7`jZlaKG&=JR&SR#vm#yvcd{<7l@yXdwMd)!+4L{AbUfKmM>F zASlQwDoX0&#fvVku7TV0VxOIvdHA?|{Tb=}J)nbDIJvnWzqz^Dq0;PHm%>jkQQLV= z9IL|Cc3sUysx>MioRRUR@O$vNme!_WO0S8=2XYOr#3S%hgRxLU!jb zUApu}j#-Zz=&s>gW!*=MLd&8RS?uiWLRJQ~HZrq+dbxc5r+>fS8|T~r^+G{i_p9OY zsSgjeK6&~S)T}9cd#iQ&baBw(v!KheH=nm#ykbR1M+XOJGGgk-L)`ia8zVrMuRK29 z5845#uB_Y)I)X%l{}#B-+ds*+eUi#w?H6CGba$+X+iP{Wo&WjG^!ctFj+51VL9_9T z-TRm2-QD%({{H#P=huNM{6&iwM{Y{t1Ra3lKi|%wQh&kKtkgR@3QJyGXl!X=nPr;o z77`-z_4W1iPft!BId%+mKbmf5?;?$wk4MFi96!D}|GwR?FE5=-N=(G{z zcWP=X==u%NO74aR1|wr*&{6@=RZq(9eJMXaJe)FZ+N9aDtFIcI`S78jsJIxkrUA6n zXK&Tlq(47Cnq*#Tsr~)!g|y>=W9NA;N|7z8*jhW=nB&O>F>%U z+rTGlwczs0jEjp}#X7=YTwcx(8sYf)eE#vvmnRFW`x#_hSg`t(v{i`)Xyl!hl{F4!rF6pXI@?gTBH;n zSJ^sciinq&mw*%aScYG(*MqiAm6e&Ps;a&yv3j@jxm+8+{5jC};j?MV#>TCj+&b+(3du8NiHBf6H^AgMAiznXP+zh(Zc<0WY zpmC<%Ww~;dPXz6LJ`whtXLIuD>FHZr9Djd*pZ@;d-jLN{y^|&h%`(XZt@--#s9PVj zFv-r&&iuDFs5g3-Nkmb6<0O@rVu{;tFPG$L>$IAir!4S2z0bDli$-H(W60_-UXAyG zpv&R5W?fZMQfj(%DF`&$R8?hlc9toqT6yuc>eHuBH#Q_PZ_T>8DEIcZ@N2ELzrPi} zx}v!%Y^~Sxb8|m_{R&zKIKTd%g(%!d3)d7n9RO%<;p8-qoz4h?fGtfrI={(sKG~(*J*dL1#n@ z3pdZ6Eq!*DY4XiYsi3}r&EGGVpFDd8ns$73ZSC*V=OsaloGmS9s`<`J*nT_n#s%pY-aoM>sOXzeXo@1hfklB)YRB!p9S4+3mBv2*`>0z#Vsjer{kq?4&DcXGK?ga)l-j3)Mo|8jiaP5b z@0BhOP2hNXIOP5Q|8-^W@Ab}}ExkGI?5=kQ1Qt|&e)i+nudpaHXV4hQk|J>7c z_GsDuk`IlF@+vAa%FN6JjlNvpmw(@GneXgl8! z-2CL(Go{-Mpc)z6=+fPOWkZl6i}!TBr>FJzU%52p`-_Xs;_)?s(fn`RqPOSCrk|UW z_~=My_|mHXs_lmlf*Mj?qS}zXLG!BLS?2Bk`^~;)AE>94d&{J&ySs2_3}{*)aIqU` zwxy`ZNZGw_N%HZ&9lLiIzqw&}tXKN@+wJ$`G!0uWzkIU!yj}9uRiPCX6`wJ1M7zwY7nr=Kobvu4euZTr3b{P@z(&s*v{+ss^N;l#ra zK}*KV%gg!XY&_1+GTmw#{=etU8J|g$Cud$>b~dzgem_BQw_1{a{JTK+qB3IB( z0@rS_!uNZ>8>OBSIXBn3{qjo{b@le9CZ=<9t(UJYFI(Ew#H8jo$78WuFSvX(%@%9t zmrr|msFgzzw0?jAbg_8V*H>?DY-FyluLrH2(=}F{vfg}K!a>k+@JGYrYeBu2v+VLU zph<;*ecx`~+?)>TyPx{c@Dx0;cD$8a9MqTv&vTe;-n6N}LWUtKKc%qHFkoLt`O&|t zB9#5WN%HmNi4zZkVxr&v--=vsFE7v*fL~_?eam*w-CO-V>%3!qXibgHWHsMKTef^@ zi@gk5tY}fF^z-LW(0Vgv_r5<%i$Hf)KRG#hYx-0!PEOA7-X)X%-9ICz*|k0)=w+3;!Jk)|dl5m(TWR<3~_zP_NPMWCHV>tc6@{hF=Q zvS7suj@f5HgGOqTId|Xf18>RVR%A)r9Qpe`_}GS~!w(N!zb;;0UcO*Y;LZgL7(zoi zIXEs_TWxM`-s7gs*Ur5AZr}3d>S3!z=lZqZ%yCvn%%Eodu3#Et?*&=s4Y2Jg=2^A-m$_p|wY z#yIEJmd*_u3_we)iqG4+_siKii-$Ey6@nyc5(p6%`E;J9?@l7_bS<4a4u zK_^;1J3IUHkH`I+L8A``6W!d{8XFsg z`8yfVih?^k3a5%$Nw1f*Z6?%1b_~*<1_8&nP#FoAeYiepTn0@xlJlolz%_w@Yy9|pvc?#SF=*Ty}2o4QNRFd zlz+J3%>VJ<@At*OzGQ;#%1%j1Sz2mS|KlOMn0{Q(s#Ty1v5!gT7kqf&2s$!yie_+u z)!aSbZe`!8|NmE_*UiYtNW!{I=jYF#AeGN%=L<jyG%k>^j zN_=%?|E<|(1Io(HDR%C*6F81H64Dv+x`B=rc`b@ zIXRQOJ2OC&$)Ifuxwp41)ZjWh%M>(5xl$x2Ej1N%B%5CBu9m|OAH3WB{@KHJ`Lut3 zeyXUbJSYL5Yw_SfBj`RD!{lQhjtKi-*p?d|wl3!6L}mAi*K4>8mS=XJ#1M{CLp3CHs1xtaaIj z#KUZA{`1ak$-M00?R_|F>!kVf&nNfW9;^L+H~i)2O`wL?pAXJ5eC%qII}MYMUD#2W zT=(mx`m=L$yQfbF%{Nt4RV}*w5>!QAUgqn({(AEE+l5whSFAd9Wo58s?Jtv6t3Xw* zUd)aI>GNybF27V!Q)?@Hd@RvKYNlPS)yIz?E#~@xM!t;C+Z+bfVy4+*X0v@)J5N^g zg&z9>x?gB}UM%RownZ9yKA*FmV^zwv{PIcJ@;e8G{cRMDJ^wS`2|Ac~ROwKF-I52F z{q3K=xw#p%NEx*MZOW7*{`P;5fTmN6pYb?4I!>87RZv30W3rkrXf>yikx=(h&^^h} z`*U({Zv(aXbi}yLX7@gLkPx*cW8vS17bR9g!or3#eL#2h=I7@h>ya$JTF|EbI@~cd zw8To*iKD5xIrGX2K}Sc&kd;BKn{U=!{pi%;rJJ~F)glcpwq{0tetsS{X3!m$B~~^z zHU?6?;aj6-`lx|!cbsom3%cI0`1!fi%gcO0>*PQ+!qzBB>oOflNy(Iylz@(eeT^9Ajhxcu_Pg@w$Z6&D#7 zonG&pvSfd-o15FxV*;L&med>plzp^eiT6^_|4HI@2KmYWgng7yqe|b+!}32N*!R9zj>+RaEQ{6Ue)@ zrPF)59%#GXfo72u7x_fsnXu$qxx43cjw;a14512v5FZY`ZIx_Rei^csn_SIG|tuFn3 zw;a@+aB_AAEp7l6jZaTa1swofQCa!%lD9r+YOsw@c9m@LpC5%bHa4I$MSp*Ln|y6e z~pFf{ppLVQA@NN8yebJU-F!_=Om=_21i#z$S`S383RwvnhMZwvZ+Clp{_)$l zXGd;M12>XPG7fa>?>n)p^fd=3XX58)XLVwBOjxr<2ei`q|G&SWeO@=?Z`SXP*0NX= zx?1e?bp7KwW|y~Shl8ddK?kT778-Uf0^N(-FK5fa%eypwe_cy^d-yB6{gW1H{P^_? zbZ;ROGxN^R=d3}C|8vaZjW;hpn6N@*$KN9P#TPwFN=!I8Id7SD9|c|e_4fAm|IVEW zixw|FdG;)5#9_q>4F(}$VNO22W7n^L=TDchEqK5%T|fTWIqUaVqRxXZKYjH0@y8Dz z79=0({-j-mF-m@#^~e^Pqz3Y}(?|*Vkn1>tyWh?W?}NYTduzzWiRL zdg$sfReSsYYV}%6>ZSet{dZRERPo%-qIjgVcST_6R5o5IgOV2kpiL>Dp`Jw=J7V<8 z-rR7!zAiTN!U9Hyxz^>N(}s>5JqjA&C@3gcnxZ>z-aM1+Ydxmf*AliyRerhXo_TGJ zZCdwUyb3oWRXTm0M))FREgzV7UyR_@O3?&K>g0{;gdd=BsL z?Y+6L*4nv^=iS-4jy9aO03ca;0w|oBl`C-Set&P6C$dwz^w=66) zWM<=0Ffo||8e-g<&T7q*Ewz%>joPvx>*^}dx|ddN@uG)^SamH-BzW!= zpSK0Aj=i}#z4+Z7OC@DxPd~q7dE2wk?^qkP_0grJ-YKc66KBpmd7zOQwCgbU_O_gR zdn7gTD_-2$X$%_wyt*nB)D*I<{#Nkhgy5&opDoMYNW8tZRarx$qw@2!iz|cGLATgX z)e6nHwnp;wbp7X({p}XU?k)ojVEE29n>cOSv2$~+EejqrgdMxE(D~!%&&luZ>;zr1 z3%X%kNT_J@{!K#LBMNSS=BM=I&djka-tqlj^~MODIrjDQZrq3fO}k!Q6?*d2DbUV& z8H<93Idf#3oSfEiy^7HjFMV}I(Z*)Zn>RUDrLRDBU14$Y?q^A7^rm<3Dt*1^@=H)v z3QB9&)}dL=g*T5sh*SaB==wQ_?vF?;Q%w}j>pQ+&laxI`tVjydy{>aJ6>Y$O(k>N9EEdw3#k-T?_}!kCnZ>b!Am(HzPCKgV(QDUwyd! zPtlXBQxAr@{$0PN=4TOTF+p6#Lsls9g7f2padASYe85WvPO)pGi&5@_W2`~EeT z<=18(jkz*ohQysacRD&d7y8Y$0(C%U8mAY5PD;zacV<)S=^57Ldd=+opm}4^Fn;;H z%JxQP_DApE_ix{BZfIyY!$&PCDG4-@IdS4dP@M)^Db_1(zA5P_*R!*;)tQgfH$=bU z)Yj7Kk~B^OolXoo8lZ(!IOE@tc~@753rk3JY~OCK8@1(v zTHx^`M*{X%l`=6gfu==4ZNBsN|7~7hU$1UpAP^rPKg&FSoo;X7)~wK1*VcNwxUhg? zIaPbV=YP0Wmiu=vh*=T5VEc2b675_{tbk&6oiHBF$l{|j*NW!v6 z1vI8`xSc=!<)x*dM6KpK3%taw#0oS43|b;?XJ@x#_wLJkDnEN2c8cDfclK`iec${0 zYPS}HHcEn4M~KH&H2Tgq6O@qXm_J`$H)_iSTT4*4y@WZ`G~>z5TiJ_edTF~46k`B<+ssFkB5#+{j& zxh3c3q_xr8CrzDt^xocT(Du-ir%wI4df<58_RaPG>p%nVI|`LSW8v%L=l}ZpI=lFS zR@L9EYil~!uh$2~kG=i;#qRw{+i$P0`e!HwI(q6+fknpkb+YVyG7rAIybM~H={wu( z;=bD7UW|cBNlK26j$1$tN7Fw~IW~5EcL)t_YHG^3x=IvuE}i#uy{XdzKY#qVuTNS#xEp9#NfJ)F9?3){g zGJNcyb=EB{8}ja2y^TptO%31c&MRed;PU0kCr)_$`u-lY66)T)d!Qx!pgj=p?(Y74 z#`ye%S+l&dva~?kRxT~|2KCiJeYU^9zXz|6v$e6Y>FDbNFHqOe;KoCc_t%@c-f6mP$JgF`=#Uc++u_i?9I2_PBI4qYA3ttx zZ)bPnkl|x@TP)}rxJ7JF*Fv$d)uE@wp1ylmclDp%bnej5t!KQ_HcNIN4brr}t{l0u38~4G?)cOE`b7pL%O+>kJ>Ynwpv|@?xQ(g*#)SV(TKe$FJI9 zmY@ILOLDQ+yZPQ-%q<#UcOI3p2;cS z{94&_(wFKve$S=m`k99=zx;K0Sj z1u7T}q5u68{r`4}{SXxs1NGlQW&5AMe=C2zTn?HS2TlHg_I^Kn_z;w$K=YERr>BL!vSa~G z*$Auq9m(6i`1)&4FR!Glt3ubr>=bG~xS-c;HOIOZ0f}BWw;l<{*=D&BJBw81Yd$dk z`}c3fDzB0f6OfV4?R=`Xwz1NR-#;AY|NLTcf7WwRrw=ce&j;-Q%8JZ5eCW^&&>=?G z*QMUuQ~Bf9uSM5igPL&0X=f7N-`fj1l78~ZCBD}y-tYZB!z@>7ZPeDJ3kw`M6o0(i z{XSw(#YVGTd1q!AwzRZ-03AXZK(qCr zb!r90#fOg{Z{L`F{L%aO=U=bizpge8bWVJSpfacj^zPj|9%-{TY4xT5x8|LE5HM*; zy*g-NXUXcHs}C+*wQ9<=X<42x7VNorEo*DmZ5cjxNl8ft7Z;a|-fLprt!vik96EgX zih@Yx~p3_aqisS<;&F-6&0hDHy=%U`045C zit1`%F|lR2=g*#9x@1Yqh7ATGp+`@jULACJ%ju^|wzjhR`ua^xO6;-qUn0Nj4CWfc_{?}(6(|8C@%b}3$PdvB~ zI^*@1ZLXjD>1n$4c(GcDFOJ_ea7EbKTW9yxyo`S`?Nk`oQ_jFmn>TCf>0LAO zn0Yo$_hQfKr%Lwr^Y7e=(LEi^8XBspr?*dQ?UtK4`?4H=XA3$Ne0=2U<>j^RXO*8H z-^`gar+zb^eRfBTUXGdc&!0a*XZdfwS@+py)vB!I2lX?{eomj`m%j8doqM8dA z*8H72XO7I9vfbOxrfr@$d2;j3oPX>0ltcVEN#$=bXrgMmetg>RZ*L`f-Ez#Nd8N%j zQ>&n*u3K_%PrGm-05pN@=f_vIS8nk|kA6AZEhjf0J$e+hat2i4i0MW#JY~GJDfM(m zPtOw2>iWt$MV9LC@0PlDi$%>0x2pc8Bi7ye;X}cKnnT~(c%?yIkIc+WP&@Cy0f!a= z(B7IGH*RFldwyY|GiZQEgA26qFaQ3(RXz*KcF(=M++SE;-v8_@)6Dz(Y#BfcDDUhn zeoZ;KEZ_zrS!P-eG zo|k-!yuZrGS(o)pm>|H$E9LV1++0r|pDWYC3UkmR4_z&;gw}u+&?;>gy}lq@+dL*7+WF z+Qir1JY|YVP*6}n=+s7L_Mp90rG0X?Rh#d}LH#@_Z&9?C1rHzJvr9|8=U5iEH8e2% z`tmY3^W)dk)AfZ#MO*v(`P~*z%)7fQaJirC+pk?;_N`i##dvq7ae7Bz-?0P((EMo| zuk;DXd1s()K$~yg*jcQ;+<*SHOMI_OtR_vK3|e-`*WL_j%}tzGc((MPme{1$Pg_NM z-IPtUuc;^~fYzr^oIn5k(Qfgre|5#UtM<-2aKHgHts%h^u{Lbdq)8SP9~9X6<({0G zY5e2&@5}pYt5=1r4AT6%d65PS8{4C|Z+lOsELyRGqx+~+M1%xeb7MmT0~a^9rjCw^ zg@pw8k_m&QOP5Y^tnlyQ`r?>aw{QLV@SXm2yeoBqe6Ib`2cO=2!!P~dJr=KcWTK@FcWy&|2 zsiCdCIBILwR5iVL%i|lAHiU?@Zp*#BspMr)NNDPX1&%Mi{#t!%jm5W9k7M-2SFOq_ zz5KFd#j37Vt8PhITFvF#xu_FVy8N}R)30w(@w{YzR99GL)hbZa&oukmlElMpbF9lj zBi;gnf(u!l;^N{&#Kaa|f1P=6kLB-gZyExG2?_~u z`TFWAqZaE_x5a{rE!Vo2uhOd8D_34#{`Sthckiz2d_R@^waRwYsz=YBEz`B(e)?HK zTP$&{gxTv7tF777KpuH{EKsC%(ITav=3$eTL?6->meJ}uG}Aa8)c62xb=t8*V)4a@ zmFhZXX4B;B|4bBC_Y)8jYPy+o?EHCu?Zrw3pFV$9wy>C??A`|&fC>r@4h)^D6}qb6 z!2w3U`F6dBA0BvF613Qjchcm^pyhLdf`TdO>5H$wUKzGlDl;=PWPRM+GiQA6+`YT? z&C2jyMbVCqjL*-{SGTkTE#5gh%M`S{X2*^QZV~OaBlB!3gQSgKzkh%JU^6=>2ZzJ@ z>!3xwph>K$@fVd|zI_W?e!!HF_Tj^ao7;G$lZ<9&+}fhKZ{NO_mX?69u(tMg zc1A|Vl6QAzwzRO6SjmD0J`@xh^kR2ysQ+JgCe4^v+U&^v`}1GC$cWJs2c;3npoNl> zl9sOM!t1YB#_zX_i;Ht{ahWiG{`r~4=^ws+UFtvIuA-u1!sN-$b#?p1WM=uOJ$m%W zqUZ_7s#TYkdW-Y0F?TJxurgS^#7cI#-`qoKn?d7#E-o$Cv@=YkwkF5wiO;nzKX&=@ zT)~WT=Q>jefIS4#ZQf1_D@8&znuQOWM|Bal3mhvF-sO5|G55-;m&It_lMnk zf7jddm-@!rZ!hz(9d?=Pw_UJf_sy>pm(*`QBgB4x{)xva)22H3Gc!xGT(CMd`|!)O zIzO#PAEzy3sg~5(y1J%HVfHh@c`ZLBB5O?ecd!1F@Bfv-{N0Mz{!`|2DkkJ-ajy0g z4ag|HXmDrp?^!HDk5XA4oJcj+e)RdIq4i4fR|ji-n>aoje){{-rZ3J+nW21d-?YHE zFDqj0vR*u9zPY92!=lP1;`3!rsBJ#d$G~L6Q?mZ8_i2y(t{govnG(BN) zvBe<2TcJ53)b8n_&Ql8|^rF{2p&pcSHeLgs;c;ovZ&3-*k zMa}bW4egCTe{Yibn5<>C{8Xd&Qky@qM(%piA4;!(zPT_b?%zix*D!Hy;ZHxGWyrCL zi7(%@)@Jh~o8s?jiG?d_HtEmayHYhmM$hiv?4#3`Bz3LPyEb3<$-n$b7E|K9R-QfD zUcKo?;l7qfM&hku;zv{Uu0*eU88b=c<*&O&phdnQ52c^b+PL|54X1tyM|#P0t(2)& z5#OB`mss4Z9y#rDI_V2y(WzRL;NzW$6fg;r0Y}v%%S5F+WG*kJeYVKVX zWPYTX@9N&4&m!b3)70n7olraAd_Mg96{Q=e9kxg9>xr3u>ZOdf2G?2Ty)*0=SaE&$ zu}LIM?TPV@Kn1JVqaE_B3lA(f*0|@EYSU)R)H~l!YW6LS?pTw~Bx`fQq(l66`~5f3 z+Mbh2f>pp4SMXCWsItvNrKBwPuV6J4xPpmg|9S4+*e7@ec%4?Ar3&|lmB z;ee`fcmLCeC7e<%l@~Uxx_)-w*-0v%|4Tt@*FgJy{6CgnHh#bE)tUR#&xX4f&DgCz zxmt?d@ZmO(y4+<=3?d?L^rp)Di$8e0?0Lwxsd9#O8X6@lA0D3m{YuikdpdDHKPnxJ zV^g~$=DA#I-;Ft1a>xG9mwK3YAyEGc^K`8Xnq1ZT(~6#lr=Q$zVfaGi+1*fC|H*yY z?rmBcGJfX4Nx?fqQVyS9a`?9I_xaZ!99SB($xGHE^q_5T6S%d%e-d=l;jiE#T|Xml znV!z>got*7I*Z5y+4ixE5_hn(&0VBg*7#{bfP#Sr7gOVQ&+}hyFV2*GdUn=jR-3(# zY`&XvD&4y8vSfYd`uA&ni|QmNn=dymJFFUJ7sPH~Zd;kQyzC#->dCUty))!qwlZX~ z?=;@yY{AW!qTKLoVw^?HRCV4}P9HozCmbv3RoHvNZ@>Pzk5?bY%*z#TW0;pEd}_nD zhZdoYj_S`Jc~AQCzQkK?$wY9?_snu~Zt90eE@3K99zV}pkZdY%(`&WKTh`^*tPR$o zeM@F*7O#>yzab!_Fi`hEi&N!;jN+9MGFp{l>Uz^1IDVwmoAvAYJ`0-IJ42y7D&*^; z7xtdHR#!iyKL07?mlCnTB4nS{q|@E2^(Mqd?VA$3WPN9~6sw_{-lX8jusWOI4!=Mx zrQlv?(J7WJKgGmT`L+g~U*s)%BlhY4HGHDi*G@kZJ4xl`u9Nd1TQ~nrc{VYQCr{+a zmE7q+9~JQZ6jKlVd*?;y@%d7Xc|2J%M~?Vr*3>A>J|o0#`|8I<<74w<#l1G{4vC8@ z7UXa4%h^16w>0}DwOic+`ONK?&))g%EA;POm7dtGo$DhFF8<52xoI`!v80&X54}zY ze`d*}eDgA;-3}Y@f8mn&%B83HBII!U?X2G$A2^uluC98RcVXdmO^qr1u76(@=-I1_ z1}BsXJxhMlxXkedgZbV*f4(Ohew8RXas0W%duH!yD^upXdEeGfQ}G1tYrhU!{&?SW zU9pDee;Kps3U-GN&6Lr;msb@P`c>)Db(V&s>xwMyp83fn9s1X2%c@tc{j=xAY@Xbr z!+q|5N+ah7g8=<2%;*1~Q9om7Z8;^(tM8`gSqJl3?p2xVpGMDlvpd9V-c8TD0sON< zJ|0;T?r~z1QpJaY`nlYD5Ww(5t3WzkKa&ap^#Z4QT}X#F3(Q&Ta?WxJ zC`EzW_aGrql?f7nmNg)zr&F5tR4`+b%FB2M(SVH7q|#MCcD~-aBv{oDfB1OW@n<4aEJGQ-Sh40$UUw;Kh1qh2`>$MS5U{N~*(tk*uNR%*o+ zb2@s??Cx3`ahHcHPR*9zgr5pX=jRCtTA0u_Lx# zNEbAm0&&O}`?IeCW^Om{PUZZ0V{TJ|KJ#L$@P*O2@p1R3wD|>AEcX|wntSLSx2n&* zN$1$hL(^TI>`p)WTxkB{g60K-JCg(Ft`-n}b8e=L_GI(r#a7FC8pv=EcDJrl0 zoI7XFUe&pWUy3nAxmJGsl{;|YvSZy`JXGw0nOn=lZi< zdyv%Fd+~MfMNO_QCdD|md6~k;WJ~}4wA|RyIs1~qi&OE%E1UAR)>Q5;7pZdHIq$Yg zGbCrIcuxAa{qLNRBRy{=d)|sU-p_V#4bm0clWwo&ceKg~+I7JWS;I(4VVi46%G zSI3;$zHn8OVWFVNG|QQrEM=Sh63@-OT2XyE_1u#wAK$&bIORr6Pt5GD`Mc+JZVyd8 zJg9ZBr9B-0E*qe&s9sf)lSERkXDlwL4sN zoXnwLb^O<5l|$X{`=1wDo%ZTp6{oT5Qx(U7lq(lkw(~xV=q(1f=q+FD%Mbr zlKn<9!OJyNQlcu8epVKp_47-MoN;}>)jT)Z^ILcyU;VoB%g;YLdtd!1%%7|v)n2_x zb*DtT)(X)l%cg!#*qk7jq2_UJvm(o!6B{;YZtU2*a_N*?S3e#2(DhlX%W37=qb<4Q zACx;U?e|}@zO%JCYvaQhMf*8to*xYrm%OlH#`bF0kJ0^NOdm=wU)DJ!yxV(1=&OY9 ztwDMHUzgW6GrVfswOL7VzUgxApZ{F!PBHqqCWw+wY$pD++S%q&&mZk3MVa6o1P2EbuN{=*XVEkv{({ z=N^x4dz5z}FiHEo!YcMtbF{2Z8Fz9it`JLjw6iR6hjfX^)SVIKyBZXAnb;>V+9e`xK|Q>_jmoX z`4DUL^60nKue4hZ++FdwahapZ%154y3;f^xw3^uB!`-#$LGC5vEuklR<-=ypo!fgc zW6#a^(^Wh#PZta51#gIpWi-j+r@i(cH1_) zx)-gKdvI)ed+`e4M}AAzb1rl$>ujEXPBidY$jyp%9kU$Y{n+{PX2G@z^UsNW%8Ft6 z_Wz9I8tGSla}&xRW&JeD&lhu?$CjfvRde>`mvWEWcgHb`?(wzCV%_<7J+hJ-4v)3w z?YX2mM{A#2+V|DR&dinU`TtT#k5PAZ)yJaaS``wdKJO%t-j3EcE)15kV%>H1TXN2I zkC;^pJeQ~|#yvf6Qht^H!Sf|bWwAO7Yq#*9U0wAs@rvM!Oa-$u&H4S$7c%U7SCw`z zu(xWVT#UvBLE%RS7cJiTf?>Hp$(nc0n>o(EjyvUKIQy*8?q(Iw?OmD6{pZh%SRc2y zS58i@@Kd8VXvvl$OW5kt-r2KnhwP2}FZ`f*^J^8)|7lKB^n(MF${)U8w|+~;9&e6M zx_WQ)rYiV1Z+lQPGx+Go`J%5yS?};#@$C9v|F2*NOQo1P6UQf$giGOdy4N4Mu(MQ3 z8oUq*3EcN=k>~n^Kj-&_wRdTqYTtNk4io=kzEF-eegEI@F@7m}=U=dTyOu_A$h#ZH zFQ#OlEB&)M#YxI@Ql6&U;)!e4==8WP&gq@5@>1THqw(;=1yNhG%y(-|QVEp1^RxQJ z$L~9=D!bzXIX>>PGAS~DQSiUFdelblk!-t=JGWkY*@7l97rI!|C4#lcQb(w`Up9CAxM#>YHu8$;Z#s~ zg7yI)eOh$#zK-XlJWh~fUQPq8XI{TvUv09dU54kRFa2sQ2lBR0o;VS-`*YsBd7sKb zDF%G;F@%$grwFq?B$dc&p*|TT!@Thw3 zN(GO&xQ2$x@U?^b5WkMY&N8`l-)H&dh1XxFmQ{F8`uDK};$VpPFP&9K)cY!)NZ|*L zKZv49D#!tj^YoJs*RNY{0o~O5%%rs4+4siPZ(i>WGsN?k@V}sf471shyGk_E z&(CuW6k%u)c=5IB(W6H%Zf$*Cuj(M;#E?ivu0P&%Xy(W>sOqX z-#H&<_uu@V-Ukz-qNG44SEOx@6-@yxmYg$31~jGgU~QA%a%D5KX)j*9c)hNos>%wq zQ~vPb=8eh6g(M_?%>MoS>C=y2F8d2hNuB!j_4UW^-}h(D*Nxr=THFho2{p>QvtxhA zdf(Y*AI}({Hz<7-;?^tmb@gvfMeyn78%*VV=N~_RULGrYe@CIReC-#| z)mD13yDn_czkmNuJV)c*yyFiG{=BkVwdzrc)z;U)SASpj=zjjrnx96XDNjyLPK_?7 z<$iMwB>sKUxNraD%F1BSA%-TImt45{_W%D^?ba{n>*2w1xSfA`s8N8C#I@8+pnTT4$q`DOLNsKP=+R#sLPcJ}zI z)d35)Y?-oWkByCu4F?z3u2(jqsVyxne;#Jv*qF@T+1Xi9Q31MIT}W7XW%&AeW%9aW z+)f;K?%oAmqLa5>SV*X8_ipO|xt3XH(|pu~87wU=ckJ9LC?oS{cYTdkl=jVTWr);Z@HRQ!dG^ z(0=FXI``kZ1L2eYt(r1LgipqzA#S~UXeekR=j!U~_r+wwOynea+Ul<~Cmr>e(DnZ7 z*8;1#9E$&@#Z9`e9UU5aZjPn1lM~avefvP`3g5l+^Yr9w=aW@3HlD2JJ1by+-QRfI zb^)g4mpd0}{Qq%(-IFINLBYW*!`FkRUd?w~U0PpIUjE;`_P(m;q`I@be^-l3N_zH6 znVvX(T3J(bWsrXCQTdP39~Xd@`ZW8#5Lx zP*_m2;u&bgK~a&BY4)`%+y0BSA3k_$s`kgPUsFFkIH(h`p@CQ0EaTw9vuVjkIs`#u z$2V?7nC0GTIhwSv_V+i?&Y2rGB7}s5Kx6afBf?0 z&(+VbORUb#wLabEvf;W=8odP5GX6ZjR;Tt=ZRu!ou8ga&$l&=-m6|K!=8c z4%q=+yTl`FCDH&oGykOee9)bapd+|Ji_Abrm;?m}7nYZU_Md_lBT1QN-8s=eTjj1m z*P;cNUv6RFzCU|kNm-ehcG#K((3xCQrUWeY60Q3B>ge0;_y5hlfBk>knl(Cc&|6`i z&n@3_Y=OPDsp(Wz?`a$I@7u}nvGYoqJeXJgZlUL7wd;Rx&!0N=DCpeMoyF>6-K{rn zM1bb(USC_QZCmpH!(o0XJKiTd_1x#r=j}f~ne1Qm@sTUYyS-ATpambGW9}H1`_E6i zu^~~$vZw_#UANe+clD_q(~Q&4fwFYe)-2FgJ>QB7i`C)lm+jagp;7N(S^Z5%+AIfD z1c7#X%redXbk_X7LE$48$lVtpqt@^LCpG)5+y1)08zOY}{QLDBLmuz{`1%C^`I4>-qZE=rU|IFJm}Wn zm+;^~BWOq5lBG)vL7Po`Bo9webly?%@e%Lt`)0F!wZDIQdir{3*wGWb%P&_Rex>d? zN#$>M=l%yzPELOG^Z9(x%Bv|AuU0NUGTGlw6LeN$U`&h-Xk$BQGkN0Xn=VmNvpjr# zMW^e<3R#!E5jb!AeU5}-Qj3ST_iydxqD~wWXV2D-+LoiaDtx`3Ud#@KM~@%7dU$la zxVwA1!R)h69EyJP?R1rtlw6wGcwM}_x&)j${{H^nec*t@#Z9T+AlvTM|F322m>yT< zX(7X>*dma>@8>dxBTGCdw_JWHQhvX7`-cx71XhQy7jxp6IB}xlEYobz%F829PftHu z{eG`HgJIScjfW*xt*cf&+Il@sxM7A>>8tf|hrhhM%&Gt~d*P~8pnVA~)2C0rv8!~o zfD^~&n>xEnUMkhp)Ew!PwQdn`irH1-37WuSVPWY=KHdk477l)X{)>y<`2#~jR9Xa* zZf;6Flw#Dmul6_B>Z?^(E7Q-+P-GC4lG5T~YyR-*)1`H54=wYZ-O}FP{^I`r{Th0D zVon@}`S)xVYIM!8tKFrcp&_s~YO7FUV&bDOFE2m3vNBl7#3ZET)fLYfXVX6Zd_JGs zp-0l#jYHAJ(XkP<3ajGn*6UlUye`G3Zoj>JSB&0bfuz@8rvyhtXaofZyJlsr0v&L4 z1ax=V-)X(__HlFUYNJ5jNJ&fMntr)D*0&tUUCvpks6T`K~Ebrg;6fn(H@l@?_;rn>MwaP5XWIwejq;pi9x5 zmS64+Tn2~jGic2qlYqb7&m|sSUP3o--UQuVp`@eZb7e)K^BkMXAPz;BprA=BLRU}g z=7{?~^-G)k{a*WTDJcF{tk4h?6;-{qCQ`Yps|%b|y7l*IFnoNq zdVScf{g3`VfAS=w-nm73Ym{qb!=Sqc?H-boEv1?k|G7W9*)gk?Xi`_UKK-JbQ zv+1W*KYjk}>gLw=tvqm7r3~Nktm&t(`f&u-Y<{f-S{|5pW5cCQn~c6x**<*uu%fau zabpB%(b2?-6G3O*OqkHHYSs1Hd7!y*dHM68i#tAlK79G|yx+l+gL+n~kllpi@RM==OxWyGk!Vo8sc?%J|e4bjkS~n@XX*apyrd zPi;tKUbQMHBElmsPA)Su6Vwpg8|S}0@9vg`8T-w@tXQ$4!!-MvfQ$@i3;BlIZ$THw zfDV?a`kM9V(W5Q3ckkZax^P|S>aeA?zrV@YR!!j+*AtMF>l0{Nut1@vriKA@R&QWn z;F2Xv0)m1To%R2-^v<1_q~v7KazD_zytA{-)s2jV8idvT61GO2JaHo7&5ey5iXQ&{ z?i>d}^Sj@_F9+?p`Fbt-&F$^{u7RM#{(pRUSW#7VX+@wi=$JUrj)$ZqrMXt6phS0N zeSG}7ddIM$@OJzCNfReF-p#w-rmEHD6c;BaU;oGO-Me?7?FttzTzG929u_8+ zeQk}RhDL{=vKweG1_Njt_KAtgKOc4LpE!G#^;G+&{2Ln@D?dM*IAu!9{{8mN?EKGO zE}wtw`0;koDge;BS2^3N2@@t9czC#-lZ)%o&Gh+C|NVaNzWnmTqMe|vjq-Ir6g4$9 zL3i@L-~0X3lga*%-o8CM%QSn2b-7;d?QKsV9Blsa>({4i(fJ=idng424t#le`Qyir zpqr0D^?vN`vWF#BnYXrRuHW;C3v_bvE2+niAA=4v)Q#E#I{N(9pVy$>{Clgvf4UkT zzcKZ+SR0Szp(iINgC_0^3k%o8?w-YOzUgqoR=eKOdijMXBesL|od~!|X<{P_8wasStR(*X1I=kn> z+UV`s->RNJd-mzy@AsSY?pk?ydv|tqJ^FZDKK;T1$NxWmP6n;II$M4?&HerD?fu>2 z`dOVaprhEA&#yZ*QQ2KXN9T&1!Mb#g>8n^^T~er^5sa{=7rZ^ z*PC+ep61-nSNP>c;F&aI(2D-pT_uU%-rRJkWV`m|%a{1Io7>ylr)fNl@=+63oBXoe zcwO1+YrRiTPdCoEpa2TY9!cXZiMe9ktQz$PK&_zTeX`2>`u%;f)>-^p_f~%gCC}2= z*EZJutpc6yes8ZegKqS;l$V#5f;M-ksHqtx9%2Ete%s~iDx9U*RbINSh}x?4_xJbX z$B#c>r8rk_I`{I+p4;>8W?Wu&mUpK1^l8)F&dxGjANBjo!gjf&$H#hkBn%id=Cj+D zya)iDV>w+f)?lVjkG%c7e*1qC8u5qHPfyc5GsCd?(xo8KF*u#V>Y%;C>F4G!o_g-c zFKacW`1v_Oaq;%J_33YJY^?cqGu`2*z;)f|Z3e~9d~`LZ9Zdpl^aowqGX1n`Vxl6b zp%-8GQ?>Nfm4&INr-Am&sn4&OG-b+^)$2ZHIr z4RSEW2((NjCr9Vu!-p;|E&_srj1C?i9A95u-TlfuIyBU8o(*R~=!fX-d9l~l#hz}L zuj}aV=Re*r|9sU>gZx333va9s4<>+)$N?4qbLXDjmU|nt7#KA4arkiapFcIABaHt1{M^~s zw=8yd*_*q&r!RDF-&FqoUUuC*(8l>KnU_Ha)-kj3JOC}e|NG@~&Yc|}-D*lfGyVSy z&6h|928wLjvcFdOnp1-2?&iXXmxQ!_A_@aR!e+WC3DpP!u#3>ED@+O%pF=*(y-sjjXw#~uRs1? zyVLoR{cDG5)23Zo7i-CIoZ0N&BS&0xe=q;x zpB6g(^itiN`Sa%am6e&5zPj@BEQ|Qv-Q~;U_Ev$G*n>`JPdLZ~T2gKM{mx=rm#=Sb zPL?!ITcE+^#L?B&wPmZKv}u-0x0vpam&@k|h_r^Sjk>tNk(mM1!LF*Z0^MepmzVeE z*4FOj^Xq2qEPig3aY12K$jTyRyP`=YcR@#ygH9D(jHWd^XKR##uevOIJ zgDMem@#M`nKYaXn@mZX_Wl_tNDI#y@e0_a=dD_`optLw;+O$tUpU*G;{dPO(95hf) zd~~$C^3TWPTkr1yZM2KroVHMd3pDl=U;lTj+57T)dn#|_n1RmZsrvH5F(^oAZ}s!_OxnTSKy4f!;F9&U1 zS-Wrrs48S&WMmAFtv|YL&mNl?J@KwZ3$DMee7$x%=*ZTc6(5yAOFoZu3O{-BBxWmL zUR@n%%<296`m4VmK7ZbRGDS&Uy*=yds*lH{^EZ^f4!d*b&J4rkHnUHQ7A$B0ZQ29f z+nRW&W%Vo2{ia%4hY}28ruH09FbMHl_VuWE{Ddh}j?A$v2KA*j-pm1Qz^?gn(H*oz z`rqH*$}I{yIz6o1Vi)#Q7H`YB>2!Zz?alr5_Q}WlQh$AU32JCOd6EJ;%+$w+M>}kd zLujZ~`MWdj^0h7@AtIn1*>7)cy;FX_HY#Cf!9%BcHJ?1~{(iaaCAmyn%JloYyWQ*8 z>pwo$yV$i`40ObvPQ(U=%{TviuK_KFSn^-9e&zEGN+*QFb^b2U;99zLsfw!V#JO|V z=AJ%#R@Krn(z_@?@Wda{=xsR<|NQ(6-su>%wF`6vK+#jLx*reOK?k(e)Yw>;y;%^q zw`xnq#U{{s1@-@G_++h)+}T;YmEFNL@WrjI+WYqHtNC47^vcHR~E2tl_IqRxc*xIP0pjL)Wr4fU6__`xW8#4~y25o%an0y>`0p86` zsocC$CJP*!*;cITid&z4e_w6Q|G(d5>}qC2ZcdvxeY*Shyt^O1e*Js*`}!n}>8n=x z%rfzOow*`-IUnex=GW`@t7&R>ifV^});speSTZ&A_w(2O`m%7xjv0#Q@vs~J9O*VaTHzOga+=a0wzg%&cm3VwGP-P)S%UQ%K* zZ{EDAb(32KWUR}0mS6UaiID-Hg?R{cG6HBp_@$+%P1g(Vc=n>9D_7KU<&X11wp+8V zGM$qC{^##sO>OPPYu3D3^;o;__ts6V0=I1c***%NrX3{mbXNWKx|LhDOmS}K6BH71 zIzP{rv7oqkvHyI#7bRAFvQ|raZ-EZ*V!AtFg23ALZ`*~Ab0~tgv2NVBv7)L9wEYmY zR=#H&Xn~}W(WJ%h{XsD?F)Q!6EuOe7_jXW7$Pv(hPYb7Tfz@14X~H9C^TWTsOcgXv zJAeO`VhJfJuap#(l`B`)TU}TkxVY^a^Vf9Hn8%-w?lv|yJN|yVt*v`4&p=|w<34N9 z6^8czeklL_{T);hgLa+vNE)-{=jR_fe0ZxVS7<1xGqvZ#A#TvoBjAGgc#2Ubzw&vT z&pe=grza<==G@sKI2UyMXws1mK@P9G57ratqGM}HD zyJX1{@D<^^%iemu{Cs z=4Q*{XFOetG9urDF8a{aRMgY+i%xnpUF%$F>Rr&8*WcdUJlrl{w<7g>_4B#qptE2= zcRA0SH}A&VvZ!r2k(s%tKwGhGySLvho4xJt#^Z92K^I!hG;Y`5_k(Hn*|yoUr5_*b z{T=+;!Ru1Jw~r4GzwH-+)6;Ypr=Ff>Zeq~#=>cd5{+u~7UESRuKYU2Iu^|z3$^z*6 zhMC6cN}&0m~-vesMfKY#E5be+`y z0LcciC9#%Fpq@wB+gq{4|GU0^1s!^}YSq-bWFsS^hEJ9EMT_s%X*28&yQwz0^UN8a zx8IjI9W^+&VS@n=+u`|I-@V)xPnv$k$3e;>!g#{Biw)x~Sp@RXN- z_j;WW#v^Dt`|N(pCI8QFidy@k#40z=atiz6ckkXsti6`q7XmsC2ejSzMXA507g5CC0)5g8d-|H@*npSz38k@%>~-hm>Z<0jj9 zrQK$m<*uvR8^6D9P3&&5{QUfo(53GEa-g}IySvMijb>)t-?!K7%GJx4g%@8u0qTWJ z*Ne5N{-y(3EFQPFN<~dAOZS$dqGHX5gX}h+PAG%=sm%u+PEXU#ytJhA+l0@*Ua!Br zHro8$Tx<8Yx3;bfUe4Fq*?Hv1k(kiqV!BZu4)fa!NK03TCN*r>U|{$Ajd4*?5eF~t z)waOeKOc{$q@{rtiO)96&A7E?Wm(VZr;Cc8p9783D!cbRc>Q|yR;_sN|Ns833|`*1 zZJyE;`wUPYWMA#?Boiq?rwjY*>!r-|&g`rGed6RvMJ=r^zqwYST<@5em_B^@0$S^S z>TM{O3LtEQ9P{hf}>Bfe{ z!|Ug{b2Rq%^Y7cYZ-$Q=sBxKfZB1pUelcv1^?b7m{ZI-!zZv9hpP-_!(cmb%el9%^CJx#aqlf#eY z^Xr&SP5vFj?;4nKaS1m*d zUb1}oXV5{d`S)xKQ zJiyLe-PU$)E4TQhnKMt`*qFTD>z#mv#0lB*J0Ev{ow#K7LM7&WPfyRKZ?8**f$mc3 z>-%@uHt+hQ*RM}M?zacs3MgS;XA`!1YTDUZfdK&vR5=YLctH0`?k>*<9bon6=VwW~ z8VkMY-TM1}fTrQpd}e%@eK7Q>&Gvb=)kgXEY(VY4xQd6YGJNdw>;G9keE6_O+WZ~= z-T8X4yFgbIZ@*vXeS2H($B!Q$_L|>IINZhy8Yo}&U_;*BU7*8qKRDAsLx7p!4N6rJe>2<7~fQ z_q()S4|Kx}s15P`e!c%(tI`+u_F7j~R_^)zZnucIIOrghrluxvt+G8&c60jqW4CY5 ze)K5G?&lL>P(SqQ>g)1c4ccxCXPiv~-A?M^al*9!^}W5;#m~+>{CwVidEjC<&;iQT z-*ULPxV9`@COZFm*GgH?us&aVGiblRv~=}V89&hc{V!dk^;^FzkJ(kyx%>UT*~036 z7uH6bzuWbC-RxLZ&tJhIp{aj=eFa~5QO&76$(2{y?9Hssuzx+EITp|{V`pX@1RV_V z?c23YiuWs@&%IT%;o;%-$&)4>0$pFeF{!oe&xwi3^;_S>vxzFZ^*lJ#$_*N1*NNQ~ zGW)r0+L;;mbrwIGfBNYn&&g__Ij578)r*hImM^&edgJZ4py{fTx3|8odi=G@cCmZ^ zvK>2SfQHU0Kc{`EvgK=U-m$|1bY_f$L&LONT_U9)k zIyZbuacJk42lWloK0iBaG1sre3LJuStx7LVQ{DL~Cj8oJrI&&2e6pZ^8f33Z=N}SKK)=5D+7;|$%|QgyC;1qFSL-k{H5xz3wT4sZ|zhLMGrr}WeXNO zDBo``>a7%#BDq&iZ+ds|azD_V*``gKK79MOEb(yLmdwk`E|u?Du%JQKx-6qJ71Xc; z-OeM`tD>q}_~S$3EVJBIy-uJZlJs+P7W&RMyCnshzmAf8_wL=6{QL8!=|(3RNO<`8 zJUKB@*(Cd#kF;-KRMe?!YooUcN`VjkJ9n^|{pRj+eZS?P9U_+&I=6cjzpXo&v=Owi zhfmh3TiY`FD4T?v3-0iJ4=zYT=`Ordd}yetmuY|Jm;; zvu1%B4?7ATGTGVLf$p=IrW+l$>)oYGL7-D}Hzpt7qGGT%di${yqrk8*(0YQcN7D21 z@<68nl)eu8^XJctD=UTBnj4#%m_T#CLBYWo@upBz%lyzl=V@%AQ_v;R7|E)#d z;r=5gzfx~{_n$vCYd5-bG)|v>Jzs6nWEIa}i$Tj!F6lmYV*y>Q^WWFp%HquV^UoLe z+g*~{5u+y_yQ}2kIqUa(Zcf*VWmjx5yYzUvpmqKp7rdOYeK-QL0*b2T%~pN z=+RrhF0NXYv^DDgv|>)h6Q@sK77}P{YZDR?`O?o3;^pN9+Oh?j!FE&sBf4tUq`7l_ z{rvdy^735V+~T(0d-?Uw{yTSKjEs#9rFua^#U)2p1~32dYV~>#AD<=h`|EfljaX)% zUADHA%b@Phk8CyTmQOsFU%q@f<80cWKYvyPEj`66`SDXZ=rq3h_VYJwGMZQa@8|1z z+Z69snE$`63o4x^sr*e0KbQP>_p+%o&ZgP@c)(os?TzPjz1WWK?#0#L-<_FZ_;^)I zocZBTPfx$Rw3J)E=EK2wJOOi1RH+5~+qMccfeMTKdwXWrp1iU?e!gQf8)yh_ zf61PsNf(9eDk~~LJ;SOmFAm<@TPo8E_8RDM!% zZs$vVduuCbM;WMcdv5lf$A=FE zpqmYHbuTtDvx6>G{CHG69(2&Skf`&>2FYb8gnh@4vKbL1kqnXb5A`BBiWrYdo9TcnxOytXOsG=H~Q`QEOXTTMHi@ z;anBE+U@Kt)5>Qv(^rJ8ofX!0HVt$K4d@WX{QUe`CYeE+nOCQp<=;CqJ-*HpbnL;N z3d5_b!;e2L`gk*aK4=6SG>i&5&?#=cx}qXue0+ROZ7pazfUmt7bfC-i|K99;G710w z{5*2(*r&VY_d#P{Edn~x+jms@oC)#=^!{^z&0H zsEJTlX9rp_c448jNzM(>8E2n9ed_4xdGh4sWEK{d3!75CW%$^a`^{C-)a2yn=a(|i z^GQj0vYxG3Z+iEUBQEFWST--x042_-tyzDS+w}iyb9zGWR(n;nv&X4pt@&=fNh+T8 zEr%aQye-;!i&L>{{qi}*&(C#o3afP-m#>ep`FbUI>Ob3`PbNR=RG%lnEv6%I^yu|c zDa956ZgD-4hKe^EkGGtDy6aQa5q|qW3U>c~Bu|_>SGPsr(T$DCi>klBJ9PTAcSU7o z=Yj}CpeE9T9Xx_Yei+pFBC0$<^dwtI_@wf^{hNORgeoo!?{K=D!XJ==FX5c~T zZQ_(EDp}XoXwEXr)w;DkU%#fdw)J9$2xzkUc)$Gqw40OFe1#fjm}CYmeYwpp%FGdusacOQR#etvX;V{=P?e?Q3K_wLs8)jTkm^gpF{=LfQvY=UI5m!OA$(~1!9qSTS_v--p zlZA~9?25$~H9&{8ftDB?>zCJea&l@}zI^$IFJDA>rOiNth1cHYFLvv7*_w3~w1K{5 z)vBa-cXldiX?d}*uyEAZ*9WeRGW`g;fBDxN&;PbYD$`X!<@>Mp?#|AK6W!$$jf_66 zO04Z#1iD1{-QMr_;$!)qod-?HGHl$qG3tG<0Mp($|NsB~R=!%f9CTv+h7A)8laGPc z6oC!}2NnM}HY$S(wzs#oz9_L;6Q(^~KmOR^!_A=OKAgg88Ly{Jn>H;dAOLi+&8daX z?VuZYLE{p>v&}&LbzW&RP?IxiZAw~N(&n2IhDj{3yUUK|ZBI6vz42xagHG(ODVD|0 zK)pfGHDF6TCxfa-VKtu}S$VV1rYWncvQ9p^M3)=XwQ9JvH9I&W!lSCn%8BDln(^~< za~B6M_XC}T59;tWv-58%d3kB|ySb}Ysi>+l2CR?U>$SeJvQki5+B-5*Qd}?QK_k1| zgQMc{EDOrt->dm{GaYob1!(j+?d+_F@7|qza&j_g%)xE(#-K>i&L$^kXHIVJ)n9Ib z?xZfiUwgcn-);qOVeHN#)qa~#9NJ-P9{hYhA2gNZGsA#!_Sv@6PZ#ajG2_S)mw8pM zG{yAed@e8Z{rRw6{>HXkY0xRblhu3=Wtf0w5uTl$?f&)E)r}E4Po6)YJbCir&*$wy z@esKwh4cNs-+G`^VRygZ2U>pi@t^ECVJ0~As+RsluYwwe|6~+j+|7=F>sv zxd#OWW!gqAzub8>>u7>O#`Ort<(D76d)K#W)r_-g3v3=_Us}TX_*n1Z>(|9y10!~o zXliO|?ugO*Qnj~i&#F}sI$|YOcSBQ|Z~a=mbm>wTclXOnJcSt+EnfWc+FI!Vxdm+O z?2E6z&b+-Xcj;Dt?Hf5}bNtlZ7JppzaC*3!mY1hz;@MfICb_q!fDT5PFhKw`S@ZC4 z`_G@x=Yy`|w3^E|{q)hf<@Z*yt_B@JbbnuM%#MOaMrJn9xx6J-vXCo57hZq8b*cHi zs@J;jcE7jN5#t7pIo`cH7i8$Sx3`%=mx4~w48F0WP?_K6gTvCQC>=2`Z|{|Hdv}Fx zckLFNIB%Yw@yvTFpq1XByuHgcMp7XyB?V;3yPePHNsCD^Ra8_kWE4xs>WNR+i&Zi; zom%|-+`|G3(4wgw6(5ztR!?0*l{)M z=+mOYhlf~m%%p#Rd%HM%eVjvOLDu%$Cr_L>@Un#IX=7GK#tYNe*&8E3Wrw1o;w>4M z$tRD@&ff=`U^^FkFu~x#yLWw;UmnTZ9$w`OzL?i)E}y@@f9~#+DVM6=-??+=!t1XV zbI+|dDi4!czG~IA)s;)Taz#(4cI=<=^Kyz&BKT^?{WBHRl$D$J?6Il(@?xRx&xem6 zJ2$iO{`mQPzNfG6)uz+h?}J?vqCd+0lWIP=V6XntrAtAH^~8x2Ud1ZfOb?bUS>jb1 z8anmHjR?>H{h?Owoo~0@&bY9Eac|YvM~&=q3Mwj3^zTZ)`~_O&4$3Gm#ezUrrBuC8 z6cQ48ac}Q$US1wX-2+yYpH%#oPk!(q0o1U1Q)d1BZn^yJ?fL7s)US=&y5sG(+X11X zu7MX`mK-^9BxA4leldpFnF~ON8SmJ!!z=a5+UW26s;Vn*#D`w>I_>hJ#0qpkSZBbU zJ9j`w*}lHM{`r}i#z&4G&0H@kCI&jTWd3~li4!N*TUA_sS#ssi{%`NBt@=LOefd|O z_B%;@FLQSs2a^s1rwa!T^leXUQta(!OW{$}3d3*dGUEf*uXj0Ni&%QacXA2+S zSM@eVvrji#$kl5~%0!XOvW-j%4ii{BzTA8LZEofNgfgFw1zMgQ58fB|^}W1%(B}M^ zcjwN$*WAB+0mF_-4?kzct)KqvSz3OVA`t0*P@PUd$ zyPqfhL4&}cyKA=Z|C<|E|F^X6V|V<7nKOS*JU98rgOqa%1)Mk(k1z$t#GExSJUN}N3ym`@&DSj4pbNT`0)5GpA2%&^XmJ*N{9O8Rl@`o3e|_kFkB_xtYqPcQxJXMxriT$^zA zHK?b%W5X0|UqW79-Q0ZonKM3F+1b52mpD5+gHB(4@+5_ei|bzb z1NJ?ye|Gftf<{HxtQ#ewy=>8 zI#kfmKj@A}^P078AOCrrhXm~L8%K_?{Qmy_`18+uU*9U2wJb0q0z6DO@szfn-m~-n z-kkq&%zWQL@%=wsLHFnx85^%$zyA4+jmaq~DG$napFDf^>B4roN3G&<54z+3D3#x@ zH5b>9OF7;rTl2p9{%q6zIeHU4T1>OAeE{7k{OPp*=cW3!oo8p8U%qnX$m;d`PTBu^ zZ2!r6|7UB4>u(!-)K2|8lWuodI)BeW^Zh@4K||->-rWB`%KrylLSUY~FYyZK(uZ{W z-;s5{F3`hU;s`!2t%xn8vX`8n(NPx$|T;Qx7$zwW^M zfA8LZ>h!N^digT5?9GkFlPQn%|2@)QCu#IEu}b6rkN*EN{{H^{`S1Js`QFp@0z*QM zyu7@;^6Av@9lzi0zPT+|Ix{n~=9%&Qhw}d(%KtbOzVA@&_q)dzI=5Stz7mQ5b!j^2 z1pY@yyRWZx{<1ZLq5jho&v|vfGUru1;uLY6`4v=Op0EG+88n2xzW(oP6BCmQ*RQAR zNEY3%{eG59Pt^5b+UAYf*Y(cLu{<2U?~~{Qum0?+s$Zs^Wk=R39)w0$go40>KR-YF z`1-EJC4lu^(iG$7_1Etbk>p(Y8B_=L3Y(JY~G;#9e!(YCbq-~x# zV}``PkMjQ~{`gUGujaGwx3{<3Z@&f2-zQ&Q=F4#X4YPutUf-cZPRSNex8>djRV_ar z^4FiZ|L^Vnr0ussivxb1E8mxV=KbzjmtQ_PsXkvtS-BZ>YH0oc@Ab!%Hf|_->hF&ECqM~QH#6(>cjg2QCI&>)enRi_7(^FIbd|7V)6ck}M zwq}dh?Vk_2?e+in`uYEV+W%iJ81wh${J)Y513KdWz6u9*aX}t_!2hoyzxH+XidCRX z8F%cc2$OyE__3yjhJvA?;0~iHKa1!8ma+eR^ZbvC{dGt7e!u5kRb_Sb=+Vv3((FDQ zUv;HzaoM+CaBS(%%JdLY8!^z2cT_^dY-^CUiV`F0#HML1IXLerAn!DB4LT1_W zKAQQ7X^2e+PozIg2#TMVbz-#UA{`TZ>| zEs;-O?mBnjf`F~9?Ts9>qN1X-TDSLq{*=b1my}F-_byMwRngk|w#D^F*Csy*;Mkbp zQJ~Qhk&)+8pwS|*y-Q_@pc98;3#bl)zQ| z!HPfr0~rI-)gs{3pyPHI2E15yCHi{lpSt~D>Y#473lmEzu&}f| zIeq`1)PHa9|9ji^{x@i^)#uswQ_|9&wY|Ik`fJgb7lHde_U37yqnt9$zUKllIl#9u>GbK$ma(_(j*C2qeB zx;x_L&6_npPscA?upr@_k%((zPEJq#@9p)A7cFW6-C%m{TG+hGXOg#W-2#=){r`XT z?<{#K^!wY}$E)N2YUOR8{p(j1Xb;W5FaH0h9692WT+eU!Lm~dpBk`Xv{p(M?yuAGR zZxJqVq$(cK;m(W53=bDS-Y@_B^ZfrdpcOTstAu6vm@nMDd-ngo`~PR}+h-?N`9yHm z?AbqmUEkk#|NZjCi<|#{-TzNKIXM|Ld3fde_0PTeHO%wRm-|X~Eiy1QJql|2fJTl| zQ&p9fm21APj!&(rnREZ|-TO<|t?N^tUn8{o>Z$Yf|BBDev*kX}Cu_Z8_3FbfOa6UR zS*@iVzD~v1IQUwIa1vv;i;{`Cd2n>JcWkWe*RNl%T)!S19`2r-tE;Q4n=z~G^ebZ< z8y{!q!==wX%FD`%K0Z1cZFGF+=FO9zJxiPEuX}I$%$br>Qc@OGUo@VdpTA$zlBGq! zsiuH?-SdL``)YUG&0Dp4b>Z&2p#I#nX=0~Now`such4T1egD3$pFC-jQ(~ebXvW>w z*H@#ThWS&iz{C&Y~OG*=X0#BV#|)TwM(pb+<(7({rdim8x50^l1`jC zbL4H={Vk{1m>A36-SM=Lc_zO92dlBMaZg|0r)RVCr4BB}!K73eXj(&HOtgZ&}DM)%2i$l5+68qc%-1w(Dq8;@@9iU6dw99*j~EQWW7zG@9A-S}{Rp^2rn> z5w3-qQemr0&sE;OxCt^!9ibzZm6g@AY2Cf+)~{vx+Bw?~&p7)myU8Q=IwuR$id9`l zlO|3S>ke^tX8!y4Z_T^n`O3=5&7idLb#;7TT%2EId{oJo~bcXxN>_B`3L z-E(*Cvf8z4SHbSPF}q4QeJ`(Avu4SzT~d=z9=UQwq@7Q8(dNy?K{50>8EULPMH&kGOT@6x49pd8fM*|}xwRza>-4Sjvklq%>tR5P=-+iyX0w@;otVK6W>Jh;%g z{l%+Shf<6JfF=4Qg<1Z{M{PN{XjIhi+>+(L(E(~dD z@9Jvni5@LaPfrI;X?F^%&zLboK~b?$H+tKInKL;L9PJhd4RXz#IrCuN_RVQ$rB0nb zy>z1^hvL0JkV;B{Cs^-^Eq$(?HxC_Y~Q}Q{C!+i zRn?FE|G)pAG-ZlQVd2L$Mkc<>s;aHWhZtJ=C%Yo+ZS(+XB>p{8ce>FN5(Ml)BeS)*cUIrGVrl(2O%op!SjTp191-H+$~prB_w`n)UYhv12RNu2q%b z*_3%%t*EHz#@=f4-R19>ZP_x#cea_Kk

    HnQx$8ghpH)ZROx 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%0A5I{rMub^QoTkT4TXDdHy-~ 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 al$MmF79}@b;ACK6VDNPHb6Mw<&;$S|NImiZ literal 0 HcmV?d00001 diff --git a/docs/_static/ble-mesh-scanner.png b/docs/_static/ble-mesh-scanner.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c45e396bc6a0a7ae12aafe5e265b8b1052b889 GIT binary patch literal 29899 zcmeAS@N?(olHy`uVBq!ia0y~yVANq?V2a>iVqjnhck#(!U|?WLcl32+VA$Bt{U?!? zfq_9G*(1o8fuTx`fuW&=f#DYe14F|L28L1t28LG&3=CE?7#PG0=IjczVPIfjOY(Mi zVfYV%3-&Ib%)r3FUgGKN%Kn&>Nm#;Y{l>j*3=GBRJzX3_DsH{KTV5mbe6Ib6_n*(? z9#y@^Z6>C3$wo?~Wa+X~;C9`)NhXL51T^ltr~e~NahOn-GF(=SH&ag+2_Hs)m> z90E+7ikx5Xm51M6ZnNKBE>qWK@`lHZuYcG5oZ4~s=FOWof4=#A#`wGqe`EY!Ru09t z0c*_}7M|pD?y%$MV37Rzc$0G9f9Fkx{Q?YT-yB>1NopweY}_oDbNKkeMNEnz3s$XS zvh)_>WKfj1Kge)p^V)s}0VXZ!-(tzro@PrwKeqY}lcr=m$4|Zmi|>)_k=hC{qGk}jP>LUlzjZ3g~90rtGS4;pAEw*6=APVhvO;}1rCY0GI*3k zFm1AVz=W2H$zvO*qEFz= zRXyL8JDBDOKE8NN(CL_u)x}l9)qfSe+OHoKnU-bzTXpAsVTCCxdK�ysl1QVPb7$ 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

    xXH0J|l2yHO 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 literal 0 HcmV?d00001 diff --git a/docs/_static/esp-ble-mesh-architecture.png b/docs/_static/esp-ble-mesh-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..a3ad4e8c47705b6ef7e1619824b78f961c7b0b2c GIT binary patch literal 111714 zcmeAS@N?(olHy`uVBq!ia0y~yVEW0xz&w+Kje&uo#om4z0|OUdiEBhjZbe#VPAY?{ zu``6FYGja`pOTrDnVO<%?4oL9WMp7yscUGcYh)f`V5n;BXkw;n>}X`7XK8F4rD|lP zYGja+Ut9uFTV7tSmr|5iu9uk)kxEQY&4bGM=2vCrL;X8at^P z8Te%8l~$-48B|!9#haNx#EQyPjUCMm^b9mrjSSpVle6?V za!Qj*O?}M0T~v*nlJm;k((@`Rf-3!kQ!<@BOEL@$%v_!F9aGA3BFrMPQ-jkg(w$s< z!-KrNLh{O-yaIBZ%rZ;eES+<`D*X%dBMsaN!ZSlmi$k2V!$XstO|sL&eO)7sD=ZB& zb2Cg-jokbKRgK(ogPgp4%QKzB3L-4>LX%tr^Nn2`Bl1lBvdeNa(~3O(f_#fB3tftR z%ER-6f;@t%JYBsbJW_)4a-0&)^Hq(U!i_>KJ%h`f3L}goB3!}@3(_L;D}##MGpfp? z{KHbcQe6Tpea+K6%R-7Q1ClC?Ey7ie+zib;OU)`>EM2pGRgIiH&5GS}DuR8JvV)Th zO)LzJ9m`dXoC<>63VbTF3`5;5tAafXDxCuZ%8R@V%PYbiD=U)Rd@~~ae3K2l%Y!pO zez6Qnay5+1_V6nx%`Gmk^2w~oPmOf7$SNz$N%csr3!@ENU3xzbj`IeO))n&^~iQA_ww~ia?T1(tx74X za4AViDyb+fGd40b4pTL9%W*SF4>Rxz_c6_Ma&+;?E_QMYPsz>-E^)OCsm!S}hGs*CAt*XpaHF8Ta^bU3P%W-s7HF7dfclSv4sWi_t5BGF8Pb#l44o`G9PmMCI zEDA0T&dtpWH#G=w^{;Spb4>Hd%=Rsba7qkFs|SBw}+9ZzgtvR zQbDG(hi_>}R<41$sdILKs*#hbk(;qsN>)i|wt10Th^moWW==&>m3gL1dRnD%a&n-D znYkq>`~v*5{4&eJ(?Wy1LlQ&NLRF2NDnXHJS!fhwKo}- z9OxL96%I}`uBA!2rUveg&ZVJF8HrIr$*JCvx#_A#PE|Q6zW#wlZjosjf#oj6?g4?W zc_GE_jy@^b1|ZK=2KWXiRypMaCM9`$2bcM~N2nUP1tb=h85wznB&r%YRd~9T7>86P z24$2drxfRu`vv+I`uMsyl?Qqnsv0>Z`bMUu6&sdCrly)DI_9VvIr$fP2IhKu8APcX zIpr8uI=KXgRe9#R6&8j#yHz@6<|n5I8b&$hd3hH4d3*RKSGagZWF+PXPH)mIs@fMi@j^IvXdu<)xut=Uqork7v45ySWaPxNsyO+s&hzDRKBNoaivLONtkO%Zm4&1NVcz0W2 zM@XTnkyEy1m3cu#rK_ruTcx)}QJJcdlZ$(ncWG8?n6a^2PLZK=fvS<4Ph>@kVWoeD zxmln^zHwP_a%Ewazi(coqq||0uScYJdRb1se^zdGlm)2lcXJB_f*+vs?p1&rl!l3bV8Tkm@|oB)`nU;Npw|pCJFxK(peYtk5jC3NNop zv+(3}$2?F>BvyL)RwQKvW)&5q!gNmg%=o@=Xhk5r5dG{2Zd!s zxOn&(CWfUYg?bt}`&ksalzaPnx#s&<7-v@{7p7;a8aV~#hZ*J=ri2=}MdUd8m-!R~ z`K6hL6{O^sl{*!?mHA{Bln1+|MtK((XQij)Is2D*hlNBIXO*fNxp@bNL}cf=c&4Ts zdxu$g=bPt6nHV~oC#9vQhvj6uC;B>tWEYy3xo4%9cxRTl2Ah}pmE;7KWLkK*CZ-kV zdFAAl24;u2dsjrIrWyn|8hHeO0z5y}(Z?}4vCObE$vw=;EilcuGRe=e%-J%<)XUL7 z8I<}X3RR8VoKgdv)BV#x3EM3@yUN?7AT!_D$ICI(yfQN}GN&xWJu2I~B+?)!Jft!t zEvK^5)3l^C!!a+w$HTJFE4vC*s^^)ex`vsg_!c;)7K6&$!eE!6Y>?#j4RU@Yir__qbfTA)tmtrq)spgcInVREg>TOwOmYQwq;cw*Rl@7{Gi6QC! zfnM(APWh3^Y0eRm7U|wurJ-3CX}L}=VdY*)C5hf?ZtfM zrs=6BQD!Da=H^K$X}O->DZb(EnSSPOJ{ctjszz?t@N zQX?|mO|w#hN&-}k+}u3MT+Iz~ywfuxJlq4lD^d-e%ZzhN9bH3G4U9azD-z56a|%+N zvI@Q3-Sg9ff{Tj013WTQa>@%U(~APiqY4cpeFKal3!Dx7b6iqVLP2FxYM!c*6Daty z!g38f%8Ux!b5xDoEL>ELok6t;v;_lh&VZ_Oa3fIF*!Ac`%^eI33Jjhujv*Cu-psA6 z@d^Kbe7~UmC7o?sl$-*kep^k;2uWIgB}mBnX!G)Kd$+iTWXIpSG;P}|N6$CE*KIuR z*AbR<-784NOK4-N-F|zU;@KyCW^~THGxKBR{7x7VWfm&v3_lXL{nE8es3WDU4l9CEuUC~?nG}} zs)?_!t-Y}^nf=^6+wR}r->2^`1RLPgVfX#rUFVPxk+L^88gp-NOMQL0cCz!o#qRwA zf`W~qtHT~1>y=J3cLCYv#9`PkZJw9#^wiWFyUX?EY^zc}RjRfA*|Ec7P0UUqLqo%x z?-UeU1e`b&RjOll6fiz~_)ty%VP)cp{=U9r%Y0|Y?MepAwoI6we7KGG$KUVw<0Ibf zRQ&(sJ_|c<RPFF*5z8S~ zDeAjdRO~qTZB~_{y^>PX?G#AJCb`G#C^+~aE=m;~dRJG4THe!y*m2^tpqyOa?(+9q z|HGXAU0WOd(|a4lTQ?N1ua7_e?d|QU_1p|cjvr4x-Y2`Nv2;*`dh-k zKR-D*ITuc`JO1QzWqo+>2grjb^^%YE9DH?kHCUB*MTNz&Ug`9Wze=i~ zot~x}{o(oi`lvNYAph*KnxYpgHC-<@Xt#9B4c(0!H&*<9yInWzey_CohTPj`Csu{7 zo-kuZ#>OsA#TJ22Ss%W<44$GJ9riX)AnE%Q?QlN-`F2}9VmmuKBlcFAs+gEudBpt> zqEtaiU(bc3Qg2#~+|s2>6-`V;HkG^#>JIhdP;8kH&fQky6es6x-#<+^nk{li!9rJU z)fNG#6RVrow*CoS9p>oL6SXG7a7+Gux%xjJ-M7737rdNL$}}tGq1C>+l zpVo=nV{zK>ie>)2JunqQt*we6Y(39 zS}*QDKg*O`sfklqZAR?()ncE_b8k&?-up|ut>*NB3H~78Oh0tb>EHbNf1HAj?R>Il z*3DMD|I6S>G+P@uvF{Q2CiW@+-X2at$Ls52zxKuND&ds1DskADbaaA#yq!tj9f|7i z@2+Ym>pYoVG%swmYej{{t7~h!n^?J(;`i08h}^u)QC?VB*s$b9z!a@eE(^UbvkxCW%v}2VnrlSFj8Lb2OOGiiC^+Qb{ePg5nUO(MJIv*H zpDgdKwNYEOZf(zB|L6Ao`|`Hc-;T^QP8X1n;IOy1-+bIhR|4ks=o9~+&CXX);^5{E z?we~{E%x)%Q|FaIOFz6^KL62Ve>=yMlT;tRd9y~m#JIgC`26p0xAPU1R>bYKI@TxK zt?1mgpycHx0YMq75)aMm8xozLoSe+==;+A7$?2Gopy0wXS11APo6w^YUA(k z?;pS2e*ai>{@$fi&dPl9wbeH;5J){W<>GG_J3T!+~k|NFBJfzyY;>_cJBbzx#!Aso@$asp5N|9swFK+tENjb!=z zdsmAOwQvdu-q}&e+)#JI{P~{h@9$o+`Y?2Kbv1P)JUY_pH8W;s(ZaH~w|11hoptee zTGs4c2J=4_GM&F?60#=Z?%Yf| zUAo4QO<(%c>X4P6f@(uRnVdth<%Y;Np--i2A~(C``_`qTEZOwVR!Qm5ivNE;^E3RN zkbkaHQBhGq5L6H?cI!R#>+5SldHH;Sg2r_32r-?A20mG<1qlb61O&glzRn-D_SzG( z^fNOirk;3zX=(RSr+uc!4$PSlzb?kId4jxvz=XF*$)cM3=W@TfO(*v6zftx3TW$x( zGQYW50pDI+TpY2lX6HrIpUdWze}5NiVQroK;laVG?EUj?Di?_tTCuZU6<9G!RURxXe^;E{zsE~CrH;Ia%uw1-&QEhwW^K-Hl z6%`NWmft(r%+5chDEs<4)7Qsti?`Nz_cMOTKXJa|@CLWVjz_yhC(fI!Q z6#VDeSQI_!2-5rf&T845>C>jQEpqK%E{+C9&Y>a;e*4;Nvao@`^(?iSf4YN0gmAMHe^KrBdETOsDsQnkKS-k6BLd2)Ve4X6mYH*Gj1F59(dcjY^T?u7 zBd|qJmR`&-xv{@~|E;?UN8a7ooX($heO>OyDoE}3>C%#=OBedhwaU1-h;^Co?6wIL z1iW4r&#@}iirSXbS$?mQonPK=%a7tokVp_yR#Ia6{QUg!Bb~yUOV1bn`}6b0?(+3l z=XFDzs@P&7G4QM~{-5Ug%O6~yYdAx+Olou3oI?vN1*Zggg--fxuyo3**K0(V8(9D0 z347D$c;~Ii>ePbOZGRq%|2wuubos`o5jN-I{GD?S1$${Oof6~~pSgJ5^2E;4X5${3gi4)SRQ0x{io+uvm4lm zm20hwk7gdZ@3}QYZoAis_>J0Z(S1i(+a*i^YreH>cKhy1crQj-l5s+gwmwtP+ zz*11`>|>sv5W`pLINL#F_|yoqR^{A&@J>LmgX7!WJ+l_)ZqK{Vs?>CO`hUlqL%}9_ z_oS2-ow8VQ(`@M!t63lmRxL_juXgzCU1!dp$?E4$T>N&G<0tp@yoQd1XMY0rg58j1 zDfvlrX^89dzgvWqntt)lc615Y8MRg>*S`3Ky~J1bGS|2bS+gTs|IEpl9pn`+3Q;|K z>n{eSrVnS?A4IPca$%|NJN7K%_e|$HzZsz?mQM+S8mj6a>*EwBE-s=SCuI21-yq{j zMF)pl&8Jq!n@z1Y7uO~8zp?mYeT%KUeY<~Q#cvV!&);uL*Zj)$yLm?O|6a3$Y~E>o ze+#)~cYi+`oj<*Kx4YeyI(3H0>ADU+e}wlIRreix^q1f2@mizQiR$@r6_vji6n2|M zZ2sF3^z1I1@6@H9R{eAQPi%TSd1L2`TiFb!rmg1*4>u246U1E0-goTg>XTJZt-iis z@2~m!@pnAGVZ{{|5#4WUpLXpyusufAXXfw4aW}aO`wdgy^{2v+8_K#`w|0pCNcx6?zd~WkD&HwMN zES?{f7kni1Noir@=FP@TQl{>^`)mWY)udWjS{}TsJio~KsVg*Vfp7b|irqP`9US-cq6L@hscEK`D(FW2$IeC{K5sAZf4VyVoH=tgl)RO@x}Muhb88DY zNljwxo#pJZ;p6t;4&REd89&rD0$1_4u-yJO|FU-Uh6(B}EOq}HtGu(9)o+$iYO;FI zlCt_ux6{77=ks@RPuD(vF4o)8w0C}-{qFCQEP@BO+poX&`up<9&mCM0-oAD{wQ`@f!=yL(#G z_4^;1p1*FLn!Ia*{omO~t5TC6%|CGUDsSoi+>RS9%Psz=OU;=(_hIlJmPPqC0jFX+ zs(W~M`+qnV@3r#qi3$FiOSLRAe{B~KE6eyxA~l1K3u!~^vj|8gHH1_VwSA9 zxa(N`Pf-^Z?rCexcfa}U^zW1|d+)c8u5y3>=C`c7*DCl}4U{!Di+(DX`^T)*l(&~% zDtGb8>Rd&oQVXkUV&f5@s;hEdo5xUN=Enqlx&dh=;J{U_&RcgQF; z>8I(}JbY{^B-_t7xBB4i(=G12;FWMwdcNKv||0s|I==!d}Ukwx?TQdZS+F! zpOVvGrO52=QMCVcjJbz*W@nM;Dvji~&)zli&8>cT;pYC3{bm|VuLv1E3HHk5tA6mM z)WXto;e_YXFRm<}-?;1RyT9!{q30^^E&nXMtd3dm;GX$mvp|s;FTTaS?$^^|d4Hcj z!WplwGfGx4Td2L~v^_}u<-3OY{yeoJb~@6@M#)1xN8 zk6t(FqUq1py?X*TOjurY=E{WqpFhZh(!5U0u7eBa=)HJvSr+&;yCujgJ`7aeoK4gY zU;Q)3^4vRNxBqg&|6Oic1#Q2(RBy-iwbvc;kI$L;*;!F(#j=ma70wA>H^twsyS%{D zyE(Ra(fZ|D^Y7%}6mNYjp6BfH!SAnI)ptRe{RPsc|F-L7zBwX!sxbD%|DtpJRsGh* zw`F&KH?{cgbSL@CulEHV9ClLQI*aaHnXv!+5z!6~uiw+$uT1^zHFIeH z)X4tXyV=9?ELY6Q_PZ(G`X`rtQQ(QsyrB5~X3dy!eIt9<^or97n!bAvs0j#~XTMN?;)c#`JBhE$H>uY@%`&}cwqo|D z)dx~*v?bn5&i6Xe-&wy%=c{JW7Kx~>uUpJycO5!?`}xlM>jJ!1?mY?0a+WVP^s76% z7?k*&C_bHgqt=OYk@zFI#R-c38Cky{Z+|@f!j@EPk#C>e9xAzB+>&aYV_y;XKBoB8 ztWVwB*%$wFx&7cBs5Fd;y&JGE$h5_0_WCuu`+Y06E%rP9uJ+;6x6J0{{GdYK_<6gM z+CR>!Z?a7NH9s#dEY?4wW)q!vdBx4&CnlTgbBTQlxpq}L`?t#(*^<8>lz&WH-Euvy z*!8E^)erL-7xUQVny35zdwKeQ!iN={6|dhGRlGF~7twjr`O5Tl(l7JwopSSoUKxPW z>q^-b-0!C8d224cVhGO2))N%x2YIcugh+zRnJY4&I{gY?3Mivz^?{-f)MT=nfnb#h zeo9|!vGkWiAV?-COaC07buOgIqyEQa(YmE8?IIw}nOnQk*V!GoQ((>UQ@iYV!sm+> zSJ`_YZKde);3vKixltp44Tn{GZ#50N(j8|jsMcc!+VvZCSDMU{876y+KGj@~=; zr}W|~eyOEklefLvv-P4y@yBgPH}AOWWXCY4V`r=6Y5(NQ6D{WK{O0i0aS2#iNbdQV z+6G_uM+U`5Ggq_J95|&Y&+te%cw!C>}YO0Pd{Oi*;h+g#X{~*FV0n(D}yRYIC#P zTP~VQLsY-Nxw*JXUSsK((6aCE?pl<*5Xif`EA{H1+^-Y%SA0x5GsBR1b@=*Y*VaaF zOgk&(rMXn=WaW$?udAi6udR*PnkAZcW=7+M3jv_^bZgDpux`!ZWe;v_O!n~gg@o~x zv=1*XE}mgmyX$DNMZ(bu_WAd0Vs@ALem%Gt9Jp7)_SXC~+V}Tcbay(aC0zUaTjJwm zy@)t^x2yDZ!Gi;gkcKj7*!9uT?p6E}5aYMy`S|cWJ3l`kl5#3OB&evWu0*u5J3Bii zj8ZzlZRWcDb$>(Tbs%Zu?a{iwzamZgjh{f;{Hr!0k|rsvEa$pUC)F=xm|Vy(*-`Yg z>!MNmxjBKSQnqDYUiRYte*16l@8=iHb6b3{`u*PYviWNM^Li|cpV@3bb8@o!<~PA^ z(&l+7a)i%N^1mMHGeyu3`pFsbFyg@w)+_tjQE z{WDcN{MFp)b0^H7GpA>n@9alEK0ZFLyPW&y*;%HSUk9hD`^;GI%i{H&t=ZxxQoPsJ z#jdW3*I0Vx5;%YM?npf?mSG}Q^W~!Zg$$Dyx3;dnShQ<`eaVY}#r5&~>ugP=c$t`( zCd`){hppaoAb>!GFsVUWgC;WYVUEkl^yYOO0!LKiwPb12l{=K=m`QuUX_&pnBz@D83 z3h6B7e}BK20Q(`u`zk!#f%Nb&;3AV)a@>N zyCMJnKJjVB=1=;i&H1Ey*(Oe$$X0*3cClghHJzt7_EvxY@b~-u#ns>6efa*}eRJB` z4=2^SjO|q}?Ojh?lw#0MtgZJ? zlOy!zZ1em@Wp8g~+~2o1?!jEsY%vol-e+fLFAx7$8J&@KXI0SmHpk^V7595j*SoQ= zcDHA$?_HSNrS1rKR3%eDZcOQoZ0I zo2*y+73Q`5;jLctXnRJAs=F>^_A1{~B7m%0FztCr?`2W;Y?GN9-U%x%~d1c!3W6`lE&i&Zo z-Y?f1wKWSgN9W<|dvtB|cAu)x1)$NgW&fl<8Q1@wIBQl{8?Q7d|HVs!GEGS8?=LSu z9}<}TF}N}~Z;$CROU3$=lT;;NdfJX4Y|XyD;9|yw6@kh=vrJANcz1XA z>$?+X&zfbdxIh2?J~r>TdFf@_FI@S1=>1gf@F`2Jq|I^y{>h1pv-i3+2QGG-88Tn* ziS%vFcE$NYzqG)O7p+|x7Z>T2&-C>DTYmD~@Av!H>)hN}cA~$#+xhOU(q6rvCr`a; ztBJk9$IE;4#l^)R-|c=cr6ZViV}qiUNk+r&^7pS~{XIP$0|F-8JG&c{*?;+fWB#IE z>@J7J7ayFNY3%lY-lR!Q({!Vq7GHdDq*K_#cWd2=_|0j&zrMfE54(Gl_hB2oD3+U|NZ-yad{cgwqa)xZQvJU_KX+7oT=a?=Hi#uH zEZqEl-|uq}>Hq)!-q?`XjF8@)ch^eBrs4uvTE602=D(+>r-#a$KA8yQc+tt@J9ATQ()A^zOD?>9BP%otc-HC7zt58g}|Y82?X(g5TeAW$bDs zc9*?9bgWlebLo^-D$f(WG()f7-CeG1WF*wt*%@~BzNHX&BFu?H(UKk3EaPLNKPzSO zmMtbNt*wb?W*BB%T_tKF#k*(kUO@>74#9<5(h?FJ`T6-7CQ@6nu5u+G?*mO7%Gp*e z`L;A!c#d^B-|6Z4$6GjsFKkS9FL`@QH1*UJ$B9a=58k~y_j-oalE%iyg7R|riAt^k z%X}nDUS49IYgKyaNT=||nx923?(VDqUfCj1Cj068+uQAy#m^Sh|NqCq!_zW0ZkdV-o zcX``3uAWf;`&+I~#0Cc6StgF_Vs>6w=*-R|ZzsborsLq^!m=)AXH!cH%aSEaR@@DB zl9@JTiinDe%7ar=wL3aG4m7cH8|2(D*phvn@7%d_8Mn4*is{94SeL(BaW}MESW2qv zP%F2xiVDl^?fK!mt)Hmg*p@2|s*jaiyAobrTAEdUJZs7F<&USw*EueB>kU{Lr26Ik zefftEAM*YDqjDtZ&c0gd#KgoG*VoIRnyTG?tXDeu zdcA&spZ>ldOxES^T&k*e-MXvPlJe-~<>favCbO&g&*Sm;_uupVUiFO~g^O?9t;$qV zRt^MN_2MFP{r|t)Z{1ZCQ?av?ySuB@d8$?@sAAf>JK6Bvy}jLmi`_s``{L$ibxKWL%B-s>Xg2px+v;yF2?+{%@%z@4g>xv1 zJ$rPtdqw+(v${)9$nPtii%A4=^|6f-Y&QY0aS^iGOs^mq(&f@2*?uy#x zC_SnF|M&Zf;N^UmE?o+F%dV*M+{4q;@$4+qh2Q>6o+)4dXQForM?<=Ic}`_--RiKl zpegdLyB#@x${%jyb)IdOd*xkQ(<{aL8ygZq-LR~;!cJFGZ*9pm6An>QR=&6~*?sG7 z$D9s6S*sg&%>6)f$%or`H)dW|+q&C{1PT3HC)#JZAsSY8IIE z8RYQf<9(sKr9mN3eo?V%j$N(P)m5RvyQR6z^Y686-faBt-rm)qz>NeI%^e-DqV-mb zeRA)U>GYhe_OxPmi@@!aKR-T77^n4Qy=`x6Tjbs^w`!|@&UC%lT@S9u*N5(wuK4rA zaL?YoptUYXT<%po=C!H(}~k}e}5H~^uNA6{_^tjssGZh-M%f|D{X#ko^7?x zjqJ~vmzVLns2o4H`D$5Culc4m4m;C;Ge!ZKAhezMGOrF>i;(|3lpH7dQRy}jV{>;m2 z9UW)?TFlgKuVMJ{X7hOgDXCRnxBchawQkt*ftV z$Da89?VH>0Z*N6!Z^>-t64lb^PrbOvRqV~qN`oi2^|EZ=$?*Pk?~`$yYgJmbMA!Y` zM0MZjsvMr5+gjZgAM6xXFIuxV?d+_?-{0O^UW*G$;rXejrx*Dzj>Ea`+~&&9Xe?MdAgb4y299Cs-GR)V9>vR2N2-sg&+ri;C&t~S^vW~}_ z>i+(cxgTeI+^O#TJlmhn;WrP!gX8%6{r_eu@9&Wo6H7C`o%Ra6y6obrQ0EK79P>*e_>W@b6FMQlHIfXL+s(-``g&%`0tYa)VPzRh9MJ zIgq37?Y+I=uTAAAjq88x|Nm)jWM*Gf^z>9gaj|o+lqslZWK;3M;r*U@w$+C&UlxA( z@}-1Xj>Mh2cMbFJ*(^PG2DB`W3A8vUdYg`rkkBT>Mbnnn{{9xRx2m*nt9kY{o!K|z zu8EWsS4Bif%$Ymau;N3);-^~S>v-(#?R#V_FTI$PcWNsS!^@X1BerA+W?fy?TJ!VM z!hnTNps|LHrLV*G7VWye`p6L%(8?9ltSb$xS8Hp9t?8Iw_v_@6<7r+e-d|eE?ZP5$ zp2zax&udwOx0RRoR+o2h@X1&tT)6k@>S{%$Q*Erfu6q<8SN~kO>>F=|*|wjbpC6yB z?k^xD)MS3YMp#czZ$-$;Ne3@2J@a-(u^z*puh-*W+}f(`HASQG(b4Y5hxzS~%(X6m z@cws`oN!^O{r&Ceqodsur%h|iyu8fOWkc3gt)=H|tG{XFv#9yaSs~-^;nC2sAaJo8 zL&J|B6;pH~g_N44OtV1COjbl}RB~Yv)ebwdxBB~(;?jlg-;e!0YkvR8r>Cc<9-m_P zMBc7OA}=pbBXpGrsNZ~=_mj7PlvG#Y<6}=fqSgK9ZJB94f7znhm&@PYYF*|#dr`*4 zMQryk96NT*$z_VU!OZ6+!Ru2QW*8(kZE{)WJKHSd!U9I<+8!4d7mcSS>%((zZ*$$4 zbkv~Wfy0)(yHb^(o-}qOyuGzmza~eY>u0~b{juBm`*{Te>uP^gUf;d){On$7b0?Rc zeK)!J_}V%&ZvRW;ySS{+|Jmm=m42_As@BKteen2kGb1w_s0CS2T-@A|aCVky496&A7Cr?_~c%>EuFZcWK>66pdRiS|komh8s{$vmk5ouYmVuehmw!2f^E` zW1_w0^@C!$BIlWp8#|v&fEvHOy*&#n>%rT%rGI{UdU$sJzLnyi&Pr}R1Bz=EeKj>T zHr=Q#8?vOgZIMw*KEd7~X`JTZ0`lJ8qM6SB=2#X#c=M*`>}>PbQ@eY5T2`;tJ~dr` zz0PmLZ87fR2}awDGcTz;IW?7g-n@Ak7Zx-Y)m&W_Dj*0N^kn#O|NlR6rKZfw%QSPB zEMKmD{(4WoK-IT5oiRI$4jw+t?A|A1SmCp4Lj9{NnorKpmp|Gidid(<@Uv3R*^?&6 zZ=bFoAII7K&FPrs-*)-BhK?6!r!p9nz6$B!SQoQ1sl?CxYt*%@tr`&<6uhSCbWWKf zvh+?PGrNM)i<_I57x{dz3SAdtd1q&_yXC*z`THGRF075-E^fDNjnlu$>iz`}53!cK zy27c{bZV-$w)d`i<$GuU+Ee-CgW{*Br`sM+S?h$@7U#Q7z7Xg`ucima%X2JXt|%u zk#$c0Zf;Hw+*$NgCiC0Re%A z)#2-WX-!R5w70dDZRHksJK7~WHQ9HTNu%3h$AAEVxazm2sov)gHnTgs6nuLV zX;bxuV`uSmw^lAuo_h`_4|IEda&`eQ(Z}kiH)u8MEm-> zBS((xIg$}I@A2{e#c5|}h2^K8n`0?qSF_{l?Tsdq&%Z=(&r=Lv7b9U+vLbHgEYs{o zZoN_r3E)_&`}4za&fK|yi(EMOzD>J)`Euc}FPVF#ZA#aN&wM`d%a;<+%BslCY5Fxe z=Wja&r$0Q@`qggX*;iHO-zyhKZz}%wCi3Zwyp`wXSQbx7mUP}?`FQe2o8sTgq;H?G z_RriJwPWW_&HF!6^N!Cc&cCyRQPA=AwY3vx>PBz75qpAt-r2?XYd-g0y}hyO#P_dX zx9;q`HLu`_=jT-U*_M+pUR>b&M71K;*5&8tA31t7a&2Al z37Jof4=yZp77z?s9p?M9#US~_{`&v_xDUG@d+r-%ey{BHHD0Cj*Phx}eqwp|?j5MG zdv<<4zu>`#huaw$LRW{mUR>n5@M_l7{9BuD$*zvwT_zy-RN6H5h2ghlt8#8`0xi*+ zsN|}kR8Um3X{WSAank-jQ>KVa(TTjYDmS9Lr`U?YB4lnk_9Y4;~zBuK4j`q2vCN_2C;gZe*)JJ>PzRq@w+2u~~0#Z+GvP zvo%OQ#-pOD`f~2%t6%fpeRlgk2Q&^j)kxO54AkO$^!V}DzEmDiA$9EYnYXj^a(ZiD zKY95wGw4M9#hupW@6LQW%y`!{IbTtpp-Wsp@5}$glOjx?Z4-qz6x)w8ZVL zVyr0JKeZ#_)02~K^5+{EnH7{e`uf;DJIxpb!M`zWo5-kvvY zcizg@t?Q>VGP6Cnoxgu;mh?5H&vQTn=*}($FD@uDoZq)lK=5hv{xik9c3%Iq*uCGN z`dd!O${^R7(`{{RF6=JP-}_;8_1;r5hZsLQ@3TC;b&<7btO#`G{Q0ae^4Wy^ zR}w;(Z*IvHj@p`)I(3)vz85Qe!xZfe4F&V}d}O90TS|M^V1g9Frh|MI1zqN-|9>gj2xc0PRgkRk4m+wR)m-vlbln(aR1 zzcAdU9e#6DYT$+h$CzCuoD3Ism1cKv)ct%aK3Oi@FY)Z_r|<6W)(BY8aP`%8vzgDo zw6w5TSXw6D*pSFD?`=}p${^LHHP0;X-pRI9uK)X0yzcMUaFhIdGD_#K9eaIG!7kr? z#~hBIGjE%IuViMht@_f?@!;3j*QbK!o>_SPhMuL>JafBeHIwG=%-^-q^xLt!S0?;l zRBWDqZ_ZQopPHJQWxqY`7Yf4X*yg*xrqnfz^!uE~7!wj(xNA{q%(X|^hMcNtsuCtF+Fz!d?C z&G|iBqI6e*Z|NJz-YF=PF6lERnQxb27L7 zTI>7FQkH>@fD>Zhkf8McXk zI$L{>;lEw}{(rx+LE}Vna&m0;yQW&MnQdiv?Anvrx34sD5^OEvR|0>nVHg?QNznj{p4pTyfEU zHAKZ@WB310@t+@$`^DB@k8|1=wmS6N=JRu{x8LJ_`{U!|spiZ4=i7z$ou6whuGA!` z?51(NB=O*lul0LV zb8rEzl-D|bmhrTzZi6(Rmif6wtraVMb)~W6-#e?5Cr^46oRau-meW_#d7ovn$C>2U*VcyJ z^t&AAWcMLwea2n0t&+}lMMXxS4uhcM#-yVcmU@f-`2E}Y$xXh9@9*!2pL%z1ue4H= zR_LmPJ39)i?mN8vr>6AdVY~d4$CBk@Cx0B1uJ{)GRn==sL&t)&v$I~@+iR^Ax{5{6 z5mZNiJ87=|WcjjXi(EQ|3d+i^&E~#xBcv0ImgidveqvP6(&CbnlRGh0TU^R4r{mmQ>%+HiOFKF`o|tcMKg%RDDWHAtg{|4) ztN(^|Uv;Wm7qfGR&Os)NPfId8kEfkWDf#gsv7(~F;mJ+Ap9c>f)QH-map&$`K?#W! z;-KK0x&4AkDNko5s0C8_>B+{5z28JWHP+8JpSk~B{f7sP$NFSf|1r3GJAZ%g!^7>; zHtv@%T>_OmPtML3pEqw_)$2NWyBd!0@bHd~d;R;CE?>Sl;b0Tnd3)LN_xFxAFfxNi z2zTt-wJ2(9mV|B9mV?q!#m;sNYhrhcDK#}TFzo&5pu47YuSwRG6Az}hzU56Ww)*hK z{jJN92kl2T{c3a$UjOvTlN6sh7J^StO>JJiTHD6f7L+7g+S*<{<&Wn1+3@nbwCt{} zFZ1{PTz0W$s-+7!2Yv$OK(3z$HmlaiT9r6FJk{D|5TPF1ELC zrtOB>-({269ek58b;ADq`*vIM@84Tgx^ERI@KzpAJLgjQ@DOXwhlA`IVQVD1L^PZD zWUU^2d3jmFqF}*6{d=>l(q>+NGTW-{NX@OS*~{MN>&hX^L#EQPI~|q0RZ%-%kAW^tANd)EB86y-sYuF8@iP<%Zd{AB=)+HA#He zcEpM4L^S;S`#XA1oRbXW+OTdRRj&mhD}$=+jaFXGGR(N3(9v;FpX;`C?XNF}Rr9Z$ zPMSL5{{H%Uornzw7L^J)o%r+fvthvlhwf`9C-|Q^D0SVtpkTu$T}{O!0(awnF7=vv z=(v1+&&S8dl@$~YyipC=c540pe_eX9yCU|)t#0{q;DEzo^Hu?;6Q?(1U)M9qyK@7) z1iN^{v9FI_zU;Ive-{CASZC?$YYXG{R%wK+5a1HgXsG)7D)HT&omI1wZ)kIJas{G2-eUR>zR{^#Ry zd9k&ZwG{84KF!U^$tkvWvVaq)Qe5QP4PGP;)&^R^er~RHG+3LUhzMxQUDOu8)h#vV z=H`26D|09w5xTR#US3X4PHgRD#rZRum#&}ADXf-oX^AJu#H9Kjd3(9}_5XIdzTR?c zR*e(KMz(45;w3(HT1Nb;dU<1`@}WbAbfVN1kNk_+mNWC{_ut#SPCV~cQ#=wCQC0V_ z#`Vm>OV_=p>%|I)il$z);#B;0d}Z+R3u~jz!NJY3Q9DQR$?LikzAm5%{pZtU&GY62 zUAwXVsL=02m#)XpG)gV__{g>UnkUCb`=q3#2d`geU$g?PVJ~Z4+xo|`nJqQaZ}rtf z*RM}kyx(?XOQx_3i}!TBv)6u3+xHKY0Dt_bfM|a7=+T3>Z*yUqbz5u7%gZaOtCekS z?`G^Qetyo;<-x;;2Pfn!--tRJa2d(yh!xhc-Gchr-pseiJgM-bz z(^kjqH2U-B4`_6DvEu(vPfx$Nw3PeQG~Mn!m7fnCKMtzRj(&W6{OhT&@9)cF9Xb&b z(~U~Gux{m7rUUWye_PkZ?go{p8gY9p?EZXk25oit_wV0|;N^XBH`V>;J?Pfo=P=i* z^ueoFT=MerUtU~fetv$w`K|ly{PIaRHY939Z_|mZ|695x_qG@(Cnsp;+FiaDv@zts z-Mg}ljg4;kcBb3+iud|&PCGkgX|PrP{e8J#{_APyJp85HVxcx|9>0v%6Z_gvBIZet z3LhR3b$R=n`FQ!9#Z#g#toSQl@bHM}A4%!{<^ETGY8FOwMDCifACyIMZ;7~ei#Q{*NC& z5)L#lg4WgCxpPNBDPnV)@3vQa3m!6ciD)vZtE-!2Tws`MRoXOty7;QFwOw4IS}$7v zwDZe9x?6s~wF9)P#&Lbz-U}NNok44!&z|LF=a;+kdj0I#(otJ78ed&qo&4p+MbIpO z3k#^vvo>mLz`mNw9vMp^PtR?aA_v#IW2lH?I%~mPXwe`{5x?uNo*QCcy z_cs(Cl+%&e%Ot(;e)Ems@Zv8&^SBkYlTZAA*e*n__pYm+M@p6~s=aZZx>~f9sjyyTTr?^dElTq6r>>X{BC-3j>7Wba67wWX3FZcE~ zM;C+gcQH?A=pAWmYkM{IYSvcJKc*Rd^z<9md1R z$hf2G>#D|G=G@`y424v;Fbuw0_{WoSQn%|K9KapI5MM`SRs)9j06F&9yFf za`|vIJYI56`Tu{ldnc^_{k{8&tzB&W|9`*p_g2~e{Sw^Kad6hJoyE^zwf=dtE&bFF zP`QyK+E&A$UOxZ$yVUkjezPe-wdH!2Etji#o2RL6Y@IOG5^7m{_@M^s$=`quoCEAc&H8tGsiTfIxuDRZP zE9KHOmL9t+SS9fLn#8+DNt!u|DXDxvIXO8Cii!?xNIcBI04j$e z^>QvO^0i+CzrDNLz1)BP-4ik3xsmbbUI8n39nzApAA-_K`P zs$X7Os&hK;{=T~jzWbIhSLYJdYH8<}2dBCfK})&3rt9SvDCd+p?^}B7w2d8uvz_9R zvdUEFe}~!oKRn(sbyd5#*UC&i8R@>`{PXwxIGL-qO<+?`>z|*WpF6u;SnSTwFSpNFUA#OAf;r8A_G&aMn!eN`(uDDBQcHBC)U)2u57 z96ui)?+>?&y(TqKt z6#sAEplq?@-I7-aTZ6J@{k}fO;mE1QV)MCk6jLO*f9~@C;5sk&=?udZ>FCM#w`2x) zaNOFK+x_zL^5hQ>4r;{gFo@cc!6@kX^V8FfRbR6hp0!34zv}JkYHDw1FM4_kG$OvJ z`1v`|I8Q);z^CWu`_+7BX?#^vR|gI32?&B_S7UdTH16MT@51u*)YQX&e}A94X&T4R zX&L8l{CI=heem$}GqZSJc>_{V#+{rnf7&O3{eE|sRAnrjGVA#!ee*{To886c@@w>ab ztDfoze$u@-Z{tT#Z*OPNXqgN%)YtJnD+_<()r`mb-Rd6dJbd^X@ zP|%k?ekl_Ur6y(fz7;o|Zy&j~Zhz_W^$R}Ch`*q2BlKGDUZ^cg;ICCX-#v=`!q?q$ z{rJKg72js9`s|nnTCOl-pX77Cm=N0y0bXZ6zxfx@a`sNr#h(YRIZd-&d|P{e$jYD} zKOXlR*8QnqVPSD_xv)%o9|kE;c%AMJY8cvMWr?7_`jae@a=T_`=TYrWyUz|uFnp3c_2H+|OC?8m(4 z%{P_i-41)JsPyCSkI(fRf1KvLtf=&(v?6CI&R_NJl;!O&WwRiW^%;$=GRcf^Clkc+JmW zeqnD!{j;vn=gr~=C*-HUa;lsC{T66~s`I0&^n-<{dt_m^Juws>vt{+YSsFYKfkfVvhwJHb+)s=Ev}sWJ>bY`Y1ch_F0QJ~ z&WYcBJmbWEnXl?@ypr9oLbD!hy&mT*sGq+jU%2J2^!JxjH;0|D-|&~?)|NjEOTDK$ z-m|F;JaSt4tkl*6%RapInmM6xvZB(Ds$<*Pp8NmH(Q;ui-ajMzTk@+5Y7(s6B8RRP zn_pb+9=~Jfu7{uf_lNwQ6&T%C^T=aG<=@(UUs?VM%dM=cSU6=?+IrD;!_?!NtIx16 zI{*%xf9vgA7w@ZA*uy1m7_n({`Rj%02P?j~ELQygW1oh|cTGM)$E94M7vBB-_CYuJ z+fDt_X`0uO)JG;i$&mR|l*&Zw)Xp;YFo};Yk>g#Uz_UN6GnDKc=iDLYB-LC%a{QG8pocro} z&n<9Fu6?a<8`~{)`C7qjAFsD3OgDP2Fil>2^7~T5C;K0z@%+5(l;-5}rRmw%&mVtO z{7^q%XIpeUaIxb4=;Lw%f<3bTG}pNvfB5TXMT~8Eu%>m+0gtB-@`G8CQQz_kPdyxN6?idsWey=cN??A6{3hpcL`rtJ2=b z!pH0Q1P`v!-yhU>bxoYKp5FS0{g0IRf zZ7b^5-stL+`6PdTf7foqg&sZsq_+FF1xTA4{>r}78{2gJ`{w=Aw?T8Ax3yzI=HX{-e{XDjXWl&Fee4pA zKUr6g9-MoeC+FT1+YS!CfA-bSWyOwO{q~SOz)Hz%RtHzd-5VRVv(^MAdm9|P(R(ps z(J?X8YZ1xbZ}uu^C~#Zaw$9|v|BZ9q zTTCPRy^mcm@$9D3Mc*fPxr9#nb3Z=iao~x9C-b%*?@tW=7<`4v zhI9VGkb-$`XOH(kygae^nq2?-z60;=pHKC!zj*AM*zaRWo&{Ym4jnS+Nz>u-I&`V~ z)!v8+s{3bsUiCzLnfT)NX#E=zx4910y{@w9;E30Y=sQ&6dU87NQ_b98XAd&BHFQiU zvzFd%_MTPn;q3L>wREHR=hZrXy8QkL$K}#hPGS79_7$^hv}NZV*GoA0C$%8!(He!H zh342%qK<`B>&vc5?@RB9yR;mguOGVWuczE(N0%LP0)11v!<#z_zU-dt zb$VOg&9Di7UNb&aQ2O%vX`gd--&C)p5Gk(9rOH1$12calY+-+s^TyG|z|_$7x?i4z z*$Z3NllngTZpXjPP0UN{d$WY8<;PtH!IOWr7QHk)sba5b<@=>BXyOX5N&jz8c6su9 zi*Rg!nWFW0-yaTd#5YY+Jee}5eIe^7gfpFc=`u|cd=UEItk>*KWkI+eWn9X!J} zcio}b3g3QDwVx|D&V(eRiGL|uyAPx|FM<3ZMWzC z`*6BaZPx|^p2d=NmggUAI~%|HI&v9}tvFOG`AG)}3<2kuKz5M>u&do4(*tjmeze0QJpBoog1O$)u$?#W4SMO}kI9=qU z^Yhe7tx5lbZ^ha4#%b)DVsz_y=9L;7@B7QI{#%rH)A5^uNJFPwp_EyA#>`m@qo=3rz{i{nY+jwM_O$m+ndVk^jyNlsJo+kbj zk5ByMemP)$p6!>{Pygl1#ym;8tm#w#lZRPQQOzVS@&7CtVefrKtGb?~&)sn3eyh2w z_@zrfQvMVO2wv0ux#fyc+A=i|ckSEab5lNUSQj7uCAmKOpWcqfHIuh)y%m3j*W2!J zNNvQY&=%GIs;5-;8|;mc=(kHhX0!9@(!C$eOwEKR+o&DBy42Fg$LGST%Iq_U*J~A&a$^6leNnn{RS4@z_J*FrD!V%Nd4Br(^=shb zHbytzcc&+hbW_xSo`MM^vTT4htD13P0ck#R+*eM6VPT>izOgyPye^Otw%RPTPpMjy7)4{n8ODFxm ze%NA%mF1$1&9d|E)>~!=lx>mk>ESU`?>XSn01X1}*u zUm5S}YTL65J`8j+Fw>pStkk5U>9TOn@fD6uPmaf1em(Xw?|q!D@~_IIlj#!6_J9kM znzogDHazK8|EX_uI5Bp=Ud$}BL+^hYW_kbH7W&!I<;4D4&glKK!dE@G=C*ktw*^-($uGUP#yuggnj-)Y{i>iV(Z^$oT%v#(F@?a=n* za?JJS{__5+>zCpqlg=CTNNjkrx?|a-`BF1FKD_`u zI=jAvr7zmQyXT6+-40Nr!QA}!gE)^(FOEmbdhVUnGdsE_aDrN3wd}N-kE-&MSkBD; zwy+5l+Zz9s3*O(9EM23Mn17pLar-W1bGMJF5zD`=alOvBHZd=)OYd#;!Q%UFZ|-hz zd6*ip(tMA>mU{k(>+8F%!lqSh?~iU32Nzu>oZkDoR%nA_oa1&~V2NN-m*BRyN}CpK z-n#GZiP**yT8}pfx>={(J>_=fox|4Co1Yj@WdxOZC9DvQhVAQ=RW$-jIFq_27QNI< z;&0C?o!x0y*zvtb=wS{=!{wCc#CT}?@9fgq6L&M6 z{(3NOS&5O7k>JW_cWb8AEHgR)is^L;C&LAORpN7gCYD^6n?8M>L)|Cki~Zf2$C{tG z@7VOEB`P)Y)67#0pwehplWM%{m8pLoxVpPemv<|Fd+=@a#(?d%5hw~GK547#{0xoxjp+!xLkk#oFLnaevXFN%30~PhRI0=gVaj)Lgo7uIVwJ zVt2PC{V#%I29NIEyKwokbFMeHh*x@ult_^vSXZORb-DZFy59)` z6JH!sTgKA0)pl9Ut!CBu-IM-nC`!1rY&`tC8|;+%tCW6PGcWd%O-s9afbDJ7m!rRo zr@O38zTSA)!R6_;IM=)VX15DgpD$hd^eZnGPY+YemZCHE>SGU-=K;E%Tlb$5P6uAl$g$xje} ztzYxbFiRlo+M34sn-5K`GyGZeioHRFQPuV12fGivyGpaTSKrziaQYC>+*?~iEYha^ znZ8nIQvTHgY$P?{M@?`TeHK!Qx)#WjU9YQf+gt z)A7^){P>XK64QCHGB)zl{WDGH#Tq*T7Q69&DR|v#sC;~(fB(J>2HP0~7v`U@tC;X> zN6haC*EUd?-QS$Z*})O_+nn=sP=3Y2X|0m8Og0@q^)q;5(!CIqvJK6Rjf_nG@1{)t z6SmH;ASa`{_q5&NWlxI(ZeL;w+nT+w)mykn?p5D=bKRr6YwtL?-1v3XCh_iq;N1xc z2@K1R@op^o7os!AlCgBHPGVwS+iyW}22ZA%{+!Si(kh%cw)*|rUi$o#r^BR%_vX5+ zf(vW2!*ym`OMYHf%dz|zucONj(Sj+e@t#`UPs(L2d|p29E|X;xJh*0>>bd2fE-N2@ zyx}9WV`4(DEIGdA<4PP(7|q@wkcc=5c%OOs45T)y1AvQl@Bua1CS^2z$$uN!wI zp43+s3{P6Hem(zdHZzaibJuP;^LBRUionHeb7os{mw%3*{^wJZYP@OVJw*j2lcEBb zFI%mP<}W>##4hs7?+w^izh3Xc<;#cfu1;?$+I?N0O)&9JHFHbR|LNkrx2B0z z)O*-FOlsIKckJNRlY+gnZ$7;F=bL!u!LT)kJN!j53 zb8jEKdZLlDKmU7QLEfVVd+x_AMw3)t-hZ&{X>r7-sY^C_*E!td-ty`avr9`pvy#ht z{l#<5LR5bm*@)?|3qEWK7ZHybk;1y|{0xYLowau~Rr2V+ucYV`iOr`~KC=_YN)}tath5 zRsO!VPU6b;*&z+ewGJ*fwq@;es#WimW>Z#n{`2_Ousqr8aK66%`YF5pGd}ao&;Q4#mur3C${sPDtmuWQ&)-GVerdW> z78mb-cNgoEy$2NfH4QdzXo*DW0#au>Q@Y z^?9>HO(H&pE>ZHTe2Z!DAzoOsD6PABje$jvV*^9US9m(LI_Ggplzt@LL|J9X9XC~gO zSiV)er-vtSN(Ji#6@`DNG^fUMb$IM3zP~?Q{L70wetEmE@7>2PsCdS`?oZwEZMrNc z`K`8cyR>Mhf7iHDe)`mTN0*8z0z4i<1^*tT2?(yMtmXF*+VK7UL)V|%^-r!X|Ncty z&D{+wm(_a?S+9+4eGVEHSiZ@7U;Z=U8>VGUf)97E-Yz)dd(bD_XZp;73-`@s7JF;e z)UZd~LU4L38?SU@$Ac@oWPkPjV(5*D@~AH?&1@__$uBtJJCioQ=YE;KS$5%x^KIMr z@+%#hr)}t^`U{j55Bu-plW8-{+Z%BBfc3$Jhp)8t$=aJA>>jc!9qL%PxFyvl#@&>?Om^x& z<>2jhW@~SDooU%)zHaeJaif_f`g4?Jr4sYA`)#AYKYUTH?&vbZ@Mm!ATYh;PCoS)P ziBY%bOT0ZeX}#>p=^uaX_F@)t~Kc>f9)3 zH`|79g35yJ(d=Jd-HOYbyI%b46t1AhZ4oxVlYhwHZ+!GbU|Nmn$~BYTPyZ_Nd)@KH z*Z2SbX1QxyQ~b4HmM5O7@$vEUYR|-Oypq=1e(wQiu%pY2FHf%@x_cj#!?S+H2ngEE z+0MgQssEt(zx#|?*PD8ig%`#++~c0(Kl#9kBO<=hTSK)j{@ZyzF)vb@SIVsE`(CAm zXUy+iil2AK-9KR0Jhf8p6O+5F1P`w3+^n#Ju~L7-MPm+^mbk;mr|gpVz4q4Zg{^>K zn2d$Cj&-rQyl?bYR>8efpRCSuE>(^H*4@2k?rEi;v$jOKHmkAP=2udJoquV23m%#GXs?@U;jQx?Rib4K^U ziwhBX@-sOr&7^ykVQT?E12wC~a<}ZVH-xkh=U05?>ENg~*I`uQte7OGCA)4z(mJP` zkbdLhJ2U=l^4OQN<6NI9x9GQzCx1LNFj>>NU;eJxv|h<)eQjmeCaAPJ`1|v>Z(A#3 zS3OUVjhUIb#_lWg&gWeJsw9q2TCX(K^;2kK{C|djCZ3Y4ykU z1}g%Y`Si{f8a|(SCtdvA|At#%r4DS$t7UDxQ~Pt;1|PGK^jD{R4{Zupy=DHH^9wJ} zg#-HpYmELWPuV%+Gmka%;wN$qwWYScuhoRVvflo7dg|BnwS39J##>(BbAMT968YUD zM5==$mW?MgV|u9j`D~q^v$ibUHA(+B)0Ue3U%WjcRpKX`t)Hy= zV&meq_fGonpZxily@App*YgS@%l1$CbHRHe*YAz1V%t^s^SpgkZYD6_X9m~vWqL)= z_Y^Kx`%(3?YGCE+78;X9pK>cJ zJNHy<-+pJ;^KZE7t#bd-!XZm3~gI+4y9?PwK=!*O$k? zv8!PD)%)$hlclepq=j>ysMh>x{pax0Y47(Jio3KVPEOOhJN@?V!%ORx|MPWEd-~0<;&K}$~-izmo%Ht-+2D7yy&;@uc~HU^HqDb>7nlW zq{L8n-IMi~cV+v>h5Sm@RCv$2CrB<69LCGaKW&fSqj^1T7T@*2?Ps;5bbe|DT?7q0 zO8EKCadbJvymcwpx$W&A*8NV9xud&ww${o0atgH#>*DpxWo{L(2}yA-PMNg+%}$1C zf3~aJDX~^>f9w7}TrNh=y2ys>vgv;(waM=md{*T8J#p*VMLXlUPT!L@5@KUx>*)8m z`0L-P-L?^*wrc-8|L#7EVB#nC_nfDb^kkbiZWPRsx^nc-OF7Vh%beL32QF+-d(`B& zXDg`3-SF5N)QEhp#`h!Zph=D4YvqWm>r~BKm-WPNQcg%o(fB*N^6k>t{kA(;t!Mw* zkQpGJEq?yc%e}7+&L*q8$(3F(tyOZ@mV8y8ttTh_Hv%Pl+but>j859i&R5@Fb2##4 z!IXZnbs@X%P3XMr=yIc4RgAGxzx|EH*1Yxxo0kEy$-%}YZ;S)-_Fh-LrT*A{#;zn~ zDf4{Y7-!!zx&{e%js)$O$%rzaxnjT79k;e!lm0I&eO|X2H083pUu*5;jVotfliO1( z>wEmk>07(jAU6&Esq~+`wA;@+)PLoTzxQ7*+vERZp+W4{C(DoAVHZsN@jx)^s-xP^ zQ)#CXPlnHLGU41L^m*dsn24;I+WQu6$V*^Wa{AD^TB=@gvgh_q+a~ee%)Glvm+SIU z=TaYEpNs3K?+$RA4NBQ7xF*F11vRnXJFC_5#CZ}!^y{h>frlO5xrShjP0XJ2VE&`o^&XUh!pYy}bPhQUhjf03iOUrnZ(4Hx${Lw^LBs#3F@76T22#aE; zo!)ixwsU)Vd2P8AZ^G$q_b?}D;hj(BcXi+Yd;i%h{oOO})4zJZF$g~NxMG{Q;@s;? zt8=EwNFL9-dtBU_f3fp?Mukaz*3)^9Uwq>C?{(?q&R?(NQvk8fz?iFo`mn~m%e+ubcY(GDar?$5ChI#459ngd>sQUkv z>&v@ac9p(TQhm1XlF{~c#}6Mkz@Vn4cEV4~A@SGj<4>Zmu4}$EEzId>xa*eIZ10^L zZ|nTLrT9NikSAMek?-nlTBpzcE?M|x`P1IrrvE^x{mKoWrStnGHRn##Q8S5?ShczQ zcHP6`-1qBkZZFpIuwzJpObwiNwV3(o>HDUDl(}0jL8dvnCh1Fme|KZe(o2Q!!+3K* z?$6u#=G=!bS7%==zL7CwMzqGBlGmk?@}{+V`>ft94bY!yBFE()7~y|;*VOhW@;hd- zC^g;QeOUC=TFZCZyPlkWcJlj-|DPJ4Z1!wbP`Y7S)|CHDc zORujWsxnd!kNZ{zgt?W)z&*RcwM z=7CyLQz!n>eO)%?_Rp0%V9jag))}nH>^Pylap|;)TVorpr#_Tf{k!#z?#%a7E4A+O^-rOAk+4uK{W} z8N6_Qx&CSJw0wbSv1cmVCjQ@-S$Aj7(W0ld@BVWbb9?XD_MRs>*qBGcJTP(gdao}N zS5zw~Sy=2oRV!&RKduMV5?kQLo4B*7COcsJrwKLx?>x`Az1IB8moFP$b!-oKmXpLd zCu+N}?_sx?3dhzH?mXU#qT{rwSRlCL&5|2piNlh-MvrmEz2AiZ2u#^?N7DqjjfgXU%q~QnDD;g z&XaZxpX+b9Z_Zv~ZM~9Vt4`KJ=iPk8M4wLD#yW`q& zPVfn8&Ncq~RnsKZIs4VkTZ_L}CtobT!rm0I&pvtHrLQJyqbof?{o6NueQljPS-*V! zdU0EH{+GIZoi$Q779772Xs~(15o_DuFPHG9PKrOjZQ-5fUAorg?VUSWJ2=|hjztuo zaZkJ(Twc^#`MuZo?1eqf;w3MP8NU7Zt68{wx%&4v&1$(}7omeyMmGKPjz4I0et72! ztDD=gf|87$y1jP_zwDmOwfX&u&&O`B+0Fmq=9lvqccn(pm@#8PXL7L5e3M|u8zvL~ ztT|G{^TwRRt<7!2s@UaM6y1_M7DU{xp31(mpVjc%<;%{>?CxJ)-D9`CQ+4p$eeRBx zoto=@v~j)8_|)nn`*IrZO+}>&+3%r0Vs8J|UjJ={)};8LPyBK*8LjnluHAY|d%pen zoya2hV@qJR%|5liu6H*)(VqUN!RFhc`xgOU#_H$n&&WMg6foLGQ1fI=1Zk z_Tm65v)`qi-@Iyj_@C=KJ1I+uP~U@i!ytN?(V?O_HDRor(SM zq>HsDJR+{G<=?G#PQtd>uEyveb1ieTxt&DxHM1$6|6ae4mRK+^*O^D=zaifpAFa~b z`M>Rxeyz&Cu*#eN%EoQ23pa8~?cCz<{`;|{^n@u+m8G}WECS6|iN=1K`6;vi(+sE2 zFUr*oSNs<+S&`p6%QS0I+})~->%U~r%(dj_6kN&mdc|*0c-ZZGZ_=?ue_hqr>+-R0 z*YCNtc$J48!=G=#TAylfck3A#IHk|AaT@>H zb82Bnh=8D*-)!Y7+ttS48M=7Ol{u5nzgT9wYSI(t1M~OW9Xh<=-w)HL)fZW(YFhvI zz1iK~So`t%e-*2@`nPV$dD{g`{o8r|;(>jFYh-1f-eY+^|F_IxHP8Ece}750&ivF0 zo`KQ^4J6NgrS+5H!}0U{AAY>@crs_>L4Nb)4>G?n*>ui03R(hk{&;_4@Py)9QvK`t zj?CXLG6lQ@B;u1~=!s{iwwDzgc@fF|;ou$pi@UP@d)x0D>}o_dTj%GdKR;h9?>S-F zJ^$U?W0QAh&)_=e|Gn(7>$%83KVK&Y{yKd-r(bn`rSR$R^IcE--AxF*&6T!Q=i;Vd zbD!U@q<^ITSE;(Y${HF7i6__J+VG@!RdU29$+n9k(`rN$Pp+S{;YoOI#3#$EhVWU? z71}=^-q!2Vv+H?xt66n_(!ojRSN0{IOqXyAj`$Snl6&(0yZ$}9TNj#0poidO}}Z3_(ll*||J?sXydB5Q}H+o`22 zN8~uKzA{rTXHzoP^8S*X+%-{fV&jUtj#*PvPd*P>x&bs6uc`A>YtgzV>8`2DHM`5+ z%NeIC=e%}JaZS%Io$a}AyI{Rp5J;zBy_%3`UHKKQ=&7pzuOB!679LpUiG2pG==|iW@SO(fabJ2d|&gmGXm&yaP(rUhPh}vwPB- z$+_@>%SkLtj>D{?sqp8z6B*yua!GQT~O(AA-L)L6-Z}b;Wha zJE=biyucvB_U-8T%6m^Q#c$bY3mWI&v*F3~Ns}fC#r!aHQRejI^>VpkX1|0*O~HJj zLy6`pz9(u=l%HOTZOPx=Q9Zp>`A6%^qZ%HQ6>>J~zl-$~`a5H_^2&pr9Jik>zi1`C zkJr)Dv-8?I&Lr=D`usH?laKiPi4{=$6&*cu)93Poynh7DnD;;V{TjSv2eg`@GVx^i z#Ko&?Cd^%MlX>gRi;*3cGF>0u{^Ph`CU~&B&&W!tX1D#J@*m3IPVlDOJ*6prb5E%P zi@EvlN9Rr!sLbnk{WH_U&0S?)|IxRTAMV(BRNSzAUFH?Po1Co{+fIeAzRql@`akoL znn-M=e~Dx2mgA34AO2vHy4vqox7qgNlg=AV6`xikYPsE%Du=VUop4a5{H%+KsxA8^Lsf_tC{_KA~ z7S9X&-MuUEWcrL%{NDSzS}Kn#{>z&6BIu++PuiWGMf?xN^RmBIDNIWdbw7Ko`*u9I zvagie&Sih+SIlZ~!D;MswOuvd)#%-mhnE^>FXHAtY%oi3!+W+(@18-l+VaSC*neI( zWp>4}cuQ8LL%;OXnJ2Nt*lm_}X$j?CK7H4@R_AC3mm3??7T&sjm92PLPkZU#U5idv zMgBC_=it}phqkXE%Sa~P|M%VcT%E86Wa-Xw^SDFu`~NPy`noY<($@padA+*wy48Nx ziyS$$s!VrF&b2L9O4a%#xs;s#z4g}lDHZU^f{(xERu-g21y305S2W*~B=~Z+!mcd_ zJd5Q7?LKl?ufMy{s-a@i)WR93LT#p={Becfy`f`*;LF7U|JJt`Zdet|Eqi<0g^Jx* zV?QZ+PT00y3=xX|q(41>U;pTVQU1lFt%(v}Eq*K&|M%;&{@Qr%Rhyr*SG;2tOgtRq z>d61ul0)ebc-T4elVsaUhYe3wPuwyiC&uzvzD??ku5(WpPxR4QaC4CUR+zLyXRYekk-$ghW{k2_t;C_id;OcRg&$?tEPhc&4-?EZ}xYHkC)ee z?jQQ&P(o5p%N>dOX@9~GK@*&*aM?ezbu#@zu}Knbf#;qkPwe=xbMfYEhtg>^9zq7S zhcpBP<&Lm&oc8K^baBZ<+bHvLt;=PQ}a%B8Jl3@>+sJl zMfcat;8t=vo#wkS=Us}AjIoGm_BDrOc5BO%{(B#`Mv0|mTsk4A{oMa(S7q?U8wdH= z*w{F1b_Q!~FL=fRT3GS&;d4e$rbkVF4*vf9yRYeLT`xX6>AXRYUgF8?3Y-gH?x@m= zDw&xm;WlrAaG(dz?)8`V|9_?T^ZK5B?1G86SN%M+soH$6gGaE)|G7U%WX-Oo_3B+@Zmk* z^+w)xvfAFPJKtSjEDV|{SO0u)dHLz7nwnki+TrSspW;*6xNq0b6I46?Np}$Y$l44Ns;k_HXJc%<~plH@C~J=7jA<*Y?C!(&g*!9KJgF z;)h#OJ;yDNKK)c4e-}JgKF31JrR8|JKJz4&lDg%hvn(yS%1@kD`U#q*(*UJF$4};H zt2Rz=Ti>-i{QQZv_L+AK*4p!cyzS%KsQO=b(&Uf^Z}z;T|4IHo-gCPi_&1xwR=WFw zG0)+Qshc-)+^833{GFF@U&)_EZcpc&B~3+ttJ6>VJFNI_Cm{GOC!%T55p4w}o&Pmn zGq?Xcyt}ROz58&Z?rG3W&CU5etb&RU ze(+7JQ7yb(CRk^A9=wXrEB(TVzT`A z4$db1+kAo#4=vO!daXO}ui=+3Uka>$wA?x#BCAqAF`h?^eQAfD4$t9?W8#gT5zpk8 zBiVh`_0!BxOBZ?n+uGw^aKAdTBr#FK&G^?lzC&l%9FN*72kMORwFj>}H0gYZ`^o>$ z=L?nGd)3};dERxpSX<(i!|K1)OYT+)s;a*74LLaJ{KJA(r4gTIzS8)~u;JmUSf9^b zqOma&ZGruo&yzO%jGE-Pj{Q{&EZ~i=hJIo^Q1#Mt&iNXxU3(087E7(~QQr_>=e7KL zvw15*S>&g`!Jj@J>@2v*+LkDBwZ~SjZ9#Rb)n2(hnmRva_?8E?s?J||ZW2GkYrXqN zk6aF1L2x*nHkyU28R+wa9stMb3x7VS{2 z+qh%q*NC^`u`4HM>Hl=C{C{w1v-w1!d8)e}81N|j7e$CMeY@YF^1t)kI>xJkpSp$C zH*|cs_(HMhbaC<@!?KEclP#Owbh(XrUR-5;xQ|`*8vl1&j_>p42Hw8Jw9c<@hIOjV zzuCf?;m^Vj1cKC*uF-k;;e9bf<;H)9p35y}TbOoM>W8Iv%uDeJ=a<(l2;5s$lGpBk zjNkoJssER^_XP7~ZzmkSbW=g;$^CsQ;bt#yMG6R(t&Khc&I~!LwmuP0^LViP`K%X8 zhYfnNYRcmeA3l>bH->%H;wSHKZeVd?S^sx-OX^DVJ)q?vzEU@npZ-}HVVJKaWqsq| zfddSU>g8Nd_2h2zF*7soxqM3$G(wy{`S`&TCj{PaDLXp9wXi{j(Q$bhNUdM5bP`Cn zMKRN>sK`Ty?(lq9)?=8&@`Le#n`Qc_#%d?l8y0^AwwWSuT zKdw8%y3}ZY&byQsuU|V)TCzJ`DB_c)Oy7-_+a~>=?{5F_(m&}%r@0Rsyn47_U&Oy_ zi+#y*E$@As*z?=@->}-++Uoo&QS$0~)LP2+^=(_4UtdxAllqf=4c?dCGG1l$Pg+#* zCsLw)S>Lx8SJtW4RTp?`l)t%sRY8eG=6)mUvWA5|D9b(ne_E87mBiB@*evqy#BM7~ zuF~M;ahtw&aGbWEuT&m$cCPf|{O^5-{!bSl4WowwBKt~fO*UaEABj$lpI_JDt`X%-zEulLXCeJ+_;wB}zDTL%aK z<7NTz5+;|H%hlIUzI}iFjs|GPw=?{_g7PJHmzLbs&$O>(U)qsqU}SW}zW+VLB$gg| zLxHV2XFj+)>L^rN_GBEsbW`f8x7ts&V{V7ma~g%6nax#OTWjOX&8ouLvA>ARr6u|P zy0nwe3oJsdt_FSLHts5TTCwECCD6u>7)ztb$UE*!{IXT<@U`bLKfOO9&^ z2nOX>O!fSW|wGU)Z4budXCI!YpluwbZRh=Sd=<6=pKGwgvt0xm&ezQjLeu zhJ<4*GbE%R9{c%MLFtFgy+vDOz^?OJIc-vX#m~xl?DG?~6nA>|7gQXrTpP!~YVnih zE%TTK6VEQ1+2B&O{r1{q=^Hn%OrM<>G)biC)-(Sn?fuV>b38p) zx1Z64<-T(C$LohJOp1$KCcAwra!%T`s3tKbr9<%jw`r?S@2r(}beZwYFep)?ch}dd z$xqzlY_wb(`dfGgPr}QpfU9+{yR=|6c+>b4e zo)>fHay*-@^8e=F*$PTN{~p*TMxI%Ud-dtUJDsud%^epO`WCN={-pWL_qV{YV{R{Q zY-{~+A^X5b*$daNw-*bu*KAFY+Bf5?;F_%KlV&>wAJe}nXkcWd^Yv-Wt83mcs_Tb}f2RDD$cI7PLdk*RJfnb;_UGZW~u|&@PW-`&{Qm?Un0LjpWzg-)%1+ zBk1}`(X(M~sqK{&i3;DFR$Y;fleo2?ea-9ZuCm(Rdu|{1^;?q4GXGw0T0$M z-s(ddh9^LsL7%0IR(bs6zgYb7&o`;L-0I4ye>xg70@m~WtCCp$xz=|7U(vdbnd?FO zRX|y$PX{y#7NWJP>&gF*O6)u5^}0;I|LOq0W;iGscLpDue)?b0`CE_A$A5xup=dum zu@$r~-ODv}VhuyZtS`KB?EHeUO{=x)MUEt$oANQ^`YOpUU%pI`tBZU7yGF!B!hUJa zbKU+v{h37N74{&3*TJ z{h!xA6mz8hMN6!?{4eOu}yDt2+<*{Vm^q_+n_pC&{*zA)sN$AQF|hhE4{t0T_tsjaul9WH!CPUm>iO^f zep}bVCp=H8%v{bzR;S3QEt@p)^-YeQ^Y}FSW_9kbuV058Oj1HGWSNwzlqcIP>(Fz4 zbzx<#_?7k1@vCpwT(33g@Ys?Z_2H({LfLck_pI3TW{%|NmQCWe&01G}%9_^jbZ~rs zTpT*>_Kqs<4i0Vc^QdGw~r%cLE&h1uYZl8K;+Sc2y(-liAq&aQB{nZo@EX%#8y?u|8=RbAf zWz(Y8Dfl)m>zxbQS{Lg2>HojCnnwOtgkt?9-%c*QBUAtXqZ4R6>xI`RdHesO?2F$e zf7_|?02G!!LW>Q+vFu>WMWD`>vw&!jEVM*}x+xfOq+|-WAa5rC`Rs3SNu|WFc;w`tPrJmip zXuiO#pAzZsQyx2{SeeE9Cf#ddp7_$DT3y=QNd9r$#o)QyWWD#bZFsVOiOETE>6uUZ zWq#RpaESY9%5U0Sa4u<~ZsaFz{!rIX#plhQC|ZS1u`gnt@cQKfQ2ySsb?o_wA(V|&7}*4q-5KiGb~;X5?puVTiYS2}5xZjWC5ZI8-#pH{=G+5O~x z_vu3_{hAr~*06$R!*``d*I4E9ZF*-nLHqf=bOs+^AJJ>)y(?Li4y|~Y+>(lSX=S3c zIg_$s#e;ww5w{N=yCrs*z2BkZKJQW?DN&*JiHF?|uk1AL;pt6C&h2*m+pciI+C}3s zXfftbL)8;Njsh__;XK>$GY9H740xrAK;B&exh+ zQ~G+%%b#bxD_f3!p0=>|HlKl^VPo%B-D5p6?H9ZSi=LLK{(QCN{q@t=n>#jCzy8kk zdj0=b(gK3pa&N5sXb4(r6t?xX!v8qIWxlf*jf0KXMDN^q%d^h&r2PrY+W{q#sU_}x z>CRiz?yl^ZwEuuQ+hM+#!{5ufT&LfCb*_9nx8a)uraIrcuR6UCm|M>KJXRr4d ze)ug=BRHkUIMcqXj&GIK^2>Ec#2Y;&cIdO{UY~#bW{b^~jFrBTpGtLpeiYO!*ig+Shv_I za{lFihd%MWwMx3N;QNII`%HDt=qBW6>#Hve*T2^?aiSp)56^}5)2)y7$@D)v8wDD; z6MH7s!RzagSY>!l=G&nw{pk%I4-Ctcw&mYxU2~*n>7?~1EiXLTaZlxEdVj^@^8S4r z@)8ysq^SS-q`Oi=KXP%%C)1Ui)t;MMuVjd|Dq|H~nEd;k#Q%ToFYaws|MKNaLRJ#j z{mYD2Q)^0ZfB$yYML>{^UuM;YUca}y51cp=VZlC2X0p$x)cuQY+`g53{#xx-zfX(` zj}1EY7WKS4VX-F8enrM4`xmjMlk&dIf)5|$RnCmtoFm)8aohZz(6p&$G0#g+UVkF9qB`c4*S}}$w>Ne? zaoKFJSHfF-9fP3at5>;8C#}z$YihfH^}ZNxW@cuY`{hyV?8Fg^6#tbd{+zWXfBCoA zgq)nFo*y=ekzcl2bG=<~5}X?03*R0X{pAI1L1{dt@$+$CTT*SrvKNaEYozp=hB9IE4z9aVbFqKo#7)NE7~f2r?-cW|KmY1Bou8>wYC?NtE#+EY9zEfy z=h7=>`ry$OpND=$VM%tcuCMK{Dt*0o>XZ82nf)<0n@l)C`Tg)(ajwhVf)7e1U0TZT zvTNsje|2;=m(Z))L;SnV-n+(~Qr@{qoxiH7V?));s9u%$%f$=c6|U3uVentiPBLxZA%QsNEgNEQ1pK}(z}SKlgS zk-WFHQon=ae$C#{Oxai0*T%nUUU+i*rH7Son5LbHXIrjzE_G6k@A<}E$Cs|dzNvj3 zEGPW+J14zgVP6zz+xqWi-+Ff!io0B5pOJbxCU(~1xVu${rn7+eN;^JxHd&?Zz3gDxm(I^m_efto z>zQFt_Hm>1Qxy@{*K6z^j;i@n+&Umhoh zTrN&mXu8C8`pPk#pH@?9)<52-prrG!X62>x>Xklhf)A@4c6gmU?^_qK>dAhqC9X4h z*2nCTa%ow7e{M)r`!(I$r#$PDLZs{@-ZENCR|^P&QpMKE(rvs|>wPAze`y);eTT`! znwc!W=k9DdRPyqJkdjkG!Iq$tpopGxA7wjy*P9!q;G~{dWoWhSJx{T5af-UA!jboE z9Ukw(1;IhHseMc2r&4|Nd8CQ|9!;v>{pDtz*5oIrU;Vn>9If*+|HRiGcZp5^SG`a= zoN;VHts=*1t>0^`XWHAizvdNpvFCHMjap;hf9L;-@9%oU4^LX}e6s$s>_bK7_QsA0 zLdEe*pB#5zGDYj>sg#$YlK=94Gfpj^wO0BDhfvf6HOJ?Cm)JGz|9udynE&B$^OfTo zKGXZwUpqSY&vkH0=zOB?vc$>XpZ~I&=hH30zx4`AGxeri|Kj&=b?LPyf0n^lo_#gV z_1?GaZS;(|o~zzV<2(9l>$x^BKO4G(dp&rQzp2^gGxg0nKWBN`sc_Et^^ikA&`7CP z#k1v;}>01DK^*Z#JtHQVcJwKV+vhSMk=g^m=9i{Kstfi~( zaC$e*nfo^?ADB$(pPIv{+uf7oa?QfqAsezQNewET0u$1n$7I_rn&bIeMvgf zrqi$`t`<~Cu z>9LT}+I7GnHTlQE*B15W;g9V$JxP+W``j(Q_g_!inNK^LMH#o7W@S&S;k{VGb(Zt7 znip%%NvXuhe|weA{74b&;3!T%D?Y7X&QQ#S<^1C3t*PH1ZMv{4^|lfCM1!}t%NQp9 zd87aRaq-2y+5VQCzP%?I1Q(`nzgMI*U23x88IjMg3QwripWY?A)N9|e=MLd$`};Qp zsm4#X$}XzozT9p2{&MS~x%oF^za089z5Gmijgfc6vfPu`x74PVR4fi_eHEUs+ahBYy8=+l6=UYl}W^wjwpL z?OvX&Kuy*5L%)ufuXCLKZEZqgUR&PWTZgj$tATcpceQ_S%)hWUI6C6$I?#k2XcX(* z+uIsbUS3|S+!1HT4=N%aZpOC%`e2s!&%NbrAG+6R8SYN{{BWOjz~X-&51;?-O{-}m0@*uKg)J&mtvJt^<&ym!}EP#dL#7w>5SM2urQsQ19EHWRU#y-A1hL0D2-}vHkXGTgsv#qUd#oPN!HoTp7c2?5O zJ11vYq-lUQ*B#?4E1LBG@3N%|N?Y>ZcBK}pC*Rw`|L{u0uktGG2O0{V4o)XB6Dxu~ zyu5ozVCfaXAQ^B_VrAUx7OK$4*c-)=skNxrT=6>X(peru<(|w zds3U1)wP{zv1#38ZT7BhwRf?Eq~zkdy=*gnedI|w^8Db%g@rR!_Z#e$FmGOD0BV-s zy0NfsFPp@#cLg!u_V#(o1wT4^G*1iYrnoa+zqKbnLgvyG4uF*R>8#Azw%wzdna0X*F|EVTh^cJ z`R~BJSkY7OK*R5#(NmV0^|c)So`E(|JFQN7{yQJ}uxJvWCsUBqdCi)@%2@DZ^YbT_ zZZl+7@1L-5=kLUtfUT4MSl+uBQEu!EcH;W{JID8MZ@-fD`NJEfc}APLEN-EmI$ z+AHwROk*EWCG?XaAT9)L(8io}UR5f-d(EW%FD`d-r7r%{-_Y@(xPlw?xBxExd-wKM zu~nPv9Q?w$_=8_|c%sBSt&`K|3Wwg=TeU38vu^&}=^GwjW4_qhT4}w__4!I$z5lNt zu;_64i0wUh=Jv)1K|*FR`&L8d0@G(`9Rjak4EZ%_&Gq{dI%+yUQ>Xt4cweia^ycOU z7T3;j2bYL{KSj1~+g1DCx^ghkMe~yQ|^a-BtC-vFwWdBZ;+J7gC@4Myv&5Bz& z$^PNrgGH16e_b?FK?!t##L><6ZT~;YYWVS0E`D;HO^#hK@$I8Xj?b1Wrijmcc-8Au z^qSAdE~a$8ICN-_EOeGOD%UROzGx25c_FZa?R12d`C2kS^T(i>?XC;^a@ixyHyr%p zX*_Z24E?Iwlnqa&$A0=>CDHz?bnS)Z(e5%gHp#fOv`o01m23Xuir$6)o6GMd`o`wV z)H9!Qo3^RFy77@qNXU`B1<~PK`?9p(*}hs8m40_?Sm0|hN0#sR9y#UCRZ!5oV72mA z*y;~||4xk00W#}fimlYmj*>Te!73hmNPjyndkNY);s$A^W|dCgno*@ zW5uR)=*~%J(OX5C|JUExmeqH#_vH6@iPjS>*B5bfPqtSQjyqhRIK`{>T(;5+c5Qvz zmitW}y>a{@y_{zAiQJwhK!B=sW+Mfsv8Z+pK#Zipm2c z^FX%DP72d_`LJtBHv7}K>dMy=tA2+#TwLVZax=%szGC*zcmG%4J|4JiiS?b>xgXB| z{ajJ`T4K#woxt_m^#7gv@*wxguGS*{dvVr#SY2AKepWvzYn}5oMnG^`@4qO=PpSN$ z(w4+D)}PfXDAiNkzAFFKSLY2|H@Zz`tCM=)en{`vwHVAtB&Cj@aC6KvwrD^iFHSIHJ zYJ!v}CGek-v6e{nt~_vLiH6?0-&0lRUyKZy_@}3b=SrM_W>vK0*$3Mb%#DnUOv-En zOrs{&sH>~nRDbhHhDM&}Nq#xMPRDL=|ixHRj> zfm4^5>aOnHn0r+QlsfZ#{^h%GSJYTw28!5AheesIwj4hlecYh_l);T#5y@8;%yU>( za&a}o{wxDyhSf~5Q@Xs z#dJ=XAkcf2eZ!N@Pc}a>ZVmpiVDPvi6!E`UjHsEbl#YB zl&kkD`--x_vnM>gilma9_znSO4L8LBf$EM>5vfIehKTdSu35n3^jv!NkZ=WQF)s(mY@rqvv7R=xk>g7!&v zhUDbrnty*Hl}jO6Q}g8i4-c7re0@!W!k?|E3aqV46S((xTTF>!s>%7?uf0Efd9z*0 zHa>*cd*8!@lkzKG%za{>e@~{JU;dg$EF^rqK7D_GzyI3W=DdcKOB6u2(@`(D~Wce$Vrt{{BBrk(<*pm#pKxT;8dt#dG*c@7K#^ zE4OfXOESIw23EB^g{XZYw^^uxRPrz>9c^?|&Vzh2+*PE~PD)%;ac zKf6x;y79wHeyG8xr|Um_@L<8jUDy2Xbsl-$?>b$yE%A!-$6Ly4=I#krzRoBuExlyv z(oB#`zc2drGg)fhJlUQjxpE#iUbXGc?!0Iyw7pWPaJKrqxxd|>TK}lDjW-FE=br!X z0L1Ui4zI7R-B|zsU(I#loqssq#q7}E1vD&bh^jg4W>#_8SXsw2&+_ zynnr6!UO>sivorFUp2ay>93Oc%jazu8~{mkOP?@19PJV<{QmB4&DD<|^0@fc*egRE z!G3M-!N=A9pTB;u|NPAN$B!RRR`<<0H0gYxy3S7@M^5?gJO3@%K52jD=d>^H?pojf z`X;7EVE^16@z^Mdw!q%k>W>6AehJw3|J&~J_kC|~Z-2gf-Wv?v{2t3M`*?Vq2$wxC zzu)P~s^mr8+=mTT34Xn7xW}h<^7{S1ckHm(l66%pzVwOMw3ONfiaESk& z(JqA=P>zoCwqyA6{=WR}ZBOe1=UiE0FYxuU;h!=izmkcdMTQ{f?|j5DPtE@P_xrz& z^-3FNTv$+ZeRA}tqQF`)Vp96HHf1 zDlE7w*`jYb<-MX6!-Q$m*rcUr*IZxhb!F#@x?exk->p;qQ<a;C>uSN@U!}cQrw3ozx#C`| z_oCC9iz6?|&p&=QAbIT^{aI`JTm9=3PMX{BIDJZGpJQKN*C^Y6_+f^+q@-lj%J1#+ zbq*OBE5crdiG}K~I-lhB=zKP}>+~SE&wnaQ8Rx}HtVPbe2R-ZsF~&tJ_4J?$7k_f2q% z>xn$QA;Y))L(089m5Vs+7W{IZdi?Qg^YZJf%H&>be&VjW&+#{-qJfbSlima3cmiu2z&l?onvXPLKK78ktl)dauon00y z54Uj_M%4b9_2O-S`;UlEU)h_d)J8)6#YJB>7A0r8_wM>#di3xOKaFdEo9@<|;y*Wq`hOePY&D5 z*3j|c>z1FE)3|rfY*L$Bz3A|PgxX~#{07Q!82aPi5>~r$!ouOd*;d9*(KxBqL(}S!IgFK5uZYR5-0w7r1wri zspYQE$;WktkdzF8$NTUV7XeLiJotLn42HY!u5de%+W+xy_bo360E z?2BCc>kKw;U=d7wwCZf-s&KWRwr)Nwf(u(#vmevpbuF2+=t=v$y`1jm{sMw~{=QP) zkX-)ofAp6Z;TkRds`2#+C(lpWoPK^$=<2W+H#e*2-QTBMR%{{TXIK3BnQzJad$Mbz zwjTQV`T5bjz8J2kPxkivTF&J*Ik-fa8HT7YYF z_APX{EB4mGC8GEj+oH+;CjXhpT|dp*FXWN*jL3*X+v677T$R4EQoVzNdHK1}3jUd@ z^~G;$Jye83KISh7$ji!*(7O8N_?h0_u22uzx4*f)oj>>Xw$`<4b@%=MS1n^vz#wm5 zC$c+IhELtwuI5L>-{0SpkM&4y*tpTLpuphU`}_K3n~QuLz5YFU_}9@z;@W@ilqE5N zyZn=UM0Rb-WhxI>7umecls@oj;K^=f~z!vc5uEjjk+>f6T>h8x(uDt8@( zw9k`|&2aqj{h=%C$^FGvGP+E!51rd4aVJK`FwaANXOYg&o0}hhKdzv3L##aHSJmdc z_(gH26ByTjn)*~Fep`K>PKQO;izWY6;&0w9%C@d{{B(6~xY|#9x4A(p%r`&L@37Fa z^>_ExymILHr1kb2o~)i=o_|ll#6)Cm)YhiXPEM<`Hyw$G+Y+Cinrcz?MdRJw-R)DR zi0rBT9rk8fkww+)?fLhWR8?8Ozq@<*;$ruWMNhqWWUWLF9z1AZYU&DVc;(%-TC#L$ z=iQL0K>?HYR~*w25WHoY-| zbNqDMgtuY(Ztj}Br;{IDSRb-Zc;!Y8t`)q(lNY_XwN5jwN3!KWm8ql449l_&bDmW1 z+OZ<_idl`N;6Kg1^~1&|ulaR; zR-X9z`T540pG7S#EekR)F9TgL6B8Son48;s;DE!_cMJwRi>(?P8#ip**0y@JwvzW$VffS`|0*P*LBS?=AjUAd*E`@k-Z_+#Af zh0bx@zIV^Mw`i$t;H}fXJATJ+%<*Ko_B+4B%k7ltly61Om)Ytpx2Z2@&%bS!&Cu!6 zWO)C}wBJ)5ubOE}uM6J4O}~R`bgLxfWrWem zaJ9!XRpakgrB2%a^_O$&btCV8Z`S?}sav1KCGWlO-vQ8~N(-eW3M%tE41S#UYf9mN zaWQUTPShPu0l~E1|6=*?=1oby^cZ?KX0OZ-C+vfJIYh{(*{ZS0J)B4Ek z751fZbGT+Jdw%icn$p*yp`Xl;++h`5n3ho7S`_Sj*kQ`=kVUMDphgTcXg%MAW()`jL=bj&*Uhq0aV%5)(hLtNhgVxE0?DoCsS$Fl+(@%fwKxL@#~>E7$Dr0ax>@J#X*3-_Vip{6&xE{+!9P_3b|0)8?7CQQ%qF zwGBFHixqyx^T_JoDed3$-Rp+kgWWq7l(xtVDCGpZf4aIdJoIG0pO^F71=AFieEw8+ zZ_s^oPX5q(5pBLWfmm?EXIc$k`(Z&vR>hx2nQp7TJPZSk0X==$_PsN`^{x1~JcfxS zdw!nmh@Ub^teneZDqr-+l47^^X^n^K%d=#sWLFnSPu{Ox!r1Xodh&X|*_$T+S+i)Z z-&~Jg*H>SiBW&x1Kt-wb?IZ;yo>zB0CH_sm`^HM==PO~!|3YVXe;3`C6DhX2aeI7i z$f}q8-q|Z}n7_NiJh|@PDuticc{YvVtS>hU9F4tm!C;5B*3~cUf(Q5dRKEG~cVj@9 z?8)%uaiuE%mn%SVDiWfHiuQhkoCcaz8O)ju~qk(L9k*>6{N;&S@5Sx;|k z+CvcmK{uW^iaFJVuO&J-iq-i;S3JM;FCH}bv9s{4^8R<6#m2!h_kS99EaBhtEd0}Y z*?UTRyDh8d+{z6K{S>% zT4Be30oIpc;OLS)Iel;cvAIL%2Yvns4qrY|BJ9mf&l1v2*RqX$sOf^{*EiBZ@Vpx5RyZrgz?6Ahw#hg1|8V0YI zZ(7@HIxD4e#kVu>wkjxv=tK)$3!C=C`en>M)&Jj7J$Y zZf?aXrU#z7%q;`D4qPPKU#{fPzP0)ord-0XXy^76CbwX$zF z=6;Ym_;+9H%%!4izVQlE?ti=Oyyf4g?26D2Z8KX{PyP6@=<))=8UB+=T0$TMuxdPfo;Y^hXG>WSMFwlKCiU2hVf>a72h{k~X&!z06B zm)6hob@n{L!NNRmuls)4_JSEa^tSKNrRO_6|E=30UZ5Mv=F&2?U0>1arTxkGhDk~t z9I7$425FfZizZ+C@cL+l#jfh6j(}sw#T#Y!HKgzKQc$Xxwz}o*ep{he?@WJLw|@K= z-#3>{>ClN*du1z4-q+{vGYT%u>n`KUT^({+UTs-gQK{H7yY!RchX4Ql)=x@G+EDkm z3e;zOdwaY4?QOY%+j1f;tgR29I@J|^GVDjojV7*?GAL`5udD8!l4$< zi2Zf8dNDg1{O8+s{$0NIqrl5)PwHz57#08SI(U7zjfzmG!D*hulg__j`=#`A;z#|B zuU?3nWZaf?VOh_0ucN2F^M%UFC(@FV2k)Gc;x3b&e0zQB`F{#ZUnK3FcR262erRgN zBg3WS^!=f5>!wY+zlE7?yHR1uxVk^SxpSptmT8`1dHMbz-})IdW-7l)>)Mce_NBCf zl1awx$S>z7sKzr}*8VbC<~zHsp@Bg}H%g>e%G7Cp-Cu+He>EFI zz26OYv$-!1tSo-|s%yt@nPaY92NuV;zVWNj{T}sWZu$n@cO25~LPfXhd#pH>m6c6$ zuCs=|HfveBmG|2G{mS8K4?pJhQv2mZ;o#p0c zHAy+ULvK;fH;;Rv%#ttXn-=X`Av~?-uyeSo{cDj~U!&L-7cQ6kk#*?%jjFeeckGN# z{x^Iz<<7VH{qd}U-?;mJn&)#X1xdtR&b+|!kh^u$$FHx?-DMM8n7CW-klFfAQxhKt zIQ=Mf6#q3x_5XiPUS8LL0D-J4D;iTzPkVTFwt13y=V8Mgd#b*2c~8@E{QBzZ#XXh9 zYvR}6YF%UCdbK+%D8jO)gI^pxl&fgRzze`Kp z;$<3l*N47uR8U%DQQ?^PdsguN=8lA z*QFHyeyqH>bjn4U`Ev6P-Mz3V$}-+0GCpro{ETqr)RX6De|Wk0$?0QHs;91BQ{uC4 z?qcILZ{IxKc3SNAEAz~4A11u#x8~E3G5+#KS3nRn)WjLiUomB^!uNu8Gd4U)pWMzb zzbNzavKQCZO5fX8s}1UZ_jOf%e#X12V)o-sT4*dJ)qwc$PribQ- zKY#3Ob3SYF>6#Ay!}B+O7TCT{&I56+v_*TxyL@d z><(@+b-#<}yrg$~oApD}Tl^f1f(x@xL?r$`cA9g;w$1LD?^l6}4PNm_35lUR?@~`G zKRPGRRT{qLA-i&NvVh<|8EfA!=l$;5nQNSk-&0}eHC?ZFv3q~lT@k+a$;V}^N;pza zO*yzJ_4JXvu1imzF8X!6^`ZxtcK?%(tR1_jNysW&Zep8sNNa_Si~9cO-DMM>nH)P|UYBzU02wb=5j`Q}fQCr(5PMmmWgY-?N z@R`eRO#uxj99kc~Fg?HP-_4b`pR*~0ZbFtY&6;xK?1KUesZ$>w9=^CS*?r^eg%ke7 zgcuuLGQA<>=jb{$vSd@Yp4UFJFlF91H)LH}b|$im_L)Ci?yI0wB4q+<(r{#4_RO<5 z^RDH(gjv4osnE5I+^dr|Y}@Mm^ww8{+}Is4wvpe1x8`2-%*?xlyScwed7=B{bG92Tzu9SB{o?*JYwIVSnqOK!L5--Yzlu>`g=NYe9H;q@X-s{g-{vdOCU7np~6|MgOy9T-auu(`~&d zWU1B%jqpHymH6orM%61{oL`{PtSW!$=o-*XruP5;1ix8UU?HRX^y};E8JCuDo||i} zUKY%-;Ysn8?sXpfwuLMA?)c2Ev!?6d&DB%x9PYKwUkYknF5SDD>$Z`1NysDVh?_SK zUA!BdS9)n@B71AQ+SIqp{cbOsykwD|OMz~v*lay@uG`OZCi^{{Q7IadI%)l@eWlS` zm<1C*y;^%{n{N2zz+1buIyzMMGu#yY&NT0-*1;oJcy8AP|Ko0okl6F&{f&*xywYYO zyCr4#mNx|Lud8)&ap}0rGp(jCN^{Mm`NFrNdPB@o(r-3?K4f+?VD7)*I)QDe(_X|D z>L;Y+@VJ}%yWFYOn{spR2PyAw^QKlan0>B$war)Um9qFhop3X)kGyAebRI^ppLpr< zip}eD%*6LETPyl2_0_JqIzL%G?uxyA5Vy>xgX{dAAD}3n^5^#U{Kc`m%NA{Oygbvc zc2_~8ZSAj;zEpMJSt?WZ7CdYUyKP$WAweK;pBVpa(`>O0j;UIqhmQBlZ`KW8=)`Ja zVe#arMXRBxR!GD8_4(_b-rAmj{?iJlsO@>ZJ9bzE-ahd6NT7Oa=%=F*yS}3eVak|TwxFvlq#EBfZnt&>^9y(`*Pgxi15AN?->$LNwblJuy##!HY-t(y2m~`|@ zm6+^IaLzh*?3jdE&WuY%p{qhbS8^Wfkz5?|$-eei%gxQ{i~Z(WZP>7(VfJk4UteA} z-`<{odaAd-|8dY|h`Y;jK_%U{Z{IR5EMVl5u}}#2Q~S9-ZttNrk()usyGKMs9Jqa3 zdf~!_T6@iBK9RR5V933_P4r&+HI2zNq4)f|#dJmFWU4#@X2zk9zCJ!5-rcokaEOYMa&mGCSn4Gj5f!z_tyfCIzRu>~zu);g|Ni>+ zmYbcOeaZ6W$xlv9+^}iWp)OJF7OTx(|4vR;PfSZ|;}X?+aC37ygF;Q_=|4Mf-?;JM z@^b$fhRJL;wzi3BX>9WH@@t~E`#s!T{pks3)z??7`uh466%`Lwuitm*^mP5e{dKih zRtBs8K6mjUs3rI9+c(y_m{R>!FX!7o_;}E)qEL;c{%%zJd4B(neO20vt>(+mI}pG9 z+QXkS6)S3X$QoVxfA!VRMJs=CIel6@`AT%JiIt~S%JECL*hE)d2X(q;mAzdXke=4b zXRH19>#fB(tUL9Yt@G_&S9*me#4PF95bL<_YDTo9M_tGx>5#Rz1%x_Tf}Iby7)7tQ zb&!;gpH}1c*6up@Nq#=QYriW$q)w{;@*>bA|DMdcb?dmC%ii7H&8~E4k!$x9uYc|Q z@)s63G7AX4y1Kf(?(Z)}4Gj*VPL^rYrWFMX_{}!sbzzB)j^2`gU#?f$yzS)w=dyN{ zpOyspsr{6-Drwlg+uF&=NkB%1M_5=`Ku}3pxwtmp=%jtc2L(Bs3I=X&ZVPK`WnEq0 zbunQl!@vHOu`FUycI#mfeE53({$p*t(jU&-|4+ELr&2+wrM;cIRL;IG=7*9&?XQx= z-<#9VJGg)hKZFJf2udr))u$FEJVZwSyQg2tzsor7n%N4m z3l`_WgXt1IPxRyV+)xPHbXMnQ_O&&Pf|_Tz?aJT92nfErzkhwmCvp8ap5)}@H@CO@ z=k4bf*E?|N5YtqxP$xGxHX|dWg!J_GNvhtD{{H^%v~zR%Qt|)$YJYRxPMH+HG0AmL z&Cj44b&rm43J6wxe>eAa^=X}-3>y*-G6@JeIXOK!l{&R1^gjQBnjPo9zP>KnXI%Wu zM?zBa;K758z0&4l+uGaPn>#?t84j4=uVGd?w5RfOOY)NC%em)QKhx)^{r#TrF!Nk&?M#7 zxwcx@J{&&LYJd9e`W&78t8%q9Kn?N3`s(%0d&Ax)J`P}7xAYWf=%ZuDZyC2^$1<+$ zi=NlI-oU%gFL?VcORK}%>lD&*Zz^v;-_)_#>$J3_^x~G)?6Ybwvaj}!3AdP*;}MY; zoIEQs;?TAoi(ky|HR5slByU&KacQZy_=$~2{L)U=|NCRv)6-*+bcAE4(blZ1PQ6m5 z3=S)w?B2b5ao}P%4?n*!2eqb?Cr|$Pe!o63Cx=I0Uw@~j?&Zsu3;+Hq-O28dx@l+W z>#&{MmMmX>_{*61X43jszZs`^*Hu+n$;iq+ zeEU{6C;QqONzm-`y4c-^?%au~_;hVFU}JA9k^kuE zPj`#`lg@XzKiL~TV{hqvgT?-7*XHbFRC4;ws8}hs@6es+H+}C$EsvD0n6g@fqvqMv z^wdf5caFy9%|EsE^}#cjxLjC-gN=RSCYWC>=R+gn^-^w)WwxS6w|lJc$t@Wx+Gc-e|-&#hRtKtNq-# zaU)l%cE}0_adB}AYwP9?g^3;oUtfi`7TxsxH`lsc`yph=j=S; zPcu9JqSVvVSc`N*^;h-tZ95Xb{hIiUif?b#YVSS$U3fnw`q-sIGPS$6ipEa*+_kjT zQmy5S=w8|SyD{DF?6)(%)C8L9{M_Xo@<`fZSLLCThrRQ@-dl0RJma#vm7(i3(blVi zpy1y5x5*?eQ=@}p+R>br_(;&1)z^FPa<0Faey7AkMJOUdbaM3VMpgUwng%ENxw)OU z=iS{>H|Nl#HIx2-K5yUL(UQpoDxQ>t-Z7<5{S&=?iS|p-3Wwe0@6URRsKDjJ#~MjggRON{`$(SP-`%)!yx;b&eHekCnhK= zDBamr%6*`I(*H+Cxm!1hYK1Te9z1>8``2;jmR+Toi=S6o8r%gk{dzK(nYt3hU%~Sxc`>O3+HX1oyEqkpO)H6P5rZTa(hz;XmP5} z&y^d0{CObp$oy6&Gz7E`2*vZr^3S(owB*{L=uuY^B(>-7EAP1qzf2ZQ zE}B*&Y3t@C&i*&)21}x}cip|0!*LU-uXLIn{+USd$QoRKPSB0!p^2qQD&UhC7DSmHNtB|VK zfm5fr`sMAXO|B`u?_W0Q|Cg7+0-nd8>c{VsacP+{MFdoS96ZRFduxl}H(qXT=7yN} zPs-ov#MXYix;mWU-{hLq`GKumqM*v;$PpKYOYJ8*IrjhiwK`_!8_jjQg z?LJ@L-`~&guzM0e!))_>Ho=9-$NRXFKZVxXJDmIc1NzPqb*a`myr$;bOP zY}|P8#l^*mDJd!*yK{W@_4oH*GW@Bhr&sj$jPEDo^Xn#0nZgnr9Bg4_b?U6%l<<@O zK0YFL``1{OyqMtVX~$6W`|b9KtyxpU`WL=({rWCStKj7JEe6MD(j1jFR^wY*Tds$y@Y$!quTQoK`&Hs9ta&uv+i ze){?O9s2XIO(=DezO?zh<^J*lf_eA$Xzp3&^J%%?T&+8Q|NZ&7c|+~VNvaA;J#w~E z4PP45l$@4&P5p2^zMk_bs7(!O6nRWkI#ibHUFYW3W-z~YqLS;ACy`gbGyhTldFt6a zdHXt>4vw<2vKN<^_iw0ud1#lYfZ{ii3KckbNTQq^-v?vC~3H`Ds_cyVH7W zm)09!cH_D|@BWm3%C-yKs}pbkd{LTl>j3|^GP4f_rB2Vy96fdks9s<8YisSr?a|SH z%1We}USDmgKda@X`u}Bqd%(Ia>U-|Qez4e#AkmH0CD`5snN zlb+3Qo9g**clmozM@B&K&fU8gH>dkwS+6hqIBa8*YvPY!P;fsva`&#RZPgcW znmhi^C(xn5zu)gad@IW8eNjMwK-HHQjN;@FJ@uctv)B%tzRSAr-c!TE7vWr!{=fQG z7_u@%OwaG=(_058>H8*%*|}LC3ZL=YX1S(G>J5u22R8kE-clPf^$#EO()}iWzbdsC zO^*2VS$M5?T|mHuOOmc%Y)`4kGcYkNv3K^JW#XAvU8a5S-{0@|XIK<2+F+Esv*@Wo z))fs_#@g@iWS9BR@9PrPK6L016CWSnis?dG*Z1Lr2MRoU8JBctMMgw4bUX<=-{P1||0&;!gx_6Bmt9x<^MIRx;I+RNqM!-((w7%DoOVQZ!aZZPlv#|F?YYm9^ZH z`a@pERwXAVm*iZHSovi6QBU!(*{1)Z_w1|=>dRcbpH1-L#i_Mks{cPf_TI34v*Y}A zxeZq$TGr3xS5{WeiBS5xsqXg`KLt~=sxvz4mMu$c-?H6*-uGGM#HNxM(=7l=G_ZwDZhuupcMwQw7pr;m^xfSBGV$*}9g_HS^|SiJ z*h7*M($dMlW;i;mDp|od@e_N~bN`8YZtBwE@%y*wuL{#!9&ql`AsN?hF~`E{=!nRu z1=I3VA9gc(@2lZj{o&)O<+p_Q9lG^==I_|cllDK&GCHH9lbD;Xe`ek1!(}g>COFQm zO51(^+-?8FloXcjm!7)(&-rxuxs~eAZQHhawc8X}%z2|cTYG=V%AgqriA>A<=61RD zN_if4-=42u_SxlRx43@5o{GY?SJrw@nlDz)<)rgRWo~|)T%vL39`8=Ce}`5o zx9m!pbbm#j@6t*6*~z`H-M4YY^EmytPw7yNw`&dkQSmhREoXe<+s|kIuFE_*{p{rX z$wyogZ(n7;9n_q2oSc8Gg@pS?{_#-*k0V5}>|SIR$^`_FfKe5}_rPQN{w}+%*@MG|u1PtM;>Ab?)TVyOVBC(uh9gocHJ7 zx6on-7tj)n*xRO}r|POo1fQO+SU6?*>^W18&%etW94yRYp0AN}+VhjK|H7U1pN+sP zXAVx9uZk3P#i74#wJrZPCFiBieXDN)nxQGN44hGZ|I8sF(d-`!-4-t^(2nJEVd->f zid(#_i+Xw5)u}Gz2u&yQ)j)4*UimML22jB z(j+|lr$RNhXWa4-5M0(L)$#e_>z22N**(&?ekdsx+h)77%0Z%NVyTAjmp1vtl$4fD zOKm>fJ;5kpyF>gh zcl8&!c4uwz<7+=W(L<%^wReWnE{&fvTWww^MVR-!cX+M#^FeL6HFIZ)Ii$#f2`*uQ_sk zzO!2SC+G9>!$+2AG%;nJ_T7GRQoKcLf4JS%c^^ct*`;r}{@hCOr=g)?)XvAp9v1Le zGcq#nsQJ0c?X}LDN#{j$bbeMgqbYuUeZBksy1yn->g|Ug*8F@r{l=Ee$!@PxK+E1g z^R2I0W&2QjP3^gi_2)~SJ{|9u@BjJvc{bRWGmX>v)cohIxo8#nNpjksJ%_dDXZeEy z(fHQu;<*$4e0zIaSxd`nacPl-jGv1Oi&gnMoNBk_-hOnfS6XWAEQ0{tAJp_44A1iH$XlQunqyJZXM8tPm=8 zs|IaT%TG;hEq{M66YR6f-`{lCtX*pwm7X~5Px^lOV?PvYFI?EqmXo^%6otI6LXMw{ zei~j=J|x z{aEIoUXpuV1hkLOMdDLvcwkD!yOft(+Wz0M*U#bpu(~bp;8_>(QY@y<%2R1S{P2X* zv=q@LPySCW;R9{`J9UYv+FWm9_1oA(k|jA;C0tsZZ-@Kw&aw^J^lOjHa$t_f^H`Roj6~f|Ig{m`QwG4Ek?I0CAXhfS9*9$*=En*DTyien z=h+wcR~vM2{5ID=eAigeOSS&~vZV%QW^OYly#uw}-FRPdvRg|>L`Ob4_p-q9WPZ$k zu~SpO9og_N{=ss2H3f|YW*r>d`=rlJny;x-1B&&B2PdshnrC0xJGYx(>Dve0i>&R5 z5|!&@_O)(sW&dFGwfEXO#$S3rGYgkHxJdj-Ro@DlTFi{F-#N29ishQ?Poubs=>={# zzJ0rwIYs+z=cTwjr<(_=Oar$~VgFbAdE3exfTz(`5b!_mxSupp^r2i=G(3XH_ z_jY>Bm^Eub@oWXllj=%2F=oLxzA0L=>G>VKxF%6{+8>|9hnMBS39d$T(yT8s554)j z^^AB9Kk@l-$+_a&hoxcbn1AX0>`(swJo3}ovp!E%V!yQ}Ui*>%@!301%VoX)q+D9u z!@n`E-1x)z-;_Jzj7yF^g0E{lHL3o)4@(CJ_wv4gjO6+2auc$$n!cz{XFa)n`{sks zk57Mc%5+^k_t)&-C@JIlr1JcG%5RQ0gHN|zCAh6%e^}eQ7rUSQFaB?%BE&h%{L}f3 zcX}UNZzz8(40MPd$BdD#mh$=gW}RnLDd0x&!{bdZ-&=SS`c$?M*|Lov6Y@ zk1Nyu)XtvP)KO6Ix~SFc{Cp!GmzLujf3u!k|1>&sVa#NlQ6q03LirS{56G(9=}u0nsdY1qrxpv8opE=uZOPpSMrx;9xs z>C9Z4kcEBTzw3`~3oQn%o6dhXuVv##!?v&go$_q{FLxLJY!mbR_{+s2eK)s9N6(lc zBHH#Gv{P4FN#|!9yLTO*<>uF1esA~a>;iA>R4$&9C`GJl%Js1D% zP#^Es3bz5Ci-GzHYF`mjEdb=^7Dmfg=irrHH$XD9cn@&0MJ5)lL6p8i&T zTa{i1$7Q$rwF*D$Q-4iWeBHbq(hQQmcgI$9N~is9?q$o=IP&fpgwISr8O{SQFrH6% zaaFy*TFtX05fmhObG?t;GgUXOvK4UNWMpJCLFQfTf=iWOKZsTAuxjiG*qo(Wb9uS3 z)fS$X=DEBb9L&{a6SUX+fSR2%Y+om)rE#UZv-e2cU^YI^=XNtl&cIN!tNV8yYtYj3 zj61BRH*8+aoYp7Xb5?ub@dmlK51&2iNV+!d!|Hdvr`gSzR&IIpPvvgAb zm54dLT&@8Qxu9bF>`}qFQS6F_ZiR+x4k`ScFE8W#$IZHTp8l#f)%zbnn~>x}^e}Yax6V(?7Vw7g zTF{BCH5+*C)ZU54dP$!BXqad&C!e((ysQkm3mTMyL3bP7b)BzbulzS2JXOhganWhb z#gYf>C1Uv{U(VlJv@c^5Y|m@L$@7XjppEV?CO=^gx>#RhA#vWnzI%UO>!Z)j$%{^F zfu^lx4*#CL|H03f`5%51RBvUm*MHu<>{T+f+zeE$KRr!XYV8!&`H2#eztnzi+S7Tz z;duKkrMktk@7k*$zl%!ZviyFyR$%`{zJ9)~jQ?*JsXAX%+%&l{P@5gJ1U?uKiMJ?IVl~QblzYWx3``9 z{6mw@i%j`YI3KiQ1{!M9YWnP4FF*HANLb+Y6uRHm`Kn#`*Qy0spwnD0YU%uxsRb?E zfbLV?H+jBu#3xC&^2C$RUj!eq`u|7#omz{u79}Sz-y^p`pN6^5ub`V zz=uKn()cNV|IXq0p4J~eeRr)WEM;4qyl2lwj(WA$hsjY+jdS#8tyq8Qdt$`*=v6N^ z9eUD#;kD+6d7{&@>(f(G4&)!qyLkI_M%yiJrO;V-yFtM?*Xh2>|D#h^RunA|o%bfY zFZ{XtI&*cx7pXPVXH=b-C7FWfwpLL?tUOCX= z0&R&~y7qbeo9~%eQM6(0i=a~(^VHd+9-sd$2s$~Sch%Rmw&m=x^|q?JHW>U|eEHYp z8v@1QOYQPEzfyQMr%QbAS)YG1N{#pquIA7D0*;f?j&pB!H+49;>?qI)`|_>h=$y>Y zjxIBd%4WUDO)PScSNZ>N9$Q8ID-9t@m{y8r_9|KTauXg z$$jO@=#R&5?+<4#%;Bb0wgIL(KdqepT>ZUa(-t9>`cpez zF52#?6Y;5(yLRHACmmWI^(A4TJn8>y@n#Rc^@~9JpjQOPU)Z!aqD(QeBs1~Tu}3q0 zXY8t(f9h}TuQ)C#ox9pqyJ!9h%>O7i$G>XT$JdQoi6^fMhUs&%3Mv{bFzn#iXLr>l ze{#fzC$GhRDg7+JKIw(gsf_;J*`e0_N2dId-N>1-{n?5amkvGYy|%hv;ZfA2sORmc zkN9$QS8GB+UdD+1Ojb#SEf$BOKYO*$}Xee{MW z#=9EyJnR^L=yn`A<^9KC*}9Eyl8j#I-CFzYr(xy!R3FP4?=RnsE0ZrRoIPWkdDzo` zX(!Dc@?v+f2L~Hh?5KQjaBWD>k1~@Ej>qnPK~ev{&#-=RbF2BOX?on*+Sxy@uj{Tf z7JGDX?G>A;-oO4D9^;$e@hUX?z@0lX=lt|u+}Wqy_tsK3T<>QZm)Myop7{A$8@6p~ zTiYx9C+ph5^{cl(TNzWfLi=Yrf2_-Iv$+AwLLz6(nsMmnNmkuiTMpgZ;OsP0a&j8z zw9bS6T))r%%G2xM;7->M{B8X5(mv@oId3P{7_&0}s^RtK_O7u{?R)V&#PQIi^Dmfo zDg8YDc;14m=i6PUi@VuI{VCyC{%+~2+B%#1-{xL^ceSd1&)oCSFqJ#*|5rzdpQoLP zWmGyeq4DsC58r}ix@`EAoR)G;6+LxKW;auLc)5V!t1F+}mp{-u^#A*-Z%c1IeHUBL zuzyymgUgD@y=J>g-}F_LGAT^-NJz|UjGeu>b<-w&`J-Jo7gPp#U-`V6fAS)=2_7mp zHYQHL^>mZ7>`D7Qzgc{I&nD#L^c>@Dy0Em?JVYm+Q>c^WTG+Ki%x9+i#V`vlOx=8q ztMtmMcc8eqv#Z4a?RgiqpNpN2HFZ3A_NeVCcUWhgpQwt?&mO%6+LQPh7MI5!xNt&y z5jW3bsr&DC^?cl||LmvCOo39)`|oz?{Q8}6B{|@Ss?WXR$hG@Gu`FfI!6f)F;7Os) zp1&gec}o{9N!Jt*?0aje93E!IeEyW{>ek$k*Cz!Y-o7lRGW4{stiABDW5;f6$l83T zuq^*JdvS5`6x~=p{=B7+4z3NkA+ou(eKz~MI8UKdt~q^7f(yNW=Q-UD)toN;%Hv-+ zc(eHFO{GP*m;3*bb7^rcp2l@MEA)Hw%$Yl5cAt~o$)n^{@S5+CS*ZQw_U%h2<)@^u zXnSkBpx?yanX50PiHS%Z=b6HXg}M`32<{`_EisrcXVmy26+>s99- zzH`zU)IS4t-U_RuudIC7Jhk}PU z&RG8FOQ+@1kDF>cd@6K*{Z0%~R{AH}bfw}%l-RTy=EuKwMr#Bu;W#x_Q{TMj*=$Bd z1*IMtBexshZvVVJ!9!)m^YHkE^LSUAEeqmDgPZ!O& zxyH1E<9T0<+oSycgq$3n^6+w%Ki>jY=>JUP;(KG&b(uXGl>Vlke%fL+N&Z{5%CvLQ zjZ5y!pU%DiWk-9HyVVT zG&crMOYijhcbr{3Aw8OB>gnm{ww=2-cj@z~YxePN5MyPoyP`7Rp-ZG~N03LY5sg*0*bo!vEdd-v&pS zX|I~LyVlZi#wKI+vi#fa?c3)o?I>jz5VW~tYp9i?Q8WQub~fD<-p4BV@cL5YqMYTsO8@ll{mj$-DC_VCZi%YU=8)Y@dlkx*w?!y-b*X-f zNb1>pF4GZOT3?JTim7^zxCsy9Ijpa`K0|G9X}9pacSGc)F%@qmdZCQ$>}@3eBn*4xS-(M znY%to_^B^@zPdm1)7PH1dA*-RPEBq{{39S8A^EPbRaOAHve`~co+jr?%@69H9{p&qZ zaq;-1h!=~WM9-L=c<6hrYW@7`>(4ZQF8`zY{jScB%$As&8=Pl5y8Nho<&hYz|9*Yo zni+w8!CC*Ni?Jp@);CziJMOOFwk-}5E zzDfHh-@3RuOG5V3&WZncdZ)>Y{w+4Le7sXfV7qbLlil@^pT2giUU)q*A)!I&sP}{D z`*lSg&(HVESV?7aM&8)}*r0>MzIXPrg8iqa?Jl(xbZPN>YxR8fyXVnaA)oT&cWVEt5aO<#`p5kHp@$KxRJ5iQAGKdOUDWH}|4WMk z?wn&QUA6n<+x#~d>=cyl?5>EoaqaT&YyrW%xwoe$%TCf`e;DY~aj0t5rv7KWHYs{K zJu*+Ur|d2%k&ZH7-T$b}te{k@HBUBL>8IC(Gy7&$Tsy>Y-)~3dHTk$++Y1*KvrW+a zc~v3dvXaTFaN)9T*EVe1KPEYg3??!0g8NY3k2b z=cxDZ=0+Vnx>97#TAd5KB5PaZ@5L-pm1j8g&erX4YU7I?KXt-BGjHaaJ9T>Sg~ZE} zCQ))79ls^BUS7TOX!7U3ywGOFUK8UhR)s2glo} z?bEAs-(PExQT=~vXM})f9z$7K$+VrFVO^VYCrvvrY5ueY`zQ6EiFgycU*Wj+m-*gy zM#+Xt`ctH$y{-f%MwhwqoSItZR{EXGD3w1ymP@Irv(vEc>(_%*AIMhBuM1tOeSOs% z-vA3E-hA9?;1Qk-YD@mRzE~TJM!u4OBZtw{VrcOZ4S3>R@lKi zoret%Y!^Or)+ccJuGu|L?%VB`zZNE@5q?YZ@^PMHTP`*SUc1Wa=H|w6Z|f=#?|mk} z*Q*}P{%Sa7_RD^~+fIhh|H$fe`y9I_Em`w;nUuw+2ddldoj9=RM9LYzW8hUZp!G|~gP4@6U;iSFZY*OX6`D@gQ{~vRH*!At^ z;)hyOrT_l5cz9~-7p;n@L-pZ%+W92O>G{>c->#6p`yf^i$FW=cct!Y)>)qph- zhIe{>!*DCzuulMzp$b%`J|oN*=g1BcW-@7NJ(K?G-tl+ zf}Gh)R5v_XuKHs8ll1f>4_3F8CZ=UIwO>5QrTYKOzpn~PImZ`{wo7h<>&iful%mTl_z>?!J5^=acXT`Pv5!A zHFkCy?kdgERQ~DGvT`w-qP4QyjdxdXFpHkv`KUfK@X)q1(#unpssEiS!+AVN_2P}q z)6VECUDD^6?^PGQ-fcz9LY}(%0>>NQzSXg4P1W!HlsYH1FE&IqewKRvza!gDmm3%< z2*s^#4lJ!>kkaFCdfW2WhcRP|4M*s6_5<0a26Z+CoVWGvAI;DIf5}5$Yx3j5%oQ6y zC`7HbZrpf0FCqMn@WrFsS8RS=CU#6_Z(rE{IWBAG^FK)U5AO*z4=317y?*f^=RMh7i(Z@GeGqUe@Y!{a)YFqSVr3tnp4O|C zvnsAN&0N{pb4J-%EdO2WX%0)H}KklrQ6s2wA@p=(r0IVkpAIgw}K}8xn8dN z{q&jnHcac^wVX7_{u3ZD@sOsEtM4bvUsH`cUrKEJuXO7Cr{mXO`!BlrN}y)*Q<*l| zJzb8!7tdfac=I3>Brt0&pW|)bMyQM;a$XS8>|=g+;tO z@^Eugj@R=|$6M~Zn7^0Ph~6c0=IoikvwN+?&cx^xUyYnv^nKmZ2knZB1NLV5&X_rK zVa&`=5|`>{%-NxKOeOIRzi`0D8rK;!XC6Ga&EnOyb?sB_*H4|9zCLH?oe5t1l=fx5 z@7=<3vdgNr>PxG$rh7zG#3IMNZ4rAbWaW;fCl%~pxMABSH~;zfjDFk8*D$)YxP6s= z9>tz|yG3=rs{G9VXII-l-LDsZ(mhS5?dw-Y!G|BW3Ol6f*kp%0KjlgliEI2frTyYT z2}=*7XIWuumwfp><>v9{ljJv5n|2iK+gNe(OQ3?%lH+pXD*Mx}dRfZ7zxC2KVtay6 z+n1M3x31nzNJvmn{yBS^$Fz-mXCFT0T$;Uudy;%{QTe>*t=Y@JZ+KGNRU@=HPU%e? z+qa`O2aa)bMwGm=3T7~KQ%Q(F!`yQB^ntnR`lh$aLE}-0VrM6w35~O=U;3g{lWW5h z?{!Age{BNiv-%+Y=UP9XGnqAXY$!XZk`iOD=~MV~%bD($&o8cRRAn;LJ++l@uid?n z*@b^o`qn>BmtW=aZ_~_VhcEYc{qwcSURbHjzq6=VjfYuqp;M%|kIY4WN9!LW-yM0#VZFuDJ?=hl`pM@77E#jEYT7O;c$&*EEOIrU zF=K|3-m7^pzJlF&V{e!dZ_dLh@Wh|Bs*b<}>rWqw|>s6EAL&fBLHU>kq#l`4KC^i$7V)^t}V! zTV&$0+e|iKOHy(l8@xzu6fd--MKuDT|*d8;jll{+`X**}3A{ zx;7)dKFh9^U$lOIo4WZ)-2UQ+0y%4RQ=fg)-W9a|jop$-|I>?>C_J93J;ib7rE?Xf z>OLCf?;j=3m?5$F^YwVn+eMT9yk7pjp<_el$&i_E8E1<%c7QV4!_y6O*P70{wqAGh zvi)DYCxm?3Y@7sY#mte}H>L7Y!L#1r4O=%ltxo$Yx{cl2uKlo}H7NcSUtL+#9rTS) z;!?fDyI6nGy>_mwxzlU(_4SvY5)Jt@QL=k>`rpKi42_)!ZN*RW+wSn3;cQp;W4qI!PO{z+p57yUjXm~O<-r?2xFcrFKB<$qvaeQWzNwf?%go1Z9H*n@?rvmi zYinB(aZc3uSY7+a!~0w3t}X5J&ZyJ-S-(7{Y4YLe1sn7mcM7*3&cAC@bN#mTxxR0p z2G?J!s{*x2;uC(}_p+L|b#>ysCGQP)gs1PYnCkSDD>Y?bM~vMz*=5VrK)MZ`tCHUx zc?h!P*s*v8t(4_aD)BczF3JJl(PaX*nW+bbdbi`2MhVgiWo;9jz&iD_3&f(Vlj6!#=x;i3M@F#{=9x)z;J${P>Vq%44kv zIt<2LYFbTOVhLC7^gs3g|B4?wcC2!mw`GLvdwJImy*)j@D*tORX|k_=Zpp^O%lq-+ zVRlT7H4GWo*U9?Lvst;pC+b7^kH_EGF1Ahyw6DJt)xRa-sCP0OJNx3`<$kYZ${0W) zcdi~ZSnjs`0_(~9JB!uR&d*!B;mqtO&J{I#=Du(`wPOC431$_urB-bCzubR5pS``k zX`huRNMj!~Iqp*U$?)L8gN8M0bXrs6?!|SUnfxkp)5ME>Mt&uSPXE}-_1o6gR#s0> z@0Cm*N?h$y`dL?3_u}^U{CQ93#m?^j@;LXS#S+uFm{|?!f`=t#A0K_%Ss`*ev= zkA8k-t^8KGK5p-Z{QGvLJjOdgx8llVe)0RXdGqFt`I#Lr79BG1`S&4SsOim{oUL<( zO*CCiv#%Xl8@>IL&iD7PQ}%e;H9X%w?cvR>H+%X%KDuVK$J4T-{{E6@H?Ln`7wdd? zSLs!QT}6DLVr8P+dlh+xZGQG0?APTxLAMdEIM$rqzoGP)hK4V$t(|@B z+xd%oE2^s>KRY{H>b5;ZtG??08_xn%V!VXFN456;IKj^G&(vq_b7o)fi+!@z1_cis zdgYoIOTWCmeZ5=Ww!bEjc;7qa54U@gK%GJqc*@T7(6@Nklz=r+=h<5oT^}FoJv_&< z*rZOT?_1ilGc!S}b&vHQ2AycL1HfmcP5>WGUvp+o<@N&zjiP6-Q-DTUF;De(14y`Xqh^ck{XKXI@rZT-ur_@wCHE zvi(84^VK>%4IfP@`#Kx{yB9Zqd3$?%z`6(CQ2U}!{(t;6{>J_{EiWD&GUzFK6I~@! z|M%baTjyC@bzQB?-Z*T}yIb)1Sg(_<-tvOtx3^3ys;jeW`2IbIWcC+ppD-Wzd}g}E zmS0<5d^%*%lf<{yu2np8y=jr0fOFTP#ft-Xm*swWd6`{OQnIzKrtjILdGq?>_Es&- zySr;q&BV)JD#D(B+A4E1Bl~t%_TDZhle^ohbbm5@u=y;?6gJ1<%u0)ku5F1Dk&FC~ zCEnlkxZ_0!___x2VP6&>o4G!_sNQi`&*VD&F3Fi1NygHc>uyw~=H*US~g zHtM#uy{3uV4O8B;UWqH=ICuHa1G9Ad9QB-Z6Njrm84G4UYroaam$;&ycb9|q189V;k=RxGx-E8h8E9Ljp`l^T()iPpCQfX8 z@gjqTl{NADy4VjNKQ=C0sAyICD&$3QaZ#7mvCmuG{V(R8jtrSw&rthEFDWub$n4m% zjHnWCAOWj;P|hqc*Ee}{KOef3HK)neoQ@bZPJP2guOtDUaWD)F<* z-`{(9VWIO3qg1Xvd-n#ekF&L~v^;qFG`Gun}@z1~c!C!_YV;@I~*Rj{EpdvB7Z*Ueo~peeDmWiTs2qqJ@<59 zRI#g#&**gyQytf?S?diC)vlZUWcm3>P?IXI{k;C1MIxc#czL+fB%q`+@ss;qRri9| zAB(3RWBpz(O z!Qx%9tPk2$vvN(l9r}qW+ry<;K3W4)A?&^YZG&FddlA3N_>89Zc@1a z%8zsA&gI>{^SA4CQ8(Ki=kLyczH`#=JsAsq=4)5m1*lqS{(Sj;(}vt z_wYWEB;n@r?x>AR#DUN^GioLJoz3oz#{36?) zLJPWw^ z6jYN~Y6t^(D69{*lkTpan~t4;Bzf5OS|2^jV5lZ)9|w(=QK;57!pB^C@S!R02l?(Or=NKDn_BsczrV%`fmL zdq&iG_E}T@Xos&`kbZui)NOnB@0a#gmxB)5&-o7zp7jd1(_{`cXJ;qN%-TOqA*8Q5-)5Bw7X_=XFAIY&g;DsJdPx9~YlP!LB zCh&LN)ZUO!bLY;zxHfuw%*{=y-EOZ1Wn_4in&!`+@7BM#d!}JBTiM%NOTYcy1Wr(( zRME8IiE+=nMck9_Z_ACIV^!+)_IIdR&`I&%%i^c~xg#tj)YK%SE<(Z--p}zZ*0qz z1|9y8o7?;7X!qjC&1n{ek6JF}E_qk-`WkOLU+3m15$^bljo`EsE&v({h}{zVN!@>5 zk8Sn06{Yi6xrLtm|9pP^g*B1J0)n7((;S=G7N(w_W?*2@(AvtXq^#V!X?y|StMhb$$^o&Lvfu9c#NMa0_QA*QG9RQ!H<^}|%{@Ixm~a40G&8vWi`^pxw& z*|UL5y+jjlUJ6|7*4R<-eNO{>G#vk9KGxoEomrPV60eG3;Vu3582KvYyUIPPXu zdV2cdbLaR{Pft54U;jsO%I=DfN*x@`Y&-!MX3g>r2oTt_ckk6*{)pTP4?$ZS8v$wQ z)z@lQNu^G^@FyfEHYVo6p334S%a)yb`1moil2cZe7K4I~jZ9r#T~YB#|9Li?N{3d3 zu5QVFHK|7F(5tJfpPtf=e*gdP`+Crb{Y<0O5AXN?=X(GD!$ao0J3AWL`Q=tz+4suJ z$A{24$@&b~Qo%R2Is-ORMF~wwKoE;+5LRJag z()$?~7ngN6zHU+1ljFyaH+O)dpM{O>*{RFR{TUhj=i429aIpE|t5;k~O|7l0`+mLB z{_^7D;scZZ@BjZ-Km6qMds}ATuloP{{r-rJNvv7d)@ar+f{rzLaIkse)Tv8r9vgR43k(69X|Z=-`{G6NtGX1 zr~X;9W=)Lh|EsIRf3ExfyzS12d-+KcU5#0>GI z0=Q(^@Z`B{X!v!_z(p)w;`(~#wW`+E(x%zhTw1wAC(NDOd*+OffZ(pOw_4l#H>aOp zrSdbr=40!e2=CL{%0E|K`Tg(j?->S(PJQMs-mdnls;-XQnR=k(Avxv0SAw#fBXm{P zdFxeP|IW-bZe9K8lJ;kY4Ov&U1O$)u%kwi-{QRWa!Lc@ayI9!miFw~YePR+!{Q2qW zp|X`*^6%~neO3Y57B2Yk&d%aP+pe#VUz~S$mzL+gO<(=Iy_p3UdQH{(@%g-cYf=5* zui{x(SG6V`?K+vGe`doI<5tz0JI`I&r`526y481ERqfxe(E7Rm|3B-jD=Qe4n!3BW zLC4^z`Oa$Sc<}CB-}e0bT&es2{mK>)+*R_jN!5E=!j%<)8@6wE|NZSP>%?hx4_@Bc zS*)ORWlf}T@N&PS@Av=bW8mQ7VG%rd=Z?(bHr~T8E-nsS7h?%Ju4SqB^suN;Q`eT4 zm6`1-eXTa-_1)d(pu=s_)6)e6)qH17dB06BBjVG{Lqc}%K7*nJQbZq`G{5}Q=OefG z|NF%qyv)b(?yl0ryu3cIsalD5b`%yB{r&T^m`B=-=iIq-pi~qdU(0H5Z=aH$o_uD8 zA!rx#gM-bC4E6v2np>5>10C%$MI(?&KW>k}x6Q@R{X*8q$u3*Ate~LazzoCW569)} z7dW+Yd3o>C`U=_@X;}R&XUFc{hwt2xQT3YQu-L5^bd1WyXOEd(gFl6{#-AzOs%%}& zekJQs>(A=GlY7!{yjf~F&D_MO$$4f`#?`g_+v2^>l+TU&@kQ+0&aX)|eW}yrtY&A+ z?kg#BojvdGM$Ip6XR5_mJ*M@ojaonP(BuDm{tvI^>L}xSg6sSGZ9(vp@DCf5^J? zjV(GeBBMBu%bJur_B~xPDgM$$*JXU6w$g{M91?P2Nfr)1bFa>$!v8_@^dFZzH(i>d z6!_LAgQbJRUMC_}MJ4{tK3=aX*7xt-4qdn);KIVKEgh17K_v9GY17hP-42dr&%|cz zwq9`{Ir+)??9^9xcf_VYl$fV``()1h3;8QfJ&pWS>hNj%`un^}haS{9i%v5(H$FaH zb)QJ=6fUR#_nN2tn-+C!>zWr|b-8rrY-Mq2c`2OU87ICm@#J%nr6Q>(^Dit*?pyC4 zu{TsH?V6c|zRru%0jRKowA*!w)=vG&gX#h(`lYF&T6y#K%b_1t?>E-hZBub)Nu zY5hFAxAtiqcg+W(zv^e(8XFnU=&s%P{g?AY)A@LWE#*aCt zw!UucnDTF{!_`l(UKLNPF8Z_6{KKgvEyRhi} z#JJLbg%_=Qw`||$xo_F;ZGX4lZ|pEAxhB;5l7*zTwGgL!FO!vI+;GT{l{iKo_Ofbhx4bklz+NUj$d5me3JR0-Q23rZ+E;DVxO@sVBMZ<)p7|@ zZMOY4L}1~R>*+2oAjtH{i0eyah$JLrL{d*~->lpB?%qBQYq`$6d$tdrJm67hPcQkg zOIXP-Zd&Dft<=jxnuJIGLxEke8pItNyH%ef(&pMn(A-$JQdz#H1Xi_MpCu+pBDwnEg(^c^-Yg z4m4}_X>Cc5w58y=zHm92Pxk-^&h zt!}{XW3G3d|KDqV5Vw;HG|9I&%eUtLU5`7vr!BgYo_Ftv_q%(od-ln!)cSd9%kw!a zKRdcaY%bY*=lApT5iClFHnFaDk@VO3DO1~N=lO49_4$ntb9iQzPSQz_I<{7-?@qOz za?{Scb23BLNc{4jrEB~>O44T5_K9|3}y0@Dm6qwH<# zG2U5Tc~VBk#}oH2E($Vx_t?MdT;I8f-6hR#D)!5h=kW2gc6Ndrwu>UYOTK?=t$wNn zYWYrGD!Oh?_teQX_t))x5O7NHZ#Dms?fd>FTS}E1E9o@v?W*UN`@Vn(Nm* zDVO}D+`$oS{%)%1&L}wrr9V=I(KoKuU&;{>bko1PVB@!I(^O5k7w2z}_hJz|xVA6- zTyEecyW@Y?bM1>*u<@g%&d)5L!09zgPLF^2PLP)bCiIf)Ybes>o*yzp9dY1 zRhIK%vSrW(wq94t8UH@PCnufXV4lXb5Eabi%JS_J&{S(3HZ3(RPan9@c)z*B;9EPVS8bZF-}S(ywy&?Q z0w|;z^ymIoFv|Hy@*zA$>*bil*&(08|LqA|_U7;pAJ7H3o`=&wYho^)bvH0FYU-ZMEqBL9_|(#4 zsSl1k+-!8YU`ptYa$FoRs~3|ER-~Q`YzW0=ycV6E+>Ulr~W!KYf4S%tzfNx zv%_*lxB33tQFWxRx4I>_)pnjppor>!&qsbLt^YfwbPG?cI5hpDVUV)AqGpZAJe7Ak zQ$8;0``cOj`0ev2rNz%KKDnOpqg4H*WzKEKA4gSA$FBYRr{b!#$=T0bpI%M)|HS{Q z<HkDhRqfX& z-`O*T;s0B9!^|@xAsXR^;V13O-!$E+ew)+H4O&{*dQNs%L&t+xK~j(1{Ej5)l?tHT}x*P~JV(C8FDQh8DSl z)|%*Zf)k?Z$@=f_{8i?CI66C|{?F$pCLheET@3tJStOMo^mk&#A@TS3|1O>QanZTI zFBd)G|KnK{nDk>#s>PHa(rW)VdAA*9_v2389`DuC*vOdEHQnX*z2+wcs~>YJIeiy= z-1=QZ`DgVy@M*STWk-wmT#8;@SS;WyE>Bl!o{r=3^GZ#DVZF_O!+w2(C|Cd)< z&ENKP`K133(xlRS<~3{Og|Z56-Lk>OfBwCW&K*mObLT`eNJq>GljSA(Eq>l zz3S?YK^h*r6n?Uov3l+MS!;Ctck#PeZ_osY)|Ay6$}@Jxy3YIl(UI@cN|963_LfE# zC3`I|n>%&Y?vPV?@jGX~Kfiv>llr=z_K!0^tkK@NaC7{6MvfbmESlTpm31AB?pvFm zJpa;SzW2Z7F}!>GOuesjU(Uay9WJ=+j>heEm9Z!D?=MsC;MlIG{_N(uS(9o)ZyW0> za&NXf*M4VTow-}h_r-TYD-4fKR_f|{lCJvh$ZV1Ni%X{j+UEy-dvsFv>9Thlo@lE# zz4mhjoi(SH_RQ<Ly5cwA z?|C>ICMR1=)8uz{cAk36;qsy7YbUMGx-aKj*Xzfu^8Mh=o7M@x(&{B8?Th}iww;e; zGxpYwh`4>CxA{*`@A<@&;mIYApw*|`N=`eCo=z)x>SD}J~Vf9$+*62@15#zdAujKKWU%8 zqh%rU{^Q@i?tXg8)Aj$Om#ek5t}gN07p?kT=J}J`^^sg<+m2416zl7`Z`IqG(HVCqzxl?F z*z}Oq_gUg4W_#47o=KGbb^c~a+@_+4ICKABJo4H3J>Y?X$biJ7#G=Juxr2K;x;#%O~;Q zPo5AKTv*t=%t(9pnz%iDE-kyZ+OSjxEq(Li{c+Cl_@&`RmxccA&ay5!P_Q&5B=VE? z&+Yo4dnGa#uTMGh>DyE5jF9~sU2@dVifz-@{yF`;=QXu;5&MMR9s9Sv=*-@1%@wc0 zc#n383U6C|XoR6mDlT-|rdYu~E3noINE zR)H@kO#F0XWzl-^Zxula@<54Aq@2F`4E2sZa-(f|7!!a(DSFtAqb9{t@Ql^o4otgg;wabZWkSd>D`$ zz4iE&kH!WcZod!MB}jqsjB}IYnB<*9z(2FY|)Q8%>IAE`m$cm{hZ?J zCog}?3R}-Q|J>b&VJBr>TD(NHPOqIj|91Mf`(+F+En0b@hD9$jX6lP~soY;X$@L#XpDS{9R)|G0y9czv5~0aP_s1i*mL&ewuk`se$VJ zi+mBEL|uX>&7Y=FnQ`Hb=I09w;+&^!DsNc1Qc^3tq`AC(okih~oW7)}^?tR9nohs< zy%$c|%Y19=%Z3XRx_|1;o~E!f>+j(Sfh*#+s)1U2>wGGCUfTQk?K?8<&+O?!7nZxn zTTCfxS=y>9btSvwYWMYri8X3$-=(>-wX?r`dAWAd6YrxXIF<|MEza6~Z(ou4-)jDr zXw`DiD#KaTr9AR~n$PI7Rn|VMz8-vWnQQb6iP;CvZQF52-+rejcxmCbCE@!GhOgp=U{zFzxQd9ViGO_eYzxc#w;323vI#zXbMfn<>yuUIUo3^rCMBLs-w~^NWo4xJ z(l@dOOa2?&**#CIs&)$482bzLJMUx%F0=-1E%+sO)n&c9x835`8^CiOT#I}soj2&o z+VJG{z9)}!b!M5Y0c~mc@b7Bq#n-1Moqu5j8F7UVy49cDUs5I=wS5I-DZgfG-aXxk zHQF8(e^;xOK75nX|Hj<#(6+k0Rt=!0dqm>x%OWSM9^L=?{oM8^esK)c({`2b(9Nj7M=FJ+`#Oo=4^WtyvXJ>Oy(SF?nGVj)rgBsDt zz-!d)Hh8&xvfh^4WwcZ_aJ5^ikEHzs)%h2z5aBLyw^cR1^kn^aBd)xurxxT$s?XGZ z{ykp?y!^UyJIlReN>(}I9UQl>od>Z$|I-u@T(xVud*>swrV0JhH`&|T+Khe&xqaGv z|A>O=JBeT4ro`0#Pt53dS{Z-)T5rJ4B*{PXDjjcpzum(TvMNC8>1j*u_t}>=h;?uT ztMg3yxwS!c{>3y5_v{z0=5T3AV*Elusm{q7Y^-?>q85_sY$;#27KKNFf)JGxj@R;;~~?ftBG zQsw^j{?b!4--6=khWxJuVZEmg$$a-+z5LPlGu)t~*50fu4M$ZL%^>)^Pq_eM}f6Yqa(J#agsN8*y~I=!U^$)CzMY~FmX_@#HKO8n%n)zhl@ zK$`^XrriF1^}^f__qPS4`Pq5fEzX8!M)33?xA(qRlm5R;710XUX)a2g`|(xaj17P9 zeLZYoXz2EwFaG3|^7S!dqNjJCIum|>d#z#zhv@0ui`=hYtGwnddh&Y8-%a|0?#XkL{yy>3hXYQ11I{pr0$WJaT`OhUG zCABE2GW^VpKeEUE|ImH&W=-m}6;q6U{f<*mno?FOu+4T~wbP8l`@T+}nKiY>_xk%! zJKq|Znl?JG6p}DrWd6)gy07bLY+}wI)8@~KW_O80^>)*Jyub;hON-d|z z_RR;cua!E*0Sdd-s9|Tfn0vz$<6CNwDUCldljhFtZPxbh1YgA7->mJgXl)&x`c7SxM}uT;N9_MoqF0lhgTf?d&4AWcBRMK=gg?8IRBZ7e4OYBnGP2epV=}Y z-uY^kUBb!qh)YFMRpJbn86+BNNtu8aR7RJWB*q zP297dsHCLV2b``5RE3nV{|-)CuP{^7UvQJE4`e-ElX<)1>xaQ0y_41~`4ITy`N_&|EJ7#3ObUpwNE_tFnn&gQsu++%{$7>9KlD5SSI-&SI#(WxphAGkx6URJFP#O z9gQzhOuce`_LRL9Md{n4j(2Ygd^debPDpHlLrGxb73Zt0jFozB2WsQC1S~s0)mDCm z(A)H-p z8#a|i9l~B2%S03kX>OP61|4*BVp7d#L#Ao-4@}(BzkGcKqeL=Hqs~H)i|3?$+3~!GLw~^>-XO9Uaf@t}BPUES~nK z|Kz!IZf|BxH(@7Tce59X4n^a&vFEz?OeDeQT_nL1ur;*%LKCXLj@! z&rg=W#5W#`|FqeBh1vZ4BUeiAJZDvSdo&uh-srsIe%q?P>WwRRls?p3>y|#+UUJs=>`6_XpE8EZ&wjk#`Bi6{yIQB7uFt=SqK}OG?iR+OujQI_UeGwH zZ@=`*^8xvp3tl`r^rYXan_pS)ajVzk=HD^f1Gk=st|L>Oubg&7u6}a!x}boaM#;Wo9N%~5O`30)4q854CA(3zd-45?Z6BX+{9$nIxQ$S3lEl-9@~?_g zI<`Rz3J=w;i+-{lYO1@mO84UZ7u!BgpF6jATDDWfuabt+aQQ`7Z%tt87k*cKccB7o z_17(hNSVLA+j{oDd6RP`x=7;lIn^#bQ=fBZET=cLoo2df7PjO3*7;4}EI}(@FTT#` zPAU9ie9P=#noM`w^GgnEb{x^(kiqS(Z|AUA)!o3@!VPI{U$KSE-o9;~V($AF^*(-o z?eX&$yFc5kI>&#YVXyecCx@7GIx1QIA6dTWw141n`-hs>j@ulX)XjgP{=93*O!0;~ z>+`Gkbn$)P`EJ2UL>Rn2KfQdS+-`}J;>VOew&*Wjtm59|SoKEe&(~M`+>U!EzI*@h z^Y#dN!TyWvN`d(w6L=3Zm~?X)WWC0C(m*<;AAwDF_Uo=g0;kL<4d zn%sR3S%s&(EamS<8$M0(V~@@scmMeBsp^AVoyCjy)K_xW+wnOS9g+C+)5uZzv1`xY z+Ifd6xVG+di@qHe{W?@+(aUYA(1=n2JF+No&5NQ_E9O7xxcKAsRc3J6^FO}GLMAif zlW5ESy+szS;v4_nJ2GjF`^N4M&mSv5R&Xz2SNeNP2&@{kK5t3l$^R-dHTOS!G%+382JQRU!8vBRetL+O*9#_t}A=dbT`J%^;DJ|^R zU8dt^t&jT6>odQ+kJmZrW%E#bSC7#w<&Q1vL4_f7rSIM+_O*Y6*u&(bK)EfYJi>9N$zU5npOk{8}54O-CD{xmlJecxHt`SC~2 zUtW@^J7tr;V%6?VPl_#MHZI;iNq);m8~=-m60%CqLS*`Kxk7*MY*39?`gzXp+lyy+ zjl#FBnSF55c~H=nXjEzboT=$wm=xXe;?g05o-ELk-8EOzvux!zJn>f9pT2!P_n!4< zelP#oz~~jTy-@uFOMr-z52`hyMmk-*;7vO%D-WjmiFFLE!k=r+`e$j zYGxl_-+;YYtU1y4!RzG8llTuVFZQ;uzIpiONx6M?xdpF3*-fhfErR`QSf%+hdb#|Q z(hrhu?4Y&E+s;|6xb~URyY8P#{kbcN;KoYB|81?7UjOXR>n)CaY^9)Nk{7_;{Hx6E z^s{;3(W_QTrSmycC;xG*wFcQ>&-1qNVRLOlVj`Q;p@+8Kt((OC^F_Cre_7Kg7aM)} z;zE@*Yjp!|U2RmIfB0d+g7!)C=g#H*e^hbNY4EbO`UiVC*+?9_fk(6!?{XSDC%*@Sgyyep>{yn`%uU?p|pj1(wqgVdtcKEl=Pe7}Q=k$Y? zhkzTzJx^8}#kO|{NIzU2@acYlT&DPJH zmi*W2=JW(mX(F@Xi80UOD25%RHvHxo(d{Jf?3bG=-v=9E9;{O6lH9=yKnw(_r+ zs{L|({`p@2{>j>F@0s-e$q!Xf5`PHFj1scVs`2J3@mkN1zRme?e)DOgr=Ui1+E=d? z0rynZ+XIDZmcPstw!^^W}xPFin0$^P?l#Y0#Bd|$tg z>vp)V9KX_`AM*P}w`pqsyqTYLVL@E!&a0($=l3*BJ8K&W&NSzagS{KP;mPa1C;#VF zGMcUmWf44hbf?I#AKRQKw{3NBDOeu^iq4Q}HG6uGJ}thTn3}4%X7c;jm)ZRK;tyTA zBolYm2DEVn;q_h0KP#lW_t$kFnevf2aQimvGiQARS2|_(NZ)jS_ji?t-MwRv-kF{h z&X1kreQ;q2XUY3ZT;@vOlb#sFPy3XnT@PCe0J@`^(cA8@ffbuKxXlbIaA3YY|AGlz zfxLluKt%MUf1gD2cYXa%y~Us%J)eXoDIZ%=x<^AAln<48!RELIPWHqcBzdiODfDQ$_;$L(mZ@$S#N&OF>UrK+tS;W>mjeXG} zo%$^i!c4b0Z_i}9kmSpub>@@%;-`%-Hh$$1o4c>^#~E>{m(HPwYuEj*JvV8+n%KE0 zCZ$7PIybvaSMa^O|GR>~#GomKy4qE>-Auns_I+Bk!qd)u|Khig^Kb5qUSXuquvJ{v zt^WV%8S+))*^7cUYQN*`Q`?_*<-ocf>Jh?IyDAr!UpW6?C`;F>Wz}cq4UhilZv6(@ z77!2tYSIWTGxC0>=lg{6iuKNdGIg|1D$LD)BT()H`iT~%OFY4~!_jvvX zk-OX57rlOVIZHzJ&huRjR=?+{#{0h5-Lmn?YBspW%RhcUp8tR0p`3ahzWwUEUKniB z;o9GRs`%T(_5b$3H8f>k)I_t;3-4l0|>$+-O z-r*Q5Ah<5JLwMWWPS3W969qds{@vy~@n*Br&Mt$0ha@@;JwbDopVpT6$k?)$W?gHY zIa5-`%W~1|a{f8iVv3*ly}P$>!)D#SD?4~_Vdf@;UqK!ds4*4|RKcrfKm-M+7H*|W8?RqlHo-!W16;hPsc!otBjLW_=H`l+3I zYT2>WgqMG&t_a<0wnk@dLUwZdFFD=~#h2q|7L{!~dhzaU@xEQx`tHRfIuDf62{In}iK`CV2i?$!DzfYVtp;hzovgVErg;&KM?)`V0pDW4YTRZ2W zZ#Oz_D=3*{-r&jPjNDQGSn*Z034`Fl#eM2EyT3U-X7f!Tc`)0RV1i#HZcdVhPX zI4Caa-#7lawq5?r*)xtVH?~yG4k$a*@lio3BKr1&*VEo!*ik1eQJ=`e%*_1eeqn^$ zC+RA)rnSAYUN@~5B;U>t*zG2JvU=^B!kqt}Q-AwiOux4|)gClbtmK5W9%9?ar8(7c zynFeSoOYYZKKT~7-miAXj2T?>mv1;O>}}^BzexA-`a?PO(`;oEj<40(arW-=X&VpK zu9Fs7az65t>x4i1Y=1X*7^L1Q0iEm-)%0Z5=L?PZPoMbRc{ttC#mIbp-~U1(9$wzW z^k|vh*XDxT@?*BLy0l~przz#^VXkV-}B^o zSx@8rI(DT)&rENpY&$>Ch~K5JcUN1ftde|Pu1E`9Hs&Pl3XT%RH~xp;=Gd!aTZ^V$x}tdP~6eYyJ^ zSFV(lyW=xiaQEfLg&y7~nie+;gU)GqGBd;S*4CE{N={E^W;mYe;MlzU%<1pX!`(g= zTe}@NrwrQNKPi9d>74spZ_2y0MB1Mf-R7xU|MYxz>M6UP^DR4P?)>2G249WPeZlRL znQh^ZHD^p@Rz8_;E2S51zJ8gmgNucHjpNdNr(YXq2Ym|7^k4ZSKWb~}4YSHOAp(MB zId_zEwBNw|f8%lf@2R_AY_kn1=}Zh+eEQnw8?~nO+8YYyzgGJR+FoV4a7|0={2eSx zpu;7KYxc&z{-&UmILqaF{w~hXpC;!oJ$&d6Q}b`$qkCT3H+K}Ij#N7YC?QK zD}0Z$zWu1_$#T_{g~jc93Q9Y6O*yt>qHy7j_Zsi+SIl_)+v}$FlZg2{FN-yFYC`P*r9LlEqIg;9SO95`lAXotG-Taebg4e8Pn_k zT#ytHG+XmWG`;CiOQ!O6{rioLjf|jzA<9YYoE$H^;6m5U(|(k`{T4IPuY96cq+{5z zz_3rnReygSt@!fgC9gVrx`5!Bvu7?Yh;uH<`R%)I(*KtsbNy5Q2A`}iFy=K@JMSpE zeN|IOR8+*G<^K6cT<%P&jPK1dEnf2-v77*u->)rsvij1zOwPy;D;{%tb9>*}|50Fh z-yC+q#De_`54}$6QLml!$3Fg^Yt@^ix<_mKCd(MIz1-;i;ri;|QnLE5?%ePCyxri> zgV2jlTTjY6^SdPaX3RkvwBU~X^m&)moqfLU0)lz5elAf>Pu%=p?x}eh`t-#HX^-C1 z4Rdn~x8&T55;*o%+e}$kk$bzIdQ-=Rm9gndU+GQVbUXarv)`Y!wSUf+f1FsOX7k!B z@5P(z*$hl&a*n6o9{2E`aOV3t-%rI>=blgcuNm&VKKbmWuZO-!e?Jx4qG{f>?umE3 z;ms{st54M~UlD7n`g7H`^e88>w7(|eE-i0gmnR=7w?7rzDxPekxk$tFpZxj96N(ab z{?*ioypNA#x_8XT>J+F=^!%}cihIw&`TD{pMsZj6?$`Rv^kbpB)tU9zFvPf6&Rh?#IW|62?h3+j4KH2Ky`h60hH_mNVU! z<;5PKyMF8DI!^h!rg>4VdfOB2Uvy*m*Pp1CSPFHet*|0uPm@{yGTHns{$K_WF*K~7V0^v3FJwD;L{o69P{1*~*C z*W0>H^HTXIkAGc9S5K{~)%;cGm2qW3=Z1fWreEGat#a>@^~?WgXiu6iww0&ehD$8g zL-Oh8e-8z1_H1;5diZo*_puMP;h_DB89aBZCGF?*J+#wz*8pvn^m0;4>@&??ID2;W zI-MUzzxlFKDtOlDu61--@%{G!#=Uz%z>(dfAT%hxMyJnBBRzO->!-t+1F>eC+k3}T_d^7oH_V&RS0i>}ySZeSEL36&`X;f&Fz;HGXgkWR8$uUy}SDZw6wP|c{RgIG6l`N0ynnmYL_vmmrT%s!3M0k92Na-_YLI z$Je7Jxj3*{?Z?O8r9p>tK|IgHX@^QCEDJr8ke1cAF7K|OSXJONi%;KT>z>!VPvg3C ztGsdAGO->lNpIe*T9EK~_~s(F!lXE1hcn=?`16r*(P@p~nj`Y^3$DC&y?0{weldHQ z#`{-AzyXo19CRV8)ZlxirPD;a^pokQY=usW@0Ly4lylKbKrpQC`{Eb5njF(=+(b3r zPRjqzyS^++?1toQ#^0@`_^~i(xqc>SATcO_jlaz z#CVlM=JY?`>J~0K&9zwO)^FLTcKYcjZa>%g>9cdWjdxx6_IDfBPu%Mfc5dfKZ)2UG zN8OXYOtfilUb%Yp;wn?^FJE6YSYPK8`z6uLo}w@H{oO~_i|4o7c-JkByzL?`>Sh@- zYisZCJ#!R6`=X7X&ikbEllQfG)6A8YZv4{S)#kx2`TaHc5u9GNdp|F-GL<|oEW8K(XD#O_^Z2O0>u+N!$$hw6UsRG;8iWPi3F>rv@~kw~E_)U&W;UGa7rmc$ozkc6ADZ2H%3Y5>D#ZZ+P-t zcv?+cU~I&vqf`H^**@{YwQEh&rm1bZ=f?F^K0fy7Bv#=IdpdiR}R&RSH?AMVojUg*`2wR3ot4#oXfHOjrU;g`06;I`a9 zZePAnP@S*27PQu(EaBvNbUT^pWsYyX^Nb#}G|XKKCe{w6bB>86egn|2zz@x5&2 zdppa?*O#}d%4$c+DSQ8u{k;|pxw*O-85s*^M|<;pf7e)5`gY;6Wo*pMwo8t-GtCUF zm{K6)!g4-NHs#ipQ%5Cl-HNLCtr3IEX62vtHalCg6RjUy+9GFSVp5=0(fr|7|LHUb zXJ_VxjoC}Lbz1C}-L>o1inkH5J6`|a`NUt+T)?H}aCNw6?(VXqIs$^*{_hF!dRAJp z<%8YhMFG=_euoPPvVDE6^XvEVN$01eJ$!7ZZ;ceb-SMA3JNx^yKkv((cI(`&Q#@~@ z%-v4CjZgjX<|?C0%aq&upK1Mkc-%bsyMFh_k5@xi*3Pro!spUrdOH17NBxC&qE0uz zbE`LY6zH7r|MI1k6r&(-z-)>X+?f1(V)&Do&FMnvf zMLcKC*_L>&Tidc){?%HY^sqBp42s?_U%!61{#koRX{vn($7QvO&VTndJ~3Vz`(wxX z(|kHVe}ZPZL&T@md{mL28Q8IN55G%`BJ*zMnSm8QALw*&Fx%XHV&&#{@_dza=QUla zh7N@yte>kAHry_A`6hc^1Kk83&|lOp`4-I0B!ScjK_6Q1!p(rF;dY z9p#z2Q$o$mS~gBp{8v@yx+?G5ZI3JEt2lQ{UE66_@%zU=PXWPSUvl(|=Ui5lE}Hsh z%Z~s7!M5t}4x!S;E8DBQg^|Nc?&PUchyG1n2;NKFntZ=2ASE?b^R02CYP{je`ftB8 z1q5fAt<#>O`y*>hfq8t9y4=k9?{=M^V-ulqTJz_m)~!}s^VL*etq(f<;MOuj&_1TQ zdY9Y8&c1SXc4l^Ic`xoK^tU$oWO|4rEYObJHy7wMzw9zyv@KCY`JTz$&#B=g>tg$oz$&)50c zqs1Zrz4B(Gs{Q;wyDWJMr%!ePovb4A_}o$#nY%@PE9bN8{Ot0yGur%qNqwF8-}B3- z87{rL}nT>A{wa^}B{ zk(^C9SWDK&hfhPVtj%T5&DGsgDWO+Y9q~zWVMz+12yp^W`a3drwG1%JQGh zs{0%E)PGjzTVtiSYli`kvi$FGxx@3{|6Bg$Jkv&%*AII|e>@KCc;U3l%Ow91i+8uy z48xPs|Et5Q_ABhIuVCR{9H#ZPYNEN$&!_xgBlpQve*7WYbnTzot~~}klkFX|{U45Pv^y;fpe#EEK@?aBcEI&W`m@N}iD>G3& zA3TNaQT+Ev?wj~hp7rxY`X4Yq@)Tp(Q!m;7zluv}mtJalKAWYWpIuf3p^gw`ykEA3t$L=G@wtk9X(AyV@RqmiJC!kSjKW=zmmwm&&-0qPFu$C?QB%{n&+y%zC-utoKLdTYL+c&S+8(1|J}2B zuU~Jz_N%#LL3%<*gLS{FHbJ z*qv(cH0l0==H=#JdV300K8jZL&SA*i+aoypPr{ep%+|D(Wqrk(GJ)+QPmw z7nYvyUwmSl;>r9CZ;NbhRq`~f3=N$T5wSQqKL1VMFWIfqyALdMob2=+Jmsxv$SD>&&7oAtVT%_~bo z1q9#Bne*^gwZf`vYj~BMuFl%J(O!*L@nk+INIE$7y8BPFOw0amr=YaPy71}#|I??v zXbj#b)FxnBUhHl=-E75zm7J!graCvJXC>QTg80dQ(Y=lL6GF_kyKGlneDP?u%9hfv z4lXlx7naXE9++~wce!$6$WflGtgIE!FR|rTeQ{M44h~r#%Wr0yy%97!>X&#J8jXRP zdME8`-m{v`>vNecSL^7qp>$36;qP$@Dwffou3o*mFgQ?Nn}6=aTkBSD=kMS+yf3EZ z!IJA7p6B*`zE}MvML>|vcW(EDC*~Yo^;~zm*bKI>)w{y&S=ZcP-M?_@;%=$Z*W9U= zs#X>+O|_)LnpT#E%Iuk|cO|my14{>otj*ryyM7b5wW_3dTL~WAw9B?E)?et<^mp8> zf(w27_f^WdPt1)jdC94CXpe7o>n2lg^7weKf}@xGC|e z)(7h{&l}5{mz#HREEd<9d@KCI-Si790vOFqvmbgq(q4I;>GAvplR7ndw8I5YsW_MY z>5CUTrQ%##Q?p`2894%3PI6`>iZuR{eRt+1$aUK;F#kdh*VacZ^CxZAZ`if6CCoanJY} zXKB>#YNuFdX6tqLCTpG6yf`Ud{7g>HL(^QnY3y@c0{#TgeEjr|-^Fd7F0n=~n(DvY zjum;?T`@E{vi+9F9 z&O~=*ZJpB+FI}SbYH{#?#mYL%fGxQ~f#)snxHavW6n|mc;nbUFLRS`QN?0b)nUK@BF;YPdsjv zS;`q-|I_65){65ypGVtcjUx5`pSO9s{QazNqOV3PF`7ba>Cq;_jTN}%l%)3T$vtO^13+ky=|4zv;Rd7%VLEt-WRI# zPP2Bs9VRqszow3q3(NF#wkfZ4@6>gG3%xEOL)HKFGx;Y-n904r%=h?lcY%U&S74ys z9**O`w;bLfZ^rk(dU}}7^=_l@;UXue->E#i?{}5^!?)SyWsa)C!55$YD4w-@=kv<# zJ1n*Zi!Js1#g$m5PVs+qbK{TS{X9$K^gk`zp=z(7 z$806~mG$I#P47=i+n#v8lQ|!;H*(b+)p*NE`)!pNmxbLdzIgk3Hpg|l=7iS)>d|Mi zuT7fO9p`jTzt8gd&gmb1ip-j{&{Rz)N(fMFmuw z{OOBr@^h_Sv3m95y6$Ztl|4RZFRrT2fALa!k>l%ct?!Db{fU`0?+Z)$dez6*T3KBF zmCa6^A*ApviRoROw_DYv%R+yv#l1JlxvoF@d&^)_ti&qBUnWQgi ztSc!NxVcWb^?P8HYJIg!)0HpXpMq|@o#L7~`OkzW<~OA0cI7#{9lo}ZZ%au@;9fu5 zFOtTgX%c-bCvSYwnr3F?oar$IzHA6k}4HNCTI-qpr-tCB~fWd7zS|EKfU*B+U= z_~MOEr3`)fw*8Bv&w<+vdw+8sHc)lA@h{tA*Hf9kT-*NmXRHcJCQ|bDH9Ft+*KSmK zeQ#zUcpTAUv4&&V#J-3CmQ^HP$hjwZG?5U91_g~2S zTN{`0uO}&|e?|xh+PL{@f!x6C(eX=m_H(%-OCQYk=&I|uvV*5B(M3h`m!h8R&d#&( zvIq7`YaadeU4L52QF(cVT|Gue-T!=K^x7Nu#JAAv-@h3qpj!3HF0H1?amf#+taosc zu=+ha=5@YBsjTj)GZdFRz`ns9N8k;#rM_ zNd}XyY%EA^wZHxv`^wO)+kErxT`)H~Ht|UK3)3Q|Enl^g-W-~mabw$J109#9CwZ@* zmnZkD>^n5&sr|yVzit=SW-$Eqa$9kEi|W3cQW~Xe?!0^S>ZSCf4|~Nw%rX-X)yZA3 zaw%_gyVZW1H-jkm5W%U~IMA%vYsdtBuHFczfiG(dVe|qne<##Gu;>}w-^-OsVKk@r; z%(y~eeq7FcMxCEL-ur5N{wE4|aMT_@@8D7NEh0(DX={|9QR#G98^+vNTLD2CX*1W= z@Af99WbxhYVq1}vsqC@u{j|RdN?+t`Bh%#9-u>OsvEkhj(H50-`vsB9Gra`_vrM!4 z`u?(U^Xq24ewA2xz%wzr^GUVuY;7LqnOm>P-+yP*G*4Nrq2og3g#w@0oq~-Y{r_3d z+p@-c-;o!R5{q`uwtls9izBPA?~0WxJ$t3kdw;V2Cf#%D*|ZmCDJHurdPVN_@02d* zE;XOm*Aggdykh0bh4Z|nx4gV4KGnx3aBrFIjVo)8PKxK7aAs=oOc zN*-O=*effd<262i{44iLN-Ajcgcgp%ZIJlEAWH6bfD^cCOhP0i{KE-xOuocp>=@#Oqt$c}qCn%X`=TeDHey_eFk_#V)K*VXFWx4fJU{pD#FNmrjOWQ6 z&u?TZDDlj)3fG7({gE@TT=8#T{Ip4bCOq*+Yt{JZta)%{kLk8K{Zk#4PDh_RuB7tw za`VESx<)Gf;5KY2FYk<~P}d80)1Brjcdp;??1k$}_T0~t{(#G|`hrW@8LwY)^2h#) z-@~lrwAt@&>a86K*$m!x!B6b;w@=z%|J3l#z8M;j-rvUO?;k(lKR)$e(JZ!2{ew9aY^HM;@NYuu^S$&zBt1;S0c*f z^uFwy7q?3Ot1XWGQnu;IX`_d`&jlTZL^FLp=wahVIU98Ja*zY3pkUELrX5FG5h`B`(j=9qN{#EAB^JqKnp<=&XljrHwKMQuB ze_))ix$0_>(7C|ni|nkv$}83UJdtbN`Q*9GDrLpxPrQ#Mo&R=w*6ZJpmYB#&kZ;;$ zcXV~P=XL1m@Eq1S_H%(o3wvDntLKec-k>qAQ%5Cd&5D08|N3R1D=W*u#LQ8A{X`=Rvz%FfvoE*I1bOUc-R)0% zC+Fv+DH^vfOEFY|wCpUOmY?qx5fF^p{e9x&1)?5Fi{-4~Ln9toC>>Ac4$0pi+ zJ9KWM-b`t`?bbip^Ot{~TeIv$ZJeH}{fYPHM`AsNz}o_Ptfx;ZsQ9b7v`%itt|#AL z-4kNXo^Q3%M?)CB9 zw;4-IOMjgEIMM3Zl8wv@7B6mHG9%$(U-hZC@k?L1#NV1-YhHeIjm`Pgg!|_dl zWPknow?k#iYA0N*Kl@?j2um2bO;i$Oihi?5 zaUa?Kn*B86?K{r7UB8UFgYr(6%0TVmf6F$!c=LT_G5^de?(OnPC)2y`JbxPW>2EFb#1dm)@TLeu zq5NeB@0$m#oX@^W=V$%?j)tvMC3|{!CVVvunh^M1Z*5}qRi<-aWk91_3dURu8?zTK zT*$av>e`37kDDbY?+LtlL*Uo1s*Zjhk#CmClg#)1Sj6;b9XrF!`E4woPi*as48mv5 zw=QP6a`o!RAOB9;oPT;@-c$vpjLgiWHy-{jbMDT2uP?AskUb$`LHnz#YaUo1XaAh` zd%>)=s-Qt^lZ4(W4fS>BCMC<>__z0povL%&y7{*wpCnHaKe>JVbpe+aQ+NGmzp4r@ zWzUETXBB+duAIo>SH;`T=Iij~<{PVx{Q>)3Z^x+GYy8vv@`d-|jSZSxO21xM`Cnk> zW1cyGmKK=XKVPG~_?bTQ#4n2+KY9FnlKOh-sp3<+{vW-qpmgJgwO8#;j*im~UvGU~ z5V)`@ATHu0zh9NE596%jV+;10%O5y*GV-Zv{mXp;CgsKLsiwQ%d=*`@>d?+q>oebO zPkX&;o2qBA`FET8t%0l&pFVqpg^88v^9sg>NwjIo?4I7HpsnqlIiGWt#?Q!n<%vbV z!+9ic*j7gznei{k$jjwN)in>tKlb}}aUGwvZADtP=ADo3X(I33<=duBQ~Om_B5>+n zdW?%{wD*?0iwvNcE4F3dk`Wf~bu8O5q&a)`v7omr1MhOpy=AWbp=R5f&#hCJt~5Ay z^TY{;zrRZkt<$~#sQ$d3_tm$TRx$^H25BkMvPVtdTwxFVX7h9H z-QNdpoMF0ECHnBmC9CVA`Kr!k9UfV7Rh;Ze@&~VN)a&2?xA#}uXPK7?-YS1^&3R#7 z-K6~$a4l2E!}>{H@S{e&!!pe&k_my&je*Nqas$#Dn zWmY~-YSN`sMgrdw~>VzM-CtEK23Mwb>dU1=w8 zZ{hOAO-r`TG{4RM>eZ`?j}LezNVcB%@W$}UZL>P%>z02%|B_nQ_Vxg17HVB-az;i* z%6-$VX3jZB7!{1U^5xo^J3id~!&x!6%&#l|Ss$Yyr*n0Yb$W_hV#9K29-c2P=?s_N zr=Bm|mvWG0sqEr4-v1s*?o*gF@28siHJQB{92=kLy*+k$V#dX#>OMMa5<-sh=@alk9E?vxhI;-r(Q{zClXP+nkIq*Y0 zgX-I9T^HdI|5FH0jUPgU?$YMo-f8RejUz zC(2^|{L2c}v)?*cR%Ql-^1|l!yep^vnG>Ax>)gkKN%uc;+}kfDAMfd%t}UK^rF@lU zsF>6aF_#wM_G>5K)E|AMsi0)Dr`pHupDNRzpJsv2%bQ1yqFu4pIa&$b1_;^Z|*42vZ(I5_;vL&4_p5Q zJ;_r_Po{VEAO0Snbn;%n8hb&-&$p(1-e}}Jp-Zbq_3CrweW(7NnYHQ9@|gJh#mcN% zSy=^nPqnJ#S7^cJdZvHU`dMjP@!N83%$$qsN|RS^`lC{Rby8-~qwX^re||r+in(pV zCg{ogbmD}sWT!`@3w>+Z}*pSzJeJn7G)t3j#l@An>f zF!S@n3kx_w{o9*&gx=cj-SF>8=*lWto4pJ3Q+kv;*I&qf!jkbi^vI1??N-;T;1O7x zlRdukl9wy0Sbm>51(aN-J1QsSK4-Yw#a57YPipeCUnhBjCvTs7loK)S7GHRBI?wgU zzkhjV{yMz3`h?HHotba12LuNnZn^k((|<*q4N+;kUkdMCxOj2vX7_qFLB)*kT{XAA ze!LnQvSPu+_tP2YFWkFe-MYT*{f9o zBYI^2VLh^`|Nq5Defq0^uWe=Mx5JN1XK!uFp6__*fB8qL$Q5N`>af%zKCR}c>i@^9 zR~Ky9?)o(=EXXc z?5104-a)@b*&0O^1>-p-y<(}Lx!i}2d zH7dR`$!rY#JpJU~gsjZTe;zEk{^G{eYw$wedcyx8yQb z=Wm-hvGIz?tjK)ji)YuW>YVXyNz~fD?oE4J8{4uzd6ljE>*L~^GewgH1l@SvX@=jH zQeTs2X$b)&NFQn{R|?bkD(gW6c7 z*|#12{n>y0sw!BO+y|ITF= zOuY2{^(R|%*V~a&zvh6hxSraiy1(Ru%1d)lv-Fe2RXfl|^{bk`lj>DIifUzK9Xhwt zao<;!)#CoqI|ZxrmNaNot0q_WO}sC~0U9B={yTn;F3;7OZ?}i8^7A_xzsX$0r6sXg z>{)dTiXHJW|LO!AJ(JlF?3~AZ<;oR-a$AQlW>eiFKFz$O2C7;@v#+_a`ugT@b)Vbp zXWjp%)G$oeu6x3hH?D`iXiF+;{975a7uNDmyw#)ECxV-IxgQl3B zod4l&zXGV$n^gM2a^>|tm3@bLuFhV#ZXI9n>s1z0KFBDZod534_OkcNrmHWk_<7nv zWv@nr<0N^J{~}et+6r=Rh}_y$_2No>htJ&e>DM!FE!6JdD6YS!ymh{nZ&X7^3+ri- zOhjC7Qu_J*e9y6V@rR2}Yb>s@v*ADf*EktAlkA>3`Oo3+@mxylmMvrZyz1(O1*O#% z+jH23xwQoZx0HU}VD@FvhO><~+&7f2;Z|zuY!$ur?N$G+-dG2h4cm$jG)D8c?qc<< z^Z2w}(b}a&wc0r~?tjcD^EtctoRfuDtXz3;Wo35Bhc9iFSF;q9cJNC_uR!E6&?-N< z$L-%U+g-$i+zH|*f~B5TiF=64P>@|0?7{pHh44du_DJocr`@D=rzUb)gr^~ttJ zZ@+c0Z2A7BsWq23l z>-(PRxTC&|=U>(T4H4_aQ{VNsu77i2Qv5&o8m1}WJwN|XI8>dnx?^w5_VGBof!3+N z#>o@pHpjtdp~d!XVur3xm?D01zU@ z_6tXDJ$>zJfB*dXPSyQ6hbEoZ^iDjP?&SqrAF{;8Yu~hKW_t7X%*wW%RudWV$y;CN z=bTL<;Cbx+-+SI1m^9!2)a)nK-{qN6_cykP%f{YyaQX4F-Ph=A z(d0i*qWKN0U&WZLxzAx`Wo2N#*&)9FpT+0bl?(lLhpjuL&nmP?|E}ZdXKNi@b`(C` z7VteU#mL0O#KGmpc2kFEKW4>eDA^ftt}K+?QTtV{t+LlexBb?Is~1~?=l+tg&jPJt zjLq6zC&!!+a#BKb`(?wd8xk*GzDnHqJ}7bieD8maZ`&6vUCO)r)wd2S`PuKQm#=sd zZ&N8M_io;X)p`87zPdAJM=tO_o@Y_@VM##x#q0MQJ05H=YCiMr{@?9U_g1N9i@a)X zy4Svm!`p7TPOIvCO-Om`-FrT%-u@TkEYmzb zC8x)2zJ?3VD{RTRCzX|zb>Zs8qYLjUf=*1G7tePf{e^^p;GSQ+2l>6PKe~5T8aIHs^XiCHNEndt0tp+1MKq;YYV_n9aJcyvyTX zzWX6ii170AZppvQ_}7MQ!8GUG6)RUd?q6i5bk0T5RFC=kp}eM!3s=|fK4Uk_B!^8` zH}~P6CvS0vg9d2X>&Q@;D52@;dLxJ0!UESt+5l(*b_?&-a=HCF`fC*D>p54xQ{@4BPI=CE~Al7-O zjcv}9mD%BU<(7q>2{?L0#D&Gw)KrH1kMFzh>o)M;I~LP-cwcVg4C7qAH>+-+_V{-d zG(G>}#XWb8;x&=nYEyk%{snB||5Po1N-5R%)BmdJeeQe?i6En~^? z9}nMu@9+Lv6?pAm#E(;aIzONG8>#+Ww{D%+-T2CBATK6vo68!Xb~@efq`j;VpOVvW zGuaKPcemN>to+Cq^!U@XKc=UzaX&X&naDr^0v6J8!UFP(f)9AE$fP9rc~P`%2uGt#jG1R(I`2@akeI(+s80+>UV? zNPL-Po~U_2TYlzA+E(EnVL8Wcjhw1%-!~tymE*_jT%8MeXCi=e6Q-ggD=~KF~V+Rkv<%J&m<4ZEoDhc#KD$y|lE{ z@s?`xC)w<4r%HYHWr`ZFEE7}tX_M2EhbwNf^NE`l`Xt8A^el_;pCR@A3tCJ z_DlXu=qKfKE{z=*&L=ah(OI)ZZjr8!{Dvpub}wJP+^}U!$gO`2KkY;0vCoem2DcFO z>!)a*+`fIgds5P(OW%%9ny=eE>E|2mkQEB5=fV3jRJ`ALEk9Pfi`h2qhs8o4rJvej z>!(>BL)*=9csYDG$E1ZW`-;W5Qb9Fe+7Al>LACzbYE!h8z$LO-qFM*X;}jmb{B$URRRW-*N3u(@j0+>a>{6x22SM-<*G2&%EyX!suQ1aPou+4DRmk zOW*QO`0xDn9_B>&3F|*S&{ofc?dPZ3%505L*`I!X-j3Se+rZxPjd)N7yZY~k~ zGNuOT%}koyVUT<&XQi2I+wlXpE=jqx{PH{Z;muY4KGW>Rj)2SO9Mk62xT@GIYDr$3 zcJ0E23kOy{DE_&xT=uckUFse)4g?{a=jr^`(u{`{r57E3@@Cd8P_)$-n$? z|MxY^uH;y)EW8<<;&rcCJ6$QYZ@mXNHZNYh_~FBc08nf$)&2K_;bQ6(O}j_^`~jeL z;uG0>r=WT8#O3El4Z%Jye}B)S=t&32=l?rj3a6j+sLP6Bdxw2{g~z?!Gnux3i(RmE zCF^3ZV}~xxzOv&D>)eu(5(k%{)mxU`@oQh`sJc=zsYpnAVnbq_GH6pvNl8h-_G!Y$ ze)_mLg=`A3byc-D)nlIhcUx1(gG*al&j`$%FWq;k@ zDckHDRObtZe|n0=tN&N8-*@QK)6-M8?f<0o)90nD(c|-{-q(rj)m_zQqzbxW>ciKs zp|}1qOsjE=IXS(&LYq7OW~1u{)xwSoi{g%HQpBN`yyt>xZ`?>pP9qo*h2*1v{H zlmFE1wLVnd$6xdO$Aj0A$`7yZ=iai#bmsao{>wne# zuh#0|mM+5%4&mcp&WC@B-k#CGI4|$$wSB4Go0jkVdw=t3EybVBY`lm5{r%0oCjI+a zyEl9Ox|BpFb{#u+gXf0+l=sFHfA7gr$e;iI=4SS0cK&IvZ%r^is`OLdz7BK)%#>~R z(`wvwq^{+&7zY>k$eD?K&s)}UPcGDD=A`vG2ZK`PyH5S1{JU;K<=djx|Sp+b{)IbA+jY(R_FTGgSByH6Skj!etv%c`+dLt)@3{26X|}!e6#$HUd)aK zQ))LpG46S_NarL!H+S$W{gz1`nm+t*oHn0JeMflhf7ZPV+WEt<~?_Ez|I0(*4!d(E@^TOWQav+27gCo}~94H0e{2 z8RfsC`sbIhlC1kY$K2XBY&3I@@8|Q_C#tijPB(}B%GIk6t&h9El9+squc5C`Y*JFw z#*~9B0)jz#|FuHZe_guwru9?UnNy!*thQu#X{ovWP~l*Hr{At;+gd5d=xFY1VQD|Ijuq6@ zl!~6{aoT;Ycj>(9875)>?1a5Py}z@w`QP8);t}~zS7z|sFOLMr!GyijH~c!IyK^1y zyU&{%_td_7`LeU6g=ObD-pb&dN%ap8v6j5L;;H#N>14XZRoBQ#?_a&@(hgszks5EQ zyzI&Uze-FXR~Z=z$VKEneZ)h93CyF1?y$sKZ4G$whkMeW8y(D`Ao!XU2AU78+*})Q z_L`8ClvI#&lhrOW=il3W11_)ATGk`o!Ml3DQ+t{3|Jh%&v?fTluK2wBq45^+4vynZ ze+taCR$TG6W0)~x#)F@qpZ~nJ^Yaf?DIIH-T^kHs7vH<&pkh<0d%YB#Dwm%(HZn@c z%hUUw-nK{6>?Auwa&q#XnxC6a{048La+RF)|I5qZ8M9`IRod@#_4g-Ot2bEWD#MLvPB;TLrQV z5*7std@>dR343?lmu;yp+4bVmA(6hb=5hxsGZ#2rXZyZ0OF4hO_;Kg$4u;b%&D``b99c3Q07@+AM> z9?9h6eYzXw#YgBIV~-P!jhS)C&`g2p_4&JDFK#`s3(tM>wpd?lm^SNQ2Gb#&qt8{I2k z57w@mrE%%`w{PDnK0b0?xUTAb-z??Bc6*VabAxVAQW z;EJjm<~!}?^{pFZZ)`7Jd1c0*O*%h!m%W`-z4i3H+5SnHKWs0mwoUAR zf9Tz!`})hfN@@${uNLe2vUB=$@o#T~?^PLD2EUoa&mblyR`KUY;ll0dph9q|#-#bv zrm;y&&vwe!_YZOW1X?k`UL75>K26nh$+LTBoiALv~IlOYP7z z_W767TiJ7~n_ugx)I+;)UXhod>;L~1*Nxs5a_Z~EXfKyf^}k-K`^+*~dExeEP(>Fc za#G$rPsS|wR>+IDPdl|h+wBC35=^gLy_y(*nCc! z5__h0$Y6SO_&3nLvf$(VlWyI&U%wvGP*G<4KIL~!Ud#<<>1^#f3k=giW1T!|>zNY5k2!N4a*s`uFpp z@~z*kN!H%+D^s)2Byag{-L>ED-{L8+n}6^5vN&VDe(Wxh&(F>VLw(G+Ap82dioa3& zb|%{MgA-os)IZbp;}=zbf7f~|Y~qS8Qm1-mL8f{fT`WZRI4&%%`fMO$tj_PY`B-V- z#zlHMx~mpA?_OrHr+lix9=EuY?rGl+@49Qfy=i7}*g~5$g5n{=QmnW@hG{umAsis4TPpZvWEf6`@xy9hK6UEQy!Z8@u_2(9FV*}0i$+F9a{Yh%{oP$@ z_dc1x3TxNLuU=_|?>*#ImzmbET-s-~#OX61g5@TLe|ow%j%)VG$?C~}e|_c3z5IT9 z@QdBLJk^zKVi#}LKG(F_KF_uO|DxW5&+Woxxo$ttzP9GzmzS5jUvi!F>-4l^D0z8_ zRo#DHNQHI%eFv4hKEj~YH-SAzq%%LvUw`9~K7)Xi6jyn9dFys&Fyxv-=e-oQ>B5Y7fS}>GsiS({BE~XG`D}J9BgM*W_cp(#l#|s}5A9x_~F96(`$ln zrJpBCpsWD}&$4;K)+@3EUC-vU0viE9wl0nMt$Spq&S-x?9 zU${43L+k9VV@eT1w&2Hy#GS8~tJxeg{9kafwKY-VD97C*zRd4W=G&PoC@}o}{ayde zaj%8O@A1~pA~GE*LvUiz^lha|9kDLzwv~w3eh}s+;d{lhX;;4 z5(>vF-bmQ*@wZmlHNoiA^VlqbOZjWHKS+J}^5w`v=k_UuH&0HTRAJD6_~De8Wj-^T zN?&vRTN4=OGWpMvB`PIvZUnyj)h{&v!I5p;i@LcF8+e^@dxkix%P!!y?XWI`~Cm59&eg#Ehi_( z!N;d}F}cV>=IQeb3!OiF`J!_%xl8RQL&3{Ss#~(IdcFH)_^`b2$@0sKPIDhN_*q=> zH}goG&xGIkyDL9G+fns3E78tv%A|F%yB{6t6lT5l`*p0;yZN%+<(ZB@KFn!3s!u>9Sf$S>AYt;^mVIMmAh zbk*K!`zu%JsH z+SPQi-1cG;KLaNxCs*gczq2)Oe0j_v?SnA9lfoh9AE39x?p?*<#mE8PFC{<7y>|HcdtlbMfr+ zieIJ|{P@lG=+(xIKcD;mJf1q+JYVmZ?4LO|_g0&?@klC_8CxthfOfEt=CRq;{W-Dd z{KFLr-@)_RUteE8zBYRMkvz8LB2SO?N@rYIG4aM( zflpsv1`7zr?Ws^K3$~E)ldJsngi|YIg~Bb_8c(mOPnd6RekUOf7_m$$dW-=r0I1=`ry2nY)+ml>yA+L(Mi=&QFx`{9%47CN^- zc=k-K%$Q+{)5+;aE?*W7UgjgXTT+J4-E*cz;UX`ccbjW}m-R>(I)O}a1ck(_!`oPo z9zNXs{NnQhufSzKGlL#oPI)#xNc$(lhP=C0A#0;T*R6bOA>;SbCnG~+Rp{zAUTL$A zyCOS{laKM}L~ZeSlUCqzriqo?ApM-o#@T@h*VaTfGcvO&n3;vWSyo{2)C@GN2Few! z>z;V7oL0l&;qUK$eO>IsmoHad;Mys}=YD5T%}=4d)!*B8@3x+2Q>j!|EOM^j{$EG_ z{=Z=$(@!+9a(msizxMO#wYAYJ!q?0F|8@3Z#{|#GY6~5k*+4^&H+(00uAE-u$tP>o zl6QAk;-MDKiZ3rF-Z=ZPz@kz`SC{wkv0mrxd3QngDsP+}yyVOr%gOEUrflYsG;&dW z_u%=o(%07x9y`WXS67#0-g;Q`bL8eUUL74BP%++lcZ$toqe=QFkM&9?rlz(&JlsD0 zRKBl_U!`wOj!qk&ESI#jbk40Uo^PIM=-%F*?|y$@t)Q%|ZdtI!(HYX_c?S+3UL4YY zaJo9^s@r9LbGy1kwUcgcNmgM+U`hR(7r&+ zlhad<_sJF%7d!v`^>t&_*Q~ftvkwa@g6=L}6}mcLeVlE{>ubEb%iap@_B=WXRFgS2 zvwisX&8@0x*Nw9eH4ORX?T##PY`(A}aIw)>*LQb!m(Q>+*NcgXF{t>EFvq4c=#5&T zh1P6gb-x3rPjl-=Z#%N5@-ygq*^av^I~Q%5qdXFfA#%vg|ndt1bY1jci7 zt<}pmTg)+(;F^Ff~2;=H}+pXPxUoZLg(ERiB-o-+$pkz{c5!HJ$q8 zY`cDbet!7Ytz98HC82X}ZcgV1-PC()Yj*OL6@fQ)m#_D-Q$PH0N{Eq>(T4Q%azB3j zIB@TtTnDGFbhMzfv~|`jsi^IFy$hY&lU`n0%E86u zR8nFhBqTIp-n_muXM8r!KK*dYu9UPiC0korPEO8_zCO2v1chljkxdJo+ZXxHHd9dA zl7CP^kff|8Oa%YS)CUb=j_@b9lu z7IyaJ=jY~r`0}M?*DkB7FE18;axNA*yE*;*gZJH2v{Z;zr)>iJ%&(1pE-&gza;X_7uclU^>D5v;%P}nOd zwIy~uc=d{_wzf7RD(cY1i-K};axbp0mj@Nv+1K@4+}yhUrpLbe^zpGf3oC2lyE{8S zeE8rnSgGxOwp-K8wy;o%WcQHwG!FZ=M}!-4DZ^}WvRd=Kx}{}=szNmF!Z^>;lc zCZ-J)AOGLB{=8$wslWa8>=SY&Tm&Z^oqB{f@BnXts9eN7GWDbvZ?S-!@aVDmLm()X&N1-}BE&16%*NBZpvJ3*uZwXE3lod0`p2U<9V4H6DC7{!OrZ2z-o zkIjyqI~A?1cb~cD2v&9CuTxl$J&}@9*!=5B~shjp5;!mzRJ1aF~C(=iOAXPnR!U0=Z!Jb#Jh_9lecX+`)!Thv z>r9?tpLfS1B_-v?7C&foDn)P0Ie4T~I5#SNX3L+B4vv)6)SC!db^m!TDJe_9dfz{P zeq8;g5Ljap>vaA2w*C9}`@Y^%35qo5oSZdhUn#dtXyq1na#2vSsQ8euF;!eAV!;&K zq9-0Rw`D&%(kUQ#XGh`UE2TL%HaKSHC(fNaS5Qi-%P{%agGWcZ&2mbAf@&Y7rm5QD zR=YG?1Re<_B`4?pDp+H*bCSN2+1h@Apq-jBpO*X1PTLq07bkb<(4lvyC&&v5Ha0S| zYn8pfC#$HaC?Lqf!t&tL)6-@-l|MmAIq2rb(z>)0>Mc{Mc@*!TKF!_O*tnzkdEd>V z^z-vtI~&(GpHEg6?FXl?XCCTzTb>(HBKI>?TYgSf?gW5 z{8<~l{m{*ul9!kJn@6vU+?3DqxVSjvHWS1Bn$Ns#{PKNmywZoxp5;Aw@F1v^>yx!k3(+$) z6x_FO-;Uzvd}-(AaO&&pgW}_>mG`RSGFi_1OmA<=yKB|K5m)`zbn^H7J39oGn#AL4 zF6!NO=TKAy6%*jFXqT_+V3)5EaCCH3b1vgathl-=G|l>FFsTxaZ%m*Gq3rGfqEu;O<@7bLY;z zxVV^INLcvdnn>de6RC>o>c@v#xxFHtD@^|=1??`&P2HPyEi%S-T7dAUwRe|wi|aeN z7$h8Em~8dv(IZeL7GMAODze{1_H?6uqQQ7^$%a@&?ZnM5Ve`RImimun|mPy+egn*8?GRsq4B zo10uCr_Gqbp{}m}qQt7^-_Pf6Pp_`oyrW8Iq1uzxH<#_$VNp?0At8JE{l4FPN}!7R zR_<>O#U(1BGI?s>A;tfvrfL@y6&>o4H10XICFiD4U|=9ag4vVymX-y{$NPS~+x^~0 z#vp;gg#}cv|NHkl-@@8@vEN*)AFtQ%pYr|w&SLiEesd4)|Nl2Xb*ADIaaq2i=jUX1 z?B2aNdV8K~-t48CCfv7|`^(4dD)Icfjp0BeGy8;z6B}=D&zD|nZftz{iFnJABQ6~r z*Ve^mU-C13t+P(fdj;putZgR+{%p^`zfLE5TaKg)%lp0G=iS}bFW}_W1kNuSGM^Y1 z6hA-5!N(WY_NM0l-|qr~I?>yB8ZuA(f4lv@7tf{E)BLhlB1%nFUtc9&T;ys|^TXiZ zpU?i2%OsutU5~HN{SdwbRPr3T^a zRV8Bs&09djD6_ZxjB~C_1T|&^56-i#4#__~RXcpblqoGyTeBQp7F^A` zxX_uMN6O?x(ux1?>;GqOse`x<9EO}H>JRkzEk6v(Y-`r&baY(Qcl>t4&WS^l6IAvr zU7T^^{MoauZi@q-_4RjmJ6BZf2-=r%Vt?6NDHoPzcK)!v@4ChHA8oxJ=luQMT}7pg zYinkH;&v(w&bVdtWPQX&CZ(n;S3*K>Pntfxz3lBRM;C+2Pbo4M1q}bc*Z*f%s9&>l z>4f-_>MQT;y}ydIZ&i$cYsPqBmT9(v(v7{<=FiU0&o2_aCx=vCi17YoxUezV{mQCP zZu`GqgiWM)#dM>V>`BY>*|&bZ{;Kfxc}8#4^!51#1qC^Hd0pe;-6u;Ok+i*iq~tmhvc3#?+ zpC4a|m8^}e?bL-%*YZw(eSKX~>BXg`-GM9i*8R15^!TyjB>Sf;8Xp~6z4f)uN;&Tc z=}%YlCO6KQAz@){?fm53Ug>XlP@H8Rb5LBcrL|R()vj3a|M&a#?wixjDkyiO(-`)G=Y^UfO zx6MrN;$EM1tx{jn-p1xmioJx7r)S_^UpAiaxg8vJKOVC0&X-qgnZg39ATK5AJz)+w zmZl%E;XuaDrRw+oy|4d&IJw_;na#{?j?HW{OtYtXM*RN!{r<*+hfXG0S2*U)oA=@C zS63e&9#GqBaeKi{qbIX3-@YxqapT4t8t*`e?pAbLCxs3|6gW;rYq4Z{}+*z2dj(Wx009iTJJMX7xdtjfzszmUU;KW;zKlC-47bEZg?wMz_w3?X!^C41GUt9CS!LyA<lcw`1GJ^Glvaw$;2c@UJPCiPB$tN%LH+g?p)NAKy=DV>2h04?eGo z7C-%7eIoyc3QufxX=u*$3w)-{SxznwUO(@4zR528@8`nr(|LYQJ^jWzeWHb?pr4D8{T2I8W=D$qCqMYWC?MD~&*0R<`SMluCnv=3oMTb!RCi$Q!|pBN=8C^BMLyj+ z`TqO^O-uU^-n=OprLS97|Ece2l~?t=j*hN|j)=e6jki9r-Gtd5TV2}P@nCCtylb-? zuYyv=|BPj;tWIU0*dMR^u4U@v*}b;&_p%E*mfX&0-#+zTywQ_zx%-ws%;wCU`|$1K zUgpT(y3=NVzhcwD(WcJ3J!09G=bfJfJ{1?;OboH zA6#r+`1zXR>qnVBbB$RW6&nkR%blCucsHb+O`81nPWh|kFH$$S9k(_G81GQ~#rv|| zf4@!7iE9%(I=VDI?ue*9ly!gS-it-q-&6ha%YT@1wdfx6*9Whjb%Ff$?xN)F zd-t=h)@{G~vG~h>ht$c6`#)KfoDt~Y`1kQkQ_^YK-`}{uNMGVlR_izXz3bLzm&DW2 zC(`G8$|^eBm3Evq*JMUuU75R8<<0R8 z@_S0+ICwZioa$a_uM`m6tlsmk_S50d(d=c4>gLkR>nu87Y`Z8R=&~)V@t^bW#lE}E zE@YT|`0&Nyn6>1oYF^oUS1f}SlsI@ek92Jjt*EoOzE=gir&x=MLrHB;2_t!|>%wJv0s+}Kpy9ds_}#C!WK_DoF7203Q}c;sv&Zpvqy zuMa)7ue!oK>&Nv5x5bXHcU-Gj5zqhQ*Uv?Xm)kgax%WPwFu(Mp2^-IAe!+uXTSQM4 znf@-i{%>wUae4FR&BnLN^ZNHW|5930C}ASSyQd`1&HQTQt@qok>iYi4ugC$V$85Ie z{jT=k-im(@>7Vu4CGfN}IOudiLHE{rdwWO1^L?@>PM+QGxw>rcEdjwj#V_9dIJ4P) zzT@MxUSF$|`u~bW)+R@5nwvg-@~k{5&PxBvs_4@hf1ZEL?>~6>Fmvy0)39l0tFz?h z%$>XO<<(xdw^e`lUVrfLVdJmFuT!ml@xA0$S69#2yXD9AdDnYSMN6#Yl$`E-ba}Urc}Rn{(&S1bw9y(oy2`Q{;oD?cY+&?ZU$U_4PuX*S9uv zyRb;dMXlHQ{pQZ{Q~!d6Kk43edG_d0Q)K^nlUw_OsH~tA@%>TM;!XX!l{`vK z%dhK(P7~dJKezPHioYo**2~QM^RGpS=lxc_?DN9Uwv>Z{;`CykpI6?=o%_k6r>Dok z%WeAV$1y+OrjCSDb2LwFT08T9Q-{LB>LyRy-QV7uaQ(F0S>FEU&8_{xk!cdEjNh-U zJhf?-qv=JhZK8G-+pfDfNBnzmBkswQXXO=2O~6x4J&nFyrzulX+Wvj@i>1*TDLh8RQ-3&@>x4-31oIJa?->-&4&~bU*T%TJrzP(br zs<_|6y7YBRb2{IYtoVIfin-@`#`dl0Ufz3cb-nG~Cs`4Nzwd?}@4w~W_UF{YeZlhD z`*+Q%zI*-r^_=~i9xs0D_Vmge^)s38C!DvNJk4G5)5^QGpKhuvD9x~|-*vIbdBOVo z3+7!~X+L?Fj8c=Uw)m{Ir>~#CuJJ_L>HC(-%TrQqf0eaw-n{wJ$-0m87ps zzna%_XSwwr^KW^^k^4;*?bXet`MD*!4dB;ThzxWcdQ`)aeur?atJKdpSX zxaH6D?Y8L~gUxc@@8!9Ea$kv9`zHRRZ^D}ul`<}@VBDlPFKn+q_wSd}+dn?~c6P?b z2Z2|r7tA{<9Cq<<-s#8gZ$TB?g%AHTGo4x*53@_eel)w|6K(QjwWa@z%NGxqg6a$f zXS=Pt*K)Pp6nWm)|G9qN+|MaSE-Wk8FPXJ=wWo{l;>n$fC;TRNsGT^eE3xYF%fyn) zmv6J*Z}Rxp8}NGjn$0T`D|!|lHopEdTKbcJyhUqe_p?QblMPa(x5h>BNL;)Bf~$Y& zg!qaTeFqOI))$^i>fi`lw%%m-d>b3voTA6MetBlQY_3QuHGR2$DdhcW|4saf2R8gW zH6xbapMOhFW@p~s-H|qlqIchKv#GmMXY?eTZ?pc~&~?}9e?5IDbh>}0&9R&l;l=X| zmM?a$OT3vLQI>K6?Gzn7@C=bM2q4zI96fyRSb!Eg!yBf3yBv z*O1v79UN_al1&lgS$llNX=-&FfhIUi|&+J^Jd`e@&SGvr^54C05SpuK%z4m$_1(-2Ek3 zC+;@?5w%;NKjrQA8^^@&Ror_eJWaP>e(QXmpHq*oof$oK^*QK;_J%n{$~aGv`Z`&{+#~YJ~Yis>(7Z_kG}0* z+XwOF>4}d&rMG_M{^0!Tv~buS?3eJ$4cPqkpg__)}{{mI^ccd8`8P&p9dr5yPF8cg@!nLZ*hfkign7zIA zVBtUhgZc;fLDl-X==K|ZhN^PP-j@%SzP&1{n;doM>RC~xrpoST)3V}^9X+nK|IC@X zBr~@jS;qTP`%Z;aem(pu`7C#~*Bwwb3C@LjoAu@$JFF}p_ABxIs(TrdmxU&-RbKe{ znxhM-A+_fIrgIt>rhl^u3~~2gc+zh2G)Zw%B3fLpW*Yq{%zW_6BF0`+TC8h zIQzWb3ds;*(?8h{4&LKWI_`ek@Xf@;H0OPZJyi)F3xeDOLCx#O%le%scc_7;Oy>S7 zHK=-&F}ZK;4!-wKzN=qY8Lhr!*Um%Bck$a*0-LcQIBR`d3s`BiP+*}{VnIBm;d<` zbgA>07_>Th*=-%?^4;$4L-7af;^N|9+1v2QOL3o|VTY;XrW@3}m{DYp&GW&ic!RxIi z*r4lPlAprg`X2kp?Jm3Tv*y;VueYyp4w}^h%JY6FsL;3svw(ZzqG5dOSnqvI=>&b^&I1#c=K|w-h`S)@@SDn^*!hGOBBXcFQ z9>h;+7KM*k*m$Kv)Jmsko?vJ2_xJZ(4xOY?to!rP{YA+xqt*M*IM>C+$=TT27XJG3 z(kPl8qWL?wy5F3J?c2*|u3LP@d0(SrGaG0^Xh-Gew3+L>Ax`^r4CJ&WOO_Z(FAA2} z-CDx{nlBO*7jKW-Ta|cvnyy9VCl$T;eL2%hA=7_J{zb*bg`b{y?)h+t`^C-8>PgAT zr=O@r7vC^_A`KeJQ}dnGa^i%?kKezQ&CJAFMq7FRzf+Sh~eXLR`GPp@G50 z%`NHD5>K=F?E6(@gg$jonm+ycg|AQ>yk+L*pLT`L3AWteqs}y<+syVqo5jy1CB0Ja zn15U7WAP$I=awL^mCZf=Y4$#vOZ!af{7i(NZ9Oz)OW}!q6JpaO_xL#fyT&Z}!e%w= zy%;0L>%qYcd*=mtX)c{|$?NS^-qjm(KUS2?6z!C4-#*QgPk-}-{a-JvmM~O5_t1JG zMA@fT7j7C>e4czgciz#{Do^JBeW+ul?rvSPJP>3`P}IG*Ce}jnaepo?tK8$`^l!ivW|=Nsr=0tvpo1ffPv*skx0>gYxy5GuwGlBraQpVd z+^yRyU%sqJKPA$WbD3@8)~thUffwBxUEmV-#RmsFF!u6~7hHcIIup)@{f83@cNtW*0 zCI)3`IomDeyCCpM*~!VkK+9`m_2p$8E5m#_OQwgf3%QrPe!~9hS0No7Mw2GJ*`%+t zcD+&Cp9FdN#pj&^j~_c~xo7RSFYkS48|-;dtgp1<_3Q3D-`R@_6BJCOcwJZyM{U)* z?pU9=K2AVzhlSB(fcem;8GR@`6FE8)l*kzvE^A7F zi!CfMky}sY?A&xA=>CBT{wqt3o&>*Q;8m4JnD+H`IK=jZ{?gzm!ITrruP};< ze_D9`@#~}Aib@&x7c_3_QHy2N4e#RcAvkMZTNR<+mU_}TYEDfRunj!RvjB*b^&)`a`Y zHZhT5CQrWST;qu{H=q3d-n}iHN=<@mqi5^H-`cK!>&j|&|L|~@-#-#cZf`pgxg$XH z^4iSc$%PO8$L@`){5ySXwxOw=+}Wn)g{8&LA#ZQZxV~2U-}c02PwO?B(RY6FDE^P~ z;+pp5W%G(#TOYoB)wRsgu_Ez`#_DgIZ%xSeoh>Y7+SDPzyL8viI@>vO=O`%s$av<-paWOK<3>FXj9xY+d3I%Flo2-WJa8s_$-ZUtN6|x0LHpbJLkNuk1eiFI6hX zjvNSIeRS>?lQVDbNXF&mxiP+fWPCos<@L1{k6(93U0s#vwAqF|i`=iN|7n59g?DA#I0xG)zKHEqEwC_0ZERSNQkJ z-Peijdbl=vaiRmmotPMbH;ZLOkG$EsLq<=a~vFE3E&;F!h6DjA!*bz4s1YvYzbb-Gas zOQKADdi&kyKRf&I)gvasgXcUad)+yo<+N{WZr!r_`VNkJa||3S-yh$crs&<`Yy}B!{ z)O73WYVG-lKl|IZ&boK+LWaqUD;pHswq><4zUG-^2M3#r+pI0t zdL|<8LRUrXvC214QejbA)Wf(v%yjohS2mfhHZCSha|C!1DhxU4jT3A~ybg!@wN=~|EpseW7?#s(0 zr`j6T+u6j%UbHX>h@8H3{t0!_mUrn~TVMM{%_=&&wO~hR@U|rnt$MSr9l7eXU+v9o zTWO`HiMh9??3jPGr6&7!&8zwUTRIZ5YW94(x4i0m+hjN9__bnlrOZO6?YB&1a$(W- zirBx^{NA#DiNsCyhnSdpnm#l>N$KEd+m;o&>hXLDVP^GSHmmaTntwNv-JY_$xH-9e zxc21asZH6MnjLFGxs?{R)V%n7c#r#3tsOh-_HKH!d-;5`%-NQoFI@)Z!aaBHgzz5@ z{Crb8FZ$}6&5yPv`}p*HV!d$uvM}@FMH@1I6x3|3*m(2%@2sr?f+bluF1)xtfBNyB z#tsELoi!o3GIzJ>y0Ana?+-t9*UQ)S_OGvsN9922$Csq*~ZmOm4+u3j+I;kj@7 zcS~p6gR~un7w&X^UEeGH@X?bNwZ=xZ;C~>?8n&DNDYd$qs=Jo|*KYO4i=6G=Em?YS zua{^^_N^^P{^sPK2*3QyIy3Xb^_Le+G(0pfZ!JuAxxNyVen1skeeNBO7w_3-8T;Es zzWKFwuG8iZqJfA1RaTynSiI}grOOLTi;aJMbvXOcUx#C9cK4rCC5bGS%|KRbJP zi{IP}76unyRMH@j`e zg@77^WVQwRu_wZV|2IBKNl{UK_-&c5+f$?E{_Pzf*U!GzQnU4V%-R3EOTX{t`8mbP z!TWIOr%RU$E1!DVSc6s(JalwCb#lWN#sAw&U$5bs;Qs79`oNbIbI-$;vccGiRO%Z)iJT+e#@yNjl$zqoTZH1=e8wTy1=>RYwTj(KZ` zJ&1pN?BPK*-zil>$w@tZu@n0HZ+>5=zvKGDxthTtTR)!o^HV@j=jG+)N4`x}d$M}v z{te;b>yI7Zoc8c#-QNoa3JXFMQ33 zN!O3znD}RQMY-?1oWR5W*Pr>V`OV#SBEqWE|K*LqoxfHrTeI}5WBvD(8e7$)OZcMO zYqsXrJ)P(d4v;60`<-1r9BbjU`2A$Ez)5+tg1rgPrno01d*?jg+fq}Ue2u#u5)$0^ z3wUe)F;8@1`7dRDE~k1+UY?txe3fmIv-85@^5gSYt~^ooh_izuO+_VQYi-+w@E>ha z#rq5snSPn&_syNGw$iR|+P2WQcE;&(RaSXRmuc;PHRJOa`~RH^^70)Vem4Jh6unvW z`if!nJ%eNRr8f*hbMIKy*|@Nrk1jl1mFcu^*01$zqFR^iu(+aCA=h#3EU&DT$$)sUDL%rTceq#A!*`1>Koi zb6DT?wz^-)RX1mWxO@BSJ5#-dKdoJBuWu{+R<43Usp+Jw^&^L(O`jAD4zFk2wr^7W zml@H&>zy-QCn(MjV$bkBG5_XHW0!aLo@D4f2`?-&lWfnjFcg!U{C@S-MZ3bf?YYyI z8_bwKudic*K+CV6qScSrCf>=e2$*oQaD8Lw>O?LTzmfc6*DzlhtKl;4JoZaSRc+$TacVp>b{#&(Ey>{YxMGp1;&v zQRzi~+Oav`Ih`ym`XnA0GZE zvfygW*}^Sb)@FU5sXCjD_2AW`tp6i-T}hHW{Qv94Ch2@JPdJnGjL6<_qvxG$&F6v}VEMrZfrX>B)ScR9Fx_~Fbyb=8`sUvC;eS)FVArF{Oe zsI!-a7vJ6%I!!vyV(q%Ci*o<|3agbnW%XpW<-ci7`EgadjBJx1-i%qde2K>Vor}O3 z=+*V`d5QsFR<2aFG_{jc`sv@J`-}TP{WLw-?xx6EE_=hUvxPc(PgdVdSFQag#m**m zmN7pg*eytCj)~NjuV0VeXZ@}z;Z*0e*v)P8>l77LN0$rR>S~{!Zk=WJP3uQfKtMWfbdwkZf_2PeiU07O}yjb7kdT@|{yjWV%9i6YduGiN-n_>Lq@+lQi zS##l%$Smf?i!Lm5WZofqhIRk03!bYpPTV)GG;(2?w{G2x3;S&4d_5fYN|}Ocx2>zg z&AEQ=Umx9`cJQEpU`xpAwuft@mGw+Slzs+>{0r0YsR=1?)m2hq;oemqe(n3`F40qa zuIs+Oa&X1AT){X4@zTi$92W0fadpbpEq2jue?BZw=)N2z#k6RanRwpzjt4s@Do=gd zrfKHN(p~j^+pEtBZ^5pc@b!qW+wbGEHY|GfLtMOO^SlZstMYP-OcRfcD;$TTwmKzT zT_qtR+q+6m?nTzKlWR4b*))GYZf~-@u=eZvUTMR1vDQ!j9<;N2ac_%e)YY}zy>85c z2M-4Zx`h{9l{pS=zx`jjRP*(P43ib{i`jOW<@KEFl~P=CVaAus|Nkt%xRHBh81FwT zSx}SG!YXQJThQ}&c_o|I&oY}={Yvx4%MwuYQ9`Wk$P9@q>jMv`tnkis-naFube_e# z>x)?D$r>LgIEj<_Q->)yL}9lX4z zl^Wg`C-Co;)9;mR{;)s+)Zd+T@7|1w6B=f}zOH=b{P`!H+yCF*z}R290&H~-TiR=SZ_gDkB>+3V_ z-npoFaP4s-HQtynNw6$74>apjfI2)t{!Ig zEG#_w4br_yKe2qOXIqVz=2F#P^_iJB?5nrk{CS&~f2Waoi>rN+iVMqZ_x`dUW_S3d zoc1lg?Wno;KX{xZYEk9Om!OVbm1&lQ?NKv*TiMH8a=${qtlU_if2(B~>wc5JK`A?g zP4%7amR<=1FJjs1bvWkd*Q_fC{^Z^L;BtQ6i->~5DfgB956KEhcDk8nJ01D8^CM{V z_DUH<{jJ!^>Wl9(^Do#H#x3vfzoq&C;~Ghu8yR;@zf^&`l#X_GGp;V>&J&&aXkyef z#rZ*5+NV{Xq=S}b2Z8-GCCF>-^=pd%r-sKh?)mfS^p4Wk)6QyvJ>aFe^h#B(eK&Sd;}UGS~?}n%=&C*=4PF`udhOL?(dV`lzv|B&yUCb zW~(h8ZrrwG*TE9MXvoOa%8k>_*SJh)H8eZ@NL(+*AuUbq$$>`ZAFo!g&k6fDWlQ0W zZ54YCmrH;~nlzU_y?cGWRJZ&5**Tka>i+$yl(DH0D0+28bB;~rr8Lp>_!Dm8d^Z<@ z-LS>Q`fT5kpYzY%*6EhFuaikXH>WXXf1PapzMsp^YOyuQwr@|$C|Yy4TncLR)q|Up zKU@>M{p>^T?QIXI$JaG79JsbN`s3^M`)`%7=F3&d&P_8rzF(~W@KoN_GW&$VgI}7Z zTc%%Mf8@1)uj}%eXFpzBo`1un{{D_a=G)tHnH&C0sDE-o5VWSfM3v#g)BgCRtX~x> zXR>>LSYDi<=%l&yisLc$&GO5p1i4Gut4qZ`+kSBIFHbYsMad7oyu55t^+lthX~O*3 zv%B|Jf4^1Z3K=98_&s6f%+Ax(^_ev|ezvuW>%|mAEC#Q(=TKbw`1kw$=DRZ1gO_|E|-p-+qt zPV4VKqCUSS=vgn!;J#*d{zrFq7N^AT7&o`954%=*gbJpQi6ZoI_udn>@z%l3cHeUNbADmZ8dq4teW8eGz z|K)CP%LRGcXW2;|zSYkA8l}zi9{l}&-#sl&&2PS4?w1r@h_NU37Ct`4JIAh8YUa$D zH+Gljul%&_)r5Qnb8~U`ez~LDa&K?Qy=^9AUnleY{QTps+~NgqZ<*fNRl0ghC}bcy z=;6-N*JA79_I7cL>pl4M^Rq?WpBVO10bZJCzfTR1JE*;WPt)7m+m(%th1vP# jj?6Sp&oT3Q_Mfr%cbdLYzQRKW1_lOCS3j3^P65Vttd6I4FP41fuyekECAj77Utjut66m2e2a/7EEIygwSNlK18+s3YKhK0ilzTmI71p5T4Sa85Hlubu5L7KD+cnvFgnxxRyOSdiCIth006EBoAdvlX6XkeSfxgBDMWRKJTnvBJPlB6iuF9CmJyOqoY0FpWiT5sTCkWUbC4kgWMEY3x91imh4/NQ/mRBJMwiCVpX8nUbEQo3DAXn5NkvmifrINRMsyqDsLwWoRRHRzIEKXHdRnlBa7o+W2T9ISvBqX3XXDV1pfXoyRrGhzwZOVeJeDP9M7fIvTr9Pbx38W/16Iu6yD9EkM+I6sFlxSMbkSb14813Aw+pRFpLyj1UG9zSIpyCQPwrJ1wxWAyxbFMhXNMc0KwSh/N9RbE1YkHNpumswzLixoeYE8jvqleHeyPRCJcV0RuiQFe+Zd6tYaY6FkCInzzZ4yrnk72eKArhdyAqEm85d775HkBwJMNbCjUezdrxDtdbtxuv3213V/lF4gV0JWL5iBADHksBFWdkjStE9Tyo8HGc1I2YeF4hLEzyi/d1KUENlAE+7gGHcLy7i7WIG7bwx3WaG7eZ5yrSsSmmnW50PEOxBFAfHikMtXBaPfyUELDj0yi83o+osKH2IOFZjbpiCHMuTjm075CkP+/3JdjtUk7rFT/ilxrz7iDgfy3ccQH679h/POjNivWfURKTaUfU+y+W9q2R2FhVFaduT4hsB1JHCHJXSVgakXTv59Gzxzs2xS74kVOcRV6b2PXRRgTQzYxwzYLpIZsBUMYNsQAVjW7o+Eubm50GACuUBhaZRceKYmg+zldMOQrFYfhQwDsGPQEnTsGALdk0B/yPMSazBlQbbKKSvOGX/Paqv22JTa+xIDt3TzaRhwcdslwNQMqLMDBwQI1+aMYffs1vYee6aAl+OrIQmKJ0Z+l1yBDSVzImFqqTCFyBiocgQ1ZAnJIqNKHMcxDJXhaoRn2NGkxDZq2I7aLz+EWxUcQVO220Iq480F450J1wS55X0YyC3cDnIETUEuB6T3JA2ez1G/fe+99VsOUMeMbst3nhC2PksV9+p065sqbizvKEdCe4/kLsiCOVlqT4RJOeCTrZoIKsy4atVEtmUKcTkM2iPeZ0QkZHTq+k9z7pXyE/yK8rv+DGjKujeV33Flr1Cp/LYxd1yOh76Q8o3vScyqzOMnYAG1ZMHYKgvloOjmkZ8/5FFQkM/Age23XQZMGSUox0cVBzcc6m01IULKx6fP8/nIbNiqSshpLROUQ6sRjUjFxJJy6WcgAnl+ayJMxbhQjrpETYo7p+tkxddq7VWp07lHjve2ewRVhXDHN1aVfbUI2CN8qDwKMJEoOx3kzcqgj+QoV1l1dU0FXlAOvLpRNf5VpdovwBsFvLkpwSFeZKssjgdnCOtKVTYsju/KSTXknDIeg3KV8Ko7nZ41C7bfggVlkGaMBTkq7vGjgtKitES7vNtlRti8HEafsnJlnuQkTGKxZee0PA2HPv+oeALAcav1ubGVBFQfMybNAvC9d5JAOcg+Xq/BePCgOfX/FkkzK4pioCLJAi7yiaYlvTGZkKtY0lWTyTflyyI5tuhAnPLH9qJkzQ/n5eEV4dMpCesW/qSDRkX/YzrbXjR4qKUz1uzXvPr8dcP2FdmwOg+7ONoebGpTTJ1V+LlyiETwr7N8/py6NV91Pgeoqq2nZlWOmsa9i+7g8RwZaM4qjBWlFBX+vqlsDnrDoz9/DlyvJQdWXVbVT4Lsyetaxsp6WML5+cRLWdPsnZZwQG+mS5RekzH6+hBexYPrvFDsJ++nielN5K9v6ek5HO2BJg/faTgOQJGwBoYcfCXY8gKjuUj8fmBDVwJbtaKfFG45KVfX5jXr+K/V5ge4Z64277bdfmIKdMWGiN7FLht0/gb9pF7Uerb+vo2G1+56+eXiG5jdRtP/FHvE70lKghXHGQKxe1Cv3muAETe02HKxavOlpdBjqKGGogRSrrKPHidlwqygrPwZq1YMP2YpC0OlDddUZ+en+9/jVm0Hv2pGl/8D \ No newline at end of file diff --git a/docs/_static/esp-ble-mesh-interface.png b/docs/_static/esp-ble-mesh-interface.png new file mode 100644 index 0000000000000000000000000000000000000000..c83c4222257d15e20c295c6730c3e3f28349cce7 GIT binary patch literal 55845 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 RV+IBW22WQ%mvv4FO#m|L#;*VX literal 0 HcmV?d00001 diff --git a/docs/_static/esp-ble-mesh-interface.xml b/docs/_static/esp-ble-mesh-interface.xml new file mode 100644 index 0000000000..3a708e7134 --- /dev/null +++ b/docs/_static/esp-ble-mesh-interface.xml @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/en/COPYRIGHT.rst b/docs/en/COPYRIGHT.rst index ce0a7f46bf..3589702cce 100644 --- a/docs/en/COPYRIGHT.rst +++ b/docs/en/COPYRIGHT.rst @@ -59,6 +59,8 @@ These third party libraries can be included into the application (firmware) prod * `mynewt-nimble`_ Apache Mynewt NimBLE, Copyright 2015-2018, The Apache Software Foundation, 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 ----------- diff --git a/docs/en/api-guides/esp-ble-mesh/ble-mesh-architecture.rst b/docs/en/api-guides/esp-ble-mesh/ble-mesh-architecture.rst new file mode 100644 index 0000000000..eedb341f3c --- /dev/null +++ b/docs/en/api-guides/esp-ble-mesh/ble-mesh-architecture.rst @@ -0,0 +1,395 @@ +ESP-BLE-MESH Architecture +========================= + +:link_to_translation:`zh_CN:[中文]` + +This document introduces ESP-BLE-MESH architecture overview, ESP-BLE-MESH architecture implementation as well as ESP-BLE-MESH auxiliary routines. + +- ESP-BLE-MESH Architecture Overview + + - Describes the five major parts of ESP-BLE-MESH architecture and the functionality of each part. + +- ESP-BLE-MESH Architecture Implementation + + - Describes the basic functions of ESP-BLE-MESH files, the correspondence between files and ESP-BLE-MESH architecture, and the interface for calling among files. + +- ESP-BLE-MESH Auxiliary Routines + + - Describe the auxiliary routines of ESP-BLE-MESH, such as Mesh network management, Mesh features, etc. + +1. ESP-BLE-MESH Architecture Overview +------------------------------------- + +Currently ESP-BLE-MESH has implemented most functions of Mesh Profile and all the Client Models defined in Mesh Model specification. Those missing functions/models are under development and will be provided soon. ESP-BLE-MESH architecture has been granted the official Bluetooth `certification `__. + +.. figure:: ../../../_static/esp-ble-mesh-architecture.png + :align: center + + Figure 1.1 ESP-BLE-MESH Architecture Diagram + +ESP-BLE-MESH architecture includes five key parts: + +- ``Mesh Protocol Stack`` + + - ``Mesh Networking`` is responsible for processing of messages of ESP-BLE-MESH nodes. + - ``Mesh Provisioning`` is responsible for provisioning flow of ESP-BLE-MESH devices. + - ``Mesh Models`` is responsible for the implementation of SIG-defined models. + +- ``Network Management`` + + - Implements several network management procedures, including node removal procedure, IV Index recovery procedure, etc. + +- ``Features`` + + - Include several ESP-BLE-MESH features, e.g. Low Power feature, Friend feature, Relay feature, etc. + +- ``Mesh Bearer Layer`` + + - Includes ``Advertising Bearer`` and ``GATT Bearer``. The bearer layer is crucial to ESP-BLE-MESH protocol stack which is built on Bluetooth Low-Energy technology, because the protocol stack must make use of the bearer layer to transmit data via the BLE advertising channel and connection channel. + +- ``Applications`` + + - Based on ESP-BLE-MESH protocol stack and ``Mesh Models``. + - By calling API and handling Event, ``Applications`` interact with ``Mesh Networking`` and ``Mesh Provisioning`` in ESP-BLE-MESH protocol stack, as well as a series of Models provided by ``Mesh Models``. + +1.1 Mesh Protocol Stack +----------------------- + +1.1.1 Mesh Networking +^^^^^^^^^^^^^^^^^^^^^ + +``Mesh Networking`` in the protocol stack architecture implements the following functions: + +- The communication between nodes in the Mesh network. +- Encryption and decryption of messages in the Mesh network. +- Management of Mesh network resources (Network Key, IV Index, etc.). +- Segmentation and reassembly of Mesh network messages. +- Model mapping of messages between different models. +- For more features, please see :doc:`ble-mesh-feature-list`. + +The implementation of ``Mesh Networking`` functions is based on hierarchy structure. Functions of each layer are shown in Table 1.1: + +.. list-table:: Table 1.1 Mesh Networking Architecture Description + :widths: 40 150 + :header-rows: 1 + + * - Layer + - Function + * - Access Layer + - Access Layer not only defines the format of application data, but also defines and controls the encryption and decryption of the data packets conducted by Upper Transport Layer. + * - Upper Transport Layer + - Upper Transport Layer encrypts, decrypts, and authenticates application data to and from the access layer; it also handles special messages called "transport control messages", including messages related to "friendship" and heartbeat messages. + * - Lower Transport Layer + - Lower Transport Layer handles segmentation and reassembly of PDU. + * - Network Layer + - Network Layer defines the address type and format of the network messages, and implements the relay function of the device. + +1.1.2 Mesh Provisioning +^^^^^^^^^^^^^^^^^^^^^^^ + +``Mesh Provisioning`` in the protocol stack architecture implements the following functions: + +- Provisioning of unprovisioned devices. +- Allocation of Mesh network resources (unicast address, IV Index, NetKey, etc.). +- Four authentication methods support during provisioning. +- For more features, please see :doc:`ble-mesh-feature-list`. + +The implementation of ``Mesh Provisioning`` functions is based on hierarchy structure. Functions of each layer are shown in Table 1.2: + +.. list-table:: Table 1.2 Mesh Provisioning Architecture Description + :widths: 40 150 + :header-rows: 1 + + * - Layer + - Function + * - Provisioning PDUs + - Provisioning PDUs from different layers are handled using provisioning protocol. + * - Generic Provisioning PDU/Proxy PDU + - The Provisioning PDUs are transmitted to an unprovisioned device using a Generic Provisioning layer or Proxy protocol layer. + * - PB-ADV/PB-GATT + - These layers define how the Provisioning PDUs are transmitted as transactions that can be segmented and reassembled. + * - Advertising/Provisioning Service + - The provisioning bearers define how sessions are established such that the transactions from the generic provisioning layer can be delivered to a single device. + +1.1.3 Mesh Models +^^^^^^^^^^^^^^^^^ + +``Mesh Models`` in the protocol stack architecture implements the following functions: + +- Configuration Client/Server Models +- Health Client/Server Models +- Generic Client/Server Models +- Sensor Client/Server Models +- Time and Scenes Client/Server Models +- Lighting Client/Server Models + +Functions of each layer are shown in Table 1.3: + +.. list-table:: Table 1.3 Mesh Models Architecture Description + :widths: 40 150 + :header-rows: 1 + + * - Layer + - Function + * - Model Layer + - Model Layer implements models used to standardize the operation of typical user scenarios, including Generic Client/Server Models, Sensor Client/Server Models, Time and Scenes Client/Server Models, Lighting Client/Server Models and several vendor models. + * - Foundation Model Layer + - Foundation Model Layer implements models related to ESP-BLE-MESH configuration, management, self diagnosis, etc. + +1.2 Mesh Network Management +--------------------------- + +``Network Management`` implements the following functions: + +- Node removal procedure is used to remove a node from the network. +- IV Index recovery procedure is used to recover a node's IV Index. +- IV update procedure is used to update the nodes' IV Index. +- Key refresh procedure is used to update the nodes' NetKey, AppKey, etc. +- Network creation procedure is used to create a mesh network. +- NVS storage is used to store node's networking information. + +1.3 Mesh Features +----------------- + +``Features`` includes the following options: + +- Low Power feature is used to reduce node's power consumption. +- Friend feature is used to store messages for Low Power nodes. +- Relay feature is used to relay/forward Network PDUs received by a node over the advertising bearer. +- Proxy Server/Client are two node roles in proxy protocol, which enable nodes to send and receive Network PDUs, mesh beacons, proxy configuration messages and Provisioning PDUs over a connection-oriented bearer. + +1.4 Mesh Bearer Layer +--------------------- + +``Bearers`` in the protocol stack architecture are responsible for passing of data between ESP-BLE-MESH protocol stack and Bluetooth Low Energy Core. + +``Bearers`` can be taken as a carrier layer based on Bluetooth Low Energy Core, which implements the function of receiving and transmitting data for the ESP-BLE-MESH protocol stack. + +.. list-table:: Table 1.3 Mesh Bearers Description + :widths: 40 150 + :header-rows: 1 + + * - Layer + - Function + * - GATT Bearer + - The GATT Bearer uses the Proxy protocol to transmit and receive ``Proxy PDUs`` between two devices over a GATT connection. + * - Advertising Bearer + - When using the Advertising Bearer, a mesh packet shall be sent in the Advertising Data of a ``Bluetooth Low Energy advertising PDU`` using the Mesh Message AD Type. + +1.5 Mesh Applications +--------------------- + +The ``Applications`` in the protocol stack architecture implement the corresponding functions by calling the API provided by the ESP-BLE-MESH protocol stack and processing the Event reported by the protocol stack. There are some common applications, such as gateway, lighting and etc. + +Interaction between application layer(``Applications``)and ``API / Event`` + +- Application layer calls API + + - Call the provisioning-related API for provisioning. + - Call the model-related API to send messages. + - Call the device-attributes-related API to get local information about the device. + +- Application layer processes Event + + The application layer is designed based on events, which take parameters to the application layer. Events are mainly divided into two categories. + + - The events completed by calling API. + - Such as nodes sending messages. + - The events that the protocol stack actively reports to the application layer. + - The Event that the protocol stack actively reports. + - The Event that Model actively reports. + +- The event is reported by the callback function registered by the application layer, and the callback function also contains the corresponding processing of the event. + +Interaction between ``API / Event`` and ESP-BLE-MESH protocol stack + +- API used by user mainly calls functions provided by ``Mesh Networking``, ``Mesh Provisioning`` and ``Mesh Models``. + +- The interaction between ``API / Event`` and the protocol stack does not operate across the hierarchy of the protocol stack. For example, API does not call functions related to ``Network Layer``. + +2. ESP-BLE-MESH Architecture Implementation +------------------------------------------- + +The design and implementation of ESP-BLE-MESH architecture is based on layers and modules. In details, Section 2.1 (Mesh Networking Implementation), Section 2.2 (Mesh Provisioning Implementation) and Section 2.3 (Mesh Bearers Implementation) are based on layers, and Section 2.4 (Mesh Models Implementation) is on modules. + +- **Layer-based Approach**: With Layer-based approach, the architecture is designed according to the layers specified in the Mesh Profile Specification. Each layer has its unique files which include APIs of this layer and etc. The specific design is shown in Figure 2.1. + +- **Module-based Approach**: Every file implements an independent function that can be called by other programs. + +.. figure:: ../../../_static/esp-ble-mesh-interface.png + :align: center + + Figure 2.1 ESP-BLE-MESH Architecture Implementation Diagram + +The design of ESP-BLE-MESH architecture uses layer-based approach. The sequence of layers which data packets are processed through is fixed, i.e., the processing of packets will form a ``message flow``. Thus, we could see flows of messages from the Protocol Stack Interface Diagram in Figure 2.1. + +2.1 Mesh Protocol Stack Implementation +-------------------------------------- + +2.1.1 Mesh Networking Implementation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The list of files and the functions implemented in each file in ``Mesh Networking`` are shown in Table 2.1: + +.. list-table:: Table 2.1 Mesh Networking File Description + :widths: 40 150 + :header-rows: 1 + + * - File + - Functionality + * - :component_file:`access.c ` + - ESP-BLE-MESH Access Layer + * - :component_file:`transport.c ` + - ESP-BLE-MESH Lower/Upper Transport Layer + * - :component_file:`net.c ` + - ESP-BLE-MESH Network Layer + * - :component_file:`adv.c ` + - A task used to send ESP-BLE-MESH advertising packets, a callback used to handle received advertising packets and APIs used to allocate adv buffers + +2.1.2 Mesh Provisioning Implementation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The implementation of Mesh Provisioning is divided into two chunks due to the Node/Provisioner coexistence. + +Specific files that provide implementation of provisioning of Node are shown in Table 2.2: + +.. list-table:: Table 2.2 Mesh Provisioning (Node) File Description + :widths: 40 150 + :header-rows: 1 + + * - File + - Functionality + * - :component_file:`prov.c ` + - ESP-BLE-MESH Node provisioning (PB-ADV & PB-GATT) + * - :component_file:`proxy.c ` + - ESP-BLE-MESH Proxy Server related functionalities + * - :component_file:`beacon.c ` + - APIs used to handle ESP-BLE-MESH Beacons + +Specific files that implement functions of Provisioner are shown in Table 2.3: + +.. list-table:: Table 2.3 Mesh Provisioning (Provisioner) File Description + :widths: 40 150 + :header-rows: 1 + + * - File + - Functionality + * - :component_file:`provisioner_prov.c ` + - ESP-BLE-MESH Provisioner provisioning (PB-ADV & PB-GATT) + * - :component_file:`provisioner_proxy.c ` + - ESP-BLE-MESH Proxy Client related functionalities + * - :component_file:`provisioner_beacon.c ` + - ESP-BLE-MESH Provisioner receives Unprovisioned Device Beacon + +2.1.3 Mesh Models Implementation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Mesh Models are used to implement the specific functions of model in nodes. Server model is used to maintain node status. Client model is used to obtain and modify node state. + +.. list-table:: Table 2.4 Mesh Models File Description + :widths: 40 150 + :header-rows: 1 + + * - File + - Functionality + * - :component_file:`cfg_cli.c ` + - Send Configuration Client messages and receive corresponding response messages + * - :component_file:`cfg_srv.c ` + - Receive Configuration Client messages and send proper response messages + * - :component_file:`health_cli.c ` + - Send Health Client messages and receive corresponding response messages + * - :component_file:`health_srv.c ` + - Receive Health Client messages and send proper response messages + * - :component_file:`client_common.c ` + - ESP-BLE-MESH model related operations + * - :component_file:`generic_client.c ` + - Send ESP-BLE-MESH Generic Client messages and receive corresponding response messages + * - :component_file:`lighting_client.c ` + - Send ESP-BLE-MESH Lighting Client messages and receive corresponding response messages + * - :component_file:`sensor_client.c ` + - Send ESP-BLE-MESH Sensor Client messages and receive corresponding response messages + * - :component_file:`time_scene_client.c ` + - Send ESP-BLE-MESH Time Scene Client messages and receive corresponding response messages + * - :component_file:`generic_server.c ` + - Receive ESP-BLE-MESH Generic Client messages and send corresponding response messages + * - :component_file:`lighting_server.c ` + - Receive ESP-BLE-MESH Lighting Client messages and send corresponding response messages + * - :component_file:`sensor_server.c ` + - Receive ESP-BLE-MESH Sensor Client messages and send corresponding response messages + * - :component_file:`time_scene_server.c ` + - Receive ESP-BLE-MESH Time Scene Client messages and send corresponding response messages + +2.2 Mesh Bearers Implementation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Portability is fully considered in the implementation of Mesh Bearers. When the ESP-BLE-MESH protocol stack is being ported to other platforms, users only need to modify :component_file:`mesh_bearer_adapt.c `. + +.. list-table:: Table 2.5 Mesh Bearers File Description + :widths: 40 150 + :header-rows: 1 + + * - File + - Functionality + * - :component_file:`mesh_bearer_adapt.c ` + - ESP-BLE-MESH Bearer Layer adapter,This file provides the interfaces used to receive and send ESP-BLE-MESH ADV & GATT related packets. + +.. note:: + + :component_file:`mesh_bearer_adapt.c ` is the implementation of ``Advertising Bearer`` and ``GATT Bearer`` in Mesh Networking framework. + +2.3 Mesh Applications Implementation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We have provided a series of application examples for customer development, and users can develop products based on :ref:`esp-ble-mesh-examples`. + +3. Auxiliary Routine +--------------------- + +Auxiliary routine refers to optional functions in the ESP-BLE-MESH protocol stack. The design of the auxiliary routine generally implement the truncation of code through :ref:`CONFIG_BLE_MESH`. + +3.1 Features +^^^^^^^^^^^^ + +- Low Power +- Friend +- Relay +- Proxy Client/Server + +3.2 Network Management +^^^^^^^^^^^^^^^^^^^^^^ + +- Node Removal procedure +- IV Index Recovery procedure +- IV Update procedure +- Key Refresh procedure +- Network Creation procedure +- NVS Storage + +3.3 Auxiliary Routine Implementation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When adopting the design of independent module, the two main factors should be considered: + +- The module can not be implemented hierarchically, and it can be completely independent, which means it does not rely on the implementation of other modules. +- The functions in the module will be used repeatedly, so it is reasonable to design it into a module. Independent module is shown in Table 3.1: + +.. list-table:: Table 3.1 Module File Description + :widths: 40 150 + :header-rows: 1 + + * - File + - Functionality + * - :component_file:`lpn.c ` + - ESP-BLE-MESH Low Power functionality + * - :component_file:`friend.c ` + - ESP-BLE-MESH Friend functionality + * - :component_file:`net.c ` + - ESP-BLE-MESH Relay feature, network creation, IV Update procedure, IV Index recovery procedure, Key Refresh procedure related functionalities + * - :component_file:`proxy.c ` + - ESP-BLE-MESH Proxy Server related functionalities + * - :component_file:`provisioner_proxy.c ` + - ESP-BLE-MESH Proxy Client related functionalities + * - :component_file:`settings.c ` + - ESP-BLE-MESH Node NVS storage functionality + * - :component_file:`mesh_main.c ` + - ESP-BLE-MESH node removal related functionality diff --git a/docs/en/api-guides/esp-ble-mesh/ble-mesh-faq.rst b/docs/en/api-guides/esp-ble-mesh/ble-mesh-faq.rst new file mode 100644 index 0000000000..6fef33dce7 --- /dev/null +++ b/docs/en/api-guides/esp-ble-mesh/ble-mesh-faq.rst @@ -0,0 +1,663 @@ +ESP-BLE-MESH FAQ +================ + +This document provides a summary of frequently asked questions about developing with ESP-BLE-MESH, and is divided into seven sections: + +* :ref:`ble-mesh-faq-provisioner-development` +* :ref:`ble-mesh-faq-node-development` +* :ref:`ble-mesh-faq-ble-mesh-and-wi-fi-coexistence` +* :ref:`ble-mesh-faq-fast-provisioning` +* :ref:`ble-mesh-faq-log-help` +* :ref:`ble-mesh-faq-example-help` +* :ref:`ble-mesh-faq-others` + +Users could refer to the sections for quick answer to their questions. This document will be updated based on the feedback collected via various channels. + + +.. _ble-mesh-faq-provisioner-development: + +1. Provisioner Development +-------------------------- + +Generally, a Provisioner is used to provision unprovisioned devices and form a mesh network. And after provisioning, roles of the unprovisioned devices will be changed to those of a node. + +1.1 What is the flow for an unprovisioned device to join ESP-BLE-MESH network? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + There are two phases for a device to join ESP-BLE-MESH network via a Provisioner, namely, provisioning and configuration. + + - The phase of provisioning is to assign unicast address, add NetKey and etc. to a device. By provisioning, the device joins the ESP-BLE-MESH network and its role is changed from an unprovisioned device to a node. + + - The phase of configuration is to add AppKeys to the node and bind AppKeys to corresponding models. And some items are optional during configuration, including adding subscription addresses to the node, set publication information, etc. By configuration, the node can actually transmit messages to a Provisioner and receive messages from it. + +1.2 If a Provisioner wants to change states of a node, what requirements should be met for a Provisioner? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Client model that corresponds to server model of the node is required. + - NetKey and AppKey used to encrypt messages shall be owned by both the node and the Provisioner. + - The address owned by the node shall be known, which could be its unicast address or subscription address. + +1.3 How can NetKey and AppKey be used? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - NetKey is used for encryption of messages in Network Layer. Nodes with the same NetKey are assumed to be in the same subnet while those with different NetKeys cannot communicate with each other. + - AppKey is used for encryption of messages in Upper Transport Layer. If client model and server model are bound to different AppKeys, the communication cannot be achieved. + +1.4 How to generate a NetKey or AppKey for Provisioner? Can we use a fixed NetKey or AppKey? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - The API :cpp:func:`esp_ble_mesh_provisioner_add_local_net_key` can be used to add a NetKey with a fixed or random value. + - The API :cpp:func:`esp_ble_mesh_provisioner_add_local_app_key` can be used to add an AppKey with a fixed or random value. + +1.5 Is the unicast address of Provisioner fixed? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The value of :code:`prov_unicast_addr` in :cpp:type:`esp_ble_mesh_prov_t` is used to set the unicast address of Provisioner, it can be set only once during initialization and can't be changed afterwards. + +1.6 Can the address of Provisioner serve as destination address of the node-reporting-status message? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The unicast address of Provisioner can be set only once during initialization and can't be changed afterwards. In theory, it can serve as the destination address of the node-reporting-status message, provided that the unicast address of the Provisioner is known by nodes. Nodes can know the unicast address of Provisioner during configuration since Provisioner sends messages to them with its unicast address used as the source address. + Subscription address can also be used. Provisioner subscribes to a group address or virtual address, and nodes send messages to the subscription address. + +1.7 Is the unicast address of the node that is firstly provisioned by Provisioner to ESP-BLE-MESH network fixed? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The value of :code:`prov_start_address` in :cpp:type:`esp_ble_mesh_prov_t` is used to set the starting address when the Provisioner provisions unprovisioned devices, i.e. the unicast address of the node it firstly provisioned. It can be set only once during initialization and can't be changed afterwards. + +1.8 Is the unicast address of the node that mobile App firstly provisioned fixed? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The App will decide the unicast address, and currently most of them are fixed. + +1.9 How to know which unprovisioned device is the Provisioner that is provisioning currently? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The value of :code:`prov_attention` in :cpp:type:`esp_ble_mesh_prov_t` is used by Provisioner set to unprovisioned device during provisioning. It can be set only once during initialization and can't be changed afterwards. When the unprovisioned device is joining the mesh network, it can display in a specific way like flashing light to notify Provisioner that it is being provisioned. + +1.10 How many ways to authenticate the devices during provisioning? Which way was used in the :example:`provided examples `? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + There are four authentication methods, i.e. No OOB, Static OOB, Output OOB and Input OOB. In the provided examples, No OOB is used. + +1.11 What information can be carried by the advertising packets of the unprovisioned device before provisioning into the network? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Device UUID + - OOB Info + - URL Hash (optional) + +1.12 Can such information be used for device identification? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + For example, each unprovisioned device contains a unique Device UUID, which can be used for device identification. + +1.13 How is the unicast address assigned when the node provisioned by Provisioner contains multiple elements? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Provisioner will assign an unicast address for the primary element of the node, and unicast address of the remaining elements are incremented one by one. + - For example: If an unprovisioned device has three elements, i.e. the primary element, the second element and the third element. After provisioning, the primary element address of the node is 0x0002 while the second element address is 0x0003, and the third element address is 0x0004. + +1.14 How can Provisioner get and parse the :ref:`Composition Data ` of nodes through Configuration Client Model? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Provisioner can get the Composition Data of nodes using the :ref:`Configuration Client Model ` API :cpp:func:`esp_ble_mesh_config_client_set_state` with :code:`comp_data_get` in the parameter :cpp:type:`esp_ble_mesh_cfg_client_get_state_t` set properly. + - Users can refer to the following code to parse the Composition Data: + + .. code:: c + + #include + #include + #include + + //test date: 0C001A0001000800030000010501000000800100001003103F002A00 + //0C00 1A00 0100 0800 0300 0001 05 01 0000 0080 0100 0010 0310 3F002A00 + + // CID is 0x000C + // PID is 0x001A + // VID is 0x0001 + // CRPL is 0x0008 + // Features is 0x0003 – Relay and Friend features. + // Loc is “front” – 0x0100 + // NumS is 5 + // NumV is 1 + // The Bluetooth SIG Models supported are: 0x0000, 0x8000, 0x0001, 0x1000, 0x1003 + // The Vendor Models supported are: Company Identifier 0x003F and Model Identifier 0x002A + + typedef struct { + int16_t cid; + int16_t pid; + int16_t vid; + int16_t crpl; + int16_t features; + int16_t all_models; + uint8_t sig_models; + uint8_t vnd_models; + } esp_ble_mesh_composition_head; + + typedef struct { + uint16_t model_id; + uint16_t vendor_id; + } tsModel; + + typedef struct { + // reserve space for up to 20 SIG models + uint16_t SIG_models[20]; + uint8_t numSIGModels; + + // reserve space for up to 4 vendor models + tsModel Vendor_models[4]; + uint8_t numVendorModels; + } esp_ble_mesh_composition_decode; + + int decode_comp_data(esp_ble_mesh_composition_head *head, esp_ble_mesh_composition_decode *data, uint8_t *mystr, int size) + { + int pos_sig_base; + int pos_vnd_base; + int i; + + memcpy(head, mystr, sizeof(*head)); + + if(size < sizeof(*head) + head->sig_models * 2 + head->vnd_models * 4) { + return -1; + } + + pos_sig_base = sizeof(*head) - 1; + + for(i = 1; i < head->sig_models * 2; i = i + 2) { + data->SIG_models[i/2] = mystr[i + pos_sig_base] | (mystr[i + pos_sig_base + 1] << 8); + printf("%d: %4.4x\n", i/2, data->SIG_models[i/2]); + } + + pos_vnd_base = head->sig_models * 2 + pos_sig_base; + + for(i = 1; i < head->vnd_models * 2; i = i + 2) { + data->Vendor_models[i/2].model_id = mystr[i + pos_vnd_base] | (mystr[i + pos_vnd_base + 1] << 8); + printf("%d: %4.4x\n", i/2, data->Vendor_models[i/2].model_id); + + data->Vendor_models[i/2].vendor_id = mystr[i + pos_vnd_base + 2] | (mystr[i + pos_vnd_base + 3] << 8); + printf("%d: %4.4x\n", i/2, data->Vendor_models[i/2].vendor_id); + } + + return 0; + } + + void app_main(void) + { + esp_ble_mesh_composition_head head = {0}; + esp_ble_mesh_composition_decode data = {0}; + uint8_t mystr[] = { 0x0C, 0x00, 0x1A, 0x00, + 0x01, 0x00, 0x08, 0x00, + 0x03, 0x00, 0x00, 0x01, + 0x05, 0x01, 0x00, 0x00, + 0x00, 0x80, 0x01, 0x00, + 0x00, 0x10, 0x03, 0x10, + 0x3F, 0x00, 0x2A, 0x00}; + int ret; + + ret = decode_comp_data(&head, &data, mystr, sizeof(mystr)); + if (ret == -1) { + printf("decode_comp_data error"); + } + } + +1.15 How can Provisioner further configure nodes through obtained Composition Data? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Provisioner do the following configuration by calling the :ref:`Configuration Client Model ` API :cpp:func:`esp_ble_mesh_config_client_set_state`. + + - Add AppKey to nodes with :code:`app_key_add` in the parameter :cpp:type:`esp_ble_mesh_cfg_client_set_state_t` set properly. + - Add subscription address to the models of nodes with :code:`model_sub_add` in the parameter :cpp:type:`esp_ble_mesh_cfg_client_set_state_t` set properly. + - Set publication information to the models of nodes with :code:`model_pub_set` in the parameter :cpp:type:`esp_ble_mesh_cfg_client_set_state_t` set properly. + +1.16 Can nodes add corresponding configurations for themselves? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This method can be used in special cases like testing period. + + - Here is an example to show nodes add new group addresses for their models. + + .. code:: c + + 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 addresses, element index: %d", __func__, i); + } + } + + return ESP_OK; + } + +.. note:: + + When the NVS storage of the node is enabled, group address added and AppKey bound by this method will not be saved in the NVS when the device is powered off currently. These configuration information can only be saved if they are configured by Configuration Client Model. + +1.17 How does Provisioner control nodes by grouping? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Generally there are two approaches to implement group control in ESP-BLE-MESH network, group address approach and virtual address approach. And supposing there are 10 devices, i.e., five devices with blue lights and five devices with red lights. + + - Method 1: 5 blue lights can subscribe to a group address, 5 red lights subscribe to another one. By sending messages to different group addresses, Provisioner can realize group control. + + - Method 2: 5 blue lights can subscribe to a virtual address, 5 red lights subscribe to another one. By sending messages to different virtual addresses, Provisioner can realize group control. + +1.18 How does Provisioner add nodes to multiple subnets? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Provisioner can add multiple NetKeys to nodes during configuration, and nodes sharing the same NetKey belong to the same subnet. Provisioner can communicate with nodes on different subnets by using different NetKeys. + +1.19 How does Provisioner know if a node in the mesh network is offline? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Node offline is usually defined as: the condition that the node cannot be properly communicated with other nodes in the mesh network due to power failure or some other reasons. + + There is no connection between nodes and nodes in the ESP-BLE-MESH network. They communicate with each other through advertising channels. + + An example is given here to show how to detect a node is offline by Provisioner. + + - The node can periodically send heartbeat messages to Provisioner. And if Provisioner failed to receive heartbeat messages in a certain period, the node is considered to be offline. + +.. note:: + + The heartbeat message should be designed into a single package (less than 11 bytes), so the transmission and reception of it can be more efficient. + +1.20 What operations should be performed when Provisioner removes nodes from the network? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Usually when Provisioner tries to remove node from the mesh network, the procedure includes three main steps: + + - Firstly, Provisioner adds the node that need to be removed to the "blacklist". + + - Secondly, Provisioner performs the :ref:`Key Refresh procedure `. + + - Lastly, the node performs node reset procedure, and switches itself to an unprovisioned device. + +1.21 In the Key Refresh procedure, how does Provisioner update the Netkey owned by nodes? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Provisioner updates the NetKey of nodes using the :ref:`Configuration Client Model ` API :cpp:func:`esp_ble_mesh_config_client_set_state` with :code:`net_key_update` in the parameter :cpp:type:`esp_ble_mesh_cfg_client_set_state_t` set properly. + + - Provisioner updates the AppKey of nodes using the :ref:`Configuration Client Model ` API :cpp:func:`esp_ble_mesh_config_client_set_state` with :code:`app_key_update` in the parameter :cpp:type:`esp_ble_mesh_cfg_client_set_state_t` set properly. + +1.22 How does Provisioner manage nodes in the mesh network? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + ESP-BLE-MESH implements several functions related to basic node management in the example, such as :cpp:func:`esp_ble_mesh_store_node_info`. And ESP-BLE-MESH also provides the API :cpp:func:`esp_ble_mesh_provisioner_set_node_name` which can be used to set the node's local name and the API :cpp:func:`esp_ble_mesh_provisioner_get_node_name` which can be used to get the node's local name. + +1.23 What does Provisioner need when trying to control the server model of nodes? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Provisioner must include corresponding client model before controlling the server model of nodes. + + Provisioner shall add its local NetKey and AppKey. + + - Provisioner add NetKey by calling the API :cpp:func:`esp_ble_mesh_provisioner_add_local_net_key`. + + - Provisioner add AppKey by calling the API :cpp:func:`esp_ble_mesh_provisioner_add_local_app_key`. + + Provisioner shall configure its own client model. + + - Provisioner bind AppKey to its own client model by calling the API :cpp:func:`esp_ble_mesh_provisioner_bind_app_key_to_local_model`. + +1.24 How does Provisoner control the server model of nodes? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + ESP-BLE-MESH supports all SIG-defined client models. Provisioner can use these client models to control the server models of nodes. And the client models are divided into 6 categories with each category has the corresponding functions. + + - Configuration Client Model + + - The API :cpp:func:`esp_ble_mesh_config_client_get_state` can be used to get the :cpp:type:`esp_ble_mesh_cfg_client_get_state_t` values of Configuration Server Model. + - The API :cpp:func:`esp_ble_mesh_config_client_set_state` can be used to set the :cpp:type:`esp_ble_mesh_cfg_client_set_state_t` values of Configuration Server Model. + + - Health Client Model + + - The API :cpp:func:`esp_ble_mesh_health_client_get_state` can be used to get the :cpp:type:`esp_ble_mesh_health_client_get_state_t` values of Health Server Model. + - The API :cpp:func:`esp_ble_mesh_health_client_set_state` can be used to set the :cpp:type:`esp_ble_mesh_health_client_set_state_t` values of Health Server Model. + + - Generic Client Models + + - The API :cpp:func:`esp_ble_mesh_generic_client_get_state` can be used to get the :cpp:type:`esp_ble_mesh_generic_client_get_state_t` values of Generic Server Models. + - The API :cpp:func:`esp_ble_mesh_generic_client_set_state` can be used to set the :cpp:type:`esp_ble_mesh_generic_client_set_state_t` values of Generic Server Models. + + - Lighting Client Models + + - The API :cpp:func:`esp_ble_mesh_light_client_get_state` can be used to get the :cpp:type:`esp_ble_mesh_light_client_get_state_t` values of Lighting Server Models. + - The API :cpp:func:`esp_ble_mesh_light_client_set_state` can be used to set the :cpp:type:`esp_ble_mesh_light_client_set_state_t` values of Lighting Server Models. + + - Sensor Client Models + + - The API :cpp:func:`esp_ble_mesh_sensor_client_get_state` can be used to get the :cpp:type:`esp_ble_mesh_sensor_client_get_state_t` values of Sensor Server Model. + - The API :cpp:func:`esp_ble_mesh_sensor_client_set_state` can be used to set the :cpp:type:`esp_ble_mesh_sensor_client_set_state_t` values of Sensor Server Model. + + - Time and Scenes Client Models + - The API :cpp:func:`esp_ble_mesh_time_scene_client_get_state` can be used to get the :cpp:type:`esp_ble_mesh_time_scene_client_get_state_t` values of Time and Scenes Server Models. + - The API :cpp:func:`esp_ble_mesh_time_scene_client_set_state` can be used to set the :cpp:type:`esp_ble_mesh_time_scene_client_set_state_t` values of Time and Scenes Server Models. + + +.. _ble-mesh-faq-node-development: + +2. Node Development +------------------- + +2.1 What kind of models are included by nodes? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - In ESP-BLE-MESH, nodes are all composed of a series of models with each model implements some functions of the node. + + - Model has two types, client model and server model. Client model can get and set the states of server model. + + - Model can also be divided into SIG model and vendor model. All behaviors of SIG models are officially defined while behaviors of vendor models are defined by users. + +2.2 Is the format of messages corresponding to each model fixed? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Messages, which consist of opcode and payload, are divided by opcode. + + - The type and the format of the messages corresponding to models are both fixed, which means the messages transmitted between models are fixed. + +2.3 Which functions can be used to send messages with the models of nodes? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - For client models, users can use the API :cpp:func:`esp_ble_mesh_client_model_send_msg` to send messages. + + - For server models, users can use the API :cpp:func:`esp_ble_mesh_server_model_send_msg` to send messages. + + - For publication, users call the API :cpp:func:`esp_ble_mesh_model_publish` to publish messages. + +2.4 How to achieve the transmission of messages without packet loss? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Acknowledegd message is needed if users want to transmit messages without packet loss. The default time to wait for corresponding response is set in :ref:`CONFIG_BLE_MESH_CLIENT_MSG_TIMEOUT`. If the sender waits for the response until the timer expires, the corresponding timeout event would be triggered. + +.. note:: + + Response timeout can be set in the API :cpp:func:`esp_ble_mesh_client_model_send_msg`. The default value (4 seconds) would be applied if the parameter :code:`msg_timeout` is set to **0**. + +2.5 How to send unacknowledged messages? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + For client models, users can use the API :cpp:func:`esp_ble_mesh_client_model_send_msg` with the parameter :code:`need_rsp` set to :code:`false` to send unacknowledged messages. + + For server models, the messages sent by using the API :cpp:func:`esp_ble_mesh_server_model_send_msg` are always unacknowledged messages. + +2.6 How to add subscription address to models? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Subscription address can be added through Configuration Client Model. + +2.7 What is the difference between messages sent and published by models? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Messages sent by calling the API :cpp:func:`esp_ble_mesh_client_model_send_msg` or :cpp:func:`esp_ble_mesh_server_model_send_msg` will be sent in the duration determined by the Network Transmit state. + + Messages published by calling the API :cpp:func:`esp_ble_mesh_model_publish` will be published determined by the Model Publication state. And the publication of messages is generally periodic or with a fixed number of counts. The publication period and publication count are controlled by the Model Publication state, and can be configured through Configuration Client Model. + +2.8 How many bytes can be carried when sending unsegmented messages? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The total payload length (which can be set by users) of unsegmented message is 11 octets, so if the opcode of the message is 2 octets, then the message can carry 9-octets of valid information. For vendor messages, due to the 3-octets opcode, the remaining payload length is 8 octets. + +2.9 When should the :ref:`Relay ` feature of nodes be enabled? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Users can enable the Relay feature of all nodes when nodes detected in the mesh network are sparse. + + For dense mesh network, users can choose to just enable the Relay feature of several nodes. + + And users can enable the Relay feature by default if the mesh network size is unknown. + +2.10 When should the :ref:`Proxy ` feature of node be enabled? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + If the unprovisioned device is expected to be provisioned by a phone, then it should enable the Proxy feature since almost all the phones do not support sending ESP-BLE-MESH packets through advertising bearer currently. And after the unprovisioned device is provisioned successfully and becoming a Proxy node, it will communicate with the phone using GATT bearer and using advertising bearer to communicate with other nodes in the mesh network. + +2.11 How to use the Proxy filter? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The Proxy filter is used to reduce the number of Network PDUs exchanged between a Proxy Client (e.g. the phone) and a Proxy Server (e.g. the node). And with the Proxy filter, Proxy Client can explicitly request to receive only mesh messages with certain destination addresses from Proxy Server. + +2.12 When a message can be relayed by a Relay node? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + If a message need to be relayed, the following conditions should be met. + + - The message is in the mesh network. + + - The message is not sent to the unicast address of the node. + + - The value of TTL in the message is greater than 1. + +2.13 If a message is segmented into several segments, should the other Relay nodes just relay when one of these segments is received or wait until the message is received completely? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Relay nodes will forward segments when one of them are received rather than keeping waiting until all the segments are received. + +2.14 What is the principle of reducing power consumption using :ref:`Low Power ` feature? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - When the radio is turned on for listening, the device is consuming energy. When low power feature of the node is enabled, it will turn off its radio in the most of the time. + + - And cooperation is needed between low power node and friend node, thus low power node can receive messages at an appropriate or lower frequency without the need to keep listening. + + - When there are some new messages for low power node, its friend node will store the messages for it. And low power node can poll friend nodes to see if there are new messages at a fixed interval. + +2.15 How to continue the communication on the network after powering-down and powering-up again? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Enable the configuration :code:`Store ESP-BLE-MESH Node configuration persistently` in `menuconfig`. + +2.16 How to send out the self-test results of nodes? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + It is recommended that nodes can publish its self-test results periodically through Health Server Model. + +2.17 How to transmit information between nodes? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + One possible application scenario for transmitting information between nodes is that spray nodes would be triggered once smoke alarm detected high smoke concentration. There are two approaches in implementation. + + - Approach 1 is that spray node subscribes to a group address. When smoke alarm detects high smoke concentration, it will publish a message whose destination address is the group address which has been subscribed by spray node. + + - Approach 2 is that Provisioner can configure the unicast address of spray node to the smoke alarm. When high smoke concentration is detected, smoke alarm can use send messages to the spray node with the spray node's unicast address as the destination address. + +2.18 Is gateway a must for nodes communication? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - Situation 1: nodes only communicate within the mesh network. In this situation, no gateway is need. ESP-BLE-MESH network is a flooded network, messages in the network have no fixed paths, and nodes can communicate with each other freely. + + - Situation 2: if users want to control the nodes remotely, for example turn on some nodes before getting home, then a gateway is needed. + +2.19 When will the IV Update procedure be performed? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + IV Update procedure would be performed once sequence number of messages sent detected by the bottom layer of node reached a critical value. + +2.20 How to perform IV Update procedure? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Nodes can perform IV Update procedure with Secure Network Beacon. + + +.. _ble-mesh-faq-ble-mesh-and-wi-fi-coexistence: + +3. ESP-BLE-MESH and Wi-Fi Coexistence +------------------------------------- + +3.1 Which modes does Wi-Fi support when it coexists with ESP-BLE-MESH? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Currently only Wi-Fi station mode supports the coexistence. + +3.2 Why is the Wi-Fi throughput so low when Wi-Fi and ESP-BLE-MESH coexist? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The `ESP32-DevKitC <../../hw-reference/get-started-devkitc>`_ board without PSRAM can run properly but the throughput of it is low since it has no PSRAM. When Bluetooth and Wi-Fi coexist, the throughput of ESP32-DevKitC with PSRAM can be stabilized to more than 1Mbps. + + And some configurations in menuconfig shall be enabled to support PSRAM. + + - :code:`ESP32-specific --> Support for external,SPI-connected RAM --> Try to allocate memories of Wi-Fi and LWIP...` + - :code:`Bluetooth --> Bluedroid Enable --> BT/BLE will first malloc the memory from the PSRAM` + - :code:`Bluetooth --> Bluedroid Enable --> Use dynamic memory allocation in BT/BLE stack.` + - :code:`Bluetooth --> Bluetooth controller --> BLE full scan feature supported.` + - :code:`Wi-Fi --> Software controls Wi-Fi/Bluetooth coexistence --> Wi-Fi` + + +.. _ble-mesh-faq-fast-provisioning: + +4. Fast Provisioning +-------------------- + +4.1 Why is fast provisioning needed? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Normally when they are several unprovisioned devices, users can provision them one by one. But when it comes to a large number of unprovisioned devices (e.g. 100), provisioning them one by one will take huge amount of time. With fast provisioning, users can provision 100 unprovisioned devices in about 50 seconds. + +4.2 Why EspBleMesh App would wait for a long time or keep waiting during fast provisioning? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + After the App provisioned one Proxy node, it will disconnect from the App during fast provisioning, and reconnect with the App when all the nodes are provisioned. + +4.3 Why is the number of node addresses displayed in the App is more than that of existing node addresses? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Each time after a fast provisioning process, and before starting a new one, the node addresses in the App should be cleared, otherwise the number of the node address will be incorrect. + +4.4 What is the usage of the **count** value which was input in EspBleMesh App? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The **count** value is provided to the Proxy node which is provisioned by the App so as to determine when to start Proxy advertising in advance. + +4.5 When will Configuration Client Model of the node running :example:`ble_mesh_fast_prov_server ` example start to work? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Configuration Client Model will start to work after the Temporary Provisioner functionality is enabled. + +4.6 Will the Temporary Provisioner functionality be enabled all the time? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + After the nodes receive messages used to turn on/off lights, all the nodes will disable its Temporary Provisioner functionality and become nodes. + + +.. _ble-mesh-faq-log-help: + +5. Log Help +----------- + +You can find meaning of errors or warnings when they appear at the bottom of ESP-BLE-MESH stack. + +5.1 What is the meaning of warning :code:`ran out of retransmit attempts`? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + When the node transmits a segmented message, and due to some reasons, the receiver doesn't receive the complete message. Then the node will retransmit the message. When the retransmission count reaches the maximum number, which is 4 currently, then this warning will appear. + +5.2 What is the meaning of warning :code:`Duplicate found in Network Message Cache`? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + When the node receives a message, it will compare the message with the ones stored in the network cache. If the same has been found in the cache, which means it has been received before, then the message will be dropped. + +5.3 What is the meaning of warning :code:`Incomplete timer expired`? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + When the node doesn't receive all the segments of a segmented message during a certain period (e.g. 10 seconds), then the Incomplete timer will expire and this warning will appear. + +5.4 What is the meaning of warning :code:`No matching TX context for ack`? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + When the node receives a segment ack and it doesn't find any self-send segmented message related with this ack, then this warning will appear. + +5.5 What is the meaning of warning :code:`No free slots for new incoming segmented messages`? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + When the node has no space for receiving new segmented message, this warning will appear. Users can make the space larger through the configuration :ref:`CONFIG_BLE_MESH_RX_SEG_MSG_COUNT`. + +5.6 What is the meaning of error :code:`Model not bound to Appkey 0x0000`? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + When the node sends messages with a model and the model has not been bound to the AppKey with AppKey Index 0x000, then this error will appear. + +5.7 What is the meaning of error :code:`Busy sending message to DST xxxx`? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This error means client model of the node has transmitted a message to the target node and now is waiting for a response, users can not send messages to the same node with the same unicast address. After the corresponding response is received or timer is expired, then another message can be sent. + + +.. _ble-mesh-faq-example-help: + +6. Example Help +--------------- + +6.1 How are the ESP-BLE-MESH callback functions classified? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + - The API :cpp:func:`esp_ble_mesh_register_prov_callback` is used to register callback function used to handle provisioning and networking related events. + - The API :cpp:func:`esp_ble_mesh_register_config_client_callback` is used to register callback function used to handle Configuration Client Model related events. + - The API :cpp:func:`esp_ble_mesh_register_config_server_callback` is used to register callback function used to handle Configuration Server Model related events. + - The API :cpp:func:`esp_ble_mesh_register_health_client_callback` is used to register callback function used to handle Health Client Model related events. + - The API :cpp:func:`esp_ble_mesh_register_health_server_callback` is used to register callback function used to handle Health Server Model related events. + - The API :cpp:func:`esp_ble_mesh_register_generic_client_callback` is used to register callback function used to handle Generic Client Models related events. + - The API :cpp:func:`esp_ble_mesh_register_light_client_callback` is used to register callback function used to handle Lighting Client Models related events. + - The API :cpp:func:`esp_ble_mesh_register_sensor_client_callback` is used to register callback function used to handle Sensor Client Model related events. + - The API :cpp:func:`esp_ble_mesh_register_time_scene_client_callback` is used to register callback function used to handle Time and Scenes Client Models related events. + - The API :cpp:func:`esp_ble_mesh_register_custom_model_callback` is used to register callback function used to handle vendor model and unrealized server models related events. + + +.. _ble-mesh-faq-others: + +7. Others +--------- + +7.1 How to print the message context? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The examples use :cpp:func:`ESP_LOG_BUFFER_HEX` to print the message context while the ESP-BLE-MESH protocol stack uses :cpp:func:`bt_hex`. + +7.2 Which API can be used to restart ESP32? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The API :cpp:func:`esp_restart`. + +7.3 How to monitor the remaining space of the stack of a task? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The API :cpp:func:`vTaskList` can be used to print the remaining space of the task stack periodically. + +7.4 How to change the level of log without changing the menuconfig output level? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The API :cpp:func:`esp_log_level_set` can be used to change the log output level rather than using menuconfig to change it. diff --git a/docs/en/api-guides/esp-ble-mesh/ble-mesh-feature-list.rst b/docs/en/api-guides/esp-ble-mesh/ble-mesh-feature-list.rst new file mode 100644 index 0000000000..12a35edff9 --- /dev/null +++ b/docs/en/api-guides/esp-ble-mesh/ble-mesh-feature-list.rst @@ -0,0 +1,147 @@ +ESP-BLE-MESH Feature List +========================= + +Supported Features +------------------ + +Mesh Core +""""""""" + +* Provisioning: Node Role + * PB-ADV and PB-GATT + * OOB Authentication + +* Provisioning: Provisioner Role + * PB-ADV and PB-GATT + * OOB Authentication + +* Networking + * Relay + * Segmentation and Reassembly + * Key Refresh Procedure + * IV Update Procedure + * Friend + * Low Power + * Proxy Server + * Proxy Client + +* Multiple Client Models Run Simultaneously + * Support multiple client models send packets to different nodes simultaneously + * No blocking between client model and server model + +* NVS Storing + * Store provisioning and configuration information of ESP-BLE-MESH Node + +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 Default Transition Time Client + * Generic Power OnOff Client + * Generic Power Level Client + * Generic Battery Client + * Generic Location Client + * Generic Property Client + +* Sensor client models + * Sensor Client + +* Time and Scenes client models + * Time Client + * Scene Client + * Scheduler Client + +* Lighting client models + * Light Lightness Client + * Light CTL Client + * Light HSL Client + * Light xyL Client + * Light LC Client + +* Generic server models + * Generic OnOff Server + * Generic Level Server + * Generic Default Transition Time Server + * Generic Power OnOff Server + * Generic Power OnOff Setup Server + * Generic Power Level Server + * Generic Power Level Setup Server + * Generic Battery Server + * Generic Location Server + * Generic Location Setup Server + * Generic User Property Server + * Generic Admin Property Server + * Generic Manufacturer Property Server + * Generic Client Property Server + +* Sensor server models + * Sensor Server + * Sensor Setup Server + +* Time and Scenes server models + * Time Server + * Time Setup Server + * Scene Server + * Scene Setup Server + * Scheduler Server + * Scheduler Setup Server + +* Lighting server models + * Light Lightness Server + * Light Lightness Setup Server + * Light CTL Server + * Light CTL Temperature Server + * Light CTL Setup Server + * Light HSL Server + * Light HSL Hue Server + * Light HSL Saturation Server + * Light HSL Setup Server + * Light xyL Server + * Light xyL Setup Server + * Light LC Server + * Light LC Setup Server + +Mesh Applications +""""""""""""""""" + +* ESP-BLE-MESH Node + * :example:`Tutorial ` + * :example:`Tutorial ` + * :example:`Example ` +* ESP-BLE-MESH Provisioner + * :example:`Tutorial ` + * :example:`Example ` +* ESP-BLE-MESH Fast Provisioning + * :example:`Fast Provisioning Client Model Tutorial ` + * :example:`Fast Provisioning Server Model Tutorial ` + * :example:`Example ` + * `Demo Video `__ +* ESP-BLE-MESH and Wi-Fi Coexistence + * :example:`Tutorial ` + * :example:`Example ` + * `Demo Video `__ +* ESP-BLE-MESH Console Commands + * :example:`Example ` + + +Future Release Features +----------------------- + +Mesh Core +""""""""" + +* Provisioner NVS Storage + +Mesh Applications +""""""""""""""""" + +* Fast OTA +* Friendship \ No newline at end of file diff --git a/docs/en/api-guides/esp-ble-mesh/ble-mesh-index.rst b/docs/en/api-guides/esp-ble-mesh/ble-mesh-index.rst new file mode 100644 index 0000000000..dbda97e83b --- /dev/null +++ b/docs/en/api-guides/esp-ble-mesh/ble-mesh-index.rst @@ -0,0 +1,274 @@ +************ +ESP-BLE-MESH +************ + +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. + +Built on top of Zephyr Bluetooth 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. + +Please see the :doc:`ble-mesh-architecture` for information about the implementation of ESP-BLE-MESH architecture and :doc:`ESP-BLE-MESH API Reference <../../api-reference/bluetooth/esp-ble-mesh>` for information about respective API. + +ESP-BLE-MESH is implemented and certified based on the latest Mesh Profile v1.0.1, users can refer `here `_ for the certification details of ESP-BLE-MESH. + +.. note:: + + If you are looking for Wi-Fi based implementation of mesh for ESP32, please check another product by Espressif called ESP-MESH. For more information and documentation see :doc:`ESP-MESH <../../api-reference/network/esp_mesh>`. + + +.. _getting-started-with-ble-mesh: + +Getting Started with ESP-BLE-MESH +================================= + +This section is intended to help you get started with ESP-BLE-MESH for the hardware based on the ESP32 chip by Espressif. + +We are going to demonstrate process of setting and operation of a small ESP-BLE-MESH network of three nodes. This process will cover device provisioning and node configuration, and then sending on/off commands to Generic OnOff Server Models on specific nodes. + +If you are new to ESP-IDF, please first set up development environment, compile , flash and run example application following top level ESP-IDF :doc:`../../get-started/index` documentation. + + +What You Need +------------- + +Hardware: + +* Three ESP32 boards, see :ref:`options `. +* USB cables to connect the boards. +* Computer configured with ESP-IDF. +* Mobile phone or tablet running Android or iOS. + +Software: + +* Example application :example:`bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server` code to load to the ESP32 boards. +* Mobile App: **nRF Mesh** for Android or iOS. Optionally you can use some other Apps: + + - `EspBleMesh `_ Android App + - Silicon Labs Android or iOS App + +Installation Step by Step +------------------------- + +This is a detailed roadmap to walk you through the installation process. + + +.. _get-started-ble-mesh-check-hardware: + +Step 1. Check Hardware +"""""""""""""""""""""" + +Both `ESP32-DevKitC`_ and `ESP-WROVER-KIT`_ development boards are supported for ESP-BLE-MESH implementation. You can choose particular board through menuconfig: :code:`idf.py menuconfig` > ``Example Configuration`` > ``Board selection for ESP-BLE-MESH`` + +.. note:: + + If you plan to use `ESP32-DevKitC`_, connect a RGB LED to GPIO pins 25, 26 and 27. + + +Step 2. Configure Software +"""""""""""""""""""""""""" + +Enter the :example:`bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server` example directory, run :code:`idf.py menuconfig` to select your board and then run :code:`idf.py build` to compile the example. + +Step 3. Upload Application to Nodes +""""""""""""""""""""""""""""""""""" + +After the :example:`bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server` example is compiled successfully, users can run :code:`idf.py flash` to upload the same generated binary files into each of the three development boards. + +Once boards are powered on, the RGB LED on each board should turn **GREEN**. + +.. figure:: ../../../_static/ble-mesh-device-power-on.png + :align: center + + ESP-BLE-MESH Devices Power On + +Step 4. Provision Nodes +""""""""""""""""""""""" + +In this section, we will use the **nRF Mesh Android** App to demonstrate how to provision an unprovisioned device. Users can also get its iOS version from the App Store. + +4.1 Scanner +^^^^^^^^^^^ + +The Scanner is App's functionality to search for unprovisioned devices in range. Open the App, press **Scanner** at the bottom and the search will start. After a short while we should see three unprovisioned devices displayed. + +.. figure:: ../../../_static/ble-mesh-scanner.png + :align: center + :height: 370 + + nRF Mesh - Scanner + +4.2 Identify +^^^^^^^^^^^^ + +Users can select any unprovisioned device, then the App will try to set up a connection with the selected device. After the BLE connection is established successfully (sometimes users need to try multiple times to get connected), and proper ESP-BLE-MESH GATT Service is discovered, users can see the **IDENTIFY** interface button on the screen. The IDENTIFY operation can be used to tell users which device is going to be provisioned. + +.. note:: + The IDENTIFY operation also needs some cooperation on the device side, then users can see which device is in the provisioning process. Currently when pressing the **IDENTIFY** interface button, no signs can been seen from the device except from the log on the serial monitor. + +After the **IDENTIFY** interface button is pressed, users can see the **PROVISION** interface button. + +.. figure:: ../../../_static/ble-mesh-identify-provision.png + :align: center + :height: 370 + + nRF Mesh - IDENTIFY - PROVISION + +4.3 Provision +^^^^^^^^^^^^^ + +Then, the App will try to provision the unprovisioned device. When the device is provisioned successfully, the RGB LED on the board will turn off, and the App will implement the following procedures: + +#. Disconnect with the node +#. Try to reconnect with the node +#. Connect successfully and discover ESP-BLE-MESH GATT Service +#. Get Composition Data of the node and add AppKey to it + +When all the procedures are finished, the node is configured properly. And after pressing **OK**, users can see that unicast address is assigned, and Composition Data of the node is decoded successfully. + +.. figure:: ../../../_static/ble-mesh-config-complete.png + :align: center + :height: 370 + + nRF Mesh - Configuration Complete + +Sometimes in procedure 2, the App may fail to reconnect with the node. In this case, after pressing **OK**, users can see that only unicast address of the node has been assigned, but no Composition Data has been got. Then users need to press **CONNECT** on the top right, and the previously provisioned node will be displayed on the screen, and users need to choose it and try to connect with the node. + +.. figure:: ../../../_static/ble-mesh-initial-config-fail.png + :align: center + :height: 370 + + nRF Mesh - Initial Configuration Failed + +After connecting successfully, the App will show the interface buttons which can be used to get Composition Data and add AppKey. + +.. figure:: ../../../_static/ble-mesh-reconnect-initial-config.png + :align: center + :height: 370 + + nRF Mesh - Reconnect - Initial Configuration + +If the device is the second or the third one which has been provisioned by the App, and after pressing **CONNECT**, users can see two or three nodes on the screen. In this situation, users can choose any device to connect with, once succeed then go back to the main screen to choose the node which needs to be configured. + +Here an example of three devices listed. + +* The left picture shows that the third device is provisioned successfully, but the App failed to connect with it. When it tries to reconnect with the third node, three nodes are displayed on the App. +* The right picture shows that after connecting with any node successfully, the App displays the information of the three nodes. Users can see that the App has got the Composition Data of the first and the second nodes, but for the third one, only the unicast address has been assigned to it while the Composition Data is unknown. + +.. figure:: ../../../_static/ble-mesh-reconnect-three.png + :align: center + :height: 370 + + nRF Mesh - Reconnect - Three Nodes + +4.4 Configuration +^^^^^^^^^^^^^^^^^ + +When provisioning and initial configuration are finished, users can start to configure the node, such as binding AppKey with each model with the elements, setting publication information to it, etc. + +Example below shows how to bind AppKey with Generic OnOff Server Model within the Primary Element. + +.. figure:: ../../../_static/ble-mesh-model-bind-appkey.png + :align: center + :height: 370 + + nRF Mesh - Model Bind AppKey + +.. note:: + + No need to bind AppKey with the Configuration Server Model, since it only uses the DevKey to encrypt messages in the Upper Transport Layer. + +Step 5. Operate Network +""""""""""""""""""""""" + +After all the Generic OnOff Server Models within the three elements are bound with proper AppKey, users can use the App to turn on/off the RGB LED. + +In the :example:`bluetooth/esp_ble_mesh/ble_mesh_node/onoff_server` example, the first Generic OnOff Server Model is used to control the **RED** color, the second one is used to control the **GREEN** color and the third one is used to control the **BLUE** color. + +.. figure:: ../../../_static/ble-mesh-generic-onoff.png + :align: center + :height: 370 + + nRF Mesh - Generic OnOff Control + +The following screenshot shows different board with different color on. + +.. figure:: ../../../_static/ble-mesh-three-nodes-on.png + :align: center + + Three ESP-BLE-MESH Nodes On + +.. note:: + For **nRF Mesh** iOS App [version 1.0.4], when the node contains more than one element, the App is not behaving correctly. If users try to turn on/off the second or the third Generic OnOff Server Model, the message sent by the App is destinated to the first Generic OnOff Server Model within the Primary Element. + + +.. _esp-ble-mesh-examples: + +ESP-BLE-MESH Examples +===================== + +* :example:`ESP-BLE-MESH Node OnOff Server ` - shows the use of ESP-BLE-MESH as a node having a Configuration Server model and a Generic OnOff Server model. A ESP-BLE-MESH Provisioner can then provision the unprovisioned device and control a RGB LED representing on/off state, see :example:`example code `. + +* :example:`ESP-BLE-MESH Node OnOff Client ` - shows how a Generic OnOff Client model works within a node. The node has a Configuration Server model and a Generic OnOff Client model, see :example:`example code `. + +* :example:`ESP-BLE-MESH Provisioner ` - shows how a device can act as an ESP-BLE-MESH Provisioner to provision devices. The Provisioner has a Configuration Server model, a Configuration Client model and a Generic OnOff Client model, see :example:`example code `. + +* ESP-BLE-MESH Fast Provisioning - :example:`Client ` and :example:`Server ` - this example is used for showing how fast provisioning can be used in order to create a mesh network. It takes no more than 60 seconds to provision 100 devices, see :example:`example client code ` and :example:`example server code `. + +* :example:`ESP-BLE-MESH and Wi-Fi Coexistence ` - an example that demonstrates the Wi-Fi and Bluetooth (BLE/BR/EDR) coexistence feature of ESP32. Simply put, users can use the Wi-Fi while operating Bluetooth, see :example:`example code `. + +* ESP-BLE-MESH Node Console - an example that implements BLE Mesh node basic features. Within this example a node can be scanned and provisioned by Provisioner and reply to get/set message from Provisioner, see :example:`example node code ` and :example:`example Provisioner code `. + + +.. _esp-ble-mesh-demo-videos: + +ESP-BLE-MESH Demo Videos +======================== + +* `Provisioning of ESP-BLE-MESH nodes using Smartphone App `_ +* `Espressif Fast Provisioning using ESP-BLE-MESH App `_ +* `Espressif ESP-BLE-MESH and Wi-Fi Coexistence `_ + + +ESP-BLE-MESH FAQ +================ + +* :ref:`ble-mesh-faq-provisioner-development` +* :ref:`ble-mesh-faq-node-development` +* :ref:`ble-mesh-faq-ble-mesh-and-wi-fi-coexistence` +* :ref:`ble-mesh-faq-fast-provisioning` +* :ref:`ble-mesh-faq-log-help` +* :ref:`ble-mesh-faq-example-help` +* :ref:`ble-mesh-faq-others` + + +Related Documents +================= + +.. toctree:: + :maxdepth: 1 + + ble-mesh-feature-list + ble-mesh-architecture + ble-mesh-faq + ble-mesh-terminology + + + +Bluetooth SIG Documentation +--------------------------- + +- `BLE Mesh Core Specification `_ +- `BLE Mesh Model Specification `_ +- `An Intro to Bluetooth Mesh Part 1 `_ / `Part 2 `__ +- `The Fundamental Concepts of Bluetooth Mesh Networking, Part 1 `_ / `Part 2 `__ +- `Bluetooth Mesh Networking: Friendship `_ +- `Management of Devices in a Bluetooth Mesh Network `_ +- `Bluetooth Mesh Security Overview `_ +- `Provisioning a Bluetooth Mesh Network Part 1 `_ / `Part 2 `__ + + +.. _ESP32-DevKitC: https://www.espressif.com/en/products/hardware/esp32-devkitc/overview +.. _ESP-WROVER-KIT: https://www.espressif.com/en/products/hardware/esp-wrover-kit/overview diff --git a/docs/en/api-guides/esp-ble-mesh/ble-mesh-terminology.rst b/docs/en/api-guides/esp-ble-mesh/ble-mesh-terminology.rst new file mode 100644 index 0000000000..ee6ce61509 --- /dev/null +++ b/docs/en/api-guides/esp-ble-mesh/ble-mesh-terminology.rst @@ -0,0 +1,221 @@ +ESP-BLE-MESH Terminology +======================== + +:link_to_translation:`zh_CN:[中文]` + +.. _ble-mesh-terminology-role: + +.. list-table:: Table 1 ESP-BLE-MESH Terminology - Role + :widths: 10 40 60 + :header-rows: 1 + + * - Term + - Official Definition + - Detailed Explanation + * - Unprovisioned Device + - A device that is not a member of a mesh network is known as an unprovisioned device. + - Examples: lighting devices, temperature control devices, manufacturing equipments and electric doors, etc. + * - Node + - A node is a provisioned device. + - The role of unprovisioned device will change to node after being provisioned to ESP-BLE-MESH network. Nodes (such as lighting devices, temperature control devices, manufacturing equipments, and electric doors) are devices that can send, receive, or relay messages in ESP-BLE-MESH network, and they can optionally support one or more subnets. + * - Relay Node + - A node that supports the Relay feature and has the Relay feature enabled is known as a Relay node. + - Relay nodes can receive and resend ESP-BLE-MESH messages, so the messages can be transferred further. Users can decide whether or not to enable forwarding function of nodes according to nodes' status. Messages can be relayed for multiple times, and each relay is considered as a "hop". Messages can hop up to 126 times, which is enough for message transmission in a wide area. + * - Proxy Node + - A node that supports the Proxy feature and has the Proxy feature enabled is known as a Proxy node. + - Proxy nodes receive messages from one bearer (it generally includes advertising bearer and GATT bearer) and resend it from another one. The purpose is to connect communication equipments that only support GATT bearer to ESP-BLE-MESH network. Generally, mobile apps need a Proxy node to access Mesh network. Without Proxy nodes, mobile apps cannot communicate with members in Mesh network. + * - Friend Node + - A node that supports the Friend feature, has the Friend feature enabled, and has a friendship with a node that supports the Low Power feature is known as a Friend node. + - Friend node, like the backup of Low Power node (LPN), can store messages that are sent to Low Power node and security updates; the stored information will be transferred to Low Power node when Low Power node needs it. Low Power node must establish "friendship" with another node that supports the Friend Feature to reduce duty cycle of its receiver, thus power consumption of Low Power node can be reduced. Low Power node needs to find a Friend node to establish a friendship with it. The process involved is called "friendship establishment". Cooperation between Low Power node and Friend nodes enables Low Power node to schedule the use of the radio, thus Low Power node can receive messages at an appropriate or lower frequency without the need of keeping listening. Low Power node will poll Friend node to see if there is new message. + * - Low Power Node + - A node that supports the Low Power feature and has a friendship with a node that supports the Friend feature is known as a Low Power node. + - By polling, Low Power node gets information from Friend node, such as messages, security updates, and etc. + * - Provisioner + - A node that is capable of adding a device to a mesh network. + - The device that can provision unprovisioned devices is called a Provisioner. This process usually needs to be implemented through an app that is typically provided by the product manufacturer and can be used on a gateway, a smartphone, tablet or other carriers. + + +.. _ble-mesh-terminology-composition: + +.. list-table:: Table 2 ESP-BLE-MESH Terminology - Composition + :widths: 10 40 60 + :header-rows: 1 + + * - Term + - Official Definition + - Detailed Explanation + * - State + - A value representing a condition of an element that is exposed by an element of a node. + - Each node in a ESP-BLE-MESH network has an independent set of state values that indicate certain states of the device, like brightness, and color of lighting device. Change of state value will lead to change of the physical state of devices. For example, changing the on/off state of a device is actually turning on/off the device. + * - Model + - A model defines the basic functionality of a node. + - A node may contain multiple models, and each model defines basic functionalities of nodes, like the states needed by the nodes, the messages controlling the states, and actions resulted from messages handling. The function implementation of the nodes is based on models, which can be divided into SIG Model and Vendor Model, with the former defined by SIG and latter defined by users. + * - Element + - An addressable entity within a device. + - A node can contain one or more elements, with each having a unicast address and one or more models, and the models contained by the same element must not be the same. + * - Composition Data State + - The Composition Data state contains information about a node, the elements it includes, and the supported models. + - By reading the value of the Composition Data state, users can know basic information of the node, such as the number of elements, and the models in each element. Provisioner gets this message to further provision the device, such as configuring subscription address and publishing address of nodes. + + +.. _ble-mesh-terminology-features: + +.. list-table:: Table 3 ESP-BLE-MESH Terminology - Features + :widths: 10 40 60 + :header-rows: 1 + + * - Term + - Official Definition + - Detailed Explanation + * - Low Power Feature + - The ability to operate within a mesh network at significantly reduced receiver duty cycles only in conjunction with a node supporting the Friend feature. + - Low Power feature reduces power consumption of nodes. When a Low Power node is searching for a Friend node, and there are multiple Friend nodes nearby, it selects the most suitable Friend node through algorithm. + * - Friend Feature + - The ability to help a node supporting the Low Power feature to operate by storing messages destined for those nodes. + - By enabling friend feature, the node can help to store information for Low Power node. The nodes enabled with friend feature may cause more power and memory consumption. + * - Relay Feature + - The ability to receive and retransmit mesh messages over the advertising bearer to enable larger networks. + - The relay feature enables ESP-BLE-MESH messages to hop among nodes for multiple times, and the transmission distance can exceed the range of direct radio transmission between two nodes, thereby covering the entire network. When a node is enabled with the relay feature to relay messages, it only relays the messages of its own subnet, and does not relay the messages of other subnets. The data integrity will not be considered when the node enabled with relay feature relays segmented messages. The node would relay every segmented message once it receives one rather than waiting for the complete message. + * - Proxy Feature + - The ability to receive and retransmit mesh messages between GATT and advertising bearers. + - The purpose of the proxy feature is to allow nodes without an advertising bearer to access the ESP-BLE-MESH network. The proxy feature is typically used in nodes that need to connect to mobile apps. + + +.. _ble-mesh-terminology-provisioning: + +.. list-table:: Table 4 ESP-BLE-MESH Terminology - Provisioning + :widths: 10 40 60 + :header-rows: 1 + + * - Term + - Official Definition + - Detailed Explanation + * - PB-ADV + - PB-ADV is a provisioning bearer used to provision a device using Generic Provisioning PDUs over the advertising channels. + - PB-ADV transfers packets generated during the provisioning process over the advertising channels. This way can only be used for provisioning when provisioner and unprovisioned device both support PB-ADV. + * - PB-GATT + - PB-GATT is a provisioning bearer used to provision a device using Proxy PDUs to encapsulate Provisioning PDUs within the Mesh Provisioning Service. + - PB-GATT uses connection channels to transfer packets generated during the provisioning process. If an unprovisioned device wants to be provisioned through this method, it needs to implement the related Mesh Provisioning Service. Unprovisioned devices which don't implement such service cannot be provisioned into mesh network through PB-GATT bearer. + * - Provisioning + - Provisioning is a process of adding an unprovisioned device to a mesh network, managed by a Provisioner. + - The process of provisioning turns the "unprovisioned device" into a "node", making it a member of the ESP-BLE-MESH network. + * - Authentication Method + - Authentication is a step during the provisioning of nodes. + - There are four authentication methods for unprovisioned devices: Output OOB, Input OOB, Static OOB, and No OOB. + * - Input OOB + - Input Out-of-Band + - For example, a Provisioner generates and displays a random number, and then prompts users to take appropriate actions to input the random number into the unprovisioned device. Taking lighting switch as an example, users can press the button for several times in a certain period of time to input the random number displayed on the Provisioner. Authentication method of the Input OOB is similar to that of Output OOB, but the role of the device is reversed. + * - Output OOB + - Output Out-of-Band + - For example, an unprovisioned device will choose a random number and output the number in a way that is compatible with its functionality. If the unprovisioned device is a bulb, it can flash a specified number of times. If the unprovisioned device has an LCD screen, the random number can display as a multi-digit value. Users who start provisioning should input the observed number to authenticate the unprovisioned device. + * - Static OOB + - Static Out-of-Band + - Authentication method of Static OOB: use Static OOB information. Use 0 as Static OOB information if No OOB information is needed. Use Static OOB information to authenticate devices which are going through provisioning if OOB information is needed. + * - No OOB + - No Out-of-Band + - Authentication method of No OOB: Set the value of the Static OOB field to 0. Using this way is like not authenticating the unprovisioned devices. + + +.. _ble-mesh-terminology-address: + +.. list-table:: Table 5 ESP-BLE-MESH Terminology - Address + :widths: 10 40 60 + :header-rows: 1 + + * - Term + - Official Definition + - Detailed Explanation + * - Unassigned Address + - This is a special address type, with a value of 0x0000. Its use indicates that an Element has not yet been configured or had a Unicast Address assigned to it. + - The addresses owned by elements which has not been configured yet or no address has been allocated are unassigned addresses. These elements will not be used for messages transfer because they have no fixed address. Unassigned address is recommended to set as the value of the address before setting the address of user code. + * - Unicast Address + - A unicast address is a unique address allocated to each element. + - During provisioning, the Provisioner will assign a unicast address to each element of node within the life cycle of the nodes in the network. A unicast address may appear in the source/destination address field of a message. Messages sent to a unicast address can only be processed by the element that owns the unicast address. + * - Virtual Address + - A virtual address represents a set of destination addresses. Each virtual address logically represents a Label UUID, which is a 128-bit value that does not have to be managed centrally. + - Associated with specific UUID labels, a virtual address may serve as the publishing or subscription address of the model. A UUID label is a 128-bit value associated with elements of one or more nodes. For virtual addresses, the 15th and 14th bits are set to 1 and 0 respectively; bits from 13th to 0 are set to hash values (providing 16384 hash values). The hash is a derivation of the Label UUID. To use subscribing elements to check the full 128-bit UUID is very inefficient while hash values provide a more efficient way to determine which elements that which messages are finally sent to. + * - Group Address + - A group address is an address that is programmed into zero or more elements + - Group address is another kind of multicast address in the ESP-BLE-MESH network, which is usually used to group nodes. A message sent to the all-proxies address shall be processed by the primary element of all nodes that have the proxy functionality enabled. A message sent to the all-friends address shall be processed by the primary element of all nodes that have the friend functionality enabled. A message sent to the all-relays address shall be processed by the primary element of all nodes that have the relay functionality enabled. A message sent to the all-nodes address shall be processed by the primary element of all nodes. + + +.. _ble-mesh-terminology-security: + +.. list-table:: Table 6 ESP-BLE-MESH Terminology - Security + :widths: 10 40 60 + :header-rows: 1 + + * - Term + - Official Definition + - Detailed Explanation + * - Device Key (DevKey) + - There is also a device key, which is a special application key that is unique to each node, is known only to the node and a Configuration Client, and is used to secure communications between the node and a Configuration Client. + - The device key enables you to provision the devices, configure the nodes. The device key is used to encrypt Configuration Messages, i.e. the message transferred between the Provisioner and the node when the device is configured. + * - Application Key (AppKey) + - Application keys are used to secure communications at the upper transport layer. + - Application key is used for decryption of application data before delivering application data to application layer and encryption of them during the delivery of application layer. Some nodes in the network have a specific purpose and can restrict access to potentially sensitive data based on the needs of the application. With specific application keys, these nodes are associated with specific applications. Generally speaking, the fields using different application keys include security (access control of buildings, machine rooms and CEO offices), lighting (plant, exterior building and sidewalks) and HVAC systems. Application keys are bound to Network keys. This means application keys are only used in a context of a Network key they are bound to. An application key shall only be bound to a single Network key. + * - Master Security Material + - The master security material is derived from the network key (NetKey) and can be used by other nodes in the same network. Messages encrypted with master security material can be decoded by any node in the same network. + - The corresponding friendship messages encrypted with the friendship security material: 1. Friend Poll, 2. Friend Update, 3. Friend Subscription List, add/delete/confirm, 4. The Stored Messages" sent by friend nodes to Low Power node. The corresponding friendship messages encrypted with the master security material: 1. Friend Clear, 2. Friend Clear Confirm. Based on the setup of the applications, the messages sent from the Low Power node to the friend nodes will be encrypted with the friendship security material or master security material, with the former being used by the messages transmitted between Low Power node and friend nodes and the latter being used by other network messages. + + +.. _ble-mesh-terminology-message: + +.. list-table:: Table 7 ESP-BLE-MESH Terminology - Message + :widths: 10 40 60 + :header-rows: 1 + + * - Term + - Official Definition + - Detailed Explanation + * - Reassembly / Segmentation + - Segmentation and reassembly (SAR) is a method of communication network, which is divided into small units before transmitting packets and reassembled in a proper order at the communication receiving end. + - The lower transport layer will automatically segment the message whose size is too big. The receiving end will return a response message, and the transmitting end will send the data packet again that the receiving end does not receive according to the response message. This is automatically completed by the lower transport layer. Unsegmented messages have at most 15 bytes, of which 4 bytes are transMIC, so the remaining is 11 bytes; in the case of segmentation, there are 12 valid bytes in the first several packets, and 8 in the last one. Special case: A shorter packet requires mandatory segmentation from lower transport layer, in which case the valid byte is 8 bytes. + * - Unacknowledged / Acknowledged + - There are two types of messages: Unacknowledged or Acknowledged + - Based on the whether or not the receiving end needs to send the response message, the messages sent are divided into two kinds. The sending end should set the maximum number of retransmission. + + +.. _ble-mesh-terminology-foundation-models: + +.. list-table:: Table 8 ESP-BLE-MESH Terminology - Foundation Models + :widths: 10 40 60 + :header-rows: 1 + + * - Term + - Official Definition + - Detailed Explanation + * - Configuration Server Model + - This model is used to represent a mesh network configuration of a device. + - The node must contain the Configuration Server Model, which is responsible for maintaining configuration-related states. The states that Configuration Server Model maintains include: NetKey List, AppKey List, Model to AppKey List, Node Identity, Key Refresh Phase, Heartbeat Publish, Heartbeat Subscription, Network Transmit, Relay Retransmit etc. + * - Configuration Client Model + - The model is used to represent an element that can control and monitor the configuration of a node. + - The Configuration Client Model uses messages to control the state maintained by the Configuration Server Model. The Provisioner must contain the Configuration Client Model, with which the configuration messages, like Configuration Composition Data Get can be sent. + * - Health Server Model + - This model is used to represent a mesh network diagnostics of a device. + - The Health Server Model is primarily used by devices to check their states and see if there is an error. The states maintained by Health Server model include: Current Fault, Registered Fault, Health Period, and Attention Timer. + * - Health Client Model + - The model is used to represent an element that can control and monitor the health of a node. + - The Health Client Model uses messages to control the state maintained by the Health Server Model. The model can get the self-test information of other nodes through the message "Health Fault Get". + + +.. _ble-mesh-terminology-network-management: + +.. list-table:: Table 9 ESP-BLE-MESH Terminology - Network Management + :widths: 10 40 60 + :header-rows: 1 + + * - Term + - Official Definition + - Detailed Explanation + * - Key Refresh procedure + - This procedure is used when the security of one or more network keys and/or one or more of the application keys has been compromised or could be compromised. + - Key Refresh Procedure is used to update network key and application key of ESP-BLE-MESH network. Key Refresh Procedure is used when the security of one or more network keys and/or one or more application keys is threatened or potentially threatened. Keys are usually updated after some nodes in the network are removed. + * - IV (Initialisation Vector) Update Procedure + - A node can also use an IV Update procedure to signal to peer nodes that it is updating the IV Index. + - The IV Update procedure is used to update the value of ESP-BLE-MESH network's IV Index. This value is related to the random number required for message encryption. To ensure that the value of the random number is not repeated, this value is periodically incremented. IV Index is a 32-bit value and a shared network resource. For example, all nodes in a mesh network share the same IV Index value. Starting from 0x00000000, the IV Index increments during the IV Update procedure and maintained by a specific process, ensuring the IV Index shared in the mesh network is the same. This can be done when the node believes that it has the risk of exhausting its sequence number, or when it determines that another node is nearly exhausting its sequence number. Note: The update time must not be less than 96 hours. It can be triggered when a secure network beacon is received, or when the node determines that its sequence number is greater than a certain value. + +For more terms, please see: `ESP-BLE-MESH Glossary of Terms `_. + + + diff --git a/docs/en/api-guides/index.rst b/docs/en/api-guides/index.rst index 17be4f178e..0ef7e2ec37 100644 --- a/docs/en/api-guides/index.rst +++ b/docs/en/api-guides/index.rst @@ -30,6 +30,7 @@ API Guides RF Calibration WiFi Driver ESP-MESH + ESP-BLE-MESH BluFi External SPI-connected RAM Linker Script Generation diff --git a/docs/en/api-reference/bluetooth/esp-ble-mesh.rst b/docs/en/api-reference/bluetooth/esp-ble-mesh.rst new file mode 100644 index 0000000000..a10c79fe29 --- /dev/null +++ b/docs/en/api-reference/bluetooth/esp-ble-mesh.rst @@ -0,0 +1,154 @@ +ESP-BLE-MESH +============ + +With various features of ESP-BLE-MESH, users can create a managed flooding mesh network for several +scenarios, such as lighting, sensor and etc. + +For an ESP32 to join and work on a ESP-BLE-MESH network, it must be provisioned firstly. By provisioning, +the ESP32, as an unprovisioned device, will join the ESP-BLE-MESH network and become a ESP-BLE-MESH node, +communicating with other nodes within or beyond the radio range. + +Apart from ESP-BLE-MESH nodes, inside ESP-BLE-MESH network, there is also ESP32 that works as ESP-BLE-MESH +Provisioner, which could provision unprovisioned devices into ESP-BLE-MESH nodes and configure the nodes +with various features. + +For information how to start using ESP32 and ESP-BLE-MESH, please see the Section :ref:`getting-started-with-ble-mesh`. If you are interested in information on ESP-BLE-MESH architecture, including some details of software implementation, please see Section :doc:`../../api-guides/esp-ble-mesh/ble-mesh-architecture`. + + +Application Examples and Demos +------------------------------ + +Please refer to Sections :ref:`esp-ble-mesh-examples` and :ref:`esp-ble-mesh-demo-videos`. + + +API Reference +------------- + +ESP-BLE-MESH APIs are divided into the following parts: + +* `ESP-BLE-MESH Definitions`_ +* `ESP-BLE-MESH Core API Reference`_ +* `ESP-BLE-MESH Models API Reference`_ + + +ESP-BLE-MESH Definitions +------------------------ + +This section contains only one header file, which lists the following items of ESP-BLE-MESH. + +* ID of all the models and related message opcodes +* Structs of model, element and Composition Data +* Structs of used by ESP-BLE-MESH Node/Provisioner for provisioning +* Structs used to transmit/receive messages +* Event types and related event parameters + +.. include:: /_build/inc/esp_ble_mesh_defs.inc + + +ESP-BLE-MESH Core API Reference +------------------------------- + +This section contains ESP-BLE-MESH Core related APIs, which can be used to initialize ESP-BLE-MESH +stack, provision, send/publish messages, etc. + +This API reference covers six components: + +* `ESP-BLE-MESH Stack Initialization`_ +* `Reading of Local Data Information`_ +* `Low Power Operation (Updating)`_ +* `Send/Publish Messages, add Local AppKey, etc.`_ +* `ESP-BLE-MESH Node/Provisioner Provisioning`_ +* `ESP-BLE-MESH GATT Proxy Server`_ + + +ESP-BLE-MESH Stack Initialization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_common_api.inc + + +Reading of Local Data Information +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_local_data_operation_api.inc + + +Low Power Operation (Updating) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_low_power_api.inc + + +Send/Publish Messages, add Local AppKey, etc. +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_networking_api.inc + + +ESP-BLE-MESH Node/Provisioner Provisioning +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_provisioning_api.inc + + +ESP-BLE-MESH GATT Proxy Server +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_proxy_api.inc + + +ESP-BLE-MESH Models API Reference +--------------------------------- + +This section contains ESP-BLE-MESH Model related APIs, event types, event parameters, etc. + +There are six categories of models: + +* `Configuration Client/Server Models`_ +* `Health Client/Server Models`_ +* `Generic Client/Server Models`_ +* `Sensor Client/Server Models`_ +* `Time and Scenes Client/Server Models`_ +* `Lighting Client/Server Models`_ + + +.. note:: + + Definitions related to Server Models are being updated, and will be released soon. + + +Configuration Client/Server Models +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_config_model_api.inc + + +Health Client/Server Models +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_generic_model_api.inc + + +Generic Client/Server Models +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_health_model_api.inc + + +Sensor Client/Server Models +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_lighting_model_api.inc + + +Time and Scenes Client/Server Models +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_sensor_model_api.inc + + +Lighting Client/Server Models +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: /_build/inc/esp_ble_mesh_time_scene_model_api.inc + diff --git a/docs/en/api-reference/bluetooth/index.rst b/docs/en/api-reference/bluetooth/index.rst index 91c2ded9c9..7056d5810b 100644 --- a/docs/en/api-reference/bluetooth/index.rst +++ b/docs/en/api-reference/bluetooth/index.rst @@ -9,7 +9,7 @@ Bluetooth API Bluetooth LE Bluetooth Classic NimBLE - + ESP-BLE-MESH ESP-IDF currently supports two host stacks. The Bluedroid based stack (default) supports classic Bluetooth as well as BLE. On the other hand, Apache NimBLE based stack is BLE only. For users to make a * For usecases involving classic Bluetooth as well as BLE, Bluedroid should be used. diff --git a/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-architecture.rst b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-architecture.rst new file mode 100644 index 0000000000..8bd54a0bbe --- /dev/null +++ b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-architecture.rst @@ -0,0 +1,388 @@ +ESP-BLE-MESH 架构 +================= + +:link_to_translation:`en:[English]` + +本文档将介绍 ESP-BLE-MESH 的架构概览、架构实现和辅助程序。 + +- ESP-BLE-MESH 架构概览 + + - 描述了 ESP-BLE-MESH 架构的 5 大部分及每个部分的功能。 + +- ESP-BLE-MESH 架构实现 + + - 描述了 ESP-BLE-MESH 文件的基本功能、文件与 ESP-BLE-MESH 架构的对应关系及文件间调用的接口。 + +- ESP-BLE-MESH 辅助程序 + + - 描述了 ESP-BLE-MESH 的辅助程序,比如 Mesh 网络管理,Mesh 特性等。 + +1. ESP-BLE-MESH 架构概览 +------------------------ + +目前,ESP-BLE-MESH 已经实现了 Mesh Profile 的大多数功能及 Mesh Model 规范中定义的所有 Client Model。未支持的功能/模型尚在开发中,会尽快提供。 ESP-BLE-MESH 已通过 Bluetooth SIG 蓝牙技术联盟的 `认证 `__。 + +.. figure:: ../../../_static/esp-ble-mesh-architecture.png + :align: center + + 图 1.1 ESP-BLE-MESH 架构图 + +ESP-BLE-MESH 架构主要由以下 5 大部分组成: + +- ``Mesh 协议栈`` + + - ``Mesh Networking`` 负责 BLE Mesh 设备的网络消息处理等。 + - ``Mesh Provisioning`` 负责 BLE Mesh 设备的启动配置流程。 + - ``Mesh Models`` 负责实现 SIG 定义的模型。 + +- ``网络管理`` + + - 负责实现网络管理程序,包括节点删除程序、网络索引 (IV Index) 恢复程序等。 + +- ``特性`` + + - 包括 BLE Mesh 特性,如低功耗特性、好友特性、中继特性等。 + +- ``Mesh 承载层`` + + - 包括 ``广播承载层`` 和 ``GATT 承载层``。承载层对于 ESP-BLE-MESH 协议栈至关重要,因为协议栈基于蓝牙低功耗技术构建而成,其必须利用承载层通过 BLE 广播通道和连接通道进行数据传输。 + +- ``应用程序`` + + - 基于 ESP-BLE-MESH 协议栈和 ``Mesh Models``。 + - 通过调用 API 和处理事件,``Applications`` 实现了与 ESP-BLE-MESH 协议栈中的 ``Mesh Networking`` 和 ``Mesh Provisioning`` 的交互,也实现了与 ``Mesh Models`` 中一系列模型的交互。 + + +1.1 Mesh 协议栈 +--------------- + +1.1.1 Mesh Networking +^^^^^^^^^^^^^^^^^^^^^ + +协议栈架构中的 ``Mesh Networking`` 实现了如下功能: + +- Mesh 网络中节点间的通讯。 +- Mesh 网络中消息的加解密。 +- Mesh 网络资源的管理,如网络秘钥 (NetKey)、网络索引等。 +- Mesh 网络消息的分包与重组。 +- 消息在不同模型间的模型映射。 +- 更多功能,请参见 :doc:`ble-mesh-feature-list`。 + +``Mesh Networking`` 功能的实现是基于层级结构的。每一层的功能如表 1.1 所示: + +.. list-table:: 表 1.1 Mesh Networking 框架描述 + :widths: 40 150 + :header-rows: 1 + + * - 层级 + - 功能 + * - 接入层 + - 接入层定义应用程序数据的格式,还对上层传输层对数据包的加密和解密进行定义和控制。 + * - 上层传输层 + - 上层传输层对接入层进出的应用数据进行加密、解密和认证,同时也处理被称为 “传输控制消息”的特殊消息,这种消息包括了与“友谊”和心跳包相关的消息。 + * - 底层传输层 + - 底层传输层处理 PDU 的分包和重组。 + * - 网络层 + - 网络层定义网络消息的地址类型和格式,实现设备的中继功能。 + +1.1.2 Mesh Provisioning +^^^^^^^^^^^^^^^^^^^^^^^ + +协议栈架构中的 ``Mesh Provisioning`` 实现了如下功能: + +- 对未配网设备的配网。 +- Mesh 网络资源的分配 (单播地址、网络索引和网络秘钥)。 +- 配网期间对 4 种验证方法的支持。 +- 更多功能,请参见 :doc:`ble-mesh-feature-list`。 + +``Mesh Provisioning`` 功能的实现是基于层级结构的。每一层的功能如表 1.2 所示: + +.. list-table:: 表 1.2 Mesh Provisioning 框架描述 + :widths: 40 150 + :header-rows: 1 + + * - 层级 + - 功能 + * - Provisioning PDUs + - 通过配网协议处理不同层级的 Provisioning PDUs。 + * - Generic Provisioning PDU/Proxy PDU + - 使用 Generic Provisioning 层或代理协议层将 Provisioning PDU 传输到未配网的设备。 + * - PB-ADV/PB-GATT + - 这些层级定义了 Provisioning PDUs 作为可分包和重组的消息进行传输的方式。 + * - Advertising/Provisioning Service + - Provisioning bearer 定义了会话建立的方式,该方式用来将 Generic Provisioning 层的传输包传送到设备。 + +1.1.3 Mesh Models +^^^^^^^^^^^^^^^^^ + +协议栈架构中的 ``Mesh Models`` 实现了如下功能: + +- Configuration Client/Server Models +- Health Client/Server Models +- Generic Client/Server Models +- Sensor Client/Server Models +- Time and Scenes Client/Server Models +- Lighting Client/Server Models + +每一层的功能如表 1.3 所示: + +.. list-table:: 表 1.3 Mesh Models 框架描述 + :widths: 40 150 + :header-rows: 1 + + * - 层级 + - 功能 + * - 模型层 + - 模型层实现用于标准化典型用户场景操作的模型,包括 Generic Client/Server Models、Sensor Client/Server Models、Time and Scenes Client/Server Models、Lighting Client/Server Models 和若干自定义模型。 + * - 基础模型层 + - 基础模型层实现与 ESP-BLE-MESH 网络配置、管理和自我诊断等相关的模型。 + +1.2 Mesh 网络管理 +----------------- + +``网络管理`` 实现了如下功能: + +- 节点移除程序:用于将节点从网络中移除。 +- 网络索引恢复程序:用于恢复节点的网络索引。 +- 网络索引更新程序:用于更新节点的网络索引。 +- 秘钥更新程序:用于更新节点的网络秘钥、应用秘钥 (AppKey) 等。 +- 网络创建程序:用于创建 mesh 网络。 +- NVS 存储器:用于存储节点的网络信息。 + +1.3 Mesh 特性 +------------- + +``特性`` 包括以下几项: + +- 低功耗特性:用于降低节点的能耗。 +- 好友特性:用于为低功耗节点存储消息。 +- 中继特性:用于中继/转发节点通过广播承载层收到的网络 PDU. +- Proxy Server/Client 是代理协议中的两个节点角色,其使节点可以通过面向连接的承载层收发 Network PDUs、mesh beacons、代理配置消息和 Provisioning PDU。 + +1.4 Mesh 承载层 +--------------- + +协议栈框架中的 ``承载层`` 负责 ESP-BLE-MESH 协议栈和低功耗蓝牙核心协议间的数据传输。 + +``承载层`` 可视为是基于蓝牙低功耗核心协议的载体层,其实现了 ESP-BLE-MESH 协议栈数据的接收和传输。 + +.. list-table:: 表 1.3 Mesh 承载层描述 + :widths: 40 150 + :header-rows: 1 + + * - 层级 + - 功能 + * - GATT 承载层 + - GATT 承载层使用代理协议通过 GATT 连接在两个设备之间发送和接收 ``Proxy PDUs``。 + * - 广播承载层 + - 使用广播承载层时,必须使用低功耗蓝牙广播通道来发送 mesh 数据包, 数据包中的 AD Type 需要设置为 mesh 数据包的类型。 + +1.5 Mesh ``应用层`` +-------------------- + +协议栈框架图中的 ``应用层`` 通过调用 ESP-BLE-MESH 协议栈提供的 API 并处理协议栈上报的事件来实现相应的功能,有一些常见应用,比如网关、照明等。 + +应用层和 ``API / 事件`` 之间的交互 + +- 应用层调用 API + + - 调用配网相关的 API 进行配网。 + - 调用模型相关的 API 发送消息。 + - 调用设备属性相关的 API 获取设备的本地信息。 + +- 应用层处理事件 + + 应用层的设计基于事件设计,事件将参数传输给应用层。事件主要分为两大类。 + + - 调用 API 完成的事件。 + - 比如接收消息的节点。 + - 协议栈主动上报给应用层的事件。 + - 协议栈主动上报的事件。 + - 模型主动上报的事件。 + +- 事件通过应用层注册的回调函数进行上报,同时回调函数中也会包含对事件的相应处理。 + +``API /事件`` 与 ESP-BLE-MESH 协议栈的交互 + +- 用户使用的 API 主要调用``Mesh Networking``、``Mesh Provisioning`` 和 ``Mesh Models`` 提供的函数。 + +- ``API /事件`` 和协议栈的交互不会跨越协议栈的层级进行操作。比如 API 不会调用 ``Network Layer`` 相关的函数。 + +2. ESP-BLE-MESH 架构实现 +------------------------ + +ESP-BLE-MESH 架构的设计和实现是基于层级和模块的。具体而言,第 2.1 节(Mesh 网络的实现),第 2.2 节(Mesh 配网实现)和第 2.3 节(Mesh 层级实现)基于层级思想,第 2.4 节(网格模型的实现)基于模块思想。 + +- **层级思想**: 基于层级思想,网络架构根据 Mesh Profile Specification 中指定的层级设计而成。每层都有独特的文件,文件包括该层的 API 等。具体设计如图 2.1 所示。 + +- **模块思想**: 每个文件实现一个独立的功能,供其它程序调用。 + +.. figure:: ../../../_static/esp-ble-mesh-interface.png + :align: center + + 图 2.1 ESP-BLE-MESH 架构实现图 + +ESP-BLE-MESH 架构采用分层的方式进行设计,数据包的处理所经过的层级顺序是固定的,也就是数据包的处理过程会形成一个 ``消息流``。因此,我们可以从图 2.1 的协议栈接口图中看到消息流。 + +2.1 Mesh 协议栈的实现 +--------------------- + +2.1.1 Mesh Networking 实现 +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``Mesh Networking`` 中的文件列表和每个文件实现的功能如表 2.1 所示: + +.. list-table:: 表 2.1 Mesh Networking 文件描述 + :widths: 40 150 + :header-rows: 1 + + * - 文件 + - 功能 + * - :component_file:`access.c ` + - BLE Mesh 接入层 + * - :component_file:`transport.c ` + - BLE Mesh 底层/上层传输层 + * - :component_file:`net.c ` + - BLE Mesh 网络层 + * - :component_file:`adv.c ` + - 用于发送 BLE Mesh 广播包的任务,一个用于处理收到的广播包的回调以及用于分配 adv 缓冲区的 API + +2.1.2 Mesh Provisioning 实现 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +由于 Node/Provisioner 共存的限制,Mesh Provisioning 的实现分为两大模块。 + +实现 Node 启动配置的特定文件如表 2.2 所示: + +.. list-table:: 表 2.2 Mesh Provisioning(节点)文件描述 + :widths: 40 150 + :header-rows: 1 + + * - 文件 + - 功能 + * - :component_file:`prov.c ` + - BLE Mesh 节点配网 (PB-ADV & PB-GATT) + * - :component_file:`proxy.c ` + - BLE Mesh 节点代理相关功能 + * - :component_file:`beacon.c ` + - 用于处理 BLE Mesh Beacon 的 API + +实现 Provisioner 配置功能的特定文件如表 2.3 所示: + +.. list-table:: 表 2.3 Mesh Provisioning (Provisioner) 文件描述 + :widths: 40 150 + :header-rows: 1 + + * - 文件 + - 功能 + * - :component_file:`provisioner_prov.c ` + - BLE Mesh Provisioner 配置入网 (PB-ADV & PB-GATT) + * - :component_file:`provisioner_proxy.c ` + - BLE Mesh 代理客户端相关功能 + * - :component_file:`provisioner_beacon.c ` + - BLE Mesh Provisioner 接收 Unprovisioned Device Beacon + +2.1.3 Mesh Models 实现 +^^^^^^^^^^^^^^^^^^^^^^ + +Mesh Models 用于实现节点中所包含的模型的具体功能。服务器模型主要用于维护节点状态。客户端模型主要用于获取和修改节点状态。 + +.. list-table:: Table 2.4 Mesh Models 文件描述 + :widths: 40 150 + :header-rows: 1 + + * - 文件 + - 功能 + * - :component_file:`cfg_cli.c ` + - 发送 Configuration Client 消息,接收相应应答消息 + * - :component_file:`cfg_srv.c ` + - 接收 Configuration Client 消息,发送适当应答消息 + * - :component_file:`health_cli.c ` + - 发送 Health Client 消息,接收相应应答消息 + * - :component_file:`health_srv.c ` + - 接收 Health Client 消息,发送适当应答消息 + * - :component_file:`client_common.c ` + - BLE Mesh 模型相关操作 + * - :component_file:`generic_client.c ` + - 发送 BLE Mesh Generic Client 消息,接收相应应答消息 + * - :component_file:`lighting_client.c ` + - 发送 BLE Mesh Lighting Client 消息,接收相应应答消息 + * - :component_file:`sensor_client.c ` + - 发送 BLE Mesh Sensor Client 消息,接收相应应答消息 + * - :component_file:`time_scene_client.c ` + - 发送 BLE Mesh Time Scene Client 消息,接收相应应答消息 + +2.2 Mesh Bearers 实现 +^^^^^^^^^^^^^^^^^^^^^ + +Mesh Bearers 在实现时充分考虑了可移植性。当 ESP-BLE-MESH 协议栈需要移植到其它平台时,用户只需要修改 :component_file:`mesh_bearer_adapt.c ` 就能移植成功。 + +.. list-table:: 表 2.5 Mesh Bearers 文件描述 + :widths: 40 150 + :header-rows: 1 + + * - 文件 + - 功能 + * - :component_file:`mesh_bearer_adapt.c ` + - BLE Mesh 承载层适配文件。此文件提供用于接收和发送 BLE Mesh ADV 和 GATT 相关数据包的接口。 + +.. note:: + + :component_file:`mesh_bearer_adapt.c ` 是对 Mesh 网络框架中 ``Advertising Bearer`` 和 ``GATT Bearer`` 的实现。 + +2.3 Mesh Applications 实现 +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +我们提供了一系列用于客户开发的应用示例,用户可以基于 :ref:`esp-ble-mesh-examples` 开发产品。 + +3. ESP-BLE-MESH 辅助程序 +------------------------- + +辅助程序指的是 ESP-BLE-MESH 协议栈中可选的功能。辅助程序的设计通常通过 :ref:`CONFIG_BLE_MESH` 来实现代码的裁剪。 + +3.1 特性 +^^^^^^^^ + +- 低功耗 +- 好友 +- 中继 +- 代理客户端/代理服务器 + +3.2 网络管理 +^^^^^^^^^^^^ + +- 节点移除程序 +- 网络索引恢复程序 +- 网络索引更新程序 +- 秘钥更新程序 +- 网络创建程序 +- NVS 存储器 + +3.3 辅助程序实现 +^^^^^^^^^^^^^^^^ + +采用独立模块的设计主要考虑到两个因素: + +- 该模块不具备分层实现的条件,其实现可以完全独立,不需要依赖其它模块。 +- 模块中的函数会被反复使用到,因此最好设计成独立模块。独立模块如表 3.1 所示: + +.. list-table:: 表 3.1 模块文件描述 + :widths: 40 150 + :header-rows: 1 + + * - 文件 + - 功能 + * - :component_file:`lpn.c ` + - BLE Mesh 低功耗功能 + * - :component_file:`friend.c ` + - BLE Mesh 好友功能 + * - :component_file:`net.c ` + - BLE Mesh 中继功能、网络创建、网络索引更新程序、网络索引恢复程序、秘钥更新程序相关功能 + * - :component_file:`proxy.c ` + - BLE Mesh 代理服务器相关功能 + * - :component_file:`provisioner_proxy.c ` + - BLE Mesh 代理客户端相关功能 + * - :component_file:`settings.c ` + - BLE Mesh 节点 NVS 存储器功能 + * - :component_file:`mesh_main.c ` + - BLE Mesh 节点移除相关功能 diff --git a/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-faq.rst b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-faq.rst new file mode 100644 index 0000000000..302cfe1f03 --- /dev/null +++ b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-faq.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-guides/esp-ble-mesh/ble-mesh-faq.rst \ No newline at end of file diff --git a/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-feature-list.rst b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-feature-list.rst new file mode 100644 index 0000000000..e208eeddd8 --- /dev/null +++ b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-feature-list.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-guides/esp-ble-mesh/ble-mesh-feature-list.rst \ No newline at end of file diff --git a/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-index.rst b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-index.rst new file mode 100644 index 0000000000..d017bd8e26 --- /dev/null +++ b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-index.rst @@ -0,0 +1,276 @@ +************ +ESP-BLE-MESH +************ + +:link_to_translation:`en:[English]` + +概述 +===== + +蓝牙 mesh 网络实现了无线设备的“多对多”通讯,其可用于建立包含大量设备的网络。 + +设备能将数据中继至不在初始设备无线电覆盖范围内的其他设备。这样,mesh 网络就能够覆盖非常大的物理区域,并且囊括大量设备。Mesh 网络非常适用于楼宇自动化、传感器网络和其他物联网解决方案,这些情景下数以十计、百计、千计的设备需要与其他设备进行安全可靠的通信。 + +蓝牙 mesh 并非无线通信技术,而是一种网络技术。该技术基于一种无线通讯协议栈,即低功耗蓝牙。 + +ESP-BLE-MESH 基于 Zephyr 蓝牙 Mesh 协议栈的顶端,其实现支持设备配网和节点控制,同时也实现了代理、中继、低功耗和朋友等节点功能。 + +有关 ESP-BLE-MESH 架构实现的信息,请参见 :doc:`ble-mesh-architecture`;有关各自 API 的信息,请参见 :doc:`ESP-BLE-MESH API Reference <../../api-reference/bluetooth/esp-ble-mesh>`。 + +ESP-BLE-MESH 的实现和认证基于最新的 `Mesh Profile v1.0.1 `_ 。有关 ESP-BLE-MESH 认证的细节,请参考 `此处 `_ 。 + +.. note:: + + 如果您在寻找 ESP32 基于 Wi-Fi 的 mesh 方案,请查阅乐鑫的另一款产品 ESP-MESH。更多相关信息及文档,请参见:doc:`ESP-MESH <../../api-reference/network/esp_mesh>`。 + +.. _getting-started-with-ble-mesh: + +ESP-BLE-MESH 快速入门 +===================== + +该章节旨在帮助您基于乐鑫的 ESP32 开发板搭建 ESP-BLE-MESH 网络。 + +我们将会展示如何搭建并运行一个包含 3 个节点的小型 ESP-BLE-MESH 网络,其中包含设备配网、节点配置,以及向特定节点上的 Generic OnOff Server Model 发送开关灯命令。 + +如果您是第一次接触 ESP-IDF,请参见 esp-idf :doc:../../get-started/index 来设置开发环境,编译、烧写和运行示例应用程序。 + +硬件及软件准备 +-------------- + +硬件: + +* 3 块 ESP32 开发板,请参见 :ref:`options `。 +* 连接开发板的 USB 线。 +* ESP-IDF 开发环境。 +* 运行 Android 或 iOS 的手机或平板。 + +软件: + +* 下载至 ESP32 开发板的示例应用 :example:`bluetooth/esp_ble_mesh/ble_mesh_node`。 +* 手机 App: **nRF Mesh** Android 或 iOS 版本。除 nRF Mesh 的 App,以下 App 也支持 ESP-BLE-MESH: + + - `EspBleMesh `_ Android App + - Silicon Labs Android 或 iOS App + +安装 +---- + +以下详细步骤可指导您完成安装过程。 + + +.. _get-started-ble-mesh-check-hardware: + +步骤 1. 检查硬件 +""""""""""""""""" + +`ESP32-DevKitC`_ 和 `ESP-WROVER-KIT`_ 开发板均支持 ESP-BLE-MESH。您可以通过 menuconfig: :code:`idf.py menuconfig` > ``Example Configuration`` > ``Board selection for ESP-BLE-MESH`` 选择特定的开发板。 + +.. note:: + + 如果您打算使用 `ESP32-DevKitC`_ 开发板,请将 RGB 灯焊接至 GPIO 管脚 25、26 和 27。 + + +步骤 2. 配置软件 +"""""""""""""""" + +进入 :example:`bluetooth/esp_ble_mesh/ble_mesh_node` 示例文件夹,运行 :code:`idf.py menuconfig` 选择所使用的开发板,然后运行 :code:`idf.py build` 编译示例。 + +步骤 3. 下载应用 +""""""""""""""""" + + :example:`bluetooth/esp_ble_mesh/ble_mesh_node` 示例编译成功后,用户可以运行 :code:`idf.py flash` 将编译生成的二进制文件下载至 3 块开发板中。 + +当开发板上电后,RGB 灯会变为 **绿色**。 + +.. figure:: ../../../_static/ble-mesh-device-power-on.png + :align: center + + ESP-BLE-MESH 设备上电 + +步骤 4. 设备配网 +""""""""""""""""" + +在该章节中,我们将使用 **nRF Mesh Android** App 演示如何配网设备。用户也可以从 App Store 下载其 iOS 版本。 + +4.1 扫描 (scanner) +^^^^^^^^^^^^^^^^^^ + +扫描 (Scanner) 是 nRF Mesh App 搜索蓝牙通信范围内未配网设备的功能。打开 App,点击底部的扫描按钮 **Scanner**。App 就会开始扫描设备,很快,我们便可在屏幕上看到 3 个未配网设备。 + +.. figure:: ../../../_static/ble-mesh-scanner.png + :align: center + :height: 370 + + nRF Mesh - 扫描 + +4.2 识别 +^^^^^^^^^^^^ + +用户可以选择任何一个未配网设备,此时 App 会尝试和该设备建立连接。连接成功(有时可能需要尝试多次),且发现相应的 ESP-BLE-MESH GATT 服务后,用户可以在屏幕中看到识别按钮 **IDENTIFY**。IDENTIFY 操作告诉用户哪个设备将被配网。 + +.. note:: + IDENTIFY 需要设备侧的支持,然后才能用来识别当前正在配网的设备。当前如果点击识别按钮 **IDENTIFY**,除了串口输出的 log,在当前的 example 中设备侧不会有其他现象。 + +点击识别按钮 **IDENTIFY** 后,用户可以看到配网按钮 **PROVISION**。 + +.. figure:: ../../../_static/ble-mesh-identify-provision.png + :align: center + :height: 370 + + nRF Mesh - 识别 - 配网 + +4.3 配网 +^^^^^^^^^ + +点击配网按钮 **PROVISION**,App 会开始配网设备。当设备配网成功后,开发板上的 RGB 灯会熄灭,此时 App 会执行以下几个步骤: + +1. 和该节点(设备配网后成为节点)断开连接 +2. 尝试和该节点重新建立连接 +3. 连接成功并且发现了相应的 ESP-BLE-MESH GATT 服务 +4. 获取节点的 Composition Data 并且给该节点添加 AppKey + +当以上所有的步骤完成后,节点初始配置完成. 此时点击 **OK**,用户可以看见节点的单播地址分配成功,并且其 Composition Data 也被成功解析. + +.. figure:: ../../../_static/ble-mesh-config-complete.png + :align: center + :height: 370 + + nRF Mesh - 配置完成 + +有时在上述步骤 2 中,App 可能与节点连接失败。这种情况下,用户点击 **OK** 后可以看到,节点只有单播地址被成功分配,Composition data 并没有被成功获取。此时用户需要点击右上角的连接按钮 **CONNECT**,屏幕上会显示原先配网的节点,用户需要选择该节点并与其建立连接。 + +.. figure:: ../../../_static/ble-mesh-initial-config-fail.png + :align: center + :height: 370 + + nRF Mesh - 初始配置失败 + +连接成功后,App 会显示获取 Composition Data 以及添加 AppKey 的按钮。 + +.. figure:: ../../../_static/ble-mesh-reconnect-initial-config.png + :align: center + :height: 370 + + nRF Mesh - 重连 - 初始配置 + +如果该设备是 App 配网的第二个或第三个节点,此时点击连接按钮 **CONNECT** 后,用户可以在屏幕中看到 2 个或 3 个节点。这种情况下,用户可以选择其中的任何一个节点建立连接,连接成功后可以返回至主界面选择需要配置的节点。 + +这里给出了一个 3 个节点的示例。 + +* 左侧图片表示第三个设备成功配网,但是 App 没有和其成功建立连接。当 App 尝试去重新连接第三个节点时,界面上会出现 3 个节点。 +* 右侧图片表示和节点成功建立连接后,App 显示这 3 个节点的信息。用户可以看到 App 已经获取了第一个和第二个节点的 Composition Data,但是对于第三个节点,只有单播地址被成功分配而节点的 Composition Data 未知。 + +.. figure:: ../../../_static/ble-mesh-reconnect-three.png + :align: center + :height: 370 + + nRF Mesh - 重连 - 3 个节点 + +4.4 配置 +^^^^^^^^^ + +当成功配网和初始配置完成后,用户可以配置节点的其余信息,例如将 AppKey 绑定至每个元素 (element) 的每个模型 (model) 中、设置模型的发布信息等。 + +下图展示了如何将 AppKey 绑定至 Primary Element 中的 Generic OnOff Server Model 上。 + +.. figure:: ../../../_static/ble-mesh-model-bind-appkey.png + :align: center + :height: 370 + + nRF Mesh - Model Bind AppKey + +.. note:: + + 用户不需要将 AppKey 绑定至 Configuration Server Model,因为该模型使用 DevKey 在 Upper Transport Layer 中对消息进行加密。 + +Step 5. 运行网络 +""""""""""""""""" + +当 3 个元素中的 Generic OnOff Server Models 均成功绑定 AppKey 后,用户可以使用 App 开关 RBG 灯。 + +在 :example:`bluetooth/esp_ble_mesh/ble_mesh_node` 示例中,第一个 Generic OnOff Server Model 用来控制 **红色**,第二个用来控制 **绿色**,同时第三个用来控制 **蓝色**. + +.. figure:: ../../../_static/ble-mesh-generic-onoff.png + :align: center + :height: 370 + + nRF Mesh - 通用开关控制 + +下图展示了打开了不同色灯的开发板。 + +.. figure:: ../../../_static/ble-mesh-three-nodes-on.png + :align: center + + 3 个上电的 ESP-BLE-MESH 节点 + +.. note:: + 对于 **nRF Mesh** iOS App [version 1.0.4],当节点包含超过一个元素时,App 表现不正确。如果用户尝试打开或者关闭第 2 个或第 3 个 Generic OnOff Server Model,App 会将相应的消息发送至第 1 个 Generic OnOff Server Model。 + + +.. _esp-ble-mesh-examples: + +ESP-BLE-MESH 示例 +=================== + +* :example:`ESP-BLE-MESH 节点 ` - 展示了将 ESP-BLE-MESH 作为拥有 Configuration Server model 和 Generic OnOff Server model 的节点设备的用法。然后,ESP-BLE-MESH Provisioner 可以配网设备,控制表示开/关状态的 RGB 灯,示例请见 :example:`example code `。 + +* :example:`ESP-BLE-MESH 客户端模型 ` - 展示了 Generic OnOff Client model 如何在节点内工作。节点拥有 Configuration Server model、Generic OnOff Server model 和 Generic OnOff Client model,示例请见::example:`example code `。 + +* :example:`ESP-BLE-MESH Provisioner ` - 展示了设备如何充当 ESP-BLE-MESH Provisioner 以配网设备。Provisioner 拥有 Configuration Server model、Configuration Client model 和 Generic OnOff Client model,示例请见 :example:`example code `。 + +* ESP-BLE-MESH 快速配网 - :example:`Client ` 和 :example:`Server ` - 该示例用于演示快速配网。配网 100 个设备费时不超过 60 秒,示例请见::example:`example client code ` 和 :example:`example server code `。 + +* :example:`Wi-Fi 和 ESP-BLE-MESH 共存 ` - 该示例用于演示 Wi-Fi 和 ESP-BLE-MESH 共存的功能。简而言之,用户可在运行 ESP-BLE-MESH 时使用 Wi-Fi,示例请见 :example:`example code `。 + +* ESP-BLE-MESH 节点控制台 - 该演示实现 ESP-BLE-MESH 节点的基本功能。在演示中,Provisioner 可以扫描、验证节点,节点可以回复 Provisioner 的获取/设置消息,示例请见::example:`example node code ` 和 :example:`example provisioner code `。 + +.. _esp-ble-mesh-demo-videos: + +ESP-BLE-MESH 演示视频 +====================== + +* `Provisioning of ESP-BLE-MESH nodes using Smartphone App `_ +* `Espressif Fast Provisioning using ESP-BLE-MESH App `_ +* `Espressif ESP-BLE-MESH and Wi-Fi Coexistence `_ + + +ESP-BLE-MESH 常见问题手册 +========================= + +* :ref:`ble-mesh-faq-provisioner-development` +* :ref:`ble-mesh-faq-node-development` +* :ref:`ble-mesh-faq-ble-mesh-and-wi-fi-coexistence` +* :ref:`ble-mesh-faq-fast-provisioning` +* :ref:`ble-mesh-faq-log-help` +* :ref:`ble-mesh-faq-example-help` +* :ref:`ble-mesh-faq-others` + + +相关文档 +======== + +.. toctree:: + :maxdepth: 1 + + ble-mesh-feature-list + ble-mesh-architecture + ble-mesh-faq + ble-mesh-terminology + + + +蓝牙 SIG 文档 +------------- + +- `BLE Mesh Profile Specification `_ +- `BLE Mesh Model Specification `_ +- `An Intro to Bluetooth Mesh Part 1 `_ / `Part 2 `__ +- `The Fundamental Concepts of Bluetooth Mesh Networking, Part 1 `_ / `Part 2 `__ +- `Bluetooth Mesh Networking: Friendship `_ +- `Management of Devices in a Bluetooth Mesh Network `_ +- `Bluetooth Mesh Security Overview `_ +- `Provisioning a Bluetooth Mesh Network Part 1 `_ / `Part 2 `__ + + +.. _ESP32-DevKitC: https://www.espressif.com/en/products/hardware/esp32-devkitc/overview +.. _ESP-WROVER-KIT: https://www.espressif.com/en/products/hardware/esp-wrover-kit/overview diff --git a/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-terminology.rst b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-terminology.rst new file mode 100644 index 0000000000..8f89cbb840 --- /dev/null +++ b/docs/zh_CN/api-guides/esp-ble-mesh/ble-mesh-terminology.rst @@ -0,0 +1,221 @@ +ESP-BLE-MESH Terminology +======================== + +.. _ble-mesh-terminology-role: + +:link_to_translation:`en:[English]` + +.. list-table:: 表 1 ESP-BLE-MESH 术语 - 身份 + :widths: 10 40 60 + :header-rows: 1 + + * - 术语 + - 官方定义 + - 详细说明 + * - 未配网设备 + - "A device that is not a member of a mesh network is known as an unprovisioned device." + - 示例:照明装置、温控设备、制造设备和电动门等。 + * - 节点 + - "A node is a provisioned device." + - 经配网加入 ESP-BLE-MESH 网络后,未配网设备的身份转变成节点。节点(如照明装置、温控设备、制造设备和电动门)是指能在蓝牙 ESP-BLE-MESH 网络中发送、接收或中继消息的设备,且节点可以选择性地支持一个或多个子网。 + * - 中继节点 + - "A node that supports the Relay feature and has the Relay feature enabled is known as a Relay node." + - 中继节点接收并中继 ESP-BLE-MESH 消息,因此消息可以传输得更远。用户可以根据节点的状态来决定是否使能节点的中继功能。消息可以中继多次,每次中继为“一跳”,消息最多可有 126 跳,足以让消息在广阔的区域内传输。 + * - 代理节点 + - "A node that supports the Proxy feature and has the Proxy feature enabled is known as a Proxy node." + - 代理节点从一个承载层(通常包括广播承载层和 GATT 承载层)接收消息,并通过另一个承载层重新发送消息。其目的是将只支持 GATT 承载层的通讯设备接入到 ESP-BLE-MESH 网络中。通常而言,手机 App 需要一个代理节点才能接入 Mesh 网络。没有代理节点,手机 App 无法与 Mesh 网络中成员通信。 + * - 好友节点 + - "A node that supports the Friend feature, has the Friend feature enabled, and has a friendship with a node that supports the Low Power feature is known as a Friend node." + - 好友节点相当于低功耗节点(LPN)的备份,可存储发往低功耗节点的消息及安全更新信息;当低功耗节点需要这些存储的信息时,这些信息便会被传输至低功耗节点。低功耗节点必须与支持好友特性的另一节点建立“友谊”,以减少其接收器的占空比,从而降低低功耗节点的功耗。低功耗节点需要找到好友节点,与其建立友谊关系,其中涉及的过程称为“友谊建立”。低功耗节点与好友节点的搭配可让低功耗节点规划对无线电的使用,从而以适当或较低的频率接收消息,无需保持收听状态。低功耗节点会轮询好友节点以查看是否有新的消息。 + * - 低功耗节点 + - "A node that supports the Low Power feature and has a friendship with a node that supports the Friend feature is known as a Low Power node." + - 低功耗节点通过轮询从好友节点获取信息,比如消息、安全更新等。 + * - 启动配置设备(以下称为 Provisioner) + - "A node that is capable of adding a device to a mesh network." + - 能够配网未配网设备的设备称为启动配置设备。这一流程通常需要通过产品制造商的提供的 App 来实现,并可在网关、智能手机、平板电脑和其他载体上使用。 + + +.. _ble-mesh-terminology-composition: + +.. list-table:: 表 2 ESP-BLE-MESH 术语 - 节点构成 + :widths: 10 40 60 + :header-rows: 1 + + * - 术语 + - 官方定义 + - 详细说明 + * - 状态 + - "A value representing a condition of an element that is exposed by an element of a node." + - ESP-BLE-MESH 网络中的每台设备都具有一组独立的状态值,表示设备的某些状态,比如照明设备的亮度、颜色等状态。更改状态值会修改设备本身的物理状态,比如更改设备的开关状态值实际是在打开或关闭设备。 + * - 模型 + - "A model defines the basic functionality of a node." + - 一个节点可能包含多个模型,而每个模型定义了节点的基本功能,比如节点所需要的状态、控制状态的消息以及处理消息所产生的动作等。节点功能的实现是基于模型的,模型可分为 SIG 模型和自定义模型,前者由 SIG 定义,而后者由用户定义。 + * - 元素 + - "An addressable entity within a device." + - 一个节点可以包含一个或多个元素,每个元素都有一个单播地址和一个或多个模型,并且同一元素所包含的模型不可以出现重复。 + * - 节点构成状态 + - "The Composition Data state contains information about a node, the elements it includes, and the supported models." + - 通过读取节点构成状态的值,用户可以了解节点的基本信息,比如元素的数量及每个元素中的模型。Provisioner 通过获取这个消息对设备进一步配置,比如配置节点的订阅地址与发布地址。 + + +.. _ble-mesh-terminology-features: + +.. list-table:: 表 3 ESP-BLE-MESH 术语 - 特性 + :widths: 10 40 60 + :header-rows: 1 + + * - 术语 + - 官方定义 + - 详细说明 + * - 低功耗特性 + - "The ability to operate within a mesh network at significantly reduced receiver duty cycles only in conjunction with a node supporting the Friend feature." + - 低功耗功能可降低节点的功耗。当低功耗节点寻找好友节点、且附近有多个好友节点时,它会通过算法选择最适合的好友节点。 + * - 好友特性 + - "The ability to help a node supporting the Low Power feature to operate by storing messages destined for those nodes." + - 通过使能好友特性,节点可以帮助存储低功耗节点的信息。使能了好友特性的节点可能会产生更大的功耗和内存消耗。 + * - 中继特性 + - "The ability to receive and retransmit mesh messages over the advertising bearer to enable larger networks." + - 中继特性能让 ESP-BLE-MESH 的消息在节点之间实现多次跳跃,传输距离可超过两个节点之间直接进行无线电传输的范围,从而覆盖整个网络。使能了中继特性的节点中继消息时,只中继其所在子网的消息,不中继其它子网的消息。使能了中继特性的节点中继分段消息时不考虑数据的完整性。节点每收到一条分段消息便直接中继,不会等待收到完整的消息。 + * - 代理特性 + - "The ability to receive and retransmit mesh messages between GATT and advertising bearers." + - 代理特性的目的是允许不具备广播承载层的节点访问 ESP-BLE-MESH 网络。代理特性通常为需要和手机 App 连接的节点所用。 + + +.. _ble-mesh-terminology-provisioning: + +.. list-table:: 表 4 ESP-BLE-MESH 术语 - 配置入网 + :widths: 10 40 60 + :header-rows: 1 + + * - 术语 + - 官方定义 + - 详细说明 + * - PB-ADV + - "PB-ADV is a provisioning bearer used to provision a device using Generic Provisioning PDUs over the advertising channels." + - PB-ADV 通过广播通道传输配网过程中产生的数据包。只有 Provisioner 和未配网设备都支持 PB-ADV 时才可使用这种方法进行配网。 + * - PB-GATT + - "PB-GATT is a provisioning bearer used to provision a device using Proxy PDUs to encapsulate Provisioning PDUs within the Mesh Provisioning Service." + - PB-GATT 通过连接通道传输配网过程中产生的数据包。如果未配网设备想使用此方式进行配网,其需要实现相关的 Mesh Provisioning Service。未实现此服务的未配网设备不能通过 PB-GATT 承载层配网接入 mesh 网络。 + * - 配置入网 + - "Provisioning is a process of adding an unprovisioned device to a mesh network, managed by a Provisioner." + - 经过配网,“未配网设备”的身份转变为“节点”,成为 ESP-BLE-MESH 网络中的一员。 + * - 认证方式 + - "Authentication is a step during the provisioning of nodes." + - 未配网设备有四种认证方法:输入带外 (Input OOB)、输出带外 (Output OOB)、静态带外 (Static OOB) 和无带外 (No OOB)。 + * - 输入带外 (Input OOB) + - Input Out-of-Band + - 比如,Provisioner 生成并显示随机数,然后提示用户采取适当操作将随机数输入未配网的设备中。以照明开关为例,用户可以在一定时间内数次按下按钮,以这种形式输入 Provisioner 端显示的随机数。输入带外认证方法与输出带外的认证方法类似,但设备的角色相反。 + * - 输出带外 (Output OOB) + - Output Out-of-Band + - 比如,未配网设备会选择一个随机数,并通过与其功能兼容的方式输出该数字。如果未配网设备是一个灯泡,则其能够闪烁指定的次数。如果未配网设备有 LCD 屏幕,则可以将随机数显示为多位数值。启动 Provisioner 的用户需要输入观察到的数字,来认证未配网的设备。 + * - 静态带外 (Static OOB) + - Static Out-of-Band + - 静态 OOB 的认证方法:使用静态 OOB 信息。如果需要使用无 OOB 信息,请将静态 OOB 字段赋值为 0。如果需要使用 OOB 信息,请使用静态 OOB 信息认证正在配网的设备。 + * - 无带外 (No OOB) + - No Out-of-Band + - 无 OOB 的认证方法:将“静态 OOB”字段赋值为 0。采用这种方式相当于不认证未配网的设备。 + + +.. _ble-mesh-terminology-address: + +.. list-table:: 表 5 ESP-BLE-MESH 术语 - 地址 + :widths: 10 40 60 + :header-rows: 1 + + * - 术语 + - 官方定义 + - 详细说明 + * - 未分配地址 + - "This is a special address type, with a value of 0x0000. Its use indicates that an Element has not yet been configured or had a Unicast Address assigned to it." + - 未配置的元素地址或未分配的元素地址都称为未分配地址。鉴于这些元素没有固定的地址,它们不会用于消息的传输。建议在设置用户代码的地址之前,将该地址的值设为未分配地址。 + * - 单播地址 + - "A unicast address is a unique address allocated to each element." + - 在配网期间,Provisioner 会给网络中处于生命周期内节点的每个元素分配一个单播地址。单播地址可能会出现在消息的源/目标地址字段中。发送到单播地址的消息只能由拥有该单播地址的元素进行处理。 + * - 虚拟地址 + - "A virtual address represents a set of destination addresses. Each virtual address logically represents a Label UUID, which is a 128-bit value that does not have to be managed centrally." + - 虚拟地址与特定的 UUID 标签相关联,可以用作模型的发布地址或订阅地址。UUID 标签是与一个或多个节点的元素相关联的 128 位值。虚拟地址的第 15 位和第 14 位分别设置为 1 和 0。从第 13 位到第 0 位设置为散列值(提供 16384 个散列值)。散列是 UUID 标签的派生。使用订阅元素检查完整的 128 位 UUID 十分低效,而散列值提供了一种更有效的方法来确定最终将哪些消息发送到哪些元素。 + * - 群组地址 + - "A group address is an address that is programmed into zero or more elements." + - 群组地址是 ESP-BLE-MESH 网络中的另一种多播地址,通常用于将节点进行分组。发送到 all-proxies 地址的信息应由启用了代理功能的所有节点的主要元素处理。 发送到 all-friends 地址的消息应由启用了好友功能的所有节点的主要元素处理。 发送到 all-relays 地址的消息应由启用了中继功能的所有节点的主要元素处理。 发送到 all-nodes 地址的消息应由所有节点的主要元素处理。 + + +.. _ble-mesh-terminology-security: + +.. list-table:: 表 6 ESP-BLE-MESH 术语 - 安全 + :widths: 10 40 60 + :header-rows: 1 + + * - 术语 + - 官方定义 + - 详细说明 + * - 设备密钥 (DevKey) + - "There is also a device key, which is a special application key that is unique to each node, is known only to the node and a Configuration Client, and is used to secure communications between the node and a Configuration Client." + - 设备密钥让您能够配网未配网设备、配置节点。设备密钥用来加密配置信息,即配置设备时 Provisioner 和节点之间传输的消息。 + * - 应用密钥 (AppKey) + - "Application keys are used to secure communications at the upper transport layer." + - 应用密钥用于应用数据传递至应用层过程中对应用数据的解密,和应用层下发过程中对数据的加密。网络中的一些节点有特定的用途,并且可以根据应用程序的需求对一些潜在敏感数据的访问进行限制。通过特定的应用密钥,这些节点与特定应用程序相关联。通常而言,使用不同应用密钥的领域有安全(楼宇门禁、机房门禁和 CEO 办公室门禁)、照明(工厂、外部楼宇和人行道)和 HVAC 系统。应用密钥绑定在网络密钥上,这意味着应用密钥仅在绑定网络密钥的情况下使用。每一个应用密钥仅可绑定到一个网络密钥。 + * - 主安全资料 + - "The master security material is derived from the network key (NetKey) and can be used by other nodes in the same network. Messages encrypted with master security material can be decoded by any node in the same network. " + - 使用好友安全材料加密的相应友谊消息有:1. 好友轮询 (Friend Poll),2. 好友更新 (Friend Update),3. 好友订阅列表 (Friend Subscription List),添加/删除/确认,4. 好友节点发送到低功耗节点的“已存储消息”,使用主安全材料加密的相应友谊消息有:1. 好友清除 (Friend Clear),2. 好友清除确认 (Friend Clear Confirm)。根据应用程序的设置,从低功耗节点发送到好友节点的消息会使用友谊安全材料或主安全材料进行加密,前者用于低功耗节点与好友节点之间的消息传输,而后者用于其他网络消息。 + + +.. _ble-mesh-terminology-message: + +.. list-table:: 表 7 ESP-BLE-MESH 术语 - 消息 + :widths: 10 40 60 + :header-rows: 1 + + * - 术语 + - 官方定义 + - 详细说明 + * - 重组 / 分包 + - "Segmentation and reassembly (SAR) is a method of communication network, which is divided into small units before transmitting packets and reassembled in a proper order at the communication receiving end." + - 底层传输层会自动分包过大的消息。接收端会回复一条应答消息,根据应答消息,发送端会重新向接收端发送其未接收到的数据包。这些都是底层传输层自动完成的。未分包的消息最多携带 15 个字节,其中 4 个字节是 transMIC,所以剩余 11 个字节;在分包的情况下,前面的包中每包有 12 个有效字节,最后一个包中有 8 个有效字节。特殊情况:一个较短的包需要底层传输端强制分包,这种情况下有 8 个有效字节。 + * - 无应答 / 有应答 + - "There are two types of messages: Unacknowledged or Acknowledged." + - 根据接收端是否需要发送应答消息,发送的消息可分为两种。发送端需要设置最大重传次数。 + +.. _ble-mesh-terminology-foundation-models: + +.. list-table:: 表 8 ESP-BLE-MESH 术语 - 基础模型 + :widths: 10 40 60 + :header-rows: 1 + + * - 术语 + - 官方定义 + - 详细说明 + * - Configuration Server Model + - "This model is used to represent a mesh network configuration of a device." + - 节点必须包含 Configuration Server Model,其负责维护配置相关的状态。Configuration Server Model 维护的状态包含:网络密钥名单 (NetKey List)、应用密钥名单 (AppKey List)、模型绑定的应用密钥名单 (Model to AppKey List)、节点身份 (Node Identity)、密钥更新阶段 (Key Refresh Phase)、心跳消息发布 (Heartbeat Publish)、心跳消息订阅 (Heartbeat Subscription)、网络传输 (Network Transmit) 和中继重传 (Relay Retransmit) 等。 + * - Configuration Client Model + - "The model is used to represent an element that can control and monitor the configuration of a node." + - Configuration Client Model 通过消息控制 Configuration Server Model 维护的状态。Provisioner 必须包含 Configuration Client Model,有了该模型才可发送 "Configuration Composition Data Get" 等配置消息。 + * - Health Server Model + - "This model is used to represent a mesh network diagnostics of a device." + - Health Server Model 主要用于设备检查自身状态,查看自身是否发生错误。Health Server model 维护的状态包含:当前故障 (Current Fault)、已登记故障 (Registered Fault)、健康周期 (Health Period) 和关注计时器 (Attention Timer)。 + * - Health Client Model + - "The model is used to represent an element that can control and monitor the health of a node." + - Health Client Model 通过消息控制 Health Server Model 维护的状态。该模型可通过消息 “Health Fault Get” 获取其他节点的自检信息。 + + +.. _ble-mesh-terminology-network-management: + +.. list-table:: 表 9 ESP-BLE-MESH 术语 - 网络管理 + :widths: 10 40 60 + :header-rows: 1 + + * - 术语 + - 官方定义 + - 详细说明 + * - 密钥更新程序 + - "This procedure is used when the security of one or more network keys and/or one or more of the application keys has been compromised or could be compromised." + - 密钥更新程序用于更新 ESP-BLE-MESH 网络的网络密钥和应用密钥。当一个或多个网络密钥和/或一个或多个应用密钥的安全受到威胁或可能受到威胁时,会启动密钥更新程序。通常而言,在网络中某些节点移除后可以进行密钥更新。 + * - IV 更新程序 + - "A node can also use an IV Update procedure to signal to peer nodes that it is updating the IV Index." + - IV 更新程序用于更新 ESP-BLE-MESH 网络的 IV Index 的值,这个值和消息加密时所需的随机数相关。为了保证随机数的值不重复,所以将这个值定期增加。IV Index 是一个 32 位的值,是一种共享网络资源,比如一个 mesh 网中的所有节点共享一个 IV Index 值。IV Index 从 0x00000000 开始,在 IV 更新过程中递增,并由特定的进程维护,以保证整个 Mesh 网内共享一个 IV Index。当节点认为它有耗尽其序列号的风险,或它确定另一个节点即将耗尽其序列号时,可以启动该程序。注意:每次的更新时间不得低于 96 小时。节点接收到 secure network beacon 或者确定自己的序列号大于特定值时,会触发 IV 更新程序。 + +官方定义摘自 `ESP-BLE-MESH Glossary of Terms `_. +查看更多术语,也请参照上述网址。 + + + diff --git a/docs/zh_CN/api-guides/index.rst b/docs/zh_CN/api-guides/index.rst index bc205d50ee..5e24c44dda 100644 --- a/docs/zh_CN/api-guides/index.rst +++ b/docs/zh_CN/api-guides/index.rst @@ -30,6 +30,7 @@ API 指南 RF Calibration WiFi Driver ESP-MESH + ESP-BLE-MESH BluFi External SPI-connected RAM 链接脚本生成机制 diff --git a/docs/zh_CN/api-reference/bluetooth/esp-ble-mesh.rst b/docs/zh_CN/api-reference/bluetooth/esp-ble-mesh.rst new file mode 100644 index 0000000000..d27fa24f90 --- /dev/null +++ b/docs/zh_CN/api-reference/bluetooth/esp-ble-mesh.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/bluetooth/esp-ble-mesh.rst \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/README.md b/examples/bluetooth/esp_ble_mesh/README.md new file mode 100644 index 0000000000..aa69e954a0 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/README.md @@ -0,0 +1,78 @@ +# ESP-BLE-MESH Examples + +[ESP-BLE-MESH]($IDF_PATH/components/bt/esp_ble_mesh/) is the official Bluetooth® Mesh stack of Espressif Systems. We will provide long-term support for new features, performance optimization, etc. + +Please help note that breaking changes may be introduced into ESP-BLE-MESH on [minor IDF versions](https://docs.espressif.com/projects/esp-idf/en/latest/versions.html). + +Note: To use examples in this directory, you need to have Bluetooth enabled in configuration, and either Bluedroid or NimBLE can be selected as the host stack. + +# Example Layout + +This directory includes examples to demonstrate ESP-BLE-MESH functionality based on [Zephyr Bluetooth Mesh stack](https://github.com/zephyrproject-rtos/zephyr/tree/master/subsys/bluetooth/mesh). + +## ble_mesh_console + +This example demonstrates how ESP-BLE-MESH uses Console for message transmitting/receiving tests. + +#### ble_mesh_node + +This example shows how ESP32 acts as a BLE Mesh Node and sends vendor messages for testing. + +See [ble_mesh_node](ble_mesh_console/ble_mesh_node) folder for more details. + +#### ble_mesh_provisioner + +This example shows how ESP32 acts as a BLE Mesh Provisioner and sends vendor messages for testing. + +See [ble_mesh_provisioner](ble_mesh_console/ble_mesh_provisioner) folder for more details. + +## ble_mesh_fast_provision + +This example illustrates the solution of ESP-BLE-MESH Fast Provisioning. + +#### ble_mesh_fast_prov_client + +This example shows how ESP32, acting as a BLE Mesh Fast Provisioning Client, provisions other unprovisioned devices and then controls the nodes. + +See [ble_mesh_fast_prov_client](ble_mesh_fast_provision/ble_mesh_fast_prov_client) folder for more details. + +#### ble_mesh_fast_prov_server + +This example illustrates the process that: +1. ESP32 as a BLE Mesh Fast Provisioning Server is provisioned into a node; +2. ESP32 as a Temporary Provisioner provisions other unprovisioned devices. + +See [ble_mesh_fast_prov_server](ble_mesh_fast_provision/ble_mesh_fast_prov_server) folder for more details. + +## ble_mesh_node + +This example demonstrates how ESP32 acts as a BLE Mesh node with Generic OnOff Server model or Generic OnOff Client model on board. + +#### onoff_client + +This example shows how ESP32 acts as a BLE Mesh Node with Generic OnOff Client model in the Primary Element. + +See [onoff_client](ble_mesh_node/onoff_client) folder for more details. + +#### onoff_server + +This example shows how ESP32 acts as a BLE Mesh Node with only Generic OnOff Server model in the Primary Element. + +See [onoff_server](ble_mesh_node/onoff_server) folder for more details. + +## ble_mesh_provisioner + +This example shows how ESP32 acts as a BLE Mesh Provisioner and provisions other unprovisioned devices. + +See [ble_mesh_provisioner](ble_mesh_provisioner) folder for more details. + +## ble_mesh_wifi_coexist + +This example shows how ESP32 acts as a BLE Mesh Fast Provisioning Server and coexists with Wi-Fi iperf functionality. + +See [ble_mesh_wifi_coexist](ble_mesh_wifi_coexist) folder for more details. + +# More + +See the [README.md](../../README.md) file in the upper level [examples](../../) directory for more information about examples. + diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/CMakeLists.txt new file mode 100644 index 0000000000..74361e558c --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/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_node) diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/Makefile b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/Makefile new file mode 100644 index 0000000000..209aa2e0cc --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/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_node + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/README.md b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/README.md new file mode 100644 index 0000000000..0bb260c816 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/README.md @@ -0,0 +1,9 @@ +# ble mesh node console demo +## Introduction +This demo implements ble mesh node basic features.Based on this demo, node can be scaned and proved by provisioner, reply get/set message to provisioner. + +Demo steps: +1. Build the ble mesh node console demo with sdkconfig.default +2. register node and set oob info, load model to init ble mesh node +3. enable bearer, so that it can be scaned and provisioned by provisioner + diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/CMakeLists.txt new file mode 100644 index 0000000000..2275f9a4e0 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/CMakeLists.txt @@ -0,0 +1,12 @@ +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_register_node_cmd.c" + "ble_mesh_register_server_cmd.c" + "register_bluetooth.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.c new file mode 100644 index 0000000000..b8d1775b34 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.c @@ -0,0 +1,164 @@ +// Copyright 2017-2019 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_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[0]; + break; + case ESP_BLE_MESH_MODEL_ID_CONFIG_CLI: + model = &config_client_models[0]; + break; + 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[1]; + 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(void) +{ + 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 init fail, mesh node semaphore create fail", __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_node_statistics_get(void) +{ + xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY); + ESP_LOGI(TAG, "statistics:%d,%d\n", ble_mesh_node_statistics.statistics, ble_mesh_node_statistics.package_num); + xSemaphoreGive(ble_mesh_node_sema); +} + +int ble_mesh_node_statistics_accumultate(uint8_t *data, uint32_t value, uint16_t type) +{ + uint16_t i; + uint16_t sequence_num = (data[0] << 8) | data[1]; + + xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY); + for (i = 0; i < ble_mesh_node_statistics.total_package_num; i++) { + if (ble_mesh_node_statistics.package_index[i] == sequence_num) { + xSemaphoreGive(ble_mesh_node_sema); + return 1; + } + } + + // package type wrong + if (data[2] != type) { + xSemaphoreGive(ble_mesh_node_sema); + return 1; + } + + for (i = 0; i < ble_mesh_node_statistics.total_package_num; i++) { + if (ble_mesh_node_statistics.package_index[i] == 0) { + ble_mesh_node_statistics.package_index[i] = sequence_num; + ble_mesh_node_statistics.package_num += 1; + ble_mesh_node_statistics.statistics += value; + break; + } + } + xSemaphoreGive(ble_mesh_node_sema); + return 0; +} + +int ble_mesh_node_statistics_init(uint16_t package_num) +{ + uint16_t i; + + ble_mesh_node_statistics.package_index = malloc(sizeof(uint16_t) * package_num); + ble_mesh_node_statistics.total_package_num = package_num; + if (ble_mesh_node_statistics.package_index == NULL) { + ESP_LOGE(TAG, " %s, %d malloc fail\n", __func__, __LINE__); + return 1; + } + + ble_mesh_node_statistics.package_num = 0; + for (i = 0; i < package_num; i++) { + ble_mesh_node_statistics.package_index[i] = 0; + } + return 0; +} + +void ble_mesh_node_statistics_destroy(void) +{ + if (ble_mesh_node_statistics.package_index != NULL) { + free(ble_mesh_node_statistics.package_index); + } +} diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.h new file mode 100644 index 0000000000..d7feee5fe4 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_adapter.h @@ -0,0 +1,97 @@ +// Copyright 2017-2019 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_node_console" + +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 { + uint32_t statistics; + uint32_t package_num; + uint16_t *package_index; + uint32_t total_package_num; +} ble_mesh_node_statistics_t; +ble_mesh_node_statistics_t ble_mesh_node_statistics; + +extern SemaphoreHandle_t ble_mesh_node_sema; + +#define arg_int_to_value(src_msg, dst_msg, message) do { \ + if (src_msg->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); +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(void); +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(void); + +#endif //_BLE_MESH_ADAOTER_H_ diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.c new file mode 100644 index 0000000000..91d8fe3969 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.c @@ -0,0 +1,208 @@ +// Copyright 2017-2019 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_SERVER) + .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/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.h new file mode 100644 index 0000000000..eba953c4ec --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_cfg_srv_model.h @@ -0,0 +1,107 @@ +// Copyright 2017-2019 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/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_decl.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_decl.h new file mode 100644 index 0000000000..2e983265d3 --- /dev/null +++ b/examples/bluetooth/esp_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(void); + +// Register blutooth +void register_bluetooth(void); + +// Register mesh node cmd +void ble_mesh_register_mesh_node(void); + +// Register mesh config server and generic server operation cmd +void ble_mesh_register_server(void); + +#if (CONFIG_BLE_MESH_CFG_CLI) +// Register mesh config client operation cmd +void ble_mesh_register_configuration_client_model(void); +#endif diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.c new file mode 100644 index 0000000000..efcb6766a7 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.c @@ -0,0 +1,128 @@ +// Copyright 2017-2019 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/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.h new file mode 100644 index 0000000000..f0e2e77c97 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_lib.h @@ -0,0 +1,31 @@ +// Copyright 2017-2019 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/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_main.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_main.c new file mode 100644 index 0000000000..25857bc96d --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_main.c @@ -0,0 +1,215 @@ +// Copyright 2017-2019 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(void) +{ + 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(void) +{ + /* 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/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_system.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_console_system.c new file mode 100644 index 0000000000..88c0a6a0a9 --- /dev/null +++ b/examples/bluetooth/esp_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(void); +static void register_restart(void); +static void register_make(void); + +void register_system(void) +{ + 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(void) +{ + 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(void) +{ + 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_node_cmd.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_node_cmd.c new file mode 100644 index 0000000000..375c224c6c --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_node_cmd.c @@ -0,0 +1,547 @@ +// Copyright 2017-2019 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(void); +// 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(void) +{ + ble_mesh_register_node_cmd(); +} + +int ble_mesh_register_node_cb(int argc, char** argv) +{ + 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(int argc, char** argv) +{ + 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_server_cmd.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_server_cmd.c new file mode 100644 index 0000000000..6fcb41d4f5 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/ble_mesh_register_server_cmd.c @@ -0,0 +1,83 @@ +// Copyright 2017-2019 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(void); + +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(void) +{ + 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/component.mk b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/component.mk new file mode 100644 index 0000000000..0b9d7585e7 --- /dev/null +++ b/examples/bluetooth/esp_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/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/register_bluetooth.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/register_bluetooth.c new file mode 100644 index 0000000000..64358ba606 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/main/register_bluetooth.c @@ -0,0 +1,45 @@ +// Copyright 2017-2019 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); + +void register_bluetooth(void) +{ + 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(void) +{ + const esp_console_cmd_t cmd = { + .command = "btmac", + .help = "get BT mac address", + .hint = NULL, + .func = &bt_mac, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} + diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/sdkconfig.defaults b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/sdkconfig.defaults new file mode 100644 index 0000000000..1625e11579 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_node/sdkconfig.defaults @@ -0,0 +1,23 @@ +# 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=n +CONFIG_BTDM_CONTROLLER_MODE_BTDM=n +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR=y +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_GATTS_SEND_SERVICE_CHANGE_MANUAL=y +CONFIG_BTU_TASK_STACK_SIZE=4512 + +# Override some defaults of ESP BLE Mesh +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_NODE=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_CFG_CLI=y + +CONFIG_CONSOLE_UART_BAUDRATE=921600 +CONFIG_ESPTOOLPY_BAUD_921600B=y +CONFIG_MONITOR_BAUD_921600B=y \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/CMakeLists.txt new file mode 100644 index 0000000000..6ce28d7d57 --- /dev/null +++ b/examples/bluetooth/esp_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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/Makefile b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/Makefile new file mode 100644 index 0000000000..6c7b6101fa --- /dev/null +++ b/examples/bluetooth/esp_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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/README.md b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/README.md new file mode 100644 index 0000000000..3925d93cf4 --- /dev/null +++ b/examples/bluetooth/esp_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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/CMakeLists.txt new file mode 100644 index 0000000000..339b93fc95 --- /dev/null +++ b/examples/bluetooth/esp_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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.c new file mode 100644 index 0000000000..eded5787bb --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.c @@ -0,0 +1,300 @@ +// Copyright 2017-2019 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(void) +{ + 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(void) +{ + 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(void) +{ + 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.h new file mode 100644 index 0000000000..305026c1a9 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_adapter.h @@ -0,0 +1,123 @@ +// Copyright 2017-2019 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); +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); +void ble_mesh_test_performance_client_model_get_received_percent(void); +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(void); + +#endif //_BLE_MESH_ADAPTER_H_ \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.c new file mode 100644 index 0000000000..7ff71bb919 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.c @@ -0,0 +1,205 @@ +// Copyright 2017-2019 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_SERVER) + .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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.h new file mode 100644 index 0000000000..eba953c4ec --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_cfg_srv_model.h @@ -0,0 +1,107 @@ +// Copyright 2017-2019 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_decl.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_decl.h new file mode 100644 index 0000000000..3ba4f496db --- /dev/null +++ b/examples/bluetooth/esp_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(void); + +// Register bluetooth +void register_bluetooth(void); + +// Register mesh node cmd +void ble_mesh_register_mesh_node(void); + +// Register Test Perf client cmd +void ble_mesh_register_mesh_test_performance_client(void); + +#if (CONFIG_BLE_MESH_CFG_CLI) +// Register mesh config client operation cmd +void ble_mesh_register_configuration_client_model(void); +#endif + +#if (CONFIG_BLE_MESH_GENERIC_ONOFF_CLI) +// Register mesh client operation cmd +void ble_mesh_register_gen_onoff_client(void); +#endif + +#if CONFIG_BLE_MESH_PROVISIONER +// Regitster mesh provisioner cmd +void ble_mesh_register_mesh_provisioner(void); +#endif diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.c new file mode 100644 index 0000000000..8116935c2c --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.c @@ -0,0 +1,124 @@ +// Copyright 2017-2019 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.h new file mode 100644 index 0000000000..da4d7c20bf --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_lib.h @@ -0,0 +1,29 @@ +// Copyright 2017-2019 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_main.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_main.c new file mode 100644 index 0000000000..4618e14b57 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_main.c @@ -0,0 +1,228 @@ +// Copyright 2017-2019 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(void) +{ + 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(void) +{ + /* 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_system.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_console_system.c new file mode 100644 index 0000000000..de76b11ae7 --- /dev/null +++ b/examples/bluetooth/esp_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(void); +static void register_restart(void); +static void register_make(void); + +void register_system(void) +{ + 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(void) +{ + 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(void) +{ + 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_cfg_client_cmd.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_cfg_client_cmd.c new file mode 100644 index 0000000000..59bae35ced --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_cfg_client_cmd.c @@ -0,0 +1,390 @@ +// Copyright 2017-2019 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); +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(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) +{ + 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_gen_onoff_client_cmd.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_gen_onoff_client_cmd.c new file mode 100644 index 0000000000..f9caa8931b --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_reg_gen_onoff_client_cmd.c @@ -0,0 +1,180 @@ +// Copyright 2017-2019 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); +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(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) +{ + 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_node_cmd.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_node_cmd.c new file mode 100644 index 0000000000..b9bd565c40 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_node_cmd.c @@ -0,0 +1,476 @@ +// Copyright 2017-2019 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(void); +// 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(void) +{ + ble_mesh_register_node_cmd(); +} + +int ble_mesh_register_node_cb(int argc, char** argv) +{ + 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_provisioner_cmd.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_provisioner_cmd.c new file mode 100644 index 0000000000..740d992a0c --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/ble_mesh_register_provisioner_cmd.c @@ -0,0 +1,424 @@ +// Copyright 2017-2019 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); + +void ble_mesh_prov_adv_cb(const esp_ble_mesh_bd_addr_t addr, const esp_ble_mesh_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(void) +{ + ble_mesh_regist_provisioner_cmd(); +} + +void ble_mesh_prov_adv_cb(const esp_ble_mesh_bd_addr_t addr, const esp_ble_mesh_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_ble_mesh_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(int argc, char** argv) +{ + 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; + esp_ble_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 = esp_ble_mesh_provisioner_get_node_with_addr(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 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/component.mk b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/component.mk new file mode 100644 index 0000000000..0b9d7585e7 --- /dev/null +++ b/examples/bluetooth/esp_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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/register_bluetooth.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/register_bluetooth.c new file mode 100644 index 0000000000..129617e7e2 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/main/register_bluetooth.c @@ -0,0 +1,45 @@ +// Copyright 2017-2019 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); + +void register_bluetooth(void) +{ + 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(void) +{ + 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/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/sdkconfig.defaults b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/sdkconfig.defaults new file mode 100644 index 0000000000..fb7e81b60a --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_provisioner/sdkconfig.defaults @@ -0,0 +1,21 @@ +# 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=n +CONFIG_BTDM_CONTROLLER_MODE_BTDM=n +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR=y +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_BTU_TASK_STACK_SIZE=4512 + +# Override some defaults of ESP BLE Mesh +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_PROVISIONER=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_PBA_SAME_TIME=10 +CONFIG_BLE_MESH_PBG_SAME_TIME=3 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_CFG_CLI=y +CONFIG_BLE_MESH_GENERIC_ONOFF_CLI=y \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/CMakeLists.txt new file mode 100644 index 0000000000..5f9bd92c1c --- /dev/null +++ b/examples/bluetooth/esp_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/esp_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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/Makefile b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/Makefile new file mode 100644 index 0000000000..9aff276e25 --- /dev/null +++ b/examples/bluetooth/esp_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/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/README.md b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/README.md new file mode 100644 index 0000000000..d49238e974 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/README.md @@ -0,0 +1,6 @@ +ESP BLE Mesh Fast Provisioning Client example +======================== + +This example shows how a BLE Mesh device functions as a Fast Provisioning Client. + +Please check the [tutorial](tutorial/ble_mesh_fast_provision_client.md) for more information about this example. \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/CMakeLists.txt new file mode 100644 index 0000000000..6644ba29cd --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(COMPONENT_SRCS "ble_mesh_demo_main.c" + "ble_mesh_demo_init.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_init.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_init.c new file mode 100644 index 0000000000..e0471fa185 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_init.c @@ -0,0 +1,143 @@ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +/* BLE */ +#ifdef CONFIG_BLUEDROID_ENABLED +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#endif + +#ifdef CONFIG_BT_NIMBLE_ENABLED +#include "esp_nimble_hci.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "console/console.h" +#endif + +#include "esp_ble_mesh_defs.h" +#include "ble_mesh_demo_init.h" +#include "esp_ble_mesh_common_api.h" + +#ifdef CONFIG_BLUEDROID_ENABLED + +void ble_mesh_get_dev_uuid(uint8_t *dev_uuid) +{ + memcpy(dev_uuid, esp_bt_dev_get_address(), BD_ADDR_LEN); +} + +esp_err_t bluetooth_init(void) +{ + esp_err_t 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; +} + +#endif + +#ifdef CONFIG_BT_NIMBLE_ENABLED +static SemaphoreHandle_t mesh_sem; +static uint8_t own_addr_type; +void ble_store_config_init(void); +static uint8_t addr_val[6] = {0}; + +void ble_mesh_get_dev_uuid(uint8_t *dev_uuid) +{ + memcpy(dev_uuid + 2, addr_val, BD_ADDR_LEN); +} + +static void mesh_on_reset(int reason) +{ + ESP_LOGI(TAG, "Resetting state; reason=%d", reason); +} + +static void mesh_on_sync(void) +{ + int rc; + + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + /* Figure out address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGI(TAG, "error determining address type; rc=%d", rc); + return; + } + + rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL); + + xSemaphoreGive(mesh_sem); +} + +void mesh_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} + +esp_err_t bluetooth_init(void) +{ + mesh_sem = xSemaphoreCreateBinary(); + if (mesh_sem == NULL) { + ESP_LOGE(TAG, "Failed to create mesh semaphore"); + return ESP_FAIL; + } + + ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init()); + + nimble_port_init(); + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = mesh_on_reset; + ble_hs_cfg.sync_cb = mesh_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + /* XXX Need to have template for store */ + ble_store_config_init(); + + nimble_port_freertos_init(mesh_host_task); + + xSemaphoreTake(mesh_sem, portMAX_DELAY); + + return ESP_OK; +} +#endif diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_init.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_init.h new file mode 100644 index 0000000000..d1af154289 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_init.h @@ -0,0 +1,18 @@ +/* + 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. +*/ + +#ifndef _BLE_MESH_DEMO_INIT_H_ +#define _BLE_MESH_DEMO_INIT_H_ + +#define TAG "FAST_PROV_CLIENT_DEMO" + +void ble_mesh_get_dev_uuid(uint8_t *dev_uuid); + +esp_err_t bluetooth_init(void); + +#endif diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_main.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_main.c new file mode 100644 index 0000000000..27e93638dc --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/ble_mesh_demo_main.c @@ -0,0 +1,601 @@ +// Copyright 2017-2019 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_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" +#include "ble_mesh_demo_init.h" + +#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_SERVER) + .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[BLE_MESH_ADDR_LEN], + esp_ble_mesh_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; + + 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_init(&prov, &comp); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to initialize BLE Mesh", __func__); + return ESP_FAIL; + } + + 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_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; +} + +void app_main(void) +{ + esp_err_t err; + + ESP_LOGI(TAG, "Initializing..."); + + err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + err = bluetooth_init(); + if (err) { + ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err); + return; + } + + ble_mesh_get_dev_uuid(dev_uuid); + + /* 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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/main/component.mk b/examples/bluetooth/esp_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/esp_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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/sdkconfig.defaults b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/sdkconfig.defaults new file mode 100644 index 0000000000..d45436ce99 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/sdkconfig.defaults @@ -0,0 +1,21 @@ +# 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=n +CONFIG_BTDM_CONTROLLER_MODE_BTDM=n +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR=y +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_BTU_TASK_STACK_SIZE=4512 + +# Override some defaults of ESP BLE Mesh +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_PROVISIONER=y +CONFIG_BLE_MESH_PBA_SAME_TIME=1 +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_ADV_BUF_COUNT=100 +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_CFG_CLI=y +CONFIG_BLE_MESH_GENERIC_ONOFF_CLI=y \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/tutorial/BLE_Mesh_Fast_Prov_Client_Example_Walkthrough.md b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/tutorial/BLE_Mesh_Fast_Prov_Client_Example_Walkthrough.md new file mode 100644 index 0000000000..60d27304ee --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_client/tutorial/BLE_Mesh_Fast_Prov_Client_Example_Walkthrough.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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/CMakeLists.txt new file mode 100644 index 0000000000..56ef44654e --- /dev/null +++ b/examples/bluetooth/esp_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/esp_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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/Makefile b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/Makefile new file mode 100644 index 0000000000..31160373ff --- /dev/null +++ b/examples/bluetooth/esp_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/esp_ble_mesh/ble_mesh_vendor_models/fast_prov_vendor_model/components + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/README.md b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/README.md new file mode 100644 index 0000000000..e12c5d3fb4 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/README.md @@ -0,0 +1,6 @@ +ESP BLE Mesh Fast Provisioning Server example +======================== + +This example shows how a BLE Mesh device functions as a Fast Provisioning Server. + +Please check the [tutorial](tutorial/ble_mesh_fast_provision_server.md) for more information about this example. \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/CMakeLists.txt new file mode 100644 index 0000000000..dc91641afd --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/CMakeLists.txt @@ -0,0 +1,7 @@ +set(COMPONENT_SRCS "ble_mesh_demo_main.c" + "ble_mesh_demo_init.c" + "board.c") + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/Kconfig.projbuild b/examples/bluetooth/esp_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/esp_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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_init.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_init.c new file mode 100644 index 0000000000..fb145ad2ca --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_init.c @@ -0,0 +1,143 @@ + +/* + * Copyright (c) 2017 Intel Corporation + * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +/* BLE */ +#ifdef CONFIG_BLUEDROID_ENABLED +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#endif + +#ifdef CONFIG_BT_NIMBLE_ENABLED +#include "esp_nimble_hci.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "console/console.h" +#endif + +#include "esp_ble_mesh_defs.h" +#include "ble_mesh_demo_init.h" +#include "esp_ble_mesh_common_api.h" + +#ifdef CONFIG_BLUEDROID_ENABLED + +void ble_mesh_get_dev_uuid(uint8_t *dev_uuid) +{ + memcpy(dev_uuid + 2, esp_bt_dev_get_address(), BD_ADDR_LEN); +} + +esp_err_t bluetooth_init(void) +{ + esp_err_t 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; +} + +#endif + +#ifdef CONFIG_BT_NIMBLE_ENABLED +static SemaphoreHandle_t mesh_sem; +static uint8_t own_addr_type; +void ble_store_config_init(void); +static uint8_t addr_val[6] = {0}; + +void ble_mesh_get_dev_uuid(uint8_t *dev_uuid) +{ + memcpy(dev_uuid + 2, addr_val, BD_ADDR_LEN); +} + +static void mesh_on_reset(int reason) +{ + ESP_LOGI(TAG, "Resetting state; reason=%d", reason); +} + +static void mesh_on_sync(void) +{ + int rc; + + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + /* Figure out address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGI(TAG, "error determining address type; rc=%d", rc); + return; + } + + rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL); + + xSemaphoreGive(mesh_sem); +} + +void mesh_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} + +esp_err_t bluetooth_init(void) +{ + mesh_sem = xSemaphoreCreateBinary(); + if (mesh_sem == NULL) { + ESP_LOGE(TAG, "Failed to create mesh semaphore"); + return ESP_FAIL; + } + + ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init()); + + nimble_port_init(); + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = mesh_on_reset; + ble_hs_cfg.sync_cb = mesh_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + /* XXX Need to have template for store */ + ble_store_config_init(); + + nimble_port_freertos_init(mesh_host_task); + + xSemaphoreTake(mesh_sem, portMAX_DELAY); + + return ESP_OK; +} +#endif diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_init.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_init.h new file mode 100644 index 0000000000..9e5272de2f --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_init.h @@ -0,0 +1,18 @@ +/* + 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. +*/ + +#ifndef _BLE_MESH_DEMO_INIT_H_ +#define _BLE_MESH_DEMO_INIT_H_ + +#define TAG "FAST_PROV_SERVER_DEMO" + +void ble_mesh_get_dev_uuid(uint8_t *dev_uuid); + +esp_err_t bluetooth_init(void); + +#endif diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_main.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_main.c new file mode 100644 index 0000000000..2bf3003091 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/ble_mesh_demo_main.c @@ -0,0 +1,764 @@ +// Copyright 2017-2019 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_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 "esp_ble_mesh_local_data_operation_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" +#include "ble_mesh_demo_init.h" + +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_SERVER) + .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, +}; + +ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_pub, 2 + 3, ROLE_FAST_PROV); +static esp_ble_mesh_gen_onoff_srv_t onoff_server = { + .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP, + .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP, +}; + +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_MODEL_GEN_ONOFF_SRV(&onoff_pub, &onoff_server), +}; + +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 example_change_led_state(uint8_t onoff) +{ + struct _led_state *led = &led_state[1]; + + board_led_operation(led->pin, onoff); + + /* 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 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[BLE_MESH_ADDR_LEN], + esp_ble_mesh_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, BLE_MESH_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_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_STATE_CHANGE_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->value.state_change.appkey_add.app_idx); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: Failed to bind app_idx 0x%04x with non-config models", + __func__, param->value.state_change.appkey_add.app_idx); + return; + } + break; + default: + break; + } + break; + default: + return; + } +} + +static void example_ble_mesh_generic_server_cb(esp_ble_mesh_generic_server_cb_event_t event, + esp_ble_mesh_generic_server_cb_param_t *param) +{ + ESP_LOGI(TAG, "event 0x%02x, opcode 0x%04x, src 0x%04x, dst 0x%04x", + event, param->ctx.recv_op, param->ctx.addr, param->ctx.recv_dst); + + switch (event) { + case ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT: + ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT"); + if (param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET || + param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK) { + ESP_LOGI(TAG, "onoff 0x%02x", param->value.state_change.onoff_set.onoff); + example_change_led_state(param->value.state_change.onoff_set.onoff); + } + break; + default: + ESP_LOGW(TAG, "Unknown Generic Server event 0x%02x", event); + break; + } +} + +static esp_err_t ble_mesh_init(void) +{ + esp_err_t err; + + 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); + esp_ble_mesh_register_generic_server_callback(example_ble_mesh_generic_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; +} + +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 = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + err = bluetooth_init(); + if (err) { + ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err); + return; + } + + ble_mesh_get_dev_uuid(dev_uuid); + + /* 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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.c new file mode 100644 index 0000000000..192d730671 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.c @@ -0,0 +1,72 @@ +// Copyright 2017-2019 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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.h new file mode 100644 index 0000000000..09ebd40075 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/board.h @@ -0,0 +1,47 @@ +// Copyright 2017-2019 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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/main/component.mk b/examples/bluetooth/esp_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/esp_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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/sdkconfig.defaults b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/sdkconfig.defaults new file mode 100644 index 0000000000..a8808f2ae1 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/sdkconfig.defaults @@ -0,0 +1,25 @@ +# 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=n +CONFIG_BTDM_CONTROLLER_MODE_BTDM=n +CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=n +CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR=y +CONFIG_BLE_MESH_SCAN_DUPLICATE_EN=y +CONFIG_GATTS_SEND_SERVICE_CHANGE_MANUAL=y +CONFIG_BTU_TASK_STACK_SIZE=4512 + +# Override some defaults of ESP BLE Mesh +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_FAST_PROV=y +CONFIG_BLE_MESH_MAX_PROV_NODES=6 +CONFIG_BLE_MESH_PBA_SAME_TIME=3 +CONFIG_BLE_MESH_PB_GATT=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_TRACE_LEVEL_ERROR=y +CONFIG_BLE_MESH_CFG_CLI=y \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/BLE_Mesh_Fast_Prov_Server_Example_Walkthrough.md b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/BLE_Mesh_Fast_Prov_Server_Example_Walkthrough.md new file mode 100644 index 0000000000..d1e597cda6 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/BLE_Mesh_Fast_Prov_Server_Example_Walkthrough.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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/EspBleMesh.md b/examples/bluetooth/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/EspBleMesh.md new file mode 100644 index 0000000000..8039d6672c --- /dev/null +++ b/examples/bluetooth/esp_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/esp_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 `idf.py menuconfig` + +![Board](images/device.png) + + +# Flash and Monitor + +1. Enter the directory: +examples/bluetooth/esp_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 `idf.py -p PORT 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/esp_ble_mesh/ble_mesh_fast_provision/ble_mesh_fast_prov_server/tutorial/images/app_ble.png b/examples/bluetooth/esp_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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

    X`!Qe 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 z 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 z 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

    ({JpKqGI@A;K@$^`X3RJNYJnFO8G)8u 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 literal 0 HcmV?d00001 diff --git a/docs/_static/ble-mesh-provision.png b/docs/_static/ble-mesh-provision.png new file mode 100644 index 0000000000000000000000000000000000000000..dc227484110bb722df1062c958542ca4dc6de725 GIT binary patch literal 47459 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 literal 0 HcmV?d00001 diff --git a/docs/_static/ble-mesh-reconnect-initial-config.png b/docs/_static/ble-mesh-reconnect-initial-config.png new file mode 100644 index 0000000000000000000000000000000000000000..67927b655c7c7ea871ec02de425e900b4d0bd71d GIT binary patch literal 144717 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 Z(vp(YqU5FvoD2*M44$rjF6*2UngCH7G*JKm literal 0 HcmV?d00001 diff --git a/docs/_static/ble-mesh-reconnect-three.png b/docs/_static/ble-mesh-reconnect-three.png new file mode 100644 index 0000000000000000000000000000000000000000..4da6f7d000136141dcbe6bc7e1bfa1efb56940a1 GIT binary patch literal 91013 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