text: // Copyright 2021 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 <stdio.h>
  #include <string.h>
  #include <errno.h>
  #include <math.h>
  
  #include "esp_log.h"
  #include "nvs_flash.h"
  
  #include "mbedtls/sha256.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_health_model_api.h"
  #include "esp_ble_mesh_generic_model_api.h"
  #include "esp_ble_mesh_lighting_model_api.h"
  #include "esp_ble_mesh_time_scene_model_api.h"
  #include "esp_ble_mesh_local_data_operation_api.h"
  
  #include "board.h"
  #include "genie_mesh.h"
  #include "ble_mesh_example_init.h"
  #include "ble_mesh_example_nvs.h"
  
  static const char *TAG  = "genie_demo";
  
  #define MESH_ELEM_COUNT         1
  #define MESH_ELEM_STATE_COUNT   MESH_ELEM_COUNT
  
  nvs_handle_t NVS_HANDLE;
  
  elem_state_t g_elem_state[MESH_ELEM_STATE_COUNT] = {0};
  model_powerup_t g_powerup[MESH_ELEM_STATE_COUNT] = {0};
  
  static uint8_t static_val[16] = {0x64, 0xda, 0x19, 0x8f, 0x58, 0xea, 0x55, 0x85,
                                   0x75, 0x84, 0x0d, 0xbf, 0x4f, 0x6e, 0x36, 0x8f
                                  };
  
  static uint8_t dev_uuid[16] = {
      0xA8, 0x01, // CID
      0x71,       // PID Bit0-3 ble adv version, 0x01
      // bit4: One machine one secret
      // bit5: Support OTA
      // bit6~7：
      // 00：BLE4.0
      // 01：BLE4.2
      // 10：BLE5.0
      // 11：BLE5.0 above
      0x7F, 0x2C, 0x00, 0x00,             // ProductID // 00002C7F
      0xda, 0x91, 0x01, 0x7a, 0xfa, 0x28, // MAC Address // 28fa7a0191da
  #if GENIE_VENDOR_MODEL_VERSION == 0
      0x00,       // Feature Flag bit7-1：uuid version, version 0
      // bit0  0: unprovisioned adv
      //       1: silent adv
  #elif GENIE_VENDOR_MODEL_VERSION == 1
      0x02,       // Feature Flag bit7-1：uuidversion, version 1
      // bit0  0: unprovisioned adv
      //       1: silent adv
  #endif
      0x00, 0x00, // RFU
  };
  
  static esp_ble_mesh_cfg_srv_t config_server = {
      .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),
      /* 3 transmissions with 20ms interval */
      .relay_retransmit = ESP_BLE_MESH_TRANSMIT(2, 20),
  };
  
  uint8_t test_ids[1] = {0x00};
  
  /** ESP BLE Mesh Health Server Model Context */
  ESP_BLE_MESH_MODEL_PUB_DEFINE(health_pub, 2 + 11, ROLE_NODE);
  static esp_ble_mesh_health_srv_t health_server = {
      .health_test.id_count = 1,
      .health_test.test_ids = test_ids,
  };
  
  #ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
  ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_pub_0, 2 + 3, ROLE_NODE);
  static esp_ble_mesh_gen_onoff_srv_t onoff_server_0 = {
      .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
  };
  #endif
  
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
  /* Light Lightness state related context */
  static esp_ble_mesh_light_lightness_state_t lightness_state;
  
  /* Light Lightness Server related context */
  ESP_BLE_MESH_MODEL_PUB_DEFINE(lightness_pub, 2 + 5, ROLE_NODE);
  static esp_ble_mesh_light_lightness_srv_t lightness_server = {
      .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .state = &lightness_state,
  };
  
  /* Light Lightness Setup Server related context */
  ESP_BLE_MESH_MODEL_PUB_DEFINE(lightness_setup_pub, 2 + 5, ROLE_NODE);
  static esp_ble_mesh_light_lightness_setup_srv_t lightness_setup_server = {
      .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .state = &lightness_state,
  };
  #endif
  
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
  /* Light CTL state related context */
  static esp_ble_mesh_light_ctl_state_t ctl_state;
  
  /* Light CTL Server related context */
  ESP_BLE_MESH_MODEL_PUB_DEFINE(ctl_pub, 2 + 9, ROLE_NODE);
  static esp_ble_mesh_light_ctl_srv_t ctl_server = {
      .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .state = &ctl_state,
  };
  
  /* Light CTL Setup Server related context */
  ESP_BLE_MESH_MODEL_PUB_DEFINE(ctl_setup_pub, 2 + 6, ROLE_NODE);
  static esp_ble_mesh_light_ctl_setup_srv_t ctl_setup_server = {
      .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .state = &ctl_state,
  };
  #endif
  
  #ifdef CONFIG_MESH_MODEL_HSL_SRV
  /* Light HSL state related context */
  static esp_ble_mesh_light_hsl_state_t hsl_state;
  
  /* Light HSL Server related context */
  ESP_BLE_MESH_MODEL_PUB_DEFINE(hsl_pub, 2 + 9, ROLE_NODE);
  static esp_ble_mesh_light_hsl_srv_t hsl_server = {
      .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .state = &hsl_state,
  };
  
  /* Light HSL Setup Server related context */
  ESP_BLE_MESH_MODEL_PUB_DEFINE(hsl_setup_pub, 2 + 9, ROLE_NODE);
  static esp_ble_mesh_light_hsl_setup_srv_t hsl_setup_server = {
      .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .state = &hsl_state,
  };
  #endif
  
  static esp_ble_mesh_model_t root_models[] = {
      ESP_BLE_MESH_MODEL_CFG_SRV(&config_server),
      ESP_BLE_MESH_MODEL_HEALTH_SRV(&health_server, &health_pub),
  #ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
      ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV(&onoff_pub_0, &onoff_server_0),
  #endif
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
      ESP_BLE_MESH_MODEL_LIGHT_LIGHTNESS_SRV(&lightness_pub, &lightness_server),
      ESP_BLE_MESH_MODEL_LIGHT_LIGHTNESS_SETUP_SRV(&lightness_setup_pub, &lightness_setup_server),
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
      ESP_BLE_MESH_MODEL_LIGHT_CTL_SRV(&ctl_pub, &ctl_server),
      ESP_BLE_MESH_MODEL_LIGHT_CTL_SETUP_SRV(&ctl_setup_pub, &ctl_setup_server),
  #endif
  #ifdef CONFIG_MESH_MODEL_HSL_SRV
      ESP_BLE_MESH_MODEL_LIGHT_HSL_SRV(&hsl_pub, &hsl_server),
      ESP_BLE_MESH_MODEL_LIGHT_HSL_SETUP_SRV(&hsl_setup_pub, &hsl_setup_server),
  #endif
  };
  
  /** @def g_vendor_model_alibaba_op
   *
   *  @brief vendor model operations struct
   *
   */
  static esp_ble_mesh_model_op_t g_vendor_model_alibaba_op[] = {
      ESP_BLE_MESH_MODEL_OP(GENIE_MESSAGE_OP_ATTR_GET_STATUS,        2),
      ESP_BLE_MESH_MODEL_OP(GENIE_MESSAGE_OP_ATTR_SET_ACK,           2),
      ESP_BLE_MESH_MODEL_OP(GENIE_MESSAGE_OP_ATTR_SET_UNACK,         2),
      ESP_BLE_MESH_MODEL_OP(GENIE_MESSAGE_OP_ATTR_STATUS,            1),
      ESP_BLE_MESH_MODEL_OP(GENIE_MESSAGE_OP_ATTR_INDICATION,        1),
      ESP_BLE_MESH_MODEL_OP(GENIE_MESSAGE_OP_ATTR_CONFIRMATION,      1),
      ESP_BLE_MESH_MODEL_OP(GENIE_MESSAGE_OP_ATTR_TRANSPARENT_MSG,   1),
      ESP_BLE_MESH_MODEL_OP_END,
  };
  
  static esp_ble_mesh_model_t ali_vnd_models[] = {
      ESP_BLE_MESH_VENDOR_MODEL(CID_ALIBABA, GENIE_VENDOR_MODEL_SRV_ID, g_vendor_model_alibaba_op, NULL, NULL),
  };
  
  ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_pub_1, 2 + 3, ROLE_NODE);
  static esp_ble_mesh_gen_onoff_srv_t onoff_server_1 = {
      .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_RSP_BY_APP,
      .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_RSP_BY_APP,
  };
  
  ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_pub_2, 2 + 3, ROLE_NODE);
  static esp_ble_mesh_gen_onoff_srv_t onoff_server_2 = {
      .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
      .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_RSP_BY_APP,
  };
  
  static esp_ble_mesh_model_t extend_model_0[] = {
      ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV(&onoff_pub_1, &onoff_server_1),
  };
  
  static esp_ble_mesh_model_t extend_model_1[] = {
      ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV(&onoff_pub_2, &onoff_server_2),
  };
  
  esp_ble_mesh_elem_t elements[] = {
      ESP_BLE_MESH_ELEMENT(0, root_models,    ali_vnd_models),
      ESP_BLE_MESH_ELEMENT(0, extend_model_0, ESP_BLE_MESH_MODEL_NONE),
      ESP_BLE_MESH_ELEMENT(0, extend_model_1, ESP_BLE_MESH_MODEL_NONE),
  };
  
  static esp_ble_mesh_comp_t composition = {
      .cid           = CID_ALIBABA,
      .elements      = elements,
      .element_count = ARRAY_SIZE(elements),
  };
  
  /* Disable OOB security for SILabs Android app */
  static esp_ble_mesh_prov_t provision = {
      .uuid           = dev_uuid,
      .static_val     = static_val,
      .static_val_len = 16,
      .output_size    = 0,                        // not support output OOB
      .output_actions = ESP_BLE_MESH_NO_OUTPUT,
      .input_size     = 0,                        // not support Input OOB
      .input_actions  = ESP_BLE_MESH_NO_INPUT,
  };
  
  static void light_color_set(uint16_t lightness, uint16_t hue, uint16_t saturation);
  
  void set_silent_broadcast(void)
  {
      dev_uuid[13] |= GENIE_UNPROV_ADV_FEATURE_SILENT_ADV;
  }
  
  void set_unprovision_broadcast(void)
  {
      dev_uuid[13] &= ~GENIE_UNPROV_ADV_FEATURE_SILENT_ADV;
  }
  
  void user_prov_complete(uint16_t net_idx, uint16_t addr)
  {
      ENTER_FUNC();
      // todo: add custom code
  }
  
  void user_prov_reset(void)
  {
      ENTER_FUNC();
      // todo: add custom code
      esp_restart();
  }
  
  void reset_light_para(void)
  {
      ENTER_FUNC();
      uint8_t i = 0;
  
      while (i < MESH_ELEM_STATE_COUNT) {
  #ifdef CONFIG_MESH_SIMPLE_MODLE
          g_elem_state[i].state.onoff[VALUE_TYPE_CUR]  = GENIE_ONOFF_DEFAULT;
          g_elem_state[i].state.onoff[VALUE_TYPE_TAR]  = GENIE_ONOFF_DEFAULT;
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
          g_elem_state[i].state.actual[VALUE_TYPE_CUR] = GENIE_LIGHTNESS_DEFAULT;
          g_elem_state[i].state.actual[VALUE_TYPE_TAR] = GENIE_LIGHTNESS_DEFAULT;
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
          g_elem_state[i].state.temp[VALUE_TYPE_CUR]   = GENIE_CTL_TEMP_DEFAULT;
          g_elem_state[i].state.temp[VALUE_TYPE_TAR]   = GENIE_CTL_TEMP_DEFAULT;
  #endif
  #ifdef CONFIG_MESH_MODEL_TRANS
          g_elem_state[i].state.trans            = 0;
          g_elem_state[i].state.delay            = 0;
          g_elem_state[i].state.trans_start_time = 0;
          g_elem_state[i].state.trans_end_time   = 0;
  #endif
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
          g_elem_state[i].powerup.last_actual = GENIE_LIGHTNESS_DEFAULT;
          g_powerup   [i].last_actual         = GENIE_LIGHTNESS_DEFAULT;
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
          g_elem_state[i].powerup.last_temp   = GENIE_CTL_TEMP_DEFAULT;
          g_powerup   [i].last_temp           = GENIE_CTL_TEMP_DEFAULT;
  #endif
  #endif
          i++;
      }
  
      ble_mesh_nvs_store(NVS_HANDLE, POWERUP_KEY, g_powerup, sizeof(g_powerup));
  }
  
  void save_light_state(elem_state_t *p_elem)
  {
      ENTER_FUNC();
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
      if (p_elem->state.actual[VALUE_TYPE_CUR] != 0) {
          p_elem->powerup.last_actual = p_elem->state.actual[VALUE_TYPE_CUR];
          g_powerup[p_elem->elem_index].last_actual = p_elem->state.actual[VALUE_TYPE_CUR];
      }
      ESP_LOGD(TAG, "elem %d, actual %d", p_elem->elem_index, g_powerup[p_elem->elem_index].last_actual);
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
      p_elem->powerup.last_temp = p_elem->state.temp[VALUE_TYPE_CUR];
      g_powerup[p_elem->elem_index].last_temp = p_elem->state.temp[VALUE_TYPE_CUR];
      ESP_LOGD(TAG, "elem %d, temp %d", p_elem->elem_index, g_powerup[p_elem->elem_index].last_temp);
  #endif
  
      ble_mesh_nvs_store(NVS_HANDLE, POWERUP_KEY, g_powerup, sizeof(g_powerup));
  }
  
  void load_light_state(void)
  {
      ENTER_FUNC();
      uint8_t   i     = 0;
      bool      exist = false;
      esp_err_t ret   = ESP_OK;
  
      ret = ble_mesh_nvs_restore(NVS_HANDLE, POWERUP_KEY, g_powerup, sizeof(g_powerup), &exist);
  
      if (ret == ESP_OK) {
          while (i < MESH_ELEM_STATE_COUNT) {
              memcpy(&g_elem_state[i].powerup, &g_powerup[i], sizeof(model_powerup_t));
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
              ESP_LOGI(TAG, "elem %d, actual %d", i, g_powerup[i].last_actual);
              if (g_powerup[i].last_actual) {
                  g_elem_state[i].state.actual[VALUE_TYPE_TAR] = g_powerup[i].last_actual;
              }
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
              ESP_LOGI(TAG, "elem %d, temp %d", i, g_powerup[i].last_temp);
              if (g_powerup[i].last_temp) {
                  g_elem_state[i].state.temp[VALUE_TYPE_TAR] = g_powerup[i].last_temp;
              }
  #endif
  #ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
              if (g_elem_state[i].state.actual[VALUE_TYPE_TAR] == 0) {
                  g_elem_state[i].state.onoff[VALUE_TYPE_TAR] = 0;
              } else {
                  g_elem_state[i].state.onoff[VALUE_TYPE_TAR] = 1;
              }
  #endif
  #ifdef CONFIG_MESH_MODEL_TRANS
              if (g_elem_state[i].state.onoff[VALUE_TYPE_TAR] == 0) {
                  g_elem_state[i].state.trans            = 0;
                  g_elem_state[i].state.delay            = 0;
                  g_elem_state[i].state.trans_start_time = 0;
                  g_elem_state[i].state.trans_end_time   = 0;
              } else {
                  g_elem_state[i].state.trans            = 0x41;
                  g_elem_state[i].state.delay            = 100;
                  g_elem_state[i].state.trans_start_time = k_uptime_get() + g_elem_state[i].state.delay * 5;
                  g_elem_state[i].state.trans_end_time   = g_elem_state[i].state.trans_start_time + get_transition_time(g_elem_state[i].state.trans);
              }
  #endif
  #endif
              i++;
          }
      }
  }
  
  void user_genie_event_handle(genie_event_t event, void *p_arg)
  {
      genie_event_t next_event = event;
  
      ESP_LOGD(TAG, "event: %02d, %p", event, p_arg);
  
      switch (event) {
      case GENIE_EVT_RESET_BY_REPEAT_NOTIFY:
          ESP_LOGI(TAG, "GENIE_EVT_RESET_BY_REPEAT_NOTIFY");
          light_driver_breath_start(0, 255, 0);   /**< green blink */
          break;
      case GENIE_EVT_SW_RESET:
      case GENIE_EVT_HW_RESET_START:
          ESP_LOGI(TAG, "GENIE_EVT_HW_RESET_START");
          light_driver_breath_start(0, 255, 0);   /**< green blink */
          // ble_mesh_nvs_erase(NVS_HANDLE, LIGHT_STATUS_STORE_KEY); // erase led status
          reset_light_para();
          break;
      case GENIE_EVT_SDK_MESH_INIT:
          ESP_LOGI(TAG, "GENIE_EVT_SDK_MESH_INIT");
          break;
      case GENIE_EVT_SDK_MESH_PROV_SUCCESS:
          ESP_LOGI(TAG, "GENIE_EVT_SDK_MESH_PROV_SUCCESS");
  
          g_elem_state[0].state.onoff[VALUE_TYPE_TAR]  = 1;
          g_indication_flag |= INDICATION_FLAG_ONOFF;
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
          g_elem_state[0].state.actual[VALUE_TYPE_TAR] = 0xFFFE;
          g_indication_flag |= INDICATION_FLAG_LIGHTNESS;
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
          g_elem_state[0].state.temp[VALUE_TYPE_TAR]   = 0xFFFE;
          g_indication_flag |= INDICATION_FLAG_CTL;
  #endif
          ESP_LOGI(TAG, "light_driver_breath_stop %s", __FUNCTION__);
          light_driver_breath_stop();
          // update led status
          genie_event(GENIE_EVT_SDK_ANALYZE_MSG, &g_elem_state[0]);
          break;
  #ifdef CONFIG_MESH_MODEL_TRANS
      case GENIE_EVT_SDK_TRANS_CYCLE:
  #endif
      case GENIE_EVT_SDK_ACTION_DONE: {
          ESP_LOGI(TAG, "GENIE_EVT_SDK_ACTION_DONE");
          elem_state_t *p_elem = (elem_state_t *)p_arg;
  #ifdef CONFIG_MESH_MODEL_HSL_SRV
          ESP_LOGI(TAG, "hue: %d, saturation: %d, lightness: %d", p_elem->state.hue[VALUE_TYPE_CUR], p_elem->state.saturation[VALUE_TYPE_CUR], p_elem->state.lightness[VALUE_TYPE_CUR]);
          board_led_hsl(p_elem->elem_index, p_elem->state.hue[VALUE_TYPE_CUR], p_elem->state.saturation[VALUE_TYPE_CUR], p_elem->state.lightness[VALUE_TYPE_CUR]);
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
          ESP_LOGI(TAG, "temperature: %d", p_elem->state.temp[VALUE_TYPE_CUR]);
          board_led_temperature(p_elem->elem_index, p_elem->state.temp[VALUE_TYPE_CUR]);
  #endif
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
          ESP_LOGI(TAG, "lightness actual: %d", p_elem->state.actual[VALUE_TYPE_CUR]);
          board_led_lightness(p_elem->elem_index, p_elem->state.actual[VALUE_TYPE_CUR]);
  #endif
  #ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
          ESP_LOGI(TAG, "onoff: %d", p_elem->state.onoff[VALUE_TYPE_CUR]);
          board_led_switch(p_elem->elem_index, p_elem->state.onoff[VALUE_TYPE_CUR]);
  #endif
          if (event == GENIE_EVT_SDK_ACTION_DONE) {
              save_light_state(p_elem);
          }
          break;
      }
      case GENIE_EVT_HW_RESET_DONE:
          ESP_LOGI(TAG, "GENIE_EVT_HW_RESET_DONE");
          // ble_mesh_nvs_erase(NVS_HANDLE, LIGHT_STATUS_STORE_KEY); // erase led status
          // esp_restart();
          break;
      case GENIE_EVT_APP_FAC_QUIT:
          ESP_LOGI(TAG, "GENIE_EVT_APP_FAC_QUIT");
          break;
      case GENIE_EVT_BUTTON_TAP:
          ESP_LOGI(TAG, "GENIE_EVT_BUTTON_TAP");
          g_elem_state[0].state.onoff[VALUE_TYPE_TAR] = !g_elem_state[0].state.onoff[VALUE_TYPE_TAR];
  #ifdef CONFIG_MESH_MODEL_VENDOR_SRV
          g_indication_flag |= INDICATION_FLAG_ONOFF;
  #endif
          // update led status
          next_event = GENIE_EVT_SDK_ANALYZE_MSG;
          p_arg = &g_elem_state[0];
          break;
      case GENIE_EVT_SDK_COLOR_ACTION: {
          ESP_LOGI(TAG, "GENIE_EVT_SDK_COLOR_ACTION");
  #ifdef CONFIG_MESH_MODEL_HSL_SRV
          uint8_t *p_data = p_arg;
  
          uint16_t lightness = *p_data++;
          lightness += (*p_data++ << 8);
  
          uint16_t hue = *p_data++;
          hue += (*p_data++ << 8);
  
          uint16_t saturation = *p_data++;
          saturation += (*p_data++ << 8);
  
          light_color_set(lightness, hue, saturation);
  #endif
          break;
      }
      default:
          ESP_LOGD(TAG, "unhandle this event: %2d", event);
          break;
      }
  
      if (next_event != event) {
          // send event to genie event loop
          genie_event(next_event, p_arg);
      }
  }
  
  esp_err_t local_operation(uint16_t app_idx)
  {
      ESP_LOGD(TAG, "app_idx: %d, primary element address: %d", app_idx, esp_ble_mesh_get_primary_element_address());
  
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_HEALTH_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_HEALTH_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_HEALTH_SRV, app_idx));
  
  #ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, app_idx));
  
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address() + 1, BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address() + 1, BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address() + 1, BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, app_idx));
  
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address() + 2, BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address() + 2, BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address() + 2, BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_SRV, app_idx));
  #endif
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV, app_idx));
  
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV, app_idx));
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SRV, app_idx));
  
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV, app_idx));
  #endif
  #ifdef CONFIG_MESH_MODEL_HSL_SRV
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SRV, app_idx));
  
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address(), BLE_MESH_CID_NVAL, ESP_BLE_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV, app_idx));
  #endif
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), CID_ALIBABA, GENIE_VENDOR_MODEL_SRV_ID, GENIE_LIGHT_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_model_subscribe_group_addr(esp_ble_mesh_get_primary_element_address(), CID_ALIBABA, GENIE_VENDOR_MODEL_SRV_ID, GENIE_ALL_GROUP_ADDR));
      ESP_ERROR_CHECK(esp_ble_mesh_node_bind_app_key_to_local_model(esp_ble_mesh_get_primary_element_address(), CID_ALIBABA, GENIE_VENDOR_MODEL_SRV_ID, app_idx));
  
      return ESP_OK;
  }
  
  #ifdef CONFIG_MESH_MODEL_HSL_SRV
  void bind_onoff_with_hsl(elem_state_t *p_elem, value_type_t type)
  {
      ENTER_FUNC();
      model_state_t *p_state = &p_elem->state;
  
      ESP_LOGI(TAG, "hsl tar(%d, %d, %d)", p_state->hue[VALUE_TYPE_TAR], p_state->saturation[VALUE_TYPE_TAR], p_state->lightness[VALUE_TYPE_TAR]);
  
      if (type == VALUE_TYPE_TAR) {
          if (p_state->onoff[VALUE_TYPE_TAR] == 0) {
              //turn on
              p_state->onoff[VALUE_TYPE_TAR] = 1;
  #ifdef CONFIG_MESH_MODEL_VENDOR_SRV
              g_indication_flag |= INDICATION_FLAG_ONOFF;
  #endif
          }
      } else if (type == VALUE_TYPE_CUR) {
      }
  }
  #endif
  
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
  void bind_onoff_with_ctl(elem_state_t *p_elem, value_type_t type)
  {
      ENTER_FUNC();
      model_state_t *p_state = &p_elem->state;
  
      // ESP_LOGI(TAG, "temperature cur(%d) tar(%d)", p_state->temp[VALUE_TYPE_CUR], p_state->temp[VALUE_TYPE_TAR]);
      if (type == VALUE_TYPE_TAR) {
          if (p_state->onoff[VALUE_TYPE_TAR] == 0) {
              //turn on
              p_state->onoff[VALUE_TYPE_TAR] = 1;
  #ifdef CONFIG_MESH_MODEL_VENDOR_SRV
              g_indication_flag |= INDICATION_FLAG_ONOFF;
  #endif
          }
      } else if (type == VALUE_TYPE_CUR) {
      }
  }
  #endif
  
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
  void bind_onoff_with_lightness(elem_state_t *p_elem, value_type_t type)
  {
      ENTER_FUNC();
      model_state_t *p_state = &p_elem->state;
  
      ESP_LOGI(TAG, "lightness cur(%d) tar(%d)", p_state->actual[VALUE_TYPE_CUR], p_state->actual[VALUE_TYPE_TAR]);
      if (type == VALUE_TYPE_TAR) {
          if (p_state->actual[VALUE_TYPE_CUR] == 0 && p_state->actual[VALUE_TYPE_TAR] != 0) {
              //turn on
              p_state->onoff[VALUE_TYPE_TAR] = 1;
  #ifdef CONFIG_MESH_MODEL_VENDOR_SRV
              g_indication_flag |= INDICATION_FLAG_ONOFF;
  #endif
          } else if (p_state->actual[VALUE_TYPE_CUR] != 1 && p_state->actual[VALUE_TYPE_TAR] == 0) {
              //turn off
              p_state->onoff[VALUE_TYPE_TAR] = 0;
  #ifdef CONFIG_MESH_MODEL_VENDOR_SRV
              g_indication_flag |= INDICATION_FLAG_ONOFF;
  #endif
          }
      } else if (type == VALUE_TYPE_CUR) {
          if (p_state->actual[VALUE_TYPE_CUR] == 0 && p_state->actual[VALUE_TYPE_TAR] == 0) {
              //turn off
              p_state->onoff[VALUE_TYPE_CUR] = 0;
  #ifdef CONFIG_MESH_MODEL_VENDOR_SRV
              g_indication_flag |= INDICATION_FLAG_ONOFF;
  #endif
          }
      }
  }
  
  void bind_lightness_with_onoff(elem_state_t *p_elem, value_type_t type)
  {
      ENTER_FUNC();
      model_state_t *p_state = &p_elem->state;
  
      ESP_LOGI(TAG, "onoff cur(%d) tar(%d)", p_state->onoff[VALUE_TYPE_CUR], p_state->onoff[VALUE_TYPE_TAR]);
      if (type == VALUE_TYPE_TAR) {
  #ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
          if (p_state->onoff[VALUE_TYPE_CUR] == 0 && p_state->onoff[VALUE_TYPE_TAR] == 1) {
              //turn on, set default lightness
  #ifdef CONFIG_MESH_SIMPLE_MODLE
              if (p_elem->powerup.last_actual) {
                  p_state->actual[VALUE_TYPE_TAR] = p_elem->powerup.last_actual;
              } else {
                  p_state->actual[VALUE_TYPE_TAR] = GENIE_LIGHTNESS_DEFAULT;
              }
  #endif
  #ifdef CONFIG_MESH_MODEL_VENDOR_SRV
              g_indication_flag |= INDICATION_FLAG_LIGHTNESS;
  #endif
          } else if (p_state->onoff[VALUE_TYPE_CUR] == 1 && p_state->onoff[VALUE_TYPE_TAR] == 0) {
              //turn off, set zero
              p_state->actual[VALUE_TYPE_TAR] = 0;
  #ifdef CONFIG_MESH_MODEL_VENDOR_SRV
              g_indication_flag |= INDICATION_FLAG_LIGHTNESS;
  #endif
          }
  #endif
      } else if (type == VALUE_TYPE_CUR) {
      }
      ESP_LOGI(TAG, "actual cur(%d) tar(%d)", p_state->actual[VALUE_TYPE_CUR], p_state->actual[VALUE_TYPE_TAR]);
  }
  #endif
  
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
  void bind_ctl_with_onoff(elem_state_t *p_elem, value_type_t type)
  {
      ENTER_FUNC();
      model_state_t *p_state = &p_elem->state;
  
      ESP_LOGI(TAG, "onoff cur(%d) tar(%d)", p_state->onoff[VALUE_TYPE_CUR], p_state->onoff[VALUE_TYPE_TAR]);
      if (type == VALUE_TYPE_TAR) {
  #ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV
          if (p_state->onoff[VALUE_TYPE_CUR] == 0 && p_state->onoff[VALUE_TYPE_TAR] == 1) {
  #ifdef CONFIG_MESH_SIMPLE_MODLE
              //turn on, check temperature
              ESP_LOGI(TAG, "temp cur(%d) tar(%d)", p_state->temp[VALUE_TYPE_CUR], p_state->temp[VALUE_TYPE_TAR]);
              if (p_state->temp[VALUE_TYPE_CUR] != p_state->temp[VALUE_TYPE_TAR]) {
                  if (p_elem->powerup.last_temp) {
                      p_state->temp[VALUE_TYPE_TAR] = p_elem->powerup.last_temp;
                  } else {
                      p_state->temp[VALUE_TYPE_TAR] = GENIE_CTL_TEMP_DEFAULT;
                  }
  #ifdef CONFIG_MESH_MODEL_VENDOR_SRV
                  g_indication_flag |= INDICATION_FLAG_CTL;
  #endif
              }
  #endif
          }
  #endif
      } else if (type == VALUE_TYPE_CUR) {
      }
      ESP_LOGI(TAG, "temp cur(%04x) tar(%04x)", p_state->temp[VALUE_TYPE_CUR], p_state->temp[VALUE_TYPE_TAR]);
  }
  #endif
  
  #ifdef CONFIG_MESH_MODEL_HSL_SRV
  static void light_color_set(uint16_t lightness, uint16_t hue, uint16_t saturation)
  {
      ENTER_FUNC();
      ESP_LOGI(TAG, "hue: %d, saturation: %d, lightness: %d", hue, saturation, lightness);
  
      g_elem_state[0].state.hue[VALUE_TYPE_TAR]        = hue;
      g_elem_state[0].state.saturation[VALUE_TYPE_TAR] = saturation;
      g_elem_state[0].state.lightness[VALUE_TYPE_TAR]  = lightness;
      if (g_elem_state[0].state.hue[VALUE_TYPE_CUR] != g_elem_state[0].state.hue[VALUE_TYPE_TAR] || g_elem_state[0].state.saturation[VALUE_TYPE_CUR] != g_elem_state[0].state.saturation[VALUE_TYPE_TAR]
              || g_elem_state[0].state.lightness[VALUE_TYPE_CUR] != g_elem_state[0].state.lightness[VALUE_TYPE_TAR]) {
          g_indication_flag |= INDICATION_FLAG_HSL;
  
          /* Change corresponding binded states in root element */
          bind_onoff_with_hsl(&g_elem_state[0], VALUE_TYPE_TAR);
  
          if (esp_ble_mesh_node_is_provisioned()) {
              // update led status
              genie_event(GENIE_EVT_SDK_ANALYZE_MSG, &g_elem_state[0]);
          }
      }
  }
  #endif
  
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
  static void light_ctl_set(uint32_t opcode, esp_ble_mesh_model_t *model,
                            esp_ble_mesh_msg_ctx_t *ctx,
                            uint16_t lightness, uint16_t temperature, uint16_t delta_uv)
  {
      ENTER_FUNC();
      ESP_LOGI(TAG, "lightness 0x%d", lightness);
  
      if (opcode == ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_SET) {
          esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS,
                                             sizeof(lightness), (uint8_t *)&lightness);
      }
  
      g_elem_state[0].state.actual[VALUE_TYPE_TAR] = lightness;
      g_elem_state[0].state.temp[VALUE_TYPE_TAR] = temperature;
      if (g_elem_state[0].state.actual[VALUE_TYPE_CUR] != g_elem_state[0].state.actual[VALUE_TYPE_TAR] || g_elem_state[0].state.temp[VALUE_TYPE_CUR] != g_elem_state[0].state.temp[VALUE_TYPE_TAR]) {
          g_indication_flag |= INDICATION_FLAG_LIGHTNESS;
          g_indication_flag |= INDICATION_FLAG_CTL;
  
          esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_STATUS,
                                             sizeof(g_elem_state[0].state.temp[VALUE_TYPE_CUR]), (uint8_t *)&g_elem_state[0].state.temp[VALUE_TYPE_CUR]);
  
          /* Change corresponding binded states in root element */
          bind_onoff_with_ctl(&g_elem_state[0], VALUE_TYPE_TAR);
  
          if (esp_ble_mesh_node_is_provisioned()) {
              // update led status
              genie_event(GENIE_EVT_SDK_ANALYZE_MSG, &g_elem_state[0]);
          }
      }
  }
  #endif
  
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
  static void light_lightness_actual_set(uint32_t opcode, esp_ble_mesh_model_t *model,
                                         esp_ble_mesh_msg_ctx_t *ctx,
                                         uint16_t lightness)
  {
      ENTER_FUNC();
      ESP_LOGI(TAG, "lightness %d", lightness);
  
      if (opcode == ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET) {
          esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS,
                                             sizeof(lightness), (uint8_t *)&lightness);
      }
  
      g_elem_state[0].state.actual[VALUE_TYPE_TAR] = lightness;
      if (g_elem_state[0].state.actual[VALUE_TYPE_CUR] != g_elem_state[0].state.actual[VALUE_TYPE_TAR]) {
          g_indication_flag |= INDICATION_FLAG_LIGHTNESS;
  
          esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_STATUS,
                                             sizeof(g_elem_state[0].state.actual[VALUE_TYPE_CUR]), (uint8_t *)&g_elem_state[0].state.actual[VALUE_TYPE_CUR]);
  
          /* Change corresponding binded states in root element */
          bind_onoff_with_lightness(&g_elem_state[0], VALUE_TYPE_TAR);
  
          if (esp_ble_mesh_node_is_provisioned()) {
              // update led status
              genie_event(GENIE_EVT_SDK_ANALYZE_MSG, &g_elem_state[0]);
          }
      }
  }
  
  static void light_lightness_linear_set(uint32_t opcode, esp_ble_mesh_model_t *model,
                                         esp_ble_mesh_msg_ctx_t *ctx,
                                         uint16_t lightness)
  {
      ENTER_FUNC();
      ESP_LOGI(TAG, "lightness 0x%d", lightness);
  
      if (opcode == ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET) {
          esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS,
                                             sizeof(lightness), (uint8_t *)&lightness);
      }
  
      g_elem_state[0].state.linear[VALUE_TYPE_TAR] = lightness;
      if (g_elem_state[0].state.linear[VALUE_TYPE_CUR] != g_elem_state[0].state.linear[VALUE_TYPE_TAR]) {
          g_indication_flag |= INDICATION_FLAG_LIGHTNESS;
  
          esp_ble_mesh_server_model_send_msg(model, ctx, ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_STATUS,
                                             sizeof(g_elem_state[0].state.linear[VALUE_TYPE_CUR]), (uint8_t *)&g_elem_state[0].state.linear[VALUE_TYPE_CUR]);
  
          /* Change corresponding binded states in root element */
          bind_onoff_with_lightness(&g_elem_state[0], VALUE_TYPE_TAR);
  
          if (esp_ble_mesh_node_is_provisioned()) {
              // update led status
              genie_event(GENIE_EVT_SDK_ANALYZE_MSG, &g_elem_state[0]);
          }
      }
  }
  #endif
  
  static void light_onoff_set(esp_ble_mesh_model_t *model,
                              esp_ble_mesh_msg_ctx_t *ctx, uint8_t onoff)
  {
      ENTER_FUNC();
      uint16_t primary_addr    = esp_ble_mesh_get_primary_element_address();
      uint8_t  elem_count      = esp_ble_mesh_get_element_count();
      elem_state_t *led_state = NULL;
  
      g_elem_state[0].state.onoff[VALUE_TYPE_TAR] = onoff;
      if (g_elem_state[0].state.onoff[VALUE_TYPE_CUR] != g_elem_state[0].state.onoff[VALUE_TYPE_TAR]) {
          g_indication_flag |= INDICATION_FLAG_ONOFF;
          // g_elem_state[0].state.onoff[VALUE_TYPE_CUR] = g_elem_state[0].state.onoff[VALUE_TYPE_TAR];
  
          if (ESP_BLE_MESH_ADDR_IS_UNICAST(ctx->recv_dst)) {
              for (uint8_t i = 0; i < elem_count; i++) {
                  if (ctx->recv_dst == (primary_addr + i)) {
                      led_state = &g_elem_state[i];
                  }
              }
          } else if (ESP_BLE_MESH_ADDR_IS_GROUP(ctx->recv_dst)) {
              if (esp_ble_mesh_is_model_subscribed_to_group(model, ctx->recv_dst)) {
                  led_state = &g_elem_state[model->element->element_addr - primary_addr];
              }
          } else if (ctx->recv_dst == 0xFFFF) {
              led_state = &g_elem_state[model->element->element_addr - primary_addr];
          }
  
          if (led_state != NULL) {
              ESP_LOGI(TAG, "led onoff 0x%d", g_elem_state[0].state.onoff[VALUE_TYPE_TAR]);
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
              // update lightness
              bind_lightness_with_onoff(&g_elem_state[0], VALUE_TYPE_TAR);
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
              // update ctl
              bind_ctl_with_onoff(&g_elem_state[0], VALUE_TYPE_TAR);
  #endif
              // update led status
              genie_event(GENIE_EVT_SDK_ANALYZE_MSG, &g_elem_state[0]);
          }
      }
  }
  
  static void prov_complete(uint16_t net_idx, uint16_t addr, uint8_t flags, uint32_t iv_index)
  {
      ENTER_FUNC();
      ESP_LOGI(TAG, "net_idx: 0x%04x, addr: 0x%04x", net_idx, addr);
      ESP_LOGI(TAG, "flags: 0x%02x, iv_index: 0x%08x", flags, iv_index);
  }
  
  static void example_ble_mesh_provisioning_cb(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_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);
          // send event to genie event loop
          genie_event(GENIE_EVT_SDK_MESH_INIT, &param->node_prov_enable_comp.err_code);
  
          if (param->node_prov_enable_comp.err_code == -EALREADY) {
              // genie mesh init
              genie_mesh_init();
          }
          break;
      case ESP_BLE_MESH_NODE_PROV_DISABLE_COMP_EVT:
          ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_DISABLE_COMP_EVT");
          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");
  
          // send event to genie event loop
          genie_event(GENIE_EVT_SDK_MESH_PROV_START, NULL);
  
          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: // already have configuration when device restart, and provisioning completed.
          ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_COMPLETE_EVT");
          ESP_LOG_BUFFER_HEX("NetKey: ", param->node_prov_complete.net_key, 16);
          ESP_LOGI(TAG, "net_idx 0x%04x, NetKey: %s, Primary address: 0x%04x", param->node_prov_complete.net_idx,
                   util_hex2str(param->node_prov_complete.net_key, 16), param->node_prov_complete.addr);
  
          prov_complete(param->node_prov_complete.net_idx, param->node_prov_complete.addr,
                        param->node_prov_complete.flags, param->node_prov_complete.iv_index);
  
          user_prov_complete(param->node_prov_complete.net_idx, param->node_prov_complete.addr);
          break;
      case ESP_BLE_MESH_NODE_PROV_RESET_EVT:
          ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_PROV_RESET_EVT");
  
          // send event to genie event loop
          genie_event(GENIE_EVT_SW_RESET, NULL);
  
          user_prov_reset();
          break;
      case ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT:
          ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_SET_UNPROV_DEV_NAME_COMP_EVT, err_code %d", param->node_set_unprov_dev_name_comp.err_code);
          break;
      case ESP_BLE_MESH_PROVISIONER_STORE_NODE_COMP_DATA_COMP_EVT:
          ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_STORE_NODE_COMP_DATA_COMP_EVT, err_code %d", param->provisioner_store_node_comp_data_comp.err_code);
          break;
      case ESP_BLE_MESH_MODEL_SUBSCRIBE_GROUP_ADDR_COMP_EVT:
          ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_SUBSCRIBE_GROUP_ADDR_COMP_EVT, err_code %d, element_addr 0x%04x, company_id 0x%04x, model_id 0x%04x, group_addr 0x%04x",
                   param->model_sub_group_addr_comp.err_code, param->model_sub_group_addr_comp.element_addr,
                   param->model_sub_group_addr_comp.company_id, param->model_sub_group_addr_comp.model_id,
                   param->model_sub_group_addr_comp.group_addr);
          break;
      case ESP_BLE_MESH_NODE_BIND_APP_KEY_TO_MODEL_COMP_EVT:
          ESP_LOGI(TAG, "ESP_BLE_MESH_NODE_BIND_APP_KEY_TO_MODEL_COMP_EVT, err_code %d, element_addr 0x%04x, company_id 0x%04x, model_id 0x%04x, app_idx 0x%04x",
                   param->node_bind_app_key_to_model_comp.err_code, param->node_bind_app_key_to_model_comp.element_addr,
                   param->node_bind_app_key_to_model_comp.company_id, param->node_bind_app_key_to_model_comp.model_id,
                   param->node_bind_app_key_to_model_comp.app_idx);
          break;
      default:
          ESP_LOGI(TAG, "event %d", event);
          break;
      }
  }
  
  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)
  {
      if (event == ESP_BLE_MESH_CFG_SERVER_STATE_CHANGE_EVT) {
          switch (param->ctx.recv_op) {
          case ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD");
              ESP_LOGI(TAG, "net_idx 0x%04x, NetKey: %s",
                       param->value.state_change.netkey_add.net_idx,
                       util_hex2str(param->value.state_change.netkey_add.net_key, 16));
              break;
          case ESP_BLE_MESH_MODEL_OP_NET_KEY_UPDATE:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_NET_KEY_UPDATE");
              break;
          case ESP_BLE_MESH_MODEL_OP_NET_KEY_DELETE:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_NET_KEY_DELETE");
              break;
  
          case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD");
              ESP_LOGI(TAG, "net_idx 0x%04x, app_idx 0x%04x, AppKey: %s",
                       param->value.state_change.appkey_add.net_idx, param->value.state_change.appkey_add.app_idx,
                       util_hex2str(param->value.state_change.appkey_add.app_key, 16));
  
              // send event to genie event loop
              genie_event(GENIE_EVT_SDK_APPKEY_ADD, &param->value.state_change.appkey_add.app_idx);
  
  #if GENIE_VENDOR_MODEL_VERSION == 0
  
  #elif GENIE_VENDOR_MODEL_VERSION == 1
              // local bind AppKEY and Subcribe Group Address
              local_operation(param->value.state_change.appkey_add.app_idx);
              // genie mesh init
              genie_mesh_init();
  #endif
              break;
          case ESP_BLE_MESH_MODEL_OP_APP_KEY_UPDATE:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_APP_KEY_UPDATE");
              break;
          case ESP_BLE_MESH_MODEL_OP_APP_KEY_DELETE:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_APP_KEY_DELETE");
              break;
  
          case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND");
              ESP_LOGI(TAG, "elem_addr 0x%04x, app_idx 0x%04x, cid 0x%04x, mod_id 0x%04x",
                       param->value.state_change.mod_app_bind.element_addr,
                       param->value.state_change.mod_app_bind.app_idx,
                       param->value.state_change.mod_app_bind.company_id,
                       param->value.state_change.mod_app_bind.model_id);
              break;
  
          case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD");
              ESP_LOGI(TAG, "elem_addr 0x%04x, sub_addr 0x%04x, cid 0x%04x, mod_id 0x%04x",
                       param->value.state_change.mod_sub_add.element_addr,
                       param->value.state_change.mod_sub_add.sub_addr,
                       param->value.state_change.mod_sub_add.company_id,
                       param->value.state_change.mod_sub_add.model_id);
  #if GENIE_VENDOR_MODEL_VERSION == 0
              // genie mesh init
              genie_mesh_init();
  #elif GENIE_VENDOR_MODEL_VERSION == 1
  
  #endif
              break;
          case ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE");
              break;
  
          case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET");
              break;
          case ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET:
              ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET");
              break;
          default:
              ESP_LOGW(TAG, "%s, event %d", __FUNCTION__, event);
              break;
          }
      }
  }
  
  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_LOGD(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) {
  
              light_onoff_set(param->model, &param->ctx, param->value.state_change.onoff_set.onoff);
  
              /* Update bound states second way */
              // light_lightness_actual_set(param->ctx.recv_op, param->model,
              //     &param->ctx, param->value.state_change.onoff_set.onoff);
          }
          break;
      case ESP_BLE_MESH_GENERIC_SERVER_RECV_GET_MSG_EVT:
          ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_SERVER_RECV_GET_MSG_EVT");
          if (param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET) {
              esp_ble_mesh_gen_onoff_srv_t *srv = param->model->user_data;
              ESP_LOGI(TAG, "onoff: 0x%02x", srv->state.onoff);
              esp_ble_mesh_server_model_send_msg(param->model, &param->ctx,
                                                 ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, sizeof(srv->state.onoff), &srv->state.onoff);
          }
          break;
      case ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT:
          ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_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, tid 0x%02x", param->value.set.onoff.onoff, param->value.set.onoff.tid);
              if (param->value.set.onoff.op_en) {
                  ESP_LOGI(TAG, "trans_time 0x%02x, delay 0x%02x",
                           param->value.set.onoff.trans_time, param->value.set.onoff.delay);
              }
  
              esp_ble_mesh_gen_onoff_srv_t *srv = param->model->user_data;
              if (param->value.set.onoff.op_en == false) {
                  srv->state.onoff = param->value.set.onoff.onoff;
              } else {
                  /* TODO: Delay and state transition */
                  srv->state.onoff = param->value.set.onoff.onoff;
              }
              g_indication_flag |= INDICATION_FLAG_ONOFF;
              if (param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET) {
                  esp_ble_mesh_server_model_send_msg(param->model, &param->ctx,
                                                     ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, sizeof(srv->state.onoff), &srv->state.onoff);
              }
  
              light_onoff_set(param->model, &param->ctx, srv->state.onoff);
          }
          break;
      default:
          ESP_LOGW(TAG, "Unknown Generic Server event 0x%02x", event);
          break;
      }
  }
  
  static void example_ble_mesh_lighting_server_cb(esp_ble_mesh_lighting_server_cb_event_t event,
          esp_ble_mesh_lighting_server_cb_param_t *param)
  {
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
      uint16_t lightness;
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
      uint16_t temperature;
      uint16_t delta_uv;
  #endif
      esp_ble_mesh_server_state_value_t state = {0};
  
      ESP_LOGD(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_LIGHTING_SERVER_STATE_CHANGE_EVT:
          switch (param->ctx.recv_op) {
  #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV
          /*!< Light Lightness Message Opcode */
          case ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET:
          case ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_SET_UNACK: {
              /* Light Lightness Server Model - Primary Element */
              ESP_LOGI(TAG, "lightness actual %d", param->value.state_change.lightness_set.lightness);
              /* Update bound states */
              lightness = param->value.state_change.lightness_set.lightness;
              state.light_lightness_linear.lightness = convert_lightness_actual_to_linear(lightness);
              esp_ble_mesh_server_model_update_state(lightness_server.model, ESP_BLE_MESH_LIGHT_LIGHTNESS_LINEAR_STATE, &state);
              state.gen_onoff.onoff = lightness ? LED_ON : LED_OFF;
              esp_ble_mesh_server_model_update_state(onoff_server_0.model, ESP_BLE_MESH_GENERIC_ONOFF_STATE, &state);
  
              /* Update bound states second way */
              light_lightness_actual_set(param->ctx.recv_op, param->model,
                                         &param->ctx, lightness);
              break;
          }
          case ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET:
          case ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_LINEAR_SET_UNACK: {
              /* Light Lightness Server Model - Primary Element */
              ESP_LOGI(TAG, "lightness linear %d", param->value.state_change.lightness_linear_set.lightness);
              /* Update bound states */
              lightness = convert_lightness_linear_to_actual(param->value.state_change.lightness_linear_set.lightness);
              state.light_lightness_actual.lightness = lightness;
              esp_ble_mesh_server_model_update_state(lightness_server.model, ESP_BLE_MESH_LIGHT_LIGHTNESS_ACTUAL_STATE, &state);
              state.gen_onoff.onoff = lightness ? LED_ON : LED_OFF;
              esp_ble_mesh_server_model_update_state(onoff_server_0.model, ESP_BLE_MESH_GENERIC_ONOFF_STATE, &state);
  
              /* Update bound states second way */
              light_lightness_linear_set(param->ctx.recv_op, param->model,
                                         &param->ctx, lightness);
              break;
          }
          /*!< Light Lightness Setup Message Opcode */
          case ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET:
          case ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_DEFAULT_SET_UNACK:
              /* Light Lightness Setup Server Model - Primary Element */
              ESP_LOGI(TAG, "lightness default %d", param->value.state_change.lightness_default_set.lightness);
              break;
          case ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET:
          case ESP_BLE_MESH_MODEL_OP_LIGHT_LIGHTNESS_RANGE_SET_UNACK:
              /* Light Lightness Setup Server Model - Primary Element */
              ESP_LOGI(TAG, "lightness min %d, max %d",
                       param->value.state_change.lightness_range_set.range_min,
                       param->value.state_change.lightness_range_set.range_max);
              break;
  #endif
  #ifdef CONFIG_MESH_MODEL_CTL_SRV
          /*!< Light CTL Message Opcode */
          case ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_SET:
          case ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_SET_UNACK:
              /* Light CTL Server Model - Primary Element */
              ESP_LOGI(TAG, "lightness 0x%04x, temperature 0x%04x, delta uv 0x%04x",
                       param->value.state_change.ctl_set.lightness,
                       param->value.state_change.ctl_set.temperature,
                       param->value.state_change.ctl_set.delta_uv);
              /* Update bound states */
              lightness = param->value.state_change.ctl_set.lightness;
              temperature = param->value.state_change.ctl_set.temperature;
              delta_uv = param->value.state_change.ctl_set.delta_uv;
              state.light_lightness_actual.lightness = lightness;
              esp_ble_mesh_server_model_update_state(lightness_server.model, ESP_BLE_MESH_LIGHT_LIGHTNESS_ACTUAL_STATE, &state);
              state.light_lightness_linear.lightness = convert_lightness_actual_to_linear(lightness);
              esp_ble_mesh_server_model_update_state(lightness_server.model, ESP_BLE_MESH_LIGHT_LIGHTNESS_LINEAR_STATE, &state);
              state.gen_onoff.onoff = lightness ? LED_ON : LED_OFF;
              esp_ble_mesh_server_model_update_state(onoff_server_0.model, ESP_BLE_MESH_GENERIC_ONOFF_STATE, &state);
  
              /* Update bound states second way */
              light_ctl_set(param->ctx.recv_op, param->model,
                            &param->ctx, lightness, temperature, delta_uv);
              break;
          case ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET:
          case ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_SET_UNACK:
              /* Light CTL Temperature Server Model - Secondary Element */
              ESP_LOGI(TAG, "temperature 0x%04x, delta uv 0x%04x",
                       param->value.state_change.ctl_temp_set.temperature,
                       param->value.state_change.ctl_temp_set.delta_uv);
              break;
          /*!< Light CTL Setup Message Opcode */
          case ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET:
          case ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_DEFAULT_SET_UNACK:
              /* Light CTL Setup Server Model - Primary Element */
              ESP_LOGI(TAG, "lightness 0x%04x, temperature 0x%04x, delta uv 0x%04x",
                       param->value.state_change.ctl_default_set.lightness,
                       param->value.state_change.ctl_default_set.temperature,
                       param->value.state_change.ctl_default_set.delta_uv);
              break;
          case ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET:
          case ESP_BLE_MESH_MODEL_OP_LIGHT_CTL_TEMPERATURE_RANGE_SET_UNACK:
              /* Light CTL Setup Server Model - Primary Element */
              ESP_LOGI(TAG, "temperature min 0x%04x, max 0x%04x",
                       param->value.state_change.ctl_temp_range_set.range_min,
                       param->value.state_change.ctl_temp_range_set.range_max);
              break;
  #endif
          }
          break;
      default:
          ESP_LOGE(TAG, "Unknown Lighting Server event 0x%02x", event);
          break;
      }
  }
  
  static void example_ble_mesh_custom_model_cb(esp_ble_mesh_model_cb_event_t event,
          esp_ble_mesh_model_cb_param_t *param)
  {
      ESP_LOGD(TAG, "event 0x%02x", event);
  
      switch (event) {
      case ESP_BLE_MESH_MODEL_OPERATION_EVT:
          ESP_LOGD(TAG, "ESP_BLE_MESH_MODEL_OPERATION_EVT");
          ESP_LOGD(TAG, "opcode: 0x%06x", param->model_operation.opcode);
          genie_model_dispatch(param->model_operation.opcode, param->model_operation.model,
                               param->model_operation.ctx, param->model_operation.msg, param->model_operation.length);
          break;
      case ESP_BLE_MESH_MODEL_SEND_COMP_EVT:
          ESP_LOGD(TAG, "ESP_BLE_MESH_MODEL_SEND_COMP_EVT");
          ESP_LOGD(TAG, "opcode: 0x%06x", param->model_send_comp.opcode);
          if (param->model_send_comp.err_code) {
              ESP_LOGE(TAG, "Failed to send message: 0x%06x, err: %d", param->model_send_comp.opcode, param->model_send_comp.err_code);
              break;
          }
          break;
      case ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT:
          ESP_LOGD(TAG, "ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT");
          break;
      case ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT:
          ESP_LOGD(TAG, "ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT");
          break;
      case ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT:
          ESP_LOGD(TAG, "ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT");
          break;
      case ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT:
          ESP_LOGD(TAG, "ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT");
          break;
      case ESP_BLE_MESH_SERVER_MODEL_UPDATE_STATE_COMP_EVT:
          ESP_LOGD(TAG, "ESP_BLE_MESH_SERVER_MODEL_UPDATE_STATE_COMP_EVT");
          ESP_LOGD(TAG, "result %d, model id 0x%04x, type 0x%02x",
                   param->server_model_update_state.err_code,
                   param->server_model_update_state.model->model_id,
                   param->server_model_update_state.type);
          break;
      case ESP_BLE_MESH_MODEL_EVT_MAX:
          ESP_LOGW(TAG, "ESP_BLE_MESH_MODEL_EVT_MAX");
          break;
      default:
          break;
      }
  }
  
  static esp_err_t ble_mesh_init(void)
  {
      esp_err_t err;
  
      ESP_ERROR_CHECK(esp_ble_mesh_register_prov_callback(example_ble_mesh_provisioning_cb));
      ESP_ERROR_CHECK(esp_ble_mesh_register_config_server_callback(example_ble_mesh_config_server_cb));
      ESP_ERROR_CHECK(esp_ble_mesh_register_generic_server_callback(example_ble_mesh_generic_server_cb));
      ESP_ERROR_CHECK(esp_ble_mesh_register_lighting_server_callback(example_ble_mesh_lighting_server_cb));
      ESP_ERROR_CHECK(esp_ble_mesh_register_custom_model_callback(example_ble_mesh_custom_model_cb));
  
      err = esp_ble_mesh_init(&provision, &composition);
      if (err) {
          ESP_LOGE(TAG, "Initializing mesh failed (err %d)", err);
          return err;
      }
  
      ESP_ERROR_CHECK(esp_ble_mesh_node_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT));
  
      if (esp_ble_mesh_node_is_provisioned()) {
          ESP_LOGW(TAG, "node already provisioned");
      } else {
          ESP_LOGW(TAG, "node not provisioned");
          light_driver_breath_start(0, 255, 0); /**< green blink */
      }
  
      return err;
  }
  
  void config_triples(void)
  {
      // AuthValue = SHA256(Product ID,MAC,Secret)
      char *authvalue_string = NULL;
      asprintf(&authvalue_string, "%08x,%s,%s", CONFIG_TRIPLES_PRODUCT_ID, CONFIG_TRIPLES_DEVICE_NAME, CONFIG_TRIPLES_DEVICE_SECRET);
      ESP_LOGI(TAG, "authvalue_string: %s", authvalue_string);
  
      uint8_t sha256_out[32] = {0};
      mbedtls_sha256_ret((const unsigned char *)authvalue_string, strlen(authvalue_string), sha256_out, 0);
      memcpy(static_val, sha256_out, 16);
      provision.static_val = static_val;
  
      uint8_t product_id[4];
      mac_str2hex(authvalue_string, product_id);
      swap_buf(dev_uuid + 3, product_id, 4);
  
      uint8_t ali_mac_address[6];
      mac_str2hex(CONFIG_TRIPLES_DEVICE_NAME, ali_mac_address);
      swap_buf(dev_uuid + 7, ali_mac_address, 6);
      ali_mac_address[5] -= 2;
      esp_base_mac_addr_set(ali_mac_address);
  
      free(authvalue_string);
  }
  
  void esp_gateway_ble_init(void)
  {
      esp_err_t err;
  
      /* Open nvs namespace for storing/restoring mesh example info */
      err = ble_mesh_nvs_open(&NVS_HANDLE);
      if (err) {
          ESP_LOGE(TAG, "ble_mesh_nvs_open failed (err %d)", err);
          return;
      }
  
      esp_log_level_set("genie_event", ESP_LOG_DEBUG);
      ESP_LOGI(TAG, "Initializing...");
  
      // board_init();
  
  #ifdef CONFIG_GENIE_RESET_BY_REPEAT
      genie_reset_by_repeat_init();
  #endif
  
      config_triples();
  
      err = bluetooth_init();
      if (err) {
          ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err);
          return;
      }
  
      /* Initialize the Bluetooth Mesh Subsystem */
      err = ble_mesh_init();
      if (err) {
          ESP_LOGE(TAG, "Bluetooth mesh init failed (err %d)", err);
      }
  }
  