From 505213c028fdff739c53fcc8132bb898d696ad98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferran=20Verd=C3=A9s?= Date: Tue, 11 Jul 2017 12:24:32 +0200 Subject: [PATCH 001/381] 'size' and 'toString' functions have been added to JSONArray --- cpp_utils/JSON.cpp | 21 +++++++++++++++++++++ cpp_utils/JSON.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index 7af015df..4bebda0a 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -175,6 +175,27 @@ std::string JsonArray::getString(int item) { } // getString +/** + * @brief Convert the JSON array to a string. + * @return A JSON string representation of the array. + */ +std::string JsonArray::toString() { + char *data = cJSON_Print(m_node); + std::string ret(data); + free(data); + return ret; +} // toString + + +/** + * @brief Get the number of elements from the array. + * @return The int value that represents the number of elements. + */ +std::size_t JsonArray::size() { + return cJSON_GetArraySize(m_node); +} // size + + JsonObject::JsonObject(cJSON* node) { m_node = node; } diff --git a/cpp_utils/JSON.h b/cpp_utils/JSON.h index c7e6dd9f..08915104 100644 --- a/cpp_utils/JSON.h +++ b/cpp_utils/JSON.h @@ -44,6 +44,8 @@ class JsonArray { void addInt(int value); void addObject(JsonObject value); void addString(std::string value); + std::string toString(); + std::size_t size(); /** * @brief The underlying cJSON node. */ From 6bbd173641fed83a923692c75ed68e2a177a3e23 Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 12 Jul 2017 09:54:15 -0500 Subject: [PATCH 002/381] Sync 2017-07-12 --- .../BLE XML/Characteristics/code/.gitignore | 3 + .../Characteristics/code/characteristics.txt | 196 ++++++++++++++++++ cpp_utils/BLE XML/Characteristics/code/run.sh | 20 ++ cpp_utils/BLE XML/README.md | 4 + cpp_utils/BLE XML/Services/code/.gitignore | 3 + cpp_utils/BLE XML/Services/code/run.sh | 20 ++ cpp_utils/BLE XML/Services/code/services.txt | 36 ++++ cpp_utils/BLEAddress.cpp | 27 ++- cpp_utils/BLEAddress.h | 5 +- cpp_utils/BLEAdvertisedDevice.cpp | 27 +-- cpp_utils/BLEAdvertisedDevice.h | 24 ++- cpp_utils/BLEAdvertising.cpp | 6 +- cpp_utils/BLECharacteristic.cpp | 8 +- cpp_utils/BLEClient.cpp | 10 +- cpp_utils/BLERemoteCharacteristic.cpp | 74 ++++++- cpp_utils/BLERemoteCharacteristic.h | 3 + cpp_utils/BLEScan.cpp | 62 +++++- cpp_utils/BLEScan.h | 29 ++- cpp_utils/BLEService.h | 28 +-- cpp_utils/README.md | 12 ++ 20 files changed, 505 insertions(+), 92 deletions(-) create mode 100644 cpp_utils/BLE XML/Characteristics/code/.gitignore create mode 100644 cpp_utils/BLE XML/Characteristics/code/characteristics.txt create mode 100755 cpp_utils/BLE XML/Characteristics/code/run.sh create mode 100644 cpp_utils/BLE XML/README.md create mode 100644 cpp_utils/BLE XML/Services/code/.gitignore create mode 100755 cpp_utils/BLE XML/Services/code/run.sh create mode 100644 cpp_utils/BLE XML/Services/code/services.txt diff --git a/cpp_utils/BLE XML/Characteristics/code/.gitignore b/cpp_utils/BLE XML/Characteristics/code/.gitignore new file mode 100644 index 00000000..982fcb1d --- /dev/null +++ b/cpp_utils/BLE XML/Characteristics/code/.gitignore @@ -0,0 +1,3 @@ +*.xml +*.json + diff --git a/cpp_utils/BLE XML/Characteristics/code/characteristics.txt b/cpp_utils/BLE XML/Characteristics/code/characteristics.txt new file mode 100644 index 00000000..8917debc --- /dev/null +++ b/cpp_utils/BLE XML/Characteristics/code/characteristics.txt @@ -0,0 +1,196 @@ +org.bluetooth.characteristic.aerobic_heart_rate_lower_limit +org.bluetooth.characteristic.aerobic_heart_rate_upper_limit +org.bluetooth.characteristic.aerobic_threshold +org.bluetooth.characteristic.age +org.bluetooth.characteristic.aggregate +org.bluetooth.characteristic.alert_category_id +org.bluetooth.characteristic.alert_category_id_bit_mask +org.bluetooth.characteristic.alert_level +org.bluetooth.characteristic.alert_notification_control_point +org.bluetooth.characteristic.alert_status +org.bluetooth.characteristic.altitude +org.bluetooth.characteristic.anaerobic_heart_rate_lower_limit +org.bluetooth.characteristic.anaerobic_heart_rate_upper_limit +org.bluetooth.characteristic.anaerobic_threshold +org.bluetooth.characteristic.analog +org.bluetooth.characteristic.apparent_wind_direction +org.bluetooth.characteristic.apparent_wind_speed +org.bluetooth.characteristic.gap.appearance +org.bluetooth.characteristic.barometric_pressure_trend +org.bluetooth.characteristic.battery_level +org.bluetooth.characteristic.blood_pressure_feature +org.bluetooth.characteristic.blood_pressure_measurement +org.bluetooth.characteristic.body_composition_feature +org.bluetooth.characteristic.body_composition_measurement +org.bluetooth.characteristic.body_sensor_location +org.bluetooth.characteristic.bond_management_control_point +org.bluetooth.characteristic.bond_management_feature +org.bluetooth.characteristic.boot_keyboard_input_report +org.bluetooth.characteristic.boot_keyboard_output_report +org.bluetooth.characteristic.boot_mouse_input_report +org.bluetooth.characteristic.gap.central_address_resolution_support +org.bluetooth.characteristic.cgm_feature +org.bluetooth.characteristic.cgm_measurement +org.bluetooth.characteristic.cgm_session_run_time +org.bluetooth.characteristic.cgm_session_start_time +org.bluetooth.characteristic.cgm_specific_ops_control_point +org.bluetooth.characteristic.cgm_status +org.bluetooth.characteristic.cross_trainer_data +org.bluetooth.characteristic.csc_feature +org.bluetooth.characteristic.csc_measurement +org.bluetooth.characteristic.current_time +org.bluetooth.characteristic.cycling_power_control_point +org.bluetooth.characteristic.cycling_power_feature +org.bluetooth.characteristic.cycling_power_measurement +org.bluetooth.characteristic.cycling_power_vector +org.bluetooth.characteristic.database_change_increment +org.bluetooth.characteristic.date_of_birth +org.bluetooth.characteristic.date_of_threshold_assessment +org.bluetooth.characteristic.date_time +org.bluetooth.characteristic.day_date_time +org.bluetooth.characteristic.day_of_week +org.bluetooth.characteristic.descriptor_value_changed +org.bluetooth.characteristic.gap.device_name +org.bluetooth.characteristic.dew_point +org.bluetooth.characteristic.digital +org.bluetooth.characteristic.dst_offset +org.bluetooth.characteristic.elevation +org.bluetooth.characteristic.email_address +org.bluetooth.characteristic.exact_time_256 +org.bluetooth.characteristic.fat_burn_heart_rate_lower_limit +org.bluetooth.characteristic.fat_burn_heart_rate_upper_limit +org.bluetooth.characteristic.firmware_revision_string +org.bluetooth.characteristic.first_name +org.bluetooth.characteristic.fitness_machine_control_point +org.bluetooth.characteristic.fitness_machine_feature +org.bluetooth.characteristic.fitness_machine_status +org.bluetooth.characteristic.five_zone_heart_rate_limits +org.bluetooth.characteristic.floor_number +org.bluetooth.characteristic.gender +org.bluetooth.characteristic.glucose_feature +org.bluetooth.characteristic.glucose_measurement +org.bluetooth.characteristic.glucose_measurement_context +org.bluetooth.characteristic.gust_factor +org.bluetooth.characteristic.hardware_revision_string +org.bluetooth.characteristic.heart_rate_control_point +org.bluetooth.characteristic.heart_rate_max +org.bluetooth.characteristic.heart_rate_measurement +org.bluetooth.characteristic.heat_index +org.bluetooth.characteristic.height +org.bluetooth.characteristic.hid_control_point +org.bluetooth.characteristic.hid_information +org.bluetooth.characteristic.hip_circumference +org.bluetooth.characteristic.http_control_point +org.bluetooth.characteristic.http_entity_body +org.bluetooth.characteristic.http_headers +org.bluetooth.characteristic.http_status_code +org.bluetooth.characteristic.https_security +org.bluetooth.characteristic.humidity +org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list +org.bluetooth.characteristic.indoor_bike_data +org.bluetooth.characteristic.indoor_positioning_configuration +org.bluetooth.characteristic.intermediate_cuff_pressure +org.bluetooth.characteristic.intermediate_temperature +org.bluetooth.characteristic.irradiance +org.bluetooth.characteristic.language +org.bluetooth.characteristic.last_name +org.bluetooth.characteristic.latitude +org.bluetooth.characteristic.ln_control_point +org.bluetooth.characteristic.ln_feature +org.bluetooth.characteristic.local_east_coordinate +org.bluetooth.characteristic.local_north_coordinate +org.bluetooth.characteristic.local_time_information +org.bluetooth.characteristic.location_and_speed +org.bluetooth.characteristic.location_name +org.bluetooth.characteristic.longitude +org.bluetooth.characteristic.magnetic_declination +org.bluetooth.characteristic.magnetic_flux_density_2D +org.bluetooth.characteristic.magnetic_flux_density_3D +org.bluetooth.characteristic.manufacturer_name_string +org.bluetooth.characteristic.maximum_recommended_heart_rate +org.bluetooth.characteristic.measurement_interval +org.bluetooth.characteristic.model_number_string +org.bluetooth.characteristic.navigation +org.bluetooth.characteristic.new_alert +org.bluetooth.characteristic.object_action_control_point +org.bluetooth.characteristic.object_changed +org.bluetooth.characteristic.object_first_created +org.bluetooth.characteristic.object_id +org.bluetooth.characteristic.object_last_modified +org.bluetooth.characteristic.object_list_control_point +org.bluetooth.characteristic.object_list_filter +org.bluetooth.characteristic.object_name +org.bluetooth.characteristic.object_properties +org.bluetooth.characteristic.object_size +org.bluetooth.characteristic.object_type +org.bluetooth.characteristic.ots_feature +org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters +org.bluetooth.characteristic.gap.peripheral_privacy_flag +org.bluetooth.characteristic.plx_continuous_measurement +org.bluetooth.characteristic.plx_features +org.bluetooth.characteristic.plx_spot_check_measurement +org.bluetooth.characteristic.pnp_id +org.bluetooth.characteristic.pollen_concentration +org.bluetooth.characteristic.position_quality +org.bluetooth.characteristic.pressure +org.bluetooth.characteristic.protocol_mode +org.bluetooth.characteristic.rainfall +org.bluetooth.characteristic.gap.reconnection_address +org.bluetooth.characteristic.record_access_control_point +org.bluetooth.characteristic.reference_time_information +org.bluetooth.characteristic.report +org.bluetooth.characteristic.report_map +org.bluetooth.characteristic.resolvable_private_address_only +org.bluetooth.characteristic.resting_heart_rate +org.bluetooth.characteristic.ringer_control_point +org.bluetooth.characteristic.ringer_setting +org.bluetooth.characteristic.rower_data +org.bluetooth.characteristic.rsc_feature +org.bluetooth.characteristic.rsc_measurement +org.bluetooth.characteristic.sc_control_point +org.bluetooth.characteristic.scan_interval_window +org.bluetooth.characteristic.scan_refresh +org.bluetooth.characteristic.sensor_location +org.bluetooth.characteristic.serial_number_string +org.bluetooth.characteristic.gatt.service_changed +org.bluetooth.characteristic.software_revision_string +org.bluetooth.characteristic.sport_type_for_aerobic_and_anaerobic_thresholds +org.bluetooth.characteristic.stair_climber_data +org.bluetooth.characteristic.step_climber_data +org.bluetooth.characteristic.supported_heart_rate_range +org.bluetooth.characteristic.supported_inclination_range +org.bluetooth.characteristic.supported_new_alert_category +org.bluetooth.characteristic.supported_power_range +org.bluetooth.characteristic.supported_resistance_level_range +org.bluetooth.characteristic.supported_speed_range +org.bluetooth.characteristic.supported_unread_alert_category +org.bluetooth.characteristic.system_id +org.bluetooth.characteristic.tds_control_point +org.bluetooth.characteristic.temperature +org.bluetooth.characteristic.temperature_measurement +org.bluetooth.characteristic.temperature_type +org.bluetooth.characteristic.three_zone_heart_rate_limits +org.bluetooth.characteristic.time_accuracy +org.bluetooth.characteristic.time_source +org.bluetooth.characteristic.time_update_control_point +org.bluetooth.characteristic.time_update_state +org.bluetooth.characteristic.time_with_dst +org.bluetooth.characteristic.time_zone +org.bluetooth.characteristic.training_status +org.bluetooth.characteristic.treadmill_data +org.bluetooth.characteristic.true_wind_direction +org.bluetooth.characteristic.true_wind_speed +org.bluetooth.characteristic.two_zone_heart_rate_limit +org.bluetooth.characteristic.tx_power_level +org.bluetooth.characteristic.uncertainty +org.bluetooth.characteristic.unread_alert_status +org.bluetooth.characteristic.uri +org.bluetooth.characteristic.user_control_point +org.bluetooth.characteristic.user_index +org.bluetooth.characteristic.uv_index +org.bluetooth.characteristic.vo2_max +org.bluetooth.characteristic.waist_circumference +org.bluetooth.characteristic.weight +org.bluetooth.characteristic.weight_measurement +org.bluetooth.characteristic.weight_scale_feature +org.bluetooth.characteristic.wind_chill \ No newline at end of file diff --git a/cpp_utils/BLE XML/Characteristics/code/run.sh b/cpp_utils/BLE XML/Characteristics/code/run.sh new file mode 100755 index 00000000..77f193fe --- /dev/null +++ b/cpp_utils/BLE XML/Characteristics/code/run.sh @@ -0,0 +1,20 @@ +#!/bin/bash +BASE_URL="https://www.bluetooth.com/api/gatt/XmlFile?xmlFileName=" +RESULT=characteristics.json +COUNT=0 +echo -e "[\n" > ${RESULT} +for fileName in `cat characteristics.txt` +do + echo "Process file ${fileName}" + wget --output-document ${fileName}.xml --quiet "${BASE_URL}${fileName}.xml" + if [ ${COUNT} -gt 0 ] + then + echo -e ",\n" >> ${RESULT} + fi + #xml2json < "${fileName}.xml" >> ${RESULT} + xml2json "${fileName}.xml" "${fileName}.json" + cat "${fileName}.json" >> ${RESULT} + COUNT=$(expr ${COUNT} + 1) +done +echo -e "\n]\n" >> ${RESULT} +echo "done" \ No newline at end of file diff --git a/cpp_utils/BLE XML/README.md b/cpp_utils/BLE XML/README.md new file mode 100644 index 00000000..889721dd --- /dev/null +++ b/cpp_utils/BLE XML/README.md @@ -0,0 +1,4 @@ +# XML to JSON +The BLE authority makes available description of Services and Characteristics in XML documents. Our goal is to work with these +in JavaScript. These tools download each of the XML documents and convert them to JSON equivalents. To perform that task we +use the NPM package called [xml2json-cli](https://www.npmjs.com/package/xml2json-cli). \ No newline at end of file diff --git a/cpp_utils/BLE XML/Services/code/.gitignore b/cpp_utils/BLE XML/Services/code/.gitignore new file mode 100644 index 00000000..982fcb1d --- /dev/null +++ b/cpp_utils/BLE XML/Services/code/.gitignore @@ -0,0 +1,3 @@ +*.xml +*.json + diff --git a/cpp_utils/BLE XML/Services/code/run.sh b/cpp_utils/BLE XML/Services/code/run.sh new file mode 100755 index 00000000..bfa084d0 --- /dev/null +++ b/cpp_utils/BLE XML/Services/code/run.sh @@ -0,0 +1,20 @@ +#!/bin/bash +BASE_URL="https://www.bluetooth.com/api/gatt/XmlFile?xmlFileName=" +RESULT=services.json +COUNT=0 +echo -e "[\n" > ${RESULT} +for fileName in `cat services.txt` +do + echo "Process file ${fileName}" + wget --output-document ${fileName}.xml --quiet "${BASE_URL}${fileName}.xml" + if [ ${COUNT} -gt 0 ] + then + echo -e ",\n" >> ${RESULT} + fi + #xml2json < "${fileName}.xml" >> ${RESULT} + xml2json "${fileName}.xml" "${fileName}.json" + cat "${fileName}.json" >> ${RESULT} + COUNT=$(expr ${COUNT} + 1) +done +echo -e "\n]\n" >> ${RESULT} +echo "done" \ No newline at end of file diff --git a/cpp_utils/BLE XML/Services/code/services.txt b/cpp_utils/BLE XML/Services/code/services.txt new file mode 100644 index 00000000..c184dc96 --- /dev/null +++ b/cpp_utils/BLE XML/Services/code/services.txt @@ -0,0 +1,36 @@ +org.bluetooth.service.alert_notification +org.bluetooth.service.automation_io +org.bluetooth.service.battery_service +org.bluetooth.service.blood_pressure +org.bluetooth.service.body_composition +org.bluetooth.service.bond_management +org.bluetooth.service.continuous_glucose_monitoring +org.bluetooth.service.current_time +org.bluetooth.service.cycling_power +org.bluetooth.service.cycling_speed_and_cadence +org.bluetooth.service.device_information +org.bluetooth.service.environmental_sensing +org.bluetooth.service.fitness_machine +org.bluetooth.service.generic_access +org.bluetooth.service.generic_attribute +org.bluetooth.service.glucose +org.bluetooth.service.health_thermometer +org.bluetooth.service.heart_rate +org.bluetooth.service.http_proxy +org.bluetooth.service.human_interface_device +org.bluetooth.service.immediate_alert +org.bluetooth.service.indoor_positioning +org.bluetooth.service.internet_protocol_support +org.bluetooth.service.link_loss +org.bluetooth.service.location_and_navigation +org.bluetooth.service.next_dst_change +org.bluetooth.service.object_transfer +org.bluetooth.service.phone_alert_status +org.bluetooth.service.pulse_oximeter +org.bluetooth.service.reference_time_update +org.bluetooth.service.running_speed_and_cadence +org.bluetooth.service.scan_parameters +org.bluetooth.service.transport_discovery +org.bluetooth.service.tx_power +org.bluetooth.service.user_data +org.bluetooth.service.weight_scale \ No newline at end of file diff --git a/cpp_utils/BLEAddress.cpp b/cpp_utils/BLEAddress.cpp index d733a6ab..21713bba 100644 --- a/cpp_utils/BLEAddress.cpp +++ b/cpp_utils/BLEAddress.cpp @@ -52,6 +52,24 @@ BLEAddress::BLEAddress(std::string stringAddress) { BLEAddress::~BLEAddress() { } // ~BLEAddress +/** + * @brief Determine if this address equals another. + * @param [in] otherAddress The other address to compare against. + * @return True if the addresses are equal. + */ +bool BLEAddress::equals(BLEAddress otherAddress) { + return memcmp(otherAddress.getNative(), m_address, 6) == 0; +} // equals + + +/** + * @brief Return the native representation of the address. + * @return The native representation of the address. + */ +esp_bd_addr_t *BLEAddress::getNative() { + return &m_address; +} // getNative + /** * @brief Convert a BLE address to a string. @@ -68,13 +86,4 @@ std::string BLEAddress::toString() { stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[5]; return stream.str(); } // toString - - -/** - * @brief Return the native representation of the address. - * @return The native representation of the address. - */ -esp_bd_addr_t *BLEAddress::getNative() { - return &m_address; -} // getNative #endif diff --git a/cpp_utils/BLEAddress.h b/cpp_utils/BLEAddress.h index a2935494..3f853e89 100644 --- a/cpp_utils/BLEAddress.h +++ b/cpp_utils/BLEAddress.h @@ -17,8 +17,9 @@ class BLEAddress { BLEAddress(esp_bd_addr_t address); BLEAddress(std::string stringAddress); virtual ~BLEAddress(); - esp_bd_addr_t *getNative(); - std::string toString(); + bool equals(BLEAddress otherAddress); + esp_bd_addr_t* getNative(); + std::string toString(); private: esp_bd_addr_t m_address; diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index b9ed7e0d..40916e44 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -157,7 +157,7 @@ bool BLEAdvertisedDevice::haveTXPower() { * * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile */ -void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload) { +void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { uint8_t length; uint8_t ad_type; uint8_t sizeConsumed = 0; @@ -173,7 +173,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload) { payload++; length--; - char *pHex = BLEUtils::buildHexData(nullptr, payload, length); + char* pHex = BLEUtils::buildHexData(nullptr, payload, length); ESP_LOGD(LOG_TAG, "Type: 0x%.2x (%s), length: %d, data: %s", ad_type, BLEUtils::advTypeToString(ad_type), length, pHex); free(pHex); @@ -182,7 +182,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload) { switch(ad_type) { case ESP_BLE_AD_TYPE_NAME_CMPL: { // Adv Data Type: 0x09 - setName(std::string((char *)payload, length)); + setName(std::string(reinterpret_cast(payload), length)); break; } // ESP_BLE_AD_TYPE_NAME_CMPL @@ -192,32 +192,32 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload) { } // ESP_BLE_AD_TYPE_TX_PWR case ESP_BLE_AD_TYPE_APPEARANCE: { // Adv Data Type: 0x19 - setAppearance(*(uint16_t *)payload); + setAppearance(*reinterpret_cast(payload)); break; } // ESP_BLE_AD_TYPE_APPEARANCE case ESP_BLE_AD_TYPE_FLAG: { // Adv Data Type: 0x01 - setAdFlag((uint8_t)*payload); + setAdFlag(*payload); break; } // ESP_BLE_AD_TYPE_FLAG case ESP_BLE_AD_TYPE_16SRV_CMPL: { // Adv Data Type: 0x03 - setServiceUUID(BLEUUID(*(uint16_t *)payload)); + setServiceUUID(BLEUUID(*reinterpret_cast(payload))); break; } // ESP_BLE_AD_TYPE_16SRV_CMPL case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02 - setServiceUUID(BLEUUID(*(uint16_t *)payload)); + setServiceUUID(BLEUUID(*reinterpret_cast(payload))); break; } // ESP_BLE_AD_TYPE_16SRV_PART case ESP_BLE_AD_TYPE_32SRV_CMPL: { // Adv Data Type: 0x05 - setServiceUUID(BLEUUID(*(uint32_t *)payload)); + setServiceUUID(BLEUUID(*reinterpret_cast(payload))); break; } // ESP_BLE_AD_TYPE_32SRV_CMPL case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04 - setServiceUUID(BLEUUID(*(uint32_t *)payload)); + setServiceUUID(BLEUUID(*reinterpret_cast(payload))); break; } // ESP_BLE_AD_TYPE_32SRV_PART @@ -232,9 +232,10 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload) { } // ESP_BLE_AD_TYPE_128SRV_PART // See CSS Part A 1.4 Manufacturer Specific Data - case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: - setManufacturerData(std::string((char *)payload, length)); + case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: { + setManufacturerData(std::string(reinterpret_cast(payload), length)); break; + } // ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE default: { ESP_LOGD(LOG_TAG, "Unhandled type"); @@ -288,7 +289,7 @@ void BLEAdvertisedDevice::setAppearance(uint16_t appearance) { void BLEAdvertisedDevice::setManufacturerData(std::string manufacturerData) { m_manufacturerData = manufacturerData; m_haveManufacturerData = true; - char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t *)m_manufacturerData.data(), (uint8_t)m_manufacturerData.length()); + char* pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)m_manufacturerData.data(), (uint8_t)m_manufacturerData.length()); ESP_LOGD(LOG_TAG, "- manufacturer data: %s", pHex); free(pHex); } // setManufacturerData @@ -357,7 +358,7 @@ std::string BLEAdvertisedDevice::toString() { ss << ", appearance: " << getApperance(); } if (haveManufacturerData()) { - char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t *)getManufacturerData().data(), getManufacturerData().length()); + char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)getManufacturerData().data(), getManufacturerData().length()); ss << ", manufacturer data: " << pHex; free(pHex); } diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index 5c1c6d3c..c408cfd5 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -9,11 +9,14 @@ #define COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) +#include + +#include + #include "BLEAddress.h" #include "BLEScan.h" #include "BLEUUID.h" -#include -#include + class BLEScan; class BLEAdvertisedDevice { @@ -26,10 +29,10 @@ class BLEAdvertisedDevice { std::string getManufacturerData(); std::string getName(); int getRSSI(); + BLEScan* getScan(); BLEUUID getServiceUUID(); - BLEScan *getScan(); int8_t getTXPower(); - std::string toString(); + bool haveAppearance(); bool haveManufacturerData(); bool haveName(); @@ -37,18 +40,21 @@ class BLEAdvertisedDevice { bool haveServiceUUID(); bool haveTXPower(); + std::string toString(); + private: friend class BLEScan; - void parseAdvertisement(uint8_t *payload); + + void parseAdvertisement(uint8_t* payload); void setAddress(BLEAddress address); void setAdFlag(uint8_t adFlag); - void setAdvertizementResult(uint8_t *payload); + void setAdvertizementResult(uint8_t* payload); void setAppearance(uint16_t appearance); void setManufacturerData(std::string manufacturerData); void setName(std::string name); void setRSSI(int rssi); + void setScan(BLEScan* pScan); void setServiceUUID(BLEUUID serviceUUID); - void setScan(BLEScan *pScan); void setTXPower(int8_t txPower); bool m_haveAppearance; @@ -59,16 +65,16 @@ class BLEAdvertisedDevice { bool m_haveTXPower; - BLEAddress m_address = BLEAddress((uint8_t *)"\0\0\0\0\0\0"); + BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); uint8_t m_adFlag; uint16_t m_appearance; int m_deviceType; std::string m_manufacturerData; std::string m_name; + BLEScan* m_pScan; int m_rssi; BLEUUID m_serviceUUID; int8_t m_txPower; - BLEScan *m_pScan; }; #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 8f94da2a..cfd7a213 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -73,17 +73,17 @@ void BLEAdvertising::setServiceUUID(BLEUUID uuid) { switch(espUUID.len) { case ESP_UUID_LEN_16: { m_advData.service_uuid_len = 2; - m_advData.p_service_uuid = (uint8_t *)&espUUID.uuid.uuid16; + m_advData.p_service_uuid = reinterpret_cast(&espUUID.uuid.uuid16); break; } case ESP_UUID_LEN_32: { m_advData.service_uuid_len = 4; - m_advData.p_service_uuid = (uint8_t *)&espUUID.uuid.uuid32; + m_advData.p_service_uuid = reinterpret_cast(&espUUID.uuid.uuid32); break; } case ESP_UUID_LEN_128: { m_advData.service_uuid_len = 16; - m_advData.p_service_uuid = (uint8_t *)&espUUID.uuid.uuid128; + m_advData.p_service_uuid = reinterpret_cast(&espUUID.uuid.uuid128); break; } } // switch diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 2801930a..0c523c57 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -29,7 +29,7 @@ static char LOG_TAG[] = "BLECharacteristic"; */ BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { m_bleUUID = uuid; - m_value.attr_value = (uint8_t *)malloc(ESP_GATT_MAX_ATTR_LEN); // Allocate storage for the value + m_value.attr_value = static_cast(malloc(ESP_GATT_MAX_ATTR_LEN)); // Allocate storage for the value m_value.attr_len = 0; // Initial length of actual data is none. m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; // Maximum length of data. m_handle = NULL_HANDLE; @@ -90,7 +90,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { esp_err_t errRc = ::esp_ble_gatts_add_char( m_pService->getHandle(), getUUID().getNative(), - (esp_gatt_perm_t)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), + static_cast(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), getProperties(), &m_value, &control); // Whether to autorespond or not. @@ -166,7 +166,7 @@ uint8_t* BLECharacteristic::getValue() { void BLECharacteristic::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { + esp_ble_gatts_cb_param_t* param) { switch(event) { // ESP_GATTS_WRITE_EVT - A request to write the value of a characteristic has arrived. @@ -419,7 +419,7 @@ void BLECharacteristic::setValue(uint8_t* data, size_t length) { * @return N/A. */ void BLECharacteristic::setValue(std::string value) { - setValue((uint8_t *)value.data(), value.length()); + setValue((uint8_t*)(value.data()), value.length()); } // setValue diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 92861058..8f256c4c 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -107,7 +107,7 @@ void BLEClient::disconnect() { void BLEClient::gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *evtParam) { + esp_ble_gattc_cb_param_t* evtParam) { // Execute handler code based on the type of event received. switch(event) { @@ -165,7 +165,7 @@ void BLEClient::gattClientEventHandler( // case ESP_GATTC_SEARCH_RES_EVT: { BLEUUID uuid = BLEUUID(evtParam->search_res.srvc_id); - BLERemoteService *pRemoteService = new BLERemoteService(evtParam->search_res.srvc_id, this); + BLERemoteService* pRemoteService = new BLERemoteService(evtParam->search_res.srvc_id, this); m_servicesMap.insert(std::pair(uuid.toString(), pRemoteService)); break; } // ESP_GATTC_SEARCH_RES_EVT @@ -227,7 +227,7 @@ BLERemoteService* BLEClient::getService(BLEUUID uuid) { * services and wait until we have received them all. * @return N/A */ -std::map * BLEClient::getServices() { +std::map* BLEClient::getServices() { /* * Design * ------ @@ -279,8 +279,4 @@ std::string BLEClient::toString() { return ss.str(); } // toString - - - - #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index eae2c11a..649daf65 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -8,21 +8,28 @@ #include "BLERemoteCharacteristic.h" #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include "BLEUtils.h" -#include "GeneralUtils.h" -#include + #include #include #include +#include + +#include "BLEUtils.h" +#include "GeneralUtils.h" + + static const char LOG_TAG[] = "BLERemoteCharacteristic"; -BLERemoteCharacteristic::BLERemoteCharacteristic(esp_gatt_id_t charId, - esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService) { +BLERemoteCharacteristic::BLERemoteCharacteristic( + esp_gatt_id_t charId, + esp_gatt_char_prop_t charProp, + BLERemoteService* pRemoteService) { m_charId = charId; m_charProp = charProp; m_pRemoteService = pRemoteService; -} +} // BLERemoteCharacteristic + static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { if (id1.id.inst_id != id2.id.inst_id) { @@ -49,6 +56,7 @@ static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { BLERemoteCharacteristic::~BLERemoteCharacteristic() { } + /** * @brief Handle GATT Client events */ @@ -79,7 +87,7 @@ void BLERemoteCharacteristic::gattClientEventHandler( if (compareGattId(evtParam->read.char_id, m_charId) == false) { break; } - m_value = std::string((char *)evtParam->read.value, evtParam->read.value_len); + m_value = std::string((char*)evtParam->read.value, evtParam->read.value_len); m_semaphoreReadCharEvt.give(); break; } // ESP_GATTC_READ_CHAR_EVT @@ -133,6 +141,49 @@ void BLERemoteCharacteristic::gattClientEventHandler( } }; // gattClientEventHandler + +/** + * @brief Read an unsigned 16 bit value + * @return The unsigned 16 bit value. + */ +uint16_t BLERemoteCharacteristic::readUInt16(void) { + std::string value = readValue(); + if (value.length() >= 2) { + return *(uint16_t*)(value.data()); + } + return 0; +} // readUInt16 + + +/** + * @brief Read an unsigned 32 bit value. + * @return the unsigned 32 bit value. + */ +uint32_t BLERemoteCharacteristic::readUInt32(void) { + std::string value = readValue(); + if (value.length() >= 4) { + return *(uint32_t*)(value.data()); + } + return 0; +} // readUInt32 + + +/** + * @brief Read a byte value + * @return The value as a byte + */ +uint8_t BLERemoteCharacteristic::readUInt8(void) { + std::string value = readValue(); + if (value.length() >= 1) { + return (uint8_t)value[0]; + } + return 0; +} // readUInt8 + +/** + * @brief Read the value of the remote characteristic. + * @return The value of the remote characteristic. + */ std::string BLERemoteCharacteristic::readValue() { ESP_LOGD(LOG_TAG, ">> readValue()"); m_semaphoreReadCharEvt.take("readValue"); @@ -150,7 +201,7 @@ std::string BLERemoteCharacteristic::readValue() { m_semaphoreReadCharEvt.give(); ESP_LOGD(LOG_TAG, "<< readValue()"); return m_value; -} +} // readValue /** @@ -202,9 +253,10 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { m_pRemoteService->getSrvcId(), &m_charId, newValue.length(), - (uint8_t *)newValue.data(), + (uint8_t*)newValue.data(), response?ESP_GATT_WRITE_TYPE_RSP:ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE); + ESP_GATT_AUTH_REQ_NONE + ); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; @@ -221,7 +273,7 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { * @return N/A. */ void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { - writeValue(std::string((char *)&newValue, 1), response); + writeValue(std::string(reinterpret_cast(&newValue), 1), response); } // writeValue diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 85ada52c..531ef4aa 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -26,6 +26,9 @@ class BLERemoteCharacteristic { // Public member functions std::string readValue(void); + uint8_t readUInt8(void); + uint16_t readUInt16(void); + uint32_t readUInt32(void); void registerForNotify(void); void writeValue(std::string newValue, bool response = false); void writeValue(uint8_t newValue, bool response = false); diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index b1a6b761..31a25d20 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -6,14 +6,18 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) + + +#include +#include + +#include + #include "BLEAdvertisedDevice.h" #include "BLEScan.h" #include "BLEUtils.h" #include "GeneralUtils.h" -#include -#include - static char LOG_TAG[] = "BLEScan"; @@ -30,6 +34,14 @@ BLEScan::BLEScan() { BLEScan::~BLEScan() { + clearAdvertisedDevices(); +} + +void BLEScan::clearAdvertisedDevices() { + for (int i=0; iscan_rst.search_evt) { case ESP_GAP_SEARCH_INQ_CMPL_EVT: { + m_stopped = true; + m_semaphoreScanEnd.give(); break; } case ESP_GAP_SEARCH_INQ_RES_EVT: { - if (m_stopped) { + if (m_stopped) { // If we are not scanning, nothing to do with the extra results. break; } - BLEAdvertisedDevice *pAdvertisedDevice = new BLEAdvertisedDevice(); - pAdvertisedDevice->setAddress(BLEAddress(param->scan_rst.bda)); + +// Examine our list of previously scanned addresses and, if we found this one already, +// ignore it. + BLEAddress advertisedAddress(param->scan_rst.bda); + bool found = false; + for (int i=0; igetAddress().equals(advertisedAddress)) { + found = true; + break; + } + } + if (found) { + ESP_LOGD(LOG_TAG, "Ignoring %s, already seen it.", advertisedAddress.toString().c_str()); + break; + } + + BLEAdvertisedDevice* pAdvertisedDevice = new BLEAdvertisedDevice(); + pAdvertisedDevice->setAddress(advertisedAddress); pAdvertisedDevice->setRSSI(param->scan_rst.rssi); pAdvertisedDevice->setAdFlag(param->scan_rst.flag); pAdvertisedDevice->parseAdvertisement((uint8_t *)param->scan_rst.ble_adv); pAdvertisedDevice->setScan(this); + m_vectorAvdertisedDevices.push_back(pAdvertisedDevice); if (m_pAdvertisedDeviceCallbacks) { m_pAdvertisedDeviceCallbacks->onResult(pAdvertisedDevice); } - delete pAdvertisedDevice; + break; } @@ -149,20 +180,27 @@ void BLEScan::setWindow(uint16_t windowMSecs) { * @param [in] duration The duration in seconds for which to scan. * @return N/A. */ -void BLEScan::start(uint32_t duration) { +std::vector BLEScan::start(uint32_t duration) { ESP_LOGD(LOG_TAG, ">> start(%d)", duration); + m_semaphoreScanEnd.take("start"); + clearAdvertisedDevices(); esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); - return; + m_semaphoreScanEnd.give(); + return m_vectorAvdertisedDevices; } errRc = ::esp_ble_gap_start_scanning(duration); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); - return; + m_semaphoreScanEnd.give(); + return m_vectorAvdertisedDevices; } m_stopped = false; + m_semaphoreScanEnd.take("start"); + m_semaphoreScanEnd.give(); ESP_LOGD(LOG_TAG, "<< start()"); + return m_vectorAvdertisedDevices; } // start @@ -178,6 +216,10 @@ void BLEScan::stop() { ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); return; } + m_semaphoreScanEnd.give(); ESP_LOGD(LOG_TAG, "<< stop()"); } // stop + + + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index 65ae3618..a424ea5e 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -11,34 +11,43 @@ #if defined(CONFIG_BT_ENABLED) #include +#include +#include "BLEAdvertisedDevice.h" #include "BLEAdvertisedDeviceCallbacks.h" #include "BLEClient.h" +#include "FreeRTOS.h" -class BLEClient; +class BLEAdvertisedDevice; class BLEAdvertisedDeviceCallbacks; +class BLEClient; class BLEScan { public: BLEScan(); virtual ~BLEScan(); - void gapEventHandler( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t *param); + void gapEventHandler( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); virtual void onResults(); void setActiveScan(bool active); - void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks *pAdvertisedDeviceCallbacks); + void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks); void setInterval(uint16_t intervalMSecs); void setWindow(uint16_t windowMSecs); - void start(uint32_t duration); + std::vector start(uint32_t duration); void stop(); private: friend class BLE; - esp_ble_scan_params_t m_scan_params; - void parseAdvertisement(BLEClient *pRemoteDevice, uint8_t *payload); - BLEAdvertisedDeviceCallbacks *m_pAdvertisedDeviceCallbacks; - bool m_stopped; + void clearAdvertisedDevices(); + void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); + + + esp_ble_scan_params_t m_scan_params; + BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks; + bool m_stopped; + FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); + std::vector m_vectorAvdertisedDevices; }; // BLEScan #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index feaff53d..c5a7a4a1 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -9,12 +9,14 @@ #define COMPONENTS_CPP_UTILS_BLESERVICE_H_ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) + #include -#include "FreeRTOS.h" -#include "BLEUUID.h" -#include "BLEServer.h" + #include "BLECharacteristic.h" #include "BLECharacteristicMap.h" +#include "BLEServer.h" +#include "BLEUUID.h" +#include "FreeRTOS.h" class BLEServer; @@ -24,12 +26,12 @@ class BLEService { virtual ~BLEService(); void addCharacteristic(BLECharacteristic *pCharacteristic); - BLECharacteristic *createCharacteristic(BLEUUID uuid, uint32_t properties); + BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); void dump(); void executeCreate(BLEServer *pServer); - BLECharacteristic *getCharacteristic(BLEUUID uuid); + BLECharacteristic* getCharacteristic(BLEUUID uuid); BLEUUID getUUID(); - BLEServer *getServer(); + BLEServer* getServer(); void start(); std::string toString(); @@ -40,17 +42,15 @@ class BLEService { friend class BLECharacteristic; friend class BLEDevice; - BLECharacteristicMap m_characteristicMap; - uint16_t m_handle; - BLECharacteristic *m_lastCreatedCharacteristic; - BLEServer *m_pServer; - FreeRTOS::Semaphore m_serializeMutex; - //esp_gatt_srvc_id_t m_srvc_id; - BLEUUID m_uuid; + BLECharacteristicMap m_characteristicMap; + uint16_t m_handle; + BLECharacteristic* m_lastCreatedCharacteristic; + BLEServer* m_pServer; + FreeRTOS::Semaphore m_serializeMutex; + BLEUUID m_uuid; uint16_t getHandle(); BLECharacteristic *getLastCreatedCharacteristic(); - //esp_gatt_srvc_id_t getService(); void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, diff --git a/cpp_utils/README.md b/cpp_utils/README.md index aeb059a8..7196997c 100644 --- a/cpp_utils/README.md +++ b/cpp_utils/README.md @@ -3,6 +3,18 @@ This directory contains a wealth of C++ classes that have been found useful when with the ESP-IDF. The classes have been documented using `doxygen` so one can run a doxygen processor over them to create the user guides and programming references. +# Compiling the C++ classes +The C++ classes found here exist as an ESP-IDF component. To build the classes and then use them in your project perform the following +steps: + +1. Create an ESP-IDF project. +2. Create a directory called `components` in the root of your ESP-IDF project. +3. Copy this directory (`cpp_utils`) into your new `components` directory. The result will be `/components/cpp_utils/`. +4. In your ESP-IDF project build as normal. + +The C++ classes will be compiled and available to be used in your own code. + + ## BLE Functions The Bluetooth BLE functions are only compiled if Bluetooth is enabled in `make menuconfig`. This is primarily because the ESP-IDF build system has chosen to only compile the underlying BLE functions if Bluetooth is enabled. From 4f7dc9979daf610e236f872fa1d2c406513abb4a Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 12 Jul 2017 10:08:20 -0500 Subject: [PATCH 003/381] sync 2017-07-12 10-08 --- cpp_utils/tests/BLE Tests/README.md | 2 + cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp | 86 +++++++++++++++++++ cpp_utils/tests/BLE Tests/Sample1.cpp | 38 +++++++++ cpp_utils/tests/BLE Tests/SampleClient.cpp | 95 +++++++++++++++++++++ cpp_utils/tests/BLE Tests/SampleNotify.cpp | 80 +++++++++++++++++ cpp_utils/tests/BLE Tests/SampleRead.cpp | 53 ++++++++++++ cpp_utils/tests/BLE Tests/SampleScan.cpp | 32 +++++++ cpp_utils/tests/BLE Tests/SampleServer.cpp | 85 ++++++++++++++++++ cpp_utils/tests/BLE Tests/SampleWrite.cpp | 53 ++++++++++++ cpp_utils/tests/BLE Tests/main.cpp | 23 +++++ 10 files changed, 547 insertions(+) create mode 100644 cpp_utils/tests/BLE Tests/README.md create mode 100644 cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp create mode 100644 cpp_utils/tests/BLE Tests/Sample1.cpp create mode 100644 cpp_utils/tests/BLE Tests/SampleClient.cpp create mode 100644 cpp_utils/tests/BLE Tests/SampleNotify.cpp create mode 100644 cpp_utils/tests/BLE Tests/SampleRead.cpp create mode 100644 cpp_utils/tests/BLE Tests/SampleScan.cpp create mode 100644 cpp_utils/tests/BLE Tests/SampleServer.cpp create mode 100644 cpp_utils/tests/BLE Tests/SampleWrite.cpp create mode 100644 cpp_utils/tests/BLE Tests/main.cpp diff --git a/cpp_utils/tests/BLE Tests/README.md b/cpp_utils/tests/BLE Tests/README.md new file mode 100644 index 00000000..dd2fc4b3 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/README.md @@ -0,0 +1,2 @@ +# Sample C++ BLE fragments +Here we find a set of C++ BLE samples illustrating different aspects of the C++ BLE library. \ No newline at end of file diff --git a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp new file mode 100644 index 00000000..d7bc9388 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp @@ -0,0 +1,86 @@ +#include "BLE.h" +#include "BLEUtils.h" +#include "BLEScan.h" +#include +#include + +#include "BLEAdvertisedDeviceCallbacks.h" +#include "BLEClient.h" +#include "sdkconfig.h" +#include "Task.h" + +static const char LOG_TAG[] = "Sample-MLE-15"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + + + +static BLEUUID serviceUUID((uint16_t)0x1802); +static BLEUUID charUUID((uint16_t)0x2a06); + +class MyClient: public Task { + void run(void *data) { + BLEAddress* pAddress = (BLEAddress *)data; + BLEClient* pClient = BLE::createClient(); + + + pClient->connect(*pAddress); + pClient->getServices(); + + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + pRemoteService->getCharacteristics(); + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + pRemoteCharacteristic->readValue(); + + pRemoteCharacteristic->writeValue("123"); + pRemoteCharacteristic->registerForNotify(); + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } +}; + + + +static void run() { + ESP_LOGD(LOG_TAG, "MLE-15 sample starting"); + BLE::initClient(); + BLEClient* pClient = BLE::createClient(); + + + pClient->connect(BLEAddress("ff:ff:45:19:14:80")); + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + pRemoteCharacteristic->writeValue((uint8_t)1); + + //BLEClient *pClient = BLE::createClient(); + //pClient->setClientCallbacks(new MyClientCallbacks()); + //pClient->connect(BLEAddress("00:00:00:00:00:00")); + +} + +void Sample_MLE_15(void) +{ + run(); +} // app_main diff --git a/cpp_utils/tests/BLE Tests/Sample1.cpp b/cpp_utils/tests/BLE Tests/Sample1.cpp new file mode 100644 index 00000000..a60b798c --- /dev/null +++ b/cpp_utils/tests/BLE Tests/Sample1.cpp @@ -0,0 +1,38 @@ +#include "BLE.h" +#include "BLEUtils.h" +#include "BLEServer.h" +#include +#include + +#include "sdkconfig.h" + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +static void run() { + BLE::initServer("MYDEVICE"); + + BLEServer *pServer = new BLEServer(); + + BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); + + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + + pService->start(); + + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); +} + +void Sample1(void) +{ + run(); +} // app_main diff --git a/cpp_utils/tests/BLE Tests/SampleClient.cpp b/cpp_utils/tests/BLE Tests/SampleClient.cpp new file mode 100644 index 00000000..c847b261 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/SampleClient.cpp @@ -0,0 +1,95 @@ +#include "BLE.h" +#include "BLEUtils.h" +#include "BLEScan.h" +#include +#include + +#include "BLEAdvertisedDeviceCallbacks.h" +#include "BLEClient.h" +#include "sdkconfig.h" +#include "Task.h" + +static const char LOG_TAG[] = "SampleClient"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + + + +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +class MyClient: public Task { + void run(void *data) { + BLEAddress* pAddress = (BLEAddress *)data; + BLEClient* pClient = BLE::createClient(); + + + pClient->connect(*pAddress); + pClient->getServices(); + + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + pRemoteService->getCharacteristics(); + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + pRemoteCharacteristic->readValue(); + + pRemoteCharacteristic->writeValue("123"); + pRemoteCharacteristic->registerForNotify(); + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } +}; + + + +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice *pAdvertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", pAdvertisedDevice->toString().c_str()); + if (pAdvertisedDevice->haveServiceUUID()) { + ESP_LOGD(LOG_TAG, "Comparing %s to %s", + pAdvertisedDevice->getServiceUUID().toString().c_str(), serviceUUID.toString().c_str()); + } + if (pAdvertisedDevice->haveServiceUUID() && pAdvertisedDevice->getServiceUUID().equals(serviceUUID)) { + pAdvertisedDevice->getScan()->stop(); + ESP_LOGD(LOG_TAG, "Found our device! address: %s", pAdvertisedDevice->getAddress().toString().c_str()); + MyClient *pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*pAdvertisedDevice->getAddress().getNative())); + + } + } +}; + + + +static void run() { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLEUUID x = BLEUUID("12345678-90ab-cdef-1234-567890abcdef"); + ESP_LOGD(LOG_TAG, "%s", x.toString().c_str()); + BLE::initClient(); + BLEScan *pBLEScan = BLE::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); + + //BLEClient *pClient = BLE::createClient(); + //pClient->setClientCallbacks(new MyClientCallbacks()); + //pClient->connect(BLEAddress("00:00:00:00:00:00")); + +} + +void SampleClient(void) +{ + run(); +} // app_main diff --git a/cpp_utils/tests/BLE Tests/SampleNotify.cpp b/cpp_utils/tests/BLE Tests/SampleNotify.cpp new file mode 100644 index 00000000..d0597da2 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/SampleNotify.cpp @@ -0,0 +1,80 @@ +#include "BLE.h" +#include "BLEUtils.h" +#include "BLEServer.h" +#include +#include +#include +#include +#include "Task.h" +#include "BLE2902.h" + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleNotify"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +BLECharacteristic *pCharacteristic; + +class MyNotifyTask: public Task { + void run(void *data) { + uint8_t value = 0; + while(1) { + delay(2000); + ESP_LOGD(LOG_TAG, "*** NOTIFY: %d ***", value); + pCharacteristic->setValue(&value, 1); + pCharacteristic->notify(); + value++; + } + } +}; + +MyNotifyTask *pMyNotifyTask; + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer *pServer) { + pMyNotifyTask->start(); + }; + + void onDisconnect(BLEServer *pServer) { + pMyNotifyTask->stop(); + } +}; + +static void run() { + pMyNotifyTask = new MyNotifyTask(); + pMyNotifyTask->setStackSize(8000); + + BLE::initServer("MYDEVICE"); + BLEServer *pServer = new BLEServer(); + + BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); + + pServer->setCallbacks(new MyServerCallbacks()); + + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY + ); + + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + BLE2902 *pBLE2902 = new BLE2902(); + pBLE2902->setNotifications(true); + pCharacteristic->addDescriptor(pBLE2902); + + pService->start(); + + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); +} + +void SampleNotify(void) +{ + run(); +} // app_main diff --git a/cpp_utils/tests/BLE Tests/SampleRead.cpp b/cpp_utils/tests/BLE Tests/SampleRead.cpp new file mode 100644 index 00000000..2704707e --- /dev/null +++ b/cpp_utils/tests/BLE Tests/SampleRead.cpp @@ -0,0 +1,53 @@ +#include "BLE.h" +#include "BLEUtils.h" +#include "BLEServer.h" +#include +#include +#include +#include + +#include "sdkconfig.h" + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +static uint8_t SERVICE_UUID_BIN[] = {0x4f, 0xaf, 0xc2, 0x01, 0x1f, 0xb5, 0x45, 0x9e, 0x8f, 0xcc, 0xc5, 0xc9, 0xc3, 0x31, 0x91, 0x4b}; + +class MyCallbackHandler: public BLECharacteristicCallbacks { + void onRead(BLECharacteristic *pCharacteristic) { + struct timeval tv; + gettimeofday(&tv, nullptr); + std::ostringstream os; + os << "Time: " << tv.tv_sec; + pCharacteristic->setValue(os.str()); + } +}; + +static void run() { + BLE::initServer("MYDEVICE"); + BLEServer *pServer = new BLEServer(); + + BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID_BIN, 16, true)); + + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setCallbacks(new MyCallbackHandler()); + + pCharacteristic->setValue("Hello World"); + + pService->start(); + + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); +} + +void SampleRead(void) +{ + run(); +} // app_main diff --git a/cpp_utils/tests/BLE Tests/SampleScan.cpp b/cpp_utils/tests/BLE Tests/SampleScan.cpp new file mode 100644 index 00000000..bf537e13 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/SampleScan.cpp @@ -0,0 +1,32 @@ +#include "BLE.h" +#include "BLEUtils.h" +#include "BLEScan.h" +#include +#include + +#include "BLEAdvertisedDeviceCallbacks.h" +#include "sdkconfig.h" + +static const char LOG_TAG[] = "SampleScan"; + +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice *pAdvertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", pAdvertisedDevice->toString().c_str()); + } +}; + +static void run() { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLE::initClient(); + BLEScan* pBLEScan = BLE::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + std::vector foundDevices = pBLEScan->start(30); + ESP_LOGD(LOG_TAG, "We found %d devices", foundDevices.size()); + ESP_LOGD(LOG_TAG, "Scanning sample ended"); +} + +void SampleScan(void) +{ + run(); +} // app_main diff --git a/cpp_utils/tests/BLE Tests/SampleServer.cpp b/cpp_utils/tests/BLE Tests/SampleServer.cpp new file mode 100644 index 00000000..cd747608 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/SampleServer.cpp @@ -0,0 +1,85 @@ +#include "BLE.h" +#include "BLEUtils.h" +#include "BLEServer.h" +#include "BLE2902.h" +#include +#include +#include + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +BLECharacteristic* pCharacteristic; + +class MyNotifyTask: public Task { + void run(void *data) { + while(1) { + ESP_LOGD(LOG_TAG, "Notify!"); + delay(2000); + pCharacteristic->indicate(); // Perform the actual indication of a notification to the peer. + } + } +}; + +class MyServer: public BLEServer { + MyNotifyTask *pMyNotifyTask; + void onConnect() { + ESP_LOGD(LOG_TAG, "My onConnect"); + /* + pMyNotifyTask = new MyNotifyTask(); + pMyNotifyTask->setStackSize(18000); + pMyNotifyTask->start(); + */ + } + + void onDisconnect() { + ESP_LOGD(LOG_TAG, "My onDisconnect"); + pMyNotifyTask->stop(); + } +}; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + BLE::initServer("MYDEVICE"); + BLEServer* pServer = new MyServer(); + pServer->createApp(0); + + BLEUUID serviceUUID((uint16_t)0x1234); + BLEService *pService = pServer->createService(serviceUUID); + + pCharacteristic = pService->createCharacteristic( + BLEUUID((uint16_t)0x99AA), + BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + + + pCharacteristic->setValue("hello steph"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + + pService->start(); + + //pServer->startAdvertising(); + BLEUUID serviceUUIDFull((uint16_t)0x1234); + serviceUUIDFull.to128(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->setServiceUUID(serviceUUIDFull); + pAdvertising->start(); + delay(1000000); + } +}; + +void SampleServer(void) +{ + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main diff --git a/cpp_utils/tests/BLE Tests/SampleWrite.cpp b/cpp_utils/tests/BLE Tests/SampleWrite.cpp new file mode 100644 index 00000000..ea836fa3 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/SampleWrite.cpp @@ -0,0 +1,53 @@ +#include "BLE.h" +#include "BLEUtils.h" +#include "BLEServer.h" +#include +#include +#include +#include + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleWrite"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + if (pCharacteristic->getLength() > 0) { + ESP_LOGD(LOG_TAG, "*********"); + ESP_LOGD(LOG_TAG, "New value: %.2x", pCharacteristic->getValue()[0]); + ESP_LOGD(LOG_TAG, "*********"); + } + } +}; + +static void run() { + BLE::initServer("MYDEVICE"); + BLEServer *pServer = new BLEServer(); + + BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); + + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setCallbacks(new MyCallbacks()); + + pCharacteristic->setValue("Hello World"); + + pService->start(); + + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); +} + +void SampleWrite(void) +{ + run(); +} // app_main diff --git a/cpp_utils/tests/BLE Tests/main.cpp b/cpp_utils/tests/BLE Tests/main.cpp new file mode 100644 index 00000000..bd7060e6 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/main.cpp @@ -0,0 +1,23 @@ +extern "C" { + void app_main(void); +} + +void SampleServer(void); +void Sample1(void); +void SampleRead(void); +void SampleWrite(void); +void SampleScan(void); +void SampleNotify(void); +void SampleClient(void); +void Sample_MLE_15(void); + +void app_main(void) { + //SampleServer(); + //Sample1(); + //SampleRead(); + //SampleWrite(); + SampleScan(); + //SampleNotify(); + //SampleClient(); + //Sample_MLE_15(); +} From f10d048525f75d419e7177c6d503410285cbfc63 Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 12 Jul 2017 10:45:49 -0500 Subject: [PATCH 004/381] Rename of NVS source files. --- cpp_utils/{NVS.cpp => CPPNVS.cpp} | 3 ++- cpp_utils/{NVS.h => CPPNVS.h} | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) rename cpp_utils/{NVS.cpp => CPPNVS.cpp} (98%) rename cpp_utils/{NVS.h => CPPNVS.h} (79%) diff --git a/cpp_utils/NVS.cpp b/cpp_utils/CPPNVS.cpp similarity index 98% rename from cpp_utils/NVS.cpp rename to cpp_utils/CPPNVS.cpp index 6c0c0bbc..734cc170 100644 --- a/cpp_utils/NVS.cpp +++ b/cpp_utils/CPPNVS.cpp @@ -5,8 +5,9 @@ * Author: kolban */ +#include "CPPNVS.h" + #include -#include "NVS.h" /** * @brief Constructor. diff --git a/cpp_utils/NVS.h b/cpp_utils/CPPNVS.h similarity index 79% rename from cpp_utils/NVS.h rename to cpp_utils/CPPNVS.h index ad0c84c5..6802ff99 100644 --- a/cpp_utils/NVS.h +++ b/cpp_utils/CPPNVS.h @@ -5,8 +5,8 @@ * Author: kolban */ -#ifndef COMPONENTS_CPP_UTILS_NVS_H_ -#define COMPONENTS_CPP_UTILS_NVS_H_ +#ifndef COMPONENTS_CPP_UTILS_CPPNVS_H_ +#define COMPONENTS_CPP_UTILS_CPPNVS_H_ #include #include @@ -28,4 +28,4 @@ class NVS { nvs_handle m_handle; }; -#endif /* COMPONENTS_CPP_UTILS_NVS_H_ */ +#endif /* COMPONENTS_CPP_UTILS_CPPNVS_H_ */ From 882f152be15fc90ace7b39cb7f169ab899da39cb Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 15 Jul 2017 18:17:20 -0500 Subject: [PATCH 005/381] Addition of MFRC522 support --- cpp_utils/BLE.cpp | 7 - cpp_utils/BLE.h | 2 - cpp_utils/BLE2902.cpp | 3 - cpp_utils/BLE2902.h | 1 - cpp_utils/BLEAddress.cpp | 3 - cpp_utils/BLEAddress.h | 1 - cpp_utils/BLEAdvertisedDevice.cpp | 4 - cpp_utils/BLEAdvertisedDevice.h | 1 - cpp_utils/BLEAdvertisedDeviceCallbacks.cpp | 6 - cpp_utils/BLEAdvertisedDeviceCallbacks.h | 3 +- cpp_utils/BLEAdvertising.cpp | 5 - cpp_utils/BLEAdvertising.h | 1 - cpp_utils/BLECharacteristicCallbacks.cpp | 5 +- cpp_utils/BLECharacteristicCallbacks.h | 1 - cpp_utils/BLECharacteristicMap.cpp | 6 - cpp_utils/BLECharacteristicMap.h | 2 - cpp_utils/BLEClient.cpp | 7 +- cpp_utils/BLEClient.h | 3 - cpp_utils/BLEClientCallbacks.cpp | 9 - cpp_utils/BLEClientCallbacks.h | 3 +- ...EDesciptorMap.cpp => BLEDescriptorMap.cpp} | 7 - cpp_utils/BLERemoteCharacteristic.cpp | 35 +- cpp_utils/BLERemoteCharacteristic.h | 3 +- cpp_utils/BLERemoteDescriptor.cpp | 9 - cpp_utils/BLERemoteDescriptor.h | 2 - cpp_utils/BLEScan.cpp | 32 +- cpp_utils/BLEServer.cpp | 4 - cpp_utils/BLEServer.h | 5 +- cpp_utils/BLEServerCallbacks.cpp | 9 - cpp_utils/BLEServerCallbacks.h | 3 +- cpp_utils/BLEService.cpp | 34 +- cpp_utils/BLEService.h | 1 - cpp_utils/BLEServiceMap.cpp | 6 - cpp_utils/BLEServiceMap.h | 2 - cpp_utils/BLEUUID.cpp | 13 +- cpp_utils/BLEUUID.h | 1 - cpp_utils/BLEUtils.cpp | 75 +- cpp_utils/BLEUtils.h | 2 - cpp_utils/File.cpp | 9 +- cpp_utils/File.h | 9 +- cpp_utils/FileSystem.cpp | 7 - cpp_utils/FileSystem.h | 10 +- cpp_utils/GPIO.cpp | 32 +- cpp_utils/GPIO.h | 29 +- cpp_utils/MFRC522.cpp | 1645 +++++++++++++++++ cpp_utils/MFRC522.h | 418 +++++ cpp_utils/MFRC522Debug.h | 14 + cpp_utils/MRFC522Debug.cpp | 46 + cpp_utils/OV7670.cpp | 3 +- cpp_utils/README.md | 18 + cpp_utils/SPI.cpp | 70 +- cpp_utils/SPI.h | 13 +- cpp_utils/tests/MFRC522/DumpInfo.cpp | 50 + cpp_utils/tests/MFRC522/main.cpp | 38 + 54 files changed, 2432 insertions(+), 295 deletions(-) rename cpp_utils/{BLEDesciptorMap.cpp => BLEDescriptorMap.cpp} (97%) create mode 100644 cpp_utils/MFRC522.cpp create mode 100644 cpp_utils/MFRC522.h create mode 100644 cpp_utils/MFRC522Debug.h create mode 100644 cpp_utils/MRFC522Debug.cpp create mode 100644 cpp_utils/tests/MFRC522/DumpInfo.cpp create mode 100644 cpp_utils/tests/MFRC522/main.cpp diff --git a/cpp_utils/BLE.cpp b/cpp_utils/BLE.cpp index 25991383..6db82d30 100644 --- a/cpp_utils/BLE.cpp +++ b/cpp_utils/BLE.cpp @@ -33,13 +33,6 @@ BLEClient *BLE::m_pClient = nullptr; #include -BLE::BLE() { -} - - -BLE::~BLE() { -} - BLEClient* BLE::createClient() { m_pClient = new BLEClient(); diff --git a/cpp_utils/BLE.h b/cpp_utils/BLE.h index 7b167a05..2d8bd98c 100644 --- a/cpp_utils/BLE.h +++ b/cpp_utils/BLE.h @@ -24,8 +24,6 @@ */ class BLE { public: - BLE(); - virtual ~BLE(); static void dumpDevices(); static BLEClient *createClient(); diff --git a/cpp_utils/BLE2902.cpp b/cpp_utils/BLE2902.cpp index 9f7669e9..e9b93642 100644 --- a/cpp_utils/BLE2902.cpp +++ b/cpp_utils/BLE2902.cpp @@ -19,9 +19,6 @@ BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t) 0x2902)) { setValue(data, 2); } -BLE2902::~BLE2902() { -} - /** * @brief Set the notifications flag. * @param [in] flag The notifications flag. diff --git a/cpp_utils/BLE2902.h b/cpp_utils/BLE2902.h index 2f4b95e8..de4c4ded 100644 --- a/cpp_utils/BLE2902.h +++ b/cpp_utils/BLE2902.h @@ -24,7 +24,6 @@ class BLE2902: public BLEDescriptor { public: BLE2902(); - virtual ~BLE2902(); void setNotifications(bool flag); void setIndications(bool flag); }; // BLE2902 diff --git a/cpp_utils/BLEAddress.cpp b/cpp_utils/BLEAddress.cpp index 21713bba..44bbfb78 100644 --- a/cpp_utils/BLEAddress.cpp +++ b/cpp_utils/BLEAddress.cpp @@ -49,9 +49,6 @@ BLEAddress::BLEAddress(std::string stringAddress) { } // BLEAddress -BLEAddress::~BLEAddress() { -} // ~BLEAddress - /** * @brief Determine if this address equals another. * @param [in] otherAddress The other address to compare against. diff --git a/cpp_utils/BLEAddress.h b/cpp_utils/BLEAddress.h index 3f853e89..994a5699 100644 --- a/cpp_utils/BLEAddress.h +++ b/cpp_utils/BLEAddress.h @@ -16,7 +16,6 @@ class BLEAddress { public: BLEAddress(esp_bd_addr_t address); BLEAddress(std::string stringAddress); - virtual ~BLEAddress(); bool equals(BLEAddress otherAddress); esp_bd_addr_t* getNative(); std::string toString(); diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 40916e44..6e9c3306 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -39,10 +39,6 @@ BLEAdvertisedDevice::BLEAdvertisedDevice() { } // BLEAdvertisedDevice -BLEAdvertisedDevice::~BLEAdvertisedDevice() { -} - - /** * @brief Get the address. * @return The address of the advertised device. diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index c408cfd5..6e8a1694 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -22,7 +22,6 @@ class BLEScan; class BLEAdvertisedDevice { public: BLEAdvertisedDevice(); - virtual ~BLEAdvertisedDevice(); BLEAddress getAddress(); uint16_t getApperance(); diff --git a/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp b/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp index cd64424e..c84e9cff 100644 --- a/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp +++ b/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp @@ -8,10 +8,4 @@ #if defined(CONFIG_BT_ENABLED) #include "BLEAdvertisedDeviceCallbacks.h" - -BLEAdvertisedDeviceCallbacks::BLEAdvertisedDeviceCallbacks() { -} - -BLEAdvertisedDeviceCallbacks::~BLEAdvertisedDeviceCallbacks() { -} #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertisedDeviceCallbacks.h b/cpp_utils/BLEAdvertisedDeviceCallbacks.h index e21ba535..7b0911d2 100644 --- a/cpp_utils/BLEAdvertisedDeviceCallbacks.h +++ b/cpp_utils/BLEAdvertisedDeviceCallbacks.h @@ -14,8 +14,7 @@ class BLEAdvertisedDevice; class BLEAdvertisedDeviceCallbacks { public: - BLEAdvertisedDeviceCallbacks(); - virtual ~BLEAdvertisedDeviceCallbacks(); + virtual ~BLEAdvertisedDeviceCallbacks() {} virtual void onResult(BLEAdvertisedDevice *pAdvertisedDevice) = 0; }; #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index cfd7a213..f6abf822 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -44,11 +44,6 @@ BLEAdvertising::BLEAdvertising() { } // BLEAdvertising -BLEAdvertising::~BLEAdvertising() { -} // ~BLEAdvertising - - - /** * @brief Set the device appearance in the advertising data. * The appearance attribute is of type 0x19. The codes for distinct appearances can be found here: diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index c1ae4630..872d38e0 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -14,7 +14,6 @@ class BLEAdvertising { public: BLEAdvertising(); - virtual ~BLEAdvertising(); void start(); void stop(); void setAppearance(uint16_t appearance); diff --git a/cpp_utils/BLECharacteristicCallbacks.cpp b/cpp_utils/BLECharacteristicCallbacks.cpp index d0db31fa..7f4d8026 100644 --- a/cpp_utils/BLECharacteristicCallbacks.cpp +++ b/cpp_utils/BLECharacteristicCallbacks.cpp @@ -10,11 +10,8 @@ #include static char LOG_TAG[] = "BLECharacteristicCallbacks"; -BLECharacteristicCallbacks::BLECharacteristicCallbacks() { -} -BLECharacteristicCallbacks::~BLECharacteristicCallbacks() { -} +BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {} /** * @brief Callback function to support a read request. diff --git a/cpp_utils/BLECharacteristicCallbacks.h b/cpp_utils/BLECharacteristicCallbacks.h index 3ee509f9..b6eab72b 100644 --- a/cpp_utils/BLECharacteristicCallbacks.h +++ b/cpp_utils/BLECharacteristicCallbacks.h @@ -14,7 +14,6 @@ class BLECharacteristic; class BLECharacteristicCallbacks { public: - BLECharacteristicCallbacks(); virtual ~BLECharacteristicCallbacks(); virtual void onRead(BLECharacteristic *pCharacteristic); virtual void onWrite(BLECharacteristic *pCharacteristic); diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index e64f9ca9..45f0fb35 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -10,12 +10,6 @@ #include #include "BLECharacteristicMap.h" -BLECharacteristicMap::BLECharacteristicMap() { -} - -BLECharacteristicMap::~BLECharacteristicMap() { -} - /** * @brief Return the characteristic by UUID. diff --git a/cpp_utils/BLECharacteristicMap.h b/cpp_utils/BLECharacteristicMap.h index 4fb3ba2a..ada3aa61 100644 --- a/cpp_utils/BLECharacteristicMap.h +++ b/cpp_utils/BLECharacteristicMap.h @@ -14,8 +14,6 @@ class BLECharacteristicMap { public: - BLECharacteristicMap(); - virtual ~BLECharacteristicMap(); void setByUUID(BLEUUID uuid, BLECharacteristic *characteristic); void setByHandle(uint16_t handle, BLECharacteristic *characteristic); BLECharacteristic *getByUUID(BLEUUID uuid); diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 8f256c4c..10d6d7e5 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -48,10 +48,6 @@ BLEClient::BLEClient() { m_haveServices = false; } // BLEClient -BLEClient::~BLEClient() { - ESP_LOGD(LOG_TAG, "BLEClient object destroyed"); -} // ~BLEClient - /** * @brief Connect to the partner. * @param [in] address The address of the partner. @@ -143,6 +139,7 @@ void BLEClient::gattClientEventHandler( break; } // ESP_GATTC_REG_EVT + // // ESP_GATTC_SEARCH_CMPL_EVT // @@ -170,6 +167,7 @@ void BLEClient::gattClientEventHandler( break; } // ESP_GATTC_SEARCH_RES_EVT + default: { break; } @@ -221,6 +219,7 @@ BLERemoteService* BLEClient::getService(BLEUUID uuid) { return nullptr; } // getService + /** * @brief Ask the remote BLE server for its services. * A BLE Server exposes a set of services for its partners. Here we ask the server for its set of diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 804b71a1..3facfd12 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -29,9 +29,6 @@ class BLEClientCallbacks; class BLEClient { public: BLEClient(); - virtual ~BLEClient(); - - void connect(BLEAddress address); void disconnect(); diff --git a/cpp_utils/BLEClientCallbacks.cpp b/cpp_utils/BLEClientCallbacks.cpp index 87042a2a..59054842 100644 --- a/cpp_utils/BLEClientCallbacks.cpp +++ b/cpp_utils/BLEClientCallbacks.cpp @@ -10,15 +10,6 @@ #include static const char LOG_TAG[] = "BLEClientCallbacks"; -BLEClientCallbacks::BLEClientCallbacks() { - // TODO Auto-generated constructor stub - -} - -BLEClientCallbacks::~BLEClientCallbacks() { - // TODO Auto-generated destructor stub -} - void BLEClientCallbacks::onConnect(BLEClient* pClient) { ESP_LOGD(LOG_TAG, ">> onConnect(): Default"); ESP_LOGD(LOG_TAG, "<< onConnect()"); diff --git a/cpp_utils/BLEClientCallbacks.h b/cpp_utils/BLEClientCallbacks.h index 42fa3424..7bef3f94 100644 --- a/cpp_utils/BLEClientCallbacks.h +++ b/cpp_utils/BLEClientCallbacks.h @@ -14,8 +14,7 @@ class BLEClient; class BLEClientCallbacks { public: - BLEClientCallbacks(); - virtual ~BLEClientCallbacks(); + virtual ~BLEClientCallbacks() {}; virtual void onConnect(BLEClient *pClient); }; #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEDesciptorMap.cpp b/cpp_utils/BLEDescriptorMap.cpp similarity index 97% rename from cpp_utils/BLEDesciptorMap.cpp rename to cpp_utils/BLEDescriptorMap.cpp index 22a0b000..ff964473 100644 --- a/cpp_utils/BLEDesciptorMap.cpp +++ b/cpp_utils/BLEDescriptorMap.cpp @@ -12,13 +12,6 @@ #include "BLEDescriptor.h" #include // ESP32 BLE -BLEDescriptorMap::BLEDescriptorMap() { -} - -BLEDescriptorMap::~BLEDescriptorMap() { -} - - /** * @brief Return the descriptor by UUID. * @param [in] UUID The UUID to look up the descriptor. diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 649daf65..a59dee9f 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -53,12 +53,14 @@ static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { } // compareCharId -BLERemoteCharacteristic::~BLERemoteCharacteristic() { -} - - /** - * @brief Handle GATT Client events + * @brief Handle GATT Client events. + * When an event arrives for a GATT client we give this characteristic the opportunity to + * take a look at it to see if there is interest in it. + * @param [in] event The type of event. + * @param [in] gattc_if The interface on which the event was received. + * @param [in] evtParam Payload data for the event. + * @returns N/A */ void BLERemoteCharacteristic::gattClientEventHandler( esp_gattc_cb_event_t event, @@ -67,6 +69,7 @@ void BLERemoteCharacteristic::gattClientEventHandler( switch(event) { // // ESP_GATTC_READ_CHAR_EVT + // This event indicates that the server has responded to the read request. // // read: // esp_gatt_status_t status @@ -81,13 +84,18 @@ void BLERemoteCharacteristic::gattClientEventHandler( if (compareSrvcId(evtParam->read.srvc_id, *m_pRemoteService->getSrvcId()) == false) { break; } + if (evtParam->read.conn_id != m_pRemoteService->getClient()->getConnId()) { break; } + if (compareGattId(evtParam->read.char_id, m_charId) == false) { break; } - m_value = std::string((char*)evtParam->read.value, evtParam->read.value_len); + + if (evtParam->read.status == ESP_GATT_OK) { + m_value = std::string((char*)evtParam->read.value, evtParam->read.value_len); + } m_semaphoreReadCharEvt.give(); break; } // ESP_GATTC_READ_CHAR_EVT @@ -186,19 +194,24 @@ uint8_t BLERemoteCharacteristic::readUInt8(void) { */ std::string BLERemoteCharacteristic::readValue() { ESP_LOGD(LOG_TAG, ">> readValue()"); + m_semaphoreReadCharEvt.take("readValue"); + esp_err_t errRc = ::esp_ble_gattc_read_char( m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getConnId(), m_pRemoteService->getSrvcId(), &m_charId, ESP_GATT_AUTH_REQ_NONE); + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return ""; } + m_semaphoreReadCharEvt.take("readValue"); m_semaphoreReadCharEvt.give(); + ESP_LOGD(LOG_TAG, "<< readValue()"); return m_value; } // readValue @@ -210,18 +223,23 @@ std::string BLERemoteCharacteristic::readValue() { */ void BLERemoteCharacteristic::registerForNotify() { ESP_LOGD(LOG_TAG, ">> registerForNotify()"); + m_semaphoreRegForNotifyEvt.take("registerForNotify"); + esp_err_t errRc = ::esp_ble_gattc_register_for_notify( m_pRemoteService->getClient()->getGattcIf(), *m_pRemoteService->getClient()->getAddress().getNative(), m_pRemoteService->getSrvcId(), &m_charId); + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + m_semaphoreRegForNotifyEvt.take("registerForNotify"); m_semaphoreRegForNotifyEvt.give(); + ESP_LOGD(LOG_TAG, "<< registerForNotify()"); } // registerForNotify @@ -246,7 +264,9 @@ std::string BLERemoteCharacteristic::toString() { */ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { ESP_LOGD(LOG_TAG, ">> writeValue(), length: %d", newValue.length()); + m_semaphoreWriteCharEvt.take("writeValue"); + esp_err_t errRc = ::esp_ble_gattc_write_char( m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getConnId(), @@ -257,12 +277,15 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { response?ESP_GATT_WRITE_TYPE_RSP:ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE ); + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + m_semaphoreWriteCharEvt.take("writeValue"); m_semaphoreWriteCharEvt.give(); + ESP_LOGD(LOG_TAG, "<< writeValue()"); } // writeValue diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 531ef4aa..e98dc5e3 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -21,8 +21,7 @@ class BLERemoteService; class BLERemoteCharacteristic { public: - BLERemoteCharacteristic(esp_gatt_id_t charId, esp_gatt_char_prop_t charProp, BLERemoteService *pRemoteService); - virtual ~BLERemoteCharacteristic(); + BLERemoteCharacteristic(esp_gatt_id_t charId, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); // Public member functions std::string readValue(void); diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index eef0cd60..2be312ae 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -8,13 +8,4 @@ #if defined(CONFIG_BT_ENABLED) #include "BLERemoteDescriptor.h" -BLERemoteDescriptor::BLERemoteDescriptor() { - // TODO Auto-generated constructor stub - -} - -BLERemoteDescriptor::~BLERemoteDescriptor() { - // TODO Auto-generated destructor stub -} - #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteDescriptor.h b/cpp_utils/BLERemoteDescriptor.h index 47c647e6..a52c8b1c 100644 --- a/cpp_utils/BLERemoteDescriptor.h +++ b/cpp_utils/BLERemoteDescriptor.h @@ -11,8 +11,6 @@ #if defined(CONFIG_BT_ENABLED) class BLERemoteDescriptor { public: - BLERemoteDescriptor(); - virtual ~BLERemoteDescriptor(); }; #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ */ diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 31a25d20..0432c632 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -21,7 +21,6 @@ static char LOG_TAG[] = "BLEScan"; - BLEScan::BLEScan() { m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan. m_scan_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; @@ -37,6 +36,11 @@ BLEScan::~BLEScan() { clearAdvertisedDevices(); } + +/** + * @brief Clear the history of previously detected advertised devices. + * @return N/A + */ void BLEScan::clearAdvertisedDevices() { for (int i=0; i BLEScan::start(uint32_t duration) { ESP_LOGD(LOG_TAG, ">> start(%d)", duration); + m_semaphoreScanEnd.take("start"); + clearAdvertisedDevices(); + esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); m_semaphoreScanEnd.give(); return m_vectorAvdertisedDevices; } + errRc = ::esp_ble_gap_start_scanning(duration); + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); m_semaphoreScanEnd.give(); return m_vectorAvdertisedDevices; } + m_stopped = false; + m_semaphoreScanEnd.take("start"); m_semaphoreScanEnd.give(); + ESP_LOGD(LOG_TAG, "<< start()"); return m_vectorAvdertisedDevices; } // start @@ -210,16 +223,19 @@ std::vector BLEScan::start(uint32_t duration) { */ void BLEScan::stop() { ESP_LOGD(LOG_TAG, ">> stop()"); - m_stopped = true; + esp_err_t errRc = ::esp_ble_gap_stop_scanning(); + + m_stopped = true; + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); return; } + m_semaphoreScanEnd.give(); + ESP_LOGD(LOG_TAG, "<< stop()"); } // stop - - #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 53a01106..dd8583a0 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -38,9 +38,6 @@ BLEServer::BLEServer() { } // BLEServer -BLEServer::~BLEServer() { -} // ~BLEServer - void BLEServer::createApp(uint16_t appId) { m_appId = appId; registerApp(); @@ -274,7 +271,6 @@ void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { */ void BLEServer::startAdvertising() { ESP_LOGD(LOG_TAG, ">> startAdvertising()"); - m_bleAdvertising.setAppearance(3); m_bleAdvertising.start(); ESP_LOGD(LOG_TAG, "<< startAdvertising()"); } // startAdvertising diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 2df39028..9bf19049 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -25,11 +25,10 @@ class BLEServerCallbacks; class BLEServer { public: BLEServer(); - virtual ~BLEServer(); void createApp(uint16_t appId); - BLEService *createService(BLEUUID uuid); - BLEAdvertising *getAdvertising(); + BLEService* createService(BLEUUID uuid); + BLEAdvertising* getAdvertising(); uint16_t getConnId(); uint16_t getGattsIf(); void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); diff --git a/cpp_utils/BLEServerCallbacks.cpp b/cpp_utils/BLEServerCallbacks.cpp index 8023a286..cfa4fb7d 100644 --- a/cpp_utils/BLEServerCallbacks.cpp +++ b/cpp_utils/BLEServerCallbacks.cpp @@ -10,15 +10,6 @@ #include static const char LOG_TAG[] = "BLEServerCallbacks"; -BLEServerCallbacks::BLEServerCallbacks() { - // TODO Auto-generated constructor stub - -} - -BLEServerCallbacks::~BLEServerCallbacks() { - // TODO Auto-generated destructor stub -} - void BLEServerCallbacks::onConnect(BLEServer* pServer) { ESP_LOGD(LOG_TAG, ">> onConnect(): Default"); ESP_LOGD(LOG_TAG, "<< onConnect()"); diff --git a/cpp_utils/BLEServerCallbacks.h b/cpp_utils/BLEServerCallbacks.h index 4a520fad..4c9d07a6 100644 --- a/cpp_utils/BLEServerCallbacks.h +++ b/cpp_utils/BLEServerCallbacks.h @@ -14,8 +14,7 @@ class BLEServer; class BLEServerCallbacks { public: - BLEServerCallbacks(); - virtual ~BLEServerCallbacks(); + virtual ~BLEServerCallbacks() {}; virtual void onConnect(BLEServer *pServer); virtual void onDisconnect(BLEServer *pServer); }; diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 993680f1..cd4170f8 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -39,10 +39,6 @@ BLEService::BLEService(BLEUUID uuid) { } // BLEService -BLEService::~BLEService() { -} - - /** * @brief Create the service. * Create the service. @@ -51,17 +47,21 @@ BLEService::~BLEService() { */ void BLEService::executeCreate(BLEServer *pServer) { ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service)"); - m_pServer = pServer; + + m_pServer = pServer; esp_gatt_srvc_id_t srvc_id; srvc_id.id.inst_id = 0; srvc_id.id.uuid = *m_uuid.getNative(); m_serializeMutex.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT + esp_err_t errRc = ::esp_ble_gatts_create_service(getServer()->getGattsIf(), &srvc_id, 10); + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + ESP_LOGD(LOG_TAG, "<< executeCreate()"); } // executeCreate @@ -107,12 +107,16 @@ void BLEService::start() { // We ask the BLE runtime to start the service and then create each of the characteristics. // ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); + esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); + while(pCharacteristic != nullptr) { m_lastCreatedCharacteristic = pCharacteristic; pCharacteristic->executeCreate(this); @@ -264,6 +268,7 @@ BLECharacteristic* BLEService::getCharacteristic(BLEUUID uuid) { return m_characteristicMap.getByUUID(uuid); } + /** * @brief Return a string representation of this service. * A service is defined by: @@ -278,14 +283,25 @@ std::string BLEService::toString() { return stringStream.str(); } // toString + +/** + * @brief Get the last created characteristic. + * It is lamentable that this function has to exist. It returns the last created characteristic. + * We need this because the descriptor API is built around the notion that a new descriptor, when created, + * is associated with the last characteristics created and we need that information. + * @return The last created characteristic. + */ BLECharacteristic* BLEService::getLastCreatedCharacteristic() { return m_lastCreatedCharacteristic; -} +} // getLastCreatedCharacteristic + +/** + * @brief Get the BLE server associated with this service. + * @return The BLEServer associated with this service. + */ BLEServer* BLEService::getServer() { return m_pServer; -} - - +} // getServer #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index c5a7a4a1..ddc4655c 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -23,7 +23,6 @@ class BLEServer; class BLEService { public: BLEService(BLEUUID uuid); - virtual ~BLEService(); void addCharacteristic(BLECharacteristic *pCharacteristic); BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index 5514c1c0..00fb5f7b 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -11,12 +11,6 @@ #include "BLEServiceMap.h" #include "BLEService.h" -BLEServiceMap::BLEServiceMap() { -} - -BLEServiceMap::~BLEServiceMap() { -} - /** * @brief Return the service by UUID. * @param [in] UUID The UUID to look up the service. diff --git a/cpp_utils/BLEServiceMap.h b/cpp_utils/BLEServiceMap.h index 17523371..3a121ecf 100644 --- a/cpp_utils/BLEServiceMap.h +++ b/cpp_utils/BLEServiceMap.h @@ -17,8 +17,6 @@ class BLEService; class BLEServiceMap { public: - BLEServiceMap(); - virtual ~BLEServiceMap(); void setByUUID(BLEUUID uuid, BLEService *service); void setByHandle(uint16_t handle, BLEService *service); BLEService *getByUUID(BLEUUID uuid); diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 761d3e38..04f81403 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -152,10 +152,6 @@ BLEUUID::BLEUUID() { } // BLEUUID -BLEUUID::~BLEUUID() { -} // ~BLEUUID - - /** * @brief Compare a UUID against this UUID. * @param [in] uuid The UUID to compare against. @@ -163,21 +159,22 @@ BLEUUID::~BLEUUID() { */ bool BLEUUID::equals(BLEUUID uuid) { //ESP_LOGD(TAG, "Comparing: %s to %s", toString().c_str(), uuid.toString().c_str()); - if (m_valueSet == false) { - return false; - } - if (uuid.m_valueSet == false) { + if (m_valueSet == false || uuid.m_valueSet == false) { return false; } + if (uuid.m_uuid.len != m_uuid.len) { return uuid.toString() == toString(); } + if (uuid.m_uuid.len == ESP_UUID_LEN_16) { return uuid.m_uuid.uuid.uuid16 == m_uuid.uuid.uuid16; } + if (uuid.m_uuid.len == ESP_UUID_LEN_32) { return uuid.m_uuid.uuid.uuid32 == m_uuid.uuid.uuid32; } + return memcmp(uuid.m_uuid.uuid.uuid128, m_uuid.uuid.uuid128, 16) == 0; } // equals diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index c85c0271..f0585776 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -21,7 +21,6 @@ class BLEUUID { BLEUUID(uint8_t *pData, size_t size, bool msbFirst); BLEUUID(esp_gatt_srvc_id_t srcvId); BLEUUID(); - virtual ~BLEUUID(); bool equals(BLEUUID uuid); esp_bt_uuid_t *getNative(); void to128(); diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 73988dfb..ab079cb2 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -25,14 +25,10 @@ static char LOG_TAG[] = "BLEUtils"; +/* static std::map g_addressMap; static std::map g_connIdMap; - -BLEUtils::BLEUtils() { -} - -BLEUtils::~BLEUtils() { -} +*/ typedef struct { uint32_t assignedNumber; @@ -1060,43 +1056,6 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { -/** - * @brief Find a %BLEDevice by a address. - * - * We keep can keep a record of BLEDevices by their address. We can use this method - * to retrieve the %BLEDevice that was previously saved by its address. - * - * @param [in] address The address of the device we want to locate. - * @return A pointer to the %BLEDevice associated with this address. - */ -BLEClient* BLEUtils::findByAddress(BLEAddress address) { - ESP_LOGD(LOG_TAG, "findByAddress(%s)", address.toString().c_str()); - return g_addressMap.at(address.toString()); -} // findByAddress - - -/** - * @brief Find a %BLEDevice by a conn_id. - * - * We keep can keep a record of BLEDevices by their conn_id. We can use this method - * to retrieve the %BLEDevice that was previously saved by its conn_id. - * - * @param [in] conn_id The conn_id of the device we want to locate. - * @return A pointer to the %BLEDevice associated with this conn_id. - */ -BLEClient* BLEUtils::findByConnId(uint16_t conn_id) { - //try { - ESP_LOGD(LOG_TAG, "findByConnId(%d)", conn_id); - return g_connIdMap.at(conn_id); - /* - } catch(std::out_of_range &e) { - ESP_LOGD(tag, "findByConnId: Not found %d", conn_id); - return nullptr; - } - */ -} // findByConnId - - /** * @brief Convert a BT GAP event type to a string representation. * @param [in] eventType The type of event. @@ -1170,7 +1129,7 @@ std::string BLEUtils::gattServiceIdToString(esp_gatt_srvc_id_t srvcId) { std::string BLEUtils::gattServiceToString(uint32_t serviceId) { - gattService_t *p = (gattService_t *)g_gattServices; + gattService_t* p = (gattService_t *)g_gattServices; while (p->name.length() > 0) { if (p->assignedNumber == serviceId) { return p->name; @@ -1273,34 +1232,10 @@ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { } // gattStatusToString -/** - * @brief Register a %BLEDevice by its address. - * - * Register a %BLEDevice by its address for subsequent lookup/retrieval. - * @param [in] address The address of the device. - * @param [in] pDevice A pointer to a %BLEDevice instance. - */ -void BLEUtils::registerByAddress(BLEAddress address, BLEClient* pDevice) { - ESP_LOGD(LOG_TAG, "registerByAddress(%s)", address.toString().c_str()); - g_addressMap.insert(std::pair(address.toString(), pDevice)); -} // registerByAddress - - -/** - * @brief Register a %BLEDevice by its conn_id. - * - * Register a %BLEDevice by its conn_id for subsequent lookup/retrieval. - * @param [in] address The conn_id of the device. - * @param [in] pDevice A pointer to a %BLEDevice instance. - */ -void BLEUtils::registerByConnId(uint16_t conn_id, BLEClient* pDevice) { - ESP_LOGD(LOG_TAG, "registerByConnId(%d)", conn_id); - g_connIdMap.insert(std::pair(conn_id, pDevice)); -} // registerByConnId - - /** * @brief convert a GAP search event to a string. + * @param [in] searchEvt + * @return The search event type as a string. */ const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { switch(searchEvt) { diff --git a/cpp_utils/BLEUtils.h b/cpp_utils/BLEUtils.h index d58f57a3..b2d9c943 100644 --- a/cpp_utils/BLEUtils.h +++ b/cpp_utils/BLEUtils.h @@ -17,8 +17,6 @@ class BLEUtils { public: - BLEUtils(); - virtual ~BLEUtils(); static const char *advTypeToString(uint8_t advType); static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id=0); diff --git a/cpp_utils/File.cpp b/cpp_utils/File.cpp index abf083a2..0f53bfe4 100644 --- a/cpp_utils/File.cpp +++ b/cpp_utils/File.cpp @@ -22,10 +22,7 @@ static const char tag[] = "File"; File::File(std::string name, uint8_t type) { m_name = name; m_type = type; -} - -File::~File() { -} +} // File /** @@ -83,7 +80,7 @@ std::string File::getContent(uint32_t offset, uint32_t readSize) { std::string ret((char *)pData, bytesRead); free(pData); return ret; -} +} // getContent /** @@ -134,5 +131,3 @@ bool File::isDirectory() { } return false; } // isDirectory - - diff --git a/cpp_utils/File.h b/cpp_utils/File.h index fa88c40d..cd64435f 100644 --- a/cpp_utils/File.h +++ b/cpp_utils/File.h @@ -16,18 +16,17 @@ class File { public: File(std::string name, uint8_t type = DT_UNKNOWN); - virtual ~File(); std::string getContent(bool base64Encode=false); std::string getContent(uint32_t offset, uint32_t size); std::string getName(); - uint8_t getType(); - bool isDirectory(); - uint32_t length(); + uint8_t getType(); + bool isDirectory(); + uint32_t length(); private: std::string m_name; - uint8_t m_type; + uint8_t m_type; }; #endif /* COMPONENTS_CPP_UTILS_FILE_H_ */ diff --git a/cpp_utils/FileSystem.cpp b/cpp_utils/FileSystem.cpp index 34148584..e4eed154 100644 --- a/cpp_utils/FileSystem.cpp +++ b/cpp_utils/FileSystem.cpp @@ -19,13 +19,6 @@ static char tag[] = "FileSystem"; -FileSystem::FileSystem() { -} - -FileSystem::~FileSystem() { -} - - /** * @brief Dump a given directory to the log. * @param [in] path The path to the directory to dump. diff --git a/cpp_utils/FileSystem.h b/cpp_utils/FileSystem.h index 0b31f208..b2ab63b6 100644 --- a/cpp_utils/FileSystem.h +++ b/cpp_utils/FileSystem.h @@ -15,13 +15,11 @@ */ class FileSystem { public: - FileSystem(); - virtual ~FileSystem(); - static std::vector getDirectoryContents(std:: string path); - static void dumpDirectory(std::string path); - static int mkdir(std::string path); + static std::vector getDirectoryContents(std:: string path); + static void dumpDirectory(std::string path); + static int mkdir(std::string path); static std::vector pathSplit(std::string path); - static int remove(std::string path); + static int remove(std::string path); }; #endif /* COMPONENTS_CPP_UTILS_FILESYSTEM_H_ */ diff --git a/cpp_utils/GPIO.cpp b/cpp_utils/GPIO.cpp index 5ab57a58..8c551c0b 100644 --- a/cpp_utils/GPIO.cpp +++ b/cpp_utils/GPIO.cpp @@ -11,14 +11,18 @@ #include static char tag[] = "GPIO"; - /** - * @brief Class instance constructor. + * @brief Set the pin high. + * + * Ensure that the pin is set to be output prior to calling this method. + * + * @param [in] pin The pin to be set high. + * @return N/A. */ -/* -GPIO::GPIO() { -} -*/ +void ESP32CPP::GPIO::high(gpio_num_t pin) { + write(pin, true); +} // high + /** * @brief Determine if the pin is a valid pin for an ESP32 (i.e. is it in range). @@ -60,6 +64,19 @@ void ESP32CPP::GPIO::interruptEnable(gpio_num_t pin) { } // interruptEnable +/** + * @brief Set the pin low. + * + * Ensure that the pin is set to be output prior to calling this method. + * + * @param [in] pin The pin to be set low. + * @return N/A. + */ +void ESP32CPP::GPIO::low(gpio_num_t pin) { + write(pin, false); +} // low + + /** * @brief Read a value from the given pin. * @@ -71,6 +88,7 @@ bool ESP32CPP::GPIO::read(gpio_num_t pin) { return ::gpio_get_level(pin); } // read + /** * @brief Set the pin as input. * @@ -135,3 +153,5 @@ void ESP32CPP::GPIO::write(gpio_num_t pin, bool value) { } // write + + diff --git a/cpp_utils/GPIO.h b/cpp_utils/GPIO.h index bae32bb9..58a1047c 100644 --- a/cpp_utils/GPIO.h +++ b/cpp_utils/GPIO.h @@ -31,41 +31,16 @@ namespace ESP32CPP */ class GPIO { public: - //GPIO(); - /** - * @brief Set the pin high. - * - * Ensure that the pin is set to be output prior to calling this method. - * - * @param [in] pin The pin to be set high. - * @return N/A. - */ - static void high(gpio_num_t pin) { - write(pin, true); - } - + static void high(gpio_num_t pin); static void interruptDisable(gpio_num_t pin); static void interruptEnable(gpio_num_t pin); - static bool inRange(gpio_num_t pin); - /** - * @brief Set the pin low. - * - * Ensure that the pin is set to be output prior to calling this method. - * - * @param [in] pin The pin to be set low. - * @return N/A. - */ - static void low(gpio_num_t pin) { - write(pin, false); - } + static void low(gpio_num_t pin); static bool read(gpio_num_t pin); static void setInput(gpio_num_t pin); static void setInterruptType(gpio_num_t pin, gpio_int_type_t intrType); static void setOutput(gpio_num_t pin); static void write(gpio_num_t pin, bool value); - - }; // End GPIO } // End ESP32CPP namespace #endif /* COMPONENTS_CPP_UTILS_GPIO_H_ */ diff --git a/cpp_utils/MFRC522.cpp b/cpp_utils/MFRC522.cpp new file mode 100644 index 00000000..a860e9ba --- /dev/null +++ b/cpp_utils/MFRC522.cpp @@ -0,0 +1,1645 @@ +/* +* MFRC522.cpp - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT. +* NOTE: Please also check the comments in MFRC522.h - they provide useful hints and background information. +* Released into the public domain. +*/ + +/* + * Notes: + * This source file modified from the Github project found here: + * + * https://github.com/miguelbalboa/rfid + * + * Changes made to accommodate native ESP32 and ESP32 C++ class wrappers. + * + * Neil Kolban, July 2017. + * + */ + +#include "MFRC522.h" +#include "MFRC522Debug.h" +#include +#include +#include +#include +#include +#include + +static const char LOG_TAG[] = "MFRC522"; + +//#include "MFRC522Debug.h" + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for setting up the Arduino +///////////////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////////////// +// Basic interface functions for communicating with the MFRC522 +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Writes a byte to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void MFRC522::PCD_WriteRegister( + PCD_Register reg, ///< The register to write to. One of the PCD_Register enums. + byte value ///< The value to write. + ) { + uint8_t data[2]; + data[0] = reg; + data[1] = value; + m_spi.transfer(data, 2); +} // End PCD_WriteRegister() + +/** + * Writes a number of bytes to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void MFRC522::PCD_WriteRegister( PCD_Register reg, ///< The register to write to. One of the PCD_Register enums. + byte count, ///< The number of bytes to write to the register + byte *values ///< The values to write. Byte array. + ) { + uint8_t* pData = new uint8_t[count+1]; + pData[0] = reg; + memcpy(pData+1, values, count); + m_spi.transfer(pData, count+1); + delete[] pData; +} // End PCD_WriteRegister() + + +/** + * Reads a byte from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +byte MFRC522::PCD_ReadRegister( PCD_Register reg ///< The register to read from. One of the PCD_Register enums. + ) { + uint8_t data[2]; + data[0] = reg | 0x80; + data[1] = 0; + m_spi.transfer(data, 2); + return data[1]; +} // End PCD_ReadRegister() + +/** + * Reads a number of bytes from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void MFRC522::PCD_ReadRegister( PCD_Register reg, ///< The register to read from. One of the PCD_Register enums. + byte count, ///< The number of bytes to read + byte *values, ///< Byte array to store the values in. + byte rxAlign ///< Only bit positions rxAlign..7 in values[0] are updated. + ) { + if (count == 0) { + return; + } + //Serial.print(F("Reading ")); Serial.print(count); Serial.println(F(" bytes from register.")); + byte address = 0x80 | reg; // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. + byte index = 0; // Index in values array. + count--; // One read is performed outside of the loop + m_spi.transferByte(address); // Tell MFRC522 which address we want to read + if (rxAlign) { // Only update bit positions rxAlign..7 in values[0] + // Create bit mask for bit positions rxAlign..7 + byte mask = (0xFF << rxAlign) & 0xFF; + // Read value and tell that we want to read the same address again. + byte value = m_spi.transferByte(address); + // Apply mask to both current value of values[0] and the new data in value. + values[0] = (values[0] & ~mask) | (value & mask); + index++; + } + while (index < count) { + values[index] = m_spi.transferByte(address); // Read value and tell that we want to read the same address again. + index++; + } + values[index] = m_spi.transferByte(0); // Read the final byte. Send 0 to stop reading. +} // End PCD_ReadRegister() + +/** + * Sets the bits given in mask in register reg. + */ +void MFRC522::PCD_SetRegisterBitMask( PCD_Register reg, ///< The register to update. One of the PCD_Register enums. + byte mask ///< The bits to set. + ) { + byte tmp; + tmp = PCD_ReadRegister(reg); + PCD_WriteRegister(reg, tmp | mask); // set bit mask +} // End PCD_SetRegisterBitMask() + +/** + * Clears the bits given in mask from register reg. + */ +void MFRC522::PCD_ClearRegisterBitMask( PCD_Register reg, ///< The register to update. One of the PCD_Register enums. + byte mask ///< The bits to clear. + ) { + byte tmp; + tmp = PCD_ReadRegister(reg); + PCD_WriteRegister(reg, tmp & (~mask)); // clear bit mask +} // End PCD_ClearRegisterBitMask() + + +/** + * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PCD_CalculateCRC( byte *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. + byte length, ///< In: The number of bytes to transfer. + byte *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low byte first. + ) { + PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. + PCD_WriteRegister(DivIrqReg, 0x04); // Clear the CRCIRq interrupt request bit + PCD_WriteRegister(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization + PCD_WriteRegister(FIFODataReg, length, data); // Write data to the FIFO + PCD_WriteRegister(CommandReg, PCD_CalcCRC); // Start the calculation + + // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs. + // TODO check/modify for other architectures than Arduino Uno 16bit + + // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. + for (uint16_t i = 5000; i > 0; i--) { + // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved + byte n = PCD_ReadRegister(DivIrqReg); + if (n & 0x04) { // CRCIRq bit set - calculation done + PCD_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. + // Transfer the result from the registers to the result buffer + result[0] = PCD_ReadRegister(CRCResultRegL); + result[1] = PCD_ReadRegister(CRCResultRegH); + return STATUS_OK; + } + } + // 89ms passed and nothing happend. Communication with the MFRC522 might be down. + return STATUS_TIMEOUT; +} // End PCD_CalculateCRC() + + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for manipulating the MFRC522 +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the MFRC522 chip. + */ +void MFRC522::PCD_Init() { + //m_spi.setHost(VSPI_HOST); + m_spi.init(); + + bool hardReset = false; + + + // Set the chipSelectPin as digital output, do not select the slave yet +/* + pinMode(_chipSelectPin, OUTPUT); + digitalWrite(_chipSelectPin, HIGH); +*/ + // If a valid pin number has been set, pull device out of power down / reset state. + if (_resetPowerDownPin != UNUSED_PIN) { + // Set the resetPowerDownPin as digital output, do not reset or power down. + //pinMode(_resetPowerDownPin, OUTPUT); + ESP32CPP::GPIO::setInput((gpio_num_t)_resetPowerDownPin); + + if (ESP32CPP::GPIO::read((gpio_num_t)_resetPowerDownPin) == false) { // The MFRC522 chip is in power down mode. + ESP32CPP::GPIO::setOutput((gpio_num_t)_resetPowerDownPin); + ESP32CPP::GPIO::high((gpio_num_t)_resetPowerDownPin); // Exit power down mode. This triggers a hard reset. + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let us be generous: 50ms. + FreeRTOS::sleep(50); + hardReset = true; + } + } + + + if (!hardReset) { // Perform a soft reset if we haven't triggered a hard reset above. + PCD_Reset(); + } + + // Reset baud rates + PCD_WriteRegister(TxModeReg, 0x00); + PCD_WriteRegister(RxModeReg, 0x00); + // Reset ModWidthReg + PCD_WriteRegister(ModWidthReg, 0x26); + + // When communicating with a PICC we need a timeout if something goes wrong. + // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. + // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. + PCD_WriteRegister(TModeReg, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds + PCD_WriteRegister(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. + PCD_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. + PCD_WriteRegister(TReloadRegL, 0xE8); + + PCD_WriteRegister(TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting + PCD_WriteRegister(ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) + PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) +} // End PCD_Init() + +/** + * Initializes the MFRC522 chip. + */ +void MFRC522::PCD_Init( byte chipSelectPin, ///< Arduino pin connected to MFRC522's SPI slave select input (Pin 24, NSS, active low) + byte resetPowerDownPin ///< Arduino pin connected to MFRC522's reset and power down input (Pin 6, NRSTPD, active low) + ) { + _chipSelectPin = chipSelectPin; + _resetPowerDownPin = resetPowerDownPin; + // Set the chipSelectPin as digital output, do not select the slave yet + PCD_Init(); +} // End PCD_Init() + +/** + * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. + */ +void MFRC522::PCD_Reset() { + PCD_WriteRegister(CommandReg, PCD_SoftReset); // Issue the SoftReset command. + // The datasheet does not mention how long the SoftRest command takes to complete. + // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let us be generous: 50ms. + FreeRTOS::sleep(50); + // Wait for the PowerDown bit in CommandReg to be cleared + while (PCD_ReadRegister(CommandReg) & (1<<4)) { + // PCD still restarting - unlikely after waiting 50ms, but better safe than sorry. + } +} // End PCD_Reset() + +/** + * Turns the antenna on by enabling pins TX1 and TX2. + * After a reset these pins are disabled. + */ +void MFRC522::PCD_AntennaOn() { + byte value = PCD_ReadRegister(TxControlReg); + if ((value & 0x03) != 0x03) { + PCD_WriteRegister(TxControlReg, value | 0x03); + } +} // End PCD_AntennaOn() + +/** + * Turns the antenna off by disabling pins TX1 and TX2. + */ +void MFRC522::PCD_AntennaOff() { + PCD_ClearRegisterBitMask(TxControlReg, 0x03); +} // End PCD_AntennaOff() + +/** + * Get the current MFRC522 Receiver Gain (RxGain[2:0]) value. + * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * NOTE: Return value scrubbed with (0x07<<4)=01110000b as RCFfgReg may use reserved bits. + * + * @return Value of the RxGain, scrubbed to the 3 bits used. + */ +byte MFRC522::PCD_GetAntennaGain() { + return PCD_ReadRegister(RFCfgReg) & (0x07<<4); +} // End PCD_GetAntennaGain() + +/** + * Set the MFRC522 Receiver Gain (RxGain) to value specified by given mask. + * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * NOTE: Given mask is scrubbed with (0x07<<4)=01110000b as RCFfgReg may use reserved bits. + */ +void MFRC522::PCD_SetAntennaGain(byte mask) { + if (PCD_GetAntennaGain() != mask) { // only bother if there is a change + PCD_ClearRegisterBitMask(RFCfgReg, (0x07<<4)); // clear needed to allow 000 pattern + PCD_SetRegisterBitMask(RFCfgReg, mask & (0x07<<4)); // only set RxGain[2:0] bits + } +} // End PCD_SetAntennaGain() + +/** + * Performs a self-test of the MFRC522 + * See 16.1.1 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * + * @return Whether or not the test passed. Or false if no firmware reference is available. + */ +bool MFRC522::PCD_PerformSelfTest() { + // This follows directly the steps outlined in 16.1.1 + // 1. Perform a soft reset. + PCD_Reset(); + + // 2. Clear the internal buffer by writing 25 bytes of 00h + byte ZEROES[25] = {0x00}; + PCD_WriteRegister(FIFOLevelReg, 0x80); // flush the FIFO buffer + PCD_WriteRegister(FIFODataReg, 25, ZEROES); // write 25 bytes of 00h to FIFO + PCD_WriteRegister(CommandReg, PCD_Mem); // transfer to internal buffer + + // 3. Enable self-test + PCD_WriteRegister(AutoTestReg, 0x09); + + // 4. Write 00h to FIFO buffer + PCD_WriteRegister(FIFODataReg, 0x00); + + // 5. Start self-test by issuing the CalcCRC command + PCD_WriteRegister(CommandReg, PCD_CalcCRC); + + // 6. Wait for self-test to complete + byte n; + for (uint8_t i = 0; i < 0xFF; i++) { + // The datasheet does not specify exact completion condition except + // that FIFO buffer should contain 64 bytes. + // While selftest is initiated by CalcCRC command + // it behaves differently from normal CRC computation, + // so one can't reliably use DivIrqReg to check for completion. + // It is reported that some devices does not trigger CRCIRq flag + // during selftest. + n = PCD_ReadRegister(FIFOLevelReg); + if (n >= 64) { + break; + } + } + PCD_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. + + // 7. Read out resulting 64 bytes from the FIFO buffer. + byte result[64]; + PCD_ReadRegister(FIFODataReg, 64, result, 0); + + // Auto self-test done + // Reset AutoTestReg register to be 0 again. Required for normal operation. + PCD_WriteRegister(AutoTestReg, 0x00); + + // Determine firmware version (see section 9.3.4.8 in spec) + byte version = PCD_ReadRegister(VersionReg); + + // Pick the appropriate reference values + const byte *reference; + switch (version) { + case 0x88: // Fudan Semiconductor FM17522 clone + reference = FM17522_firmware_reference; + break; + case 0x90: // Version 0.0 + reference = MFRC522_firmware_referenceV0_0; + break; + case 0x91: // Version 1.0 + reference = MFRC522_firmware_referenceV1_0; + break; + case 0x92: // Version 2.0 + reference = MFRC522_firmware_referenceV2_0; + break; + default: // Unknown version + return false; // abort test + } + + // Verify that the results match up to our expectations + for (uint8_t i = 0; i < 64; i++) { + if (result[i] != (reference[i])) { + return false; + } + } + + // Test passed; all is good. + return true; +} // End PCD_PerformSelfTest() + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for communicating with PICCs +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Executes the Transceive command. + * CRC validation can only be done if backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PCD_TransceiveData( byte *sendData, ///< Pointer to the data to transfer to the FIFO. + byte sendLen, ///< Number of bytes to transfer to the FIFO. + byte *backData, ///< nullptr or pointer to buffer if data should be read back after executing the command. + byte *backLen, ///< In: Max number of bytes to write to *backData. Out: The number of bytes returned. + byte *validBits, ///< In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. Default nullptr. + byte rxAlign, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. + bool checkCRC ///< In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. + ) { + byte waitIRq = 0x30; // RxIRq and IdleIRq + return PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, sendData, sendLen, backData, backLen, validBits, rxAlign, checkCRC); +} // End PCD_TransceiveData() + +/** + * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. + * CRC validation can only be done if backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The command to execute. One of the PCD_Command enums. + byte waitIRq, ///< The bits in the ComIrqReg register that signals successful completion of the command. + byte *sendData, ///< Pointer to the data to transfer to the FIFO. + byte sendLen, ///< Number of bytes to transfer to the FIFO. + byte *backData, ///< nullptr or pointer to buffer if data should be read back after executing the command. + byte *backLen, ///< In: Max number of bytes to write to *backData. Out: The number of bytes returned. + byte *validBits, ///< In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. + byte rxAlign, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. + bool checkCRC ///< In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. + ) { + // Prepare values for BitFramingReg + byte txLastBits = validBits ? *validBits : 0; + byte bitFraming = (rxAlign << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. + PCD_WriteRegister(ComIrqReg, 0x7F); // Clear all seven interrupt request bits + PCD_WriteRegister(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization + PCD_WriteRegister(FIFODataReg, sendLen, sendData); // Write sendData to the FIFO + PCD_WriteRegister(BitFramingReg, bitFraming); // Bit adjustments + PCD_WriteRegister(CommandReg, command); // Execute the command + if (command == PCD_Transceive) { + PCD_SetRegisterBitMask(BitFramingReg, 0x80); // StartSend=1, transmission of data starts + } + + // Wait for the command to complete. + // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops transmitting. + // Each iteration of the do-while-loop takes 17.86μs. + // TODO check/modify for other architectures than Arduino Uno 16bit + uint16_t i; + for (i = 2000; i > 0; i--) { + byte n = PCD_ReadRegister(ComIrqReg); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq + if (n & waitIRq) { // One of the interrupts that signal success has been set. + break; + } + if (n & 0x01) { // Timer interrupt - nothing received in 25ms + return STATUS_TIMEOUT; + } + } + // 35.7ms and nothing happend. Communication with the MFRC522 might be down. + if (i == 0) { + return STATUS_TIMEOUT; + } + + // Stop now if any errors except collisions were detected. + byte errorRegValue = PCD_ReadRegister(ErrorReg); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr + if (errorRegValue & 0x13) { // BufferOvfl ParityErr ProtocolErr + return STATUS_ERROR; + } + + byte _validBits = 0; + + // If the caller wants data back, get it from the MFRC522. + if (backData && backLen) { + byte n = PCD_ReadRegister(FIFOLevelReg); // Number of bytes in the FIFO + if (n > *backLen) { + return STATUS_NO_ROOM; + } + *backLen = n; // Number of bytes returned + PCD_ReadRegister(FIFODataReg, n, backData, rxAlign); // Get received data from FIFO + _validBits = PCD_ReadRegister(ControlReg) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last received byte. If this value is 000b, the whole byte is valid. + if (validBits) { + *validBits = _validBits; + } + } + + // Tell about collisions + if (errorRegValue & 0x08) { // CollErr + return STATUS_COLLISION; + } + + // Perform CRC_A validation if requested. + if (backData && backLen && checkCRC) { + // In this case a MIFARE Classic NAK is not OK. + if (*backLen == 1 && _validBits == 4) { + return STATUS_MIFARE_NACK; + } + // We need at least the CRC_A value and all 8 bits of the last byte must be received. + if (*backLen < 2 || _validBits != 0) { + return STATUS_CRC_WRONG; + } + // Verify CRC_A - do our own calculation and store the control in controlBuffer. + byte controlBuffer[2]; + MFRC522::StatusCode status = PCD_CalculateCRC(&backData[0], *backLen - 2, &controlBuffer[0]); + if (status != STATUS_OK) { + return status; + } + if ((backData[*backLen - 2] != controlBuffer[0]) || (backData[*backLen - 1] != controlBuffer[1])) { + return STATUS_CRC_WRONG; + } + } + + return STATUS_OK; +} // End PCD_CommunicateWithPICC() + +/** + * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PICC_RequestA( byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in + byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. + ) { + return PICC_REQA_or_WUPA(PICC_CMD_REQA, bufferATQA, bufferSize); +} // End PICC_RequestA() + +/** + * Transmits a Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PICC_WakeupA( byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in + byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. + ) { + return PICC_REQA_or_WUPA(PICC_CMD_WUPA, bufferATQA, bufferSize); +} // End PICC_WakeupA() + +/** + * Transmits REQA or WUPA commands. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PICC_REQA_or_WUPA( byte command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in + byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. + ) { + byte validBits; + MFRC522::StatusCode status; + + if (bufferATQA == nullptr || *bufferSize < 2) { // The ATQA response is 2 bytes long. + return STATUS_NO_ROOM; + } + PCD_ClearRegisterBitMask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. + validBits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) byte. TxLastBits = BitFramingReg[2..0] + status = PCD_TransceiveData(&command, 1, bufferATQA, bufferSize, &validBits); + if (status != STATUS_OK) { + return status; + } + if (*bufferSize != 2 || validBits != 0) { // ATQA must be exactly 16 bits. + return STATUS_ERROR; + } + return STATUS_OK; +} // End PICC_REQA_or_WUPA() + +/** + * Transmits SELECT/ANTICOLLISION commands to select a single PICC. + * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or PICC_WakeupA(). + * On success: + * - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the ISO/IEC 14443-3 draft.) + * - The UID size and value of the chosen PICC is returned in *uid along with the SAK. + * + * A PICC UID consists of 4, 7 or 10 bytes. + * Only 4 bytes can be specified in a SELECT command, so for the longer UIDs two or three iterations are used: + * UID size Number of UID bytes Cascade levels Example of PICC + * ======== =================== ============== =============== + * single 4 1 MIFARE Classic + * double 7 2 MIFARE Ultralight + * triple 10 3 Not currently in use? + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. + byte validBits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply uid->size. + ) { + bool uidComplete; + bool selectDone; + bool useCascadeTag; + byte cascadeLevel = 1; + MFRC522::StatusCode result; + byte count; + byte index; + byte uidIndex; // The first index in uid->uidByte[] that is used in the current Cascade Level. + int8_t currentLevelKnownBits; // The number of known UID bits in the current Cascade Level. + byte buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A + byte bufferUsed; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO. + byte rxAlign; // Used in BitFramingReg. Defines the bit position for the first bit received. + byte txLastBits; // Used in BitFramingReg. The number of valid bits in the last transmitted byte. + byte *responseBuffer; + byte responseLength; + + // Description of buffer structure: + // Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 + // Byte 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete bytes, Low nibble: Extra bits. + // Byte 2: UID-data or CT See explanation below. CT means Cascade Tag. + // Byte 3: UID-data + // Byte 4: UID-data + // Byte 5: UID-data + // Byte 6: BCC Block Check Character - XOR of bytes 2-5 + // Byte 7: CRC_A + // Byte 8: CRC_A + // The BCC and CRC_A are only transmitted if we know all the UID bits of the current Cascade Level. + // + // Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) + // UID size Cascade level Byte2 Byte3 Byte4 Byte5 + // ======== ============= ===== ===== ===== ===== + // 4 bytes 1 uid0 uid1 uid2 uid3 + // 7 bytes 1 CT uid0 uid1 uid2 + // 2 uid3 uid4 uid5 uid6 + // 10 bytes 1 CT uid0 uid1 uid2 + // 2 CT uid3 uid4 uid5 + // 3 uid6 uid7 uid8 uid9 + + // Sanity checks + if (validBits > 80) { + return STATUS_INVALID; + } + + // Prepare MFRC522 + PCD_ClearRegisterBitMask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. + + // Repeat Cascade Level loop until we have a complete UID. + uidComplete = false; + while (!uidComplete) { + // Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2. + switch (cascadeLevel) { + case 1: + buffer[0] = PICC_CMD_SEL_CL1; + uidIndex = 0; + useCascadeTag = validBits && uid->size > 4; // When we know that the UID has more than 4 bytes + break; + + case 2: + buffer[0] = PICC_CMD_SEL_CL2; + uidIndex = 3; + useCascadeTag = validBits && uid->size > 7; // When we know that the UID has more than 7 bytes + break; + + case 3: + buffer[0] = PICC_CMD_SEL_CL3; + uidIndex = 6; + useCascadeTag = false; // Never used in CL3. + break; + + default: + return STATUS_INTERNAL_ERROR; + break; + } + + // How many UID bits are known in this Cascade Level? + currentLevelKnownBits = validBits - (8 * uidIndex); + if (currentLevelKnownBits < 0) { + currentLevelKnownBits = 0; + } + // Copy the known bits from uid->uidByte[] to buffer[] + index = 2; // destination index in buffer[] + if (useCascadeTag) { + buffer[index++] = PICC_CMD_CT; + } + byte bytesToCopy = currentLevelKnownBits / 8 + (currentLevelKnownBits % 8 ? 1 : 0); // The number of bytes needed to represent the known bits for this level. + if (bytesToCopy) { + byte maxBytes = useCascadeTag ? 3 : 4; // Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag + if (bytesToCopy > maxBytes) { + bytesToCopy = maxBytes; + } + for (count = 0; count < bytesToCopy; count++) { + buffer[index++] = uid->uidByte[uidIndex + count]; + } + } + // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits + if (useCascadeTag) { + currentLevelKnownBits += 8; + } + + // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. + selectDone = false; + while (!selectDone) { + // Find out how many bits and bytes to send and receive. + if (currentLevelKnownBits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT. + //Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); + buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole bytes + // Calculate BCC - Block Check Character + buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; + // Calculate CRC_A + result = PCD_CalculateCRC(buffer, 7, &buffer[7]); + if (result != STATUS_OK) { + return result; + } + txLastBits = 0; // 0 => All 8 bits are valid. + bufferUsed = 9; + // Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx) + responseBuffer = &buffer[6]; + responseLength = 3; + } + else { // This is an ANTICOLLISION. + //Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); + txLastBits = currentLevelKnownBits % 8; + count = currentLevelKnownBits / 8; // Number of whole bytes in the UID part. + index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs + buffer[1] = (index << 4) + txLastBits; // NVB - Number of Valid Bits + bufferUsed = index + (txLastBits ? 1 : 0); + // Store response in the unused part of buffer + responseBuffer = &buffer[index]; + responseLength = sizeof(buffer) - index; + } + + // Set bit adjustments + rxAlign = txLastBits; // Having a separate variable is overkill. But it makes the next line easier to read. + PCD_WriteRegister(BitFramingReg, (rxAlign << 4) + txLastBits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + // Transmit the buffer and receive the response. + result = PCD_TransceiveData(buffer, bufferUsed, responseBuffer, &responseLength, &txLastBits, rxAlign); + if (result == STATUS_COLLISION) { // More than one PICC in the field => collision. + byte valueOfCollReg = PCD_ReadRegister(CollReg); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] + if (valueOfCollReg & 0x20) { // CollPosNotValid + return STATUS_COLLISION; // Without a valid collision position we cannot continue + } + byte collisionPos = valueOfCollReg & 0x1F; // Values 0-31, 0 means bit 32. + if (collisionPos == 0) { + collisionPos = 32; + } + if (collisionPos <= currentLevelKnownBits) { // No progress - should not happen + return STATUS_INTERNAL_ERROR; + } + // Choose the PICC with the bit set. + currentLevelKnownBits = collisionPos; + count = (currentLevelKnownBits - 1) % 8; // The bit to modify + index = 1 + (currentLevelKnownBits / 8) + (count ? 1 : 0); // First byte is index 0. + buffer[index] |= (1 << count); + } + else if (result != STATUS_OK) { + return result; + } + else { // STATUS_OK + if (currentLevelKnownBits >= 32) { // This was a SELECT. + selectDone = true; // No more anticollision + // We continue below outside the while. + } + else { // This was an ANTICOLLISION. + // We now have all 32 bits of the UID in this Cascade Level + currentLevelKnownBits = 32; + // Run loop again to do the SELECT. + } + } + } // End of while (!selectDone) + + // We do not check the CBB - it was constructed by us above. + + // Copy the found UID bytes from buffer[] to uid->uidByte[] + index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] + bytesToCopy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; + for (count = 0; count < bytesToCopy; count++) { + uid->uidByte[uidIndex + count] = buffer[index++]; + } + + // Check response SAK (Select Acknowledge) + if (responseLength != 3 || txLastBits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A). + return STATUS_ERROR; + } + // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed anymore. + result = PCD_CalculateCRC(responseBuffer, 1, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + if ((buffer[2] != responseBuffer[1]) || (buffer[3] != responseBuffer[2])) { + return STATUS_CRC_WRONG; + } + if (responseBuffer[0] & 0x04) { // Cascade bit set - UID not complete yes + cascadeLevel++; + } + else { + uidComplete = true; + uid->sak = responseBuffer[0]; + } + } // End of while (!uidComplete) + + // Set correct uid->size + uid->size = 3 * cascadeLevel + 1; + + return STATUS_OK; +} // End PICC_Select() + +/** + * Instructs a PICC in state ACTIVE(*) to go to state HALT. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PICC_HaltA() { + MFRC522::StatusCode result; + byte buffer[4]; + + // Build command buffer + buffer[0] = PICC_CMD_HLTA; + buffer[1] = 0; + // Calculate CRC_A + result = PCD_CalculateCRC(buffer, 2, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + + // Send the command. + // The standard says: + // If the PICC responds with any modulation during a period of 1 ms after the end of the frame containing the + // HLTA command, this response shall be interpreted as 'not acknowledge'. + // We interpret that this way: Only STATUS_TIMEOUT is a success. + result = PCD_TransceiveData(buffer, sizeof(buffer), nullptr, 0); + if (result == STATUS_TIMEOUT) { + return STATUS_OK; + } + if (result == STATUS_OK) { // That is ironically NOT ok in this case ;-) + return STATUS_ERROR; + } + return result; +} // End PICC_HaltA() + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for communicating with MIFARE PICCs +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Executes the MFRC522 MFAuthent command. + * This command manages MIFARE authentication to enable a secure communication to any MIFARE Mini, MIFARE 1K and MIFARE 4K card. + * The authentication is described in the MFRC522 datasheet section 10.3.1.9 and http://www.nxp.com/documents/data_sheet/MF1S503x.pdf section 10.1. + * For use with MIFARE Classic PICCs. + * The PICC must be selected - ie in state ACTIVE(*) - before calling this function. + * Remember to call PCD_StopCrypto1() after communicating with the authenticated PICC - otherwise no new communications can start. + * + * All keys are set to FFFFFFFFFFFFh at chip delivery. + * + * @return STATUS_OK on success, STATUS_??? otherwise. Probably STATUS_TIMEOUT if you supply the wrong key. + */ +MFRC522::StatusCode MFRC522::PCD_Authenticate(byte command, ///< PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B + byte blockAddr, ///< The block number. See numbering in the comments in the .h file. + MIFARE_Key *key, ///< Pointer to the Crypteo1 key to use (6 bytes) + Uid *uid ///< Pointer to Uid struct. The first 4 bytes of the UID is used. + ) { + byte waitIRq = 0x10; // IdleIRq + + // Build command buffer + byte sendData[12]; + sendData[0] = command; + sendData[1] = blockAddr; + for (byte i = 0; i < MF_KEY_SIZE; i++) { // 6 key bytes + sendData[2+i] = key->keyByte[i]; + } + // Use the last uid bytes as specified in http://cache.nxp.com/documents/application_note/AN10927.pdf + // section 3.2.5 "MIFARE Classic Authentication". + // The only missed case is the MF1Sxxxx shortcut activation, + // but it requires cascade tag (CT) byte, that is not part of uid. + for (byte i = 0; i < 4; i++) { // The last 4 bytes of the UID + sendData[8+i] = uid->uidByte[i+uid->size-4]; + } + + // Start the authentication. + return PCD_CommunicateWithPICC(PCD_MFAuthent, waitIRq, &sendData[0], sizeof(sendData)); +} // End PCD_Authenticate() + +/** + * Used to exit the PCD from its authenticated state. + * Remember to call this function after communicating with an authenticated PICC - otherwise no new communications can start. + */ +void MFRC522::PCD_StopCrypto1() { + // Clear MFCrypto1On bit + PCD_ClearRegisterBitMask(Status2Reg, 0x08); // Status2Reg[7..0] bits are: TempSensClear I2CForceHS reserved reserved MFCrypto1On ModemState[2:0] +} // End PCD_StopCrypto1() + +/** + * Reads 16 bytes (+ 2 bytes CRC_A) from the active PICC. + * + * For MIFARE Classic the sector containing the block must be authenticated before calling this function. + * + * For MIFARE Ultralight only addresses 00h to 0Fh are decoded. + * The MF0ICU1 returns a NAK for higher addresses. + * The MF0ICU1 responds to the READ command by sending 16 bytes starting from the page address defined by the command argument. + * For example; if blockAddr is 03h then pages 03h, 04h, 05h, 06h are returned. + * A roll-back is implemented: If blockAddr is 0Eh, then the contents of pages 0Eh, 0Fh, 00h and 01h are returned. + * + * The buffer must be at least 18 bytes because a CRC_A is also returned. + * Checks the CRC_A before returning STATUS_OK. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_Read( byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The first page to return data from. + byte *buffer, ///< The buffer to store the data in + byte *bufferSize ///< Buffer size, at least 18 bytes. Also number of bytes returned if STATUS_OK. + ) { + MFRC522::StatusCode result; + + // Sanity check + if (buffer == nullptr || *bufferSize < 18) { + return STATUS_NO_ROOM; + } + + // Build command buffer + buffer[0] = PICC_CMD_MF_READ; + buffer[1] = blockAddr; + // Calculate CRC_A + result = PCD_CalculateCRC(buffer, 2, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + + // Transmit the buffer and receive the response, validate CRC_A. + return PCD_TransceiveData(buffer, 4, buffer, bufferSize, nullptr, 0, true); +} // End MIFARE_Read() + +/** + * Writes 16 bytes to the active PICC. + * + * For MIFARE Classic the sector containing the block must be authenticated before calling this function. + * + * For MIFARE Ultralight the operation is called "COMPATIBILITY WRITE". + * Even though 16 bytes are transferred to the Ultralight PICC, only the least significant 4 bytes (bytes 0 to 3) + * are written to the specified address. It is recommended to set the remaining bytes 04h to 0Fh to all logic 0. + * * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_Write( byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The page (2-15) to write to. + byte *buffer, ///< The 16 bytes to write to the PICC + byte bufferSize ///< Buffer size, must be at least 16 bytes. Exactly 16 bytes are written. + ) { + MFRC522::StatusCode result; + + // Sanity check + if (buffer == nullptr || bufferSize < 16) { + return STATUS_INVALID; + } + + // Mifare Classic protocol requires two communications to perform a write. + // Step 1: Tell the PICC we want to write to block blockAddr. + byte cmdBuffer[2]; + cmdBuffer[0] = PICC_CMD_MF_WRITE; + cmdBuffer[1] = blockAddr; + result = PCD_MIFARE_Transceive(cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + + // Step 2: Transfer the data + result = PCD_MIFARE_Transceive(buffer, bufferSize); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + + return STATUS_OK; +} // End MIFARE_Write() + +/** + * Writes a 4 byte page to the active MIFARE Ultralight PICC. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_Ultralight_Write( byte page, ///< The page (2-15) to write to. + byte *buffer, ///< The 4 bytes to write to the PICC + byte bufferSize ///< Buffer size, must be at least 4 bytes. Exactly 4 bytes are written. + ) { + MFRC522::StatusCode result; + + // Sanity check + if (buffer == nullptr || bufferSize < 4) { + return STATUS_INVALID; + } + + // Build commmand buffer + byte cmdBuffer[6]; + cmdBuffer[0] = PICC_CMD_UL_WRITE; + cmdBuffer[1] = page; + memcpy(&cmdBuffer[2], buffer, 4); + + // Perform the write + result = PCD_MIFARE_Transceive(cmdBuffer, 6); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + return STATUS_OK; +} // End MIFARE_Ultralight_Write() + +/** + * MIFARE Decrement subtracts the delta from the value of the addressed block, and stores the result in a volatile memory. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_Decrement( byte blockAddr, ///< The block (0-0xff) number. + int32_t delta ///< This number is subtracted from the value of block blockAddr. + ) { + return MIFARE_TwoStepHelper(PICC_CMD_MF_DECREMENT, blockAddr, delta); +} // End MIFARE_Decrement() + +/** + * MIFARE Increment adds the delta to the value of the addressed block, and stores the result in a volatile memory. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_Increment( byte blockAddr, ///< The block (0-0xff) number. + int32_t delta ///< This number is added to the value of block blockAddr. + ) { + return MIFARE_TwoStepHelper(PICC_CMD_MF_INCREMENT, blockAddr, delta); +} // End MIFARE_Increment() + +/** + * MIFARE Restore copies the value of the addressed block into a volatile memory. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_Restore( byte blockAddr ///< The block (0-0xff) number. + ) { + // The datasheet describes Restore as a two step operation, but does not explain what data to transfer in step 2. + // Doing only a single step does not work, so I chose to transfer 0L in step two. + return MIFARE_TwoStepHelper(PICC_CMD_MF_RESTORE, blockAddr, 0L); +} // End MIFARE_Restore() + +/** + * Helper function for the two-step MIFARE Classic protocol operations Decrement, Increment and Restore. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_TwoStepHelper( byte command, ///< The command to use + byte blockAddr, ///< The block (0-0xff) number. + int32_t data ///< The data to transfer in step 2 + ) { + MFRC522::StatusCode result; + byte cmdBuffer[2]; // We only need room for 2 bytes. + + // Step 1: Tell the PICC the command and block address + cmdBuffer[0] = command; + cmdBuffer[1] = blockAddr; + result = PCD_MIFARE_Transceive( cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + + // Step 2: Transfer the data + result = PCD_MIFARE_Transceive( (byte *)&data, 4, true); // Adds CRC_A and accept timeout as success. + if (result != STATUS_OK) { + return result; + } + + return STATUS_OK; +} // End MIFARE_TwoStepHelper() + +/** + * MIFARE Transfer writes the value stored in the volatile memory into one MIFARE Classic block. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_Transfer( byte blockAddr ///< The block (0-0xff) number. + ) { + MFRC522::StatusCode result; + byte cmdBuffer[2]; // We only need room for 2 bytes. + + // Tell the PICC we want to transfer the result into block blockAddr. + cmdBuffer[0] = PICC_CMD_MF_TRANSFER; + cmdBuffer[1] = blockAddr; + result = PCD_MIFARE_Transceive( cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + return STATUS_OK; +} // End MIFARE_Transfer() + +/** + * Helper routine to read the current value from a Value Block. + * + * Only for MIFARE Classic and only for blocks in "value block" mode, that + * is: with access bits [C1 C2 C3] = [110] or [001]. The sector containing + * the block must be authenticated before calling this function. + * + * @param[in] blockAddr The block (0x00-0xff) number. + * @param[out] value Current value of the Value Block. + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_GetValue(byte blockAddr, int32_t *value) { + MFRC522::StatusCode status; + byte buffer[18]; + byte size = sizeof(buffer); + + // Read the block + status = MIFARE_Read(blockAddr, buffer, &size); + if (status == STATUS_OK) { + // Extract the value + *value = (int32_t(buffer[3])<<24) | (int32_t(buffer[2])<<16) | (int32_t(buffer[1])<<8) | int32_t(buffer[0]); + } + return status; +} // End MIFARE_GetValue() + +/** + * Helper routine to write a specific value into a Value Block. + * + * Only for MIFARE Classic and only for blocks in "value block" mode, that + * is: with access bits [C1 C2 C3] = [110] or [001]. The sector containing + * the block must be authenticated before calling this function. + * + * @param[in] blockAddr The block (0x00-0xff) number. + * @param[in] value New value of the Value Block. + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::MIFARE_SetValue(byte blockAddr, int32_t value) { + byte buffer[18]; + + // Translate the int32_t into 4 bytes; repeated 2x in value block + buffer[0] = buffer[ 8] = (value & 0xFF); + buffer[1] = buffer[ 9] = (value & 0xFF00) >> 8; + buffer[2] = buffer[10] = (value & 0xFF0000) >> 16; + buffer[3] = buffer[11] = (value & 0xFF000000) >> 24; + // Inverse 4 bytes also found in value block + buffer[4] = ~buffer[0]; + buffer[5] = ~buffer[1]; + buffer[6] = ~buffer[2]; + buffer[7] = ~buffer[3]; + // Address 2x with inverse address 2x + buffer[12] = buffer[14] = blockAddr; + buffer[13] = buffer[15] = ~blockAddr; + + // Write the whole data block + return MIFARE_Write(blockAddr, buffer, 16); +} // End MIFARE_SetValue() + +/** + * Authenticate with a NTAG216. + * + * Only for NTAG216. First implemented by Gargantuanman. + * + * @param[in] passWord password. + * @param[in] pACK result success???. + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) //Authenticate with 32bit password +{ + // TODO: Fix cmdBuffer length and rxlength. They really should match. + // (Better still, rxlength should not even be necessary.) + + MFRC522::StatusCode result; + byte cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. + + cmdBuffer[0] = 0x1B; //Comando de autentificacion + + for (byte i = 0; i<4; i++) + cmdBuffer[i+1] = passWord[i]; + + result = PCD_CalculateCRC(cmdBuffer, 5, &cmdBuffer[5]); + + if (result!=STATUS_OK) { + return result; + } + + // Transceive the data, store the reply in cmdBuffer[] + byte waitIRq = 0x30; // RxIRq and IdleIRq +// byte cmdBufferSize = sizeof(cmdBuffer); + byte validBits = 0; + byte rxlength = 5; + result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, 7, cmdBuffer, &rxlength, &validBits); + + pACK[0] = cmdBuffer[0]; + pACK[1] = cmdBuffer[1]; + + if (result!=STATUS_OK) { + return result; + } + + return STATUS_OK; +} // End PCD_NTAG216_AUTH() + + +///////////////////////////////////////////////////////////////////////////////////// +// Support functions +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Wrapper for MIFARE protocol communication. + * Adds CRC_A, executes the Transceive command and checks that the response is MF_ACK or a timeout. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +MFRC522::StatusCode MFRC522::PCD_MIFARE_Transceive( byte *sendData, ///< Pointer to the data to transfer to the FIFO. Do NOT include the CRC_A. + byte sendLen, ///< Number of bytes in sendData. + bool acceptTimeout ///< True => A timeout is also success + ) { + MFRC522::StatusCode result; + byte cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. + + // Sanity check + if (sendData == nullptr || sendLen > 16) { + return STATUS_INVALID; + } + + // Copy sendData[] to cmdBuffer[] and add CRC_A + memcpy(cmdBuffer, sendData, sendLen); + result = PCD_CalculateCRC(cmdBuffer, sendLen, &cmdBuffer[sendLen]); + if (result != STATUS_OK) { + return result; + } + sendLen += 2; + + // Transceive the data, store the reply in cmdBuffer[] + byte waitIRq = 0x30; // RxIRq and IdleIRq + byte cmdBufferSize = sizeof(cmdBuffer); + byte validBits = 0; + result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, sendLen, cmdBuffer, &cmdBufferSize, &validBits); + if (acceptTimeout && result == STATUS_TIMEOUT) { + return STATUS_OK; + } + if (result != STATUS_OK) { + return result; + } + // The PICC must reply with a 4 bit ACK + if (cmdBufferSize != 1 || validBits != 4) { + return STATUS_ERROR; + } + if (cmdBuffer[0] != MF_ACK) { + return STATUS_MIFARE_NACK; + } + return STATUS_OK; +} // End PCD_MIFARE_Transceive() + + +/** + * Translates the SAK (Select Acknowledge) to a PICC type. + * + * @return PICC_Type + */ +MFRC522::PICC_Type MFRC522::PICC_GetType(byte sak ///< The SAK byte returned from PICC_Select(). + ) { + // http://www.nxp.com/documents/application_note/AN10833.pdf + // 3.2 Coding of Select Acknowledge (SAK) + // ignore 8-bit (iso14443 starts with LSBit = bit 1) + // fixes wrong type for manufacturer Infineon (http://nfc-tools.org/index.php?title=ISO14443A) + sak &= 0x7F; + switch (sak) { + case 0x04: return PICC_TYPE_NOT_COMPLETE; // UID not complete + case 0x09: return PICC_TYPE_MIFARE_MINI; + case 0x08: return PICC_TYPE_MIFARE_1K; + case 0x18: return PICC_TYPE_MIFARE_4K; + case 0x00: return PICC_TYPE_MIFARE_UL; + case 0x10: + case 0x11: return PICC_TYPE_MIFARE_PLUS; + case 0x01: return PICC_TYPE_TNP3XXX; + case 0x20: return PICC_TYPE_ISO_14443_4; + case 0x40: return PICC_TYPE_ISO_18092; + default: return PICC_TYPE_UNKNOWN; + } +} // End PICC_GetType() + + +/** + * Dumps debug info about the connected PCD to Serial. + * Shows all known firmware versions + */ +void MFRC522::PCD_DumpVersionToSerial() { + // Get the MFRC522 firmware version + byte v = PCD_ReadRegister(VersionReg); + std::ostringstream oss; + oss << "Firmware Version: 0x" << std::hex << (int)v; + + // Lookup which version + switch(v) { + case 0x88: oss << " = (clone)"; break; + case 0x90: oss << " = v0.0"; break; + case 0x91: oss << " = v1.0"; break; + case 0x92: oss << " = v2.0"; break; + default: oss << " = (unknown)"; + } + + ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); + // When 0x00 or 0xFF is returned, communication probably failed + if ((v == 0x00) || (v == 0xFF)) + ESP_LOGD(LOG_TAG, "WARNING: Communication failure, is the MFRC522 properly connected?"); +} // End PCD_DumpVersionToSerial() + +/** + * Dumps debug info about the selected PICC to Serial. + * On success the PICC is halted after dumping the data. + * For MIFARE Classic the factory default key of 0xFFFFFFFFFFFF is tried. + * + * @DEPRECATED Kept for bakward compatibility + */ +void MFRC522::PICC_DumpToSerial(Uid *uid ///< Pointer to Uid struct returned from a successful PICC_Select(). + ) { + MIFARE_Key key; + + // Dump UID, SAK and Type + PICC_DumpDetailsToSerial(uid); + + // Dump contents + PICC_Type piccType = PICC_GetType(uid->sak); + switch (piccType) { + case PICC_TYPE_MIFARE_MINI: + case PICC_TYPE_MIFARE_1K: + case PICC_TYPE_MIFARE_4K: + // All keys are set to FFFFFFFFFFFFh at chip delivery from the factory. + for (byte i = 0; i < 6; i++) { + key.keyByte[i] = 0xFF; + } + PICC_DumpMifareClassicToSerial(uid, piccType, &key); + break; + + case PICC_TYPE_MIFARE_UL: + PICC_DumpMifareUltralightToSerial(); + break; + + case PICC_TYPE_ISO_14443_4: + case PICC_TYPE_MIFARE_DESFIRE: + case PICC_TYPE_ISO_18092: + case PICC_TYPE_MIFARE_PLUS: + case PICC_TYPE_TNP3XXX: + ESP_LOGD(LOG_TAG, "Dumping memory contents not implemented for that PICC type."); + break; + + case PICC_TYPE_UNKNOWN: + case PICC_TYPE_NOT_COMPLETE: + default: + break; // No memory dump here + } + + ESP_LOGD(LOG_TAG,""); + PICC_HaltA(); // Already done if it was a MIFARE Classic PICC. +} // End PICC_DumpToSerial() + +/** + * Dumps card info (UID,SAK,Type) about the selected PICC to Serial. + * + * @DEPRECATED kept for backward compatibility + */ +void MFRC522::PICC_DumpDetailsToSerial(Uid *uid ///< Pointer to Uid struct returned from a successful PICC_Select(). + ) { + // UID + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + + oss << "Card UID: "; + for (byte i = 0; i < uid->size; i++) { + oss << std::setw(2) << (int)uid->uidByte[i]; + } + ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); + + // SAK + oss.str(""); + oss << "" << "Card SAK: " << std::setw(2) << (int)uid->sak; + ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); + + + // (suggested) PICC type + PICC_Type piccType = PICC_GetType(uid->sak); + oss.str(""); + oss << "PICC type: " << MFRC522Debug::PICC_GetTypeName(piccType); + ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); +} // End PICC_DumpDetailsToSerial() + +/** + * Dumps memory contents of a MIFARE Classic PICC. + * On success the PICC is halted after dumping the data. + */ +void MFRC522::PICC_DumpMifareClassicToSerial( Uid *uid, ///< Pointer to Uid struct returned from a successful PICC_Select(). + PICC_Type piccType, ///< One of the PICC_Type enums. + MIFARE_Key *key ///< Key A used for all sectors. + ) { + byte no_of_sectors = 0; + switch (piccType) { + case PICC_TYPE_MIFARE_MINI: + // Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes. + no_of_sectors = 5; + break; + + case PICC_TYPE_MIFARE_1K: + // Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes. + no_of_sectors = 16; + break; + + case PICC_TYPE_MIFARE_4K: + // Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes. + no_of_sectors = 40; + break; + + default: // Should not happen. Ignore. + break; + } + + // Dump sectors, highest address first. + if (no_of_sectors) { + ESP_LOGD(LOG_TAG, "Sector Block 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 AccessBits"); + for (int8_t i = no_of_sectors - 1; i >= 0; i--) { + PICC_DumpMifareClassicSectorToSerial(uid, key, i); + } + } + PICC_HaltA(); // Halt the PICC before stopping the encrypted session. + PCD_StopCrypto1(); +} // End PICC_DumpMifareClassicToSerial() + +/** + * Dumps memory contents of a sector of a MIFARE Classic PICC. + * Uses PCD_Authenticate(), MIFARE_Read() and PCD_StopCrypto1. + * Always uses PICC_CMD_MF_AUTH_KEY_A because only Key A can always read the sector trailer access bits. + */ +void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to Uid struct returned from a successful PICC_Select(). + MIFARE_Key *key, ///< Key A for the sector. + byte sector ///< The sector to dump, 0..39. + ) { + MFRC522::StatusCode status; + byte firstBlock; // Address of lowest address to dump actually last block dumped) + byte no_of_blocks; // Number of blocks in sector + bool isSectorTrailer; // Set to true while handling the "last" (ie highest address) in the sector. + + // The access bits are stored in a peculiar fashion. + // There are four groups: + // g[3] Access bits for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) + // g[2] Access bits for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) + // g[1] Access bits for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) + // g[0] Access bits for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) + // Each group has access bits [C1 C2 C3]. In this code C1 is MSB and C3 is LSB. + // The four CX bits are stored together in a nible cx and an inverted nible cx_. + byte c1, c2, c3; // Nibbles + byte c1_, c2_, c3_; // Inverted nibbles + bool invertedError; // True if one of the inverted nibbles did not match + byte g[4]; // Access bits for each of the four groups. + byte group; // 0-3 - active group for access bits + bool firstInGroup; // True for the first block dumped in the group + + // Determine position and size of sector. + if (sector < 32) { // Sectors 0..31 has 4 blocks each + no_of_blocks = 4; + firstBlock = sector * no_of_blocks; + } + else if (sector < 40) { // Sectors 32-39 has 16 blocks each + no_of_blocks = 16; + firstBlock = 128 + (sector - 32) * no_of_blocks; + } + else { // Illegal input, no MIFARE Classic PICC has more than 40 sectors. + return; + } + + // Dump blocks, highest address first. + byte byteCount; + byte buffer[18]; + byte blockAddr; + isSectorTrailer = true; + invertedError = false; // Avoid "unused variable" warning. + for (int8_t blockOffset = no_of_blocks - 1; blockOffset >= 0; blockOffset--) { + blockAddr = firstBlock + blockOffset; + // Sector number - only on first line + std::ostringstream oss; + if (isSectorTrailer) { + if(sector < 10) { + oss << " "; // Pad with spaces + } + else { + oss << " "; // Pad with spaces + } + oss << (int)sector; + + oss << " "; + } + else { + oss << " "; + } + // Block number + if(blockAddr < 10) { + oss << " "; // Pad with spaces + } + else { + if(blockAddr < 100) { + oss << " "; // Pad with spaces + } + else { + oss << " "; // Pad with spaces + } + } + oss << (int) blockAddr; + oss << " "; + // Establish encrypted communications before reading the first block + if (isSectorTrailer) { + status = PCD_Authenticate(PICC_CMD_MF_AUTH_KEY_A, firstBlock, key, uid); + if (status != STATUS_OK) { + oss << "PCD_Authenticate() failed: "; + oss << (int) MFRC522Debug::GetStatusCodeName(status); + ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); + return; + } + } + // Read block + byteCount = sizeof(buffer); + status = MIFARE_Read(blockAddr, buffer, &byteCount); + if (status != STATUS_OK) { + oss << "MIFARE_Read() failed: "; + oss << (int)MFRC522Debug::GetStatusCodeName(status); + ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); + continue; + } + // Dump data + oss << std::hex << std::setfill('0'); + for (byte index = 0; index < 16; index++) { + oss << " " << std::setw(2) << (int)buffer[index]; + + if ((index % 4) == 3) { + oss << " "; + } + } + // Parse sector trailer data + if (isSectorTrailer) { + c1 = buffer[7] >> 4; + c2 = buffer[8] & 0xF; + c3 = buffer[8] >> 4; + c1_ = buffer[6] & 0xF; + c2_ = buffer[6] >> 4; + c3_ = buffer[7] & 0xF; + invertedError = (c1 != (~c1_ & 0xF)) || (c2 != (~c2_ & 0xF)) || (c3 != (~c3_ & 0xF)); + g[0] = ((c1 & 1) << 2) | ((c2 & 1) << 1) | ((c3 & 1) << 0); + g[1] = ((c1 & 2) << 1) | ((c2 & 2) << 0) | ((c3 & 2) >> 1); + g[2] = ((c1 & 4) << 0) | ((c2 & 4) >> 1) | ((c3 & 4) >> 2); + g[3] = ((c1 & 8) >> 1) | ((c2 & 8) >> 2) | ((c3 & 8) >> 3); + isSectorTrailer = false; + } + + // Which access group is this block in? + if (no_of_blocks == 4) { + group = blockOffset; + firstInGroup = true; + } + else { + group = blockOffset / 5; + firstInGroup = (group == 3) || (group != (blockOffset + 1) / 5); + } + + if (firstInGroup) { + // Print access bits + oss << std::dec; + oss << " [ " << (int)((g[group] >> 2) & 1) << " " << (int)((g[group] >> 1) & 1) << " " << (int) ((g[group] >> 0) & 1) << " ] "; + if (invertedError) { + oss << " Inverted access bits did not match! "; + } + } + + if (group != 3 && (g[group] == 1 || g[group] == 6)) { // Not a sector trailer, a value block + int32_t value = (int32_t(buffer[3])<<24) | (int32_t(buffer[2])<<16) | (int32_t(buffer[1])<<8) | int32_t(buffer[0]); + oss << " Value=0x" << std::hex << value << " Adr=0x" << (int)buffer[12]; + } + ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); + } + + return; +} // End PICC_DumpMifareClassicSectorToSerial() + +/** + * Dumps memory contents of a MIFARE Ultralight PICC. + */ +void MFRC522::PICC_DumpMifareUltralightToSerial() { + MFRC522::StatusCode status; + byte byteCount; + byte buffer[18]; + byte i; + + std::ostringstream oss; + oss << "Page 0 1 2 3"; + // Try the mpages of the original Ultralight. Ultralight C has more pages. + for (byte page = 0; page < 16; page +=4) { // Read returns data for 4 pages at a time. + // Read pages + byteCount = sizeof(buffer); + status = MIFARE_Read(page, buffer, &byteCount); + if (status != STATUS_OK) { + oss << "MIFARE_Read() failed: "; + oss << std::dec << (int) MFRC522Debug::GetStatusCodeName(status); + ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); + break; + } + // Dump data + oss << std::dec; + for (byte offset = 0; offset < 4; offset++) { + i = page + offset; + oss << " " << std::setw(2) << (int) i << " "; + + for (byte index = 0; index < 4; index++) { + i = 4 * offset + index; + oss << std::hex << std::setw(2) << (int)buffer[i]; + } + ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); + } + } +} // End PICC_DumpMifareUltralightToSerial() + +/** + * Calculates the bit pattern needed for the specified access bits. In the [C1 C2 C3] tuples C1 is MSB (=4) and C3 is LSB (=1). + */ +void MFRC522::MIFARE_SetAccessBits( byte *accessBitBuffer, ///< Pointer to byte 6, 7 and 8 in the sector trailer. Bytes [0..2] will be set. + byte g0, ///< Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) + byte g1, ///< Access bits C1 C2 C3] for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) + byte g2, ///< Access bits C1 C2 C3] for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) + byte g3 ///< Access bits C1 C2 C3] for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) + ) { + byte c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2); + byte c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1); + byte c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0); + + accessBitBuffer[0] = (~c2 & 0xF) << 4 | (~c1 & 0xF); + accessBitBuffer[1] = c1 << 4 | (~c3 & 0xF); + accessBitBuffer[2] = c3 << 4 | c2; +} // End MIFARE_SetAccessBits() + +///////////////////////////////////////////////////////////////////////////////////// +// Convenience functions - does not add extra functionality +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns true if a PICC responds to PICC_CMD_REQA. + * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. + * + * @return bool + */ +bool MFRC522::PICC_IsNewCardPresent() { + byte bufferATQA[2]; + byte bufferSize = sizeof(bufferATQA); + + // Reset baud rates + PCD_WriteRegister(TxModeReg, 0x00); + PCD_WriteRegister(RxModeReg, 0x00); + // Reset ModWidthReg + PCD_WriteRegister(ModWidthReg, 0x26); + + MFRC522::StatusCode result = PICC_RequestA(bufferATQA, &bufferSize); + return (result == STATUS_OK || result == STATUS_COLLISION); +} // End PICC_IsNewCardPresent() + +/** + * Simple wrapper around PICC_Select. + * Returns true if a UID could be read. + * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first. + * The read UID is available in the class variable uid. + * + * @return bool + */ +bool MFRC522::PICC_ReadCardSerial() { + MFRC522::StatusCode result = PICC_Select(&uid); + return (result == STATUS_OK); +} // End diff --git a/cpp_utils/MFRC522.h b/cpp_utils/MFRC522.h new file mode 100644 index 00000000..2e060712 --- /dev/null +++ b/cpp_utils/MFRC522.h @@ -0,0 +1,418 @@ +/** + * MFRC522.h - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT. + * Based on code Dr.Leong ( WWW.B2CQSHOP.COM ) + * Created by Miguel Balboa (circuitito.com), Jan, 2012. + * Rewritten by Søren Thing Andersen (access.thing.dk), fall of 2013 (Translation to English, refactored, comments, anti collision, cascade levels.) + * Extended by Tom Clement with functionality to write to sector 0 of UID changeable Mifare cards. + * Released into the public domain. + * + * Please read this file for an overview and then MFRC522.cpp for comments on the specific functions. + * Search for "mf-rc522" on ebay.com to purchase the MF-RC522 board. + * + * There are three hardware components involved: + * 1) The micro controller: An Arduino + * 2) The PCD (short for Proximity Coupling Device): NXP MFRC522 Contactless Reader IC + * 3) The PICC (short for Proximity Integrated Circuit Card): A card or tag using the ISO 14443A interface, eg Mifare or NTAG203. + * + * The microcontroller and card reader uses SPI for communication. + * The protocol is described in the MFRC522 datasheet: http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * + * The card reader and the tags communicate using a 13.56MHz electromagnetic field. + * The protocol is defined in ISO/IEC 14443-3 Identification cards -- Contactless integrated circuit cards -- Proximity cards -- Part 3: Initialization and anticollision". + * A free version of the final draft can be found at http://wg8.de/wg8n1496_17n3613_Ballot_FCD14443-3.pdf + * Details are found in chapter 6, Type A – Initialization and anticollision. + * + * If only the PICC UID is wanted, the above documents has all the needed information. + * To read and write from MIFARE PICCs, the MIFARE protocol is used after the PICC has been selected. + * The MIFARE Classic chips and protocol is described in the datasheets: + * 1K: http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf + * 4K: http://datasheet.octopart.com/MF1S7035DA4,118-NXP-Semiconductors-datasheet-11046188.pdf + * Mini: http://www.idcardmarket.com/download/mifare_S20_datasheet.pdf + * The MIFARE Ultralight chip and protocol is described in the datasheets: + * Ultralight: http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf + * Ultralight C: http://www.nxp.com/documents/short_data_sheet/MF0ICU2_SDS.pdf + * + * MIFARE Classic 1K (MF1S503x): + * Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes. + * The blocks are numbered 0-63. + * Block 3 in each sector is the Sector Trailer. See http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf sections 8.6 and 8.7: + * Bytes 0-5: Key A + * Bytes 6-8: Access Bits + * Bytes 9: User data + * Bytes 10-15: Key B (or user data) + * Block 0 is read-only manufacturer data. + * To access a block, an authentication using a key from the block's sector must be performed first. + * Example: To read from block 10, first authenticate using a key from sector 3 (blocks 8-11). + * All keys are set to FFFFFFFFFFFFh at chip delivery. + * Warning: Please read section 8.7 "Memory Access". It includes this text: if the PICC detects a format violation the whole sector is irreversibly blocked. + * To use a block in "value block" mode (for Increment/Decrement operations) you need to change the sector trailer. Use PICC_SetAccessBits() to calculate the bit patterns. + * MIFARE Classic 4K (MF1S703x): + * Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes. + * The blocks are numbered 0-255. + * The last block in each sector is the Sector Trailer like above. + * MIFARE Classic Mini (MF1 IC S20): + * Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes. + * The blocks are numbered 0-19. + * The last block in each sector is the Sector Trailer like above. + * + * MIFARE Ultralight (MF0ICU1): + * Has 16 pages of 4 bytes = 64 bytes. + * Pages 0 + 1 is used for the 7-byte UID. + * Page 2 contains the last check digit for the UID, one byte manufacturer internal data, and the lock bytes (see http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2) + * Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0. + * Pages 4-15 are read/write unless blocked by the lock bytes in page 2. + * MIFARE Ultralight C (MF0ICU2): + * Has 48 pages of 4 bytes = 192 bytes. + * Pages 0 + 1 is used for the 7-byte UID. + * Page 2 contains the last check digit for the UID, one byte manufacturer internal data, and the lock bytes (see http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2) + * Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0. + * Pages 4-39 are read/write unless blocked by the lock bytes in page 2. + * Page 40 Lock bytes + * Page 41 16 bit one way counter + * Pages 42-43 Authentication configuration + * Pages 44-47 Authentication key + */ +#ifndef MFRC522_h +#define MFRC522_h + + + +#include +#include + +typedef uint8_t byte; + +// Firmware data for self-test +// Reference values based on firmware version +// Hint: if needed, you can remove unused self-test data to save flash memory +// +// Version 0.0 (0x90) +// Philips Semiconductors; Preliminary Specification Revision 2.0 - 01 August 2005; 16.1 self-test +const byte MFRC522_firmware_referenceV0_0[] = { + 0x00, 0x87, 0x98, 0x0f, 0x49, 0xFF, 0x07, 0x19, + 0xBF, 0x22, 0x30, 0x49, 0x59, 0x63, 0xAD, 0xCA, + 0x7F, 0xE3, 0x4E, 0x03, 0x5C, 0x4E, 0x49, 0x50, + 0x47, 0x9A, 0x37, 0x61, 0xE7, 0xE2, 0xC6, 0x2E, + 0x75, 0x5A, 0xED, 0x04, 0x3D, 0x02, 0x4B, 0x78, + 0x32, 0xFF, 0x58, 0x3B, 0x7C, 0xE9, 0x00, 0x94, + 0xB4, 0x4A, 0x59, 0x5B, 0xFD, 0xC9, 0x29, 0xDF, + 0x35, 0x96, 0x98, 0x9E, 0x4F, 0x30, 0x32, 0x8D +}; +// Version 1.0 (0x91) +// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test +const byte MFRC522_firmware_referenceV1_0[] = { + 0x00, 0xC6, 0x37, 0xD5, 0x32, 0xB7, 0x57, 0x5C, + 0xC2, 0xD8, 0x7C, 0x4D, 0xD9, 0x70, 0xC7, 0x73, + 0x10, 0xE6, 0xD2, 0xAA, 0x5E, 0xA1, 0x3E, 0x5A, + 0x14, 0xAF, 0x30, 0x61, 0xC9, 0x70, 0xDB, 0x2E, + 0x64, 0x22, 0x72, 0xB5, 0xBD, 0x65, 0xF4, 0xEC, + 0x22, 0xBC, 0xD3, 0x72, 0x35, 0xCD, 0xAA, 0x41, + 0x1F, 0xA7, 0xF3, 0x53, 0x14, 0xDE, 0x7E, 0x02, + 0xD9, 0x0F, 0xB5, 0x5E, 0x25, 0x1D, 0x29, 0x79 +}; +// Version 2.0 (0x92) +// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test +const byte MFRC522_firmware_referenceV2_0[] = { + 0x00, 0xEB, 0x66, 0xBA, 0x57, 0xBF, 0x23, 0x95, + 0xD0, 0xE3, 0x0D, 0x3D, 0x27, 0x89, 0x5C, 0xDE, + 0x9D, 0x3B, 0xA7, 0x00, 0x21, 0x5B, 0x89, 0x82, + 0x51, 0x3A, 0xEB, 0x02, 0x0C, 0xA5, 0x00, 0x49, + 0x7C, 0x84, 0x4D, 0xB3, 0xCC, 0xD2, 0x1B, 0x81, + 0x5D, 0x48, 0x76, 0xD5, 0x71, 0x61, 0x21, 0xA9, + 0x86, 0x96, 0x83, 0x38, 0xCF, 0x9D, 0x5B, 0x6D, + 0xDC, 0x15, 0xBA, 0x3E, 0x7D, 0x95, 0x3B, 0x2F +}; +// Clone +// Fudan Semiconductor FM17522 (0x88) +const byte FM17522_firmware_reference[] = { + 0x00, 0xD6, 0x78, 0x8C, 0xE2, 0xAA, 0x0C, 0x18, + 0x2A, 0xB8, 0x7A, 0x7F, 0xD3, 0x6A, 0xCF, 0x0B, + 0xB1, 0x37, 0x63, 0x4B, 0x69, 0xAE, 0x91, 0xC7, + 0xC3, 0x97, 0xAE, 0x77, 0xF4, 0x37, 0xD7, 0x9B, + 0x7C, 0xF5, 0x3C, 0x11, 0x8F, 0x15, 0xC3, 0xD7, + 0xC1, 0x5B, 0x00, 0x2A, 0xD0, 0x75, 0xDE, 0x9E, + 0x51, 0x64, 0xAB, 0x3E, 0xE9, 0x15, 0xB5, 0xAB, + 0x56, 0x9A, 0x98, 0x82, 0x26, 0xEA, 0x2A, 0x62 +}; + +class MFRC522 { +public: + // Size of the MFRC522 FIFO + static constexpr byte FIFO_SIZE = 64; // The FIFO is 64 bytes. + // Default value for unused pin + static constexpr uint8_t UNUSED_PIN = UINT8_MAX; + + // MFRC522 registers. Described in chapter 9 of the datasheet. + // When using SPI all addresses are shifted one bit left in the "SPI address byte" (section 8.1.2.3) + enum PCD_Register : byte { + // Page 0: Command and status + // 0x00 // reserved for future use + CommandReg = 0x01 << 1, // starts and stops command execution + ComIEnReg = 0x02 << 1, // enable and disable interrupt request control bits + DivIEnReg = 0x03 << 1, // enable and disable interrupt request control bits + ComIrqReg = 0x04 << 1, // interrupt request bits + DivIrqReg = 0x05 << 1, // interrupt request bits + ErrorReg = 0x06 << 1, // error bits showing the error status of the last command executed + Status1Reg = 0x07 << 1, // communication status bits + Status2Reg = 0x08 << 1, // receiver and transmitter status bits + FIFODataReg = 0x09 << 1, // input and output of 64 byte FIFO buffer + FIFOLevelReg = 0x0A << 1, // number of bytes stored in the FIFO buffer + WaterLevelReg = 0x0B << 1, // level for FIFO underflow and overflow warning + ControlReg = 0x0C << 1, // miscellaneous control registers + BitFramingReg = 0x0D << 1, // adjustments for bit-oriented frames + CollReg = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface + // 0x0F // reserved for future use + + // Page 1: Command + // 0x10 // reserved for future use + ModeReg = 0x11 << 1, // defines general modes for transmitting and receiving + TxModeReg = 0x12 << 1, // defines transmission data rate and framing + RxModeReg = 0x13 << 1, // defines reception data rate and framing + TxControlReg = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2 + TxASKReg = 0x15 << 1, // controls the setting of the transmission modulation + TxSelReg = 0x16 << 1, // selects the internal sources for the antenna driver + RxSelReg = 0x17 << 1, // selects internal receiver settings + RxThresholdReg = 0x18 << 1, // selects thresholds for the bit decoder + DemodReg = 0x19 << 1, // defines demodulator settings + // 0x1A // reserved for future use + // 0x1B // reserved for future use + MfTxReg = 0x1C << 1, // controls some MIFARE communication transmit parameters + MfRxReg = 0x1D << 1, // controls some MIFARE communication receive parameters + // 0x1E // reserved for future use + SerialSpeedReg = 0x1F << 1, // selects the speed of the serial UART interface + + // Page 2: Configuration + // 0x20 // reserved for future use + CRCResultRegH = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation + CRCResultRegL = 0x22 << 1, + // 0x23 // reserved for future use + ModWidthReg = 0x24 << 1, // controls the ModWidth setting? + // 0x25 // reserved for future use + RFCfgReg = 0x26 << 1, // configures the receiver gain + GsNReg = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation + CWGsPReg = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation + ModGsPReg = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation + TModeReg = 0x2A << 1, // defines settings for the internal timer + TPrescalerReg = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. + TReloadRegH = 0x2C << 1, // defines the 16-bit timer reload value + TReloadRegL = 0x2D << 1, + TCounterValueRegH = 0x2E << 1, // shows the 16-bit timer value + TCounterValueRegL = 0x2F << 1, + + // Page 3: Test Registers + // 0x30 // reserved for future use + TestSel1Reg = 0x31 << 1, // general test signal configuration + TestSel2Reg = 0x32 << 1, // general test signal configuration + TestPinEnReg = 0x33 << 1, // enables pin output driver on pins D1 to D7 + TestPinValueReg = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus + TestBusReg = 0x35 << 1, // shows the status of the internal test bus + AutoTestReg = 0x36 << 1, // controls the digital self-test + VersionReg = 0x37 << 1, // shows the software version + AnalogTestReg = 0x38 << 1, // controls the pins AUX1 and AUX2 + TestDAC1Reg = 0x39 << 1, // defines the test value for TestDAC1 + TestDAC2Reg = 0x3A << 1, // defines the test value for TestDAC2 + TestADCReg = 0x3B << 1 // shows the value of ADC I and Q channels + // 0x3C // reserved for production tests + // 0x3D // reserved for production tests + // 0x3E // reserved for production tests + // 0x3F // reserved for production tests + }; + + // MFRC522 commands. Described in chapter 10 of the datasheet. + enum PCD_Command : byte { + PCD_Idle = 0x00, // no action, cancels current command execution + PCD_Mem = 0x01, // stores 25 bytes into the internal buffer + PCD_GenerateRandomID = 0x02, // generates a 10-byte random ID number + PCD_CalcCRC = 0x03, // activates the CRC coprocessor or performs a self-test + PCD_Transmit = 0x04, // transmits data from the FIFO buffer + PCD_NoCmdChange = 0x07, // no command change, can be used to modify the CommandReg register bits without affecting the command, for example, the PowerDown bit + PCD_Receive = 0x08, // activates the receiver circuits + PCD_Transceive = 0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission + PCD_MFAuthent = 0x0E, // performs the MIFARE standard authentication as a reader + PCD_SoftReset = 0x0F // resets the MFRC522 + }; + + // MFRC522 RxGain[2:0] masks, defines the receiver's signal voltage gain factor (on the PCD). + // Described in 9.3.3.6 / table 98 of the datasheet at http://www.nxp.com/documents/data_sheet/MFRC522.pdf + enum PCD_RxGain : byte { + RxGain_18dB = 0x00 << 4, // 000b - 18 dB, minimum + RxGain_23dB = 0x01 << 4, // 001b - 23 dB + RxGain_18dB_2 = 0x02 << 4, // 010b - 18 dB, it seems 010b is a duplicate for 000b + RxGain_23dB_2 = 0x03 << 4, // 011b - 23 dB, it seems 011b is a duplicate for 001b + RxGain_33dB = 0x04 << 4, // 100b - 33 dB, average, and typical default + RxGain_38dB = 0x05 << 4, // 101b - 38 dB + RxGain_43dB = 0x06 << 4, // 110b - 43 dB + RxGain_48dB = 0x07 << 4, // 111b - 48 dB, maximum + RxGain_min = 0x00 << 4, // 000b - 18 dB, minimum, convenience for RxGain_18dB + RxGain_avg = 0x04 << 4, // 100b - 33 dB, average, convenience for RxGain_33dB + RxGain_max = 0x07 << 4 // 111b - 48 dB, maximum, convenience for RxGain_48dB + }; + + // Commands sent to the PICC. + enum PICC_Command : byte { + // The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) + PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. + PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. + PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision. + PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 + PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 + PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 + PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. + PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset. + // The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) + // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on the sector. + // The read/write commands can also be used for MIFARE Ultralight. + PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A + PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B + PICC_CMD_MF_READ = 0x30, // Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. + PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight. + PICC_CMD_MF_DECREMENT = 0xC0, // Decrements the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_INCREMENT = 0xC1, // Increments the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register. + PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block. + // The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) + // The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight. + PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 byte page to the PICC. + }; + + // MIFARE constants that does not fit anywhere else + enum MIFARE_Misc { + MF_ACK = 0xA, // The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK. + MF_KEY_SIZE = 6 // A Mifare Crypto1 key is 6 bytes. + }; + + // PICC types we can detect. Remember to update PICC_GetTypeName() if you add more. + // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered + enum PICC_Type : byte { + PICC_TYPE_UNKNOWN , + PICC_TYPE_ISO_14443_4 , // PICC compliant with ISO/IEC 14443-4 + PICC_TYPE_ISO_18092 , // PICC compliant with ISO/IEC 18092 (NFC) + PICC_TYPE_MIFARE_MINI , // MIFARE Classic protocol, 320 bytes + PICC_TYPE_MIFARE_1K , // MIFARE Classic protocol, 1KB + PICC_TYPE_MIFARE_4K , // MIFARE Classic protocol, 4KB + PICC_TYPE_MIFARE_UL , // MIFARE Ultralight or Ultralight C + PICC_TYPE_MIFARE_PLUS , // MIFARE Plus + PICC_TYPE_MIFARE_DESFIRE, // MIFARE DESFire + PICC_TYPE_TNP3XXX , // Only mentioned in NXP AN 10833 MIFARE Type Identification Procedure + PICC_TYPE_NOT_COMPLETE = 0xff // SAK indicates UID is not complete. + }; + + // Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. + // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered + enum StatusCode : byte { + STATUS_OK , // Success + STATUS_ERROR , // Error in communication + STATUS_COLLISION , // Collission detected + STATUS_TIMEOUT , // Timeout in communication. + STATUS_NO_ROOM , // A buffer is not big enough. + STATUS_INTERNAL_ERROR , // Internal error in the code. Should not happen ;-) + STATUS_INVALID , // Invalid argument. + STATUS_CRC_WRONG , // The CRC_A does not match + STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK. + }; + + // A struct used for passing the UID of a PICC. + typedef struct { + byte size; // Number of bytes in the UID. 4, 7 or 10. + byte uidByte[10]; + byte sak; // The SAK (Select acknowledge) byte returned from the PICC after successful selection. + } Uid; + + // A struct used for passing a MIFARE Crypto1 key + typedef struct { + byte keyByte[MF_KEY_SIZE]; + } MIFARE_Key; + + // Member variables + Uid uid; // Used by PICC_ReadCardSerial(). + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for setting up the Arduino + ///////////////////////////////////////////////////////////////////////////////////// + //MFRC522(); + + ///////////////////////////////////////////////////////////////////////////////////// + // Basic interface functions for communicating with the MFRC522 + ///////////////////////////////////////////////////////////////////////////////////// + void PCD_WriteRegister(PCD_Register reg, byte value); + void PCD_WriteRegister(PCD_Register reg, byte count, byte *values); + byte PCD_ReadRegister(PCD_Register reg); + void PCD_ReadRegister(PCD_Register reg, byte count, byte *values, byte rxAlign = 0); + void PCD_SetRegisterBitMask(PCD_Register reg, byte mask); + void PCD_ClearRegisterBitMask(PCD_Register reg, byte mask); + StatusCode PCD_CalculateCRC(byte *data, byte length, byte *result); + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for manipulating the MFRC522 + ///////////////////////////////////////////////////////////////////////////////////// + void PCD_Init(); + void PCD_Init(byte chipSelectPin, byte resetPowerDownPin); + void PCD_Reset(); + void PCD_AntennaOn(); + void PCD_AntennaOff(); + byte PCD_GetAntennaGain(); + void PCD_SetAntennaGain(byte mask); + bool PCD_PerformSelfTest(); + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for communicating with PICCs + ///////////////////////////////////////////////////////////////////////////////////// + StatusCode PCD_TransceiveData(byte *sendData, byte sendLen, byte *backData, byte *backLen, byte *validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); + StatusCode PCD_CommunicateWithPICC(byte command, byte waitIRq, byte *sendData, byte sendLen, byte *backData = nullptr, byte *backLen = nullptr, byte *validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); + StatusCode PICC_RequestA(byte *bufferATQA, byte *bufferSize); + StatusCode PICC_WakeupA(byte *bufferATQA, byte *bufferSize); + StatusCode PICC_REQA_or_WUPA(byte command, byte *bufferATQA, byte *bufferSize); + virtual StatusCode PICC_Select(Uid *uid, byte validBits = 0); + StatusCode PICC_HaltA(); + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for communicating with MIFARE PICCs + ///////////////////////////////////////////////////////////////////////////////////// + StatusCode PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key *key, Uid *uid); + void PCD_StopCrypto1(); + StatusCode MIFARE_Read(byte blockAddr, byte *buffer, byte *bufferSize); + StatusCode MIFARE_Write(byte blockAddr, byte *buffer, byte bufferSize); + StatusCode MIFARE_Ultralight_Write(byte page, byte *buffer, byte bufferSize); + StatusCode MIFARE_Decrement(byte blockAddr, int32_t delta); + StatusCode MIFARE_Increment(byte blockAddr, int32_t delta); + StatusCode MIFARE_Restore(byte blockAddr); + StatusCode MIFARE_Transfer(byte blockAddr); + StatusCode MIFARE_GetValue(byte blockAddr, int32_t *value); + StatusCode MIFARE_SetValue(byte blockAddr, int32_t value); + StatusCode PCD_NTAG216_AUTH(byte *passWord, byte pACK[]); + + ///////////////////////////////////////////////////////////////////////////////////// + // Support functions + ///////////////////////////////////////////////////////////////////////////////////// + StatusCode PCD_MIFARE_Transceive(byte *sendData, byte sendLen, bool acceptTimeout = false); + static PICC_Type PICC_GetType(byte sak); + + // Support functions for debuging + void PCD_DumpVersionToSerial(); + void PICC_DumpToSerial(Uid *uid); + void PICC_DumpDetailsToSerial(Uid *uid); + void PICC_DumpMifareClassicToSerial(Uid *uid, PICC_Type piccType, MIFARE_Key *key); + void PICC_DumpMifareClassicSectorToSerial(Uid *uid, MIFARE_Key *key, byte sector); + void PICC_DumpMifareUltralightToSerial(); + + // Advanced functions for MIFARE + void MIFARE_SetAccessBits(byte *accessBitBuffer, byte g0, byte g1, byte g2, byte g3); + + ///////////////////////////////////////////////////////////////////////////////////// + // Convenience functions - does not add extra functionality + ///////////////////////////////////////////////////////////////////////////////////// + virtual bool PICC_IsNewCardPresent(); + virtual bool PICC_ReadCardSerial(); + +protected: + // Pins + byte _chipSelectPin; // Arduino pin connected to MFRC522's SPI slave select input (Pin 24, NSS, active low) + byte _resetPowerDownPin; // Arduino pin connected to MFRC522's reset and power down input (Pin 6, NRSTPD, active low) + + + // Functions for communicating with MIFARE PICCs + StatusCode MIFARE_TwoStepHelper(byte command, byte blockAddr, int32_t data); + SPI m_spi; +}; + +#endif diff --git a/cpp_utils/MFRC522Debug.h b/cpp_utils/MFRC522Debug.h new file mode 100644 index 00000000..6c1c9ac6 --- /dev/null +++ b/cpp_utils/MFRC522Debug.h @@ -0,0 +1,14 @@ +#include "MFRC522.h" + +#ifndef MFRC522Debug_h +#define MFRC522Debug_h + +class MFRC522Debug { +private: + +public: + // Get human readable code and type + static const char* PICC_GetTypeName(MFRC522::PICC_Type type); + static const char* GetStatusCodeName(MFRC522::StatusCode code); +}; +#endif // MFRC522Debug_h diff --git a/cpp_utils/MRFC522Debug.cpp b/cpp_utils/MRFC522Debug.cpp new file mode 100644 index 00000000..82cd031d --- /dev/null +++ b/cpp_utils/MRFC522Debug.cpp @@ -0,0 +1,46 @@ + +#include "MFRC522Debug.h" + +/** + * Returns a __FlashStringHelper pointer to the PICC type name. + * + * @return const __FlashStringHelper * + */ +const char* MFRC522Debug::PICC_GetTypeName(MFRC522::PICC_Type piccType ///< One of the PICC_Type enums. +) { + switch (piccType) { + case MFRC522::PICC_TYPE_ISO_14443_4: return "PICC compliant with ISO/IEC 14443-4"; + case MFRC522::PICC_TYPE_ISO_18092: return "PICC compliant with ISO/IEC 18092 (NFC)"; + case MFRC522::PICC_TYPE_MIFARE_MINI: return "MIFARE Mini, 320 bytes"; + case MFRC522::PICC_TYPE_MIFARE_1K: return "MIFARE 1KB"; + case MFRC522::PICC_TYPE_MIFARE_4K: return "MIFARE 4KB"; + case MFRC522::PICC_TYPE_MIFARE_UL: return "MIFARE Ultralight or Ultralight C"; + case MFRC522::PICC_TYPE_MIFARE_PLUS: return "MIFARE Plus"; + case MFRC522::PICC_TYPE_MIFARE_DESFIRE: return "MIFARE DESFire"; + case MFRC522::PICC_TYPE_TNP3XXX: return "MIFARE TNP3XXX"; + case MFRC522::PICC_TYPE_NOT_COMPLETE: return "SAK indicates UID is not complete."; + case MFRC522::PICC_TYPE_UNKNOWN: + default: return "Unknown type"; + } +} // End PICC_GetTypeName() + +/** + * Returns a __FlashStringHelper pointer to a status code name. + * + * @return const __FlashStringHelper * + */ +const char *MFRC522Debug::GetStatusCodeName(MFRC522::StatusCode code ///< One of the StatusCode enums. +) { + switch (code) { + case MFRC522::STATUS_OK: return "Success."; + case MFRC522::STATUS_ERROR: return "Error in communication."; + case MFRC522::STATUS_COLLISION: return "Collission detected."; + case MFRC522::STATUS_TIMEOUT: return "Timeout in communication."; + case MFRC522::STATUS_NO_ROOM: return "A buffer is not big enough."; + case MFRC522::STATUS_INTERNAL_ERROR: return "Internal error in the code. Should not happen."; + case MFRC522::STATUS_INVALID: return "Invalid argument."; + case MFRC522::STATUS_CRC_WRONG: return "The CRC_A does not match."; + case MFRC522::STATUS_MIFARE_NACK: return "A MIFARE PICC responded with NAK."; + default: return "Unknown error"; + } +} // End GetStatusCodeName() diff --git a/cpp_utils/OV7670.cpp b/cpp_utils/OV7670.cpp index d09d431d..c5047ce3 100644 --- a/cpp_utils/OV7670.cpp +++ b/cpp_utils/OV7670.cpp @@ -113,7 +113,7 @@ static esp_err_t camera_enable_out_clock(camera_config_t* config) return ESP_OK; } // camera_enable_out_clock - +/* static void i2s_init() { // Enable and configure I2S peripheral periph_module_enable(PERIPH_I2S0_MODULE); @@ -156,6 +156,7 @@ static void i2s_init() { I2S0.timing.val = 0; } +*/ /** * @brief Dump the settings. diff --git a/cpp_utils/README.md b/cpp_utils/README.md index 7196997c..d5050634 100644 --- a/cpp_utils/README.md +++ b/cpp_utils/README.md @@ -14,6 +14,24 @@ steps: The C++ classes will be compiled and available to be used in your own code. +# Adding a main function +When working with C++, your calling function should also be written in C++. Consider replacing your `main.c` with the following +`main.cpp` file: + +``` +extern "C" { + void app_main(); +} + +void app_main() { + // Your code goes here +} +``` + +The way to read the above is that we are defining a global function called `app_main` but we are saying that its external +linkage (i.e. how it is found and called) is using the C language convention. However, since the source file is `main.cpp` and +hence compiled by the C++ compiler, you can utilize C++ classes and language features within and, since it has C linkage, it will +satisfy the ESP-IDF environment as the entry point into your own code. ## BLE Functions The Bluetooth BLE functions are only compiled if Bluetooth is enabled in `make menuconfig`. This is primarily because diff --git a/cpp_utils/SPI.cpp b/cpp_utils/SPI.cpp index 78a576b3..3b644002 100644 --- a/cpp_utils/SPI.cpp +++ b/cpp_utils/SPI.cpp @@ -19,7 +19,8 @@ static char tag[] = "SPI"; * @return N/A. */ SPI::SPI() { - handle = nullptr; + m_handle = nullptr; + m_host = HSPI_HOST; } /** @@ -27,10 +28,10 @@ SPI::SPI() { */ SPI::~SPI() { ESP_LOGI(tag, "... Removing device."); - ESP_ERROR_CHECK(spi_bus_remove_device(handle)); + ESP_ERROR_CHECK(::spi_bus_remove_device(m_handle)); ESP_LOGI(tag, "... Freeing bus."); - ESP_ERROR_CHECK(spi_bus_free(HSPI_HOST)); + ESP_ERROR_CHECK(::spi_bus_free(m_host)); } /** @@ -44,14 +45,27 @@ SPI::~SPI() { */ void SPI::init(int mosiPin, int misoPin, int clkPin, int csPin) { ESP_LOGD(tag, "init: mosi=%d, miso=%d, clk=%d, cs=%d", mosiPin, misoPin, clkPin, csPin); + spi_bus_config_t bus_config; - bus_config.sclk_io_num = clkPin; // CLK - bus_config.mosi_io_num = mosiPin; // MOSI - bus_config.miso_io_num = misoPin; // MISO - bus_config.quadwp_io_num = -1; // Not used - bus_config.quadhd_io_num = -1; // Not used - ESP_LOGI(tag, "... Initializing bus."); - ESP_ERROR_CHECK(spi_bus_initialize(HSPI_HOST, &bus_config, 1)); + bus_config.sclk_io_num = clkPin; // CLK + bus_config.mosi_io_num = mosiPin; // MOSI + bus_config.miso_io_num = misoPin; // MISO + bus_config.quadwp_io_num = -1; // Not used + bus_config.quadhd_io_num = -1; // Not used + bus_config.max_transfer_sz = 0; // 0 means use default. + + ESP_LOGI(tag, "... Initializing bus; host=%d", m_host); + + esp_err_t errRc = ::spi_bus_initialize( + m_host, + &bus_config, + 1 // DMA Channel + ); + + if (errRc != ESP_OK) { + ESP_LOGE(tag, "spi_bus_initialize(): rc=%d", errRc); + abort(); + } spi_device_interface_config_t dev_config; dev_config.address_bits = 0; @@ -68,17 +82,30 @@ void SPI::init(int mosiPin, int misoPin, int clkPin, int csPin) { dev_config.pre_cb = NULL; dev_config.post_cb = NULL; ESP_LOGI(tag, "... Adding device bus."); - ESP_ERROR_CHECK(spi_bus_add_device(HSPI_HOST, &dev_config, &handle)); -} + errRc = ::spi_bus_add_device(m_host, &dev_config, &m_handle); + if (errRc != ESP_OK) { + ESP_LOGE(tag, "spi_bus_add_device(): rc=%d", errRc); + abort(); + } +} // init + +/** + * @brief Set the SPI host to use. + * Call this prior to init(). + * @param [in] host The SPI host to use. Either HSPI_HOST (default) or VSPI_HOST. + */ +void SPI::setHost(spi_host_device_t host) { + m_host = host; +} // setHost /** - * @brief send and receive data through %SPI. + * @brief Send and receive data through %SPI. This is a blocking call. * * @param [in] data A data buffer used to send and receive. * @param [in] dataLen The number of bytes to transmit and receive. */ -void SPI::transfer(uint8_t *data, size_t dataLen) { +void SPI::transfer(uint8_t* data, size_t dataLen) { assert(data != nullptr); assert(dataLen > 0); #ifdef DEBUG @@ -96,8 +123,21 @@ void SPI::transfer(uint8_t *data, size_t dataLen) { trans_desc.rx_buffer = data; //ESP_LOGI(tag, "... Transferring"); - esp_err_t rc = spi_device_transmit(handle, &trans_desc); + esp_err_t rc = ::spi_device_transmit(m_handle, &trans_desc); if (rc != ESP_OK) { ESP_LOGE(tag, "transfer:spi_device_transmit: %d", rc); } } // transmit + + +/** + * @brief Send and receive a single byte. + * @param [in] value The byte to send. + * @return The byte value received. + */ +uint8_t SPI::transferByte(uint8_t value) { + transfer(&value, 1); + return value; +} // transferByte + + diff --git a/cpp_utils/SPI.h b/cpp_utils/SPI.h index 667d3330..d2de5d99 100644 --- a/cpp_utils/SPI.h +++ b/cpp_utils/SPI.h @@ -16,8 +16,14 @@ class SPI { public: SPI(); virtual ~SPI(); - void init(int mosiPin=DEFAULT_MOSI_PIN, int misoPin=DEFAULT_MISO_PIN, int clkPin=DEFAULT_CLK_PIN, int csPin=DEFAULT_CS_PIN); - void transfer(uint8_t *data, size_t dataLen); + void init( + int mosiPin = DEFAULT_MOSI_PIN, + int misoPin = DEFAULT_MISO_PIN, + int clkPin = DEFAULT_CLK_PIN, + int csPin = DEFAULT_CS_PIN); + void setHost(spi_host_device_t host); + void transfer(uint8_t *data, size_t dataLen); + uint8_t transferByte(uint8_t value); /** * @brief The default MOSI pin. */ @@ -44,7 +50,8 @@ class SPI { static const int PIN_NOT_SET = -1; private: - spi_device_handle_t handle; + spi_device_handle_t m_handle; + spi_host_device_t m_host; }; diff --git a/cpp_utils/tests/MFRC522/DumpInfo.cpp b/cpp_utils/tests/MFRC522/DumpInfo.cpp new file mode 100644 index 00000000..74421e05 --- /dev/null +++ b/cpp_utils/tests/MFRC522/DumpInfo.cpp @@ -0,0 +1,50 @@ +/* + * -------------------------------------------------------------------------------------------------------------------- + * Example sketch/program showing how to read data from a PICC to serial. + * -------------------------------------------------------------------------------------------------------------------- + * This is a MFRC522 library example; for further details and other examples see: https://github.com/miguelbalboa/rfid + * + * Example sketch/program showing how to read data from a PICC (that is: a RFID Tag or Card) using a MFRC522 based RFID + * Reader on the Arduino SPI interface. + * + * When the Arduino and the MFRC522 module are connected (see the pin layout below), load this sketch into Arduino IDE + * then verify/compile and upload it. To see the output: use Tools, Serial Monitor of the IDE (hit Ctrl+Shft+M). When + * you present a PICC (that is: a RFID Tag or Card) at reading distance of the MFRC522 Reader/PCD, the serial output + * will show the ID/UID, type and any data blocks it can read. Note: you may see "Timeout in communication" messages + * when removing the PICC from reading distance too early. + * + * If your reader supports it, this sketch/program will read all the PICCs presented (that is: multiple tag reading). + * So if you stack two or more PICCs on top of each other and present them to the reader, it will first output all + * details of the first and then the next PICC. Note that this may take some time as all data blocks are dumped, so + * keep the PICCs at reading distance until complete. + * + * @license Released into the public domain. + * + */ + +#include "MFRC522.h" +#include +static const char LOG_TAG[] = "DumpInfo"; + +MFRC522 mfrc522; // Create MFRC522 instance + + +void dumpInfo() { + mfrc522.PCD_Init(); // Init MFRC522 + mfrc522.PCD_DumpVersionToSerial(); // Show details of PCD - MFRC522 Card Reader details + ESP_LOGD(LOG_TAG, "Scan PICC to see UID, SAK, type, and data blocks..."); + while(1) { + // Look for new cards + if ( ! mfrc522.PICC_IsNewCardPresent()) { + continue; + } + + // Select one of the cards + if ( ! mfrc522.PICC_ReadCardSerial()) { + continue; + } + + // Dump debug info about the card; PICC_HaltA() is automatically called + mfrc522.PICC_DumpToSerial(&(mfrc522.uid)); + } +} diff --git a/cpp_utils/tests/MFRC522/main.cpp b/cpp_utils/tests/MFRC522/main.cpp new file mode 100644 index 00000000..bfe00f9a --- /dev/null +++ b/cpp_utils/tests/MFRC522/main.cpp @@ -0,0 +1,38 @@ +#include +#include +extern void dumpInfo(); + + +extern "C" { + void app_main(); +} + +class MyTask: public Task { + void run(void* data) { + int count = 0; + /* + while(1) { + printf("count: %d\n", count); + count++; + vTaskDelay(1000/portTICK_PERIOD_MS); + } + */ + /* + char* x = "hello"; + uint32_t i = 0; + printf("Hello world!"); + printf("Hello world! again!"); + char* p = (char*) i; + *p = 123; + *p */ + dumpInfo(); + printf("Done\n"); + } +}; + +void app_main() { + MyTask* pMyTask = new MyTask(); + pMyTask->setStackSize(20000); + pMyTask->start(); + +} From ca5f25551a9e87c5e5e6d1a95f814ae398cd78ec Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 16 Jul 2017 09:13:20 -0500 Subject: [PATCH 006/381] Build breaking typo fix. --- cpp_utils/BLEDescriptorMap.h | 2 -- cpp_utils/tests/MFRC522/DumpInfo.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cpp_utils/BLEDescriptorMap.h b/cpp_utils/BLEDescriptorMap.h index bce707e6..09e9a88d 100644 --- a/cpp_utils/BLEDescriptorMap.h +++ b/cpp_utils/BLEDescriptorMap.h @@ -17,8 +17,6 @@ class BLEDescriptor; class BLEDescriptorMap { public: - BLEDescriptorMap(); - virtual ~BLEDescriptorMap(); void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor); void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor); BLEDescriptor *getByUUID(BLEUUID uuid); diff --git a/cpp_utils/tests/MFRC522/DumpInfo.cpp b/cpp_utils/tests/MFRC522/DumpInfo.cpp index 74421e05..87aca27e 100644 --- a/cpp_utils/tests/MFRC522/DumpInfo.cpp +++ b/cpp_utils/tests/MFRC522/DumpInfo.cpp @@ -22,7 +22,7 @@ * */ -#include "MFRC522.h" +#include #include static const char LOG_TAG[] = "DumpInfo"; From 5cc98cf7d19e542160382cbcd04ce6921b7d7c65 Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 16 Jul 2017 11:57:30 -0500 Subject: [PATCH 007/381] Sync 2017-07-16 --- cpp_utils/BLEAdvertising.cpp | 28 ++++++++------ cpp_utils/BLEAdvertising.h | 2 +- cpp_utils/BLECharacteristic.cpp | 27 ++++++++++--- cpp_utils/BLECharacteristic.h | 2 + cpp_utils/BLERemoteCharacteristic.cpp | 2 +- cpp_utils/BLEServer.cpp | 26 +++++++------ cpp_utils/BLEServer.h | 3 +- cpp_utils/BLEService.cpp | 44 +++++++++++++++++----- cpp_utils/BLEService.h | 5 ++- cpp_utils/BLEUUID.cpp | 23 ++++------- cpp_utils/BLEUtils.cpp | 5 +++ cpp_utils/FreeRTOS.cpp | 22 +++++++++-- cpp_utils/FreeRTOS.h | 1 + cpp_utils/tests/BLE Tests/SampleServer.cpp | 2 +- 14 files changed, 130 insertions(+), 62 deletions(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index f6abf822..499ea249 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -58,31 +58,35 @@ void BLEAdvertising::setAppearance(uint16_t appearance) { /** * @brief Set the service UUID. + * We maintain a class member called m_advData (esp_ble_adv_data_t) that is passed to the + * ESP-IDF advertising functions. In this method, we see two fields within that structure + * namely service_uuid_len and p_service_uuid to be the information supplied in the passed + * in service uuid. * @param [in] uuid The UUID of the service. * @return N/A. */ -void BLEAdvertising::setServiceUUID(BLEUUID uuid) { - ESP_LOGD(LOG_TAG, ">> setServiceUUID(%s)", uuid.toString().c_str()); - m_serviceUUID = uuid; // Save the new service UUID - esp_bt_uuid_t espUUID = *m_serviceUUID.getNative(); - switch(espUUID.len) { +void BLEAdvertising::setServiceUUID(BLEUUID serviceUUID) { + ESP_LOGD(LOG_TAG, ">> setServiceUUID - %s", serviceUUID.toString().c_str()); + m_serviceUUID = serviceUUID; // Save the new service UUID + esp_bt_uuid_t* espUUID = m_serviceUUID.getNative(); + switch(espUUID->len) { case ESP_UUID_LEN_16: { m_advData.service_uuid_len = 2; - m_advData.p_service_uuid = reinterpret_cast(&espUUID.uuid.uuid16); + m_advData.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid16); break; } case ESP_UUID_LEN_32: { m_advData.service_uuid_len = 4; - m_advData.p_service_uuid = reinterpret_cast(&espUUID.uuid.uuid32); + m_advData.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid32); break; } case ESP_UUID_LEN_128: { m_advData.service_uuid_len = 16; - m_advData.p_service_uuid = reinterpret_cast(&espUUID.uuid.uuid128); + m_advData.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid128); break; } } // switch - ESP_LOGD(LOG_TAG, "<< setServiceUUID()"); + ESP_LOGD(LOG_TAG, "<< setServiceUUID"); } // setServiceUUID @@ -92,7 +96,7 @@ void BLEAdvertising::setServiceUUID(BLEUUID uuid) { * @return N/A. */ void BLEAdvertising::start() { - ESP_LOGD(LOG_TAG, ">> start()"); + ESP_LOGD(LOG_TAG, ">> start"); if (m_advData.service_uuid_len > 0) { uint8_t hexData[16*2+1]; @@ -118,7 +122,7 @@ void BLEAdvertising::start() { ESP_LOGE(LOG_TAG, "<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } - ESP_LOGD(LOG_TAG, "<< start();") + ESP_LOGD(LOG_TAG, "<< start") } // start @@ -128,10 +132,12 @@ void BLEAdvertising::start() { * @return N/A. */ void BLEAdvertising::stop() { + ESP_LOGD(LOG_TAG, ">> stop"); esp_err_t errRc = ::esp_ble_gap_stop_advertising(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + ESP_LOGD(LOG_TAG, "<< stop"); } // stop #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 872d38e0..42f3a15a 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -17,7 +17,7 @@ class BLEAdvertising { void start(); void stop(); void setAppearance(uint16_t appearance); - void setServiceUUID(BLEUUID uuid); + void setServiceUUID(BLEUUID serviceUUID); private: esp_ble_adv_data_t m_advData; esp_ble_adv_params_t m_advParams; diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 0c523c57..0553b998 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -87,19 +87,22 @@ void BLECharacteristic::executeCreate(BLEService* pService) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; + m_semaphoreCreateEvt.take("executeCreate"); esp_err_t errRc = ::esp_ble_gatts_add_char( m_pService->getHandle(), getUUID().getNative(), static_cast(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), getProperties(), &m_value, - &control); // Whether to autorespond or not. + &control); // Whether to auto respond or not. if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_add_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + m_semaphoreCreateEvt.wait("executeCreate"); + // Now that we have registered the characteristic, we must also register all the descriptors associated with this // characteristic. We iterate through each of those and invoke the registration call to register them with the // ESP environment. @@ -111,7 +114,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { pDescriptor = m_descriptorMap.getNext(); } // End while - ESP_LOGD(LOG_TAG, "<< executeCreate()"); + ESP_LOGD(LOG_TAG, "<< executeCreate"); } // executeCreate @@ -168,6 +171,19 @@ void BLECharacteristic::handleGATTServerEvent( esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { switch(event) { + // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. + // add_char: + // - esp_gatt_status_t status + // - uint16_t attr_handle + // - uint16_t service_handle + // - esp_bt_uuid_t char_uuid + case ESP_GATTS_ADD_CHAR_EVT: { + if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) && + getService()->getHandle()==param->add_char.service_handle) { + m_semaphoreCreateEvt.give(); + } + break; + } // ESP_GATTS_ADD_CHAR_EVT // ESP_GATTS_WRITE_EVT - A request to write the value of a characteristic has arrived. // @@ -357,9 +373,9 @@ void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) { void BLECharacteristic::setHandle(uint16_t handle) { - ESP_LOGD(LOG_TAG, ">> setHandle(0x%.2x): Setting handle to be 0x%.2x", handle, handle); + ESP_LOGD(LOG_TAG, ">> setHandle: handle=0x%.2x, characteristic uuid=%s", handle, getUUID().toString().c_str()); m_handle = handle; - ESP_LOGD(LOG_TAG, "<< setHandle()"); + ESP_LOGD(LOG_TAG, "<< setHandle"); } // setHandle @@ -400,7 +416,7 @@ void BLECharacteristic::setReadProperty(bool value) { */ void BLECharacteristic::setValue(uint8_t* data, size_t length) { char *pHex = BLEUtils::buildHexData(nullptr, data, length); - ESP_LOGD(LOG_TAG, ">> setValue(length: %d, %s)", length, pHex); + ESP_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s", length, pHex, getUUID().toString().c_str()); free(pHex); if (length > ESP_GATT_MAX_ATTR_LEN) { ESP_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, ESP_GATT_MAX_ATTR_LEN); @@ -408,6 +424,7 @@ void BLECharacteristic::setValue(uint8_t* data, size_t length) { } m_value.attr_len = length; memcpy(m_value.attr_value, data, length); + ESP_LOGD(LOG_TAG, "<< setValue"); } // setValue diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 491e3f59..c37d50b3 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -15,6 +15,7 @@ #include "BLEDescriptor.h" #include "BLEDescriptorMap.h" #include "BLECharacteristicCallbacks.h" +#include "FreeRTOS.h" class BLEService; class BLEDescriptor; @@ -75,6 +76,7 @@ class BLECharacteristic { esp_gatt_char_prop_t getProperties(); BLEService *getService(); void setHandle(uint16_t handle); + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); }; // BLECharacteristic #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ */ diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index a59dee9f..a8987056 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -286,7 +286,7 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { m_semaphoreWriteCharEvt.take("writeValue"); m_semaphoreWriteCharEvt.give(); - ESP_LOGD(LOG_TAG, "<< writeValue()"); + ESP_LOGD(LOG_TAG, "<< writeValue"); } // writeValue diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index dd8583a0..242ea18b 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -31,7 +31,6 @@ BLEServer::BLEServer() { m_appId = -1; m_gatts_if = -1; m_connId = -1; - m_serializeMutex.setName("BLEServer"); BLE::m_bleServer = this; m_pServerCallbacks = nullptr; createApp(0); @@ -51,14 +50,14 @@ void BLEServer::createApp(uint16_t appId) { * @return A reference to the new service object. */ BLEService *BLEServer::createService(BLEUUID uuid) { - ESP_LOGD(LOG_TAG, ">> createService(%s)", uuid.toString().c_str()); - m_serializeMutex.take("createService"); + ESP_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str()); + m_semaphoreCreateEvt.take("createService"); // Check that a service with the supplied UUID does not already exist. if (m_serviceMap.getByUUID(uuid) != nullptr) { ESP_LOGE(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.", uuid.toString().c_str()); - m_serializeMutex.give(); + m_semaphoreCreateEvt.give(); return nullptr; } @@ -66,6 +65,8 @@ BLEService *BLEServer::createService(BLEUUID uuid) { m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server. pService->executeCreate(this); // Perform the API calls to actually create the service. + m_semaphoreCreateEvt.wait("createService"); + ESP_LOGD(LOG_TAG, "<< createService"); return pService; } // createService @@ -160,7 +161,7 @@ void BLEServer::handleGATTServerEvent( case ESP_GATTS_REG_EVT: { m_gatts_if = gatts_if; - m_serializeMutex.give(); + m_semaphoreRegisterAppEvt.give(); break; } // ESP_GATTS_REG_EVT @@ -176,8 +177,8 @@ void BLEServer::handleGATTServerEvent( case ESP_GATTS_CREATE_EVT: { BLEService *pService = m_serviceMap.getByUUID(param->create.service_id.id.uuid); m_serviceMap.setByHandle(param->create.service_handle, pService); - pService->setHandle(param->create.service_handle); - m_serializeMutex.give(); + //pService->setHandle(param->create.service_handle); + m_semaphoreCreateEvt.give(); break; } // ESP_GATTS_CREATE_EVT @@ -249,10 +250,11 @@ void BLEServer::handleGATTServerEvent( * @return N/A */ void BLEServer::registerApp() { - ESP_LOGD(LOG_TAG, ">> registerApp(%d)", m_appId); - m_serializeMutex.take("registerApp"); // Take the mutex, will be released by ESP_GATTS_REG_EVT event. + ESP_LOGD(LOG_TAG, ">> registerApp - %d", m_appId); + m_semaphoreRegisterAppEvt.take("registerApp"); // Take the mutex, will be released by ESP_GATTS_REG_EVT event. ::esp_ble_gatts_app_register(m_appId); - ESP_LOGD(LOG_TAG, "<< registerApp()"); + m_semaphoreRegisterAppEvt.wait("registerApp"); + ESP_LOGD(LOG_TAG, "<< registerApp"); } // registerApp @@ -270,9 +272,9 @@ void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { * Start the server advertising its existence. */ void BLEServer::startAdvertising() { - ESP_LOGD(LOG_TAG, ">> startAdvertising()"); + ESP_LOGD(LOG_TAG, ">> startAdvertising"); m_bleAdvertising.start(); - ESP_LOGD(LOG_TAG, "<< startAdvertising()"); + ESP_LOGD(LOG_TAG, "<< startAdvertising"); } // startAdvertising diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 9bf19049..e5cf9ea3 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -45,7 +45,8 @@ class BLEServer { BLEAdvertising m_bleAdvertising; uint16_t m_gatts_if; uint16_t m_connId; - FreeRTOS::Semaphore m_serializeMutex; + FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt"); + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); BLEServiceMap m_serviceMap; BLEServerCallbacks *m_pServerCallbacks; }; // BLEServer diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index cd4170f8..c5247ff6 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -34,7 +34,7 @@ BLEService::BLEService(BLEUUID uuid) { m_uuid = uuid; m_handle = NULL_HANDLE; m_pServer = nullptr; - m_serializeMutex.setName("BLEService"); + //m_serializeMutex.setName("BLEService"); m_lastCreatedCharacteristic = nullptr; } // BLEService @@ -46,14 +46,14 @@ BLEService::BLEService(BLEUUID uuid) { * @return N/A. */ void BLEService::executeCreate(BLEServer *pServer) { - ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service)"); + ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); m_pServer = pServer; esp_gatt_srvc_id_t srvc_id; srvc_id.id.inst_id = 0; srvc_id.id.uuid = *m_uuid.getNative(); - m_serializeMutex.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT + m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT esp_err_t errRc = ::esp_ble_gatts_create_service(getServer()->getGattsIf(), &srvc_id, 10); @@ -62,7 +62,9 @@ void BLEService::executeCreate(BLEServer *pServer) { return; } - ESP_LOGD(LOG_TAG, "<< executeCreate()"); + m_semaphoreCreateEvt.wait("executeCreate"); + + ESP_LOGD(LOG_TAG, "<< executeCreate"); } // executeCreate @@ -101,19 +103,29 @@ BLEUUID BLEService::getUUID() { /** * @brief Start the service. + * Here we wish to start the service which means that we will respond to partner requests about it. + * Starting a service also means that we can create the corresponding characteristics. * @return Start the service. */ void BLEService::start() { // We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). // ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); + if (m_handle == NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!"); + return; + } + m_semaphoreStartEvt.take("start"); esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + m_semaphoreStartEvt.wait("start"); BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); @@ -134,13 +146,13 @@ void BLEService::start() { * @param [in] handle The handle associated with the service. */ void BLEService::setHandle(uint16_t handle) { - ESP_LOGD(LOG_TAG, ">> setHandle(0x%.2x)", handle); + ESP_LOGD(LOG_TAG, ">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str()); if (m_handle != NULL_HANDLE) { - ESP_LOGE(LOG_TAG, "Handle is already set %.2x", m_handle); + ESP_LOGE(LOG_TAG, "!!! Handle is already set %.2x", m_handle); return; } m_handle = handle; - ESP_LOGD(LOG_TAG, "<< setHandle()"); + ESP_LOGD(LOG_TAG, "<< setHandle"); } // setHandle @@ -222,18 +234,30 @@ void BLEService::handleGATTServerEvent( ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", BLEUUID(param->add_char.char_uuid).toString().c_str()); dump(); - m_serializeMutex.give(); + m_semaphoreAddCharEvt.give(); break; } pCharacteristic->setHandle(param->add_char.attr_handle); m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic); //ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str()); - m_serializeMutex.give(); + m_semaphoreAddCharEvt.give(); break; } // Reached the correct service. break; } // ESP_GATTS_ADD_CHAR_EVT + // ESP_GATTS_START_EVT + // + // start: + // esp_gatt_status_t status + // uint16_t service_handle + case ESP_GATTS_START_EVT: { + if (param->start.service_handle == getHandle()) { + m_semaphoreStartEvt.give(); + } + break; + } // ESP_GATTS_START_EVT + // ESP_GATTS_CREATE_EVT // Called when a new service is registered as having been created. @@ -250,7 +274,7 @@ void BLEService::handleGATTServerEvent( case ESP_GATTS_CREATE_EVT: { if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid))) { setHandle(param->create.service_handle); - m_serializeMutex.give(); + m_semaphoreCreateEvt.give(); } break; } // ESP_GATTS_CREATE_EVT diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index ddc4655c..fea31747 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -45,7 +45,10 @@ class BLEService { uint16_t m_handle; BLECharacteristic* m_lastCreatedCharacteristic; BLEServer* m_pServer; - FreeRTOS::Semaphore m_serializeMutex; + //FreeRTOS::Semaphore m_serializeMutex; + FreeRTOS::Semaphore m_semaphoreAddCharEvt = FreeRTOS::Semaphore("AddCharEvt"); + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); BLEUUID m_uuid; uint16_t getHandle(); diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 04f81403..06cf25d8 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -201,26 +201,14 @@ esp_bt_uuid_t *BLEUUID::getNative() { */ void BLEUUID::to128() { //ESP_LOGD(LOG_TAG, ">> toFull() - %s", toString().c_str()); - if (m_valueSet == false) { - return; - } - if (m_uuid.len == ESP_UUID_LEN_128) { + + // If we either don't have a value or are already a 128 bit UUID, nothing further to do. + if (m_valueSet == false || m_uuid.len == ESP_UUID_LEN_128) { return; } - m_uuid.len = ESP_UUID_LEN_128; - /* - if (value.length() == 2) { - m_uuid.len = ESP_UUID_LEN_16; - m_uuid.uuid.uuid16 = value[0] | (value[1] << 8); - m_valueSet = true; - } else if (value.length() == 4) { - m_uuid.len = ESP_UUID_LEN_32; - m_uuid.uuid.uuid32 = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); - m_valueSet = true; - */ + // If we are 16 bit or 32 bit, then set the 4 bytes of the variable part of the UUID. if (m_uuid.len == ESP_UUID_LEN_16) { - uint16_t temp = m_uuid.uuid.uuid16; m_uuid.uuid.uuid128[15] = 0; m_uuid.uuid.uuid128[14] = 0; @@ -236,6 +224,7 @@ void BLEUUID::to128() { m_uuid.uuid.uuid128[12] = temp & 0xff; } + // Set the fixed parts of the UUID. m_uuid.uuid.uuid128[11] = 0x00; m_uuid.uuid.uuid128[10] = 0x00; @@ -251,6 +240,8 @@ void BLEUUID::to128() { m_uuid.uuid.uuid128[2] = 0x9b; m_uuid.uuid.uuid128[1] = 0x34; m_uuid.uuid.uuid128[0] = 0xfb; + + m_uuid.len = ESP_UUID_LEN_128; //ESP_LOGD(TAG, "<< toFull <- %s", toString().c_str()); } // to128 diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index ab079cb2..597400a0 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -1004,6 +1004,11 @@ void BLEUtils::dumpGattServerEvent( break; } // ESP_GATTS_REG_EVT + // ESP_GATTS_START_EVT + // + // start: + // esp_gatt_status_t status + // uint16_t service_handle case ESP_GATTS_START_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, service_handle: 0x%.2x]", gattStatusToString(evtParam->start.status).c_str(), diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index c9119f6c..60ab464c 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -76,6 +76,19 @@ uint32_t FreeRTOS::getTimeSinceStart() { * */ +/** + * @brief Wait for a semaphore to be released by trying to take it and + * then releasing it again. + * @param [in] owner A debug tag. + */ +void FreeRTOS::Semaphore::wait(std::string owner) { + ESP_LOGV(TAG, "Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); + xSemaphoreTake(m_semaphore, portMAX_DELAY); + m_owner = owner; + xSemaphoreGive(m_semaphore); + ESP_LOGV(TAG, "Semaphore released: %s", toString().c_str()); + m_owner = ""; +} // wait FreeRTOS::Semaphore::Semaphore(std::string name) { m_semaphore = xSemaphoreCreateMutex(); @@ -94,7 +107,7 @@ FreeRTOS::Semaphore::~Semaphore() { */ void FreeRTOS::Semaphore::give() { xSemaphoreGive(m_semaphore); - ESP_LOGD(TAG, "Semaphore giving: %s", toString().c_str()); + ESP_LOGV(TAG, "Semaphore giving: %s", toString().c_str()); m_owner = ""; } // Semaphore::give @@ -106,10 +119,10 @@ void FreeRTOS::Semaphore::give() { void FreeRTOS::Semaphore::take(std::string owner) { - ESP_LOGD(TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); + ESP_LOGV(TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); xSemaphoreTake(m_semaphore, portMAX_DELAY); m_owner = owner; - ESP_LOGD(TAG, "Semaphore taken: %s", toString().c_str()); + ESP_LOGV(TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take @@ -119,8 +132,10 @@ void FreeRTOS::Semaphore::take(std::string owner) * @param [in] timeoutMs Timeout in milliseconds. */ void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { + ESP_LOGV(TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); m_owner = owner; xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); + ESP_LOGV(TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take std::string FreeRTOS::Semaphore::toString() { @@ -132,3 +147,4 @@ std::string FreeRTOS::Semaphore::toString() { void FreeRTOS::Semaphore::setName(std::string name) { m_name = name; } + diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index e2d730fe..60eb2699 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -36,6 +36,7 @@ class FreeRTOS { void setName(std::string name); void take(std::string owner=""); void take(uint32_t timeoutMs, std::string owner=""); + void wait(std::string owner=""); std::string toString(); private: SemaphoreHandle_t m_semaphore; diff --git a/cpp_utils/tests/BLE Tests/SampleServer.cpp b/cpp_utils/tests/BLE Tests/SampleServer.cpp index cd747608..27926c65 100644 --- a/cpp_utils/tests/BLE Tests/SampleServer.cpp +++ b/cpp_utils/tests/BLE Tests/SampleServer.cpp @@ -44,7 +44,7 @@ class MainBLEServer: public Task { ESP_LOGD(LOG_TAG, "Starting BLE work!"); BLE::initServer("MYDEVICE"); BLEServer* pServer = new MyServer(); - pServer->createApp(0); + //pServer->createApp(0); BLEUUID serviceUUID((uint16_t)0x1234); BLEService *pService = pServer->createService(serviceUUID); From 6112e1864484c8e38aac594eae6acefc89184384 Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 17 Jul 2017 15:32:54 -0500 Subject: [PATCH 008/381] sync 2017-07-17 15:32CT --- cpp_utils/BLE2902.cpp | 41 +++++++++---- cpp_utils/BLE2902.h | 3 + cpp_utils/BLECharacteristic.cpp | 69 ++++++++++++++++++++++ cpp_utils/BLECharacteristic.h | 9 +-- cpp_utils/BLEDescriptorMap.cpp | 2 +- cpp_utils/BLEUUID.cpp | 5 +- cpp_utils/BLEUUID.h | 2 +- cpp_utils/BLEUtils.cpp | 31 +++++++++- cpp_utils/tests/BLE Tests/SampleNotify.cpp | 65 +++++++++++++------- cpp_utils/tests/BLE Tests/SampleServer.cpp | 50 ++++------------ 10 files changed, 197 insertions(+), 80 deletions(-) diff --git a/cpp_utils/BLE2902.cpp b/cpp_utils/BLE2902.cpp index e9b93642..783b0582 100644 --- a/cpp_utils/BLE2902.cpp +++ b/cpp_utils/BLE2902.cpp @@ -19,18 +19,23 @@ BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t) 0x2902)) { setValue(data, 2); } + /** - * @brief Set the notifications flag. - * @param [in] flag The notifications flag. + * @brief Get the notifications value. + * @return The notifications value. */ -void BLE2902::setNotifications(bool flag) { - uint8_t *pValue = getValue(); - if (flag) { - pValue[0] |= 1<<0; - } else { - pValue[0] &= ~(1<<0); - } -} // setNotifications +bool BLE2902::getNotifications() { + return (getValue()[0] & (1 << 0)) != 0; +} // getNotifications + + +/** + * @brief Get the indications value. + * @return The indications value. + */ +bool BLE2902::getIndications() { + return (getValue()[0] & (1 << 1)) != 0; +} // getIndications /** @@ -45,4 +50,20 @@ void BLE2902::setIndications(bool flag) { pValue[0] &= ~(1<<1); } } // setIndications + + +/** + * @brief Set the notifications flag. + * @param [in] flag The notifications flag. + */ +void BLE2902::setNotifications(bool flag) { + uint8_t *pValue = getValue(); + if (flag) { + pValue[0] |= 1<<0; + } else { + pValue[0] &= ~(1<<0); + } +} // setNotifications + + #endif diff --git a/cpp_utils/BLE2902.h b/cpp_utils/BLE2902.h index de4c4ded..a7b5e874 100644 --- a/cpp_utils/BLE2902.h +++ b/cpp_utils/BLE2902.h @@ -24,8 +24,11 @@ class BLE2902: public BLEDescriptor { public: BLE2902(); + bool getNotifications(); + bool getIndications(); void setNotifications(bool flag); void setIndications(bool flag); + }; // BLE2902 #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 0553b998..37d25019 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -16,6 +16,7 @@ #include "BLECharacteristic.h" #include "BLEService.h" #include "BLEUtils.h" +#include "BLE2902.h" #include "GeneralUtils.h" static char LOG_TAG[] = "BLECharacteristic"; @@ -118,6 +119,16 @@ void BLECharacteristic::executeCreate(BLEService* pService) { } // executeCreate +/** + * @brief Return the BLE Descriptor for the given UUID if associated with this characteristic. + * @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve. + * @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned. + */ +BLEDescriptor* BLECharacteristic::getDescriptorByUUID(BLEUUID descriptorUUID) { + return m_descriptorMap.getByUUID(descriptorUUID); +} // getDescriptorByUUID + + /** * @brief Get the handle of the characteristic. * @return The handle of the characteristic. @@ -171,6 +182,11 @@ void BLECharacteristic::handleGATTServerEvent( esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { switch(event) { + // Events handled: + // ESP_GATTS_ADD_CHAR_EVT + // ESP_GATTS_WRITE_EVT + // ESP_GATTS_READ_EVT + // // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. // add_char: // - esp_gatt_status_t status @@ -185,6 +201,7 @@ void BLECharacteristic::handleGATTServerEvent( break; } // ESP_GATTS_ADD_CHAR_EVT + // ESP_GATTS_WRITE_EVT - A request to write the value of a characteristic has arrived. // // write: @@ -312,6 +329,17 @@ void BLECharacteristic::indicate() { assert(getService() != nullptr); assert(getService()->getServer() != nullptr); + + // Test to see if we have a 0x2902 descriptor. If we do, then check to see if indications are enabled + // and, if not, prevent the indication. + + BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); + if (p2902 != nullptr && !p2902->getIndications()) { + ESP_LOGD(LOG_TAG, "<< indications disabled; ignoring"); + return; + } + + esp_err_t errRc = ::esp_ble_gatts_send_indicate( getService()->getServer()->getGattsIf(), getService()->getServer()->getConnId(), @@ -337,6 +365,16 @@ void BLECharacteristic::notify() { assert(getService() != nullptr); assert(getService()->getServer() != nullptr); + + // Test to see if we have a 0x2902 descriptor. If we do, then check to see if notification is enabled + // and, if not, prevent the notification. + + BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); + if (p2902 != nullptr && !p2902->getNotifications()) { + ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring"); + return; + } + esp_err_t errRc = ::esp_ble_gatts_send_indicate( getService()->getServer()->getGattsIf(), getService()->getServer()->getConnId(), @@ -364,6 +402,7 @@ void BLECharacteristic::setBroadcastProperty(bool value) { } } // setBroadcastProperty + /** * @brief Set the callback handlers for this characteristic. */ @@ -372,6 +411,16 @@ void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) { } // setCallbacks +/** + * @brief Set the BLE handle associated with this characteristic. + * A user program will request that a characteristic be created against a service. When the characteristic has been + * registered, the service will be given a "handle" that it knows the characteristic as. This handle is unique to the + * server/service but it is told to the service, not the characteristic associated with the service. This internally + * exposed function can be invoked by the service against this model of the characteristic to allow the characteristic + * to learn its own handle. Once the characteristic knows its own handle, it will be able to see incoming GATT events + * that will be propagated down to it which contain a handle value and now know that the event is destined for it. + * @param [in] handle The handle associated with this characteristic. + */ void BLECharacteristic::setHandle(uint16_t handle) { ESP_LOGD(LOG_TAG, ">> setHandle: handle=0x%.2x, characteristic uuid=%s", handle, getUUID().toString().c_str()); m_handle = handle; @@ -379,6 +428,10 @@ void BLECharacteristic::setHandle(uint16_t handle) { } // setHandle +/** + * @brief Set the Indicate property value. + * @param [in] value Set to true if we are to allow indicate messages. + */ void BLECharacteristic::setIndicateProperty(bool value) { //ESP_LOGD(LOG_TAG, "setIndicateProperty(%d)", value); if (value) { @@ -389,6 +442,10 @@ void BLECharacteristic::setIndicateProperty(bool value) { } // setIndicateProperty +/** + * @brief Set the Notify property value. + * @param [in] value Set to true if we are to allow notification messages. + */ void BLECharacteristic::setNotifyProperty(bool value) { //ESP_LOGD(LOG_TAG, "setNotifyProperty(%d)", value); if (value) { @@ -399,6 +456,10 @@ void BLECharacteristic::setNotifyProperty(bool value) { } // setNotifyProperty +/** + * @brief Set the Read property value. + * @param [in] value Set to true if we are to allow reads. + */ void BLECharacteristic::setReadProperty(bool value) { //ESP_LOGD(LOG_TAG, "setReadProperty(%d)", value); if (value) { @@ -440,6 +501,10 @@ void BLECharacteristic::setValue(std::string value) { } // setValue +/** + * @brief Set the Write No Response property value. + * @param [in] value Set to true if we are to allow writes with no response. + */ void BLECharacteristic::setWriteNoResponseProperty(bool value) { //ESP_LOGD(LOG_TAG, "setWriteNoResponseProperty(%d)", value); if (value) { @@ -450,6 +515,10 @@ void BLECharacteristic::setWriteNoResponseProperty(bool value) { } // setWriteNoResponseProperty +/** + * @brief Set the Write property value. + * @param [in] value Set to true if we are to allow writes. + */ void BLECharacteristic::setWriteProperty(bool value) { //ESP_LOGD(LOG_TAG, "setWriteProperty(%d)", value); if (value) { diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index c37d50b3..d81c7f9e 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -26,10 +26,11 @@ class BLECharacteristic { BLECharacteristic(BLEUUID uuid, uint32_t properties = 0); virtual ~BLECharacteristic(); - void addDescriptor(BLEDescriptor *pDescriptor); - size_t getLength(); - BLEUUID getUUID(); - uint8_t *getValue(); + void addDescriptor(BLEDescriptor *pDescriptor); + BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); + size_t getLength(); + BLEUUID getUUID(); + uint8_t* getValue(); void indicate(); void notify(); diff --git a/cpp_utils/BLEDescriptorMap.cpp b/cpp_utils/BLEDescriptorMap.cpp index ff964473..5318ddf9 100644 --- a/cpp_utils/BLEDescriptorMap.cpp +++ b/cpp_utils/BLEDescriptorMap.cpp @@ -15,7 +15,7 @@ /** * @brief Return the descriptor by UUID. * @param [in] UUID The UUID to look up the descriptor. - * @return The descriptor. + * @return The descriptor. If not present, then nullptr is returned. */ BLEDescriptor* BLEDescriptorMap::getByUUID(BLEUUID uuid) { for (auto &myPair : m_uuidMap) { diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 06cf25d8..90d680fe 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -199,12 +199,12 @@ esp_bt_uuid_t *BLEUUID::getNative() { * A UUID can be internally represented as 16bit, 32bit or the full 128bit. This method * will convert 16 or 32 bit representations to the full 128bit. */ -void BLEUUID::to128() { +BLEUUID BLEUUID::to128() { //ESP_LOGD(LOG_TAG, ">> toFull() - %s", toString().c_str()); // If we either don't have a value or are already a 128 bit UUID, nothing further to do. if (m_valueSet == false || m_uuid.len == ESP_UUID_LEN_128) { - return; + return *this; } // If we are 16 bit or 32 bit, then set the 4 bytes of the variable part of the UUID. @@ -243,6 +243,7 @@ void BLEUUID::to128() { m_uuid.len = ESP_UUID_LEN_128; //ESP_LOGD(TAG, "<< toFull <- %s", toString().c_str()); + return *this; } // to128 diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index f0585776..ff6c9c2f 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -23,7 +23,7 @@ class BLEUUID { BLEUUID(); bool equals(BLEUUID uuid); esp_bt_uuid_t *getNative(); - void to128(); + BLEUUID to128(); std::string toString(); private: diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 597400a0..22c21e29 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -963,15 +963,42 @@ void BLEUtils::dumpGattServerEvent( break; } // ESP_GATTS_DISCONNECT_EVT + + // ESP_GATTS_EXEC_WRITE_EVT + // exec_write: + // - uint16_t conn_id + // - uint32_t trans_id + // - esp_bd_addr_t bda + // - uint8_t exec_write_flag + // case ESP_GATTS_EXEC_WRITE_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, exec_write_flag: 0x%.2x]", + char* pWriteFlagText; + switch(evtParam->exec_write.exec_write_flag) { + case ESP_GATT_PREP_WRITE_EXEC: { + pWriteFlagText = (char*)"WRITE"; + break; + } + + case ESP_GATT_PREP_WRITE_CANCEL: { + pWriteFlagText = (char*)"CANCEL"; + break; + } + + default: + pWriteFlagText = (char*)""; + break; + } + + ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, exec_write_flag: 0x%.2x=%s]", evtParam->exec_write.conn_id, evtParam->exec_write.trans_id, BLEAddress(evtParam->exec_write.bda).toString().c_str(), - evtParam->exec_write.exec_write_flag); + evtParam->exec_write.exec_write_flag, + pWriteFlagText); break; } // ESP_GATTS_DISCONNECT_EVT + case ESP_GATTS_MTU_EVT: { ESP_LOGD(LOG_TAG, "[conn_id: %d, mtu: %d]", evtParam->mtu.conn_id, diff --git a/cpp_utils/tests/BLE Tests/SampleNotify.cpp b/cpp_utils/tests/BLE Tests/SampleNotify.cpp index d0597da2..0e3058f9 100644 --- a/cpp_utils/tests/BLE Tests/SampleNotify.cpp +++ b/cpp_utils/tests/BLE Tests/SampleNotify.cpp @@ -1,14 +1,35 @@ -#include "BLE.h" -#include "BLEUtils.h" -#include "BLEServer.h" +/** + * Create a BLE server that, once we receive a connection, will send periodic notifications. + * The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b + * And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8 + * + * The design of creating the BLE server is: + * 1. Create a BLE Server + * 2. Create a BLE Service + * 3. Create a BLE Characteristic on the Service + * 4. Create a BLE Descriptor on the characteristic + * 5. Start the service. + * 6. Start advertising. + * + * A connect hander associated with the server starts a background task that performs notification + * every couple of seconds. + * + * @author: Neil Kolban, July 2017 + * + */ +#include "sdkconfig.h" + #include #include -#include #include -#include "Task.h" +#include + +#include "BLE.h" +#include "BLEServer.h" +#include "BLEUtils.h" #include "BLE2902.h" +#include "Task.h" -#include "sdkconfig.h" static char LOG_TAG[] = "SampleNotify"; @@ -29,18 +50,18 @@ class MyNotifyTask: public Task { pCharacteristic->setValue(&value, 1); pCharacteristic->notify(); value++; - } - } -}; + } // While 1 + } // run +}; // MyNotifyTask MyNotifyTask *pMyNotifyTask; class MyServerCallbacks: public BLEServerCallbacks { - void onConnect(BLEServer *pServer) { + void onConnect(BLEServer* pServer) { pMyNotifyTask->start(); }; - void onDisconnect(BLEServer *pServer) { + void onDisconnect(BLEServer* pServer) { pMyNotifyTask->stop(); } }; @@ -49,29 +70,33 @@ static void run() { pMyNotifyTask = new MyNotifyTask(); pMyNotifyTask->setStackSize(8000); + // Create the BLE Device BLE::initServer("MYDEVICE"); + + // Create the BLE Server BLEServer *pServer = new BLEServer(); + pServer->setCallbacks(new MyServerCallbacks()); + // Create the BLE Service BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); - pServer->setCallbacks(new MyServerCallbacks()); - + // Create a BLE Characteristic pCharacteristic = pService->createCharacteristic( BLEUUID(CHARACTERISTIC_UUID), - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml - BLE2902 *pBLE2902 = new BLE2902(); - pBLE2902->setNotifications(true); - pCharacteristic->addDescriptor(pBLE2902); + // Create a BLE Descriptor + pCharacteristic->addDescriptor(new BLE2902()); + // Start the service pService->start(); - BLEAdvertising *pAdvertising = pServer->getAdvertising(); - pAdvertising->start(); + // Start advertising + pServer->getAdvertising()->start(); } void SampleNotify(void) diff --git a/cpp_utils/tests/BLE Tests/SampleServer.cpp b/cpp_utils/tests/BLE Tests/SampleServer.cpp index 27926c65..5332bdca 100644 --- a/cpp_utils/tests/BLE Tests/SampleServer.cpp +++ b/cpp_utils/tests/BLE Tests/SampleServer.cpp @@ -10,46 +10,16 @@ static char LOG_TAG[] = "SampleServer"; -BLECharacteristic* pCharacteristic; - -class MyNotifyTask: public Task { - void run(void *data) { - while(1) { - ESP_LOGD(LOG_TAG, "Notify!"); - delay(2000); - pCharacteristic->indicate(); // Perform the actual indication of a notification to the peer. - } - } -}; - -class MyServer: public BLEServer { - MyNotifyTask *pMyNotifyTask; - void onConnect() { - ESP_LOGD(LOG_TAG, "My onConnect"); - /* - pMyNotifyTask = new MyNotifyTask(); - pMyNotifyTask->setStackSize(18000); - pMyNotifyTask->start(); - */ - } - - void onDisconnect() { - ESP_LOGD(LOG_TAG, "My onDisconnect"); - pMyNotifyTask->stop(); - } -}; - class MainBLEServer: public Task { void run(void *data) { ESP_LOGD(LOG_TAG, "Starting BLE work!"); + BLE::initServer("MYDEVICE"); - BLEServer* pServer = new MyServer(); - //pServer->createApp(0); + BLEServer* pServer = new BLEServer(); - BLEUUID serviceUUID((uint16_t)0x1234); - BLEService *pService = pServer->createService(serviceUUID); + BLEService *pService = pServer->createService(BLEUUID((uint16_t)0x1234)); - pCharacteristic = pService->createCharacteristic( + BLECharacteristic* pCharacteristic = pService->createCharacteristic( BLEUUID((uint16_t)0x99AA), BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | @@ -57,7 +27,7 @@ class MainBLEServer: public Task { ); - pCharacteristic->setValue("hello steph"); + pCharacteristic->setValue("Hello World!"); BLE2902* p2902Descriptor = new BLE2902(); p2902Descriptor->setNotifications(true); @@ -65,19 +35,19 @@ class MainBLEServer: public Task { pService->start(); - //pServer->startAdvertising(); - BLEUUID serviceUUIDFull((uint16_t)0x1234); - serviceUUIDFull.to128(); - BLEAdvertising* pAdvertising = pServer->getAdvertising(); - pAdvertising->setServiceUUID(serviceUUIDFull); + pAdvertising->setServiceUUID(pService->getUUID().to128()); pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); delay(1000000); } }; + void SampleServer(void) { + esp_log_level_set("*", ESP_LOG_DEBUG); MainBLEServer* pMainBleServer = new MainBLEServer(); pMainBleServer->setStackSize(20000); pMainBleServer->start(); From 569895aa8d02745f4c212895ee5791074ff656cd Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 18 Jul 2017 19:52:49 -0500 Subject: [PATCH 009/381] Fixes for #25 --- cpp_utils/BLECharacteristic.cpp | 174 +++++++++++++++++---- cpp_utils/BLECharacteristic.h | 8 +- cpp_utils/BLEDescriptor.cpp | 12 +- cpp_utils/BLEDescriptor.h | 3 + cpp_utils/BLEUtils.cpp | 31 +++- cpp_utils/BLEValue.cpp | 110 +++++++++++++ cpp_utils/BLEValue.h | 31 ++++ cpp_utils/tests/BLE Tests/SampleNotify.cpp | 4 +- cpp_utils/tests/BLE Tests/SampleServer.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleWrite.cpp | 5 +- 10 files changed, 339 insertions(+), 41 deletions(-) create mode 100644 cpp_utils/BLEValue.cpp create mode 100644 cpp_utils/BLEValue.h diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 37d25019..44ef9017 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -30,9 +30,9 @@ static char LOG_TAG[] = "BLECharacteristic"; */ BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { m_bleUUID = uuid; - m_value.attr_value = static_cast(malloc(ESP_GATT_MAX_ATTR_LEN)); // Allocate storage for the value - m_value.attr_len = 0; // Initial length of actual data is none. - m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; // Maximum length of data. + //m_value.attr_value = static_cast(malloc(ESP_GATT_MAX_ATTR_LEN)); // Allocate storage for the value + //m_value.attr_len = 0; // Initial length of actual data is none. + //m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; // Maximum length of data. m_handle = NULL_HANDLE; m_properties = 0; m_pCallbacks = nullptr; @@ -49,7 +49,7 @@ BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { * @brief Destructor. */ BLECharacteristic::~BLECharacteristic() { - free(m_value.attr_value); // Release the storage for the value. + //free(m_value.attr_value); // Release the storage for the value. } // ~BLECharacteristic @@ -77,24 +77,27 @@ void BLECharacteristic::executeCreate(BLEService* pService) { return; } - m_pService = pService; // Save the service for this characteristic. + m_pService = pService; // Save the service for to which this characteristic belongs. ESP_LOGD(LOG_TAG, "Registering characteristic (esp_ble_gatts_add_char): uuid: %s, service: %s", getUUID().toString().c_str(), m_pService->toString().c_str()); - //m_serializeMutex.take("addCharacteristic"); // Take the mutex, released by event ESP_GATTS_ADD_CHAR_EVT - esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; m_semaphoreCreateEvt.take("executeCreate"); + esp_attr_value_t value; + std::string strValue = m_value.getValue(); + value.attr_len = strValue.length(); + value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; + value.attr_value = (uint8_t*)strValue.data(); esp_err_t errRc = ::esp_ble_gatts_add_char( m_pService->getHandle(), getUUID().getNative(), static_cast(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), getProperties(), - &m_value, + &value, &control); // Whether to auto respond or not. if (errRc != ESP_OK) { @@ -108,7 +111,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { // characteristic. We iterate through each of those and invoke the registration call to register them with the // ESP environment. - BLEDescriptor *pDescriptor = m_descriptorMap.getFirst(); + BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); while (pDescriptor != nullptr) { pDescriptor->executeCreate(this); @@ -141,10 +144,11 @@ uint16_t BLECharacteristic::getHandle() { /** * @brief Get the length of the value. */ +/* size_t BLECharacteristic::getLength() { return m_value.attr_len; } // getLength - +*/ esp_gatt_char_prop_t BLECharacteristic::getProperties() { return m_properties; @@ -172,8 +176,8 @@ BLEUUID BLECharacteristic::getUUID() { * @brief Retrieve the current value of the characteristic. * @return A pointer to storage containing the current characteristic value. */ -uint8_t* BLECharacteristic::getValue() { - return m_value.attr_value; +std::string BLECharacteristic::getValue() { + return m_value.getValue(); } // getValue @@ -187,6 +191,37 @@ void BLECharacteristic::handleGATTServerEvent( // ESP_GATTS_WRITE_EVT // ESP_GATTS_READ_EVT // + + // ESP_GATTS_EXEC_WRITE_EVT + // When we receive this event it is an indication that a previous write long needs to be committed. + // + // exec_write: + // - uint16_t conn_id + // - uint32_t trans_id + // - esp_bd_addr_t bda + // - uint8_t exec_write_flag + // + case ESP_GATTS_EXEC_WRITE_EVT: { + if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { + m_value.commit(); + if (m_pCallbacks != nullptr) { + m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. + } + } else { + m_value.cancel(); + } + + esp_err_t errRc = ::esp_ble_gatts_send_response( + gatts_if, + param->write.conn_id, + param->write.trans_id, ESP_GATT_OK, nullptr); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + break; + } // ESP_GATTS_EXEC_WRITE_EVT + + // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. // add_char: // - esp_gatt_status_t status @@ -220,7 +255,11 @@ void BLECharacteristic::handleGATTServerEvent( // we save the new value. Next we look at the need_rsp flag which indicates whether or not we need // to send a response. If we do, then we formulate a response and send it. if (param->write.handle == m_handle) { - setValue(param->write.value, param->write.len); + if (param->write.is_prep) { + m_value.addPart(param->write.value, param->write.len); + } else { + setValue(param->write.value, param->write.len); + } ESP_LOGD(LOG_TAG, " - Response to write event: New value: handle: %.2x, uuid: %s", getHandle(), getUUID().toString().c_str()); @@ -231,11 +270,13 @@ void BLECharacteristic::handleGATTServerEvent( if (param->write.need_rsp) { esp_gatt_rsp_t rsp; - rsp.attr_value.len = getLength(); + + rsp.attr_value.len = param->write.len; rsp.attr_value.handle = m_handle; - rsp.attr_value.offset = 0; + rsp.attr_value.offset = param->write.offset; rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; - memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.len); + memcpy(rsp.attr_value.value, param->write.value, param->write.len); + esp_err_t errRc = ::esp_ble_gatts_send_response( gatts_if, param->write.conn_id, @@ -245,7 +286,7 @@ void BLECharacteristic::handleGATTServerEvent( } } // Response needed - if (m_pCallbacks != nullptr) { + if (m_pCallbacks != nullptr && param->write.is_prep != true) { m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. } } // Match on handles. @@ -275,17 +316,60 @@ void BLECharacteristic::handleGATTServerEvent( // Here's an interesting thing. The read request has the option of saying whether we need a response // or not. What would it "mean" to receive a read request and NOT send a response back? That feels like // a very strange read. +// +// We have to handle the case where the data we wish to send back to the client is greater than the maximum +// packet size of 22 bytes. In this case, we become responsible for chunking the data into uints of 22 bytes. +// The apparent algorithm is as follows. +// If the is_long flag is set then this is a follow on from an original read and we will already have sent at least 22 bytes. +// If the is_long flag is not set then we need to check how much data we are going to send. If we are sending LESS than +// 22 bytes, then we "just" send it and thats the end of the story. +// If we are sending 22 bytes exactly, we just send it BUT we will get a follow on request. +// If we are sending more than 22 bytes, we send the first 22 bytes and we will get a follow on request. +// Because of follow on request processing, we need to maintain an offset of how much data we have already sent +// so that when a follow on request arrives, we know where to start in the data to send the next sequence. +// Note that the indication that the client will send a follow on request is that we sent exactly 22 bytes as a response. +// If our payload is divisible by 22 then the last response will be a response of 0 bytes in length. +// +// The following code has deliberately not been factored to make it fewer statements because this would cloud the +// the logic flow comprehension. +// if (param->read.need_rsp) { ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); esp_gatt_rsp_t rsp; - rsp.attr_value.len = getLength(); + std::string value = m_value.getValue(); + if (param->read.is_long) { + if (value.length() - m_value.getReadOffset() < 22) { + // This is the last in the chain + rsp.attr_value.len = value.length() - m_value.getReadOffset(); + rsp.attr_value.offset = m_value.getReadOffset(); + memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len); + m_value.setReadOffset(0); + } else { + // There will be more to come. + rsp.attr_value.len = 22; + rsp.attr_value.offset = m_value.getReadOffset(); + memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len); + m_value.setReadOffset(rsp.attr_value.offset + 22); + } + } else { + if (value.length() > 21) { + // Too big for a single shot entry. + m_value.setReadOffset(22); + rsp.attr_value.len = 22; + rsp.attr_value.offset = 0; + memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len); + } else { + // Will fit in a single packet with no callbacks required. + rsp.attr_value.len = value.length(); + rsp.attr_value.offset = 0; + memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len); + } + } rsp.attr_value.handle = param->read.handle; - rsp.attr_value.offset = 0; rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; - memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.len); char *pHexData = BLEUtils::buildHexData(nullptr, rsp.attr_value.value, rsp.attr_value.len); - ESP_LOGD(LOG_TAG, " - Data: length: %d, data: %s", rsp.attr_value.len, pHexData); + ESP_LOGD(LOG_TAG, " - Data: length=%d, data=%s, offset=%d", rsp.attr_value.len, pHexData, rsp.attr_value.offset); free(pHexData); esp_err_t errRc = ::esp_ble_gatts_send_response( @@ -301,6 +385,17 @@ void BLECharacteristic::handleGATTServerEvent( break; } // ESP_GATTS_READ_EVT + // ESP_GATTS_CONF_EVT + // + // conf: + // - esp_gatt_status_t status – The status code. + // - uint16_t conn_id – The connection used. + // + case ESP_GATTS_CONF_EVT: { + m_semaphoreConfEvt.give(); + break; + } + default: { break; } // default @@ -323,8 +418,8 @@ void BLECharacteristic::handleGATTServerEvent( */ void BLECharacteristic::indicate() { - char *pHexData = BLEUtils::buildHexData(nullptr, getValue(), getLength()); - ESP_LOGD(LOG_TAG, ">> indicate: length: %d, data: [%s]", getLength(), pHexData ); + char *pHexData = BLEUtils::buildHexData(nullptr, (uint8_t*)m_value.getValue().data(), m_value.getValue().length()); + ESP_LOGD(LOG_TAG, ">> indicate: length: %d, data: [%s]", m_value.getValue().length(), pHexData ); free(pHexData); assert(getService() != nullptr); @@ -339,16 +434,28 @@ void BLECharacteristic::indicate() { return; } + if (m_value.getValue().length() > 20) { + ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)"); + } + + size_t length = m_value.getValue().length(); + if (length > 20) { + length = 20; + } + + m_semaphoreConfEvt.take("indicate"); esp_err_t errRc = ::esp_ble_gatts_send_indicate( getService()->getServer()->getGattsIf(), getService()->getServer()->getConnId(), - getHandle(), getLength(), getValue(), true); // The need_confirm = true makes this an indication. + getHandle(), length, (uint8_t*)m_value.getValue().data(), true); // The need_confirm = true makes this an indication. + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + m_semaphoreConfEvt.wait("indicate"); ESP_LOGD(LOG_TAG, "<< indicate"); } // indicate @@ -359,10 +466,11 @@ void BLECharacteristic::indicate() { */ void BLECharacteristic::notify() { - char *pHexData = BLEUtils::buildHexData(nullptr, getValue(), getLength()); - ESP_LOGD(LOG_TAG, ">> notify: length: %d, data: [%s]", getLength(), pHexData ); + char *pHexData = BLEUtils::buildHexData(nullptr, (uint8_t*)m_value.getValue().data(), m_value.getValue().length()); + ESP_LOGD(LOG_TAG, ">> notify: length: %d, data: [%s]", m_value.getValue().length(), pHexData ); free(pHexData); + assert(getService() != nullptr); assert(getService()->getServer() != nullptr); @@ -375,10 +483,19 @@ void BLECharacteristic::notify() { return; } + if (m_value.getValue().length() > 20) { + ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)"); + } + + size_t length = m_value.getValue().length(); + if (length > 20) { + length = 20; + } + esp_err_t errRc = ::esp_ble_gatts_send_indicate( getService()->getServer()->getGattsIf(), getService()->getServer()->getConnId(), - getHandle(), getLength(), getValue(), false); // The need_confirm = false makes this a notify. + getHandle(), length, (uint8_t*)m_value.getValue().data(), false); // The need_confirm = false makes this a notify. if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; @@ -483,8 +600,7 @@ void BLECharacteristic::setValue(uint8_t* data, size_t length) { ESP_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, ESP_GATT_MAX_ATTR_LEN); return; } - m_value.attr_len = length; - memcpy(m_value.attr_value, data, length); + m_value.setValue(data, length); ESP_LOGD(LOG_TAG, "<< setValue"); } // setValue diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index d81c7f9e..3437505f 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -15,6 +15,7 @@ #include "BLEDescriptor.h" #include "BLEDescriptorMap.h" #include "BLECharacteristicCallbacks.h" +#include "BLEValue.h" #include "FreeRTOS.h" class BLEService; @@ -28,9 +29,9 @@ class BLECharacteristic { void addDescriptor(BLEDescriptor *pDescriptor); BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); - size_t getLength(); + //size_t getLength(); BLEUUID getUUID(); - uint8_t* getValue(); + std::string getValue(); void indicate(); void notify(); @@ -61,7 +62,7 @@ class BLECharacteristic { BLEUUID m_bleUUID; esp_gatt_char_prop_t m_properties; - esp_attr_value_t m_value; + BLEValue m_value; uint16_t m_handle; BLEService *m_pService; BLEDescriptorMap m_descriptorMap; @@ -78,6 +79,7 @@ class BLECharacteristic { BLEService *getService(); void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt"); }; // BLECharacteristic #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ */ diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 28ab528e..f2f7dcb2 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -59,6 +59,7 @@ void BLEDescriptor::executeCreate(BLECharacteristic* pCharacteristic) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; + m_semaphoreCreateEvt.take("executeCreate"); esp_err_t errRc = ::esp_ble_gatts_add_char_descr( pCharacteristic->getService()->getHandle(), getUUID().getNative(), @@ -69,6 +70,8 @@ void BLEDescriptor::executeCreate(BLECharacteristic* pCharacteristic) { ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_add_char_descr: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + + m_semaphoreCreateEvt.wait("executeCreate"); ESP_LOGD(LOG_TAG, "<< executeCreate"); } // executeCreate @@ -124,10 +127,11 @@ void BLEDescriptor::handleGATTServerEvent( // // add_char_descr: // - esp_gatt_status_t status - // - uint16_t attr_handle - // - uint16_t service_handle - // - esp_bt_uuid_t char_uuid + // - uint16_t attr_handle + // - uint16_t service_handle + // - esp_bt_uuid_t char_uuid case ESP_GATTS_ADD_CHAR_DESCR_EVT: { + /* ESP_LOGD(LOG_TAG, "DEBUG: m_pCharacteristic: %x", (uint32_t)m_pCharacteristic); ESP_LOGD(LOG_TAG, "DEBUG: m_bleUUID: %s, add_char_descr.char_uuid: %s, equals: %d", m_bleUUID.toString().c_str(), @@ -137,11 +141,13 @@ void BLEDescriptor::handleGATTServerEvent( m_pCharacteristic->getService()->getHandle(), param->add_char_descr.service_handle); ESP_LOGD(LOG_TAG, "DEBUG: service->lastCharacteristic: %x", (uint32_t)m_pCharacteristic->getService()->getLastCreatedCharacteristic()); + */ if (m_pCharacteristic != nullptr && m_bleUUID.equals(BLEUUID(param->add_char_descr.char_uuid)) && m_pCharacteristic->getService()->getHandle() == param->add_char_descr.service_handle && m_pCharacteristic == m_pCharacteristic->getService()->getLastCreatedCharacteristic()) { setHandle(param->add_char_descr.attr_handle); + m_semaphoreCreateEvt.give(); } break; } // ESP_GATTS_ADD_CHAR_DESCR_EVT diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index 39483730..a2c05fac 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -13,6 +13,8 @@ #include "BLEUUID.h" #include "BLECharacteristic.h" #include +#include "FreeRTOS.h" + class BLEService; class BLECharacteristic; @@ -42,6 +44,7 @@ class BLEDescriptor { void executeCreate(BLECharacteristic *pCharacteristic); uint16_t getHandle(); void setHandle(uint16_t handle); + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); }; #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ */ diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 22c21e29..93545b36 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -925,6 +925,13 @@ void BLEUtils::dumpGattServerEvent( break; } // ESP_GATTS_ADD_CHAR_EVT + + // ESP_GATTS_CONF_EVT + // + // conf: + // - esp_gatt_status_t status – The status code. + // - uint16_t conn_id – The connection used. + // case ESP_GATTS_CONF_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, conn_id: 0x%.2x]", gattStatusToString(evtParam->conf.status).c_str(), @@ -932,6 +939,7 @@ void BLEUtils::dumpGattServerEvent( break; } // ESP_GATTS_CONF_EVT + case ESP_GATTS_CONGEST_EVT: { ESP_LOGD(LOG_TAG, "[conn_id: %d, congested: %d]", evtParam->congest.conn_id, @@ -1031,11 +1039,13 @@ void BLEUtils::dumpGattServerEvent( break; } // ESP_GATTS_REG_EVT + // ESP_GATTS_START_EVT // // start: - // esp_gatt_status_t status - // uint16_t service_handle + // - esp_gatt_status_t status + // - uint16_t service_handle + // case ESP_GATTS_START_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, service_handle: 0x%.2x]", gattStatusToString(evtParam->start.status).c_str(), @@ -1043,6 +1053,20 @@ void BLEUtils::dumpGattServerEvent( break; } // ESP_GATTS_START_EVT + + // ESP_GATTS_WRITE_EVT + // + // write: + // - uint16_t conn_id – The connection id. + // - uint16_t trans_id – The transfer id. + // - esp_bd_addr_t bda – The address of the partner. + // - uint16_t handle – The attribute handle. + // - uint16_t offset – The offset of the currently received within the whole value. + // - bool need_rsp – Do we need a response? + // - bool is_prep – Is this a write prepare? If set, then this is to be considered part of the received value and not the whole value. A subsequent ESP_GATTS_EXEC_WRITE will mark the total. + // - uint16_t len – The length of the incoming value part. + // - uint8_t* value – The data for this value part. + // case ESP_GATTS_WRITE_EVT: { ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, offset: %d, need_rsp: %d, is_prep: %d, len: %d]", evtParam->write.conn_id, @@ -1053,6 +1077,9 @@ void BLEUtils::dumpGattServerEvent( evtParam->write.need_rsp, evtParam->write.is_prep, evtParam->write.len); + char *pHex = buildHexData(nullptr, evtParam->write.value, evtParam->write.len); + ESP_LOGD(LOG_TAG, "[Data: %s]", pHex); + free(pHex); break; } // ESP_GATTS_WRITE_EVT diff --git a/cpp_utils/BLEValue.cpp b/cpp_utils/BLEValue.cpp new file mode 100644 index 00000000..dc63999c --- /dev/null +++ b/cpp_utils/BLEValue.cpp @@ -0,0 +1,110 @@ +/* + * BLEValue.cpp + * + * Created on: Jul 17, 2017 + * Author: kolban + */ + +#include "BLEValue.h" +#include + +const char* LOG_TAG="BLEValue"; + +BLEValue::BLEValue() { + m_accumulation = ""; + m_value = ""; +} // BLEValue + + +/** + * @brief Add a message part to the accumulation. + * @param [in] part A message part being added. + */ +void BLEValue::addPart(std::string part) { + ESP_LOGD(LOG_TAG, ">> addPart: length=%d", part.length()); + m_accumulation += part; +} // addPart + + +/** + * @brief Add a message part to the accumulation. + * @param [in] pData A message part being added. + * @param [in] length The number of bytes being added. + */ +void BLEValue::addPart(uint8_t* pData, size_t length) { + ESP_LOGD(LOG_TAG, ">> addPart: length=%d", length); + m_accumulation += std::string((char *)pData, length); +} // addPart + + +/** + * @brief Cancel the current accumulation. + */ +void BLEValue::cancel() { + ESP_LOGD(LOG_TAG, ">> cancel"); + m_accumulation = ""; +} // cancel + + +/** + * @brief Commit the current accumulation. + * When writing a value, we may find that we write it in "parts" meaning that the writes come in in pieces + * of the overall message. After the last part has been received, we may perform a commit which means that + * we now have the complete message and commit the change as a unit. + */ +void BLEValue::commit() { + ESP_LOGD(LOG_TAG, ">> commit"); + // If there is nothing to commit, do nothing. + if (m_accumulation.length() == 0) { + return; + } + setValue(m_accumulation); + m_accumulation = ""; +} // commit + + +/** + * @brief Get the read offset. + * @return The read offset into the read. + */ +uint16_t BLEValue::getReadOffset() { + return m_readOffset; +} + + +/** + * @brief Get the current value. + */ +std::string BLEValue::getValue() { + return m_value; +} // getValue + + +/** + * @brief Set the read offset + * @param [in] readOffset The offset into the read. + */ +void BLEValue::setReadOffset(uint16_t readOffset) { + m_readOffset = readOffset; +} // setReadOffset + + +/** + * @brief Set the current value. + */ +void BLEValue::setValue(std::string value) { + m_value = value; +} // setValue + + + + + +/** + * @brief Set the current value. + * @param [in] pData The data for the current value. + * @param [in] The length of the new current value. + */ +void BLEValue::setValue(uint8_t* pData, size_t length) { + m_value = std::string((char *)pData, length); +} // setValue diff --git a/cpp_utils/BLEValue.h b/cpp_utils/BLEValue.h new file mode 100644 index 00000000..f899ee68 --- /dev/null +++ b/cpp_utils/BLEValue.h @@ -0,0 +1,31 @@ +/* + * BLEValue.h + * + * Created on: Jul 17, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEVALUE_H_ +#define COMPONENTS_CPP_UTILS_BLEVALUE_H_ +#include + +class BLEValue { +public: + BLEValue(); + void addPart(std::string part); + void addPart(uint8_t* pData, size_t length); + void cancel(); + void commit(); + uint16_t getReadOffset(); + std::string getValue(); + void setReadOffset(uint16_t readOffset); + void setValue(std::string value); + void setValue(uint8_t* pData, size_t length); + +private: + std::string m_accumulation; + uint16_t m_readOffset; + std::string m_value; +}; + +#endif /* COMPONENTS_CPP_UTILS_BLEVALUE_H_ */ diff --git a/cpp_utils/tests/BLE Tests/SampleNotify.cpp b/cpp_utils/tests/BLE Tests/SampleNotify.cpp index 0e3058f9..170b8aba 100644 --- a/cpp_utils/tests/BLE Tests/SampleNotify.cpp +++ b/cpp_utils/tests/BLE Tests/SampleNotify.cpp @@ -49,6 +49,7 @@ class MyNotifyTask: public Task { ESP_LOGD(LOG_TAG, "*** NOTIFY: %d ***", value); pCharacteristic->setValue(&value, 1); pCharacteristic->notify(); + //pCharacteristic->indicate(); value++; } // While 1 } // run @@ -85,7 +86,8 @@ static void run() { BLEUUID(CHARACTERISTIC_UUID), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | - BLECharacteristic::PROPERTY_NOTIFY + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_INDICATE ); // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml diff --git a/cpp_utils/tests/BLE Tests/SampleServer.cpp b/cpp_utils/tests/BLE Tests/SampleServer.cpp index 5332bdca..2628597b 100644 --- a/cpp_utils/tests/BLE Tests/SampleServer.cpp +++ b/cpp_utils/tests/BLE Tests/SampleServer.cpp @@ -17,7 +17,7 @@ class MainBLEServer: public Task { BLE::initServer("MYDEVICE"); BLEServer* pServer = new BLEServer(); - BLEService *pService = pServer->createService(BLEUUID((uint16_t)0x1234)); + BLEService* pService = pServer->createService(BLEUUID((uint16_t)0x1234)); BLECharacteristic* pCharacteristic = pService->createCharacteristic( BLEUUID((uint16_t)0x99AA), diff --git a/cpp_utils/tests/BLE Tests/SampleWrite.cpp b/cpp_utils/tests/BLE Tests/SampleWrite.cpp index ea836fa3..49fa4917 100644 --- a/cpp_utils/tests/BLE Tests/SampleWrite.cpp +++ b/cpp_utils/tests/BLE Tests/SampleWrite.cpp @@ -18,9 +18,10 @@ static char LOG_TAG[] = "SampleWrite"; class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { - if (pCharacteristic->getLength() > 0) { + std::string value = pCharacteristic->getValue(); + if (value.length() > 0) { ESP_LOGD(LOG_TAG, "*********"); - ESP_LOGD(LOG_TAG, "New value: %.2x", pCharacteristic->getValue()[0]); + ESP_LOGD(LOG_TAG, "New value: %.2x", value[0]); ESP_LOGD(LOG_TAG, "*********"); } } From c46542802b8dba7fc613a2359444de63a28fdb6c Mon Sep 17 00:00:00 2001 From: kolban Date: Fri, 21 Jul 2017 23:29:11 -0500 Subject: [PATCH 010/381] sync - 2017-07-21 --- cpp_utils/BLE.cpp | 2 +- cpp_utils/BLE2902.cpp | 2 +- cpp_utils/BLEAdvertisedDevice.cpp | 2 +- cpp_utils/BLEAdvertisedDevice.h | 6 ++++ cpp_utils/BLEAdvertisedDeviceCallbacks.cpp | 2 +- cpp_utils/BLEAdvertisedDeviceCallbacks.h | 21 ------------ cpp_utils/BLEAdvertising.cpp | 2 +- cpp_utils/BLECharacteristic.cpp | 40 ++++++++++------------ cpp_utils/BLECharacteristic.h | 29 ++++++++++++++-- cpp_utils/BLECharacteristicCallbacks.cpp | 4 +-- cpp_utils/BLECharacteristicCallbacks.h | 22 ------------ cpp_utils/BLECharacteristicMap.cpp | 2 +- cpp_utils/BLECharacteristicMap.h | 37 -------------------- cpp_utils/BLEClient.cpp | 18 +++++----- cpp_utils/BLEClient.h | 9 +++-- cpp_utils/BLEClientCallbacks.cpp | 4 +-- cpp_utils/BLEClientCallbacks.h | 21 ------------ cpp_utils/BLEDescriptor.cpp | 2 +- cpp_utils/BLEDescriptorMap.cpp | 2 +- cpp_utils/BLEDescriptorMap.h | 37 -------------------- cpp_utils/BLERemoteCharacteristic.cpp | 4 ++- cpp_utils/BLERemoteService.cpp | 2 +- cpp_utils/BLEScan.cpp | 15 +++++--- cpp_utils/BLEScan.h | 1 - cpp_utils/BLEServer.cpp | 2 +- cpp_utils/BLEServer.h | 32 ++++++++++++++--- cpp_utils/BLEServerCallbacks.cpp | 4 +-- cpp_utils/BLEServerCallbacks.h | 22 ------------ cpp_utils/BLEService.cpp | 2 +- cpp_utils/BLEService.h | 23 ++++++++++++- cpp_utils/BLEServiceMap.cpp | 1 - cpp_utils/BLEServiceMap.h | 34 ------------------ cpp_utils/BLEUUID.cpp | 2 +- cpp_utils/BLEUtils.cpp | 2 +- cpp_utils/BLEValue.cpp | 16 +++++---- cpp_utils/File.cpp | 10 +++--- cpp_utils/FileSystem.cpp | 18 +++++----- cpp_utils/FreeRTOS.cpp | 40 +++++++++++++--------- cpp_utils/FreeRTOS.h | 6 ++-- cpp_utils/GPIO.cpp | 8 ++--- cpp_utils/GeneralUtils.cpp | 6 ++-- cpp_utils/I2C.cpp | 20 +++++------ 42 files changed, 216 insertions(+), 318 deletions(-) delete mode 100644 cpp_utils/BLEAdvertisedDeviceCallbacks.h delete mode 100644 cpp_utils/BLECharacteristicCallbacks.h delete mode 100644 cpp_utils/BLECharacteristicMap.h delete mode 100644 cpp_utils/BLEClientCallbacks.h delete mode 100644 cpp_utils/BLEDescriptorMap.h delete mode 100644 cpp_utils/BLEServerCallbacks.h delete mode 100644 cpp_utils/BLEServiceMap.h diff --git a/cpp_utils/BLE.cpp b/cpp_utils/BLE.cpp index 6db82d30..21867682 100644 --- a/cpp_utils/BLE.cpp +++ b/cpp_utils/BLE.cpp @@ -25,7 +25,7 @@ #include "BLEUtils.h" #include "GeneralUtils.h" -static char LOG_TAG[] = "BLE"; +static const char* LOG_TAG = "BLE"; BLEServer *BLE::m_bleServer = nullptr; BLEScan *BLE::m_pScan = nullptr; diff --git a/cpp_utils/BLE2902.cpp b/cpp_utils/BLE2902.cpp index 783b0582..c7f15580 100644 --- a/cpp_utils/BLE2902.cpp +++ b/cpp_utils/BLE2902.cpp @@ -17,7 +17,7 @@ BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t) 0x2902)) { uint8_t data[2] = {0,0}; setValue(data, 2); -} +} // BLE2902 /** diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 6e9c3306..0693819e 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -17,7 +17,7 @@ #include #include "BLEAdvertisedDevice.h" #include "BLEUtils.h" -static const char LOG_TAG[]="BLEAdvertisedDevice"; +static const char* LOG_TAG="BLEAdvertisedDevice"; BLEAdvertisedDevice::BLEAdvertisedDevice() { m_adFlag = 0; diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index 6e8a1694..2369c993 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -76,5 +76,11 @@ class BLEAdvertisedDevice { int8_t m_txPower; }; +class BLEAdvertisedDeviceCallbacks { +public: + virtual ~BLEAdvertisedDeviceCallbacks() {} + virtual void onResult(BLEAdvertisedDevice* pAdvertisedDevice) = 0; +}; + #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ */ diff --git a/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp b/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp index c84e9cff..915e0690 100644 --- a/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp +++ b/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp @@ -7,5 +7,5 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include "BLEAdvertisedDeviceCallbacks.h" +#include "BLEAdvertisedDevice.h" #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertisedDeviceCallbacks.h b/cpp_utils/BLEAdvertisedDeviceCallbacks.h deleted file mode 100644 index 7b0911d2..00000000 --- a/cpp_utils/BLEAdvertisedDeviceCallbacks.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * BLEAdvertisedDeviceCallback.h - * - * Created on: Jul 3, 2017 - * Author: kolban - */ - -#ifndef COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICECALLBACKS_H_ -#define COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICECALLBACKS_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include "BLEAdvertisedDevice.h" -class BLEAdvertisedDevice; - -class BLEAdvertisedDeviceCallbacks { -public: - virtual ~BLEAdvertisedDeviceCallbacks() {} - virtual void onResult(BLEAdvertisedDevice *pAdvertisedDevice) = 0; -}; -#endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICECALLBACKS_H_ */ diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 499ea249..744ac38f 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -13,7 +13,7 @@ #include "BLEUtils.h" #include "GeneralUtils.h" -static char LOG_TAG[] = "BLEAdvertising"; +static const char* LOG_TAG = "BLEAdvertising"; /** diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 44ef9017..54dffc6d 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -19,7 +19,7 @@ #include "BLE2902.h" #include "GeneralUtils.h" -static char LOG_TAG[] = "BLECharacteristic"; +static const char* LOG_TAG = "BLECharacteristic"; #define NULL_HANDLE (0xffff) @@ -29,13 +29,10 @@ static char LOG_TAG[] = "BLECharacteristic"; * @param [in] properties - Properties for the characteristic. */ BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { - m_bleUUID = uuid; - //m_value.attr_value = static_cast(malloc(ESP_GATT_MAX_ATTR_LEN)); // Allocate storage for the value - //m_value.attr_len = 0; // Initial length of actual data is none. - //m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; // Maximum length of data. - m_handle = NULL_HANDLE; - m_properties = 0; - m_pCallbacks = nullptr; + m_bleUUID = uuid; + m_handle = NULL_HANDLE; + m_properties = 0; + m_pCallbacks = nullptr; setBroadcastProperty((properties & PROPERTY_BROADCAST) !=0); setReadProperty((properties & PROPERTY_READ) !=0); @@ -87,11 +84,14 @@ void BLECharacteristic::executeCreate(BLEService* pService) { control.auto_rsp = ESP_GATT_RSP_BY_APP; m_semaphoreCreateEvt.take("executeCreate"); - esp_attr_value_t value; + std::string strValue = m_value.getValue(); - value.attr_len = strValue.length(); + + esp_attr_value_t value; + value.attr_len = strValue.length(); value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; - value.attr_value = (uint8_t*)strValue.data(); + value.attr_value = (uint8_t*)strValue.data(); + esp_err_t errRc = ::esp_ble_gatts_add_char( m_pService->getHandle(), getUUID().getNative(), @@ -141,15 +141,6 @@ uint16_t BLECharacteristic::getHandle() { } // getHandle -/** - * @brief Get the length of the value. - */ -/* -size_t BLECharacteristic::getLength() { - return m_value.attr_len; -} // getLength -*/ - esp_gatt_char_prop_t BLECharacteristic::getProperties() { return m_properties; } // getProperties @@ -414,6 +405,8 @@ void BLECharacteristic::handleGATTServerEvent( /** * @brief Send an indication. + * An indication is a transmission of up to the first 20 bytes of the characteristic value. An indication + * will block waiting a positive confirmation from the client. * @return N/A */ void BLECharacteristic::indicate() { @@ -462,6 +455,8 @@ void BLECharacteristic::indicate() { /** * @brief Send a notify. + * A notification is a transmission of up to the first 20 bytes of the characteristic value. An notification + * will not block; it is a fire and forget. * @return N/A. */ void BLECharacteristic::notify() { @@ -507,7 +502,9 @@ void BLECharacteristic::notify() { /** * @brief Set the permission to broadcast. - * @param [in] value The value of the property. + * A characteristics has properties associated with it which define what it is capable of doing. + * One of these is the broadcast flag. + * @param [in] value The flag value of the property. * @return N/A */ void BLECharacteristic::setBroadcastProperty(bool value) { @@ -522,6 +519,7 @@ void BLECharacteristic::setBroadcastProperty(bool value) { /** * @brief Set the callback handlers for this characteristic. + * @param [in] pCallbacks An instance of a callbacks structure used to define any callbacks for the characteristic. */ void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) { m_pCallbacks = pCallbacks; diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 3437505f..39ea77f2 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -10,11 +10,10 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) #include +#include #include "BLEUUID.h" #include #include "BLEDescriptor.h" -#include "BLEDescriptorMap.h" -#include "BLECharacteristicCallbacks.h" #include "BLEValue.h" #include "FreeRTOS.h" @@ -22,6 +21,25 @@ class BLEService; class BLEDescriptor; class BLECharacteristicCallbacks; +class BLEDescriptorMap { +public: + void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor); + void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor); + BLEDescriptor *getByUUID(BLEUUID uuid); + BLEDescriptor *getByHandle(uint16_t handle); + std::string toString(); + void handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param); + BLEDescriptor *getFirst(); + BLEDescriptor *getNext(); +private: + std::map m_uuidMap; + std::map m_handleMap; + std::map::iterator m_iterator; +}; + class BLECharacteristic { public: BLECharacteristic(BLEUUID uuid, uint32_t properties = 0); @@ -81,5 +99,12 @@ class BLECharacteristic { FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt"); }; // BLECharacteristic + +class BLECharacteristicCallbacks { +public: + virtual ~BLECharacteristicCallbacks(); + virtual void onRead(BLECharacteristic* pCharacteristic); + virtual void onWrite(BLECharacteristic* pCharacteristic); +}; #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ */ diff --git a/cpp_utils/BLECharacteristicCallbacks.cpp b/cpp_utils/BLECharacteristicCallbacks.cpp index 7f4d8026..b7338659 100644 --- a/cpp_utils/BLECharacteristicCallbacks.cpp +++ b/cpp_utils/BLECharacteristicCallbacks.cpp @@ -6,9 +6,9 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include "BLECharacteristicCallbacks.h" +#include "BLECharacteristic.h" #include -static char LOG_TAG[] = "BLECharacteristicCallbacks"; +static const char* LOG_TAG = "BLECharacteristicCallbacks"; BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {} diff --git a/cpp_utils/BLECharacteristicCallbacks.h b/cpp_utils/BLECharacteristicCallbacks.h deleted file mode 100644 index b6eab72b..00000000 --- a/cpp_utils/BLECharacteristicCallbacks.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * BLECharacteristicCallbacks.h - * - * Created on: Jul 2, 2017 - * Author: kolban - */ - -#ifndef COMPONENTS_CPP_UTILS_BLECHARACTERISTICCALLBACKS_H_ -#define COMPONENTS_CPP_UTILS_BLECHARACTERISTICCALLBACKS_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -class BLECharacteristic; - -class BLECharacteristicCallbacks { -public: - virtual ~BLECharacteristicCallbacks(); - virtual void onRead(BLECharacteristic *pCharacteristic); - virtual void onWrite(BLECharacteristic *pCharacteristic); -}; -#endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTICCALLBACKS_H_ */ diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index 45f0fb35..689692fc 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -8,7 +8,7 @@ #if defined(CONFIG_BT_ENABLED) #include #include -#include "BLECharacteristicMap.h" +#include "BLEService.h" /** diff --git a/cpp_utils/BLECharacteristicMap.h b/cpp_utils/BLECharacteristicMap.h deleted file mode 100644 index ada3aa61..00000000 --- a/cpp_utils/BLECharacteristicMap.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * BLECharacteristicMap.h - * - * Created on: Jun 22, 2017 - * Author: kolban - */ - -#ifndef COMPONENTS_CPP_UTILS_BLECHARACTERISTICMAP_H_ -#define COMPONENTS_CPP_UTILS_BLECHARACTERISTICMAP_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include "BLECharacteristic.h" - -class BLECharacteristicMap { -public: - void setByUUID(BLEUUID uuid, BLECharacteristic *characteristic); - void setByHandle(uint16_t handle, BLECharacteristic *characteristic); - BLECharacteristic *getByUUID(BLEUUID uuid); - BLECharacteristic *getByHandle(uint16_t handle); - BLECharacteristic *getFirst(); - BLECharacteristic *getNext(); - std::string toString(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); - - -private: - std::map m_uuidMap; - std::map m_handleMap; - std::map::iterator m_iterator; -}; - -#endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTICMAP_H_ */ diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 10d6d7e5..73c63e50 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -39,7 +39,7 @@ * * */ -static char LOG_TAG[] = "BLEClient"; +static const char* LOG_TAG = "BLEClient"; BLEClient::BLEClient() { m_pClientCallbacks = nullptr; @@ -52,7 +52,7 @@ BLEClient::BLEClient() { * @brief Connect to the partner. * @param [in] address The address of the partner. */ -void BLEClient::connect(BLEAddress address) { +bool BLEClient::connect(BLEAddress address) { ESP_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str()); // We need the connection handle that we get from registering the application. We register the app // and then block on its completion. When the event has arrived, we will have the handle. @@ -60,10 +60,9 @@ void BLEClient::connect(BLEAddress address) { esp_err_t errRc = esp_ble_gattc_app_register(0); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + return false; } - m_semaphoreRegEvt.take("connect"); - m_semaphoreRegEvt.give(); + m_semaphoreRegEvt.wait("connect"); m_address = address; @@ -75,11 +74,12 @@ void BLEClient::connect(BLEAddress address) { ); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + return false; } - m_semaphoreOpenEvt.take("connect"); - m_semaphoreOpenEvt.give(); - ESP_LOGD(LOG_TAG, "<< connect()"); + + uint32_t rc = m_semaphoreOpenEvt.wait("connect"); + ESP_LOGD(LOG_TAG, "<< connect(), rc=%d", rc==ESP_GATT_OK); + return rc == ESP_GATT_OK; } // connect diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 3facfd12..c109279b 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -18,7 +18,6 @@ #include #include "BLEService.h" #include "BLEAddress.h" -#include "BLEClientCallbacks.h" class BLERemoteService; class BLEClientCallbacks; @@ -29,7 +28,7 @@ class BLEClientCallbacks; class BLEClient { public: BLEClient(); - void connect(BLEAddress address); + bool connect(BLEAddress address); void disconnect(); BLEAddress getAddress(); @@ -64,5 +63,11 @@ class BLEClient { bool m_haveServices; }; // class BLEDevice +class BLEClientCallbacks { +public: + virtual ~BLEClientCallbacks() {}; + virtual void onConnect(BLEClient *pClient); +}; + #endif // CONFIG_BT_ENABLED #endif /* MAIN_BLEDEVICE_H_ */ diff --git a/cpp_utils/BLEClientCallbacks.cpp b/cpp_utils/BLEClientCallbacks.cpp index 59054842..d5c8f952 100644 --- a/cpp_utils/BLEClientCallbacks.cpp +++ b/cpp_utils/BLEClientCallbacks.cpp @@ -6,9 +6,9 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include "BLEClientCallbacks.h" +#include "BLEClient.h" #include -static const char LOG_TAG[] = "BLEClientCallbacks"; +static const char* LOG_TAG = "BLEClientCallbacks"; void BLEClientCallbacks::onConnect(BLEClient* pClient) { ESP_LOGD(LOG_TAG, ">> onConnect(): Default"); diff --git a/cpp_utils/BLEClientCallbacks.h b/cpp_utils/BLEClientCallbacks.h deleted file mode 100644 index 7bef3f94..00000000 --- a/cpp_utils/BLEClientCallbacks.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * BLEClientCallbacks.h - * - * Created on: Jul 5, 2017 - * Author: kolban - */ - -#ifndef COMPONENTS_CPP_UTILS_BLECLIENTCALLBACKS_H_ -#define COMPONENTS_CPP_UTILS_BLECLIENTCALLBACKS_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include "BLEClient.h" - -class BLEClient; -class BLEClientCallbacks { -public: - virtual ~BLEClientCallbacks() {}; - virtual void onConnect(BLEClient *pClient); -}; -#endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLECLIENTCALLBACKS_H_ */ diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index f2f7dcb2..eeb66900 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -17,7 +17,7 @@ #include "BLEDescriptor.h" #include "GeneralUtils.h" -static char LOG_TAG[] = "BLEDescriptor"; +static const char* LOG_TAG = "BLEDescriptor"; #define NULL_HANDLE (0xffff) diff --git a/cpp_utils/BLEDescriptorMap.cpp b/cpp_utils/BLEDescriptorMap.cpp index 5318ddf9..3beb9096 100644 --- a/cpp_utils/BLEDescriptorMap.cpp +++ b/cpp_utils/BLEDescriptorMap.cpp @@ -8,7 +8,7 @@ #if defined(CONFIG_BT_ENABLED) #include #include -#include "BLEDescriptorMap.h" +#include "BLECharacteristic.h" #include "BLEDescriptor.h" #include // ESP32 BLE diff --git a/cpp_utils/BLEDescriptorMap.h b/cpp_utils/BLEDescriptorMap.h deleted file mode 100644 index 09e9a88d..00000000 --- a/cpp_utils/BLEDescriptorMap.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * BLEDescriptorMap.h - * - * Created on: Jun 22, 2017 - * Author: kolban - */ - -#ifndef COMPONENTS_CPP_UTILS_BLEDESCRIPTORMAP_H_ -#define COMPONENTS_CPP_UTILS_BLEDESCRIPTORMAP_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include "BLEUUID.h" -#include // ESP32 BLE - -class BLEDescriptor; - -class BLEDescriptorMap { -public: - void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor); - void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor); - BLEDescriptor *getByUUID(BLEUUID uuid); - BLEDescriptor *getByHandle(uint16_t handle); - std::string toString(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); - BLEDescriptor *getFirst(); - BLEDescriptor *getNext(); -private: - std::map m_uuidMap; - std::map m_handleMap; - std::map::iterator m_iterator; -}; -#endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLEDESCRIPTORMAP_H_ */ diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index a8987056..f714e379 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -19,7 +19,7 @@ #include "GeneralUtils.h" -static const char LOG_TAG[] = "BLERemoteCharacteristic"; +static const char* LOG_TAG = "BLERemoteCharacteristic"; BLERemoteCharacteristic::BLERemoteCharacteristic( esp_gatt_id_t charId, @@ -119,6 +119,7 @@ void BLERemoteCharacteristic::gattClientEventHandler( break; } // ESP_GATTC_REG_FOR_NOTIFY_EVT + // // ESP_GATTC_WRITE_CHAR_EVT // @@ -188,6 +189,7 @@ uint8_t BLERemoteCharacteristic::readUInt8(void) { return 0; } // readUInt8 + /** * @brief Read the value of the remote characteristic. * @return The value of the remote characteristic. diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index 747d28ac..8f8645e7 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -14,7 +14,7 @@ #include #include -static const char LOG_TAG[] = "BLERemoteService"; +static const char* LOG_TAG = "BLERemoteService"; BLERemoteService::BLERemoteService( esp_gatt_srvc_id_t srvcId, diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 0432c632..8771c400 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -18,7 +18,7 @@ #include "BLEUtils.h" #include "GeneralUtils.h" -static char LOG_TAG[] = "BLEScan"; +static const char* LOG_TAG = "BLEScan"; BLEScan::BLEScan() { @@ -29,12 +29,12 @@ BLEScan::BLEScan() { setWindow(100); m_pAdvertisedDeviceCallbacks = nullptr; m_stopped = true; -} +} // BLEScan BLEScan::~BLEScan() { clearAdvertisedDevices(); -} +} // ~BLEScan /** @@ -46,7 +46,7 @@ void BLEScan::clearAdvertisedDevices() { delete m_vectorAvdertisedDevices[i]; } m_vectorAvdertisedDevices.clear(); -} +} // clearAdvertisedDevices /** @@ -57,6 +57,7 @@ void BLEScan::clearAdvertisedDevices() { void BLEScan::gapEventHandler( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) { + switch(event) { // ESP_GAP_BLE_SCAN_RESULT_EVT @@ -102,12 +103,15 @@ void BLEScan::gapEventHandler( break; } + // We now construct a model of the advertised device that we have just found for the first + // time. BLEAdvertisedDevice* pAdvertisedDevice = new BLEAdvertisedDevice(); pAdvertisedDevice->setAddress(advertisedAddress); pAdvertisedDevice->setRSSI(param->scan_rst.rssi); pAdvertisedDevice->setAdFlag(param->scan_rst.flag); - pAdvertisedDevice->parseAdvertisement((uint8_t *)param->scan_rst.ble_adv); + pAdvertisedDevice->parseAdvertisement((uint8_t*)param->scan_rst.ble_adv); pAdvertisedDevice->setScan(this); + m_vectorAvdertisedDevices.push_back(pAdvertisedDevice); if (m_pAdvertisedDeviceCallbacks) { m_pAdvertisedDeviceCallbacks->onResult(pAdvertisedDevice); @@ -131,6 +135,7 @@ void BLEScan::gapEventHandler( } // End switch } // gapEventHandler + void BLEScan::onResults() { ESP_LOGD(LOG_TAG, ">> onResults: default"); ESP_LOGD(LOG_TAG, "<< onResults"); diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index a424ea5e..b5113373 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -13,7 +13,6 @@ #include #include "BLEAdvertisedDevice.h" -#include "BLEAdvertisedDeviceCallbacks.h" #include "BLEClient.h" #include "FreeRTOS.h" diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 242ea18b..87b4bfbd 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -21,7 +21,7 @@ #include #include -static char LOG_TAG[] = "BLEServer"; +static const char* LOG_TAG = "BLEServer"; /** diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index e5cf9ea3..12322883 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -17,11 +17,25 @@ #include "BLEAdvertising.h" #include "BLECharacteristic.h" #include "BLEService.h" -#include "BLECharacteristicMap.h" -#include "BLEServiceMap.h" -#include "BLEServerCallbacks.h" class BLEServerCallbacks; + +class BLEServiceMap { +public: + void setByUUID(BLEUUID uuid, BLEService *service); + void setByHandle(uint16_t handle, BLEService *service); + BLEService *getByUUID(BLEUUID uuid); + BLEService *getByHandle(uint16_t handle); + std::string toString(); + void handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param); + private: + std::map m_uuidMap; + std::map m_handleMap; +}; + class BLEServer { public: BLEServer(); @@ -48,7 +62,17 @@ class BLEServer { FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt"); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); BLEServiceMap m_serviceMap; - BLEServerCallbacks *m_pServerCallbacks; + BLEServerCallbacks* m_pServerCallbacks; }; // BLEServer + +class BLEServerCallbacks { +public: + virtual ~BLEServerCallbacks() {}; + virtual void onConnect(BLEServer* pServer); + virtual void onDisconnect(BLEServer* pServer); +}; // BLEServerCallbacks + + + #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLESERVER_H_ */ diff --git a/cpp_utils/BLEServerCallbacks.cpp b/cpp_utils/BLEServerCallbacks.cpp index cfa4fb7d..88087209 100644 --- a/cpp_utils/BLEServerCallbacks.cpp +++ b/cpp_utils/BLEServerCallbacks.cpp @@ -6,9 +6,9 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include "BLEServerCallbacks.h" +#include "BLEServer.h" #include -static const char LOG_TAG[] = "BLEServerCallbacks"; +static const char* LOG_TAG = "BLEServerCallbacks"; void BLEServerCallbacks::onConnect(BLEServer* pServer) { ESP_LOGD(LOG_TAG, ">> onConnect(): Default"); diff --git a/cpp_utils/BLEServerCallbacks.h b/cpp_utils/BLEServerCallbacks.h deleted file mode 100644 index 4c9d07a6..00000000 --- a/cpp_utils/BLEServerCallbacks.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * BLEServerCallbacks.h - * - * Created on: Jul 4, 2017 - * Author: kolban - */ - -#ifndef COMPONENTS_CPP_UTILS_BLESERVERCALLBACKS_H_ -#define COMPONENTS_CPP_UTILS_BLESERVERCALLBACKS_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include "BLEServer.h" -class BLEServer; - -class BLEServerCallbacks { -public: - virtual ~BLEServerCallbacks() {}; - virtual void onConnect(BLEServer *pServer); - virtual void onDisconnect(BLEServer *pServer); -}; -#endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLESERVERCALLBACKS_H_ */ diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index c5247ff6..c884b2fd 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -24,7 +24,7 @@ #define NULL_HANDLE (0xffff) -static char LOG_TAG[] = "BLEService"; // Tag for logging. +static const char* LOG_TAG = "BLEService"; // Tag for logging. /** * @brief Construct an instance of the BLEService diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index fea31747..c7730093 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -13,13 +13,33 @@ #include #include "BLECharacteristic.h" -#include "BLECharacteristicMap.h" #include "BLEServer.h" #include "BLEUUID.h" #include "FreeRTOS.h" class BLEServer; +class BLECharacteristicMap { +public: + void setByUUID(BLEUUID uuid, BLECharacteristic *characteristic); + void setByHandle(uint16_t handle, BLECharacteristic *characteristic); + BLECharacteristic *getByUUID(BLEUUID uuid); + BLECharacteristic *getByHandle(uint16_t handle); + BLECharacteristic *getFirst(); + BLECharacteristic *getNext(); + std::string toString(); + void handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param); + + +private: + std::map m_uuidMap; + std::map m_handleMap; + std::map::iterator m_iterator; +}; + class BLEService { public: BLEService(BLEUUID uuid); @@ -61,5 +81,6 @@ class BLEService { //void setService(esp_gatt_srvc_id_t srvc_id); }; // BLEService + #endif // CONFIG_BT_ENABLED #endif /* COMPONENTS_CPP_UTILS_BLESERVICE_H_ */ diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index 00fb5f7b..3969a10f 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -8,7 +8,6 @@ #if defined(CONFIG_BT_ENABLED) #include #include -#include "BLEServiceMap.h" #include "BLEService.h" /** diff --git a/cpp_utils/BLEServiceMap.h b/cpp_utils/BLEServiceMap.h deleted file mode 100644 index 3a121ecf..00000000 --- a/cpp_utils/BLEServiceMap.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * BLEServiceMap.h - * - * Created on: Jun 22, 2017 - * Author: kolban - */ - -#ifndef COMPONENTS_CPP_UTILS_BLESERVICEMAP_H_ -#define COMPONENTS_CPP_UTILS_BLESERVICEMAP_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include "BLEUUID.h" -#include - -class BLEService; - -class BLEServiceMap { -public: - void setByUUID(BLEUUID uuid, BLEService *service); - void setByHandle(uint16_t handle, BLEService *service); - BLEService *getByUUID(BLEUUID uuid); - BLEService *getByHandle(uint16_t handle); - std::string toString(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); - private: - std::map m_uuidMap; - std::map m_handleMap; -}; -#endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLESERVICEMAP_H_ */ diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 90d680fe..048ca885 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -12,7 +12,7 @@ #include #include #include "BLEUUID.h" -static const char LOG_TAG[] = "BLEUUID"; +static const char* LOG_TAG = "BLEUUID"; /** diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 93545b36..50e8294e 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -23,7 +23,7 @@ #include #include -static char LOG_TAG[] = "BLEUtils"; +static const char* LOG_TAG = "BLEUtils"; /* static std::map g_addressMap; diff --git a/cpp_utils/BLEValue.cpp b/cpp_utils/BLEValue.cpp index dc63999c..3bc4a333 100644 --- a/cpp_utils/BLEValue.cpp +++ b/cpp_utils/BLEValue.cpp @@ -4,20 +4,22 @@ * Created on: Jul 17, 2017 * Author: kolban */ +#include #include "BLEValue.h" -#include -const char* LOG_TAG="BLEValue"; +static const char* LOG_TAG="BLEValue"; BLEValue::BLEValue() { m_accumulation = ""; m_value = ""; + m_readOffset = 0; } // BLEValue /** * @brief Add a message part to the accumulation. + * The accumulation is a growing set of data that is added to until a commit or cancel. * @param [in] part A message part being added. */ void BLEValue::addPart(std::string part) { @@ -28,6 +30,7 @@ void BLEValue::addPart(std::string part) { /** * @brief Add a message part to the accumulation. + * The accumulation is a growing set of data that is added to until a commit or cancel. * @param [in] pData A message part being added. * @param [in] length The number of bytes being added. */ @@ -43,6 +46,7 @@ void BLEValue::addPart(uint8_t* pData, size_t length) { void BLEValue::cancel() { ESP_LOGD(LOG_TAG, ">> cancel"); m_accumulation = ""; + m_readOffset = 0; } // cancel @@ -60,6 +64,7 @@ void BLEValue::commit() { } setValue(m_accumulation); m_accumulation = ""; + m_readOffset = 0; } // commit @@ -69,7 +74,7 @@ void BLEValue::commit() { */ uint16_t BLEValue::getReadOffset() { return m_readOffset; -} +} // getReadOffset /** @@ -97,14 +102,11 @@ void BLEValue::setValue(std::string value) { } // setValue - - - /** * @brief Set the current value. * @param [in] pData The data for the current value. * @param [in] The length of the new current value. */ void BLEValue::setValue(uint8_t* pData, size_t length) { - m_value = std::string((char *)pData, length); + m_value = std::string((char*)pData, length); } // setValue diff --git a/cpp_utils/File.cpp b/cpp_utils/File.cpp index 0f53bfe4..6b0627af 100644 --- a/cpp_utils/File.cpp +++ b/cpp_utils/File.cpp @@ -13,7 +13,7 @@ #include #include -static const char tag[] = "File"; +static const char* LOG_TAG = "File"; /** * @brief Construct a file. * @param [in] name The name of the file. @@ -32,13 +32,13 @@ File::File(std::string name, uint8_t type) { */ std::string File::getContent(bool base64Encode) { uint32_t size = length(); - ESP_LOGD(tag, "File:: getContent(), name=%s, length=%d", m_name.c_str(), size); + ESP_LOGD(LOG_TAG, "File:: getContent(), name=%s, length=%d", m_name.c_str(), size); if (size == 0) { return ""; } uint8_t *pData = (uint8_t *)malloc(size); if (pData == nullptr) { - ESP_LOGE(tag, "getContent: Failed to allocate memory"); + ESP_LOGE(LOG_TAG, "getContent: Failed to allocate memory"); return ""; } FILE *file = fopen(m_name.c_str(), "r"); @@ -63,14 +63,14 @@ std::string File::getContent(bool base64Encode) { */ std::string File::getContent(uint32_t offset, uint32_t readSize) { uint32_t fileSize = length(); - ESP_LOGD(tag, "File:: getContent(), name=%s, fileSize=%d, offset=%d, readSize=%d", + ESP_LOGD(LOG_TAG, "File:: getContent(), name=%s, fileSize=%d, offset=%d, readSize=%d", m_name.c_str(), fileSize, offset, readSize); if (fileSize == 0 || offset > fileSize) { return ""; } uint8_t *pData = (uint8_t *)malloc(readSize); if (pData == nullptr) { - ESP_LOGE(tag, "getContent: Failed to allocate memory"); + ESP_LOGE(LOG_TAG, "getContent: Failed to allocate memory"); return ""; } FILE *file = fopen(m_name.c_str(), "r"); diff --git a/cpp_utils/FileSystem.cpp b/cpp_utils/FileSystem.cpp index e4eed154..d0240745 100644 --- a/cpp_utils/FileSystem.cpp +++ b/cpp_utils/FileSystem.cpp @@ -17,7 +17,7 @@ #include "FileSystem.h" -static char tag[] = "FileSystem"; +static const char* LOG_TAG = "FileSystem"; /** * @brief Dump a given directory to the log. @@ -27,11 +27,11 @@ static char tag[] = "FileSystem"; void FileSystem::dumpDirectory(std::string path) { DIR *pDir = ::opendir(path.c_str()); if (pDir == nullptr) { - ESP_LOGD(tag, "Unable to open directory: %s [errno=%d]", path.c_str(), errno); + ESP_LOGD(LOG_TAG, "Unable to open directory: %s [errno=%d]", path.c_str(), errno); return; } struct dirent *pDirent; - ESP_LOGD(tag, "Directory dump of %s", path.c_str()); + ESP_LOGD(LOG_TAG, "Directory dump of %s", path.c_str()); while((pDirent = readdir(pDir)) != nullptr) { std::string type; switch(pDirent->d_type) { @@ -45,7 +45,7 @@ void FileSystem::dumpDirectory(std::string path) { type = "Directory"; break; } - ESP_LOGD(tag, "Entry: d_ino: %d, d_name: %s, d_type: %s", pDirent->d_ino, pDirent->d_name, type.c_str()); + ESP_LOGD(LOG_TAG, "Entry: d_ino: %d, d_name: %s, d_type: %s", pDirent->d_ino, pDirent->d_name, type.c_str()); } ::closedir(pDir); } // dumpDirectory @@ -60,11 +60,11 @@ std::vector FileSystem::getDirectoryContents(std::string path) { std::vector ret; DIR *pDir = ::opendir(path.c_str()); if (pDir == nullptr) { - ESP_LOGE(tag, "getDirectoryContents:: Unable to open directory: %s [errno=%d]", path.c_str(), errno); + ESP_LOGE(LOG_TAG, "getDirectoryContents:: Unable to open directory: %s [errno=%d]", path.c_str(), errno); return ret; } struct dirent *pDirent; - ESP_LOGD(tag, "Directory dump of %s", path.c_str()); + ESP_LOGD(LOG_TAG, "Directory dump of %s", path.c_str()); while((pDirent = readdir(pDir)) != nullptr) { File file(std::string(pDirent->d_name), pDirent->d_type); ret.push_back(file); @@ -82,7 +82,7 @@ std::vector FileSystem::getDirectoryContents(std::string path) { int FileSystem::mkdir(std::string path) { int rc = ::mkdir(path.c_str(), 0); if (rc != 0) { - ESP_LOGE(tag, "mkdir: errno=%d", errno); + ESP_LOGE(LOG_TAG, "mkdir: errno=%d", errno); rc = errno; } return rc; @@ -117,7 +117,7 @@ std::vector FileSystem::pathSplit(std::string path) { } // Debug for (int i=0; i FileSystem::pathSplit(std::string path) { int FileSystem::remove(std::string path) { int rc = ::unlink(path.c_str()); if (rc != 0) { - ESP_LOGE(tag, "unlink: errno=%d", errno); + ESP_LOGE(LOG_TAG, "unlink: errno=%d", errno); rc = errno; } return rc; diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 60ab464c..9c5e247b 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -13,15 +13,7 @@ #include #include "sdkconfig.h" -static char TAG[] = "FreeRTOS"; - -FreeRTOS::FreeRTOS() { -} - - -FreeRTOS::~FreeRTOS() { -} - +static const char* LOG_TAG = "FreeRTOS"; /** * Sleep for the specified number of milliseconds. @@ -80,20 +72,23 @@ uint32_t FreeRTOS::getTimeSinceStart() { * @brief Wait for a semaphore to be released by trying to take it and * then releasing it again. * @param [in] owner A debug tag. + * @return The value associated with the semaphore. */ -void FreeRTOS::Semaphore::wait(std::string owner) { - ESP_LOGV(TAG, "Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); +uint32_t FreeRTOS::Semaphore::wait(std::string owner) { + ESP_LOGV(LOG_TAG, "Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); xSemaphoreTake(m_semaphore, portMAX_DELAY); m_owner = owner; xSemaphoreGive(m_semaphore); - ESP_LOGV(TAG, "Semaphore released: %s", toString().c_str()); + ESP_LOGV(LOG_TAG, "Semaphore released: %s", toString().c_str()); m_owner = ""; + return m_value; } // wait FreeRTOS::Semaphore::Semaphore(std::string name) { m_semaphore = xSemaphoreCreateMutex(); m_name = name; m_owner = ""; + m_value = 0; } FreeRTOS::Semaphore::~Semaphore() { @@ -107,11 +102,22 @@ FreeRTOS::Semaphore::~Semaphore() { */ void FreeRTOS::Semaphore::give() { xSemaphoreGive(m_semaphore); - ESP_LOGV(TAG, "Semaphore giving: %s", toString().c_str()); + ESP_LOGV(LOG_TAG, "Semaphore giving: %s", toString().c_str()); m_owner = ""; } // Semaphore::give +/** + * @brief Give a semaphore. + * The Semaphore is given with an associated value. + * @param [in] value The value to associate with the semaphore. + */ +void FreeRTOS::Semaphore::give(uint32_t value) { + m_value = value; + give(); +} + + /** * @brief Take a semaphore. * Take a semaphore and wait indefinitely. @@ -119,10 +125,10 @@ void FreeRTOS::Semaphore::give() { void FreeRTOS::Semaphore::take(std::string owner) { - ESP_LOGV(TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); + ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); xSemaphoreTake(m_semaphore, portMAX_DELAY); m_owner = owner; - ESP_LOGV(TAG, "Semaphore taken: %s", toString().c_str()); + ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take @@ -132,10 +138,10 @@ void FreeRTOS::Semaphore::take(std::string owner) * @param [in] timeoutMs Timeout in milliseconds. */ void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { - ESP_LOGV(TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); + ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); m_owner = owner; xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); - ESP_LOGV(TAG, "Semaphore taken: %s", toString().c_str()); + ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take std::string FreeRTOS::Semaphore::toString() { diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index 60eb2699..f714bd46 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -20,8 +20,6 @@ */ class FreeRTOS { public: - FreeRTOS(); - virtual ~FreeRTOS(); static void sleep(uint32_t ms); static void startTask(void task(void *), std::string taskName, void *param=nullptr, int stackSize = 2048); static void deleteTask(TaskHandle_t pTask = nullptr); @@ -33,15 +31,17 @@ class FreeRTOS { Semaphore(std::string owner = ""); ~Semaphore(); void give(); + void give(uint32_t value); void setName(std::string name); void take(std::string owner=""); void take(uint32_t timeoutMs, std::string owner=""); - void wait(std::string owner=""); + uint32_t wait(std::string owner=""); std::string toString(); private: SemaphoreHandle_t m_semaphore; std::string m_name; std::string m_owner; + uint32_t m_value; }; }; diff --git a/cpp_utils/GPIO.cpp b/cpp_utils/GPIO.cpp index 8c551c0b..6e5b8110 100644 --- a/cpp_utils/GPIO.cpp +++ b/cpp_utils/GPIO.cpp @@ -10,7 +10,7 @@ #include "sdkconfig.h" #include -static char tag[] = "GPIO"; +static const char* LOG_TAG = "GPIO"; /** * @brief Set the pin high. * @@ -46,7 +46,7 @@ bool ESP32CPP::GPIO::inRange(gpio_num_t pin) { void ESP32CPP::GPIO::interruptDisable(gpio_num_t pin) { esp_err_t rc = ::gpio_intr_disable(pin); if (rc != ESP_OK) { - ESP_LOGE(tag, "interruptDisable: %d", rc); + ESP_LOGE(LOG_TAG, "interruptDisable: %d", rc); } } // interruptDisable @@ -59,7 +59,7 @@ void ESP32CPP::GPIO::interruptDisable(gpio_num_t pin) { void ESP32CPP::GPIO::interruptEnable(gpio_num_t pin) { esp_err_t rc = ::gpio_intr_enable(pin); if (rc != ESP_OK) { - ESP_LOGE(tag, "interruptEnable: %d", rc); + ESP_LOGE(LOG_TAG, "interruptEnable: %d", rc); } } // interruptEnable @@ -121,7 +121,7 @@ void ESP32CPP::GPIO::setInterruptType( gpio_int_type_t intrType) { esp_err_t rc = ::gpio_set_intr_type(pin, intrType); if (rc != ESP_OK) { - ESP_LOGE(tag, "setInterruptType: %d", rc); + ESP_LOGE(LOG_TAG, "setInterruptType: %d", rc); } } // setInterruptType diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 4bc15ba6..080c358c 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -17,7 +17,7 @@ #include #include -static char tag[] = "GeneralUtils"; +static const char* LOG_TAG = "GeneralUtils"; static const char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" @@ -279,7 +279,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { strcat(ascii, tempBuf); index++; if (index % 16 == 0) { - ESP_LOGD(tag, "%s %s", hex, ascii); + ESP_LOGD(LOG_TAG, "%s %s", hex, ascii); strcpy(ascii, ""); strcpy(hex, ""); } @@ -289,7 +289,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { strcat(hex, " "); index++; } - ESP_LOGD(tag, "%s %s", hex, ascii); + ESP_LOGD(LOG_TAG, "%s %s", hex, ascii); } } // hexDump diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index 10fee3ad..b3a19923 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -13,7 +13,7 @@ #include "sdkconfig.h" #include -static char tag[] = "I2C"; +static const char* LOG_TAG = "I2C"; static bool driverInstalled = false; static bool debug = false; @@ -38,7 +38,7 @@ I2C::I2C() { */ void I2C::beginTransaction() { if (debug) { - ESP_LOGD(tag, "beginTransaction()"); + ESP_LOGD(LOG_TAG, "beginTransaction()"); } cmd = ::i2c_cmd_link_create(); ESP_ERROR_CHECK(::i2c_master_start(cmd)); @@ -55,7 +55,7 @@ void I2C::beginTransaction() { */ void I2C::endTransaction() { if (debug) { - ESP_LOGD(tag, "endTransaction()"); + ESP_LOGD(LOG_TAG, "endTransaction()"); } ESP_ERROR_CHECK(::i2c_master_stop(cmd)); ESP_ERROR_CHECK(::i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS)); @@ -73,7 +73,7 @@ void I2C::endTransaction() { * @return N/A. */ void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin) { - ESP_LOGD(tag, ">> I2c::init. address=%d, sda=%d, scl=%d", address, sdaPin, sclPin); + ESP_LOGD(LOG_TAG, ">> I2c::init. address=%d, sda=%d, scl=%d", address, sdaPin, sclPin); this->sdaPin = sdaPin; this->sclPin = sclPin; this->address = address; @@ -102,7 +102,7 @@ void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin) { */ void I2C::read(uint8_t* bytes, size_t length, bool ack) { if (debug) { - ESP_LOGD(tag, "read(size=%d, ack=%d)", length, ack); + ESP_LOGD(LOG_TAG, "read(size=%d, ack=%d)", length, ack); } if (directionKnown == false) { directionKnown = true; @@ -121,7 +121,7 @@ void I2C::read(uint8_t* bytes, size_t length, bool ack) { */ void I2C::read(uint8_t *byte, bool ack) { if (debug) { - ESP_LOGD(tag, "read(size=1, ack=%d)", ack); + ESP_LOGD(LOG_TAG, "read(size=1, ack=%d)", ack); } if (directionKnown == false) { directionKnown = true; @@ -182,7 +182,7 @@ void I2C::setDebug(bool enabled) { */ void I2C::start() { if (debug) { - ESP_LOGD(tag, "start()"); + ESP_LOGD(LOG_TAG, "start()"); } ESP_ERROR_CHECK(::i2c_master_start(cmd)); } // start @@ -194,7 +194,7 @@ void I2C::start() { */ void I2C::stop() { if (debug) { - ESP_LOGD(tag, "stop()"); + ESP_LOGD(LOG_TAG, "stop()"); } ESP_ERROR_CHECK(::i2c_master_stop(cmd)); directionKnown = false; @@ -210,7 +210,7 @@ void I2C::stop() { */ void I2C::write(uint8_t byte, bool ack) { if (debug) { - ESP_LOGD(tag, "write(val=0x%.2x, ack=%d)", byte, ack); + ESP_LOGD(LOG_TAG, "write(val=0x%.2x, ack=%d)", byte, ack); } if (directionKnown == false) { directionKnown = true; @@ -230,7 +230,7 @@ void I2C::write(uint8_t byte, bool ack) { */ void I2C::write(uint8_t *bytes, size_t length, bool ack) { if (debug) { - ESP_LOGD(tag, "write(length=%d, ack=%d)", length, ack); + ESP_LOGD(LOG_TAG, "write(length=%d, ack=%d)", length, ack); } if (directionKnown == false) { directionKnown = true; From 232a173ec2f170cb6295291dd23398487e2518d0 Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 29 Jul 2017 13:47:02 -0500 Subject: [PATCH 011/381] Sync commit - 2017-07-09 --- cpp_utils/BLE.cpp | 56 +-- cpp_utils/BLE2902.cpp | 4 +- cpp_utils/BLE2902.h | 3 +- cpp_utils/BLEAddress.cpp | 9 +- cpp_utils/BLEAddress.h | 6 + cpp_utils/BLEAdvertisedDevice.cpp | 33 +- cpp_utils/BLEAdvertisedDevice.h | 19 + cpp_utils/BLEAdvertising.h | 6 + cpp_utils/BLECharacteristic.h | 44 ++- cpp_utils/BLEClient.cpp | 28 +- cpp_utils/BLEClient.h | 22 +- cpp_utils/BLEDescriptor.h | 19 +- cpp_utils/BLERemoteCharacteristic.cpp | 17 +- cpp_utils/BLERemoteCharacteristic.h | 3 + cpp_utils/BLERemoteDescriptor.h | 4 + cpp_utils/BLERemoteService.cpp | 22 +- cpp_utils/BLERemoteService.h | 6 +- cpp_utils/BLEScan.cpp | 2 + cpp_utils/BLEScan.h | 15 +- cpp_utils/BLEServer.cpp | 50 ++- cpp_utils/BLEServer.h | 57 ++- cpp_utils/BLEService.h | 36 +- cpp_utils/BLEUUID.cpp | 69 +++- cpp_utils/BLEUUID.h | 7 +- cpp_utils/BLEUtils.cpp | 8 +- cpp_utils/BLEUtils.h | 26 +- cpp_utils/BLEValue.cpp | 4 + cpp_utils/BLEValue.h | 7 +- cpp_utils/Doxyfile | 23 +- cpp_utils/FreeRTOS.cpp | 9 + cpp_utils/FreeRTOS.h | 1 + cpp_utils/GPIO.cpp | 60 ++- cpp_utils/GPIO.h | 2 + cpp_utils/I2S.cpp | 410 ++++++++++++++++++++ cpp_utils/I2S.h | 62 +++ cpp_utils/OV7670.cpp | 317 +++++++++++++-- cpp_utils/OV7670.h | 18 + cpp_utils/Task.cpp | 28 +- cpp_utils/Task.h | 12 +- cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleClient.cpp | 103 ++--- cpp_utils/tests/BLE Tests/SampleScan.cpp | 2 +- 42 files changed, 1343 insertions(+), 288 deletions(-) create mode 100644 cpp_utils/I2S.cpp create mode 100644 cpp_utils/I2S.h diff --git a/cpp_utils/BLE.cpp b/cpp_utils/BLE.cpp index 21867682..49596f67 100644 --- a/cpp_utils/BLE.cpp +++ b/cpp_utils/BLE.cpp @@ -6,7 +6,7 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) - +#include #include #include #include // ESP32 BLE @@ -131,18 +131,19 @@ void BLE::gapEventHandler( } // gapEventHandler -/** - * @brief Initialize the server %BLE environment. - * - */ - void BLE::initServer(std::string deviceName) { - esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - esp_err_t errRc = esp_bt_controller_init(&bt_cfg); +static void commonInit() { + esp_err_t errRc = ::nvs_flash_init(); if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + errRc = esp_bt_controller_init(&bt_cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); if (errRc != ESP_OK) { @@ -161,8 +162,16 @@ void BLE::gapEventHandler( ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } +} // commonInit + +/** + * @brief Initialize the server %BLE environment. + * + */ +void BLE::initServer(std::string deviceName) { + commonInit(); - errRc = esp_ble_gap_register_callback(BLE::gapEventHandler); + esp_err_t errRc = esp_ble_gap_register_callback(BLE::gapEventHandler); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; @@ -195,32 +204,9 @@ void BLE::gapEventHandler( * @brief Initialize the client %BLE environment. */ void BLE::initClient() { - esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - esp_err_t errRc = esp_bt_controller_init(&bt_cfg); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - errRc = esp_bluedroid_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - errRc = esp_bluedroid_enable(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + commonInit(); - errRc = esp_ble_gap_register_callback(BLE::gapEventHandler); + esp_err_t errRc = esp_ble_gap_register_callback(BLE::gapEventHandler); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; diff --git a/cpp_utils/BLE2902.cpp b/cpp_utils/BLE2902.cpp index c7f15580..8984da81 100644 --- a/cpp_utils/BLE2902.cpp +++ b/cpp_utils/BLE2902.cpp @@ -22,7 +22,7 @@ BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t) 0x2902)) { /** * @brief Get the notifications value. - * @return The notifications value. + * @return The notifications value. True if notifications are enabled and false if not. */ bool BLE2902::getNotifications() { return (getValue()[0] & (1 << 0)) != 0; @@ -31,7 +31,7 @@ bool BLE2902::getNotifications() { /** * @brief Get the indications value. - * @return The indications value. + * @return The indications value. True if indications are enabled and false if not. */ bool BLE2902::getIndications() { return (getValue()[0] & (1 << 1)) != 0; diff --git a/cpp_utils/BLE2902.h b/cpp_utils/BLE2902.h index a7b5e874..397360ab 100644 --- a/cpp_utils/BLE2902.h +++ b/cpp_utils/BLE2902.h @@ -12,7 +12,7 @@ #include "BLEDescriptor.h" -/* +/** * @brief Descriptor for Client Characteristic Configuration. * * This is a convenience descriptor for the Client Characteristic Configuration which has a UUID of 0x2902. @@ -20,7 +20,6 @@ * See also: * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml */ - class BLE2902: public BLEDescriptor { public: BLE2902(); diff --git a/cpp_utils/BLEAddress.cpp b/cpp_utils/BLEAddress.cpp index 44bbfb78..d2f7f8b0 100644 --- a/cpp_utils/BLEAddress.cpp +++ b/cpp_utils/BLEAddress.cpp @@ -16,7 +16,7 @@ /** - * @brief Create an address from the native representation. + * @brief Create an address from the native ESP32 representation. * @param [in] address The native representation. */ BLEAddress::BLEAddress(esp_bd_addr_t address) { @@ -26,6 +26,7 @@ BLEAddress::BLEAddress(esp_bd_addr_t address) { /** * @brief Create an address from a hex string + * * A hex string is of the format: * ``` * 00:00:00:00:00:00 @@ -71,6 +72,12 @@ esp_bd_addr_t *BLEAddress::getNative() { /** * @brief Convert a BLE address to a string. * + * A string representation of an address is in the format: + * + * ``` + * xx:xx:xx:xx:xx:xx + * ``` + * * @return The string representation of the address. */ std::string BLEAddress::toString() { diff --git a/cpp_utils/BLEAddress.h b/cpp_utils/BLEAddress.h index 994a5699..7eff4da4 100644 --- a/cpp_utils/BLEAddress.h +++ b/cpp_utils/BLEAddress.h @@ -12,6 +12,12 @@ #include // ESP32 BLE #include + +/** + * @brief A %BLE device address. + * + * Every %BLE device has a unique address which can be used to identify it and form connections. + */ class BLEAddress { public: BLEAddress(esp_bd_addr_t address); diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 0693819e..a34ad5bc 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -41,6 +41,10 @@ BLEAdvertisedDevice::BLEAdvertisedDevice() { /** * @brief Get the address. + * + * Every %BLE device exposes an address that is used to identify it and subsequently connect to it. + * Call this function to obtain the address of the advertised device. + * * @return The address of the advertised device. */ BLEAddress BLEAdvertisedDevice::getAddress() { @@ -50,6 +54,10 @@ BLEAddress BLEAdvertisedDevice::getAddress() { /** * @brief Get the appearance. + * + * A %BLE device can declare its own appearance. The appearance is how it would like to be shown to an end user + * typcially in the form of an icon. + * * @return The appearance of the advertised device. */ uint16_t BLEAdvertisedDevice::getApperance() { @@ -110,32 +118,55 @@ int8_t BLEAdvertisedDevice::getTXPower() { return m_txPower; } // getTXPower - +/** + * @brief Does this advertisement have an appearance value? + * @return True if there is an appearance value present. + */ bool BLEAdvertisedDevice::haveAppearance() { return m_haveAppearance; } // haveAppearance +/** + * @brief Does this advertisement have manufacturer data? + * @return True if there is manufacturer data present. + */ bool BLEAdvertisedDevice::haveManufacturerData() { return m_haveManufacturerData; } // haveManufacturerData +/** + * @brief Does this advertisement have a name value? + * @return True if there is a name value present. + */ bool BLEAdvertisedDevice::haveName() { return m_haveName; } // haveName +/** + * @brief Does this advertisement have a signal strength value? + * @return True if there is a signal strength value present. + */ bool BLEAdvertisedDevice::haveRSSI() { return m_haveRSSI; } // haveRSSI +/** + * @brief Does this advertisement have a service UUID value? + * @return True if there is a service UUID value present. + */ bool BLEAdvertisedDevice::haveServiceUUID() { return m_haveServiceUUID; } // haveServiceUUID +/** + * @brief Does this advertisement have a transmission power value? + * @return True if there is a transmission power value present. + */ bool BLEAdvertisedDevice::haveTXPower() { return m_haveTXPower; } // haveTXPower diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index 2369c993..c02f1762 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -19,6 +19,12 @@ class BLEScan; +/** + * @brief A representation of a %BLE advertised device found by a scan. + * + * When we perform a %BLE scan, the result will be a set of devices that are advertising. This + * class provides a model of a detected device. + */ class BLEAdvertisedDevice { public: BLEAdvertisedDevice(); @@ -76,9 +82,22 @@ class BLEAdvertisedDevice { int8_t m_txPower; }; +/** + * @brief A callback handler for callbacks associated device scanning. + * + * When we are performing a scan as a %BLE client, we may wish to know when a new device that is advertising + * has been found. This class can be sub-classed and registered such that when a scan is performed and + * a new advertised device has been found, we will be called back to be notified. + */ class BLEAdvertisedDeviceCallbacks { public: virtual ~BLEAdvertisedDeviceCallbacks() {} + /** + * @brief Called when a new scan result is detected. + * + * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the + * device that was found. During any individual scan, a device will only be detected one time. + */ virtual void onResult(BLEAdvertisedDevice* pAdvertisedDevice) = 0; }; diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 42f3a15a..5c86bd96 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -11,6 +11,12 @@ #if defined(CONFIG_BT_ENABLED) #include #include "BLEUUID.h" + +/** + * @brief Perform and manage %BLE advertising. + * + * A %BLE server will want to perform advertising in order to make itself known to %BLE clients. + */ class BLEAdvertising { public: BLEAdvertising(); diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 39ea77f2..854c323c 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -21,6 +21,9 @@ class BLEService; class BLEDescriptor; class BLECharacteristicCallbacks; +/** + * @brief A management structure for %BLE descriptors. + */ class BLEDescriptorMap { public: void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor); @@ -40,12 +43,19 @@ class BLEDescriptorMap { std::map::iterator m_iterator; }; + +/** + * @brief The model of a %BLE Characteristic. + * + * A %BLE Characteristic is an identified value container that manages a value. It is exposed by a %BLE server and + * can be read and written to by a %BLE client. + */ class BLECharacteristic { public: BLECharacteristic(BLEUUID uuid, uint32_t properties = 0); virtual ~BLECharacteristic(); - void addDescriptor(BLEDescriptor *pDescriptor); + void addDescriptor(BLEDescriptor* pDescriptor); BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); //size_t getLength(); BLEUUID getUUID(); @@ -54,11 +64,11 @@ class BLECharacteristic { void indicate(); void notify(); void setBroadcastProperty(bool value); - void setCallbacks(BLECharacteristicCallbacks *pCallbacks); + void setCallbacks(BLECharacteristicCallbacks* pCallbacks); void setIndicateProperty(bool value); void setNotifyProperty(bool value); void setReadProperty(bool value); - void setValue(uint8_t *data, size_t size); + void setValue(uint8_t* data, size_t size); void setValue(std::string value); void setWriteProperty(bool value); void setWriteNoResponseProperty(bool value); @@ -78,28 +88,36 @@ class BLECharacteristic { friend class BLEDescriptor; friend class BLECharacteristicMap; - BLEUUID m_bleUUID; - esp_gatt_char_prop_t m_properties; - BLEValue m_value; - uint16_t m_handle; - BLEService *m_pService; - BLEDescriptorMap m_descriptorMap; - BLECharacteristicCallbacks *m_pCallbacks; + BLEUUID m_bleUUID; + BLEDescriptorMap m_descriptorMap; + uint16_t m_handle; + esp_gatt_char_prop_t m_properties; + BLECharacteristicCallbacks* m_pCallbacks; + BLEService* m_pService; + BLEValue m_value; void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); + esp_ble_gatts_cb_param_t* param); - void executeCreate(BLEService *pService); + void executeCreate(BLEService* pService); uint16_t getHandle(); esp_gatt_char_prop_t getProperties(); - BLEService *getService(); + BLEService* getService(); void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt"); }; // BLECharacteristic + +/** + * @brief Callbacks that can be associated with a %BLE characteristic to inform of events. + * + * When a server application creates a %BLE characteristic, we may wish to be informed when there is either + * a read or write request to the characteristic's value. An application can register a + * sub-classed instance of this class and will be notified when such an event happens. + */ class BLECharacteristicCallbacks { public: virtual ~BLECharacteristicCallbacks(); diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 73c63e50..d4704f6d 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -64,13 +64,13 @@ bool BLEClient::connect(BLEAddress address) { } m_semaphoreRegEvt.wait("connect"); + m_peerAddress = address; - m_address = address; m_semaphoreOpenEvt.take("connect"); errRc = ::esp_ble_gattc_open( getGattcIf(), - *getAddress().getNative(), // address - 1 // direct connection + *getPeerAddress().getNative(), // address + 1 // direct connection ); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -94,9 +94,11 @@ void BLEClient::disconnect() { ESP_LOGE(LOG_TAG, "esp_ble_gattc_close: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + m_peerAddress = BLEAddress("00:00:00:00:00:00"); ESP_LOGD(LOG_TAG, "<< disconnect()"); } // disconnect + /** * @brief Handle GATT Client events */ @@ -180,8 +182,13 @@ void BLEClient::gattClientEventHandler( } // gattClientEventHandler -BLEAddress BLEClient::getAddress() { - return m_address; +/** + * @brief Retrieve the address of the peer. + * + * Returns the address of the %BLE peer to which this client is connected. + */ +BLEAddress BLEClient::getPeerAddress() { + return m_peerAddress; } // getAddress @@ -221,8 +228,8 @@ BLERemoteService* BLEClient::getService(BLEUUID uuid) { /** - * @brief Ask the remote BLE server for its services. - * A BLE Server exposes a set of services for its partners. Here we ask the server for its set of + * @brief Ask the remote %BLE server for its services. + * A %BLE Server exposes a set of services for its partners. Here we ask the server for its set of * services and wait until we have received them all. * @return N/A */ @@ -246,9 +253,8 @@ std::map* BLEClient::getServices() { ESP_LOGE(LOG_TAG, "esp_ble_gattc_search_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return &m_servicesMap; } - m_semaphoreSearchCmplEvt.take("getServices"); - m_semaphoreSearchCmplEvt.give(); - m_haveServices = true; + m_semaphoreSearchCmplEvt.wait("getServices"); + m_haveServices = true; // Remember that we now have services. ESP_LOGD(LOG_TAG, "<< getServices"); return &m_servicesMap; } // getServices @@ -269,7 +275,7 @@ void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks) { */ std::string BLEClient::toString() { std::ostringstream ss; - ss << "address: " << m_address.toString(); + ss << "peer address: " << m_peerAddress.toString(); ss << "\nServices:\n"; for (auto &myPair : m_servicesMap) { ss << myPair.second->toString() << "\n"; diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index c109279b..b66b75c2 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -23,17 +23,15 @@ class BLERemoteService; class BLEClientCallbacks; /** - * @brief A %BLE device. + * @brief A model of a %BLE client. */ class BLEClient { public: BLEClient(); bool connect(BLEAddress address); void disconnect(); - - BLEAddress getAddress(); - - std::map *getServices(); + BLEAddress getPeerAddress(); + std::map* getServices(); BLERemoteService* getService(BLEUUID uuid); void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); std::string toString(); @@ -46,23 +44,27 @@ class BLEClient { void gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param); + esp_ble_gattc_cb_param_t* param); uint16_t getConnId(); esp_gatt_if_t getGattcIf(); - BLEAddress m_address = BLEAddress((uint8_t *)"\0\0\0\0\0\0"); + BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); uint16_t m_conn_id; // int m_deviceType; esp_gatt_if_t m_gattc_if; - BLEClientCallbacks *m_pClientCallbacks; + BLEClientCallbacks* m_pClientCallbacks; FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt"); - std::map m_servicesMap; - bool m_haveServices; + std::map m_servicesMap; + bool m_haveServices; // Have we previously obtain the set of services. }; // class BLEDevice + +/** + * @brief Callbacks associated with a %BLE client. + */ class BLEClientCallbacks { public: virtual ~BLEClientCallbacks() {}; diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index a2c05fac..089ff4cb 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -18,30 +18,33 @@ class BLEService; class BLECharacteristic; +/** + * @brief A model of a %BLE descriptor. + */ class BLEDescriptor { public: BLEDescriptor(BLEUUID uuid); virtual ~BLEDescriptor(); - size_t getLength(); - BLEUUID getUUID(); - uint8_t *getValue(); + size_t getLength(); + BLEUUID getUUID(); + uint8_t* getValue(); void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); - void setValue(uint8_t *data, size_t size); + esp_ble_gatts_cb_param_t* param); + void setValue(uint8_t* data, size_t size); void setValue(std::string value); std::string toString(); private: friend class BLEDescriptorMap; friend class BLECharacteristic; - BLEUUID m_bleUUID; + BLEUUID m_bleUUID; esp_attr_value_t m_value; uint16_t m_handle; - BLECharacteristic *m_pCharacteristic; - void executeCreate(BLECharacteristic *pCharacteristic); + BLECharacteristic* m_pCharacteristic; + void executeCreate(BLECharacteristic* pCharacteristic); uint16_t getHandle(); void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index f714e379..c1f826bd 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -95,7 +95,10 @@ void BLERemoteCharacteristic::gattClientEventHandler( if (evtParam->read.status == ESP_GATT_OK) { m_value = std::string((char*)evtParam->read.value, evtParam->read.value_len); + } else { + m_value = ""; } + m_semaphoreReadCharEvt.give(); break; } // ESP_GATTC_READ_CHAR_EVT @@ -199,6 +202,7 @@ std::string BLERemoteCharacteristic::readValue() { m_semaphoreReadCharEvt.take("readValue"); + // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. esp_err_t errRc = ::esp_ble_gattc_read_char( m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getConnId(), @@ -211,8 +215,9 @@ std::string BLERemoteCharacteristic::readValue() { return ""; } - m_semaphoreReadCharEvt.take("readValue"); - m_semaphoreReadCharEvt.give(); + // Block waiting for the event that indicates that the read has completed. When it has, the std::string found + // in m_value will contain our data. + m_semaphoreReadCharEvt.wait("readValue"); ESP_LOGD(LOG_TAG, "<< readValue()"); return m_value; @@ -230,7 +235,7 @@ void BLERemoteCharacteristic::registerForNotify() { esp_err_t errRc = ::esp_ble_gattc_register_for_notify( m_pRemoteService->getClient()->getGattcIf(), - *m_pRemoteService->getClient()->getAddress().getNative(), + *m_pRemoteService->getClient()->getPeerAddress().getNative(), m_pRemoteService->getSrvcId(), &m_charId); @@ -239,8 +244,7 @@ void BLERemoteCharacteristic::registerForNotify() { return; } - m_semaphoreRegForNotifyEvt.take("registerForNotify"); - m_semaphoreRegForNotifyEvt.give(); + m_semaphoreRegForNotifyEvt.wait("registerForNotify"); ESP_LOGD(LOG_TAG, "<< registerForNotify()"); } // registerForNotify @@ -285,8 +289,7 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { return; } - m_semaphoreWriteCharEvt.take("writeValue"); - m_semaphoreWriteCharEvt.give(); + m_semaphoreWriteCharEvt.wait("writeValue"); ESP_LOGD(LOG_TAG, "<< writeValue"); } // writeValue diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index e98dc5e3..02fcf9b8 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -19,6 +19,9 @@ class BLERemoteService; +/** + * @brief A model of a remote %BLE characteristic. + */ class BLERemoteCharacteristic { public: BLERemoteCharacteristic(esp_gatt_id_t charId, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); diff --git a/cpp_utils/BLERemoteDescriptor.h b/cpp_utils/BLERemoteDescriptor.h index a52c8b1c..a8d944d4 100644 --- a/cpp_utils/BLERemoteDescriptor.h +++ b/cpp_utils/BLERemoteDescriptor.h @@ -9,6 +9,10 @@ #define COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) + +/** + * @brief A model of remote %BLE descriptor. + */ class BLERemoteDescriptor { public: }; diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index 8f8645e7..2821f563 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -72,14 +72,20 @@ void BLERemoteService::gattClientEventHandler( break; } - m_characteristicMap.insert(std::pair( + // This is an indication that we now have the characteristic details for a characteristic owned + // by this service so remember it. + m_characteristicMap.insert(std::pair( BLEUUID(evtParam->get_char.char_id.uuid).toString(), new BLERemoteCharacteristic(evtParam->get_char.char_id, evtParam->get_char.char_prop, this) )); + + /* ::esp_ble_gattc_get_characteristic( m_pClient->getGattcIf(), m_pClient->getConnId(), &m_srvcId, &evtParam->get_char.char_id); + */ + m_semaphoreGetCharEvt.give(); break; } // ESP_GATTC_GET_CHAR_EVT @@ -88,6 +94,7 @@ void BLERemoteService::gattClientEventHandler( } } // switch + // Send the event to each of the characteristics owned by this service. for (auto &myPair : m_characteristicMap) { myPair.second->gattClientEventHandler(event, gattc_if, evtParam); } @@ -126,20 +133,25 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { void BLERemoteService::getCharacteristics() { ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); - removeCharacteristics(); + + removeCharacteristics(); // Forget any previous characteristics. + m_semaphoreGetCharEvt.take("getCharacteristics"); + esp_err_t errRc = ::esp_ble_gattc_get_characteristic( m_pClient->getGattcIf(), m_pClient->getConnId(), &m_srvcId, nullptr); + if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } - m_semaphoreGetCharEvt.take("getCharacteristics"); - m_semaphoreGetCharEvt.give(); - m_haveCharacteristics = true; + + m_semaphoreGetCharEvt.wait("getCharacteristics"); // Wait for the characteristics to become available. + + m_haveCharacteristics = true; // Remember that we have received the characteristics. ESP_LOGD(LOG_TAG, "<< getCharacteristics()"); } // getCharacteristics diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index ad098e30..c73376aa 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -20,6 +20,10 @@ class BLEClient; class BLERemoteCharacteristic; + +/** + * @brief A model of a remote %BLE service. + */ class BLERemoteService { public: BLERemoteService(esp_gatt_srvc_id_t srvcId, BLEClient* pClient); @@ -47,7 +51,7 @@ class BLERemoteService { // Properties std::map m_characteristicMap; - bool m_haveCharacteristics; + bool m_haveCharacteristics; // Have we previously obtained the characteristics. BLEClient* m_pClient; FreeRTOS::Semaphore m_semaphoreGetCharEvt = FreeRTOS::Semaphore("GetCharEvt"); esp_gatt_srvc_id_t m_srvcId; diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 8771c400..e53cd41a 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -136,10 +136,12 @@ void BLEScan::gapEventHandler( } // gapEventHandler +/* void BLEScan::onResults() { ESP_LOGD(LOG_TAG, ">> onResults: default"); ESP_LOGD(LOG_TAG, "<< onResults"); } // onResults +*/ /** diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index b5113373..e9a6d90d 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -20,14 +20,17 @@ class BLEAdvertisedDevice; class BLEAdvertisedDeviceCallbacks; class BLEClient; +/** + * @brief Perform and manage %BLE scans. + * + * Scanning is associated with a %BLE client that is attempting to locate BLE servers. + */ class BLEScan { public: BLEScan(); virtual ~BLEScan(); - void gapEventHandler( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param); - virtual void onResults(); + + //virtual void onResults(); void setActiveScan(bool active); void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks); void setInterval(uint16_t intervalMSecs); @@ -37,7 +40,9 @@ class BLEScan { private: friend class BLE; - + void gapEventHandler( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); void clearAdvertisedDevices(); void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 87b4bfbd..7492310c 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -25,14 +25,18 @@ static const char* LOG_TAG = "BLEServer"; /** - * Construct a BLE Server + * @brief Construct a %BLE Server + * + * This class is not designed to be individually instantiated. Instead one should create a server by asking + * the BLE device class. */ BLEServer::BLEServer() { - m_appId = -1; - m_gatts_if = -1; - m_connId = -1; - BLE::m_bleServer = this; + m_appId = -1; + m_gatts_if = -1; + m_connId = -1; + BLE::m_bleServer = this; m_pServerCallbacks = nullptr; + createApp(0); } // BLEServer @@ -43,13 +47,14 @@ void BLEServer::createApp(uint16_t appId) { } /** - * @brief Create a BLE Service. - * With a BLE server, we can host one or more services. Invoking this function causes the creation of a definition + * @brief Create a %BLE Service. + * + * With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition * of a new service. Every service must have a unique UUID. * @param [in] uuid The UUID of the new service. * @return A reference to the new service object. */ -BLEService *BLEServer::createService(BLEUUID uuid) { +BLEService* BLEServer::createService(BLEUUID uuid) { ESP_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str()); m_semaphoreCreateEvt.take("createService"); @@ -61,7 +66,7 @@ BLEService *BLEServer::createService(BLEUUID uuid) { return nullptr; } - BLEService *pService = new BLEService(uuid); + BLEService* pService = new BLEService(uuid); m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server. pService->executeCreate(this); // Perform the API calls to actually create the service. @@ -72,6 +77,11 @@ BLEService *BLEServer::createService(BLEUUID uuid) { } // createService +/** + * @brief Retrieve the advertising object that can be used to advertise the existence of the server. + * + * @return An advertising object. + */ BLEAdvertising* BLEServer::getAdvertising() { return &m_bleAdvertising; } @@ -86,12 +96,13 @@ uint16_t BLEServer::getGattsIf() { /** * @brief Handle a receiver GAP event. + * * @param [in] event * @param [in] param */ void BLEServer::handleGAPEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t *param) { + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param) { ESP_LOGD(LOG_TAG, "BLEServer ... handling GAP event!"); switch(event) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { @@ -121,6 +132,7 @@ void BLEServer::handleGAPEvent( /** * @brief Handle a GATT Server Event. + * * @param [in] event * @param [in] gatts_if * @param [in] param @@ -129,7 +141,7 @@ void BLEServer::handleGAPEvent( void BLEServer::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { + esp_ble_gatts_cb_param_t* param) { ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); @@ -175,7 +187,7 @@ void BLEServer::handleGATTServerEvent( // * esp_gatt_srvc_id_t service_id // case ESP_GATTS_CREATE_EVT: { - BLEService *pService = m_serviceMap.getByUUID(param->create.service_id.id.uuid); + BLEService* pService = m_serviceMap.getByUUID(param->create.service_id.id.uuid); m_serviceMap.setByHandle(param->create.service_handle, pService); //pService->setHandle(param->create.service_handle); m_semaphoreCreateEvt.give(); @@ -217,6 +229,8 @@ void BLEServer::handleGATTServerEvent( } // ESP_GATTS_DISCONNECT_EVT + // If we receive a disconnect event then invoke the callback for disconnects (if one is present). + // we also want to start advertising again. case ESP_GATTS_DISCONNECT_EVT: { if (m_pServerCallbacks != nullptr) { m_pServerCallbacks->onDisconnect(this); @@ -247,6 +261,7 @@ void BLEServer::handleGATTServerEvent( /** * @brief Register the app. + * * @return N/A */ void BLEServer::registerApp() { @@ -260,6 +275,11 @@ void BLEServer::registerApp() { /** * @brief Set the callbacks. + * + * As a %BLE server operates, it will generate server level events such as a new client connecting or a previous client + * disconnecting. This function can be called to register a callback handler that will be invoked when these + * events are detected. + * * @param [in] pCallbacks The callbacks to be invoked. */ void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { @@ -269,7 +289,9 @@ void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { /** * @brief Start advertising. - * Start the server advertising its existence. + * + * Start the server advertising its existence. This is a convenience function and is equivalent to + * retrieving the advertising object and invoking start upon it. */ void BLEServer::startAdvertising() { ESP_LOGD(LOG_TAG, ">> startAdvertising"); diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 12322883..c09d81a6 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -20,40 +20,45 @@ class BLEServerCallbacks; + +/** + * @brief A data structure that manages the %BLE servers owned by a BLE server. + */ class BLEServiceMap { public: - void setByUUID(BLEUUID uuid, BLEService *service); - void setByHandle(uint16_t handle, BLEService *service); - BLEService *getByUUID(BLEUUID uuid); - BLEService *getByHandle(uint16_t handle); + void setByUUID(BLEUUID uuid, BLEService* service); + void setByHandle(uint16_t handle, BLEService* service); + BLEService* getByUUID(BLEUUID uuid); + BLEService* getByHandle(uint16_t handle); std::string toString(); void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); private: - std::map m_uuidMap; - std::map m_handleMap; + std::map m_uuidMap; + std::map m_handleMap; }; + +/** + * @brief The model of a %BLE server. + */ class BLEServer { public: BLEServer(); - void createApp(uint16_t appId); + BLEService* createService(BLEUUID uuid); BLEAdvertising* getAdvertising(); - uint16_t getConnId(); - uint16_t getGattsIf(); - void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); - void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); - void registerApp(); void setCallbacks(BLEServerCallbacks *pCallbacks); - void setUUID(uint8_t uuid[32]); void startAdvertising(); private: + friend class BLEService; + friend class BLECharacteristic; + friend class BLE; esp_ble_adv_data_t m_adv_data; uint16_t m_appId; BLEAdvertising m_bleAdvertising; @@ -63,12 +68,38 @@ class BLEServer { FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); BLEServiceMap m_serviceMap; BLEServerCallbacks* m_pServerCallbacks; + + void createApp(uint16_t appId); + uint16_t getConnId(); + uint16_t getGattsIf(); + void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + void registerApp(); }; // BLEServer + +/** + * @brief Callbacks associated with the operation of a %BLE server. + */ class BLEServerCallbacks { public: virtual ~BLEServerCallbacks() {}; + /** + * @brief Handle a new client connection. + * + * When a new client connects, we are invoked. + * + * @param [in] pServer A reference to the %BLE server that received the client connection. + */ virtual void onConnect(BLEServer* pServer); + + /** + * @brief Handle an existing client disconnection. + * + * When an existing client disconnects, we are invoked. + * + * @param [in] pServer A reference to the %BLE server that received the existing client disconnection. + */ virtual void onDisconnect(BLEServer* pServer); }; // BLEServerCallbacks diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index c7730093..313437d2 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -19,35 +19,43 @@ class BLEServer; +/** + * @brief A data mapping used to manage the set of %BLE characteristics known to the server. + */ class BLECharacteristicMap { public: - void setByUUID(BLEUUID uuid, BLECharacteristic *characteristic); - void setByHandle(uint16_t handle, BLECharacteristic *characteristic); - BLECharacteristic *getByUUID(BLEUUID uuid); - BLECharacteristic *getByHandle(uint16_t handle); - BLECharacteristic *getFirst(); - BLECharacteristic *getNext(); + void setByUUID(BLEUUID uuid, BLECharacteristic* pCharacteristic); + void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); + BLECharacteristic* getByUUID(BLEUUID uuid); + BLECharacteristic* getByHandle(uint16_t handle); + BLECharacteristic* getFirst(); + BLECharacteristic* getNext(); std::string toString(); void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); + esp_ble_gatts_cb_param_t* param); private: - std::map m_uuidMap; - std::map m_handleMap; - std::map::iterator m_iterator; + std::map m_uuidMap; + std::map m_handleMap; + std::map::iterator m_iterator; }; + +/** + * @brief The model of a %BLE service. + * + */ class BLEService { public: BLEService(BLEUUID uuid); - void addCharacteristic(BLECharacteristic *pCharacteristic); + void addCharacteristic(BLECharacteristic* pCharacteristic); BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); void dump(); - void executeCreate(BLEServer *pServer); + void executeCreate(BLEServer* pServer); BLECharacteristic* getCharacteristic(BLEUUID uuid); BLEUUID getUUID(); BLEServer* getServer(); @@ -72,11 +80,11 @@ class BLEService { BLEUUID m_uuid; uint16_t getHandle(); - BLECharacteristic *getLastCreatedCharacteristic(); + BLECharacteristic* getLastCreatedCharacteristic(); void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); + esp_ble_gatts_cb_param_t* param); void setHandle(uint16_t handle); //void setService(esp_gatt_srvc_id_t srvc_id); }; // BLEService diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 048ca885..9a4fe451 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -17,13 +17,26 @@ static const char* LOG_TAG = "BLEUUID"; /** * @brief Copy memory from source to target but in reverse order. + * + * When we move memory from one location it is normally: + * + * ``` + * [0][1][2]...[n] -> [0][1][2]...[n] + * ``` + * + * with this function, it is: + * + * ``` + * [0][1][2]...[n] -> [n][n-1][n-2]...[0] + * ``` + * * @param [in] target The target of the copy * @param [in] source The source of the copy * @param [in] size The number of bytes to copy */ -static void memrcpy(uint8_t *target, uint8_t *source, uint32_t size) { - target+=(size-1); - while (size >0) { +static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { + target+=(size-1); // Point target to the last byte of the target data + while (size > 0) { *target = *source; target--; source++; @@ -34,12 +47,17 @@ static void memrcpy(uint8_t *target, uint8_t *source, uint32_t size) { /** * @brief Create a UUID from a string. + * * Create a UUID from a string. There will be two possible stories here. Either the string represents * a binary data field or the string represents a hex encoding of a UUID. * For the hex encoding, here is an example: + * + * ``` * "beb5483e-36e1-4688-b7f5-ea07361b26a8" * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * 12345678-90ab-cdef-1234-567890abcdef + * ``` + * * This has a length of 36 characters. We need to parse this into 16 bytes. * * @param [in] value The string to build a UUID from. @@ -54,7 +72,7 @@ BLEUUID::BLEUUID(std::string value) { m_uuid.uuid.uuid32 = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); } else if (value.length() == 16) { m_uuid.len = ESP_UUID_LEN_128; - memrcpy(m_uuid.uuid.uuid128, (uint8_t *)value.data(), 16); + memrcpy(m_uuid.uuid.uuid128, (uint8_t*)value.data(), 16); } else if (value.length() == 36) { // If the length of the string is 36 bytes then we will assume it is a long hex string in // UUID format. @@ -93,16 +111,17 @@ BLEUUID::BLEUUID(std::string value) { /** * @brief Create a UUID from 16 bytes of memory. + * * @param [in] pData The pointer to the start of the UUID. * @param [in] size The size of the data. * @param [in] msbFirst Is the MSB first in pData memory? */ -BLEUUID::BLEUUID(uint8_t *pData, size_t size, bool msbFirst) { +BLEUUID::BLEUUID(uint8_t* pData, size_t size, bool msbFirst) { if (size != 16) { ESP_LOGE(LOG_TAG, "ERROR: UUID length not 16 bytes"); return; } - m_uuid.len = ESP_UUID_LEN_128; + m_uuid.len = ESP_UUID_LEN_128; if (msbFirst) { memrcpy(m_uuid.uuid.uuid128, pData, 16); } else { @@ -113,6 +132,7 @@ BLEUUID::BLEUUID(uint8_t *pData, size_t size, bool msbFirst) { /** * @brief Create a UUID from the 16bit value. + * * @param [in] uuid The 16bit short form UUID. */ BLEUUID::BLEUUID(uint16_t uuid) { @@ -124,6 +144,7 @@ BLEUUID::BLEUUID(uint16_t uuid) { /** * @brief Create a UUID from the 32bit value. + * * @param [in] uuid The 32bit short form UUID. */ BLEUUID::BLEUUID(uint32_t uuid) { @@ -135,6 +156,7 @@ BLEUUID::BLEUUID(uint32_t uuid) { /** * @brief Create a UUID from the native UUID. + * * @param [in] uuid The native UUID. */ BLEUUID::BLEUUID(esp_bt_uuid_t uuid) { @@ -143,8 +165,13 @@ BLEUUID::BLEUUID(esp_bt_uuid_t uuid) { } // BLEUUID +/** + * @brief Create a UUID from the ESP32 esp_gatt_srvc_id_t. + * + * @param [in] srvcId The data to create the UUID from. + */ BLEUUID::BLEUUID(esp_gatt_srvc_id_t srcvId) : BLEUUID(srcvId.id.uuid) { -} +} // BLEUUID BLEUUID::BLEUUID() { @@ -154,6 +181,7 @@ BLEUUID::BLEUUID() { /** * @brief Compare a UUID against this UUID. + * * @param [in] uuid The UUID to compare against. * @return True if the UUIDs are equal and false otherwise. */ @@ -181,9 +209,10 @@ bool BLEUUID::equals(BLEUUID uuid) { /** * @brief Get the native UUID value. + * * @return The native UUID value or NULL if not set. */ -esp_bt_uuid_t *BLEUUID::getNative() { +esp_bt_uuid_t* BLEUUID::getNative() { //ESP_LOGD(TAG, ">> getNative()") if (m_valueSet == false) { ESP_LOGD(LOG_TAG, "<< Return of un-initialized UUID!"); @@ -196,6 +225,7 @@ esp_bt_uuid_t *BLEUUID::getNative() { /** * @brief Convert a UUID to its 128 bit representation. + * * A UUID can be internally represented as 16bit, 32bit or the full 128bit. This method * will convert 16 or 32 bit representations to the full 128bit. */ @@ -253,6 +283,7 @@ BLEUUID BLEUUID::to128() { /** * @brief Get a string representation of the UUID. + * * @return A string representation of the UUID. */ std::string BLEUUID::toString() { @@ -274,21 +305,21 @@ std::string BLEUUID::toString() { std::stringstream ss; ss << std::hex << std::setfill('0') << - std::setw(2) << (int)m_uuid.uuid.uuid128[15] << - std::setw(2) << (int)m_uuid.uuid.uuid128[14] << - std::setw(2) << (int)m_uuid.uuid.uuid128[13] << - std::setw(2) << (int)m_uuid.uuid.uuid128[12] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[11] << - std::setw(2) << (int)m_uuid.uuid.uuid128[10] << "-" << + std::setw(2) << (int)m_uuid.uuid.uuid128[15] << + std::setw(2) << (int)m_uuid.uuid.uuid128[14] << + std::setw(2) << (int)m_uuid.uuid.uuid128[13] << + std::setw(2) << (int)m_uuid.uuid.uuid128[12] << "-" << + std::setw(2) << (int)m_uuid.uuid.uuid128[11] << + std::setw(2) << (int)m_uuid.uuid.uuid128[10] << "-" << std::setw(2) << (int)m_uuid.uuid.uuid128[9] << std::setw(2) << (int)m_uuid.uuid.uuid128[8] << "-" << std::setw(2) << (int)m_uuid.uuid.uuid128[7] << std::setw(2) << (int)m_uuid.uuid.uuid128[6] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[5] << - std::setw(2) << (int)m_uuid.uuid.uuid128[4] << - std::setw(2) << (int)m_uuid.uuid.uuid128[3] << - std::setw(2) << (int)m_uuid.uuid.uuid128[2] << - std::setw(2) << (int)m_uuid.uuid.uuid128[1] << + std::setw(2) << (int)m_uuid.uuid.uuid128[5] << + std::setw(2) << (int)m_uuid.uuid.uuid128[4] << + std::setw(2) << (int)m_uuid.uuid.uuid128[3] << + std::setw(2) << (int)m_uuid.uuid.uuid128[2] << + std::setw(2) << (int)m_uuid.uuid.uuid128[1] << std::setw(2) << (int)m_uuid.uuid.uuid128[0]; return ss.str(); } // toString diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index ff6c9c2f..c3647965 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -12,17 +12,20 @@ #include #include +/** + * @brief A model of a %BLE UUID. + */ class BLEUUID { public: BLEUUID(std::string uuid); BLEUUID(uint16_t uuid); BLEUUID(uint32_t uuid); BLEUUID(esp_bt_uuid_t uuid); - BLEUUID(uint8_t *pData, size_t size, bool msbFirst); + BLEUUID(uint8_t* pData, size_t size, bool msbFirst); BLEUUID(esp_gatt_srvc_id_t srcvId); BLEUUID(); bool equals(BLEUUID uuid); - esp_bt_uuid_t *getNative(); + esp_bt_uuid_t* getNative(); BLEUUID to128(); std::string toString(); diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 50e8294e..395473e2 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -10,6 +10,7 @@ #include "BLEUUID.h" #include "BLEClient.h" #include "BLEAddress.h" +#include "GeneralUtils.h" #include #include @@ -740,7 +741,7 @@ void BLEUtils::dumpGattClientEvent( ); } break; - } + } // ESP_GATTC_GET_CHAR_EVT // @@ -765,6 +766,8 @@ void BLEUtils::dumpGattClientEvent( // // ESP_GATTC_READ_CHAR_EVT // + // Callback to indicate that requested data that we wanted to read is now available. + // // read: // esp_gatt_status_t status // uint16_t conn_id @@ -785,9 +788,12 @@ void BLEUtils::dumpGattClientEvent( evtParam->read.value_len ); if (evtParam->read.status == ESP_GATT_OK) { + GeneralUtils::hexDump(evtParam->read.value, evtParam->read.value_len); + /* char *pHexData = BLEUtils::buildHexData(nullptr, evtParam->read.value, evtParam->read.value_len); ESP_LOGD(LOG_TAG, "value: %s \"%s\"", pHexData, BLEUtils::buildPrintData(evtParam->read.value, evtParam->read.value_len).c_str()); free(pHexData); + */ } break; } // ESP_GATTC_READ_CHAR_EVT diff --git a/cpp_utils/BLEUtils.h b/cpp_utils/BLEUtils.h index b2d9c943..28c7a7ef 100644 --- a/cpp_utils/BLEUtils.h +++ b/cpp_utils/BLEUtils.h @@ -15,44 +15,46 @@ #include #include "BLEClient.h" +/** + * @brief A set of general %BLE utilities. + */ class BLEUtils { public: - static const char *advTypeToString(uint8_t advType); + static const char* advTypeToString(uint8_t advType); static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id=0); static esp_gatt_srvc_id_t buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary=true); static std::string characteristicPropertiesToString(esp_gatt_char_prop_t prop); - static char *buildHexData(uint8_t *target, uint8_t *source, uint8_t length); - static BLEClient *findByConnId(uint16_t conn_id); - static BLEClient *findByAddress(BLEAddress address); + static char* buildHexData(uint8_t *target, uint8_t *source, uint8_t length); + static BLEClient* findByConnId(uint16_t conn_id); + static BLEClient* findByAddress(BLEAddress address); static std::string gattClientEventTypeToString(esp_gattc_cb_event_t eventType); static std::string gattServerEventTypeToString(esp_gatts_cb_event_t eventType); static std::string gattServiceIdToString(esp_gatt_srvc_id_t srvcId); static std::string gattStatusToString(esp_gatt_status_t status); static std::string gattServiceToString(uint32_t serviceId); static std::string gattCloseReasonToString(esp_gatt_conn_reason_t reason); - static void registerByAddress(BLEAddress address, BLEClient *pDevice); - static void registerByConnId(uint16_t conn_id, BLEClient *pDevice); + static void registerByAddress(BLEAddress address, BLEClient* pDevice); + static void registerByConnId(uint16_t conn_id, BLEClient* pDevice); static std::string gattCharacteristicUUIDToString(uint32_t characteristicUUID); - static std::string buildPrintData(uint8_t *source, size_t length); + static std::string buildPrintData(uint8_t* source, size_t length); static void dumpGattClientEvent( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *evtParam); + esp_ble_gattc_cb_param_t* evtParam); static void dumpGattServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *evtParam); + esp_ble_gatts_cb_param_t* evtParam); static const char* devTypeToString(esp_bt_dev_type_t type); static void dumpGapEvent( esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t *param); - static const char *gapEventToString(uint32_t eventType); + esp_ble_gap_cb_param_t* param); + static const char* gapEventToString(uint32_t eventType); static const char* searchEventTypeToString(esp_gap_search_evt_t searchEvt); static const char* addressTypeToString(esp_ble_addr_type_t type); static const char *eventTypeToString(esp_ble_evt_type_t eventType); }; - #endif // CONFIG_BT_ENABLED #endif /* COMPONENTS_CPP_UTILS_BLEUTILS_H_ */ diff --git a/cpp_utils/BLEValue.cpp b/cpp_utils/BLEValue.cpp index 3bc4a333..1989993a 100644 --- a/cpp_utils/BLEValue.cpp +++ b/cpp_utils/BLEValue.cpp @@ -4,6 +4,9 @@ * Created on: Jul 17, 2017 * Author: kolban */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + #include #include "BLEValue.h" @@ -110,3 +113,4 @@ void BLEValue::setValue(std::string value) { void BLEValue::setValue(uint8_t* pData, size_t length) { m_value = std::string((char*)pData, length); } // setValue +#endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEValue.h b/cpp_utils/BLEValue.h index f899ee68..a292c6e1 100644 --- a/cpp_utils/BLEValue.h +++ b/cpp_utils/BLEValue.h @@ -7,8 +7,13 @@ #ifndef COMPONENTS_CPP_UTILS_BLEVALUE_H_ #define COMPONENTS_CPP_UTILS_BLEVALUE_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) #include +/** + * @brief The model of a %BLE value. + */ class BLEValue { public: BLEValue(); @@ -27,5 +32,5 @@ class BLEValue { uint16_t m_readOffset; std::string m_value; }; - +#endif // CONFIG_BT_ENABLED #endif /* COMPONENTS_CPP_UTILS_BLEVALUE_H_ */ diff --git a/cpp_utils/Doxyfile b/cpp_utils/Doxyfile index 483be3d8..5ac15096 100644 --- a/cpp_utils/Doxyfile +++ b/cpp_utils/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.11 +# Doxyfile 1.8.13 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -303,6 +303,15 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -803,8 +812,8 @@ INPUT_ENCODING = UTF-8 # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl, -# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js. +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. FILE_PATTERNS = *.c \ *.cc \ @@ -2091,7 +2100,8 @@ INCLUDE_FILE_PATTERNS = # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. PREDEFINED = CONFIG_MONGOOSE_PRESENT \ - CONFIG_WIFI_ENABLED + CONFIG_WIFI_ENABLED \ + CONFIG_BT_ENABLED # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2415,6 +2425,11 @@ DIAFILE_DIRS = PLANTUML_JAR_PATH = +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + # When using plantuml, the specified paths are searched for files specified by # the !include statement in a plantuml block. diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 9c5e247b..663128d7 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -118,6 +118,15 @@ void FreeRTOS::Semaphore::give(uint32_t value) { } +/** + * @brief Give a semaphore from an ISR. + */ +void FreeRTOS::Semaphore::giveFromISR() { + BaseType_t higherPriorityTaskWoken; + xSemaphoreGiveFromISR(m_semaphore, &higherPriorityTaskWoken); +} // giveFromISR + + /** * @brief Take a semaphore. * Take a semaphore and wait indefinitely. diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index f714bd46..320f4cc6 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -31,6 +31,7 @@ class FreeRTOS { Semaphore(std::string owner = ""); ~Semaphore(); void give(); + void giveFromISR(); void give(uint32_t value); void setName(std::string name); void take(std::string owner=""); diff --git a/cpp_utils/GPIO.cpp b/cpp_utils/GPIO.cpp index 6e5b8110..4188d307 100644 --- a/cpp_utils/GPIO.cpp +++ b/cpp_utils/GPIO.cpp @@ -9,8 +9,46 @@ #include #include "sdkconfig.h" #include +#include "GeneralUtils.h" static const char* LOG_TAG = "GPIO"; + +static bool g_isrServiceInstalled = false; + +/** + * @brief Add an ISR handler to the pin. + * @param [in] pin The pin to have the ISR associated with it. + * @param [in] handler The function to be invoked when the interrupt is detected. + * @param [in] pArgs Optional arguments to pass to the handler. + */ +void ESP32CPP::GPIO::addISRHandler( + gpio_num_t pin, + gpio_isr_t handler, + void* pArgs) { + + ESP_LOGD(LOG_TAG, ">> addISRHandler: pin=%d", pin); + + // If we have not yet installed the ISR service handler, install it now. + if (g_isrServiceInstalled == false) { + ESP_LOGD(LOG_TAG, "Installing the global ISR service"); + esp_err_t errRc = ::gpio_install_isr_service(0); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< gpio_install_isr_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + g_isrServiceInstalled = true; + } + + esp_err_t errRc = ::gpio_isr_handler_add(pin, handler, pArgs); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< gpio_isr_handler_add: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + ESP_LOGD(LOG_TAG, "<< addISRHandler"); +} // addISRHandler + + /** * @brief Set the pin high. * @@ -149,9 +187,25 @@ void ESP32CPP::GPIO::setOutput(gpio_num_t pin) { * @return N/A. */ void ESP32CPP::GPIO::write(gpio_num_t pin, bool value) { - ::gpio_set_level(pin, value); + //ESP_LOGD(LOG_TAG, ">> write: pin: %d, value: %d", pin, value); + esp_err_t errRc = ::gpio_set_level(pin, value); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< gpio_set_level: pin=%d, rc=%d %s", pin, errRc, GeneralUtils::errorToString(errRc)); + } } // write - - +/** + * @brief Write up to 8 bits of data to a set of pins. + * @param [in] pins An array of pins to set their values. + * @param [in] value The data value to write. + * @param [in] bits The number of bits to write. + */ +void ESP32CPP::GPIO::writeByte(gpio_num_t pins[], uint8_t value, int bits) { + ESP_LOGD(LOG_TAG, ">> writeByte: value: %.2x, bits: %d", value, bits); + for (int i=0; i +#include +#include +#include +#include "I2S.h" +#include "GPIO.h" +#include "FreeRTOS.h" +#include "GeneralUtils.h" + +extern "C" { + #include + #include +} + +#include +#include +#include + +static const char* LOG_TAG = "I2S"; + +static intr_handle_t i2s_intr_handle; + + + +/** + * A representation of a DMA buffer. + */ +class DMABuffer { +public: + DMABuffer(); + ~DMABuffer(); + + void dump(); + uint32_t getData(uint8_t* pData, uint32_t length); + lldesc_t* getDesc(); + uint32_t getLength(); + DMABuffer* getNext(); + bool isEoF(); + void setNext(DMABuffer* pNext); + +private: + lldesc_t m_desc; + DMABuffer* m_pNext; +}; + + +DMABuffer::DMABuffer() { + m_desc.length = 0; + m_desc.size = 4092; + m_desc.owner = 1; + m_desc.sosf = 1; + m_desc.offset = 0; + m_desc.eof = 0; + m_desc.empty = 0; + m_desc.buf = new uint8_t[m_desc.size]; + m_pNext = nullptr; +} // DMABuffer + + +DMABuffer::~DMABuffer() { + delete[] m_desc.buf; +} // ~DMABuffer + +/** + * @brief Dump the state of the buffer. + * @return N/A + */ +void DMABuffer::dump() { + std::ostringstream ss; + ss << "size: " << m_desc.size; + ss << ", buf: 0x" << std::hex << (uint32_t)m_desc.buf << std::dec; + ss << ", length: " << m_desc.length; + ss << ", offset: " << m_desc.offset; + ss << ", sosf: " << m_desc.sosf; + ss << ", eof: " << m_desc.eof; + ss << ", owner: " << m_desc.owner; + ESP_LOGD(LOG_TAG, "Desc: %s", ss.str().c_str()); + int length = 100; + if (length > m_desc.length) { + length = m_desc.length; + } + GeneralUtils::hexDump((uint8_t*)m_desc.buf, length); +} + +/** + * @brief Populate a buffer of data with the DMA data. + * @param [in] pData A pointer to data to be populated with the DMA data. + * @param [in] length The size in bytes of the pData buffer. + * @return The number of bytes actually copied. + */ +uint32_t DMABuffer::getData(uint8_t* pData, uint32_t length) { + uint8_t* pBuf = (uint8_t*)m_desc.buf; + if (length > getLength()) { + length = getLength(); + } + uint32_t i; + // + // The descriptor buffer is filled with data that contains: + // + // b1 00 b0 00 b3 00 b2 00 b5 00 b4 00 b7 00 b6 00 + // + // Our goal is to populate the passed in buffer with data of the form: + // + // b0 b1 b2 b3 b4 b5 b6 b7 ... + // + // The following alogrithm does that. + // + for (i=0; igetDesc(); +} // setNext + + +I2S::I2S() { + // TODO Auto-generated constructor stub + +} + +I2S::~I2S() { + // TODO Auto-generated destructor stub +} + +DMABuffer *pCurrentDMABuffer; +DMABuffer *pLastDMABuffer; + + +static void i2s_conf_reset() +{ + const uint32_t lc_conf_reset_flags = I2S_IN_RST_M | I2S_AHBM_RST_M + | I2S_AHBM_FIFO_RST_M; + I2S0.lc_conf.val |= lc_conf_reset_flags; + I2S0.lc_conf.val &= ~lc_conf_reset_flags; + + const uint32_t conf_reset_flags = I2S_RX_RESET_M | I2S_RX_FIFO_RESET_M + | I2S_TX_RESET_M | I2S_TX_FIFO_RESET_M; + I2S0.conf.val |= conf_reset_flags; + I2S0.conf.val &= ~conf_reset_flags; + while (I2S0.state.rx_fifo_reset_back) { + ; + } +} + + +static void IRAM_ATTR logI2SIntr() { + std::ostringstream ss; + ss << "IN_DONE: " << I2S0.int_raw.in_done; + ss << ", IN_DSCR_EMPTY: " << I2S0.int_raw.in_dscr_empty; + ss << ", IN_DSCR_ERR: " << I2S0.int_raw.in_dscr_err; + ss << ", IN_ERR_EOF: " << I2S0.int_raw.in_err_eof; + ss << ", IN_SUC_EOF: " << I2S0.int_raw.in_suc_eof; + ESP_EARLY_LOGV(LOG_TAG, "I2S Intr: %s", ss.str().c_str()); +} + +static void IRAM_ATTR logDesc(lldesc_t* pDesc) { + std::ostringstream ss; + ss << "size: " << pDesc->size; + ss << ", buf: 0x" << std::hex << (uint32_t)pDesc->buf << std::dec; + ss << ", length: " << pDesc->length; + ss << ", offset: " << pDesc->offset; + ss << ", sosf: " << pDesc->sosf; + ss << ", eof: " << pDesc->eof; + ss << ", owner: " << pDesc->owner; + ESP_EARLY_LOGV(LOG_TAG, "Desc: %s", ss.str().c_str()); + +} + + +/** + * @brief I2S DMA Interrupt handler + */ +static void IRAM_ATTR i2s_isr(void* arg) +{ + I2S* pI2S = (I2S*)arg; + //ESP_EARLY_LOGV(LOG_TAG, "I2S isr"); + pLastDMABuffer = pCurrentDMABuffer; + //logI2SIntr(); + //logDesc(pCurrentDMABuffer->getDesc()); + pCurrentDMABuffer = pCurrentDMABuffer->getNext(); + I2S0.int_clr.val = I2S0.int_raw.val; + pI2S->m_dmaSemaphore.giveFromISR(); +} + +/** + * @brief EXPERIMENTAL + */ +void I2S::cameraMode(dma_config_t config, int desc_count, int sample_count) { + ESP_LOGD(LOG_TAG, ">> cameraMode"); + ESP32CPP::GPIO::setInput(config.pin_d0); + ESP32CPP::GPIO::setInput(config.pin_d1); + ESP32CPP::GPIO::setInput(config.pin_d2); + ESP32CPP::GPIO::setInput(config.pin_d3); + ESP32CPP::GPIO::setInput(config.pin_d4); + ESP32CPP::GPIO::setInput(config.pin_d5); + ESP32CPP::GPIO::setInput(config.pin_d6); + ESP32CPP::GPIO::setInput(config.pin_d7); + //ESP32CPP::GPIO::setInput(config.pin_xclk); + ESP32CPP::GPIO::setInput(config.pin_vsync); + ESP32CPP::GPIO::setInput(config.pin_href); + ESP32CPP::GPIO::setInput(config.pin_pclk); + //ESP32CPP::GPIO::setOutput(config.pin_reset); + + const uint32_t const_high = 0x38; + + gpio_matrix_in(config.pin_d0, I2S0I_DATA_IN0_IDX, false); + gpio_matrix_in(config.pin_d1, I2S0I_DATA_IN1_IDX, false); + gpio_matrix_in(config.pin_d2, I2S0I_DATA_IN2_IDX, false); + gpio_matrix_in(config.pin_d3, I2S0I_DATA_IN3_IDX, false); + gpio_matrix_in(config.pin_d4, I2S0I_DATA_IN4_IDX, false); + gpio_matrix_in(config.pin_d5, I2S0I_DATA_IN5_IDX, false); + gpio_matrix_in(config.pin_d6, I2S0I_DATA_IN6_IDX, false); + gpio_matrix_in(config.pin_d7, I2S0I_DATA_IN7_IDX, false); + gpio_matrix_in(config.pin_vsync, I2S0I_V_SYNC_IDX, true); + gpio_matrix_in(config.pin_href, I2S0I_H_SYNC_IDX, false); + //gpio_matrix_in(const_high, I2S0I_V_SYNC_IDX, false); + //gpio_matrix_in(const_high, I2S0I_H_SYNC_IDX, false); + gpio_matrix_in(const_high, I2S0I_H_ENABLE_IDX, false); + gpio_matrix_in(config.pin_pclk, I2S0I_WS_IN_IDX, false); + + // Enable and configure I2S peripheral + periph_module_enable(PERIPH_I2S0_MODULE); + // Toggle some reset bits in LC_CONF register + // Toggle some reset bits in CONF register + // Enable slave mode (sampling clock is external) + + i2s_conf_reset(); + + // Switch on Slave mode. + // I2S_CONF_REG -> I2S_RX_SLAVE_MOD + // Set to 1 to enable slave mode. + I2S0.conf.rx_slave_mod = 1; + + // Enable parallel mode + // I2S_CONF2_REG -> I2S_LCD_END + // Set to 1 to enable LCD mode. + I2S0.conf2.lcd_en = 1; + + // Use HSYNC/VSYNC/HREF to control sampling + // I2S_CONF2_REG -> I2S_CAMERA_EN + // Set to 1 to enable camera mode. + I2S0.conf2.camera_en = 1; + + + // Configure clock divider + I2S0.clkm_conf.clkm_div_a = 1; + I2S0.clkm_conf.clkm_div_b = 0; + I2S0.clkm_conf.clkm_div_num = 2; + + // I2S_FIFO_CONF_REG -> I2S_DSCR_EN + // FIFO will sink data to DMA + I2S0.fifo_conf.dscr_en = 1; + + // FIFO configuration + // I2S_FIFO_CONF_REG -> RX_FIFO_MOD + I2S0.fifo_conf.rx_fifo_mod = 1; // 0-3??? + + // I2S_FIFO_CONF_REG -> RX_FIFO_MOD_FORCE_EN + I2S0.fifo_conf.rx_fifo_mod_force_en = 1; + + // I2S_CONF_CHAN_REG -> I2S_RX_CHAN_MOD + I2S0.conf_chan.rx_chan_mod = 1; + + // Clear flags which are used in I2S serial mode + // I2S_SAMPLE_RATE_CONF_REG -> I2S_RX_BITS_MOD + I2S0.sample_rate_conf.rx_bits_mod = 0; + + // I2S_CONF_REG -> I2S_RX_RIGHT_FIRST + I2S0.conf.rx_right_first = 0; + //I2S0.conf.rx_right_first = 0; + + // I2S_CONF_REG -> I2S_RX_MSB_RIGHT + I2S0.conf.rx_msb_right = 0; + //I2S0.conf.rx_msb_right = 1; + + // I2S_CONF_REG -> I2S_RX_MSB_SHIFT + I2S0.conf.rx_msb_shift = 0; + //I2S0.conf.rx_msb_shift = 1; + + // I2S_CONF_REG -> I2S_RX_MSB_MONO + I2S0.conf.rx_mono = 0; + + // I2S_CONF_REG -> I2S_RX_SHORT_SYNC + I2S0.conf.rx_short_sync = 0; + + I2S0.timing.val = 0; + + + ESP_LOGD(LOG_TAG, "Initializing %d descriptors", desc_count); + DMABuffer *pFirst = new DMABuffer(); + DMABuffer *pLast = pFirst; + for (int i=1; isetNext(pNewDMABuffer); + pLast = pNewDMABuffer; + } + pLast->setNext(pFirst); + pCurrentDMABuffer = pFirst; + + // I2S_RX_EOF_NUM_REG + I2S0.rx_eof_num = sample_count; + + // I2S_IN_LINK_REG -> I2S_INLINK_ADDR + I2S0.in_link.addr = (uint32_t) pFirst; + + // I2S_IN_LINK_REG -> I2S_INLINK_START + I2S0.in_link.start = 1; + + I2S0.int_clr.val = I2S0.int_raw.val; + I2S0.int_ena.val = 0; + I2S0.int_ena.in_done = 1; + + // Register the interrupt handler. + esp_intr_alloc( + ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, + &i2s_isr, + this, + &i2s_intr_handle); + + m_dmaSemaphore.take(); + // Start the interrupt handler + esp_intr_enable(i2s_intr_handle); + + I2S0.conf.rx_start = 1; + + /* + while(1) { + m_dmaSemaphore.wait(); + uint32_t dataLength = pLastDMABuffer->getLength(); + ESP_LOGD(LOG_TAG, "Got a DMA buffer; length=%d", dataLength); + //pLastDMABuffer->dump(); + uint8_t *pData = new uint8_t[dataLength]; + pLastDMABuffer->getData(pData, dataLength); + GeneralUtils::hexDump(pData, dataLength); + delete[] pData; + m_dmaSemaphore.take(); + } + */ + + ESP_LOGD(LOG_TAG, "<< cameraMode"); +} + + +DMAData I2S::waitForData() { + DMAData dmaData; + m_dmaSemaphore.wait(); + dmaData.m_length = pLastDMABuffer->getLength(); + ESP_LOGD(LOG_TAG, "Got a DMA buffer; length=%d", dmaData.m_length); + //pLastDMABuffer->dump(); + dmaData.m_pData = new uint8_t[dmaData.m_length]; + pLastDMABuffer->getData(dmaData.m_pData, dmaData.m_length); + return dmaData; +} diff --git a/cpp_utils/I2S.h b/cpp_utils/I2S.h new file mode 100644 index 00000000..7d8fd269 --- /dev/null +++ b/cpp_utils/I2S.h @@ -0,0 +1,62 @@ +/* + * I2S.h + * + * Created on: Jul 23, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_I2S_H_ +#define COMPONENTS_CPP_UTILS_I2S_H_ +#include +#include +typedef struct { + gpio_num_t pin_reset; /*!< GPIO pin for camera reset line - OUT */ + gpio_num_t pin_xclk; /*!< GPIO pin for camera XCLK line - IN */ + gpio_num_t pin_sscb_sda; /*!< GPIO pin for camera SDA line - OUT */ + gpio_num_t pin_sscb_scl; /*!< GPIO pin for camera SCL line - OUT */ + gpio_num_t pin_d7; /*!< GPIO pin for camera D7 line - IN */ + gpio_num_t pin_d6; /*!< GPIO pin for camera D6 line - IN */ + gpio_num_t pin_d5; /*!< GPIO pin for camera D5 line - IN */ + gpio_num_t pin_d4; /*!< GPIO pin for camera D4 line - IN */ + gpio_num_t pin_d3; /*!< GPIO pin for camera D3 line - IN */ + gpio_num_t pin_d2; /*!< GPIO pin for camera D2 line - IN */ + gpio_num_t pin_d1; /*!< GPIO pin for camera D1 line - IN */ + gpio_num_t pin_d0; /*!< GPIO pin for camera D0 line - IN */ + gpio_num_t pin_vsync; /*!< GPIO pin for camera VSYNC line - IN */ + gpio_num_t pin_href; /*!< GPIO pin for camera HREF line - IN */ + gpio_num_t pin_pclk; /*!< GPIO pin for camera PCLK line - IN */ + + int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz */ +} dma_config_t; + +class DMAData { +public: + uint32_t getLength() { + return m_length; + } + uint8_t* getData() { + return m_pData; + } + + void free() { + delete[] m_pData; + m_pData = nullptr; + } +private: + friend class I2S; + uint32_t m_length; + uint8_t* m_pData; +}; + +class I2S { +public: + I2S(); + virtual ~I2S(); + void cameraMode(dma_config_t config, int desc_count, int sample_count); + DMAData waitForData(); + FreeRTOS::Semaphore m_dmaSemaphore; +private: + +}; + +#endif /* COMPONENTS_CPP_UTILS_I2S_H_ */ diff --git a/cpp_utils/OV7670.cpp b/cpp_utils/OV7670.cpp index c5047ce3..f8dc8ccd 100644 --- a/cpp_utils/OV7670.cpp +++ b/cpp_utils/OV7670.cpp @@ -6,10 +6,12 @@ */ #include "OV7670.h" +#include "I2S.h" #include #include -#include -#include +#include "GPIO.h" +#include "FreeRTOS.h" +#include "GeneralUtils.h" #include "sdkconfig.h" #include extern "C" { @@ -17,8 +19,77 @@ extern "C" { #include } -static char tag[]="OV7670"; +static const char *LOG_TAG="OV7670"; +static bool getBit(uint8_t value, uint8_t bitNum) { + return (value & (1<> 3) << 3; + uint8_t green = (((byte1 & 0b111) << 3) | ((byte2 & 0b11100000) >> 5)) << 2; + uint8_t blue = (byte2 & 0b11111) << 3; + *pLine = (red + green + blue)/3; + pLine++; + i+=2; + } + pLine = pLineSave; + GeneralUtils::hexDump(pLine, length/2); +} + +void OV7670::setFormat(uint8_t value) { + uint8_t com7 = readRegister(OV7670_REG_COM7); + com7 = setBit(com7, 2, getBit(value, 1)); + com7 = setBit(com7, 0, getBit(value, 0)); + writeRegister(OV7670_REG_COM7, com7); +} + +void OV7670::resetCamera() { + uint8_t com7 = readRegister(OV7670_REG_COM7); + com7 = setBit(com7, 7, true); + writeRegister(OV7670_REG_COM7, com7); +} + +void OV7670::setRGBFormat(uint8_t value) { + uint8_t com15 = readRegister(OV7670_REG_COM15); + com15 = setBit(com15, 5, getBit(value, 1)); + com15 = setBit(com15, 4, getBit(value, 0)); + writeRegister(OV7670_REG_COM15, com15); +} + +void OV7670::setTestPattern(uint8_t value) { + uint8_t com7 = readRegister(OV7670_REG_COM7); + if (value == OV7670_TESTPATTERN_NONE) { + com7 = setBit(com7, 1, false); + } else { + com7 = setBit(com7, 1, true); + } + writeRegister(OV7670_REG_COM7, com7); + + uint8_t scaling_xsc = readRegister(OV7670_REG_SCALING_XSC); + scaling_xsc = setBit(scaling_xsc, 7, getBit(value, 1)); + writeRegister(OV7670_REG_SCALING_XSC, scaling_xsc); + uint8_t scaling_ysc = readRegister(OV7670_REG_SCALING_YSC); + scaling_ysc = setBit(scaling_ysc, 7, getBit(value, 0)); + writeRegister(OV7670_REG_SCALING_YSC, scaling_ysc); +} /* * * COM7 @@ -52,10 +123,31 @@ static char tag[]="OV7670"; #define OV7670_I2C_ADDR (0x21) static void IRAM_ATTR isr_vsync(void* arg) { - ESP_EARLY_LOGD(tag, "VSYNC"); + ESP_EARLY_LOGD(LOG_TAG, "VSYNC"); +} + +static uint32_t vsyncCounter = 0; +static uint32_t hrefCounter = 0; +static uint32_t lastHref = 0; +static uint32_t pclkCounter = 0; +static uint32_t lastPclk = 0; + +static void IRAM_ATTR vsyncHandler(void* arg) { + vsyncCounter++; + lastHref = hrefCounter; + hrefCounter = 0; } +static void IRAM_ATTR hrefHandler(void* arg) { + hrefCounter++; +} + +static void IRAM_ATTR pclckHandler(void* arg) { + pclkCounter++; + portYIELD_FROM_ISR(); +} +/* static inline void i2s_conf_reset() { const uint32_t lc_conf_reset_flags = I2S_IN_RST_M | I2S_AHBM_RST_M | I2S_AHBM_FIFO_RST_M; @@ -69,6 +161,7 @@ static inline void i2s_conf_reset() ; } } +*/ OV7670::OV7670() { m_i2c = nullptr; @@ -85,32 +178,35 @@ OV7670::~OV7670() { static esp_err_t camera_enable_out_clock(camera_config_t* config) { - periph_module_enable(PERIPH_LEDC_MODULE); - - ledc_timer_config_t timer_conf; - timer_conf.bit_num = (ledc_timer_bit_t)1; - timer_conf.freq_hz = config->xclk_freq_hz; - timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE; - timer_conf.timer_num = config->ledc_timer; - esp_err_t err = ledc_timer_config(&timer_conf); - if (err != ESP_OK) { - ESP_LOGE(tag, "ledc_timer_config failed, rc=%x", err); - return err; - } + ESP_LOGD(LOG_TAG, ">> camera_enable_out_clock: freq_hz=%d, pin=%d", config->xclk_freq_hz, config->pin_xclk); + periph_module_enable(PERIPH_LEDC_MODULE); + + ledc_timer_config_t timer_conf; + timer_conf.bit_num = (ledc_timer_bit_t)1; + timer_conf.freq_hz = config->xclk_freq_hz; + timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE; + timer_conf.timer_num = config->ledc_timer; + esp_err_t err = ledc_timer_config(&timer_conf); + if (err != ESP_OK) { + ESP_LOGE(LOG_TAG, "ledc_timer_config failed, rc=%x", err); + return err; + } - ledc_channel_config_t ch_conf; - ch_conf.channel = config->ledc_channel; - ch_conf.timer_sel = config->ledc_timer; - ch_conf.intr_type = LEDC_INTR_DISABLE; - ch_conf.duty = 1; - ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE; - ch_conf.gpio_num = config->pin_xclk; - err = ledc_channel_config(&ch_conf); - if (err != ESP_OK) { - ESP_LOGE(tag, "ledc_channel_config failed, rc=%x", err); - return err; - } - return ESP_OK; + ledc_channel_config_t ch_conf; + ch_conf.channel = config->ledc_channel; + ch_conf.timer_sel = config->ledc_timer; + ch_conf.intr_type = LEDC_INTR_DISABLE; + ch_conf.duty = 1; + ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE; + ch_conf.gpio_num = config->pin_xclk; + + err = ledc_channel_config(&ch_conf); + if (err != ESP_OK) { + ESP_LOGE(LOG_TAG, "ledc_channel_config failed, rc=%x", err); + return err; + } + ESP_LOGD(LOG_TAG, "<< camera_enable_out_clock"); + return ESP_OK; } // camera_enable_out_clock /* @@ -121,6 +217,7 @@ static void i2s_init() { // Toggle some reset bits in CONF register // Enable slave mode (sampling clock is external) + // Switch on Slave mode. // I2S_CONF_REG -> I2S_RX_SLAVE_MOD I2S0.conf.rx_slave_mod = 1; @@ -162,20 +259,108 @@ static void i2s_init() { * @brief Dump the settings. */ void OV7670::dump() { - ESP_LOGD(tag, "PID: 0x%.2x, VER: 0x%.2x, MID: 0x%.4x", + ESP_LOGD(LOG_TAG, "PID: 0x%.2x, VER: 0x%.2x, MID: 0x%.4x", readRegister(OV7670_REG_PID), readRegister(OV7670_REG_VER), readRegister(OV7670_REG_MIDH) << 8 | readRegister(OV7670_REG_MIDL)); + uint8_t com7 = readRegister(OV7670_REG_COM7); + uint32_t outputFormat = getBit(com7, 2) << 1 | getBit(com7, 0); + //uint32_t outputFormat = (com7 & (1<<2)) >> 1 | (com7 & (1<<0)); + std::string outputFormatString; + switch(outputFormat) { + case 0b00: + outputFormatString = "YUV"; + break; + case 0b10: + outputFormatString = "RGB"; + break; + case 0b01: + outputFormatString = "Raw Bayer RGB"; + break; + case 0b11: + outputFormatString = "Process Bayer RGB"; + break; + default: + outputFormatString = "Unknown"; + break; + } + ESP_LOGD(LOG_TAG, "Output format: %s", outputFormatString.c_str()); + if (outputFormat == 0b10) { + uint8_t com15 = readRegister(OV7670_REG_COM15); + uint8_t rgbType = getBit(com15, 5) << 1 | getBit(com15,4); + char *rgbTypeString; + switch(rgbType) { + case 0b00: + case 0b10: + rgbTypeString = (char*)"Normal RGB Output"; + break; + case 0b01: + rgbTypeString = (char*)"RGB 565"; + break; + case 0b11: + rgbTypeString = (char*)"RGB 555"; + break; + default: + rgbTypeString = (char*)"Unknown"; + break; + } + ESP_LOGD(LOG_TAG, "Rgb Type: %s", rgbTypeString); + } + ESP_LOGD(LOG_TAG, "Color bar: %s", getBit(com7, 1)?"Enabled":"Disabled"); + + uint8_t scaling_xsc = readRegister(OV7670_REG_SCALING_XSC); + uint8_t scaling_ysc = readRegister(OV7670_REG_SCALING_YSC); + uint32_t testPattern = getBit(scaling_xsc, 7) << 1 | getBit(scaling_ysc, 7); + char *testPatternString; + switch(testPattern) { + case 0b00: + testPatternString = (char*)"No test output"; + break; + case 0b01: + testPatternString = (char*)"Shifting 1"; + break; + case 0b10: + testPatternString = (char*)"8-bar color bar"; + break; + case 0b11: + testPatternString = (char*)"Fade to gray color bar"; + break; + default: + testPatternString = (char*)"Unknown"; + break; + } + ESP_LOGD(LOG_TAG, "Test pattern: %s", testPatternString); + ESP_LOGD(LOG_TAG, "Horizontal scale factor: %d", scaling_xsc & 0x3f); + ESP_LOGD(LOG_TAG, "Vertical scale factor: %d", scaling_ysc & 0x3f); + uint8_t com15 = readRegister(OV7670_REG_COM15); + switch((com15 & 0b11000000) >> 6) { + case 0b00: + case 0b01: + ESP_LOGD(LOG_TAG, "Output range: 0x10 to 0xf0"); + break; + case 0b10: + ESP_LOGD(LOG_TAG, "Output range: 0x01 to 0xfe"); + break; + case 0b11: + ESP_LOGD(LOG_TAG, "Output range: 0x00 to 0xff"); + break; + } } // dump +static void log(char *marker) { + ESP_LOGD(LOG_TAG, "%s", marker); + FreeRTOS::sleep(100); +} /** * @brief Initialize the camera. */ void OV7670::init(camera_config_t cameraConfig) { + ESP_LOGD(LOG_TAG, ">> init"); m_cameraConfig = cameraConfig; // Define the GPIO pin directions. + /* ESP32CPP::GPIO::setInput(m_cameraConfig.pin_d0); ESP32CPP::GPIO::setInput(m_cameraConfig.pin_d1); ESP32CPP::GPIO::setInput(m_cameraConfig.pin_d2); @@ -184,15 +369,17 @@ void OV7670::init(camera_config_t cameraConfig) { ESP32CPP::GPIO::setInput(m_cameraConfig.pin_d5); ESP32CPP::GPIO::setInput(m_cameraConfig.pin_d6); ESP32CPP::GPIO::setInput(m_cameraConfig.pin_d7); - ESP32CPP::GPIO::setInput(m_cameraConfig.pin_xclk); + ESP32CPP::GPIO::setInput(m_cameraConfig.pin_vsync); ESP32CPP::GPIO::setInput(m_cameraConfig.pin_href); ESP32CPP::GPIO::setInput(m_cameraConfig.pin_pclk); + */ + ESP32CPP::GPIO::setInput(m_cameraConfig.pin_xclk); ESP32CPP::GPIO::setOutput(m_cameraConfig.pin_reset); + // Reset the camera. reset(); - // provide a 20MHz clock on the pin_xclck camera_enable_out_clock(&m_cameraConfig); @@ -200,27 +387,82 @@ void OV7670::init(camera_config_t cameraConfig) { m_i2c = new I2C(); m_i2c->init(OV7670_I2C_ADDR, (gpio_num_t)m_cameraConfig.pin_sscb_sda, (gpio_num_t)m_cameraConfig.pin_sscb_scl); m_i2c->scan(); - ESP_LOGD(tag, "Do you see 0x21 listed?"); + ESP_LOGD(LOG_TAG, "Do you see 0x21 listed?"); + resetCamera(); + FreeRTOS::sleep(100); + resetCamera(); + setFormat(OV7670_FORMAT_RGB); + setRGBFormat(OV7670_FORMAT_RGB_RGB_565); + setTestPattern(OV7670_TESTPATTERN_GRAY_FADE); dump(); // Setup the VSYNC interrupt handler +/* + ESP32CPP::GPIO::addISRHandler(m_cameraConfig.pin_vsync, vsyncHandler, nullptr); ESP32CPP::GPIO::setInterruptType(m_cameraConfig.pin_vsync, GPIO_INTR_NEGEDGE); ESP32CPP::GPIO::interruptEnable(m_cameraConfig.pin_vsync); + + + ESP32CPP::GPIO::addISRHandler(m_cameraConfig.pin_href, hrefHandler, nullptr); + ESP32CPP::GPIO::setInterruptType(m_cameraConfig.pin_href, GPIO_INTR_NEGEDGE); + ESP32CPP::GPIO::interruptEnable(m_cameraConfig.pin_href); +*/ + + + //ESP32CPP::GPIO::addISRHandler(m_cameraConfig.pin_pclk, pclckHandler, nullptr); + //ESP32CPP::GPIO::setInterruptType(m_cameraConfig.pin_pclk, GPIO_INTR_NEGEDGE); + //ESP32CPP::GPIO::interruptEnable(m_cameraConfig.pin_pclk); + + /* gpio_isr_handle_t handle; esp_err_t err = ::gpio_isr_register(isr_vsync, nullptr, ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM, &handle); if (err != ESP_OK) { - ESP_LOGD(tag, "gpio_isr_register: %d", err); + ESP_LOGD(LOG_TAG, "gpio_isr_register: %d", err); } + */ + + I2S i2s; + dma_config_t dmaConfig; + dmaConfig.pin_d0 = cameraConfig.pin_d0; + dmaConfig.pin_d1 = cameraConfig.pin_d1; + dmaConfig.pin_d2 = cameraConfig.pin_d2; + dmaConfig.pin_d3 = cameraConfig.pin_d3; + dmaConfig.pin_d4 = cameraConfig.pin_d4; + dmaConfig.pin_d5 = cameraConfig.pin_d5; + dmaConfig.pin_d6 = cameraConfig.pin_d6; + dmaConfig.pin_d7 = cameraConfig.pin_d7; + dmaConfig.pin_href = cameraConfig.pin_href; + dmaConfig.pin_pclk = cameraConfig.pin_pclk; + dmaConfig.pin_vsync = cameraConfig.pin_vsync; + i2s.cameraMode(dmaConfig, 50, 360*2); + ESP_LOGD(LOG_TAG, "Waiting for data!"); + while(1) { + DMAData dmaData = i2s.waitForData(); + //GeneralUtils::hexDump(dmaData.getData(), dmaData.getLength()); + toGrayscale(dmaData.getData(), dmaData.getLength()); + dmaData.free(); + } - ESP_LOGD(tag, "Waiting for positive edge on VSYNC"); + ESP_LOGD(LOG_TAG, "Waiting for positive edge on VSYNC"); while (gpio_get_level(m_cameraConfig.pin_vsync) == 0) { ; } while (gpio_get_level(m_cameraConfig.pin_vsync) != 0) { ; } - ESP_LOGD(tag, "Got VSYNC"); + + ESP_LOGD(LOG_TAG, "Got VSYNC"); + + + + + while(1) { + FreeRTOS::sleep(1000); + ESP_LOGD(LOG_TAG, "VSYNC Counter: %d, lastHref=%d, pclk=%d", vsyncCounter, lastHref, pclkCounter); + } + + ESP_LOGD(LOG_TAG, "<< init"); } // init @@ -261,8 +503,9 @@ void OV7670::writeRegister(uint8_t reg, uint8_t value) { */ void OV7670::reset() { // Reset the camera + ESP_LOGD(LOG_TAG, "x1"); ESP32CPP::GPIO::low(m_cameraConfig.pin_reset); FreeRTOS::sleep(10); ESP32CPP::GPIO::high(m_cameraConfig.pin_reset); FreeRTOS::sleep(10); -} +} // reset diff --git a/cpp_utils/OV7670.h b/cpp_utils/OV7670.h index cde4defc..b7baf1f9 100644 --- a/cpp_utils/OV7670.h +++ b/cpp_utils/OV7670.h @@ -151,6 +151,20 @@ #define OV7670_REG_AD_CHGr (0xC1) #define OV7670_REG_SATCTR (0xC9) +// COM7[2] and COM7[0] +#define OV7670_FORMAT_YUV (0b00) +#define OV7670_FORMAT_RGB (0b10) + +#define OV7670_FORMAT_RGB_RGB_NORMAL (0b00) +#define OV7670_FORMAT_RGB_RGB_565 (0b01) +#define OV7670_FORMAT_RGB_RGB_555 (0b11) + +// SCALING_XSC[7] and SCALINT_YSC[7] +#define OV7670_TESTPATTERN_NONE (0b00) +#define OV7670_TESTPATTERN_SHIFT_1 (0b01) +#define OV7670_TESTPATTERN_BAR_8 (0b10) +#define OV7670_TESTPATTERN_GRAY_FADE (0b11) + typedef struct { gpio_num_t pin_reset; /*!< GPIO pin for camera reset line - OUT */ gpio_num_t pin_xclk; /*!< GPIO pin for camera XCLK line - IN */ @@ -180,6 +194,10 @@ class OV7670 { void init(camera_config_t cameraConfig); void dump(); void reset(); + void setFormat(uint8_t value); + void setRGBFormat(uint8_t value); + void setTestPattern(uint8_t value); + void resetCamera(); private: uint8_t readRegister(uint8_t reg); void writeRegister(uint8_t reg, uint8_t value); diff --git a/cpp_utils/Task.cpp b/cpp_utils/Task.cpp index 806363b9..dba7a760 100644 --- a/cpp_utils/Task.cpp +++ b/cpp_utils/Task.cpp @@ -25,9 +25,9 @@ static char tag[] = "Task"; * @return N/A. */ Task::Task(std::string taskName, uint16_t stackSize) { - this->stackSize = stackSize; - taskData = nullptr; - handle = nullptr; + m_stackSize = stackSize; + m_taskData = nullptr; + m_handle = nullptr; } // Task Task::~Task() { @@ -50,10 +50,10 @@ void Task::delay(int ms) { * The code here will run on the task thread. * @param [in] pTaskInstance The task to run. */ -void Task::runTask(void *pTaskInstance) { +void Task::runTask(void* pTaskInstance) { ESP_LOGD(tag, ">> runTask"); - Task *pTask = (Task *)pTaskInstance; - pTask->run(pTask->taskData); + Task* pTask = (Task*)pTaskInstance; + pTask->run(pTask->m_taskData); pTask->stop(); } // runTask @@ -63,12 +63,12 @@ void Task::runTask(void *pTaskInstance) { * @param [in] taskData Data to be passed into the task. * @return N/A. */ -void Task::start(void *taskData) { - if (handle != nullptr) { +void Task::start(void* taskData) { + if (m_handle != nullptr) { ESP_LOGW(tag, "Task::start - There might be a task already running!"); } - this->taskData = taskData; - ::xTaskCreate(&runTask, taskName.c_str(), stackSize, this, 5, &handle); + m_taskData = taskData; + ::xTaskCreate(&runTask, m_taskName.c_str(), m_stackSize, this, 5, &m_handle); } // start @@ -78,11 +78,11 @@ void Task::start(void *taskData) { * @return N/A. */ void Task::stop() { - if (handle == nullptr) { + if (m_handle == nullptr) { return; } - xTaskHandle temp = handle; - handle = nullptr; + xTaskHandle temp = m_handle; + m_handle = nullptr; ::vTaskDelete(temp); } // stop @@ -93,5 +93,5 @@ void Task::stop() { * @return N/A. */ void Task::setStackSize(uint16_t stackSize) { - this->stackSize = stackSize; + m_stackSize = stackSize; } // setStackSize diff --git a/cpp_utils/Task.h b/cpp_utils/Task.h index a1f84175..51e18fc8 100644 --- a/cpp_utils/Task.h +++ b/cpp_utils/Task.h @@ -33,10 +33,10 @@ */ class Task { public: - Task(std::string taskName="Task", uint16_t stackSize=2048); + Task(std::string taskName="Task", uint16_t stackSize=10000); virtual ~Task(); void setStackSize(uint16_t stackSize); - void start(void *taskData=nullptr); + void start(void* taskData=nullptr); void stop(); /** * @brief Body of the task to execute. @@ -51,11 +51,11 @@ class Task { void delay(int ms); private: - xTaskHandle handle; - void *taskData; + xTaskHandle m_handle; + void* m_taskData; static void runTask(void *data); - std::string taskName; - uint16_t stackSize; + std::string m_taskName; + uint16_t m_stackSize; }; #endif /* COMPONENTS_CPP_UTILS_TASK_H_ */ diff --git a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp index d7bc9388..a7280ffe 100644 --- a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp +++ b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp @@ -4,7 +4,7 @@ #include #include -#include "BLEAdvertisedDeviceCallbacks.h" +#include "BLEAdvertisedDevice.h" #include "BLEClient.h" #include "sdkconfig.h" #include "Task.h" diff --git a/cpp_utils/tests/BLE Tests/SampleClient.cpp b/cpp_utils/tests/BLE Tests/SampleClient.cpp index c847b261..61ac572d 100644 --- a/cpp_utils/tests/BLE Tests/SampleClient.cpp +++ b/cpp_utils/tests/BLE Tests/SampleClient.cpp @@ -1,95 +1,108 @@ -#include "BLE.h" -#include "BLEUtils.h" -#include "BLEScan.h" #include #include +#include +#include -#include "BLEAdvertisedDeviceCallbacks.h" +#include "BLE.h" +#include "BLEAdvertisedDevice.h" #include "BLEClient.h" -#include "sdkconfig.h" +#include "BLEScan.h" +#include "BLEUtils.h" #include "Task.h" -static const char LOG_TAG[] = "SampleClient"; +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient"; // See the following for generating UUIDs: // https://www.uuidgenerator.net/ - - - +// The remote service we wish to connect to. static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); -class MyClient: public Task { - void run(void *data) { - BLEAddress* pAddress = (BLEAddress *)data; - BLEClient* pClient = BLE::createClient(); +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLE::createClient(); + // Connect to the remove BLE Server. pClient->connect(*pAddress); - pClient->getServices(); + // Obtain a reference to the service we are after in the remote BLE server. BLERemoteService* pRemoteService = pClient->getService(serviceUUID); if (pRemoteService == nullptr) { ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); return; } - pRemoteService->getCharacteristics(); + + // Obtain a reference to the characteristic in the service of the remote BLE server. BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); if (pRemoteCharacteristic == nullptr) { ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); return; } - pRemoteCharacteristic->readValue(); - pRemoteCharacteristic->writeValue("123"); - pRemoteCharacteristic->registerForNotify(); + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + pClient->disconnect(); ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); ESP_LOGD(LOG_TAG, "-- End of task"); - } -}; - + } // run +}; // MyClient +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ void onResult(BLEAdvertisedDevice *pAdvertisedDevice) { ESP_LOGD(LOG_TAG, "Advertised Device: %s", pAdvertisedDevice->toString().c_str()); - if (pAdvertisedDevice->haveServiceUUID()) { - ESP_LOGD(LOG_TAG, "Comparing %s to %s", - pAdvertisedDevice->getServiceUUID().toString().c_str(), serviceUUID.toString().c_str()); - } + if (pAdvertisedDevice->haveServiceUUID() && pAdvertisedDevice->getServiceUUID().equals(serviceUUID)) { pAdvertisedDevice->getScan()->stop(); + ESP_LOGD(LOG_TAG, "Found our device! address: %s", pAdvertisedDevice->getAddress().toString().c_str()); - MyClient *pMyClient = new MyClient(); + MyClient* pMyClient = new MyClient(); pMyClient->setStackSize(18000); pMyClient->start(new BLEAddress(*pAdvertisedDevice->getAddress().getNative())); - - } - } -}; - + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks -static void run() { +/** + * Perform the work of a sample BLE client. + */ +void SampleClient(void) { ESP_LOGD(LOG_TAG, "Scanning sample starting"); - BLEUUID x = BLEUUID("12345678-90ab-cdef-1234-567890abcdef"); - ESP_LOGD(LOG_TAG, "%s", x.toString().c_str()); BLE::initClient(); BLEScan *pBLEScan = BLE::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); pBLEScan->start(30); - - //BLEClient *pClient = BLE::createClient(); - //pClient->setClientCallbacks(new MyClientCallbacks()); - //pClient->connect(BLEAddress("00:00:00:00:00:00")); - -} - -void SampleClient(void) -{ - run(); -} // app_main +} // SampleClient diff --git a/cpp_utils/tests/BLE Tests/SampleScan.cpp b/cpp_utils/tests/BLE Tests/SampleScan.cpp index bf537e13..716b3cf2 100644 --- a/cpp_utils/tests/BLE Tests/SampleScan.cpp +++ b/cpp_utils/tests/BLE Tests/SampleScan.cpp @@ -4,7 +4,7 @@ #include #include -#include "BLEAdvertisedDeviceCallbacks.h" +#include "BLEAdvertisedDevice.h" #include "sdkconfig.h" static const char LOG_TAG[] = "SampleScan"; From 7040add49a0a94e1c1fb4aa027b8b5a25cd3f8e7 Mon Sep 17 00:00:00 2001 From: copercini Date: Sat, 5 Aug 2017 14:37:11 -0300 Subject: [PATCH 012/381] Missing semicolon on BLEAdvertising.cpp --- cpp_utils/BLEAdvertising.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 744ac38f..6031fb7d 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -122,7 +122,7 @@ void BLEAdvertising::start() { ESP_LOGE(LOG_TAG, "<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } - ESP_LOGD(LOG_TAG, "<< start") + ESP_LOGD(LOG_TAG, "<< start"); } // start From 0ef44495153c689ef2b2bb8fff0170864a9136c6 Mon Sep 17 00:00:00 2001 From: copercini Date: Sat, 5 Aug 2017 14:39:13 -0300 Subject: [PATCH 013/381] Missing semicolon on BLEUtils.cpp --- cpp_utils/BLEUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 395473e2..c061658b 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -636,7 +636,7 @@ void BLEUtils::dumpGapEvent( } // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT default: { - ESP_LOGD(LOG_TAG, "*** dumpGapEvent: Logger not coded ***") + ESP_LOGD(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); break; } // default } // switch From 5cec383e57588727e6737219e68813f039fef9d5 Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 6 Aug 2017 23:16:07 -0500 Subject: [PATCH 014/381] Sync 2017-08-06 --- cpp_utils/.gitignore | 1 + .../Arduino_ESP32_BLE.library.properties | 10 +++ cpp_utils/BLE.cpp | 1 + cpp_utils/BLECharacteristic.cpp | 50 ++++++++------ cpp_utils/BLEDescriptor.cpp | 2 +- cpp_utils/BLEServer.cpp | 13 ++++ cpp_utils/BLEServer.h | 29 +++++---- cpp_utils/BLEService.cpp | 17 ++--- cpp_utils/GeneralUtils.cpp | 8 ++- cpp_utils/I2S.cpp | 6 +- cpp_utils/Makefile.arduino | 65 +++++++++++++++++++ cpp_utils/OV7670.cpp | 13 +++- cpp_utils/README.md | 13 +++- 13 files changed, 179 insertions(+), 49 deletions(-) create mode 100644 cpp_utils/Arduino_ESP32_BLE.library.properties create mode 100644 cpp_utils/Makefile.arduino diff --git a/cpp_utils/.gitignore b/cpp_utils/.gitignore index 6e684992..190415da 100644 --- a/cpp_utils/.gitignore +++ b/cpp_utils/.gitignore @@ -1 +1,2 @@ /docs/ +/Arduino/ diff --git a/cpp_utils/Arduino_ESP32_BLE.library.properties b/cpp_utils/Arduino_ESP32_BLE.library.properties new file mode 100644 index 00000000..e8fe2808 --- /dev/null +++ b/cpp_utils/Arduino_ESP32_BLE.library.properties @@ -0,0 +1,10 @@ +name=ESP32_BLE +version=1.0 +author=Neil Kolban +maintainer=Neil Kolban +sentence=BLE functions for ESP32 +paragraph=BLE functions for ESP32 +category=Communication +url=http://example.com/ +architectures=esp32 +includes=BLE.h BLEUtils.h BLEScan.h BLEAdvertisedDevice.h \ No newline at end of file diff --git a/cpp_utils/BLE.cpp b/cpp_utils/BLE.cpp index 49596f67..3dfc98cc 100644 --- a/cpp_utils/BLE.cpp +++ b/cpp_utils/BLE.cpp @@ -6,6 +6,7 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) +#include #include #include #include diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 54dffc6d..7f2a856c 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -31,7 +31,7 @@ static const char* LOG_TAG = "BLECharacteristic"; BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { m_bleUUID = uuid; m_handle = NULL_HANDLE; - m_properties = 0; + m_properties = (esp_gatt_char_prop_t)0; m_pCallbacks = nullptr; setBroadcastProperty((properties & PROPERTY_BROADCAST) !=0); @@ -411,13 +411,18 @@ void BLECharacteristic::handleGATTServerEvent( */ void BLECharacteristic::indicate() { - char *pHexData = BLEUtils::buildHexData(nullptr, (uint8_t*)m_value.getValue().data(), m_value.getValue().length()); - ESP_LOGD(LOG_TAG, ">> indicate: length: %d, data: [%s]", m_value.getValue().length(), pHexData ); - free(pHexData); + ESP_LOGD(LOG_TAG, ">> indicate: length: %d", m_value.getValue().length()); assert(getService() != nullptr); assert(getService()->getServer() != nullptr); + GeneralUtils::hexDump((uint8_t*)m_value.getValue().data(), m_value.getValue().length()); + + if (getService()->getServer()->getConnectedCount() == 0) { + ESP_LOGD(LOG_TAG, "<< indicate: No connected clients."); + return; + } + // Test to see if we have a 0x2902 descriptor. If we do, then check to see if indications are enabled // and, if not, prevent the indication. @@ -460,15 +465,20 @@ void BLECharacteristic::indicate() { * @return N/A. */ void BLECharacteristic::notify() { - - char *pHexData = BLEUtils::buildHexData(nullptr, (uint8_t*)m_value.getValue().data(), m_value.getValue().length()); - ESP_LOGD(LOG_TAG, ">> notify: length: %d, data: [%s]", m_value.getValue().length(), pHexData ); - free(pHexData); + ESP_LOGD(LOG_TAG, ">> notify: length: %d", m_value.getValue().length()); assert(getService() != nullptr); assert(getService()->getServer() != nullptr); + + GeneralUtils::hexDump((uint8_t*)m_value.getValue().data(), m_value.getValue().length()); + + if (getService()->getServer()->getConnectedCount() == 0) { + ESP_LOGD(LOG_TAG, "<< notify: No connected clients."); + return; + } + // Test to see if we have a 0x2902 descriptor. If we do, then check to see if notification is enabled // and, if not, prevent the notification. @@ -510,9 +520,9 @@ void BLECharacteristic::notify() { void BLECharacteristic::setBroadcastProperty(bool value) { //ESP_LOGD(LOG_TAG, "setBroadcastProperty(%d)", value); if (value) { - m_properties |= ESP_GATT_CHAR_PROP_BIT_BROADCAST; + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_BROADCAST); } else { - m_properties &= ~ESP_GATT_CHAR_PROP_BIT_BROADCAST; + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); } } // setBroadcastProperty @@ -550,9 +560,9 @@ void BLECharacteristic::setHandle(uint16_t handle) { void BLECharacteristic::setIndicateProperty(bool value) { //ESP_LOGD(LOG_TAG, "setIndicateProperty(%d)", value); if (value) { - m_properties |= ESP_GATT_CHAR_PROP_BIT_INDICATE; + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_INDICATE); } else { - m_properties &= ~ESP_GATT_CHAR_PROP_BIT_INDICATE; + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); } } // setIndicateProperty @@ -564,9 +574,9 @@ void BLECharacteristic::setIndicateProperty(bool value) { void BLECharacteristic::setNotifyProperty(bool value) { //ESP_LOGD(LOG_TAG, "setNotifyProperty(%d)", value); if (value) { - m_properties |= ESP_GATT_CHAR_PROP_BIT_NOTIFY; + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_NOTIFY); } else { - m_properties &= ~ESP_GATT_CHAR_PROP_BIT_NOTIFY; + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); } } // setNotifyProperty @@ -578,9 +588,9 @@ void BLECharacteristic::setNotifyProperty(bool value) { void BLECharacteristic::setReadProperty(bool value) { //ESP_LOGD(LOG_TAG, "setReadProperty(%d)", value); if (value) { - m_properties |= ESP_GATT_CHAR_PROP_BIT_READ; + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_READ); } else { - m_properties &= ~ESP_GATT_CHAR_PROP_BIT_READ; + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_READ); } } // setReadProperty @@ -622,9 +632,9 @@ void BLECharacteristic::setValue(std::string value) { void BLECharacteristic::setWriteNoResponseProperty(bool value) { //ESP_LOGD(LOG_TAG, "setWriteNoResponseProperty(%d)", value); if (value) { - m_properties |= ESP_GATT_CHAR_PROP_BIT_WRITE_NR; + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } else { - m_properties &= ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR; + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } } // setWriteNoResponseProperty @@ -636,9 +646,9 @@ void BLECharacteristic::setWriteNoResponseProperty(bool value) { void BLECharacteristic::setWriteProperty(bool value) { //ESP_LOGD(LOG_TAG, "setWriteProperty(%d)", value); if (value) { - m_properties |= ESP_GATT_CHAR_PROP_BIT_WRITE; + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE); } else { - m_properties &= ~ESP_GATT_CHAR_PROP_BIT_WRITE; + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE); } } // setWriteProperty diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index eeb66900..5f96c97b 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -63,7 +63,7 @@ void BLEDescriptor::executeCreate(BLECharacteristic* pCharacteristic) { esp_err_t errRc = ::esp_ble_gatts_add_char_descr( pCharacteristic->getService()->getHandle(), getUUID().getNative(), - ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + (esp_gatt_perm_t)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), &m_value, &control); if (errRc != ESP_OK) { diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 7492310c..5555c9e7 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -33,6 +33,7 @@ static const char* LOG_TAG = "BLEServer"; BLEServer::BLEServer() { m_appId = -1; m_gatts_if = -1; + m_connectedCount = 0; m_connId = -1; BLE::m_bleServer = this; m_pServerCallbacks = nullptr; @@ -90,6 +91,16 @@ uint16_t BLEServer::getConnId() { return m_connId; } + +/** + * @brief Return the number of connected clients. + * @return The number of connected clients. + */ +uint32_t BLEServer::getConnectedCount() { + return m_connectedCount; +} // getConnectedCount + + uint16_t BLEServer::getGattsIf() { return m_gatts_if; } @@ -162,6 +173,7 @@ void BLEServer::handleGATTServerEvent( if (m_pServerCallbacks != nullptr) { m_pServerCallbacks->onConnect(this); } + m_connectedCount++; break; } // ESP_GATTS_CONNECT_EVT @@ -232,6 +244,7 @@ void BLEServer::handleGATTServerEvent( // If we receive a disconnect event then invoke the callback for disconnects (if one is present). // we also want to start advertising again. case ESP_GATTS_DISCONNECT_EVT: { + m_connectedCount--; if (m_pServerCallbacks != nullptr) { m_pServerCallbacks->onDisconnect(this); } diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index c09d81a6..12b997f9 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -9,14 +9,16 @@ #define COMPONENTS_CPP_UTILS_BLESERVER_H_ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) +#include + #include #include -#include -#include "FreeRTOS.h" + #include "BLEUUID.h" #include "BLEAdvertising.h" #include "BLECharacteristic.h" #include "BLEService.h" +#include "FreeRTOS.h" class BLEServerCallbacks; @@ -26,18 +28,19 @@ class BLEServerCallbacks; */ class BLEServiceMap { public: - void setByUUID(BLEUUID uuid, BLEService* service); - void setByHandle(uint16_t handle, BLEService* service); - BLEService* getByUUID(BLEUUID uuid); BLEService* getByHandle(uint16_t handle); - std::string toString(); - void handleGATTServerEvent( + BLEService* getByUUID(BLEUUID uuid); + void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); - private: - std::map m_uuidMap; - std::map m_handleMap; + esp_ble_gatts_cb_param_t* param); + void setByHandle(uint16_t handle, BLEService* service); + void setByUUID(BLEUUID uuid, BLEService* service); + std::string toString(); + +private: + std::map m_uuidMap; + std::map m_handleMap; }; @@ -49,6 +52,7 @@ class BLEServer { BLEServer(); + uint32_t getConnectedCount(); BLEService* createService(BLEUUID uuid); BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks *pCallbacks); @@ -62,8 +66,9 @@ class BLEServer { esp_ble_adv_data_t m_adv_data; uint16_t m_appId; BLEAdvertising m_bleAdvertising; - uint16_t m_gatts_if; uint16_t m_connId; + uint32_t m_connectedCount; + uint16_t m_gatts_if; FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt"); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); BLEServiceMap m_serviceMap; diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index c884b2fd..99706590 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -118,14 +118,6 @@ void BLEService::start() { return; } - m_semaphoreStartEvt.take("start"); - esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - m_semaphoreStartEvt.wait("start"); BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); @@ -137,6 +129,15 @@ void BLEService::start() { } // Start each of the characteristics ... these are found in the m_characteristicMap. + m_semaphoreStartEvt.take("start"); + esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreStartEvt.wait("start"); + ESP_LOGD(LOG_TAG, "<< start()"); } // start diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 080c358c..c3504b8e 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -265,6 +265,9 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { char ascii[80]; char hex[80]; char tempBuf[80]; + uint32_t lineNumber = 0; + + ESP_LOGD(LOG_TAG, " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ----------------"); strcpy(ascii, ""); strcpy(hex, ""); uint32_t index=0; @@ -279,9 +282,10 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { strcat(ascii, tempBuf); index++; if (index % 16 == 0) { - ESP_LOGD(LOG_TAG, "%s %s", hex, ascii); + ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); strcpy(ascii, ""); strcpy(hex, ""); + lineNumber++; } } if (index %16 != 0) { @@ -289,7 +293,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { strcat(hex, " "); index++; } - ESP_LOGD(LOG_TAG, "%s %s", hex, ascii); + ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); } } // hexDump diff --git a/cpp_utils/I2S.cpp b/cpp_utils/I2S.cpp index 82d4c108..c5bcb997 100644 --- a/cpp_utils/I2S.cpp +++ b/cpp_utils/I2S.cpp @@ -198,7 +198,7 @@ static void i2s_conf_reset() } } - +/* static void IRAM_ATTR logI2SIntr() { std::ostringstream ss; ss << "IN_DONE: " << I2S0.int_raw.in_done; @@ -208,7 +208,9 @@ static void IRAM_ATTR logI2SIntr() { ss << ", IN_SUC_EOF: " << I2S0.int_raw.in_suc_eof; ESP_EARLY_LOGV(LOG_TAG, "I2S Intr: %s", ss.str().c_str()); } +*/ +/* static void IRAM_ATTR logDesc(lldesc_t* pDesc) { std::ostringstream ss; ss << "size: " << pDesc->size; @@ -219,8 +221,8 @@ static void IRAM_ATTR logDesc(lldesc_t* pDesc) { ss << ", eof: " << pDesc->eof; ss << ", owner: " << pDesc->owner; ESP_EARLY_LOGV(LOG_TAG, "Desc: %s", ss.str().c_str()); - } +*/ /** diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino new file mode 100644 index 00000000..a60ffa99 --- /dev/null +++ b/cpp_utils/Makefile.arduino @@ -0,0 +1,65 @@ +# A Makefile that will build the Arduino libraries +# +# Targets +# build_ble - Build the Arduino BLE library. This will result in a ZIP that can be found at Arduino/ESP32_BLE.zip +# + +# +# The source file for BLE +# +BLE_FILES= \ + BLE.cpp \ + BLE.h \ + BLE2902.cpp \ + BLE2902.h \ + BLEAddress.cpp \ + BLEAddress.h \ + BLEAdvertisedDevice.cpp \ + BLEAdvertisedDevice.h \ + BLEAdvertisedDeviceCallbacks.cpp \ + BLEAdvertising.cpp \ + BLEAdvertising.h \ + BLECharacteristic.cpp \ + BLECharacteristic.h \ + BLECharacteristicCallbacks.cpp \ + BLECharacteristicMap.cpp \ + BLEClient.cpp \ + BLEClient.h \ + BLEClientCallbacks.cpp \ + BLEDescriptor.cpp \ + BLEDescriptor.h \ + BLEDescriptorMap.cpp \ + BLERemoteCharacteristic.cpp \ + BLERemoteCharacteristic.h \ + BLERemoteDescriptor.cpp \ + BLERemoteDescriptor.h \ + BLERemoteService.cpp \ + BLERemoteService.h \ + BLEScan.cpp \ + BLEScan.h \ + BLEServerCallbacks.cpp \ + BLEServer.cpp \ + BLEServer.h \ + BLEService.cpp \ + BLEService.h \ + BLEServiceMap.cpp \ + BLEUtils.cpp \ + BLEUtils.h \ + BLEUUID.cpp \ + BLEUUID.h \ + BLEValue.cpp \ + BLEValue.h \ + FreeRTOS.h \ + FreeRTOS.cpp \ + GeneralUtils.h \ + GeneralUtils.cpp + + +build_ble: + rm -rf Arduino/ESP32_BLE + mkdir -p Arduino/ESP32_BLE/src + cp $(BLE_FILES) Arduino/ESP32_BLE/src + cp Arduino_ESP32_BLE.library.properties Arduino/ESP32_BLE/library.properties + rm -f Arduino/ESP32_BLE.zip + cd Arduino; zip -r ESP32_BLE.zip ESP32_BLE + rm -rf Arduino/ESP32_BLE \ No newline at end of file diff --git a/cpp_utils/OV7670.cpp b/cpp_utils/OV7670.cpp index f8dc8ccd..c7e395ce 100644 --- a/cpp_utils/OV7670.cpp +++ b/cpp_utils/OV7670.cpp @@ -121,31 +121,38 @@ void OV7670::setTestPattern(uint8_t value) { */ #define OV7670_I2C_ADDR (0x21) - +/* static void IRAM_ATTR isr_vsync(void* arg) { ESP_EARLY_LOGD(LOG_TAG, "VSYNC"); } +*/ static uint32_t vsyncCounter = 0; -static uint32_t hrefCounter = 0; +//static uint32_t hrefCounter = 0; static uint32_t lastHref = 0; static uint32_t pclkCounter = 0; -static uint32_t lastPclk = 0; +//static uint32_t lastPclk = 0; +/* static void IRAM_ATTR vsyncHandler(void* arg) { vsyncCounter++; lastHref = hrefCounter; hrefCounter = 0; } +*/ +/* static void IRAM_ATTR hrefHandler(void* arg) { hrefCounter++; } +*/ +/* static void IRAM_ATTR pclckHandler(void* arg) { pclkCounter++; portYIELD_FROM_ISR(); } +*/ /* static inline void i2s_conf_reset() diff --git a/cpp_utils/README.md b/cpp_utils/README.md index d5050634..edbcd751 100644 --- a/cpp_utils/README.md +++ b/cpp_utils/README.md @@ -39,4 +39,15 @@ the ESP-IDF build system has chosen to only compile the underlying BLE functions ## Building the Documentation The code is commented using the Doxygen tags. As such we can run Doxygen to generate the data. I use `doxywizard` using -the `Doxyfile` located in this directory. \ No newline at end of file +the `Doxyfile` located in this directory. + +## Building the Arduino libraries +Some of the classes in this package also have applicability in an Arduino environment. A `Makefile` called `Makefile.arduino` is provided to build the libraries. For example: + +``` +$ make -f Makefile.arduino +``` + +The results of this will be ZIP files found in the `Arduino` directory relative to this one. Targets include: + +* `build_ble` - Build the BLE libraries. \ No newline at end of file From 4401dc05e44b442a34e85400ab9ce92ca80b2d29 Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 7 Aug 2017 19:03:05 -0500 Subject: [PATCH 015/381] Code changes for #30 --- cpp_utils/BLEAdvertisedDevice.h | 2 +- cpp_utils/BLEAdvertisedDeviceCallbacks.cpp | 11 ---- cpp_utils/BLEScan.cpp | 68 +++++++++++++++------- cpp_utils/BLEScan.h | 27 ++++++--- cpp_utils/Makefile.arduino | 1 - cpp_utils/tests/BLE Tests/SampleClient.cpp | 12 ++-- cpp_utils/tests/BLE Tests/SampleScan.cpp | 8 +-- 7 files changed, 77 insertions(+), 52 deletions(-) delete mode 100644 cpp_utils/BLEAdvertisedDeviceCallbacks.cpp diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index c02f1762..a2c33613 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -98,7 +98,7 @@ class BLEAdvertisedDeviceCallbacks { * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the * device that was found. During any individual scan, a device will only be detected one time. */ - virtual void onResult(BLEAdvertisedDevice* pAdvertisedDevice) = 0; + virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0; }; #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp b/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp deleted file mode 100644 index 915e0690..00000000 --- a/cpp_utils/BLEAdvertisedDeviceCallbacks.cpp +++ /dev/null @@ -1,11 +0,0 @@ -/* - * BLEAdvertisedDeviceCallback.cpp - * - * Created on: Jul 3, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - -#include "BLEAdvertisedDevice.h" -#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index e53cd41a..9fdcffdf 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -32,21 +32,19 @@ BLEScan::BLEScan() { } // BLEScan -BLEScan::~BLEScan() { - clearAdvertisedDevices(); -} // ~BLEScan - /** * @brief Clear the history of previously detected advertised devices. * @return N/A */ +/* void BLEScan::clearAdvertisedDevices() { for (int i=0; iscan_rst.bda); bool found = false; + /* for (int i=0; igetAddress().equals(advertisedAddress)) { found = true; break; } } + */ + for (int i=0; isetAddress(advertisedAddress); - pAdvertisedDevice->setRSSI(param->scan_rst.rssi); - pAdvertisedDevice->setAdFlag(param->scan_rst.flag); - pAdvertisedDevice->parseAdvertisement((uint8_t*)param->scan_rst.ble_adv); - pAdvertisedDevice->setScan(this); - - m_vectorAvdertisedDevices.push_back(pAdvertisedDevice); + BLEAdvertisedDevice advertisedDevice; + advertisedDevice.setAddress(advertisedAddress); + advertisedDevice.setRSSI(param->scan_rst.rssi); + advertisedDevice.setAdFlag(param->scan_rst.flag); + advertisedDevice.parseAdvertisement((uint8_t*)param->scan_rst.ble_adv); + advertisedDevice.setScan(this); + + //m_vectorAvdertisedDevices.push_back(pAdvertisedDevice); if (m_pAdvertisedDeviceCallbacks) { - m_pAdvertisedDeviceCallbacks->onResult(pAdvertisedDevice); + m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); } + m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice); + break; } // ESP_GAP_SEARCH_INQ_RES_EVT @@ -160,8 +168,8 @@ void BLEScan::setActiveScan(bool active) { /** - * @brief Set the callbacks to be invoked. - * @param [in] pAdvertisedDeviceCallbacks Callbacks to be invoked. + * @brief Set the call backs to be invoked. + * @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked. */ void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks) { m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; @@ -191,19 +199,19 @@ void BLEScan::setWindow(uint16_t windowMSecs) { * @param [in] duration The duration in seconds for which to scan. * @return N/A. */ -std::vector BLEScan::start(uint32_t duration) { +BLEScanResults BLEScan::start(uint32_t duration) { ESP_LOGD(LOG_TAG, ">> start(%d)", duration); m_semaphoreScanEnd.take("start"); - clearAdvertisedDevices(); + m_scanResults.m_vectorAdvertisedDevices.empty(); esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); m_semaphoreScanEnd.give(); - return m_vectorAvdertisedDevices; + return m_scanResults; } errRc = ::esp_ble_gap_start_scanning(duration); @@ -211,7 +219,7 @@ std::vector BLEScan::start(uint32_t duration) { if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); m_semaphoreScanEnd.give(); - return m_vectorAvdertisedDevices; + return m_scanResults; } m_stopped = false; @@ -220,7 +228,7 @@ std::vector BLEScan::start(uint32_t duration) { m_semaphoreScanEnd.give(); ESP_LOGD(LOG_TAG, "<< start()"); - return m_vectorAvdertisedDevices; + return m_scanResults; } // start @@ -245,4 +253,24 @@ void BLEScan::stop() { ESP_LOGD(LOG_TAG, "<< stop()"); } // stop + +/** + * @brief Return the count of devices found in the last scan. + * @return The number of devices found in the last scan. + */ +int BLEScanResults::getCount() { + return m_vectorAdvertisedDevices.size(); +} // getCount + + +/** + * @brief Return the specified device at the given index. + * The index should be between 0 and getCount()-1. + * @param [in] i The index of the device. + * @return The device at the specified index. + */ +BLEAdvertisedDevice BLEScanResults::getDevice(uint32_t i) { + return m_vectorAdvertisedDevices.at(i); +} + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index e9a6d90d..fc75305c 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -19,6 +19,16 @@ class BLEAdvertisedDevice; class BLEAdvertisedDeviceCallbacks; class BLEClient; +class BLEScan; + +class BLEScanResults { +public: + int getCount(); + BLEAdvertisedDevice getDevice(uint32_t i); +private: + friend BLEScan; + std::vector m_vectorAdvertisedDevices; +}; /** * @brief Perform and manage %BLE scans. @@ -28,22 +38,20 @@ class BLEClient; class BLEScan { public: BLEScan(); - virtual ~BLEScan(); //virtual void onResults(); - void setActiveScan(bool active); - void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks); - void setInterval(uint16_t intervalMSecs); - void setWindow(uint16_t windowMSecs); - std::vector start(uint32_t duration); - void stop(); + void setActiveScan(bool active); + void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks); + void setInterval(uint16_t intervalMSecs); + void setWindow(uint16_t windowMSecs); + BLEScanResults start(uint32_t duration); + void stop(); private: friend class BLE; void gapEventHandler( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); - void clearAdvertisedDevices(); void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); @@ -51,7 +59,8 @@ class BLEScan { BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks; bool m_stopped; FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); - std::vector m_vectorAvdertisedDevices; + //std::vector m_vectorAvdertisedDevices; + BLEScanResults m_scanResults; }; // BLEScan #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index a60ffa99..024fa31c 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -16,7 +16,6 @@ BLE_FILES= \ BLEAddress.h \ BLEAdvertisedDevice.cpp \ BLEAdvertisedDevice.h \ - BLEAdvertisedDeviceCallbacks.cpp \ BLEAdvertising.cpp \ BLEAdvertising.h \ BLECharacteristic.cpp \ diff --git a/cpp_utils/tests/BLE Tests/SampleClient.cpp b/cpp_utils/tests/BLE Tests/SampleClient.cpp index 61ac572d..5749880d 100644 --- a/cpp_utils/tests/BLE Tests/SampleClient.cpp +++ b/cpp_utils/tests/BLE Tests/SampleClient.cpp @@ -80,16 +80,16 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { /** * Called for each advertising BLE server. */ - void onResult(BLEAdvertisedDevice *pAdvertisedDevice) { - ESP_LOGD(LOG_TAG, "Advertised Device: %s", pAdvertisedDevice->toString().c_str()); + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); - if (pAdvertisedDevice->haveServiceUUID() && pAdvertisedDevice->getServiceUUID().equals(serviceUUID)) { - pAdvertisedDevice->getScan()->stop(); + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + advertisedDevice.getScan()->stop(); - ESP_LOGD(LOG_TAG, "Found our device! address: %s", pAdvertisedDevice->getAddress().toString().c_str()); + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); MyClient* pMyClient = new MyClient(); pMyClient->setStackSize(18000); - pMyClient->start(new BLEAddress(*pAdvertisedDevice->getAddress().getNative())); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); } // Found our server } // onResult }; // MyAdvertisedDeviceCallbacks diff --git a/cpp_utils/tests/BLE Tests/SampleScan.cpp b/cpp_utils/tests/BLE Tests/SampleScan.cpp index 716b3cf2..f2257ff9 100644 --- a/cpp_utils/tests/BLE Tests/SampleScan.cpp +++ b/cpp_utils/tests/BLE Tests/SampleScan.cpp @@ -10,8 +10,8 @@ static const char LOG_TAG[] = "SampleScan"; class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { - void onResult(BLEAdvertisedDevice *pAdvertisedDevice) { - ESP_LOGD(LOG_TAG, "Advertised Device: %s", pAdvertisedDevice->toString().c_str()); + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); } }; @@ -21,8 +21,8 @@ static void run() { BLEScan* pBLEScan = BLE::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); - std::vector foundDevices = pBLEScan->start(30); - ESP_LOGD(LOG_TAG, "We found %d devices", foundDevices.size()); + BLEScanResults scanResults = pBLEScan->start(30); + ESP_LOGD(LOG_TAG, "We found %d devices", scanResults.getCount()); ESP_LOGD(LOG_TAG, "Scanning sample ended"); } From 77b0670b523bd84493a6e7cac8e973a5aad70afd Mon Sep 17 00:00:00 2001 From: copercini Date: Tue, 8 Aug 2017 20:16:55 -0300 Subject: [PATCH 016/381] Provide signatures to allow BLEUUID as string Code changes for https://github.com/nkolban/esp32-snippets/issues/31 --- cpp_utils/BLEAdvertisedDevice.cpp | 8 ++++++++ cpp_utils/BLEAdvertisedDevice.h | 1 + cpp_utils/BLEAdvertising.cpp | 12 ++++++++++++ cpp_utils/BLEAdvertising.h | 1 + cpp_utils/BLECharacteristic.cpp | 20 ++++++++++++++++++++ cpp_utils/BLECharacteristic.h | 4 ++++ cpp_utils/BLECharacteristicMap.cpp | 8 ++++++++ cpp_utils/BLEClient.cpp | 10 ++++++++++ cpp_utils/BLEClient.h | 1 + cpp_utils/BLEDescriptor.cpp | 8 ++++++++ cpp_utils/BLEDescriptor.h | 1 + cpp_utils/BLEDescriptorMap.cpp | 26 +++++++++++++++++++++++--- cpp_utils/BLERemoteService.cpp | 10 ++++++++++ cpp_utils/BLERemoteService.h | 1 + cpp_utils/BLEServer.cpp | 14 ++++++++++++++ cpp_utils/BLEServer.h | 3 +++ cpp_utils/BLEService.cpp | 28 +++++++++++++++++++++++++--- cpp_utils/BLEService.h | 5 +++++ cpp_utils/BLEServiceMap.cpp | 10 ++++++++++ 19 files changed, 165 insertions(+), 6 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index a34ad5bc..97ec1e33 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -352,6 +352,14 @@ void BLEAdvertisedDevice::setScan(BLEScan* pScan) { m_pScan = pScan; } // setScan +/** + * @brief Set the Service UUID for this device. + * @param [in] serviceUUID The discovered serviceUUID + */ +void BLEAdvertisedDevice::setServiceUUID(const char* serviceUUID) { + return setServiceUUID(BLEUUID(serviceUUID)); +} // setRSSI + /** * @brief Set the Service UUID for this device. * @param [in] serviceUUID The discovered serviceUUID diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index a2c33613..2fb26522 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -59,6 +59,7 @@ class BLEAdvertisedDevice { void setName(std::string name); void setRSSI(int rssi); void setScan(BLEScan* pScan); + void setServiceUUID(const char* serviceUUID); void setServiceUUID(BLEUUID serviceUUID); void setTXPower(int8_t txPower); diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 6031fb7d..ad076ddd 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -56,6 +56,18 @@ void BLEAdvertising::setAppearance(uint16_t appearance) { } // setAppearance +/** + * @brief Set the service UUID. + * We maintain a class member called m_advData (esp_ble_adv_data_t) that is passed to the + * ESP-IDF advertising functions. In this method, we see two fields within that structure + * namely service_uuid_len and p_service_uuid to be the information supplied in the passed + * in service uuid. + * @param [in] uuid The UUID of the service. + * @return N/A. + */ +void BLEAdvertising::setServiceUUID(const char* serviceUUID) { + return setServiceUUID(BLEUUID(serviceUUID)); +} /** * @brief Set the service UUID. * We maintain a class member called m_advData (esp_ble_adv_data_t) that is passed to the diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 5c86bd96..2d0b51c2 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -23,6 +23,7 @@ class BLEAdvertising { void start(); void stop(); void setAppearance(uint16_t appearance); + void setServiceUUID(const char* serviceUUID); void setServiceUUID(BLEUUID serviceUUID); private: esp_ble_adv_data_t m_advData; diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 7f2a856c..2f77301d 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -23,6 +23,16 @@ static const char* LOG_TAG = "BLECharacteristic"; #define NULL_HANDLE (0xffff) + +/** + * @brief Construct a characteristic + * @param [in] uuid - UUID (const char*) for the characteristic. + * @param [in] properties - Properties for the characteristic. + */ +BLECharacteristic::BLECharacteristic(const char* uuid, uint32_t properties) { + BLECharacteristic(BLEUUID(uuid), properties); +} + /** * @brief Construct a characteristic * @param [in] uuid - UUID for the characteristic. @@ -122,6 +132,16 @@ void BLECharacteristic::executeCreate(BLEService* pService) { } // executeCreate + +/** + * @brief Return the BLE Descriptor for the given UUID if associated with this characteristic. + * @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve. + * @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned. + */ +BLEDescriptor* BLECharacteristic::getDescriptorByUUID(const char* descriptorUUID) { + return m_descriptorMap.getByUUID(BLEUUID(descriptorUUID)); +} // getDescriptorByUUID + /** * @brief Return the BLE Descriptor for the given UUID if associated with this characteristic. * @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve. diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 854c323c..b39c0226 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -26,8 +26,10 @@ class BLECharacteristicCallbacks; */ class BLEDescriptorMap { public: + void setByUUID(const char* uuid, BLEDescriptor *pDescriptor); void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor); void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor); + BLEDescriptor *getByUUID(const char* uuid); BLEDescriptor *getByUUID(BLEUUID uuid); BLEDescriptor *getByHandle(uint16_t handle); std::string toString(); @@ -52,10 +54,12 @@ class BLEDescriptorMap { */ class BLECharacteristic { public: + BLECharacteristic(const char* uuid, uint32_t properties = 0); BLECharacteristic(BLEUUID uuid, uint32_t properties = 0); virtual ~BLECharacteristic(); void addDescriptor(BLEDescriptor* pDescriptor); + BLEDescriptor* getDescriptorByUUID(const char* descriptorUUID); BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); //size_t getLength(); BLEUUID getUUID(); diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index 689692fc..f475e835 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -10,6 +10,14 @@ #include #include "BLEService.h" +/** + * @brief Return the characteristic by UUID. + * @param [in] UUID The UUID to look up the characteristic. + * @return The characteristic. + */ +BLECharacteristic* BLECharacteristicMap::getByUUID(const char* uuid) { + return getByUUID(BLEUUID(uuid)); +} /** * @brief Return the characteristic by UUID. diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index d4704f6d..27086e85 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -202,6 +202,16 @@ esp_gatt_if_t BLEClient::getGattcIf() { } // getGattcIf + +/** + * @brief Get the service object corresponding to the uuid. + * @param [in] uuid The UUID of the service being sought. + * @return A reference to the Service or nullptr if don't know about it. + */ +BLERemoteService* BLEClient::getService(const char* uuid) { + return getService(BLEUUID(uuid)); +} + /** * @brief Get the service object corresponding to the uuid. * @param [in] uuid The UUID of the service being sought. diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index b66b75c2..8aa3edbc 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -32,6 +32,7 @@ class BLEClient { void disconnect(); BLEAddress getPeerAddress(); std::map* getServices(); + BLERemoteService* getService(const char* uuid); BLERemoteService* getService(BLEUUID uuid); void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); std::string toString(); diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 5f96c97b..901cf8a6 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -21,6 +21,14 @@ static const char* LOG_TAG = "BLEDescriptor"; #define NULL_HANDLE (0xffff) + + +/** + * @brief BLEDescriptor constructor. + */ +BLEDescriptor::BLEDescriptor(const char* uuid) { + BLEDescriptor(BLEUUID(uuid)); +} /** * @brief BLEDescriptor constructor. */ diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index 089ff4cb..1d32d500 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -23,6 +23,7 @@ class BLECharacteristic; */ class BLEDescriptor { public: + BLEDescriptor(const char* uuid); BLEDescriptor(BLEUUID uuid); virtual ~BLEDescriptor(); diff --git a/cpp_utils/BLEDescriptorMap.cpp b/cpp_utils/BLEDescriptorMap.cpp index 3beb9096..b2116521 100644 --- a/cpp_utils/BLEDescriptorMap.cpp +++ b/cpp_utils/BLEDescriptorMap.cpp @@ -12,6 +12,16 @@ #include "BLEDescriptor.h" #include // ESP32 BLE +/** + * @brief Return the descriptor by UUID. + * @param [in] UUID The UUID to look up the descriptor. + * @return The descriptor. If not present, then nullptr is returned. + */ +BLEDescriptor* BLEDescriptorMap::getByUUID(const char* uuid) { + return getByUUID(BLEUUID(uuid)); +} + + /** * @brief Return the descriptor by UUID. * @param [in] UUID The UUID to look up the descriptor. @@ -44,9 +54,19 @@ BLEDescriptor* BLEDescriptorMap::getByHandle(uint16_t handle) { * @param [in] characteristic The descriptor to cache. * @return N/A. */ -void BLEDescriptorMap::setByUUID( - BLEUUID uuid, - BLEDescriptor *pDescriptor) { +void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor *pDescriptor){ + m_uuidMap.insert(std::pair(uuid, pDescriptor)); +} // setByUUID + + + +/** + * @brief Set the descriptor by UUID. + * @param [in] uuid The uuid of the descriptor. + * @param [in] characteristic The descriptor to cache. + * @return N/A. + */ +void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor) { m_uuidMap.insert(std::pair(uuid.toString(), pDescriptor)); } // setByUUID diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index 2821f563..dbfe27b5 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -101,6 +101,16 @@ void BLERemoteService::gattClientEventHandler( } // gattClientEventHandler +/** + * @brief Get the characteristic object for the UUID. + * @param [in] uuid Characteristic uuid. + * @return Reference to the characteristic object. + */ +BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) { + return getCharacteristic(BLEUUID(uuid)); +} + + /** * @brief Get the characteristic object for the UUID. * @param [in] uuid Characteristic uuid. diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index c73376aa..4393fbcf 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -30,6 +30,7 @@ class BLERemoteService { virtual ~BLERemoteService(); // Public methods + BLERemoteCharacteristic* getCharacteristic(const char* uuid); BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); void getCharacteristics(void); BLEClient* getClient(void); diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 5555c9e7..1cf4edec 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -47,6 +47,20 @@ void BLEServer::createApp(uint16_t appId) { registerApp(); } + +/** + * @brief Create a %BLE Service. + * + * With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition + * of a new service. Every service must have a unique UUID. + * @param [in] uuid The UUID of the new service. + * @return A reference to the new service object. + */ +BLEService* BLEServer::createService(const char* uuid) { + return createService(BLEUUID(uuid)); +} + + /** * @brief Create a %BLE Service. * diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 12b997f9..916cdc2d 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -29,12 +29,14 @@ class BLEServerCallbacks; class BLEServiceMap { public: BLEService* getByHandle(uint16_t handle); + BLEService* getByUUID(const char* uuid); BLEService* getByUUID(BLEUUID uuid); void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); void setByHandle(uint16_t handle, BLEService* service); + void setByUUID(const char* uuid, BLEService* service); void setByUUID(BLEUUID uuid, BLEService* service); std::string toString(); @@ -53,6 +55,7 @@ class BLEServer { uint32_t getConnectedCount(); + BLEService* createService(const char* uuid); BLEService* createService(BLEUUID uuid); BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks *pCallbacks); diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 99706590..8db43713 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -26,6 +26,15 @@ static const char* LOG_TAG = "BLEService"; // Tag for logging. +/** + * @brief Construct an instance of the BLEService + * @param [in] uuid The UUID of the service. + */ +BLEService::BLEService(const char* uuid) { + BLEService(BLEUUID(uuid)); +} + + /** * @brief Construct an instance of the BLEService * @param [in] uuid The UUID of the service. @@ -200,9 +209,17 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { * @param [in] properties - The properties of the characteristic. * @return The new BLE characteristic. */ -BLECharacteristic* BLEService::createCharacteristic( - BLEUUID uuid, - uint32_t properties) { +BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) { + return createCharacteristic(BLEUUID(uuid), properties); +} + +/** + * @brief Create a new BLE Characteristic associated with this service. + * @param [in] uuid - The UUID of the characteristic. + * @param [in] properties - The properties of the characteristic. + * @return The new BLE characteristic. + */ +BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) { BLECharacteristic *pCharacteristic = new BLECharacteristic(uuid, properties); addCharacteristic(pCharacteristic); return pCharacteristic; @@ -289,6 +306,11 @@ void BLEService::handleGATTServerEvent( } // handleGATTServerEvent +BLECharacteristic* BLEService::getCharacteristic(const char* uuid) { + return getCharacteristic(BLEUUID(uuid)); +} + + BLECharacteristic* BLEService::getCharacteristic(BLEUUID uuid) { return m_characteristicMap.getByUUID(uuid); } diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 313437d2..86d0776b 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -24,8 +24,10 @@ class BLEServer; */ class BLECharacteristicMap { public: + void setByUUID(const char* uuid, BLECharacteristic* pCharacteristic); void setByUUID(BLEUUID uuid, BLECharacteristic* pCharacteristic); void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); + BLECharacteristic* getByUUID(const char* uuid); BLECharacteristic* getByUUID(BLEUUID uuid); BLECharacteristic* getByHandle(uint16_t handle); BLECharacteristic* getFirst(); @@ -50,12 +52,15 @@ class BLECharacteristicMap { */ class BLEService { public: + BLEService(const char* uuid); BLEService(BLEUUID uuid); void addCharacteristic(BLECharacteristic* pCharacteristic); + BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties); BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); void dump(); void executeCreate(BLEServer* pServer); + BLECharacteristic* getCharacteristic(const char* uuid); BLECharacteristic* getCharacteristic(BLEUUID uuid); BLEUUID getUUID(); BLEServer* getServer(); diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index 3969a10f..8fdbd5ac 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -10,6 +10,16 @@ #include #include "BLEService.h" + +/** + * @brief Return the service by UUID. + * @param [in] UUID The UUID to look up the service. + * @return The characteristic. + */ +BLEService* BLEServiceMap::getByUUID(const char* uuid) { + return getByUUID(BLEUUID(uuid)); +} + /** * @brief Return the service by UUID. * @param [in] UUID The UUID to look up the service. From ca2e7918cc08195694a4c914460c6db0a796c78a Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 8 Aug 2017 23:27:14 -0500 Subject: [PATCH 017/381] Added build instructions for building the ZIP --- cpp_utils/ArduinoBLE.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 cpp_utils/ArduinoBLE.md diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md new file mode 100644 index 00000000..149c1d66 --- /dev/null +++ b/cpp_utils/ArduinoBLE.md @@ -0,0 +1,20 @@ +# Arduino BLE Support +As part of this Github project, we provide libraries for Bluetooth Low Energy (BLE) for the ESP32 Arduino environment. Support for this capability is still in the process of being cooked (as of August 2017). As such you should not build product based on these functions as changes to the API and implementation are extremely likely over the next couple of months. + +That said, we now have the ability to produce a driver you can use for testing. This will give you early access to the function while give those who choose to build and maintain the code early feedback on what works, what doesn't and what can be improved from a usage perspective. + +When complete, the BLE support for ESP32 Arduino will be available as a single ZIP file. The file will be called **ESP32_BLE.zip**. It is this file that will be able to be imported into the Arduino IDE from the `Sketch -> Include Library -> Add .ZIP library`. When initial development of the library has been completed, this ZIP will be placed under some form of release control so that an ordinary Arduino IDE user can simply download this as a unit and install. + +However, today, we are choosing **not** to distribute the BLE support as a downloadable library. The reason for this is that we believe that the code base for the BLE support is too fluid and it is being actively worked upon. Each day there will be changes and that means that for any given instance of the ZIP you get, if it is more than a day old, it is likely out of date. + +Instead of giving you a fish (a new ESP32_BLE.zip) we are going to teach you to fish so that you yourself can build the very latest ESP32_BLE.zip yourself. Hopefully you will find this quite easy to accomplish. + +Here is the recipe. + +1. Create a new directory called `build` +2. Enter that directory and run `git clone https://github.com/nkolban/esp32-snippets.git` +3. Change into the directory called `./esp32_snippets/cpp_utils` +4. Run `make -f Makefile.arduino build_ble` +5. Change into the directory called `./Arduino` + +And here you will find the `ESP32_BLE.zip` that is build from the latest source. You can then install this into your Arduino IDE environment are you are ready to go. \ No newline at end of file From 7b95ae5d3eafcde6b267978cd2b82c4b843cbc2d Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 12 Aug 2017 20:02:34 -0500 Subject: [PATCH 018/381] Update of the BLE C++ Guide documentation to reflect BLEScan changes --- Documentation/BLE C++ Guide.pdf | Bin 93029 -> 283509 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Documentation/BLE C++ Guide.pdf b/Documentation/BLE C++ Guide.pdf index 3049bb1c5641a0483cd9da6845c46f695c150278..e68e0398173ea68d4dc5817a7e3f16b01ef18740 100644 GIT binary patch delta 257397 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�Mg7S>vYGwB?Aym^T>AQOr({Yg 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

~ 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

aMllGe$BEHf~r4ooS~Ptmp#%OZ&2> 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 Ld2UJ8l6CQFUJsQG5UG>8}qmQKh28 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�pdz);&G7z-9tG#{W~d~c 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�`g|ql^OUiWbJE3tkGmDeQ8?ZeLT%T72}(5>1O? z2lWn=!cchu2gH|SS!gU8z3lGK?VlcJv9Fq09#*c?{`}r=8PGCOr{Ub7 zqAUh5aqEI_;!WOSt#rMBv@$0zA9ybwf;4xqNHHkqka}bQ0Z)2GcjD>egD;-e9!N*i z4$mE3>KkWYk+|J8D>m~)o5yn?vxzwQGp_rm-)hE@JH1UGgmd{JyKfqZWS@6ih@zf$ zK?ZyF3N@U(rFYNF2%rPIqV6Pz9o7^&;yOLE!DZuTP|r;SrpuS6BxkD?_dQ~{n>xtUK$FO>B5$qRsh zB}LTy`kghhjmxUR-vY-zCRJmV4MQiK``VV-?*g3*y$$o4dpDz9iBcoL93J#Jk{+Jrxpn~zt+cFT!DhJ*zV z$!um@Upqy|DH8qt&F(Q3G?d<+4~~K9_8&rN;aeTyF1Z{wz0b!L^w4MOI3_xrPtU7_ z&y4qji?GCRu*j@bUq%=$gei&`r_8?fpoaCO(0wbbWp#eO6F%G506cCFeKi8hJq}nt zAAWPQCm+fA0&iYlsj|ITNt-j>xeCtzG;%Igo`)a%qsP!G2IkOG?TF;hj@JV56qwQ@ zsWr?Oc5p(v->gynzzhr_eUI(6HIY&ezD}uUsJ~JO_J56`0HvTOf=8edX-YIiUw z*>F|r|0pX`d>B0IW~p1(YRCofc<`!pdhA`;bum{T%+-->--4nHXYwavsje}+iI`CM zPvTYA8Z`OsUN?R9=*QHnr2JL={B>|#)ak@BC}T}yXQi)>!dA35J7eI{D1Gw&Si?le(TLuA*GOo&TnCe*l?N8*xG2b^5_D1>mF4@Ow%q2 zJnyG%D!i<|BM@=#u|~C20{daSqW1R2pEuoJJfxq?1H_H9Njv!bec#tk@YOgu!LQvb zGuTiXv<&Rpl`_*XaY`HAYr%f&;+SZ2j*70%%Ee`^44SND(uF#jQ_DunC?rZok|waD zkA>b6HzxVO75R{ky;%ciyf;()nQI%RZZKu;2Mh;#+f|ikdz&|{X--#10BDPF`?PD= z6yI+;Keg=7)vt$Sz6A!}k9%$QF^P(?Ig5U?$Bt$Y=3}XDzDD?}<0@J6C?!JX$pcV!&?jHlHyI|q) zL8i*)-SEzHG0FgBq$J5-{yeM>v3`|Tvleqma!AaQs{(k!BGJr-G~9i-xshd$hKA@q zDJsJ$sz2hDP1%?gIY#~HS<;WQF-VE1ngNK2bJhz!I7oDH7r*g35)hAi8PEbZ+MF^d z^V49>{xEln{32c?^V0{%63*2}@6X`KBTIh8v`1Eb2I~g8%wOMrAU zwauxCRS(9-bVSJHMYKwUoF}P5eao0UeAsVspa|Gx52nL(d6L%uW^MoMrMW%ES=?9$ zF$oop9@NDc!w( zpq8?WPBcegV!(hIz}X%EZF6W zz8H6%4R?NaGpHrUNNINS_sMZp=PIOy&W^qB5G3Hxt3~g+3K`wDV~y1?mxqfr;n*t@ zHuDh(&*#{CsdE(p-}M_z^d`+L<}m@@``iy&yJk5NtnAk|4lCn%4$QFG{uA+FsF7cv zWR5zZ!``y+GLO0!uiO#ez(lJ9gEJD=P+|h_CKiG>h2^EWV(W`s(!+qu#>a165iEVo zlorN!gEzMCe*-&RXKxMek>dU$uvC}q zi~RR!EA&EQy=d~C+y%kb~AnT3*NrHVn!Bsb4?g> zHP%Ie9usRx5B|f`FsEuxbrZ}g;s}kry$d=Oj$nkFR@vkj7ySDqXATrs4Odn?B1L+n zgr9(I42e<0&DA%U(p4ZhF=)1~WZRaBZr+w8ntnl{U{7V^haw!IPTbYnm)qp4+x;AX z^j5bu=pD^!lgITZE=GiN^z4@_W_Ct9cK>YyteMNd`99-~q*>ChZhuo~h&^i{jG#{$ zx>}qVKFQ#=tRT&EwD70B+D0IfCy9{aAgrK<0wCT*K0hILMnUiqdI&&QL~FH9AcJwk zcu7Fm-FtVXLHG)uhQV#Wf$VY z_kG?5Gi!e3d%pUKRBIb}knd)-zw0)IXCXT>yU-kgA1!_60I;1SGF$jwKec8^OVq0Yi^c4NNg%^_T)Hi$jR-r=!QY)aaRQl!7(%+-wxfhVZ)s;vmpOB-ME|f_; zcDr22q`3h1#(6=#EksG9(OpUhj+RqqPk2zZWJWZ-fz{>?Er7Fp6{U*G^;Pf zSq?Q#_kDa{$XoY*@_zHd_5{rb@^ir62kj%&lYu8)Eeam%k8F5j_=^jGqI|YwQSWx00}z@xZ#Lco!@c6`8{zvnE2P9l{!2qc0V za}sZZB0<|CJ!S7D1HyrpmQi~MTs{(9dOe`;NXHHW$>91(G5GR(AhV#V&#SBzdyn{S6Z-yKSNo6*b6%=?&7~MpyP55qi!Yy~t zeKm3CXoPJ95B`AdL~4=|hVD7)+#f9pT~QWzt3_)I@iX`e-bBC2hYO`9H7kdNDpV;= zrMWVPMh{=U{_Gn@c=CA?kskK+?;^=I&RR^qV^aMI3x?Je-}NgS2-2$?Uyi;KgfMlg zxLi5jQMw+tIC-68J8iGX=qiK_oVVz#Qs0oOpvauRP=2GRPclGsE{0N})7`L`)*24>DCQU^jT`J}?PDJHE_5&h2s@ zcw!4i_<(`YdLSnb@HypAgSu>f1F~AVayC}Kiu@M!aN^=AJ!eYm=s-#3X)U2LG|VrY z4>!0zNgB=N=~Y)&M-PXl3zV%E%U|YJ9lT$X7$a{5JBZtOpsI-Mo1oek@5O+WLGKFb zJE6)5>l;_##13rFuVI2i2^Ug9m!!%fgVRP|3z`oR&zr#mAgSS4Oqr50L;JK8up@-x znrdm`CTEqu&_uCCIy1u!=U@G%x>IVP&1a;9({GDjHHQXQ_#u)@zZvYm#3N%t7$QXE z%z$B>_-eX2?+79tN9s6xQL84!T5rSZSzm7V0mcbrMl>JP;s3t7&u&=7YXU>VXQ551FZO&kceEwM*=Pf#cTa^%fFXerm>;5MwaVir>- zMTFssWt!H{o3dO7keoeI$U!Z9X6t%~UjG-u2;s#y_7Sh3Tf zz~_mX5E;`S*79PCbvpXDfGl3b^AC@bSM(%BqY$nW0 zS%IwVCtvb^w&d2C&V4*@w|h+gd406m?5M1+aM5h3D+N&B_Pf@j2>s~fK8sN7vmHX; zqyMx4D&<;*%XeN!hZgTy{yqC}H?y7mww%BZvsxsqA@2fFLWv*so~jgbXNDOcK1+vW z#^pFB*6iz+-+ArQgRXsTJn(tYIvLnRF2zfi#Tf3C=5GmH3V20R(OB5Y+0YH4D#h^h zAG19UR*MO>I^K)ws`_jXW;1QNZ!5_te@{*T@h%_79^FrWE-p8OhIOCjiJaeW26m0k zPF51rtBG~L*1m<^NO)~g1ks$V^%h(6TZ$3 z2)~hg7sN0BV$qPU<+Pb-bZ}1|+?%(B>LkH5xtluo`kF81n`MrHS;p1Cs1;fifo(*N zz0jCPWMXvU`~E}MV>A5*@kXvmAeCnWHu~q|-6)>kqVcm76|E3CT=H0Orl_cD3zoH- zG~Kg94NXzfTZ?wZjCInD2X8@UCJ%)Zzyw9+Go|-C0|`97e_(G7A08W4^PGKe;}T*N zHt$Qw7g;WnlYqyJnacor%7MA7*4Gn@5X{H(21%pTqRXXKH6VmeNZ?&8-OB)&Z`LmT9Xod|Pn9s-YBFA5RL*xm+ z{PHnIBPDiZ%)}g&jvyo08$nt$46aroz?La*(?~5LiNu&H=oM-zDU#W87oJMoPIl>f zc2_IbZgF_bAnZo_X1LAsT7fnN0(8|Hvw7YjgEVVj_2u*rc;*ekYmL%s$E-&>JNSM* z24zJsU`35O`rxfP$|g0K{k)S8s6nBD-<=VXHD0G^B_v#jh-O*eZ@hA)<)N=GX~A}Vk|yN26$tE@vV$09|WSJ zr{d@fTUbM?O25{g7^b)E6yd4Rr?^O${BOwM*v744#;3?`Dujb~tU1AKgtv2r$%MsQ z@VeaTync8_8B8Z0csIr#0>$Omb>d>%!Q87@r8aR1RqSZ`^QJty#RtCkvgNp7yopW% zS>FlNqK9tlqlcnRGMj(8Hqg8K*1FG@sv_+{cg)B!V14aC$&WNzk`;d7V3w$s8jLIb7E9N8L2X&A-j`Jtb~GUc{**G}J^mA`-7qIE7=)lwxV(REe( zUDXqx!}*cGXffVC5`RPN%OFOF>J`sLhWKTJQia&(uF2}Z2Xu&*zo+z}noJ3xq;yrl z@8~blT~28HV21BSlRi8CCROvxvxQ6RRI#`e>kZ+sZ@X8XMI4b}qJZDhPwT|y=id|K z?~fZyfYSgLO(u&AkqAq7*!R~{sqUtCd#SDa=34&+AA=v{Yc|0#QH0E(PS2#-z9C4# zL8g7rND2roU_htcUR@hD*_nN=2pq3nIJIe5iaM_aGxe0aT*jj0v%t+SDk7*Gl33)F zNp&X44TE#cz%<1>H|Gj>_fVbG7{pB%C!w%S<9b?_K-kmPg2d9daT|VD;g7pi^pT}o@2w6&EsnMa!mXH^cnWq7k~x+G`se|_oZz!$v*FRxg_Lr zOZ+zHE~(@~cB!;3UAw$KhD^FYSfu_!EMF%AkUw5Nu!wj7thK!Ps?7Hp zojI%ZWdNY<b_ zZY5w_rPD@hAO_ba?0TTSdkIzvXO}Z%R@o6n;<57U*q~fRT3Ge%=U;cg^6O-)MO^Y| zyI~Z@3c0VAUUSjgxZ}S zTD1BW%ZqlX&&AuHA7s&do>#uCBfZUI&Ew6y06JIn``wmTJ2cW($Iqs5kL=Hrc>ZY+ zbZ#x(p}#2|h8UjFA9u#G)2rtceXnzCq+U`JhxXjzpp%Ap?~tJ2U1{yjU2nP`>+PR* zfXt`RlQJJ=x4pTb&MVM6Hq94^@l#hp?KbI+y%ChAHd?(6d11g!ObyQgQ;>ibXbwk~3mzB2P{1vK@<4??fQrrYT z6XQShC_!?FO;z;4X7f|9^}sK=B5araMz%foUJyq~@U0t@s)R4Yp_0%lI;DUdF zMSr{QNB+1w3?_Qtbu#8x?)^(&gFMDCCSfe_(fUR&?Gso7;&}so13y9X4BcW$m`vn5 zZntHPUdEkxi#bQF@`cVl0V!M}*}o|uDMdqeWK`RkjJ`yEnoIwrzdg6Y<06$7xH4Ee zX<3KCS?48ylv~I8Z7p2SW)rb-AH;J&XuO2l0?I`gOyuRKC+_osA1-KD=o$b_2rHE>pf63FE4DEB)#i3~Uk{iwY73&PoiZFe&j z!CbuiXTHa~5rnd5yXbVh0^v0`2%2Gu(Z%JAu)G6zHA2X!nsi@?p4N^9``xY~8xfK( z%kk(LCNZO^@KgzdGE%xls-!a+d$JRif*J_y8Ro`hyT}t)8gbKgZI)6p0i-N-2q|Ih zVVH_7OID77KbmA*3{;^D;(}_1J+LS1sk3*9DbmNBQnkh29_d!xU@rML_3J|pl!{_x zw*@ch>(!PUv(*>VD-nWDkqh}#-muFddF!@-y=+R(b$Un;y7WN7F{a0FOqZER^YuT* z4)&J3c7uzUlNEcGN2aUVz(#h*oCxukU#6r8(g6b^J?Ebn9|^4+{$c5YL}X5p5(*m# zQgmGl(Ps^+c&~Gp-p|R?RlsKzw{d8?k-FT*^$&yi*6Y?h^d(i|&-iW0O}ryDGP#TvDW@N|K&EQ>^u93GfRQ~6 zdvC$6UmixHJn6;l$@fkUFK`ukwL<@T4N*>NAvJf8Q`y(?RhRZ*D;ZXE`z-?bHZ?O0 z7me)JW0>ep=9{7Z;FQ3lpvxA}stz4*!7d#8iuYR-fv+bAaL_p(BVTfJ1g zf9XNIF7-@aLhd@h=>_Ody+;r>IM307R~G_K_3<>tK@0c36Yk$H+XT!Z3q`&gysV8$ z4SNezX`f1_QySTRk_5H8EFyE5Y+{$FI`JjFCn@Y=(7P-PAeDZ_jf|&bw zuA&Td`v8%vQj-Mw@lf!2Bxj<~U+!>4$z=4W{6Z~{qxe<98)4J1bpn47D;nRE@Wm0BgaRY{0}O!gbFXi98n&35J_r#^}@HDrM(|ZyGo%4lIhk> zVdn(~gKjhigRNMK=67+;Bv6-iVsGUJ!7Ys&4jluZfT)8s-ps}9cSi#Ed~XA!1UlB91M?f8!lc>T}QXZM>?^OdRxICxS#93dp-GrT37AVNGfrVrtzg|6avw*u`66iD|@1`?XtN{p@#OMmZBMwowY=vn?A*3(HyQmNp#Viraht? zSK<;Xw1}b^FH?V?G8Z(Lo)L`|vD*ZE>ZeL&iW_Yrdn~hRwq&{lJ@-!>-r_eK`QVio zW!yh{QBs9C`iD14W=RwKl1hKE*A&gN_U)R1f;n8Y9i)HYL9e1Tm1ObH`K1k6``4F6 zbKfV{-&U2^1dPwTvnBCHjock*tVq?4tA7+u8U}qRtGpIHY&}?LrVSnrZHd*E)}NhT zJjhse?(G**>zj?z92HH{?e$GvR83tBZ9Pmy?PUZWDILdr0_gEwA{J4Y5wxM zGT>|Ic!--!v)YMkc_^i~qt-T{9?i|pwi2XoY%wDaZ?0|5 z%`a`$N}MYcWt7o9G_gI<#Ba^bt#58Ew7A_aj?~lXs9TURn~8ha;5l~`76RhhTXn+P zi>-yVzpwS3sD7lYel1(+DlBhz;3hCokW+Gzl7e<#TG?o+^>XlFXj)oY*<1-g7p`mM zxR(zjCK;$2ipo2++RW6UX)1d2TFeay;gp0Pd_w;e#e# zz0)0Br63qNSgQ+Y-R@N!l=*rgOy3)pcS8GeXhmxV_Br~8Wx;9GT>3-cB z)DUA#x;G#4EGEqsD?2AzMX`xqj+jfD8ZkrtL@&DS$i+vh5ul!_-i31(Zb(wpUYm5Y z`D6XhSAv3+{h2r$>q>N`a>e|T!p7oX=1faAeV9qVIwRH7HmiQ9D*fO-)hIlqs6!l{ z4T*s)fVOBPmSq`4kw69bK6UUub)GfTPDc|G_F5#bs3zq!Mba zbANZE{mN9oOs66x)rCL!iJa6hqs?NaV&;LUUs0saq^XoE%J8SsUw=`jU>IY?fOg3y zuS42^eYOcBRbK02z)>r#OpSQ3{RAe>tn+Tez2*qs&DQQjT}B*e&U|pA4y1i1)Km?9 z&RXbDPFtK+a3odE9Yn$powLD{;ZSsEpI-}0a4Imj7HR#`T}#BI(#$lNjI=iVut1f_ zHLrMQUAWELu#I#sT<}||m8Z)lcCIv5QRmG;QH05{!i`*dZk#F15TtEs?sQ_3W!NX@~#Xso22svtyJsa0OpQ!1=zTS%t%Jddsn zeIiLFkTU(m)uV3hA}8N!YC@r~$*=MIVm#OHNo6vf)Ll7%HjyJSZ?d5=uR7O_wot~V zY+05o&-hL0d)1gK<@#hVeM+Z!nxaJ!C)?r3ryZ}6woZcI>RCQFOA;ODkWuO4cC3cedfk#u+Lv+#XuABD zJX&&oHtI1T^r$hXBJH;E(6NkRcv>iN$a&&%?)pw%6LbE%W#0|PNVUNKV=5UZNd6$unH!|$vyY&AX;;IP|hNh*6DB!`JEOcP{YAsooNw@mz4yAJYh zP}l8X*%(jf!@>RHgH(bMFkNJ&o~Vb#zap2f@Q(8V0a}!@j%2+q%7WUO+NUk+@Y zcu+nANS{bG7F)(Tf@e9L>dY%_D;%!&wx+hO^2?8&pSKg!+2Ig$(~#K)esYzr=0{4F zdrUPvn4S@#yfLmmCjiPfWGJEto{=r!r`=VTsP7K1iyR!%Ca)()zBX9P`qu>>Xj(m- z)L>Ta4|Hx^PkrAn^n+#^SKJ|T5Pxrq<6b>LM0DqY!uz+MBvHQ_)ZSwod>PfpUDavu z_e6ME#*5>Ad*I>E#r7mm?R(%KX*o;Dn)-akm0DBt8NB<7S+P|%+;g#3_ZnA&-u*NY ze{@TMg8MT`zcWl3*tPs7HXhj&VG(#dL)sM#OUm|aFW<#e)j_b{#3zKoAcF|>c|5@4 z8?7HLDea0uzOdr>97LK&S31a?%wz{n2`Zdy)ejy@^`A;YMYzAUg8x<5Vg9e?&i|9wVPj;AS2!jD zbP{Bu(-~2MF5lpYnu9T1E%nsGNR8qn6NLE}ECCQuN>Ou=@B1;p&n>r2)l-sLgA^Qo zLhPGAzg)0izJFp4KVTa>Tm;hxt-D5BF&5jN1W$yWyg19!zS~q!CLBmzT-&Ah-!ohSqFstE{h`E8E?9#=`N znhxSsJ(pv;4v%*M>2xD2%X2@S^--34Nk@p}d4MzguKT?JxxPD8=z_1bh%$ z$Gv<)$$lJ1&up9Vyc6({-xEy(QgNS9(w~|Zb3f(hF;4XqhG)0)yYjn*tEAq=|9*9P z>zz;DXR%+nGsb7PbC04v@}z-Tt~;#A;_l_NJBE9%cgQ3AlXt;Sf_H+K!}(W#?CduV zu5V^EjO;0A{J%}tJR-)aPOr`o^j}^`Qo4yk%rat%y0>FHz?=R(9S-rB$4H>e$#&D= z=*ck-;CRVaD&Ww`@k8L0%Bl@!YHk!?& zkbzrCuTdlv>9jjp)HRJaC6Q~*SDFz{HWi&wh8Dh0wJs0I&w25^o_Fg!Z@qGN-*R^k zKR$9JkCqN(5zhfR)B~iw{3qe?m6MS$xkMxZ@NXVUZ@V&wivrzID7VeIPfz>5r>))z zgzBVVs2L*V&XG5tk?UJw%=%yscyWivjTEau>T<;@44$ErgKM5YMUE4vvT4@>vNoeBR__nlCq08ijlzW5A(&ZX2J z`h77}M`XY@Nq1Y2AEec5=144kcA4)l4HPT%`@Vg?f+JDre$w*KT#7rIkqn_^OgDZJ zmwX9F^l$CG@W|E!I>-DokPTnn$zBNMT|)F%f9A~}7fbVvg;)uo<)V-aAH&_=ih4ye z$>l{(;0a^~5ON$e<~wHy6<>eR+g;Hgpab@|d5-}>SEM|~wQ$0i^9JB9`bLlfdJ~?E z(x(=4Gn8wFGiBUy829z?ZzL>kzZ+XRt-yum zQzJl?#Oe^di0PHP3GE;!_S^%$!}8|?zs7d=j2;90xpl5BbPu=Q_lnO&sz%K0_6v_mgX zT-`cz3pr#X4KFkA1Ua?-@#X;DG)w_z$M^zbNx1Mh_2K3mO#QyG;?K7JvFY7OaKfBu zXZ23c7Xry`K{E|Dt9`+KeP7fC+C94M)QdH|tP6ZdqH9a*DX6m`y{OHgzv(jH2}ppZ za|1VfaD4ffB;z>u@)YxrMDYosg3K;~HQt?*b4IOiy6u%5nKplNbWFijQdKQ~(3VQ_ z32BQ5KWnTfo{%|kv9fCx!>WUKRc5iC`Pa@{9^Ku6n|bN9Ev>~?@m>YNk|GybEk#W^ z63nh~w3rrOwn=0pshw23-Mzx})ph~550UuU0}9F~TjBxIEO>K&Rf9;9Tj6W6vM%aw zbmsH_?&r=IQWBRnicQ$kPFyK_A~y)-nUcLeYQAqe4Wutc23>1bqq{Hb{ajh6x&)3k})4Atv4y}rLNO$kZ zekmijt1=Fh-T51H5Lhuf0uU$RAn7wxvd_o8GXfUuk|nGwi;A3}a9jjhy<1_-n?HI*Oz0Fn_ zH}a)W!AzVoQ_b_}&NTQKY}Sb2N?UB|{H(s*^;VQpGq8g|q{S3mS;Oh5ff7a1A^#9$ zq8;UuNo&pzi}}3K-A`B1*+>4*nXx9mt|o1NRu_rIc8{@K{eH-b;KC?S#$}^uaGxT| zChzl?FEs3t(Rc~ok4x`LM?igDYbRAzwRC3*bSFUyuDLVtQIAD428v^@(bJ?xIF9TO zUc#8fcdKKKnIdADnflye%WKVnachH|^kjs77!1%xlU(>D>SK99FYDvnZ+?ZL=)9M; zP?fqCDRS`E$f1!v!U{=1Pfm(dD{fbABg8i!SdAQyT)`e`m&Md%zx0cuKZC~8GMk<7 zn{N$cYaFaH30mZVFg%wsWQu)4^;^=*V9dFdH4isjDRYiOa?@~NsS~@6BB6~Oj8+6U z4f1B{au-ZQ#7%JWL>GA0&Fgw*7TJw^!r=8_e5?(t4jRx$eZu`f7CeG6dqk6m32pAQ zLu#;$zdcSLx%AwQguf|nKv39NIs-h6%&abs(M%J7L^h^Gcg1)!%|t3kiCb0_1Ti2G zEq#uPSzy$o6xu;OmcRUsGzRv&y&!oBBx3wkP&FYj8R!0) zzIuy_K@|hpU4;v?wc|=BrxGc3?euQMEH8nO8-D3HQ}%st_(Xfv$0F@pg*|~YJ0=c1 zhGp%XzPi_XnfV!WU|i!~Q76sB z`H!_{B?@j}-l=A2%X2!(jWdL)gzXj6Bi0M{L;>;?lSXO*gBjD4W~Tfezk2=vXLE}u zvqD;d{a6KEAvhA)w*}R$-(h^Kv@8L;OhaEXT5PVQDRE@S{42P3kIiEg5hyw}M6uMO zmH2G*JTLyhZ;zJ$<<(_X4X7{5XSzyMko&ygKcfL^v$HyFUwCw%C&G+gCx)g?W_NXz zf=4;Bb^RD#c@4$9gk)faM1y!Dg-Vsn5z21r;z*O52U3dloX&I9iswsobR$(^r|8|K zHah?QW!tOO;gbhS+y(pVkRMOgil>|6WkCX@WrWA`r0VR6*=9Zca+95ZGAencT3Y&> zZ}JY%M*o^5jnpU~l9XFh(^ zhMlYy?uatJx4&>g7 zx3_)lkJfXv_waZ6ZMEKwes#yTy* zUm;3-R}xGsd&Z5JF}pzODdbbx_9c;zYb_kcnK!9 zzD*S?w;(CXl_la=D`-7fIYaNaVQa`2%bUfG-W_U}%o(XJL5PkV;zeRuS1TmPXa7}I zx;n(_v>Y6Wf{+8%G_YTwqmu?HmZwk)BSt?mK+Rf=vFt#a4^4<(B}jEVRIAg8os4cw zws}9RG?^xP-|21V=lBe~>|SqcI-Ad*AVSh%x+8p3wwMf)>Hi75(5~3qxg5qk++7@I z4Pf*Xa?Veyj!yecLkE}}SX(oBen|#cu#;0#_WJaj)pSc#&NjA!cu1x*AMO9@JKl7( zo@3TM9AF(aN|hX|ys0gJey&fnHFKUpJAP>5vIo z5?|5xnW+b?sE<%>g<5Xk9L$caB=K*5^eOSDcz=F%+u4OCd^vx+Hgh#J&+3$%CUO6xL@xn;EoLR;|5DKhkrJMrv;xJ$RID6Y-#=01=3ZD?r<8 z{En3K*ZDz0gJ}{Z`*jtB!D2cF6Kuq)k{vqx$e}#1RJY7uT?5+sy83`RfxpIpUSmrERdDC2SJ&I^(~bJDjX+X zrMqYpHigEH&p-#tktxU8RNq4Nm*X4VuRw`=p%ZzE#HR_bcW?p~XPwbBXxWwQ@cN@e z+Uw9KfkYWI2HguTE+U}Ay{UVrS}fe|gSfpF;tI(KnDhqMXU*5m)zs8HVJ>G*NKV!B zVl%rPIA{!^uNEtG9=&nzKWz(fP2~KIAlpVGaVx7`gsx6@p#bqTVKX6|toJyEIH=t6 zRr!sePLwJ8>G&G$)x}wqQh1FUo5Rv@y90}9RhV(QYOXOCtrZBwNTfj2$4;yhs$Ni2 z!IR3B3};R99M9qSnOtt~=Fk|SSmo$;VBBE1+%{`8`qQ>WLW0Mtzk#n{FVT0I?Q0a6 z*|--=^R9$Skxt|1(Qldx_Ke`la}kQ$XLo+x=6S$)V%=rEe{$k*LW?4}|Ldzn=>H>Cf)l@`Qm=`&G+YW+!y!aZUWo@5QeOoJ#JG+9TZ% z{vm&lMlHuXxaQg!kP=GKvf;csx_QjD{4E*rknkx$BEWgvwVwSYpkte z`c<%v9H>5id~>#1AJ`= znqr-b?YEKk5R-v^AZrfR>I_AGGfU0iKPG1N-{C3F-d)9Ry-^i;-R0$UN6Jm>I_2{L z*FpNCvvZM&SY`NezjmMY|EGZJzE5twZo)(h1I3qN zw0p7>N?r&5>Sm6=u6kAX;(YTyDEj z9lzAL@79N*M?(JvOFHcy$JM+6p~r2p9k$PO+onHG_m?y%GTwA6pmo#%iNm$H#yCm7 za4C7H`T9m|i4C#(@Y^mE#V-%FCQK*rP0npHfFUFgHSsK5o5|-EbDtTx8U5gnN!YM- z@{+V66NF99jL2eZN=dvDv{>d^tI~@leci;5ABBmY|u<`Ma`#iSo5K8C_13zYnJq|3%;i>FA91C90HOGTD7cA-X=A8O+K76k-s-)pKT;{2bfL5`jM8AE$b4!tK8xbb#tY^u z6A59$k+F_;Q&}8*+#O?v zM0Q2?6xHD-1Pw|SE`WAnD##7O<6c$)dX-Ll#1C3r$YycKu9GdHs1>COv{wF6l4(PE zpM#^(OTL^$p;ZZqpJa*A&VstC6{wY>v`97L9h$U=9q&+WG0e7=M!(GN0cy_D4P;%3 z+7W4Vitd%D*P(L^abFfc2=J3Fqdn5~3vi1Ps*)|Ss#2R|8W9%96TeHF)w2S=Yjdo& zkZVWyQF^Nss9CnXXNXlOrHe4ka!)W~U`0%cPnLT|Eag?5t&{N}U#b!VPkb6>K8z$y z`4S@uV4$A;84veA>YhKvO!>0*fNwT0!=WjNKJIiJ0;mU^gJ`#!{lLhHKsX7N zPDZ?)Fl^{{k^FE$sq|07pNHVPnluQp*BeRUWZ)#Wqe$@E*|! zLl%OF*n8>7Sgwp;A->dT52j2h;m_aBsBa_zNlrNIP`K;Z(jk2}rc9#_a98`#CQ02V z%V**mAektJW`si^gl>26s|nX`|ETvPp~u<_^Fro2%Lu@N+0SOmUKWRa*6V76Vgbx@7tNL zvh{m>y4TFbvx;xo$8BD69KhT;jrI{tLMF#Z!e~dxtOv)oI zIXZ|5S&qySB`Nx2-C*m$;$j?+B1FtUcHkQbTz|pDz-3S~GL>LtKGzzuB-2k!D2BLH z@D{ACm{QQH;7owz<`2xEPgtDkLPQ}-Lk!Q?(k-0G{s6wPr*|v67$HO%{fC)Zjyx}; zPGYu)fmY@dy>eh!O`~u*GlcWtIc(mCCBIBRD-0Uw+B7Ne##d74S6OaP-*9$vu5Pjo z1Tp@O3Ub1R%=~ZuC;W$vHERgAdp3CLHPOAaV0W-fFnvI@*Zr;L?GNmtba|0q@y%eW zw6pwRXriiP@{(v-$_v`bGKky#1@LsCr9FM*iGJmogWrh+poFO?+YwJQ9!Wo>hv^4X zA;tk&xjM^}D3A7xO`gz)Np6WxO`^QiL*&LBa$Vt|K*Ks@J`%TqLQa96gno>Dev z1e(OJBsYM^cbM^*Z1UO)t*7+x3R$P0pOX%}1K>^dXrJ?V0wWads+oVs}Q5xupRgo_U$W}Ur=^5&hZEx`#+ zj4`Mr?<8UbnG`>Ha$Gf>36pW)8kR5o{MpUy1{9!Jx5N?x-UG5oDq`7btgcW&8{3o~ zMkZ|w(njiwo;?zVp-Mh63HHpvIpT`yk)sx;Fq|VHBc3h-_18?3^|G;_NaL267`STY zhY1t1lC5&$BF-Zf2r=`%vE7j21yKgFlR(5`)+_0O55{&H8v_#mVCu;@SvIkec7RhO zRxBV7=_c0a%!-g9o88Z9YC%?j_j7qa+`uVvXYM6I^T7p(wEEF-bj0pTuE*~XEmAoQ)2OnLOEy%WGt7ka!B9KHnA4*4)MC() z*c5I6@6f#z_Y)Kv^*hK2X?uj5FOp-I&=LSqBUz>C-vZ)$d9wv#cUL`9WeInn9ngcc zM&H{*i1mw}kI=ED&lklWccVZIT#9SwGJeg?a(%}xY0Yki;yPyLD!{$v;R?);vXi&i zjj?ezWv>EY*i)}i0E{#4hs{Vw41 zqYLYbg<}WI=r{boD_;7I^-Zm-L5?zUyd6z3k#`Oz-7 z$C2d(9QddS?0%<1Sp+BJQA_Y}47J&WyG-hm9^= zl8XBd0v(awpR06|6FiI>4A<=!)dy=i1Ka+>jQag+_qIDZckR9SIYnm7xR!> zR~5FY-g8<^&Ug|zc-Fh!jddZ~965EWZv0M1@3==!H+KiabnakY-67*n%vp+Dw(VT1 z<4yd?^w_UN=e-AK@J-Z*&_Ypdn##jn2q~Rfn;Jc84=cKD_Q2#Gs_e z8ye%juiaSoXWe1fCQnyZyE6cyESc(^L=5ag!`Nl#I!i+AOZ~o-qYO!ztUsv5NrX!N z4cq<2t?})O&Q4)T=tqvbA!8W~H!lS>)d8K5F>wuUF;^(9gkr=@3bwRWCu?hyE;<~X zj+q@cZ6dl%#{Q*;@%*?{OZ5cn!|f|LZzyj+D^wAYxLQGR-rL{!IST>5ofyKRF3;VZ z@j;`uuCS_0I@0IeTsC8`h~0aZKGyAr&^P^~J&P(w_>>RWV!xel#f}$ZnQ-QI#7L3s z)WwM-&3@KbTo1EbD*x=>8`hBf1A81Y_d< zKbU(?);@>{1!|}3*REN6IMadfPm-c!hzN0FnoH1d{-V^#y6A?Tr$-M<+TWnB(H}bd z%+iZl_=UcAXA-M{$6Xv_eQaCV5Pl(<9CVYdb90P^ soT4?o`e4aRX^Ks&Ri7iZ= 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

*o?QwI1L4ULN z8-H$S!TkvrHw4V~XYQZ{{EtPT8_sX;#J@QMmHnfRAgs{d8voFPK!86B4#EcdN1h;T z?2!MM?BZl(Wozd2`#Mp@%FB!#%+8|h-~ipyp=pDXII>9Fn>&#I-qrs4B1W!5&L<3E z6X)b&6NN}ffP^I=+z^Nes|0i*up5|DoRdvVL;&Ug*7+}$-@CP#gNdl6nTd_Fs~tHD z8$=u;%qh+R;S_~lsj_i!3UhIDL-#8#E> Date: Sun, 13 Aug 2017 10:32:10 -0500 Subject: [PATCH 019/381] Updated docs for CONFIG_BTC_STACK_SIZE --- cpp_utils/ArduinoBLE.md | 18 +++++++++++++++++- cpp_utils/README.md | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index 149c1d66..c37ed58c 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -17,4 +17,20 @@ Here is the recipe. 4. Run `make -f Makefile.arduino build_ble` 5. Change into the directory called `./Arduino` -And here you will find the `ESP32_BLE.zip` that is build from the latest source. You can then install this into your Arduino IDE environment are you are ready to go. \ No newline at end of file +And here you will find the `ESP32_BLE.zip` that is build from the latest source. You can then install this into your Arduino IDE environment are you are ready to go. + + +## Modifying the Arduino environment for BLE +The Arduino environment supplied for the ESP32 is **not** sufficient for full BLE support as supplied. A modification must be made. + +Here are the instructions + +1. Find the directory where your Arduino IDE is installed +2. Navigate relative from there to `/hardware/espressif/esp32/tools/sdk/include/config` +3. Edit the file called `sdkconfig.h` +4. Find the line which reads `#define CONFIG_BTC_TASK_STACK_SIZE 2048` and change to `#define CONFIG_BTC_TASK_STACK_SIZE 8000` + +You are now ready to build (re-build) your BLE based applications. + +A request has been made to he owners of the Arduino on ESP32 project to see if we can't make this the default or otherwise supply an easier story for modification see [arduino-esp32 issue 567](https://github.com/espressif/arduino-esp32/issues/567). + diff --git a/cpp_utils/README.md b/cpp_utils/README.md index edbcd751..fee451e4 100644 --- a/cpp_utils/README.md +++ b/cpp_utils/README.md @@ -50,4 +50,4 @@ $ make -f Makefile.arduino The results of this will be ZIP files found in the `Arduino` directory relative to this one. Targets include: -* `build_ble` - Build the BLE libraries. \ No newline at end of file +* `build_ble` - Build the BLE libraries. See also: [Arduino BLE Support](ArduinoBLE.md) . \ No newline at end of file From fcd327610d69225d52a2f62bd83187ffaabedea0 Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 14 Aug 2017 22:42:39 -0500 Subject: [PATCH 020/381] Changes from BLE to BLEDevice() ... #33 --- cpp_utils/BLECharacteristic.cpp | 3 +- cpp_utils/BLEClient.h | 2 +- cpp_utils/BLEDescriptor.cpp | 4 +- cpp_utils/{BLE.cpp => BLEDevice.cpp} | 81 ++++++++------------- cpp_utils/{BLE.h => BLEDevice.h} | 13 ++-- cpp_utils/BLEScan.h | 2 +- cpp_utils/BLEServer.cpp | 4 +- cpp_utils/BLEServer.h | 2 +- cpp_utils/BLEService.cpp | 3 +- cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp | 2 +- cpp_utils/tests/BLE Tests/Sample1.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleClient.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleNotify.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleRead.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleScan.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleServer.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleWrite.cpp | 2 +- 17 files changed, 54 insertions(+), 76 deletions(-) rename cpp_utils/{BLE.cpp => BLEDevice.cpp} (77%) rename cpp_utils/{BLE.h => BLEDevice.h} (87%) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 2f77301d..f5b82006 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -29,8 +29,7 @@ static const char* LOG_TAG = "BLECharacteristic"; * @param [in] uuid - UUID (const char*) for the characteristic. * @param [in] properties - Properties for the characteristic. */ -BLECharacteristic::BLECharacteristic(const char* uuid, uint32_t properties) { - BLECharacteristic(BLEUUID(uuid), properties); +BLECharacteristic::BLECharacteristic(const char* uuid, uint32_t properties) : BLECharacteristic(BLEUUID(uuid), properties) { } /** diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 8aa3edbc..555e85fa 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -38,7 +38,7 @@ class BLEClient { std::string toString(); private: - friend class BLE; + friend class BLEDevice; friend class BLERemoteCharacteristic; friend class BLERemoteService; diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 901cf8a6..4a7fda60 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -26,9 +26,9 @@ static const char* LOG_TAG = "BLEDescriptor"; /** * @brief BLEDescriptor constructor. */ -BLEDescriptor::BLEDescriptor(const char* uuid) { - BLEDescriptor(BLEUUID(uuid)); +BLEDescriptor::BLEDescriptor(const char* uuid) : BLEDescriptor(BLEUUID(uuid)) { } + /** * @brief BLEDescriptor constructor. */ diff --git a/cpp_utils/BLE.cpp b/cpp_utils/BLEDevice.cpp similarity index 77% rename from cpp_utils/BLE.cpp rename to cpp_utils/BLEDevice.cpp index 3dfc98cc..6bc3524a 100644 --- a/cpp_utils/BLE.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -21,21 +21,21 @@ #include #include -#include "BLE.h" +#include "BLEDevice.h" #include "BLEClient.h" #include "BLEUtils.h" #include "GeneralUtils.h" static const char* LOG_TAG = "BLE"; -BLEServer *BLE::m_bleServer = nullptr; -BLEScan *BLE::m_pScan = nullptr; -BLEClient *BLE::m_pClient = nullptr; +BLEServer *BLEDevice::m_bleServer = nullptr; +BLEScan *BLEDevice::m_pScan = nullptr; +BLEClient *BLEDevice::m_pClient = nullptr; #include -BLEClient* BLE::createClient() { +BLEClient* BLEDevice::createClient() { m_pClient = new BLEClient(); return m_pClient; } // createClient @@ -48,7 +48,7 @@ BLEClient* BLE::createClient() { * @param [in] gatts_if * @param [in] param */ -void BLE::gattServerEventHandler( +void BLEDevice::gattServerEventHandler( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param @@ -57,8 +57,8 @@ void BLE::gattServerEventHandler( gatts_if, BLEUtils::gattServerEventTypeToString(event).c_str()); BLEUtils::dumpGattServerEvent(event, gatts_if, param); - if (BLE::m_bleServer != nullptr) { - BLE::m_bleServer->handleGATTServerEvent(event, gatts_if, param); + if (BLEDevice::m_bleServer != nullptr) { + BLEDevice::m_bleServer->handleGATTServerEvent(event, gatts_if, param); } } // gattServerEventHandler @@ -76,7 +76,7 @@ void BLE::gattServerEventHandler( * @param [in] gattc_if * @param [in] param */ -void BLE::gattClientEventHandler( +void BLEDevice::gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { @@ -92,8 +92,8 @@ void BLE::gattClientEventHandler( } // switch // If we have a client registered, call it. - if (BLE::m_pClient != nullptr) { - BLE::m_pClient->gattClientEventHandler(event, gattc_if, param); + if (BLEDevice::m_pClient != nullptr) { + BLEDevice::m_pClient->gattClientEventHandler(event, gattc_if, param); } } // gattClientEventHandler @@ -102,7 +102,7 @@ void BLE::gattClientEventHandler( /** * @brief Handle GAP events. */ -void BLE::gapEventHandler( +void BLEDevice::gapEventHandler( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { @@ -122,17 +122,21 @@ void BLE::gapEventHandler( } } // switch - if (BLE::m_bleServer != nullptr) { - BLE::m_bleServer->handleGAPEvent(event, param); + if (BLEDevice::m_bleServer != nullptr) { + BLEDevice::m_bleServer->handleGAPEvent(event, param); } - if (BLE::m_pScan != nullptr) { - BLE::getScan()->gapEventHandler(event, param); + if (BLEDevice::m_pScan != nullptr) { + BLEDevice::getScan()->gapEventHandler(event, param); } } // gapEventHandler -static void commonInit() { +/** + * @brief Initialize the %BLE environment. + * @param deviceName The device name of the device. + */ +void BLEDevice::init(std::string deviceName) { esp_err_t errRc = ::nvs_flash_init(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -163,22 +167,20 @@ static void commonInit() { ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } -} // commonInit -/** - * @brief Initialize the server %BLE environment. - * - */ -void BLE::initServer(std::string deviceName) { - commonInit(); - - esp_err_t errRc = esp_ble_gap_register_callback(BLE::gapEventHandler); + errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } - errRc = esp_ble_gatts_register_callback(BLE::gattServerEventHandler); + errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; @@ -197,36 +199,15 @@ void BLE::initServer(std::string deviceName) { return; }; - return; -} // initServer - - -/** - * @brief Initialize the client %BLE environment. - */ -void BLE::initClient() { - commonInit(); - - esp_err_t errRc = esp_ble_gap_register_callback(BLE::gapEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - errRc = esp_ble_gattc_register_callback(BLE::gattClientEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } +} // init -} // initClient /** * @brief Retrieve the Scan object that we use for scanning. * @return The scanning object reference. */ -BLEScan* BLE::getScan() { +BLEScan* BLEDevice::getScan() { if (m_pScan == nullptr) { m_pScan = new BLEScan(); } diff --git a/cpp_utils/BLE.h b/cpp_utils/BLEDevice.h similarity index 87% rename from cpp_utils/BLE.h rename to cpp_utils/BLEDevice.h index 2d8bd98c..9d767c1e 100644 --- a/cpp_utils/BLE.h +++ b/cpp_utils/BLEDevice.h @@ -1,12 +1,12 @@ /* - * BLE.h + * BLEDevice.h * * Created on: Mar 16, 2017 * Author: kolban */ -#ifndef MAIN_BLE_H_ -#define MAIN_BLE_H_ +#ifndef MAIN_BLEDevice_H_ +#define MAIN_BLEDevice_H_ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) #include // ESP32 BLE @@ -22,13 +22,12 @@ /** * @brief %BLE functions. */ -class BLE { +class BLEDevice { public: static void dumpDevices(); static BLEClient *createClient(); - static void initClient(); - static void initServer(std::string deviceName); + static void init(std::string deviceName); //static void scan(int duration, esp_ble_scan_type_t scan_type = BLE_SCAN_TYPE_PASSIVE); static BLEScan *getScan(); static BLEServer *m_bleServer; @@ -52,4 +51,4 @@ class BLE { }; // class BLE #endif // CONFIG_BT_ENABLED -#endif /* MAIN_BLE_H_ */ +#endif /* MAIN_BLEDevice_H_ */ diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index fc75305c..f9575eac 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -48,7 +48,7 @@ class BLEScan { void stop(); private: - friend class BLE; + friend class BLEDevice; void gapEventHandler( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 1cf4edec..2d29fbf3 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -12,7 +12,7 @@ #include #include #include -#include "BLE.h" +#include "BLEDevice.h" #include "BLEServer.h" #include "BLEService.h" #include "BLEUtils.h" @@ -35,7 +35,7 @@ BLEServer::BLEServer() { m_gatts_if = -1; m_connectedCount = 0; m_connId = -1; - BLE::m_bleServer = this; + BLEDevice::m_bleServer = this; m_pServerCallbacks = nullptr; createApp(0); diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 916cdc2d..c6307bc6 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -65,7 +65,7 @@ class BLEServer { private: friend class BLEService; friend class BLECharacteristic; - friend class BLE; + friend class BLEDevice; esp_ble_adv_data_t m_adv_data; uint16_t m_appId; BLEAdvertising m_bleAdvertising; diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 8db43713..ec16db88 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -30,8 +30,7 @@ static const char* LOG_TAG = "BLEService"; // Tag for logging. * @brief Construct an instance of the BLEService * @param [in] uuid The UUID of the service. */ -BLEService::BLEService(const char* uuid) { - BLEService(BLEUUID(uuid)); +BLEService::BLEService(const char* uuid) : BLEService(BLEUUID(uuid)) { } diff --git a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp index a7280ffe..9850a3fd 100644 --- a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp +++ b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp @@ -1,9 +1,9 @@ -#include "BLE.h" #include "BLEUtils.h" #include "BLEScan.h" #include #include +#include "../../BLEDevice.h" #include "BLEAdvertisedDevice.h" #include "BLEClient.h" #include "sdkconfig.h" diff --git a/cpp_utils/tests/BLE Tests/Sample1.cpp b/cpp_utils/tests/BLE Tests/Sample1.cpp index a60b798c..9bcacca1 100644 --- a/cpp_utils/tests/BLE Tests/Sample1.cpp +++ b/cpp_utils/tests/BLE Tests/Sample1.cpp @@ -1,9 +1,9 @@ -#include "BLE.h" #include "BLEUtils.h" #include "BLEServer.h" #include #include +#include "../../BLEDevice.h" #include "sdkconfig.h" // See the following for generating UUIDs: diff --git a/cpp_utils/tests/BLE Tests/SampleClient.cpp b/cpp_utils/tests/BLE Tests/SampleClient.cpp index 5749880d..6e4da19c 100644 --- a/cpp_utils/tests/BLE Tests/SampleClient.cpp +++ b/cpp_utils/tests/BLE Tests/SampleClient.cpp @@ -2,8 +2,8 @@ #include #include #include +#include "../../BLEDevice.h" -#include "BLE.h" #include "BLEAdvertisedDevice.h" #include "BLEClient.h" #include "BLEScan.h" diff --git a/cpp_utils/tests/BLE Tests/SampleNotify.cpp b/cpp_utils/tests/BLE Tests/SampleNotify.cpp index 170b8aba..07715c4c 100644 --- a/cpp_utils/tests/BLE Tests/SampleNotify.cpp +++ b/cpp_utils/tests/BLE Tests/SampleNotify.cpp @@ -23,8 +23,8 @@ #include #include #include +#include "../../BLEDevice.h" -#include "BLE.h" #include "BLEServer.h" #include "BLEUtils.h" #include "BLE2902.h" diff --git a/cpp_utils/tests/BLE Tests/SampleRead.cpp b/cpp_utils/tests/BLE Tests/SampleRead.cpp index 2704707e..4debf851 100644 --- a/cpp_utils/tests/BLE Tests/SampleRead.cpp +++ b/cpp_utils/tests/BLE Tests/SampleRead.cpp @@ -1,10 +1,10 @@ -#include "BLE.h" #include "BLEUtils.h" #include "BLEServer.h" #include #include #include #include +#include "../../BLEDevice.h" #include "sdkconfig.h" diff --git a/cpp_utils/tests/BLE Tests/SampleScan.cpp b/cpp_utils/tests/BLE Tests/SampleScan.cpp index f2257ff9..c5afd88e 100644 --- a/cpp_utils/tests/BLE Tests/SampleScan.cpp +++ b/cpp_utils/tests/BLE Tests/SampleScan.cpp @@ -1,9 +1,9 @@ -#include "BLE.h" #include "BLEUtils.h" #include "BLEScan.h" #include #include +#include "../../BLEDevice.h" #include "BLEAdvertisedDevice.h" #include "sdkconfig.h" diff --git a/cpp_utils/tests/BLE Tests/SampleServer.cpp b/cpp_utils/tests/BLE Tests/SampleServer.cpp index 2628597b..892637ba 100644 --- a/cpp_utils/tests/BLE Tests/SampleServer.cpp +++ b/cpp_utils/tests/BLE Tests/SampleServer.cpp @@ -1,10 +1,10 @@ -#include "BLE.h" #include "BLEUtils.h" #include "BLEServer.h" #include "BLE2902.h" #include #include #include +#include "../../BLEDevice.h" #include "sdkconfig.h" diff --git a/cpp_utils/tests/BLE Tests/SampleWrite.cpp b/cpp_utils/tests/BLE Tests/SampleWrite.cpp index 49fa4917..23761268 100644 --- a/cpp_utils/tests/BLE Tests/SampleWrite.cpp +++ b/cpp_utils/tests/BLE Tests/SampleWrite.cpp @@ -1,10 +1,10 @@ -#include "BLE.h" #include "BLEUtils.h" #include "BLEServer.h" #include #include #include #include +#include "../../BLEDevice.h" #include "sdkconfig.h" From c4925834e1726c5253ada43c42389a9641d9c28b Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 15 Aug 2017 08:13:26 -0500 Subject: [PATCH 021/381] Update samples --- cpp_utils/BLEServer.cpp | 2 +- cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp | 8 ++++---- cpp_utils/tests/BLE Tests/SampleClient.cpp | 8 ++++---- cpp_utils/tests/BLE Tests/SampleNotify.cpp | 4 ++-- cpp_utils/tests/BLE Tests/SampleRead.cpp | 4 ++-- cpp_utils/tests/BLE Tests/SampleScan.cpp | 6 +++--- cpp_utils/tests/BLE Tests/SampleServer.cpp | 4 ++-- cpp_utils/tests/BLE Tests/SampleWrite.cpp | 4 ++-- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 2d29fbf3..be9773d9 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -28,7 +28,7 @@ static const char* LOG_TAG = "BLEServer"; * @brief Construct a %BLE Server * * This class is not designed to be individually instantiated. Instead one should create a server by asking - * the BLE device class. + * the BLEDevice class. */ BLEServer::BLEServer() { m_appId = -1; diff --git a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp index 9850a3fd..ec40c476 100644 --- a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp +++ b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp @@ -3,7 +3,7 @@ #include #include -#include "../../BLEDevice.h" +#include "../components/cpp_utils/BLEDevice.h" #include "BLEAdvertisedDevice.h" #include "BLEClient.h" #include "sdkconfig.h" @@ -22,7 +22,7 @@ static BLEUUID charUUID((uint16_t)0x2a06); class MyClient: public Task { void run(void *data) { BLEAddress* pAddress = (BLEAddress *)data; - BLEClient* pClient = BLE::createClient(); + BLEClient* pClient = BLEDevice::createClient(); pClient->connect(*pAddress); @@ -55,8 +55,8 @@ class MyClient: public Task { static void run() { ESP_LOGD(LOG_TAG, "MLE-15 sample starting"); - BLE::initClient(); - BLEClient* pClient = BLE::createClient(); + BLEDevice::init(""); + BLEClient* pClient = BLEDevice::createClient(); pClient->connect(BLEAddress("ff:ff:45:19:14:80")); diff --git a/cpp_utils/tests/BLE Tests/SampleClient.cpp b/cpp_utils/tests/BLE Tests/SampleClient.cpp index 6e4da19c..41b4e4db 100644 --- a/cpp_utils/tests/BLE Tests/SampleClient.cpp +++ b/cpp_utils/tests/BLE Tests/SampleClient.cpp @@ -2,7 +2,7 @@ #include #include #include -#include "../../BLEDevice.h" +#include "../components/cpp_utils/BLEDevice.h" #include "BLEAdvertisedDevice.h" #include "BLEClient.h" @@ -29,7 +29,7 @@ static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); class MyClient: public Task { void run(void* data) { BLEAddress* pAddress = (BLEAddress*)data; - BLEClient* pClient = BLE::createClient(); + BLEClient* pClient = BLEDevice::createClient(); // Connect to the remove BLE Server. pClient->connect(*pAddress); @@ -100,8 +100,8 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { */ void SampleClient(void) { ESP_LOGD(LOG_TAG, "Scanning sample starting"); - BLE::initClient(); - BLEScan *pBLEScan = BLE::getScan(); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); pBLEScan->start(30); diff --git a/cpp_utils/tests/BLE Tests/SampleNotify.cpp b/cpp_utils/tests/BLE Tests/SampleNotify.cpp index 07715c4c..2a974fd6 100644 --- a/cpp_utils/tests/BLE Tests/SampleNotify.cpp +++ b/cpp_utils/tests/BLE Tests/SampleNotify.cpp @@ -23,7 +23,7 @@ #include #include #include -#include "../../BLEDevice.h" +#include "../components/cpp_utils/BLEDevice.h" #include "BLEServer.h" #include "BLEUtils.h" @@ -72,7 +72,7 @@ static void run() { pMyNotifyTask->setStackSize(8000); // Create the BLE Device - BLE::initServer("MYDEVICE"); + BLEDevice::init("MYDEVICE"); // Create the BLE Server BLEServer *pServer = new BLEServer(); diff --git a/cpp_utils/tests/BLE Tests/SampleRead.cpp b/cpp_utils/tests/BLE Tests/SampleRead.cpp index 4debf851..4fa684b6 100644 --- a/cpp_utils/tests/BLE Tests/SampleRead.cpp +++ b/cpp_utils/tests/BLE Tests/SampleRead.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "../../BLEDevice.h" +#include "../components/cpp_utils/BLEDevice.h" #include "sdkconfig.h" @@ -27,7 +27,7 @@ class MyCallbackHandler: public BLECharacteristicCallbacks { }; static void run() { - BLE::initServer("MYDEVICE"); + BLEDevice::init("MYDEVICE"); BLEServer *pServer = new BLEServer(); BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID_BIN, 16, true)); diff --git a/cpp_utils/tests/BLE Tests/SampleScan.cpp b/cpp_utils/tests/BLE Tests/SampleScan.cpp index c5afd88e..ef4c4005 100644 --- a/cpp_utils/tests/BLE Tests/SampleScan.cpp +++ b/cpp_utils/tests/BLE Tests/SampleScan.cpp @@ -3,7 +3,7 @@ #include #include -#include "../../BLEDevice.h" +#include "../components/cpp_utils/BLEDevice.h" #include "BLEAdvertisedDevice.h" #include "sdkconfig.h" @@ -17,8 +17,8 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { static void run() { ESP_LOGD(LOG_TAG, "Scanning sample starting"); - BLE::initClient(); - BLEScan* pBLEScan = BLE::getScan(); + BLEDevice::init(""); + BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); BLEScanResults scanResults = pBLEScan->start(30); diff --git a/cpp_utils/tests/BLE Tests/SampleServer.cpp b/cpp_utils/tests/BLE Tests/SampleServer.cpp index 892637ba..80939379 100644 --- a/cpp_utils/tests/BLE Tests/SampleServer.cpp +++ b/cpp_utils/tests/BLE Tests/SampleServer.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "../../BLEDevice.h" +#include "../components/cpp_utils/BLEDevice.h" #include "sdkconfig.h" @@ -14,7 +14,7 @@ class MainBLEServer: public Task { void run(void *data) { ESP_LOGD(LOG_TAG, "Starting BLE work!"); - BLE::initServer("MYDEVICE"); + BLEDevice::init("MYDEVICE"); BLEServer* pServer = new BLEServer(); BLEService* pService = pServer->createService(BLEUUID((uint16_t)0x1234)); diff --git a/cpp_utils/tests/BLE Tests/SampleWrite.cpp b/cpp_utils/tests/BLE Tests/SampleWrite.cpp index 23761268..b76d5f9d 100644 --- a/cpp_utils/tests/BLE Tests/SampleWrite.cpp +++ b/cpp_utils/tests/BLE Tests/SampleWrite.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "../../BLEDevice.h" +#include "../components/cpp_utils/BLEDevice.h" #include "sdkconfig.h" @@ -28,7 +28,7 @@ class MyCallbacks: public BLECharacteristicCallbacks { }; static void run() { - BLE::initServer("MYDEVICE"); + BLEDevice::init("MYDEVICE"); BLEServer *pServer = new BLEServer(); BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); From 8c11102b25379a2675fcc16d1954c19926fbf807 Mon Sep 17 00:00:00 2001 From: Ronny Hansen Date: Tue, 15 Aug 2017 19:38:11 +0200 Subject: [PATCH 022/381] Code updated to SDK v2.1 Code refactored to dump more information For details see the updated README.MD --- BLE/scanner/README.md | 49 ++++- BLE/scanner/ble1.c | 480 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 475 insertions(+), 54 deletions(-) diff --git a/BLE/scanner/README.md b/BLE/scanner/README.md index e549ec88..f58364ad 100644 --- a/BLE/scanner/README.md +++ b/BLE/scanner/README.md @@ -1 +1,48 @@ -WARNING .... This is a VERY EARLY piece of work ... it is far from finished. \ No newline at end of file +# BLE GAP DUMP + +Code updated to support SDK v2.1 + +Supports dumping of Bluetooth GAP frames. +For more information about [Bluetooth Generic Access Profile](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile) + +It will dump the header and then start dumping the payload. +Also added is decoding of some payloads frames + +In addition to dumping raw frames, the code supports decoding of some frames. +Frames that support decoding: +* 0x16, Subtype 0x1809 Temperature +* 0x16, Subtype 0x180F Battery +* 0x09, Complete local name + + +|EXAMPLE FRAME DUMP| +|----| +|I (88679) ble1: Device address (bda): ea:ed:37:7a:0f:34| +|I (88689) ble1: Device type : ESP_BT_DEVICE_TYPE_BLE| +|I (88689) ble1: Search_evt : ESP_GAP_SEARCH_INQ_RES_EVT| +|I (88689) ble1: Addr_type : BLE_ADDR_TYPE_RANDOM| +|I (88699) ble1: RSSI : -85| +|I (88699) ble1: Flag : 4| +|I (88709) ble1: num_resps : 1| +|I (88709) ble1: # Payload type: 0x01 (Flags), length: 2| +|I (88719) ble1: * Payload: 04:09 (..)| +|I (88719) ble1: # Payload type: 0x09 (Complete Local Name), length: 9| +|I (88729) ble1: # Complete local name: 43F0DEAE| +|I (88739) ble1: * Payload: 34:33:46:30:44:45:41:45:07 (43F0DEAE.)| +|I (88739) ble1: # Payload type: 0x16 (Service Data - 16-bit UUID), length: 7| +|I (88749) ble1: @ 0x1809 Temperature 28.540000| +|I (88759) ble1: * Payload: 09:18:26:0B:00:FE:04 (..&....)| +|I (88759) ble1: # Payload type: 0x16 (Service Data - 16-bit UUID), length: 4| +|I (88769) ble1: @ 0x180F Battery 100 %| +|I (88769) ble1: * Payload: 0F:18:64:00 (..d.)| +|I (88779) ble1: Payload total length: 26| +|I (88779) ble1:| + + +I have added prefix to the payload dump to make it easier to read + +|Tag| Description | +|---|--- | +|\# | Payload information on the frame level (typical "Frame type" + "Frame length") | +|\* | Raw frame dump (excluding "Frame type")| +|\@ | 16-BIT UUID frames that are decoded (P.t. only 0x1809 and 0x180F is implemented)| diff --git a/BLE/scanner/ble1.c b/BLE/scanner/ble1.c index 3f14ec23..3dceeb5c 100644 --- a/BLE/scanner/ble1.c +++ b/BLE/scanner/ble1.c @@ -1,74 +1,322 @@ -#include +# include #include #include #include +#include +#include #include #include +#include +#include +#include +#include "nvs_flash.h" #include "sdkconfig.h" #define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" #define BT_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] +// Prototypes static const char *bt_event_type_to_string(uint32_t eventType); static const char *bt_gap_search_event_type_to_string(uint32_t searchEvt); static const char *bt_addr_t_to_string(esp_ble_addr_type_t type); static const char *bt_dev_type_to_string(esp_bt_dev_type_t type); +static void gap_callback_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +static const char *btsig_gap_type(uint32_t gap_type); -static const char tag[] = "ble1"; +#define tag "ble1" -static void dump_adv_payload(uint8_t *payload) { + +static uint32_t convertU16ToU32(uint16_t inShortA, uint16_t inShortB) +{ + return inShortA<< 16 | inShortB; +} + + +static uint16_t convertU8ToU16(uint8_t inByteA, uint8_t inByteB) +{ + return inByteA<<8 | inByteB; +} + + +// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.health_thermometer.xml +static double decode1809(uint8_t *payload) +{ + return ((float)(payload[4]<<16 | payload[3]<<8 | payload[2])) / 100.0; +} + +static uint8_t decode180f(uint8_t *payload) +{ + return payload[2]; +} + +static void dump_16bituuid(uint8_t *payload,uint8_t length) +{ + //Find UUID from the two first bytes + uint16_t uuid =0; + + uuid = convertU8ToU16 (payload[1],payload[0]); + ESP_LOGD(tag,"Dump16 UUID %04X, len %d", uuid,length); + + + length -=2; //Reduce with header size + + // A list of all GATT Services is here https://www.bluetooth.com/specifications/gatt/services + switch(uuid) + { + + case 0x1809: // Health Thermometer + if (length >=4) //Validate input payload length; + { + ESP_LOGI(tag,"@ 0x1809 Temperature %f", decode1809(payload)); + } + + break; + case 0x180f : //Battery Service (mandatory) + if (length >=1) + { + ESP_LOGI(tag,"@ 0x180F Battery %d %%", decode180f(payload)); + } + break; + + default: + ESP_LOGI(tag,"@ 16 BIT UUID 0x%04X - Packet decoding for thtis type not implemented",uuid); // Read the Bluetooth spec and implement it + break; + } +} + + +void bin_to_strhex(unsigned char *bin, unsigned int binsz, char **result) +{ + char hex_str[]= "0123456789abcdef"; + unsigned int i; + + *result = (char *)malloc(binsz * 2 + 1); + (*result)[binsz * 2] = 0; + + if (!binsz) + return; + + for (i = 0; i < binsz; i++) + { + (*result)[i * 2 + 0] = hex_str[(bin[i] >> 4) & 0x0F]; + (*result)[i * 2 + 1] = hex_str[(bin[i] ) & 0x0F]; + } +} + +static uint8_t dump_adv_payload(uint8_t *payload) +{ uint8_t length; uint8_t ad_type; uint8_t sizeConsumed = 0; int finished = 0; - int i; - char text[31*2+1]; + uint8_t total_length=0; //sprintf(text, "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x") + + // GAP assigned numbers for the Type in the payload is defined here: + // https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile + while(!finished) { length = *payload; payload++; if (length != 0) { ad_type = *payload; - payload += length; - ESP_LOGD(tag, "Type: 0x%.2x, length: %d", ad_type, length); + + ESP_LOGI(tag, "# Payload type: 0x%.2x (%s), length: %d", ad_type, btsig_gap_type(ad_type), length); - } + + // Decode packets - implemented just a few types + switch(ad_type) + { + case 0x16: // 16bit UUID - Bluetooth Core Specification:Vol. 3, Part C, sections 11.1.10 and 18.10 (v4.0) + dump_16bituuid(payload+1, length); + break; + + case 0x09: //Complete local name - Bluetooth Core Specification:Vol. 3, Part C, section 8.1.2 (v2.1 + EDR, 3.0 + HS and 4.0)Vol. 3, Part C, sections 11.1.2 and 18.4 (v4.0)Core Specification Supplement, Part A, section 1.2 + ESP_LOGI(tag, "# Complete local name: %.*s", length, payload); + break; + + default: + break; + } + + // Dump the raw HEX data + // Also dump as string, if possible, to make it possible to scan for text + int i; + int size = length / sizeof(char); + char *hex_str = (char*) calloc(3 * size,1); + char *ascii_str = (char*) calloc(length+1,1); + char *buf_ptr1 = hex_str; + char *buf_ptr2 = ascii_str; + + unsigned char *source = (unsigned char *)payload+1; + + if ((hex_str) && (ascii_str)) + { + for (i = 0; i < size; i++) + { + buf_ptr1 += sprintf(buf_ptr1, i < size - 1 ? "%02X:" : "%02X", source[i]); + + + char ch = source[i]; + + //quick fix since isalpha had unexpected results + int ichar = ((int) ch) & 0xFF; + if ((ichar<32) || (ichar>126)) + { + ch = '.'; // unprintable characters are represented as "." + } + + buf_ptr2 += sprintf(buf_ptr2, "%c", ch); + + } + + ESP_LOGI(tag,"* Payload: %s (%s)", hex_str, ascii_str); + } + if (hex_str) free(hex_str); + hex_str=0; + + if (ascii_str) free(ascii_str); + ascii_str=0; + + payload += length; + total_length += length+1; + } sizeConsumed = 1 + length; if (sizeConsumed >=31 || length == 0) { finished = 1; } } // !finished + return total_length; } // dump_adv_payload -static void gap_event_handler(uint32_t event, void *param) { +static void gap_callback_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ ESP_LOGD(tag, "Received a GAP event: %s", bt_event_type_to_string(event)); esp_ble_gap_cb_param_t *p = (esp_ble_gap_cb_param_t *)param; - if (event == ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT) { - ESP_LOGD(tag, "status: %d", p->scan_param_cmpl.status); - } else if (event == ESP_GAP_BLE_SCAN_RESULT_EVT) { - ESP_LOGD(tag, "device address (bda): %02x:%02x:%02x:%02x:%02x:%02x", BT_BD_ADDR_HEX(p->scan_rst.bda)); - ESP_LOGD(tag, "device type: %s", bt_dev_type_to_string(p->scan_rst.dev_type)); - ESP_LOGD(tag, "search_evt: %s", bt_gap_search_event_type_to_string(p->scan_rst.search_evt)); - ESP_LOGD(tag, "addr_type: %s", bt_addr_t_to_string(p->scan_rst.ble_addr_type)); - ESP_LOGD(tag, "rssi: %d", p->scan_rst.rssi); - ESP_LOGD(tag, "flag: %d", p->scan_rst.flag); - ESP_LOGD(tag, "num_resps: %d", p->scan_rst.num_resps); - - - if (p->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { - ESP_LOGD(tag, "payload:"); - uint8_t len; - uint8_t *data = esp_ble_resolve_adv_data(p->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &len); - ESP_LOGD(tag, "len: %d, %.*s", len, len, data); - ESP_LOGD(tag, "dump -"); - dump_adv_payload(p->scan_rst.ble_adv); + + esp_err_t status; + + + switch (event) + { + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: + { + ESP_LOGD(tag, "status: %d", p->scan_param_cmpl.status); + + // This procedure keep the device scanning the peer device which advertising on the air. + //the unit of the duration is second + uint32_t duration = 30; + status = esp_ble_gap_start_scanning(duration); + if (status != ESP_OK) + { + ESP_LOGE(tag, "esp_ble_gap_start_scanning: rc=%d", status); + } } - ESP_LOGD(tag, ""); + break; + + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + { + //scan start complete event to indicate scan start successfully or failed + if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) + { + ESP_LOGE(tag, "Scan start failed"); + } + } + break; + + case ESP_GAP_BLE_SCAN_RESULT_EVT: + { + + ESP_LOGI(tag, "Device address (bda): %02x:%02x:%02x:%02x:%02x:%02x", BT_BD_ADDR_HEX(p->scan_rst.bda)); + + ESP_LOGI(tag, "Device type : %s", bt_dev_type_to_string(p->scan_rst.dev_type)); + ESP_LOGI(tag, "Search_evt : %s", bt_gap_search_event_type_to_string(p->scan_rst.search_evt)); + ESP_LOGI(tag, "Addr_type : %s", bt_addr_t_to_string(p->scan_rst.ble_addr_type)); + ESP_LOGI(tag, "RSSI : %d", p->scan_rst.rssi); + ESP_LOGI(tag, "Flag : %d", p->scan_rst.flag); + + //bit 0 (OFF) LE Limited Discoverable Mode + //bit 1 (OFF) LE General Discoverable Mode + //bit 2 (ON) BR/EDR Not Supported + //bit 3 (OFF) Simultaneous LE and BR/EDR to Same Device Capable (controller) + //bit 4 (OFF) Simultaneous LE and BR/EDR to Same Device Capable (Host) + + ESP_LOGI(tag, "num_resps : %d", p->scan_rst.num_resps); + + if ( p->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) + { + // Scan is done. + + // The next 5 codelines automatically restarts the scan. You you only want + // one scan round, you can comment it. + uint32_t duration = 30; + status = esp_ble_gap_start_scanning (duration); + if (status != ESP_OK) + { + ESP_LOGE(tag, "esp_ble_gap_start_scanning: rc=%d", status); + } + + return; + } + + + if (p->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) + { + + // NOTE! + // Instead of this code that dumps the hole payload + // you can search for elements in the payload using the + // function esp_ble_resolve_adv_data() + // + // Like this, that scans for the "Complete name" (looking inside the payload buffer) + /* + uint8_t len; + uint8_t *data = esp_ble_resolve_adv_data(p->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &len); + ESP_LOGD(tag, "len: %d, %.*s", len, len, data); + */ + + uint8_t length; + length = dump_adv_payload(p->scan_rst.ble_adv); + ESP_LOGI(tag, "Payload total length: %d", length); + } + ESP_LOGI(tag, ""); + + } + break; + + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + { + if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) + { + ESP_LOGE(tag, "Scan stop failed"); + } + else + { + ESP_LOGI(tag, "Stop scan successfully"); + } + } + + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + { + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) + { + ESP_LOGE(tag, "Adv stop failed"); + } + else + { + ESP_LOGI(tag, "Stop adv successfully"); + } + } + break; + + + default: + break; } -} // gap_event_handler +} // gap_callback_handler static const char *bt_dev_type_to_string(esp_bt_dev_type_t type) { switch(type) { @@ -137,36 +385,162 @@ static const char *bt_event_type_to_string(uint32_t eventType) { } } // bt_event_type_to_string +static const char *btsig_gap_type(uint32_t gap_type) { + switch (gap_type) + { + case 0x01: return "Flags"; + case 0x02: return "Incomplete List of 16-bit Service Class UUIDs"; + case 0x03: return "Complete List of 16-bit Service Class UUIDs"; + case 0x04: return "Incomplete List of 32-bit Service Class UUIDs"; + case 0x05: return "Complete List of 32-bit Service Class UUIDs"; + case 0x06: return "Incomplete List of 128-bit Service Class UUIDs"; + case 0x07: return "Complete List of 128-bit Service Class UUIDs"; + case 0x08: return "Shortened Local Name"; + case 0x09: return "Complete Local Name"; + case 0x0A: return "Tx Power Level"; + case 0x0D: return "Class of Device"; + case 0x0E: return "Simple Pairing Hash C/C-192"; + case 0x0F: return "Simple Pairing Randomizer R/R-192"; + case 0x10: return "Device ID/Security Manager TK Value"; + case 0x11: return "Security Manager Out of Band Flags"; + case 0x12: return "Slave Connection Interval Range"; + case 0x14: return "List of 16-bit Service Solicitation UUIDs"; + case 0x1F: return "List of 32-bit Service Solicitation UUIDs"; + case 0x15: return "List of 128-bit Service Solicitation UUIDs"; + case 0x16: return "Service Data - 16-bit UUID"; + case 0x20: return "Service Data - 32-bit UUID"; + case 0x21: return "Service Data - 128-bit UUID"; + case 0x22: return "LE Secure Connections Confirmation Value"; + case 0x23: return "LE Secure Connections Random Value"; + case 0x24: return "URI"; + case 0x25: return "Indoor Positioning"; + case 0x26: return "Transport Discovery Data"; + case 0x17: return "Public Target Address"; + case 0x18: return "Random Target Address"; + case 0x19: return "Appearance"; + case 0x1A: return "Advertising Interval"; + case 0x1B: return "LE Bluetooth Device Address"; + case 0x1C: return "LE Role"; + case 0x1D: return "Simple Pairing Hash C-256"; + case 0x1E: return "Simple Pairing Randomizer R-256"; + case 0x3D: return "3D Information Data"; + case 0xFF: return "Manufacturer Specific Data"; + + default: + return "Unknown type"; + } +} -void bt_task(void *ignore) { - int errRc; - bt_controller_init(); - esp_init_bluetooth(); - esp_enable_bluetooth(); - errRc = esp_ble_gap_register_callback(gap_event_handler); - if (errRc != ESP_OK) { - ESP_LOGE(tag, "esp_ble_gap_register_callback: rc=%d", errRc); - goto end; + +esp_err_t register_ble_functionality(void) +{ + esp_err_t status; + + ESP_LOGI(tag, "Register GAP callback"); + + // This function is called to occur gap event, such as scan result. + //register the scan callback function to the gap module + status = esp_ble_gap_register_callback(gap_callback_handler); + if (status != ESP_OK) + { + ESP_LOGE(tag, "esp_ble_gap_register_callback: rc=%d", status); + return ESP_FAIL; } - static esp_ble_scan_params_t ble_scan_params = { - .scan_type = BLE_SCAN_TYPE_ACTIVE, - .own_addr_type = ESP_PUBLIC_ADDR, - .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, - .scan_interval = 0x50, - .scan_window = 0x30 + + static esp_ble_scan_params_t ble_scan_params = + { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30 }; - errRc = esp_ble_gap_set_scan_params(&ble_scan_params); - if (errRc != ESP_OK) { - ESP_LOGE(tag, "esp_ble_gap_set_scan_params: rc=%d", errRc); - goto end; + + ESP_LOGI(tag, "Set GAP scan parameters"); + + // This function is called to set scan parameters. + status = esp_ble_gap_set_scan_params(&ble_scan_params); + if (status != ESP_OK) + { + ESP_LOGE(tag, "esp_ble_gap_set_scan_params: rc=%d", status); + return ESP_FAIL; + } + + + ESP_LOGD(tag, "We have registered what we need!"); + + return ESP_OK ; +} + +// Main start code running in its own Xtask +void bt_task(void *ignore) +{ + esp_err_t status; + + // Initialize NVS flash storage with layout given in the partition table + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) + { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK( ret ); + + ESP_LOGI(tag, "Enabling Bluetooth Controller"); + + // Initialize BT controller to allocate task and other resource. + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + if (esp_bt_controller_init(&bt_cfg) != ESP_OK) + { + ESP_LOGE(tag, "Bluetooth controller initialize failed"); + goto end; } - errRc = esp_ble_gap_start_scanning(20); - if (errRc != ESP_OK) { - ESP_LOGE(tag, "esp_ble_gap_start_scanning: rc=%d", errRc); + // Enable BT controller + if (esp_bt_controller_enable(ESP_BT_MODE_BTDM) != ESP_OK) + { + ESP_LOGE(tag, "Bluetooth controller enable failed"); + goto end; + } + + ESP_LOGI(tag, "Bluetooth Controller Enabled"); + + ESP_LOGI(tag, "Init Bluetooth stack"); + + // Init and alloc the resource for bluetooth, must be prior to every bluetooth stuff + status = esp_bluedroid_init(); + if (status != ESP_OK) + { + ESP_LOGE(tag, "%s init bluetooth failed\n", __func__); + goto end; + } + + // Enable bluetooth, must after esp_bluedroid_init() + status = esp_bluedroid_enable(); + if (status != ESP_OK) + { + ESP_LOGE(tag, "%s enable bluetooth failed\n", __func__); + goto end; + } + + ESP_LOGI(tag, "Bluetooth stack initialized"); + + ESP_LOGI(tag, "Register BLE functionality"); + status = register_ble_functionality(); + if (status != ESP_OK) + { + ESP_LOGE(tag, "Register BLE functionality failed"); goto end; } - ESP_LOGD(tag, "We have registered what we need!"); + + while(1) + { + vTaskDelay(1000); + } + end: + ESP_LOGI(tag, "Terminating BT logging task"); vTaskDelete(NULL); + + } // bt_task From 9708e30886cbeef68dda9d38c154d328105adb89 Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 16 Aug 2017 08:27:53 -0500 Subject: [PATCH 023/381] Fix for #37 --- cpp_utils/Makefile.arduino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index 024fa31c..e59704fe 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -8,8 +8,8 @@ # The source file for BLE # BLE_FILES= \ - BLE.cpp \ - BLE.h \ + BLEDevice.cpp \ + BLEDevice.h \ BLE2902.cpp \ BLE2902.h \ BLEAddress.cpp \ From e2996a469870ca5278504a9d4818db8d1166f621 Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 16 Aug 2017 14:13:43 -0500 Subject: [PATCH 024/381] Updated sample header file locations We renamed a header using Eclipse refactoring which changed paths in an unexpected way. This commit corrects them by hand. --- cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp | 2 +- cpp_utils/tests/BLE Tests/Sample1.cpp | 4 ++-- cpp_utils/tests/BLE Tests/SampleClient.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleNotify.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleRead.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleScan.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleServer.cpp | 2 +- cpp_utils/tests/BLE Tests/SampleWrite.cpp | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp index ec40c476..6db156fe 100644 --- a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp +++ b/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp @@ -3,7 +3,7 @@ #include #include -#include "../components/cpp_utils/BLEDevice.h" +#include "BLEDevice.h" #include "BLEAdvertisedDevice.h" #include "BLEClient.h" #include "sdkconfig.h" diff --git a/cpp_utils/tests/BLE Tests/Sample1.cpp b/cpp_utils/tests/BLE Tests/Sample1.cpp index 9bcacca1..88f6b3d1 100644 --- a/cpp_utils/tests/BLE Tests/Sample1.cpp +++ b/cpp_utils/tests/BLE Tests/Sample1.cpp @@ -3,7 +3,7 @@ #include #include -#include "../../BLEDevice.h" +#include "BLEDevice.h" #include "sdkconfig.h" // See the following for generating UUIDs: @@ -13,7 +13,7 @@ #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" static void run() { - BLE::initServer("MYDEVICE"); + BLEDevice::init("MYDEVICE"); BLEServer *pServer = new BLEServer(); diff --git a/cpp_utils/tests/BLE Tests/SampleClient.cpp b/cpp_utils/tests/BLE Tests/SampleClient.cpp index 41b4e4db..76995633 100644 --- a/cpp_utils/tests/BLE Tests/SampleClient.cpp +++ b/cpp_utils/tests/BLE Tests/SampleClient.cpp @@ -2,7 +2,7 @@ #include #include #include -#include "../components/cpp_utils/BLEDevice.h" +#include "BLEDevice.h" #include "BLEAdvertisedDevice.h" #include "BLEClient.h" diff --git a/cpp_utils/tests/BLE Tests/SampleNotify.cpp b/cpp_utils/tests/BLE Tests/SampleNotify.cpp index 2a974fd6..738aacb3 100644 --- a/cpp_utils/tests/BLE Tests/SampleNotify.cpp +++ b/cpp_utils/tests/BLE Tests/SampleNotify.cpp @@ -23,7 +23,7 @@ #include #include #include -#include "../components/cpp_utils/BLEDevice.h" +#include "BLEDevice.h" #include "BLEServer.h" #include "BLEUtils.h" diff --git a/cpp_utils/tests/BLE Tests/SampleRead.cpp b/cpp_utils/tests/BLE Tests/SampleRead.cpp index 4fa684b6..ba498f4b 100644 --- a/cpp_utils/tests/BLE Tests/SampleRead.cpp +++ b/cpp_utils/tests/BLE Tests/SampleRead.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "../components/cpp_utils/BLEDevice.h" +#include "BLEDevice.h" #include "sdkconfig.h" diff --git a/cpp_utils/tests/BLE Tests/SampleScan.cpp b/cpp_utils/tests/BLE Tests/SampleScan.cpp index ef4c4005..3dc972ed 100644 --- a/cpp_utils/tests/BLE Tests/SampleScan.cpp +++ b/cpp_utils/tests/BLE Tests/SampleScan.cpp @@ -3,7 +3,7 @@ #include #include -#include "../components/cpp_utils/BLEDevice.h" +#include "BLEDevice.h" #include "BLEAdvertisedDevice.h" #include "sdkconfig.h" diff --git a/cpp_utils/tests/BLE Tests/SampleServer.cpp b/cpp_utils/tests/BLE Tests/SampleServer.cpp index 80939379..d497d009 100644 --- a/cpp_utils/tests/BLE Tests/SampleServer.cpp +++ b/cpp_utils/tests/BLE Tests/SampleServer.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "../components/cpp_utils/BLEDevice.h" +#include "BLEDevice.h" #include "sdkconfig.h" diff --git a/cpp_utils/tests/BLE Tests/SampleWrite.cpp b/cpp_utils/tests/BLE Tests/SampleWrite.cpp index b76d5f9d..9132b7af 100644 --- a/cpp_utils/tests/BLE Tests/SampleWrite.cpp +++ b/cpp_utils/tests/BLE Tests/SampleWrite.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "../components/cpp_utils/BLEDevice.h" +#include "BLEDevice.h" #include "sdkconfig.h" From d6cd7dd127d50d29c4704ead083d349d993d365e Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 16 Aug 2017 18:48:27 -0500 Subject: [PATCH 025/381] Delay of 200msecs for Arduino environment --- cpp_utils/BLEDevice.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 6bc3524a..b615ce74 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -6,6 +6,8 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) +#include +#include #include #include #include @@ -199,6 +201,7 @@ void BLEDevice::init(std::string deviceName) { return; }; + vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. } // init From 209d8ddf104d38e1623c602587731f54aa30b911 Mon Sep 17 00:00:00 2001 From: copercini Date: Wed, 16 Aug 2017 22:10:28 -0300 Subject: [PATCH 026/381] Upload arduino examples --- .../Arduino/BLE_notify/BLE_notify.ino | 93 +++++++++++++++++++ .../BLE Tests/Arduino/BLE_scan/BLE_scan.ino | 36 +++++++ .../Arduino/BLE_server/BLE_server.ino | 41 ++++++++ .../BLE Tests/Arduino/BLE_write/BLE_write.ino | 68 ++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 cpp_utils/tests/BLE Tests/Arduino/BLE_notify/BLE_notify.ino create mode 100644 cpp_utils/tests/BLE Tests/Arduino/BLE_scan/BLE_scan.ino create mode 100644 cpp_utils/tests/BLE Tests/Arduino/BLE_server/BLE_server.ino create mode 100644 cpp_utils/tests/BLE Tests/Arduino/BLE_write/BLE_write.ino diff --git a/cpp_utils/tests/BLE Tests/Arduino/BLE_notify/BLE_notify.ino b/cpp_utils/tests/BLE Tests/Arduino/BLE_notify/BLE_notify.ino new file mode 100644 index 00000000..44506c26 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/Arduino/BLE_notify/BLE_notify.ino @@ -0,0 +1,93 @@ +/* + Video: https://www.youtube.com/watch?v=oCMOYS71NIU + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + Ported to Arduino ESP32 by Evandro Copercini + + Create a BLE server that, once we receive a connection, will send periodic notifications. + The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b + And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8 + + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create a BLE Service + 3. Create a BLE Characteristic on the Service + 4. Create a BLE Descriptor on the characteristic + 5. Start the service. + 6. Start advertising. + + A connect hander associated with the server starts a background task that performs notification + every couple of seconds. +*/ +#include +#include +#include +#include + +BLECharacteristic *pCharacteristic; +bool deviceConnected = false; +uint8_t value = 0; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + } +}; + + + +void setup() { + Serial.begin(115200); + + // Create the BLE Device + BLEDevice::init("MyESP32"); + + // Create the BLE Server + BLEServer *pServer = new BLEServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_INDICATE + ); + + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + // Create a BLE Descriptor + pCharacteristic->addDescriptor(new BLE2902()); + + // Start the service + pService->start(); + + // Start advertising + pServer->getAdvertising()->start(); + Serial.println("Waiting a client connection to notify..."); +} + +void loop() { + + if (deviceConnected) { + Serial.printf("*** NOTIFY: %d ***\n", value); + pCharacteristic->setValue(&value, 1); + pCharacteristic->notify(); + //pCharacteristic->indicate(); + value++; + } + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLE Tests/Arduino/BLE_scan/BLE_scan.ino b/cpp_utils/tests/BLE Tests/Arduino/BLE_scan/BLE_scan.ino new file mode 100644 index 00000000..ef7d8924 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/Arduino/BLE_scan/BLE_scan.ino @@ -0,0 +1,36 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include +#include + +int scanTime = 30; //In seconds + +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str()); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Scanning..."); + + BLEDevice::init(""); + BLEScan* pBLEScan = BLEDevice::getScan(); //create new scan + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster + BLEScanResults foundDevices = pBLEScan->start(scanTime); + Serial.print("Devices found: "); + Serial.println(foundDevices.getCount()); + Serial.println("Scan done!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLE Tests/Arduino/BLE_server/BLE_server.ino b/cpp_utils/tests/BLE Tests/Arduino/BLE_server/BLE_server.ino new file mode 100644 index 00000000..45ebf99f --- /dev/null +++ b/cpp_utils/tests/BLE Tests/Arduino/BLE_server/BLE_server.ino @@ -0,0 +1,41 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +BLEDevice ble; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + ble.init("MyESP32"); + BLEServer *pServer = new BLEServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLE Tests/Arduino/BLE_write/BLE_write.ino b/cpp_utils/tests/BLE Tests/Arduino/BLE_write/BLE_write.ino new file mode 100644 index 00000000..ed5ebc64 --- /dev/null +++ b/cpp_utils/tests/BLE Tests/Arduino/BLE_write/BLE_write.ino @@ -0,0 +1,68 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +BLEDevice ble; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + std::string value = pCharacteristic->getValue(); + + if (value.length() > 0) { + Serial.println("*********"); + Serial.print("New value: "); + for (int i = 0; i < value.length(); i++) + Serial.print(value[i]); + + Serial.println(); + Serial.println("*********"); + } + } +}; + +void setup() { + Serial.begin(115200); + + Serial.println("1- Download and install an BLE scanner app in your phone"); + Serial.println("2- Scan for BLE devices in the app"); + Serial.println("3- Connect to MyESP32"); + Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something"); + Serial.println("5- See the magic =)"); + + //ble.begin("MyESP32"); + ble.init("MyESP32"); + BLEServer *pServer = new BLEServer(); + + BLEService *pService = pServer->createService(SERVICE_UUID); + + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setCallbacks(new MyCallbacks()); + + pCharacteristic->setValue("Hello World"); + pService->start(); + + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file From ea4153269b94ceac7712f25b596b9f7f3b9c6898 Mon Sep 17 00:00:00 2001 From: copercini Date: Wed, 16 Aug 2017 22:25:21 -0300 Subject: [PATCH 027/381] Add arduino examples to the zip --- cpp_utils/Makefile.arduino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index e59704fe..61f7ab3d 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -59,6 +59,8 @@ build_ble: mkdir -p Arduino/ESP32_BLE/src cp $(BLE_FILES) Arduino/ESP32_BLE/src cp Arduino_ESP32_BLE.library.properties Arduino/ESP32_BLE/library.properties + mkdir -p Arduino/ESP32_BLE/examples + cp tests/BLE\ Tests/Arduino/* Arduino/ESP32_BLE/examples rm -f Arduino/ESP32_BLE.zip cd Arduino; zip -r ESP32_BLE.zip ESP32_BLE - rm -rf Arduino/ESP32_BLE \ No newline at end of file + rm -rf Arduino/ESP32_BLE From 0b4404d17a75b22433f27e143d705a025d523bbc Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 16 Aug 2017 22:11:09 -0500 Subject: [PATCH 028/381] Removal of space in directory name; Makefile cleanup There was a space in a directory name. That just causes problems on all platforms for no great reason. Slight problem with the Linux Makefile.arduino that was corrected. Fantastic samples added to the project courtesy of Evandro. --- .../{BLE XML => BLEXML}/Characteristics/code/.gitignore | 0 .../Characteristics/code/characteristics.txt | 0 cpp_utils/{BLE XML => BLEXML}/Characteristics/code/run.sh | 0 cpp_utils/{BLE XML => BLEXML}/README.md | 0 cpp_utils/{BLE XML => BLEXML}/Services/code/.gitignore | 0 cpp_utils/{BLE XML => BLEXML}/Services/code/run.sh | 0 cpp_utils/{BLE XML => BLEXML}/Services/code/services.txt | 0 cpp_utils/Makefile.arduino | 8 +++++--- .../Arduino/BLE_notify/BLE_notify.ino | 0 .../{BLE Tests => BLETests}/Arduino/BLE_scan/BLE_scan.ino | 0 .../Arduino/BLE_server/BLE_server.ino | 0 .../Arduino/BLE_write/BLE_write.ino | 0 cpp_utils/tests/{BLE Tests => BLETests}/README.md | 0 cpp_utils/tests/{BLE Tests => BLETests}/Sample-MLE-15.cpp | 0 cpp_utils/tests/{BLE Tests => BLETests}/Sample1.cpp | 0 cpp_utils/tests/{BLE Tests => BLETests}/SampleClient.cpp | 0 cpp_utils/tests/{BLE Tests => BLETests}/SampleNotify.cpp | 0 cpp_utils/tests/{BLE Tests => BLETests}/SampleRead.cpp | 0 cpp_utils/tests/{BLE Tests => BLETests}/SampleScan.cpp | 0 cpp_utils/tests/{BLE Tests => BLETests}/SampleServer.cpp | 0 cpp_utils/tests/{BLE Tests => BLETests}/SampleWrite.cpp | 0 cpp_utils/tests/{BLE Tests => BLETests}/main.cpp | 0 22 files changed, 5 insertions(+), 3 deletions(-) rename cpp_utils/{BLE XML => BLEXML}/Characteristics/code/.gitignore (100%) rename cpp_utils/{BLE XML => BLEXML}/Characteristics/code/characteristics.txt (100%) rename cpp_utils/{BLE XML => BLEXML}/Characteristics/code/run.sh (100%) rename cpp_utils/{BLE XML => BLEXML}/README.md (100%) rename cpp_utils/{BLE XML => BLEXML}/Services/code/.gitignore (100%) rename cpp_utils/{BLE XML => BLEXML}/Services/code/run.sh (100%) rename cpp_utils/{BLE XML => BLEXML}/Services/code/services.txt (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/Arduino/BLE_notify/BLE_notify.ino (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/Arduino/BLE_scan/BLE_scan.ino (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/Arduino/BLE_server/BLE_server.ino (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/Arduino/BLE_write/BLE_write.ino (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/README.md (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/Sample-MLE-15.cpp (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/Sample1.cpp (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/SampleClient.cpp (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/SampleNotify.cpp (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/SampleRead.cpp (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/SampleScan.cpp (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/SampleServer.cpp (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/SampleWrite.cpp (100%) rename cpp_utils/tests/{BLE Tests => BLETests}/main.cpp (100%) diff --git a/cpp_utils/BLE XML/Characteristics/code/.gitignore b/cpp_utils/BLEXML/Characteristics/code/.gitignore similarity index 100% rename from cpp_utils/BLE XML/Characteristics/code/.gitignore rename to cpp_utils/BLEXML/Characteristics/code/.gitignore diff --git a/cpp_utils/BLE XML/Characteristics/code/characteristics.txt b/cpp_utils/BLEXML/Characteristics/code/characteristics.txt similarity index 100% rename from cpp_utils/BLE XML/Characteristics/code/characteristics.txt rename to cpp_utils/BLEXML/Characteristics/code/characteristics.txt diff --git a/cpp_utils/BLE XML/Characteristics/code/run.sh b/cpp_utils/BLEXML/Characteristics/code/run.sh similarity index 100% rename from cpp_utils/BLE XML/Characteristics/code/run.sh rename to cpp_utils/BLEXML/Characteristics/code/run.sh diff --git a/cpp_utils/BLE XML/README.md b/cpp_utils/BLEXML/README.md similarity index 100% rename from cpp_utils/BLE XML/README.md rename to cpp_utils/BLEXML/README.md diff --git a/cpp_utils/BLE XML/Services/code/.gitignore b/cpp_utils/BLEXML/Services/code/.gitignore similarity index 100% rename from cpp_utils/BLE XML/Services/code/.gitignore rename to cpp_utils/BLEXML/Services/code/.gitignore diff --git a/cpp_utils/BLE XML/Services/code/run.sh b/cpp_utils/BLEXML/Services/code/run.sh similarity index 100% rename from cpp_utils/BLE XML/Services/code/run.sh rename to cpp_utils/BLEXML/Services/code/run.sh diff --git a/cpp_utils/BLE XML/Services/code/services.txt b/cpp_utils/BLEXML/Services/code/services.txt similarity index 100% rename from cpp_utils/BLE XML/Services/code/services.txt rename to cpp_utils/BLEXML/Services/code/services.txt diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index 61f7ab3d..0eabecd7 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -8,8 +8,6 @@ # The source file for BLE # BLE_FILES= \ - BLEDevice.cpp \ - BLEDevice.h \ BLE2902.cpp \ BLE2902.h \ BLEAddress.cpp \ @@ -28,6 +26,8 @@ BLE_FILES= \ BLEDescriptor.cpp \ BLEDescriptor.h \ BLEDescriptorMap.cpp \ + BLEDevice.cpp \ + BLEDevice.h \ BLERemoteCharacteristic.cpp \ BLERemoteCharacteristic.h \ BLERemoteDescriptor.cpp \ @@ -60,7 +60,9 @@ build_ble: cp $(BLE_FILES) Arduino/ESP32_BLE/src cp Arduino_ESP32_BLE.library.properties Arduino/ESP32_BLE/library.properties mkdir -p Arduino/ESP32_BLE/examples - cp tests/BLE\ Tests/Arduino/* Arduino/ESP32_BLE/examples + cp --recursive tests/BLETests/Arduino Arduino/ESP32_BLE/examples rm -f Arduino/ESP32_BLE.zip cd Arduino; zip -r ESP32_BLE.zip ESP32_BLE rm -rf Arduino/ESP32_BLE + @echo "---------------------------------------" + @echo "ESP32_BLE.zip Arduino library now built" diff --git a/cpp_utils/tests/BLE Tests/Arduino/BLE_notify/BLE_notify.ino b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino similarity index 100% rename from cpp_utils/tests/BLE Tests/Arduino/BLE_notify/BLE_notify.ino rename to cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino diff --git a/cpp_utils/tests/BLE Tests/Arduino/BLE_scan/BLE_scan.ino b/cpp_utils/tests/BLETests/Arduino/BLE_scan/BLE_scan.ino similarity index 100% rename from cpp_utils/tests/BLE Tests/Arduino/BLE_scan/BLE_scan.ino rename to cpp_utils/tests/BLETests/Arduino/BLE_scan/BLE_scan.ino diff --git a/cpp_utils/tests/BLE Tests/Arduino/BLE_server/BLE_server.ino b/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino similarity index 100% rename from cpp_utils/tests/BLE Tests/Arduino/BLE_server/BLE_server.ino rename to cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino diff --git a/cpp_utils/tests/BLE Tests/Arduino/BLE_write/BLE_write.ino b/cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino similarity index 100% rename from cpp_utils/tests/BLE Tests/Arduino/BLE_write/BLE_write.ino rename to cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino diff --git a/cpp_utils/tests/BLE Tests/README.md b/cpp_utils/tests/BLETests/README.md similarity index 100% rename from cpp_utils/tests/BLE Tests/README.md rename to cpp_utils/tests/BLETests/README.md diff --git a/cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp b/cpp_utils/tests/BLETests/Sample-MLE-15.cpp similarity index 100% rename from cpp_utils/tests/BLE Tests/Sample-MLE-15.cpp rename to cpp_utils/tests/BLETests/Sample-MLE-15.cpp diff --git a/cpp_utils/tests/BLE Tests/Sample1.cpp b/cpp_utils/tests/BLETests/Sample1.cpp similarity index 100% rename from cpp_utils/tests/BLE Tests/Sample1.cpp rename to cpp_utils/tests/BLETests/Sample1.cpp diff --git a/cpp_utils/tests/BLE Tests/SampleClient.cpp b/cpp_utils/tests/BLETests/SampleClient.cpp similarity index 100% rename from cpp_utils/tests/BLE Tests/SampleClient.cpp rename to cpp_utils/tests/BLETests/SampleClient.cpp diff --git a/cpp_utils/tests/BLE Tests/SampleNotify.cpp b/cpp_utils/tests/BLETests/SampleNotify.cpp similarity index 100% rename from cpp_utils/tests/BLE Tests/SampleNotify.cpp rename to cpp_utils/tests/BLETests/SampleNotify.cpp diff --git a/cpp_utils/tests/BLE Tests/SampleRead.cpp b/cpp_utils/tests/BLETests/SampleRead.cpp similarity index 100% rename from cpp_utils/tests/BLE Tests/SampleRead.cpp rename to cpp_utils/tests/BLETests/SampleRead.cpp diff --git a/cpp_utils/tests/BLE Tests/SampleScan.cpp b/cpp_utils/tests/BLETests/SampleScan.cpp similarity index 100% rename from cpp_utils/tests/BLE Tests/SampleScan.cpp rename to cpp_utils/tests/BLETests/SampleScan.cpp diff --git a/cpp_utils/tests/BLE Tests/SampleServer.cpp b/cpp_utils/tests/BLETests/SampleServer.cpp similarity index 100% rename from cpp_utils/tests/BLE Tests/SampleServer.cpp rename to cpp_utils/tests/BLETests/SampleServer.cpp diff --git a/cpp_utils/tests/BLE Tests/SampleWrite.cpp b/cpp_utils/tests/BLETests/SampleWrite.cpp similarity index 100% rename from cpp_utils/tests/BLE Tests/SampleWrite.cpp rename to cpp_utils/tests/BLETests/SampleWrite.cpp diff --git a/cpp_utils/tests/BLE Tests/main.cpp b/cpp_utils/tests/BLETests/main.cpp similarity index 100% rename from cpp_utils/tests/BLE Tests/main.cpp rename to cpp_utils/tests/BLETests/main.cpp From 09ea2c2afcea52f412d3fd1fc4ddd820f63ef17b Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 21 Aug 2017 13:16:06 -0500 Subject: [PATCH 029/381] #40 --- cpp_utils/BLEClient.h | 2 +- cpp_utils/BLEClientCallbacks.cpp | 17 ----------------- cpp_utils/BLEDevice.cpp | 2 +- cpp_utils/Makefile.arduino | 1 - hardware/neopixels/README.md | 6 +++++- 5 files changed, 7 insertions(+), 21 deletions(-) delete mode 100644 cpp_utils/BLEClientCallbacks.cpp diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 555e85fa..6a74ea2c 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -69,7 +69,7 @@ class BLEClient { class BLEClientCallbacks { public: virtual ~BLEClientCallbacks() {}; - virtual void onConnect(BLEClient *pClient); + virtual void onConnect(BLEClient *pClient) = 0; }; #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEClientCallbacks.cpp b/cpp_utils/BLEClientCallbacks.cpp deleted file mode 100644 index d5c8f952..00000000 --- a/cpp_utils/BLEClientCallbacks.cpp +++ /dev/null @@ -1,17 +0,0 @@ -/* - * BLEClientCallbacks.cpp - * - * Created on: Jul 5, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include "BLEClient.h" -#include -static const char* LOG_TAG = "BLEClientCallbacks"; - -void BLEClientCallbacks::onConnect(BLEClient* pClient) { - ESP_LOGD(LOG_TAG, ">> onConnect(): Default"); - ESP_LOGD(LOG_TAG, "<< onConnect()"); -} -#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index b615ce74..04cae14c 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -28,7 +28,7 @@ #include "BLEUtils.h" #include "GeneralUtils.h" -static const char* LOG_TAG = "BLE"; +static const char* LOG_TAG = "BLEDevice"; BLEServer *BLEDevice::m_bleServer = nullptr; BLEScan *BLEDevice::m_pScan = nullptr; diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index 0eabecd7..c14be636 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -22,7 +22,6 @@ BLE_FILES= \ BLECharacteristicMap.cpp \ BLEClient.cpp \ BLEClient.h \ - BLEClientCallbacks.cpp \ BLEDescriptor.cpp \ BLEDescriptor.h \ BLEDescriptorMap.cpp \ diff --git a/hardware/neopixels/README.md b/hardware/neopixels/README.md index 5dbc0139..b7c2caa1 100644 --- a/hardware/neopixels/README.md +++ b/hardware/neopixels/README.md @@ -1,2 +1,6 @@ # Moved -The class supporting the WS2812 pixels has moved to the `cpp_utils` folder. \ No newline at end of file +The class supporting the WS2812 pixels has moved to the `cpp_utils` folder. + +See also: +* Github: [MartyMacGyver/ESP32-Digital-RGB-LED-Drivers](https://github.com/MartyMacGyver/ESP32-Digital-RGB-LED-Drivers) +* Github: [FozzTexx/ws2812-demo](https://github.com/FozzTexx/ws2812-demo) \ No newline at end of file From ed4628eac4b6c63eac46d501ce750a6dbfd426a0 Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 28 Aug 2017 18:41:16 -0500 Subject: [PATCH 030/381] Changes for #42 - 2017-08-28 --- cpp_utils/SockServ.cpp | 161 +++++++++++++++++------------ cpp_utils/SockServ.h | 26 +++-- curl/build_files/README.md | 26 +---- curl/build_files/lib/curl_config.h | 6 +- filesystems/sendZip/.gitignore | 2 + 5 files changed, 120 insertions(+), 101 deletions(-) create mode 100644 filesystems/sendZip/.gitignore diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 951ac4f8..60cd4ef7 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -7,16 +7,17 @@ #include #include +#include #include + #include #include #include -#include "FreeRTOS.h" #include "sdkconfig.h" #include "SockServ.h" -static char tag[] = "SockServ"; +static const char* LOG_TAG = "SockServ"; /** @@ -26,9 +27,10 @@ static char tag[] = "SockServ"; * @param [in] port The TCP/IP port number on which we will listen for incoming connection requests. */ SockServ::SockServ(uint16_t port) { - this->port = port; - clientSock = -1; - sock = -1; + this->m_port = port; + m_clientSock = -1; + m_sock = -1; + m_clientSemaphore.take("SockServ"); } // SockServ @@ -39,63 +41,83 @@ SockServ::SockServ(uint16_t port) { */ void SockServ::acceptTask(void *data) { - SockServ *pSockServ = (SockServ *)data; + SockServ* pSockServ = (SockServ*)data; struct sockaddr_in clientAddress; while(1) { socklen_t clientAddressLength = sizeof(clientAddress); - int tempSock = ::accept(pSockServ->sock, (struct sockaddr *)&clientAddress, &clientAddressLength); + int tempSock = ::accept(pSockServ->m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); if (tempSock == -1) { - ESP_LOGE(tag, "close(): %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); } - ESP_LOGD(tag, "accept() - New socket"); - if (pSockServ->clientSock != -1) { - int rc = ::close(pSockServ->clientSock); + ESP_LOGD(LOG_TAG, "accept() - New socket"); + if (pSockServ->m_clientSock != -1) { + int rc = ::close(pSockServ->m_clientSock); if (rc == -1) { - ESP_LOGE(tag, "close(): %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); } } - pSockServ->clientSock = tempSock; + pSockServ->m_clientSock = tempSock; + pSockServ->m_clientSemaphore.give(); } } // acceptTask /** - * @brief Start listening for new partner connections. + * @brief Determine the number of connected partners. * - * The port number on which we will listen is the one defined when the class was created. + * @return The number of connected partners. */ -void SockServ::start() { - sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sock == -1) { - ESP_LOGE(tag, "socket(): %s", strerror(errno)); - } - struct sockaddr_in serverAddress; - serverAddress.sin_family = AF_INET; - serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); - serverAddress.sin_port = htons(port); - int rc = ::bind(sock, (const struct sockaddr *)&serverAddress, sizeof(serverAddress)); - if (rc == -1) { - ESP_LOGE(tag, "bind(): %s", strerror(errno)); +int SockServ::connectedCount() { + if (m_clientSock == -1) { + return 0; } - rc = ::listen(sock, 5); - if (rc == -1) { - ESP_LOGE(tag, "listen(): %s", strerror(errno)); + return 1; +} // connectedCount + + +/** + * @brief Disconnect any connected partners. + */ +void SockServ::disconnect() { + if (m_clientSock != -1) { + int rc = ::close(m_clientSock); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); + } + m_clientSock = -1; + m_clientSemaphore.take("disconnect"); } - ESP_LOGD(tag, "Now listening on port %d", port); - FreeRTOS::startTask(acceptTask, "acceptTask", this); -} // start +} // disconnect /** - * @brief Stop listening for new partner connections. + * @brief Wait for data + * @param [in] pData Pointer to buffer to hold the data. + * @param [in] maxData Maximum size of the data to receive. + * @return The amount of data returned or 0 if there was an error. */ -void SockServ::stop() { - int rc = ::close(sock); +size_t SockServ::receiveData(void* pData, size_t maxData) { + if (m_clientSock == -1) { + return 0; + } + int rc = ::recv(m_clientSock, pData, maxData, 0); if (rc == -1) { - ESP_LOGE(tag, "close(): %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "recv(): %s", strerror(errno)); + return 0; } -} // stop + return rc; +} // receiveData + + +/** + * @brief Send data from a string to any connected partners. + * + * @param[in] str A string from which sequence of bytes will be used to send to the partner. + */ +void SockServ::sendData(std::string str) { + sendData((uint8_t *)str.data(), str.size()); +} // sendData /** @@ -104,49 +126,60 @@ void SockServ::stop() { * @param[in] data A sequence of bytes to send to the partner. * @param[in] length The length of the sequence of bytes to send to the partner. */ -void SockServ::sendData(uint8_t *data, size_t length) { +void SockServ::sendData(uint8_t* data, size_t length) { if (connectedCount() == 0) { return; } - int rc = ::send(clientSock, data, length, 0); + int rc = ::send(m_clientSock, data, length, 0); if (rc == -1) { - ESP_LOGE(tag, "send(): %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "send(): %s", strerror(errno)); } } // sendData /** - * @brief Send data from a string to any connected partners. + * @brief Start listening for new partner connections. * - * @param[in] str A string from which sequence of bytes will be used to send to the partner. + * The port number on which we will listen is the one defined when the class was created. */ -void SockServ::sendData(std::string str) { - sendData((uint8_t *)str.data(), str.size()); -} // sendData +void SockServ::start() { + m_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_sock == -1) { + ESP_LOGE(LOG_TAG, "socket(): %s", strerror(errno)); + } + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(m_port); + int rc = ::bind(m_sock, (const struct sockaddr *)&serverAddress, sizeof(serverAddress)); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "bind(): %s", strerror(errno)); + } + rc = ::listen(m_sock, 5); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "listen(): %s", strerror(errno)); + } + ESP_LOGD(LOG_TAG, "Now listening on port %d", m_port); + FreeRTOS::startTask(acceptTask, "acceptTask", this); +} // start /** - * @brief Determine the number of connected partners. - * - * @return The number of connected partners. + * @brief Stop listening for new partner connections. */ -int SockServ::connectedCount() { - if (clientSock == -1) { - return 0; +void SockServ::stop() { + int rc = ::close(m_sock); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); } - return 1; -} // connectedCount +} // stop /** - * @brief Disconnect any connected partners. + * @brief Wait for a client connection to be present. + * Returns when a client connection is present. This can block until a client connects + * or can return immediately is there is already a client connection in existence. */ -void SockServ::disconnect() { - if (clientSock == -1) { - int rc = ::close(clientSock); - if (rc == -1) { - ESP_LOGE(tag, "close(): %s", strerror(errno)); - } - clientSock = -1; - } -} // disconnect +void SockServ::waitForClient() { + m_clientSemaphore.wait("waitForClient"); +} // waitForClient diff --git a/cpp_utils/SockServ.h b/cpp_utils/SockServ.h index a7221ca3..8de7bd28 100644 --- a/cpp_utils/SockServ.h +++ b/cpp_utils/SockServ.h @@ -4,6 +4,8 @@ #define MAIN_SOCKSERV_H_ #include #include +#include "FreeRTOS.h" + /** * @brief Provide a socket listener and the ability to send data to connected partners. @@ -23,20 +25,24 @@ * @endcode * */ + class SockServ { private: - uint16_t port; - int sock; - int clientSock; - static void acceptTask(void *data); + static void acceptTask(void*); + uint16_t m_port; + int m_sock; + int m_clientSock; + FreeRTOS::Semaphore m_clientSemaphore; public: SockServ(uint16_t port); - int connectedCount(); - void disconnect(); - void sendData(uint8_t *data, size_t length); - void sendData(std::string str); - void start(); - void stop(); + int connectedCount(); + void disconnect(); + size_t receiveData(void* pData, size_t maxData); + void sendData(uint8_t *data, size_t length); + void sendData(std::string str); + void start(); + void stop(); + void waitForClient(); }; #endif /* MAIN_SOCKSERV_H_ */ diff --git a/curl/build_files/README.md b/curl/build_files/README.md index afd1fa56..dd515c98 100644 --- a/curl/build_files/README.md +++ b/curl/build_files/README.md @@ -13,30 +13,6 @@ We now need to copy some ESP32 specific configuration files. These are: * `lib/curl_config.h` * `component.mk` -Finally, we need to make a small edit. Find the file called: -`curl/include/curl/system.h` -and open it in your favorite editor. Find the section that reads: - -``` -#elif defined(__GNUC__) -# if !defined(__LP64__) && (defined(__ILP32__) || \ - defined(__i386__) || defined(__ppc__) || defined(__arm__) || \ - defined(__sparc__) || defined(__mips__) || defined(__sh__)) -``` - -and change to: - -``` -#elif defined(__GNUC__) -# if !defined(__LP64__) && (defined(__ILP32__) || \ - defined(__i386__) || defined(__ppc__) || defined(__arm__) || \ - defined(__sparc__) || defined(__mips__) || defined(__sh__) || defined(__XTENSA__)) -``` - -A request has been made to the owners of libcurl to make this change in their own source. See [issue 1598](https://github.com/curl/curl/issues/1598). - -Once the above steps have been completed, we should be able to build our ESP-IDF project as normal and that will include the construction -of the curl library ready for use. Now you can code directly to the curl APIs or you can code to the -C++ REST classes that leverage curl. \ No newline at end of file +Once the above steps have been completed, we should be able to build our ESP-IDF project as normal and that will include the construction of the curl library ready for use. Now you can code directly to the curl APIs or you can code to the C++ REST classes that leverage curl. \ No newline at end of file diff --git a/curl/build_files/lib/curl_config.h b/curl/build_files/lib/curl_config.h index 27b378c5..baffe474 100644 --- a/curl/build_files/lib/curl_config.h +++ b/curl/build_files/lib/curl_config.h @@ -904,6 +904,8 @@ /* The size of `void*', as computed by sizeof. */ #define SIZEOF_VOIDP 4 +#define SIZEOF_CURL_OFF_T 4 + /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 @@ -944,8 +946,8 @@ #define USE_MANUAL 1 /* if mbedTLS is enabled */ -/* #undef USE_MBEDTLS */ -#define USE_MBEDTLS 1 +#undef USE_MBEDTLS +//#define USE_MBEDTLS 1 /* Define to enable metalink support */ /* #undef USE_METALINK */ diff --git a/filesystems/sendZip/.gitignore b/filesystems/sendZip/.gitignore new file mode 100644 index 00000000..515f5c44 --- /dev/null +++ b/filesystems/sendZip/.gitignore @@ -0,0 +1,2 @@ +/package-lock.json +/node_modules/ From 9cb4b0533ea0e887d0ac506bc8858e4fb12da5d1 Mon Sep 17 00:00:00 2001 From: Chester Date: Mon, 28 Aug 2017 17:28:46 -1000 Subject: [PATCH 031/381] BLE_uart example Combines the Notify and Write examples to create a basic UART example that works with the Nordic nRF Android app and many others. Could use a Print function and possibly read function, but basic RX/TX does work. --- .../BLETests/Arduino/BLE_uart/BLE_uart.ino | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino new file mode 100644 index 00000000..a8ab2d7f --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino @@ -0,0 +1,111 @@ +/* + Video: https://www.youtube.com/watch?v=oCMOYS71NIU + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + Ported to Arduino ESP32 by Evandro Copercini + + Create a BLE server that, once we receive a connection, will send periodic notifications. + The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E + Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE" + Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with "NOTIFY" + + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create a BLE Service + 3. Create a BLE Characteristic on the Service + 4. Create a BLE Descriptor on the characteristic + 5. Start the service. + 6. Start advertising. + + In this example rxValue is the data received (only accessible inside that function). + And txValue is the data to be sent, in this example just a byte incremented every second. +*/ +#include +#include +#include +#include + +BLECharacteristic *pCharacteristic; +bool deviceConnected = false; +uint8_t txValue = 0; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID +#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" +#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + } +}; + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + std::string rxValue = pCharacteristic->getValue(); + + if (rxValue.length() > 0) { + Serial.println("*********"); + Serial.print("Received Value: "); + for (int i = 0; i < rxValue.length(); i++) + Serial.print(rxValue[i]); + + Serial.println(); + Serial.println("*********"); + } + } +}; + + +void setup() { + Serial.begin(115200); + + // Create the BLE Device + BLEDevice::init("UART Service"); + + // Create the BLE Server + BLEServer *pServer = new BLEServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_TX, + BLECharacteristic::PROPERTY_NOTIFY + ); + + pCharacteristic->addDescriptor(new BLE2902()); + + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_RX, + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setCallbacks(new MyCallbacks()); + + // Start the service + pService->start(); + + // Start advertising + pServer->getAdvertising()->start(); + Serial.println("Waiting a client connection to notify..."); +} + +void loop() { + + if (deviceConnected) { + Serial.printf("*** Sent Value: %d ***\n", txValue); + pCharacteristic->setValue(&txValue, 1); + pCharacteristic->notify(); + txValue++; + } + delay(1000); +} From c05db2a4b761848f4023eae3b81e29d5236937b1 Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 2 Sep 2017 16:45:12 -0500 Subject: [PATCH 032/381] Sync commit --- cpp_utils/GeneralUtils.cpp | 1 + cpp_utils/HttpParser.cpp | 229 +++++++++++++++++++++++++++++++++++ cpp_utils/HttpParser.h | 36 ++++++ cpp_utils/HttpRequest.cpp | 230 ++++++++++++++++++++++++++++++++++++ cpp_utils/HttpRequest.h | 87 ++++++++++++++ cpp_utils/HttpResponse.cpp | 18 +++ cpp_utils/HttpResponse.h | 35 ++++++ cpp_utils/HttpServer.cpp | 153 ++++++++++++++++++++++++ cpp_utils/HttpServer.h | 52 ++++++++ cpp_utils/SPI.cpp | 4 +- cpp_utils/SockServ.cpp | 141 ++++++++++++---------- cpp_utils/SockServ.h | 20 +++- cpp_utils/Socket.cpp | 119 ++++++++++++++++--- cpp_utils/Socket.h | 30 +++-- cpp_utils/Task.cpp | 4 +- cpp_utils/WebServer.cpp | 2 +- cpp_utils/WebSocket.cpp | 161 +++++++++++++++++++++++++ cpp_utils/WebSocket.h | 32 +++++ cpp_utils/WiFi.cpp | 4 - cpp_utils/WiFi.h | 3 +- filesystems/sendZip/test.js | 24 ++++ 21 files changed, 1277 insertions(+), 108 deletions(-) create mode 100644 cpp_utils/HttpParser.cpp create mode 100644 cpp_utils/HttpParser.h create mode 100644 cpp_utils/HttpRequest.cpp create mode 100644 cpp_utils/HttpRequest.h create mode 100644 cpp_utils/HttpResponse.cpp create mode 100644 cpp_utils/HttpResponse.h create mode 100644 cpp_utils/HttpServer.cpp create mode 100644 cpp_utils/HttpServer.h create mode 100644 cpp_utils/WebSocket.cpp create mode 100644 cpp_utils/WebSocket.h create mode 100644 filesystems/sendZip/test.js diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index c3504b8e..4237b145 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -308,6 +308,7 @@ std::string GeneralUtils::ipToString(uint8_t *ip) { return s.str(); } // ipToString + /** * @brief Convert an ESP error code to a string. * @param [in] errCode The errCode to be converted. diff --git a/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp new file mode 100644 index 00000000..3f934558 --- /dev/null +++ b/cpp_utils/HttpParser.cpp @@ -0,0 +1,229 @@ +/* + * HttpParser.cpp + * + * Created on: Aug 28, 2017 + * Author: kolban + */ + +#include "HttpParser.h" +#include +#include +/** + * RFC7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing + * RFC3986 - URI + * + * + *

+ *
+ * ... + *
+ * + * + * + * Example: + * GET /hello.txt HTTP/1.1 + */ + +static const char* LOG_TAG = "HttpParser"; + +static std::string lineTerminator = "\r\n"; + + +/** + * @brief Remove white space from a string. + */ +static std::string trim(const std::string& str) +{ + size_t first = str.find_first_not_of(' '); + if (std::string::npos == first) + { + return str; + } + size_t last = str.find_last_not_of(' '); + return str.substr(first, (last - first + 1)); +} // trim + + +/** + * @brief Parse an incoming line of text until we reach a delimiter. + * @param [in/out] it The current iterator in the text input. + * @param [in] str The string we are parsing. + * @param [in] token The token delimiter. + */ +static std::string toStringToken(std::string::iterator &it, std::string &str, std::string &token) { + std::string ret; + std::string part; + auto itToken = token.begin(); + for(; it != str.end(); ++it) { + if ((*it) == (*itToken)) { + part += (*itToken); + ++itToken; + if (itToken == token.end()) { + ++it; + break; + } + } else { + if (part.empty()) { + ret += part; + part.clear(); + itToken = token.begin(); + } + ret += *it; + } + + } // for + return ret; +} // toStringToken + + +/** + * @brief Parse a string until the given token is found. + * @param [in] it The current iterator. + * @param [in] str The string being parsed. + * @param [in] token The token terminating the parse. + * @return The parsed string token. + */ +static std::string toCharToken(std::string::iterator &it, std::string &str, char token) { + std::string ret; + for(; it != str.end(); ++it) { + if ((*it) == token) { + ++it; + break; + } + ret += *it; + } + return ret; +} // toCharToken + + + + +/** + * @brief Parse a header line. + * An HTTP Header is of the form: + * + * Name":" Value + * + * We parse this and return a pair. + * @param [in] line The line of text to parse. + * @return A pair of the form name/value. + */ +std::pair parseHeader(std::string &line) { + auto it = line.begin(); + auto name = toCharToken(it, line, ':'); + auto value = trim(toStringToken(it, line, lineTerminator)); + return std::pair(name, value); +} // parseHeader + + +HttpParser::HttpParser() { + // TODO Auto-generated constructor stub + +} + +HttpParser::~HttpParser() { + // TODO Auto-generated destructor stub +} + + +/** + * @brief Dump the outcome of the parse. + * + */ +void HttpParser::dump() { + ESP_LOGD(LOG_TAG, "Method: %s, URL: \"%s\", Version: %s", m_method.c_str(), m_url.c_str(), m_version.c_str()); + auto it2 = m_headers.begin(); + for (; it2 != m_headers.end(); ++it2) { + ESP_LOGD(LOG_TAG, "name=\"%s\", value=\"%s\"", it2->first.c_str(), it2->second.c_str()); + } + ESP_LOGD(LOG_TAG, "Body: \"%s\"", m_body.c_str()); +} // dump + +std::string HttpParser::getBody() { + return m_body; +} + +std::string HttpParser::getHeader(std::string& name) { + if (m_headers.find(name) == m_headers.end()) { + return ""; + } + return m_headers.at(name); +} + +std::map HttpParser::getHeaders() { + return m_headers; +} + +std::string HttpParser::getMethod() { + return m_method; +} // getMethod + +std::string HttpParser::getURL() { + return m_url; +} // getURL + +std::string HttpParser::getVersion() { + return m_version; +} // getVersion + + + +/** + * @brief Parse socket data. + * @param [in] s The socket from which to retrieve data. + */ +void HttpParser::parse(Socket s) { + std::string line; + line = s.readToDelim(lineTerminator); + parseRequestLine(line); + line = s.readToDelim(lineTerminator); + while(!line.empty()) { + m_headers.insert(parseHeader(line)); + line = s.readToDelim(lineTerminator); + } +} // parse + + +/** + * @brief Parse a string message. + * @param [in] message The HTTP message to parse. + */ +void HttpParser::parse(std::string message) { + auto it = message.begin(); + auto line = toStringToken(it, message, lineTerminator); + parseRequestLine(line); + + line = toStringToken(it, message, lineTerminator); + while(!line.empty()) { + //ESP_LOGD(LOG_TAG, "Header: \"%s\"", line.c_str()); + m_headers.insert(parseHeader(line)); + line = toStringToken(it, message, lineTerminator); + } + + m_body = message.substr(std::distance(message.begin(), it)); +} // parse + + +/** + * @brief Parse A request line. + * @param [in] line The request line to parse. + */ +// A request Line is built from: +// +// +void HttpParser::parseRequestLine(std::string &line) { + ESP_LOGD(LOG_TAG, ">> parseRequestLine: %s [%d]", line.c_str(), line.length()); + std::string::iterator it = line.begin(); + + // Get the method + m_method = toCharToken(it, line, ' '); + + // Get the url + m_url = toCharToken(it, line, ' '); + + // Get the version + m_version = toCharToken(it, line, ' '); +} // parseRequestLine + + + diff --git a/cpp_utils/HttpParser.h b/cpp_utils/HttpParser.h new file mode 100644 index 00000000..5147b2ab --- /dev/null +++ b/cpp_utils/HttpParser.h @@ -0,0 +1,36 @@ +/* + * HttpParser.h + * + * Created on: Aug 28, 2017 + * Author: kolban + */ + +#ifndef CPP_UTILS_HTTPPARSER_H_ +#define CPP_UTILS_HTTPPARSER_H_ +#include +#include +#include "Socket.h" + +class HttpParser { +private: + std::string m_method; + std::string m_url; + std::string m_version; + std::string m_body; + std::map m_headers; + void dump(); + void parseRequestLine(std::string &line); +public: + HttpParser(); + virtual ~HttpParser(); + std::string getBody(); + std::string getHeader(std::string& name); + std::map getHeaders(); + std::string getMethod(); + std::string getURL(); + std::string getVersion(); + void parse(std::string message); + void parse(Socket s); +}; + +#endif /* CPP_UTILS_HTTPPARSER_H_ */ diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp new file mode 100644 index 00000000..f1bff4a1 --- /dev/null +++ b/cpp_utils/HttpRequest.cpp @@ -0,0 +1,230 @@ +/* + * HTTPRequest.cpp + * + * Created on: Aug 30, 2017 + * Author: kolban + */ + +/* + * A Websocket hand shake request looks like: + * + * GET /chat HTTP/1.1 + * Host: server.example.com + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== + * Origin: http://example.com + * Sec-WebSocket-Protocol: chat, superchat + * Sec-WebSocket-Version: 13 + * + * + * A corresponding hand shake response looks like: + * + * HTTP/1.1 101 Switching Protocols + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= + * Sec-WebSocket-Protocol: chat + * + * The server key returned in Sec-WebSocket-Accept is the value of Sec-WebSocket-Key passed in the + * request concatenated with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" and then take the SHA-1 hash + * of the result to give a 20 byte value which is then base64() encoded. + */ + +#include +#include "HttpRequest.h" +#include "GeneralUtils.h" + +#include +#include + + +static const char* LOG_TAG="HttpRequest"; + +static std::string lineTerminator = "\r\n"; + +const std::string HttpRequest::HTTP_HEADER_ACCEPT = "Accept"; +const std::string HttpRequest::HTTP_HEADER_ALLOW = "Allow"; +const std::string HttpRequest::HTTP_HEADER_CONNECTION = "Connection"; +const std::string HttpRequest::HTTP_HEADER_CONTENT_LENGTH = "Content-Length"; +const std::string HttpRequest::HTTP_HEADER_CONTENT_TYPE = "Content-Type"; +const std::string HttpRequest::HTTP_HEADER_COOKIE = "Cookie"; +const std::string HttpRequest::HTTP_HEADER_HOST = "Host"; +const std::string HttpRequest::HTTP_HEADER_LAST_MODIFIED = "Last-Modified"; +const std::string HttpRequest::HTTP_HEADER_ORIGIN = "Origin"; +const std::string HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept"; +const std::string HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; +const std::string HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; +const std::string HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version"; +const std::string HttpRequest::HTTP_HEADER_UPGRADE = "Upgrade"; +const std::string HttpRequest::HTTP_HEADER_USER_AGENT = "User-Agent"; + +const std::string HttpRequest::HTTP_METHOD_CONNECT = "CONNECT"; +const std::string HttpRequest::HTTP_METHOD_DELETE = "DELETE"; +const std::string HttpRequest::HTTP_METHOD_GET = "GET"; +const std::string HttpRequest::HTTP_METHOD_HEAD = "HEAD"; +const std::string HttpRequest::HTTP_METHOD_OPTIONS = "OPTIONS"; +const std::string HttpRequest::HTTP_METHOD_PATCH = "PATCH"; +const std::string HttpRequest::HTTP_METHOD_POST = "POST"; +const std::string HttpRequest::HTTP_METHOD_PUT = "PUT"; + + +const int HttpRequest::HTTP_STATUS_CONTINUE = 100; +const int HttpRequest::HTTP_STATUS_SWITCHING_PROTOCOL = 101; +const int HttpRequest::HTTP_STATUS_OK = 200; +const int HttpRequest::HTTP_STATUS_MOVED_PERMANENTLY = 301; +const int HttpRequest::HTTP_STATUS_BAD_REQUEST = 400; +const int HttpRequest::HTTP_STATUS_UNAUTHORIZED = 401; +const int HttpRequest::HTTP_STATUS_FORBIDDEN = 403; +const int HttpRequest::HTTP_STATUS_NOT_FOUND = 404; +const int HttpRequest::HTTP_STATUS_METHOD_NOT_ALLOWED = 405; +const int HttpRequest::HTTP_STATUS_INTERNAL_SERVER_ERROR = 500; +const int HttpRequest::HTTP_STATUS_NOT_IMPLEMENTED = 501; +const int HttpRequest::HTTP_STATUS_SERVICE_UNAVAILABLE = 503; + +std::string buildResponseHash(std::string requestKey) { + std::string newKey = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + uint8_t shaData[20]; + esp_sha(SHA1, (uint8_t*)newKey.data(), newKey.length(), shaData); + //GeneralUtils::hexDump(shaData, 20); + std::string retStr; + GeneralUtils::base64Encode(std::string((char*)shaData, sizeof(shaData)), &retStr); + return retStr; +} + + +HttpRequest::HttpRequest(Socket clientSocket) { + m_clientSocket = clientSocket; + m_status = 0; + m_isWebsocket = false; + + m_parser.parse(clientSocket); + + // Is this a Web Socket? + if (getMethod() == HTTP_METHOD_GET && + !getRequestHeader(HTTP_HEADER_HOST).empty() && + getRequestHeader(HTTP_HEADER_UPGRADE) == "websocket" && + getRequestHeader(HTTP_HEADER_CONNECTION) == "Upgrade" && + !getRequestHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY).empty() && + !getRequestHeader(HTTP_HEADER_SEC_WEBSOCKET_VERSION).empty()) { + ESP_LOGD(LOG_TAG, "Websocket detected!"); + m_isWebsocket = true; + // do something + // Process the web socket request + + + setStatus(HTTP_STATUS_SWITCHING_PROTOCOL, "Switching Protocols"); + addResponseHeader(HTTP_HEADER_UPGRADE, "websocket"); + addResponseHeader(HTTP_HEADER_CONNECTION, "Upgrade"); + addResponseHeader(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, + buildResponseHash(getRequestHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY))); + sendResponse(); + } else { + ESP_LOGD(LOG_TAG, "Not a Websocket"); + } +} // HttpRequest + + +HttpRequest::~HttpRequest() { +} // ~HttpRequest + + +void HttpRequest::addResponseHeader(const std::string name, const std::string value) { + m_responseHeaders.insert(std::pair(name, value)); +} // addResponseHeader + + +void HttpRequest::close_cpp() { + m_clientSocket.close_cpp(); +} // close_cpp + + +void HttpRequest::dump() { + ESP_LOGD(LOG_TAG, "Method: %s, URL: \"%s\", Version: %s", getMethod().c_str(), getPath().c_str(), getVersion().c_str()); + auto headers = getRequestHeaders(); + auto it2 = headers.begin(); + for (; it2 != headers.end(); ++it2) { + ESP_LOGD(LOG_TAG, "name=\"%s\", value=\"%s\"", it2->first.c_str(), it2->second.c_str()); + } + ESP_LOGD(LOG_TAG, "Body: \"%s\"", getRequestBody().c_str()); +} // dump + + +std::string HttpRequest::getMethod() { + return m_parser.getMethod(); +} // getMethod + + +std::string HttpRequest::getPath() { + return m_parser.getURL(); +} // getURL + +std::string HttpRequest::getRequestBody() { + return m_parser.getBody(); +} // getRequestBody + + +std::string HttpRequest::getRequestHeader(std::string name) { + return m_parser.getHeader(name); +} // getRequestHeader + + +std::map HttpRequest::getRequestHeaders() { + return m_parser.getHeaders(); +} // getRequestHeaders + + +std::string HttpRequest::getResponseHeader(std::string name) { + if (m_responseHeaders.find(name) == m_responseHeaders.end()) { + return ""; + } + return m_responseHeaders.at(name); +} // getResponseHeader + + +std::map HttpRequest::getResponseHeaders() { + return m_responseHeaders; +} // getResponseHeaders + + +Socket HttpRequest::getSocket() { + return m_clientSocket; +} + + + + + +std::string HttpRequest::getVersion() { + return m_parser.getVersion(); +} // getVersion + + +bool HttpRequest::isWebsocket() { + return m_isWebsocket; +} + + +void HttpRequest::sendResponse() { + std::ostringstream oss; + oss << getVersion() << " " << m_status << " " << m_responseMessage << lineTerminator; + for (auto it = m_responseHeaders.begin(); it != m_responseHeaders.end(); ++it) { + oss << it->first.c_str() << ": " << it->second.c_str() << lineTerminator; + } + oss << lineTerminator; + oss << m_responseBody; + ESP_LOGD(LOG_TAG, ">> sendResponse: %s", oss.str().c_str()); + m_clientSocket.send_cpp(oss.str()); + /*TODO*/ +} // sendResponse + + +void HttpRequest::setResponseBody(const std::string body) { + m_responseBody = body; +} // setResponseBody + + +void HttpRequest::setStatus(const int status, const std::string message) { + m_status = status; + m_responseMessage = message; +} // setStatus diff --git a/cpp_utils/HttpRequest.h b/cpp_utils/HttpRequest.h new file mode 100644 index 00000000..37fea208 --- /dev/null +++ b/cpp_utils/HttpRequest.h @@ -0,0 +1,87 @@ +/* + * HTTPRequest.h + * + * Created on: Aug 30, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ +#define COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ +#include +#include +#include "Socket.h" +#include "HttpParser.h" + + + +class HttpRequest { +private: + Socket m_clientSocket; + HttpParser m_parser; + int m_status; + bool m_isWebsocket; + std::string m_responseMessage; + std::string m_responseBody; + std::map m_responseHeaders; +public: + + HttpRequest(Socket s); + virtual ~HttpRequest(); + static const std::string HTTP_HEADER_ACCEPT; + static const std::string HTTP_HEADER_ALLOW; + static const std::string HTTP_HEADER_CONNECTION; + static const std::string HTTP_HEADER_CONTENT_LENGTH; + static const std::string HTTP_HEADER_CONTENT_TYPE; + static const std::string HTTP_HEADER_COOKIE; + static const std::string HTTP_HEADER_HOST; + static const std::string HTTP_HEADER_LAST_MODIFIED; + static const std::string HTTP_HEADER_ORIGIN; + static const std::string HTTP_HEADER_SEC_WEBSOCKET_ACCEPT; + static const std::string HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL; + static const std::string HTTP_HEADER_SEC_WEBSOCKET_KEY; + static const std::string HTTP_HEADER_SEC_WEBSOCKET_VERSION; + static const std::string HTTP_HEADER_UPGRADE; + static const std::string HTTP_HEADER_USER_AGENT; + + static const std::string HTTP_METHOD_CONNECT; + static const std::string HTTP_METHOD_DELETE; + static const std::string HTTP_METHOD_GET; + static const std::string HTTP_METHOD_HEAD; + static const std::string HTTP_METHOD_OPTIONS; + static const std::string HTTP_METHOD_PATCH; + static const std::string HTTP_METHOD_POST; + static const std::string HTTP_METHOD_PUT; + + static const int HTTP_STATUS_CONTINUE; + static const int HTTP_STATUS_SWITCHING_PROTOCOL; + static const int HTTP_STATUS_OK; + static const int HTTP_STATUS_MOVED_PERMANENTLY; + static const int HTTP_STATUS_BAD_REQUEST; + static const int HTTP_STATUS_UNAUTHORIZED; + static const int HTTP_STATUS_FORBIDDEN; + static const int HTTP_STATUS_NOT_FOUND; + static const int HTTP_STATUS_METHOD_NOT_ALLOWED; + static const int HTTP_STATUS_INTERNAL_SERVER_ERROR; + static const int HTTP_STATUS_NOT_IMPLEMENTED; + static const int HTTP_STATUS_SERVICE_UNAVAILABLE; + + void addResponseHeader(const std::string name, const std::string value); + void close_cpp(); + void dump(); + std::string getMethod(); + std::string getPath(); + std::string getRequestBody(); + std::string getRequestHeader(std::string name); + std::map getRequestHeaders(); + std::string getResponseHeader(std::string name); + std::map getResponseHeaders(); + Socket getSocket(); + + std::string getVersion(); + bool isWebsocket(); + void sendResponse(); + void setResponseBody(const std::string body); + void setStatus(const int status, const std::string message); +}; + +#endif /* COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ */ diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp new file mode 100644 index 00000000..b471062a --- /dev/null +++ b/cpp_utils/HttpResponse.cpp @@ -0,0 +1,18 @@ +/* + * HttpResponse.cpp + * + * Created on: Sep 2, 2017 + * Author: kolban + */ + +#include "HttpResponse.h" + +HttpResponse::HttpResponse() { + // TODO Auto-generated constructor stub + +} + +HttpResponse::~HttpResponse() { + // TODO Auto-generated destructor stub +} + diff --git a/cpp_utils/HttpResponse.h b/cpp_utils/HttpResponse.h new file mode 100644 index 00000000..41a67ebe --- /dev/null +++ b/cpp_utils/HttpResponse.h @@ -0,0 +1,35 @@ +/* + * HttpResponse.h + * + * Created on: Sep 2, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_HTTPRESPONSE_H_ +#define COMPONENTS_CPP_UTILS_HTTPRESPONSE_H_ +#include +#include +#include "HttpRequest.h" + +class HttpResponse { +private: + HttpRequest m_httpRequest; + std::string m_responseMessage; + std::string m_responseBody; + std::map m_responseHeaders; +public: + HttpResponse(HttpRequest httpRequest); + virtual ~HttpResponse(); + + + void addHeader(std::string name, std::string value); + //std::string getRootPath(); + + void setHeaders(std::map headers); + void setStatus(int status); + void sendData(std::string data); + void sendData(uint8_t *pData, size_t length); + //void setRootPath(std::string path); +}; + +#endif /* COMPONENTS_CPP_UTILS_HTTPRESPONSE_H_ */ diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp new file mode 100644 index 00000000..d127a2fb --- /dev/null +++ b/cpp_utils/HttpServer.cpp @@ -0,0 +1,153 @@ +/* + * HttpServer.cpp + * + * Created on: Aug 30, 2017 + * Author: kolban + */ + +#include "HttpServer.h" +#include "SockServ.h" +#include "Task.h" +#include +#include "HttpRequest.h" +#include "WebSocket.h" +static const char* LOG_TAG = "HttpServer"; + +HttpServer::HttpServer() { + m_portNumber = 0; +} + +HttpServer::~HttpServer() { + ESP_LOGD(LOG_TAG, "~HttpServer"); +} + +class HttpServerTask: public Task { +public: + HttpServerTask(std::string name): Task(name) {}; +private: + HttpServer* m_pHttpServer; + void processRequest(HttpRequest &request) { + for (auto it = m_pHttpServer->m_pathHandlers.begin(); it != m_pHttpServer->m_pathHandlers.end(); ++it) { + if ((*it).match(request.getMethod(), request.getPath())) { + (*it).invoke(&request, &httpResponse); + ESP_LOGD(LOG_TAG, "Found a match!!"); + return; + } + } + } + void run(void* data) { + m_pHttpServer = (HttpServer*)data; + SockServ sockServ(m_pHttpServer->getPort()); + sockServ.start(); + ESP_LOGD(LOG_TAG, "Listening on port %d", m_pHttpServer->getPort()); + while(1) { + ESP_LOGD(LOG_TAG, "Waiting for new client"); + Socket clientSocket = sockServ.waitForNewClient(); + HttpRequest request(clientSocket); + request.dump(); + if (request.isWebsocket()) { + WebSocket *pWebSocket = new WebSocket(request.getSocket()); + } else { + processRequest(request); + /* + request.setStatus(HttpRequest::HTTP_STATUS_OK, "OK"); + request.addResponseHeader(HttpRequest::HTTP_HEADER_CONTENT_TYPE, "text/plain"); + request.setResponseBody("Hello World"); + request.sendResponse(); + request.close_cpp(); + */ + } + ESP_LOGD(LOG_TAG, "Got a new client"); + } // while + } // run +}; // HttpServerTask + +/** + * @brief Register a handler for a path. + * + * When a browser request arrives, the request will contain a method (GET, POST, etc) and a path + * to be accessed. Using this method we can register a regular expression and, if the incoming method + * and path match the expression, the corresponding handler will be called. + * + * Example: + * @code{.cpp} + * static void handle_REST_WiFi(WebServer::HttpRequest *pRequest, WebServer::HttpResponse *pResponse) { + * ... + * } + * + * webServer.addPathHandler("GET", "\\/ESP32\\/WiFi", handle_REST_WiFi); + * @endcode + * + * @param [in] method The method being used for access ("GET", "POST" etc). + * @param [in] pathExpr The path being accessed. + * @param [in] handler The callback function to be invoked when a request arrives. + */ +void HttpServer::addPathHandler( + std::string method, + std::string pathExpr, + void (*handler)(HttpRequest *pHttpRequest, HttpResponse *pHttpResponse)) { + m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); +} // addPathHandler + + +/** + * @brief Get the port number on which the HTTP Server is listening. + * @return The port number on which the HTTP server is listening. + */ +uint16_t HttpServer::getPort() { + return m_portNumber; +} // getPort + + +/** + * @brief Start the HTTP server listening. + * @param [in] portNumber The port number on which the HTTP server should listen. + */ +void HttpServer::start(uint16_t portNumber) { + ESP_LOGD(LOG_TAG, ">> start"); + m_portNumber = portNumber; + HttpServerTask *pHttpServerTask = new HttpServerTask("HttpServerTask"); + pHttpServerTask->start(this); +} + + +/** + * @brief Construct an instance of a PathHandler. + * + * @param [in] method The method to be matched. + * @param [in] pathPattern The path pattern to be matched. + * @param [in] webServerRequestHandler The request handler to be called. + */ +PathHandler::PathHandler(std::string method, std::string pathPattern, + void (*webServerRequestHandler)(HttpRequest *pHttpRequest, HttpResponse *pHttpResponse)) { + m_method = method; + m_pattern = std::regex(pathPattern); + m_requestHandler = webServerRequestHandler; +} // PathHandler + + +/** + * @brief Determine if the path matches. + * + * @param [in] method The method to be matched. + * @param [in] path The path to be matched. + * @return True if the path matches. + */ +bool PathHandler::match(std::string method, std::string path) { + //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); + if (method != m_method) { + return false; + } + return std::regex_search(path, m_pattern); +} // match + + +/** + * @brief Invoke the handler. + * @param [in] request An object representing the request. + * @param [in] response An object representing the response. + * @return N/A. + */ +void PathHandler::invoke(HttpRequest* request, HttpResponse *response) { + m_requestHandler(request, response); +} // invoke diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h new file mode 100644 index 00000000..98988fe5 --- /dev/null +++ b/cpp_utils/HttpServer.h @@ -0,0 +1,52 @@ +/* + * HttpServer.h + * + * Created on: Aug 30, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_HTTPSERVER_H_ +#define COMPONENTS_CPP_UTILS_HTTPSERVER_H_ +#include +#include +#include +#include "SockServ.h" +#include "HttpRequest.h" +class HttpResponse; +class HttpServerTask; + +class PathHandler { + public: + PathHandler( + std::string method, + std::string pathPattern, + void (*webServerRequestHandler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse)); + bool match(std::string method, std::string path); + void invoke(HttpRequest* request, HttpResponse* response); + private: + std::string m_method; + std::regex m_pattern; + void (*m_requestHandler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse); +}; // PathHandler + + + + +class HttpServer { +public: + HttpServer(); + virtual ~HttpServer(); + void addPathHandler(std::string method, + std::string pathExpr, + void (*webServerRequestHandler)( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) ); + uint16_t getPort(); + void start(uint16_t portNumber); +private: + friend class HttpServerTask; + uint16_t m_portNumber; + std::vector m_pathHandlers; +}; // HttpServer + +#endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ diff --git a/cpp_utils/SPI.cpp b/cpp_utils/SPI.cpp index 3b644002..e20131f3 100644 --- a/cpp_utils/SPI.cpp +++ b/cpp_utils/SPI.cpp @@ -114,8 +114,8 @@ void SPI::transfer(uint8_t* data, size_t dataLen) { } #endif spi_transaction_t trans_desc; - trans_desc.address = 0; - trans_desc.command = 0; + //trans_desc.address = 0; + //trans_desc.command = 0; trans_desc.flags = 0; trans_desc.length = dataLen * 8; trans_desc.rxlength = 0; diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 60cd4ef7..8c6cccb8 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -8,7 +8,8 @@ #include #include #include -#include +#include +#include #include #include @@ -16,6 +17,7 @@ #include "sdkconfig.h" #include "SockServ.h" +#include "Socket.h" static const char* LOG_TAG = "SockServ"; @@ -26,68 +28,56 @@ static const char* LOG_TAG = "SockServ"; * We won't actually start listening for clients until after the start() method has been called. * @param [in] port The TCP/IP port number on which we will listen for incoming connection requests. */ -SockServ::SockServ(uint16_t port) { +SockServ::SockServ(uint16_t port) : SockServ() { this->m_port = port; - m_clientSock = -1; - m_sock = -1; - m_clientSemaphore.take("SockServ"); + } // SockServ +SockServ::SockServ() { + m_port = 0; + m_clientSemaphore.take("SockServ"); + m_acceptQueue = xQueueCreate(10, sizeof(Socket)); +} // SockServ /** * @brief Accept an incoming connection. + * @private * * Block waiting for an incoming connection and accept it when it arrives. */ void SockServ::acceptTask(void *data) { SockServ* pSockServ = (SockServ*)data; - struct sockaddr_in clientAddress; - while(1) { - socklen_t clientAddressLength = sizeof(clientAddress); - int tempSock = ::accept(pSockServ->m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); - if (tempSock == -1) { - ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); + Socket tempSock = pSockServ->m_serverSocket.accept_cpp(); + if (!tempSock.isValid()) { + continue; } - ESP_LOGD(LOG_TAG, "accept() - New socket"); - if (pSockServ->m_clientSock != -1) { - int rc = ::close(pSockServ->m_clientSock); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); - } - } - pSockServ->m_clientSock = tempSock; + + pSockServ->m_clientSet.insert(tempSock); + xQueueSendToBack(pSockServ->m_acceptQueue, &tempSock, portMAX_DELAY); pSockServ->m_clientSemaphore.give(); } } // acceptTask + /** * @brief Determine the number of connected partners. * * @return The number of connected partners. */ int SockServ::connectedCount() { - if (m_clientSock == -1) { - return 0; - } - return 1; + return m_clientSet.size(); } // connectedCount /** * @brief Disconnect any connected partners. */ -void SockServ::disconnect() { - if (m_clientSock != -1) { - int rc = ::close(m_clientSock); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); - } - m_clientSock = -1; - m_clientSemaphore.take("disconnect"); - } +void SockServ::disconnect(Socket s) { + auto search = m_clientSet.find(s); + m_clientSet.erase(search); } // disconnect @@ -97,11 +87,8 @@ void SockServ::disconnect() { * @param [in] maxData Maximum size of the data to receive. * @return The amount of data returned or 0 if there was an error. */ -size_t SockServ::receiveData(void* pData, size_t maxData) { - if (m_clientSock == -1) { - return 0; - } - int rc = ::recv(m_clientSock, pData, maxData, 0); +size_t SockServ::receiveData(Socket s, void* pData, size_t maxData) { + int rc = s.receive_cpp((uint8_t*)pData, maxData); if (rc == -1) { ESP_LOGE(LOG_TAG, "recv(): %s", strerror(errno)); return 0; @@ -127,38 +114,28 @@ void SockServ::sendData(std::string str) { * @param[in] length The length of the sequence of bytes to send to the partner. */ void SockServ::sendData(uint8_t* data, size_t length) { - if (connectedCount() == 0) { - return; - } - int rc = ::send(m_clientSock, data, length, 0); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "send(): %s", strerror(errno)); - } + for (auto it = m_clientSet.begin(); it != m_clientSet.end(); ++it) { + (*it).send_cpp(data, length); + } } // sendData +/** + * @brief Set the port number to use. + * @param port The port number to use. + */ +void SockServ::setPort(uint16_t port) { + m_port = port; +} // setPort + /** * @brief Start listening for new partner connections. * * The port number on which we will listen is the one defined when the class was created. */ void SockServ::start() { - m_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (m_sock == -1) { - ESP_LOGE(LOG_TAG, "socket(): %s", strerror(errno)); - } - struct sockaddr_in serverAddress; - serverAddress.sin_family = AF_INET; - serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); - serverAddress.sin_port = htons(m_port); - int rc = ::bind(m_sock, (const struct sockaddr *)&serverAddress, sizeof(serverAddress)); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "bind(): %s", strerror(errno)); - } - rc = ::listen(m_sock, 5); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "listen(): %s", strerror(errno)); - } + assert(m_port != 0); + m_serverSocket.listen_cpp(m_port); ESP_LOGD(LOG_TAG, "Now listening on port %d", m_port); FreeRTOS::startTask(acceptTask, "acceptTask", this); } // start @@ -168,11 +145,41 @@ void SockServ::start() { * @brief Stop listening for new partner connections. */ void SockServ::stop() { - int rc = ::close(m_sock); +} // stop + + +Socket SockServ::waitForData(std::set& socketSet) { + fd_set readSet; + int maxFd = -1; + + for ( auto it = socketSet.begin(); it != socketSet.end(); ++it) { + FD_SET(it->getFD(), &readSet); + if (it->getFD() > maxFd) { + maxFd = it->getFD(); + } + } // End for + + int rc = ::select( + maxFd+1, // Number of sockets to scan + &readSet, // Set of read sockets + nullptr, // Set of write sockets + nullptr, // Set of exception sockets + nullptr // Timeout + ); if (rc == -1) { - ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "Error with select"); + Socket s; + return s; } -} // stop + + for ( auto it = socketSet.begin(); it != socketSet.end(); ++it) { + if (FD_ISSET(it->getFD(), &readSet)) { + return *it; + } + } // End for + Socket s; + return s; +} /** @@ -180,6 +187,12 @@ void SockServ::stop() { * Returns when a client connection is present. This can block until a client connects * or can return immediately is there is already a client connection in existence. */ -void SockServ::waitForClient() { +Socket SockServ::waitForNewClient() { + m_clientSemaphore.wait("waitForClient"); + Socket tempSocket; + xQueueReceive(m_acceptQueue, &tempSocket,portMAX_DELAY); + return tempSocket; } // waitForClient + + diff --git a/cpp_utils/SockServ.h b/cpp_utils/SockServ.h index 8de7bd28..48524a14 100644 --- a/cpp_utils/SockServ.h +++ b/cpp_utils/SockServ.h @@ -4,7 +4,11 @@ #define MAIN_SOCKSERV_H_ #include #include +#include +#include "Socket.h" #include "FreeRTOS.h" +#include +#include /** @@ -30,19 +34,23 @@ class SockServ { private: static void acceptTask(void*); uint16_t m_port; - int m_sock; - int m_clientSock; + Socket m_serverSocket; FreeRTOS::Semaphore m_clientSemaphore; + std::set m_clientSet; + QueueHandle_t m_acceptQueue; public: SockServ(uint16_t port); + SockServ(); int connectedCount(); - void disconnect(); - size_t receiveData(void* pData, size_t maxData); - void sendData(uint8_t *data, size_t length); + void disconnect(Socket s); + size_t receiveData(Socket s, void* pData, size_t maxData); + void sendData(uint8_t* data, size_t length); void sendData(std::string str); + void setPort(uint16_t port); void start(); void stop(); - void waitForClient(); + Socket waitForData(std::set& socketSet); + Socket waitForNewClient(); }; #endif /* MAIN_SOCKSERV_H_ */ diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 115ce599..ea8c7b20 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -5,27 +5,47 @@ * Author: kolban */ +#include + #include #include #include #include #include -#include +#include #include "sdkconfig.h" #include "Socket.h" -static char tag[] = "Socket"; +static const char* LOG_TAG = "Socket"; Socket::Socket() { m_sock = -1; } Socket::~Socket() { - close_cpp(); // When the class instance has ended, delete the socket. + //close_cpp(); // When the class instance has ended, delete the socket. } + +Socket Socket::accept_cpp() { + struct sockaddr addr; + getBind_cpp(&addr); + ESP_LOGD(LOG_TAG, "Accepting on %s", addressToString(&addr).c_str()); + struct sockaddr_in clientAddress; + socklen_t clientAddressLength = sizeof(clientAddress); + int clientSockFD = ::accept(m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); + if (clientSockFD == -1) { + ESP_LOGE(LOG_TAG, "accept(): %s, m_sock=%d", strerror(errno), m_sock); + Socket newSocket; + return newSocket; + } + Socket newSocket; + newSocket.m_sock = clientSockFD; + return newSocket; +} + /** * @brief Convert a socket address to a string representation. * @param [in] addr The address to parse. @@ -50,8 +70,9 @@ std::string Socket::addressToString(struct sockaddr* addr) { * @return N/A */ void Socket::bind_cpp(uint16_t port, uint32_t address) { + ESP_LOGD(LOG_TAG, "bind_cpp: port=%d, address=0x%x", port, address); if (m_sock == -1) { - ESP_LOGE(tag, "bind_cpp: Socket is not initialized."); + ESP_LOGE(LOG_TAG, "bind_cpp: Socket is not initialized."); } struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; @@ -59,7 +80,7 @@ void Socket::bind_cpp(uint16_t port, uint32_t address) { serverAddress.sin_port = htons(port); int rc = ::bind(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); if (rc == -1) { - ESP_LOGE(tag, "bind_cpp: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); + ESP_LOGE(LOG_TAG, "bind_cpp: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); return; } } // bind_cpp @@ -71,6 +92,7 @@ void Socket::bind_cpp(uint16_t port, uint32_t address) { * @return N/A. */ void Socket::close_cpp() { + ESP_LOGD(LOG_TAG, "close_cpp: m_sock=%d", m_sock); if (m_sock != -1) { ::close(m_sock); } @@ -92,15 +114,15 @@ int Socket::connect_cpp(struct in_addr address, uint16_t port) { serverAddress.sin_port = htons(port); char msg[50]; inet_ntop(AF_INET, &address, msg, sizeof(msg)); - ESP_LOGD(tag, "Connecting to %s:[%d]", msg, port); + ESP_LOGD(LOG_TAG, "Connecting to %s:[%d]", msg, port); createSocket_cpp(); int rc = ::connect(m_sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); if (rc == -1) { - ESP_LOGE(tag, "connect_cpp: Error: %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "connect_cpp: Error: %s", strerror(errno)); close_cpp(); return -1; } else { - ESP_LOGD(tag, "Connected to partner"); + ESP_LOGD(LOG_TAG, "Connected to partner"); return 0; } } // connect_cpp @@ -133,7 +155,7 @@ int Socket::createSocket_cpp(bool isDatagram) { m_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } if (m_sock == -1) { - ESP_LOGE(tag, "createSocket_cpp: socket: %d", errno); + ESP_LOGE(LOG_TAG, "createSocket_cpp: socket: %d", errno); return m_sock; } return m_sock; @@ -145,15 +167,28 @@ int Socket::createSocket_cpp(bool isDatagram) { * @param [out] pAddr The storage to hold the address. * @return N/A. */ -void Socket::getBind_cpp(struct sockaddr *pAddr) { +void Socket::getBind_cpp(struct sockaddr* pAddr) { if (m_sock == -1) { - ESP_LOGE(tag, "getBind_cpp: Socket is not initialized."); + ESP_LOGE(LOG_TAG, "getBind_cpp: Socket is not initialized."); } socklen_t nameLen = sizeof(struct sockaddr); ::getsockname(m_sock, pAddr, &nameLen); } // getBind_cpp +/** + * @brief Get the underlying socket file descriptor. + * @return The underlying socket file descriptor. + */ +int Socket::getFD() const { + return m_sock; +} // getFD + + +bool Socket::isValid() { + return m_sock != -1; +} // isValid + /** * @brief Create a listening socket. * @param [in] port The port number to listen upon. @@ -161,9 +196,49 @@ void Socket::getBind_cpp(struct sockaddr *pAddr) { */ void Socket::listen_cpp(uint16_t port, bool isDatagram) { createSocket_cpp(isDatagram); - bind_cpp(port, INADDR_ANY); + bind_cpp(port, 0); + int rc = ::listen(m_sock, 5); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "listen_cpp: %s", strerror(errno)); + } } // listen_cpp +bool Socket::operator <(const Socket& other) const { + return m_sock < other.m_sock; +} + + +std::string Socket::readToDelim(std::string delim) { + std::string ret; + std::string part; + auto it = delim.begin(); + while(1) { + uint8_t val; + int rc = receive_cpp(&val, 1); + if (rc == -1) { + return ""; + } + if (rc == 0) { + return ret+part; + } + if (*it == val) { + part+= val; + ++it; + if (it == delim.end()) { + return ret; + } + } else { + if (part.empty()) { + ret += part; + part.clear(); + it = delim.begin(); + } + ret += val; + } + } // While +} // readToDelim + + /** * @brief Receive data from the partner. @@ -175,7 +250,7 @@ void Socket::listen_cpp(uint16_t port, bool isDatagram) { int Socket::receive_cpp(uint8_t* data, size_t length) { int rc = ::recv(m_sock, data, length, 0); if (rc == -1) { - ESP_LOGE(tag, "receive_cpp: %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); } return rc; } // receive_cpp @@ -203,10 +278,10 @@ int Socket::receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr *pAddr * @return N/A. * */ -void Socket::send_cpp(const uint8_t* data, size_t length) { +void Socket::send_cpp(const uint8_t* data, size_t length) const { int rc = ::send(m_sock, data, length, 0); if (rc == -1) { - ESP_LOGE(tag, "send: socket=%d, %s", m_sock, strerror(errno)); + ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); } } // send_cpp @@ -217,7 +292,7 @@ void Socket::send_cpp(const uint8_t* data, size_t length) { * @param [in] value The string to send to the partner. * @return N/A. */ -void Socket::send_cpp(std::string value) { +void Socket::send_cpp(std::string value) const { send_cpp((uint8_t *)value.data(), value.size()); } // send_cpp @@ -231,6 +306,16 @@ void Socket::send_cpp(std::string value) { void Socket::sendTo_cpp(const uint8_t* data, size_t length, struct sockaddr* pAddr) { int rc = ::sendto(m_sock, data, length, 0, pAddr, sizeof(struct sockaddr)); if (rc == -1) { - ESP_LOGE(tag, "sendto_cpp: socket=%d %s", m_sock, strerror(errno)); + ESP_LOGE(LOG_TAG, "sendto_cpp: socket=%d %s", m_sock, strerror(errno)); } } // sendTo_cpp + +/** + * @brief Get the string representation of this socket + * @return the string representation of the socket. + */ +std::string Socket::toString() { + std::ostringstream oss; + oss << "fd: " << m_sock; + return oss.str(); +} // toString diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 5885ba51..5c3fcca1 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -23,19 +23,27 @@ class Socket { Socket(); virtual ~Socket(); - void send_cpp(std::string value); - static std::string addressToString(struct sockaddr *addr); + Socket accept_cpp(); + static std::string addressToString(struct sockaddr* addr); void bind_cpp(uint16_t port, uint32_t address); void close_cpp(); - int connect_cpp(struct in_addr address, uint16_t port); - int connect_cpp(char *address, uint16_t port); - int createSocket_cpp(bool isDatagram = false); - void getBind_cpp(struct sockaddr *pAddr); - void listen_cpp(uint16_t port, bool isDatagram); - int receive_cpp(uint8_t *data, size_t length); - int receiveFrom_cpp(uint8_t *data, size_t length, struct sockaddr *pAddr); - void send_cpp(const uint8_t *data, size_t length); - void sendTo_cpp(const uint8_t *data, size_t length, struct sockaddr *pAddr); + int connect_cpp(struct in_addr address, uint16_t port); + int connect_cpp(char* address, uint16_t port); + int createSocket_cpp(bool isDatagram = false); + void getBind_cpp(struct sockaddr* pAddr); + int getFD() const; + bool isValid(); + void listen_cpp(uint16_t port, bool isDatagram=false); + bool operator<(const Socket& other) const; + std::string readToDelim(std::string delim); + int receive_cpp(uint8_t* data, size_t length); + int receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr* pAddr); + void send_cpp(std::string value) const; + void send_cpp(const uint8_t* data, size_t length) const; + void sendTo_cpp(const uint8_t* data, size_t length, struct sockaddr* pAddr); + std::string toString(); + + private: int m_sock; }; diff --git a/cpp_utils/Task.cpp b/cpp_utils/Task.cpp index dba7a760..fe6ba2d0 100644 --- a/cpp_utils/Task.cpp +++ b/cpp_utils/Task.cpp @@ -25,6 +25,7 @@ static char tag[] = "Task"; * @return N/A. */ Task::Task(std::string taskName, uint16_t stackSize) { + m_taskName = taskName; m_stackSize = stackSize; m_taskData = nullptr; m_handle = nullptr; @@ -51,10 +52,11 @@ void Task::delay(int ms) { * @param [in] pTaskInstance The task to run. */ void Task::runTask(void* pTaskInstance) { - ESP_LOGD(tag, ">> runTask"); Task* pTask = (Task*)pTaskInstance; + ESP_LOGD(tag, ">> runTask: taskName=%s", pTask->m_taskName.c_str()); pTask->run(pTask->m_taskData); pTask->stop(); + ESP_LOGD(tag, "<< runTask: taskName=%s", pTask->m_taskName.c_str()); } // runTask /** diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index f4d50f79..9a2696e7 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -526,7 +526,7 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m * Iterate through each of the path handlers looking for a match with the method and specified path. */ std::vector::iterator it; - for (it = m_pathHandlers.begin(); it < m_pathHandlers.end(); it++) { + for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { if ((*it).match(mgStrToString(message->method), uri)) { HTTPRequest httpRequest(message); (*it).invoke(&httpRequest, &httpResponse); diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp new file mode 100644 index 00000000..3cd09133 --- /dev/null +++ b/cpp_utils/WebSocket.cpp @@ -0,0 +1,161 @@ +/* + * WebSocket.cpp + * + * Created on: Sep 2, 2017 + * Author: kolban + */ + +#include +#include "WebSocket.h" +#include "Task.h" +#include "GeneralUtils.h" +#include + +static const char* LOG_TAG = "WebSocket"; +static const int OPCODE_CONTINUE = 0x0; +static const int OPCODE_TEXT = 0x1; +static const int OPCODE_BINARY = 0x02; +static const int OPCODE_CLOSE = 0x08; +static const int OPCODE_PING = 0x09; +static const int OPCODE_PONG = 0x0a; +struct Frame { + unsigned int opCode : 4; // [7:4] + unsigned int rsv3 : 1; // [3] + unsigned int rsv2 : 1; // [2] + unsigned int rsv1 : 1; // [1] + unsigned int fin : 1; // [0] + + unsigned int len : 7; // [7:1] + unsigned int mask : 1; // [0] +}; + +/* + * struct Frame { + unsigned int fin : 1; + unsigned int rsv1 : 1; + unsigned int rsv2 : 1; + unsigned int rsv3 : 1; + unsigned int opCode : 4; + unsigned int mask : 1; + unsigned int len : 7; +}; + */ + +static void dumpFrame(Frame frame) { + std::ostringstream oss; + oss << "Fin: " << frame.fin << ", OpCode: " << frame.opCode; + switch(frame.opCode) { + case OPCODE_BINARY: { + oss << " BINARY"; + break; + } + case OPCODE_CONTINUE: { + oss << " CONTINUE"; + break; + } + case OPCODE_CLOSE: { + oss << " CLOSE"; + break; + } + case OPCODE_PING: { + oss << " PING"; + break; + } + case OPCODE_PONG: { + oss << " PONG"; + break; + } + case OPCODE_TEXT: { + oss << " TEXT"; + break; + } + default: { + oss << " Unknown"; + break; + } + } + oss << ", Mask: " << frame.mask << ", len: " << frame.len; + ESP_LOGD(LOG_TAG, "WebSocket frame: %s", oss.str().c_str()); +} + +class WebSocketReader: public Task { + void run(void* data) { + uint8_t buffer[1000]; + WebSocket *pWebSocket = (WebSocket*) data; + // do something + Socket peerSocket = pWebSocket->getSocket(); + + ESP_LOGD(LOG_TAG, "Waiting on socket data for socket %s", peerSocket.toString().c_str()); + int length = peerSocket.receive_cpp(buffer, sizeof(buffer)); + ESP_LOGD(LOG_TAG, "Received data from web socket. Length: %d", length); + GeneralUtils::hexDump(buffer, length); + dumpFrame(*(Frame *)buffer); + if (length > 0) { + Frame* pFrame = (Frame*)buffer; + uint32_t payloadLen = 0; + uint8_t* pMask = nullptr; + uint8_t* pData; + if (pFrame->len < 126) { + payloadLen = pFrame->len; + pMask = buffer + 2; + } else if (pFrame->len == 126) { + payloadLen = *(uint16_t*)(buffer+2); + pMask = buffer + 4; + } else if (pFrame->len == 127) { + ESP_LOGE(LOG_TAG, "Too much data!"); + return; + } + if (pFrame->mask == 1) { + pData = pMask + 4; + for (int i=0; istart(this); +} // WebSocket + + +WebSocket::~WebSocket() { +} + + +void WebSocket::close_cpp() { +} // close_cpp + + +Socket WebSocket::getSocket() { + return m_socket; +} // getSocket + + +void WebSocket::send_cpp(std::string data) { +} // send_cpp + + +void WebSocket::setHandler(WebSocketHandler handler) { + m_webSocketHandler = handler; +} // setHandler diff --git a/cpp_utils/WebSocket.h b/cpp_utils/WebSocket.h new file mode 100644 index 00000000..78cc3954 --- /dev/null +++ b/cpp_utils/WebSocket.h @@ -0,0 +1,32 @@ +/* + * WebSocket.h + * + * Created on: Sep 2, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_WEBSOCKET_H_ +#define COMPONENTS_WEBSOCKET_H_ +#include +#include "Socket.h" + +class WebSocketHandler { +public: + virtual void onData(std::string data); + virtual void onError(std::string error); +}; + +class WebSocket { +private: + Socket m_socket; + WebSocketHandler m_webSocketHandler; +public: + WebSocket(Socket socket); + virtual ~WebSocket(); + void close_cpp(); + Socket getSocket(); + void send_cpp(std::string data); + void setHandler(WebSocketHandler handler); +}; + +#endif /* COMPONENTS_WEBSOCKET_H_ */ diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 2f545d86..a207f956 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -10,7 +10,6 @@ #include #include #include "sdkconfig.h" -#if defined(CONFIG_WIFI_ENABLED) #include "WiFi.h" @@ -433,6 +432,3 @@ void MDNS::setHostname(std::string hostname) { void MDNS::setInstance(std::string instance) { ESP_ERROR_CHECK(mdns_set_instance(m_mdns_server, instance.c_str())); } // setInstance - - -#endif // CONFIG_WIFI_ENABLED diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 9804e492..979c04f7 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -8,7 +8,7 @@ #ifndef MAIN_WIFI_H_ #define MAIN_WIFI_H_ #include "sdkconfig.h" -#if defined(CONFIG_WIFI_ENABLED) + #include #include #include @@ -134,5 +134,4 @@ class WiFi { }; -#endif // CONFIG_WIFI_ENABLED #endif /* MAIN_WIFI_H_ */ diff --git a/filesystems/sendZip/test.js b/filesystems/sendZip/test.js new file mode 100644 index 00000000..3afbb789 --- /dev/null +++ b/filesystems/sendZip/test.js @@ -0,0 +1,24 @@ +// https://www.npmjs.com/package/adm-zip +// https://nodejs.org/api/stream.html +// https://nodejs.org/api/net.html + +var AdmZip = require("adm-zip"); +const net = require("net"); + + +const lenBuf = Buffer.allocUnsafe(4); + + +const client = net.createConnection({host: "192.168.1.99", port: 9876}, ()=>{ + console.log("Connected!"); + var zip = new AdmZip("./foo.zip"); + var zipEntries = zip.getEntries(); + zipEntries.forEach(zipEntry=>{ + console.log("Entry: " + zipEntry.entryName); + lenBuf.writeUInt32LE(zipEntry.entryName.length, 0); + client.write(lenBuf); + client.write(zipEntry.entryName); + }); + + client.end(); +}); \ No newline at end of file From eafc132d70e9421b0dd8bbf02be810cc97a6aab6 Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 3 Sep 2017 23:01:31 -0500 Subject: [PATCH 033/381] Code changes for #46 --- cpp_utils/ArduinoBLE.md | 15 ------------ cpp_utils/BLEClient.cpp | 32 ++++++++++++++++++++++++- cpp_utils/BLEClient.h | 4 ++-- cpp_utils/BLERemoteCharacteristic.cpp | 34 ++++++++++++++++++++++++--- cpp_utils/BLERemoteCharacteristic.h | 9 +++++-- cpp_utils/BLEUtils.cpp | 25 ++++++++++++++++++++ 6 files changed, 96 insertions(+), 23 deletions(-) diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index c37ed58c..a7bda98d 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -19,18 +19,3 @@ Here is the recipe. And here you will find the `ESP32_BLE.zip` that is build from the latest source. You can then install this into your Arduino IDE environment are you are ready to go. - -## Modifying the Arduino environment for BLE -The Arduino environment supplied for the ESP32 is **not** sufficient for full BLE support as supplied. A modification must be made. - -Here are the instructions - -1. Find the directory where your Arduino IDE is installed -2. Navigate relative from there to `/hardware/espressif/esp32/tools/sdk/include/config` -3. Edit the file called `sdkconfig.h` -4. Find the line which reads `#define CONFIG_BTC_TASK_STACK_SIZE 2048` and change to `#define CONFIG_BTC_TASK_STACK_SIZE 8000` - -You are now ready to build (re-build) your BLE based applications. - -A request has been made to he owners of the Arduino on ESP32 project to see if we can't make this the default or otherwise supply an easier story for modification see [arduino-esp32 issue 567](https://github.com/espressif/arduino-esp32/issues/567). - diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 27086e85..a5d60cf3 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -109,6 +109,36 @@ void BLEClient::gattClientEventHandler( // Execute handler code based on the type of event received. switch(event) { + // + // ESP_GATTC_NOTIFY_EVT + // + // notify + // uint16_t conn_id + // esp_bd_addr_t remote_bda + // esp_gatt_srvc_id_t srvc_id + // esp_gatt_id_t char_id + // esp_gatt_id_t descr_id + // uint16_t value_len + // uint8_t* value + // bool is_notify + // + case ESP_GATTC_NOTIFY_EVT: { + BLERemoteService *pBLERemoteService = getService(BLEUUID(evtParam->notify.srvc_id.id.uuid)); + if (pBLERemoteService == nullptr) { + ESP_LOGE(LOG_TAG, "Could not find service with UUID %s for notification", BLEUUID(evtParam->notify.srvc_id.id.uuid).toString().c_str()); + break; + } + BLERemoteCharacteristic* pBLERemoteCharacteristic = pBLERemoteService->getCharacteristic(BLEUUID(evtParam->notify.char_id.uuid)); + if (pBLERemoteCharacteristic == nullptr) { + ESP_LOGE(LOG_TAG, "Could not find characteristic with UUID %s for notification", BLEUUID(evtParam->notify.char_id.uuid).toString().c_str()); + break; + } + if (pBLERemoteCharacteristic->m_notifyCallback != nullptr) { + pBLERemoteCharacteristic->m_notifyCallback(pBLERemoteCharacteristic, evtParam->notify.value, evtParam->notify.value_len, evtParam->notify.is_notify); + } + break; + } // ESP_GATTC_NOTIFY_EVT + // // ESP_GATTC_OPEN_EVT // @@ -212,6 +242,7 @@ BLERemoteService* BLEClient::getService(const char* uuid) { return getService(BLEUUID(uuid)); } + /** * @brief Get the service object corresponding to the uuid. * @param [in] uuid The UUID of the service being sought. @@ -278,7 +309,6 @@ void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks) { } // setClientCallbacks - /** * @brief Return a string representation of this client. * @return A string representation of this client. diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 6a74ea2c..898f98c1 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -55,8 +55,8 @@ class BLEClient { esp_gatt_if_t m_gattc_if; BLEClientCallbacks* m_pClientCallbacks; - FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); - FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); + FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); + FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt"); std::map m_servicesMap; bool m_haveServices; // Have we previously obtain the set of services. diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index c1f826bd..5227bf6c 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -6,6 +6,7 @@ */ #include "BLERemoteCharacteristic.h" + #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) @@ -14,7 +15,6 @@ #include #include - #include "BLEUtils.h" #include "GeneralUtils.h" @@ -28,6 +28,7 @@ BLERemoteCharacteristic::BLERemoteCharacteristic( m_charId = charId; m_charProp = charProp; m_pRemoteService = pRemoteService; + m_notifyCallback = nullptr; } // BLERemoteCharacteristic @@ -154,6 +155,10 @@ void BLERemoteCharacteristic::gattClientEventHandler( }; // gattClientEventHandler +BLEUUID BLERemoteCharacteristic::getUUID() { + return BLEUUID(m_charId.uuid); +} + /** * @brief Read an unsigned 16 bit value * @return The unsigned 16 bit value. @@ -226,11 +231,19 @@ std::string BLERemoteCharacteristic::readValue() { /** * @brief Register for notifications. + * @param [in] notifyCallback A callback to be invoked for a notification. * @return N/A. */ -void BLERemoteCharacteristic::registerForNotify() { +void BLERemoteCharacteristic::registerForNotify( + void (*notifyCallback)( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify)) { ESP_LOGD(LOG_TAG, ">> registerForNotify()"); + m_notifyCallback = notifyCallback; // Save the notification callback. + m_semaphoreRegForNotifyEvt.take("registerForNotify"); esp_err_t errRc = ::esp_ble_gattc_register_for_notify( @@ -266,6 +279,7 @@ std::string BLERemoteCharacteristic::toString() { /** * @brief Write the new value for the characteristic. * @param [in] newValue The new value to write. + * @param [in] response Do we expect a response? * @return N/A. */ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { @@ -297,7 +311,10 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { /** * @brief Write the new value for the characteristic. - * @param [in] newValue The new value to write. + * + * This is a convenience function. Many BLE characteristics are a single byte of data. + * @param [in] newValue The new byte value to write. + * @param [in] response Whether we require a response from the write. * @return N/A. */ void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { @@ -305,4 +322,15 @@ void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { } // writeValue +/** + * @brief Write the new value for the characteristic from a data buffer. + * @param [in] data A pointer to a data buffer. + * @param [in] length The length of the data in the data buffer. + * @param [in] response Whether we require a response from the write. + */ +void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) { + writeValue(std::string((char *)data, length), response); +} // writeValue + + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 02fcf9b8..b5b22b60 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -15,6 +15,7 @@ #include #include "BLERemoteService.h" +#include "BLEUUID.h" #include "FreeRTOS.h" class BLERemoteService; @@ -27,16 +28,19 @@ class BLERemoteCharacteristic { BLERemoteCharacteristic(esp_gatt_id_t charId, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); // Public member functions + BLEUUID getUUID(); std::string readValue(void); uint8_t readUInt8(void); uint16_t readUInt16(void); uint32_t readUInt32(void); - void registerForNotify(void); + void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)); + void writeValue(uint8_t* data, size_t length, bool response = false); void writeValue(std::string newValue, bool response = false); void writeValue(uint8_t newValue, bool response = false); std::string toString(void); private: + friend class BLEClient; friend class BLERemoteService; // Private member functions @@ -52,7 +56,8 @@ class BLERemoteCharacteristic { FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt"); FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt"); FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); - std::string m_value; + std::string m_value; + void (*m_notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify); }; // BLERemoteCharacteristic #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ */ diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index c061658b..468a7e14 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -743,6 +743,31 @@ void BLEUtils::dumpGattClientEvent( break; } // ESP_GATTC_GET_CHAR_EVT + // + // ESP_GATTC_NOTIFY_EVT + // + // notify + // uint16_t conn_id + // esp_bd_addr_t remote_bda + // esp_gatt_srvc_id_t srvc_id + // esp_gatt_id_t char_id + // esp_gatt_id_t descr_id + // uint16_t value_len + // uint8_t* value + // bool is_notify + // + case ESP_GATTC_NOTIFY_EVT: { + ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, srvc_id: <%s>, char_id: <%s>, descr_id: <%s>, value_len: %d, is_notify: %d]", + evtParam->notify.conn_id, + BLEAddress(evtParam->notify.remote_bda).toString().c_str(), + BLEUtils::gattServiceIdToString(evtParam->notify.srvc_id).c_str(), + gattIdToString(evtParam->notify.char_id).c_str(), + gattIdToString(evtParam->notify.descr_id).c_str(), + evtParam->notify.value_len, + evtParam->notify.is_notify + ); + break; + } // // ESP_GATTC_OPEN_EVT From f51aca1ada41efa395e205ae0d7df0cada6dfbcd Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 3 Sep 2017 23:02:11 -0500 Subject: [PATCH 034/381] Code for #45 --- .../Arduino/BLE_client/BLE_client.ino | 116 ++++++++++++++++++ cpp_utils/tests/BLETests/Sample-MLE-15.cpp | 5 +- cpp_utils/tests/BLETests/SampleClient.cpp | 5 + .../tests/BLETests/SampleClient_Notify.cpp | 108 ++++++++++++++++ cpp_utils/tests/BLETests/SampleRead.cpp | 6 + cpp_utils/tests/BLETests/SampleScan.cpp | 3 + cpp_utils/tests/BLETests/SampleServer.cpp | 3 + cpp_utils/tests/BLETests/SampleWrite.cpp | 8 ++ cpp_utils/tests/BLETests/main.cpp | 15 ++- 9 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino create mode 100644 cpp_utils/tests/BLETests/SampleClient_Notify.cpp diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino new file mode 100644 index 00000000..7a15c229 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino @@ -0,0 +1,116 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); +} + +void connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + Serial.println(" - Connected to server"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + Serial.print("Found our device! address: "); + Serial.println(advertisedDevice.getAddress().toString().c_str()); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = true; + + /* + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + */ + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + +void setup() { + Serial.begin(115200); + // put your setup code here, to run once: + Serial.println("Starting ..."); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} + +void loop() { + // put your main code here, to run repeatedly: + if (doConnect == true) { + connectToServer(*pServerAddress); + doConnect = false; + connected = true; + } + if (connected) { + String newValue = "Time since boot: " + String(millis()/1000); + pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + } + delay(1000); +} diff --git a/cpp_utils/tests/BLETests/Sample-MLE-15.cpp b/cpp_utils/tests/BLETests/Sample-MLE-15.cpp index 6db156fe..5e5b2d29 100644 --- a/cpp_utils/tests/BLETests/Sample-MLE-15.cpp +++ b/cpp_utils/tests/BLETests/Sample-MLE-15.cpp @@ -18,6 +18,9 @@ static const char LOG_TAG[] = "Sample-MLE-15"; static BLEUUID serviceUUID((uint16_t)0x1802); static BLEUUID charUUID((uint16_t)0x2a06); +static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { + +} class MyClient: public Task { void run(void *data) { @@ -43,7 +46,7 @@ class MyClient: public Task { pRemoteCharacteristic->readValue(); pRemoteCharacteristic->writeValue("123"); - pRemoteCharacteristic->registerForNotify(); + pRemoteCharacteristic->registerForNotify(notifyCallback); pClient->disconnect(); ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); diff --git a/cpp_utils/tests/BLETests/SampleClient.cpp b/cpp_utils/tests/BLETests/SampleClient.cpp index 76995633..31a65bd6 100644 --- a/cpp_utils/tests/BLETests/SampleClient.cpp +++ b/cpp_utils/tests/BLETests/SampleClient.cpp @@ -1,3 +1,8 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ #include #include #include diff --git a/cpp_utils/tests/BLETests/SampleClient_Notify.cpp b/cpp_utils/tests/BLETests/SampleClient_Notify.cpp new file mode 100644 index 00000000..5ebdbf47 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleClient_Notify.cpp @@ -0,0 +1,108 @@ +/** + * Create a sample BLE client that connects to a BLE server and then waits for server generated + * notifications and logs them when they arrive. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient_Notify"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + ESP_LOGD(LOG_TAG, "Notify callback for characteristic %s of data length %d", + pBLERemoteCharacteristic->getUUID().toString().c_str(), length); +} + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClient_Notify(void) { + ESP_LOGD(LOG_TAG, "SampleClient_Notify starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleRead.cpp b/cpp_utils/tests/BLETests/SampleRead.cpp index ba498f4b..e0307857 100644 --- a/cpp_utils/tests/BLETests/SampleRead.cpp +++ b/cpp_utils/tests/BLETests/SampleRead.cpp @@ -1,3 +1,9 @@ +/** + * Create a BLE Server that when it receive a read request from a BLE client for the value + * of a characteristic will have the BLECharacteristicCallback invoked in its onRead() method. + * This can be then used to set the value of the corresponding characteristic which will then + * be returned back to the client. + */ #include "BLEUtils.h" #include "BLEServer.h" #include diff --git a/cpp_utils/tests/BLETests/SampleScan.cpp b/cpp_utils/tests/BLETests/SampleScan.cpp index 3dc972ed..299bf5b6 100644 --- a/cpp_utils/tests/BLETests/SampleScan.cpp +++ b/cpp_utils/tests/BLETests/SampleScan.cpp @@ -1,3 +1,6 @@ +/** + * Perform scanning for BLE advertised servers. + */ #include "BLEUtils.h" #include "BLEScan.h" #include diff --git a/cpp_utils/tests/BLETests/SampleServer.cpp b/cpp_utils/tests/BLETests/SampleServer.cpp index d497d009..ad44d055 100644 --- a/cpp_utils/tests/BLETests/SampleServer.cpp +++ b/cpp_utils/tests/BLETests/SampleServer.cpp @@ -1,3 +1,6 @@ +/** + * Create a new BLE server. + */ #include "BLEUtils.h" #include "BLEServer.h" #include "BLE2902.h" diff --git a/cpp_utils/tests/BLETests/SampleWrite.cpp b/cpp_utils/tests/BLETests/SampleWrite.cpp index 9132b7af..47616dd7 100644 --- a/cpp_utils/tests/BLETests/SampleWrite.cpp +++ b/cpp_utils/tests/BLETests/SampleWrite.cpp @@ -1,3 +1,9 @@ +/** + * Create a BLE Server such that when a client connects and requests a change to the characteristic + * value, the callback associated with the server will be invoked such that the server can perform + * some action based on the new value. The action in this sample is merely to log the new value to + * the console. + */ #include "BLEUtils.h" #include "BLEServer.h" #include @@ -27,6 +33,7 @@ class MyCallbacks: public BLECharacteristicCallbacks { } }; + static void run() { BLEDevice::init("MYDEVICE"); BLEServer *pServer = new BLEServer(); @@ -48,6 +55,7 @@ static void run() { pAdvertising->start(); } + void SampleWrite(void) { run(); diff --git a/cpp_utils/tests/BLETests/main.cpp b/cpp_utils/tests/BLETests/main.cpp index bd7060e6..1ddedb26 100644 --- a/cpp_utils/tests/BLETests/main.cpp +++ b/cpp_utils/tests/BLETests/main.cpp @@ -1,23 +1,34 @@ +/** + * Main file for running the BLE samples. + */ extern "C" { void app_main(void); } + +// The list of sample entry points. void SampleServer(void); void Sample1(void); void SampleRead(void); void SampleWrite(void); void SampleScan(void); void SampleNotify(void); +void SampleClient_Notify(void); void SampleClient(void); void Sample_MLE_15(void); + +// +// Un-comment ONE of the following +// --- void app_main(void) { //SampleServer(); //Sample1(); //SampleRead(); //SampleWrite(); - SampleScan(); + //SampleScan(); //SampleNotify(); //SampleClient(); + SampleClient_Notify(); //Sample_MLE_15(); -} +} // app_main From 10aa8d152bbbe24459353eb4040a8132029bf9ea Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 3 Sep 2017 23:02:51 -0500 Subject: [PATCH 035/381] Sync 2017-09-03 --- cpp_utils/FATFS_VFS.cpp | 4 +- cpp_utils/HttpParser.cpp | 54 ++++++++-- cpp_utils/HttpParser.h | 3 +- cpp_utils/HttpRequest.cpp | 216 ++++++++++++++++++++++--------------- cpp_utils/HttpRequest.h | 40 +++---- cpp_utils/HttpResponse.cpp | 86 ++++++++++++++- cpp_utils/HttpResponse.h | 33 ++++-- cpp_utils/HttpServer.cpp | 106 ++++++++++++++---- cpp_utils/HttpServer.h | 11 +- cpp_utils/Socket.cpp | 28 ++++- cpp_utils/Socket.h | 2 +- cpp_utils/WebSocket.cpp | 59 ++++++---- cpp_utils/WebSocket.h | 8 +- cpp_utils/component.mk | 3 +- 14 files changed, 461 insertions(+), 192 deletions(-) diff --git a/cpp_utils/FATFS_VFS.cpp b/cpp_utils/FATFS_VFS.cpp index 3b0e5c06..4143899b 100644 --- a/cpp_utils/FATFS_VFS.cpp +++ b/cpp_utils/FATFS_VFS.cpp @@ -7,9 +7,9 @@ #include "FATFS_VFS.h" #include -extern "C" { + #include -} + /** * @brief Constructor. diff --git a/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp index 3f934558..91374318 100644 --- a/cpp_utils/HttpParser.cpp +++ b/cpp_utils/HttpParser.cpp @@ -5,8 +5,12 @@ * Author: kolban */ -#include "HttpParser.h" #include +#include +#include +#include "HttpParser.h" +#include "HttpRequest.h" + #include /** * RFC7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing @@ -70,7 +74,6 @@ static std::string toStringToken(std::string::iterator &it, std::string &str, st } ret += *it; } - } // for return ret; } // toStringToken @@ -96,8 +99,6 @@ static std::string toCharToken(std::string::iterator &it, std::string &str, char } // toCharToken - - /** * @brief Parse a header line. * An HTTP Header is of the form: @@ -139,34 +140,49 @@ void HttpParser::dump() { ESP_LOGD(LOG_TAG, "Body: \"%s\"", m_body.c_str()); } // dump + std::string HttpParser::getBody() { return m_body; } -std::string HttpParser::getHeader(std::string& name) { - if (m_headers.find(name) == m_headers.end()) { + +std::string HttpParser::getHeader(const std::string& name) { + if (!hasHeader(name)) { return ""; } return m_headers.at(name); } + std::map HttpParser::getHeaders() { return m_headers; } + std::string HttpParser::getMethod() { return m_method; } // getMethod + std::string HttpParser::getURL() { return m_url; } // getURL + std::string HttpParser::getVersion() { return m_version; } // getVersion +/** + * @brief Determine if we have a header of the given name. + * @param [in] name The name of the header to find. + * @return True if the header is present and false otherwise. + */ +bool HttpParser::hasHeader(const std::string& name) { + return m_headers.find(name) != m_headers.end(); +} // hasHeader + /** * @brief Parse socket data. @@ -181,6 +197,26 @@ void HttpParser::parse(Socket s) { m_headers.insert(parseHeader(line)); line = s.readToDelim(lineTerminator); } + // Only PUT and POST requests have a body + if (getMethod() != "POST" && getMethod() != "PUT") { + return; + } + + // We have now parsed up to and including the separator ... we are now at the point where we + // want to read the body. There are two stories here. The first is that we know the exact length + // of the body or we read until we can't read anymore. + if (hasHeader(HttpRequest::HTTP_HEADER_CONTENT_LENGTH)) { + std::string val = getHeader(HttpRequest::HTTP_HEADER_CONTENT_LENGTH); + int length = std::atoi(val.c_str()); + uint8_t data[length]; + s.receive_cpp(data, length, true); + m_body = std::string((char *)data, length); + } else { + uint8_t data[512]; + int rc = s.receive_cpp(data, sizeof(data)); + m_body = std::string((char *)data, rc); + } + ESP_LOGD(LOG_TAG, "Size of body: %d", m_body.length()); } // parse @@ -188,6 +224,7 @@ void HttpParser::parse(Socket s) { * @brief Parse a string message. * @param [in] message The HTTP message to parse. */ +/* void HttpParser::parse(std::string message) { auto it = message.begin(); auto line = toStringToken(it, message, lineTerminator); @@ -202,7 +239,7 @@ void HttpParser::parse(std::string message) { m_body = message.substr(std::distance(message.begin(), it)); } // parse - +*/ /** * @brief Parse A request line. @@ -224,6 +261,3 @@ void HttpParser::parseRequestLine(std::string &line) { // Get the version m_version = toCharToken(it, line, ' '); } // parseRequestLine - - - diff --git a/cpp_utils/HttpParser.h b/cpp_utils/HttpParser.h index 5147b2ab..77191163 100644 --- a/cpp_utils/HttpParser.h +++ b/cpp_utils/HttpParser.h @@ -24,11 +24,12 @@ class HttpParser { HttpParser(); virtual ~HttpParser(); std::string getBody(); - std::string getHeader(std::string& name); + std::string getHeader(const std::string& name); std::map getHeaders(); std::string getMethod(); std::string getURL(); std::string getVersion(); + bool hasHeader(const std::string& name); void parse(std::string message); void parse(Socket s); }; diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index f1bff4a1..226333d7 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -32,6 +32,8 @@ */ #include +#include +#include "HttpResponse.h" #include "HttpRequest.h" #include "GeneralUtils.h" @@ -41,7 +43,7 @@ static const char* LOG_TAG="HttpRequest"; -static std::string lineTerminator = "\r\n"; +//static std::string lineTerminator = "\r\n"; const std::string HttpRequest::HTTP_HEADER_ACCEPT = "Accept"; const std::string HttpRequest::HTTP_HEADER_ALLOW = "Allow"; @@ -69,18 +71,7 @@ const std::string HttpRequest::HTTP_METHOD_POST = "POST"; const std::string HttpRequest::HTTP_METHOD_PUT = "PUT"; -const int HttpRequest::HTTP_STATUS_CONTINUE = 100; -const int HttpRequest::HTTP_STATUS_SWITCHING_PROTOCOL = 101; -const int HttpRequest::HTTP_STATUS_OK = 200; -const int HttpRequest::HTTP_STATUS_MOVED_PERMANENTLY = 301; -const int HttpRequest::HTTP_STATUS_BAD_REQUEST = 400; -const int HttpRequest::HTTP_STATUS_UNAUTHORIZED = 401; -const int HttpRequest::HTTP_STATUS_FORBIDDEN = 403; -const int HttpRequest::HTTP_STATUS_NOT_FOUND = 404; -const int HttpRequest::HTTP_STATUS_METHOD_NOT_ALLOWED = 405; -const int HttpRequest::HTTP_STATUS_INTERNAL_SERVER_ERROR = 500; -const int HttpRequest::HTTP_STATUS_NOT_IMPLEMENTED = 501; -const int HttpRequest::HTTP_STATUS_SERVICE_UNAVAILABLE = 503; + std::string buildResponseHash(std::string requestKey) { std::string newKey = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -96,29 +87,31 @@ std::string buildResponseHash(std::string requestKey) { HttpRequest::HttpRequest(Socket clientSocket) { m_clientSocket = clientSocket; m_status = 0; - m_isWebsocket = false; + m_webSocket = nullptr; - m_parser.parse(clientSocket); + m_parser.parse(clientSocket); // Parse the socket stream to build the HTTP data. // Is this a Web Socket? if (getMethod() == HTTP_METHOD_GET && - !getRequestHeader(HTTP_HEADER_HOST).empty() && - getRequestHeader(HTTP_HEADER_UPGRADE) == "websocket" && - getRequestHeader(HTTP_HEADER_CONNECTION) == "Upgrade" && - !getRequestHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY).empty() && - !getRequestHeader(HTTP_HEADER_SEC_WEBSOCKET_VERSION).empty()) { + !getHeader(HTTP_HEADER_HOST).empty() && + getHeader(HTTP_HEADER_UPGRADE) == "websocket" && + getHeader(HTTP_HEADER_CONNECTION) == "Upgrade" && + !getHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY).empty() && + !getHeader(HTTP_HEADER_SEC_WEBSOCKET_VERSION).empty()) { ESP_LOGD(LOG_TAG, "Websocket detected!"); - m_isWebsocket = true; // do something // Process the web socket request - setStatus(HTTP_STATUS_SWITCHING_PROTOCOL, "Switching Protocols"); - addResponseHeader(HTTP_HEADER_UPGRADE, "websocket"); - addResponseHeader(HTTP_HEADER_CONNECTION, "Upgrade"); - addResponseHeader(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, - buildResponseHash(getRequestHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY))); - sendResponse(); + HttpResponse response(this); + + response.setStatus(HttpResponse::HTTP_STATUS_SWITCHING_PROTOCOL, "Switching Protocols"); + response.addHeader(HTTP_HEADER_UPGRADE, "websocket"); + response.addHeader(HTTP_HEADER_CONNECTION, "Upgrade"); + response.addHeader(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, + buildResponseHash(getHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY))); + response.sendData(""); + m_webSocket = new WebSocket(clientSocket); } else { ESP_LOGD(LOG_TAG, "Not a Websocket"); } @@ -129,11 +122,6 @@ HttpRequest::~HttpRequest() { } // ~HttpRequest -void HttpRequest::addResponseHeader(const std::string name, const std::string value) { - m_responseHeaders.insert(std::pair(name, value)); -} // addResponseHeader - - void HttpRequest::close_cpp() { m_clientSocket.close_cpp(); } // close_cpp @@ -141,90 +129,144 @@ void HttpRequest::close_cpp() { void HttpRequest::dump() { ESP_LOGD(LOG_TAG, "Method: %s, URL: \"%s\", Version: %s", getMethod().c_str(), getPath().c_str(), getVersion().c_str()); - auto headers = getRequestHeaders(); + auto headers = getHeaders(); auto it2 = headers.begin(); for (; it2 != headers.end(); ++it2) { ESP_LOGD(LOG_TAG, "name=\"%s\", value=\"%s\"", it2->first.c_str(), it2->second.c_str()); } - ESP_LOGD(LOG_TAG, "Body: \"%s\"", getRequestBody().c_str()); + ESP_LOGD(LOG_TAG, "Body: \"%s\"", getBody().c_str()); } // dump -std::string HttpRequest::getMethod() { - return m_parser.getMethod(); -} // getMethod - - -std::string HttpRequest::getPath() { - return m_parser.getURL(); -} // getURL - -std::string HttpRequest::getRequestBody() { +std::string HttpRequest::getBody() { return m_parser.getBody(); -} // getRequestBody +} // getBody -std::string HttpRequest::getRequestHeader(std::string name) { +std::string HttpRequest::getHeader(std::string name) { return m_parser.getHeader(name); -} // getRequestHeader +} // getHeader -std::map HttpRequest::getRequestHeaders() { +std::map HttpRequest::getHeaders() { return m_parser.getHeaders(); -} // getRequestHeaders +} // getHeaders -std::string HttpRequest::getResponseHeader(std::string name) { - if (m_responseHeaders.find(name) == m_responseHeaders.end()) { - return ""; - } - return m_responseHeaders.at(name); -} // getResponseHeader - +std::string HttpRequest::getMethod() { + return m_parser.getMethod(); +} // getMethod -std::map HttpRequest::getResponseHeaders() { - return m_responseHeaders; -} // getResponseHeaders +std::string HttpRequest::getPath() { + return m_parser.getURL(); +} // getPath -Socket HttpRequest::getSocket() { - return m_clientSocket; -} +#define STATE_NAME 0 +#define STATE_VALUE 1 +/** + * @brief Get the query part of the request. + * The query is a set of name = value pairs. The return is a map keyed by the name items. + * + * @return The query part of the request. + */ +std::map HttpRequest::getQuery() { + // Walk through all the characters in the query string maintaining a simple state machine + // that lets us know what we are parsing. + std::map queryMap; + std::string queryString = ""; + int i=0; + + /* + * We maintain a simple state machine with states of: + * * STATE_NAME - We are parsing a name. + * * STATE_VALUE - We are parsing a value. + */ + int state = STATE_NAME; + std::string name = ""; + std::string value; + // Loop through each character in the query string. + for (i=0; ifirst.c_str() << ": " << it->second.c_str() << lineTerminator; +/** + * @brief Return the constituent parts of the path. + * If we imagine a path as composed of parts separated by slashes, then this function + * returns a vector composed of the parts. For example: + * + * ``` + * /x/y/z + * ``` + * will break out to: + * + * ``` + * path[0] = "" + * path[1] = "x" + * path[2] = "y" + * path[3] = "z" + * ``` + * + * @return A vector of the constituent parts of the path. + */ +std::vector HttpRequest::pathSplit() { + std::istringstream stream(getPath()); + std::vector ret; + std::string pathPart; + while(std::getline(stream, pathPart, '/')) { + ret.push_back(pathPart); } - oss << lineTerminator; - oss << m_responseBody; - ESP_LOGD(LOG_TAG, ">> sendResponse: %s", oss.str().c_str()); - m_clientSocket.send_cpp(oss.str()); - /*TODO*/ -} // sendResponse - - -void HttpRequest::setResponseBody(const std::string body) { - m_responseBody = body; -} // setResponseBody - - -void HttpRequest::setStatus(const int status, const std::string message) { - m_status = status; - m_responseMessage = message; -} // setStatus + // Debug + for (int i=0; i #include +#include #include "Socket.h" +#include "WebSocket.h" #include "HttpParser.h" @@ -19,10 +21,7 @@ class HttpRequest { Socket m_clientSocket; HttpParser m_parser; int m_status; - bool m_isWebsocket; - std::string m_responseMessage; - std::string m_responseBody; - std::map m_responseHeaders; + WebSocket *m_webSocket; public: HttpRequest(Socket s); @@ -52,36 +51,21 @@ class HttpRequest { static const std::string HTTP_METHOD_POST; static const std::string HTTP_METHOD_PUT; - static const int HTTP_STATUS_CONTINUE; - static const int HTTP_STATUS_SWITCHING_PROTOCOL; - static const int HTTP_STATUS_OK; - static const int HTTP_STATUS_MOVED_PERMANENTLY; - static const int HTTP_STATUS_BAD_REQUEST; - static const int HTTP_STATUS_UNAUTHORIZED; - static const int HTTP_STATUS_FORBIDDEN; - static const int HTTP_STATUS_NOT_FOUND; - static const int HTTP_STATUS_METHOD_NOT_ALLOWED; - static const int HTTP_STATUS_INTERNAL_SERVER_ERROR; - static const int HTTP_STATUS_NOT_IMPLEMENTED; - static const int HTTP_STATUS_SERVICE_UNAVAILABLE; - void addResponseHeader(const std::string name, const std::string value); + void close_cpp(); void dump(); + std::string getBody(); + std::string getHeader(std::string name); + std::map getHeaders(); std::string getMethod(); std::string getPath(); - std::string getRequestBody(); - std::string getRequestHeader(std::string name); - std::map getRequestHeaders(); - std::string getResponseHeader(std::string name); - std::map getResponseHeaders(); - Socket getSocket(); - + std::map getQuery(); + Socket getSocket(); std::string getVersion(); - bool isWebsocket(); - void sendResponse(); - void setResponseBody(const std::string body); - void setStatus(const int status, const std::string message); + WebSocket* getWebSocket(); + bool isWebsocket(); + std::vector pathSplit(); }; #endif /* COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ */ diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp index b471062a..947b3e62 100644 --- a/cpp_utils/HttpResponse.cpp +++ b/cpp_utils/HttpResponse.cpp @@ -4,15 +4,95 @@ * Created on: Sep 2, 2017 * Author: kolban */ - +#include +#include "HttpRequest.h" #include "HttpResponse.h" +#include + +//static const char* LOG_TAG = "HttpResponse"; -HttpResponse::HttpResponse() { - // TODO Auto-generated constructor stub +const int HttpResponse::HTTP_STATUS_CONTINUE = 100; +const int HttpResponse::HTTP_STATUS_SWITCHING_PROTOCOL = 101; +const int HttpResponse::HTTP_STATUS_OK = 200; +const int HttpResponse::HTTP_STATUS_MOVED_PERMANENTLY = 301; +const int HttpResponse::HTTP_STATUS_BAD_REQUEST = 400; +const int HttpResponse::HTTP_STATUS_UNAUTHORIZED = 401; +const int HttpResponse::HTTP_STATUS_FORBIDDEN = 403; +const int HttpResponse::HTTP_STATUS_NOT_FOUND = 404; +const int HttpResponse::HTTP_STATUS_METHOD_NOT_ALLOWED = 405; +const int HttpResponse::HTTP_STATUS_INTERNAL_SERVER_ERROR = 500; +const int HttpResponse::HTTP_STATUS_NOT_IMPLEMENTED = 501; +const int HttpResponse::HTTP_STATUS_SERVICE_UNAVAILABLE = 503; +static std::string lineTerminator = "\r\n"; +HttpResponse::HttpResponse(HttpRequest *request) { + m_request = request; + m_status = 0; + m_headerCommitted = false; // We have not yet sent a header. } HttpResponse::~HttpResponse() { // TODO Auto-generated destructor stub } + +/** + * @brief Add a header to the response message. + * If the response has already been committed then ignore this request. + * @param [in] name The name of the header. + * @param [in] value The value of the header. + */ +void HttpResponse::addHeader(const std::string name, const std::string value) { + if (m_headerCommitted) { + return; + } + m_responseHeaders.insert(std::pair(name, value)); +} // addHeader + + +void HttpResponse::close_cpp() { + m_request->close_cpp(); +} // close_cpp + + +std::string HttpResponse::getHeader(std::string name) { + if (m_responseHeaders.find(name) == m_responseHeaders.end()) { + return ""; + } + return m_responseHeaders.at(name); +} // getHeader + + +std::map HttpResponse::getHeaders() { + return m_responseHeaders; +} // getHeaders + + +/** + * @brief Send data to the partner. + * Send some data to the partner. If we haven't yet sent the HTTP header then send that now. + * @param [in] data The data to send to the partner. + */ +void HttpResponse::sendData(std::string data) { + if (m_headerCommitted == false) { + std::ostringstream oss; + oss << m_request->getVersion() << " " << m_status << " " << m_statusMessage << lineTerminator; + for (auto it = m_responseHeaders.begin(); it != m_responseHeaders.end(); ++it) { + oss << it->first.c_str() << ": " << it->second.c_str() << lineTerminator; + } + oss << lineTerminator; + m_headerCommitted = true; + m_request->getSocket().send_cpp(oss.str()); + } + m_request->getSocket().send_cpp(data); +} // sendData + + +void HttpResponse::setStatus(const int status, const std::string message) { + if (m_headerCommitted) { + return; + } + m_status = status; + m_statusMessage = message; +} // setStatus + diff --git a/cpp_utils/HttpResponse.h b/cpp_utils/HttpResponse.h index 41a67ebe..089fab91 100644 --- a/cpp_utils/HttpResponse.h +++ b/cpp_utils/HttpResponse.h @@ -13,22 +13,37 @@ class HttpResponse { private: - HttpRequest m_httpRequest; - std::string m_responseMessage; - std::string m_responseBody; + HttpRequest* m_request; + std::string m_statusMessage; + int m_status; + bool m_headerCommitted; std::map m_responseHeaders; public: - HttpResponse(HttpRequest httpRequest); - virtual ~HttpResponse(); + static const int HTTP_STATUS_CONTINUE; + static const int HTTP_STATUS_SWITCHING_PROTOCOL; + static const int HTTP_STATUS_OK; + static const int HTTP_STATUS_MOVED_PERMANENTLY; + static const int HTTP_STATUS_BAD_REQUEST; + static const int HTTP_STATUS_UNAUTHORIZED; + static const int HTTP_STATUS_FORBIDDEN; + static const int HTTP_STATUS_NOT_FOUND; + static const int HTTP_STATUS_METHOD_NOT_ALLOWED; + static const int HTTP_STATUS_INTERNAL_SERVER_ERROR; + static const int HTTP_STATUS_NOT_IMPLEMENTED; + static const int HTTP_STATUS_SERVICE_UNAVAILABLE; + HttpResponse(HttpRequest* httpRequest); + virtual ~HttpResponse(); void addHeader(std::string name, std::string value); + void close_cpp(); //std::string getRootPath(); - - void setHeaders(std::map headers); - void setStatus(int status); + std::string getHeader(std::string name); + std::map getHeaders(); void sendData(std::string data); - void sendData(uint8_t *pData, size_t length); + //void sendData(uint8_t *pData, size_t length); + //void setHeaders(std::map headers); + void setStatus(int status, std::string message); //void setRootPath(std::string path); }; diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index d127a2fb..36bf7b1f 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -10,31 +10,70 @@ #include "Task.h" #include #include "HttpRequest.h" +#include "HttpResponse.h" #include "WebSocket.h" static const char* LOG_TAG = "HttpServer"; +/** + * Constructor for HTTP Server + */ HttpServer::HttpServer() { - m_portNumber = 0; + m_portNumber = 80; } HttpServer::~HttpServer() { ESP_LOGD(LOG_TAG, "~HttpServer"); } +/** + * @brief Be an HTTP server task. + */ class HttpServerTask: public Task { public: HttpServerTask(std::string name): Task(name) {}; + private: - HttpServer* m_pHttpServer; + HttpServer* m_pHttpServer; // Reference to the HTTP Server + + /** + * @brief Process an incoming HTTP Request + * We examine each of the path handlers to see if we have a match for the method/path pair. If we do, + * we invoke the handler callback passing in both the request and response. + * + * If we didn't find a handler, then we are going to behave as a Web Server and try and serve up the + * content from the file on the "file system". + * @param [in] request The HTTP request to process. + */ void processRequest(HttpRequest &request) { + ESP_LOGD(LOG_TAG, ">> processRequest: Method: %s, Path: %s", + request.getMethod().c_str(), request.getPath().c_str()) for (auto it = m_pHttpServer->m_pathHandlers.begin(); it != m_pHttpServer->m_pathHandlers.end(); ++it) { - if ((*it).match(request.getMethod(), request.getPath())) { - (*it).invoke(&request, &httpResponse); - ESP_LOGD(LOG_TAG, "Found a match!!"); + if (it->match(request.getMethod(), request.getPath())) { + ESP_LOGD(LOG_TAG, "Found a path handler match!!"); + if (request.isWebsocket()) { + it->invoke(&request, nullptr); + } else { + HttpResponse response(&request); + + it->invoke(&request, &response); + } return; - } - } - } + } // Path handler match + } // For each path handler + + // Serve up the content from the file on the file system ... if found ... + + ESP_LOGD(LOG_TAG, "No Path handler found"); + HttpResponse response(&request); + response.setStatus(HttpResponse::HTTP_STATUS_NOT_FOUND, "Not Found"); + response.sendData(""); + + } // processRequest + + + /** + * Perform the task handling for server. + */ void run(void* data) { m_pHttpServer = (HttpServer*)data; SockServ sockServ(m_pHttpServer->getPort()); @@ -45,23 +84,16 @@ class HttpServerTask: public Task { Socket clientSocket = sockServ.waitForNewClient(); HttpRequest request(clientSocket); request.dump(); - if (request.isWebsocket()) { - WebSocket *pWebSocket = new WebSocket(request.getSocket()); - } else { - processRequest(request); - /* - request.setStatus(HttpRequest::HTTP_STATUS_OK, "OK"); - request.addResponseHeader(HttpRequest::HTTP_HEADER_CONTENT_TYPE, "text/plain"); - request.setResponseBody("Hello World"); - request.sendResponse(); + processRequest(request); + if (!request.isWebsocket()) { request.close_cpp(); - */ } ESP_LOGD(LOG_TAG, "Got a new client"); } // while } // run }; // HttpServerTask + /** * @brief Register a handler for a path. * @@ -98,17 +130,48 @@ uint16_t HttpServer::getPort() { return m_portNumber; } // getPort +/** + * @brief Get the current root path. + * @return The current root path. + */ +std::string HttpServer::getRootPath() { + return m_rootPath; +} // getRootPath + +/** + * @brief Set the root path for URL file mapping. + * + * When a browser requests a file, it uses the address form: + * + * @code{.unparsed} + * http://:/ + * @endcode + * + * The path part can be considered the path to where the file should be retrieved on the + * file system available to the web server. Typically, we want a directory structure on the file + * system to host the web served files and not expose the whole file system. Using this method + * we specify the root directory from which the files will be served. + * + * @param [in] path The root path on the file system. + * @return N/A. + */ +void HttpServer::setRootPath(std::string path) { + m_rootPath = path; +} // setRootPath + /** * @brief Start the HTTP server listening. + * We start an instance of the HTTP server listening. A new task is spawned to perform this work in the + * back ground. * @param [in] portNumber The port number on which the HTTP server should listen. */ void HttpServer::start(uint16_t portNumber) { ESP_LOGD(LOG_TAG, ">> start"); m_portNumber = portNumber; - HttpServerTask *pHttpServerTask = new HttpServerTask("HttpServerTask"); + HttpServerTask* pHttpServerTask = new HttpServerTask("HttpServerTask"); pHttpServerTask->start(this); -} +} // start /** @@ -122,6 +185,7 @@ PathHandler::PathHandler(std::string method, std::string pathPattern, void (*webServerRequestHandler)(HttpRequest *pHttpRequest, HttpResponse *pHttpResponse)) { m_method = method; m_pattern = std::regex(pathPattern); + m_textPattern = pathPattern; m_requestHandler = webServerRequestHandler; } // PathHandler @@ -134,7 +198,7 @@ PathHandler::PathHandler(std::string method, std::string pathPattern, * @return True if the path matches. */ bool PathHandler::match(std::string method, std::string path) { - //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); + ESP_LOGD(LOG_TAG, "match: %s with %s", m_textPattern.c_str(), path.c_str()); if (method != m_method) { return false; } diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index 98988fe5..5a96931e 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -12,7 +12,8 @@ #include #include "SockServ.h" #include "HttpRequest.h" -class HttpResponse; +#include "HttpResponse.h" + class HttpServerTask; class PathHandler { @@ -25,7 +26,8 @@ class PathHandler { void invoke(HttpRequest* request, HttpResponse* response); private: std::string m_method; - std::regex m_pattern; + std::regex m_pattern; + std::string m_textPattern; void (*m_requestHandler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse); }; // PathHandler @@ -42,11 +44,14 @@ class HttpServer { HttpRequest* pHttpRequest, HttpResponse* pHttpResponse) ); uint16_t getPort(); + std::string getRootPath(); + void setRootPath(std::string path); void start(uint16_t portNumber); private: friend class HttpServerTask; - uint16_t m_portNumber; + uint16_t m_portNumber; std::vector m_pathHandlers; + std::string m_rootPath; }; // HttpServer #endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index ea8c7b20..02cacd96 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -14,6 +14,7 @@ #include #include +#include "GeneralUtils.h" #include "sdkconfig.h" #include "Socket.h" @@ -245,14 +246,31 @@ std::string Socket::readToDelim(std::string delim) { * * @param [in] data The buffer into which the received data will be stored. * @param [in] length The size of the buffer. + * @param [in] exact Read exactly this amount? * @return The length of the data received or -1 on an error. */ -int Socket::receive_cpp(uint8_t* data, size_t length) { - int rc = ::recv(m_sock, data, length, 0); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); +int Socket::receive_cpp(uint8_t* data, size_t length, bool exact) { + //ESP_LOGD(LOG_TAG, ">> receive_cpp: length: %d, exact: %d", length, exact); + if (exact == false) { + int rc = ::recv(m_sock, data, length, 0); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); + } + //GeneralUtils::hexDump(data, rc); + return rc; } - return rc; + size_t amountToRead = length; + while(amountToRead > 0) { + int rc = ::recv(m_sock, data, amountToRead, 0); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); + return 0; + } + amountToRead -= rc; + data+= rc; + } + //GeneralUtils::hexDump(data, length); + return length; } // receive_cpp diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 5c3fcca1..7fdb2302 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -36,7 +36,7 @@ class Socket { void listen_cpp(uint16_t port, bool isDatagram=false); bool operator<(const Socket& other) const; std::string readToDelim(std::string delim); - int receive_cpp(uint8_t* data, size_t length); + int receive_cpp(uint8_t* data, size_t length, bool exact=false); int receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr* pAddr); void send_cpp(std::string value) const; void send_cpp(const uint8_t* data, size_t length) const; diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp index 3cd09133..990c2df0 100644 --- a/cpp_utils/WebSocket.cpp +++ b/cpp_utils/WebSocket.cpp @@ -12,12 +12,17 @@ #include static const char* LOG_TAG = "WebSocket"; -static const int OPCODE_CONTINUE = 0x0; -static const int OPCODE_TEXT = 0x1; + +// WebSocket op codes as found in a WebSocket frame. +static const int OPCODE_CONTINUE = 0x00; +static const int OPCODE_TEXT = 0x01; static const int OPCODE_BINARY = 0x02; static const int OPCODE_CLOSE = 0x08; static const int OPCODE_PING = 0x09; static const int OPCODE_PONG = 0x0a; + + +// Structure definition for the WebSocket frame. struct Frame { unsigned int opCode : 4; // [7:4] unsigned int rsv3 : 1; // [3] @@ -29,18 +34,11 @@ struct Frame { unsigned int mask : 1; // [0] }; -/* - * struct Frame { - unsigned int fin : 1; - unsigned int rsv1 : 1; - unsigned int rsv2 : 1; - unsigned int rsv3 : 1; - unsigned int opCode : 4; - unsigned int mask : 1; - unsigned int len : 7; -}; - */ +/** + * @brief Dump the content of the frame for debugging. + * @param [in] frame The frame to dump. + */ static void dumpFrame(Frame frame) { std::ostringstream oss; oss << "Fin: " << frame.fin << ", OpCode: " << frame.opCode; @@ -76,8 +74,12 @@ static void dumpFrame(Frame frame) { } oss << ", Mask: " << frame.mask << ", len: " << frame.len; ESP_LOGD(LOG_TAG, "WebSocket frame: %s", oss.str().c_str()); -} +} // dumpFrame + +/** + * @brief A task that will watch web socket inputs. + */ class WebSocketReader: public Task { void run(void* data) { uint8_t buffer[1000]; @@ -90,6 +92,8 @@ class WebSocketReader: public Task { ESP_LOGD(LOG_TAG, "Received data from web socket. Length: %d", length); GeneralUtils::hexDump(buffer, length); dumpFrame(*(Frame *)buffer); + + // The following section parses the WebSocket frame. if (length > 0) { Frame* pFrame = (Frame*)buffer; uint32_t payloadLen = 0; @@ -121,15 +125,15 @@ class WebSocketReader: public Task { GeneralUtils::hexDump(pData, payloadLen); } } // run -}; +}; // WebSocketReader void WebSocketHandler::onData(std::string data) { -} +} // onData void WebSocketHandler::onError(std::string error) { -} +} // onError WebSocket::WebSocket(Socket socket) { @@ -140,22 +144,41 @@ WebSocket::WebSocket(Socket socket) { WebSocket::~WebSocket() { -} +} // ~WebSocket +/** + * @brief Close the Web socket + */ void WebSocket::close_cpp() { + } // close_cpp +/** + * @brief Get the underlying socket for the websocket. + * @return The socket associated with the Web socket. + */ Socket WebSocket::getSocket() { return m_socket; } // getSocket +/** + * @brief Send data down the web socket + * @param [in] data The data to send down the websocket. + */ void WebSocket::send_cpp(std::string data) { } // send_cpp +/** + * @brief Set the Web socket handler associated with this Websocket. + * + * This will be the user supplied code that will be invoked to process incoming WebSocket + * events. An instance of WebSocketHandler is passed in. + * + */ void WebSocket::setHandler(WebSocketHandler handler) { m_webSocketHandler = handler; } // setHandler diff --git a/cpp_utils/WebSocket.h b/cpp_utils/WebSocket.h index 78cc3954..5230a096 100644 --- a/cpp_utils/WebSocket.h +++ b/cpp_utils/WebSocket.h @@ -18,15 +18,17 @@ class WebSocketHandler { class WebSocket { private: + Socket m_socket; WebSocketHandler m_webSocketHandler; + public: WebSocket(Socket socket); virtual ~WebSocket(); - void close_cpp(); + void close_cpp(); Socket getSocket(); - void send_cpp(std::string data); - void setHandler(WebSocketHandler handler); + void send_cpp(std::string data); + void setHandler(WebSocketHandler handler); }; #endif /* COMPONENTS_WEBSOCKET_H_ */ diff --git a/cpp_utils/component.mk b/cpp_utils/component.mk index 32f27517..6952ed54 100644 --- a/cpp_utils/component.mk +++ b/cpp_utils/component.mk @@ -11,4 +11,5 @@ COMPONENT_ADD_INCLUDEDIRS=. ##CXXFLAGS+=-DESP_HAVE_CURL ## Uncomment the following line to enable exception handling -#CXXFLAGS+=-fexceptions \ No newline at end of file +#CXXFLAGS+=-fexceptions +#CXXFLAGS+= -std=c++11 \ No newline at end of file From 367d7c5130da36e58a91130ee07ff7c1467caa5e Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 4 Sep 2017 17:01:53 -0500 Subject: [PATCH 036/381] Fixes for #48 --- cpp_utils/BLERemoteService.cpp | 16 ++++--- .../Arduino/BLE_client/BLE_client.ino | 42 ++++++++++++------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index dbfe27b5..a29a7589 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -67,6 +67,7 @@ void BLERemoteService::gattClientEventHandler( break; } + // If the status is NOT OK then we have a problem and continue. if (evtParam->get_char.status != ESP_GATT_OK) { m_semaphoreGetCharEvt.give(); break; @@ -78,14 +79,19 @@ void BLERemoteService::gattClientEventHandler( BLEUUID(evtParam->get_char.char_id.uuid).toString(), new BLERemoteCharacteristic(evtParam->get_char.char_id, evtParam->get_char.char_prop, this) )); - /* - ::esp_ble_gattc_get_characteristic( + + // Now that we have received a characteristic, lets ask for the next one. + esp_err_t errRc = ::esp_ble_gattc_get_characteristic( m_pClient->getGattcIf(), m_pClient->getConnId(), &m_srvcId, &evtParam->get_char.char_id); - */ - m_semaphoreGetCharEvt.give(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + break; + } + + //m_semaphoreGetCharEvt.give(); break; } // ESP_GATTC_GET_CHAR_EVT @@ -119,7 +125,7 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) { BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { // Design // ------ -// We wish to retrieve the characteritic given its UUID. It is possible that we have not yet asked the +// We wish to retrieve the characteristic given its UUID. It is possible that we have not yet asked the // device what characteristics it has in which case we have nothing to match against. If we have not // asked the device about its characteristics, then we do that now. Once we get the results we can then // examine the characteristics map to see if it has the characteristic we are looking for. diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino index 7a15c229..3fc5efa2 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino @@ -72,45 +72,57 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { Serial.print("BLE Advertised Device found: "); Serial.println(advertisedDevice.toString().c_str()); + // We have found a device, let us now see if it contains the service we are looking for. if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { - advertisedDevice.getScan()->stop(); - Serial.print("Found our device! address: "); - Serial.println(advertisedDevice.getAddress().toString().c_str()); + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); pServerAddress = new BLEAddress(advertisedDevice.getAddress()); doConnect = true; - /* - MyClient* pMyClient = new MyClient(); - pMyClient->setStackSize(18000); - pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); - */ } // Found our server } // onResult }; // MyAdvertisedDeviceCallbacks + void setup() { Serial.begin(115200); - // put your setup code here, to run once: - Serial.println("Starting ..."); + Serial.println("Starting Arduino BLE Client application..."); BLEDevice::init(""); - BLEScan *pBLEScan = BLEDevice::getScan(); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); pBLEScan->start(30); -} +} // End of setup. + +// This is the Arduino main loop function. void loop() { - // put your main code here, to run repeatedly: + + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. if (doConnect == true) { connectToServer(*pServerAddress); doConnect = false; connected = true; } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. if (connected) { String newValue = "Time since boot: " + String(millis()/1000); + Serial.println("Setting new characteristic value to \"" + newValue + "\""); + + // Set the characteristic's value to be the array of bytes that is actually a string. pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); } - delay(1000); -} + + delay(1000); // Delay a second between loops. +} // End of loop From 49eac9a5ee1865072382d6a99005275d86cd38b0 Mon Sep 17 00:00:00 2001 From: kolban Date: Thu, 7 Sep 2017 17:37:30 -0500 Subject: [PATCH 037/381] Sync commit - 2017-09-07 --- cpp_utils/ArduinoBLE.md | 43 +++ cpp_utils/DesignNotes/README.md | 2 + cpp_utils/DesignNotes/WebSockets.md | 30 ++ cpp_utils/GeneralUtils.cpp | 2 +- cpp_utils/GeneralUtils.h | 2 +- cpp_utils/HttpRequest.cpp | 10 +- cpp_utils/HttpRequest.h | 2 +- cpp_utils/HttpServer.cpp | 4 +- cpp_utils/HttpServer.h | 1 + cpp_utils/Socket.cpp | 61 ++++- cpp_utils/Socket.h | 27 +- cpp_utils/WebSocket.cpp | 258 ++++++++++++++---- cpp_utils/WebSocket.h | 37 ++- .../Arduino/BLE_client/BLE_client.ino | 14 +- 14 files changed, 415 insertions(+), 78 deletions(-) create mode 100644 cpp_utils/DesignNotes/README.md create mode 100644 cpp_utils/DesignNotes/WebSockets.md diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index a7bda98d..9e5169ad 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -19,3 +19,46 @@ Here is the recipe. And here you will find the `ESP32_BLE.zip` that is build from the latest source. You can then install this into your Arduino IDE environment are you are ready to go. + +## Installing a new version +If you have previously installed a version of the Arduino BLE Support and need to install a new one, follow the steps above to build yourself a new instance of the `ESP32_BLE.zip` that is ready for installation. I recommend removing the old one before installing the new one. To remove the old one, find the directory where the Arduino IDE stores your libraries (on Linux this is commonly `$HOME/Arduino`). In there you will find a directory called `libraries` and, if you have previously installed the BLE support, you will find a directory called `ESP32_BLE`. Remove that directory. + +## Switching on debugging +The BLE support contains extensive internal diagnostics which can be switched on by editing the file called `sdkconfig` and finding the lines which read: + +``` +# +# Log output +# +# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set +CONFIG_LOG_DEFAULT_LEVEL_ERROR=y +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_LOG_DEFAULT_LEVEL=1 +``` + +Change this to: + +``` +# +# Log output +# +# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set +# CONFIG_LOG_DEFAULT_LEVEL_ERROR=y +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=y +CONFIG_LOG_DEFAULT_LEVEL=5 +# CONFIG_LOG_COLORS is not set +``` + +and rebuild/deploy your project. + +This file can be found in your Arduino IDE installation directories at: + +``` +/hardware/espressif/esp32/tools/sdk +``` \ No newline at end of file diff --git a/cpp_utils/DesignNotes/README.md b/cpp_utils/DesignNotes/README.md new file mode 100644 index 00000000..b28c9078 --- /dev/null +++ b/cpp_utils/DesignNotes/README.md @@ -0,0 +1,2 @@ +# Design Notes +Here is where we keep some of our design documents. \ No newline at end of file diff --git a/cpp_utils/DesignNotes/WebSockets.md b/cpp_utils/DesignNotes/WebSockets.md new file mode 100644 index 00000000..3187fe89 --- /dev/null +++ b/cpp_utils/DesignNotes/WebSockets.md @@ -0,0 +1,30 @@ +#WebSockets +The WebSocket implementation attempts to implement [RFC 6455 - The WebSocket Protocol](https://tools.ietf.org/html/rfc6455). + + + +When an HTTP request arrives, we examine the request to see if it is a request to open a new WebSocket connection. If it is, we respond with the acknowledgement that we are prepared to be a WebSocket partner. Once done, the socket that was once used to handle the REST request now becomes the one used to handle bi-directional communications between the ESP32 (original WebSocket server) and the client (browser/Node.js). + +Since we don't know when a client may transmit, it could actually transmit at any time. As such we want to register an asynchronous callback handler that will be invoked when the client sends in some WebSocket data. To handle this, we then need to `select()` on the WebSocket socket and, when it wakes, invoke the callback handler. We must **not** issue a new `select()` until the WebSocket message has been consumed. + +It is anticipated that a WebSocket message could be large. The first use case I have come across for using WebSockets is for file transfer and since we have 4MBytes of flash and only 512K of RAM (of which likely only 100K or so may be available) we are likely going to run out of RAM if we wish to write a large file to flash file systems. What this means is that we likely can't buffer a large WebSocket data message in RAM. + +For example, imagine a WebSocket client wants to send a file of 1MByte to the ESP32. That is more data than we have RAM so we can't hold that message in its entirety in RAM. What we need to do is "read some" and then "write some" and repeat until all has been consumed. This sounds like the concept of a stream. We would have an input stream (data coming into the ESP32) associated with the WebSocket and an output stream (data going out from the ESP32) being written to the file. We could thus read a small section of the input, write it to the output and continue while we have new data in the input. + +This sounds workable ... so let us now think about how we might go about creating an input stream for a WebSocket message. Each WebSocket message starts with a WebSocket frame which contains, amongst other things, the length of the payload data. This means that we know up front how much of the remaining data is payload. This becomes essential as we can't rely on an "end of file" marker in the input stream to indicate the end of the WebSocket payload. The reason for this is that the WebSocket is a TCP stream that will be used to carry multiple sequential messages. + +Let us now invent a new class. Let us call it a SocketInputRecordStream. +It will have a constructor of the form: + +``` +SocketInputRecordStream(Socket &socket, size_t dataLength, size_t bufferSize=512) +``` + +The `socket` is the TCP/IP socket that we are going to read data from. The `dataLength` is the size of the data we wish to read. The class will extend `std::streambuf`. It will internally maintain a data buffer of size `bufferSize`. Initially, the buffer will be empty. When a read is performed on the stream, a call to `underflow()` will be made (this is a `std::streambuf` virtual function). + +Our rules for this class include: + +* We must **not** read more the `dataLength` bytes from the socket. +* We must **indicate** and `EOF` once we have had `dataLength` bytes consumed by a stream reader. +* The class must implement a `discard()` method that will discard further bytes from the socket such that the total number of bytes read from the socket will equal `dataLength`. +* Deleting an instance of the class must invoke `discard()`. \ No newline at end of file diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 4237b145..87c55bd0 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -261,7 +261,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { * @param [in] length Length of the data (in bytes) to be logged. * @return N/A. */ -void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { +void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { char ascii[80]; char hex[80]; char tempBuf[80]; diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index 152abca7..ca38c754 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -18,7 +18,7 @@ class GeneralUtils { public: GeneralUtils(); virtual ~GeneralUtils(); - static void hexDump(uint8_t *pData, uint32_t length); + static void hexDump(const uint8_t *pData, uint32_t length); static std::string ipToString(uint8_t *ip); static bool base64Encode(const std::string &in, std::string *out); static bool base64Decode(const std::string &in, std::string *out); diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index 226333d7..cd98ecc4 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -87,7 +87,7 @@ std::string buildResponseHash(std::string requestKey) { HttpRequest::HttpRequest(Socket clientSocket) { m_clientSocket = clientSocket; m_status = 0; - m_webSocket = nullptr; + m_pWebSocket = nullptr; m_parser.parse(clientSocket); // Parse the socket stream to build the HTTP data. @@ -102,7 +102,7 @@ HttpRequest::HttpRequest(Socket clientSocket) { // do something // Process the web socket request - + // Send the response HTTP message to switch to being a Web Socket HttpResponse response(this); response.setStatus(HttpResponse::HTTP_STATUS_SWITCHING_PROTOCOL, "Switching Protocols"); @@ -111,7 +111,7 @@ HttpRequest::HttpRequest(Socket clientSocket) { response.addHeader(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, buildResponseHash(getHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY))); response.sendData(""); - m_webSocket = new WebSocket(clientSocket); + m_pWebSocket = new WebSocket(clientSocket); } else { ESP_LOGD(LOG_TAG, "Not a Websocket"); } @@ -226,7 +226,7 @@ std::string HttpRequest::getVersion() { } // getVersion WebSocket* HttpRequest::getWebSocket() { - return m_webSocket; + return m_pWebSocket; } // getWebSocket @@ -235,7 +235,7 @@ WebSocket* HttpRequest::getWebSocket() { * @return True if the request creates a web socket. */ bool HttpRequest::isWebsocket() { - return m_webSocket != nullptr; + return m_pWebSocket != nullptr; } // isWebsocket /** diff --git a/cpp_utils/HttpRequest.h b/cpp_utils/HttpRequest.h index deb9e769..e48ba75c 100644 --- a/cpp_utils/HttpRequest.h +++ b/cpp_utils/HttpRequest.h @@ -21,7 +21,7 @@ class HttpRequest { Socket m_clientSocket; HttpParser m_parser; int m_status; - WebSocket *m_webSocket; + WebSocket *m_pWebSocket; public: HttpRequest(Socket s); diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 36bf7b1f..9661902a 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -52,6 +52,7 @@ class HttpServerTask: public Task { ESP_LOGD(LOG_TAG, "Found a path handler match!!"); if (request.isWebsocket()) { it->invoke(&request, nullptr); + request.getWebSocket()->startReader(); } else { HttpResponse response(&request); @@ -82,13 +83,14 @@ class HttpServerTask: public Task { while(1) { ESP_LOGD(LOG_TAG, "Waiting for new client"); Socket clientSocket = sockServ.waitForNewClient(); + ESP_LOGD(LOG_TAG, "Got a new client"); HttpRequest request(clientSocket); request.dump(); processRequest(request); if (!request.isWebsocket()) { request.close_cpp(); } - ESP_LOGD(LOG_TAG, "Got a new client"); + } // while } // run }; // HttpServerTask diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index 5a96931e..6b173b57 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -49,6 +49,7 @@ class HttpServer { void start(uint16_t portNumber); private: friend class HttpServerTask; + friend class WebSocket; uint16_t m_portNumber; std::vector m_pathHandlers; std::string m_rootPath; diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 02cacd96..0d81b696 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -4,6 +4,12 @@ * Created on: Mar 5, 2017 * Author: kolban */ +#include +#include +#include +#include +#include +#include #include @@ -296,11 +302,14 @@ int Socket::receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr *pAddr * @return N/A. * */ -void Socket::send_cpp(const uint8_t* data, size_t length) const { +int Socket::send_cpp(const uint8_t* data, size_t length) const { + ESP_LOGD(LOG_TAG, "send_cpp: Raw binary of length: %d", length); + GeneralUtils::hexDump(data, length); int rc = ::send(m_sock, data, length, 0); if (rc == -1) { ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); } + return rc; } // send_cpp @@ -310,8 +319,20 @@ void Socket::send_cpp(const uint8_t* data, size_t length) const { * @param [in] value The string to send to the partner. * @return N/A. */ -void Socket::send_cpp(std::string value) const { - send_cpp((uint8_t *)value.data(), value.size()); +int Socket::send_cpp(std::string value) const { + ESP_LOGD(LOG_TAG, "send_cpp: Binary of length: %d", value.length()); + return send_cpp((uint8_t *)value.data(), value.size()); +} // send_cpp + + +int Socket::send_cpp(uint16_t value) { + ESP_LOGD(LOG_TAG, "send_cpp: 16bit value: %.2x", value); + return send_cpp((uint8_t *)&value, sizeof(value)); +} // send_cpp + +int Socket::send_cpp(uint32_t value) { + ESP_LOGD(LOG_TAG, "send_cpp: 32bit value: %.2x", value); + return send_cpp((uint8_t *)&value, sizeof(value)); } // send_cpp @@ -337,3 +358,37 @@ std::string Socket::toString() { oss << "fd: " << m_sock; return oss.str(); } // toString + + +/** + * @brief Create a socket input record streambuf + * @param [in] socket The socket we will be reading from. + * @param [in] dataLength The size of a record. + * @param [in] bufferSize The size of the buffer we wish to allocate to hold data. + */ +SocketInputRecordStream::SocketInputRecordStream( + Socket* socket, + size_t dataLength, + size_t bufferSize) { + m_pSocket = socket; // The socket we will be reading from + m_dataLength = dataLength; // The size of the record we wish to read. + m_bufferSize = bufferSize; // The size of the buffer used to hold data + m_sizeRead = 0; // The size of data read from the socket + m_buffer = new char[bufferSize]; // Create the buffer used to hold the data read from the socket. + + setg(m_buffer, m_buffer, m_buffer); // Set the initial get buffer pointers to no data. +} // SocketInputRecordStream + + +/** + * @brief Handle the request to read data from the stream but we need more data from the source. + * + */ +SocketInputRecordStream::int_type SocketInputRecordStream::underflow() { + return 0; +} // underflow + + +SocketInputRecordStream::~SocketInputRecordStream() { + delete[] m_buffer; +} // ~SocketInputRecordStream diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 7fdb2302..f97ecd28 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -7,9 +7,15 @@ #ifndef COMPONENTS_CPP_UTILS_SOCKET_H_ #define COMPONENTS_CPP_UTILS_SOCKET_H_ + +#include +#include +#include +#include +#include #include #include -#include + /** * @brief Encapsulate a socket. @@ -38,8 +44,10 @@ class Socket { std::string readToDelim(std::string delim); int receive_cpp(uint8_t* data, size_t length, bool exact=false); int receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr* pAddr); - void send_cpp(std::string value) const; - void send_cpp(const uint8_t* data, size_t length) const; + int send_cpp(std::string value) const; + int send_cpp(const uint8_t* data, size_t length) const; + int send_cpp(uint16_t value); + int send_cpp(uint32_t value); void sendTo_cpp(const uint8_t* data, size_t length, struct sockaddr* pAddr); std::string toString(); @@ -48,4 +56,17 @@ class Socket { int m_sock; }; +class SocketInputRecordStream : public std::streambuf { +public: + SocketInputRecordStream(Socket* socket, size_t dataLength, size_t bufferSize=512); + ~SocketInputRecordStream(); + int_type underflow(); +private: + char *m_buffer; + Socket* m_pSocket; + size_t m_dataLength; + size_t m_bufferSize; + size_t m_sizeRead; +}; + #endif /* COMPONENTS_CPP_UTILS_SOCKET_H_ */ diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp index 990c2df0..5c18039c 100644 --- a/cpp_utils/WebSocket.cpp +++ b/cpp_utils/WebSocket.cpp @@ -24,24 +24,26 @@ static const int OPCODE_PONG = 0x0a; // Structure definition for the WebSocket frame. struct Frame { - unsigned int opCode : 4; // [7:4] - unsigned int rsv3 : 1; // [3] - unsigned int rsv2 : 1; // [2] - unsigned int rsv1 : 1; // [1] - unsigned int fin : 1; // [0] - - unsigned int len : 7; // [7:1] - unsigned int mask : 1; // [0] + // Byte 0 + uint8_t opCode : 4; // [7:4] + uint8_t rsv3 : 1; // [3] + uint8_t rsv2 : 1; // [2] + uint8_t rsv1 : 1; // [1] + uint8_t fin : 1; // [0] + + // Byte 1 + uint8_t len : 7; // [7:1] + uint8_t mask : 1; // [0] }; /** - * @brief Dump the content of the frame for debugging. + * @brief Dump the content of the WebSocket frame for debugging. * @param [in] frame The frame to dump. */ static void dumpFrame(Frame frame) { std::ostringstream oss; - oss << "Fin: " << frame.fin << ", OpCode: " << frame.opCode; + oss << "Fin: " << (int)frame.fin << ", OpCode: " << (int)frame.opCode; switch(frame.opCode) { case OPCODE_BINARY: { oss << " BINARY"; @@ -72,74 +74,158 @@ static void dumpFrame(Frame frame) { break; } } - oss << ", Mask: " << frame.mask << ", len: " << frame.len; + oss << ", Mask: " << (int)frame.mask << ", len: " << (int)frame.len; ESP_LOGD(LOG_TAG, "WebSocket frame: %s", oss.str().c_str()); } // dumpFrame /** * @brief A task that will watch web socket inputs. + * + * When a WebSocket is created it is created by the client requesting an HTTP protocol changed to WebSockets. + * After the original Socket has been flagged as being a WebSocket, we must now start watching that socket for + * incoming asynchronous events. We spawn a task to do this. This is the implementation of that task. */ class WebSocketReader: public Task { + /** + * @brief Loop over the web socket waiting for new input. + * @param [in] data A pointer to an instance of the WebSocket. + */ void run(void* data) { - uint8_t buffer[1000]; WebSocket *pWebSocket = (WebSocket*) data; - // do something + ESP_LOGD("WebSocketReader", "WebSocketReader Task started, socket: %s", pWebSocket->getSocket().toString().c_str()); + uint8_t buffer[1000]; + Socket peerSocket = pWebSocket->getSocket(); - ESP_LOGD(LOG_TAG, "Waiting on socket data for socket %s", peerSocket.toString().c_str()); - int length = peerSocket.receive_cpp(buffer, sizeof(buffer)); - ESP_LOGD(LOG_TAG, "Received data from web socket. Length: %d", length); - GeneralUtils::hexDump(buffer, length); - dumpFrame(*(Frame *)buffer); - - // The following section parses the WebSocket frame. - if (length > 0) { - Frame* pFrame = (Frame*)buffer; - uint32_t payloadLen = 0; - uint8_t* pMask = nullptr; - uint8_t* pData; - if (pFrame->len < 126) { - payloadLen = pFrame->len; - pMask = buffer + 2; - } else if (pFrame->len == 126) { - payloadLen = *(uint16_t*)(buffer+2); - pMask = buffer + 4; - } else if (pFrame->len == 127) { - ESP_LOGE(LOG_TAG, "Too much data!"); + while(1) { + ESP_LOGD("WebSocketReader", "Waiting on socket data for socket %s", peerSocket.toString().c_str()); + int length = peerSocket.receive_cpp(buffer, sizeof(buffer)); + if (length == -1 || length == 0) { + ESP_LOGD(LOG_TAG, "Socket read error"); + pWebSocket->close_cpp(); return; } - if (pFrame->mask == 1) { - pData = pMask + 4; - for (int i=0; i 0) { + Frame* pFrame = (Frame*)buffer; + uint32_t payloadLen = 0; + uint8_t* pMask = nullptr; + uint8_t* pData; + if (pFrame->len < 126) { + payloadLen = pFrame->len; + pMask = buffer + 2; + } else if (pFrame->len == 126) { + payloadLen = *(uint16_t*)(buffer+2); + pMask = buffer + 4; + } else if (pFrame->len == 127) { + ESP_LOGE(LOG_TAG, "Too much data!"); + return; } - pData = pMask + 4; - } else { - pData = pMask; - } - std::string retData = std::string((char *)pData, payloadLen); + if (pFrame->mask == 1) { + pData = pMask + 4; + for (int i=0; igetHandler(); + switch(pFrame->opCode) { + case OPCODE_BINARY: { + if (pWebSocketHandler != nullptr) { + pWebSocketHandler->onMessage(payloadData); + } + break; + } + + case OPCODE_CLOSE: { + pWebSocket->m_receivedClose = true; + if (pWebSocketHandler != nullptr) { + pWebSocketHandler->onClose(); + pWebSocket->close_cpp(); + } + break; + } + + case OPCODE_CONTINUE: { + break; + } + + case OPCODE_PING: { + break; + } + + case OPCODE_PONG: { + break; + } + + case OPCODE_TEXT: { + if (pWebSocketHandler != nullptr) { + pWebSocketHandler->onMessage(payloadData); + } + break; + } + + default: { + ESP_LOGD("WebSocketReader", "Unknown opcode: %d", pFrame->opCode); + break; + } + } // Switch opCode + } // Length of data > 0 + } // While (1) } // run }; // WebSocketReader -void WebSocketHandler::onData(std::string data) { +/** + * @brief The default onClose handler. + * If no over-riding handler is provided for the "close" event, this method is called. + */ +void WebSocketHandler::onClose() { + ESP_LOGD(LOG_TAG, ">> WebSocketHandler:onClose()"); +} // onClose + + +/** + * @brief The default onData handler. + * If no over-riding handler is provided for the "message" event, this method is called. + */ +void WebSocketHandler::onMessage(std::string data) { + ESP_LOGD(LOG_TAG, ">> WebSocketHandler:onMessage(), length: %d", data.length()); + GeneralUtils::hexDump((uint8_t*)data.data(), data.length()); } // onData +/** + * @brief The default onError handler. + * If no over-riding handler is provided for the "error" event, this method is called. + */ void WebSocketHandler::onError(std::string error) { + ESP_LOGD(LOG_TAG, ">> WebSocketHandler:onError()"); } // onError WebSocket::WebSocket(Socket socket) { - m_socket = socket; - WebSocketReader *pWebSocketReader = new WebSocketReader(); - pWebSocketReader->start(this); + m_receivedClose = false; + m_sentClose = false; + m_socket = socket; + m_pWebSockerReader = new WebSocketReader(); } // WebSocket @@ -150,11 +236,45 @@ WebSocket::~WebSocket() { /** * @brief Close the Web socket */ -void WebSocket::close_cpp() { +void WebSocket::close_cpp(uint16_t status, std::string message) { + ESP_LOGD(LOG_TAG, ">> close_cpp(): status: %d, message: %s", status, message.c_str()); + if (m_sentClose) { + m_socket.close_cpp(); + return; + } + m_sentClose = true; + Frame frame; + frame.fin = 1; + frame.rsv1 = 0; + frame.rsv2 = 0; + frame.rsv3 = 0; + frame.opCode = OPCODE_CLOSE; + frame.mask = 0; + frame.len = message.length() + 2; + int rc = m_socket.send_cpp((uint8_t *)&frame, sizeof(frame)); + if (rc > 0) { + rc = m_socket.send_cpp(status); + } + if (rc > 0) { + m_socket.send_cpp(message); + } + if (m_receivedClose || rc == 0 || rc == -1) { + m_socket.close_cpp(); + } } // close_cpp +/** + * @brief Get the current WebSocketHandler + * A web socket handler is a user registered class instance that is called when an incoming + * event received over the network needs to be handled by user code. + */ +WebSocketHandler* WebSocket::getHandler() { + return &m_webSocketHandler; +} // getHandler + + /** * @brief Get the underlying socket for the websocket. * @return The socket associated with the Web socket. @@ -166,9 +286,30 @@ Socket WebSocket::getSocket() { /** * @brief Send data down the web socket - * @param [in] data The data to send down the websocket. + * See the WebSocket spec (RFC6455) section "6.1 Sending Data". + * We build a WebSocket frame, send the frame followed by the data. + * @param [in] data The data to send down the WebSocket. + * @param [in] sendType The type of payload. Either SEND_TYPE_TEXT or SEND_TYPE_BINARY. */ -void WebSocket::send_cpp(std::string data) { +void WebSocket::send_cpp(std::string data, uint8_t sendType) { + ESP_LOGD(LOG_TAG, ">> send_cpp: Length: %d", data.length()); + Frame frame; + frame.fin = 1; + frame.rsv1 = 0; + frame.rsv2 = 0; + frame.rsv3 = 0; + frame.opCode = sendType==SEND_TYPE_TEXT?OPCODE_TEXT:OPCODE_BINARY; + frame.mask = 0; + if (data.length() < 126) { + frame.len = data.length(); + m_socket.send_cpp((uint8_t *)&frame, sizeof(frame)); + } else { + frame.len = 126; + m_socket.send_cpp((uint8_t *)&frame, sizeof(frame)); + m_socket.send_cpp((uint16_t)data.length()); + } + m_socket.send_cpp((uint8_t*)data.data(), data.length()); + ESP_LOGD(LOG_TAG, "<< send_cpp"); } // send_cpp @@ -182,3 +323,14 @@ void WebSocket::send_cpp(std::string data) { void WebSocket::setHandler(WebSocketHandler handler) { m_webSocketHandler = handler; } // setHandler + + +/** + * @brief Start the WebSocket reader reading the socket. + * When we have a new web socket, we want to start watching for new incoming events. This + * function starts that activity. We want to have control over when we start watching. + */ +void WebSocket::startReader() { + ESP_LOGD(LOG_TAG, ">> startReader: Socket: %s", m_socket.toString().c_str()); + m_pWebSockerReader->start(this); +} // startReader diff --git a/cpp_utils/WebSocket.h b/cpp_utils/WebSocket.h index 5230a096..122a9dbb 100644 --- a/cpp_utils/WebSocket.h +++ b/cpp_utils/WebSocket.h @@ -12,22 +12,49 @@ class WebSocketHandler { public: - virtual void onData(std::string data); + virtual void onClose(); + virtual void onMessage(std::string data); virtual void onError(std::string error); }; +class WebSocketReader; class WebSocket { private: + friend class WebSocketReader; + friend class HttpServerTask; + void startReader(); + bool m_receivedClose; // True when we have received a close request. + bool m_sentClose; // True when we have sent a close request. + Socket m_socket; // Partner socket. + WebSocketHandler m_webSocketHandler; + WebSocketReader *m_pWebSockerReader; - Socket m_socket; - WebSocketHandler m_webSocketHandler; public: + static const uint16_t CLOSE_NORMAL_CLOSURE = 1000; + static const uint16_t CLOSE_GOING_AWAY = 1001; + static const uint16_t CLOSE_PROTOCOL_ERROR = 1002; + static const uint16_t CLOSE_CANNOT_ACCEPT = 1003; + static const uint16_t CLOSE_NO_STATUS_CODE = 1005; + static const uint16_t CLOSE_CLOSED_ABNORMALLY = 1006; + static const uint16_t CLOSE_NOT_CONSISTENT = 1007; + static const uint16_t CLOSE_VIOLATED_POLICY = 1008; + static const uint16_t CLOSE_TOO_BIG = 1009; + static const uint16_t CLOSE_NO_EXTENSION = 1010; + static const uint16_t CLOSE_UNEXPECTED_CONDITION = 1011; + static const uint16_t CLOSE_SERVICE_RESTART = 1012; + static const uint16_t CLOSE_TRY_AGAIN_LATER = 1013; + static const uint16_t CLOSE_TLS_HANDSHAKE_FAILURE = 1015; + + static const uint8_t SEND_TYPE_BINARY = 0x01; + static const uint8_t SEND_TYPE_TEXT = 0x02; + WebSocket(Socket socket); virtual ~WebSocket(); - void close_cpp(); + void close_cpp(uint16_t status=CLOSE_NORMAL_CLOSURE, std::string message = ""); + WebSocketHandler* getHandler(); Socket getSocket(); - void send_cpp(std::string data); + void send_cpp(std::string data, uint8_t sendType = SEND_TYPE_BINARY); void setHandler(WebSocketHandler handler); }; diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino index 3fc5efa2..ed7fbf0c 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino @@ -26,7 +26,7 @@ static void notifyCallback( Serial.println(length); } -void connectToServer(BLEAddress pAddress) { +bool connectToServer(BLEAddress pAddress) { Serial.print("Forming a connection to "); Serial.println(pAddress.toString().c_str()); @@ -42,7 +42,7 @@ void connectToServer(BLEAddress pAddress) { if (pRemoteService == nullptr) { Serial.print("Failed to find our service UUID: "); Serial.println(serviceUUID.toString().c_str()); - return; + return false; } @@ -51,7 +51,7 @@ void connectToServer(BLEAddress pAddress) { if (pRemoteCharacteristic == nullptr) { Serial.print("Failed to find our characteristic UUID: "); Serial.println(charUUID.toString().c_str()); - return; + return false; } // Read the value of the characteristic. @@ -109,9 +109,13 @@ void loop() { // BLE Server with which we wish to connect. Now we connect to it. Once we are // connected we set the connected flag to be true. if (doConnect == true) { - connectToServer(*pServerAddress); + if (connectToServer(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } else { + Serial.println("We have failed to connect to the server; there is nothin more we will do."); + } doConnect = false; - connected = true; } // If we are connected to a peer BLE Server, update the characteristic each time we are reached From 4b465fe185076ec7b9d1b6d8cc5cac059e00671d Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 10 Sep 2017 10:42:25 -0500 Subject: [PATCH 038/381] Sync update 2017-09-10 --- cpp_utils/.gitignore | 1 - cpp_utils/Arduino/ESP32_BLE.zip | Bin 0 -> 79700 bytes cpp_utils/ArduinoBLE.md | 35 +--- cpp_utils/DesignNotes/WebSockets.md | 40 +++- cpp_utils/FATFS_VFS.cpp | 6 +- cpp_utils/FATFS_VFS.h | 4 +- cpp_utils/HttpRequest.cpp | 2 +- cpp_utils/HttpRequest.h | 8 +- cpp_utils/HttpResponse.cpp | 9 +- cpp_utils/HttpResponse.h | 2 +- cpp_utils/HttpServer.cpp | 74 +++++-- cpp_utils/HttpServer.h | 13 +- cpp_utils/JSON.cpp | 12 ++ cpp_utils/JSON.h | 54 ++--- cpp_utils/OV7670.cpp | 2 + cpp_utils/Socket.cpp | 47 +++-- cpp_utils/Socket.h | 10 +- cpp_utils/WebSocket.cpp | 292 ++++++++++++++++++++-------- cpp_utils/WebSocket.h | 50 ++++- cpp_utils/WebSocketFileTransfer.cpp | 112 +++++++++++ cpp_utils/WebSocketFileTransfer.h | 24 +++ filesystems/sendZip/ws_send_zip.js | 63 ++++++ 22 files changed, 647 insertions(+), 213 deletions(-) create mode 100644 cpp_utils/Arduino/ESP32_BLE.zip create mode 100644 cpp_utils/WebSocketFileTransfer.cpp create mode 100644 cpp_utils/WebSocketFileTransfer.h create mode 100644 filesystems/sendZip/ws_send_zip.js diff --git a/cpp_utils/.gitignore b/cpp_utils/.gitignore index 190415da..6e684992 100644 --- a/cpp_utils/.gitignore +++ b/cpp_utils/.gitignore @@ -1,2 +1 @@ /docs/ -/Arduino/ diff --git a/cpp_utils/Arduino/ESP32_BLE.zip b/cpp_utils/Arduino/ESP32_BLE.zip new file mode 100644 index 0000000000000000000000000000000000000000..c253727271995ac48c301d61f25f45738e616c62 GIT binary patch literal 79700 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 Include Library -> Add .ZIP library`. When initial development of the library has been completed, this ZIP will be placed under some form of release control so that an ordinary Arduino IDE user can simply download this as a unit and install. -However, today, we are choosing **not** to distribute the BLE support as a downloadable library. The reason for this is that we believe that the code base for the BLE support is too fluid and it is being actively worked upon. Each day there will be changes and that means that for any given instance of the ZIP you get, if it is more than a day old, it is likely out of date. +We provide sample builds of the `ESP32_BLE.zip` file in the `Arduino` folder found relative to this directory. -Instead of giving you a fish (a new ESP32_BLE.zip) we are going to teach you to fish so that you yourself can build the very latest ESP32_BLE.zip yourself. Hopefully you will find this quite easy to accomplish. - -Here is the recipe. +The build of the Arduino support will be current as of the date of the ZIP file however should you wish to build your own instance of the ZIP from the source, here is the recipe. 1. Create a new directory called `build` 2. Enter that directory and run `git clone https://github.com/nkolban/esp32-snippets.git` @@ -24,35 +22,16 @@ And here you will find the `ESP32_BLE.zip` that is build from the latest source. If you have previously installed a version of the Arduino BLE Support and need to install a new one, follow the steps above to build yourself a new instance of the `ESP32_BLE.zip` that is ready for installation. I recommend removing the old one before installing the new one. To remove the old one, find the directory where the Arduino IDE stores your libraries (on Linux this is commonly `$HOME/Arduino`). In there you will find a directory called `libraries` and, if you have previously installed the BLE support, you will find a directory called `ESP32_BLE`. Remove that directory. ## Switching on debugging -The BLE support contains extensive internal diagnostics which can be switched on by editing the file called `sdkconfig` and finding the lines which read: +The BLE support contains extensive internal diagnostics which can be switched on by editing the file called `sdkconfig.h` and finding the lines which read: ``` -# -# Log output -# -# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set -CONFIG_LOG_DEFAULT_LEVEL_ERROR=y -# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set -# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set -# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set -# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set -CONFIG_LOG_DEFAULT_LEVEL=1 +#define CONFIG_LOG_DEFAULT 1 ``` Change this to: ``` -# -# Log output -# -# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set -# CONFIG_LOG_DEFAULT_LEVEL_ERROR=y -# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set -# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set -# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set -CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=y -CONFIG_LOG_DEFAULT_LEVEL=5 -# CONFIG_LOG_COLORS is not set +#define CONFIG_LOG_DEFAULT 5 ``` and rebuild/deploy your project. @@ -60,5 +39,5 @@ and rebuild/deploy your project. This file can be found in your Arduino IDE installation directories at: ``` -/hardware/espressif/esp32/tools/sdk +/hardware/espressif/esp32/tools/sdk/include/config ``` \ No newline at end of file diff --git a/cpp_utils/DesignNotes/WebSockets.md b/cpp_utils/DesignNotes/WebSockets.md index 3187fe89..0a5ef897 100644 --- a/cpp_utils/DesignNotes/WebSockets.md +++ b/cpp_utils/DesignNotes/WebSockets.md @@ -13,11 +13,11 @@ For example, imagine a WebSocket client wants to send a file of 1MByte to the ES This sounds workable ... so let us now think about how we might go about creating an input stream for a WebSocket message. Each WebSocket message starts with a WebSocket frame which contains, amongst other things, the length of the payload data. This means that we know up front how much of the remaining data is payload. This becomes essential as we can't rely on an "end of file" marker in the input stream to indicate the end of the WebSocket payload. The reason for this is that the WebSocket is a TCP stream that will be used to carry multiple sequential messages. -Let us now invent a new class. Let us call it a SocketInputRecordStream. +Let us now invent a new class. Let us call it a SocketInputRecordStreambuf. It will have a constructor of the form: ``` -SocketInputRecordStream(Socket &socket, size_t dataLength, size_t bufferSize=512) +SocketInputRecordStreambuf(Socket &socket, size_t dataLength, size_t bufferSize=512) ``` The `socket` is the TCP/IP socket that we are going to read data from. The `dataLength` is the size of the data we wish to read. The class will extend `std::streambuf`. It will internally maintain a data buffer of size `bufferSize`. Initially, the buffer will be empty. When a read is performed on the stream, a call to `underflow()` will be made (this is a `std::streambuf` virtual function). @@ -27,4 +27,38 @@ Our rules for this class include: * We must **not** read more the `dataLength` bytes from the socket. * We must **indicate** and `EOF` once we have had `dataLength` bytes consumed by a stream reader. * The class must implement a `discard()` method that will discard further bytes from the socket such that the total number of bytes read from the socket will equal `dataLength`. -* Deleting an instance of the class must invoke `discard()`. \ No newline at end of file +* Deleting an instance of the class must invoke `discard()`. + +## File transfer +WebSockets make a great file transfer technology. While this is more an application utilization of the technology than the design of the framework, we'll capture it here. Let us first set the scene. We have a client application that wishes to transmit a file. We will assume that the file is composed of three logical components: + +* The file name +* The file length +* The content of the file + +It would be wrong to expect the client to send the file as one continuous unit in one WebSocket message. The reason for this is that the client would have to have loaded the complete file into its memory buffers in order to send it. As such, we should assume that the client will send the files as one or more "parts" where each part represents a piece of the file. + +We thus invent the following protocol: + +For the first message we have: + +``` ++-----------------------+-----------+----+--------------------+-----------+ +| transferId (32bit/LE) | file name | \0 | length (32bits/LE) | Data .... | ++-----------------------+-----------+----+--------------------+-----------+ +``` + +For subsequent messages we have: + +``` ++-----------------------+-----------+ +| transferId (32bit/LE) | Data .... | ++-----------------------+-----------+ +``` +Let us look at these. + +* `transferId` - An Id that is randomly generated by the client. This is used to associate multiple messages for the same file together. +* `file name` - The name of the file that the client wishes to send. Can include paths. This will be used to determine where on the file system the file will be written. +* `length` - The size of the file in bytes. Knowing the size will allow us to know when the whole file has been received. + +We will create an encapsulation class called `WebSocketFileTransfer`. \ No newline at end of file diff --git a/cpp_utils/FATFS_VFS.cpp b/cpp_utils/FATFS_VFS.cpp index 4143899b..30af8e4d 100644 --- a/cpp_utils/FATFS_VFS.cpp +++ b/cpp_utils/FATFS_VFS.cpp @@ -26,10 +26,10 @@ * @param [in] partitionName The name of the partition used to store the FAT file system. */ FATFS_VFS::FATFS_VFS(std::string mountPath, std::string partitionName) { - m_mountPath = mountPath; + m_mountPath = mountPath; m_partitionName = partitionName; - m_maxFiles = 4; - m_wl_handle = WL_INVALID_HANDLE; + m_maxFiles = 4; + m_wl_handle = WL_INVALID_HANDLE; } // FATFS_VFS diff --git a/cpp_utils/FATFS_VFS.h b/cpp_utils/FATFS_VFS.h index fe9a7e57..24ce4928 100644 --- a/cpp_utils/FATFS_VFS.h +++ b/cpp_utils/FATFS_VFS.h @@ -10,6 +10,7 @@ #include extern "C" { #include +#include } /** * @brief Provide access to the FAT file system on %SPI flash. @@ -42,6 +43,7 @@ class FATFS_VFS { public: FATFS_VFS(std::string mountPath, std::string partitionName); virtual ~FATFS_VFS(); + void mount(); void setMaxFiles(int maxFiles); void unmount(); @@ -49,7 +51,7 @@ class FATFS_VFS { wl_handle_t m_wl_handle; std::string m_mountPath; std::string m_partitionName; - int m_maxFiles; + int m_maxFiles; }; #endif /* COMPONENTS_CPP_UTILS_FATFS_VFS_H_ */ diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index cd98ecc4..265d4ef9 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -122,7 +122,7 @@ HttpRequest::~HttpRequest() { } // ~HttpRequest -void HttpRequest::close_cpp() { +void HttpRequest::close() { m_clientSocket.close_cpp(); } // close_cpp diff --git a/cpp_utils/HttpRequest.h b/cpp_utils/HttpRequest.h index e48ba75c..398e02ed 100644 --- a/cpp_utils/HttpRequest.h +++ b/cpp_utils/HttpRequest.h @@ -14,7 +14,7 @@ #include "WebSocket.h" #include "HttpParser.h" - +#undef close class HttpRequest { private: @@ -51,10 +51,8 @@ class HttpRequest { static const std::string HTTP_METHOD_POST; static const std::string HTTP_METHOD_PUT; - - - void close_cpp(); - void dump(); + void close(); + void dump(); std::string getBody(); std::string getHeader(std::string name); std::map getHeaders(); diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp index 947b3e62..4063932d 100644 --- a/cpp_utils/HttpResponse.cpp +++ b/cpp_utils/HttpResponse.cpp @@ -50,9 +50,9 @@ void HttpResponse::addHeader(const std::string name, const std::string value) { } // addHeader -void HttpResponse::close_cpp() { - m_request->close_cpp(); -} // close_cpp +void HttpResponse::close() { + m_request->close(); +} // close std::string HttpResponse::getHeader(std::string name) { @@ -74,6 +74,7 @@ std::map HttpResponse::getHeaders() { * @param [in] data The data to send to the partner. */ void HttpResponse::sendData(std::string data) { + // If we haven't yet sent the header of the data, send that now. if (m_headerCommitted == false) { std::ostringstream oss; oss << m_request->getVersion() << " " << m_status << " " << m_statusMessage << lineTerminator; @@ -84,6 +85,8 @@ void HttpResponse::sendData(std::string data) { m_headerCommitted = true; m_request->getSocket().send_cpp(oss.str()); } + + // Send the payload data. m_request->getSocket().send_cpp(data); } // sendData diff --git a/cpp_utils/HttpResponse.h b/cpp_utils/HttpResponse.h index 089fab91..5a9fe2ec 100644 --- a/cpp_utils/HttpResponse.h +++ b/cpp_utils/HttpResponse.h @@ -36,7 +36,7 @@ class HttpResponse { virtual ~HttpResponse(); void addHeader(std::string name, std::string value); - void close_cpp(); + void close(); //std::string getRootPath(); std::string getHeader(std::string name); std::map getHeaders(); diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 9661902a..c342d098 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -5,6 +5,7 @@ * Author: kolban */ +#include #include "HttpServer.h" #include "SockServ.h" #include "Task.h" @@ -14,6 +15,7 @@ #include "WebSocket.h" static const char* LOG_TAG = "HttpServer"; +#undef close /** * Constructor for HTTP Server */ @@ -30,13 +32,16 @@ HttpServer::~HttpServer() { */ class HttpServerTask: public Task { public: - HttpServerTask(std::string name): Task(name) {}; + HttpServerTask(std::string name): Task(name) { + m_pHttpServer = nullptr; + }; private: HttpServer* m_pHttpServer; // Reference to the HTTP Server /** * @brief Process an incoming HTTP Request + * * We examine each of the path handlers to see if we have a match for the method/path pair. If we do, * we invoke the handler callback passing in both the request and response. * @@ -45,52 +50,81 @@ class HttpServerTask: public Task { * @param [in] request The HTTP request to process. */ void processRequest(HttpRequest &request) { - ESP_LOGD(LOG_TAG, ">> processRequest: Method: %s, Path: %s", - request.getMethod().c_str(), request.getPath().c_str()) + ESP_LOGD("HttpServerTask", ">> processRequest: Method: %s, Path: %s", + request.getMethod().c_str(), request.getPath().c_str()); + for (auto it = m_pHttpServer->m_pathHandlers.begin(); it != m_pHttpServer->m_pathHandlers.end(); ++it) { if (it->match(request.getMethod(), request.getPath())) { - ESP_LOGD(LOG_TAG, "Found a path handler match!!"); + ESP_LOGD("HttpServerTask", "Found a path handler match!!"); if (request.isWebsocket()) { - it->invoke(&request, nullptr); + it->invokePathHandler(&request, nullptr); request.getWebSocket()->startReader(); } else { HttpResponse response(&request); - - it->invoke(&request, &response); + it->invokePathHandler(&request, &response); } return; } // Path handler match } // For each path handler + ESP_LOGD("HttpServerTask", "No Path handler found"); + // If we reach here, then we did not find a handler for the request. + + // Check to see if we have an un-handled WebSocket + if (request.isWebsocket()) { + request.getWebSocket()->close_cpp(); + return; + } + // Serve up the content from the file on the file system ... if found ... - ESP_LOGD(LOG_TAG, "No Path handler found"); + std::ifstream ifStream; + std::string fileName = m_pHttpServer->getRootPath() + request.getPath(); + ESP_LOGD("HttpServerTask", "Opening file: %s", fileName.c_str()); + ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); + + // If we failed to open the requested file, then it probably didn't exist so return a not found. + if (!ifStream.is_open()) { + HttpResponse response(&request); + response.setStatus(HttpResponse::HTTP_STATUS_NOT_FOUND, "Not Found"); + response.sendData(""); + return; + } + + // We now have an open file and want to push the content of that file through to the browser. HttpResponse response(&request); - response.setStatus(HttpResponse::HTTP_STATUS_NOT_FOUND, "Not Found"); - response.sendData(""); + response.setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + std::stringstream ss; + ss << ifStream.rdbuf(); + response.sendData(ss.str()); + ifStream.close(); } // processRequest /** - * Perform the task handling for server. + * @brief Perform the task handling for server. + * @param [in] data A reference to the HttpServer. */ void run(void* data) { m_pHttpServer = (HttpServer*)data; + + // Create a socket server and start it running. SockServ sockServ(m_pHttpServer->getPort()); sockServ.start(); - ESP_LOGD(LOG_TAG, "Listening on port %d", m_pHttpServer->getPort()); + ESP_LOGD("HttpServerTask", "Listening on port %d", m_pHttpServer->getPort()); + while(1) { - ESP_LOGD(LOG_TAG, "Waiting for new client"); + ESP_LOGD("HttpServerTask", "Waiting for new peer client"); Socket clientSocket = sockServ.waitForNewClient(); - ESP_LOGD(LOG_TAG, "Got a new client"); + ESP_LOGD("HttpServerTask", "HttpServer listening on port %d received a new client connection", m_pHttpServer->getPort()); + HttpRequest request(clientSocket); request.dump(); processRequest(request); if (!request.isWebsocket()) { - request.close_cpp(); + request.close(); } - } // while } // run }; // HttpServerTask @@ -132,6 +166,7 @@ uint16_t HttpServer::getPort() { return m_portNumber; } // getPort + /** * @brief Get the current root path. * @return The current root path. @@ -140,6 +175,7 @@ std::string HttpServer::getRootPath() { return m_rootPath; } // getRootPath + /** * @brief Set the root path for URL file mapping. * @@ -200,7 +236,7 @@ PathHandler::PathHandler(std::string method, std::string pathPattern, * @return True if the path matches. */ bool PathHandler::match(std::string method, std::string path) { - ESP_LOGD(LOG_TAG, "match: %s with %s", m_textPattern.c_str(), path.c_str()); + ESP_LOGD(LOG_TAG, "matching: %s with %s", m_textPattern.c_str(), path.c_str()); if (method != m_method) { return false; } @@ -214,6 +250,6 @@ bool PathHandler::match(std::string method, std::string path) { * @param [in] response An object representing the response. * @return N/A. */ -void PathHandler::invoke(HttpRequest* request, HttpResponse *response) { +void PathHandler::invokePathHandler(HttpRequest* request, HttpResponse *response) { m_requestHandler(request, response); -} // invoke +} // invokePathHandler diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index 6b173b57..4e4de640 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -23,7 +23,7 @@ class PathHandler { std::string pathPattern, void (*webServerRequestHandler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse)); bool match(std::string method, std::string path); - void invoke(HttpRequest* request, HttpResponse* response); + void invokePathHandler(HttpRequest* request, HttpResponse* response); private: std::string m_method; std::regex m_pattern; @@ -38,21 +38,22 @@ class HttpServer { public: HttpServer(); virtual ~HttpServer(); - void addPathHandler(std::string method, + + void addPathHandler(std::string method, std::string pathExpr, void (*webServerRequestHandler)( HttpRequest* pHttpRequest, HttpResponse* pHttpResponse) ); - uint16_t getPort(); + uint16_t getPort(); std::string getRootPath(); - void setRootPath(std::string path); - void start(uint16_t portNumber); + void setRootPath(std::string path); + void start(uint16_t portNumber); private: friend class HttpServerTask; friend class WebSocket; uint16_t m_portNumber; std::vector m_pathHandlers; - std::string m_rootPath; + std::string m_rootPath; // Root path into the file system. }; // HttpServer #endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index 4bebda0a..f7b296f8 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -258,6 +258,17 @@ std::string JsonObject::getString(std::string name) { } // getString +/** + * @brief Determine if the object has the specified item. + * @param [in] name The name of the property to check for presence. + * @return True if the object contains this property. + */ +bool JsonObject::hasItem(std::string name) { + return cJSON_GetObjectItem(m_node, name.c_str()) != nullptr; +} // hasItem + + + /** * @brief Set the named array property. * @param [in] name The name of the property to add. @@ -334,3 +345,4 @@ std::string JsonObject::toString() { free(data); return ret; } // toString + diff --git a/cpp_utils/JSON.h b/cpp_utils/JSON.h index 08915104..bca2d647 100644 --- a/cpp_utils/JSON.h +++ b/cpp_utils/JSON.h @@ -20,11 +20,11 @@ class JsonArray; class JSON { public: static JsonObject createObject(); - static JsonArray createArray(); - static void deleteObject(JsonObject jsonObject); - static void deleteArray(JsonArray jsonArray); + static JsonArray createArray(); + static void deleteObject(JsonObject jsonObject); + static void deleteArray(JsonArray jsonArray); static JsonObject parseObject(std::string text); - static JsonArray parseArray(std::string text); + static JsonArray parseArray(std::string text); }; // JSON @@ -33,17 +33,17 @@ class JSON { */ class JsonArray { public: - JsonArray(cJSON *node); - int getInt(int item); - JsonObject getObject(int item); + JsonArray(cJSON* node); + int getInt(int item); + JsonObject getObject(int item); std::string getString(int item); - bool getBoolean(int item); - double getDouble(int item); - void addBoolean(bool value); - void addDouble(double value); - void addInt(int value); - void addObject(JsonObject value); - void addString(std::string value); + bool getBoolean(int item); + double getDouble(int item); + void addBoolean(bool value); + void addDouble(double value); + void addInt(int value); + void addObject(JsonObject value); + void addString(std::string value); std::string toString(); std::size_t size(); /** @@ -58,23 +58,25 @@ class JsonArray { */ class JsonObject { public: - JsonObject(cJSON *node); - int getInt(std::string name); - JsonObject getObject(std::string name); + JsonObject(cJSON* node); + bool getBoolean(std::string name); + double getDouble(std::string name); + int getInt(std::string name); + JsonObject getObject(std::string name); std::string getString(std::string name); - bool getBoolean(std::string name); - double getDouble(std::string name); - void setArray(std::string name, JsonArray array); - void setBoolean(std::string name, bool value); - void setDouble(std::string name, double value); - void setInt(std::string name, int value); - void setObject(std::string name, JsonObject value); - void setString(std::string name, std::string value); + bool hasItem(std::string name); + void setArray(std::string name, JsonArray array); + void setBoolean(std::string name, bool value); + void setDouble(std::string name, double value); + void setInt(std::string name, int value); + void setObject(std::string name, JsonObject value); + void setString(std::string name, std::string value); std::string toString(); + /** * @brief The underlying cJSON node. */ - cJSON *m_node; + cJSON* m_node; }; // JsonObject diff --git a/cpp_utils/OV7670.cpp b/cpp_utils/OV7670.cpp index c7e395ce..9e4fa7a1 100644 --- a/cpp_utils/OV7670.cpp +++ b/cpp_utils/OV7670.cpp @@ -354,10 +354,12 @@ void OV7670::dump() { } } // dump +/* static void log(char *marker) { ESP_LOGD(LOG_TAG, "%s", marker); FreeRTOS::sleep(100); } +*/ /** * @brief Initialize the camera. diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 0d81b696..a4b38a72 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -249,13 +249,15 @@ std::string Socket::readToDelim(std::string delim) { /** * @brief Receive data from the partner. - * + * Receive data from the socket partner. If exact = false, we read as much data as + * is available without blocking up to length. If exact = true, we will block until + * we have received exactly length bytes or there are no more bytes to read. * @param [in] data The buffer into which the received data will be stored. * @param [in] length The size of the buffer. - * @param [in] exact Read exactly this amount? + * @param [in] exact Read exactly this amount. * @return The length of the data received or -1 on an error. */ -int Socket::receive_cpp(uint8_t* data, size_t length, bool exact) { +size_t Socket::receive_cpp(uint8_t* data, size_t length, bool exact) { //ESP_LOGD(LOG_TAG, ">> receive_cpp: length: %d, exact: %d", length, exact); if (exact == false) { int rc = ::recv(m_sock, data, length, 0); @@ -272,6 +274,9 @@ int Socket::receive_cpp(uint8_t* data, size_t length, bool exact) { ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); return 0; } + if (rc == 0) { + break; + } amountToRead -= rc; data+= rc; } @@ -304,7 +309,7 @@ int Socket::receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr *pAddr */ int Socket::send_cpp(const uint8_t* data, size_t length) const { ESP_LOGD(LOG_TAG, "send_cpp: Raw binary of length: %d", length); - GeneralUtils::hexDump(data, length); + //GeneralUtils::hexDump(data, length); int rc = ::send(m_sock, data, length, 0); if (rc == -1) { ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); @@ -366,29 +371,37 @@ std::string Socket::toString() { * @param [in] dataLength The size of a record. * @param [in] bufferSize The size of the buffer we wish to allocate to hold data. */ -SocketInputRecordStream::SocketInputRecordStream( - Socket* socket, - size_t dataLength, - size_t bufferSize) { - m_pSocket = socket; // The socket we will be reading from +SocketInputRecordStreambuf::SocketInputRecordStreambuf( + Socket socket, + size_t dataLength, + size_t bufferSize) { + m_socket = socket; // The socket we will be reading from m_dataLength = dataLength; // The size of the record we wish to read. m_bufferSize = bufferSize; // The size of the buffer used to hold data m_sizeRead = 0; // The size of data read from the socket m_buffer = new char[bufferSize]; // Create the buffer used to hold the data read from the socket. setg(m_buffer, m_buffer, m_buffer); // Set the initial get buffer pointers to no data. -} // SocketInputRecordStream +} // SocketInputRecordStreambuf + +SocketInputRecordStreambuf::~SocketInputRecordStreambuf() { + delete[] m_buffer; +} // ~SocketInputRecordStreambuf /** * @brief Handle the request to read data from the stream but we need more data from the source. * */ -SocketInputRecordStream::int_type SocketInputRecordStream::underflow() { - return 0; +SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() { + if (m_sizeRead >= m_dataLength) { + return EOF; + } + int bytesRead = m_socket.receive_cpp((uint8_t*)m_buffer, m_bufferSize, true); + if (bytesRead == 0) { + return EOF; + } + m_sizeRead += bytesRead; + setg(m_buffer, m_buffer, m_buffer + bytesRead); + return traits_type::to_int_type(*gptr()); } // underflow - - -SocketInputRecordStream::~SocketInputRecordStream() { - delete[] m_buffer; -} // ~SocketInputRecordStream diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index f97ecd28..3219b770 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -42,7 +42,7 @@ class Socket { void listen_cpp(uint16_t port, bool isDatagram=false); bool operator<(const Socket& other) const; std::string readToDelim(std::string delim); - int receive_cpp(uint8_t* data, size_t length, bool exact=false); + size_t receive_cpp(uint8_t* data, size_t length, bool exact=false); int receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr* pAddr); int send_cpp(std::string value) const; int send_cpp(const uint8_t* data, size_t length) const; @@ -56,14 +56,14 @@ class Socket { int m_sock; }; -class SocketInputRecordStream : public std::streambuf { +class SocketInputRecordStreambuf : public std::streambuf { public: - SocketInputRecordStream(Socket* socket, size_t dataLength, size_t bufferSize=512); - ~SocketInputRecordStream(); + SocketInputRecordStreambuf(Socket socket, size_t dataLength, size_t bufferSize=512); + ~SocketInputRecordStreambuf(); int_type underflow(); private: char *m_buffer; - Socket* m_pSocket; + Socket m_socket; size_t m_dataLength; size_t m_bufferSize; size_t m_sizeRead; diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp index 5c18039c..61192ef4 100644 --- a/cpp_utils/WebSocket.cpp +++ b/cpp_utils/WebSocket.cpp @@ -11,6 +11,12 @@ #include "GeneralUtils.h" #include +extern "C" { + extern uint16_t lwip_ntohs(uint16_t); + extern uint32_t lwip_ntohl(uint32_t); + extern uint16_t lwip_htons(uint16_t); + extern uint32_t lwip_htonl(uint32_t); +} static const char* LOG_TAG = "WebSocket"; // WebSocket op codes as found in a WebSocket frame. @@ -87,108 +93,106 @@ static void dumpFrame(Frame frame) { * incoming asynchronous events. We spawn a task to do this. This is the implementation of that task. */ class WebSocketReader: public Task { +public: + WebSocketReader() { + m_end = false; + } + void end() { + m_end = true; + } +private: + bool m_end; /** * @brief Loop over the web socket waiting for new input. * @param [in] data A pointer to an instance of the WebSocket. */ void run(void* data) { - WebSocket *pWebSocket = (WebSocket*) data; + WebSocket* pWebSocket = (WebSocket*) data; ESP_LOGD("WebSocketReader", "WebSocketReader Task started, socket: %s", pWebSocket->getSocket().toString().c_str()); - uint8_t buffer[1000]; Socket peerSocket = pWebSocket->getSocket(); + Frame frame; while(1) { + if (m_end) { + break; + } ESP_LOGD("WebSocketReader", "Waiting on socket data for socket %s", peerSocket.toString().c_str()); - int length = peerSocket.receive_cpp(buffer, sizeof(buffer)); - if (length == -1 || length == 0) { + int length = peerSocket.receive_cpp((uint8_t*)&frame, sizeof(frame), true); // Read exact + if (length != sizeof(frame)) { ESP_LOGD(LOG_TAG, "Socket read error"); pWebSocket->close_cpp(); return; } ESP_LOGD("WebSocketReader", "Received data from web socket. Length: %d", length); - GeneralUtils::hexDump(buffer, length); - dumpFrame(*(Frame *)buffer); + dumpFrame(frame); // The following section parses the WebSocket frame. - if (length > 0) { - Frame* pFrame = (Frame*)buffer; - uint32_t payloadLen = 0; - uint8_t* pMask = nullptr; - uint8_t* pData; - if (pFrame->len < 126) { - payloadLen = pFrame->len; - pMask = buffer + 2; - } else if (pFrame->len == 126) { - payloadLen = *(uint16_t*)(buffer+2); - pMask = buffer + 4; - } else if (pFrame->len == 127) { - ESP_LOGE(LOG_TAG, "Too much data!"); - return; - } - if (pFrame->mask == 1) { - pData = pMask + 4; - for (int i=0; igetHandler(); - switch(pFrame->opCode) { - case OPCODE_BINARY: { - if (pWebSocketHandler != nullptr) { - pWebSocketHandler->onMessage(payloadData); - } - break; + WebSocketHandler *pWebSocketHandler = pWebSocket->getHandler(); + switch(frame.opCode) { + case OPCODE_TEXT: + case OPCODE_BINARY: { + if (pWebSocketHandler != nullptr) { + WebSocketInputRecordStreambuf streambuf(pWebSocket->getSocket(), payloadLen, frame.mask==1?mask:nullptr); + pWebSocketHandler->onMessage(&streambuf); + //streambuf.discard(); } + break; + } - case OPCODE_CLOSE: { - pWebSocket->m_receivedClose = true; - if (pWebSocketHandler != nullptr) { - pWebSocketHandler->onClose(); - pWebSocket->close_cpp(); - } - break; + case OPCODE_CLOSE: { + pWebSocket->m_receivedClose = true; + if (pWebSocketHandler != nullptr) { + pWebSocketHandler->onClose(); + pWebSocket->close_cpp(); } + break; + } - case OPCODE_CONTINUE: { - break; - } + case OPCODE_CONTINUE: { + break; + } - case OPCODE_PING: { - break; - } + case OPCODE_PING: { + break; + } - case OPCODE_PONG: { - break; - } + case OPCODE_PONG: { + break; + } - case OPCODE_TEXT: { - if (pWebSocketHandler != nullptr) { - pWebSocketHandler->onMessage(payloadData); - } - break; - } - default: { - ESP_LOGD("WebSocketReader", "Unknown opcode: %d", pFrame->opCode); - break; - } - } // Switch opCode - } // Length of data > 0 + default: { + ESP_LOGD("WebSocketReader", "Unknown opcode: %d", frame.opCode); + break; + } + } // Switch opCode + } // While (1) + ESP_LOGD("WebSocketReader", "<< run"); } // run }; // WebSocketReader @@ -198,17 +202,22 @@ class WebSocketReader: public Task { * If no over-riding handler is provided for the "close" event, this method is called. */ void WebSocketHandler::onClose() { - ESP_LOGD(LOG_TAG, ">> WebSocketHandler:onClose()"); + ESP_LOGD("WebSocketHandler", ">> WebSocketHandler:onClose()"); } // onClose /** * @brief The default onData handler. * If no over-riding handler is provided for the "message" event, this method is called. + * A particularly useful pattern for using onMessage is: + * ``` + * std::stringstream buffer; + * buffer << pWebSocketInputRecordStreambuf; + * ``` + * This will read the whole message into the string stream. */ -void WebSocketHandler::onMessage(std::string data) { - ESP_LOGD(LOG_TAG, ">> WebSocketHandler:onMessage(), length: %d", data.length()); - GeneralUtils::hexDump((uint8_t*)data.data(), data.length()); +void WebSocketHandler::onMessage(WebSocketInputRecordStreambuf* pWebSocketInputRecordStreambuf) { + ESP_LOGD("WebSocketHandler", ">> onMessage") } // onData @@ -217,19 +226,22 @@ void WebSocketHandler::onMessage(std::string data) { * If no over-riding handler is provided for the "error" event, this method is called. */ void WebSocketHandler::onError(std::string error) { - ESP_LOGD(LOG_TAG, ">> WebSocketHandler:onError()"); + ESP_LOGD("WebSocketHandler", ">> WebSocketHandler:onError()"); } // onError WebSocket::WebSocket(Socket socket) { - m_receivedClose = false; - m_sentClose = false; - m_socket = socket; - m_pWebSockerReader = new WebSocketReader(); + m_receivedClose = false; + m_sentClose = false; + m_socket = socket; + m_pWebSockerReader = new WebSocketReader(); + m_pWebSocketHandler = nullptr; } // WebSocket WebSocket::~WebSocket() { + m_pWebSockerReader->stop(); + delete m_pWebSockerReader; } // ~WebSocket @@ -240,6 +252,7 @@ void WebSocket::close_cpp(uint16_t status, std::string message) { ESP_LOGD(LOG_TAG, ">> close_cpp(): status: %d, message: %s", status, message.c_str()); if (m_sentClose) { m_socket.close_cpp(); + m_pWebSockerReader->end(); return; } m_sentClose = true; @@ -261,6 +274,7 @@ void WebSocket::close_cpp(uint16_t status, std::string message) { } if (m_receivedClose || rc == 0 || rc == -1) { m_socket.close_cpp(); + m_pWebSockerReader->end(); } } // close_cpp @@ -271,7 +285,7 @@ void WebSocket::close_cpp(uint16_t status, std::string message) { * event received over the network needs to be handled by user code. */ WebSocketHandler* WebSocket::getHandler() { - return &m_webSocketHandler; + return m_pWebSocketHandler; } // getHandler @@ -320,8 +334,8 @@ void WebSocket::send_cpp(std::string data, uint8_t sendType) { * events. An instance of WebSocketHandler is passed in. * */ -void WebSocket::setHandler(WebSocketHandler handler) { - m_webSocketHandler = handler; +void WebSocket::setHandler(WebSocketHandler *pHandler) { + m_pWebSocketHandler = pHandler; } // setHandler @@ -334,3 +348,111 @@ void WebSocket::startReader() { ESP_LOGD(LOG_TAG, ">> startReader: Socket: %s", m_socket.toString().c_str()); m_pWebSockerReader->start(this); } // startReader + + +/** + * @brief Create a Web Socket input record streambuf + * @param [in] socket The socket we will be reading from. + * @param [in] dataLength The size of a record. + * @param [in] bufferSize The size of the buffer we wish to allocate to hold data. + */ +WebSocketInputRecordStreambuf::WebSocketInputRecordStreambuf( + Socket socket, + size_t dataLength, + uint8_t *pMask, + size_t bufferSize) { + m_socket = socket; // The socket we will be reading from + m_dataLength = dataLength; // The size of the record we wish to read. + m_pMask = pMask; + m_bufferSize = bufferSize; // The size of the buffer used to hold data + m_sizeRead = 0; // The size of data read from the socket + m_buffer = new char[bufferSize]; // Create the buffer used to hold the data read from the socket. + + setg(m_buffer, m_buffer, m_buffer); // Set the initial get buffer pointers to no data. +} // WebSocketInputRecordStreambuf + + +/** + * @brief Destructor + */ +WebSocketInputRecordStreambuf::~WebSocketInputRecordStreambuf() { + delete[] m_buffer; + discard(); +} // ~WebSocketInputRecordStreambuf + + +/** + * @brief Discard data for the record that has not yet been read. + * + * We are working on a logical fixed length record in a socket stream. This means that we know in advance + * how big the record should be. If we have read some data from the stream and no longer wish to consume + * any further, we have to discard the remaining bytes in the stream before we can get to process the + * next record. This function discards the remainder of the data. + * + * For example, if our record size is 1000 bytes and we have read 700 bytes and determine that we no + * longer need to continue, we can't just stop. There are still 300 bytes in the socket stream that + * need to be consumed/discarded before we can move on to the next record. + */ +void WebSocketInputRecordStreambuf::discard() { + uint8_t byte; + ESP_LOGD("WebSocketInputRecordStreambuf", ">> discard: Discarding %d bytes", m_dataLength - m_sizeRead); + while(m_sizeRead < m_dataLength) { + m_socket.receive_cpp(&byte, 1); + m_sizeRead++; + } + ESP_LOGD("WebSocketInputRecordStreambuf", "<< discard"); +} // discard + + +/** + * @brief Get the size of the expected record. + * @return The size of the expected record. + */ +size_t WebSocketInputRecordStreambuf::getRecordSize() { + return m_dataLength; +} // getRecordSize + + +/** + * @brief Handle the request to read data from the stream but we need more data from the source. + * + */ +WebSocketInputRecordStreambuf::int_type WebSocketInputRecordStreambuf::underflow() { + ESP_LOGD("WebSocketInputRecordStreambuf", ">> underflow"); + if (m_sizeRead >= getRecordSize()) { + ESP_LOGD("WebSocketInputRecordStreambuf", "<< underflow: Already read maximum"); + return EOF; + } + + // We wish to refill the buffer. We want to read data from the socket. We want to read either + // the size of the buffer to fill it or the maximum number of bytes remaining to be read. + // We will choose which ever is smaller as the number of bytes to read into the buffer. + int remainingBytes = getRecordSize()-m_sizeRead; + size_t sizeToRead; + if (remainingBytes < m_bufferSize) { + sizeToRead = remainingBytes; + } else { + sizeToRead = m_bufferSize; + } + + ESP_LOGD("WebSocketInputRecordStreambuf", "- getting next buffer of data; size request: %d", sizeToRead); + int bytesRead = m_socket.receive_cpp((uint8_t*)m_buffer, sizeToRead, true); + if (bytesRead == 0) { + ESP_LOGD("WebSocketInputRecordStreambuf", "<< underflow: Read 0 bytes"); + return EOF; + } + + if (m_pMask != nullptr) { + for (int i=0; i #include "Socket.h" +class WebSocketReader; + +// +-------------------------------+ +// | WebSocketInputRecordStreambuf | +// +-------------------------------+ +class WebSocketInputRecordStreambuf : public std::streambuf { +public: + WebSocketInputRecordStreambuf( + Socket socket, + size_t dataLength, + uint8_t* pMask=nullptr, + size_t bufferSize=2048); + ~WebSocketInputRecordStreambuf(); + int_type underflow(); + void discard(); + size_t getRecordSize(); +private: + char* m_buffer; + Socket m_socket; + size_t m_dataLength; + size_t m_bufferSize; + size_t m_sizeRead; + uint8_t* m_pMask; +}; + +// +------------------+ +// | WebSocketHandler | +// +------------------+ class WebSocketHandler { public: + virtual ~WebSocketHandler(); virtual void onClose(); - virtual void onMessage(std::string data); + virtual void onMessage(WebSocketInputRecordStreambuf *pWebSocketInputRecordStreambuf); virtual void onError(std::string error); }; -class WebSocketReader; + +// +-----------+ +// | WebSocket | +// +-----------+ class WebSocket { private: friend class WebSocketReader; @@ -26,10 +58,9 @@ class WebSocket { bool m_receivedClose; // True when we have received a close request. bool m_sentClose; // True when we have sent a close request. Socket m_socket; // Partner socket. - WebSocketHandler m_webSocketHandler; + WebSocketHandler *m_pWebSocketHandler; WebSocketReader *m_pWebSockerReader; - public: static const uint16_t CLOSE_NORMAL_CLOSURE = 1000; static const uint16_t CLOSE_GOING_AWAY = 1001; @@ -51,11 +82,12 @@ class WebSocket { WebSocket(Socket socket); virtual ~WebSocket(); - void close_cpp(uint16_t status=CLOSE_NORMAL_CLOSURE, std::string message = ""); + + void close_cpp(uint16_t status=CLOSE_NORMAL_CLOSURE, std::string message = ""); WebSocketHandler* getHandler(); - Socket getSocket(); - void send_cpp(std::string data, uint8_t sendType = SEND_TYPE_BINARY); - void setHandler(WebSocketHandler handler); -}; + Socket getSocket(); + void send_cpp(std::string data, uint8_t sendType = SEND_TYPE_BINARY); + void setHandler(WebSocketHandler *handler); +}; // WebSocket #endif /* COMPONENTS_WEBSOCKET_H_ */ diff --git a/cpp_utils/WebSocketFileTransfer.cpp b/cpp_utils/WebSocketFileTransfer.cpp new file mode 100644 index 00000000..2ada459b --- /dev/null +++ b/cpp_utils/WebSocketFileTransfer.cpp @@ -0,0 +1,112 @@ +/* + * WebSocketFileTransfer.cpp + * + * Created on: Sep 9, 2017 + * Author: kolban + */ +#include +#include +#include +#include +#include "JSON.h" +static const char* LOG_TAG = "WebSocketFileTransfer"; + +#include "WebSocketFileTransfer.h" + +#undef close + +WebSocketFileTransfer::WebSocketFileTransfer() { + m_fileName = ""; + m_length = 0; + m_pWebSocket = nullptr; +} + +WebSocketFileTransfer::~WebSocketFileTransfer() { + // TODO Auto-generated destructor stub +} + + +// Hide the class in an un-named namespace +namespace { + +class FileTransferWebSocketHandler : public WebSocketHandler { +public: + FileTransferWebSocketHandler() { + m_fileName = ""; + m_fileLength = 0; + m_sizeReceived = 0; + m_active = false; + } + +private: + std::string m_fileName; // The name of the file we are receiving. + uint32_t m_fileLength; // We may optionally receive a file length. + uint32_t m_sizeReceived; // The size of the data actually received so far. + bool m_active; // Are we actively processing a file. + std::ofstream m_ofStream; + + virtual void onMessage(WebSocketInputRecordStreambuf* pWebSocketInputRecordStreambuf) { + ESP_LOGD("FileTransferWebSocketHandler", ">> onMessage"); + if (!m_active) { + std::stringstream buffer; + buffer << pWebSocketInputRecordStreambuf; + ESP_LOGD("FileTransferWebSocketHandler", "Data read: %s", buffer.str().c_str()); + JsonObject jo = JSON::parseObject(buffer.str()); + m_fileName = jo.getString("name"); + if (jo.hasItem("length")) { + m_fileLength = jo.getInt("length"); + } + std::string fileName = "/spiflash/" + m_fileName; + if (m_fileName.length() > 0 && m_fileName.substr(m_fileName.size()-1)=="/") { + ESP_LOGD("FileTransferWebSocketHandler", "Is a directory!!"); + fileName = fileName.substr(0, fileName.size()-1); + struct stat statbuf; + if (stat(fileName.c_str(), &statbuf) == 0) { + if (S_ISREG(statbuf.st_mode)) { + ESP_LOGE("FileTransferWebSocketHandler", "File already exists and is a file not a directory!"); + } + } + if (mkdir(fileName.c_str(), 0) != 0) { + ESP_LOGE("FileTransferWebSocketHandler", "Failed to make directory \"%s\", error: %s", fileName.c_str(), strerror(errno)); + } + } else { + m_ofStream.open(fileName, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); + if (!m_ofStream.is_open()) { + ESP_LOGE("FileTransferWebSocketHandler", "Failed to open file %s", m_fileName.c_str()); + } + } + m_active = true; + ESP_LOGD("FileTransferWebSocketHandler", "Filename: %s, length: %d", fileName.c_str(), m_fileLength); + } else { + // We are about to receive a chunk of file + m_ofStream << pWebSocketInputRecordStreambuf; + /* + std::stringstream bufferStream; + bufferStream << pWebSocketInputRecordStreambuf; + m_sizeReceived += bufferStream.str().length(); + ESP_LOGD("FileTransferWebSocketHandler", "Received %d bytes of file data", bufferStream.str().length()); + if (m_fileLength > 0 && m_sizeReceived > m_fileLength) { + ESP_LOGD("FileTransferWebSocketHandler", + "ERROR: Received a total of %d bytes when only %d bytes expected!", m_sizeReceived, m_fileLength); + } + */ + } + } // onMessage + + + virtual void onClose() { + ESP_LOGD("FileTransferWebSocketHandler", ">> onClose: fileName: %s, sizeReceived: %d", m_fileName.c_str(), m_sizeReceived); + if (m_fileLength > 0 && m_sizeReceived != m_fileLength) { + ESP_LOGD("FileTransferWebSocketHandler", + "ERROR: Transfer finished but we received total of %d bytes and expected %d bytes!", m_sizeReceived, m_fileLength); + } + m_ofStream.close(); + } // onClose +}; // FileTransferWebSocketHandler + +} // End un-named namespace + +void WebSocketFileTransfer::start(WebSocket* pWebSocket) { + ESP_LOGD(LOG_TAG, ">> start"); + pWebSocket->setHandler(new FileTransferWebSocketHandler()); +} // start diff --git a/cpp_utils/WebSocketFileTransfer.h b/cpp_utils/WebSocketFileTransfer.h new file mode 100644 index 00000000..d3db986b --- /dev/null +++ b/cpp_utils/WebSocketFileTransfer.h @@ -0,0 +1,24 @@ +/* + * WebSocketFileTransfer.h + * + * Created on: Sep 9, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_WEBSOCKETFILETRANSFER_H_ +#define COMPONENTS_CPP_UTILS_WEBSOCKETFILETRANSFER_H_ +#include +#include "WebSocket.h" + +class WebSocketFileTransfer { +private: + std::string m_fileName; + size_t m_length; + WebSocket* m_pWebSocket; +public: + WebSocketFileTransfer(); + virtual ~WebSocketFileTransfer(); + void start(WebSocket *pWebSocket); +}; + +#endif /* COMPONENTS_CPP_UTILS_WEBSOCKETFILETRANSFER_H_ */ diff --git a/filesystems/sendZip/ws_send_zip.js b/filesystems/sendZip/ws_send_zip.js new file mode 100644 index 00000000..67279218 --- /dev/null +++ b/filesystems/sendZip/ws_send_zip.js @@ -0,0 +1,63 @@ +// https://www.npmjs.com/package/adm-zip +// https://nodejs.org/api/stream.html +// https://nodejs.org/api/net.html +//API docs ...https://github.com/websockets/ws/blob/master/doc/ws.md + +const WebSocket = require("ws"); +var AdmZip = require("adm-zip"); + +function *processZip(zipFileName) { + var zip = new AdmZip("./foo.zip"); + var zipEntries = zip.getEntries(); + for (var i=0; i, // The file name to send + * "data": , // The content of the file + * } + * ``` + * @returns N/A + */ +function sendZipEntry(entry) { + const ws = new WebSocket("ws://192.168.1.99:9080/upload"); + ws.on("open", ()=>{ + console.log("Sending file: " + entry.name); + var file = { + "name": entry.name + //"length": 100 + } + ws.send(JSON.stringify(file)); + ws.send(entry.data); + ws.close(); + }); + + ws.on("error", (error)=>{ + console.log("Error: %O", error); + }); + + ws.on("close", (code)=>{ + console.log("WS closed"); + var nextEntry = it.next(); + if (!nextEntry.done) { + sendZipEntry(nextEntry.value); + } + }); +} // sendZipEntry + + +var it = processZip("./foo.zip"); +var nextZip = it.next(); +if (!nextZip.done) { + sendZipEntry(nextZip.value); +} From fc2c344f2cfde28266b845af1928023e08b4f703 Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 11 Sep 2017 23:25:04 -0500 Subject: [PATCH 039/381] Addition of C++ Http Server --- Documentation/C++ HTTP Server.pdf | Bin 0 -> 81135 bytes LICENSE | 2 +- .../Arduino_ESP32_BLE.library.properties | 9 +- cpp_utils/HttpParser.cpp | 6 +- cpp_utils/HttpRequest.cpp | 65 ++++++++-- cpp_utils/HttpRequest.h | 36 +++--- cpp_utils/HttpResponse.cpp | 59 +++++++-- cpp_utils/HttpResponse.h | 31 ++--- cpp_utils/HttpServer.cpp | 85 ++++++++----- cpp_utils/HttpServer.h | 39 +++--- cpp_utils/SockServ.cpp | 8 +- cpp_utils/Socket.cpp | 101 +++++++-------- cpp_utils/Socket.h | 34 +++-- cpp_utils/TFTP.cpp | 21 ++-- cpp_utils/WebSocket.cpp | 117 +++++++++++------- cpp_utils/WebSocket.h | 17 +-- cpp_utils/WebSocketFileTransfer.cpp | 6 +- cpp_utils/library.properties | 11 ++ 18 files changed, 407 insertions(+), 240 deletions(-) create mode 100755 Documentation/C++ HTTP Server.pdf create mode 100644 cpp_utils/library.properties diff --git a/Documentation/C++ HTTP Server.pdf b/Documentation/C++ HTTP Server.pdf new file mode 100755 index 0000000000000000000000000000000000000000..1f68419d3ab54c42bf3110cd81405fd5f85fc044 GIT binary patch literal 81135 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 maintainer=Neil Kolban sentence=BLE functions for ESP32 paragraph=BLE functions for ESP32 category=Communication -url=http://example.com/ +url=https://github.com/nkolban/ESP32_BLE_Arduino architectures=esp32 -includes=BLE.h BLEUtils.h BLEScan.h BLEAdvertisedDevice.h \ No newline at end of file +includes=BLE.h BLEUtils.h BLEScan.h BLEAdvertisedDevice.h +dot_a_linkage=true \ No newline at end of file diff --git a/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp index 91374318..406a5584 100644 --- a/cpp_utils/HttpParser.cpp +++ b/cpp_utils/HttpParser.cpp @@ -12,6 +12,8 @@ #include "HttpRequest.h" #include + +#undef close /** * RFC7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing * RFC3986 - URI @@ -209,11 +211,11 @@ void HttpParser::parse(Socket s) { std::string val = getHeader(HttpRequest::HTTP_HEADER_CONTENT_LENGTH); int length = std::atoi(val.c_str()); uint8_t data[length]; - s.receive_cpp(data, length, true); + s.receive(data, length, true); m_body = std::string((char *)data, length); } else { uint8_t data[512]; - int rc = s.receive_cpp(data, sizeof(data)); + int rc = s.receive(data, sizeof(data)); m_body = std::string((char *)data, rc); } ESP_LOGD(LOG_TAG, "Size of body: %d", m_body.length()); diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index 265d4ef9..4866f4ca 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -72,8 +72,10 @@ const std::string HttpRequest::HTTP_METHOD_PUT = "PUT"; - -std::string buildResponseHash(std::string requestKey) { +/** + * @brief Build a WebSockey response has. + */ +std::string buildWebsocketKeyResponseHash(std::string requestKey) { std::string newKey = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; uint8_t shaData[20]; esp_sha(SHA1, (uint8_t*)newKey.data(), newKey.length(), shaData); @@ -81,13 +83,16 @@ std::string buildResponseHash(std::string requestKey) { std::string retStr; GeneralUtils::base64Encode(std::string((char*)shaData, sizeof(shaData)), &retStr); return retStr; -} +} // buildWebsocketKeyResponseHash +/** + * @brief Create an HTTP Request instance. + */ HttpRequest::HttpRequest(Socket clientSocket) { m_clientSocket = clientSocket; - m_status = 0; m_pWebSocket = nullptr; + m_isClosed = false; m_parser.parse(clientSocket); // Parse the socket stream to build the HTTP data. @@ -102,19 +107,19 @@ HttpRequest::HttpRequest(Socket clientSocket) { // do something // Process the web socket request - // Send the response HTTP message to switch to being a Web Socket + // Build and send the response HTTP message to switch to being a Web Socket HttpResponse response(this); response.setStatus(HttpResponse::HTTP_STATUS_SWITCHING_PROTOCOL, "Switching Protocols"); response.addHeader(HTTP_HEADER_UPGRADE, "websocket"); response.addHeader(HTTP_HEADER_CONNECTION, "Upgrade"); response.addHeader(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, - buildResponseHash(getHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY))); + buildWebsocketKeyResponseHash(getHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY))); response.sendData(""); + + // Now that we have converted the request into a WebSocket, create the new WebSocket entry. m_pWebSocket = new WebSocket(clientSocket); - } else { - ESP_LOGD(LOG_TAG, "Not a Websocket"); - } + } // if this is a web socket ... } // HttpRequest @@ -122,11 +127,21 @@ HttpRequest::~HttpRequest() { } // ~HttpRequest +/** + * @brief Close the HttpRequest + */ void HttpRequest::close() { - m_clientSocket.close_cpp(); + if (isWebsocket()) { + ESP_LOGW(LOG_TAG, "Request to close an HTTP Request but we think it is a web socket!"); + } + m_clientSocket.close(); + m_isClosed = true; } // close_cpp +/** + * @brief Dump the HttpRequest for debugging purposes. + */ void HttpRequest::dump() { ESP_LOGD(LOG_TAG, "Method: %s, URL: \"%s\", Version: %s", getMethod().c_str(), getPath().c_str(), getVersion().c_str()); auto headers = getHeaders(); @@ -138,11 +153,19 @@ void HttpRequest::dump() { } // dump +/** + * @brief Get the body of the HttpRequest. + */ std::string HttpRequest::getBody() { return m_parser.getBody(); } // getBody +/** + * @brief Get the named header. + * @param [in] name The name of the header field to retrieve. + * @return The value of the header field. + */ std::string HttpRequest::getHeader(std::string name) { return m_parser.getHeader(name); } // getHeader @@ -163,7 +186,7 @@ std::string HttpRequest::getPath() { } // getPath -#define STATE_NAME 0 +#define STATE_NAME 0 #define STATE_VALUE 1 /** * @brief Get the query part of the request. @@ -176,7 +199,6 @@ std::map HttpRequest::getQuery() { // that lets us know what we are parsing. std::map queryMap; std::string queryString = ""; - int i=0; /* * We maintain a simple state machine with states of: @@ -187,7 +209,7 @@ std::map HttpRequest::getQuery() { std::string name = ""; std::string value; // Loop through each character in the query string. - for (i=0; i HttpRequest::getQuery() { } // getQuery + +/** + * @brief Get the underlying socket. + * @return The underlying socket. + */ Socket HttpRequest::getSocket() { return m_clientSocket; } // getSocket @@ -225,11 +252,21 @@ std::string HttpRequest::getVersion() { return m_parser.getVersion(); } // getVersion + WebSocket* HttpRequest::getWebSocket() { return m_pWebSocket; } // getWebSocket +/** + * @brief Determine if the request is closed. + * @return Returns true if the request is closed. + */ +bool HttpRequest::isClosed() { + return m_isClosed; +} // isClosed + + /** * @brief Determine if this request represents a WebSocket * @return True if the request creates a web socket. @@ -238,6 +275,7 @@ bool HttpRequest::isWebsocket() { return m_pWebSocket != nullptr; } // isWebsocket + /** * @brief Return the constituent parts of the path. * If we imagine a path as composed of parts separated by slashes, then this function @@ -270,3 +308,4 @@ std::vector HttpRequest::pathSplit() { } return ret; } // pathSplit + diff --git a/cpp_utils/HttpRequest.h b/cpp_utils/HttpRequest.h index 398e02ed..246da4b4 100644 --- a/cpp_utils/HttpRequest.h +++ b/cpp_utils/HttpRequest.h @@ -18,10 +18,11 @@ class HttpRequest { private: - Socket m_clientSocket; - HttpParser m_parser; - int m_status; - WebSocket *m_pWebSocket; + Socket m_clientSocket; // The socket connected to the client. + bool m_isClosed; // Is the client connection closed? + HttpParser m_parser; // The parse to parse HTTP data. + WebSocket* m_pWebSocket; // A possible reference to a WebSocket object instance. + public: HttpRequest(Socket s); @@ -51,19 +52,20 @@ class HttpRequest { static const std::string HTTP_METHOD_POST; static const std::string HTTP_METHOD_PUT; - void close(); - void dump(); - std::string getBody(); - std::string getHeader(std::string name); - std::map getHeaders(); - std::string getMethod(); - std::string getPath(); - std::map getQuery(); - Socket getSocket(); - std::string getVersion(); - WebSocket* getWebSocket(); - bool isWebsocket(); - std::vector pathSplit(); + void close(); // Close the connection to the client. + void dump(); // Diagnostic dump of the Http request. + std::string getBody(); // Get the body of the request. + std::string getHeader(std::string name); // Get the value of a named header. + std::map getHeaders(); // Get all the headers. + std::string getMethod(); // Get the request method. + std::string getPath(); // Get the request path. + std::map getQuery(); // Get the query part of the request. + Socket getSocket(); // Get the underlying TCP/IP socket. + std::string getVersion(); // Get the HTTP version. + WebSocket* getWebSocket(); // Get the WebSocket reference if this is a web socket. + bool isClosed(); // Has the connection been closed? + bool isWebsocket(); // Is this request to create a web socket? + std::vector pathSplit(); }; #endif /* COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ */ diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp index 4063932d..ee2acc72 100644 --- a/cpp_utils/HttpResponse.cpp +++ b/cpp_utils/HttpResponse.cpp @@ -9,7 +9,7 @@ #include "HttpResponse.h" #include -//static const char* LOG_TAG = "HttpResponse"; +static const char* LOG_TAG = "HttpResponse"; const int HttpResponse::HTTP_STATUS_CONTINUE = 100; const int HttpResponse::HTTP_STATUS_SWITCHING_PROTOCOL = 101; @@ -32,7 +32,6 @@ HttpResponse::HttpResponse(HttpRequest *request) { } HttpResponse::~HttpResponse() { - // TODO Auto-generated destructor stub } @@ -50,11 +49,25 @@ void HttpResponse::addHeader(const std::string name, const std::string value) { } // addHeader +/** + * @brief Close the response. + * We close the response. If we haven't yet sent the header, we send that now and then close + * the socket. + */ void HttpResponse::close() { + // If we haven't yet sent the header of the data, send that now. + if (m_headerCommitted == false) { + sendHeader(); + } m_request->close(); } // close +/** + * @brief Get the value of the named header. + * @param [in] name The name of the header for which the value is to be returned. + * @return The value of the named header. + */ std::string HttpResponse::getHeader(std::string name) { if (m_responseHeaders.find(name) == m_responseHeaders.end()) { return ""; @@ -70,10 +83,31 @@ std::map HttpResponse::getHeaders() { /** * @brief Send data to the partner. - * Send some data to the partner. If we haven't yet sent the HTTP header then send that now. + * Send some data to the partner. If we haven't yet sent the HTTP header then send that now. We can call this function + * any number of times before calling close. After calling close, we may not call this function again. * @param [in] data The data to send to the partner. */ void HttpResponse::sendData(std::string data) { + // If the request is already closed, nothing further to do. + if (m_request->isClosed()) { + ESP_LOGE(LOG_TAG, "Request to send more data but the request/response is already closed"); + return; + } + + // If we haven't yet sent the header of the data, send that now. + if (m_headerCommitted == false) { + sendHeader(); + } + + // Send the payload data. + m_request->getSocket().send(data); +} // sendData + + +/** + * @brief Send the header + */ +void HttpResponse::sendHeader() { // If we haven't yet sent the header of the data, send that now. if (m_headerCommitted == false) { std::ostringstream oss; @@ -83,19 +117,24 @@ void HttpResponse::sendData(std::string data) { } oss << lineTerminator; m_headerCommitted = true; - m_request->getSocket().send_cpp(oss.str()); + m_request->getSocket().send(oss.str()); } - - // Send the payload data. - m_request->getSocket().send_cpp(data); -} // sendData +} // sendHeader +/** + * @brief Set the status code that is to be sent back to the client. + * When a client makes a request, the response contains a status. This call sets the status that + * will be sent back to the client. + * @param [in] status The status code to be sent back to the caller. + * @param [in] message The message to be sent with the status code. + */ void HttpResponse::setStatus(const int status, const std::string message) { - if (m_headerCommitted) { + if (m_headerCommitted) { // If the header has already been sent, don't set the new values. + ESP_LOGW(LOG_TAG, "Attempt to set a new status(%d)/message(%s) but header has already been committed.", status, message.c_str()); return; } - m_status = status; + m_status = status; m_statusMessage = message; } // setStatus diff --git a/cpp_utils/HttpResponse.h b/cpp_utils/HttpResponse.h index 5a9fe2ec..5c8c5ec0 100644 --- a/cpp_utils/HttpResponse.h +++ b/cpp_utils/HttpResponse.h @@ -1,6 +1,8 @@ /* * HttpResponse.h * + * Encapsulate a response to be sent to the Http client. + * * Created on: Sep 2, 2017 * Author: kolban */ @@ -13,11 +15,14 @@ class HttpResponse { private: - HttpRequest* m_request; - std::string m_statusMessage; - int m_status; - bool m_headerCommitted; - std::map m_responseHeaders; + bool m_headerCommitted; // Has the header been sent? + HttpRequest* m_request; // The request associated with this response. + std::map m_responseHeaders; // The headers to be sent with the response. + int m_status; // The status to be sent with the response. + std::string m_statusMessage; // The status message to be sent with the response. + + void sendHeader(); // Send the header to the client. + public: static const int HTTP_STATUS_CONTINUE; static const int HTTP_STATUS_SWITCHING_PROTOCOL; @@ -35,16 +40,12 @@ class HttpResponse { HttpResponse(HttpRequest* httpRequest); virtual ~HttpResponse(); - void addHeader(std::string name, std::string value); - void close(); - //std::string getRootPath(); - std::string getHeader(std::string name); - std::map getHeaders(); - void sendData(std::string data); - //void sendData(uint8_t *pData, size_t length); - //void setHeaders(std::map headers); - void setStatus(int status, std::string message); - //void setRootPath(std::string path); + void addHeader(std::string name, std::string value); // Add a header to be sent to the client. + void close(); // Close the request/response. + std::string getHeader(std::string name); // Get a named header. + std::map getHeaders(); // Get all headers. + void sendData(std::string data); // Send data to the client. + void setStatus(int status, std::string message); // Set the response status. }; #endif /* COMPONENTS_CPP_UTILS_HTTPRESPONSE_H_ */ diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index c342d098..9353c540 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -20,8 +20,9 @@ static const char* LOG_TAG = "HttpServer"; * Constructor for HTTP Server */ HttpServer::HttpServer() { - m_portNumber = 80; -} + m_portNumber = 80; // The default port number. + m_rootPath = "/"; // The default path. +} // HttpServer HttpServer::~HttpServer() { ESP_LOGD(LOG_TAG, "~HttpServer"); @@ -29,6 +30,9 @@ HttpServer::~HttpServer() { /** * @brief Be an HTTP server task. + * Here we define a Task that will be run when the HTTP server starts. It is this task + * that executes the majority of the passive work of the server. It listens for incoming + * connections and processes them when they arrive. */ class HttpServerTask: public Task { public: @@ -53,42 +57,45 @@ class HttpServerTask: public Task { ESP_LOGD("HttpServerTask", ">> processRequest: Method: %s, Path: %s", request.getMethod().c_str(), request.getPath().c_str()); - for (auto it = m_pHttpServer->m_pathHandlers.begin(); it != m_pHttpServer->m_pathHandlers.end(); ++it) { - if (it->match(request.getMethod(), request.getPath())) { + // Loop over all the path handlers we have looking for the first one that matches. Note that none of them + // may match. If we find one that does, then invoke the handler and that is the end of processing. + for (auto pathHandlerIterartor = m_pHttpServer->m_pathHandlers.begin(); + pathHandlerIterartor != m_pHttpServer->m_pathHandlers.end(); + ++pathHandlerIterartor) { + if (pathHandlerIterartor->match(request.getMethod(), request.getPath())) { // Did we match the handler? ESP_LOGD("HttpServerTask", "Found a path handler match!!"); - if (request.isWebsocket()) { - it->invokePathHandler(&request, nullptr); + if (request.isWebsocket()) { // Is this handler to be invoked for a web socket? + pathHandlerIterartor->invokePathHandler(&request, nullptr); // Invoke the handler. request.getWebSocket()->startReader(); } else { HttpResponse response(&request); - it->invokePathHandler(&request, &response); + pathHandlerIterartor->invokePathHandler(&request, &response); // Invoke the handler. } - return; + return; // End of processing the request } // Path handler match } // For each path handler ESP_LOGD("HttpServerTask", "No Path handler found"); // If we reach here, then we did not find a handler for the request. - // Check to see if we have an un-handled WebSocket - if (request.isWebsocket()) { - request.getWebSocket()->close_cpp(); + + if (request.isWebsocket()) { // Check to see if we have an un-handled WebSocket + request.getWebSocket()->close(); // If we do, close the socket as there is nothing further to do. return; } // Serve up the content from the file on the file system ... if found ... - std::ifstream ifStream; - std::string fileName = m_pHttpServer->getRootPath() + request.getPath(); + std::string fileName = m_pHttpServer->getRootPath() + request.getPath(); // Build the absolute file name to read. ESP_LOGD("HttpServerTask", "Opening file: %s", fileName.c_str()); - ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); + ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); // Attempt to open the file for reading. // If we failed to open the requested file, then it probably didn't exist so return a not found. if (!ifStream.is_open()) { HttpResponse response(&request); response.setStatus(HttpResponse::HTTP_STATUS_NOT_FOUND, "Not Found"); response.sendData(""); - return; + return; // Since we failed to open the file, no further work to be done. } // We now have an open file and want to push the content of that file through to the browser. @@ -104,26 +111,32 @@ class HttpServerTask: public Task { /** * @brief Perform the task handling for server. + * We loop forever waiting for new client connections to arrive. When they do, we parse the + * content and look for a handler for that content. * @param [in] data A reference to the HttpServer. */ void run(void* data) { - m_pHttpServer = (HttpServer*)data; + m_pHttpServer = (HttpServer*)data; // The passed in data is an instance of an HttpServer. + + SockServ sockServ(m_pHttpServer->getPort()); // Create a socket server on our target port. + sockServ.start(); // Start the socket server listening. - // Create a socket server and start it running. - SockServ sockServ(m_pHttpServer->getPort()); - sockServ.start(); ESP_LOGD("HttpServerTask", "Listening on port %d", m_pHttpServer->getPort()); - while(1) { + while(1) { // Loop forever. + ESP_LOGD("HttpServerTask", "Waiting for new peer client"); - Socket clientSocket = sockServ.waitForNewClient(); + + Socket clientSocket = sockServ.waitForNewClient(); // Block waiting for a new external client connection. + ESP_LOGD("HttpServerTask", "HttpServer listening on port %d received a new client connection", m_pHttpServer->getPort()); - HttpRequest request(clientSocket); - request.dump(); - processRequest(request); - if (!request.isWebsocket()) { - request.close(); + + HttpRequest request(clientSocket); // Build the HTTP Request from the socket. + request.dump(); // debug. + processRequest(request); // Process the request. + if (!request.isWebsocket()) { // If this is NOT a WebSocket, then close it as the request + request.close(); // has been completed. } } // while } // run @@ -154,6 +167,8 @@ void HttpServer::addPathHandler( std::string method, std::string pathExpr, void (*handler)(HttpRequest *pHttpRequest, HttpResponse *pHttpResponse)) { + + // We are maintaining a C++ vector of PathHandler objects. We add a new entry into that vector. m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); } // addPathHandler @@ -220,11 +235,15 @@ void HttpServer::start(uint16_t portNumber) { * @param [in] webServerRequestHandler The request handler to be called. */ PathHandler::PathHandler(std::string method, std::string pathPattern, - void (*webServerRequestHandler)(HttpRequest *pHttpRequest, HttpResponse *pHttpResponse)) { - m_method = method; - m_pattern = std::regex(pathPattern); - m_textPattern = pathPattern; - m_requestHandler = webServerRequestHandler; + void (*pWebServerRequestHandler) + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ) { + m_method = method; // Save the method we are looking for. + m_pattern = std::regex(pathPattern); // Create the Regex pattern. + m_textPattern = pathPattern; // The plain text of the regex pattern. + m_pRequestHandler = pWebServerRequestHandler; // The handler to be invoked if the pattern matches. } // PathHandler @@ -236,7 +255,7 @@ PathHandler::PathHandler(std::string method, std::string pathPattern, * @return True if the path matches. */ bool PathHandler::match(std::string method, std::string path) { - ESP_LOGD(LOG_TAG, "matching: %s with %s", m_textPattern.c_str(), path.c_str()); + ESP_LOGD("PathHandler", "matching: %s with %s", m_textPattern.c_str(), path.c_str()); if (method != m_method) { return false; } @@ -251,5 +270,5 @@ bool PathHandler::match(std::string method, std::string path) { * @return N/A. */ void PathHandler::invokePathHandler(HttpRequest* request, HttpResponse *response) { - m_requestHandler(request, response); + m_pRequestHandler(request, response); } // invokePathHandler diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index 4e4de640..a55d32ee 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -3,6 +3,9 @@ * * Created on: Aug 30, 2017 * Author: kolban + * + * Implementation of an HTTP server for the ESP32. + * */ #ifndef COMPONENTS_CPP_UTILS_HTTPSERVER_H_ @@ -16,37 +19,45 @@ class HttpServerTask; +/** + * @brief Handle path matching for an incoming HTTP request. + */ class PathHandler { public: PathHandler( - std::string method, - std::string pathPattern, - void (*webServerRequestHandler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse)); - bool match(std::string method, std::string path); + std::string method, // The method in the request to be matched. + std::string pathPattern, // The pattern in the request to be matched + void (*pWebServerRequestHandler) // The handler function to be invoked upon a match. + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ); + bool match(std::string method, std::string path); // Does the request method and pattern match? void invokePathHandler(HttpRequest* request, HttpResponse* response); private: std::string m_method; std::regex m_pattern; std::string m_textPattern; - void (*m_requestHandler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse); + void (*m_pRequestHandler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse); }; // PathHandler - - class HttpServer { public: HttpServer(); virtual ~HttpServer(); - void addPathHandler(std::string method, + void addPathHandler( + std::string method, std::string pathExpr, - void (*webServerRequestHandler)( - HttpRequest* pHttpRequest, - HttpResponse* pHttpResponse) ); - uint16_t getPort(); - std::string getRootPath(); - void setRootPath(std::string path); + void (*webServerRequestHandler) + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ); + uint16_t getPort(); // Get the port on which the Http server is listening. + std::string getRootPath(); // Get the root of the file system path. + void setRootPath(std::string path); // Set the root of the file system path. void start(uint16_t portNumber); private: friend class HttpServerTask; diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 8c6cccb8..637a42de 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -49,7 +49,7 @@ void SockServ::acceptTask(void *data) { SockServ* pSockServ = (SockServ*)data; while(1) { - Socket tempSock = pSockServ->m_serverSocket.accept_cpp(); + Socket tempSock = pSockServ->m_serverSocket.accept(); if (!tempSock.isValid()) { continue; } @@ -88,7 +88,7 @@ void SockServ::disconnect(Socket s) { * @return The amount of data returned or 0 if there was an error. */ size_t SockServ::receiveData(Socket s, void* pData, size_t maxData) { - int rc = s.receive_cpp((uint8_t*)pData, maxData); + int rc = s.receive((uint8_t*)pData, maxData); if (rc == -1) { ESP_LOGE(LOG_TAG, "recv(): %s", strerror(errno)); return 0; @@ -115,7 +115,7 @@ void SockServ::sendData(std::string str) { */ void SockServ::sendData(uint8_t* data, size_t length) { for (auto it = m_clientSet.begin(); it != m_clientSet.end(); ++it) { - (*it).send_cpp(data, length); + (*it).send(data, length); } } // sendData @@ -135,7 +135,7 @@ void SockServ::setPort(uint16_t port) { */ void SockServ::start() { assert(m_port != 0); - m_serverSocket.listen_cpp(m_port); + m_serverSocket.listen(m_port); ESP_LOGD(LOG_TAG, "Now listening on port %d", m_port); FreeRTOS::startTask(acceptTask, "acceptTask", this); } // start diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index a4b38a72..9a3d9137 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -36,13 +36,13 @@ Socket::~Socket() { -Socket Socket::accept_cpp() { +Socket Socket::accept() { struct sockaddr addr; - getBind_cpp(&addr); + getBind(&addr); ESP_LOGD(LOG_TAG, "Accepting on %s", addressToString(&addr).c_str()); struct sockaddr_in clientAddress; socklen_t clientAddressLength = sizeof(clientAddress); - int clientSockFD = ::accept(m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); + int clientSockFD = ::lwip_accept(m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); if (clientSockFD == -1) { ESP_LOGE(LOG_TAG, "accept(): %s, m_sock=%d", strerror(errno), m_sock); Socket newSocket; @@ -51,7 +51,7 @@ Socket Socket::accept_cpp() { Socket newSocket; newSocket.m_sock = clientSockFD; return newSocket; -} +} // accept /** * @brief Convert a socket address to a string representation. @@ -76,21 +76,21 @@ std::string Socket::addressToString(struct sockaddr* addr) { * @param [in] address Address to bind. * @return N/A */ -void Socket::bind_cpp(uint16_t port, uint32_t address) { - ESP_LOGD(LOG_TAG, "bind_cpp: port=%d, address=0x%x", port, address); +void Socket::bind(uint16_t port, uint32_t address) { + ESP_LOGD(LOG_TAG, "bind: port=%d, address=0x%x", port, address); if (m_sock == -1) { - ESP_LOGE(LOG_TAG, "bind_cpp: Socket is not initialized."); + ESP_LOGE(LOG_TAG, "bind: Socket is not initialized."); } struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = htonl(address); serverAddress.sin_port = htons(port); - int rc = ::bind(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + int rc = ::lwip_bind(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); if (rc == -1) { - ESP_LOGE(LOG_TAG, "bind_cpp: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); + ESP_LOGE(LOG_TAG, "bind: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); return; } -} // bind_cpp +} // bind /** @@ -98,13 +98,13 @@ void Socket::bind_cpp(uint16_t port, uint32_t address) { * * @return N/A. */ -void Socket::close_cpp() { - ESP_LOGD(LOG_TAG, "close_cpp: m_sock=%d", m_sock); +void Socket::close() { + ESP_LOGD(LOG_TAG, "close: m_sock=%d", m_sock); if (m_sock != -1) { - ::close(m_sock); + ::lwip_close(m_sock); } m_sock = -1; -} // close_cpp +} // close /** @@ -114,7 +114,7 @@ void Socket::close_cpp() { * @param [in] port The port number of the partner. * @return Success or failure of the connection. */ -int Socket::connect_cpp(struct in_addr address, uint16_t port) { +int Socket::connect(struct in_addr address, uint16_t port) { struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_addr = address; @@ -123,10 +123,10 @@ int Socket::connect_cpp(struct in_addr address, uint16_t port) { inet_ntop(AF_INET, &address, msg, sizeof(msg)); ESP_LOGD(LOG_TAG, "Connecting to %s:[%d]", msg, port); createSocket_cpp(); - int rc = ::connect(m_sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); + int rc = ::lwip_connect(m_sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); if (rc == -1) { ESP_LOGE(LOG_TAG, "connect_cpp: Error: %s", strerror(errno)); - close_cpp(); + close(); return -1; } else { ESP_LOGD(LOG_TAG, "Connected to partner"); @@ -142,10 +142,10 @@ int Socket::connect_cpp(struct in_addr address, uint16_t port) { * @param [in] port The port number of the partner. * @return Success or failure of the connection. */ -int Socket::connect_cpp(char* strAddress, uint16_t port) { +int Socket::connect(char* strAddress, uint16_t port) { struct in_addr address; inet_pton(AF_INET, (char *)strAddress, &address); - return connect_cpp(address, port); + return connect(address, port); } @@ -174,13 +174,13 @@ int Socket::createSocket_cpp(bool isDatagram) { * @param [out] pAddr The storage to hold the address. * @return N/A. */ -void Socket::getBind_cpp(struct sockaddr* pAddr) { +void Socket::getBind(struct sockaddr* pAddr) { if (m_sock == -1) { - ESP_LOGE(LOG_TAG, "getBind_cpp: Socket is not initialized."); + ESP_LOGE(LOG_TAG, "getBind: Socket is not initialized."); } socklen_t nameLen = sizeof(struct sockaddr); ::getsockname(m_sock, pAddr, &nameLen); -} // getBind_cpp +} // getBind /** @@ -201,14 +201,14 @@ bool Socket::isValid() { * @param [in] port The port number to listen upon. * @param [in] isDatagram True if we are listening on a datagram. */ -void Socket::listen_cpp(uint16_t port, bool isDatagram) { +void Socket::listen(uint16_t port, bool isDatagram) { createSocket_cpp(isDatagram); - bind_cpp(port, 0); - int rc = ::listen(m_sock, 5); + bind(port, 0); + int rc = ::lwip_listen(m_sock, 5); if (rc == -1) { - ESP_LOGE(LOG_TAG, "listen_cpp: %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "listen: %s", strerror(errno)); } -} // listen_cpp +} // listen bool Socket::operator <(const Socket& other) const { return m_sock < other.m_sock; @@ -221,7 +221,7 @@ std::string Socket::readToDelim(std::string delim) { auto it = delim.begin(); while(1) { uint8_t val; - int rc = receive_cpp(&val, 1); + int rc = receive(&val, 1); if (rc == -1) { return ""; } @@ -257,10 +257,10 @@ std::string Socket::readToDelim(std::string delim) { * @param [in] exact Read exactly this amount. * @return The length of the data received or -1 on an error. */ -size_t Socket::receive_cpp(uint8_t* data, size_t length, bool exact) { +size_t Socket::receive(uint8_t* data, size_t length, bool exact) { //ESP_LOGD(LOG_TAG, ">> receive_cpp: length: %d, exact: %d", length, exact); if (exact == false) { - int rc = ::recv(m_sock, data, length, 0); + int rc = ::lwip_recv(m_sock, data, length, 0); if (rc == -1) { ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); } @@ -269,7 +269,7 @@ size_t Socket::receive_cpp(uint8_t* data, size_t length, bool exact) { } size_t amountToRead = length; while(amountToRead > 0) { - int rc = ::recv(m_sock, data, amountToRead, 0); + int rc = ::lwip_recv(m_sock, data, amountToRead, 0); if (rc == -1) { ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); return 0; @@ -307,15 +307,15 @@ int Socket::receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr *pAddr * @return N/A. * */ -int Socket::send_cpp(const uint8_t* data, size_t length) const { - ESP_LOGD(LOG_TAG, "send_cpp: Raw binary of length: %d", length); +int Socket::send(const uint8_t* data, size_t length) const { + ESP_LOGD(LOG_TAG, "send: Raw binary of length: %d", length); //GeneralUtils::hexDump(data, length); - int rc = ::send(m_sock, data, length, 0); + int rc = ::lwip_send(m_sock, data, length, 0); if (rc == -1) { ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); } return rc; -} // send_cpp +} // send /** @@ -324,21 +324,22 @@ int Socket::send_cpp(const uint8_t* data, size_t length) const { * @param [in] value The string to send to the partner. * @return N/A. */ -int Socket::send_cpp(std::string value) const { - ESP_LOGD(LOG_TAG, "send_cpp: Binary of length: %d", value.length()); - return send_cpp((uint8_t *)value.data(), value.size()); -} // send_cpp +int Socket::send(std::string value) const { + ESP_LOGD(LOG_TAG, "send: Binary of length: %d", value.length()); + return send((uint8_t *)value.data(), value.size()); +} // send -int Socket::send_cpp(uint16_t value) { - ESP_LOGD(LOG_TAG, "send_cpp: 16bit value: %.2x", value); - return send_cpp((uint8_t *)&value, sizeof(value)); +int Socket::send(uint16_t value) { + ESP_LOGD(LOG_TAG, "send: 16bit value: %.2x", value); + return send((uint8_t *)&value, sizeof(value)); } // send_cpp -int Socket::send_cpp(uint32_t value) { - ESP_LOGD(LOG_TAG, "send_cpp: 32bit value: %.2x", value); - return send_cpp((uint8_t *)&value, sizeof(value)); -} // send_cpp + +int Socket::send(uint32_t value) { + ESP_LOGD(LOG_TAG, "send: 32bit value: %.2x", value); + return send((uint8_t *)&value, sizeof(value)); +} // send /** @@ -347,12 +348,12 @@ int Socket::send_cpp(uint32_t value) { * @param [in] length The length of the data to send/ * @param [in] pAddr The address to send the data. */ -void Socket::sendTo_cpp(const uint8_t* data, size_t length, struct sockaddr* pAddr) { +void Socket::sendTo(const uint8_t* data, size_t length, struct sockaddr* pAddr) { int rc = ::sendto(m_sock, data, length, 0, pAddr, sizeof(struct sockaddr)); if (rc == -1) { - ESP_LOGE(LOG_TAG, "sendto_cpp: socket=%d %s", m_sock, strerror(errno)); + ESP_LOGE(LOG_TAG, "sendto: socket=%d %s", m_sock, strerror(errno)); } -} // sendTo_cpp +} // sendTo /** * @brief Get the string representation of this socket @@ -397,7 +398,7 @@ SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() { if (m_sizeRead >= m_dataLength) { return EOF; } - int bytesRead = m_socket.receive_cpp((uint8_t*)m_buffer, m_bufferSize, true); + int bytesRead = m_socket.receive((uint8_t*)m_buffer, m_bufferSize, true); if (bytesRead == 0) { return EOF; } diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 3219b770..82bb2b4e 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -16,6 +16,13 @@ #include #include +#undef accept +#undef bind +#undef close +#undef connect +#undef listen +#undef recv +#undef send /** * @brief Encapsulate a socket. @@ -29,26 +36,26 @@ class Socket { Socket(); virtual ~Socket(); - Socket accept_cpp(); + Socket accept(); static std::string addressToString(struct sockaddr* addr); - void bind_cpp(uint16_t port, uint32_t address); - void close_cpp(); - int connect_cpp(struct in_addr address, uint16_t port); - int connect_cpp(char* address, uint16_t port); + void bind(uint16_t port, uint32_t address); + void close(); + int connect(struct in_addr address, uint16_t port); + int connect(char* address, uint16_t port); int createSocket_cpp(bool isDatagram = false); - void getBind_cpp(struct sockaddr* pAddr); + void getBind(struct sockaddr* pAddr); int getFD() const; bool isValid(); - void listen_cpp(uint16_t port, bool isDatagram=false); + void listen(uint16_t port, bool isDatagram=false); bool operator<(const Socket& other) const; std::string readToDelim(std::string delim); - size_t receive_cpp(uint8_t* data, size_t length, bool exact=false); + size_t receive(uint8_t* data, size_t length, bool exact=false); int receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr* pAddr); - int send_cpp(std::string value) const; - int send_cpp(const uint8_t* data, size_t length) const; - int send_cpp(uint16_t value); - int send_cpp(uint32_t value); - void sendTo_cpp(const uint8_t* data, size_t length, struct sockaddr* pAddr); + int send(std::string value) const; + int send(const uint8_t* data, size_t length) const; + int send(uint16_t value); + int send(uint32_t value); + void sendTo(const uint8_t* data, size_t length, struct sockaddr* pAddr); std::string toString(); @@ -69,4 +76,5 @@ class SocketInputRecordStreambuf : public std::streambuf { size_t m_sizeRead; }; + #endif /* COMPONENTS_CPP_UTILS_SOCKET_H_ */ diff --git a/cpp_utils/TFTP.cpp b/cpp_utils/TFTP.cpp index afd5cffe..32d473c3 100644 --- a/cpp_utils/TFTP.cpp +++ b/cpp_utils/TFTP.cpp @@ -19,6 +19,13 @@ #include "sdkconfig.h" +extern "C" { + extern uint16_t lwip_ntohs(uint16_t); + extern uint32_t lwip_ntohl(uint32_t); + extern uint16_t lwip_htons(uint16_t); + extern uint32_t lwip_htonl(uint32_t); +} + static char tag[] = "TFTP"; enum opcode { @@ -118,7 +125,7 @@ void TFTP::TFTP_Transaction::processRRQ() { ESP_LOGD(tag, "Sending data to %s, blockNumber=%d, size=%d", Socket::addressToString(&m_partnerAddress).c_str(), blockNumber, sizeRead); - m_partnerSocket.sendTo_cpp(buf, sizeRead+4, &m_partnerAddress); + m_partnerSocket.sendTo(buf, sizeRead+4, &m_partnerAddress); if (sizeRead < TFTP_DATA_SIZE) { @@ -180,7 +187,7 @@ void TFTP::TFTP_Transaction::processWRQ() { } } // Finished fclose(file); - m_partnerSocket.close_cpp(); + m_partnerSocket.close(); } // process @@ -201,7 +208,7 @@ void TFTP::TFTP_Transaction::sendAck(uint16_t blockNumber) { ackData.blockNumber = htons(blockNumber); ESP_LOGD(tag, "Sending ack to %s, blockNumber=%d", Socket::addressToString(&m_partnerAddress).c_str(), blockNumber); - m_partnerSocket.sendTo_cpp((uint8_t *)&ackData, sizeof(ackData), &m_partnerAddress); + m_partnerSocket.sendTo((uint8_t *)&ackData, sizeof(ackData), &m_partnerAddress); } // sendAck @@ -222,7 +229,7 @@ void TFTP::start(uint16_t port) { */ ESP_LOGD(tag, "Starting TFTP::start() on port %d", port); Socket serverSocket; - serverSocket.listen_cpp(port, true); // Create a listening socket that is a datagram. + serverSocket.listen(port, true); // Create a listening socket that is a datagram. while(true) { // This would be a good place to start a transaction in the background. TFTP_Transaction *pTFTPTransaction = new TFTP_Transaction(); @@ -336,7 +343,7 @@ uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { // Handle the Write Request command. case TFTP_OPCODE_WRQ: { m_partnerSocket.createSocket_cpp(true); - m_partnerSocket.bind_cpp(0, INADDR_ANY); + m_partnerSocket.bind(0, INADDR_ANY); sendAck(0); break; } @@ -345,7 +352,7 @@ uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { // Handle the Read request command. case TFTP_OPCODE_RRQ: { m_partnerSocket.createSocket_cpp(true); - m_partnerSocket.bind_cpp(0, INADDR_ANY); + m_partnerSocket.bind(0, INADDR_ANY); break; } @@ -375,6 +382,6 @@ void TFTP::TFTP_Transaction::sendError(uint16_t code, std::string message) { *(uint16_t *)(&buf[0]) = htons(opcode::TFTP_OPCODE_ERROR); *(uint16_t *)(&buf[2]) = htons(code); strcpy((char *)(&buf[4]), message.c_str()); - m_partnerSocket.sendTo_cpp(buf, size, &m_partnerAddress); + m_partnerSocket.sendTo(buf, size, &m_partnerAddress); free(buf); } // sendError diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp index 61192ef4..791be38c 100644 --- a/cpp_utils/WebSocket.cpp +++ b/cpp_utils/WebSocket.cpp @@ -17,6 +17,7 @@ extern "C" { extern uint16_t lwip_htons(uint16_t); extern uint32_t lwip_htonl(uint32_t); } + static const char* LOG_TAG = "WebSocket"; // WebSocket op codes as found in a WebSocket frame. @@ -118,10 +119,10 @@ class WebSocketReader: public Task { break; } ESP_LOGD("WebSocketReader", "Waiting on socket data for socket %s", peerSocket.toString().c_str()); - int length = peerSocket.receive_cpp((uint8_t*)&frame, sizeof(frame), true); // Read exact + int length = peerSocket.receive((uint8_t*)&frame, sizeof(frame), true); // Read exact if (length != sizeof(frame)) { ESP_LOGD(LOG_TAG, "Socket read error"); - pWebSocket->close_cpp(); + pWebSocket->close(); return; } ESP_LOGD("WebSocketReader", "Received data from web socket. Length: %d", length); @@ -134,15 +135,15 @@ class WebSocketReader: public Task { payloadLen = frame.len; } else if (frame.len == 126) { uint16_t tempLen; - peerSocket.receive_cpp((uint8_t*)&tempLen, sizeof(tempLen), true); + peerSocket.receive((uint8_t*)&tempLen, sizeof(tempLen), true); payloadLen = ntohs(tempLen); } else if (frame.len == 127) { uint64_t tempLen; - peerSocket.receive_cpp((uint8_t*)&tempLen, sizeof(tempLen), true); + peerSocket.receive((uint8_t*)&tempLen, sizeof(tempLen), true); payloadLen = ntohl((uint32_t)tempLen); } if (frame.mask == 1) { - peerSocket.receive_cpp(mask, sizeof(mask), true); + peerSocket.receive(mask, sizeof(mask), true); } if (payloadLen == 0) { @@ -156,19 +157,20 @@ class WebSocketReader: public Task { case OPCODE_TEXT: case OPCODE_BINARY: { if (pWebSocketHandler != nullptr) { - WebSocketInputRecordStreambuf streambuf(pWebSocket->getSocket(), payloadLen, frame.mask==1?mask:nullptr); + WebSocketInputStreambuf streambuf(pWebSocket->getSocket(), payloadLen, frame.mask==1?mask:nullptr); pWebSocketHandler->onMessage(&streambuf); //streambuf.discard(); } break; } + // If the WebSocket operation code is close then we are closing the connection. case OPCODE_CLOSE: { pWebSocket->m_receivedClose = true; - if (pWebSocketHandler != nullptr) { + if (pWebSocketHandler != nullptr) { // If we have a handler, invoke the onClose method upon it. pWebSocketHandler->onClose(); - pWebSocket->close_cpp(); } + pWebSocket->close(); // Close the websocket. break; } @@ -184,7 +186,6 @@ class WebSocketReader: public Task { break; } - default: { ESP_LOGD("WebSocketReader", "Unknown opcode: %d", frame.opCode); break; @@ -216,7 +217,7 @@ void WebSocketHandler::onClose() { * ``` * This will read the whole message into the string stream. */ -void WebSocketHandler::onMessage(WebSocketInputRecordStreambuf* pWebSocketInputRecordStreambuf) { +void WebSocketHandler::onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf) { ESP_LOGD("WebSocketHandler", ">> onMessage") } // onData @@ -230,6 +231,9 @@ void WebSocketHandler::onError(std::string error) { } // onError +/** + * @brief Construct a WebSocket instance. + */ WebSocket::WebSocket(Socket socket) { m_receivedClose = false; m_sentClose = false; @@ -239,6 +243,9 @@ WebSocket::WebSocket(Socket socket) { } // WebSocket +/** + * @brief Destructor. + */ WebSocket::~WebSocket() { m_pWebSockerReader->stop(); delete m_pWebSockerReader; @@ -247,36 +254,43 @@ WebSocket::~WebSocket() { /** * @brief Close the Web socket + * @param [in] status The code passed in the close request. + * @param [in] message A clarification message on the close request. */ -void WebSocket::close_cpp(uint16_t status, std::string message) { - ESP_LOGD(LOG_TAG, ">> close_cpp(): status: %d, message: %s", status, message.c_str()); - if (m_sentClose) { - m_socket.close_cpp(); - m_pWebSockerReader->end(); +void WebSocket::close(uint16_t status, std::string message) { + ESP_LOGD(LOG_TAG, ">> close(): status: %d, message: %s", status, message.c_str()); + + if (m_sentClose) { // If we have previously sent a close request then we can close the underlying socket. + ESP_LOGD(LOG_TAG, "Closing the underlying socket"); + m_socket.close(); // Close the underlying socket. + m_pWebSockerReader->end(); // Stop the web socket reader. return; } - m_sentClose = true; + m_sentClose = true; // Flag that we have sent a close request. - Frame frame; + Frame frame; // Build the web socket frame indicating a close request. frame.fin = 1; frame.rsv1 = 0; frame.rsv2 = 0; frame.rsv3 = 0; frame.opCode = OPCODE_CLOSE; frame.mask = 0; - frame.len = message.length() + 2; - int rc = m_socket.send_cpp((uint8_t *)&frame, sizeof(frame)); + frame.len = message.length() + 2; + int rc = m_socket.send((uint8_t *)&frame, sizeof(frame)); + if (rc > 0) { - rc = m_socket.send_cpp(status); + rc = m_socket.send(status); } + if (rc > 0) { - m_socket.send_cpp(message); + m_socket.send(message); } + if (m_receivedClose || rc == 0 || rc == -1) { - m_socket.close_cpp(); - m_pWebSockerReader->end(); + m_socket.close(); // Close the underlying socket. + m_pWebSockerReader->end(); // Stop the web socket reader. } -} // close_cpp +} // close /** @@ -305,8 +319,8 @@ Socket WebSocket::getSocket() { * @param [in] data The data to send down the WebSocket. * @param [in] sendType The type of payload. Either SEND_TYPE_TEXT or SEND_TYPE_BINARY. */ -void WebSocket::send_cpp(std::string data, uint8_t sendType) { - ESP_LOGD(LOG_TAG, ">> send_cpp: Length: %d", data.length()); +void WebSocket::send(std::string data, uint8_t sendType) { + ESP_LOGD(LOG_TAG, ">> send: Length: %d", data.length()); Frame frame; frame.fin = 1; frame.rsv1 = 0; @@ -316,14 +330,14 @@ void WebSocket::send_cpp(std::string data, uint8_t sendType) { frame.mask = 0; if (data.length() < 126) { frame.len = data.length(); - m_socket.send_cpp((uint8_t *)&frame, sizeof(frame)); + m_socket.send((uint8_t *)&frame, sizeof(frame)); } else { frame.len = 126; - m_socket.send_cpp((uint8_t *)&frame, sizeof(frame)); - m_socket.send_cpp((uint16_t)data.length()); + m_socket.send((uint8_t *)&frame, sizeof(frame)); + m_socket.send((uint16_t)data.length()); } - m_socket.send_cpp((uint8_t*)data.data(), data.length()); - ESP_LOGD(LOG_TAG, "<< send_cpp"); + m_socket.send((uint8_t*)data.data(), data.length()); + ESP_LOGD(LOG_TAG, "<< send"); } // send_cpp @@ -334,7 +348,7 @@ void WebSocket::send_cpp(std::string data, uint8_t sendType) { * events. An instance of WebSocketHandler is passed in. * */ -void WebSocket::setHandler(WebSocketHandler *pHandler) { +void WebSocket::setHandler(WebSocketHandler* pHandler) { m_pWebSocketHandler = pHandler; } // setHandler @@ -356,7 +370,7 @@ void WebSocket::startReader() { * @param [in] dataLength The size of a record. * @param [in] bufferSize The size of the buffer we wish to allocate to hold data. */ -WebSocketInputRecordStreambuf::WebSocketInputRecordStreambuf( +WebSocketInputStreambuf::WebSocketInputStreambuf( Socket socket, size_t dataLength, uint8_t *pMask, @@ -369,13 +383,13 @@ WebSocketInputRecordStreambuf::WebSocketInputRecordStreambuf( m_buffer = new char[bufferSize]; // Create the buffer used to hold the data read from the socket. setg(m_buffer, m_buffer, m_buffer); // Set the initial get buffer pointers to no data. -} // WebSocketInputRecordStreambuf +} // WebSocketInputStreambuf /** * @brief Destructor */ -WebSocketInputRecordStreambuf::~WebSocketInputRecordStreambuf() { +WebSocketInputStreambuf::~WebSocketInputStreambuf() { delete[] m_buffer; discard(); } // ~WebSocketInputRecordStreambuf @@ -393,14 +407,14 @@ WebSocketInputRecordStreambuf::~WebSocketInputRecordStreambuf() { * longer need to continue, we can't just stop. There are still 300 bytes in the socket stream that * need to be consumed/discarded before we can move on to the next record. */ -void WebSocketInputRecordStreambuf::discard() { +void WebSocketInputStreambuf::discard() { uint8_t byte; - ESP_LOGD("WebSocketInputRecordStreambuf", ">> discard: Discarding %d bytes", m_dataLength - m_sizeRead); + ESP_LOGD("WebSocketInputStreambuf", ">> discard: Discarding %d bytes", m_dataLength - m_sizeRead); while(m_sizeRead < m_dataLength) { - m_socket.receive_cpp(&byte, 1); + m_socket.receive(&byte, 1); m_sizeRead++; } - ESP_LOGD("WebSocketInputRecordStreambuf", "<< discard"); + ESP_LOGD("WebSocketInputStreambuf", "<< discard"); } // discard @@ -408,7 +422,7 @@ void WebSocketInputRecordStreambuf::discard() { * @brief Get the size of the expected record. * @return The size of the expected record. */ -size_t WebSocketInputRecordStreambuf::getRecordSize() { +size_t WebSocketInputStreambuf::getRecordSize() { return m_dataLength; } // getRecordSize @@ -417,10 +431,13 @@ size_t WebSocketInputRecordStreambuf::getRecordSize() { * @brief Handle the request to read data from the stream but we need more data from the source. * */ -WebSocketInputRecordStreambuf::int_type WebSocketInputRecordStreambuf::underflow() { - ESP_LOGD("WebSocketInputRecordStreambuf", ">> underflow"); +WebSocketInputStreambuf::int_type WebSocketInputStreambuf::underflow() { + ESP_LOGD("WebSocketInputStreambuf", ">> underflow"); + + // If we have already read as many bytes as our record definition says we should read + // then don't attempt to ready any further. if (m_sizeRead >= getRecordSize()) { - ESP_LOGD("WebSocketInputRecordStreambuf", "<< underflow: Already read maximum"); + ESP_LOGD("WebSocketInputStreambuf", "<< underflow: Already read maximum"); return EOF; } @@ -436,23 +453,29 @@ WebSocketInputRecordStreambuf::int_type WebSocketInputRecordStreambuf::underflow } ESP_LOGD("WebSocketInputRecordStreambuf", "- getting next buffer of data; size request: %d", sizeToRead); - int bytesRead = m_socket.receive_cpp((uint8_t*)m_buffer, sizeToRead, true); + int bytesRead = m_socket.receive((uint8_t*)m_buffer, sizeToRead, true); if (bytesRead == 0) { ESP_LOGD("WebSocketInputRecordStreambuf", "<< underflow: Read 0 bytes"); return EOF; } + // If the WebSocket frame shows that we have a mask bit set then we have to unmask the data. if (m_pMask != nullptr) { for (int i=0; i #include "Socket.h" + +#undef close +#undef send class WebSocketReader; // +-------------------------------+ -// | WebSocketInputRecordStreambuf | +// | WebSocketInputStreambuf | // +-------------------------------+ -class WebSocketInputRecordStreambuf : public std::streambuf { +class WebSocketInputStreambuf : public std::streambuf { public: - WebSocketInputRecordStreambuf( + WebSocketInputStreambuf( Socket socket, size_t dataLength, uint8_t* pMask=nullptr, size_t bufferSize=2048); - ~WebSocketInputRecordStreambuf(); + ~WebSocketInputStreambuf(); int_type underflow(); void discard(); size_t getRecordSize(); @@ -42,7 +45,7 @@ class WebSocketHandler { public: virtual ~WebSocketHandler(); virtual void onClose(); - virtual void onMessage(WebSocketInputRecordStreambuf *pWebSocketInputRecordStreambuf); + virtual void onMessage(WebSocketInputStreambuf *pWebSocketInputStreambuf); virtual void onError(std::string error); }; @@ -83,10 +86,10 @@ class WebSocket { WebSocket(Socket socket); virtual ~WebSocket(); - void close_cpp(uint16_t status=CLOSE_NORMAL_CLOSURE, std::string message = ""); + void close(uint16_t status=CLOSE_NORMAL_CLOSURE, std::string message = ""); WebSocketHandler* getHandler(); Socket getSocket(); - void send_cpp(std::string data, uint8_t sendType = SEND_TYPE_BINARY); + void send(std::string data, uint8_t sendType = SEND_TYPE_BINARY); void setHandler(WebSocketHandler *handler); }; // WebSocket diff --git a/cpp_utils/WebSocketFileTransfer.cpp b/cpp_utils/WebSocketFileTransfer.cpp index 2ada459b..0475b4f7 100644 --- a/cpp_utils/WebSocketFileTransfer.cpp +++ b/cpp_utils/WebSocketFileTransfer.cpp @@ -45,11 +45,11 @@ class FileTransferWebSocketHandler : public WebSocketHandler { bool m_active; // Are we actively processing a file. std::ofstream m_ofStream; - virtual void onMessage(WebSocketInputRecordStreambuf* pWebSocketInputRecordStreambuf) { + virtual void onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf) { ESP_LOGD("FileTransferWebSocketHandler", ">> onMessage"); if (!m_active) { std::stringstream buffer; - buffer << pWebSocketInputRecordStreambuf; + buffer << pWebSocketInputStreambuf; ESP_LOGD("FileTransferWebSocketHandler", "Data read: %s", buffer.str().c_str()); JsonObject jo = JSON::parseObject(buffer.str()); m_fileName = jo.getString("name"); @@ -79,7 +79,7 @@ class FileTransferWebSocketHandler : public WebSocketHandler { ESP_LOGD("FileTransferWebSocketHandler", "Filename: %s, length: %d", fileName.c_str(), m_fileLength); } else { // We are about to receive a chunk of file - m_ofStream << pWebSocketInputRecordStreambuf; + m_ofStream << pWebSocketInputStreambuf; /* std::stringstream bufferStream; bufferStream << pWebSocketInputRecordStreambuf; diff --git a/cpp_utils/library.properties b/cpp_utils/library.properties new file mode 100644 index 00000000..09a02bac --- /dev/null +++ b/cpp_utils/library.properties @@ -0,0 +1,11 @@ +name=ESP32 BLE Arduino +version=0.1.0 +author=Neil Kolban +maintainer=Neil Kolban +sentence=BLE functions for ESP32 +paragraph=BLE functions for ESP32 +category=Communication +url=https://github.com/nkolban/ESP32_BLE_Arduino +architectures=esp32 +includes=BLE.h BLEUtils.h BLEScan.h BLEAdvertisedDevice.h +dot_a_linkage=true \ No newline at end of file From 725003df240f859b9311b360bd607ca99c8b1710 Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 13 Sep 2017 18:02:46 -0500 Subject: [PATCH 040/381] Removed the dot_a_linkage flag for BLE for Arduino --- cpp_utils/Arduino_ESP32_BLE.library.properties | 7 +++---- cpp_utils/PWM.h | 12 ++++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cpp_utils/Arduino_ESP32_BLE.library.properties b/cpp_utils/Arduino_ESP32_BLE.library.properties index 09a02bac..b9f5972f 100644 --- a/cpp_utils/Arduino_ESP32_BLE.library.properties +++ b/cpp_utils/Arduino_ESP32_BLE.library.properties @@ -1,11 +1,10 @@ name=ESP32 BLE Arduino -version=0.1.0 +version=0.4.2 author=Neil Kolban maintainer=Neil Kolban sentence=BLE functions for ESP32 -paragraph=BLE functions for ESP32 +paragraph=This library provides an implementation Bluetooth Low Energy support for the ESP32 using the Arduino platform. category=Communication url=https://github.com/nkolban/ESP32_BLE_Arduino architectures=esp32 -includes=BLE.h BLEUtils.h BLEScan.h BLEAdvertisedDevice.h -dot_a_linkage=true \ No newline at end of file +includes=BLE.h, BLEUtils.h, BLEScan.h, BLEAdvertisedDevice.h \ No newline at end of file diff --git a/cpp_utils/PWM.h b/cpp_utils/PWM.h index a048b81b..fdc1fcf1 100644 --- a/cpp_utils/PWM.h +++ b/cpp_utils/PWM.h @@ -29,13 +29,13 @@ class PWM { uint32_t getDuty(); uint32_t getFrequency(); - void setDuty(uint32_t duty); - void setDutyPercentage(uint8_t percent); - void setFrequency(uint32_t freq); - void stop(bool idleLevel=false); + void setDuty(uint32_t duty); + void setDutyPercentage(uint8_t percent); + void setFrequency(uint32_t freq); + void stop(bool idleLevel=false); private: - ledc_channel_t channel; - ledc_timer_t timer; + ledc_channel_t channel; + ledc_timer_t timer; ledc_timer_bit_t bitSize; // Bit size of timer. }; From 5d56f8f65c638e4a849d2e9c656cea64ec98e027 Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 17 Sep 2017 11:48:57 -0500 Subject: [PATCH 041/381] Fixes for #65 --- cpp_utils/HttpServer.cpp | 22 +++- cpp_utils/HttpServer.h | 4 +- cpp_utils/SSLUtils.cpp | 44 +++++++ cpp_utils/SSLUtils.h | 24 ++++ cpp_utils/SockServ.cpp | 23 +++- cpp_utils/SockServ.h | 10 +- cpp_utils/Socket.cpp | 242 ++++++++++++++++++++++++++++++++++----- cpp_utils/Socket.h | 38 ++++-- cpp_utils/TFTP.cpp | 10 +- 9 files changed, 357 insertions(+), 60 deletions(-) create mode 100644 cpp_utils/SSLUtils.cpp create mode 100644 cpp_utils/SSLUtils.h diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 9353c540..6786064e 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -22,6 +22,7 @@ static const char* LOG_TAG = "HttpServer"; HttpServer::HttpServer() { m_portNumber = 80; // The default port number. m_rootPath = "/"; // The default path. + m_useSSL = false; } // HttpServer HttpServer::~HttpServer() { @@ -36,7 +37,7 @@ HttpServer::~HttpServer() { */ class HttpServerTask: public Task { public: - HttpServerTask(std::string name): Task(name) { + HttpServerTask(std::string name): Task(name, 16*1024) { m_pHttpServer = nullptr; }; @@ -116,10 +117,11 @@ class HttpServerTask: public Task { * @param [in] data A reference to the HttpServer. */ void run(void* data) { - m_pHttpServer = (HttpServer*)data; // The passed in data is an instance of an HttpServer. + m_pHttpServer = (HttpServer*)data; // The passed in data is an instance of an HttpServer. - SockServ sockServ(m_pHttpServer->getPort()); // Create a socket server on our target port. - sockServ.start(); // Start the socket server listening. + SockServ sockServ(m_pHttpServer->getPort()); // Create a socket server on our target port. + sockServ.setSSL(m_pHttpServer->getSSL()); + sockServ.start(); // Start the socket server listening. ESP_LOGD("HttpServerTask", "Listening on port %d", m_pHttpServer->getPort()); @@ -191,6 +193,10 @@ std::string HttpServer::getRootPath() { } // getRootPath +bool HttpServer::getSSL() { + return m_useSSL; +} // getSSL + /** * @brief Set the root path for URL file mapping. * @@ -218,9 +224,11 @@ void HttpServer::setRootPath(std::string path) { * We start an instance of the HTTP server listening. A new task is spawned to perform this work in the * back ground. * @param [in] portNumber The port number on which the HTTP server should listen. + * @param [in] useSSL Should we use SSL? */ -void HttpServer::start(uint16_t portNumber) { - ESP_LOGD(LOG_TAG, ">> start"); +void HttpServer::start(uint16_t portNumber, bool useSSL) { + ESP_LOGD(LOG_TAG, ">> start: port: %d, useSSL: %d", portNumber, useSSL); + m_useSSL = useSSL; m_portNumber = portNumber; HttpServerTask* pHttpServerTask = new HttpServerTask("HttpServerTask"); pHttpServerTask->start(this); @@ -272,3 +280,5 @@ bool PathHandler::match(std::string method, std::string path) { void PathHandler::invokePathHandler(HttpRequest* request, HttpResponse *response) { m_pRequestHandler(request, response); } // invokePathHandler + + diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index a55d32ee..a7cebac0 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -57,14 +57,16 @@ class HttpServer { ); uint16_t getPort(); // Get the port on which the Http server is listening. std::string getRootPath(); // Get the root of the file system path. + bool getSSL(); // Are we using SSL? void setRootPath(std::string path); // Set the root of the file system path. - void start(uint16_t portNumber); + void start(uint16_t portNumber, bool useSSL=false); private: friend class HttpServerTask; friend class WebSocket; uint16_t m_portNumber; std::vector m_pathHandlers; std::string m_rootPath; // Root path into the file system. + bool m_useSSL; }; // HttpServer #endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ diff --git a/cpp_utils/SSLUtils.cpp b/cpp_utils/SSLUtils.cpp new file mode 100644 index 00000000..b06a8957 --- /dev/null +++ b/cpp_utils/SSLUtils.cpp @@ -0,0 +1,44 @@ +/* + * SSLUtils.cpp + * + * Created on: Sep 16, 2017 + * Author: kolban + */ + +#include "SSLUtils.h" +#include +#include + +char* SSLUtils::m_certificate = nullptr; +char* SSLUtils::m_key = nullptr; + +SSLUtils::SSLUtils() { + // TODO Auto-generated constructor stub + +} + +SSLUtils::~SSLUtils() { + // TODO Auto-generated destructor stub +} + +void SSLUtils::setCertificate(std::string certificate) { + size_t len = certificate.length(); + m_certificate = (char*)malloc(len + 1); + memcpy(m_certificate, certificate.data(), len); + m_certificate[len] = '\0'; +} + +char* SSLUtils::getCertificate() { + return m_certificate; +} + +void SSLUtils::setKey(std::string key) { + size_t len = key.length(); + m_key = (char*)malloc(len + 1); + memcpy(m_key, key.data(), len); + m_key[len] = '\0'; +} + +char* SSLUtils::getKey() { + return m_key; +} diff --git a/cpp_utils/SSLUtils.h b/cpp_utils/SSLUtils.h new file mode 100644 index 00000000..90546b83 --- /dev/null +++ b/cpp_utils/SSLUtils.h @@ -0,0 +1,24 @@ +/* + * SSLUtils.h + * + * Created on: Sep 16, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_SSLUTILS_H_ +#define COMPONENTS_CPP_UTILS_SSLUTILS_H_ +#include +class SSLUtils { +private: + static char* m_certificate; + static char* m_key; +public: + SSLUtils(); + virtual ~SSLUtils(); + static void setCertificate(std::string certificate); + static char* getCertificate(); + static void setKey(std::string key); + static char* getKey(); +}; + +#endif /* COMPONENTS_CPP_UTILS_SSLUTILS_H_ */ diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 637a42de..d4d23292 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -49,7 +49,7 @@ void SockServ::acceptTask(void *data) { SockServ* pSockServ = (SockServ*)data; while(1) { - Socket tempSock = pSockServ->m_serverSocket.accept(); + Socket tempSock = pSockServ->m_serverSocket.accept(true); if (!tempSock.isValid()) { continue; } @@ -57,6 +57,7 @@ void SockServ::acceptTask(void *data) { pSockServ->m_clientSet.insert(tempSock); xQueueSendToBack(pSockServ->m_acceptQueue, &tempSock, portMAX_DELAY); pSockServ->m_clientSemaphore.give(); + FreeRTOS::sleep(9999999); } } // acceptTask @@ -128,6 +129,12 @@ void SockServ::setPort(uint16_t port) { m_port = port; } // setPort + +void SockServ::setSSL(bool use) { + m_useSSL = use; +} // setSSL + + /** * @brief Start listening for new partner connections. * @@ -135,9 +142,10 @@ void SockServ::setPort(uint16_t port) { */ void SockServ::start() { assert(m_port != 0); + //m_serverSocket.setSSL(m_useSSL); m_serverSocket.listen(m_port); ESP_LOGD(LOG_TAG, "Now listening on port %d", m_port); - FreeRTOS::startTask(acceptTask, "acceptTask", this); + FreeRTOS::startTask(acceptTask, "acceptTask", this, 16*1024); } // start @@ -148,6 +156,8 @@ void SockServ::stop() { } // stop + + Socket SockServ::waitForData(std::set& socketSet) { fd_set readSet; int maxFd = -1; @@ -160,11 +170,11 @@ Socket SockServ::waitForData(std::set& socketSet) { } // End for int rc = ::select( - maxFd+1, // Number of sockets to scan + maxFd+1, // Number of sockets to scan &readSet, // Set of read sockets - nullptr, // Set of write sockets - nullptr, // Set of exception sockets - nullptr // Timeout + nullptr, // Set of write sockets + nullptr, // Set of exception sockets + nullptr // Timeout ); if (rc == -1) { ESP_LOGE(LOG_TAG, "Error with select"); @@ -192,6 +202,7 @@ Socket SockServ::waitForNewClient() { m_clientSemaphore.wait("waitForClient"); Socket tempSocket; xQueueReceive(m_acceptQueue, &tempSocket,portMAX_DELAY); + ESP_LOGD(LOG_TAG, "<< waitForClient"); return tempSocket; } // waitForClient diff --git a/cpp_utils/SockServ.h b/cpp_utils/SockServ.h index 48524a14..ba4801d2 100644 --- a/cpp_utils/SockServ.h +++ b/cpp_utils/SockServ.h @@ -33,11 +33,12 @@ class SockServ { private: static void acceptTask(void*); - uint16_t m_port; - Socket m_serverSocket; + uint16_t m_port; + Socket m_serverSocket; FreeRTOS::Semaphore m_clientSemaphore; - std::set m_clientSet; - QueueHandle_t m_acceptQueue; + std::set m_clientSet; + QueueHandle_t m_acceptQueue; + bool m_useSSL; public: SockServ(uint16_t port); SockServ(); @@ -47,6 +48,7 @@ class SockServ { void sendData(uint8_t* data, size_t length); void sendData(std::string str); void setPort(uint16_t port); + void setSSL(bool use=true); void start(); void stop(); Socket waitForData(std::set& socketSet); diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 9a3d9137..a62e3390 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -13,6 +13,8 @@ #include + + #include #include #include @@ -21,13 +23,29 @@ #include #include "GeneralUtils.h" +#include "SSLUtils.h" #include "sdkconfig.h" #include "Socket.h" static const char* LOG_TAG = "Socket"; +#undef bind + +static void my_debug( + void *ctx, + int level, + const char *file, + int line, + const char *str) { + + ((void) level); + ((void) ctx); + printf("%s:%04d: %s", file, line, str); +} + Socket::Socket() { - m_sock = -1; + m_sock = -1; + m_useSSL = false; } Socket::~Socket() { @@ -35,11 +53,14 @@ Socket::~Socket() { } - -Socket Socket::accept() { +/** + * @brief Accept a new socket. + */ +Socket Socket::accept(bool useSSL) { struct sockaddr addr; getBind(&addr); - ESP_LOGD(LOG_TAG, "Accepting on %s", addressToString(&addr).c_str()); + ESP_LOGD(LOG_TAG, ">> accept: Accepting on %s", addressToString(&addr).c_str()); + struct sockaddr_in clientAddress; socklen_t clientAddressLength = sizeof(clientAddress); int clientSockFD = ::lwip_accept(m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); @@ -48,11 +69,23 @@ Socket Socket::accept() { Socket newSocket; return newSocket; } + + ESP_LOGD(LOG_TAG, " - accept: Received new client!: sockFd: %d", clientSockFD); Socket newSocket; newSocket.m_sock = clientSockFD; + if (useSSL) { + newSocket.setSSL(true); + newSocket.m_sslSock.fd = clientSockFD; + newSocket.sslHandshake(); + ESP_LOGD(LOG_TAG, "DEBUG DEBUG "); + uint8_t x; + newSocket.receive(&x, 1, 0); // FIX FIX FIX + } + ESP_LOGD(LOG_TAG, "<< accept: sockFd: %d", clientSockFD); return newSocket; } // accept + /** * @brief Convert a socket address to a string representation. * @param [in] addr The address to parse. @@ -77,19 +110,21 @@ std::string Socket::addressToString(struct sockaddr* addr) { * @return N/A */ void Socket::bind(uint16_t port, uint32_t address) { - ESP_LOGD(LOG_TAG, "bind: port=%d, address=0x%x", port, address); + ESP_LOGD(LOG_TAG, ">> bind: port=%d, address=0x%x", port, address); + if (m_sock == -1) { ESP_LOGE(LOG_TAG, "bind: Socket is not initialized."); } struct sockaddr_in serverAddress; - serverAddress.sin_family = AF_INET; + serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = htonl(address); - serverAddress.sin_port = htons(port); + serverAddress.sin_port = htons(port); int rc = ::lwip_bind(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); if (rc == -1) { - ESP_LOGE(LOG_TAG, "bind: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); + ESP_LOGE(LOG_TAG, "<< bind: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); return; } + ESP_LOGD(LOG_TAG, "<< bind"); } // bind @@ -100,6 +135,12 @@ void Socket::bind(uint16_t port, uint32_t address) { */ void Socket::close() { ESP_LOGD(LOG_TAG, "close: m_sock=%d", m_sock); + if (getSSL()) { + int rc = mbedtls_ssl_close_notify(&m_sslContext); + if (rc < 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_close_notify: %d", rc); + } + } if (m_sock != -1) { ::lwip_close(m_sock); } @@ -122,7 +163,7 @@ int Socket::connect(struct in_addr address, uint16_t port) { char msg[50]; inet_ntop(AF_INET, &address, msg, sizeof(msg)); ESP_LOGD(LOG_TAG, "Connecting to %s:[%d]", msg, port); - createSocket_cpp(); + createSocket(); int rc = ::lwip_connect(m_sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); if (rc == -1) { ESP_LOGE(LOG_TAG, "connect_cpp: Error: %s", strerror(errno)); @@ -154,7 +195,8 @@ int Socket::connect(char* strAddress, uint16_t port) { * @param [in] isDatagram Set to true to create a datagram socket. Default is false. * @return The socket descriptor. */ -int Socket::createSocket_cpp(bool isDatagram) { +int Socket::createSocket(bool isDatagram) { + ESP_LOGD(LOG_TAG, ">> createSocket: isDatagram: %d", isDatagram); if (isDatagram) { m_sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); } @@ -162,11 +204,12 @@ int Socket::createSocket_cpp(bool isDatagram) { m_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } if (m_sock == -1) { - ESP_LOGE(LOG_TAG, "createSocket_cpp: socket: %d", errno); + ESP_LOGE(LOG_TAG, "<< createSocket: socket: %d", errno); return m_sock; } + ESP_LOGD(LOG_TAG, "<< createSocket: sockFd: %d", m_sock); return m_sock; -} // createSocket_cpp +} // createSocket /** @@ -191,6 +234,9 @@ int Socket::getFD() const { return m_sock; } // getFD +bool Socket::getSSL() const { + return m_useSSL; +} bool Socket::isValid() { return m_sock != -1; @@ -202,14 +248,21 @@ bool Socket::isValid() { * @param [in] isDatagram True if we are listening on a datagram. */ void Socket::listen(uint16_t port, bool isDatagram) { - createSocket_cpp(isDatagram); + ESP_LOGD(LOG_TAG, ">> listen: port: %d, isDatagram: %d", port, isDatagram); + createSocket(isDatagram); bind(port, 0); - int rc = ::lwip_listen(m_sock, 5); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "listen: %s", strerror(errno)); + // For a datagram socket, we don't execute a listen call. That is is only for connection oriented + // sockets. + if (!isDatagram) { + int rc = ::lwip_listen(m_sock, 5); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "<< listen: %s", strerror(errno)); + } } + ESP_LOGD(LOG_TAG, "<< listen"); } // listen + bool Socket::operator <(const Socket& other) const { return m_sock < other.m_sock; } @@ -258,18 +311,35 @@ std::string Socket::readToDelim(std::string delim) { * @return The length of the data received or -1 on an error. */ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { - //ESP_LOGD(LOG_TAG, ">> receive_cpp: length: %d, exact: %d", length, exact); + ESP_LOGD(LOG_TAG, ">> receive: sockFd: %d, length: %d, exact: %d", m_sock, length, exact); if (exact == false) { - int rc = ::lwip_recv(m_sock, data, length, 0); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); + int rc; + if (getSSL()) { + do { + rc = mbedtls_ssl_read(&m_sslContext, data, length); + ESP_LOGD(LOG_TAG, "rc=%d, MBEDTLS_ERR_SSL_WANT_READ=%d", rc, MBEDTLS_ERR_SSL_WANT_READ); + } while(rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); + } else { + rc = ::lwip_recv(m_sock, data, length, 0); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "receive: %s", strerror(errno)); + } } //GeneralUtils::hexDump(data, rc); + ESP_LOGD(LOG_TAG, "<< receive: rc: %d", rc); return rc; - } + } // Read what we can, doesn't need to be an exact amount. + size_t amountToRead = length; + int rc; while(amountToRead > 0) { - int rc = ::lwip_recv(m_sock, data, amountToRead, 0); + if (getSSL()) { + do { + rc = mbedtls_ssl_read(&m_sslContext, data, amountToRead); + } while(rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); + } else { + rc = ::lwip_recv(m_sock, data, amountToRead, 0); + } if (rc == -1) { ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); return 0; @@ -278,9 +348,10 @@ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { break; } amountToRead -= rc; - data+= rc; + data += rc; } //GeneralUtils::hexDump(data, length); + ESP_LOGD(LOG_TAG, "<< receive: %d", length); return length; } // receive_cpp @@ -292,11 +363,11 @@ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { * @param [in] pAddr An area into which we can store the address of the partner. * @return The length of the data received. */ -int Socket::receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr *pAddr) { +int Socket::receiveFrom(uint8_t* data, size_t length, struct sockaddr *pAddr) { socklen_t addrLen = sizeof(struct sockaddr); int rc = ::recvfrom(m_sock, data, length, 0, pAddr, &addrLen); return rc; -} // receiveFrom_cpp +} // receiveFrom /** @@ -310,7 +381,12 @@ int Socket::receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr *pAddr int Socket::send(const uint8_t* data, size_t length) const { ESP_LOGD(LOG_TAG, "send: Raw binary of length: %d", length); //GeneralUtils::hexDump(data, length); - int rc = ::lwip_send(m_sock, data, length, 0); + int rc; + if (getSSL()) { + rc = mbedtls_ssl_write((mbedtls_ssl_context*)&m_sslContext, data, length); + } else { + rc = ::lwip_send(m_sock, data, length, 0); + } if (rc == -1) { ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); } @@ -349,12 +425,119 @@ int Socket::send(uint32_t value) { * @param [in] pAddr The address to send the data. */ void Socket::sendTo(const uint8_t* data, size_t length, struct sockaddr* pAddr) { - int rc = ::sendto(m_sock, data, length, 0, pAddr, sizeof(struct sockaddr)); - if (rc == -1) { + int rc; + if (getSSL()) { + rc = mbedtls_ssl_write(&m_sslContext, data, length); + } else { + rc = ::sendto(m_sock, data, length, 0, pAddr, sizeof(struct sockaddr)); + } + if (rc < 0) { ESP_LOGE(LOG_TAG, "sendto: socket=%d %s", m_sock, strerror(errno)); } } // sendTo + +/** + * @brief Flag the socket as using SSL + * @param [in] sslValue True if we wish to use SSL. + */ +void Socket::setSSL(bool sslValue) { + const char* pers = "ssl_server"; + ESP_LOGD(LOG_TAG, ">> setSSL: %s", sslValue?"Yes":"No"); + m_useSSL = sslValue; + + if (sslValue == true) { + char* pvtKey = SSLUtils::getKey(); + char* certificate = SSLUtils::getCertificate(); + + mbedtls_net_init(&m_sslSock); + mbedtls_ssl_init(&m_sslContext); + mbedtls_ssl_config_init(&m_conf); + mbedtls_x509_crt_init(&m_srvcert); + mbedtls_pk_init(&m_pkey); + mbedtls_entropy_init(&m_entropy); + mbedtls_ctr_drbg_init(&m_ctr_drbg); + + + int ret = mbedtls_x509_crt_parse(&m_srvcert, (unsigned char*)certificate, strlen(certificate)+1); + if( ret != 0 ) { + ESP_LOGD(LOG_TAG, "mbedtls_x509_crt_parse returned 0x%x", -ret ); + goto exit; + } + + + ret = mbedtls_pk_parse_key(&m_pkey, (unsigned char*)pvtKey, strlen(pvtKey)+1, NULL, 0 ); + if( ret != 0 ) { + ESP_LOGD(LOG_TAG, "mbedtls_pk_parse_key returned 0x%x", -ret); + goto exit; + } + + ret = mbedtls_ctr_drbg_seed( &m_ctr_drbg, mbedtls_entropy_func, &m_entropy, (const unsigned char*) pers, strlen(pers)); + if( ret != 0 ) { + ESP_LOGD(LOG_TAG, "! mbedtls_ctr_drbg_seed returned %d\n",ret); + goto exit; + } + + ret = mbedtls_ssl_config_defaults(&m_conf, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if(ret != 0 ) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_config_defaults returned %d\n\n", ret); + goto exit; + } + + mbedtls_ssl_conf_rng(&m_conf, mbedtls_ctr_drbg_random, &m_ctr_drbg); + //mbedtls_ssl_conf_dbg(&m_conf, my_debug, stdout ); + + mbedtls_ssl_conf_ca_chain( &m_conf, m_srvcert.next, NULL); + ret = mbedtls_ssl_conf_own_cert( &m_conf, &m_srvcert, &m_pkey); + if(ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_conf_own_cert returned %d\n\n", ret); + goto exit; + } + + mbedtls_ssl_conf_dbg(&m_conf, my_debug, nullptr); + mbedtls_debug_set_threshold(4); + + ret = mbedtls_ssl_setup(&m_sslContext, &m_conf); + if(ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_setup returned %d\n\n", ret ); + goto exit; + } +/* + ret = mbedtls_ssl_set_hostname(&m_sslContext, "192.168.1.99"); + if(ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_set_hostname returned %d\n\n", ret ); + goto exit; + } + */ + + exit: + return; + } +} // setSSL + + +/** + * @brief perform the SSL handshake + */ +void Socket::sslHandshake() { + ESP_LOGD(LOG_TAG, ">> sslHandshake: sock: %d", m_sslSock.fd); + mbedtls_ssl_session_reset(&m_sslContext); + ESP_LOGD(LOG_TAG, " - Reset complete"); + mbedtls_ssl_set_bio(&m_sslContext, &m_sslSock, mbedtls_net_send, mbedtls_net_recv, NULL); + int ret; + while((ret = mbedtls_ssl_handshake(&m_sslContext))!=0) { + if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_handshake returned %d\n\n", ret ); + return; + } + } // End while + ESP_LOGD(LOG_TAG, "<< sslHandshake"); +} // sslHandshake + + /** * @brief Get the string representation of this socket * @return the string representation of the socket. @@ -406,3 +589,6 @@ SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() { setg(m_buffer, m_buffer, m_buffer + bytesRead); return traits_type::to_int_type(*gptr()); } // underflow + + + diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 82bb2b4e..90bac3de 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -7,6 +7,14 @@ #ifndef COMPONENTS_CPP_UTILS_SOCKET_H_ #define COMPONENTS_CPP_UTILS_SOCKET_H_ +#include + +#include +#include +#include +#include +#include +#include #include #include @@ -36,31 +44,41 @@ class Socket { Socket(); virtual ~Socket(); - Socket accept(); + Socket accept(bool useSSL=false); static std::string addressToString(struct sockaddr* addr); void bind(uint16_t port, uint32_t address); void close(); int connect(struct in_addr address, uint16_t port); int connect(char* address, uint16_t port); - int createSocket_cpp(bool isDatagram = false); + int createSocket(bool isDatagram = false); void getBind(struct sockaddr* pAddr); - int getFD() const; + int getFD() const; + bool getSSL() const; bool isValid(); void listen(uint16_t port, bool isDatagram=false); bool operator<(const Socket& other) const; std::string readToDelim(std::string delim); size_t receive(uint8_t* data, size_t length, bool exact=false); - int receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr* pAddr); - int send(std::string value) const; - int send(const uint8_t* data, size_t length) const; - int send(uint16_t value); - int send(uint32_t value); + int receiveFrom(uint8_t* data, size_t length, struct sockaddr* pAddr); + int send(std::string value) const; + int send(const uint8_t* data, size_t length) const; + int send(uint16_t value); + int send(uint32_t value); void sendTo(const uint8_t* data, size_t length, struct sockaddr* pAddr); + void setSSL(bool sslValue=true); std::string toString(); - private: - int m_sock; + int m_sock; // The underlying TCP/IP socket + bool m_useSSL; // Should we use SSL + mbedtls_net_context m_sslSock; + mbedtls_entropy_context m_entropy; + mbedtls_ctr_drbg_context m_ctr_drbg; + mbedtls_ssl_context m_sslContext; + mbedtls_ssl_config m_conf; + mbedtls_x509_crt m_srvcert; + mbedtls_pk_context m_pkey; + void sslHandshake(); }; class SocketInputRecordStreambuf : public std::streambuf { diff --git a/cpp_utils/TFTP.cpp b/cpp_utils/TFTP.cpp index 32d473c3..4e395591 100644 --- a/cpp_utils/TFTP.cpp +++ b/cpp_utils/TFTP.cpp @@ -172,7 +172,7 @@ void TFTP::TFTP_Transaction::processWRQ() { } while(!finished) { pRecv_data = (struct recv_data *)dataBuffer; - int receivedSize = m_partnerSocket.receiveFrom_cpp(dataBuffer, sizeof(dataBuffer), &recvAddr); + int receivedSize = m_partnerSocket.receiveFrom(dataBuffer, sizeof(dataBuffer), &recvAddr); if (receivedSize == -1) { ESP_LOGE(tag, "rc == -1 from receive_from"); } @@ -287,7 +287,7 @@ void TFTP::TFTP_Transaction::waitForAck(uint16_t blockNumber) { } ackData; ESP_LOGD(tag, "TFTP: Waiting for an acknowledgment request"); - int sizeRead = m_partnerSocket.receiveFrom_cpp((uint8_t *)&ackData, sizeof(ackData), &m_partnerAddress); + int sizeRead = m_partnerSocket.receiveFrom((uint8_t *)&ackData, sizeof(ackData), &m_partnerAddress); ESP_LOGD(tag, "TFTP: Received some data."); if (sizeRead != sizeof(ackData)) { @@ -331,7 +331,7 @@ uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { size_t length = 100; ESP_LOGD(tag, "TFTP: Waiting for a request"); - pServerSocket->receiveFrom_cpp(buf, length, &m_partnerAddress); + pServerSocket->receiveFrom(buf, length, &m_partnerAddress); // Save the filename, mode and op code. @@ -342,7 +342,7 @@ uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { // Handle the Write Request command. case TFTP_OPCODE_WRQ: { - m_partnerSocket.createSocket_cpp(true); + m_partnerSocket.createSocket(true); m_partnerSocket.bind(0, INADDR_ANY); sendAck(0); break; @@ -351,7 +351,7 @@ uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { // Handle the Read request command. case TFTP_OPCODE_RRQ: { - m_partnerSocket.createSocket_cpp(true); + m_partnerSocket.createSocket(true); m_partnerSocket.bind(0, INADDR_ANY); break; } From 01bfcfed4b7c8b1611c2cf6c18122227b482c4da Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 17 Sep 2017 17:13:48 -0500 Subject: [PATCH 042/381] Re-arranged headers for #65 --- cpp_utils/Socket.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 90bac3de..9f046f57 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -7,6 +7,7 @@ #ifndef COMPONENTS_CPP_UTILS_SOCKET_H_ #define COMPONENTS_CPP_UTILS_SOCKET_H_ + #include #include @@ -16,11 +17,6 @@ #include #include -#include -#include -#include -#include -#include #include #include @@ -29,8 +25,16 @@ #undef close #undef connect #undef listen +#undef read #undef recv #undef send +#undef write + +#include +#include +#include +#include +#include /** * @brief Encapsulate a socket. From b91f16b79862e6facb0986dfbd3a5ce722838e8a Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 19 Sep 2017 00:24:19 -0500 Subject: [PATCH 043/381] Sync 2017-09-18 --- cpp_utils/File.cpp | 5 +-- cpp_utils/FileSystem.cpp | 19 ++++++++++- cpp_utils/FileSystem.h | 1 + cpp_utils/HttpResponse.cpp | 2 +- cpp_utils/HttpServer.cpp | 25 ++++++++++++--- cpp_utils/HttpServer.h | 26 ++++++++------- cpp_utils/SSLUtils.cpp | 3 -- cpp_utils/SockServ.cpp | 19 +++++++---- cpp_utils/SockServ.h | 1 + cpp_utils/Socket.cpp | 65 ++++++++++++++++++++++++-------------- 10 files changed, 111 insertions(+), 55 deletions(-) diff --git a/cpp_utils/File.cpp b/cpp_utils/File.cpp index 6b0627af..85114ec4 100644 --- a/cpp_utils/File.cpp +++ b/cpp_utils/File.cpp @@ -126,8 +126,5 @@ bool File::isDirectory() { if (rc != 0) { return false; } - if (S_ISDIR(buf.st_mode)) { - return true; - } - return false; + return S_ISDIR(buf.st_mode); } // isDirectory diff --git a/cpp_utils/FileSystem.cpp b/cpp_utils/FileSystem.cpp index d0240745..0ae62ae1 100644 --- a/cpp_utils/FileSystem.cpp +++ b/cpp_utils/FileSystem.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -74,15 +75,31 @@ std::vector FileSystem::getDirectoryContents(std::string path) { } // getDirectoryContents +/** + * @brief Does the path refer to a directory? + * @param [in] path The path to the directory. + */ +bool FileSystem::isDirectory(std::string path) { + struct stat statBuf; + int rc = stat(path.c_str(), &statBuf); + if (rc != 0) { + return false; + } + return S_ISDIR(statBuf.st_mode); +} // isDirectory + + + /** * @brief Create a directory * @param [in] path The directory to create. * @return N/A. */ int FileSystem::mkdir(std::string path) { + ESP_LOGD(LOG_TAG, ">> mkdir: %s", path.c_str()); int rc = ::mkdir(path.c_str(), 0); if (rc != 0) { - ESP_LOGE(LOG_TAG, "mkdir: errno=%d", errno); + ESP_LOGE(LOG_TAG, "mkdir: errno=%d %s", errno, strerror(errno)); rc = errno; } return rc; diff --git a/cpp_utils/FileSystem.h b/cpp_utils/FileSystem.h index b2ab63b6..cb1e9ccd 100644 --- a/cpp_utils/FileSystem.h +++ b/cpp_utils/FileSystem.h @@ -17,6 +17,7 @@ class FileSystem { public: static std::vector getDirectoryContents(std:: string path); static void dumpDirectory(std::string path); + static bool isDirectory(std::string path); static int mkdir(std::string path); static std::vector pathSplit(std::string path); static int remove(std::string path); diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp index ee2acc72..70182ca0 100644 --- a/cpp_utils/HttpResponse.cpp +++ b/cpp_utils/HttpResponse.cpp @@ -27,7 +27,7 @@ const int HttpResponse::HTTP_STATUS_SERVICE_UNAVAILABLE = 503; static std::string lineTerminator = "\r\n"; HttpResponse::HttpResponse(HttpRequest *request) { m_request = request; - m_status = 0; + m_status = 200; m_headerCommitted = false; // We have not yet sent a header. } diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 6786064e..809ee407 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -12,6 +12,7 @@ #include #include "HttpRequest.h" #include "HttpResponse.h" +#include "FileSystem.h" #include "WebSocket.h" static const char* LOG_TAG = "HttpServer"; @@ -20,11 +21,13 @@ static const char* LOG_TAG = "HttpServer"; * Constructor for HTTP Server */ HttpServer::HttpServer() { - m_portNumber = 80; // The default port number. - m_rootPath = "/"; // The default path. - m_useSSL = false; + m_portNumber = 80; // The default port number. + m_rootPath = "/"; // The default path. + m_useSSL = false; // Default SSL is no. + setDirectoryListing(false); // Default directory listing is no. } // HttpServer + HttpServer::~HttpServer() { ESP_LOGD(LOG_TAG, "~HttpServer"); } @@ -88,6 +91,10 @@ class HttpServerTask: public Task { // Serve up the content from the file on the file system ... if found ... std::ifstream ifStream; std::string fileName = m_pHttpServer->getRootPath() + request.getPath(); // Build the absolute file name to read. + if (FileSystem::isDirectory(fileName)) { + ESP_LOGD(LOG_TAG, "Path is a directory"); + return; + } ESP_LOGD("HttpServerTask", "Opening file: %s", fileName.c_str()); ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); // Attempt to open the file for reading. @@ -131,7 +138,7 @@ class HttpServerTask: public Task { Socket clientSocket = sockServ.waitForNewClient(); // Block waiting for a new external client connection. - ESP_LOGD("HttpServerTask", "HttpServer listening on port %d received a new client connection", m_pHttpServer->getPort()); + ESP_LOGD("HttpServerTask", "HttpServer listening on port %d received a new client connection; sockFd=%d", m_pHttpServer->getPort(), clientSocket.getFD()); HttpRequest request(clientSocket); // Build the HTTP Request from the socket. @@ -197,6 +204,16 @@ bool HttpServer::getSSL() { return m_useSSL; } // getSSL + +/** + * @brief Set whether or not we will list directories. + * @param [in] use Set to true to enable directory listing. + */ +void HttpServer::setDirectoryListing(bool use) { + m_directoryListing = use; +} // setDirectoryListening + + /** * @brief Set the root path for URL file mapping. * diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index a7cebac0..0f3e6f5a 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -25,14 +25,14 @@ class HttpServerTask; class PathHandler { public: PathHandler( - std::string method, // The method in the request to be matched. - std::string pathPattern, // The pattern in the request to be matched - void (*pWebServerRequestHandler) // The handler function to be invoked upon a match. + std::string method, // The method in the request to be matched. + std::string pathPattern, // The pattern in the request to be matched + void (*pWebServerRequestHandler) // The handler function to be invoked upon a match. ( HttpRequest* pHttpRequest, HttpResponse* pHttpResponse) ); - bool match(std::string method, std::string path); // Does the request method and pattern match? + bool match(std::string method, std::string path); // Does the request method and pattern match? void invokePathHandler(HttpRequest* request, HttpResponse* response); private: std::string m_method; @@ -55,18 +55,20 @@ class HttpServer { HttpRequest* pHttpRequest, HttpResponse* pHttpResponse) ); - uint16_t getPort(); // Get the port on which the Http server is listening. - std::string getRootPath(); // Get the root of the file system path. - bool getSSL(); // Are we using SSL? - void setRootPath(std::string path); // Set the root of the file system path. + uint16_t getPort(); // Get the port on which the Http server is listening. + std::string getRootPath(); // Get the root of the file system path. + bool getSSL(); // Are we using SSL? + void setDirectoryListing(bool use); // Should we list the content of directories? + void setRootPath(std::string path); // Set the root of the file system path. void start(uint16_t portNumber, bool useSSL=false); private: friend class HttpServerTask; friend class WebSocket; - uint16_t m_portNumber; - std::vector m_pathHandlers; - std::string m_rootPath; // Root path into the file system. - bool m_useSSL; + uint16_t m_portNumber; // Port number on which server is listening. + std::vector m_pathHandlers; // Vector of path handlers. + std::string m_rootPath; // Root path into the file system. + bool m_useSSL; // Is this server listening on an HTTPS port? + bool m_directoryListing; // Should we list directory content? }; // HttpServer #endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ diff --git a/cpp_utils/SSLUtils.cpp b/cpp_utils/SSLUtils.cpp index b06a8957..92ebcfc4 100644 --- a/cpp_utils/SSLUtils.cpp +++ b/cpp_utils/SSLUtils.cpp @@ -13,12 +13,9 @@ char* SSLUtils::m_certificate = nullptr; char* SSLUtils::m_key = nullptr; SSLUtils::SSLUtils() { - // TODO Auto-generated constructor stub - } SSLUtils::~SSLUtils() { - // TODO Auto-generated destructor stub } void SSLUtils::setCertificate(std::string certificate) { diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index d4d23292..d18283a9 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -49,7 +49,7 @@ void SockServ::acceptTask(void *data) { SockServ* pSockServ = (SockServ*)data; while(1) { - Socket tempSock = pSockServ->m_serverSocket.accept(true); + Socket tempSock = pSockServ->m_serverSocket.accept(pSockServ->getSSL()); if (!tempSock.isValid()) { continue; } @@ -57,7 +57,6 @@ void SockServ::acceptTask(void *data) { pSockServ->m_clientSet.insert(tempSock); xQueueSendToBack(pSockServ->m_acceptQueue, &tempSock, portMAX_DELAY); pSockServ->m_clientSemaphore.give(); - FreeRTOS::sleep(9999999); } } // acceptTask @@ -82,6 +81,14 @@ void SockServ::disconnect(Socket s) { } // disconnect +/** + * Get the SSL status. + */ +bool SockServ::getSSL() { + return m_useSSL; +} // getSSL + + /** * @brief Wait for data * @param [in] pData Pointer to buffer to hold the data. @@ -198,12 +205,12 @@ Socket SockServ::waitForData(std::set& socketSet) { * or can return immediately is there is already a client connection in existence. */ Socket SockServ::waitForNewClient() { - - m_clientSemaphore.wait("waitForClient"); + ESP_LOGD(LOG_TAG, ">> waitForNewClient") + m_clientSemaphore.wait("waitForNewClient"); Socket tempSocket; xQueueReceive(m_acceptQueue, &tempSocket,portMAX_DELAY); - ESP_LOGD(LOG_TAG, "<< waitForClient"); + ESP_LOGD(LOG_TAG, "<< waitForNewClient"); return tempSocket; -} // waitForClient +} // waitForNewClient diff --git a/cpp_utils/SockServ.h b/cpp_utils/SockServ.h index ba4801d2..b7e170da 100644 --- a/cpp_utils/SockServ.h +++ b/cpp_utils/SockServ.h @@ -44,6 +44,7 @@ class SockServ { SockServ(); int connectedCount(); void disconnect(Socket s); + bool getSSL(); size_t receiveData(Socket s, void* pData, size_t maxData); void sendData(uint8_t* data, size_t length); void sendData(std::string str); diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index a62e3390..ce464b45 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -59,11 +59,11 @@ Socket::~Socket() { Socket Socket::accept(bool useSSL) { struct sockaddr addr; getBind(&addr); - ESP_LOGD(LOG_TAG, ">> accept: Accepting on %s", addressToString(&addr).c_str()); + ESP_LOGD(LOG_TAG, ">> accept: Accepting on %s; sockFd: %d, using SSL: %d", addressToString(&addr).c_str(), m_sock, useSSL); struct sockaddr_in clientAddress; socklen_t clientAddressLength = sizeof(clientAddress); - int clientSockFD = ::lwip_accept(m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); + int clientSockFD = ::lwip_accept_r(m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); if (clientSockFD == -1) { ESP_LOGE(LOG_TAG, "accept(): %s, m_sock=%d", strerror(errno), m_sock); Socket newSocket; @@ -119,7 +119,7 @@ void Socket::bind(uint16_t port, uint32_t address) { serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = htonl(address); serverAddress.sin_port = htons(port); - int rc = ::lwip_bind(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + int rc = ::lwip_bind_r(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); if (rc == -1) { ESP_LOGE(LOG_TAG, "<< bind: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); return; @@ -134,7 +134,7 @@ void Socket::bind(uint16_t port, uint32_t address) { * @return N/A. */ void Socket::close() { - ESP_LOGD(LOG_TAG, "close: m_sock=%d", m_sock); + ESP_LOGD(LOG_TAG, "close: m_sock=%d, ssl: %d", m_sock, getSSL()); if (getSSL()) { int rc = mbedtls_ssl_close_notify(&m_sslContext); if (rc < 0) { @@ -142,7 +142,11 @@ void Socket::close() { } } if (m_sock != -1) { - ::lwip_close(m_sock); + ESP_LOGD(LOG_TAG, "Calling lwip_close on %d", m_sock); + int rc = lwip_close_r(m_sock); + if (rc != 0) { + ESP_LOGE(LOG_TAG, "Error with lwip_close"); + } } m_sock = -1; } // close @@ -164,7 +168,7 @@ int Socket::connect(struct in_addr address, uint16_t port) { inet_ntop(AF_INET, &address, msg, sizeof(msg)); ESP_LOGD(LOG_TAG, "Connecting to %s:[%d]", msg, port); createSocket(); - int rc = ::lwip_connect(m_sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); + int rc = ::lwip_connect_r(m_sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); if (rc == -1) { ESP_LOGE(LOG_TAG, "connect_cpp: Error: %s", strerror(errno)); close(); @@ -254,7 +258,7 @@ void Socket::listen(uint16_t port, bool isDatagram) { // For a datagram socket, we don't execute a listen call. That is is only for connection oriented // sockets. if (!isDatagram) { - int rc = ::lwip_listen(m_sock, 5); + int rc = ::lwip_listen_r(m_sock, 5); if (rc == -1) { ESP_LOGE(LOG_TAG, "<< listen: %s", strerror(errno)); } @@ -311,7 +315,7 @@ std::string Socket::readToDelim(std::string delim) { * @return The length of the data received or -1 on an error. */ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { - ESP_LOGD(LOG_TAG, ">> receive: sockFd: %d, length: %d, exact: %d", m_sock, length, exact); + //ESP_LOGD(LOG_TAG, ">> receive: sockFd: %d, length: %d, exact: %d", m_sock, length, exact); if (exact == false) { int rc; if (getSSL()) { @@ -320,13 +324,13 @@ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { ESP_LOGD(LOG_TAG, "rc=%d, MBEDTLS_ERR_SSL_WANT_READ=%d", rc, MBEDTLS_ERR_SSL_WANT_READ); } while(rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); } else { - rc = ::lwip_recv(m_sock, data, length, 0); + rc = ::lwip_recv_r(m_sock, data, length, 0); if (rc == -1) { ESP_LOGE(LOG_TAG, "receive: %s", strerror(errno)); } } //GeneralUtils::hexDump(data, rc); - ESP_LOGD(LOG_TAG, "<< receive: rc: %d", rc); + //ESP_LOGD(LOG_TAG, "<< receive: rc: %d", rc); return rc; } // Read what we can, doesn't need to be an exact amount. @@ -338,10 +342,10 @@ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { rc = mbedtls_ssl_read(&m_sslContext, data, amountToRead); } while(rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); } else { - rc = ::lwip_recv(m_sock, data, amountToRead, 0); + rc = ::lwip_recv_r(m_sock, data, amountToRead, 0); } if (rc == -1) { - ESP_LOGE(LOG_TAG, "receive_cpp: %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "receive: %s", strerror(errno)); return 0; } if (rc == 0) { @@ -351,7 +355,7 @@ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { data += rc; } //GeneralUtils::hexDump(data, length); - ESP_LOGD(LOG_TAG, "<< receive: %d", length); + //ESP_LOGD(LOG_TAG, "<< receive: %d", length); return length; } // receive_cpp @@ -385,7 +389,7 @@ int Socket::send(const uint8_t* data, size_t length) const { if (getSSL()) { rc = mbedtls_ssl_write((mbedtls_ssl_context*)&m_sslContext, data, length); } else { - rc = ::lwip_send(m_sock, data, length, 0); + rc = ::lwip_send_r(m_sock, data, length, 0); } if (rc == -1) { ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); @@ -447,8 +451,16 @@ void Socket::setSSL(bool sslValue) { m_useSSL = sslValue; if (sslValue == true) { - char* pvtKey = SSLUtils::getKey(); + char* pvtKey = SSLUtils::getKey(); char* certificate = SSLUtils::getCertificate(); + if (pvtKey == nullptr) { + ESP_LOGE(LOG_TAG, "No private key file"); + return; + } + if (certificate == nullptr) { + ESP_LOGE(LOG_TAG, "No certificate file"); + return; + } mbedtls_net_init(&m_sslSock); mbedtls_ssl_init(&m_sslContext); @@ -458,14 +470,12 @@ void Socket::setSSL(bool sslValue) { mbedtls_entropy_init(&m_entropy); mbedtls_ctr_drbg_init(&m_ctr_drbg); - int ret = mbedtls_x509_crt_parse(&m_srvcert, (unsigned char*)certificate, strlen(certificate)+1); if( ret != 0 ) { ESP_LOGD(LOG_TAG, "mbedtls_x509_crt_parse returned 0x%x", -ret ); goto exit; } - ret = mbedtls_pk_parse_key(&m_pkey, (unsigned char*)pvtKey, strlen(pvtKey)+1, NULL, 0 ); if( ret != 0 ) { ESP_LOGD(LOG_TAG, "mbedtls_pk_parse_key returned 0x%x", -ret); @@ -487,10 +497,12 @@ void Socket::setSSL(bool sslValue) { goto exit; } + mbedtls_ssl_conf_authmode(&m_conf, MBEDTLS_SSL_VERIFY_NONE); + mbedtls_ssl_conf_rng(&m_conf, mbedtls_ctr_drbg_random, &m_ctr_drbg); - //mbedtls_ssl_conf_dbg(&m_conf, my_debug, stdout ); - mbedtls_ssl_conf_ca_chain( &m_conf, m_srvcert.next, NULL); + +// mbedtls_ssl_conf_ca_chain( &m_conf, m_srvcert.next, NULL); ret = mbedtls_ssl_conf_own_cert( &m_conf, &m_srvcert, &m_pkey); if(ret != 0) { ESP_LOGD(LOG_TAG, "mbedtls_ssl_conf_own_cert returned %d\n\n", ret); @@ -498,7 +510,9 @@ void Socket::setSSL(bool sslValue) { } mbedtls_ssl_conf_dbg(&m_conf, my_debug, nullptr); +#ifdef CONFIG_MBEDTLS_DEBUG mbedtls_debug_set_threshold(4); +#endif ret = mbedtls_ssl_setup(&m_sslContext, &m_conf); if(ret != 0) { @@ -527,8 +541,13 @@ void Socket::sslHandshake() { mbedtls_ssl_session_reset(&m_sslContext); ESP_LOGD(LOG_TAG, " - Reset complete"); mbedtls_ssl_set_bio(&m_sslContext, &m_sslSock, mbedtls_net_send, mbedtls_net_recv, NULL); - int ret; - while((ret = mbedtls_ssl_handshake(&m_sslContext))!=0) { + + while(1) { + int ret = mbedtls_ssl_handshake(&m_sslContext); + if (ret == 0) { + break; + } + if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { ESP_LOGD(LOG_TAG, "mbedtls_ssl_handshake returned %d\n\n", ret ); return; @@ -568,6 +587,7 @@ SocketInputRecordStreambuf::SocketInputRecordStreambuf( setg(m_buffer, m_buffer, m_buffer); // Set the initial get buffer pointers to no data. } // SocketInputRecordStreambuf + SocketInputRecordStreambuf::~SocketInputRecordStreambuf() { delete[] m_buffer; } // ~SocketInputRecordStreambuf @@ -589,6 +609,3 @@ SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() { setg(m_buffer, m_buffer, m_buffer + bytesRead); return traits_type::to_int_type(*gptr()); } // underflow - - - From 3012c0fb89d805189ac59f1bf91c8fa2e64dfe87 Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 19 Sep 2017 15:38:48 -0500 Subject: [PATCH 044/381] Fix for #71 --- cpp_utils/ArduinoBLE.md | 4 ++-- cpp_utils/cpp_utils | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 120000 cpp_utils/cpp_utils diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index a2cb3be6..59903d2c 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -25,13 +25,13 @@ If you have previously installed a version of the Arduino BLE Support and need t The BLE support contains extensive internal diagnostics which can be switched on by editing the file called `sdkconfig.h` and finding the lines which read: ``` -#define CONFIG_LOG_DEFAULT 1 +#define CONFIG_LOG_DEFAULT_LEVEL 1 ``` Change this to: ``` -#define CONFIG_LOG_DEFAULT 5 +#define CONFIG_LOG_DEFAULT_LEVEL 5 ``` and rebuild/deploy your project. diff --git a/cpp_utils/cpp_utils b/cpp_utils/cpp_utils new file mode 120000 index 00000000..5d96b78d --- /dev/null +++ b/cpp_utils/cpp_utils @@ -0,0 +1 @@ +/home/kolban/esp32/esptest/apps/workspace/esp32-snippets/cpp_utils \ No newline at end of file From 19e21f1e5b51593c993f6c647c2d83ce9b71661d Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 19 Sep 2017 15:42:18 -0500 Subject: [PATCH 045/381] Fix for #70 --- cpp_utils/I2C.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index b3a19923..636efd8c 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -26,7 +26,7 @@ I2C::I2C() { address = 0; cmd = 0; sdaPin = DEFAULT_SDA_PIN; - sclPin = DEFAULT_SDA_PIN; + sclPin = DEFAULT_CLK_PIN; } // I2C From 754b18834b5b063325a652d93098338a4cee592c Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Wed, 20 Sep 2017 10:02:06 +0300 Subject: [PATCH 046/381] Fix WebServer headers --- cpp_utils/WebServer.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 9a2696e7..1f34c7e9 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -449,15 +449,14 @@ void WebServer::HTTPResponse::sendData(uint8_t *pData, size_t length) { } m_dataSent = true; - std::map::iterator iter; std::string headers; - for (iter = m_headers.begin(); iter != m_headers.end(); iter++) { - if (headers.length() == 0) { - headers = iter->first + ": " + iter->second; - } else { - headers = "; " + iter->first + "=" + iter->second; - } + for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + if(iter != m_headers.begin()) + headers += "\r\n"; + headers += iter->first; + headers += ": "; + headers += iter->second; } mg_send_head(m_nc, m_status, length, headers.c_str()); mg_send(m_nc, pData, length); From bf1bfc67cceb14ee0f7b9d1953f8c2dc6c00705a Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Wed, 20 Sep 2017 13:28:58 +0300 Subject: [PATCH 047/381] Lots of general optimizations for the WebServer --- cpp_utils/WebServer.cpp | 68 ++++++++++++++++++++++++++++------------- cpp_utils/WebServer.h | 49 +++++++++++++++++------------ 2 files changed, 75 insertions(+), 42 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 9a2696e7..d40eb3fd 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -294,7 +294,7 @@ WebServer::~WebServer() { * @brief Get the current root path. * @return The current root path. */ -std::string WebServer::getRootPath() { +const std::string& WebServer::getRootPath() { return m_rootPath; } // getRootPath @@ -319,10 +319,17 @@ std::string WebServer::getRootPath() { * @param [in] pathExpr The path being accessed. * @param [in] handler The callback function to be invoked when a request arrives. */ -void WebServer::addPathHandler(std::string method, std::string pathExpr, void (*handler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse)) { +void WebServer::addPathHandler(const std::string& method, const std::string& pathExpr, + void (* handler)(WebServer::HTTPRequest* pHttpRequest, + WebServer::HTTPResponse* pHttpResponse)) { m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); } // addPathHandler +void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr, + void (* handler)(WebServer::HTTPRequest* pHttpRequest, + WebServer::HTTPResponse* pHttpResponse)) { + m_pathHandlers.push_back(PathHandler(std::move(method), pathExpr, handler)); +} // addPathHandler /** * @brief Run the web server listening at the given port. @@ -387,7 +394,7 @@ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { * @param [in] path The root path on the file system. * @return N/A. */ -void WebServer::setRootPath(std::string path) { +void WebServer::setRootPath(const std::string& path) { m_rootPath = path; } // setRootPath @@ -419,10 +426,13 @@ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { * @param [in] name The name of the header. * @param [in] value The value of the header. */ -void WebServer::HTTPResponse::addHeader(std::string name, std::string value) { +void WebServer::HTTPResponse::addHeader(const std::string& name, const std::string& value) { m_headers[name] = value; } // addHeader +void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) { + m_headers[std::move(name)] = std::move(value); +} /** * @brief Send data to the HTTP caller. @@ -430,7 +440,7 @@ void WebServer::HTTPResponse::addHeader(std::string name, std::string value) { * @param [in] data The data to be sent to the HTTP caller. * @return N/A. */ -void WebServer::HTTPResponse::sendData(std::string data) { +void WebServer::HTTPResponse::sendData(const std::string& data) { sendData((uint8_t *)data.data(), data.length()); } // sendData @@ -442,7 +452,7 @@ void WebServer::HTTPResponse::sendData(std::string data) { * @param [in] length The length of the data to be sent. * @return N/A. */ -void WebServer::HTTPResponse::sendData(uint8_t *pData, size_t length) { +void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { if (m_dataSent) { ESP_LOGE(tag, "HTTPResponse: Data already sent! Attempt to send again/more."); return; @@ -470,10 +480,14 @@ void WebServer::HTTPResponse::sendData(uint8_t *pData, size_t length) { * @param [in] headers The complete set of headers to send to the caller. * @return N/A. */ -void WebServer::HTTPResponse::setHeaders(std::map headers) { +void WebServer::HTTPResponse::setHeaders(const std::map& headers) { m_headers = headers; } // setHeaders +void WebServer::HTTPResponse::setHeaders(std::map&& headers) { + m_headers = std::move(headers); +} // setHeaders + /** * @brief Get the current root path. @@ -489,11 +503,10 @@ std::string WebServer::HTTPResponse::getRootPath() { * @param [in] path The root path on the file system. * @return N/A. */ -void WebServer::HTTPResponse::setRootPath(std::string path) { +void WebServer::HTTPResponse::setRootPath(const std::string& path) { m_rootPath = path; } // setRootPath - /** * @brief Set the status value in the HTTP response. * @@ -537,7 +550,8 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m // Because we reached here, it means that we did NOT match a handler. Now we want to attempt // to retrieve the corresponding file content. - std::string filePath = httpResponse.getRootPath() + uri; + std::string filePath = httpResponse.getRootPath(); + filePath += uri; ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); FILE *file = fopen(filePath.c_str(), "r"); if (file != nullptr) { @@ -564,12 +578,22 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m * @param [in] pathPattern The path pattern to be matched. * @param [in] webServerRequestHandler The request handler to be called. */ -WebServer::PathHandler::PathHandler(std::string method, std::string pathPattern, void (*webServerRequestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse)) { +WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, + void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, + WebServer::HTTPResponse* pHttpResponse)) { m_method = method; m_pattern = std::regex(pathPattern); m_requestHandler = webServerRequestHandler; } // PathHandler +WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, + void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, + WebServer::HTTPResponse* pHttpResponse)) { + m_method = std::move(method); + m_pattern = std::regex(pathPattern); + m_requestHandler = webServerRequestHandler; +} // PathHandler + /** * @brief Determine if the path matches. @@ -578,7 +602,7 @@ WebServer::PathHandler::PathHandler(std::string method, std::string pathPattern, * @param [in] path The path to be matched. * @return True if the path matches. */ -bool WebServer::PathHandler::match(std::string method, std::string path) { +bool WebServer::PathHandler::match(const std::string& method, const std::string& path) { //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); if (method != m_method) { return false; @@ -615,7 +639,7 @@ WebServer::HTTPRequest::HTTPRequest(struct http_message* message) { * known as the body. This method returns that payload (if it exists). * @return The body of the request. */ -std::string WebServer::HTTPRequest::getBody() { +std::string WebServer::HTTPRequest::getBody() const { return mgStrToString(m_message->body); } // getBody @@ -625,7 +649,7 @@ std::string WebServer::HTTPRequest::getBody() { * An HTTP request contains a request method which is one of GET, PUT, POST, etc. * @return The method of the request. */ -std::string WebServer::HTTPRequest::getMethod() { +std::string WebServer::HTTPRequest::getMethod() const { return mgStrToString(m_message->method); } // getMethod @@ -636,7 +660,7 @@ std::string WebServer::HTTPRequest::getMethod() { * but does not include any query parameters. * @return The path of the request. */ -std::string WebServer::HTTPRequest::getPath() { +std::string WebServer::HTTPRequest::getPath() const { return mgStrToString(m_message->uri); } // getPath @@ -648,7 +672,7 @@ std::string WebServer::HTTPRequest::getPath() { * * @return The query part of the request. */ -std::map WebServer::HTTPRequest::getQuery() { +std::map WebServer::HTTPRequest::getQuery() const { // Walk through all the characters in the query string maintaining a simple state machine // that lets us know what we are parsing. std::map queryMap; @@ -712,7 +736,7 @@ std::map WebServer::HTTPRequest::getQuery() { * * @return A vector of the constituent parts of the path. */ -std::vector WebServer::HTTPRequest::pathSplit() { +std::vector WebServer::HTTPRequest::pathSplit() const { std::istringstream stream(getPath()); std::vector ret; std::string pathPart; @@ -734,7 +758,7 @@ std::vector WebServer::HTTPRequest::pathSplit() { * @param [in] fileName The name of the file being uploaded (may not be present). * @return N/A. */ -void WebServer::HTTPMultiPart::begin(std::string varName, std::string fileName) { +void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::string& fileName) { ESP_LOGD(tag, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", varName.c_str(), fileName.c_str()); } // WebServer::HTTPMultiPart::begin @@ -757,7 +781,7 @@ void WebServer::HTTPMultiPart::end() { * @param [in] data The data received in this callback. * @return N/A. */ -void WebServer::HTTPMultiPart::data(std::string data) { +void WebServer::HTTPMultiPart::data(const std::string& data) { ESP_LOGD(tag, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); } // WebServer::HTTPMultiPart::data @@ -794,7 +818,7 @@ void WebServer::WebSocketHandler::onCreated() { * @param [in] message The message received from the client. * @return N/A. */ -void WebServer::WebSocketHandler::onMessage(std::string message){ +void WebServer::WebSocketHandler::onMessage(const std::string& message){ } // onMessage @@ -812,7 +836,7 @@ void WebServer::WebSocketHandler::onClosed() { * @param [in] message The message to send down the socket. * @return N/A. */ -void WebServer::WebSocketHandler::sendData(std::string message) { +void WebServer::WebSocketHandler::sendData(const std::string& message) { ESP_LOGD(tag, "WebSocketHandler::sendData(length=%d)", message.length()); mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, @@ -825,7 +849,7 @@ void WebServer::WebSocketHandler::sendData(std::string message) { * @param [in] size The size of the message * @return N/A. */ -void WebServer::WebSocketHandler::sendData(uint8_t *data, uint32_t size) { +void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, data, size); diff --git a/cpp_utils/WebServer.h b/cpp_utils/WebServer.h index adbba786..8d08b5ac 100644 --- a/cpp_utils/WebServer.h +++ b/cpp_utils/WebServer.h @@ -32,11 +32,11 @@ class WebServer { class HTTPRequest { public: HTTPRequest(struct http_message* message); - std::string getMethod(); - std::string getPath(); - std::map getQuery(); - std::string getBody(); - std::vector pathSplit(); + std::string getMethod() const; + std::string getPath() const; + std::map getQuery() const; + std::string getBody() const; + std::vector pathSplit() const; private: struct http_message* m_message; }; // HTTPRequest @@ -47,13 +47,15 @@ class WebServer { class HTTPResponse { public: HTTPResponse(struct mg_connection *nc); - void addHeader(std::string name, std::string value); + void addHeader(const std::string& name, const std::string& value); + void addHeader(std::string&& name, std::string&& value); std::string getRootPath(); void setStatus(int status); - void setHeaders(std::map headers); - void sendData(std::string data); - void sendData(uint8_t *pData, size_t length); - void setRootPath(std::string path); + void setHeaders(const std::map& headers); + void setHeaders(std::map&& headers); + void sendData(const std::string& data); + void sendData(const uint8_t* pData, size_t length); + void setRootPath(const std::string& path); private: struct mg_connection *m_nc; std::string m_rootPath; @@ -87,9 +89,9 @@ class WebServer { public: virtual ~HTTPMultiPart() { }; - virtual void begin(std::string varName, std::string fileName); + virtual void begin(const std::string& varName, const std::string& fileName); virtual void end(); - virtual void data(std::string data); + virtual void data(const std::string& data); virtual void multipartEnd(); virtual void multipartStart(); }; // HTTPMultiPart @@ -145,8 +147,9 @@ class WebServer { */ class PathHandler { public: - PathHandler(std::string method, std::string pathPattern, void (*webServerRequestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse)); - bool match(std::string method, std::string path); + PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + PathHandler(std::string&& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + bool match(const std::string& method, const std::string& path); void invoke(HTTPRequest *request, HTTPResponse *response); private: std::string m_method; @@ -160,10 +163,11 @@ class WebServer { class WebSocketHandler { public: void onCreated(); - virtual void onMessage(std::string message); + virtual void onMessage(const std::string& message); + // virtual void onMessage(std::string&& message); // ? Don't know what this is used for yet so i will not touch it void onClosed(); - void sendData(std::string message); - void sendData(uint8_t *data, uint32_t size); + void sendData(const std::string& message); + void sendData(const uint8_t* data, uint32_t size); void close(); private: struct mg_connection *m_mgConnection; @@ -176,10 +180,15 @@ class WebServer { WebServer(); virtual ~WebServer(); - void addPathHandler(std::string method, std::string pathExpr, void (*webServerRequestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse) ); - std::string getRootPath(); + void addPathHandler(const std::string& method, const std::string& pathExpr, + void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, + WebServer::HTTPResponse* pHttpResponse)); + void addPathHandler(std::string&& method, const std::string& pathExpr, + void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, + WebServer::HTTPResponse* pHttpResponse)); + const std::string& getRootPath(); void setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory); - void setRootPath(std::string path); + void setRootPath(const std::string& path); void setWebSocketHandlerFactory(WebSocketHandlerFactory *pWebSocketHandlerFactory); void start(unsigned short port = 80); void processRequest(struct mg_connection *mgConnection, struct http_message *message); From f0f287603328e2fe2f5b5df85e30e529b8e028c5 Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 20 Sep 2017 08:46:33 -0500 Subject: [PATCH 048/381] Sync - 2017-09-20 --- cpp_utils/BLEAdvertisedDevice.cpp | 2 +- cpp_utils/BLEClient.cpp | 7 +- cpp_utils/WebSocketFileTransfer.cpp | 86 +++++++++++++------ cpp_utils/WebSocketFileTransfer.h | 11 ++- .../Arduino/BLE_client/BLE_client.ino | 2 + 5 files changed, 74 insertions(+), 34 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 97ec1e33..599c548c 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -265,7 +265,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { } // ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE default: { - ESP_LOGD(LOG_TAG, "Unhandled type"); + ESP_LOGD(LOG_TAG, "Unhandled type: adType: %d - 0x%.2x", ad_type, ad_type); break; } } // switch diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index a5d60cf3..0440ae83 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -249,6 +249,7 @@ BLERemoteService* BLEClient::getService(const char* uuid) { * @return A reference to the Service or nullptr if don't know about it. */ BLERemoteService* BLEClient::getService(BLEUUID uuid) { + ESP_LOGD(LOG_TAG, ">> getService: uuid: %s", uuid.toString().c_str()); // Design // ------ // We wish to retrieve the service given its UUID. It is possible that we have not yet asked the @@ -258,12 +259,14 @@ BLERemoteService* BLEClient::getService(BLEUUID uuid) { if (!m_haveServices) { getServices(); } - std::string v = uuid.toString(); + std::string uuidStr = uuid.toString(); for (auto &myPair : m_servicesMap) { - if (myPair.first == v) { + if (myPair.first == uuidStr) { + ESP_LOGD(LOG_TAG, "<< getService: found"); return myPair.second; } } + ESP_LOGD(LOG_TAG, "<< getService: not found"); return nullptr; } // getService diff --git a/cpp_utils/WebSocketFileTransfer.cpp b/cpp_utils/WebSocketFileTransfer.cpp index 0475b4f7..606f3401 100644 --- a/cpp_utils/WebSocketFileTransfer.cpp +++ b/cpp_utils/WebSocketFileTransfer.cpp @@ -15,69 +15,97 @@ static const char* LOG_TAG = "WebSocketFileTransfer"; #undef close -WebSocketFileTransfer::WebSocketFileTransfer() { - m_fileName = ""; - m_length = 0; +/** + * @brief Constructor + * @param [in] rootPath The path prefix for new files. + */ +WebSocketFileTransfer::WebSocketFileTransfer(std::string rootPath) { + m_rootPath = rootPath; m_pWebSocket = nullptr; } -WebSocketFileTransfer::~WebSocketFileTransfer() { - // TODO Auto-generated destructor stub -} - // Hide the class in an un-named namespace namespace { +/** + * @brief Transfer handler. + */ class FileTransferWebSocketHandler : public WebSocketHandler { +private: + std::string m_fileName; // The name of the file we are receiving. + uint32_t m_fileLength; // We may optionally receive a file length. + uint32_t m_sizeReceived; // The size of the data actually received so far. + bool m_active; // Are we actively processing a file. + std::ofstream m_ofStream; // The file stream we are writing to when active. + std::string m_rootPath; // The root path for file names. + public: - FileTransferWebSocketHandler() { + + FileTransferWebSocketHandler(std::string rootPath) { m_fileName = ""; m_fileLength = 0; m_sizeReceived = 0; m_active = false; - } + m_rootPath = rootPath; + } // FileTransferWebSocketHandler -private: - std::string m_fileName; // The name of the file we are receiving. - uint32_t m_fileLength; // We may optionally receive a file length. - uint32_t m_sizeReceived; // The size of the data actually received so far. - bool m_active; // Are we actively processing a file. - std::ofstream m_ofStream; + /** + * @brief Handler for the message received over the web socket. + */ virtual void onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf) { ESP_LOGD("FileTransferWebSocketHandler", ">> onMessage"); + // Test to see if we are currently active. If not, this is the start of a transfer. if (!m_active) { + + // Read a chunk of data into memory. std::stringstream buffer; buffer << pWebSocketInputStreambuf; + ESP_LOGD("FileTransferWebSocketHandler", "Data read: %s", buffer.str().c_str()); + + // We expect the first chunk received to be a JSON object that contains + // { + // "name": , // Name of file to create. + // "length": // Length of file. Optional. + // } JsonObject jo = JSON::parseObject(buffer.str()); m_fileName = jo.getString("name"); + assert(m_fileName.length() > 0); // Doesn't make any sense to receive a zero length file name. if (jo.hasItem("length")) { m_fileLength = jo.getInt("length"); } - std::string fileName = "/spiflash/" + m_fileName; - if (m_fileName.length() > 0 && m_fileName.substr(m_fileName.size()-1)=="/") { + std::string fileName = m_rootPath + m_fileName; + + // If the file to create ends in a "/" then we are being asked to create a directory. + if (m_fileName.substr(m_fileName.size()-1)=="/") { ESP_LOGD("FileTransferWebSocketHandler", "Is a directory!!"); - fileName = fileName.substr(0, fileName.size()-1); + fileName = fileName.substr(0, fileName.size()-1); // Remove the trailing slash struct stat statbuf; if (stat(fileName.c_str(), &statbuf) == 0) { if (S_ISREG(statbuf.st_mode)) { ESP_LOGE("FileTransferWebSocketHandler", "File already exists and is a file not a directory!"); } + } // Stat the directory we are trying to create. + else { // The stat on the path failed ... which is ok, as it likely means that the entry didn't exist. + if (mkdir(fileName.c_str(), 0) != 0) { + ESP_LOGE("FileTransferWebSocketHandler", "Failed to make directory \"%s\", error: %s", fileName.c_str(), strerror(errno)); + } } - if (mkdir(fileName.c_str(), 0) != 0) { - ESP_LOGE("FileTransferWebSocketHandler", "Failed to make directory \"%s\", error: %s", fileName.c_str(), strerror(errno)); - } - } else { + } + // We are NOT creating a directory but are instead creating a file. + else { m_ofStream.open(fileName, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); if (!m_ofStream.is_open()) { ESP_LOGE("FileTransferWebSocketHandler", "Failed to open file %s", m_fileName.c_str()); + return; } } m_active = true; ESP_LOGD("FileTransferWebSocketHandler", "Filename: %s, length: %d", fileName.c_str(), m_fileLength); - } else { + } // !active --- Not active + else { // We are about to receive a chunk of file m_ofStream << pWebSocketInputStreambuf; /* @@ -94,19 +122,27 @@ class FileTransferWebSocketHandler : public WebSocketHandler { } // onMessage + /** + * @brief Handle a close event on the web socket. + */ virtual void onClose() { ESP_LOGD("FileTransferWebSocketHandler", ">> onClose: fileName: %s, sizeReceived: %d", m_fileName.c_str(), m_sizeReceived); if (m_fileLength > 0 && m_sizeReceived != m_fileLength) { ESP_LOGD("FileTransferWebSocketHandler", "ERROR: Transfer finished but we received total of %d bytes and expected %d bytes!", m_sizeReceived, m_fileLength); } - m_ofStream.close(); + if (m_ofStream.is_open()) { + m_ofStream.close(); // Close the file now that we have finished writing to it. + } } // onClose }; // FileTransferWebSocketHandler } // End un-named namespace + void WebSocketFileTransfer::start(WebSocket* pWebSocket) { ESP_LOGD(LOG_TAG, ">> start"); - pWebSocket->setHandler(new FileTransferWebSocketHandler()); + pWebSocket->setHandler(new FileTransferWebSocketHandler(m_rootPath)); } // start + + diff --git a/cpp_utils/WebSocketFileTransfer.h b/cpp_utils/WebSocketFileTransfer.h index d3db986b..98cbda9b 100644 --- a/cpp_utils/WebSocketFileTransfer.h +++ b/cpp_utils/WebSocketFileTransfer.h @@ -12,13 +12,12 @@ class WebSocketFileTransfer { private: - std::string m_fileName; - size_t m_length; - WebSocket* m_pWebSocket; + WebSocket* m_pWebSocket; // The WebSocket over which the file data will arrive. + std::string m_rootPath; + public: - WebSocketFileTransfer(); - virtual ~WebSocketFileTransfer(); - void start(WebSocket *pWebSocket); + WebSocketFileTransfer(std::string rootPath); + void start(WebSocket *pWebSocket); }; #endif /* COMPONENTS_CPP_UTILS_WEBSOCKETFILETRANSFER_H_ */ diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino index ed7fbf0c..c0b6163c 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino @@ -44,6 +44,7 @@ bool connectToServer(BLEAddress pAddress) { Serial.println(serviceUUID.toString().c_str()); return false; } + Serial.println(" - Found our service"); // Obtain a reference to the characteristic in the service of the remote BLE server. @@ -53,6 +54,7 @@ bool connectToServer(BLEAddress pAddress) { Serial.println(charUUID.toString().c_str()); return false; } + Serial.println(" - Found our characteristic"); // Read the value of the characteristic. std::string value = pRemoteCharacteristic->readValue(); From 0e12736378e644cfc8fa23717cd7268916f6e913 Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 20 Sep 2017 08:48:30 -0500 Subject: [PATCH 049/381] Sync - 2017-09-20 --- cpp_utils/Arduino/ESP32_BLE.zip | Bin 79700 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 cpp_utils/Arduino/ESP32_BLE.zip diff --git a/cpp_utils/Arduino/ESP32_BLE.zip b/cpp_utils/Arduino/ESP32_BLE.zip deleted file mode 100644 index c253727271995ac48c301d61f25f45738e616c62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79700 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 Date: Thu, 21 Sep 2017 11:30:15 -0500 Subject: [PATCH 050/381] Fix for #77 --- cpp_utils/Makefile.arduino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index c14be636..345b52bf 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -59,7 +59,7 @@ build_ble: cp $(BLE_FILES) Arduino/ESP32_BLE/src cp Arduino_ESP32_BLE.library.properties Arduino/ESP32_BLE/library.properties mkdir -p Arduino/ESP32_BLE/examples - cp --recursive tests/BLETests/Arduino Arduino/ESP32_BLE/examples + cp -R tests/BLETests/Arduino Arduino/ESP32_BLE/examples rm -f Arduino/ESP32_BLE.zip cd Arduino; zip -r ESP32_BLE.zip ESP32_BLE rm -rf Arduino/ESP32_BLE From e1040b8ea7f3488e8314325ebdce28d817e7779f Mon Sep 17 00:00:00 2001 From: kolban Date: Thu, 21 Sep 2017 22:23:26 -0500 Subject: [PATCH 051/381] Improvements on the ArduinoBLE docs --- cpp_utils/ArduinoBLE.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index 59903d2c..07fcf418 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -5,9 +5,11 @@ That said, we now have the ability to produce a driver you can use for testing. When complete, the BLE support for ESP32 Arduino will be available as a single ZIP file. The file will be called **ESP32_BLE.zip**. It is this file that will be able to be imported into the Arduino IDE from the `Sketch -> Include Library -> Add .ZIP library`. When initial development of the library has been completed, this ZIP will be placed under some form of release control so that an ordinary Arduino IDE user can simply download this as a unit and install. -We provide sample builds of the `ESP32_BLE.zip` file in the `Arduino` folder found relative to this directory. +A build of the BLE support for Arduino can be found through the Arduino IDE. Visit Sketch -> Include Library -> Manage Libraries. In the library filter, enter "esp32 ble arduino". The search will narrow and you should see "ESP32 BLE Arduino" available for installation or upgrade. -The build of the Arduino support will be current as of the date of the ZIP file however should you wish to build your own instance of the ZIP from the source, here is the recipe. + + +Should you wish to build your own instance of the ZIP from the source, here is the recipe. 1. Create a new directory called `build` 2. Enter that directory and run `git clone https://github.com/nkolban/esp32-snippets.git` From 2e05c69f7d5afc8fece25df3c16af5775c8b9c25 Mon Sep 17 00:00:00 2001 From: kolban Date: Fri, 22 Sep 2017 18:40:14 -0500 Subject: [PATCH 052/381] Updates for #68 --- cpp_utils/ArduinoBLE.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index 07fcf418..07b37fd9 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -42,4 +42,13 @@ This file can be found in your Arduino IDE installation directories at: ``` /hardware/espressif/esp32/tools/sdk/include/config -``` \ No newline at end of file +``` + +## Decoding an exception stack trace +While using the BLE C++ classes there is always the unfortunate possibility that something will go wrong and your application crash. Fortunately, this results in some debug information being logged to the console. This is known as a *stack trace*. Included in the stack trace are a sequence of hexadecimal numbers known as the *back trace* which are the list of addresses of functions that were executed just before the crash was detected. If we could decode these we would have a lot of great information that could be used to aid in the resolution. Fortunately there is a fantastic project that makes decoding this information very easy indeed. + +This project can be found here: + +https://github.com/me-no-dev/EspExceptionDecoder + +If you start to encounter crashes in your BLE C++ applications and wish to report these through Github issues, please help us to help you by installing this tool int your Arduino IDE and including the decoded information in the issue report. From f19354ccced46d53a1e391a14eace4e15634e3d4 Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 24 Sep 2017 23:56:20 -0500 Subject: [PATCH 053/381] Iterim fixes for #82 --- cpp_utils/BLEAdvertisedDevice.cpp | 6 +- cpp_utils/BLECharacteristic.h | 14 +-- cpp_utils/BLEClient.cpp | 60 +++++++---- cpp_utils/BLEClient.h | 1 + cpp_utils/BLEDevice.cpp | 12 +-- cpp_utils/BLERemoteCharacteristic.cpp | 144 ++++++++++++++------------ cpp_utils/BLERemoteCharacteristic.h | 12 ++- cpp_utils/BLERemoteDescriptor.cpp | 53 ++++++++++ cpp_utils/BLERemoteDescriptor.h | 25 +++++ cpp_utils/BLERemoteService.cpp | 113 ++++++++++++++++++-- cpp_utils/BLERemoteService.h | 16 ++- cpp_utils/BLEScan.cpp | 3 + cpp_utils/BLEUUID.cpp | 6 +- cpp_utils/BLEUUID.h | 2 +- cpp_utils/BLEUtils.cpp | 128 +++++++++++++---------- cpp_utils/BLEUtils.h | 1 + cpp_utils/File.cpp | 27 +++-- cpp_utils/File.h | 3 +- cpp_utils/FileSystem.cpp | 2 +- cpp_utils/FreeRTOS.cpp | 5 +- cpp_utils/GeneralUtils.cpp | 18 ++++ cpp_utils/GeneralUtils.h | 1 + cpp_utils/HttpParser.cpp | 3 +- cpp_utils/HttpServer.cpp | 61 ++++++++++- cpp_utils/WebSocketFileTransfer.cpp | 17 ++- 25 files changed, 540 insertions(+), 193 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 599c548c..f1752eda 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -329,7 +329,7 @@ void BLEAdvertisedDevice::setManufacturerData(std::string manufacturerData) { void BLEAdvertisedDevice::setName(std::string name) { m_name = name; m_haveName = true; - ESP_LOGD(LOG_TAG, "- name: %s", m_name.c_str()); + ESP_LOGD(LOG_TAG, "- setName(): name: %s", m_name.c_str()); } // setName @@ -340,7 +340,7 @@ void BLEAdvertisedDevice::setName(std::string name) { void BLEAdvertisedDevice::setRSSI(int rssi) { m_rssi = rssi; m_haveRSSI = true; - ESP_LOGD(LOG_TAG, "- rssi: %d", m_rssi); + ESP_LOGD(LOG_TAG, "- setRSSI(): rssi: %d", m_rssi); } // setRSSI @@ -367,7 +367,7 @@ void BLEAdvertisedDevice::setServiceUUID(const char* serviceUUID) { void BLEAdvertisedDevice::setServiceUUID(BLEUUID serviceUUID) { m_serviceUUID = serviceUUID; m_haveServiceUUID = true; - ESP_LOGD(LOG_TAG, "- serviceUUID: %s", serviceUUID.toString().c_str()); + ESP_LOGD(LOG_TAG, "- setServiceUUID(): serviceUUID: %s", serviceUUID.toString().c_str()); } // setRSSI diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index b39c0226..24977c00 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -29,16 +29,16 @@ class BLEDescriptorMap { void setByUUID(const char* uuid, BLEDescriptor *pDescriptor); void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor); void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor); - BLEDescriptor *getByUUID(const char* uuid); - BLEDescriptor *getByUUID(BLEUUID uuid); - BLEDescriptor *getByHandle(uint16_t handle); - std::string toString(); + BLEDescriptor* getByUUID(const char* uuid); + BLEDescriptor* getByUUID(BLEUUID uuid); + BLEDescriptor* getByHandle(uint16_t handle); + std::string toString(); void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); - BLEDescriptor *getFirst(); - BLEDescriptor *getNext(); + esp_ble_gatts_cb_param_t* param); + BLEDescriptor* getFirst(); + BLEDescriptor* getNext(); private: std::map m_uuidMap; std::map m_handleMap; diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 0440ae83..5a0b70e0 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -51,6 +51,7 @@ BLEClient::BLEClient() { /** * @brief Connect to the partner. * @param [in] address The address of the partner. + * @return True on success. */ bool BLEClient::connect(BLEAddress address) { ESP_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str()); @@ -113,29 +114,38 @@ void BLEClient::gattClientEventHandler( // ESP_GATTC_NOTIFY_EVT // // notify - // uint16_t conn_id - // esp_bd_addr_t remote_bda - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id - // uint16_t value_len - // uint8_t* value - // bool is_notify + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + // - uint16_t handle + // - uint16_t value_len + // - uint8_t* value + // - bool is_notify + // + // We have received a notification event which means that the server wishes us to know about a notification + // piece of data. What we must now do is find the characteristic with the associated handle and then + // invoke its notification callback (if it has one). // case ESP_GATTC_NOTIFY_EVT: { - BLERemoteService *pBLERemoteService = getService(BLEUUID(evtParam->notify.srvc_id.id.uuid)); + BLERemoteService* pBLERemoteService = getService(evtParam->notify.handle); if (pBLERemoteService == nullptr) { - ESP_LOGE(LOG_TAG, "Could not find service with UUID %s for notification", BLEUUID(evtParam->notify.srvc_id.id.uuid).toString().c_str()); + ESP_LOGE(LOG_TAG, "Could not find service containing handle %d 0x%.2x for notification", + evtParam->notify.handle, evtParam->notify.handle); break; } - BLERemoteCharacteristic* pBLERemoteCharacteristic = pBLERemoteService->getCharacteristic(BLEUUID(evtParam->notify.char_id.uuid)); + BLERemoteCharacteristic* pBLERemoteCharacteristic = pBLERemoteService->getCharacteristic(evtParam->notify.handle); if (pBLERemoteCharacteristic == nullptr) { - ESP_LOGE(LOG_TAG, "Could not find characteristic with UUID %s for notification", BLEUUID(evtParam->notify.char_id.uuid).toString().c_str()); + ESP_LOGE(LOG_TAG, "Could not find characteristic with handle %d 0x%.2x for notification", + evtParam->notify.handle, evtParam->notify.handle); break; } if (pBLERemoteCharacteristic->m_notifyCallback != nullptr) { - pBLERemoteCharacteristic->m_notifyCallback(pBLERemoteCharacteristic, evtParam->notify.value, evtParam->notify.value_len, evtParam->notify.is_notify); - } + pBLERemoteCharacteristic->m_notifyCallback( + pBLERemoteCharacteristic, + evtParam->notify.value, + evtParam->notify.value_len, + evtParam->notify.is_notify + ); + } // End we have a callback function ... break; } // ESP_GATTC_NOTIFY_EVT @@ -189,12 +199,19 @@ void BLEClient::gattClientEventHandler( // ESP_GATTC_SEARCH_RES_EVT // // search_res: - // - uint16_t conn_id - // - esp_gatt_srvc_id_t srvc_id + // - uint16_t conn_id + // - uint16_t start_handle + // - uint16_t end_handle + // - esp_gatt_id_t srvc_id // case ESP_GATTC_SEARCH_RES_EVT: { BLEUUID uuid = BLEUUID(evtParam->search_res.srvc_id); - BLERemoteService* pRemoteService = new BLERemoteService(evtParam->search_res.srvc_id, this); + BLERemoteService* pRemoteService = new BLERemoteService( + evtParam->search_res.srvc_id, + this, + evtParam->search_res.start_handle, + evtParam->search_res.end_handle + ); m_servicesMap.insert(std::pair(uuid.toString(), pRemoteService)); break; } // ESP_GATTC_SEARCH_RES_EVT @@ -231,7 +248,12 @@ esp_gatt_if_t BLEClient::getGattcIf() { return m_gattc_if; } // getGattcIf - +BLERemoteService* BLEClient::getService(uint16_t handle) { + ESP_LOGD(LOG_TAG, ">> getService: handle: %d", handle); + ESP_LOGE(LOG_TAG, "!!! NOT IMPLEMENTED !!!"); + ESP_LOGD(LOG_TAG, "<< getService"); + return nullptr; +} /** * @brief Get the service object corresponding to the uuid. @@ -262,7 +284,7 @@ BLERemoteService* BLEClient::getService(BLEUUID uuid) { std::string uuidStr = uuid.toString(); for (auto &myPair : m_servicesMap) { if (myPair.first == uuidStr) { - ESP_LOGD(LOG_TAG, "<< getService: found"); + ESP_LOGD(LOG_TAG, "<< getService: found the service with uuid: %s", uuid.toString().c_str()); return myPair.second; } } diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 898f98c1..65f69986 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -34,6 +34,7 @@ class BLEClient { std::map* getServices(); BLERemoteService* getService(const char* uuid); BLERemoteService* getService(BLEUUID uuid); + BLERemoteService* getService(uint16_t handle); void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); std::string toString(); diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 04cae14c..37748037 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -69,19 +69,19 @@ void BLEDevice::gattServerEventHandler( * @brief Handle GATT client events. * * Handler for the GATT client events. - * * `ESP_GATTC_OPEN_EVT` – Invoked when a connection is opened. + * * `ESP_GATTC_OPEN_EVT` – Invoked when a connection is opened. * * `ESP_GATTC_PREP_WRITE_EVT` – Response to write a characteristic. - * * `ESP_GATTC_READ_CHAR_EVT` – Response to read a characteristic. - * * `ESP_GATTC_REG_EVT` – Invoked when a GATT client has been registered. + * * `ESP_GATTC_READ_CHAR_EVT` – Response to read a characteristic. + * * `ESP_GATTC_REG_EVT` – Invoked when a GATT client has been registered. * * @param [in] event * @param [in] gattc_if * @param [in] param */ void BLEDevice::gattClientEventHandler( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param) { + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* param) { ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 5227bf6c..372723ed 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -22,16 +22,18 @@ static const char* LOG_TAG = "BLERemoteCharacteristic"; BLERemoteCharacteristic::BLERemoteCharacteristic( - esp_gatt_id_t charId, + uint16_t handle, + BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService) { - m_charId = charId; + m_handle = handle; + m_uuid = uuid; m_charProp = charProp; m_pRemoteService = pRemoteService; m_notifyCallback = nullptr; } // BLERemoteCharacteristic - +/* static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { if (id1.id.inst_id != id2.id.inst_id) { return false; @@ -41,8 +43,9 @@ static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { } return true; } // compareSrvcId +*/ - +/* static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { if (id1.inst_id != id2.inst_id) { return false; @@ -52,6 +55,7 @@ static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { } return true; } // compareCharId +*/ /** @@ -73,27 +77,20 @@ void BLERemoteCharacteristic::gattClientEventHandler( // This event indicates that the server has responded to the read request. // // read: - // esp_gatt_status_t status - // uint16_t conn_id - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id - // uint8_t* value - // uint16_t value_type - // uint16_t value_len + // - esp_gatt_status_t status + // - uint16_t conn_id + // - uint16_t handle + // - uint8_t* value + // - uint16_t value_len + // case ESP_GATTC_READ_CHAR_EVT: { - if (compareSrvcId(evtParam->read.srvc_id, *m_pRemoteService->getSrvcId()) == false) { - break; - } - - if (evtParam->read.conn_id != m_pRemoteService->getClient()->getConnId()) { - break; - } - - if (compareGattId(evtParam->read.char_id, m_charId) == false) { + // If this event is not for us, then nothing further to do. + if (evtParam->read.handle != getHandle()) { break; } + // At this point, we have determined that the event is for us, so now we save the value + // and unlock the semaphore to ensure that the requestor of the data can continue. if (evtParam->read.status == ESP_GATT_OK) { m_value = std::string((char*)evtParam->read.value, evtParam->read.value_len); } else { @@ -110,15 +107,15 @@ void BLERemoteCharacteristic::gattClientEventHandler( // // reg_for_notify: // - esp_gatt_status_t status - // - esp_gatt_srvc_id_t srvc_id - // - esp_gatt_id_t char_id + // - uint16_t handle + // case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - if (compareSrvcId(evtParam->reg_for_notify.srvc_id, *m_pRemoteService->getSrvcId()) == false) { - break; - } - if (compareGattId(evtParam->reg_for_notify.char_id, m_charId) == false) { + // If the request is not for this BLERemoteCharacteristic then move on to the next. + if (evtParam->reg_for_notify.handle != getHandle()) { break; } + + // We have process the notify and can unlock the semaphore. m_semaphoreRegForNotifyEvt.give(); break; } // ESP_GATTC_REG_FOR_NOTIFY_EVT @@ -128,21 +125,18 @@ void BLERemoteCharacteristic::gattClientEventHandler( // ESP_GATTC_WRITE_CHAR_EVT // // write: - // esp_gatt_status_t status - // uint16_t conn_id - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id + // - esp_gatt_status_t status + // - uint16_t conn_id + // - uint16_t handle + // case ESP_GATTC_WRITE_CHAR_EVT: { - if (compareSrvcId(evtParam->write.srvc_id, *m_pRemoteService->getSrvcId()) == false) { - break; - } - if (evtParam->write.conn_id != m_pRemoteService->getClient()->getConnId()) { - break; - } - if (compareGattId(evtParam->write.char_id, m_charId) == false) { + // Determine if this event is for us and, if not, pass onwards. + if (evtParam->write.handle != getHandle()) { break; } + + // There is nothing further we need to do here. This is merely an indication + // that the write has completed and we can unlock the caller. m_semaphoreWriteCharEvt.give(); break; } // ESP_GATTC_WRITE_CHAR_EVT @@ -151,12 +145,18 @@ void BLERemoteCharacteristic::gattClientEventHandler( default: { break; } - } + } // End switch }; // gattClientEventHandler +uint16_t BLERemoteCharacteristic::getHandle() { + ESP_LOGD(LOG_TAG, ">> getHandle: Characteristic: %s", getUUID().toString().c_str()); + ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", m_handle, m_handle); + return m_handle; +} + BLEUUID BLERemoteCharacteristic::getUUID() { - return BLEUUID(m_charId.uuid); + return m_uuid; } /** @@ -203,17 +203,16 @@ uint8_t BLERemoteCharacteristic::readUInt8(void) { * @return The value of the remote characteristic. */ std::string BLERemoteCharacteristic::readValue() { - ESP_LOGD(LOG_TAG, ">> readValue()"); + ESP_LOGD(LOG_TAG, ">> readValue(): uuid: %s, handle: %d 0x%.2x", getUUID().toString().c_str(), getHandle(), getHandle()); m_semaphoreReadCharEvt.take("readValue"); // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. esp_err_t errRc = ::esp_ble_gattc_read_char( m_pRemoteService->getClient()->getGattcIf(), - m_pRemoteService->getClient()->getConnId(), - m_pRemoteService->getSrvcId(), - &m_charId, - ESP_GATT_AUTH_REQ_NONE); + m_pRemoteService->getClient()->getConnId(), // The connection ID to the BLE server + getHandle(), // The handle of this characteristic + ESP_GATT_AUTH_REQ_NONE); // Security if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -224,38 +223,51 @@ std::string BLERemoteCharacteristic::readValue() { // in m_value will contain our data. m_semaphoreReadCharEvt.wait("readValue"); - ESP_LOGD(LOG_TAG, "<< readValue()"); + ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length()); return m_value; } // readValue /** * @brief Register for notifications. - * @param [in] notifyCallback A callback to be invoked for a notification. + * @param [in] notifyCallback A callback to be invoked for a notification. If NULL is provided then we are + * unregistering a notification. * @return N/A. */ void BLERemoteCharacteristic::registerForNotify( void (*notifyCallback)( BLERemoteCharacteristic* pBLERemoteCharacteristic, - uint8_t* pData, - size_t length, - bool isNotify)) { + uint8_t* pData, + size_t length, + bool isNotify)) { ESP_LOGD(LOG_TAG, ">> registerForNotify()"); - m_notifyCallback = notifyCallback; // Save the notification callback. + m_notifyCallback = notifyCallback; // Save the notification callback. m_semaphoreRegForNotifyEvt.take("registerForNotify"); - esp_err_t errRc = ::esp_ble_gattc_register_for_notify( - m_pRemoteService->getClient()->getGattcIf(), - *m_pRemoteService->getClient()->getPeerAddress().getNative(), - m_pRemoteService->getSrvcId(), - &m_charId); + if (notifyCallback != nullptr) { + esp_err_t errRc = ::esp_ble_gattc_register_for_notify( + m_pRemoteService->getClient()->getGattcIf(), + *m_pRemoteService->getClient()->getPeerAddress().getNative(), + getHandle() + ); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } // Register + else { + esp_err_t errRc = ::esp_ble_gattc_unregister_for_notify( + m_pRemoteService->getClient()->getGattcIf(), + *m_pRemoteService->getClient()->getPeerAddress().getNative(), + getHandle() + ); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_unregister_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } // Unregister m_semaphoreRegForNotifyEvt.wait("registerForNotify"); @@ -269,9 +281,9 @@ void BLERemoteCharacteristic::registerForNotify( */ std::string BLERemoteCharacteristic::toString() { std::ostringstream ss; - ss << "Characteristic: uuid: " << BLEUUID(m_charId.uuid).toString() << - ", props: " << BLEUtils::characteristicPropertiesToString(m_charProp) << - ", inst_id: " << (int)m_charId.inst_id; + ss << "Characteristic: uuid: " << m_uuid.toString() << + ", handle: " << getHandle() << " 0x" << std::hex << getHandle() << + ", props: " << BLEUtils::characteristicPropertiesToString(m_charProp); return ss.str(); } // toString @@ -287,11 +299,11 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { m_semaphoreWriteCharEvt.take("writeValue"); + // Invoke the ESP-IDF API to perform the write. esp_err_t errRc = ::esp_ble_gattc_write_char( m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getConnId(), - m_pRemoteService->getSrvcId(), - &m_charId, + getHandle(), newValue.length(), (uint8_t*)newValue.data(), response?ESP_GATT_WRITE_TYPE_RSP:ESP_GATT_WRITE_TYPE_NO_RSP, diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index b5b22b60..26eccffa 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -19,13 +19,14 @@ #include "FreeRTOS.h" class BLERemoteService; +class BLERemoteDescriptor; /** * @brief A model of a remote %BLE characteristic. */ class BLERemoteCharacteristic { public: - BLERemoteCharacteristic(esp_gatt_id_t charId, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); + // Public member functions BLEUUID getUUID(); @@ -40,6 +41,7 @@ class BLERemoteCharacteristic { std::string toString(void); private: + BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); friend class BLEClient; friend class BLERemoteService; @@ -47,17 +49,21 @@ class BLERemoteCharacteristic { void gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *evtParam); + esp_ble_gattc_cb_param_t* evtParam); + uint16_t getHandle(); // Private properties - esp_gatt_id_t m_charId; + BLEUUID m_uuid; esp_gatt_char_prop_t m_charProp; + uint16_t m_handle; BLERemoteService* m_pRemoteService; FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt"); FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt"); FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); std::string m_value; void (*m_notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify); + // We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID. + std::map m_descriptorMap; }; // BLERemoteCharacteristic #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ */ diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index 2be312ae..f854d022 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -9,3 +9,56 @@ #include "BLERemoteDescriptor.h" #endif /* CONFIG_BT_ENABLED */ + +/** + * @brief Retrieve the handle associated with this remote descriptor. + * @return The handle associated with this remote descriptor. + */ +uint16_t BLERemoteDescriptor::getHandle() { + return m_handle; +} // getHandle + + +/** + * @brief Retrieve the UUID associated this remote descriptor. + * @return The UUID associated this remote descriptor. + */ +BLEUUID BLERemoteDescriptor::getUUID() { + return m_uuid; +} // getUUID + + +std::string BLERemoteDescriptor::readValue(void) { + return ""; +} // readValue + + +uint8_t BLERemoteDescriptor::readUInt8(void) { + return 0; +} // readUInt8 + + +uint16_t BLERemoteDescriptor::readUInt16(void) { + return 0; +} // readUInt16 + + +uint32_t BLERemoteDescriptor::readUInt32(void) { + return 0; +} // readUInt32 + +std::string BLERemoteDescriptor::toString(void) { + return ""; +} // toString + +void BLERemoteDescriptor::writeValue(uint8_t* data, size_t length, + bool response) { +} // writeValue + + +void BLERemoteDescriptor::writeValue(std::string newValue, bool response) { +} // writeValue + + +void BLERemoteDescriptor::writeValue(uint8_t newValue, bool response) { +} // writeValue diff --git a/cpp_utils/BLERemoteDescriptor.h b/cpp_utils/BLERemoteDescriptor.h index a8d944d4..f9acec61 100644 --- a/cpp_utils/BLERemoteDescriptor.h +++ b/cpp_utils/BLERemoteDescriptor.h @@ -9,12 +9,37 @@ #define COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) +#include +#include + +#include "BLERemoteCharacteristic.h" +#include "BLEUUID.h" +#include "FreeRTOS.h" /** * @brief A model of remote %BLE descriptor. */ class BLERemoteDescriptor { public: + BLEUUID getUUID(); + std::string readValue(void); + uint8_t readUInt8(void); + uint16_t readUInt16(void); + uint32_t readUInt32(void); + std::string toString(void); + //void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)); + void writeValue(uint8_t* data, size_t length, bool response = false); + void writeValue(std::string newValue, bool response = false); + void writeValue(uint8_t newValue, bool response = false); + + +private: + uint16_t m_handle; + BLEUUID m_uuid; + std::string m_value; + BLERemoteCharacteristic* m_pRemoteCharacteristic; + + uint16_t getHandle(); }; #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ */ diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index a29a7589..4c5e7ac4 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -17,13 +17,21 @@ static const char* LOG_TAG = "BLERemoteService"; BLERemoteService::BLERemoteService( - esp_gatt_srvc_id_t srvcId, - BLEClient *pClient) { + esp_gatt_id_t srvcId, + BLEClient* pClient, + uint16_t startHandle, + uint16_t endHandle + ) { + ESP_LOGD(LOG_TAG, ">> BLERemoteService()"); m_srvcId = srvcId; m_pClient = pClient; m_uuid = BLEUUID(m_srvcId); m_haveCharacteristics = false; + m_startHandle = startHandle; + m_endHandle = endHandle; + + ESP_LOGD(LOG_TAG, "<< BLERemoteService()"); } @@ -31,6 +39,7 @@ BLERemoteService::~BLERemoteService() { removeCharacteristics(); } +/* static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { if (id1.id.inst_id != id2.id.inst_id) { return false; @@ -40,7 +49,7 @@ static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { } return true; } // compareSrvcId - +*/ /** * @brief Handle GATT Client events @@ -60,6 +69,7 @@ void BLERemoteService::gattClientEventHandler( // - esp_gatt_id_t char_id // - esp_gatt_char_prop_t char_prop // + /* case ESP_GATTC_GET_CHAR_EVT: { // Is this event for this service? If yes, then the local srvc_id and the event srvc_id will be // the same. @@ -94,7 +104,7 @@ void BLERemoteService::gattClientEventHandler( //m_semaphoreGetCharEvt.give(); break; } // ESP_GATTC_GET_CHAR_EVT - +*/ default: { break; } @@ -106,6 +116,12 @@ void BLERemoteService::gattClientEventHandler( } } // gattClientEventHandler +BLERemoteCharacteristic* BLERemoteService::getCharacteristic(uint16_t handle) { + ESP_LOGD(LOG_TAG, ">> getCharacteristic: handle: %d", handle); + ESP_LOGE(LOG_TAG, "!!! NOT IMPLEMENTED !!!"); + ESP_LOGD(LOG_TAG, "<< getCharacteristic"); + return nullptr; +} /** * @brief Get the characteristic object for the UUID. @@ -149,7 +165,7 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { void BLERemoteService::getCharacteristics() { ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); - +/* removeCharacteristics(); // Forget any previous characteristics. m_semaphoreGetCharEvt.take("getCharacteristics"); @@ -167,6 +183,81 @@ void BLERemoteService::getCharacteristics() { m_semaphoreGetCharEvt.wait("getCharacteristics"); // Wait for the characteristics to become available. + m_haveCharacteristics = true; // Remember that we have received the characteristics. + */ + //ESP_LOGE(LOG_TAG, "!!! NOT IMPLEMENTED !!!"); + //ESP_LOGD(LOG_TAG, "--- test code ---"); + uint16_t count; + esp_gatt_status_t status = ::esp_ble_gattc_get_attr_count( + getClient()->getGattcIf(), + getClient()->getConnId(), + ESP_GATT_DB_CHARACTERISTIC, + m_startHandle, + m_endHandle, + 0, // Characteristic handle ... only used for ESP_GATT_DB_DESCRIPTOR + &count + ); + if (status != ESP_GATT_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_attr_count: %s", BLEUtils::gattStatusToString(status).c_str()); + } else { + ESP_LOGD(LOG_TAG, "Number of characteristics associated with service is %d", count); + } + + count = 1; + esp_gattc_service_elem_t srvcElem; + status = ::esp_ble_gattc_get_service( + getClient()->getGattcIf(), + getClient()->getConnId(), + &m_srvcId.uuid, // UUID of service + &srvcElem, // Records + &count, // records retrieved + 0 // offset + ); + if (status != ESP_GATT_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_service: %s", BLEUtils::gattStatusToString(status).c_str()); + } + else { + ESP_LOGD(LOG_TAG, "%s", BLEUtils::gattcServiceElementToString(&srvcElem).c_str()); + } + + uint16_t offset = 0; + esp_gattc_char_elem_t result; + while(1) { + count = 1; + status = ::esp_ble_gattc_get_all_char( + getClient()->getGattcIf(), + getClient()->getConnId(), + m_startHandle, + m_endHandle, + &result, + &count, + offset + ); + if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. + break; + } + + if (status != ESP_GATT_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_all_char: %s", BLEUtils::gattStatusToString(status).c_str()); + break; + } + if (count == 0) { + break; + } + ESP_LOGD(LOG_TAG, "Found a characteristic: Handle: %d, UUID: %s", result.char_handle, BLEUUID(result.uuid).toString().c_str()); + + // We now have a new characteristic ... let us add that to our set of known characteristics + BLERemoteCharacteristic *pNewRemoteCharacteristic = new BLERemoteCharacteristic( + result.char_handle, + BLEUUID(result.uuid), + result.properties, + this + ); + + m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic->getUUID().toString(), pNewRemoteCharacteristic)); + + offset++; + } m_haveCharacteristics = true; // Remember that we have received the characteristics. ESP_LOGD(LOG_TAG, "<< getCharacteristics()"); } // getCharacteristics @@ -176,10 +267,18 @@ BLEClient* BLERemoteService::getClient() { return m_pClient; } -esp_gatt_srvc_id_t* BLERemoteService::getSrvcId() { +esp_gatt_id_t* BLERemoteService::getSrvcId() { return &m_srvcId; } +uint16_t BLERemoteService::getHandle() { + ESP_LOGD(LOG_TAG, ">> getHandle: service: %s", getUUID().toString().c_str()); + //ESP_LOGE(LOG_TAG, "!!! getHandle: NOT IMPLEMENTED !!!"); + ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", m_startHandle, m_startHandle); + return m_startHandle; +} + + BLEUUID BLERemoteService::getUUID() { return m_uuid; } @@ -208,6 +307,8 @@ void BLERemoteService::removeCharacteristics() { std::string BLERemoteService::toString() { std::ostringstream ss; ss << "Service: uuid: " + m_uuid.toString(); + ss << ", start_handle: " << std::dec << m_startHandle << " 0x" << std::hex << m_startHandle << + ", end_handle: " << std::dec << m_endHandle << " 0x" << std::hex << m_endHandle; for (auto &myPair : m_characteristicMap) { ss << "\n" << myPair.second->toString(); // myPair.second is the value diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 4393fbcf..a61d25b1 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -26,24 +26,29 @@ class BLERemoteCharacteristic; */ class BLERemoteService { public: - BLERemoteService(esp_gatt_srvc_id_t srvcId, BLEClient* pClient); + virtual ~BLERemoteService(); // Public methods BLERemoteCharacteristic* getCharacteristic(const char* uuid); BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); + BLERemoteCharacteristic* getCharacteristic(uint16_t handle); void getCharacteristics(void); BLEClient* getClient(void); BLEUUID getUUID(void); std::string toString(void); private: + // Private constructor ... never meant to be created by a user application. + BLERemoteService(esp_gatt_id_t srvcId, BLEClient* pClient, uint16_t startHandle, uint16_t endHandle); + // Friends friend class BLEClient; friend class BLERemoteCharacteristic; // Private methods - esp_gatt_srvc_id_t* getSrvcId(void); + uint16_t getHandle(); + esp_gatt_id_t* getSrvcId(void); void gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, @@ -51,12 +56,17 @@ class BLERemoteService { void removeCharacteristics(); // Properties + + // We maintain a map of characteristics owned by this service keyed by a string representation of the UUID. std::map m_characteristicMap; + bool m_haveCharacteristics; // Have we previously obtained the characteristics. BLEClient* m_pClient; FreeRTOS::Semaphore m_semaphoreGetCharEvt = FreeRTOS::Semaphore("GetCharEvt"); - esp_gatt_srvc_id_t m_srvcId; + esp_gatt_id_t m_srvcId; BLEUUID m_uuid; + uint16_t m_startHandle; + uint16_t m_endHandle; }; // BLERemoteService #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 9fdcffdf..c5a41fc1 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -203,10 +203,13 @@ BLEScanResults BLEScan::start(uint32_t duration) { ESP_LOGD(LOG_TAG, ">> start(%d)", duration); m_semaphoreScanEnd.take("start"); + ESP_LOGD(LOG_TAG, "A"); m_scanResults.m_vectorAdvertisedDevices.empty(); + ESP_LOGD(LOG_TAG, "B"); esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); + ESP_LOGD(LOG_TAG, "C"); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 9a4fe451..e02a9895 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -166,11 +166,11 @@ BLEUUID::BLEUUID(esp_bt_uuid_t uuid) { /** - * @brief Create a UUID from the ESP32 esp_gatt_srvc_id_t. + * @brief Create a UUID from the ESP32 esp_gat_id_t. * - * @param [in] srvcId The data to create the UUID from. + * @param [in] gattId The data to create the UUID from. */ -BLEUUID::BLEUUID(esp_gatt_srvc_id_t srcvId) : BLEUUID(srcvId.id.uuid) { +BLEUUID::BLEUUID(esp_gatt_id_t gattId) : BLEUUID(gattId.uuid) { } // BLEUUID diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index c3647965..da0e594c 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -22,7 +22,7 @@ class BLEUUID { BLEUUID(uint32_t uuid); BLEUUID(esp_bt_uuid_t uuid); BLEUUID(uint8_t* pData, size_t size, bool msbFirst); - BLEUUID(esp_gatt_srvc_id_t srcvId); + BLEUUID(esp_gatt_id_t gattId); BLEUUID(); bool equals(BLEUUID uuid); esp_bt_uuid_t* getNative(); diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 468a7e14..eb8597f7 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -349,12 +349,12 @@ std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType return "ESP_GATTC_ENC_CMPL_CB_EVT"; case ESP_GATTC_EXEC_EVT: return "ESP_GATTC_EXEC_EVT"; - case ESP_GATTC_GET_CHAR_EVT: - return "ESP_GATTC_GET_CHAR_EVT"; - case ESP_GATTC_GET_DESCR_EVT: - return "ESP_GATTC_GET_DESCR_EVT"; - case ESP_GATTC_GET_INCL_SRVC_EVT: - return "ESP_GATTC_GET_INCL_SRVC_EVT"; + //case ESP_GATTC_GET_CHAR_EVT: +// return "ESP_GATTC_GET_CHAR_EVT"; + //case ESP_GATTC_GET_DESCR_EVT: +// return "ESP_GATTC_GET_DESCR_EVT"; + //case ESP_GATTC_GET_INCL_SRVC_EVT: +// return "ESP_GATTC_GET_INCL_SRVC_EVT"; case ESP_GATTC_MULT_ADV_DATA_EVT: return "ESP_GATTC_MULT_ADV_DATA_EVT"; case ESP_GATTC_MULT_ADV_DIS_EVT: @@ -716,6 +716,7 @@ void BLEUtils::dumpGattClientEvent( // - esp_gatt_id_t char_id // - esp_gatt_char_prop_t char_prop // + /* case ESP_GATTC_GET_CHAR_EVT: { // If the status of the event shows that we have a value other than ESP_GATT_OK then the @@ -742,6 +743,7 @@ void BLEUtils::dumpGattClientEvent( } break; } // ESP_GATTC_GET_CHAR_EVT + */ // // ESP_GATTC_NOTIFY_EVT @@ -749,20 +751,17 @@ void BLEUtils::dumpGattClientEvent( // notify // uint16_t conn_id // esp_bd_addr_t remote_bda - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id + // handle handle // uint16_t value_len // uint8_t* value // bool is_notify // case ESP_GATTC_NOTIFY_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, srvc_id: <%s>, char_id: <%s>, descr_id: <%s>, value_len: %d, is_notify: %d]", + ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, handle: %d 0x%.2x, value_len: %d, is_notify: %d]", evtParam->notify.conn_id, BLEAddress(evtParam->notify.remote_bda).toString().c_str(), - BLEUtils::gattServiceIdToString(evtParam->notify.srvc_id).c_str(), - gattIdToString(evtParam->notify.char_id).c_str(), - gattIdToString(evtParam->notify.descr_id).c_str(), + evtParam->notify.handle, + evtParam->notify.handle, evtParam->notify.value_len, evtParam->notify.is_notify ); @@ -796,20 +795,16 @@ void BLEUtils::dumpGattClientEvent( // read: // esp_gatt_status_t status // uint16_t conn_id - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id + // uint16_t handle // uint8_t* value // uint16_t value_type // uint16_t value_len case ESP_GATTC_READ_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: <%s>, char_id: <%s>, descr_id: <%s>, value_type: 0x%x, value_len: %d]", + ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x, value_len: %d]", BLEUtils::gattStatusToString(evtParam->read.status).c_str(), evtParam->read.conn_id, - BLEUtils::gattServiceIdToString(evtParam->read.srvc_id).c_str(), - gattIdToString(evtParam->read.char_id).c_str(), - gattIdToString(evtParam->read.descr_id).c_str(), - evtParam->read.value_type, + evtParam->read.handle, + evtParam->read.handle, evtParam->read.value_len ); if (evtParam->read.status == ESP_GATT_OK) { @@ -844,13 +839,13 @@ void BLEUtils::dumpGattClientEvent( // // reg_for_notify: // - esp_gatt_status_t status - // - esp_gatt_srvc_id_t srvc_id - // - esp_gatt_id_t char_id + // - uint16_t handle case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, srvc_id: <%s>, char_id: <%s>]", + ESP_LOGD(LOG_TAG, "[status: %s, handle: %d 0x%.2x]", BLEUtils::gattStatusToString(evtParam->reg_for_notify.status).c_str(), - BLEUtils::gattServiceIdToString(evtParam->reg_for_notify.srvc_id).c_str(), - gattIdToString(evtParam->reg_for_notify.char_id).c_str()); + evtParam->reg_for_notify.handle, + evtParam->reg_for_notify.handle + ); break; } // ESP_GATTC_REG_FOR_NOTIFY_EVT @@ -874,23 +869,19 @@ void BLEUtils::dumpGattClientEvent( // ESP_GATTC_SEARCH_RES_EVT // // search_res: - // - uint16_t conn_id - // - esp_gatt_srvc_id_t srvc_id + // - uint16_t conn_id + // - uint16_t start_handle + // - uint16_t end_handle + // - esp_gatt_id_t srvc_id // case ESP_GATTC_SEARCH_RES_EVT: { - std::string name = ""; - if (evtParam->search_res.srvc_id.id.uuid.len == ESP_UUID_LEN_16) { - name = BLEUtils::gattServiceToString(evtParam->search_res.srvc_id.id.uuid.uuid.uuid16); - } - if (name.length() == 0) { - name = ""; - } - - ESP_LOGD(LOG_TAG, "[srvc_id: %s [%s], instanceId: 0x%.2x conn_id: %d]", - BLEUtils::gattServiceIdToString(evtParam->search_res.srvc_id).c_str(), - name.c_str(), - evtParam->search_res.srvc_id.id.inst_id, - evtParam->search_res.conn_id); + ESP_LOGD(LOG_TAG, "[conn_id: %d, start_handle: %d 0x%.2x, end_handle: %d 0x%.2x, srvc_id: %s", + evtParam->search_res.conn_id, + evtParam->search_res.start_handle, + evtParam->search_res.start_handle, + evtParam->search_res.end_handle, + evtParam->search_res.end_handle, + gattIdToString(evtParam->search_res.srvc_id).c_str()); break; } // ESP_GATTC_SEARCH_RES_EVT @@ -899,21 +890,19 @@ void BLEUtils::dumpGattClientEvent( // ESP_GATTC_WRITE_CHAR_EVT // // write: - // esp_gatt_status_t status - // uint16_t conn_id - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id + // - esp_gatt_status_t status + // - uint16_t conn_id + // - uint16_t handle + // case ESP_GATTC_WRITE_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: <%s>, char_id: <%s>, descr_id: <%s>]", + ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x]", BLEUtils::gattStatusToString(evtParam->write.status).c_str(), evtParam->write.conn_id, - BLEUtils::gattServiceIdToString(evtParam->write.srvc_id).c_str(), - gattIdToString(evtParam->write.char_id).c_str(), - gattIdToString(evtParam->write.descr_id).c_str() + evtParam->write.handle, + evtParam->write.handle ); break; - } + } // ESP_GATTC_WRITE_CHAR_EVT default: break; @@ -939,18 +928,22 @@ void BLEUtils::dumpGattServerEvent( switch(event) { case ESP_GATTS_ADD_CHAR_DESCR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: 0x%.2x, service_handle: 0x%.2x, char_uuid: %s]", + ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", gattStatusToString(evtParam->add_char_descr.status).c_str(), evtParam->add_char_descr.attr_handle, + evtParam->add_char_descr.attr_handle, + evtParam->add_char_descr.service_handle, evtParam->add_char_descr.service_handle, BLEUUID(evtParam->add_char_descr.char_uuid).toString().c_str()); break; } // ESP_GATTS_ADD_CHAR_DESCR_EVT case ESP_GATTS_ADD_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: 0x%.2x, service_handle: 0x%.2x, char_uuid: %s]", + ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", gattStatusToString(evtParam->add_char.status).c_str(), evtParam->add_char.attr_handle, + evtParam->add_char.attr_handle, + evtParam->add_char.service_handle, evtParam->add_char.service_handle, BLEUUID(evtParam->add_char.char_uuid).toString().c_str()); break; @@ -987,9 +980,10 @@ void BLEUtils::dumpGattServerEvent( } // ESP_GATTS_CONNECT_EVT case ESP_GATTS_CREATE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, service_handle: 0x%.2x, service_id: [%s]]", + ESP_LOGD(LOG_TAG, "[status: %s, service_handle: %d 0x%.2x, service_id: [%s]]", gattStatusToString(evtParam->create.status).c_str(), evtParam->create.service_handle, + evtParam->create.service_handle, gattServiceIdToString(evtParam->create.service_id).c_str()); break; } // ESP_GATTS_CREATE_EVT @@ -1139,7 +1133,7 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { case ESP_BLE_EVT_SCAN_RSP: return "ESP_BLE_EVT_SCAN_RSP"; default: - ESP_LOGD(LOG_TAG, "Unknown esp_ble_evt_type_t: %d", eventType); + ESP_LOGD(LOG_TAG, "Unknown esp_ble_evt_type_t: %d (0x%.2x)", eventType, eventType); return "*** Unknown ***"; } } // eventTypeToString @@ -1210,6 +1204,22 @@ std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID } // gattCharacteristicUUIDToString +/** + * @brief Return a string representation of an esp_gattc_service_elem_t. + * @return A string representation of an esp_gattc_service_elem_t. + */ +std::string BLEUtils::gattcServiceElementToString(esp_gattc_service_elem_t *pGATTCServiceElement) { + std::stringstream ss; + + ss << "[uuid: " << BLEUUID(pGATTCServiceElement->uuid).toString() << + ", start_handle: " << pGATTCServiceElement->start_handle << + " 0x" << std::hex << pGATTCServiceElement->start_handle << + ", end_handle: " << std::dec << pGATTCServiceElement->end_handle << + " 0x" << std::hex << pGATTCServiceElement->end_handle << "]"; + return ss.str(); +} // gattcServiceElementToString + + /** * @brief Convert an esp_gatt_srvc_id_t to a string. */ @@ -1312,10 +1322,18 @@ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { return "ESP_GATT_ALREADY_OPEN"; case ESP_GATT_CANCEL: return "ESP_GATT_CANCEL"; + case ESP_GATT_STACK_RSP: + return "ESP_GATT_STACK_RSP"; + case ESP_GATT_APP_RSP: + return "ESP_GATT_APP_RSP"; + case ESP_GATT_UNKNOWN_ERROR: + return "ESP_GATT_UNKNOWN_ERROR"; case ESP_GATT_CCC_CFG_ERR: return "ESP_GATT_CCC_CFG_ERR"; case ESP_GATT_PRC_IN_PROGRESS: return "ESP_GATT_PRC_IN_PROGRESS"; + case ESP_GATT_OUT_OF_RANGE: + return "ESP_GATT_OUT_OF_RANGE"; default: return "Unknown"; } diff --git a/cpp_utils/BLEUtils.h b/cpp_utils/BLEUtils.h index 28c7a7ef..b49d0f8f 100644 --- a/cpp_utils/BLEUtils.h +++ b/cpp_utils/BLEUtils.h @@ -30,6 +30,7 @@ class BLEUtils { static BLEClient* findByAddress(BLEAddress address); static std::string gattClientEventTypeToString(esp_gattc_cb_event_t eventType); static std::string gattServerEventTypeToString(esp_gatts_cb_event_t eventType); + static std::string gattcServiceElementToString(esp_gattc_service_elem_t *pGATTCServiceElement); static std::string gattServiceIdToString(esp_gatt_srvc_id_t srvcId); static std::string gattStatusToString(esp_gatt_status_t status); static std::string gattServiceToString(uint32_t serviceId); diff --git a/cpp_utils/File.cpp b/cpp_utils/File.cpp index 85114ec4..670936aa 100644 --- a/cpp_utils/File.cpp +++ b/cpp_utils/File.cpp @@ -19,8 +19,8 @@ static const char* LOG_TAG = "File"; * @param [in] name The name of the file. * @param [in] type The type of the file (DT_REGULAR, DT_DIRECTORY or DT_UNKNOWN). */ -File::File(std::string name, uint8_t type) { - m_name = name; +File::File(std::string path, uint8_t type) { + m_path = path; m_type = type; } // File @@ -32,7 +32,7 @@ File::File(std::string name, uint8_t type) { */ std::string File::getContent(bool base64Encode) { uint32_t size = length(); - ESP_LOGD(LOG_TAG, "File:: getContent(), name=%s, length=%d", m_name.c_str(), size); + ESP_LOGD(LOG_TAG, "File:: getContent(), path=%s, length=%d", m_path.c_str(), size); if (size == 0) { return ""; } @@ -41,7 +41,7 @@ std::string File::getContent(bool base64Encode) { ESP_LOGE(LOG_TAG, "getContent: Failed to allocate memory"); return ""; } - FILE *file = fopen(m_name.c_str(), "r"); + FILE *file = fopen(m_path.c_str(), "r"); fread(pData, size, 1, file); fclose(file); std::string ret((char *)pData, size); @@ -64,7 +64,7 @@ std::string File::getContent(bool base64Encode) { std::string File::getContent(uint32_t offset, uint32_t readSize) { uint32_t fileSize = length(); ESP_LOGD(LOG_TAG, "File:: getContent(), name=%s, fileSize=%d, offset=%d, readSize=%d", - m_name.c_str(), fileSize, offset, readSize); + m_path.c_str(), fileSize, offset, readSize); if (fileSize == 0 || offset > fileSize) { return ""; } @@ -73,7 +73,7 @@ std::string File::getContent(uint32_t offset, uint32_t readSize) { ESP_LOGE(LOG_TAG, "getContent: Failed to allocate memory"); return ""; } - FILE *file = fopen(m_name.c_str(), "r"); + FILE *file = fopen(m_path.c_str(), "r"); fseek(file, offset, SEEK_SET); size_t bytesRead = fread(pData, 1, readSize, file); fclose(file); @@ -83,12 +83,19 @@ std::string File::getContent(uint32_t offset, uint32_t readSize) { } // getContent +std::string File::getPath() { + return m_path; +} /** * @brief Get the name of the file. * @return The name of the file. */ std::string File::getName() { - return m_name; + size_t pos = m_path.find_last_of('/'); + if (pos == std::string::npos) { + return m_path; + } + return m_path.substr(pos+1); } // getName @@ -108,8 +115,8 @@ uint8_t File::getType() { */ uint32_t File::length() { struct stat buf; - int rc = stat(m_name.c_str(), &buf); - if (rc != 0) { + int rc = stat(m_path.c_str(), &buf); + if (rc != 0 || S_ISDIR(buf.st_mode)) { return 0; } return buf.st_size; @@ -122,7 +129,7 @@ uint32_t File::length() { */ bool File::isDirectory() { struct stat buf; - int rc = stat(m_name.c_str(), &buf); + int rc = stat(m_path.c_str(), &buf); if (rc != 0) { return false; } diff --git a/cpp_utils/File.h b/cpp_utils/File.h index cd64435f..19ef7c34 100644 --- a/cpp_utils/File.h +++ b/cpp_utils/File.h @@ -20,12 +20,13 @@ class File { std::string getContent(bool base64Encode=false); std::string getContent(uint32_t offset, uint32_t size); std::string getName(); + std::string getPath(); uint8_t getType(); bool isDirectory(); uint32_t length(); private: - std::string m_name; + std::string m_path; uint8_t m_type; }; diff --git a/cpp_utils/FileSystem.cpp b/cpp_utils/FileSystem.cpp index 0ae62ae1..564c68b4 100644 --- a/cpp_utils/FileSystem.cpp +++ b/cpp_utils/FileSystem.cpp @@ -67,7 +67,7 @@ std::vector FileSystem::getDirectoryContents(std::string path) { struct dirent *pDirent; ESP_LOGD(LOG_TAG, "Directory dump of %s", path.c_str()); while((pDirent = readdir(pDir)) != nullptr) { - File file(std::string(pDirent->d_name), pDirent->d_type); + File file(path +"/" + std::string(pDirent->d_name), pDirent->d_type); ret.push_back(file); } ::closedir(pDir); diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 663128d7..dea73bfe 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -133,11 +133,10 @@ void FreeRTOS::Semaphore::giveFromISR() { */ void FreeRTOS::Semaphore::take(std::string owner) { - - ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); + ESP_LOGD(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); xSemaphoreTake(m_semaphore, portMAX_DELAY); m_owner = owner; - ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); + ESP_LOGD(LOG_TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 87c55bd0..7f99b972 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -106,6 +106,23 @@ bool GeneralUtils::base64Encode(const std::string &in, std::string *out) { } // base64Encode +/** + * @brief Does the string end with a specific character? + * @param [in] str The string to examine. + * @param [in] c The character to look form. + * @return True if the string ends with the given character. + */ +bool GeneralUtils::endsWith(std::string str, char c) { + if (str.empty()) { + return false; + } + if (str.at(str.length()-1) == c) { + return true; + } + return false; +} // endsWidth + + static int DecodedLength(const std::string &in) { int numEq = 0; int n = in.size(); @@ -386,3 +403,4 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { return "Unknown ESP_ERR error"; } // errorToString + diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index ca38c754..9042d8a7 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -22,6 +22,7 @@ class GeneralUtils { static std::string ipToString(uint8_t *ip); static bool base64Encode(const std::string &in, std::string *out); static bool base64Decode(const std::string &in, std::string *out); + static bool endsWith(std::string str, char c); static const char *errorToString(esp_err_t errCode); }; diff --git a/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp index 406a5584..9594316a 100644 --- a/cpp_utils/HttpParser.cpp +++ b/cpp_utils/HttpParser.cpp @@ -251,7 +251,7 @@ void HttpParser::parse(std::string message) { // // void HttpParser::parseRequestLine(std::string &line) { - ESP_LOGD(LOG_TAG, ">> parseRequestLine: %s [%d]", line.c_str(), line.length()); + ESP_LOGD(LOG_TAG, ">> parseRequestLine: \"%s\" [%d]", line.c_str(), line.length()); std::string::iterator it = line.begin(); // Get the method @@ -262,4 +262,5 @@ void HttpParser::parseRequestLine(std::string &line) { // Get the version m_version = toCharToken(it, line, ' '); + ESP_LOGD(LOG_TAG, "<< parseRequestLine: method: %s, url: %s, version: %s", m_method.c_str(), m_url.c_str(), m_version.c_str()); } // parseRequestLine diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 809ee407..b6da6c1a 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -14,15 +14,59 @@ #include "HttpResponse.h" #include "FileSystem.h" #include "WebSocket.h" +#include "GeneralUtils.h" static const char* LOG_TAG = "HttpServer"; #undef close + +/** + * Send a directory listing back to the browser. + * @param [in] path The path of the directory to list. + * @param [in] response The response object to use to send data back to the browser. + */ +static void listDirectory(std::string path, HttpResponse& response) { + // If path ends with a "/" then remove it. + if (GeneralUtils::endsWith(path, '/')) { + path = path.substr(0, path.length()-1); + } + response.addHeader("Content-Type", "text/html"); + response.setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + response.sendData(""); + if (!GeneralUtils::endsWith(path, '/')) { + response.sendData(""); + } + response.sendData(""); + response.sendData("

" + path + "

"); + response.sendData("
"); + response.sendData("

[To Parent Directory]

"); + response.sendData(""); + auto files = FileSystem::getDirectoryContents(path); + for (auto it = files.begin(); it != files.end(); ++it) { + std::stringstream ss; + ss << ""; + if (it->isDirectory()) { + ss << ""; + } + else { + ss << ""; + } + + ss << ""; + response.sendData(ss.str()); + ESP_LOGD(LOG_TAG, "file: %s", ss.str().c_str()); + } + response.sendData("
" << it->getName() << "<dir>" << it->length() << "
"); + response.sendData("
"); + response.sendData(""); + response.close(); +} // listDirectory + /** * Constructor for HTTP Server */ HttpServer::HttpServer() { m_portNumber = 80; // The default port number. - m_rootPath = "/"; // The default path. + m_rootPath = ""; // The default path. m_useSSL = false; // Default SSL is no. setDirectoryListing(false); // Default directory listing is no. } // HttpServer @@ -91,15 +135,26 @@ class HttpServerTask: public Task { // Serve up the content from the file on the file system ... if found ... std::ifstream ifStream; std::string fileName = m_pHttpServer->getRootPath() + request.getPath(); // Build the absolute file name to read. + + // If the file name ends with a '/' then remove it ... we are normalizing to NO trailing slashes. + if (GeneralUtils::endsWith(fileName, '/')) { + fileName = fileName.substr(0, fileName.length()-1); + } + + // Test if the path is a directory. if (FileSystem::isDirectory(fileName)) { - ESP_LOGD(LOG_TAG, "Path is a directory"); + ESP_LOGD(LOG_TAG, "Path %s is a directory", fileName.c_str()); + HttpResponse response(&request); + listDirectory(fileName, response); return; - } + } // Path was a directory. + ESP_LOGD("HttpServerTask", "Opening file: %s", fileName.c_str()); ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); // Attempt to open the file for reading. // If we failed to open the requested file, then it probably didn't exist so return a not found. if (!ifStream.is_open()) { + ESP_LOGE("HttpServerTask", "Unable to open file %s for reading", fileName.c_str()); HttpResponse response(&request); response.setStatus(HttpResponse::HTTP_STATUS_NOT_FOUND, "Not Found"); response.sendData(""); diff --git a/cpp_utils/WebSocketFileTransfer.cpp b/cpp_utils/WebSocketFileTransfer.cpp index 606f3401..2c81e1ad 100644 --- a/cpp_utils/WebSocketFileTransfer.cpp +++ b/cpp_utils/WebSocketFileTransfer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "GeneralUtils.h" #include "JSON.h" static const char* LOG_TAG = "WebSocketFileTransfer"; @@ -20,9 +21,18 @@ static const char* LOG_TAG = "WebSocketFileTransfer"; * @param [in] rootPath The path prefix for new files. */ WebSocketFileTransfer::WebSocketFileTransfer(std::string rootPath) { + // If the root path doesn't end with a '/', add one. + if (!GeneralUtils::endsWith(rootPath, '/')) { + rootPath += '/'; + } m_rootPath = rootPath; m_pWebSocket = nullptr; -} + if (rootPath.empty()) { + ESP_LOGE(LOG_TAG, "Root path can not be empty"); + } else if (m_rootPath.substr(m_rootPath.size()-1) != "/") { + ESP_LOGE(LOG_TAG, "Root path must end with a \"/\""); + } +} // WebSocketFileTransfer // Hide the class in an un-named namespace @@ -59,6 +69,7 @@ class FileTransferWebSocketHandler : public WebSocketHandler { // Test to see if we are currently active. If not, this is the start of a transfer. if (!m_active) { + ESP_LOGD("FileTransferWebSocketHandler", "Not yet active!"); // Read a chunk of data into memory. std::stringstream buffer; buffer << pWebSocketInputStreambuf; @@ -77,6 +88,7 @@ class FileTransferWebSocketHandler : public WebSocketHandler { m_fileLength = jo.getInt("length"); } std::string fileName = m_rootPath + m_fileName; + ESP_LOGD("FileTransferWebSocketHandler", "Target file is %s", fileName.c_str()); // If the file to create ends in a "/" then we are being asked to create a directory. if (m_fileName.substr(m_fileName.size()-1)=="/") { @@ -98,7 +110,7 @@ class FileTransferWebSocketHandler : public WebSocketHandler { else { m_ofStream.open(fileName, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); if (!m_ofStream.is_open()) { - ESP_LOGE("FileTransferWebSocketHandler", "Failed to open file %s", m_fileName.c_str()); + ESP_LOGE("FileTransferWebSocketHandler", "Failed to open file %s for writing", m_fileName.c_str()); return; } } @@ -134,6 +146,7 @@ class FileTransferWebSocketHandler : public WebSocketHandler { if (m_ofStream.is_open()) { m_ofStream.close(); // Close the file now that we have finished writing to it. } + delete this; // Delete ourself. } // onClose }; // FileTransferWebSocketHandler From 2bc0d669c1848434d085f8ee5dcfcbeecab36508 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Mon, 25 Sep 2017 11:49:53 +0300 Subject: [PATCH 054/381] Lots of WiFi optimizations --- cpp_utils/WiFi.cpp | 178 ++++++++++++++++++++++++++++++++++----------- cpp_utils/WiFi.h | 37 +++++++--- 2 files changed, 159 insertions(+), 56 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index a207f956..89ca6fd8 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -28,7 +28,6 @@ #include - static char tag[]= "WiFi"; @@ -68,14 +67,46 @@ WiFi::WiFi() { * @param [in] ip The IP address of the DNS Server. * @return N/A. */ -void WiFi::addDNSServer(std::string ip) { - ip_addr_t dnsserver; - ESP_LOGD(tag, "Setting DNS[%d] to %s", m_dnsCount, ip.c_str()); - inet_pton(AF_INET, ip.c_str(), &dnsserver); - ::dns_setserver(m_dnsCount, &dnsserver); - m_dnsCount++; +void WiFi::addDNSServer(const std::string& ip) { + addDNSServer(ip.c_str()); +} // addDNSServer + +void WiFi::addDNSServer(const char* ip) { + ip_addr_t dnsserver; + ESP_LOGD(tag, "Setting DNS[%d] to %s", m_dnsCount, ip); + inet_pton(AF_INET, ip, &dnsserver); + ::dns_setserver(m_dnsCount, &dnsserver); + m_dnsCount++; + m_dnsCount %= 2; } // addDNSServer +/** + * @brief Set a reference to a DNS server. + * + * Here we define a server that will act as a DNS server. We use numdns to specify which DNS server to set + * + * For example: + * + * @code{.cpp} + * wifi.setDNSServer(0, "8.8.8.8"); + * wifi.setDNSServer(1, "8.8.4.4"); + * @endcode + * + * @param [in] numdns The DNS number we wish to set + * @param [in] ip The IP address of the DNS Server. + * @return N/A. + */ +void WiFi::setDNSServer(int numdns, const std::string& ip) { + setDNSServer(numdns, ip.c_str()); +} // setDNSServer + +void WiFi::setDNSServer(int numdns, const char* ip) { + ip_addr_t dnsserver; + ESP_LOGD(tag, "Setting DNS[%d] to %s", numdns, ip); + inet_pton(AF_INET, ip, &dnsserver); + ::dns_setserver(numdns, &dnsserver); +} // setDNSServer + /** * @brief Connect to an external access point. * @@ -85,7 +116,7 @@ void WiFi::addDNSServer(std::string ip) { * @param[in] password The password of the access point to which we wish to connect. * @return N/A. */ -void WiFi::connectAP(std::string ssid, std::string password){ +void WiFi::connectAP(const std::string& ssid, const std::string& password){ ::nvs_flash_init(); ::tcpip_adapter_init(); if (ip.length() > 0 && gw.length() > 0 && netmask.length() > 0) { @@ -115,7 +146,6 @@ void WiFi::connectAP(std::string ssid, std::string password){ ESP_ERROR_CHECK(::esp_wifi_connect()); } // connectAP - /** * @brief Dump diagnostics to the log. */ @@ -147,9 +177,9 @@ tcpip_adapter_ip_info_t WiFi::getApIpInfo() { std::string WiFi::getApMac() { uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_AP, mac); - std::stringstream s; - s << std::hex << std::setfill('0') << std::setw(2) << (int) mac[0] << ':' << (int) mac[1] << ':' << (int) mac[2] << ':' << (int) mac[3] << ':' << (int) mac[4] << ':' << (int) mac[5]; - return s.str(); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); } // getApMac @@ -171,18 +201,21 @@ std::string WiFi::getApSSID() { * * @return The IP address of the host or 0.0.0.0 if not found. */ -struct in_addr WiFi::getHostByName(std::string hostName) { - struct in_addr retAddr; - struct hostent *he = gethostbyname(hostName.c_str()); - if (he == nullptr) { - retAddr.s_addr = 0; - ESP_LOGD(tag, "Unable to resolve %s - %d", hostName.c_str(), h_errno); - } else { - retAddr = *(struct in_addr *)(he->h_addr_list[0]); - //ESP_LOGD(tag, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); +struct in_addr WiFi::getHostByName(const std::string& hostName) { + return getHostByName(hostName.c_str()); +} // getHostByName - } - return retAddr; +struct in_addr WiFi::getHostByName(const char* hostName) { + struct in_addr retAddr; + struct hostent *he = gethostbyname(hostName); + if (he == nullptr) { + retAddr.s_addr = 0; + ESP_LOGD(tag, "Unable to resolve %s - %d", hostName, h_errno); + } else { + retAddr = *(struct in_addr *)(he->h_addr_list[0]); + ESP_LOGD(tag, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); + } + return retAddr; } // getHostByName @@ -226,9 +259,9 @@ tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { std::string WiFi::getStaMac() { uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_STA, mac); - std::stringstream s; - s << std::hex << std::setfill('0') << std::setw(2) << (int) mac[0] << ':' << (int) mac[1] << ':' << (int) mac[2] << ':' << (int) mac[3] << ':' << (int) mac[4] << ':' << (int) mac[5]; - return s.str(); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); } // getStaMac @@ -295,7 +328,7 @@ std::vector WiFi::scan() { * @param[in] password The password to use for station connections. * @return N/A. */ -void WiFi::startAP(std::string ssid, std::string password) { +void WiFi::startAP(const std::string& ssid, const std::string& password) { ::nvs_flash_init(); ::tcpip_adapter_init(); ESP_ERROR_CHECK(esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); @@ -306,7 +339,7 @@ void WiFi::startAP(std::string ssid, std::string password) { wifi_config_t apConfig; ::memset(&apConfig, 0, sizeof(apConfig)); ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); - apConfig.ap.ssid_len = 0; + apConfig.ap.ssid_len = ssid.size(); ::memcpy(apConfig.ap.password, password.data(), password.size()); apConfig.ap.channel = 0; apConfig.ap.authmode = WIFI_AUTH_OPEN; @@ -334,12 +367,18 @@ void WiFi::startAP(std::string ssid, std::string password) { * @param [in] netmask Netmask value. * @return N/A. */ -void WiFi::setIPInfo(std::string ip, std::string gw, std::string netmask) { +void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask) { this->ip = ip; this->gw = gw; this->netmask = netmask; } // setIPInfo +void WiFi::setIPInfo(std::string&& ip, std::string&& gw, std::string&& netmask) { + this->ip = std::move(ip); + this->gw = std::move(gw); + this->netmask = std::move(netmask); +} // setIPInfo + /** * @brief Return a string representation of the WiFi access point record. @@ -368,9 +407,11 @@ std::string WiFiAPRecord::toString() { auth = ""; break; } - std::stringstream s; - s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; - return s.str(); +// std::stringstream s; +// s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; + auto info_str = (char*) malloc(6 + 32 + 8 + 22 + 8 + 3 + 1); + sprintf(info_str, "ssid: %s, auth: %s, rssi: %d", m_ssid.c_str(), auth.c_str(), (int) m_rssi); + return std::string(std::move(info_str)); } // toString MDNS::MDNS() { @@ -392,23 +433,72 @@ MDNS::~MDNS() { * @param [in] port * @return N/A. */ -void MDNS::serviceAdd(std::string service, std::string proto, uint16_t port) { - ESP_ERROR_CHECK(mdns_service_add(m_mdns_server, service.c_str(), proto.c_str(), port)); +void MDNS::serviceAdd(const std::string& service, const std::string& proto, uint16_t port) { + serviceAdd(service.c_str(), proto.c_str(), port); +} // serviceAdd + + +void MDNS::serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance) { + serviceInstanceSet(service.c_str(), proto.c_str(), instance.c_str()); +} // serviceInstanceSet + + +void MDNS::servicePortSet(const std::string& service, const std::string& proto, uint16_t port) { + servicePortSet(service.c_str(), proto.c_str(), port); +} // servicePortSet + + +void MDNS::serviceRemove(const std::string& service, const std::string& proto) { + serviceRemove(service.c_str(), proto.c_str()); +} // serviceRemove + + +/** + * @brief Set the mDNS hostname. + * + * @param [in] hostname The host name to set against the mDNS. + * @return N/A. + */ +void MDNS::setHostname(const std::string& hostname) { + setHostname(hostname.c_str()); +} // setHostname + + +/** + * @brief Set the mDNS instance. + * + * @param [in] instance The instance name to set against the mDNS. + * @return N/A. + */ +void MDNS::setInstance(const std::string& instance) { + setInstance(instance.c_str()); +} // setInstance + +/** + * @brief Define the service for mDNS. + * + * @param [in] service + * @param [in] proto + * @param [in] port + * @return N/A. + */ +void MDNS::serviceAdd(const char* service, const char* proto, uint16_t port) { + ESP_ERROR_CHECK(mdns_service_add(m_mdns_server, service, proto, port)); } // serviceAdd -void MDNS::serviceInstanceSet(std::string service, std::string proto, std::string instance) { - ESP_ERROR_CHECK(mdns_service_instance_set(m_mdns_server, service.c_str(), proto.c_str(), instance.c_str())); +void MDNS::serviceInstanceSet(const char* service, const char* proto, const char* instance) { + ESP_ERROR_CHECK(mdns_service_instance_set(m_mdns_server, service, proto, instance)); } // serviceInstanceSet -void MDNS::servicePortSet(std::string service, std::string proto, uint16_t port) { - ESP_ERROR_CHECK(mdns_service_port_set(m_mdns_server, service.c_str(), proto.c_str(), port)); +void MDNS::servicePortSet(const char* service, const char* proto, uint16_t port) { + ESP_ERROR_CHECK(mdns_service_port_set(m_mdns_server, service, proto, port)); } // servicePortSet -void MDNS::serviceRemove(std::string service, std::string proto) { - ESP_ERROR_CHECK(mdns_service_remove(m_mdns_server, service.c_str(), proto.c_str())); +void MDNS::serviceRemove(const char* service, const char* proto) { + ESP_ERROR_CHECK(mdns_service_remove(m_mdns_server, service, proto)); } // serviceRemove @@ -418,8 +508,8 @@ void MDNS::serviceRemove(std::string service, std::string proto) { * @param [in] hostname The host name to set against the mDNS. * @return N/A. */ -void MDNS::setHostname(std::string hostname) { - ESP_ERROR_CHECK(mdns_set_hostname(m_mdns_server,hostname.c_str())); +void MDNS::setHostname(const char* hostname) { + ESP_ERROR_CHECK(mdns_set_hostname(m_mdns_server,hostname)); } // setHostname @@ -429,6 +519,6 @@ void MDNS::setHostname(std::string hostname) { * @param [in] instance The instance name to set against the mDNS. * @return N/A. */ -void MDNS::setInstance(std::string instance) { - ESP_ERROR_CHECK(mdns_set_instance(m_mdns_server, instance.c_str())); +void MDNS::setInstance(const char* instance) { + ESP_ERROR_CHECK(mdns_set_instance(m_mdns_server, instance)); } // setInstance diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 979c04f7..4f159d87 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -21,12 +21,20 @@ class MDNS { public: MDNS(); ~MDNS(); - void serviceAdd(std::string service, std::string proto, uint16_t port); - void serviceInstanceSet(std::string service, std::string proto, std::string instance); - void servicePortSet(std::string service, std::string proto, uint16_t port); - void serviceRemove(std::string service, std::string proto); - void setHostname(std::string hostname); - void setInstance(std::string instance); + void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); + void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); + void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); + void serviceRemove(const std::string& service, const std::string& proto); + void setHostname(const std::string& hostname); + void setInstance(const std::string& instance); + // If we the above functions with a basic char*, a copy would be created into an std::string, + // making the whole thing require twice as much processing power and speed + void serviceAdd(const char* service, const char* proto, uint16_t port); + void serviceInstanceSet(const char* service, const char* proto, const char* instance); + void servicePortSet(const char* service, const char* proto, uint16_t port); + void serviceRemove(const char* service, const char* proto); + void setHostname(const char* hostname); + void setInstance(const char* instance); private: mdns_server_t *m_mdns_server = nullptr; }; @@ -102,9 +110,13 @@ class WiFi { public: WiFi(); - void addDNSServer(std::string ip); - struct in_addr getHostByName(std::string hostName); - void connectAP(std::string ssid, std::string passwd); + void addDNSServer(const std::string& ip); + void addDNSServer(const char* ip); + void setDNSServer(int numdns, const std::string& ip); + void setDNSServer(int numdns, const char* ip); + struct in_addr getHostByName(const std::string& hostName); + struct in_addr getHostByName(const char* hostName); + void connectAP(const std::string& ssid, const std::string& password); void dump(); static std::string getApMac(); static tcpip_adapter_ip_info_t getApIpInfo(); @@ -114,8 +126,9 @@ class WiFi { static std::string getStaMac(); static std::string getStaSSID(); std::vector scan(); - void startAP(std::string ssid, std::string passwd); - void setIPInfo(std::string ip, std::string gw, std::string netmask); + void startAP(const std::string& ssid, const std::string& passwd); + void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); + void setIPInfo(std::string&& ip, std::string&& gw, std::string&& netmask); @@ -129,7 +142,7 @@ class WiFi { this->wifiEventHandler = wifiEventHandler; } private: - int m_dnsCount=0; + uint8_t m_dnsCount=0; //char *m_dnsServer = nullptr; }; From ba3f0bbcd3fd34cd5b0d1393b1100b9cb9808e65 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Mon, 25 Sep 2017 12:42:29 +0300 Subject: [PATCH 055/381] WebServer optimizations --- cpp_utils/WebServer.cpp | 111 +++++++++++++++++++++++++++------------- cpp_utils/WebServer.h | 42 ++++++++------- 2 files changed, 96 insertions(+), 57 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index a3ecb1d6..962d8d6e 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -18,7 +18,6 @@ #include #include - static char tag[] = "WebServer"; struct WebServerUserData { @@ -102,24 +101,16 @@ static std::string mongoose_eventToString(int event) { case MG_EV_WEBSOCKET_CONTROL_FRAME: return "MG_EV_WEBSOCKET_CONTROL_FRAME"; } - std::ostringstream s; - s << "Unknown event: " << event; - return s.str(); + std::string s; + s += "Unknown event: "; + s += event; + return s; } //eventToString -/** - * @brief Convert a Mongoose string type to a string. - * @param [in] mgStr The Mongoose string. - * @return A std::string representation of the Mongoose string. - */ -static std::string mgStrToString(struct mg_str mgStr) { - return std::string(mgStr.p, mgStr.len); -} // mgStrToStr - static void dumpHttpMessage(struct http_message *pHttpMessage) { ESP_LOGD(tag, "HTTP Message"); - ESP_LOGD(tag, "Message: %s", mgStrToString(pHttpMessage->message).c_str()); - ESP_LOGD(tag, "URI: %s", mgStrToString(pHttpMessage->uri).c_str()); + ESP_LOGD(tag, "Message: %s", pHttpMessage->message.p); + ESP_LOGD(tag, "URI: %s", pHttpMessage->uri.p); } /* @@ -210,7 +201,7 @@ static void mongoose_event_handler_web_server( ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", part->file_name, part->var_name, part->status, (uint32_t)part->user_data); if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->data(mgStrToString(part->data)); + pWebServerUserData->pMultiPart->data(std::string(part->data.p, part->data.len)); } break; } // MG_EV_HTTP_PART_DATA @@ -398,6 +389,10 @@ void WebServer::setRootPath(const std::string& path) { m_rootPath = path; } // setRootPath +void WebServer::setRootPath(std::string&& path) { + m_rootPath = std::move(path); +} // setRootPath + /** * @brief Register the factory for creating web socket handlers. @@ -460,6 +455,17 @@ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { m_dataSent = true; std::string headers; + unsigned long headers_len = 0; + + for(auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + if(iter != m_headers.begin()) + headers_len += 2; + headers_len += iter->first.length(); + headers_len += 2; + headers_len += iter->second.length(); + } + headers_len += 1; + headers.resize(headers_len); // Will not have to resize and recopy during the next loop, we have 2 loops but it still ends up being faster for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { if(iter != m_headers.begin()) @@ -492,7 +498,7 @@ void WebServer::HTTPResponse::setHeaders(std::map&& he * @brief Get the current root path. * @return The current root path. */ -std::string WebServer::HTTPResponse::getRootPath() { +const std::string& WebServer::HTTPResponse::getRootPath() const { return m_rootPath; } // getRootPath @@ -506,6 +512,10 @@ void WebServer::HTTPResponse::setRootPath(const std::string& path) { m_rootPath = path; } // setRootPath +void WebServer::HTTPResponse::setRootPath(std::string&& path) { + m_rootPath = std::move(path); +} // setRootPath + /** * @brief Set the status value in the HTTP response. * @@ -529,8 +539,7 @@ void WebServer::HTTPResponse::setStatus(int status) { * @param [in] message The message representing the request. */ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_message* message) { - std::string uri = mgStrToString(message->uri); - ESP_LOGD(tag, "WebServer::processRequest: Matching: %s", uri.c_str()); + ESP_LOGD(tag, "WebServer::processRequest: Matching: %s", message->uri.p); HTTPResponse httpResponse = HTTPResponse(mgConnection); httpResponse.setRootPath(getRootPath()); @@ -539,7 +548,7 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m */ std::vector::iterator it; for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { - if ((*it).match(mgStrToString(message->method), uri)) { + if ((*it).match(message->method.p, message->method.len, message->uri.p)) { HTTPRequest httpRequest(message); (*it).invoke(&httpRequest, &httpResponse); ESP_LOGD(tag, "Found a match!!"); @@ -550,7 +559,7 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m // Because we reached here, it means that we did NOT match a handler. Now we want to attempt // to retrieve the corresponding file content. std::string filePath = httpResponse.getRootPath(); - filePath += uri; + filePath += message->uri.p; ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); FILE *file = fopen(filePath.c_str(), "r"); if (file != nullptr) { @@ -598,15 +607,17 @@ WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pat * @brief Determine if the path matches. * * @param [in] method The method to be matched. + * @param [in] method_len The method's length * @param [in] path The path to be matched. * @return True if the path matches. */ -bool WebServer::PathHandler::match(const std::string& method, const std::string& path) { - //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); - if (method != m_method) { - return false; - } - return std::regex_search(path, m_pattern); + +bool WebServer::PathHandler::match(const char* method, unsigned long method_len, const char* path) { + //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); + if (method_len != m_method.length() || strcmp(method, m_method.c_str()) != 0) { + return false; + } + return std::regex_search(path, m_pattern); } // match @@ -638,20 +649,49 @@ WebServer::HTTPRequest::HTTPRequest(struct http_message* message) { * known as the body. This method returns that payload (if it exists). * @return The body of the request. */ -std::string WebServer::HTTPRequest::getBody() const { - return mgStrToString(m_message->body); +const char* WebServer::HTTPRequest::getBody() const { + return m_message->body.p; } // getBody +/** + * @brief Get the length of the body of the request. + * When an HTTP request is either PUT or POST then it may contain a payload that is also + * known as the body. This method returns that payload (if it exists). + * @return The length of the body of the request. + */ +size_t WebServer::HTTPRequest::getBodyLen() const { + return m_message->body.len; +} // getBodyLen + /** * @brief Get the method of the request. * An HTTP request contains a request method which is one of GET, PUT, POST, etc. * @return The method of the request. */ -std::string WebServer::HTTPRequest::getMethod() const { - return mgStrToString(m_message->method); +const char* WebServer::HTTPRequest::getMethod() const { + return m_message->method.p; } // getMethod +/** + * @brief Get the length of the method of the request. + * An HTTP request contains a request method which is one of GET, PUT, POST, etc. + * @return The length of the method of the request. + */ +size_t WebServer::HTTPRequest::getMethodLen() const { + return m_message->method.len; +} // getMethodLen + + +/** + * @brief Get the path of the request. + * The path of an HTTP request is the portion of the URL that follows the hostname/port pair + * but does not include any query parameters. + * @return The path of the request. + */ +const char* WebServer::HTTPRequest::getPath() const { + return m_message->uri.p; +} // getPath /** * @brief Get the path of the request. @@ -659,8 +699,8 @@ std::string WebServer::HTTPRequest::getMethod() const { * but does not include any query parameters. * @return The path of the request. */ -std::string WebServer::HTTPRequest::getPath() const { - return mgStrToString(m_message->uri); +size_t WebServer::HTTPRequest::getPathLen() const { + return m_message->uri.len; } // getPath #define STATE_NAME 0 @@ -675,7 +715,8 @@ std::map WebServer::HTTPRequest::getQuery() const { // Walk through all the characters in the query string maintaining a simple state machine // that lets us know what we are parsing. std::map queryMap; - std::string queryString = mgStrToString(m_message->query_string); + const char* queryString = m_message->query_string.p; + size_t queryStringLen = m_message->query_string.len; int i=0; /* @@ -687,7 +728,7 @@ std::map WebServer::HTTPRequest::getQuery() const { std::string name = ""; std::string value; // Loop through each character in the query string. - for (i=0; i - - class WebServer; /** @@ -32,11 +30,14 @@ class WebServer { class HTTPRequest { public: HTTPRequest(struct http_message* message); - std::string getMethod() const; - std::string getPath() const; - std::map getQuery() const; - std::string getBody() const; - std::vector pathSplit() const; + const char* getMethod() const; + const char* getPath() const; + const char* getBody() const; + size_t getMethodLen() const; + size_t getPathLen() const; + size_t getBodyLen() const; + std::map getQuery() const; + std::vector pathSplit() const; private: struct http_message* m_message; }; // HTTPRequest @@ -49,13 +50,14 @@ class WebServer { HTTPResponse(struct mg_connection *nc); void addHeader(const std::string& name, const std::string& value); void addHeader(std::string&& name, std::string&& value); - std::string getRootPath(); void setStatus(int status); - void setHeaders(const std::map& headers); - void setHeaders(std::map&& headers); - void sendData(const std::string& data); - void sendData(const uint8_t* pData, size_t length); - void setRootPath(const std::string& path); + void setHeaders(const std::map& headers); + void setHeaders(std::map&& headers); + void sendData(const std::string& data); + void sendData(const uint8_t* pData, size_t length); + const std::string& getRootPath() const; + void setRootPath(const std::string& path); + void setRootPath(std::string&& path); private: struct mg_connection *m_nc; std::string m_rootPath; @@ -149,7 +151,7 @@ class WebServer { public: PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); PathHandler(std::string&& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); - bool match(const std::string& method, const std::string& path); + bool match(const char* method, size_t method_len, const char* path); void invoke(HTTPRequest *request, HTTPResponse *response); private: std::string m_method; @@ -164,7 +166,6 @@ class WebServer { public: void onCreated(); virtual void onMessage(const std::string& message); - // virtual void onMessage(std::string&& message); // ? Don't know what this is used for yet so i will not touch it void onClosed(); void sendData(const std::string& message); void sendData(const uint8_t* data, uint32_t size); @@ -180,15 +181,12 @@ class WebServer { WebServer(); virtual ~WebServer(); - void addPathHandler(const std::string& method, const std::string& pathExpr, - void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, - WebServer::HTTPResponse* pHttpResponse)); - void addPathHandler(std::string&& method, const std::string& pathExpr, - void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, - WebServer::HTTPResponse* pHttpResponse)); + void addPathHandler(const std::string& method, const std::string& pathExpr, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + void addPathHandler(std::string&& method, const std::string& pathExpr, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); const std::string& getRootPath(); void setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory); - void setRootPath(const std::string& path); + void setRootPath(const std::string& path); + void setRootPath(std::string&& path); void setWebSocketHandlerFactory(WebSocketHandlerFactory *pWebSocketHandlerFactory); void start(unsigned short port = 80); void processRequest(struct mg_connection *mgConnection, struct http_message *message); From 1c250e523cd82bb73316cf9eef7039634f1e0237 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Mon, 25 Sep 2017 12:52:48 +0300 Subject: [PATCH 056/381] Fix a small compilation error, whoops --- cpp_utils/WebServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 962d8d6e..3d2fcb0c 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -612,7 +612,7 @@ WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pat * @return True if the path matches. */ -bool WebServer::PathHandler::match(const char* method, unsigned long method_len, const char* path) { +bool WebServer::PathHandler::match(const char* method, size_t method_len, const char* path) { //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); if (method_len != m_method.length() || strcmp(method, m_method.c_str()) != 0) { return false; From 4d7655aa5d8f08f0b6cd37e9e6f75c994a62b472 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Mon, 25 Sep 2017 12:54:28 +0300 Subject: [PATCH 057/381] Tabs to spaces (makes github code a lot more readable) --- cpp_utils/WebServer.cpp | 750 ++++++++++++++++++++-------------------- cpp_utils/WebServer.h | 290 ++++++++-------- 2 files changed, 520 insertions(+), 520 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 3d2fcb0c..dcbe674a 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -21,10 +21,10 @@ static char tag[] = "WebServer"; struct WebServerUserData { - WebServer *pWebServer; - WebServer::HTTPMultiPart *pMultiPart; - WebServer::WebSocketHandler *pWebSocketHandler; - void *originalUserData; + WebServer *pWebServer; + WebServer::HTTPMultiPart *pMultiPart; + WebServer::WebSocketHandler *pWebSocketHandler; + void *originalUserData; }; /** @@ -33,90 +33,90 @@ struct WebServerUserData { * @return The string representation of the event. */ static std::string mongoose_eventToString(int event) { - switch (event) { - case MG_EV_CONNECT: - return "MG_EV_CONNECT"; - case MG_EV_ACCEPT: - return "MG_EV_ACCEPT"; - case MG_EV_CLOSE: - return "MG_EV_CLOSE"; - case MG_EV_SEND: - return "MG_EV_SEND"; - case MG_EV_RECV: - return "MG_EV_RECV"; - case MG_EV_POLL: - return "MG_EV_POLL"; - case MG_EV_TIMER: - return "MG_EV_TIMER"; - case MG_EV_HTTP_PART_DATA: - return "MG_EV_HTTP_PART_DATA"; - case MG_EV_HTTP_MULTIPART_REQUEST: - return "MG_EV_HTTP_MULTIPART_REQUEST"; - case MG_EV_HTTP_PART_BEGIN: - return "MG_EV_HTTP_PART_BEGIN"; - case MG_EV_HTTP_PART_END: - return "MG_EV_HTTP_PART_END"; - case MG_EV_HTTP_MULTIPART_REQUEST_END: - return "MG_EV_HTTP_MULTIPART_REQUEST_END"; - case MG_EV_HTTP_REQUEST: - return "MG_EV_HTTP_REQUEST"; - case MG_EV_HTTP_REPLY: - return "MG_EV_HTTP_REPLY"; - case MG_EV_HTTP_CHUNK: - return "MG_EV_HTTP_CHUNK"; - case MG_EV_MQTT_CONNACK: - return "MG_EV_MQTT_CONNACK"; - case MG_EV_MQTT_CONNECT: - return "MG_EV_MQTT_CONNECT"; - case MG_EV_MQTT_DISCONNECT: - return "MG_EV_MQTT_DISCONNECT"; - case MG_EV_MQTT_PINGREQ: - return "MG_EV_MQTT_PINGREQ"; - case MG_EV_MQTT_PINGRESP: - return "MG_EV_MQTT_PINGRESP"; - case MG_EV_MQTT_PUBACK: - return "MG_EV_MQTT_PUBACK"; - case MG_EV_MQTT_PUBCOMP: - return "MG_EV_MQTT_PUBCOMP"; - case MG_EV_MQTT_PUBLISH: - return "MG_EV_MQTT_PUBLISH"; - case MG_EV_MQTT_PUBREC: - return "MG_EV_MQTT_PUBREC"; - case MG_EV_MQTT_PUBREL: - return "MG_EV_MQTT_PUBREL"; - case MG_EV_MQTT_SUBACK: - return "MG_EV_MQTT_SUBACK"; - case MG_EV_MQTT_SUBSCRIBE: - return "MG_EV_MQTT_SUBSCRIBE"; - case MG_EV_MQTT_UNSUBACK: - return "MG_EV_MQTT_UNSUBACK"; - case MG_EV_MQTT_UNSUBSCRIBE: - return "MG_EV_MQTT_UNSUBSCRIBE"; - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: - return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: - return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; - case MG_EV_WEBSOCKET_FRAME: - return "MG_EV_WEBSOCKET_FRAME"; - case MG_EV_WEBSOCKET_CONTROL_FRAME: - return "MG_EV_WEBSOCKET_CONTROL_FRAME"; - } - std::string s; - s += "Unknown event: "; + switch (event) { + case MG_EV_CONNECT: + return "MG_EV_CONNECT"; + case MG_EV_ACCEPT: + return "MG_EV_ACCEPT"; + case MG_EV_CLOSE: + return "MG_EV_CLOSE"; + case MG_EV_SEND: + return "MG_EV_SEND"; + case MG_EV_RECV: + return "MG_EV_RECV"; + case MG_EV_POLL: + return "MG_EV_POLL"; + case MG_EV_TIMER: + return "MG_EV_TIMER"; + case MG_EV_HTTP_PART_DATA: + return "MG_EV_HTTP_PART_DATA"; + case MG_EV_HTTP_MULTIPART_REQUEST: + return "MG_EV_HTTP_MULTIPART_REQUEST"; + case MG_EV_HTTP_PART_BEGIN: + return "MG_EV_HTTP_PART_BEGIN"; + case MG_EV_HTTP_PART_END: + return "MG_EV_HTTP_PART_END"; + case MG_EV_HTTP_MULTIPART_REQUEST_END: + return "MG_EV_HTTP_MULTIPART_REQUEST_END"; + case MG_EV_HTTP_REQUEST: + return "MG_EV_HTTP_REQUEST"; + case MG_EV_HTTP_REPLY: + return "MG_EV_HTTP_REPLY"; + case MG_EV_HTTP_CHUNK: + return "MG_EV_HTTP_CHUNK"; + case MG_EV_MQTT_CONNACK: + return "MG_EV_MQTT_CONNACK"; + case MG_EV_MQTT_CONNECT: + return "MG_EV_MQTT_CONNECT"; + case MG_EV_MQTT_DISCONNECT: + return "MG_EV_MQTT_DISCONNECT"; + case MG_EV_MQTT_PINGREQ: + return "MG_EV_MQTT_PINGREQ"; + case MG_EV_MQTT_PINGRESP: + return "MG_EV_MQTT_PINGRESP"; + case MG_EV_MQTT_PUBACK: + return "MG_EV_MQTT_PUBACK"; + case MG_EV_MQTT_PUBCOMP: + return "MG_EV_MQTT_PUBCOMP"; + case MG_EV_MQTT_PUBLISH: + return "MG_EV_MQTT_PUBLISH"; + case MG_EV_MQTT_PUBREC: + return "MG_EV_MQTT_PUBREC"; + case MG_EV_MQTT_PUBREL: + return "MG_EV_MQTT_PUBREL"; + case MG_EV_MQTT_SUBACK: + return "MG_EV_MQTT_SUBACK"; + case MG_EV_MQTT_SUBSCRIBE: + return "MG_EV_MQTT_SUBSCRIBE"; + case MG_EV_MQTT_UNSUBACK: + return "MG_EV_MQTT_UNSUBACK"; + case MG_EV_MQTT_UNSUBSCRIBE: + return "MG_EV_MQTT_UNSUBSCRIBE"; + case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: + return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: + return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; + case MG_EV_WEBSOCKET_FRAME: + return "MG_EV_WEBSOCKET_FRAME"; + case MG_EV_WEBSOCKET_CONTROL_FRAME: + return "MG_EV_WEBSOCKET_CONTROL_FRAME"; + } + std::string s; + s += "Unknown event: "; s += event; - return s; + return s; } //eventToString static void dumpHttpMessage(struct http_message *pHttpMessage) { - ESP_LOGD(tag, "HTTP Message"); - ESP_LOGD(tag, "Message: %s", pHttpMessage->message.p); - ESP_LOGD(tag, "URI: %s", pHttpMessage->uri.p); + ESP_LOGD(tag, "HTTP Message"); + ESP_LOGD(tag, "Message: %s", pHttpMessage->message.p); + ESP_LOGD(tag, "URI: %s", pHttpMessage->uri.p); } /* static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, struct mg_str fname) { - ESP_LOGD(tag, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); - return fname; + ESP_LOGD(tag, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); + return fname; } */ @@ -131,139 +131,139 @@ static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, s * @return N/A. */ static void mongoose_event_handler_web_server( - struct mg_connection *mgConnection, // The network connection associated with the event. - int event, // The type of event. - void *eventData // Data associated with the event. + struct mg_connection *mgConnection, // The network connection associated with the event. + int event, // The type of event. + void *eventData // Data associated with the event. ) { - if (event == MG_EV_POLL) { - return; - } - ESP_LOGD(tag, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); - switch (event) { - case MG_EV_HTTP_REQUEST: { - struct http_message *message = (struct http_message *) eventData; - dumpHttpMessage(message); - - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; - pWebServer->processRequest(mgConnection, message); - break; - } // MG_EV_HTTP_REQUEST - - case MG_EV_HTTP_MULTIPART_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - ESP_LOGD(tag, "User_data address 0x%d", (uint32_t)pWebServerUserData); - WebServer *pWebServer = pWebServerUserData->pWebServer; - if (pWebServer->m_pMultiPartFactory == nullptr) { - return; - } - WebServer::HTTPMultiPart *pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pMultiPart = pMultiPart; - p2->pWebSocketHandler = nullptr; - mgConnection->user_data = p2; - //struct http_message *message = (struct http_message *) eventData; - //dumpHttpMessage(message); - break; - } // MG_EV_HTTP_MULTIPART_REQUEST - - case MG_EV_HTTP_MULTIPART_REQUEST_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pMultiPart != nullptr) { - delete pWebServerUserData->pMultiPart; - pWebServerUserData->pMultiPart = nullptr; - } - mgConnection->user_data = pWebServerUserData->originalUserData; - delete pWebServerUserData; - WebServer::HTTPResponse httpResponse = WebServer::HTTPResponse(mgConnection); - httpResponse.setStatus(200); - httpResponse.sendData(""); - break; - } // MG_EV_HTTP_MULTIPART_REQUEST_END - - case MG_EV_HTTP_PART_BEGIN: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->begin(std::string(part->var_name), std::string(part->file_name)); - } - break; - } // MG_EV_HTTP_PART_BEGIN - - case MG_EV_HTTP_PART_DATA: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->data(std::string(part->data.p, part->data.len)); - } - break; - } // MG_EV_HTTP_PART_DATA - - case MG_EV_HTTP_PART_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->end(); - } - break; - } // MG_EV_HTTP_PART_END - - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; - if (pWebServer->m_pWebSocketHandlerFactory != nullptr) { - if (pWebServerUserData->pWebSocketHandler != nullptr) { - ESP_LOGD(tag, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); - } - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); - mgConnection->user_data = p2; - } else { - ESP_LOGD(tag, "We received a WebSocket request but we have no handler factory!"); - } - break; - } // MG_EV_WEBSOCKET_HANDSHAKE_REQUEST - - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); - return; - } - pWebServerUserData->pWebSocketHandler->onCreated(); - break; - } // MG_EV_WEBSOCKET_HANDSHAKE_DONE - - - /* - * When we receive a MG_EV_WEBSOCKET_FRAME then we have received a chunk of data over the network. - * Our goal will be to send this to the web socket handler (if one exists). - */ - case MG_EV_WEBSOCKET_FRAME: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); - return; - } - struct websocket_message *ws_message = (websocket_message *)eventData; - ESP_LOGD(tag, "Received data length: %d", ws_message->size); - pWebServerUserData->pWebSocketHandler->onMessage(std::string((char *)ws_message->data, ws_message->size)); - break; - } // MG_EV_WEBSOCKET_FRAME - - } // End of switch + if (event == MG_EV_POLL) { + return; + } + ESP_LOGD(tag, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); + switch (event) { + case MG_EV_HTTP_REQUEST: { + struct http_message *message = (struct http_message *) eventData; + dumpHttpMessage(message); + + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + WebServer *pWebServer = pWebServerUserData->pWebServer; + pWebServer->processRequest(mgConnection, message); + break; + } // MG_EV_HTTP_REQUEST + + case MG_EV_HTTP_MULTIPART_REQUEST: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + ESP_LOGD(tag, "User_data address 0x%d", (uint32_t)pWebServerUserData); + WebServer *pWebServer = pWebServerUserData->pWebServer; + if (pWebServer->m_pMultiPartFactory == nullptr) { + return; + } + WebServer::HTTPMultiPart *pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); + struct WebServerUserData *p2 = new WebServerUserData(); + ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pMultiPart = pMultiPart; + p2->pWebSocketHandler = nullptr; + mgConnection->user_data = p2; + //struct http_message *message = (struct http_message *) eventData; + //dumpHttpMessage(message); + break; + } // MG_EV_HTTP_MULTIPART_REQUEST + + case MG_EV_HTTP_MULTIPART_REQUEST_END: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + if (pWebServerUserData->pMultiPart != nullptr) { + delete pWebServerUserData->pMultiPart; + pWebServerUserData->pMultiPart = nullptr; + } + mgConnection->user_data = pWebServerUserData->originalUserData; + delete pWebServerUserData; + WebServer::HTTPResponse httpResponse = WebServer::HTTPResponse(mgConnection); + httpResponse.setStatus(200); + httpResponse.sendData(""); + break; + } // MG_EV_HTTP_MULTIPART_REQUEST_END + + case MG_EV_HTTP_PART_BEGIN: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; + ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->begin(std::string(part->var_name), std::string(part->file_name)); + } + break; + } // MG_EV_HTTP_PART_BEGIN + + case MG_EV_HTTP_PART_DATA: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; + ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->data(std::string(part->data.p, part->data.len)); + } + break; + } // MG_EV_HTTP_PART_DATA + + case MG_EV_HTTP_PART_END: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; + ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->end(); + } + break; + } // MG_EV_HTTP_PART_END + + case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + WebServer *pWebServer = pWebServerUserData->pWebServer; + if (pWebServer->m_pWebSocketHandlerFactory != nullptr) { + if (pWebServerUserData->pWebSocketHandler != nullptr) { + ESP_LOGD(tag, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); + } + struct WebServerUserData *p2 = new WebServerUserData(); + ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); + mgConnection->user_data = p2; + } else { + ESP_LOGD(tag, "We received a WebSocket request but we have no handler factory!"); + } + break; + } // MG_EV_WEBSOCKET_HANDSHAKE_REQUEST + + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + if (pWebServerUserData->pWebSocketHandler == nullptr) { + ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + return; + } + pWebServerUserData->pWebSocketHandler->onCreated(); + break; + } // MG_EV_WEBSOCKET_HANDSHAKE_DONE + + + /* + * When we receive a MG_EV_WEBSOCKET_FRAME then we have received a chunk of data over the network. + * Our goal will be to send this to the web socket handler (if one exists). + */ + case MG_EV_WEBSOCKET_FRAME: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + if (pWebServerUserData->pWebSocketHandler == nullptr) { + ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + return; + } + struct websocket_message *ws_message = (websocket_message *)eventData; + ESP_LOGD(tag, "Received data length: %d", ws_message->size); + pWebServerUserData->pWebSocketHandler->onMessage(std::string((char *)ws_message->data, ws_message->size)); + break; + } // MG_EV_WEBSOCKET_FRAME + + } // End of switch } // End of mongoose_event_handler @@ -271,9 +271,9 @@ static void mongoose_event_handler_web_server( * @brief Constructor. */ WebServer::WebServer() { - m_rootPath = ""; - m_pMultiPartFactory = nullptr; - m_pWebSocketHandlerFactory = nullptr; + m_rootPath = ""; + m_pMultiPartFactory = nullptr; + m_pWebSocketHandlerFactory = nullptr; } // WebServer @@ -286,7 +286,7 @@ WebServer::~WebServer() { * @return The current root path. */ const std::string& WebServer::getRootPath() { - return m_rootPath; + return m_rootPath; } // getRootPath @@ -313,7 +313,7 @@ const std::string& WebServer::getRootPath() { void WebServer::addPathHandler(const std::string& method, const std::string& pathExpr, void (* handler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { - m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); + m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); } // addPathHandler void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr, @@ -331,31 +331,31 @@ void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr * @return N/A. */ void WebServer::start(uint16_t port) { - ESP_LOGD(tag, "WebServer task starting"); - struct mg_mgr mgr; - mg_mgr_init(&mgr, NULL); - - std::stringstream stringStream; - stringStream << ':' << port; - struct mg_connection *mgConnection = mg_bind(&mgr, stringStream.str().c_str(), mongoose_event_handler_web_server); - - if (mgConnection == NULL) { - ESP_LOGE(tag, "No connection from the mg_bind()"); - vTaskDelete(NULL); - return; - } - - struct WebServerUserData *pWebServerUserData = new WebServerUserData(); - pWebServerUserData->pWebServer = this; - pWebServerUserData->pMultiPart = nullptr; - mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. - ESP_LOGD(tag, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); - mg_set_protocol_http_websocket(mgConnection); - - ESP_LOGD(tag, "WebServer listening on port %d", port); - while (1) { - mg_mgr_poll(&mgr, 2000); - } + ESP_LOGD(tag, "WebServer task starting"); + struct mg_mgr mgr; + mg_mgr_init(&mgr, NULL); + + std::stringstream stringStream; + stringStream << ':' << port; + struct mg_connection *mgConnection = mg_bind(&mgr, stringStream.str().c_str(), mongoose_event_handler_web_server); + + if (mgConnection == NULL) { + ESP_LOGE(tag, "No connection from the mg_bind()"); + vTaskDelete(NULL); + return; + } + + struct WebServerUserData *pWebServerUserData = new WebServerUserData(); + pWebServerUserData->pWebServer = this; + pWebServerUserData->pMultiPart = nullptr; + mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. + ESP_LOGD(tag, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); + mg_set_protocol_http_websocket(mgConnection); + + ESP_LOGD(tag, "WebServer listening on port %d", port); + while (1) { + mg_mgr_poll(&mgr, 2000); + } } // run @@ -364,7 +364,7 @@ void WebServer::start(uint16_t port) { * @param [in] pMultiPart A pointer to the multi part factory. */ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { - m_pMultiPartFactory = pMultiPartFactory; + m_pMultiPartFactory = pMultiPartFactory; } @@ -386,7 +386,7 @@ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { * @return N/A. */ void WebServer::setRootPath(const std::string& path) { - m_rootPath = path; + m_rootPath = path; } // setRootPath void WebServer::setRootPath(std::string&& path) { @@ -401,7 +401,7 @@ void WebServer::setRootPath(std::string&& path) { * @return N/A. */ void WebServer::setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHandlerFactory) { - m_pWebSocketHandlerFactory = pWebSocketHandlerFactory; + m_pWebSocketHandlerFactory = pWebSocketHandlerFactory; } // setWebSocketHandlerFactory @@ -410,9 +410,9 @@ void WebServer::setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHa * @param [in] nc The network connection for the response. */ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { - m_nc = nc; - m_status = 200; - m_dataSent = false; + m_nc = nc; + m_status = 200; + m_dataSent = false; } // HTTPResponse @@ -422,7 +422,7 @@ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { * @param [in] value The value of the header. */ void WebServer::HTTPResponse::addHeader(const std::string& name, const std::string& value) { - m_headers[name] = value; + m_headers[name] = value; } // addHeader void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) { @@ -436,7 +436,7 @@ void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) * @return N/A. */ void WebServer::HTTPResponse::sendData(const std::string& data) { - sendData((uint8_t *)data.data(), data.length()); + sendData((uint8_t *)data.data(), data.length()); } // sendData @@ -448,13 +448,13 @@ void WebServer::HTTPResponse::sendData(const std::string& data) { * @return N/A. */ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { - if (m_dataSent) { - ESP_LOGE(tag, "HTTPResponse: Data already sent! Attempt to send again/more."); - return; - } - m_dataSent = true; + if (m_dataSent) { + ESP_LOGE(tag, "HTTPResponse: Data already sent! Attempt to send again/more."); + return; + } + m_dataSent = true; - std::string headers; + std::string headers; unsigned long headers_len = 0; for(auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { @@ -467,16 +467,16 @@ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { headers_len += 1; headers.resize(headers_len); // Will not have to resize and recopy during the next loop, we have 2 loops but it still ends up being faster - for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { if(iter != m_headers.begin()) headers += "\r\n"; headers += iter->first; headers += ": "; headers += iter->second; - } - mg_send_head(m_nc, m_status, length, headers.c_str()); - mg_send(m_nc, pData, length); - m_nc->flags |= MG_F_SEND_AND_CLOSE; + } + mg_send_head(m_nc, m_status, length, headers.c_str()); + mg_send(m_nc, pData, length); + m_nc->flags |= MG_F_SEND_AND_CLOSE; } // sendData @@ -486,7 +486,7 @@ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { * @return N/A. */ void WebServer::HTTPResponse::setHeaders(const std::map& headers) { - m_headers = headers; + m_headers = headers; } // setHeaders void WebServer::HTTPResponse::setHeaders(std::map&& headers) { @@ -499,7 +499,7 @@ void WebServer::HTTPResponse::setHeaders(std::map&& he * @return The current root path. */ const std::string& WebServer::HTTPResponse::getRootPath() const { - return m_rootPath; + return m_rootPath; } // getRootPath @@ -509,11 +509,11 @@ const std::string& WebServer::HTTPResponse::getRootPath() const { * @return N/A. */ void WebServer::HTTPResponse::setRootPath(const std::string& path) { - m_rootPath = path; + m_rootPath = path; } // setRootPath void WebServer::HTTPResponse::setRootPath(std::string&& path) { - m_rootPath = std::move(path); + m_rootPath = std::move(path); } // setRootPath /** @@ -524,7 +524,7 @@ void WebServer::HTTPResponse::setRootPath(std::string&& path) { * @return N/A. */ void WebServer::HTTPResponse::setStatus(int status) { - m_status = status; + m_status = status; } // setStatus @@ -539,43 +539,43 @@ void WebServer::HTTPResponse::setStatus(int status) { * @param [in] message The message representing the request. */ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_message* message) { - ESP_LOGD(tag, "WebServer::processRequest: Matching: %s", message->uri.p); - HTTPResponse httpResponse = HTTPResponse(mgConnection); - httpResponse.setRootPath(getRootPath()); - - /* - * Iterate through each of the path handlers looking for a match with the method and specified path. - */ - std::vector::iterator it; - for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { - if ((*it).match(message->method.p, message->method.len, message->uri.p)) { - HTTPRequest httpRequest(message); - (*it).invoke(&httpRequest, &httpResponse); - ESP_LOGD(tag, "Found a match!!"); - return; - } - } // End of examine path handlers. - - // Because we reached here, it means that we did NOT match a handler. Now we want to attempt - // to retrieve the corresponding file content. - std::string filePath = httpResponse.getRootPath(); + ESP_LOGD(tag, "WebServer::processRequest: Matching: %s", message->uri.p); + HTTPResponse httpResponse = HTTPResponse(mgConnection); + httpResponse.setRootPath(getRootPath()); + + /* + * Iterate through each of the path handlers looking for a match with the method and specified path. + */ + std::vector::iterator it; + for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { + if ((*it).match(message->method.p, message->method.len, message->uri.p)) { + HTTPRequest httpRequest(message); + (*it).invoke(&httpRequest, &httpResponse); + ESP_LOGD(tag, "Found a match!!"); + return; + } + } // End of examine path handlers. + + // Because we reached here, it means that we did NOT match a handler. Now we want to attempt + // to retrieve the corresponding file content. + std::string filePath = httpResponse.getRootPath(); filePath += message->uri.p; - ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); - FILE *file = fopen(filePath.c_str(), "r"); - if (file != nullptr) { - fseek(file, 0L, SEEK_END); - size_t length = ftell(file); - fseek(file, 0L, SEEK_SET); - uint8_t *pData = (uint8_t *)malloc(length); - fread(pData, length, 1, file); - fclose(file); - httpResponse.sendData(pData, length); - free(pData); - } else { - // Handle unable to open file - httpResponse.setStatus(404); // Not found - httpResponse.sendData(""); - } + ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); + FILE *file = fopen(filePath.c_str(), "r"); + if (file != nullptr) { + fseek(file, 0L, SEEK_END); + size_t length = ftell(file); + fseek(file, 0L, SEEK_SET); + uint8_t *pData = (uint8_t *)malloc(length); + fread(pData, length, 1, file); + fclose(file); + httpResponse.sendData(pData, length); + free(pData); + } else { + // Handle unable to open file + httpResponse.setStatus(404); // Not found + httpResponse.sendData(""); + } } // processRequest @@ -589,9 +589,9 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { - m_method = method; - m_pattern = std::regex(pathPattern); - m_requestHandler = webServerRequestHandler; + m_method = method; + m_pattern = std::regex(pathPattern); + m_requestHandler = webServerRequestHandler; } // PathHandler WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, @@ -628,7 +628,7 @@ bool WebServer::PathHandler::match(const char* method, size_t method_len, const * @return N/A. */ void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer::HTTPResponse *response) { - m_requestHandler(request, response); + m_requestHandler(request, response); } // invoke @@ -639,7 +639,7 @@ void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer:: * @param [in] message The description of the underlying Mongoose message. */ WebServer::HTTPRequest::HTTPRequest(struct http_message* message) { - m_message = message; + m_message = message; } // HTTPRequest @@ -670,7 +670,7 @@ size_t WebServer::HTTPRequest::getBodyLen() const { * @return The method of the request. */ const char* WebServer::HTTPRequest::getMethod() const { - return m_message->method.p; + return m_message->method.p; } // getMethod /** @@ -690,7 +690,7 @@ size_t WebServer::HTTPRequest::getMethodLen() const { * @return The path of the request. */ const char* WebServer::HTTPRequest::getPath() const { - return m_message->uri.p; + return m_message->uri.p; } // getPath /** @@ -712,48 +712,48 @@ size_t WebServer::HTTPRequest::getPathLen() const { * @return The query part of the request. */ std::map WebServer::HTTPRequest::getQuery() const { - // Walk through all the characters in the query string maintaining a simple state machine - // that lets us know what we are parsing. - std::map queryMap; - const char* queryString = m_message->query_string.p; + // Walk through all the characters in the query string maintaining a simple state machine + // that lets us know what we are parsing. + std::map queryMap; + const char* queryString = m_message->query_string.p; size_t queryStringLen = m_message->query_string.len; - int i=0; - - /* - * We maintain a simple state machine with states of: - * * STATE_NAME - We are parsing a name. - * * STATE_VALUE - We are parsing a value. - */ - int state = STATE_NAME; - std::string name = ""; - std::string value; - // Loop through each character in the query string. - for (i=0; i WebServer::HTTPRequest::getQuery() const { * @return A vector of the constituent parts of the path. */ std::vector WebServer::HTTPRequest::pathSplit() const { - std::istringstream stream(getPath()); - std::vector ret; - std::string pathPart; - while(std::getline(stream, pathPart, '/')) { - ret.push_back(pathPart); - } - // Debug - for (int i=0; i ret; + std::string pathPart; + while(std::getline(stream, pathPart, '/')) { + ret.push_back(pathPart); + } + // Debug + for (int i=0; i WebServer::HTTPRequest::pathSplit() const { * @return N/A. */ void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::string& fileName) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", - varName.c_str(), fileName.c_str()); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", + varName.c_str(), fileName.c_str()); } // WebServer::HTTPMultiPart::begin @@ -810,7 +810,7 @@ void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::stri * @return N/A. */ void WebServer::HTTPMultiPart::end() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::end()"); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::end()"); } // WebServer::HTTPMultiPart::end @@ -822,7 +822,7 @@ void WebServer::HTTPMultiPart::end() { * @return N/A. */ void WebServer::HTTPMultiPart::data(const std::string& data) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); } // WebServer::HTTPMultiPart::data @@ -831,7 +831,7 @@ void WebServer::HTTPMultiPart::data(const std::string& data) { * @return N/A. */ void WebServer::HTTPMultiPart::multipartEnd() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartEnd()"); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartEnd()"); } // WebServer::HTTPMultiPart::multipartEnd @@ -840,7 +840,7 @@ void WebServer::HTTPMultiPart::multipartEnd() { * @return N/A. */ void WebServer::HTTPMultiPart::multipartStart() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartStart()"); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartStart()"); } // WebServer::HTTPMultiPart::multipartStart @@ -877,10 +877,10 @@ void WebServer::WebSocketHandler::onClosed() { * @return N/A. */ void WebServer::WebSocketHandler::sendData(const std::string& message) { - ESP_LOGD(tag, "WebSocketHandler::sendData(length=%d)", message.length()); - mg_send_websocket_frame(m_mgConnection, - WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, - message.data(), message.length()); + ESP_LOGD(tag, "WebSocketHandler::sendData(length=%d)", message.length()); + mg_send_websocket_frame(m_mgConnection, + WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, + message.data(), message.length()); } // sendData /** @@ -890,9 +890,9 @@ void WebServer::WebSocketHandler::sendData(const std::string& message) { * @return N/A. */ void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { - mg_send_websocket_frame(m_mgConnection, - WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, - data, size); + mg_send_websocket_frame(m_mgConnection, + WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, + data, size); } // sendData @@ -903,7 +903,7 @@ void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { * @return N/A. */ void WebServer::WebSocketHandler::close() { - mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_CLOSE, nullptr, 0); + mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_CLOSE, nullptr, 0); } // close diff --git a/cpp_utils/WebServer.h b/cpp_utils/WebServer.h index c7d621fb..39d55e38 100644 --- a/cpp_utils/WebServer.h +++ b/cpp_utils/WebServer.h @@ -24,13 +24,13 @@ class WebServer; */ class WebServer { public: - /** - * @brief Request wrapper for an HTTP request. - */ - class HTTPRequest { - public: - HTTPRequest(struct http_message* message); - const char* getMethod() const; + /** + * @brief Request wrapper for an HTTP request. + */ + class HTTPRequest { + public: + HTTPRequest(struct http_message* message); + const char* getMethod() const; const char* getPath() const; const char* getBody() const; size_t getMethodLen() const; @@ -38,19 +38,19 @@ class WebServer { size_t getBodyLen() const; std::map getQuery() const; std::vector pathSplit() const; - private: - struct http_message* m_message; - }; // HTTPRequest + private: + struct http_message* m_message; + }; // HTTPRequest - /** - * @brief Response wrapper for an HTTP response. - */ - class HTTPResponse { - public: - HTTPResponse(struct mg_connection *nc); - void addHeader(const std::string& name, const std::string& value); - void addHeader(std::string&& name, std::string&& value); - void setStatus(int status); + /** + * @brief Response wrapper for an HTTP response. + */ + class HTTPResponse { + public: + HTTPResponse(struct mg_connection *nc); + void addHeader(const std::string& name, const std::string& value); + void addHeader(std::string&& name, std::string&& value); + void setStatus(int status); void setHeaders(const std::map& headers); void setHeaders(std::map&& headers); void sendData(const std::string& data); @@ -58,143 +58,143 @@ class WebServer { const std::string& getRootPath() const; void setRootPath(const std::string& path); void setRootPath(std::string&& path); - private: - struct mg_connection *m_nc; - std::string m_rootPath; - int m_status; - std::map m_headers; - bool m_dataSent; - }; // HTTPResponse + private: + struct mg_connection *m_nc; + std::string m_rootPath; + int m_status; + std::map m_headers; + bool m_dataSent; + }; // HTTPResponse - /** - * @brief Handler for a Multipart. - * - * This class is usually subclassed to provide your own implementation. Typically - * a factory is implemented based on HTTPMultiPartFactory that creates instances. This - * is then registered with the WebServer. When done, when ever the WebServer receives multi - * part requests, this handler for Multipart is called. The call sequence is usually: - * ~~~ - * multipartStart - * begin - * data* - * end - * begin - * data* - * end - * ... - * multipartEnd - * ~~~ - * - * There can be multiple begin, data, data, ..., end groups. - */ - class HTTPMultiPart { - public: - virtual ~HTTPMultiPart() { - }; - virtual void begin(const std::string& varName, const std::string& fileName); - virtual void end(); - virtual void data(const std::string& data); - virtual void multipartEnd(); - virtual void multipartStart(); - }; // HTTPMultiPart + /** + * @brief Handler for a Multipart. + * + * This class is usually subclassed to provide your own implementation. Typically + * a factory is implemented based on HTTPMultiPartFactory that creates instances. This + * is then registered with the WebServer. When done, when ever the WebServer receives multi + * part requests, this handler for Multipart is called. The call sequence is usually: + * ~~~ + * multipartStart + * begin + * data* + * end + * begin + * data* + * end + * ... + * multipartEnd + * ~~~ + * + * There can be multiple begin, data, data, ..., end groups. + */ + class HTTPMultiPart { + public: + virtual ~HTTPMultiPart() { + }; + virtual void begin(const std::string& varName, const std::string& fileName); + virtual void end(); + virtual void data(const std::string& data); + virtual void multipartEnd(); + virtual void multipartStart(); + }; // HTTPMultiPart - /** - * @brief Factory for creating Multipart instances. - * This class is meant to be implemented to provide a constructor for a custom - * HTTPMultiPart instance. - * @code{.cpp} - * class MyMultiPart : public WebServer::HTTPMultiPart { - * public: - * void begin(std::string varName, std::string fileName) { - * ESP_LOGD(tag, "MyMultiPart begin(): varName=%s, fileName=%s", - * varName.c_str(), fileName.c_str()); - * } - * - * void end() { - * ESP_LOGD(tag, "MyMultiPart end()"); - * } - * - * void data(std::string data) { - * ESP_LOGD(tag, "MyMultiPart data(): length=%d", data.length()); - * } - * - * void multipartEnd() { - * ESP_LOGD(tag, "MyMultiPart multipartEnd()"); - * } - * - * void multipartStart() { - * ESP_LOGD(tag, "MyMultiPart multipartStart()"); - * } - * }; - * - * class MyMultiPartFactory : public WebServer::HTTPMultiPartFactory { - * WebServer::HTTPMultiPart *createNew() { - * return new MyMultiPart(); - * } - * }; - * @endcode - */ - class HTTPMultiPartFactory { - public: - /** - * @brief Create a new HTTPMultiPart instance. - * @return A new HTTPMultiPart instance. - */ - virtual HTTPMultiPart *newInstance() = 0; - }; + /** + * @brief Factory for creating Multipart instances. + * This class is meant to be implemented to provide a constructor for a custom + * HTTPMultiPart instance. + * @code{.cpp} + * class MyMultiPart : public WebServer::HTTPMultiPart { + * public: + * void begin(std::string varName, std::string fileName) { + * ESP_LOGD(tag, "MyMultiPart begin(): varName=%s, fileName=%s", + * varName.c_str(), fileName.c_str()); + * } + * + * void end() { + * ESP_LOGD(tag, "MyMultiPart end()"); + * } + * + * void data(std::string data) { + * ESP_LOGD(tag, "MyMultiPart data(): length=%d", data.length()); + * } + * + * void multipartEnd() { + * ESP_LOGD(tag, "MyMultiPart multipartEnd()"); + * } + * + * void multipartStart() { + * ESP_LOGD(tag, "MyMultiPart multipartStart()"); + * } + * }; + * + * class MyMultiPartFactory : public WebServer::HTTPMultiPartFactory { + * WebServer::HTTPMultiPart *createNew() { + * return new MyMultiPart(); + * } + * }; + * @endcode + */ + class HTTPMultiPartFactory { + public: + /** + * @brief Create a new HTTPMultiPart instance. + * @return A new HTTPMultiPart instance. + */ + virtual HTTPMultiPart *newInstance() = 0; + }; - /** - * @brief The handler for path matching. - * - */ - class PathHandler { - public: - PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); - PathHandler(std::string&& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + /** + * @brief The handler for path matching. + * + */ + class PathHandler { + public: + PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + PathHandler(std::string&& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); bool match(const char* method, size_t method_len, const char* path); - void invoke(HTTPRequest *request, HTTPResponse *response); - private: - std::string m_method; - std::regex m_pattern; - void (*m_requestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse); - }; // PathHandler + void invoke(HTTPRequest *request, HTTPResponse *response); + private: + std::string m_method; + std::regex m_pattern; + void (*m_requestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse); + }; // PathHandler - /** - * @brief A WebSocket handler for handling WebSockets. - */ - class WebSocketHandler { - public: - void onCreated(); - virtual void onMessage(const std::string& message); - void onClosed(); - void sendData(const std::string& message); - void sendData(const uint8_t* data, uint32_t size); - void close(); - private: - struct mg_connection *m_mgConnection; - }; + /** + * @brief A WebSocket handler for handling WebSockets. + */ + class WebSocketHandler { + public: + void onCreated(); + virtual void onMessage(const std::string& message); + void onClosed(); + void sendData(const std::string& message); + void sendData(const uint8_t* data, uint32_t size); + void close(); + private: + struct mg_connection *m_mgConnection; + }; - class WebSocketHandlerFactory { - public: - virtual WebSocketHandler *newInstance() = 0; - }; + class WebSocketHandlerFactory { + public: + virtual WebSocketHandler *newInstance() = 0; + }; - WebServer(); - virtual ~WebServer(); - void addPathHandler(const std::string& method, const std::string& pathExpr, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + WebServer(); + virtual ~WebServer(); + void addPathHandler(const std::string& method, const std::string& pathExpr, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); void addPathHandler(std::string&& method, const std::string& pathExpr, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); - const std::string& getRootPath(); - void setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory); + const std::string& getRootPath(); + void setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory); void setRootPath(const std::string& path); void setRootPath(std::string&& path); - void setWebSocketHandlerFactory(WebSocketHandlerFactory *pWebSocketHandlerFactory); - void start(unsigned short port = 80); - void processRequest(struct mg_connection *mgConnection, struct http_message *message); - HTTPMultiPartFactory *m_pMultiPartFactory; - WebSocketHandlerFactory *m_pWebSocketHandlerFactory; + void setWebSocketHandlerFactory(WebSocketHandlerFactory *pWebSocketHandlerFactory); + void start(unsigned short port = 80); + void processRequest(struct mg_connection *mgConnection, struct http_message *message); + HTTPMultiPartFactory *m_pMultiPartFactory; + WebSocketHandlerFactory *m_pWebSocketHandlerFactory; private: - std::string m_rootPath; - std::vector m_pathHandlers; + std::string m_rootPath; + std::vector m_pathHandlers; }; #endif // CONFIG_MONGOOSE_PRESENT From 440c953ac6d505fb326e4b509bff5ee3cbb1d0e3 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Mon, 25 Sep 2017 12:55:35 +0300 Subject: [PATCH 058/381] Tabs to spaces (makes github code a lot more readable) --- cpp_utils/WiFi.cpp | 294 ++++++++++++++++++++++----------------------- cpp_utils/WiFi.h | 146 +++++++++++----------- 2 files changed, 220 insertions(+), 220 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 89ca6fd8..38ac9811 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -33,20 +33,20 @@ static char tag[]= "WiFi"; /* static void setDNSServer(char *ip) { - ip_addr_t dnsserver; - ESP_LOGD(tag, "Setting DNS[%d] to %s", 0, ip); - inet_pton(AF_INET, ip, &dnsserver); - ESP_LOGD(tag, "ip of DNS is %.8x", *(uint32_t *)&dnsserver); - dns_setserver(0, &dnsserver); + ip_addr_t dnsserver; + ESP_LOGD(tag, "Setting DNS[%d] to %s", 0, ip); + inet_pton(AF_INET, ip, &dnsserver); + ESP_LOGD(tag, "ip of DNS is %.8x", *(uint32_t *)&dnsserver); + dns_setserver(0, &dnsserver); } */ WiFi::WiFi() { - ip = ""; - gw = ""; - netmask = ""; - wifiEventHandler = new WiFiEventHandler(); + ip = ""; + gw = ""; + netmask = ""; + wifiEventHandler = new WiFiEventHandler(); } @@ -68,7 +68,7 @@ WiFi::WiFi() { * @return N/A. */ void WiFi::addDNSServer(const std::string& ip) { - addDNSServer(ip.c_str()); + addDNSServer(ip.c_str()); } // addDNSServer void WiFi::addDNSServer(const char* ip) { @@ -117,45 +117,45 @@ void WiFi::setDNSServer(int numdns, const char* ip) { * @return N/A. */ void WiFi::connectAP(const std::string& ssid, const std::string& password){ - ::nvs_flash_init(); - ::tcpip_adapter_init(); - if (ip.length() > 0 && gw.length() > 0 && netmask.length() > 0) { - ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client - tcpip_adapter_ip_info_t ipInfo; - - inet_pton(AF_INET, ip.data(), &ipInfo.ip); - inet_pton(AF_INET, gw.data(), &ipInfo.gw); - inet_pton(AF_INET, netmask.data(), &ipInfo.netmask); - ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - } - - - ESP_ERROR_CHECK( esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(::esp_wifi_init(&cfg)); - ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); - wifi_config_t sta_config; - ::memset(&sta_config, 0, sizeof(sta_config)); - ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); - ::memcpy(sta_config.sta.password, password.data(), password.size()); - sta_config.sta.bssid_set = 0; - ESP_ERROR_CHECK(::esp_wifi_set_config(WIFI_IF_STA, &sta_config)); - ESP_ERROR_CHECK(::esp_wifi_start()); - - ESP_ERROR_CHECK(::esp_wifi_connect()); + ::nvs_flash_init(); + ::tcpip_adapter_init(); + if (ip.length() > 0 && gw.length() > 0 && netmask.length() > 0) { + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client + tcpip_adapter_ip_info_t ipInfo; + + inet_pton(AF_INET, ip.data(), &ipInfo.ip); + inet_pton(AF_INET, gw.data(), &ipInfo.gw); + inet_pton(AF_INET, netmask.data(), &ipInfo.netmask); + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + } + + + ESP_ERROR_CHECK( esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(::esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); + wifi_config_t sta_config; + ::memset(&sta_config, 0, sizeof(sta_config)); + ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); + ::memcpy(sta_config.sta.password, password.data(), password.size()); + sta_config.sta.bssid_set = 0; + ESP_ERROR_CHECK(::esp_wifi_set_config(WIFI_IF_STA, &sta_config)); + ESP_ERROR_CHECK(::esp_wifi_start()); + + ESP_ERROR_CHECK(::esp_wifi_connect()); } // connectAP /** * @brief Dump diagnostics to the log. */ void WiFi::dump() { - ESP_LOGD(tag, "WiFi Dump"); - ESP_LOGD(tag, "---------"); - char ipAddrStr[30]; - ip_addr_t ip = ::dns_getserver(0); - inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); - ESP_LOGD(tag, "DNS Server[0]: %s", ipAddrStr); + ESP_LOGD(tag, "WiFi Dump"); + ESP_LOGD(tag, "---------"); + char ipAddrStr[30]; + ip_addr_t ip = ::dns_getserver(0); + inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); + ESP_LOGD(tag, "DNS Server[0]: %s", ipAddrStr); } // dump /** @@ -163,9 +163,9 @@ void WiFi::dump() { * @return The AP IP Info. */ tcpip_adapter_ip_info_t WiFi::getApIpInfo() { - tcpip_adapter_ip_info_t ipInfo; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); - return ipInfo; + tcpip_adapter_ip_info_t ipInfo; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); + return ipInfo; } // getApIpInfo @@ -175,11 +175,11 @@ tcpip_adapter_ip_info_t WiFi::getApIpInfo() { * @return The MAC address of the AP interface. */ std::string WiFi::getApMac() { - uint8_t mac[6]; - esp_wifi_get_mac(WIFI_IF_AP, mac); + uint8_t mac[6]; + esp_wifi_get_mac(WIFI_IF_AP, mac); auto mac_str = (char*) malloc(18); sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(std::move(mac_str)); + return std::string(std::move(mac_str)); } // getApMac @@ -188,9 +188,9 @@ std::string WiFi::getApMac() { * @return The AP SSID. */ std::string WiFi::getApSSID() { - wifi_config_t conf; - esp_wifi_get_config(WIFI_IF_AP, &conf); - return std::string((char *)conf.sta.ssid); + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_AP, &conf); + return std::string((char *)conf.sta.ssid); } // getApSSID @@ -202,7 +202,7 @@ std::string WiFi::getApSSID() { * @return The IP address of the host or 0.0.0.0 if not found. */ struct in_addr WiFi::getHostByName(const std::string& hostName) { - return getHostByName(hostName.c_str()); + return getHostByName(hostName.c_str()); } // getHostByName struct in_addr WiFi::getHostByName(const char* hostName) { @@ -224,20 +224,20 @@ struct in_addr WiFi::getHostByName(const char* hostName) { * @return The WiFi Mode. */ std::string WiFi::getMode() { - wifi_mode_t mode; - esp_wifi_get_mode(&mode); - switch(mode) { - case WIFI_MODE_NULL: - return "WIFI_MODE_NULL"; - case WIFI_MODE_STA: - return "WIFI_MODE_STA"; - case WIFI_MODE_AP: - return "WIFI_MODE_AP"; - case WIFI_MODE_APSTA: - return "WIFI_MODE_APSTA"; - default: - return "unknown"; - } + wifi_mode_t mode; + esp_wifi_get_mode(&mode); + switch(mode) { + case WIFI_MODE_NULL: + return "WIFI_MODE_NULL"; + case WIFI_MODE_STA: + return "WIFI_MODE_STA"; + case WIFI_MODE_AP: + return "WIFI_MODE_AP"; + case WIFI_MODE_APSTA: + return "WIFI_MODE_APSTA"; + default: + return "unknown"; + } } // getMode @@ -246,9 +246,9 @@ std::string WiFi::getMode() { * @return The STA IP Info. */ tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { - tcpip_adapter_ip_info_t ipInfo; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - return ipInfo; + tcpip_adapter_ip_info_t ipInfo; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + return ipInfo; } // getStaIpInfo @@ -257,8 +257,8 @@ tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { * @return The MAC address of the STA interface. */ std::string WiFi::getStaMac() { - uint8_t mac[6]; - esp_wifi_get_mac(WIFI_IF_STA, mac); + uint8_t mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, mac); auto mac_str = (char*) malloc(18); sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return std::string(std::move(mac_str)); @@ -270,9 +270,9 @@ std::string WiFi::getStaMac() { * @return The STA SSID. */ std::string WiFi::getStaSSID() { - wifi_config_t conf; - esp_wifi_get_config(WIFI_IF_STA, &conf); - return std::string((char *)conf.ap.ssid); + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_STA, &conf); + return std::string((char *)conf.ap.ssid); } // getStaSSID @@ -287,34 +287,34 @@ std::string WiFi::getStaSSID() { * @return A vector of WiFiAPRecord instances. */ std::vector WiFi::scan() { - ::nvs_flash_init(); - ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); - ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK( esp_wifi_start() ); - wifi_scan_config_t conf; - memset(&conf, 0, sizeof(conf)); - conf.show_hidden = true; - esp_err_t rc = ::esp_wifi_scan_start(&conf, true); - if (rc != ESP_OK) { - ESP_LOGE(tag, "esp_wifi_scan_start: %d", rc); - } - uint16_t apCount; - rc = ::esp_wifi_scan_get_ap_num(&apCount); - ESP_LOGD(tag, "Count of found access points: %d", apCount); + ::nvs_flash_init(); + ::tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK( esp_wifi_start() ); + wifi_scan_config_t conf; + memset(&conf, 0, sizeof(conf)); + conf.show_hidden = true; + esp_err_t rc = ::esp_wifi_scan_start(&conf, true); + if (rc != ESP_OK) { + ESP_LOGE(tag, "esp_wifi_scan_start: %d", rc); + } + uint16_t apCount; + rc = ::esp_wifi_scan_get_ap_num(&apCount); + ESP_LOGD(tag, "Count of found access points: %d", apCount); wifi_ap_record_t *list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, list)); std::vector apRecords; for (auto i=0; i WiFi::scan() { * @return N/A. */ void WiFi::startAP(const std::string& ssid, const std::string& password) { - ::nvs_flash_init(); - ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); - ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); - ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_AP) ); - wifi_config_t apConfig; - ::memset(&apConfig, 0, sizeof(apConfig)); - ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); - apConfig.ap.ssid_len = ssid.size(); - ::memcpy(apConfig.ap.password, password.data(), password.size()); - apConfig.ap.channel = 0; - apConfig.ap.authmode = WIFI_AUTH_OPEN; - apConfig.ap.ssid_hidden = 0; - apConfig.ap.max_connection = 4; - apConfig.ap.beacon_interval = 100; - ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &apConfig) ); - ESP_ERROR_CHECK( esp_wifi_start() ); + ::nvs_flash_init(); + ::tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_AP) ); + wifi_config_t apConfig; + ::memset(&apConfig, 0, sizeof(apConfig)); + ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); + apConfig.ap.ssid_len = ssid.size(); + ::memcpy(apConfig.ap.password, password.data(), password.size()); + apConfig.ap.channel = 0; + apConfig.ap.authmode = WIFI_AUTH_OPEN; + apConfig.ap.ssid_hidden = 0; + apConfig.ap.max_connection = 4; + apConfig.ap.beacon_interval = 100; + ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &apConfig) ); + ESP_ERROR_CHECK( esp_wifi_start() ); } // startAP @@ -368,9 +368,9 @@ void WiFi::startAP(const std::string& ssid, const std::string& password) { * @return N/A. */ void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask) { - this->ip = ip; - this->gw = gw; - this->netmask = netmask; + this->ip = ip; + this->gw = gw; + this->netmask = netmask; } // setIPInfo void WiFi::setIPInfo(std::string&& ip, std::string&& gw, std::string&& netmask) { @@ -386,43 +386,43 @@ void WiFi::setIPInfo(std::string&& ip, std::string&& gw, std::string&& netmask) * @return A string representation of the WiFi access point record. */ std::string WiFiAPRecord::toString() { - std::string auth; - switch(getAuthMode()) { - case WIFI_AUTH_OPEN: - auth = "WIFI_AUTH_OPEN"; - break; - case WIFI_AUTH_WEP: - auth = "WIFI_AUTH_WEP"; - break; - case WIFI_AUTH_WPA_PSK: - auth = "WIFI_AUTH_WPA_PSK"; - break; - case WIFI_AUTH_WPA2_PSK: - auth = "WIFI_AUTH_WPA2_PSK"; - break; - case WIFI_AUTH_WPA_WPA2_PSK: - auth = "WIFI_AUTH_WPA_WPA2_PSK"; - break; - default: - auth = ""; - break; - } + std::string auth; + switch(getAuthMode()) { + case WIFI_AUTH_OPEN: + auth = "WIFI_AUTH_OPEN"; + break; + case WIFI_AUTH_WEP: + auth = "WIFI_AUTH_WEP"; + break; + case WIFI_AUTH_WPA_PSK: + auth = "WIFI_AUTH_WPA_PSK"; + break; + case WIFI_AUTH_WPA2_PSK: + auth = "WIFI_AUTH_WPA2_PSK"; + break; + case WIFI_AUTH_WPA_WPA2_PSK: + auth = "WIFI_AUTH_WPA_WPA2_PSK"; + break; + default: + auth = ""; + break; + } // std::stringstream s; // s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; auto info_str = (char*) malloc(6 + 32 + 8 + 22 + 8 + 3 + 1); sprintf(info_str, "ssid: %s, auth: %s, rssi: %d", m_ssid.c_str(), auth.c_str(), (int) m_rssi); - return std::string(std::move(info_str)); + return std::string(std::move(info_str)); } // toString MDNS::MDNS() { - ESP_ERROR_CHECK(mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server)); + ESP_ERROR_CHECK(mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server)); } MDNS::~MDNS() { - if (m_mdns_server != nullptr) { - mdns_free(m_mdns_server); - } - m_mdns_server = nullptr; + if (m_mdns_server != nullptr) { + mdns_free(m_mdns_server); + } + m_mdns_server = nullptr; } /** diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 4f159d87..e525ce5d 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -19,14 +19,14 @@ */ class MDNS { public: - MDNS(); - ~MDNS(); - void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); - void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); - void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); - void serviceRemove(const std::string& service, const std::string& proto); - void setHostname(const std::string& hostname); - void setInstance(const std::string& instance); + MDNS(); + ~MDNS(); + void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); + void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); + void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); + void serviceRemove(const std::string& service, const std::string& proto); + void setHostname(const std::string& hostname); + void setInstance(const std::string& instance); // If we the above functions with a basic char*, a copy would be created into an std::string, // making the whole thing require twice as much processing power and speed void serviceAdd(const char* service, const char* proto, uint16_t port); @@ -36,44 +36,44 @@ class MDNS { void setHostname(const char* hostname); void setInstance(const char* instance); private: - mdns_server_t *m_mdns_server = nullptr; + mdns_server_t *m_mdns_server = nullptr; }; class WiFiAPRecord { public: - friend class WiFi; - - /** - * @brief Get the auth mode. - * @return The auth mode. - */ - wifi_auth_mode_t getAuthMode() { - return m_authMode; - } - - /** - * @brief Get the RSSI. - * @return the RSSI. - */ - int8_t getRSSI() { - return m_rssi; - } - - /** - * @brief Get the SSID. - * @return the SSID. - */ - std::string getSSID() { - return m_ssid; - } - - std::string toString(); + friend class WiFi; + + /** + * @brief Get the auth mode. + * @return The auth mode. + */ + wifi_auth_mode_t getAuthMode() { + return m_authMode; + } + + /** + * @brief Get the RSSI. + * @return the RSSI. + */ + int8_t getRSSI() { + return m_rssi; + } + + /** + * @brief Get the SSID. + * @return the SSID. + */ + std::string getSSID() { + return m_ssid; + } + + std::string toString(); private: - uint8_t m_bssid[6]; - int8_t m_rssi; - std::string m_ssid; - wifi_auth_mode_t m_authMode; + uint8_t m_bssid[6]; + int8_t m_rssi; + std::string m_ssid; + wifi_auth_mode_t m_authMode; }; /** @@ -103,47 +103,47 @@ class WiFiAPRecord { */ class WiFi { private: - std::string ip; - std::string gw; - std::string netmask; - WiFiEventHandler *wifiEventHandler; + std::string ip; + std::string gw; + std::string netmask; + WiFiEventHandler *wifiEventHandler; public: - WiFi(); - void addDNSServer(const std::string& ip); + WiFi(); + void addDNSServer(const std::string& ip); void addDNSServer(const char* ip); void setDNSServer(int numdns, const std::string& ip); void setDNSServer(int numdns, const char* ip); struct in_addr getHostByName(const std::string& hostName); struct in_addr getHostByName(const char* hostName); - void connectAP(const std::string& ssid, const std::string& password); - void dump(); - static std::string getApMac(); - static tcpip_adapter_ip_info_t getApIpInfo(); - static std::string getApSSID(); - static std::string getMode(); - static tcpip_adapter_ip_info_t getStaIpInfo(); - static std::string getStaMac(); - static std::string getStaSSID(); - std::vector scan(); - void startAP(const std::string& ssid, const std::string& passwd); - void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); - void setIPInfo(std::string&& ip, std::string&& gw, std::string&& netmask); - - - - - - /** - * Set the event handler to use to process detected events. - * @param[in] wifiEventHandler The class that will be used to process events. - */ - void setWifiEventHandler(WiFiEventHandler *wifiEventHandler) { - this->wifiEventHandler = wifiEventHandler; - } + void connectAP(const std::string& ssid, const std::string& password); + void dump(); + static std::string getApMac(); + static tcpip_adapter_ip_info_t getApIpInfo(); + static std::string getApSSID(); + static std::string getMode(); + static tcpip_adapter_ip_info_t getStaIpInfo(); + static std::string getStaMac(); + static std::string getStaSSID(); + std::vector scan(); + void startAP(const std::string& ssid, const std::string& passwd); + void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); + void setIPInfo(std::string&& ip, std::string&& gw, std::string&& netmask); + + + + + + /** + * Set the event handler to use to process detected events. + * @param[in] wifiEventHandler The class that will be used to process events. + */ + void setWifiEventHandler(WiFiEventHandler *wifiEventHandler) { + this->wifiEventHandler = wifiEventHandler; + } private: - uint8_t m_dnsCount=0; - //char *m_dnsServer = nullptr; + uint8_t m_dnsCount=0; + //char *m_dnsServer = nullptr; }; From e25b26148703ba8799cccd2e147bbfc7da432329 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Mon, 25 Sep 2017 13:35:09 +0300 Subject: [PATCH 059/381] Fix some bugs i created while removing mgStrToString Everything works now --- cpp_utils/WebServer.cpp | 851 +++++++++++++++++++++------------------- 1 file changed, 446 insertions(+), 405 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index a3ecb1d6..51893ae7 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -18,14 +18,13 @@ #include #include - static char tag[] = "WebServer"; struct WebServerUserData { - WebServer *pWebServer; - WebServer::HTTPMultiPart *pMultiPart; - WebServer::WebSocketHandler *pWebSocketHandler; - void *originalUserData; + WebServer *pWebServer; + WebServer::HTTPMultiPart *pMultiPart; + WebServer::WebSocketHandler *pWebSocketHandler; + void *originalUserData; }; /** @@ -34,98 +33,89 @@ struct WebServerUserData { * @return The string representation of the event. */ static std::string mongoose_eventToString(int event) { - switch (event) { - case MG_EV_CONNECT: - return "MG_EV_CONNECT"; - case MG_EV_ACCEPT: - return "MG_EV_ACCEPT"; - case MG_EV_CLOSE: - return "MG_EV_CLOSE"; - case MG_EV_SEND: - return "MG_EV_SEND"; - case MG_EV_RECV: - return "MG_EV_RECV"; - case MG_EV_POLL: - return "MG_EV_POLL"; - case MG_EV_TIMER: - return "MG_EV_TIMER"; - case MG_EV_HTTP_PART_DATA: - return "MG_EV_HTTP_PART_DATA"; - case MG_EV_HTTP_MULTIPART_REQUEST: - return "MG_EV_HTTP_MULTIPART_REQUEST"; - case MG_EV_HTTP_PART_BEGIN: - return "MG_EV_HTTP_PART_BEGIN"; - case MG_EV_HTTP_PART_END: - return "MG_EV_HTTP_PART_END"; - case MG_EV_HTTP_MULTIPART_REQUEST_END: - return "MG_EV_HTTP_MULTIPART_REQUEST_END"; - case MG_EV_HTTP_REQUEST: - return "MG_EV_HTTP_REQUEST"; - case MG_EV_HTTP_REPLY: - return "MG_EV_HTTP_REPLY"; - case MG_EV_HTTP_CHUNK: - return "MG_EV_HTTP_CHUNK"; - case MG_EV_MQTT_CONNACK: - return "MG_EV_MQTT_CONNACK"; - case MG_EV_MQTT_CONNECT: - return "MG_EV_MQTT_CONNECT"; - case MG_EV_MQTT_DISCONNECT: - return "MG_EV_MQTT_DISCONNECT"; - case MG_EV_MQTT_PINGREQ: - return "MG_EV_MQTT_PINGREQ"; - case MG_EV_MQTT_PINGRESP: - return "MG_EV_MQTT_PINGRESP"; - case MG_EV_MQTT_PUBACK: - return "MG_EV_MQTT_PUBACK"; - case MG_EV_MQTT_PUBCOMP: - return "MG_EV_MQTT_PUBCOMP"; - case MG_EV_MQTT_PUBLISH: - return "MG_EV_MQTT_PUBLISH"; - case MG_EV_MQTT_PUBREC: - return "MG_EV_MQTT_PUBREC"; - case MG_EV_MQTT_PUBREL: - return "MG_EV_MQTT_PUBREL"; - case MG_EV_MQTT_SUBACK: - return "MG_EV_MQTT_SUBACK"; - case MG_EV_MQTT_SUBSCRIBE: - return "MG_EV_MQTT_SUBSCRIBE"; - case MG_EV_MQTT_UNSUBACK: - return "MG_EV_MQTT_UNSUBACK"; - case MG_EV_MQTT_UNSUBSCRIBE: - return "MG_EV_MQTT_UNSUBSCRIBE"; - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: - return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: - return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; - case MG_EV_WEBSOCKET_FRAME: - return "MG_EV_WEBSOCKET_FRAME"; - case MG_EV_WEBSOCKET_CONTROL_FRAME: - return "MG_EV_WEBSOCKET_CONTROL_FRAME"; - } - std::ostringstream s; - s << "Unknown event: " << event; - return s.str(); + switch (event) { + case MG_EV_CONNECT: + return "MG_EV_CONNECT"; + case MG_EV_ACCEPT: + return "MG_EV_ACCEPT"; + case MG_EV_CLOSE: + return "MG_EV_CLOSE"; + case MG_EV_SEND: + return "MG_EV_SEND"; + case MG_EV_RECV: + return "MG_EV_RECV"; + case MG_EV_POLL: + return "MG_EV_POLL"; + case MG_EV_TIMER: + return "MG_EV_TIMER"; + case MG_EV_HTTP_PART_DATA: + return "MG_EV_HTTP_PART_DATA"; + case MG_EV_HTTP_MULTIPART_REQUEST: + return "MG_EV_HTTP_MULTIPART_REQUEST"; + case MG_EV_HTTP_PART_BEGIN: + return "MG_EV_HTTP_PART_BEGIN"; + case MG_EV_HTTP_PART_END: + return "MG_EV_HTTP_PART_END"; + case MG_EV_HTTP_MULTIPART_REQUEST_END: + return "MG_EV_HTTP_MULTIPART_REQUEST_END"; + case MG_EV_HTTP_REQUEST: + return "MG_EV_HTTP_REQUEST"; + case MG_EV_HTTP_REPLY: + return "MG_EV_HTTP_REPLY"; + case MG_EV_HTTP_CHUNK: + return "MG_EV_HTTP_CHUNK"; + case MG_EV_MQTT_CONNACK: + return "MG_EV_MQTT_CONNACK"; + case MG_EV_MQTT_CONNECT: + return "MG_EV_MQTT_CONNECT"; + case MG_EV_MQTT_DISCONNECT: + return "MG_EV_MQTT_DISCONNECT"; + case MG_EV_MQTT_PINGREQ: + return "MG_EV_MQTT_PINGREQ"; + case MG_EV_MQTT_PINGRESP: + return "MG_EV_MQTT_PINGRESP"; + case MG_EV_MQTT_PUBACK: + return "MG_EV_MQTT_PUBACK"; + case MG_EV_MQTT_PUBCOMP: + return "MG_EV_MQTT_PUBCOMP"; + case MG_EV_MQTT_PUBLISH: + return "MG_EV_MQTT_PUBLISH"; + case MG_EV_MQTT_PUBREC: + return "MG_EV_MQTT_PUBREC"; + case MG_EV_MQTT_PUBREL: + return "MG_EV_MQTT_PUBREL"; + case MG_EV_MQTT_SUBACK: + return "MG_EV_MQTT_SUBACK"; + case MG_EV_MQTT_SUBSCRIBE: + return "MG_EV_MQTT_SUBSCRIBE"; + case MG_EV_MQTT_UNSUBACK: + return "MG_EV_MQTT_UNSUBACK"; + case MG_EV_MQTT_UNSUBSCRIBE: + return "MG_EV_MQTT_UNSUBSCRIBE"; + case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: + return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: + return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; + case MG_EV_WEBSOCKET_FRAME: + return "MG_EV_WEBSOCKET_FRAME"; + case MG_EV_WEBSOCKET_CONTROL_FRAME: + return "MG_EV_WEBSOCKET_CONTROL_FRAME"; + } + std::string s; + s += "Unknown event: "; + s += event; + return s; } //eventToString -/** - * @brief Convert a Mongoose string type to a string. - * @param [in] mgStr The Mongoose string. - * @return A std::string representation of the Mongoose string. - */ -static std::string mgStrToString(struct mg_str mgStr) { - return std::string(mgStr.p, mgStr.len); -} // mgStrToStr - static void dumpHttpMessage(struct http_message *pHttpMessage) { - ESP_LOGD(tag, "HTTP Message"); - ESP_LOGD(tag, "Message: %s", mgStrToString(pHttpMessage->message).c_str()); - ESP_LOGD(tag, "URI: %s", mgStrToString(pHttpMessage->uri).c_str()); + ESP_LOGD(tag, "HTTP Message"); + ESP_LOGD(tag, "Message: %.*s", (int)pHttpMessage->uri.len, pHttpMessage->message.p); } /* static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, struct mg_str fname) { - ESP_LOGD(tag, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); - return fname; + ESP_LOGD(tag, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); + return fname; } */ @@ -140,139 +130,139 @@ static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, s * @return N/A. */ static void mongoose_event_handler_web_server( - struct mg_connection *mgConnection, // The network connection associated with the event. - int event, // The type of event. - void *eventData // Data associated with the event. + struct mg_connection *mgConnection, // The network connection associated with the event. + int event, // The type of event. + void *eventData // Data associated with the event. ) { - if (event == MG_EV_POLL) { - return; - } - ESP_LOGD(tag, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); - switch (event) { - case MG_EV_HTTP_REQUEST: { - struct http_message *message = (struct http_message *) eventData; - dumpHttpMessage(message); - - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; - pWebServer->processRequest(mgConnection, message); - break; - } // MG_EV_HTTP_REQUEST - - case MG_EV_HTTP_MULTIPART_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - ESP_LOGD(tag, "User_data address 0x%d", (uint32_t)pWebServerUserData); - WebServer *pWebServer = pWebServerUserData->pWebServer; - if (pWebServer->m_pMultiPartFactory == nullptr) { - return; - } - WebServer::HTTPMultiPart *pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pMultiPart = pMultiPart; - p2->pWebSocketHandler = nullptr; - mgConnection->user_data = p2; - //struct http_message *message = (struct http_message *) eventData; - //dumpHttpMessage(message); - break; - } // MG_EV_HTTP_MULTIPART_REQUEST - - case MG_EV_HTTP_MULTIPART_REQUEST_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pMultiPart != nullptr) { - delete pWebServerUserData->pMultiPart; - pWebServerUserData->pMultiPart = nullptr; - } - mgConnection->user_data = pWebServerUserData->originalUserData; - delete pWebServerUserData; - WebServer::HTTPResponse httpResponse = WebServer::HTTPResponse(mgConnection); - httpResponse.setStatus(200); - httpResponse.sendData(""); - break; - } // MG_EV_HTTP_MULTIPART_REQUEST_END - - case MG_EV_HTTP_PART_BEGIN: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->begin(std::string(part->var_name), std::string(part->file_name)); - } - break; - } // MG_EV_HTTP_PART_BEGIN - - case MG_EV_HTTP_PART_DATA: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->data(mgStrToString(part->data)); - } - break; - } // MG_EV_HTTP_PART_DATA - - case MG_EV_HTTP_PART_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->end(); - } - break; - } // MG_EV_HTTP_PART_END - - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; - if (pWebServer->m_pWebSocketHandlerFactory != nullptr) { - if (pWebServerUserData->pWebSocketHandler != nullptr) { - ESP_LOGD(tag, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); - } - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); - mgConnection->user_data = p2; - } else { - ESP_LOGD(tag, "We received a WebSocket request but we have no handler factory!"); - } - break; - } // MG_EV_WEBSOCKET_HANDSHAKE_REQUEST - - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); - return; - } - pWebServerUserData->pWebSocketHandler->onCreated(); - break; - } // MG_EV_WEBSOCKET_HANDSHAKE_DONE - - - /* - * When we receive a MG_EV_WEBSOCKET_FRAME then we have received a chunk of data over the network. - * Our goal will be to send this to the web socket handler (if one exists). - */ - case MG_EV_WEBSOCKET_FRAME: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); - return; - } - struct websocket_message *ws_message = (websocket_message *)eventData; - ESP_LOGD(tag, "Received data length: %d", ws_message->size); - pWebServerUserData->pWebSocketHandler->onMessage(std::string((char *)ws_message->data, ws_message->size)); - break; - } // MG_EV_WEBSOCKET_FRAME - - } // End of switch + if (event == MG_EV_POLL) { + return; + } + ESP_LOGD(tag, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); + switch (event) { + case MG_EV_HTTP_REQUEST: { + struct http_message *message = (struct http_message *) eventData; + dumpHttpMessage(message); + + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + WebServer *pWebServer = pWebServerUserData->pWebServer; + pWebServer->processRequest(mgConnection, message); + break; + } // MG_EV_HTTP_REQUEST + + case MG_EV_HTTP_MULTIPART_REQUEST: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + ESP_LOGD(tag, "User_data address 0x%d", (uint32_t)pWebServerUserData); + WebServer *pWebServer = pWebServerUserData->pWebServer; + if (pWebServer->m_pMultiPartFactory == nullptr) { + return; + } + WebServer::HTTPMultiPart *pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); + struct WebServerUserData *p2 = new WebServerUserData(); + ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pMultiPart = pMultiPart; + p2->pWebSocketHandler = nullptr; + mgConnection->user_data = p2; + //struct http_message *message = (struct http_message *) eventData; + //dumpHttpMessage(message); + break; + } // MG_EV_HTTP_MULTIPART_REQUEST + + case MG_EV_HTTP_MULTIPART_REQUEST_END: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + if (pWebServerUserData->pMultiPart != nullptr) { + delete pWebServerUserData->pMultiPart; + pWebServerUserData->pMultiPart = nullptr; + } + mgConnection->user_data = pWebServerUserData->originalUserData; + delete pWebServerUserData; + WebServer::HTTPResponse httpResponse = WebServer::HTTPResponse(mgConnection); + httpResponse.setStatus(200); + httpResponse.sendData(""); + break; + } // MG_EV_HTTP_MULTIPART_REQUEST_END + + case MG_EV_HTTP_PART_BEGIN: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; + ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->begin(std::string(part->var_name), std::string(part->file_name)); + } + break; + } // MG_EV_HTTP_PART_BEGIN + + case MG_EV_HTTP_PART_DATA: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; + ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->data(std::string(part->data.p, part->data.len)); + } + break; + } // MG_EV_HTTP_PART_DATA + + case MG_EV_HTTP_PART_END: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; + ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->end(); + } + break; + } // MG_EV_HTTP_PART_END + + case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + WebServer *pWebServer = pWebServerUserData->pWebServer; + if (pWebServer->m_pWebSocketHandlerFactory != nullptr) { + if (pWebServerUserData->pWebSocketHandler != nullptr) { + ESP_LOGD(tag, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); + } + struct WebServerUserData *p2 = new WebServerUserData(); + ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); + mgConnection->user_data = p2; + } else { + ESP_LOGD(tag, "We received a WebSocket request but we have no handler factory!"); + } + break; + } // MG_EV_WEBSOCKET_HANDSHAKE_REQUEST + + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + if (pWebServerUserData->pWebSocketHandler == nullptr) { + ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + return; + } + pWebServerUserData->pWebSocketHandler->onCreated(); + break; + } // MG_EV_WEBSOCKET_HANDSHAKE_DONE + + + /* + * When we receive a MG_EV_WEBSOCKET_FRAME then we have received a chunk of data over the network. + * Our goal will be to send this to the web socket handler (if one exists). + */ + case MG_EV_WEBSOCKET_FRAME: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + if (pWebServerUserData->pWebSocketHandler == nullptr) { + ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + return; + } + struct websocket_message *ws_message = (websocket_message *)eventData; + ESP_LOGD(tag, "Received data length: %d", ws_message->size); + pWebServerUserData->pWebSocketHandler->onMessage(std::string((char *)ws_message->data, ws_message->size)); + break; + } // MG_EV_WEBSOCKET_FRAME + + } // End of switch } // End of mongoose_event_handler @@ -280,9 +270,9 @@ static void mongoose_event_handler_web_server( * @brief Constructor. */ WebServer::WebServer() { - m_rootPath = ""; - m_pMultiPartFactory = nullptr; - m_pWebSocketHandlerFactory = nullptr; + m_rootPath = ""; + m_pMultiPartFactory = nullptr; + m_pWebSocketHandlerFactory = nullptr; } // WebServer @@ -295,7 +285,7 @@ WebServer::~WebServer() { * @return The current root path. */ const std::string& WebServer::getRootPath() { - return m_rootPath; + return m_rootPath; } // getRootPath @@ -322,7 +312,7 @@ const std::string& WebServer::getRootPath() { void WebServer::addPathHandler(const std::string& method, const std::string& pathExpr, void (* handler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { - m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); + m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); } // addPathHandler void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr, @@ -340,31 +330,31 @@ void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr * @return N/A. */ void WebServer::start(uint16_t port) { - ESP_LOGD(tag, "WebServer task starting"); - struct mg_mgr mgr; - mg_mgr_init(&mgr, NULL); - - std::stringstream stringStream; - stringStream << ':' << port; - struct mg_connection *mgConnection = mg_bind(&mgr, stringStream.str().c_str(), mongoose_event_handler_web_server); - - if (mgConnection == NULL) { - ESP_LOGE(tag, "No connection from the mg_bind()"); - vTaskDelete(NULL); - return; - } - - struct WebServerUserData *pWebServerUserData = new WebServerUserData(); - pWebServerUserData->pWebServer = this; - pWebServerUserData->pMultiPart = nullptr; - mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. - ESP_LOGD(tag, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); - mg_set_protocol_http_websocket(mgConnection); - - ESP_LOGD(tag, "WebServer listening on port %d", port); - while (1) { - mg_mgr_poll(&mgr, 2000); - } + ESP_LOGD(tag, "WebServer task starting"); + struct mg_mgr mgr; + mg_mgr_init(&mgr, NULL); + + std::stringstream stringStream; + stringStream << ':' << port; + struct mg_connection *mgConnection = mg_bind(&mgr, stringStream.str().c_str(), mongoose_event_handler_web_server); + + if (mgConnection == NULL) { + ESP_LOGE(tag, "No connection from the mg_bind()"); + vTaskDelete(NULL); + return; + } + + struct WebServerUserData *pWebServerUserData = new WebServerUserData(); + pWebServerUserData->pWebServer = this; + pWebServerUserData->pMultiPart = nullptr; + mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. + ESP_LOGD(tag, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); + mg_set_protocol_http_websocket(mgConnection); + + ESP_LOGD(tag, "WebServer listening on port %d", port); + while (1) { + mg_mgr_poll(&mgr, 2000); + } } // run @@ -373,7 +363,7 @@ void WebServer::start(uint16_t port) { * @param [in] pMultiPart A pointer to the multi part factory. */ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { - m_pMultiPartFactory = pMultiPartFactory; + m_pMultiPartFactory = pMultiPartFactory; } @@ -395,7 +385,11 @@ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { * @return N/A. */ void WebServer::setRootPath(const std::string& path) { - m_rootPath = path; + m_rootPath = path; +} // setRootPath + +void WebServer::setRootPath(std::string&& path) { + m_rootPath = std::move(path); } // setRootPath @@ -406,7 +400,7 @@ void WebServer::setRootPath(const std::string& path) { * @return N/A. */ void WebServer::setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHandlerFactory) { - m_pWebSocketHandlerFactory = pWebSocketHandlerFactory; + m_pWebSocketHandlerFactory = pWebSocketHandlerFactory; } // setWebSocketHandlerFactory @@ -415,9 +409,9 @@ void WebServer::setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHa * @param [in] nc The network connection for the response. */ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { - m_nc = nc; - m_status = 200; - m_dataSent = false; + m_nc = nc; + m_status = 200; + m_dataSent = false; } // HTTPResponse @@ -427,7 +421,7 @@ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { * @param [in] value The value of the header. */ void WebServer::HTTPResponse::addHeader(const std::string& name, const std::string& value) { - m_headers[name] = value; + m_headers[name] = value; } // addHeader void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) { @@ -441,7 +435,7 @@ void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) * @return N/A. */ void WebServer::HTTPResponse::sendData(const std::string& data) { - sendData((uint8_t *)data.data(), data.length()); + sendData((uint8_t *)data.data(), data.length()); } // sendData @@ -453,24 +447,35 @@ void WebServer::HTTPResponse::sendData(const std::string& data) { * @return N/A. */ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { - if (m_dataSent) { - ESP_LOGE(tag, "HTTPResponse: Data already sent! Attempt to send again/more."); - return; - } - m_dataSent = true; + if (m_dataSent) { + ESP_LOGE(tag, "HTTPResponse: Data already sent! Attempt to send again/more."); + return; + } + m_dataSent = true; - std::string headers; + std::string headers; + unsigned long headers_len = 0; - for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + for(auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + if(iter != m_headers.begin()) + headers_len += 2; + headers_len += iter->first.length(); + headers_len += 2; + headers_len += iter->second.length(); + } + headers_len += 1; + headers.resize(headers_len); // Will not have to resize and recopy during the next loop, we have 2 loops but it still ends up being faster + + for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { if(iter != m_headers.begin()) headers += "\r\n"; headers += iter->first; headers += ": "; headers += iter->second; - } - mg_send_head(m_nc, m_status, length, headers.c_str()); - mg_send(m_nc, pData, length); - m_nc->flags |= MG_F_SEND_AND_CLOSE; + } + mg_send_head(m_nc, m_status, length, headers.c_str()); + mg_send(m_nc, pData, length); + m_nc->flags |= MG_F_SEND_AND_CLOSE; } // sendData @@ -480,7 +485,7 @@ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { * @return N/A. */ void WebServer::HTTPResponse::setHeaders(const std::map& headers) { - m_headers = headers; + m_headers = headers; } // setHeaders void WebServer::HTTPResponse::setHeaders(std::map&& headers) { @@ -492,8 +497,8 @@ void WebServer::HTTPResponse::setHeaders(std::map&& he * @brief Get the current root path. * @return The current root path. */ -std::string WebServer::HTTPResponse::getRootPath() { - return m_rootPath; +const std::string& WebServer::HTTPResponse::getRootPath() const { + return m_rootPath; } // getRootPath @@ -503,7 +508,11 @@ std::string WebServer::HTTPResponse::getRootPath() { * @return N/A. */ void WebServer::HTTPResponse::setRootPath(const std::string& path) { - m_rootPath = path; + m_rootPath = path; +} // setRootPath + +void WebServer::HTTPResponse::setRootPath(std::string&& path) { + m_rootPath = std::move(path); } // setRootPath /** @@ -514,7 +523,7 @@ void WebServer::HTTPResponse::setRootPath(const std::string& path) { * @return N/A. */ void WebServer::HTTPResponse::setStatus(int status) { - m_status = status; + m_status = status; } // setStatus @@ -529,44 +538,43 @@ void WebServer::HTTPResponse::setStatus(int status) { * @param [in] message The message representing the request. */ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_message* message) { - std::string uri = mgStrToString(message->uri); - ESP_LOGD(tag, "WebServer::processRequest: Matching: %s", uri.c_str()); - HTTPResponse httpResponse = HTTPResponse(mgConnection); - httpResponse.setRootPath(getRootPath()); - - /* - * Iterate through each of the path handlers looking for a match with the method and specified path. - */ - std::vector::iterator it; - for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { - if ((*it).match(mgStrToString(message->method), uri)) { - HTTPRequest httpRequest(message); - (*it).invoke(&httpRequest, &httpResponse); - ESP_LOGD(tag, "Found a match!!"); - return; - } - } // End of examine path handlers. - - // Because we reached here, it means that we did NOT match a handler. Now we want to attempt - // to retrieve the corresponding file content. - std::string filePath = httpResponse.getRootPath(); - filePath += uri; - ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); - FILE *file = fopen(filePath.c_str(), "r"); - if (file != nullptr) { - fseek(file, 0L, SEEK_END); - size_t length = ftell(file); - fseek(file, 0L, SEEK_SET); - uint8_t *pData = (uint8_t *)malloc(length); - fread(pData, length, 1, file); - fclose(file); - httpResponse.sendData(pData, length); - free(pData); - } else { - // Handle unable to open file - httpResponse.setStatus(404); // Not found - httpResponse.sendData(""); - } + ESP_LOGD(tag, "WebServer::processRequest: Matching: %.*s", (int)message->uri.len, message->uri.p); + HTTPResponse httpResponse = HTTPResponse(mgConnection); + httpResponse.setRootPath(getRootPath()); + + /* + * Iterate through each of the path handlers looking for a match with the method and specified path. + */ + std::vector::iterator it; + for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { + if ((*it).match(message->method.p, message->method.len, message->uri.p)) { + HTTPRequest httpRequest(message); + (*it).invoke(&httpRequest, &httpResponse); + ESP_LOGD(tag, "Found a match!!"); + return; + } + } // End of examine path handlers. + + // Because we reached here, it means that we did NOT match a handler. Now we want to attempt + // to retrieve the corresponding file content. + std::string filePath = httpResponse.getRootPath(); + filePath.append(message->uri.p, message->uri.len); + ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); + FILE *file = fopen(filePath.c_str(), "r"); + if (file != nullptr) { + fseek(file, 0L, SEEK_END); + size_t length = ftell(file); + fseek(file, 0L, SEEK_SET); + uint8_t *pData = (uint8_t *)malloc(length); + fread(pData, length, 1, file); + fclose(file); + httpResponse.sendData(pData, length); + free(pData); + } else { + // Handle unable to open file + httpResponse.setStatus(404); // Not found + httpResponse.sendData(""); + } } // processRequest @@ -577,17 +585,13 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m * @param [in] pathPattern The path pattern to be matched. * @param [in] webServerRequestHandler The request handler to be called. */ -WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, - void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, - WebServer::HTTPResponse* pHttpResponse)) { - m_method = method; - m_pattern = std::regex(pathPattern); - m_requestHandler = webServerRequestHandler; +WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { + m_method = method; + m_pattern = std::regex(pathPattern); + m_requestHandler = webServerRequestHandler; } // PathHandler -WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, - void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, - WebServer::HTTPResponse* pHttpResponse)) { +WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { m_method = std::move(method); m_pattern = std::regex(pathPattern); m_requestHandler = webServerRequestHandler; @@ -598,15 +602,17 @@ WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pat * @brief Determine if the path matches. * * @param [in] method The method to be matched. + * @param [in] method_len The method's length * @param [in] path The path to be matched. * @return True if the path matches. */ -bool WebServer::PathHandler::match(const std::string& method, const std::string& path) { - //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); - if (method != m_method) { - return false; - } - return std::regex_search(path, m_pattern); + +bool WebServer::PathHandler::match(const char* method, size_t method_len, const char* path) { + //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); + if (method_len != m_method.length() || strncmp(method, m_method.c_str(), method_len) != 0) { + return false; + } + return std::regex_search(path, m_pattern); } // match @@ -617,7 +623,7 @@ bool WebServer::PathHandler::match(const std::string& method, const std::string& * @return N/A. */ void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer::HTTPResponse *response) { - m_requestHandler(request, response); + m_requestHandler(request, response); } // invoke @@ -628,30 +634,64 @@ void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer:: * @param [in] message The description of the underlying Mongoose message. */ WebServer::HTTPRequest::HTTPRequest(struct http_message* message) { - m_message = message; + m_message = message; } // HTTPRequest /** * @brief Get the body of the request. * When an HTTP request is either PUT or POST then it may contain a payload that is also - * known as the body. This method returns that payload (if it exists). + * known as the body. This method returns that payload (if it exists). Careful, because it's not a standard string + * that is terminated by a null character, use the getBodyLen() function to determine the body length * @return The body of the request. */ -std::string WebServer::HTTPRequest::getBody() const { - return mgStrToString(m_message->body); +const char* WebServer::HTTPRequest::getBody() const { + return m_message->body.p; } // getBody +/** + * @brief Get the length of the body of the request. + * When an HTTP request is either PUT or POST then it may contain a payload that is also + * known as the body. This method returns that payload (if it exists). + * @return The length of the body of the request. + */ +size_t WebServer::HTTPRequest::getBodyLen() const { + return m_message->body.len; +} // getBodyLen + /** * @brief Get the method of the request. * An HTTP request contains a request method which is one of GET, PUT, POST, etc. - * @return The method of the request. + * @return The method of the request. Careful, because it's not a standard string + * that is terminated by a null character, use the getMethodLen() function to determine the method length + * @return The body of the request. */ -std::string WebServer::HTTPRequest::getMethod() const { - return mgStrToString(m_message->method); +const char* WebServer::HTTPRequest::getMethod() const { + return m_message->method.p; } // getMethod +/** + * @brief Get the length of the method of the request. + * An HTTP request contains a request method which is one of GET, PUT, POST, etc. + * @return The length of the method of the request. + */ +size_t WebServer::HTTPRequest::getMethodLen() const { + return m_message->method.len; +} // getMethodLen + + +/** + * @brief Get the path of the request. + * The path of an HTTP request is the portion of the URL that follows the hostname/port pair + * but does not include any query parameters. Careful, because it's not a standard string + * that is terminated by a null character, use the getPathLen() function to determine the path length + * @return The body of the request. + * @return The path of the request. + */ +const char* WebServer::HTTPRequest::getPath() const { + return m_message->uri.p; +} // getPath /** * @brief Get the path of the request. @@ -659,8 +699,8 @@ std::string WebServer::HTTPRequest::getMethod() const { * but does not include any query parameters. * @return The path of the request. */ -std::string WebServer::HTTPRequest::getPath() const { - return mgStrToString(m_message->uri); +size_t WebServer::HTTPRequest::getPathLen() const { + return m_message->uri.len; } // getPath #define STATE_NAME 0 @@ -672,47 +712,48 @@ std::string WebServer::HTTPRequest::getPath() const { * @return The query part of the request. */ std::map WebServer::HTTPRequest::getQuery() const { - // Walk through all the characters in the query string maintaining a simple state machine - // that lets us know what we are parsing. - std::map queryMap; - std::string queryString = mgStrToString(m_message->query_string); - int i=0; - - /* - * We maintain a simple state machine with states of: - * * STATE_NAME - We are parsing a name. - * * STATE_VALUE - We are parsing a value. - */ - int state = STATE_NAME; - std::string name = ""; - std::string value; - // Loop through each character in the query string. - for (i=0; i queryMap; + const char* queryString = m_message->query_string.p; + size_t queryStringLen = m_message->query_string.len; + int i=0; + + /* + * We maintain a simple state machine with states of: + * * STATE_NAME - We are parsing a name. + * * STATE_VALUE - We are parsing a value. + */ + int state = STATE_NAME; + std::string name = ""; + std::string value; + // Loop through each character in the query string. + for (i=0; i WebServer::HTTPRequest::getQuery() const { * @return A vector of the constituent parts of the path. */ std::vector WebServer::HTTPRequest::pathSplit() const { - std::istringstream stream(getPath()); - std::vector ret; - std::string pathPart; - while(std::getline(stream, pathPart, '/')) { - ret.push_back(pathPart); - } - // Debug - for (int i=0; i ret; + std::string pathPart; + while(std::getline(stream, pathPart, '/')) { + ret.push_back(pathPart); + } + // Debug + for (int i=0; i WebServer::HTTPRequest::pathSplit() const { * @return N/A. */ void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::string& fileName) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", - varName.c_str(), fileName.c_str()); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", + varName.c_str(), fileName.c_str()); } // WebServer::HTTPMultiPart::begin @@ -769,7 +810,7 @@ void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::stri * @return N/A. */ void WebServer::HTTPMultiPart::end() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::end()"); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::end()"); } // WebServer::HTTPMultiPart::end @@ -781,7 +822,7 @@ void WebServer::HTTPMultiPart::end() { * @return N/A. */ void WebServer::HTTPMultiPart::data(const std::string& data) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); } // WebServer::HTTPMultiPart::data @@ -790,7 +831,7 @@ void WebServer::HTTPMultiPart::data(const std::string& data) { * @return N/A. */ void WebServer::HTTPMultiPart::multipartEnd() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartEnd()"); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartEnd()"); } // WebServer::HTTPMultiPart::multipartEnd @@ -799,7 +840,7 @@ void WebServer::HTTPMultiPart::multipartEnd() { * @return N/A. */ void WebServer::HTTPMultiPart::multipartStart() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartStart()"); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartStart()"); } // WebServer::HTTPMultiPart::multipartStart @@ -836,10 +877,10 @@ void WebServer::WebSocketHandler::onClosed() { * @return N/A. */ void WebServer::WebSocketHandler::sendData(const std::string& message) { - ESP_LOGD(tag, "WebSocketHandler::sendData(length=%d)", message.length()); - mg_send_websocket_frame(m_mgConnection, - WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, - message.data(), message.length()); + ESP_LOGD(tag, "WebSocketHandler::sendData(length=%d)", message.length()); + mg_send_websocket_frame(m_mgConnection, + WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, + message.data(), message.length()); } // sendData /** @@ -849,9 +890,9 @@ void WebServer::WebSocketHandler::sendData(const std::string& message) { * @return N/A. */ void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { - mg_send_websocket_frame(m_mgConnection, - WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, - data, size); + mg_send_websocket_frame(m_mgConnection, + WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, + data, size); } // sendData @@ -862,7 +903,7 @@ void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { * @return N/A. */ void WebServer::WebSocketHandler::close() { - mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_CLOSE, nullptr, 0); + mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_CLOSE, nullptr, 0); } // close From bdec81c5ea77fd703c4fde9ad98790749926162e Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Mon, 25 Sep 2017 13:35:09 +0300 Subject: [PATCH 060/381] Revert "Fix some bugs i created while removing mgStrToString" This reverts commit e25b26148703ba8799cccd2e147bbfc7da432329. --- cpp_utils/WebServer.cpp | 851 +++++++++++++++++++--------------------- 1 file changed, 405 insertions(+), 446 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 51893ae7..a3ecb1d6 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -18,13 +18,14 @@ #include #include + static char tag[] = "WebServer"; struct WebServerUserData { - WebServer *pWebServer; - WebServer::HTTPMultiPart *pMultiPart; - WebServer::WebSocketHandler *pWebSocketHandler; - void *originalUserData; + WebServer *pWebServer; + WebServer::HTTPMultiPart *pMultiPart; + WebServer::WebSocketHandler *pWebSocketHandler; + void *originalUserData; }; /** @@ -33,89 +34,98 @@ struct WebServerUserData { * @return The string representation of the event. */ static std::string mongoose_eventToString(int event) { - switch (event) { - case MG_EV_CONNECT: - return "MG_EV_CONNECT"; - case MG_EV_ACCEPT: - return "MG_EV_ACCEPT"; - case MG_EV_CLOSE: - return "MG_EV_CLOSE"; - case MG_EV_SEND: - return "MG_EV_SEND"; - case MG_EV_RECV: - return "MG_EV_RECV"; - case MG_EV_POLL: - return "MG_EV_POLL"; - case MG_EV_TIMER: - return "MG_EV_TIMER"; - case MG_EV_HTTP_PART_DATA: - return "MG_EV_HTTP_PART_DATA"; - case MG_EV_HTTP_MULTIPART_REQUEST: - return "MG_EV_HTTP_MULTIPART_REQUEST"; - case MG_EV_HTTP_PART_BEGIN: - return "MG_EV_HTTP_PART_BEGIN"; - case MG_EV_HTTP_PART_END: - return "MG_EV_HTTP_PART_END"; - case MG_EV_HTTP_MULTIPART_REQUEST_END: - return "MG_EV_HTTP_MULTIPART_REQUEST_END"; - case MG_EV_HTTP_REQUEST: - return "MG_EV_HTTP_REQUEST"; - case MG_EV_HTTP_REPLY: - return "MG_EV_HTTP_REPLY"; - case MG_EV_HTTP_CHUNK: - return "MG_EV_HTTP_CHUNK"; - case MG_EV_MQTT_CONNACK: - return "MG_EV_MQTT_CONNACK"; - case MG_EV_MQTT_CONNECT: - return "MG_EV_MQTT_CONNECT"; - case MG_EV_MQTT_DISCONNECT: - return "MG_EV_MQTT_DISCONNECT"; - case MG_EV_MQTT_PINGREQ: - return "MG_EV_MQTT_PINGREQ"; - case MG_EV_MQTT_PINGRESP: - return "MG_EV_MQTT_PINGRESP"; - case MG_EV_MQTT_PUBACK: - return "MG_EV_MQTT_PUBACK"; - case MG_EV_MQTT_PUBCOMP: - return "MG_EV_MQTT_PUBCOMP"; - case MG_EV_MQTT_PUBLISH: - return "MG_EV_MQTT_PUBLISH"; - case MG_EV_MQTT_PUBREC: - return "MG_EV_MQTT_PUBREC"; - case MG_EV_MQTT_PUBREL: - return "MG_EV_MQTT_PUBREL"; - case MG_EV_MQTT_SUBACK: - return "MG_EV_MQTT_SUBACK"; - case MG_EV_MQTT_SUBSCRIBE: - return "MG_EV_MQTT_SUBSCRIBE"; - case MG_EV_MQTT_UNSUBACK: - return "MG_EV_MQTT_UNSUBACK"; - case MG_EV_MQTT_UNSUBSCRIBE: - return "MG_EV_MQTT_UNSUBSCRIBE"; - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: - return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: - return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; - case MG_EV_WEBSOCKET_FRAME: - return "MG_EV_WEBSOCKET_FRAME"; - case MG_EV_WEBSOCKET_CONTROL_FRAME: - return "MG_EV_WEBSOCKET_CONTROL_FRAME"; - } - std::string s; - s += "Unknown event: "; - s += event; - return s; + switch (event) { + case MG_EV_CONNECT: + return "MG_EV_CONNECT"; + case MG_EV_ACCEPT: + return "MG_EV_ACCEPT"; + case MG_EV_CLOSE: + return "MG_EV_CLOSE"; + case MG_EV_SEND: + return "MG_EV_SEND"; + case MG_EV_RECV: + return "MG_EV_RECV"; + case MG_EV_POLL: + return "MG_EV_POLL"; + case MG_EV_TIMER: + return "MG_EV_TIMER"; + case MG_EV_HTTP_PART_DATA: + return "MG_EV_HTTP_PART_DATA"; + case MG_EV_HTTP_MULTIPART_REQUEST: + return "MG_EV_HTTP_MULTIPART_REQUEST"; + case MG_EV_HTTP_PART_BEGIN: + return "MG_EV_HTTP_PART_BEGIN"; + case MG_EV_HTTP_PART_END: + return "MG_EV_HTTP_PART_END"; + case MG_EV_HTTP_MULTIPART_REQUEST_END: + return "MG_EV_HTTP_MULTIPART_REQUEST_END"; + case MG_EV_HTTP_REQUEST: + return "MG_EV_HTTP_REQUEST"; + case MG_EV_HTTP_REPLY: + return "MG_EV_HTTP_REPLY"; + case MG_EV_HTTP_CHUNK: + return "MG_EV_HTTP_CHUNK"; + case MG_EV_MQTT_CONNACK: + return "MG_EV_MQTT_CONNACK"; + case MG_EV_MQTT_CONNECT: + return "MG_EV_MQTT_CONNECT"; + case MG_EV_MQTT_DISCONNECT: + return "MG_EV_MQTT_DISCONNECT"; + case MG_EV_MQTT_PINGREQ: + return "MG_EV_MQTT_PINGREQ"; + case MG_EV_MQTT_PINGRESP: + return "MG_EV_MQTT_PINGRESP"; + case MG_EV_MQTT_PUBACK: + return "MG_EV_MQTT_PUBACK"; + case MG_EV_MQTT_PUBCOMP: + return "MG_EV_MQTT_PUBCOMP"; + case MG_EV_MQTT_PUBLISH: + return "MG_EV_MQTT_PUBLISH"; + case MG_EV_MQTT_PUBREC: + return "MG_EV_MQTT_PUBREC"; + case MG_EV_MQTT_PUBREL: + return "MG_EV_MQTT_PUBREL"; + case MG_EV_MQTT_SUBACK: + return "MG_EV_MQTT_SUBACK"; + case MG_EV_MQTT_SUBSCRIBE: + return "MG_EV_MQTT_SUBSCRIBE"; + case MG_EV_MQTT_UNSUBACK: + return "MG_EV_MQTT_UNSUBACK"; + case MG_EV_MQTT_UNSUBSCRIBE: + return "MG_EV_MQTT_UNSUBSCRIBE"; + case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: + return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: + return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; + case MG_EV_WEBSOCKET_FRAME: + return "MG_EV_WEBSOCKET_FRAME"; + case MG_EV_WEBSOCKET_CONTROL_FRAME: + return "MG_EV_WEBSOCKET_CONTROL_FRAME"; + } + std::ostringstream s; + s << "Unknown event: " << event; + return s.str(); } //eventToString +/** + * @brief Convert a Mongoose string type to a string. + * @param [in] mgStr The Mongoose string. + * @return A std::string representation of the Mongoose string. + */ +static std::string mgStrToString(struct mg_str mgStr) { + return std::string(mgStr.p, mgStr.len); +} // mgStrToStr + static void dumpHttpMessage(struct http_message *pHttpMessage) { - ESP_LOGD(tag, "HTTP Message"); - ESP_LOGD(tag, "Message: %.*s", (int)pHttpMessage->uri.len, pHttpMessage->message.p); + ESP_LOGD(tag, "HTTP Message"); + ESP_LOGD(tag, "Message: %s", mgStrToString(pHttpMessage->message).c_str()); + ESP_LOGD(tag, "URI: %s", mgStrToString(pHttpMessage->uri).c_str()); } /* static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, struct mg_str fname) { - ESP_LOGD(tag, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); - return fname; + ESP_LOGD(tag, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); + return fname; } */ @@ -130,139 +140,139 @@ static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, s * @return N/A. */ static void mongoose_event_handler_web_server( - struct mg_connection *mgConnection, // The network connection associated with the event. - int event, // The type of event. - void *eventData // Data associated with the event. + struct mg_connection *mgConnection, // The network connection associated with the event. + int event, // The type of event. + void *eventData // Data associated with the event. ) { - if (event == MG_EV_POLL) { - return; - } - ESP_LOGD(tag, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); - switch (event) { - case MG_EV_HTTP_REQUEST: { - struct http_message *message = (struct http_message *) eventData; - dumpHttpMessage(message); - - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; - pWebServer->processRequest(mgConnection, message); - break; - } // MG_EV_HTTP_REQUEST - - case MG_EV_HTTP_MULTIPART_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - ESP_LOGD(tag, "User_data address 0x%d", (uint32_t)pWebServerUserData); - WebServer *pWebServer = pWebServerUserData->pWebServer; - if (pWebServer->m_pMultiPartFactory == nullptr) { - return; - } - WebServer::HTTPMultiPart *pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pMultiPart = pMultiPart; - p2->pWebSocketHandler = nullptr; - mgConnection->user_data = p2; - //struct http_message *message = (struct http_message *) eventData; - //dumpHttpMessage(message); - break; - } // MG_EV_HTTP_MULTIPART_REQUEST - - case MG_EV_HTTP_MULTIPART_REQUEST_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pMultiPart != nullptr) { - delete pWebServerUserData->pMultiPart; - pWebServerUserData->pMultiPart = nullptr; - } - mgConnection->user_data = pWebServerUserData->originalUserData; - delete pWebServerUserData; - WebServer::HTTPResponse httpResponse = WebServer::HTTPResponse(mgConnection); - httpResponse.setStatus(200); - httpResponse.sendData(""); - break; - } // MG_EV_HTTP_MULTIPART_REQUEST_END - - case MG_EV_HTTP_PART_BEGIN: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->begin(std::string(part->var_name), std::string(part->file_name)); - } - break; - } // MG_EV_HTTP_PART_BEGIN - - case MG_EV_HTTP_PART_DATA: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->data(std::string(part->data.p, part->data.len)); - } - break; - } // MG_EV_HTTP_PART_DATA - - case MG_EV_HTTP_PART_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->end(); - } - break; - } // MG_EV_HTTP_PART_END - - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; - if (pWebServer->m_pWebSocketHandlerFactory != nullptr) { - if (pWebServerUserData->pWebSocketHandler != nullptr) { - ESP_LOGD(tag, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); - } - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); - mgConnection->user_data = p2; - } else { - ESP_LOGD(tag, "We received a WebSocket request but we have no handler factory!"); - } - break; - } // MG_EV_WEBSOCKET_HANDSHAKE_REQUEST - - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); - return; - } - pWebServerUserData->pWebSocketHandler->onCreated(); - break; - } // MG_EV_WEBSOCKET_HANDSHAKE_DONE - - - /* - * When we receive a MG_EV_WEBSOCKET_FRAME then we have received a chunk of data over the network. - * Our goal will be to send this to the web socket handler (if one exists). - */ - case MG_EV_WEBSOCKET_FRAME: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); - return; - } - struct websocket_message *ws_message = (websocket_message *)eventData; - ESP_LOGD(tag, "Received data length: %d", ws_message->size); - pWebServerUserData->pWebSocketHandler->onMessage(std::string((char *)ws_message->data, ws_message->size)); - break; - } // MG_EV_WEBSOCKET_FRAME - - } // End of switch + if (event == MG_EV_POLL) { + return; + } + ESP_LOGD(tag, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); + switch (event) { + case MG_EV_HTTP_REQUEST: { + struct http_message *message = (struct http_message *) eventData; + dumpHttpMessage(message); + + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + WebServer *pWebServer = pWebServerUserData->pWebServer; + pWebServer->processRequest(mgConnection, message); + break; + } // MG_EV_HTTP_REQUEST + + case MG_EV_HTTP_MULTIPART_REQUEST: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + ESP_LOGD(tag, "User_data address 0x%d", (uint32_t)pWebServerUserData); + WebServer *pWebServer = pWebServerUserData->pWebServer; + if (pWebServer->m_pMultiPartFactory == nullptr) { + return; + } + WebServer::HTTPMultiPart *pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); + struct WebServerUserData *p2 = new WebServerUserData(); + ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pMultiPart = pMultiPart; + p2->pWebSocketHandler = nullptr; + mgConnection->user_data = p2; + //struct http_message *message = (struct http_message *) eventData; + //dumpHttpMessage(message); + break; + } // MG_EV_HTTP_MULTIPART_REQUEST + + case MG_EV_HTTP_MULTIPART_REQUEST_END: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + if (pWebServerUserData->pMultiPart != nullptr) { + delete pWebServerUserData->pMultiPart; + pWebServerUserData->pMultiPart = nullptr; + } + mgConnection->user_data = pWebServerUserData->originalUserData; + delete pWebServerUserData; + WebServer::HTTPResponse httpResponse = WebServer::HTTPResponse(mgConnection); + httpResponse.setStatus(200); + httpResponse.sendData(""); + break; + } // MG_EV_HTTP_MULTIPART_REQUEST_END + + case MG_EV_HTTP_PART_BEGIN: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; + ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->begin(std::string(part->var_name), std::string(part->file_name)); + } + break; + } // MG_EV_HTTP_PART_BEGIN + + case MG_EV_HTTP_PART_DATA: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; + ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->data(mgStrToString(part->data)); + } + break; + } // MG_EV_HTTP_PART_DATA + + case MG_EV_HTTP_PART_END: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; + ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->end(); + } + break; + } // MG_EV_HTTP_PART_END + + case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + WebServer *pWebServer = pWebServerUserData->pWebServer; + if (pWebServer->m_pWebSocketHandlerFactory != nullptr) { + if (pWebServerUserData->pWebSocketHandler != nullptr) { + ESP_LOGD(tag, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); + } + struct WebServerUserData *p2 = new WebServerUserData(); + ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); + mgConnection->user_data = p2; + } else { + ESP_LOGD(tag, "We received a WebSocket request but we have no handler factory!"); + } + break; + } // MG_EV_WEBSOCKET_HANDSHAKE_REQUEST + + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + if (pWebServerUserData->pWebSocketHandler == nullptr) { + ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + return; + } + pWebServerUserData->pWebSocketHandler->onCreated(); + break; + } // MG_EV_WEBSOCKET_HANDSHAKE_DONE + + + /* + * When we receive a MG_EV_WEBSOCKET_FRAME then we have received a chunk of data over the network. + * Our goal will be to send this to the web socket handler (if one exists). + */ + case MG_EV_WEBSOCKET_FRAME: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + if (pWebServerUserData->pWebSocketHandler == nullptr) { + ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + return; + } + struct websocket_message *ws_message = (websocket_message *)eventData; + ESP_LOGD(tag, "Received data length: %d", ws_message->size); + pWebServerUserData->pWebSocketHandler->onMessage(std::string((char *)ws_message->data, ws_message->size)); + break; + } // MG_EV_WEBSOCKET_FRAME + + } // End of switch } // End of mongoose_event_handler @@ -270,9 +280,9 @@ static void mongoose_event_handler_web_server( * @brief Constructor. */ WebServer::WebServer() { - m_rootPath = ""; - m_pMultiPartFactory = nullptr; - m_pWebSocketHandlerFactory = nullptr; + m_rootPath = ""; + m_pMultiPartFactory = nullptr; + m_pWebSocketHandlerFactory = nullptr; } // WebServer @@ -285,7 +295,7 @@ WebServer::~WebServer() { * @return The current root path. */ const std::string& WebServer::getRootPath() { - return m_rootPath; + return m_rootPath; } // getRootPath @@ -312,7 +322,7 @@ const std::string& WebServer::getRootPath() { void WebServer::addPathHandler(const std::string& method, const std::string& pathExpr, void (* handler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { - m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); + m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); } // addPathHandler void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr, @@ -330,31 +340,31 @@ void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr * @return N/A. */ void WebServer::start(uint16_t port) { - ESP_LOGD(tag, "WebServer task starting"); - struct mg_mgr mgr; - mg_mgr_init(&mgr, NULL); - - std::stringstream stringStream; - stringStream << ':' << port; - struct mg_connection *mgConnection = mg_bind(&mgr, stringStream.str().c_str(), mongoose_event_handler_web_server); - - if (mgConnection == NULL) { - ESP_LOGE(tag, "No connection from the mg_bind()"); - vTaskDelete(NULL); - return; - } - - struct WebServerUserData *pWebServerUserData = new WebServerUserData(); - pWebServerUserData->pWebServer = this; - pWebServerUserData->pMultiPart = nullptr; - mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. - ESP_LOGD(tag, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); - mg_set_protocol_http_websocket(mgConnection); - - ESP_LOGD(tag, "WebServer listening on port %d", port); - while (1) { - mg_mgr_poll(&mgr, 2000); - } + ESP_LOGD(tag, "WebServer task starting"); + struct mg_mgr mgr; + mg_mgr_init(&mgr, NULL); + + std::stringstream stringStream; + stringStream << ':' << port; + struct mg_connection *mgConnection = mg_bind(&mgr, stringStream.str().c_str(), mongoose_event_handler_web_server); + + if (mgConnection == NULL) { + ESP_LOGE(tag, "No connection from the mg_bind()"); + vTaskDelete(NULL); + return; + } + + struct WebServerUserData *pWebServerUserData = new WebServerUserData(); + pWebServerUserData->pWebServer = this; + pWebServerUserData->pMultiPart = nullptr; + mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. + ESP_LOGD(tag, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); + mg_set_protocol_http_websocket(mgConnection); + + ESP_LOGD(tag, "WebServer listening on port %d", port); + while (1) { + mg_mgr_poll(&mgr, 2000); + } } // run @@ -363,7 +373,7 @@ void WebServer::start(uint16_t port) { * @param [in] pMultiPart A pointer to the multi part factory. */ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { - m_pMultiPartFactory = pMultiPartFactory; + m_pMultiPartFactory = pMultiPartFactory; } @@ -385,11 +395,7 @@ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { * @return N/A. */ void WebServer::setRootPath(const std::string& path) { - m_rootPath = path; -} // setRootPath - -void WebServer::setRootPath(std::string&& path) { - m_rootPath = std::move(path); + m_rootPath = path; } // setRootPath @@ -400,7 +406,7 @@ void WebServer::setRootPath(std::string&& path) { * @return N/A. */ void WebServer::setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHandlerFactory) { - m_pWebSocketHandlerFactory = pWebSocketHandlerFactory; + m_pWebSocketHandlerFactory = pWebSocketHandlerFactory; } // setWebSocketHandlerFactory @@ -409,9 +415,9 @@ void WebServer::setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHa * @param [in] nc The network connection for the response. */ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { - m_nc = nc; - m_status = 200; - m_dataSent = false; + m_nc = nc; + m_status = 200; + m_dataSent = false; } // HTTPResponse @@ -421,7 +427,7 @@ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { * @param [in] value The value of the header. */ void WebServer::HTTPResponse::addHeader(const std::string& name, const std::string& value) { - m_headers[name] = value; + m_headers[name] = value; } // addHeader void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) { @@ -435,7 +441,7 @@ void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) * @return N/A. */ void WebServer::HTTPResponse::sendData(const std::string& data) { - sendData((uint8_t *)data.data(), data.length()); + sendData((uint8_t *)data.data(), data.length()); } // sendData @@ -447,35 +453,24 @@ void WebServer::HTTPResponse::sendData(const std::string& data) { * @return N/A. */ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { - if (m_dataSent) { - ESP_LOGE(tag, "HTTPResponse: Data already sent! Attempt to send again/more."); - return; - } - m_dataSent = true; + if (m_dataSent) { + ESP_LOGE(tag, "HTTPResponse: Data already sent! Attempt to send again/more."); + return; + } + m_dataSent = true; - std::string headers; - unsigned long headers_len = 0; + std::string headers; - for(auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { - if(iter != m_headers.begin()) - headers_len += 2; - headers_len += iter->first.length(); - headers_len += 2; - headers_len += iter->second.length(); - } - headers_len += 1; - headers.resize(headers_len); // Will not have to resize and recopy during the next loop, we have 2 loops but it still ends up being faster - - for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { if(iter != m_headers.begin()) headers += "\r\n"; headers += iter->first; headers += ": "; headers += iter->second; - } - mg_send_head(m_nc, m_status, length, headers.c_str()); - mg_send(m_nc, pData, length); - m_nc->flags |= MG_F_SEND_AND_CLOSE; + } + mg_send_head(m_nc, m_status, length, headers.c_str()); + mg_send(m_nc, pData, length); + m_nc->flags |= MG_F_SEND_AND_CLOSE; } // sendData @@ -485,7 +480,7 @@ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { * @return N/A. */ void WebServer::HTTPResponse::setHeaders(const std::map& headers) { - m_headers = headers; + m_headers = headers; } // setHeaders void WebServer::HTTPResponse::setHeaders(std::map&& headers) { @@ -497,8 +492,8 @@ void WebServer::HTTPResponse::setHeaders(std::map&& he * @brief Get the current root path. * @return The current root path. */ -const std::string& WebServer::HTTPResponse::getRootPath() const { - return m_rootPath; +std::string WebServer::HTTPResponse::getRootPath() { + return m_rootPath; } // getRootPath @@ -508,11 +503,7 @@ const std::string& WebServer::HTTPResponse::getRootPath() const { * @return N/A. */ void WebServer::HTTPResponse::setRootPath(const std::string& path) { - m_rootPath = path; -} // setRootPath - -void WebServer::HTTPResponse::setRootPath(std::string&& path) { - m_rootPath = std::move(path); + m_rootPath = path; } // setRootPath /** @@ -523,7 +514,7 @@ void WebServer::HTTPResponse::setRootPath(std::string&& path) { * @return N/A. */ void WebServer::HTTPResponse::setStatus(int status) { - m_status = status; + m_status = status; } // setStatus @@ -538,43 +529,44 @@ void WebServer::HTTPResponse::setStatus(int status) { * @param [in] message The message representing the request. */ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_message* message) { - ESP_LOGD(tag, "WebServer::processRequest: Matching: %.*s", (int)message->uri.len, message->uri.p); - HTTPResponse httpResponse = HTTPResponse(mgConnection); - httpResponse.setRootPath(getRootPath()); - - /* - * Iterate through each of the path handlers looking for a match with the method and specified path. - */ - std::vector::iterator it; - for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { - if ((*it).match(message->method.p, message->method.len, message->uri.p)) { - HTTPRequest httpRequest(message); - (*it).invoke(&httpRequest, &httpResponse); - ESP_LOGD(tag, "Found a match!!"); - return; - } - } // End of examine path handlers. - - // Because we reached here, it means that we did NOT match a handler. Now we want to attempt - // to retrieve the corresponding file content. - std::string filePath = httpResponse.getRootPath(); - filePath.append(message->uri.p, message->uri.len); - ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); - FILE *file = fopen(filePath.c_str(), "r"); - if (file != nullptr) { - fseek(file, 0L, SEEK_END); - size_t length = ftell(file); - fseek(file, 0L, SEEK_SET); - uint8_t *pData = (uint8_t *)malloc(length); - fread(pData, length, 1, file); - fclose(file); - httpResponse.sendData(pData, length); - free(pData); - } else { - // Handle unable to open file - httpResponse.setStatus(404); // Not found - httpResponse.sendData(""); - } + std::string uri = mgStrToString(message->uri); + ESP_LOGD(tag, "WebServer::processRequest: Matching: %s", uri.c_str()); + HTTPResponse httpResponse = HTTPResponse(mgConnection); + httpResponse.setRootPath(getRootPath()); + + /* + * Iterate through each of the path handlers looking for a match with the method and specified path. + */ + std::vector::iterator it; + for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { + if ((*it).match(mgStrToString(message->method), uri)) { + HTTPRequest httpRequest(message); + (*it).invoke(&httpRequest, &httpResponse); + ESP_LOGD(tag, "Found a match!!"); + return; + } + } // End of examine path handlers. + + // Because we reached here, it means that we did NOT match a handler. Now we want to attempt + // to retrieve the corresponding file content. + std::string filePath = httpResponse.getRootPath(); + filePath += uri; + ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); + FILE *file = fopen(filePath.c_str(), "r"); + if (file != nullptr) { + fseek(file, 0L, SEEK_END); + size_t length = ftell(file); + fseek(file, 0L, SEEK_SET); + uint8_t *pData = (uint8_t *)malloc(length); + fread(pData, length, 1, file); + fclose(file); + httpResponse.sendData(pData, length); + free(pData); + } else { + // Handle unable to open file + httpResponse.setStatus(404); // Not found + httpResponse.sendData(""); + } } // processRequest @@ -585,13 +577,17 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m * @param [in] pathPattern The path pattern to be matched. * @param [in] webServerRequestHandler The request handler to be called. */ -WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { - m_method = method; - m_pattern = std::regex(pathPattern); - m_requestHandler = webServerRequestHandler; +WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, + void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, + WebServer::HTTPResponse* pHttpResponse)) { + m_method = method; + m_pattern = std::regex(pathPattern); + m_requestHandler = webServerRequestHandler; } // PathHandler -WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { +WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, + void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, + WebServer::HTTPResponse* pHttpResponse)) { m_method = std::move(method); m_pattern = std::regex(pathPattern); m_requestHandler = webServerRequestHandler; @@ -602,17 +598,15 @@ WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pat * @brief Determine if the path matches. * * @param [in] method The method to be matched. - * @param [in] method_len The method's length * @param [in] path The path to be matched. * @return True if the path matches. */ - -bool WebServer::PathHandler::match(const char* method, size_t method_len, const char* path) { - //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); - if (method_len != m_method.length() || strncmp(method, m_method.c_str(), method_len) != 0) { - return false; - } - return std::regex_search(path, m_pattern); +bool WebServer::PathHandler::match(const std::string& method, const std::string& path) { + //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); + if (method != m_method) { + return false; + } + return std::regex_search(path, m_pattern); } // match @@ -623,7 +617,7 @@ bool WebServer::PathHandler::match(const char* method, size_t method_len, const * @return N/A. */ void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer::HTTPResponse *response) { - m_requestHandler(request, response); + m_requestHandler(request, response); } // invoke @@ -634,64 +628,30 @@ void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer:: * @param [in] message The description of the underlying Mongoose message. */ WebServer::HTTPRequest::HTTPRequest(struct http_message* message) { - m_message = message; + m_message = message; } // HTTPRequest /** * @brief Get the body of the request. * When an HTTP request is either PUT or POST then it may contain a payload that is also - * known as the body. This method returns that payload (if it exists). Careful, because it's not a standard string - * that is terminated by a null character, use the getBodyLen() function to determine the body length + * known as the body. This method returns that payload (if it exists). * @return The body of the request. */ -const char* WebServer::HTTPRequest::getBody() const { - return m_message->body.p; +std::string WebServer::HTTPRequest::getBody() const { + return mgStrToString(m_message->body); } // getBody -/** - * @brief Get the length of the body of the request. - * When an HTTP request is either PUT or POST then it may contain a payload that is also - * known as the body. This method returns that payload (if it exists). - * @return The length of the body of the request. - */ -size_t WebServer::HTTPRequest::getBodyLen() const { - return m_message->body.len; -} // getBodyLen - /** * @brief Get the method of the request. * An HTTP request contains a request method which is one of GET, PUT, POST, etc. - * @return The method of the request. Careful, because it's not a standard string - * that is terminated by a null character, use the getMethodLen() function to determine the method length - * @return The body of the request. + * @return The method of the request. */ -const char* WebServer::HTTPRequest::getMethod() const { - return m_message->method.p; +std::string WebServer::HTTPRequest::getMethod() const { + return mgStrToString(m_message->method); } // getMethod -/** - * @brief Get the length of the method of the request. - * An HTTP request contains a request method which is one of GET, PUT, POST, etc. - * @return The length of the method of the request. - */ -size_t WebServer::HTTPRequest::getMethodLen() const { - return m_message->method.len; -} // getMethodLen - - -/** - * @brief Get the path of the request. - * The path of an HTTP request is the portion of the URL that follows the hostname/port pair - * but does not include any query parameters. Careful, because it's not a standard string - * that is terminated by a null character, use the getPathLen() function to determine the path length - * @return The body of the request. - * @return The path of the request. - */ -const char* WebServer::HTTPRequest::getPath() const { - return m_message->uri.p; -} // getPath /** * @brief Get the path of the request. @@ -699,8 +659,8 @@ const char* WebServer::HTTPRequest::getPath() const { * but does not include any query parameters. * @return The path of the request. */ -size_t WebServer::HTTPRequest::getPathLen() const { - return m_message->uri.len; +std::string WebServer::HTTPRequest::getPath() const { + return mgStrToString(m_message->uri); } // getPath #define STATE_NAME 0 @@ -712,48 +672,47 @@ size_t WebServer::HTTPRequest::getPathLen() const { * @return The query part of the request. */ std::map WebServer::HTTPRequest::getQuery() const { - // Walk through all the characters in the query string maintaining a simple state machine - // that lets us know what we are parsing. - std::map queryMap; - const char* queryString = m_message->query_string.p; - size_t queryStringLen = m_message->query_string.len; - int i=0; - - /* - * We maintain a simple state machine with states of: - * * STATE_NAME - We are parsing a name. - * * STATE_VALUE - We are parsing a value. - */ - int state = STATE_NAME; - std::string name = ""; - std::string value; - // Loop through each character in the query string. - for (i=0; i queryMap; + std::string queryString = mgStrToString(m_message->query_string); + int i=0; + + /* + * We maintain a simple state machine with states of: + * * STATE_NAME - We are parsing a name. + * * STATE_VALUE - We are parsing a value. + */ + int state = STATE_NAME; + std::string name = ""; + std::string value; + // Loop through each character in the query string. + for (i=0; i WebServer::HTTPRequest::getQuery() const { * @return A vector of the constituent parts of the path. */ std::vector WebServer::HTTPRequest::pathSplit() const { - std::istringstream stream(std::string(getPath(), getPathLen())); // I don't know if there's a better istringstream constructor for this - std::vector ret; - std::string pathPart; - while(std::getline(stream, pathPart, '/')) { - ret.push_back(pathPart); - } - // Debug - for (int i=0; i ret; + std::string pathPart; + while(std::getline(stream, pathPart, '/')) { + ret.push_back(pathPart); + } + // Debug + for (int i=0; i WebServer::HTTPRequest::pathSplit() const { * @return N/A. */ void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::string& fileName) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", - varName.c_str(), fileName.c_str()); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", + varName.c_str(), fileName.c_str()); } // WebServer::HTTPMultiPart::begin @@ -810,7 +769,7 @@ void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::stri * @return N/A. */ void WebServer::HTTPMultiPart::end() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::end()"); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::end()"); } // WebServer::HTTPMultiPart::end @@ -822,7 +781,7 @@ void WebServer::HTTPMultiPart::end() { * @return N/A. */ void WebServer::HTTPMultiPart::data(const std::string& data) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); } // WebServer::HTTPMultiPart::data @@ -831,7 +790,7 @@ void WebServer::HTTPMultiPart::data(const std::string& data) { * @return N/A. */ void WebServer::HTTPMultiPart::multipartEnd() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartEnd()"); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartEnd()"); } // WebServer::HTTPMultiPart::multipartEnd @@ -840,7 +799,7 @@ void WebServer::HTTPMultiPart::multipartEnd() { * @return N/A. */ void WebServer::HTTPMultiPart::multipartStart() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartStart()"); + ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartStart()"); } // WebServer::HTTPMultiPart::multipartStart @@ -877,10 +836,10 @@ void WebServer::WebSocketHandler::onClosed() { * @return N/A. */ void WebServer::WebSocketHandler::sendData(const std::string& message) { - ESP_LOGD(tag, "WebSocketHandler::sendData(length=%d)", message.length()); - mg_send_websocket_frame(m_mgConnection, - WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, - message.data(), message.length()); + ESP_LOGD(tag, "WebSocketHandler::sendData(length=%d)", message.length()); + mg_send_websocket_frame(m_mgConnection, + WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, + message.data(), message.length()); } // sendData /** @@ -890,9 +849,9 @@ void WebServer::WebSocketHandler::sendData(const std::string& message) { * @return N/A. */ void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { - mg_send_websocket_frame(m_mgConnection, - WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, - data, size); + mg_send_websocket_frame(m_mgConnection, + WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, + data, size); } // sendData @@ -903,7 +862,7 @@ void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { * @return N/A. */ void WebServer::WebSocketHandler::close() { - mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_CLOSE, nullptr, 0); + mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_CLOSE, nullptr, 0); } // close From 32111fbba5d7577898552bad5f9ed6e1679dc4f0 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Mon, 25 Sep 2017 13:37:57 +0300 Subject: [PATCH 061/381] Fix some bugs i created while removing mgStrToString Everything works now --- cpp_utils/WebServer.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index dcbe674a..51893ae7 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -109,8 +109,7 @@ static std::string mongoose_eventToString(int event) { static void dumpHttpMessage(struct http_message *pHttpMessage) { ESP_LOGD(tag, "HTTP Message"); - ESP_LOGD(tag, "Message: %s", pHttpMessage->message.p); - ESP_LOGD(tag, "URI: %s", pHttpMessage->uri.p); + ESP_LOGD(tag, "Message: %.*s", (int)pHttpMessage->uri.len, pHttpMessage->message.p); } /* @@ -539,7 +538,7 @@ void WebServer::HTTPResponse::setStatus(int status) { * @param [in] message The message representing the request. */ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_message* message) { - ESP_LOGD(tag, "WebServer::processRequest: Matching: %s", message->uri.p); + ESP_LOGD(tag, "WebServer::processRequest: Matching: %.*s", (int)message->uri.len, message->uri.p); HTTPResponse httpResponse = HTTPResponse(mgConnection); httpResponse.setRootPath(getRootPath()); @@ -559,7 +558,7 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m // Because we reached here, it means that we did NOT match a handler. Now we want to attempt // to retrieve the corresponding file content. std::string filePath = httpResponse.getRootPath(); - filePath += message->uri.p; + filePath.append(message->uri.p, message->uri.len); ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); FILE *file = fopen(filePath.c_str(), "r"); if (file != nullptr) { @@ -586,17 +585,13 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m * @param [in] pathPattern The path pattern to be matched. * @param [in] webServerRequestHandler The request handler to be called. */ -WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, - void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, - WebServer::HTTPResponse* pHttpResponse)) { +WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { m_method = method; m_pattern = std::regex(pathPattern); m_requestHandler = webServerRequestHandler; } // PathHandler -WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, - void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, - WebServer::HTTPResponse* pHttpResponse)) { +WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { m_method = std::move(method); m_pattern = std::regex(pathPattern); m_requestHandler = webServerRequestHandler; @@ -614,7 +609,7 @@ WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pat bool WebServer::PathHandler::match(const char* method, size_t method_len, const char* path) { //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); - if (method_len != m_method.length() || strcmp(method, m_method.c_str()) != 0) { + if (method_len != m_method.length() || strncmp(method, m_method.c_str(), method_len) != 0) { return false; } return std::regex_search(path, m_pattern); @@ -646,7 +641,8 @@ WebServer::HTTPRequest::HTTPRequest(struct http_message* message) { /** * @brief Get the body of the request. * When an HTTP request is either PUT or POST then it may contain a payload that is also - * known as the body. This method returns that payload (if it exists). + * known as the body. This method returns that payload (if it exists). Careful, because it's not a standard string + * that is terminated by a null character, use the getBodyLen() function to determine the body length * @return The body of the request. */ const char* WebServer::HTTPRequest::getBody() const { @@ -667,7 +663,9 @@ size_t WebServer::HTTPRequest::getBodyLen() const { /** * @brief Get the method of the request. * An HTTP request contains a request method which is one of GET, PUT, POST, etc. - * @return The method of the request. + * @return The method of the request. Careful, because it's not a standard string + * that is terminated by a null character, use the getMethodLen() function to determine the method length + * @return The body of the request. */ const char* WebServer::HTTPRequest::getMethod() const { return m_message->method.p; @@ -686,7 +684,9 @@ size_t WebServer::HTTPRequest::getMethodLen() const { /** * @brief Get the path of the request. * The path of an HTTP request is the portion of the URL that follows the hostname/port pair - * but does not include any query parameters. + * but does not include any query parameters. Careful, because it's not a standard string + * that is terminated by a null character, use the getPathLen() function to determine the path length + * @return The body of the request. * @return The path of the request. */ const char* WebServer::HTTPRequest::getPath() const { @@ -777,7 +777,7 @@ std::map WebServer::HTTPRequest::getQuery() const { * @return A vector of the constituent parts of the path. */ std::vector WebServer::HTTPRequest::pathSplit() const { - std::istringstream stream(getPath()); + std::istringstream stream(std::string(getPath(), getPathLen())); // I don't know if there's a better istringstream constructor for this std::vector ret; std::string pathPart; while(std::getline(stream, pathPart, '/')) { From c544fbf721d6acaca37eeaa79d82c111126bd8a4 Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 25 Sep 2017 23:08:17 -0500 Subject: [PATCH 062/381] Code changes for #83 --- cpp_utils/BLEClient.cpp | 52 +------ cpp_utils/BLEClient.h | 4 +- cpp_utils/BLERemoteCharacteristic.cpp | 189 +++++++++++++++++++++++++- cpp_utils/BLERemoteCharacteristic.h | 10 +- cpp_utils/BLERemoteDescriptor.cpp | 107 ++++++++++++++- cpp_utils/BLERemoteDescriptor.h | 17 ++- cpp_utils/BLERemoteService.cpp | 47 ++++--- cpp_utils/BLERemoteService.h | 6 +- 8 files changed, 343 insertions(+), 89 deletions(-) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 5a0b70e0..3b2ca2c9 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -110,45 +110,6 @@ void BLEClient::gattClientEventHandler( // Execute handler code based on the type of event received. switch(event) { - // - // ESP_GATTC_NOTIFY_EVT - // - // notify - // - uint16_t conn_id - // - esp_bd_addr_t remote_bda - // - uint16_t handle - // - uint16_t value_len - // - uint8_t* value - // - bool is_notify - // - // We have received a notification event which means that the server wishes us to know about a notification - // piece of data. What we must now do is find the characteristic with the associated handle and then - // invoke its notification callback (if it has one). - // - case ESP_GATTC_NOTIFY_EVT: { - BLERemoteService* pBLERemoteService = getService(evtParam->notify.handle); - if (pBLERemoteService == nullptr) { - ESP_LOGE(LOG_TAG, "Could not find service containing handle %d 0x%.2x for notification", - evtParam->notify.handle, evtParam->notify.handle); - break; - } - BLERemoteCharacteristic* pBLERemoteCharacteristic = pBLERemoteService->getCharacteristic(evtParam->notify.handle); - if (pBLERemoteCharacteristic == nullptr) { - ESP_LOGE(LOG_TAG, "Could not find characteristic with handle %d 0x%.2x for notification", - evtParam->notify.handle, evtParam->notify.handle); - break; - } - if (pBLERemoteCharacteristic->m_notifyCallback != nullptr) { - pBLERemoteCharacteristic->m_notifyCallback( - pBLERemoteCharacteristic, - evtParam->notify.value, - evtParam->notify.value_len, - evtParam->notify.is_notify - ); - } // End we have a callback function ... - break; - } // ESP_GATTC_NOTIFY_EVT - // // ESP_GATTC_OPEN_EVT // @@ -222,6 +183,7 @@ void BLEClient::gattClientEventHandler( } } // Switch + // Pass the request on to all services. for (auto &myPair : m_servicesMap) { myPair.second->gattClientEventHandler(event, gattc_if, evtParam); } @@ -232,7 +194,7 @@ void BLEClient::gattClientEventHandler( /** * @brief Retrieve the address of the peer. * - * Returns the address of the %BLE peer to which this client is connected. + * Returns the Bluetooth device address of the %BLE peer to which this client is connected. */ BLEAddress BLEClient::getPeerAddress() { return m_peerAddress; @@ -248,21 +210,15 @@ esp_gatt_if_t BLEClient::getGattcIf() { return m_gattc_if; } // getGattcIf -BLERemoteService* BLEClient::getService(uint16_t handle) { - ESP_LOGD(LOG_TAG, ">> getService: handle: %d", handle); - ESP_LOGE(LOG_TAG, "!!! NOT IMPLEMENTED !!!"); - ESP_LOGD(LOG_TAG, "<< getService"); - return nullptr; -} /** - * @brief Get the service object corresponding to the uuid. + * @brief Get the service BLE Remote Service instance corresponding to the uuid. * @param [in] uuid The UUID of the service being sought. * @return A reference to the Service or nullptr if don't know about it. */ BLERemoteService* BLEClient::getService(const char* uuid) { return getService(BLEUUID(uuid)); -} +} // getService /** diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 65f69986..a3f51bcc 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -34,14 +34,14 @@ class BLEClient { std::map* getServices(); BLERemoteService* getService(const char* uuid); BLERemoteService* getService(BLEUUID uuid); - BLERemoteService* getService(uint16_t handle); void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); std::string toString(); private: friend class BLEDevice; - friend class BLERemoteCharacteristic; friend class BLERemoteService; + friend class BLERemoteCharacteristic; + friend class BLERemoteDescriptor; void gattClientEventHandler( esp_gattc_cb_event_t event, diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 372723ed..e96fc9dd 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -17,22 +17,43 @@ #include #include "BLEUtils.h" #include "GeneralUtils.h" +#include "BLERemoteDescriptor.h" -static const char* LOG_TAG = "BLERemoteCharacteristic"; +static const char* LOG_TAG = "BLERemoteCharacteristic"; // The logging tag for this class. +/** + * @brief Constructor. + * @param [in] handle The BLE server side handle of this characteristic. + * @param [in] uuid The UUID of this characteristic. + * @param [in] charProp The properties of this characteristic. + * @param [in] pRemoteService A reference to the remote service to which this remote characteristic pertains. + */ BLERemoteCharacteristic::BLERemoteCharacteristic( uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService) { + ESP_LOGD(LOG_TAG, ">> BLERemoteCharacteristic: handle: %d 0x%d, uuid: %s", handle, handle, uuid.toString().c_str()); m_handle = handle; m_uuid = uuid; m_charProp = charProp; m_pRemoteService = pRemoteService; m_notifyCallback = nullptr; + + getDescriptors(); // Get the descriptors for this characteristic + ESP_LOGD(LOG_TAG, "<< BLERemoteCharacteristic"); } // BLERemoteCharacteristic + +/** + *@brief Destructor. + */ +BLERemoteCharacteristic::~BLERemoteCharacteristic() { + removeDescriptors(); // Release resources for any descriptor information we may have allocated. +} // ~BLERemoteCharacteristic + + /* static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { if (id1.id.inst_id != id2.id.inst_id) { @@ -72,6 +93,36 @@ void BLERemoteCharacteristic::gattClientEventHandler( esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { switch(event) { + // + // ESP_GATTC_NOTIFY_EVT + // + // notify + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + // - uint16_t handle + // - uint16_t value_len + // - uint8_t* value + // - bool is_notify + // + // We have received a notification event which means that the server wishes us to know about a notification + // piece of data. What we must now do is find the characteristic with the associated handle and then + // invoke its notification callback (if it has one). + // + case ESP_GATTC_NOTIFY_EVT: { + if (evtParam->notify.handle != getHandle()) { + break; + } + if (m_notifyCallback != nullptr) { + m_notifyCallback( + this, + evtParam->notify.value, + evtParam->notify.value_len, + evtParam->notify.is_notify + ); + } // End we have a callback function ... + break; + } // ESP_GATTC_NOTIFY_EVT + // // ESP_GATTC_READ_CHAR_EVT // This event indicates that the server has responded to the read request. @@ -148,16 +199,127 @@ void BLERemoteCharacteristic::gattClientEventHandler( } // End switch }; // gattClientEventHandler + +/** + * @brief Populate the descriptors (if any) for this characteristic. + */ +void BLERemoteCharacteristic::getDescriptors() { + ESP_LOGD(LOG_TAG, ">> getDescriptors() for characteristic: %s", getUUID().toString().c_str()); + + removeDescriptors(); // Remove any existing descriptors. + + /* + uint16_t count; + esp_gatt_status_t status = ::esp_ble_gattc_get_attr_count( + getRemoteService()->getClient()->getGattcIf(), + getRemoteService()->getClient()->getConnId(), + ESP_GATT_DB_DESCRIPTOR, + getRemoteService()->getStartHandle(), + getRemoteService()->getEndHandle(), + getHandle(), // Characteristic handle ... only used for ESP_GATT_DB_DESCRIPTOR + &count + ); + if (status != ESP_GATT_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_attr_count: %s", BLEUtils::gattStatusToString(status).c_str()); + } else { + ESP_LOGD(LOG_TAG, "Number of descriptors associated with characteristic is %d", count); + } + */ + + + // Loop over each of the descriptors within the service associated with this characteristic. + // For each descriptor we find, create a BLERemoteDescriptor instance. + uint16_t offset = 0; + esp_gattc_descr_elem_t result; + while(1) { + uint16_t count = 1; + esp_gatt_status_t status = ::esp_ble_gattc_get_all_descr( + getRemoteService()->getClient()->getGattcIf(), + getRemoteService()->getClient()->getConnId(), + getHandle(), + &result, + &count, + offset + ); + + if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. + break; + } + + if (status != ESP_GATT_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_all_descr: %s", BLEUtils::gattStatusToString(status).c_str()); + break; + } + + if (count == 0) { + break; + } + ESP_LOGD(LOG_TAG, "Found a descriptor: Handle: %d, UUID: %s", result.handle, BLEUUID(result.uuid).toString().c_str()); + + // We now have a new characteristic ... let us add that to our set of known characteristics + BLERemoteDescriptor *pNewRemoteDescriptor = new BLERemoteDescriptor( + result.handle, + BLEUUID(result.uuid), + this + ); + + m_descriptorMap.insert(std::pair(pNewRemoteDescriptor->getUUID().toString(), pNewRemoteDescriptor)); + + offset++; + } // while true + //m_haveCharacteristics = true; // Remember that we have received the characteristics. + ESP_LOGD(LOG_TAG, "<< getDescriptors(): Found %d descriptors.", offset); +} // getDescriptors + + + +/** + * @brief Get the handle for this characteristic. + * @return The handle for this characteristic. + */ uint16_t BLERemoteCharacteristic::getHandle() { - ESP_LOGD(LOG_TAG, ">> getHandle: Characteristic: %s", getUUID().toString().c_str()); - ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", m_handle, m_handle); + //ESP_LOGD(LOG_TAG, ">> getHandle: Characteristic: %s", getUUID().toString().c_str()); + //ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", m_handle, m_handle); return m_handle; -} +} // getHandle +/** + * @brief Get the descriptor instance with the given UUID that belongs to this characteristic. + * @param [in] uuid The UUID of the descriptor to find. + * @return The Remote descriptor (if present) or null if not present. + */ +BLERemoteDescriptor* BLERemoteCharacteristic::getDescriptor(BLEUUID uuid) { + ESP_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str()); + std::string v = uuid.toString(); + for (auto &myPair : m_descriptorMap) { + if (myPair.first == v) { + ESP_LOGD(LOG_TAG, "<< getDescriptor: found"); + return myPair.second; + } + } + ESP_LOGD(LOG_TAG, "<< getDescriptor: Not found"); + return nullptr; +} // getDescriptor + + +/** + * @brief Get the remote service associated with this characteristic. + * @return The remote service associated with this characteristic. + */ +BLERemoteService *BLERemoteCharacteristic::getRemoteService() { + return m_pRemoteService; +} // getRemoteService + + +/** + * @brief Get the UUID for this characteristic. + * @return The UUID for this characteristic. + */ BLEUUID BLERemoteCharacteristic::getUUID() { return m_uuid; -} +} // getUUID + /** * @brief Read an unsigned 16 bit value @@ -240,7 +402,7 @@ void BLERemoteCharacteristic::registerForNotify( uint8_t* pData, size_t length, bool isNotify)) { - ESP_LOGD(LOG_TAG, ">> registerForNotify()"); + ESP_LOGD(LOG_TAG, ">> registerForNotify(): %s", toString().c_str()); m_notifyCallback = notifyCallback; // Save the notification callback. @@ -275,6 +437,21 @@ void BLERemoteCharacteristic::registerForNotify( } // registerForNotify +/** + * @brief Delete the descriptors in the descriptor map. + * We maintain a map called m_descriptorMap that contains pointers to BLERemoteDescriptors + * object references. Since we allocated these in this class, we are also responsible for deleteing + * them. This method does just that. + * @return N/A. + */ +void BLERemoteCharacteristic::removeDescriptors() { + for (auto &myPair : m_descriptorMap) { + delete myPair.second; + } + m_descriptorMap.empty(); +} // removeCharacteristics + + /** * @brief Convert a BLERemoteCharacteristic to a string representation; * @return a String representation. diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 26eccffa..5af55a69 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -15,6 +15,7 @@ #include #include "BLERemoteService.h" +#include "BLERemoteDescriptor.h" #include "BLEUUID.h" #include "FreeRTOS.h" @@ -26,9 +27,10 @@ class BLERemoteDescriptor; */ class BLERemoteCharacteristic { public: - + ~BLERemoteCharacteristic(); // Public member functions + BLERemoteDescriptor *getDescriptor(BLEUUID uuid); BLEUUID getUUID(); std::string readValue(void); uint8_t readUInt8(void); @@ -44,14 +46,18 @@ class BLERemoteCharacteristic { BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); friend class BLEClient; friend class BLERemoteService; + friend class BLERemoteDescriptor; // Private member functions void gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); + void getDescriptors(); + uint16_t getHandle(); + BLERemoteService* getRemoteService(); + void removeDescriptors(); - uint16_t getHandle(); // Private properties BLEUUID m_uuid; esp_gatt_char_prop_t m_charProp; diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index f854d022..a606929d 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -6,9 +6,23 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) +#include #include "BLERemoteDescriptor.h" +#include "GeneralUtils.h" +#include -#endif /* CONFIG_BT_ENABLED */ +static const char* LOG_TAG = "BLERemoteDescriptor"; + + +BLERemoteDescriptor::BLERemoteDescriptor( + uint16_t handle, + BLEUUID uuid, + BLERemoteCharacteristic* pRemoteCharacteristic) { + + m_handle = handle; + m_uuid = uuid; + m_pRemoteCharacteristic = pRemoteCharacteristic; +} /** * @brief Retrieve the handle associated with this remote descriptor. @@ -29,36 +43,117 @@ BLEUUID BLERemoteDescriptor::getUUID() { std::string BLERemoteDescriptor::readValue(void) { + m_semaphoreReadDescrEvt.take("readValue"); + + // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. + esp_err_t errRc = ::esp_ble_gattc_read_char_descr( + m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), + m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), // The connection ID to the BLE server + getHandle(), // The handle of this characteristic + ESP_GATT_AUTH_REQ_NONE); // Security + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return ""; + } + + // Block waiting for the event that indicates that the read has completed. When it has, the std::string found + // in m_value will contain our data. + m_semaphoreReadDescrEvt.wait("readValue"); + + ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length()); + return m_value; return ""; } // readValue uint8_t BLERemoteDescriptor::readUInt8(void) { + std::string value = readValue(); + if (value.length() >= 1) { + return (uint8_t)value[0]; + } return 0; } // readUInt8 uint16_t BLERemoteDescriptor::readUInt16(void) { + std::string value = readValue(); + if (value.length() >= 2) { + return *(uint16_t*)(value.data()); + } return 0; } // readUInt16 uint32_t BLERemoteDescriptor::readUInt32(void) { + std::string value = readValue(); + if (value.length() >= 4) { + return *(uint32_t*)(value.data()); + } return 0; } // readUInt32 + +/** + * @brief Return a string representation of this BLE Remote Descriptor. + * @retun A string representation of this BLE Remote Descriptor. + */ std::string BLERemoteDescriptor::toString(void) { - return ""; + std::stringstream ss; + ss << "handle: " << getHandle() << ", uuid: " << getUUID().toString(); + return ss.str(); } // toString -void BLERemoteDescriptor::writeValue(uint8_t* data, size_t length, - bool response) { + +/** + * @brief Write data to the BLE Remote Descriptor. + * @param [in] data The data to send to the remote descriptor. + * @param [in] length The length of the data to send. + * @param [in] response True if we expect a response. + */ +void BLERemoteDescriptor::writeValue( + uint8_t* data, + size_t length, + bool response) { + ESP_LOGD(LOG_TAG, ">> writeValue: %s", toString().c_str()); + esp_err_t errRc = ::esp_ble_gattc_write_char_descr( + m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), + m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), + getHandle(), + length, // Data length + data, // Data + ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE + ); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char_descr: %d", errRc); + } + ESP_LOGD(LOG_TAG, "<< writeValue"); } // writeValue -void BLERemoteDescriptor::writeValue(std::string newValue, bool response) { +/** + * @brief Write data represented as a string to the BLE Remote Descriptor. + * @param [in] newValue The data to send to the remote descriptor. + * @param [in] response True if we expect a response. + */ +void BLERemoteDescriptor::writeValue( + std::string newValue, + bool response) { + writeValue(newValue.data(), newValue.length()); } // writeValue -void BLERemoteDescriptor::writeValue(uint8_t newValue, bool response) { +/** + * @brief Write a byte value to the Descriptor. + * @param [in] The single byte to write. + * @param [in] True if we expect a response. + */ +void BLERemoteDescriptor::writeValue( + uint8_t newValue, + bool response) { + writeValue(&newValue, 1, response); } // writeValue + + +#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteDescriptor.h b/cpp_utils/BLERemoteDescriptor.h index f9acec61..89a2e221 100644 --- a/cpp_utils/BLERemoteDescriptor.h +++ b/cpp_utils/BLERemoteDescriptor.h @@ -16,6 +16,8 @@ #include "BLERemoteCharacteristic.h" #include "BLEUUID.h" #include "FreeRTOS.h" + +class BLERemoteCharacteristic; /** * @brief A model of remote %BLE descriptor. */ @@ -34,10 +36,17 @@ class BLERemoteDescriptor { private: - uint16_t m_handle; - BLEUUID m_uuid; - std::string m_value; - BLERemoteCharacteristic* m_pRemoteCharacteristic; + friend class BLERemoteCharacteristic; + BLERemoteDescriptor( + uint16_t handle, + BLEUUID uuid, + BLERemoteCharacteristic* pRemoteCharacteristic + ); + uint16_t m_handle; // Server handle of this descriptor. + BLEUUID m_uuid; // UUID of this descriptor. + std::string m_value; // Last received value of the descriptor. + BLERemoteCharacteristic* m_pRemoteCharacteristic; // Reference to the Remote characteristic of which this descriptor is associated. + FreeRTOS::Semaphore m_semaphoreReadDescrEvt = FreeRTOS::Semaphore("ReadDescrEvt"); uint16_t getHandle(); }; diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index 4c5e7ac4..a6cf0566 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -116,21 +116,15 @@ void BLERemoteService::gattClientEventHandler( } } // gattClientEventHandler -BLERemoteCharacteristic* BLERemoteService::getCharacteristic(uint16_t handle) { - ESP_LOGD(LOG_TAG, ">> getCharacteristic: handle: %d", handle); - ESP_LOGE(LOG_TAG, "!!! NOT IMPLEMENTED !!!"); - ESP_LOGD(LOG_TAG, "<< getCharacteristic"); - return nullptr; -} /** - * @brief Get the characteristic object for the UUID. - * @param [in] uuid Characteristic uuid. - * @return Reference to the characteristic object. + * @brief Get the remote characteristic object for the characteristic UUID. + * @param [in] uuid Remote characteristic uuid. + * @return Reference to the remote characteristic object. */ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) { return getCharacteristic(BLEUUID(uuid)); -} +} // getCharacteristic /** @@ -165,9 +159,9 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { void BLERemoteService::getCharacteristics() { ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); -/* - removeCharacteristics(); // Forget any previous characteristics. + removeCharacteristics(); // Forget any previous characteristics. + /* m_semaphoreGetCharEvt.take("getCharacteristics"); esp_err_t errRc = ::esp_ble_gattc_get_characteristic( @@ -187,6 +181,7 @@ void BLERemoteService::getCharacteristics() { */ //ESP_LOGE(LOG_TAG, "!!! NOT IMPLEMENTED !!!"); //ESP_LOGD(LOG_TAG, "--- test code ---"); + /* uint16_t count; esp_gatt_status_t status = ::esp_ble_gattc_get_attr_count( getClient()->getGattcIf(), @@ -219,12 +214,13 @@ void BLERemoteService::getCharacteristics() { else { ESP_LOGD(LOG_TAG, "%s", BLEUtils::gattcServiceElementToString(&srvcElem).c_str()); } + */ uint16_t offset = 0; esp_gattc_char_elem_t result; while(1) { - count = 1; - status = ::esp_ble_gattc_get_all_char( + uint16_t count = 1; + esp_gatt_status_t status = ::esp_ble_gattc_get_all_char( getClient()->getGattcIf(), getClient()->getConnId(), m_startHandle, @@ -233,17 +229,20 @@ void BLERemoteService::getCharacteristics() { &count, offset ); + if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. break; } - if (status != ESP_GATT_OK) { + if (status != ESP_GATT_OK) { // If we got an error, end. ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_all_char: %s", BLEUtils::gattStatusToString(status).c_str()); break; } - if (count == 0) { + + if (count == 0) { // If we failed to get any new records, end. break; } + ESP_LOGD(LOG_TAG, "Found a characteristic: Handle: %d, UUID: %s", result.char_handle, BLEUUID(result.uuid).toString().c_str()); // We now have a new characteristic ... let us add that to our set of known characteristics @@ -256,7 +255,7 @@ void BLERemoteService::getCharacteristics() { m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic->getUUID().toString(), pNewRemoteCharacteristic)); - offset++; + offset++; // Increment our count of number of descriptors found. } m_haveCharacteristics = true; // Remember that we have received the characteristics. ESP_LOGD(LOG_TAG, "<< getCharacteristics()"); @@ -267,15 +266,23 @@ BLEClient* BLERemoteService::getClient() { return m_pClient; } +uint16_t BLERemoteService::getEndHandle() { + return m_endHandle; +} + esp_gatt_id_t* BLERemoteService::getSrvcId() { return &m_srvcId; } +uint16_t BLERemoteService::getStartHandle() { + return m_startHandle; +} + uint16_t BLERemoteService::getHandle() { ESP_LOGD(LOG_TAG, ">> getHandle: service: %s", getUUID().toString().c_str()); //ESP_LOGE(LOG_TAG, "!!! getHandle: NOT IMPLEMENTED !!!"); - ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", m_startHandle, m_startHandle); - return m_startHandle; + ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", getStartHandle(), getStartHandle()); + return getStartHandle(); } @@ -318,4 +325,6 @@ std::string BLERemoteService::toString() { + + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index a61d25b1..a6241cc0 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -32,8 +32,7 @@ class BLERemoteService { // Public methods BLERemoteCharacteristic* getCharacteristic(const char* uuid); BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); - BLERemoteCharacteristic* getCharacteristic(uint16_t handle); - void getCharacteristics(void); + BLEClient* getClient(void); BLEUUID getUUID(void); std::string toString(void); @@ -47,8 +46,11 @@ class BLERemoteService { friend class BLERemoteCharacteristic; // Private methods + void getCharacteristics(void); uint16_t getHandle(); esp_gatt_id_t* getSrvcId(void); + uint16_t getStartHandle(); + uint16_t getEndHandle(); void gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, From e777afec8fffb4822a34243bb3b09799e2a043b1 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Tue, 26 Sep 2017 10:01:32 +0300 Subject: [PATCH 063/381] Fix a memory leak in WiFi --- cpp_utils/WiFi.h | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index e525ce5d..9730b854 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -139,6 +139,7 @@ class WiFi { * @param[in] wifiEventHandler The class that will be used to process events. */ void setWifiEventHandler(WiFiEventHandler *wifiEventHandler) { + delete this->wifiEventHandler; this->wifiEventHandler = wifiEventHandler; } private: From 8c2ab32c5302a598b4a9906f97dcca56654e04e2 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Tue, 26 Sep 2017 10:06:53 +0300 Subject: [PATCH 064/381] Remove an unneeded if statemenet Also tabs to spaces --- cpp_utils/WiFiEventHandler.cpp | 132 ++++++++++++++++----------------- cpp_utils/WiFiEventHandler.h | 58 +++++++-------- 2 files changed, 94 insertions(+), 96 deletions(-) diff --git a/cpp_utils/WiFiEventHandler.cpp b/cpp_utils/WiFiEventHandler.cpp index 1c454d54..59519ed4 100644 --- a/cpp_utils/WiFiEventHandler.cpp +++ b/cpp_utils/WiFiEventHandler.cpp @@ -24,49 +24,49 @@ static char tag[] = "WiFiEventHandler"; * @return ESP_OK if the event was handled otherwise an error. */ esp_err_t WiFiEventHandler::eventHandler(void *ctx, system_event_t *event) { - ESP_LOGD(tag, "eventHandler called"); - WiFiEventHandler *pWiFiEventHandler = (WiFiEventHandler *)ctx; - if (ctx == nullptr) { - ESP_LOGD(tag, "No context"); - return ESP_OK; - } - esp_err_t rc = ESP_OK; - switch(event->event_id) { - - case SYSTEM_EVENT_AP_START: - rc = pWiFiEventHandler->apStart(); - break; - case SYSTEM_EVENT_AP_STOP: - rc = pWiFiEventHandler->apStop(); - break; - case SYSTEM_EVENT_STA_CONNECTED: - rc = pWiFiEventHandler->staConnected(); - break; - case SYSTEM_EVENT_STA_DISCONNECTED: - rc = pWiFiEventHandler->staDisconnected(); - break; - case SYSTEM_EVENT_STA_GOT_IP: - rc = pWiFiEventHandler->staGotIp(event->event_info.got_ip); - break; - case SYSTEM_EVENT_STA_START: - rc = pWiFiEventHandler->staStart(); - break; - case SYSTEM_EVENT_STA_STOP: - rc = pWiFiEventHandler->staStop(); - break; - case SYSTEM_EVENT_WIFI_READY: - rc = pWiFiEventHandler->wifiReady(); - break; - default: - break; - } - if (pWiFiEventHandler->nextHandler != nullptr) { - printf("Found a next handler\n"); - rc = eventHandler(pWiFiEventHandler->nextHandler, event); - } else { - //printf("NOT Found a next handler\n"); - } - return rc; + ESP_LOGD(tag, "eventHandler called"); + WiFiEventHandler *pWiFiEventHandler = (WiFiEventHandler *)ctx; + if (ctx == nullptr) { + ESP_LOGD(tag, "No context"); + return ESP_OK; + } + esp_err_t rc = ESP_OK; + switch(event->event_id) { + + case SYSTEM_EVENT_AP_START: + rc = pWiFiEventHandler->apStart(); + break; + case SYSTEM_EVENT_AP_STOP: + rc = pWiFiEventHandler->apStop(); + break; + case SYSTEM_EVENT_STA_CONNECTED: + rc = pWiFiEventHandler->staConnected(); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + rc = pWiFiEventHandler->staDisconnected(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + rc = pWiFiEventHandler->staGotIp(event->event_info.got_ip); + break; + case SYSTEM_EVENT_STA_START: + rc = pWiFiEventHandler->staStart(); + break; + case SYSTEM_EVENT_STA_STOP: + rc = pWiFiEventHandler->staStop(); + break; + case SYSTEM_EVENT_WIFI_READY: + rc = pWiFiEventHandler->wifiReady(); + break; + default: + break; + } + if (pWiFiEventHandler->nextHandler != nullptr) { + printf("Found a next handler\n"); + rc = eventHandler(pWiFiEventHandler->nextHandler, event); + } else { + //printf("NOT Found a next handler\n"); + } + return rc; } WiFiEventHandler::WiFiEventHandler() { @@ -79,7 +79,7 @@ WiFiEventHandler::WiFiEventHandler() { * @return The event handler function. */ system_event_cb_t WiFiEventHandler::getEventHandler() { - return eventHandler; + return eventHandler; } // getEventHandler @@ -90,8 +90,8 @@ system_event_cb_t WiFiEventHandler::getEventHandler() { * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { - ESP_LOGD(tag, "default staGotIp"); - return ESP_OK; + ESP_LOGD(tag, "default staGotIp"); + return ESP_OK; } // staGotIp /** @@ -100,8 +100,8 @@ esp_err_t WiFiEventHandler::staGotIp(system_event_sta_got_ip_t event_sta_got_ip) * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStart() { - ESP_LOGD(tag, "default apStart"); - return ESP_OK; + ESP_LOGD(tag, "default apStart"); + return ESP_OK; } // apStart /** @@ -110,47 +110,45 @@ esp_err_t WiFiEventHandler::apStart() { * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStop() { - ESP_LOGD(tag, "default apStop"); - return ESP_OK; + ESP_LOGD(tag, "default apStop"); + return ESP_OK; } // apStop esp_err_t WiFiEventHandler::wifiReady() { - ESP_LOGD(tag, "default wifiReady"); - return ESP_OK; + ESP_LOGD(tag, "default wifiReady"); + return ESP_OK; } // wifiReady esp_err_t WiFiEventHandler::staStart() { - ESP_LOGD(tag, "default staStart"); - return ESP_OK; + ESP_LOGD(tag, "default staStart"); + return ESP_OK; } // staStart esp_err_t WiFiEventHandler::staStop() { - ESP_LOGD(tag, "default staStop"); - return ESP_OK; + ESP_LOGD(tag, "default staStop"); + return ESP_OK; } // staStop esp_err_t WiFiEventHandler::staConnected() { - ESP_LOGD(tag, "default staConnected"); - return ESP_OK; + ESP_LOGD(tag, "default staConnected"); + return ESP_OK; } // staConnected esp_err_t WiFiEventHandler::staDisconnected() { - ESP_LOGD(tag, "default staDisconnected"); - return ESP_OK; + ESP_LOGD(tag, "default staDisconnected"); + return ESP_OK; } // staDisconnected esp_err_t WiFiEventHandler::apStaConnected() { - ESP_LOGD(tag, "default apStaConnected"); - return ESP_OK; + ESP_LOGD(tag, "default apStaConnected"); + return ESP_OK; } // apStaConnected esp_err_t WiFiEventHandler::apStaDisconnected() { - ESP_LOGD(tag, "default apStaDisconnected"); - return ESP_OK; + ESP_LOGD(tag, "default apStaDisconnected"); + return ESP_OK; } // apStaDisconnected WiFiEventHandler::~WiFiEventHandler() { - if (nextHandler != nullptr) { - delete nextHandler; - } + delete nextHandler; } // ~WiFiEventHandler diff --git a/cpp_utils/WiFiEventHandler.h b/cpp_utils/WiFiEventHandler.h index 9bfb7e19..0596e92c 100644 --- a/cpp_utils/WiFiEventHandler.h +++ b/cpp_utils/WiFiEventHandler.h @@ -79,39 +79,39 @@ */ class WiFiEventHandler { public: - WiFiEventHandler(); - virtual ~WiFiEventHandler(); - system_event_cb_t getEventHandler(); - virtual esp_err_t apStaConnected(); - virtual esp_err_t apStaDisconnected(); - virtual esp_err_t apStart(); - virtual esp_err_t apStop(); - virtual esp_err_t staConnected(); - virtual esp_err_t staDisconnected(); - virtual esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip); - virtual esp_err_t staStart(); - virtual esp_err_t staStop(); - virtual esp_err_t wifiReady(); + WiFiEventHandler(); + virtual ~WiFiEventHandler(); + system_event_cb_t getEventHandler(); + virtual esp_err_t apStaConnected(); + virtual esp_err_t apStaDisconnected(); + virtual esp_err_t apStart(); + virtual esp_err_t apStop(); + virtual esp_err_t staConnected(); + virtual esp_err_t staDisconnected(); + virtual esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip); + virtual esp_err_t staStart(); + virtual esp_err_t staStop(); + virtual esp_err_t wifiReady(); - /** - * Get the next WiFi event handler in the chain, if there is one. - * @return The next WiFi event handler in the chain or nullptr if there is none. - */ - WiFiEventHandler *getNextHandler() { - return nextHandler; - } + /** + * Get the next WiFi event handler in the chain, if there is one. + * @return The next WiFi event handler in the chain or nullptr if there is none. + */ + WiFiEventHandler *getNextHandler() { + return nextHandler; + } - /** - * Set the next WiFi event handler in the chain. - * @param [in] nextHandler The next WiFi event handler in the chain. - */ - void setNextHandler(WiFiEventHandler* nextHandler) { - this->nextHandler = nextHandler; - } + /** + * Set the next WiFi event handler in the chain. + * @param [in] nextHandler The next WiFi event handler in the chain. + */ + void setNextHandler(WiFiEventHandler* nextHandler) { + this->nextHandler = nextHandler; + } private: - WiFiEventHandler *nextHandler = nullptr; - static esp_err_t eventHandler(void *ctx, system_event_t *event); + WiFiEventHandler *nextHandler = nullptr; + static esp_err_t eventHandler(void *ctx, system_event_t *event); }; #endif /* MAIN_WIFIEVENTHANDLER_H_ */ From fbc7022fc56be09e9e6fbab9df6f95f0b386d2c9 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Tue, 26 Sep 2017 11:18:16 +0300 Subject: [PATCH 065/381] Frees the memory used by the event handler on destruction Also a small optimization in the WiFi constructor --- cpp_utils/WiFi.cpp | 22 ++++++++++++++++++---- cpp_utils/WiFi.h | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 38ac9811..548c60da 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -42,13 +42,27 @@ static void setDNSServer(char *ip) { */ -WiFi::WiFi() { - ip = ""; - gw = ""; - netmask = ""; +} +*/ + +/** + * @brief Creates and uses a default event handler + */ +WiFi::WiFi() + : ip("") + , gw("") + , netmask("") + , wifiEventHandler(nullptr) +{ wifiEventHandler = new WiFiEventHandler(); } +/** + * @brief Deletes the event handler that was used by the class + */ +WiFi::~WiFi() { + delete wifiEventHandler; +} /** * @brief Add a reference to a DNS server. diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 9730b854..8cb51631 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -110,6 +110,7 @@ class WiFi { public: WiFi(); + ~WiFi(); void addDNSServer(const std::string& ip); void addDNSServer(const char* ip); void setDNSServer(int numdns, const std::string& ip); From 07383014bdcc30b3c1ef56b8f903788c119a6a71 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Tue, 26 Sep 2017 12:18:31 +0300 Subject: [PATCH 066/381] Small optimization --- cpp_utils/WebServer.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 51893ae7..c1d5dabe 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -557,10 +557,12 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m // Because we reached here, it means that we did NOT match a handler. Now we want to attempt // to retrieve the corresponding file content. - std::string filePath = httpResponse.getRootPath(); + std::string filePath; + filePath.reserve(httpResponse.getRootPath().length() + message->uri.len + 1); + filePath += httpResponse.getRootPath(); filePath.append(message->uri.p, message->uri.len); ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); - FILE *file = fopen(filePath.c_str(), "r"); + FILE *file = fopen(filePath.c_str(), "rb"); if (file != nullptr) { fseek(file, 0L, SEEK_END); size_t length = ftell(file); From 3018c013a283d8d2c5713b8f0f1d83465e50fb5b Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Tue, 26 Sep 2017 14:56:08 +0300 Subject: [PATCH 067/381] Simplified a condition that is always true --- cpp_utils/WebServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index c1d5dabe..72f28dae 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -740,7 +740,7 @@ std::map WebServer::HTTPRequest::getQuery() const { value = ""; } } // End state = STATE_NAME - else if (state == STATE_VALUE) { + else { // if (state == STATE_VALUE) if (currentChar != '&') { value += currentChar; } else { From 2e5bfa1b004f0192c65fb16abc4a3961d82a2457 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Tue, 26 Sep 2017 15:49:54 +0300 Subject: [PATCH 068/381] Remove an unnessesary line --- cpp_utils/WebServer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 72f28dae..2b9f51ca 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -540,7 +540,6 @@ void WebServer::HTTPResponse::setStatus(int status) { void WebServer::processRequest(struct mg_connection *mgConnection, struct http_message* message) { ESP_LOGD(tag, "WebServer::processRequest: Matching: %.*s", (int)message->uri.len, message->uri.p); HTTPResponse httpResponse = HTTPResponse(mgConnection); - httpResponse.setRootPath(getRootPath()); /* * Iterate through each of the path handlers looking for a match with the method and specified path. From 4c04ce1d65f423afc1ba31c6c4084145e969ba49 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Tue, 26 Sep 2017 17:31:17 +0300 Subject: [PATCH 069/381] More optimizations, setIPInfo now enables or disables DHCP That way you can disable DHCP after already connecting to an AP --- cpp_utils/WiFi.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 8cb51631..b7499c48 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -103,9 +103,9 @@ class WiFiAPRecord { */ class WiFi { private: - std::string ip; - std::string gw; - std::string netmask; + uint32_t ip; + uint32_t gw; + uint32_t netmask; WiFiEventHandler *wifiEventHandler; public: @@ -113,8 +113,10 @@ class WiFi { ~WiFi(); void addDNSServer(const std::string& ip); void addDNSServer(const char* ip); + void addDNSServer(ip_addr_t ip); void setDNSServer(int numdns, const std::string& ip); void setDNSServer(int numdns, const char* ip); + void setDNSServer(int numdns, ip_addr_t ip); struct in_addr getHostByName(const std::string& hostName); struct in_addr getHostByName(const char* hostName); void connectAP(const std::string& ssid, const std::string& password); @@ -129,11 +131,8 @@ class WiFi { std::vector scan(); void startAP(const std::string& ssid, const std::string& passwd); void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); - void setIPInfo(std::string&& ip, std::string&& gw, std::string&& netmask); - - - - + void setIPInfo(const char* ip, const char* gw, const char* netmask); + void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); /** * Set the event handler to use to process detected events. From 2174296149008dba40fcaf76fe617f45199fb57b Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Tue, 26 Sep 2017 17:31:36 +0300 Subject: [PATCH 070/381] File i forgot to stage in the last commit --- cpp_utils/WiFi.cpp | 86 +++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 548c60da..d3c44a9c 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -41,17 +41,13 @@ static void setDNSServer(char *ip) { } */ - -} -*/ - /** * @brief Creates and uses a default event handler */ WiFi::WiFi() - : ip("") - , gw("") - , netmask("") + : ip(0) + , gw(0) + , netmask(0) , wifiEventHandler(nullptr) { wifiEventHandler = new WiFiEventHandler(); @@ -86,10 +82,14 @@ void WiFi::addDNSServer(const std::string& ip) { } // addDNSServer void WiFi::addDNSServer(const char* ip) { - ip_addr_t dnsserver; - ESP_LOGD(tag, "Setting DNS[%d] to %s", m_dnsCount, ip); - inet_pton(AF_INET, ip, &dnsserver); - ::dns_setserver(m_dnsCount, &dnsserver); + ip_addr_t dns_server; + if(inet_pton(AF_INET, ip, &dns_server)) + addDNSServer(ip); +} // addDNSServer + +void WiFi::addDNSServer(ip_addr_t ip) { + ESP_LOGD(tag, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + ::dns_setserver(m_dnsCount, &ip); m_dnsCount++; m_dnsCount %= 2; } // addDNSServer @@ -115,10 +115,14 @@ void WiFi::setDNSServer(int numdns, const std::string& ip) { } // setDNSServer void WiFi::setDNSServer(int numdns, const char* ip) { - ip_addr_t dnsserver; - ESP_LOGD(tag, "Setting DNS[%d] to %s", numdns, ip); - inet_pton(AF_INET, ip, &dnsserver); - ::dns_setserver(numdns, &dnsserver); + ip_addr_t dns_server; + if(inet_pton(AF_INET, ip, &dns_server)) + setDNSServer(numdns, dns_server); +} // setDNSServer + +void WiFi::setDNSServer(int numdns, ip_addr_t ip) { + ESP_LOGD(tag, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + ::dns_setserver(numdns, &ip); } // setDNSServer /** @@ -133,20 +137,19 @@ void WiFi::setDNSServer(int numdns, const char* ip) { void WiFi::connectAP(const std::string& ssid, const std::string& password){ ::nvs_flash_init(); ::tcpip_adapter_init(); - if (ip.length() > 0 && gw.length() > 0 && netmask.length() > 0) { + if (ip != 0 && gw != 0 && netmask != 0) { ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; - inet_pton(AF_INET, ip.data(), &ipInfo.ip); - inet_pton(AF_INET, gw.data(), &ipInfo.gw); - inet_pton(AF_INET, netmask.data(), &ipInfo.netmask); ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); } ESP_ERROR_CHECK( esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(::esp_wifi_init(&cfg)); ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); wifi_config_t sta_config; @@ -366,7 +369,8 @@ void WiFi::startAP(const std::string& ssid, const std::string& password) { /** - * @brief Set the IP info used when connecting as a station to an external access point. + * @brief Set the IP info and enable DHCP if ip != 0. If called with ip == 0 then DHCP is enabled. + * If called with bad values it will do nothing. * * Do not call this method if we are being an access point ourselves. * @@ -382,17 +386,43 @@ void WiFi::startAP(const std::string& ssid, const std::string& password) { * @return N/A. */ void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask) { + setIPInfo(ip.c_str(), gw.c_str(), netmask.c_str()); +} // setIPInfo + +void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { + uint32_t new_ip; + uint32_t new_gw; + uint32_t new_netmask; + + auto success = (bool)inet_pton(AF_INET, ip, &new_ip); + success = success && inet_pton(AF_INET, gw, &new_gw); + success = success && inet_pton(AF_INET, netmask, &new_netmask); + + if(!success) { + return; + } + + setIPInfo(new_ip, new_gw, new_netmask); +} // setIPInfo + +void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { this->ip = ip; this->gw = gw; this->netmask = netmask; -} // setIPInfo -void WiFi::setIPInfo(std::string&& ip, std::string&& gw, std::string&& netmask) { - this->ip = std::move(ip); - this->gw = std::move(gw); - this->netmask = std::move(netmask); -} // setIPInfo + if(ip != 0 && gw != 0 && netmask != 0) { + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + } else { + ip = 0; + ::tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + } +} /** * @brief Return a string representation of the WiFi access point record. From 7ef6a44d1c4d086530f3810bac1b9b7fbccd5019 Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 26 Sep 2017 22:24:04 -0500 Subject: [PATCH 071/381] Code changes for #44 --- cpp_utils/BLEDevice.cpp | 4 + cpp_utils/BLEDevice.h | 3 +- cpp_utils/BLERemoteCharacteristic.cpp | 1 + cpp_utils/BLEServer.h | 4 +- cpp_utils/tests/BLETests/Sample-MLE-15.cpp | 1 - cpp_utils/tests/BLETests/Sample1.cpp | 2 +- cpp_utils/tests/BLETests/SampleClient.cpp | 2 +- cpp_utils/tests/BLETests/SampleNotify.cpp | 2 +- cpp_utils/tests/BLETests/SampleRead.cpp | 2 +- cpp_utils/tests/BLETests/SampleSensorTag.cpp | 171 +++++++++++++++++++ cpp_utils/tests/BLETests/SampleServer.cpp | 10 +- cpp_utils/tests/BLETests/SampleWrite.cpp | 2 +- cpp_utils/tests/BLETests/main.cpp | 2 + 13 files changed, 191 insertions(+), 15 deletions(-) create mode 100644 cpp_utils/tests/BLETests/SampleSensorTag.cpp diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 37748037..f383f371 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -42,6 +42,10 @@ BLEClient* BLEDevice::createClient() { return m_pClient; } // createClient +BLEServer* BLEDevice::createServer() { + return new BLEServer(); +} + /** * @brief Handle GATT server events. diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 9d767c1e..28d78cb4 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -25,7 +25,8 @@ class BLEDevice { public: static void dumpDevices(); - static BLEClient *createClient(); + static BLEClient* createClient(); + static BLEServer* createServer(); static void init(std::string deviceName); //static void scan(int duration, esp_ble_scan_type_t scan_type = BLE_SCAN_TYPE_PASSIVE); diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index e96fc9dd..88db2e2a 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -113,6 +113,7 @@ void BLERemoteCharacteristic::gattClientEventHandler( break; } if (m_notifyCallback != nullptr) { + ESP_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s", toString().c_str()); m_notifyCallback( this, evtParam->notify.value, diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index c6307bc6..c7580d24 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -51,9 +51,6 @@ class BLEServiceMap { */ class BLEServer { public: - BLEServer(); - - uint32_t getConnectedCount(); BLEService* createService(const char* uuid); BLEService* createService(BLEUUID uuid); @@ -63,6 +60,7 @@ class BLEServer { private: + BLEServer(); friend class BLEService; friend class BLECharacteristic; friend class BLEDevice; diff --git a/cpp_utils/tests/BLETests/Sample-MLE-15.cpp b/cpp_utils/tests/BLETests/Sample-MLE-15.cpp index 5e5b2d29..bb0bc588 100644 --- a/cpp_utils/tests/BLETests/Sample-MLE-15.cpp +++ b/cpp_utils/tests/BLETests/Sample-MLE-15.cpp @@ -37,7 +37,6 @@ class MyClient: public Task { return; } - pRemoteService->getCharacteristics(); BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); if (pRemoteCharacteristic == nullptr) { ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); diff --git a/cpp_utils/tests/BLETests/Sample1.cpp b/cpp_utils/tests/BLETests/Sample1.cpp index 88f6b3d1..ad83ca7f 100644 --- a/cpp_utils/tests/BLETests/Sample1.cpp +++ b/cpp_utils/tests/BLETests/Sample1.cpp @@ -15,7 +15,7 @@ static void run() { BLEDevice::init("MYDEVICE"); - BLEServer *pServer = new BLEServer(); + BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); diff --git a/cpp_utils/tests/BLETests/SampleClient.cpp b/cpp_utils/tests/BLETests/SampleClient.cpp index 31a65bd6..c91942db 100644 --- a/cpp_utils/tests/BLETests/SampleClient.cpp +++ b/cpp_utils/tests/BLETests/SampleClient.cpp @@ -109,5 +109,5 @@ void SampleClient(void) { BLEScan *pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); - pBLEScan->start(30); + pBLEScan->start(15); } // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleNotify.cpp b/cpp_utils/tests/BLETests/SampleNotify.cpp index 738aacb3..3714db34 100644 --- a/cpp_utils/tests/BLETests/SampleNotify.cpp +++ b/cpp_utils/tests/BLETests/SampleNotify.cpp @@ -75,7 +75,7 @@ static void run() { BLEDevice::init("MYDEVICE"); // Create the BLE Server - BLEServer *pServer = new BLEServer(); + BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Create the BLE Service diff --git a/cpp_utils/tests/BLETests/SampleRead.cpp b/cpp_utils/tests/BLETests/SampleRead.cpp index e0307857..e0d207c0 100644 --- a/cpp_utils/tests/BLETests/SampleRead.cpp +++ b/cpp_utils/tests/BLETests/SampleRead.cpp @@ -34,7 +34,7 @@ class MyCallbackHandler: public BLECharacteristicCallbacks { static void run() { BLEDevice::init("MYDEVICE"); - BLEServer *pServer = new BLEServer(); + BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID_BIN, 16, true)); diff --git a/cpp_utils/tests/BLETests/SampleSensorTag.cpp b/cpp_utils/tests/BLETests/SampleSensorTag.cpp new file mode 100644 index 00000000..3b081f34 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleSensorTag.cpp @@ -0,0 +1,171 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleSensorTag"; + +static BLEUUID luxometerServiceUUID("f000aa70-0451-4000-b000-000000000000"); +static BLEUUID luxometerDataUUID("f000aa71-0451-4000-b000-000000000000"); +static BLEUUID luxometerConfigUUID("f000aa72-0451-4000-b000-000000000000"); +static BLEUUID luxometerPeriodUUID("f000aa73-0451-4000-b000-000000000000"); + +static BLEUUID irTempServiceUUID("f000aa00-0451-4000-b000-000000000000"); +static BLEUUID irTempDataUUID("f000aa01-0451-4000-b000-000000000000"); +static BLEUUID irTempConfigUUID("f000aa02-0451-4000-b000-000000000000"); +static BLEUUID irTempPeriodUUID("f000aa03-0451-4000-b000-000000000000"); + +static BLEUUID keyPressServiceUUID("0000ffe0-0000-1000-8000-00805f9b34fb"); +static BLEUUID keyPressStateUUID("0000ffe1-0000-1000-8000-00805f9b34fb"); + + +void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* data, + size_t length, + bool isNotify) +{ + ESP_LOGD(LOG_TAG, "Notified!"); +} + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + BLERemoteService* pIRTempService = pClient->getService(irTempServiceUUID); + BLERemoteCharacteristic* pIRTempDataCharacteristic = pIRTempService->getCharacteristic(irTempDataUUID); + BLERemoteCharacteristic* pIRTempConfigCharacteristic = pIRTempService->getCharacteristic(irTempConfigUUID); + pIRTempConfigCharacteristic->writeValue(1); + while(1) { + uint32_t rawValue = pIRTempDataCharacteristic->readUInt32(); + float obj = ((rawValue & 0x0000ffff) >> 2) * 0.03125; + float amb = ((rawValue & 0xffff0000) >> 18) * 0.03125; + ESP_LOGD(LOG_TAG, "IT Values: obj: %f, amb: %f", obj, amb); + FreeRTOS::sleep(1000); + } + + /* + BLERemoteService* pKeyPressService = pClient->getService(keyPressServiceUUID); + BLERemoteCharacteristic* pKeyStateCharacteristic = pKeyPressService->getCharacteristic(keyPressStateUUID); + BLERemoteDescriptor *p2902Descriptor = pKeyStateCharacteristic->getDescriptor(BLEUUID("00002902-0000-1000-8000-00805f9b34fb")); + p2902Descriptor->writeValue(1); + pKeyStateCharacteristic->registerForNotify(notifyCallback); + */ + + /* + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(luxometerServiceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", luxometerServiceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pLuxometerConfigCharacteristic = pRemoteService->getCharacteristic(luxometerConfigUUID); + if (pLuxometerConfigCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", luxometerDataUUID.toString().c_str()); + return; + } + BLERemoteCharacteristic* pLuxometerPeriodCharacteristic = pRemoteService->getCharacteristic(luxometerPeriodUUID); + if (pLuxometerPeriodCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", luxometerDataUUID.toString().c_str()); + return; + } + BLERemoteCharacteristic* pLuxometerDataCharacteristic = pRemoteService->getCharacteristic(luxometerDataUUID); + if (pLuxometerDataCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", luxometerDataUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + uint16_t configValue = pLuxometerConfigCharacteristic->readUInt16(); + ESP_LOGD(LOG_TAG, "The pLuxometerConfigCharacteristic value was: %d", configValue); + + pLuxometerConfigCharacteristic->writeValue(1); + + while(1) { + uint16_t lightValue = pLuxometerDataCharacteristic->readUInt16(); + ESP_LOGD(LOG_TAG, "Light value: %d", lightValue); + FreeRTOS::sleep(1000); + } + */ + + /* + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + */ + + while(1) { + FreeRTOS::sleep(5000); + } + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the SensorTag. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + if (advertisedDevice.getName() == "CC2650 SensorTag") { + ESP_LOGD(LOG_TAG, "Found our SensorTag device! address: %s", advertisedDevice.getAddress().toString().c_str()); + advertisedDevice.getScan()->stop(); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleSensorTag(void) { + ESP_LOGD(LOG_TAG, "SampleSensorTag starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(15); +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleServer.cpp b/cpp_utils/tests/BLETests/SampleServer.cpp index ad44d055..15a52cd6 100644 --- a/cpp_utils/tests/BLETests/SampleServer.cpp +++ b/cpp_utils/tests/BLETests/SampleServer.cpp @@ -1,13 +1,14 @@ /** * Create a new BLE server. */ -#include "BLEUtils.h" +#include "BLEDevice.h" #include "BLEServer.h" +#include "BLEUtils.h" #include "BLE2902.h" #include #include #include -#include "BLEDevice.h" + #include "sdkconfig.h" @@ -18,7 +19,7 @@ class MainBLEServer: public Task { ESP_LOGD(LOG_TAG, "Starting BLE work!"); BLEDevice::init("MYDEVICE"); - BLEServer* pServer = new BLEServer(); + BLEServer* pServer = BLEDevice::createServer(); BLEService* pService = pServer->createService(BLEUUID((uint16_t)0x1234)); @@ -29,7 +30,6 @@ class MainBLEServer: public Task { BLECharacteristic::PROPERTY_INDICATE ); - pCharacteristic->setValue("Hello World!"); BLE2902* p2902Descriptor = new BLE2902(); @@ -50,7 +50,7 @@ class MainBLEServer: public Task { void SampleServer(void) { - esp_log_level_set("*", ESP_LOG_DEBUG); + //esp_log_level_set("*", ESP_LOG_DEBUG); MainBLEServer* pMainBleServer = new MainBLEServer(); pMainBleServer->setStackSize(20000); pMainBleServer->start(); diff --git a/cpp_utils/tests/BLETests/SampleWrite.cpp b/cpp_utils/tests/BLETests/SampleWrite.cpp index 47616dd7..2832f9ec 100644 --- a/cpp_utils/tests/BLETests/SampleWrite.cpp +++ b/cpp_utils/tests/BLETests/SampleWrite.cpp @@ -36,7 +36,7 @@ class MyCallbacks: public BLECharacteristicCallbacks { static void run() { BLEDevice::init("MYDEVICE"); - BLEServer *pServer = new BLEServer(); + BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); diff --git a/cpp_utils/tests/BLETests/main.cpp b/cpp_utils/tests/BLETests/main.cpp index 1ddedb26..079cf3cf 100644 --- a/cpp_utils/tests/BLETests/main.cpp +++ b/cpp_utils/tests/BLETests/main.cpp @@ -16,6 +16,7 @@ void SampleNotify(void); void SampleClient_Notify(void); void SampleClient(void); void Sample_MLE_15(void); +void SampleSensorTag(void); // @@ -31,4 +32,5 @@ void app_main(void) { //SampleClient(); SampleClient_Notify(); //Sample_MLE_15(); + //SampleSensorTag(); } // app_main From 8dafeddaa9ee5c8a264302650e5ca0e29cd4524d Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 27 Sep 2017 11:25:57 -0500 Subject: [PATCH 072/381] Addition of extra event types for #89 --- cpp_utils/BLEUtils.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index eb8597f7..a6bab96f 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -1185,8 +1185,24 @@ const char* BLEUtils::gapEventToString(uint32_t eventType) { return "ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT"; case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT"; + case ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT: + return "ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT"; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + return "ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT"; + case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: + return "ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT"; + case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: + return "ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT"; + case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: + return "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: + return "ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT: + return "ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + return "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT"; default: - ESP_LOGD(LOG_TAG, "gapEventToString: Unknown event type 0x%x", eventType); + ESP_LOGD(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); return "Unknown event type"; } } // gapEventToString From 5dcf485fe34c80fcf2d1adc8298ad53ac48f9c78 Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 27 Sep 2017 11:35:22 -0500 Subject: [PATCH 073/381] Log added for #89 --- cpp_utils/BLEUtils.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index a6bab96f..dca4f423 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -635,6 +635,23 @@ void BLEUtils::dumpGapEvent( break; } // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT + + // + // ESP_GAP_BLE_SCAN_UPDATE_CONN_PARAMS_EVT + // + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: { + ESP_LOGD(LOG_TAG, "[status: %d, bd_addr: %s, min_int: %d, max_int: %d, latency: %d, conn_int: %d, timeout: %d", + param->update_conn_params.status, + BLEAddress(param->update_conn_params.bda).toString().c_str(), + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.latency, + param->update_conn_params.conn_int, + param->update_conn_params.timeout + ); + } // ESP_GAP_BLE_SCAN_UPDATE_CONN_PARAMS_EVT + + default: { ESP_LOGD(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); break; From 7c136cbfd5d21e36f0fd96395cb68c01a96d1552 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Thu, 28 Sep 2017 12:18:01 +0300 Subject: [PATCH 074/381] Some typecasting to make things easier for the user --- cpp_utils/WebServer.cpp | 3 +++ cpp_utils/WebServer.h | 1 + 2 files changed, 4 insertions(+) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 2b9f51ca..2c975939 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -478,6 +478,9 @@ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { m_nc->flags |= MG_F_SEND_AND_CLOSE; } // sendData +void WebServer::HTTPResponse::sendData(const char* pData, size_t length) { + sendData((uint8_t*) pData, length); +} // sendData /** * @brief Set the headers to be sent in the HTTP response. diff --git a/cpp_utils/WebServer.h b/cpp_utils/WebServer.h index 39d55e38..7bb1e585 100644 --- a/cpp_utils/WebServer.h +++ b/cpp_utils/WebServer.h @@ -55,6 +55,7 @@ class WebServer { void setHeaders(std::map&& headers); void sendData(const std::string& data); void sendData(const uint8_t* pData, size_t length); + void sendData(const char* pData, size_t length); const std::string& getRootPath() const; void setRootPath(const std::string& path); void setRootPath(std::string&& path); From 447a52e0d2fcd01645e64966b12c92384b4e11c4 Mon Sep 17 00:00:00 2001 From: kolban Date: Thu, 28 Sep 2017 08:43:34 -0500 Subject: [PATCH 075/381] Insertions for #91 --- cpp_utils/BLECharacteristic.cpp | 5 +- cpp_utils/BLEClient.cpp | 25 ++- cpp_utils/BLEClient.h | 1 + cpp_utils/BLERemoteCharacteristic.cpp | 4 +- cpp_utils/BLERemoteService.cpp | 3 +- cpp_utils/BLEUtils.cpp | 257 +++++++++++++++++++++++--- cpp_utils/WiFi.cpp | 110 ++++++----- cpp_utils/WiFi.h | 18 +- cpp_utils/WiFiEventHandler.cpp | 137 ++++++++------ cpp_utils/WiFiEventHandler.h | 8 +- 10 files changed, 426 insertions(+), 142 deletions(-) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index f5b82006..cdc2200e 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -131,7 +131,6 @@ void BLECharacteristic::executeCreate(BLEService* pService) { } // executeCreate - /** * @brief Return the BLE Descriptor for the given UUID if associated with this characteristic. * @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve. @@ -141,6 +140,7 @@ BLEDescriptor* BLECharacteristic::getDescriptorByUUID(const char* descriptorUUID return m_descriptorMap.getByUUID(BLEUUID(descriptorUUID)); } // getDescriptorByUUID + /** * @brief Return the BLE Descriptor for the given UUID if associated with this characteristic. * @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve. @@ -274,7 +274,7 @@ void BLECharacteristic::handleGATTServerEvent( ESP_LOGD(LOG_TAG, " - Response to write event: New value: handle: %.2x, uuid: %s", getHandle(), getUUID().toString().c_str()); - char *pHexData = BLEUtils::buildHexData(nullptr, param->write.value, param->write.len); + char* pHexData = BLEUtils::buildHexData(nullptr, param->write.value, param->write.len); ESP_LOGD(LOG_TAG, " - Data: length: %d, data: %s", param->write.len, pHexData); free(pHexData); @@ -422,6 +422,7 @@ void BLECharacteristic::handleGATTServerEvent( } // handleGATTServerEvent + /** * @brief Send an indication. * An indication is a transmission of up to the first 20 bytes of the characteristic value. An indication diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 3b2ca2c9..d518d531 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -48,13 +48,28 @@ BLEClient::BLEClient() { m_haveServices = false; } // BLEClient + +/** + * @brief Destructor. + */ +BLEClient::~BLEClient() { + // We may have allocated service references associated with this client. Before we are finished + // with the client, we must release resources. + for (auto &myPair : m_servicesMap) { + delete myPair.second; + } + m_servicesMap.clear(); +} // ~BLEClient + + /** - * @brief Connect to the partner. + * @brief Connect to the partner (BLE Server). * @param [in] address The address of the partner. * @return True on success. */ bool BLEClient::connect(BLEAddress address) { ESP_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str()); + // We need the connection handle that we get from registering the application. We register the app // and then block on its completion. When the event has arrived, we will have the handle. m_semaphoreRegEvt.take("connect"); @@ -63,10 +78,12 @@ bool BLEClient::connect(BLEAddress address) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return false; } + m_semaphoreRegEvt.wait("connect"); m_peerAddress = address; + // Perform the open connection request against the target BLE Server. m_semaphoreOpenEvt.take("connect"); errRc = ::esp_ble_gattc_open( getGattcIf(), @@ -243,7 +260,7 @@ BLERemoteService* BLEClient::getService(BLEUUID uuid) { ESP_LOGD(LOG_TAG, "<< getService: found the service with uuid: %s", uuid.toString().c_str()); return myPair.second; } - } + } // End of each of the services. ESP_LOGD(LOG_TAG, "<< getService: not found"); return nullptr; } // getService @@ -268,7 +285,7 @@ std::map* BLEClient::getServices() { esp_err_t errRc = esp_ble_gattc_search_service( getGattcIf(), getConnId(), - NULL // Filter UUID + nullptr // Filter UUID ); m_semaphoreSearchCmplEvt.take("getServices"); if (errRc != ESP_OK) { @@ -300,7 +317,7 @@ std::string BLEClient::toString() { ss << "\nServices:\n"; for (auto &myPair : m_servicesMap) { ss << myPair.second->toString() << "\n"; - // myPair.second is the value + // myPair.second is the value } return ss.str(); } // toString diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index a3f51bcc..494f51dd 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -28,6 +28,7 @@ class BLEClientCallbacks; class BLEClient { public: BLEClient(); + ~BLEClient(); bool connect(BLEAddress address); void disconnect(); BLEAddress getPeerAddress(); diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 88db2e2a..fdd5b1ff 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -446,10 +446,12 @@ void BLERemoteCharacteristic::registerForNotify( * @return N/A. */ void BLERemoteCharacteristic::removeDescriptors() { + // Iterate through all the descriptors releasing their storage and erasing them from the map. for (auto &myPair : m_descriptorMap) { + m_descriptorMap.erase(myPair.first); delete myPair.second; } - m_descriptorMap.empty(); + m_descriptorMap.clear(); // Technically not neeeded, but just to be sure. } // removeCharacteristics diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index a6cf0566..b9fd29de 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -301,8 +301,9 @@ BLEUUID BLERemoteService::getUUID() { void BLERemoteService::removeCharacteristics() { for (auto &myPair : m_characteristicMap) { delete myPair.second; + m_characteristicMap.erase(myPair.first); } - m_characteristicMap.empty(); + m_characteristicMap.clear(); // Clear the map } // removeCharacteristics diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index dca4f423..4c088d66 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -6,10 +6,10 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) +#include "BLEAddress.h" +#include "BLEClient.h" #include "BLEUtils.h" #include "BLEUUID.h" -#include "BLEClient.h" -#include "BLEAddress.h" #include "GeneralUtils.h" #include @@ -24,7 +24,7 @@ #include #include -static const char* LOG_TAG = "BLEUtils"; +static const char* LOG_TAG = "BLEUtils"; // Tag for logging. /* static std::map g_addressMap; @@ -32,13 +32,227 @@ static std::map g_connIdMap; */ typedef struct { - uint32_t assignedNumber; + uint32_t assignedNumber; std::string name; } characteristicMap_t; -static characteristicMap_t g_characteristicsMappings[] = { - {0x2a00, "Device Name"}, - {0x2a01, "Appearance"}, +static const characteristicMap_t g_characteristicsMappings[] = { + {0x2A7E,"Aerobic Heart Rate Lower Limit"}, + {0x2A84,"Aerobic Heart Rate Upper Limit"}, + {0x2A7F,"Aerobic Threshold"}, + {0x2A80,"Age"}, + {0x2A5A,"Aggregate"}, + {0x2A43,"Alert Category ID"}, + {0x2A42,"Alert Category ID Bit Mask"}, + {0x2A06,"Alert Level"}, + {0x2A44,"Alert Notification Control Point"}, + {0x2A3F,"Alert Status"}, + {0x2AB3,"Altitude"}, + {0x2A81,"Anaerobic Heart Rate Lower Limit"}, + {0x2A82,"Anaerobic Heart Rate Upper Limit"}, + {0x2A83,"Anaerobic Threshold"}, + {0x2A58,"Analog"}, + {0x2A59,"Analog Output"}, + {0x2A73,"Apparent Wind Direction"}, + {0x2A72,"Apparent Wind Speed"}, + {0x2A01,"Appearance"}, + {0x2AA3,"Barometric Pressure Trend"}, + {0x2A19,"Battery Level"}, + {0x2A1B,"Battery Level State"}, + {0x2A1A,"Battery Power State"}, + {0x2A49,"Blood Pressure Feature"}, + {0x2A35,"Blood Pressure Measurement"}, + {0x2A9B,"Body Composition Feature"}, + {0x2A9C,"Body Composition Measurement"}, + {0x2A38,"Body Sensor Location"}, + {0x2AA4,"Bond Management Control Point"}, + {0x2AA5,"Bond Management Features"}, + {0x2A22,"Boot Keyboard Input Report"}, + {0x2A32,"Boot Keyboard Output Report"}, + {0x2A33,"Boot Mouse Input Report"}, + {0x2AA6,"Central Address Resolution"}, + {0x2AA8,"CGM Feature"}, + {0x2AA7,"CGM Measurement"}, + {0x2AAB,"CGM Session Run Time"}, + {0x2AAA,"CGM Session Start Time"}, + {0x2AAC,"CGM Specific Ops Control Point"}, + {0x2AA9,"CGM Status"}, + {0x2ACE,"Cross Trainer Data"}, + {0x2A5C,"CSC Feature"}, + {0x2A5B,"CSC Measurement"}, + {0x2A2B,"Current Time"}, + {0x2A66,"Cycling Power Control Point"}, + {0x2A66,"Cycling Power Control Point"}, + {0x2A65,"Cycling Power Feature"}, + {0x2A65,"Cycling Power Feature"}, + {0x2A63,"Cycling Power Measurement"}, + {0x2A64,"Cycling Power Vector"}, + {0x2A99,"Database Change Increment"}, + {0x2A85,"Date of Birth"}, + {0x2A86,"Date of Threshold Assessment"}, + {0x2A08,"Date Time"}, + {0x2A0A,"Day Date Time"}, + {0x2A09,"Day of Week"}, + {0x2A7D,"Descriptor Value Changed"}, + {0x2A00,"Device Name"}, + {0x2A7B,"Dew Point"}, + {0x2A56,"Digital"}, + {0x2A57,"Digital Output"}, + {0x2A0D,"DST Offset"}, + {0x2A6C,"Elevation"}, + {0x2A87,"Email Address"}, + {0x2A0B,"Exact Time 100"}, + {0x2A0C,"Exact Time 256"}, + {0x2A88,"Fat Burn Heart Rate Lower Limit"}, + {0x2A89,"Fat Burn Heart Rate Upper Limit"}, + {0x2A26,"Firmware Revision String"}, + {0x2A8A,"First Name"}, + {0x2AD9,"Fitness Machine Control Point"}, + {0x2ACC,"Fitness Machine Feature"}, + {0x2ADA,"Fitness Machine Status"}, + {0x2A8B,"Five Zone Heart Rate Limits"}, + {0x2AB2,"Floor Number"}, + {0x2A8C,"Gender"}, + {0x2A51,"Glucose Feature"}, + {0x2A18,"Glucose Measurement"}, + {0x2A34,"Glucose Measurement Context"}, + {0x2A74,"Gust Factor"}, + {0x2A27,"Hardware Revision String"}, + {0x2A39,"Heart Rate Control Point"}, + {0x2A8D,"Heart Rate Max"}, + {0x2A37,"Heart Rate Measurement"}, + {0x2A7A,"Heat Index"}, + {0x2A8E,"Height"}, + {0x2A4C,"HID Control Point"}, + {0x2A4A,"HID Information"}, + {0x2A8F,"Hip Circumference"}, + {0x2ABA,"HTTP Control Point"}, + {0x2AB9,"HTTP Entity Body"}, + {0x2AB7,"HTTP Headers"}, + {0x2AB8,"HTTP Status Code"}, + {0x2ABB,"HTTPS Security"}, + {0x2A6F,"Humidity"}, + {0x2A2A,"IEEE 11073-20601 Regulatory Certification Data List"}, + {0x2AD2,"Indoor Bike Data"}, + {0x2AAD,"Indoor Positioning Configuration"}, + {0x2A36,"Intermediate Cuff Pressure"}, + {0x2A1E,"Intermediate Temperature"}, + {0x2A77,"Irradiance"}, + {0x2AA2,"Language"}, + {0x2A90,"Last Name"}, + {0x2AAE,"Latitude"}, + {0x2A6B,"LN Control Point"}, + {0x2A6A,"LN Feature"}, + {0x2AB1,"Local East Coordinate"}, + {0x2AB0,"Local North Coordinate"}, + {0x2A0F,"Local Time Information"}, + {0x2A67,"Location and Speed Characteristic"}, + {0x2AB5,"Location Name"}, + {0x2AAF,"Longitude"}, + {0x2A2C,"Magnetic Declination"}, + {0x2AA0,"Magnetic Flux Density - 2D"}, + {0x2AA1,"Magnetic Flux Density - 3D"}, + {0x2A29,"Manufacturer Name String"}, + {0x2A91,"Maximum Recommended Heart Rate"}, + {0x2A21,"Measurement Interval"}, + {0x2A24,"Model Number String"}, + {0x2A68,"Navigation"}, + {0x2A3E,"Network Availability"}, + {0x2A46,"New Alert"}, + {0x2AC5,"Object Action Control Point"}, + {0x2AC8,"Object Changed"}, + {0x2AC1,"Object First-Created"}, + {0x2AC3,"Object ID"}, + {0x2AC2,"Object Last-Modified"}, + {0x2AC6,"Object List Control Point"}, + {0x2AC7,"Object List Filter"}, + {0x2ABE,"Object Name"}, + {0x2AC4,"Object Properties"}, + {0x2AC0,"Object Size"}, + {0x2ABF,"Object Type"}, + {0x2ABD,"OTS Feature"}, + {0x2A04,"Peripheral Preferred Connection Parameters"}, + {0x2A02,"Peripheral Privacy Flag"}, + {0x2A5F,"PLX Continuous Measurement Characteristic"}, + {0x2A60,"PLX Features"}, + {0x2A5E,"PLX Spot-Check Measurement"}, + {0x2A50,"PnP ID"}, + {0x2A75,"Pollen Concentration"}, + {0x2A2F,"Position 2D"}, + {0x2A30,"Position 3D"}, + {0x2A69,"Position Quality"}, + {0x2A6D,"Pressure"}, + {0x2A4E,"Protocol Mode"}, + {0x2A62,"Pulse Oximetry Control Point"}, + {0x2A60,"Pulse Oximetry Pulsatile Event Characteristic"}, + {0x2A78,"Rainfall"}, + {0x2A03,"Reconnection Address"}, + {0x2A52,"Record Access Control Point"}, + {0x2A14,"Reference Time Information"}, + {0x2A3A,"Removable"}, + {0x2A4D,"Report"}, + {0x2A4B,"Report Map"}, + {0x2AC9,"Resolvable Private Address Only"}, + {0x2A92,"Resting Heart Rate"}, + {0x2A40,"Ringer Control point"}, + {0x2A41,"Ringer Setting"}, + {0x2AD1,"Rower Data"}, + {0x2A54,"RSC Feature"}, + {0x2A53,"RSC Measurement"}, + {0x2A55,"SC Control Point"}, + {0x2A4F,"Scan Interval Window"}, + {0x2A31,"Scan Refresh"}, + {0x2A3C,"Scientific Temperature Celsius"}, + {0x2A10,"Secondary Time Zone"}, + {0x2A5D,"Sensor Location"}, + {0x2A25,"Serial Number String"}, + {0x2A05,"Service Changed"}, + {0x2A3B,"Service Required"}, + {0x2A28,"Software Revision String"}, + {0x2A93,"Sport Type for Aerobic and Anaerobic Thresholds"}, + {0x2AD0,"Stair Climber Data"}, + {0x2ACF,"Step Climber Data"}, + {0x2A3D,"String"}, + {0x2AD7,"Supported Heart Rate Range"}, + {0x2AD5,"Supported Inclination Range"}, + {0x2A47,"Supported New Alert Category"}, + {0x2AD8,"Supported Power Range"}, + {0x2AD6,"Supported Resistance Level Range"}, + {0x2AD4,"Supported Speed Range"}, + {0x2A48,"Supported Unread Alert Category"}, + {0x2A23,"System ID"}, + {0x2ABC,"TDS Control Point"}, + {0x2A6E,"Temperature"}, + {0x2A1F,"Temperature Celsius"}, + {0x2A20,"Temperature Fahrenheit"}, + {0x2A1C,"Temperature Measurement"}, + {0x2A1D,"Temperature Type"}, + {0x2A94,"Three Zone Heart Rate Limits"}, + {0x2A12,"Time Accuracy"}, + {0x2A15,"Time Broadcast"}, + {0x2A13,"Time Source"}, + {0x2A16,"Time Update Control Point"}, + {0x2A17,"Time Update State"}, + {0x2A11,"Time with DST"}, + {0x2A0E,"Time Zone"}, + {0x2AD3,"Training Status"}, + {0x2ACD,"Treadmill Data"}, + {0x2A71,"True Wind Direction"}, + {0x2A70,"True Wind Speed"}, + {0x2A95,"Two Zone Heart Rate Limit"}, + {0x2A07,"Tx Power Level"}, + {0x2AB4,"Uncertainty"}, + {0x2A45,"Unread Alert Status"}, + {0x2AB6,"URI"}, + {0x2A9F,"User Control Point"}, + {0x2A9A,"User Index"}, + {0x2A76,"UV Index"}, + {0x2A96,"VO2 Max"}, + {0x2A97,"Waist Circumference"}, + {0x2A98,"Weight"}, + {0x2A9D,"Weight Measurement"}, + {0x2A9E,"Weight Scale Feature"}, + {0x2A79,"Wind Chill"}, {0, ""} }; @@ -48,7 +262,7 @@ static characteristicMap_t g_characteristicsMappings[] = { typedef struct { std::string name; std::string type; - uint32_t assignedNumber; + uint32_t assignedNumber; } gattService_t; @@ -94,6 +308,7 @@ static const gattService_t g_gattServices[] = { {"", "", 0 } }; + /** * @brief Convert characteristic properties into a string representation. * @param [in] prop Characteristic properties. @@ -137,7 +352,7 @@ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { case BLE_ADDR_TYPE_RPA_RANDOM: return "BLE_ADDR_TYPE_RPA_RANDOM"; default: - return "Unknown addr_t"; + return "Unknown esp_ble_addr_type_t"; } } // addressTypeToString @@ -232,13 +447,12 @@ esp_gatt_srvc_id_t BLEUtils::buildGattSrvcId(esp_gatt_id_t gattId, * @param [in] length The length of the data to convert. * @return A pointer to the formatted buffer. */ -char *BLEUtils::buildHexData(uint8_t *target, uint8_t *source, uint8_t length) { +char* BLEUtils::buildHexData(uint8_t *target, uint8_t *source, uint8_t length) { // Guard against too much data. if (length > 100) { length = 100; } - if (target == nullptr) { target = (uint8_t *)malloc(length * 2 + 1); if (target == nullptr) { @@ -640,7 +854,7 @@ void BLEUtils::dumpGapEvent( // ESP_GAP_BLE_SCAN_UPDATE_CONN_PARAMS_EVT // case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d, bd_addr: %s, min_int: %d, max_int: %d, latency: %d, conn_int: %d, timeout: %d", + ESP_LOGD(LOG_TAG, "[status: %d, bd_addr: %s, min_int: %d, max_int: %d, latency: %d, conn_int: %d, timeout: %d]", param->update_conn_params.status, BLEAddress(param->update_conn_params.bda).toString().c_str(), param->update_conn_params.min_int, @@ -649,6 +863,7 @@ void BLEUtils::dumpGapEvent( param->update_conn_params.conn_int, param->update_conn_params.timeout ); + break; } // ESP_GAP_BLE_SCAN_UPDATE_CONN_PARAMS_EVT @@ -667,9 +882,9 @@ void BLEUtils::dumpGapEvent( * @param [in] evtParam The data associated with the event. */ void BLEUtils::dumpGattClientEvent( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *evtParam) { + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* evtParam) { //esp_ble_gattc_cb_param_t *evtParam = (esp_ble_gattc_cb_param_t *)param; ESP_LOGD(LOG_TAG, "GATT Event: %s", BLEUtils::gattClientEventTypeToString(event).c_str()); @@ -938,9 +1153,9 @@ void BLEUtils::dumpGattClientEvent( * @param [in] evtParam A union of structures only one of which is populated. */ void BLEUtils::dumpGattServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *evtParam) { + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* evtParam) { ESP_LOGD(LOG_TAG, "GATT ServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); switch(event) { @@ -1119,7 +1334,7 @@ void BLEUtils::dumpGattServerEvent( evtParam->write.need_rsp, evtParam->write.is_prep, evtParam->write.len); - char *pHex = buildHexData(nullptr, evtParam->write.value, evtParam->write.len); + char* pHex = buildHexData(nullptr, evtParam->write.value, evtParam->write.len); ESP_LOGD(LOG_TAG, "[Data: %s]", pHex); free(pHex); break; @@ -1226,7 +1441,7 @@ const char* BLEUtils::gapEventToString(uint32_t eventType) { std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID) { - characteristicMap_t *p = g_characteristicsMappings; + const characteristicMap_t *p = g_characteristicsMappings; while (p->name.length() > 0) { if (p->assignedNumber == characteristicUUID) { return p->name; @@ -1241,7 +1456,7 @@ std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID * @brief Return a string representation of an esp_gattc_service_elem_t. * @return A string representation of an esp_gattc_service_elem_t. */ -std::string BLEUtils::gattcServiceElementToString(esp_gattc_service_elem_t *pGATTCServiceElement) { +std::string BLEUtils::gattcServiceElementToString(esp_gattc_service_elem_t* pGATTCServiceElement) { std::stringstream ss; ss << "[uuid: " << BLEUUID(pGATTCServiceElement->uuid).toString() << diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index d3c44a9c..cc556ec1 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -28,7 +28,7 @@ #include -static char tag[]= "WiFi"; +static const char* LOG_TAG = "WiFi"; /* @@ -48,16 +48,20 @@ WiFi::WiFi() : ip(0) , gw(0) , netmask(0) - , wifiEventHandler(nullptr) + , m_wifiEventHandler(nullptr) { - wifiEventHandler = new WiFiEventHandler(); -} + ::nvs_flash_init(); + wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); + esp_wifi_init(&config); + ::tcpip_adapter_init(); + m_wifiEventHandler = new WiFiEventHandler(); +} // WiFi /** * @brief Deletes the event handler that was used by the class */ WiFi::~WiFi() { - delete wifiEventHandler; + delete m_wifiEventHandler; } /** @@ -88,7 +92,7 @@ void WiFi::addDNSServer(const char* ip) { } // addDNSServer void WiFi::addDNSServer(ip_addr_t ip) { - ESP_LOGD(tag, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); ::dns_setserver(m_dnsCount, &ip); m_dnsCount++; m_dnsCount %= 2; @@ -121,7 +125,7 @@ void WiFi::setDNSServer(int numdns, const char* ip) { } // setDNSServer void WiFi::setDNSServer(int numdns, ip_addr_t ip) { - ESP_LOGD(tag, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); ::dns_setserver(numdns, &ip); } // setDNSServer @@ -135,44 +139,45 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { * @return N/A. */ void WiFi::connectAP(const std::string& ssid, const std::string& password){ - ::nvs_flash_init(); - ::tcpip_adapter_init(); - if (ip != 0 && gw != 0 && netmask != 0) { - ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client + ESP_LOGD(LOG_TAG, ">> connectAP"); - tcpip_adapter_ip_info_t ipInfo; - ipInfo.ip.addr = ip; - ipInfo.gw.addr = gw; - ipInfo.netmask.addr = netmask; + if (ip != 0 && gw != 0 && netmask != 0) { + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client - ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - } + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + } - ESP_ERROR_CHECK( esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); - ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); - wifi_config_t sta_config; - ::memset(&sta_config, 0, sizeof(sta_config)); - ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); - ::memcpy(sta_config.sta.password, password.data(), password.size()); - sta_config.sta.bssid_set = 0; - ESP_ERROR_CHECK(::esp_wifi_set_config(WIFI_IF_STA, &sta_config)); - ESP_ERROR_CHECK(::esp_wifi_start()); - - ESP_ERROR_CHECK(::esp_wifi_connect()); + + ESP_ERROR_CHECK( esp_event_loop_init(m_wifiEventHandler->getEventHandler(), m_wifiEventHandler)); + ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); + wifi_config_t sta_config; + ::memset(&sta_config, 0, sizeof(sta_config)); + ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); + ::memcpy(sta_config.sta.password, password.data(), password.size()); + sta_config.sta.bssid_set = 0; + ESP_ERROR_CHECK(::esp_wifi_set_config(WIFI_IF_STA, &sta_config)); + ESP_ERROR_CHECK(::esp_wifi_start()); + + ESP_ERROR_CHECK(::esp_wifi_connect()); + ESP_LOGD(LOG_TAG, "<< connectAP"); } // connectAP /** * @brief Dump diagnostics to the log. */ void WiFi::dump() { - ESP_LOGD(tag, "WiFi Dump"); - ESP_LOGD(tag, "---------"); + ESP_LOGD(LOG_TAG, "WiFi Dump"); + ESP_LOGD(LOG_TAG, "---------"); char ipAddrStr[30]; ip_addr_t ip = ::dns_getserver(0); inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); - ESP_LOGD(tag, "DNS Server[0]: %s", ipAddrStr); + ESP_LOGD(LOG_TAG, "DNS Server[0]: %s", ipAddrStr); } // dump /** @@ -227,10 +232,10 @@ struct in_addr WiFi::getHostByName(const char* hostName) { struct hostent *he = gethostbyname(hostName); if (he == nullptr) { retAddr.s_addr = 0; - ESP_LOGD(tag, "Unable to resolve %s - %d", hostName, h_errno); + ESP_LOGD(LOG_TAG, "Unable to resolve %s - %d", hostName, h_errno); } else { retAddr = *(struct in_addr *)(he->h_addr_list[0]); - ESP_LOGD(tag, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); + ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); } return retAddr; } // getHostByName @@ -306,7 +311,7 @@ std::string WiFi::getStaSSID() { std::vector WiFi::scan() { ::nvs_flash_init(); ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); + ESP_ERROR_CHECK(esp_event_loop_init(m_wifiEventHandler->getEventHandler(), m_wifiEventHandler)); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); @@ -317,11 +322,11 @@ std::vector WiFi::scan() { conf.show_hidden = true; esp_err_t rc = ::esp_wifi_scan_start(&conf, true); if (rc != ESP_OK) { - ESP_LOGE(tag, "esp_wifi_scan_start: %d", rc); + ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); } uint16_t apCount; rc = ::esp_wifi_scan_get_ap_num(&apCount); - ESP_LOGD(tag, "Count of found access points: %d", apCount); + ESP_LOGD(LOG_TAG, "Count of found access points: %d", apCount); wifi_ap_record_t *list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, list)); @@ -348,7 +353,7 @@ std::vector WiFi::scan() { void WiFi::startAP(const std::string& ssid, const std::string& password) { ::nvs_flash_init(); ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); + ESP_ERROR_CHECK(esp_event_loop_init(m_wifiEventHandler->getEventHandler(), m_wifiEventHandler)); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); @@ -368,6 +373,15 @@ void WiFi::startAP(const std::string& ssid, const std::string& password) { } // startAP +/** + * @brief Set the event handler to use to process detected events. + * @param[in] wifiEventHandler The class that will be used to process events. + */ +void WiFi::setWifiEventHandler(WiFiEventHandler *wifiEventHandler) { + this->m_wifiEventHandler = wifiEventHandler; +} // setWifiEventHandler + + /** * @brief Set the IP info and enable DHCP if ip != 0. If called with ip == 0 then DHCP is enabled. * If called with bad values it will do nothing. @@ -389,6 +403,8 @@ void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::st setIPInfo(ip.c_str(), gw.c_str(), netmask.c_str()); } // setIPInfo + + void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { uint32_t new_ip; uint32_t new_gw; @@ -405,15 +421,22 @@ void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { setIPInfo(new_ip, new_gw, new_netmask); } // setIPInfo + +/** + * @brief Set the IP Info based on the IP address, gateway and netmask. + * @param [in] ip The IP address of our ESP32. + * @param [in] gw The gateway we should use. + * @param [in] netmask Our TCP/IP netmask value. + */ void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { - this->ip = ip; - this->gw = gw; + this->ip = ip; + this->gw = gw; this->netmask = netmask; if(ip != 0 && gw != 0 && netmask != 0) { tcpip_adapter_ip_info_t ipInfo; - ipInfo.ip.addr = ip; - ipInfo.gw.addr = gw; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; ipInfo.netmask.addr = netmask; ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); @@ -422,7 +445,8 @@ void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { ip = 0; ::tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); } -} +} // setIPInfo + /** * @brief Return a string representation of the WiFi access point record. diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index b7499c48..1299f9f1 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -70,9 +70,9 @@ class WiFiAPRecord { std::string toString(); private: - uint8_t m_bssid[6]; - int8_t m_rssi; - std::string m_ssid; + uint8_t m_bssid[6]; + int8_t m_rssi; + std::string m_ssid; wifi_auth_mode_t m_authMode; }; @@ -106,7 +106,7 @@ class WiFi { uint32_t ip; uint32_t gw; uint32_t netmask; - WiFiEventHandler *wifiEventHandler; + WiFiEventHandler *m_wifiEventHandler; public: WiFi(); @@ -133,15 +133,7 @@ class WiFi { void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); void setIPInfo(const char* ip, const char* gw, const char* netmask); void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); - - /** - * Set the event handler to use to process detected events. - * @param[in] wifiEventHandler The class that will be used to process events. - */ - void setWifiEventHandler(WiFiEventHandler *wifiEventHandler) { - delete this->wifiEventHandler; - this->wifiEventHandler = wifiEventHandler; - } + void setWifiEventHandler(WiFiEventHandler *wifiEventHandler); private: uint8_t m_dnsCount=0; //char *m_dnsServer = nullptr; diff --git a/cpp_utils/WiFiEventHandler.cpp b/cpp_utils/WiFiEventHandler.cpp index 59519ed4..405058ad 100644 --- a/cpp_utils/WiFiEventHandler.cpp +++ b/cpp_utils/WiFiEventHandler.cpp @@ -13,7 +13,7 @@ #include #include "sdkconfig.h" -static char tag[] = "WiFiEventHandler"; +static const char* LOG_TAG = "WiFiEventHandler"; /** * @brief The entry point into the event handler. @@ -23,54 +23,74 @@ static char tag[] = "WiFiEventHandler"; * @param [in] event * @return ESP_OK if the event was handled otherwise an error. */ -esp_err_t WiFiEventHandler::eventHandler(void *ctx, system_event_t *event) { - ESP_LOGD(tag, "eventHandler called"); - WiFiEventHandler *pWiFiEventHandler = (WiFiEventHandler *)ctx; - if (ctx == nullptr) { - ESP_LOGD(tag, "No context"); - return ESP_OK; +esp_err_t WiFiEventHandler::eventHandler(void* ctx, system_event_t* event) { + ESP_LOGD(LOG_TAG, ">> eventHandler called: ctx=0x%x, event=0x%x", (uint32_t)ctx, (uint32_t)event); + WiFiEventHandler *pWiFiEventHandler = (WiFiEventHandler *)ctx; + if (ctx == nullptr) { + ESP_LOGD(LOG_TAG, "No context"); + return ESP_OK; + } + + esp_err_t rc = ESP_OK; + switch(event->event_id) { + case SYSTEM_EVENT_AP_START: { + rc = pWiFiEventHandler->apStart(); + break; + } + case SYSTEM_EVENT_AP_STOP: { + rc = pWiFiEventHandler->apStop(); + break; + } + case SYSTEM_EVENT_STA_CONNECTED: { + rc = pWiFiEventHandler->staConnected(); + break; + } + + case SYSTEM_EVENT_STA_DISCONNECTED: { + rc = pWiFiEventHandler->staDisconnected(); + break; + } + + case SYSTEM_EVENT_STA_GOT_IP: { + rc = pWiFiEventHandler->staGotIp(event->event_info.got_ip); + break; + } + + case SYSTEM_EVENT_STA_START: { + rc = pWiFiEventHandler->staStart(); + break; + } + + case SYSTEM_EVENT_STA_STOP: { + rc = pWiFiEventHandler->staStop(); + break; + } + + case SYSTEM_EVENT_WIFI_READY: { + rc = pWiFiEventHandler->wifiReady(); + break; + } + + default: + break; } - esp_err_t rc = ESP_OK; - switch(event->event_id) { - - case SYSTEM_EVENT_AP_START: - rc = pWiFiEventHandler->apStart(); - break; - case SYSTEM_EVENT_AP_STOP: - rc = pWiFiEventHandler->apStop(); - break; - case SYSTEM_EVENT_STA_CONNECTED: - rc = pWiFiEventHandler->staConnected(); - break; - case SYSTEM_EVENT_STA_DISCONNECTED: - rc = pWiFiEventHandler->staDisconnected(); - break; - case SYSTEM_EVENT_STA_GOT_IP: - rc = pWiFiEventHandler->staGotIp(event->event_info.got_ip); - break; - case SYSTEM_EVENT_STA_START: - rc = pWiFiEventHandler->staStart(); - break; - case SYSTEM_EVENT_STA_STOP: - rc = pWiFiEventHandler->staStop(); - break; - case SYSTEM_EVENT_WIFI_READY: - rc = pWiFiEventHandler->wifiReady(); - break; - default: - break; - } - if (pWiFiEventHandler->nextHandler != nullptr) { + + if (pWiFiEventHandler->m_nextHandler != nullptr) { printf("Found a next handler\n"); - rc = eventHandler(pWiFiEventHandler->nextHandler, event); + rc = eventHandler(pWiFiEventHandler->m_nextHandler, event); } else { //printf("NOT Found a next handler\n"); } return rc; -} +} // eventHandler + +/** + * @brief Constructor + */ WiFiEventHandler::WiFiEventHandler() { -} + m_nextHandler = nullptr; +} // WiFiEventHandler /** @@ -79,7 +99,9 @@ WiFiEventHandler::WiFiEventHandler() { * @return The event handler function. */ system_event_cb_t WiFiEventHandler::getEventHandler() { - return eventHandler; + ESP_LOGD(LOG_TAG, ">> getEventHandler()"); + ESP_LOGD(LOG_TAG, "<< getEventHandler: 0x%x", (uint32_t)eventHandler); + return eventHandler; } // getEventHandler @@ -90,65 +112,74 @@ system_event_cb_t WiFiEventHandler::getEventHandler() { * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { - ESP_LOGD(tag, "default staGotIp"); + ESP_LOGD(LOG_TAG, "default staGotIp"); return ESP_OK; } // staGotIp + /** * @brief Handle the Access Point started event. * Handle an indication that the ESP32 has started being an access point. * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStart() { - ESP_LOGD(tag, "default apStart"); + ESP_LOGD(LOG_TAG, "default apStart"); return ESP_OK; } // apStart + /** * @brief Handle the Access Point stop event. * Handle an indication that the ESP32 has stopped being an access point. * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStop() { - ESP_LOGD(tag, "default apStop"); + ESP_LOGD(LOG_TAG, "default apStop"); return ESP_OK; } // apStop + esp_err_t WiFiEventHandler::wifiReady() { - ESP_LOGD(tag, "default wifiReady"); + ESP_LOGD(LOG_TAG, "default wifiReady"); return ESP_OK; } // wifiReady + esp_err_t WiFiEventHandler::staStart() { - ESP_LOGD(tag, "default staStart"); + ESP_LOGD(LOG_TAG, "default staStart"); return ESP_OK; } // staStart + esp_err_t WiFiEventHandler::staStop() { - ESP_LOGD(tag, "default staStop"); + ESP_LOGD(LOG_TAG, "default staStop"); return ESP_OK; } // staStop + esp_err_t WiFiEventHandler::staConnected() { - ESP_LOGD(tag, "default staConnected"); + ESP_LOGD(LOG_TAG, "default staConnected"); return ESP_OK; } // staConnected + esp_err_t WiFiEventHandler::staDisconnected() { - ESP_LOGD(tag, "default staDisconnected"); + ESP_LOGD(LOG_TAG, "default staDisconnected"); return ESP_OK; } // staDisconnected + esp_err_t WiFiEventHandler::apStaConnected() { - ESP_LOGD(tag, "default apStaConnected"); + ESP_LOGD(LOG_TAG, "default apStaConnected"); return ESP_OK; } // apStaConnected + esp_err_t WiFiEventHandler::apStaDisconnected() { - ESP_LOGD(tag, "default apStaDisconnected"); + ESP_LOGD(LOG_TAG, "default apStaDisconnected"); return ESP_OK; } // apStaDisconnected + WiFiEventHandler::~WiFiEventHandler() { - delete nextHandler; } // ~WiFiEventHandler diff --git a/cpp_utils/WiFiEventHandler.h b/cpp_utils/WiFiEventHandler.h index 0596e92c..fe39e332 100644 --- a/cpp_utils/WiFiEventHandler.h +++ b/cpp_utils/WiFiEventHandler.h @@ -98,7 +98,7 @@ class WiFiEventHandler { * @return The next WiFi event handler in the chain or nullptr if there is none. */ WiFiEventHandler *getNextHandler() { - return nextHandler; + return m_nextHandler; } /** @@ -106,12 +106,12 @@ class WiFiEventHandler { * @param [in] nextHandler The next WiFi event handler in the chain. */ void setNextHandler(WiFiEventHandler* nextHandler) { - this->nextHandler = nextHandler; + this->m_nextHandler = nextHandler; } private: - WiFiEventHandler *nextHandler = nullptr; - static esp_err_t eventHandler(void *ctx, system_event_t *event); + WiFiEventHandler *m_nextHandler; + static esp_err_t eventHandler(void* ctx, system_event_t* event); }; #endif /* MAIN_WIFIEVENTHANDLER_H_ */ From 5b074fbe93daac2bff459f4caad853f52b2aa633 Mon Sep 17 00:00:00 2001 From: kolban Date: Thu, 28 Sep 2017 08:45:35 -0500 Subject: [PATCH 076/381] Fixes for issue #93 --- cpp_utils/BLEScan.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index c5a41fc1..54fcac01 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -203,13 +203,10 @@ BLEScanResults BLEScan::start(uint32_t duration) { ESP_LOGD(LOG_TAG, ">> start(%d)", duration); m_semaphoreScanEnd.take("start"); - ESP_LOGD(LOG_TAG, "A"); - m_scanResults.m_vectorAdvertisedDevices.empty(); - ESP_LOGD(LOG_TAG, "B"); + m_scanResults.m_vectorAdvertisedDevices.clear(); esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); - ESP_LOGD(LOG_TAG, "C"); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); From 2f550acbd476b7a8c504833cbf092efeb39ca6f9 Mon Sep 17 00:00:00 2001 From: kolban Date: Thu, 28 Sep 2017 08:48:54 -0500 Subject: [PATCH 077/381] Changes for #92 --- cpp_utils/BLEUtils.cpp | 271 +++++++++++++++++++++++++++++++++++++++++ cpp_utils/BLEUtils.h | 1 + 2 files changed, 272 insertions(+) diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 4c088d66..7a3cdf1a 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -31,6 +31,264 @@ static std::map g_addressMap; static std::map g_connIdMap; */ +typedef struct { + uint32_t assignedNumber; + std::string name; +} member_t; + +static const member_t members_ids[] = { + {0xFE08, "Microsoft"}, + {0xFE09, "Pillsy, Inc."}, + {0xFE0A, "ruwido austria gmbh"}, + {0xFE0B, "ruwido austria gmbh"}, + {0xFE0C, "Procter & Gamble"}, + {0xFE0D, "Procter & Gamble"}, + {0xFE0E, "Setec Pty Ltd"}, + {0xFE0F, "Philips Lighting B.V."}, + {0xFE10, "Lapis Semiconductor Co., Ltd."}, + {0xFE11, "GMC-I Messtechnik GmbH"}, + {0xFE12, "M-Way Solutions GmbH"}, + {0xFE13, "Apple Inc."}, + {0xFE14, "Flextronics International USA Inc."}, + {0xFE15, "Amazon Fulfillment Services, Inc."}, + {0xFE16, "Footmarks, Inc."}, + {0xFE17, "Telit Wireless Solutions GmbH"}, + {0xFE18, "Runtime, Inc."}, + {0xFE19, "Google Inc."}, + {0xFE1A, "Tyto Life LLC"}, + {0xFE1B, "Tyto Life LLC"}, + {0xFE1C, "NetMedia, Inc."}, + {0xFE1D, "Illuminati Instrument Corporation"}, + {0xFE1E, "Smart Innovations Co., Ltd"}, + {0xFE1F, "Garmin International, Inc."}, + {0xFE20, "Emerson"}, + {0xFE21, "Bose Corporation"}, + {0xFE22, "Zoll Medical Corporation"}, + {0xFE23, "Zoll Medical Corporation"}, + {0xFE24, "August Home Inc"}, + {0xFE25, "Apple, Inc. "}, + {0xFE26, "Google Inc."}, + {0xFE27, "Google Inc."}, + {0xFE28, "Ayla Networks"}, + {0xFE29, "Gibson Innovations"}, + {0xFE2A, "DaisyWorks, Inc."}, + {0xFE2B, "ITT Industries"}, + {0xFE2C, "Google Inc."}, + {0xFE2D, "SMART INNOVATION Co.,Ltd"}, + {0xFE2E, "ERi,Inc."}, + {0xFE2F, "CRESCO Wireless, Inc"}, + {0xFE30, "Volkswagen AG"}, + {0xFE31, "Volkswagen AG"}, + {0xFE32, "Pro-Mark, Inc."}, + {0xFE33, "CHIPOLO d.o.o."}, + {0xFE34, "SmallLoop LLC"}, + {0xFE35, "HUAWEI Technologies Co., Ltd"}, + {0xFE36, "HUAWEI Technologies Co., Ltd"}, + {0xFE37, "Spaceek LTD"}, + {0xFE38, "Spaceek LTD"}, + {0xFE39, "TTS Tooltechnic Systems AG & Co. KG"}, + {0xFE3A, "TTS Tooltechnic Systems AG & Co. KG"}, + {0xFE3B, "Dolby Laboratories"}, + {0xFE3C, "Alibaba"}, + {0xFE3D, "BD Medical"}, + {0xFE3E, "BD Medical"}, + {0xFE3F, "Friday Labs Limited"}, + {0xFE40, "Inugo Systems Limited"}, + {0xFE41, "Inugo Systems Limited"}, + {0xFE42, "Nets A/S "}, + {0xFE43, "Andreas Stihl AG & Co. KG"}, + {0xFE44, "SK Telecom "}, + {0xFE45, "Snapchat Inc"}, + {0xFE46, "B&O Play A/S "}, + {0xFE47, "General Motors"}, + {0xFE48, "General Motors"}, + {0xFE49, "SenionLab AB"}, + {0xFE4A, "OMRON HEALTHCARE Co., Ltd."}, + {0xFE4B, "Philips Lighting B.V."}, + {0xFE4C, "Volkswagen AG"}, + {0xFE4D, "Casambi Technologies Oy"}, + {0xFE4E, "NTT docomo"}, + {0xFE4F, "Molekule, Inc."}, + {0xFE50, "Google Inc."}, + {0xFE51, "SRAM"}, + {0xFE52, "SetPoint Medical"}, + {0xFE53, "3M"}, + {0xFE54, "Motiv, Inc."}, + {0xFE55, "Google Inc."}, + {0xFE56, "Google Inc."}, + {0xFE57, "Dotted Labs"}, + {0xFE58, "Nordic Semiconductor ASA"}, + {0xFE59, "Nordic Semiconductor ASA"}, + {0xFE5A, "Chronologics Corporation"}, + {0xFE5B, "GT-tronics HK Ltd"}, + {0xFE5C, "million hunters GmbH"}, + {0xFE5D, "Grundfos A/S"}, + {0xFE5E, "Plastc Corporation"}, + {0xFE5F, "Eyefi, Inc."}, + {0xFE60, "Lierda Science & Technology Group Co., Ltd."}, + {0xFE61, "Logitech International SA"}, + {0xFE62, "Indagem Tech LLC"}, + {0xFE63, "Connected Yard, Inc."}, + {0xFE64, "Siemens AG"}, + {0xFE65, "CHIPOLO d.o.o."}, + {0xFE66, "Intel Corporation"}, + {0xFE67, "Lab Sensor Solutions"}, + {0xFE68, "Qualcomm Life Inc"}, + {0xFE69, "Qualcomm Life Inc"}, + {0xFE6A, "Kontakt Micro-Location Sp. z o.o."}, + {0xFE6B, "TASER International, Inc."}, + {0xFE6C, "TASER International, Inc."}, + {0xFE6D, "The University of Tokyo"}, + {0xFE6E, "The University of Tokyo"}, + {0xFE6F, "LINE Corporation"}, + {0xFE70, "Beijing Jingdong Century Trading Co., Ltd."}, + {0xFE71, "Plume Design Inc"}, + {0xFE72, "St. Jude Medical, Inc."}, + {0xFE73, "St. Jude Medical, Inc."}, + {0xFE74, "unwire"}, + {0xFE75, "TangoMe"}, + {0xFE76, "TangoMe"}, + {0xFE77, "Hewlett-Packard Company"}, + {0xFE78, "Hewlett-Packard Company"}, + {0xFE79, "Zebra Technologies"}, + {0xFE7A, "Bragi GmbH"}, + {0xFE7B, "Orion Labs, Inc."}, + {0xFE7C, "Telit Wireless Solutions (Formerly Stollmann E+V GmbH)"}, + {0xFE7D, "Aterica Health Inc."}, + {0xFE7E, "Awear Solutions Ltd"}, + {0xFE7F, "Doppler Lab"}, + {0xFE80, "Doppler Lab"}, + {0xFE81, "Medtronic Inc."}, + {0xFE82, "Medtronic Inc."}, + {0xFE83, "Blue Bite"}, + {0xFE84, "RF Digital Corp"}, + {0xFE85, "RF Digital Corp"}, + {0xFE86, "HUAWEI Technologies Co., Ltd. ( )"}, + {0xFE87, "Qingdao Yeelink Information Technology Co., Ltd. ( )"}, + {0xFE88, "SALTO SYSTEMS S.L."}, + {0xFE89, "B&O Play A/S"}, + {0xFE8A, "Apple, Inc."}, + {0xFE8B, "Apple, Inc."}, + {0xFE8C, "TRON Forum"}, + {0xFE8D, "Interaxon Inc."}, + {0xFE8E, "ARM Ltd"}, + {0xFE8F, "CSR"}, + {0xFE90, "JUMA"}, + {0xFE91, "Shanghai Imilab Technology Co.,Ltd"}, + {0xFE92, "Jarden Safety & Security"}, + {0xFE93, "OttoQ Inc."}, + {0xFE94, "OttoQ Inc."}, + {0xFE95, "Xiaomi Inc."}, + {0xFE96, "Tesla Motor Inc."}, + {0xFE97, "Tesla Motor Inc."}, + {0xFE98, "Currant, Inc."}, + {0xFE99, "Currant, Inc."}, + {0xFE9A, "Estimote"}, + {0xFE9B, "Samsara Networks, Inc"}, + {0xFE9C, "GSI Laboratories, Inc."}, + {0xFE9D, "Mobiquity Networks Inc"}, + {0xFE9E, "Dialog Semiconductor B.V."}, + {0xFE9F, "Google Inc."}, + {0xFEA0, "Google Inc."}, + {0xFEA1, "Intrepid Control Systems, Inc."}, + {0xFEA2, "Intrepid Control Systems, Inc."}, + {0xFEA3, "ITT Industries"}, + {0xFEA4, "Paxton Access Ltd"}, + {0xFEA5, "GoPro, Inc."}, + {0xFEA6, "GoPro, Inc."}, + {0xFEA7, "UTC Fire and Security"}, + {0xFEA8, "Savant Systems LLC"}, + {0xFEA9, "Savant Systems LLC"}, + {0xFEAA, "Google Inc."}, + {0xFEAB, "Nokia Corporation"}, + {0xFEAC, "Nokia Corporation"}, + {0xFEAD, "Nokia Corporation"}, + {0xFEAE, "Nokia Corporation"}, + {0xFEAF, "Nest Labs Inc."}, + {0xFEB0, "Nest Labs Inc."}, + {0xFEB1, "Electronics Tomorrow Limited"}, + {0xFEB2, "Microsoft Corporation"}, + {0xFEB3, "Taobao"}, + {0xFEB4, "WiSilica Inc."}, + {0xFEB5, "WiSilica Inc."}, + {0xFEB6, "Vencer Co, Ltd"}, + {0xFEB7, "Facebook, Inc."}, + {0xFEB8, "Facebook, Inc."}, + {0xFEB9, "LG Electronics"}, + {0xFEBA, "Tencent Holdings Limited"}, + {0xFEBB, "adafruit industries"}, + {0xFEBC, "Dexcom, Inc. "}, + {0xFEBD, "Clover Network, Inc."}, + {0xFEBE, "Bose Corporation"}, + {0xFEBF, "Nod, Inc."}, + {0xFEC0, "KDDI Corporation"}, + {0xFEC1, "KDDI Corporation"}, + {0xFEC2, "Blue Spark Technologies, Inc."}, + {0xFEC3, "360fly, Inc."}, + {0xFEC4, "PLUS Location Systems"}, + {0xFEC5, "Realtek Semiconductor Corp."}, + {0xFEC6, "Kocomojo, LLC"}, + {0xFEC7, "Apple, Inc."}, + {0xFEC8, "Apple, Inc."}, + {0xFEC9, "Apple, Inc."}, + {0xFECA, "Apple, Inc."}, + {0xFECB, "Apple, Inc."}, + {0xFECC, "Apple, Inc."}, + {0xFECD, "Apple, Inc."}, + {0xFECE, "Apple, Inc."}, + {0xFECF, "Apple, Inc."}, + {0xFED0, "Apple, Inc."}, + {0xFED1, "Apple, Inc."}, + {0xFED2, "Apple, Inc."}, + {0xFED3, "Apple, Inc."}, + {0xFED4, "Apple, Inc."}, + {0xFED5, "Plantronics Inc."}, + {0xFED6, "Broadcom Corporation"}, + {0xFED7, "Broadcom Corporation"}, + {0xFED8, "Google Inc."}, + {0xFED9, "Pebble Technology Corporation"}, + {0xFEDA, "ISSC Technologies Corporation"}, + {0xFEDB, "Perka, Inc."}, + {0xFEDC, "Jawbone"}, + {0xFEDD, "Jawbone"}, + {0xFEDE, "Coin, Inc."}, + {0xFEDF, "Design SHIFT"}, + {0xFEE0, "Anhui Huami Information Technology Co."}, + {0xFEE1, "Anhui Huami Information Technology Co."}, + {0xFEE2, "Anki, Inc."}, + {0xFEE3, "Anki, Inc."}, + {0xFEE4, "Nordic Semiconductor ASA"}, + {0xFEE5, "Nordic Semiconductor ASA"}, + {0xFEE6, "Silvair, Inc."}, + {0xFEE7, "Tencent Holdings Limited"}, + {0xFEE8, "Quintic Corp."}, + {0xFEE9, "Quintic Corp."}, + {0xFEEA, "Swirl Networks, Inc."}, + {0xFEEB, "Swirl Networks, Inc."}, + {0xFEEC, "Tile, Inc."}, + {0xFEED, "Tile, Inc."}, + {0xFEEE, "Polar Electro Oy"}, + {0xFEEF, "Polar Electro Oy"}, + {0xFEF0, "Intel"}, + {0xFEF1, "CSR"}, + {0xFEF2, "CSR"}, + {0xFEF3, "Google Inc."}, + {0xFEF4, "Google Inc."}, + {0xFEF5, "Dialog Semiconductor GmbH"}, + {0xFEF6, "Wicentric, Inc."}, + {0xFEF7, "Aplix Corporation"}, + {0xFEF8, "Aplix Corporation"}, + {0xFEF9, "PayPal, Inc."}, + {0xFEFA, "PayPal, Inc."}, + {0xFEFB, "Telit Wireless Solutions (Formerly Stollmann E+V GmbH)"}, + {0xFEFC, "Gimbal, Inc."}, + {0xFEFD, "Gimbal, Inc."}, + {0xFEFE, "GN ReSound A/S"}, + {0xFEFF, "GN Netcom"}, + {0, "" } +}; + + typedef struct { uint32_t assignedNumber; std::string name; @@ -1588,6 +1846,19 @@ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { } // gattStatusToString + +std::string BLEUtils::getMember(uint32_t memberId) { + member_t* p = (member_t *)members_ids; + + while (p->name.length() > 0) { + if (p->assignedNumber == memberId) { + return p->name; + } + p++; + } + return "Unknown"; +} + /** * @brief convert a GAP search event to a string. * @param [in] searchEvt diff --git a/cpp_utils/BLEUtils.h b/cpp_utils/BLEUtils.h index b49d0f8f..96066af9 100644 --- a/cpp_utils/BLEUtils.h +++ b/cpp_utils/BLEUtils.h @@ -38,6 +38,7 @@ class BLEUtils { static void registerByAddress(BLEAddress address, BLEClient* pDevice); static void registerByConnId(uint16_t conn_id, BLEClient* pDevice); static std::string gattCharacteristicUUIDToString(uint32_t characteristicUUID); + static std::string getMember(uint32_t memberId); static std::string buildPrintData(uint8_t* source, size_t length); static void dumpGattClientEvent( esp_gattc_cb_event_t event, From bf61cd7f40c81cdd950c7784e9b88a6bdbe1d434 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Fri, 29 Sep 2017 14:25:25 +0300 Subject: [PATCH 078/381] Small annoyance i accidentally created --- cpp_utils/WebServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 2c975939..64659aa7 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -109,7 +109,7 @@ static std::string mongoose_eventToString(int event) { static void dumpHttpMessage(struct http_message *pHttpMessage) { ESP_LOGD(tag, "HTTP Message"); - ESP_LOGD(tag, "Message: %.*s", (int)pHttpMessage->uri.len, pHttpMessage->message.p); + ESP_LOGD(tag, "Message: %.*s", (int)pHttpMessage->message.len, pHttpMessage->message.p); } /* From a78c8cb8d116a5d81c4bab015439bde82f2d31cd Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Fri, 29 Sep 2017 16:03:19 +0300 Subject: [PATCH 079/381] Add sending files by chunks support. Now if the file is too big, the WebServer will send it in chunks instead. You can specify how big is too big in the definition on top of WebServer.h --- cpp_utils/WebServer.cpp | 73 ++++++++++++++++++++++++++++++++++------- cpp_utils/WebServer.h | 11 +++++-- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 2c975939..b9e18df1 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -139,6 +139,13 @@ static void mongoose_event_handler_web_server( } ESP_LOGD(tag, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); switch (event) { + case MG_EV_SEND: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + WebServer *pWebServer = pWebServerUserData->pWebServer; + pWebServer->continueConnection(mgConnection); + break; + } + case MG_EV_HTTP_REQUEST: { struct http_message *message = (struct http_message *) eventData; dumpHttpMessage(message); @@ -315,9 +322,7 @@ void WebServer::addPathHandler(const std::string& method, const std::string& pat m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); } // addPathHandler -void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr, - void (* handler)(WebServer::HTTPRequest* pHttpRequest, - WebServer::HTTPResponse* pHttpResponse)) { +void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr, void (* handler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { m_pathHandlers.push_back(PathHandler(std::move(method), pathExpr, handler)); } // addPathHandler @@ -482,6 +487,27 @@ void WebServer::HTTPResponse::sendData(const char* pData, size_t length) { sendData((uint8_t*) pData, length); } // sendData +void WebServer::HTTPResponse::sendChunkHead() { + if(m_dataSent) { + ESP_LOGE(tag, "HTTPResponse: Chunk headers already sent! Attempt to send again/more."); + } + m_dataSent = true; + mg_send_head(m_nc, m_status, -1, m_headers.c_str()); +} + +void WebServer::HTTPResponse::sendChunk(const char* pData, size_t length) { + mg_send_http_chunk(m_nc, pData, length); +} // sendChunkHead + +void WebServer::HTTPResponse::closeConnection() { + m_nc->flags |= MG_F_SEND_AND_CLOSE; +} + + +void WebServer::HTTPResponse::sendData(const char* pData, size_t length) { + sendData((uint8_t*) pData, length); +} + /** * @brief Set the headers to be sent in the HTTP response. * @param [in] headers The complete set of headers to send to the caller. @@ -564,15 +590,20 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m filePath += httpResponse.getRootPath(); filePath.append(message->uri.p, message->uri.len); ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); - FILE *file = fopen(filePath.c_str(), "rb"); + FILE* file = fopen(filePath.c_str(), "rb"); if (file != nullptr) { - fseek(file, 0L, SEEK_END); - size_t length = ftell(file); - fseek(file, 0L, SEEK_SET); - uint8_t *pData = (uint8_t *)malloc(length); - fread(pData, length, 1, file); - fclose(file); - httpResponse.sendData(pData, length); + auto pData = (uint8_t*)malloc(MAX_CHUNK_LENGTH); + size_t read = fread(pData, 1, MAX_CHUNK_LENGTH, file); + + if(read >= MAX_CHUNK_LENGTH) { + httpResponse.sendChunkHead(); + httpResponse.sendChunk((char*)pData, read); + fclose(unfinishedConnection[mgConnection->sock]); + unfinishedConnection[mgConnection->sock] = file; + } else { + fclose(file); + httpResponse.sendData(pData, read); + } free(pData); } else { // Handle unable to open file @@ -581,6 +612,26 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m } } // processRequest +void WebServer::continueConnection(struct mg_connection* mgConnection) { + if(unfinishedConnection.count(mgConnection->sock) == 0) { + return; + } + + HTTPResponse httpResponse = HTTPResponse(mgConnection); + + FILE* file = unfinishedConnection[mgConnection->sock]; + auto pData = (char*) malloc(MAX_CHUNK_LENGTH); + size_t length = fread(pData, MAX_CHUNK_LENGTH, 1, file); + + httpResponse.sendChunk(pData, length); + if(length < MAX_CHUNK_LENGTH) { + fclose(file); + httpResponse.closeConnection(); + unfinishedConnection.erase(mgConnection->sock); + } + free(pData); +} + /** * @brief Construct an instance of a PathHandler. diff --git a/cpp_utils/WebServer.h b/cpp_utils/WebServer.h index 7bb1e585..ef879f91 100644 --- a/cpp_utils/WebServer.h +++ b/cpp_utils/WebServer.h @@ -12,9 +12,12 @@ #include #include #include "sdkconfig.h" + #ifdef CONFIG_MONGOOSE_PRESENT #include +#define MAX_CHUNK_LENGTH 4090 // 4 kilobytes + class WebServer; /** @@ -59,6 +62,9 @@ class WebServer { const std::string& getRootPath() const; void setRootPath(const std::string& path); void setRootPath(std::string&& path); + void sendChunkHead(); + void sendChunk(const char* pData, size_t length); + void closeConnection(); private: struct mg_connection *m_nc; std::string m_rootPath; @@ -90,8 +96,7 @@ class WebServer { */ class HTTPMultiPart { public: - virtual ~HTTPMultiPart() { - }; + virtual ~HTTPMultiPart() = default; virtual void begin(const std::string& varName, const std::string& fileName); virtual void end(); virtual void data(const std::string& data); @@ -191,11 +196,13 @@ class WebServer { void setWebSocketHandlerFactory(WebSocketHandlerFactory *pWebSocketHandlerFactory); void start(unsigned short port = 80); void processRequest(struct mg_connection *mgConnection, struct http_message *message); + void continueConnection(struct mg_connection* mgConnection); HTTPMultiPartFactory *m_pMultiPartFactory; WebSocketHandlerFactory *m_pWebSocketHandlerFactory; private: std::string m_rootPath; std::vector m_pathHandlers; + std::map unfinishedConnection; }; #endif // CONFIG_MONGOOSE_PRESENT From 90876140650d4f18847cb97e27d7dbb0f8d77bf4 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Fri, 29 Sep 2017 17:38:52 +0300 Subject: [PATCH 080/381] Fix to the chunk sending system --- cpp_utils/WebServer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index 3758d73d..ebdd54aa 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -621,13 +621,14 @@ void WebServer::continueConnection(struct mg_connection* mgConnection) { FILE* file = unfinishedConnection[mgConnection->sock]; auto pData = (char*) malloc(MAX_CHUNK_LENGTH); - size_t length = fread(pData, MAX_CHUNK_LENGTH, 1, file); + size_t length = fread(pData, 1, MAX_CHUNK_LENGTH, file); httpResponse.sendChunk(pData, length); if(length < MAX_CHUNK_LENGTH) { fclose(file); httpResponse.closeConnection(); unfinishedConnection.erase(mgConnection->sock); + httpResponse.sendChunk("", 0); } free(pData); } From 7d97376a2865b841947d1bdbfdf9e1931db6ad3d Mon Sep 17 00:00:00 2001 From: kolban Date: Fri, 29 Sep 2017 10:24:40 -0500 Subject: [PATCH 081/381] Code changes for #103 --- cpp_utils/BLEAdvertisedDevice.cpp | 4 +- cpp_utils/BLEAdvertisedDevice.h | 2 +- cpp_utils/BLECharacteristicMap.cpp | 122 +++++++++++++------------- cpp_utils/BLEDevice.cpp | 99 +++++++++++---------- cpp_utils/BLEDevice.h | 27 +++--- cpp_utils/BLERemoteCharacteristic.cpp | 78 +++++++++++----- cpp_utils/BLERemoteCharacteristic.h | 6 ++ cpp_utils/BLEScan.cpp | 42 ++------- cpp_utils/BLEScan.h | 17 ++-- cpp_utils/BLEServer.cpp | 11 ++- cpp_utils/BLEServerCallbacks.cpp | 22 ----- cpp_utils/BLEService.cpp | 18 +--- cpp_utils/BLEUUID.h | 2 +- 13 files changed, 230 insertions(+), 220 deletions(-) delete mode 100644 cpp_utils/BLEServerCallbacks.cpp diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index f1752eda..5074b811 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -60,7 +60,7 @@ BLEAddress BLEAdvertisedDevice::getAddress() { * * @return The appearance of the advertised device. */ -uint16_t BLEAdvertisedDevice::getApperance() { +uint16_t BLEAdvertisedDevice::getAppearance() { return m_appearance; } @@ -390,7 +390,7 @@ std::string BLEAdvertisedDevice::toString() { std::stringstream ss; ss << "Name: " << getName() << ", Address: " << getAddress().toString(); if (haveAppearance()) { - ss << ", appearance: " << getApperance(); + ss << ", appearance: " << getAppearance(); } if (haveManufacturerData()) { char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)getManufacturerData().data(), getManufacturerData().length()); diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index 2fb26522..fbdeeec7 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -30,7 +30,7 @@ class BLEAdvertisedDevice { BLEAdvertisedDevice(); BLEAddress getAddress(); - uint16_t getApperance(); + uint16_t getAppearance(); std::string getManufacturerData(); std::string getName(); int getRSSI(); diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index f475e835..6ded0a63 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -10,6 +10,17 @@ #include #include "BLEService.h" + +/** + * @brief Return the characteristic by handle. + * @param [in] handle The handle to look up the characteristic. + * @return The characteristic. + */ +BLECharacteristic* BLECharacteristicMap::getByHandle(uint16_t handle) { + return m_handleMap.at(handle); +} // getByHandle + + /** * @brief Return the characteristic by UUID. * @param [in] UUID The UUID to look up the characteristic. @@ -19,6 +30,7 @@ BLECharacteristic* BLECharacteristicMap::getByUUID(const char* uuid) { return getByUUID(BLEUUID(uuid)); } + /** * @brief Return the characteristic by UUID. * @param [in] UUID The UUID to look up the characteristic. @@ -36,26 +48,49 @@ BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) { /** - * @brief Return the characteristic by handle. - * @param [in] handle The handle to look up the characteristic. - * @return The characteristic. + * @brief Get the first characteristic in the map. + * @return The first characteristic in the map. */ -BLECharacteristic* BLECharacteristicMap::getByHandle(uint16_t handle) { - return m_handleMap.at(handle); -} // getByHandle +BLECharacteristic* BLECharacteristicMap::getFirst() { + m_iterator = m_uuidMap.begin(); + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } + BLECharacteristic* pRet = m_iterator->second; + m_iterator++; + return pRet; +} // getFirst /** - * @brief Set the characteristic by UUID. - * @param [in] uuid The uuid of the characteristic. - * @param [in] characteristic The characteristic to cache. - * @return N/A. + * @brief Get the next characteristic in the map. + * @return The next characteristic in the map. */ -void BLECharacteristicMap::setByUUID( - BLEUUID uuid, - BLECharacteristic *pCharacteristic) { - m_uuidMap.insert(std::pair(uuid.toString(), pCharacteristic)); -} // setByUUID +BLECharacteristic* BLECharacteristicMap::getNext() { + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } + BLECharacteristic* pRet = m_iterator->second; + m_iterator++; + return pRet; +} // getNext + + +/** + * @brief Pass the GATT server event onwards to each of the characteristics found in the mapping + * @param [in] event + * @param [in] gatts_if + * @param [in] param + */ +void BLECharacteristicMap::handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param) { + // Invoke the handler for every Service we have. + for (auto &myPair : m_uuidMap) { + myPair.second->handleGATTServerEvent(event, gatts_if, param); + } +} // handleGATTServerEvent /** @@ -70,6 +105,19 @@ void BLECharacteristicMap::setByHandle(uint16_t handle, } // setByHandle +/** + * @brief Set the characteristic by UUID. + * @param [in] uuid The uuid of the characteristic. + * @param [in] characteristic The characteristic to cache. + * @return N/A. + */ +void BLECharacteristicMap::setByUUID( + BLEUUID uuid, + BLECharacteristic *pCharacteristic) { + m_uuidMap.insert(std::pair(uuid.toString(), pCharacteristic)); +} // setByUUID + + /** * @brief Return a string representation of the characteristic map. * @return A string representation of the characteristic map. @@ -89,48 +137,4 @@ std::string BLECharacteristicMap::toString() { } // toString -/** - * @breif Pass the GATT server event onwards to each of the characteristics found in the mapping - * @param [in] event - * @param [in] gatts_if - * @param [in] param - */ -void BLECharacteristicMap::handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { - // Invoke the handler for every Service we have. - for (auto &myPair : m_uuidMap) { - myPair.second->handleGATTServerEvent(event, gatts_if, param); - } -} // handleGATTServerEvent - - -/** - * @brief Get the first characteristic in the map. - * @return The first characteristic in the map. - */ -BLECharacteristic* BLECharacteristicMap::getFirst() { - m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLECharacteristic *pRet = m_iterator->second; - m_iterator++; - return pRet; -} // getFirst - - -/** - * @brief Get the next characteristic in the map. - * @return The next characteristic in the map. - */ -BLECharacteristic* BLECharacteristicMap::getNext() { - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLECharacteristic *pRet = m_iterator->second; - m_iterator++; - return pRet; -} // getNext #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index f383f371..4470a48a 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -7,21 +7,20 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) #include +#include #include #include #include -#include -#include -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -// ESP32 BLE -#include // ESP32 BLE -#include // ESP32 ESP-IDF -#include // ESP32 ESP-IDF -#include // Part of C++ STL -#include -#include +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 ESP-IDF +#include // ESP32 ESP-IDF +#include // Part of C++ Standard library +#include // Part of C++ Standard library +#include // Part of C++ Standard library #include "BLEDevice.h" #include "BLEClient.h" @@ -30,41 +29,54 @@ static const char* LOG_TAG = "BLEDevice"; -BLEServer *BLEDevice::m_bleServer = nullptr; -BLEScan *BLEDevice::m_pScan = nullptr; -BLEClient *BLEDevice::m_pClient = nullptr; - -#include +/** + * Singletons for the BLEDevice. + */ +BLEServer* BLEDevice::m_pServer = nullptr; +BLEScan* BLEDevice::m_pScan = nullptr; +BLEClient* BLEDevice::m_pClient = nullptr; +/** + * @brief Create a new instance of a client. + * @return A new instance of the client. + */ BLEClient* BLEDevice::createClient() { m_pClient = new BLEClient(); return m_pClient; } // createClient + +/** + * @brief Create a new instance of a server. + * @return A new instance of the server. + */ BLEServer* BLEDevice::createServer() { - return new BLEServer(); -} + m_pServer = new BLEServer(); + return m_pServer; +} // createServer /** * @brief Handle GATT server events. * - * @param [in] event - * @param [in] gatts_if - * @param [in] param + * @param [in] event The event that has been newly received. + * @param [in] gatts_if The connection to the GATT interface. + * @param [in] param Parameters for the event. */ void BLEDevice::gattServerEventHandler( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param + esp_ble_gatts_cb_param_t* param ) { ESP_LOGD(LOG_TAG, "gattServerEventHandler [esp_gatt_if: %d] ... %s", gatts_if, BLEUtils::gattServerEventTypeToString(event).c_str()); + BLEUtils::dumpGattServerEvent(event, gatts_if, param); - if (BLEDevice::m_bleServer != nullptr) { - BLEDevice::m_bleServer->handleGATTServerEvent(event, gatts_if, param); + + if (BLEDevice::m_pServer != nullptr) { + BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); } } // gattServerEventHandler @@ -73,10 +85,6 @@ void BLEDevice::gattServerEventHandler( * @brief Handle GATT client events. * * Handler for the GATT client events. - * * `ESP_GATTC_OPEN_EVT` – Invoked when a connection is opened. - * * `ESP_GATTC_PREP_WRITE_EVT` – Response to write a characteristic. - * * `ESP_GATTC_READ_CHAR_EVT` – Response to read a characteristic. - * * `ESP_GATTC_REG_EVT` – Invoked when a GATT client has been registered. * * @param [in] event * @param [in] gattc_if @@ -90,12 +98,13 @@ void BLEDevice::gattClientEventHandler( ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); BLEUtils::dumpGattClientEvent(event, gattc_if, param); - +/* switch(event) { default: { break; } } // switch + */ // If we have a client registered, call it. if (BLEDevice::m_pClient != nullptr) { @@ -128,8 +137,8 @@ void BLEDevice::gapEventHandler( } } // switch - if (BLEDevice::m_bleServer != nullptr) { - BLEDevice::m_bleServer->handleGAPEvent(event, param); + if (BLEDevice::m_pServer != nullptr) { + BLEDevice::m_pServer->handleGAPEvent(event, param); } if (BLEDevice::m_pScan != nullptr) { @@ -138,6 +147,18 @@ void BLEDevice::gapEventHandler( } // gapEventHandler +/** + * @brief Retrieve the Scan object that we use for scanning. + * @return The scanning object reference. + */ +BLEScan* BLEDevice::getScan() { + if (m_pScan == nullptr) { + m_pScan = new BLEScan(); + } + return m_pScan; +} // getScan + + /** * @brief Initialize the %BLE environment. * @param deviceName The device name of the device. @@ -209,18 +230,4 @@ void BLEDevice::init(std::string deviceName) { } // init - -/** - * @brief Retrieve the Scan object that we use for scanning. - * @return The scanning object reference. - */ -BLEScan* BLEDevice::getScan() { - if (m_pScan == nullptr) { - m_pScan = new BLEScan(); - } - return m_pScan; -} // getScan - - - #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 28d78cb4..eb9fdaa7 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -24,31 +24,34 @@ */ class BLEDevice { public: - static void dumpDevices(); + static BLEClient* createClient(); static BLEServer* createServer(); + static void dumpDevices(); + static BLEScan* getScan(); + static void init(std::string deviceName); - static void init(std::string deviceName); - //static void scan(int duration, esp_ble_scan_type_t scan_type = BLE_SCAN_TYPE_PASSIVE); - static BLEScan *getScan(); - static BLEServer *m_bleServer; +private: + static BLEServer *m_pServer; static BLEScan *m_pScan; static BLEClient *m_pClient; -private: static esp_gatt_if_t getGattcIF(); static void gattClientEventHandler( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param); + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* param); + static void gattServerEventHandler( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param); + esp_ble_gatts_cb_param_t* param); + static void gapEventHandler( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t *param); + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); + }; // class BLE #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index fdd5b1ff..81e6725e 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -54,6 +54,60 @@ BLERemoteCharacteristic::~BLERemoteCharacteristic() { } // ~BLERemoteCharacteristic +/** + * @brief Does the characteristic support broadcasting? + * @return True if the characteristic supports broadcasting. + */ +bool BLERemoteCharacteristic::canBroadcast() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_BROADCAST) != 0; +} // canBroadcast + + +/** + * @brief Does the characteristic support indications? + * @return True if the characteristic supports indications. + */ +bool BLERemoteCharacteristic::canIndicate() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_INDICATE) != 0; +} // canIndicate + + +/** + * @brief Does the characteristic support notifications? + * @return True if the characteristic supports notifications. + */ +bool BLERemoteCharacteristic::canNotify() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_NOTIFY) != 0; +} // canNotify + + +/** + * @brief Does the characteristic support reading? + * @return True if the characteristic supports reading. + */ +bool BLERemoteCharacteristic::canRead() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_READ) != 0; +} // canRead + + +/** + * @brief Does the characteristic support writing? + * @return True if the characteristic supports writing. + */ +bool BLERemoteCharacteristic::canWrite() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_WRITE) != 0; +} // canWrite + + +/** + * @brief Does the characteristic support writing with no response? + * @return True if the characteristic supports writing with no response. + */ +bool BLERemoteCharacteristic::canWriteNoResponse() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) != 0; +} // canWriteNoResponse + + /* static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { if (id1.id.inst_id != id2.id.inst_id) { @@ -209,25 +263,6 @@ void BLERemoteCharacteristic::getDescriptors() { removeDescriptors(); // Remove any existing descriptors. - /* - uint16_t count; - esp_gatt_status_t status = ::esp_ble_gattc_get_attr_count( - getRemoteService()->getClient()->getGattcIf(), - getRemoteService()->getClient()->getConnId(), - ESP_GATT_DB_DESCRIPTOR, - getRemoteService()->getStartHandle(), - getRemoteService()->getEndHandle(), - getHandle(), // Characteristic handle ... only used for ESP_GATT_DB_DESCRIPTOR - &count - ); - if (status != ESP_GATT_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_attr_count: %s", BLEUtils::gattStatusToString(status).c_str()); - } else { - ESP_LOGD(LOG_TAG, "Number of descriptors associated with characteristic is %d", count); - } - */ - - // Loop over each of the descriptors within the service associated with this characteristic. // For each descriptor we find, create a BLERemoteDescriptor instance. uint16_t offset = 0; @@ -308,7 +343,7 @@ BLERemoteDescriptor* BLERemoteCharacteristic::getDescriptor(BLEUUID uuid) { * @brief Get the remote service associated with this characteristic. * @return The remote service associated with this characteristic. */ -BLERemoteService *BLERemoteCharacteristic::getRemoteService() { +BLERemoteService* BLERemoteCharacteristic::getRemoteService() { return m_pRemoteService; } // getRemoteService @@ -371,6 +406,8 @@ std::string BLERemoteCharacteristic::readValue() { m_semaphoreReadCharEvt.take("readValue"); // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. + // This is an asynchronous request which means that we must block waiting for the response + // to become available. esp_err_t errRc = ::esp_ble_gattc_read_char( m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getConnId(), // The connection ID to the BLE server @@ -524,5 +561,4 @@ void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool resp writeValue(std::string((char *)data, length), response); } // writeValue - #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 5af55a69..b34e3649 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -30,6 +30,12 @@ class BLERemoteCharacteristic { ~BLERemoteCharacteristic(); // Public member functions + bool canBroadcast(); + bool canIndicate(); + bool canNotify(); + bool canRead(); + bool canWrite(); + bool canWriteNoResponse(); BLERemoteDescriptor *getDescriptor(BLEUUID uuid); BLEUUID getUUID(); std::string readValue(void); diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 54fcac01..925c09db 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -21,32 +21,20 @@ static const char* LOG_TAG = "BLEScan"; +/** + * Constructor + */ BLEScan::BLEScan() { m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan. m_scan_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; m_scan_params.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; + m_pAdvertisedDeviceCallbacks = nullptr; + m_stopped = true; setInterval(100); setWindow(100); - m_pAdvertisedDeviceCallbacks = nullptr; - m_stopped = true; } // BLEScan - -/** - * @brief Clear the history of previously detected advertised devices. - * @return N/A - */ -/* -void BLEScan::clearAdvertisedDevices() { - for (int i=0; iscan_rst.bda); bool found = false; - /* - for (int i=0; igetAddress().equals(advertisedAddress)) { - found = true; - break; - } - } - */ + for (int i=0; iscan_rst.ble_adv); advertisedDevice.setScan(this); - //m_vectorAvdertisedDevices.push_back(pAdvertisedDevice); if (m_pAdvertisedDeviceCallbacks) { m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); } @@ -144,14 +124,6 @@ void BLEScan::gapEventHandler( } // gapEventHandler -/* -void BLEScan::onResults() { - ESP_LOGD(LOG_TAG, ">> onResults: default"); - ESP_LOGD(LOG_TAG, "<< onResults"); -} // onResults -*/ - - /** * @brief Should we perform an active or passive scan? * The default is a passive scan. An active scan means that we will wish a scan response. @@ -200,7 +172,7 @@ void BLEScan::setWindow(uint16_t windowMSecs) { * @return N/A. */ BLEScanResults BLEScan::start(uint32_t duration) { - ESP_LOGD(LOG_TAG, ">> start(%d)", duration); + ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); m_semaphoreScanEnd.take("start"); diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index f9575eac..bc7f4314 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -21,10 +21,19 @@ class BLEAdvertisedDeviceCallbacks; class BLEClient; class BLEScan; + +/** + * @brief The result of having performed a scan. + * When a scan completes, we have a set of found devices. Each device is described + * by a BLEAdvertisedDevice object. The number of items in the set is given by + * getCount(). We can retrieve a device by calling getDevice() passing in the + * index (starting at 0) of the desired device. + */ class BLEScanResults { public: - int getCount(); + int getCount(); BLEAdvertisedDevice getDevice(uint32_t i); + private: friend BLEScan; std::vector m_vectorAdvertisedDevices; @@ -39,7 +48,6 @@ class BLEScan { public: BLEScan(); - //virtual void onResults(); void setActiveScan(bool active); void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks); void setInterval(uint16_t intervalMSecs); @@ -58,9 +66,8 @@ class BLEScan { esp_ble_scan_params_t m_scan_params; BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks; bool m_stopped; - FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); - //std::vector m_vectorAvdertisedDevices; - BLEScanResults m_scanResults; + FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); + BLEScanResults m_scanResults; }; // BLEScan #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index be9773d9..57d70d2d 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -35,7 +35,6 @@ BLEServer::BLEServer() { m_gatts_if = -1; m_connectedCount = 0; m_connId = -1; - BLEDevice::m_bleServer = this; m_pServerCallbacks = nullptr; createApp(0); @@ -350,4 +349,14 @@ void BLEServer::addCharacteristic(BLECharacteristic *characteristic, BLEService } */ +void BLEServerCallbacks::onConnect(BLEServer* pServer) { + ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); + ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); +} // onConnect + +void BLEServerCallbacks::onDisconnect(BLEServer* pServer) { + ESP_LOGD("BLEServerCallbacks", ">> onDisconnect(): Default"); + ESP_LOGD("BLEServerCallbacks", "<< onDisconnect()"); +} // onDisconnect + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEServerCallbacks.cpp b/cpp_utils/BLEServerCallbacks.cpp deleted file mode 100644 index 88087209..00000000 --- a/cpp_utils/BLEServerCallbacks.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * BLEServerCallbacks.cpp - * - * Created on: Jul 4, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include "BLEServer.h" -#include -static const char* LOG_TAG = "BLEServerCallbacks"; - -void BLEServerCallbacks::onConnect(BLEServer* pServer) { - ESP_LOGD(LOG_TAG, ">> onConnect(): Default"); - ESP_LOGD(LOG_TAG, "<< onConnect()"); -} - -void BLEServerCallbacks::onDisconnect(BLEServer* pServer) { - ESP_LOGD(LOG_TAG, ">> onDisconnect(): Default"); - ESP_LOGD(LOG_TAG, "<< onDisconnect()"); -} -#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index ec16db88..4810f07b 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -57,12 +57,11 @@ void BLEService::executeCreate(BLEServer *pServer) { ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); m_pServer = pServer; + m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT + esp_gatt_srvc_id_t srvc_id; srvc_id.id.inst_id = 0; srvc_id.id.uuid = *m_uuid.getNative(); - - m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT - esp_err_t errRc = ::esp_ble_gatts_create_service(getServer()->getGattsIf(), &srvc_id, 10); if (errRc != ESP_OK) { @@ -87,18 +86,6 @@ void BLEService::dump() { ESP_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str()); } // dump -/* -void BLEService::setService(esp_gatt_srvc_id_t srvc_id) { - m_srvc_id = srvc_id; -} -*/ - -/* -esp_gatt_srvc_id_t BLEService::getService() { - return m_srvc_id; -} -*/ - /** * @brief Get the UUID of the service. @@ -212,6 +199,7 @@ BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t p return createCharacteristic(BLEUUID(uuid), properties); } + /** * @brief Create a new BLE Characteristic associated with this service. * @param [in] uuid - The UUID of the characteristic. diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index da0e594c..cfce7c93 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -31,7 +31,7 @@ class BLEUUID { private: esp_bt_uuid_t m_uuid; - bool m_valueSet; + bool m_valueSet; // Is there a value set for this instance. }; // BLEUUID #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEUUID_H_ */ From cb84cd9a380d8b8b500ee6920d16008fee9b1e31 Mon Sep 17 00:00:00 2001 From: kolban Date: Fri, 29 Sep 2017 22:03:01 -0500 Subject: [PATCH 082/381] Work for #98 --- cpp_utils/BLEClient.cpp | 2 +- cpp_utils/BLERemoteService.cpp | 13 ++++++++-- cpp_utils/BLERemoteService.h | 3 ++- cpp_utils/WiFi.cpp | 43 ++++++++++++++++++++++++++-------- cpp_utils/WiFi.h | 12 +++++----- cpp_utils/WiFiEventHandler.h | 1 + 6 files changed, 54 insertions(+), 20 deletions(-) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index d518d531..645c4aea 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -281,7 +281,7 @@ std::map* BLEClient::getServices() { * and will culminate with an ESP_GATTC_SEARCH_CMPL_EVT when all have been received. */ ESP_LOGD(LOG_TAG, ">> getServices"); - m_servicesMap.empty(); + m_servicesMap.clear(); esp_err_t errRc = esp_ble_gattc_search_service( getGattcIf(), getConnId(), diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index b9fd29de..712954d4 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -140,7 +140,7 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { // asked the device about its characteristics, then we do that now. Once we get the results we can then // examine the characteristics map to see if it has the characteristic we are looking for. if (!m_haveCharacteristics) { - getCharacteristics(); + retrieveCharacteristics(); } std::string v = uuid.toString(); for (auto &myPair : m_characteristicMap) { @@ -156,7 +156,7 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { * @brief Retrieve all the characteristics for this service. * @return N/A */ -void BLERemoteService::getCharacteristics() { +void BLERemoteService::retrieveCharacteristics() { ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); @@ -262,6 +262,15 @@ void BLERemoteService::getCharacteristics() { } // getCharacteristics +/** + * @brief Retrieve a map of all the characteristics of this service. + * @return A map of all the characteristics of this service. + */ +std::map* BLERemoteService::getCharacteristics() { + return &m_characteristicMap; +} // getCharacteristics + + BLEClient* BLERemoteService::getClient() { return m_pClient; } diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index a6241cc0..75262868 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -32,6 +32,7 @@ class BLERemoteService { // Public methods BLERemoteCharacteristic* getCharacteristic(const char* uuid); BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); + std::map* getCharacteristics(); BLEClient* getClient(void); BLEUUID getUUID(void); @@ -46,7 +47,7 @@ class BLERemoteService { friend class BLERemoteCharacteristic; // Private methods - void getCharacteristics(void); + void retrieveCharacteristics(void); uint16_t getHandle(); esp_gatt_id_t* getSrvcId(void); uint16_t getStartHandle(); diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index cc556ec1..ecbb76e0 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -41,6 +41,9 @@ static void setDNSServer(char *ip) { } */ + + + /** * @brief Creates and uses a default event handler */ @@ -48,20 +51,20 @@ WiFi::WiFi() : ip(0) , gw(0) , netmask(0) - , m_wifiEventHandler(nullptr) + , m_pWifiEventHandler(nullptr) { ::nvs_flash_init(); wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&config); ::tcpip_adapter_init(); - m_wifiEventHandler = new WiFiEventHandler(); + m_pWifiEventHandler = new WiFiEventHandler(); } // WiFi /** * @brief Deletes the event handler that was used by the class */ WiFi::~WiFi() { - delete m_wifiEventHandler; + delete m_pWifiEventHandler; } /** @@ -134,11 +137,12 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { * * The event handler will be called back with the outcome of the connection. * - * @param[in] ssid The network SSID of the access point to which we wish to connect. - * @param[in] password The password of the access point to which we wish to connect. + * @param [in] ssid The network SSID of the access point to which we wish to connect. + * @param [in] password The password of the access point to which we wish to connect. + * @param [in] waitForConnection Block until the connection has an outcome. * @return N/A. */ -void WiFi::connectAP(const std::string& ssid, const std::string& password){ +void WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ ESP_LOGD(LOG_TAG, ">> connectAP"); if (ip != 0 && gw != 0 && netmask != 0) { @@ -153,7 +157,8 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password){ } - ESP_ERROR_CHECK( esp_event_loop_init(m_wifiEventHandler->getEventHandler(), m_wifiEventHandler)); + ESP_ERROR_CHECK(esp_event_loop_init(WiFi::eventHandler, this)); + //ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); wifi_config_t sta_config; @@ -164,7 +169,9 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password){ ESP_ERROR_CHECK(::esp_wifi_set_config(WIFI_IF_STA, &sta_config)); ESP_ERROR_CHECK(::esp_wifi_start()); + m_gotIpEvt.take("connectAP"); ESP_ERROR_CHECK(::esp_wifi_connect()); + m_gotIpEvt.wait("connectAP"); ESP_LOGD(LOG_TAG, "<< connectAP"); } // connectAP @@ -180,6 +187,22 @@ void WiFi::dump() { ESP_LOGD(LOG_TAG, "DNS Server[0]: %s", ipAddrStr); } // dump + +/** + * @brief Primary event handler interface. + */ +esp_err_t WiFi::eventHandler(void* ctx, system_event_t* event) { + WiFi *pWiFi = (WiFi *)ctx; + esp_err_t rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); + // If the event we received indicates that we now have an IP address then unlock the mutex that + // indicates we are waiting for an IP. + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { + pWiFi->m_gotIpEvt.give(); + } + return rc; +} // eventHandler + + /** * @brief Get the AP IP Info. * @return The AP IP Info. @@ -311,7 +334,7 @@ std::string WiFi::getStaSSID() { std::vector WiFi::scan() { ::nvs_flash_init(); ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(m_wifiEventHandler->getEventHandler(), m_wifiEventHandler)); + ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); @@ -353,7 +376,7 @@ std::vector WiFi::scan() { void WiFi::startAP(const std::string& ssid, const std::string& password) { ::nvs_flash_init(); ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(m_wifiEventHandler->getEventHandler(), m_wifiEventHandler)); + ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); @@ -378,7 +401,7 @@ void WiFi::startAP(const std::string& ssid, const std::string& password) { * @param[in] wifiEventHandler The class that will be used to process events. */ void WiFi::setWifiEventHandler(WiFiEventHandler *wifiEventHandler) { - this->m_wifiEventHandler = wifiEventHandler; + this->m_pWifiEventHandler = wifiEventHandler; } // setWifiEventHandler diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 1299f9f1..3279f284 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -12,6 +12,7 @@ #include #include #include +#include "FreeRTOS.h" #include "WiFiEventHandler.h" /** @@ -103,10 +104,13 @@ class WiFiAPRecord { */ class WiFi { private: + static esp_err_t eventHandler(void* ctx, system_event_t* event); uint32_t ip; uint32_t gw; uint32_t netmask; - WiFiEventHandler *m_wifiEventHandler; + WiFiEventHandler* m_pWifiEventHandler; + uint8_t m_dnsCount=0; + FreeRTOS::Semaphore m_gotIpEvt = FreeRTOS::Semaphore("GotIpEvt"); public: WiFi(); @@ -119,7 +123,7 @@ class WiFi { void setDNSServer(int numdns, ip_addr_t ip); struct in_addr getHostByName(const std::string& hostName); struct in_addr getHostByName(const char* hostName); - void connectAP(const std::string& ssid, const std::string& password); + void connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); void dump(); static std::string getApMac(); static tcpip_adapter_ip_info_t getApIpInfo(); @@ -134,10 +138,6 @@ class WiFi { void setIPInfo(const char* ip, const char* gw, const char* netmask); void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); void setWifiEventHandler(WiFiEventHandler *wifiEventHandler); -private: - uint8_t m_dnsCount=0; - //char *m_dnsServer = nullptr; - }; #endif /* MAIN_WIFI_H_ */ diff --git a/cpp_utils/WiFiEventHandler.h b/cpp_utils/WiFiEventHandler.h index fe39e332..0791089a 100644 --- a/cpp_utils/WiFiEventHandler.h +++ b/cpp_utils/WiFiEventHandler.h @@ -110,6 +110,7 @@ class WiFiEventHandler { } private: + friend class WiFi; WiFiEventHandler *m_nextHandler; static esp_err_t eventHandler(void* ctx, system_event_t* event); }; From 16e78d2dc8be2551b6963e0b7b41694a745490b1 Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 30 Sep 2017 09:30:04 -0500 Subject: [PATCH 083/381] Fixes for #105 --- cpp_utils/BLEDevice.cpp | 3 ++ cpp_utils/BLERemoteCharacteristic.cpp | 40 +++++++++++++++++++-------- cpp_utils/BLEServer.cpp | 6 ++-- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 4470a48a..b2777e58 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -52,7 +52,10 @@ BLEClient* BLEDevice::createClient() { * @return A new instance of the server. */ BLEServer* BLEDevice::createServer() { + ESP_LOGD(LOG_TAG, ">> createServer"); m_pServer = new BLEServer(); + m_pServer->createApp(0); + ESP_LOGD(LOG_TAG, "<< createServer"); return m_pServer; } // createServer diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 81e6725e..68fbaf38 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -151,12 +151,12 @@ void BLERemoteCharacteristic::gattClientEventHandler( // ESP_GATTC_NOTIFY_EVT // // notify - // - uint16_t conn_id - // - esp_bd_addr_t remote_bda - // - uint16_t handle - // - uint16_t value_len - // - uint8_t* value - // - bool is_notify + // - uint16_t conn_id - The connection identifier of the server. + // - esp_bd_addr_t remote_bda - The device address of the BLE server. + // - uint16_t handle - The handle of the characteristic for which the event is being received. + // - uint16_t value_len - The length of the received data. + // - uint8_t* value - The received data. + // - bool is_notify - True if this is a notify, false if it is an indicate. // // We have received a notification event which means that the server wishes us to know about a notification // piece of data. What we must now do is find the characteristic with the associated handle and then @@ -178,6 +178,7 @@ void BLERemoteCharacteristic::gattClientEventHandler( break; } // ESP_GATTC_NOTIFY_EVT + // // ESP_GATTC_READ_CHAR_EVT // This event indicates that the server has responded to the read request. @@ -221,12 +222,29 @@ void BLERemoteCharacteristic::gattClientEventHandler( break; } - // We have process the notify and can unlock the semaphore. + // We have processed the notify registration and can unlock the semaphore. m_semaphoreRegForNotifyEvt.give(); break; } // ESP_GATTC_REG_FOR_NOTIFY_EVT + // + // ESP_GATTC_UNREG_FOR_NOTIFY_EVT + // + // unreg_for_notify: + // - esp_gatt_status_t status + // - uint16_t handle + // + case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { + if (evtParam->unreg_for_notify.handle != getHandle()) { + break; + } + // We have processed the notify un-registration and can unlock the semaphore. + m_semaphoreRegForNotifyEvt.give(); + break; + } // ESP_GATTC_UNREG_FOR_NOTIFY_EVT: + + // // ESP_GATTC_WRITE_CHAR_EVT // @@ -446,7 +464,7 @@ void BLERemoteCharacteristic::registerForNotify( m_semaphoreRegForNotifyEvt.take("registerForNotify"); - if (notifyCallback != nullptr) { + if (notifyCallback != nullptr) { // If we have a callback function, then this is a registration. esp_err_t errRc = ::esp_ble_gattc_register_for_notify( m_pRemoteService->getClient()->getGattcIf(), *m_pRemoteService->getClient()->getPeerAddress().getNative(), @@ -456,8 +474,8 @@ void BLERemoteCharacteristic::registerForNotify( if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - } // Register - else { + } // End Register + else { // If we weren't passed a callback function, then this is an unregistration. esp_err_t errRc = ::esp_ble_gattc_unregister_for_notify( m_pRemoteService->getClient()->getGattcIf(), *m_pRemoteService->getClient()->getPeerAddress().getNative(), @@ -467,7 +485,7 @@ void BLERemoteCharacteristic::registerForNotify( if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_unregister_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - } // Unregister + } // End Unregister m_semaphoreRegForNotifyEvt.wait("registerForNotify"); diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 57d70d2d..14e44a0d 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -37,14 +37,14 @@ BLEServer::BLEServer() { m_connId = -1; m_pServerCallbacks = nullptr; - createApp(0); + //createApp(0); } // BLEServer void BLEServer::createApp(uint16_t appId) { m_appId = appId; registerApp(); -} +} // createApp /** @@ -198,7 +198,7 @@ void BLEServer::handleGATTServerEvent( case ESP_GATTS_REG_EVT: { m_gatts_if = gatts_if; - m_semaphoreRegisterAppEvt.give(); + m_semaphoreRegisterAppEvt.give(); // Unlock the mutex waiting for the registration of the app. break; } // ESP_GATTS_REG_EVT From 658f0d48f44ebc1c716f73559458772bb45b0101 Mon Sep 17 00:00:00 2001 From: dadosch Date: Mon, 2 Oct 2017 20:10:07 +0200 Subject: [PATCH 084/381] Deleted copy of BLEServerCallback because this file doesn't exist any more --- cpp_utils/Makefile.arduino | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index 345b52bf..609f8450 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -35,7 +35,6 @@ BLE_FILES= \ BLERemoteService.h \ BLEScan.cpp \ BLEScan.h \ - BLEServerCallbacks.cpp \ BLEServer.cpp \ BLEServer.h \ BLEService.cpp \ From e85eefa9b43eaf763083c1ff1080f2aa1d3c28a6 Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 2 Oct 2017 22:08:25 -0500 Subject: [PATCH 085/381] Fixes for BLE Advertising --- cpp_utils/BLEAdvertising.cpp | 42 +++++++--- cpp_utils/BLEAdvertising.h | 1 + cpp_utils/BLEUtils.cpp | 150 ++++++++++++++++++++++++----------- cpp_utils/I2C.cpp | 124 ++++++++++++++++++----------- cpp_utils/I2C.h | 57 +++++-------- 5 files changed, 233 insertions(+), 141 deletions(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index ad076ddd..049aa4dc 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -35,6 +35,20 @@ BLEAdvertising::BLEAdvertising() { m_advData.p_service_uuid = nullptr; m_advData.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT); + m_advDataScanResponse.set_scan_rsp = true; + m_advDataScanResponse.include_name = false; + m_advDataScanResponse.include_txpower = false; + m_advDataScanResponse.min_interval = 0x20; + m_advDataScanResponse.max_interval = 0x40; + m_advDataScanResponse.appearance = 0x00; + m_advDataScanResponse.manufacturer_len = 0; + m_advDataScanResponse.p_manufacturer_data = nullptr; + m_advDataScanResponse.service_data_len = 0; + m_advDataScanResponse.p_service_data = nullptr; + m_advDataScanResponse.service_uuid_len = 0; + m_advDataScanResponse.p_service_uuid = nullptr; + m_advDataScanResponse.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT); + m_advParams.adv_int_min = 0x20; m_advParams.adv_int_max = 0x40; m_advParams.adv_type = ADV_TYPE_IND; @@ -83,18 +97,18 @@ void BLEAdvertising::setServiceUUID(BLEUUID serviceUUID) { esp_bt_uuid_t* espUUID = m_serviceUUID.getNative(); switch(espUUID->len) { case ESP_UUID_LEN_16: { - m_advData.service_uuid_len = 2; - m_advData.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid16); + m_advDataScanResponse.service_uuid_len = 2; + m_advDataScanResponse.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid16); break; } case ESP_UUID_LEN_32: { - m_advData.service_uuid_len = 4; - m_advData.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid32); + m_advDataScanResponse.service_uuid_len = 4; + m_advDataScanResponse.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid32); break; } case ESP_UUID_LEN_128: { - m_advData.service_uuid_len = 16; - m_advData.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid128); + m_advDataScanResponse.service_uuid_len = 16; + m_advDataScanResponse.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid128); break; } } // switch @@ -110,13 +124,13 @@ void BLEAdvertising::setServiceUUID(BLEUUID serviceUUID) { void BLEAdvertising::start() { ESP_LOGD(LOG_TAG, ">> start"); - if (m_advData.service_uuid_len > 0) { + if (m_advDataScanResponse.service_uuid_len > 0) { uint8_t hexData[16*2+1]; - BLEUtils::buildHexData(hexData, m_advData.p_service_uuid, m_advData.service_uuid_len); + BLEUtils::buildHexData(hexData, m_advDataScanResponse.p_service_uuid, m_advDataScanResponse.service_uuid_len); ESP_LOGD(LOG_TAG, " - Service: service_uuid_len=%d, p_service_uuid=0x%x (data=%s)", - m_advData.service_uuid_len, - (uint32_t)m_advData.p_service_uuid, - (m_advData.service_uuid_len > 0?(char *)hexData:"N/A") + m_advDataScanResponse.service_uuid_len, + (uint32_t)m_advDataScanResponse.p_service_uuid, + (m_advDataScanResponse.service_uuid_len > 0?(char *)hexData:"N/A") ); } // We have a service to advertise @@ -128,6 +142,12 @@ void BLEAdvertising::start() { return; } + errRc = ::esp_ble_gap_config_adv_data(&m_advDataScanResponse); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + // Start advertising. errRc = ::esp_ble_gap_start_advertising(&m_advParams); if (errRc != ESP_OK) { diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 2d0b51c2..afe761d8 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -27,6 +27,7 @@ class BLEAdvertising { void setServiceUUID(BLEUUID serviceUUID); private: esp_ble_adv_data_t m_advData; + esp_ble_adv_data_t m_advDataScanResponse; esp_ble_adv_params_t m_advParams; BLEUUID m_serviceUUID; }; diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 7a3cdf1a..a5fa6239 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -965,18 +965,35 @@ void BLEUtils::dumpGapEvent( switch(event) { // // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT + // adv_data_cmpl + // - esp_bt_status_t // case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_data_cmpl.status); break; } // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT + // + // ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT + // + // adv_data_raw_cmpl + // - esp_bt_status_t status + // + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { + ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_data_raw_cmpl.status); + break; + } // ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT + + // // ESP_GAP_BLE_ADV_START_COMPLETE_EVT // + // adv_start_cmpl + // - esp_bt_status_t status + // case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_start_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_start_cmpl.status); break; } // ESP_GAP_BLE_ADV_START_COMPLETE_EVT @@ -984,8 +1001,11 @@ void BLEUtils::dumpGapEvent( // // ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT // + // adv_stop_cmpl + // - esp_bt_status_t status + // case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_stop_cmpl.status); break; } // ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT @@ -993,6 +1013,15 @@ void BLEUtils::dumpGapEvent( // // ESP_GAP_BLE_AUTH_CMPL_EVT // + // auth_cmpl + // - esp_bd_addr_t bd_addr + // - bool key_present + // - esp_link_key key + // - bool success + // - uint8_t fail_reason + // - esp_bd_addr_type_t addr_type + // - esp_bt_dev_type_t dev_type + // case ESP_GAP_BLE_AUTH_CMPL_EVT: { ESP_LOGD(LOG_TAG, "[bd_addr: %s, key_present: %d, key: ***, key_type: %d, success: %d, fail_reason: %d, addr_type: ***, dev_type: %s]", BLEAddress(param->ble_security.auth_cmpl.bd_addr).toString().c_str(), @@ -1006,6 +1035,18 @@ void BLEUtils::dumpGapEvent( } // ESP_GAP_BLE_AUTH_CMPL_EVT + // + // ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT + // + // clear_bond_dev_cmpl + // - esp_bt_status_t status + // + case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: { + ESP_LOGD(LOG_TAG, "[status: %d]", param->clear_bond_dev_cmpl.status); + break; + } // ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT + + // // ESP_GAP_BLE_LOCAL_IR_EVT // @@ -1033,6 +1074,23 @@ void BLEUtils::dumpGapEvent( } // ESP_GAP_BLE_NC_REQ_EVT + // + // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT + // + // read_rssi_cmpl + // - esp_bt_status_t status + // - int8_t rssi + // - esp_bd_addr_t remote_addr + // + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: { + ESP_LOGD(LOG_TAG, "[status: %d, rssi: %d, remote_addr: %s]", + param->read_rssi_cmpl.status, + param->read_rssi_cmpl.rssi, + BLEAddress(param->read_rssi_cmpl.remote_addr).toString().c_str() + ); + break; + } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT + // // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT // @@ -1090,15 +1148,6 @@ void BLEUtils::dumpGapEvent( } // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT - // - // ESP_GAP_BLE_SEC_REQ_EVT - // - case ESP_GAP_BLE_SEC_REQ_EVT: { - ESP_LOGD(LOG_TAG, "[bd_addr: %s]", BLEAddress(param->ble_security.ble_req.bd_addr).toString().c_str()); - break; - } // ESP_GAP_BLE_SEC_REQ_EVT - - // // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT // @@ -1125,6 +1174,14 @@ void BLEUtils::dumpGapEvent( } // ESP_GAP_BLE_SCAN_UPDATE_CONN_PARAMS_EVT + // + // ESP_GAP_BLE_SEC_REQ_EVT + // + case ESP_GAP_BLE_SEC_REQ_EVT: { + ESP_LOGD(LOG_TAG, "[bd_addr: %s]", BLEAddress(param->ble_security.ble_req.bd_addr).toString().c_str()); + break; + } // ESP_GAP_BLE_SEC_REQ_EVT + default: { ESP_LOGD(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); break; @@ -1637,60 +1694,61 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { */ const char* BLEUtils::gapEventToString(uint32_t eventType) { switch(eventType) { - case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: - return "ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT"; case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + return "ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT"; case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_START_COMPLETE_EVT"; - case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: - return "ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT"; - case ESP_GAP_BLE_SCAN_RESULT_EVT: - return "ESP_GAP_BLE_SCAN_RESULT_EVT"; - case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: - return "ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT"; - case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: - return "ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT"; - case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: - return "ESP_GAP_BLE_SCAN_START_COMPLETE_EVT"; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: /*!< When stop adv complete, the event comes */ + return "ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT"; case ESP_GAP_BLE_AUTH_CMPL_EVT: /* Authentication complete indication. */ return "ESP_GAP_BLE_AUTH_CMPL_EVT"; + case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: + return "ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT: + return "ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT"; case ESP_GAP_BLE_KEY_EVT: /* BLE key event for peer device keys */ return "ESP_GAP_BLE_KEY_EVT"; - case ESP_GAP_BLE_SEC_REQ_EVT: /* BLE security request */ - return "ESP_GAP_BLE_SEC_REQ_EVT"; - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: /* passkey notification event */ - return "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"; - case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ - return "ESP_GAP_BLE_PASSKEY_REQ_EVT"; - case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ - return "ESP_GAP_BLE_OOB_REQ_EVT"; case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ return "ESP_GAP_BLE_LOCAL_IR_EVT"; case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ return "ESP_GAP_BLE_LOCAL_ER_EVT"; case ESP_GAP_BLE_NC_REQ_EVT: /* Numeric Comparison request event */ return "ESP_GAP_BLE_NC_REQ_EVT"; - case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: /*!< When stop adv complete, the event comes */ - return "ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT"; + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + return "ESP_GAP_BLE_OOB_REQ_EVT"; + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: /* passkey notification event */ + return "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + return "ESP_GAP_BLE_PASSKEY_REQ_EVT"; + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + return "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT"; + case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: + return "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: + return "ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_RESULT_EVT: + return "ESP_GAP_BLE_SCAN_RESULT_EVT"; + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: + return "ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: + return "ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + return "ESP_GAP_BLE_SCAN_START_COMPLETE_EVT"; case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT"; + case ESP_GAP_BLE_SEC_REQ_EVT: /* BLE security request */ + return "ESP_GAP_BLE_SEC_REQ_EVT"; + case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: + return "ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT"; + case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: + return "ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT"; case ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT: return "ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT"; case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: return "ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT"; - case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: - return "ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT"; - case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: - return "ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT"; - case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: - return "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT"; - case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: - return "ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT"; - case ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT: - return "ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT"; - case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: - return "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT"; + default: ESP_LOGD(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); return "Unknown event type"; diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index 636efd8c..d6a7121a 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -22,11 +22,11 @@ static bool debug = false; * @return N/A. */ I2C::I2C() { - directionKnown = false; - address = 0; - cmd = 0; - sdaPin = DEFAULT_SDA_PIN; - sclPin = DEFAULT_CLK_PIN; + m_directionKnown = false; + m_address = 0; + m_cmd = 0; + m_sdaPin = DEFAULT_SDA_PIN; + m_sclPin = DEFAULT_CLK_PIN; } // I2C @@ -40,9 +40,9 @@ void I2C::beginTransaction() { if (debug) { ESP_LOGD(LOG_TAG, "beginTransaction()"); } - cmd = ::i2c_cmd_link_create(); - ESP_ERROR_CHECK(::i2c_master_start(cmd)); - directionKnown = false; + m_cmd = ::i2c_cmd_link_create(); + ESP_ERROR_CHECK(::i2c_master_start(m_cmd)); + m_directionKnown = false; } // beginTransaction @@ -57,13 +57,24 @@ void I2C::endTransaction() { if (debug) { ESP_LOGD(LOG_TAG, "endTransaction()"); } - ESP_ERROR_CHECK(::i2c_master_stop(cmd)); - ESP_ERROR_CHECK(::i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS)); - ::i2c_cmd_link_delete(cmd); - directionKnown = false; + ESP_ERROR_CHECK(::i2c_master_stop(m_cmd)); + ESP_ERROR_CHECK(::i2c_master_cmd_begin(I2C_NUM_0, m_cmd, 1000/portTICK_PERIOD_MS)); + ::i2c_cmd_link_delete(m_cmd); + m_directionKnown = false; } // endTransaction +/** + * @brief Get the address of the %I2C slave against which we are working. + * + * @return The address of the %I2C slave. + */ +uint8_t I2C::getAddress() const +{ + return m_address; +} + + /** * @brief Initialize the I2C interface. * @@ -74,9 +85,9 @@ void I2C::endTransaction() { */ void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin) { ESP_LOGD(LOG_TAG, ">> I2c::init. address=%d, sda=%d, scl=%d", address, sdaPin, sclPin); - this->sdaPin = sdaPin; - this->sclPin = sclPin; - this->address = address; + this->m_sdaPin = sdaPin; + this->m_sclPin = sclPin; + this->m_address = address; i2c_config_t conf; conf.mode = I2C_MODE_MASTER; conf.sda_io_num = sdaPin; @@ -104,11 +115,11 @@ void I2C::read(uint8_t* bytes, size_t length, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "read(size=%d, ack=%d)", length, ack); } - if (directionKnown == false) { - directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, !ack)); + if (m_directionKnown == false) { + m_directionKnown = true; + ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack)); } - ESP_ERROR_CHECK(::i2c_master_read(cmd, bytes, length, !ack)); + ESP_ERROR_CHECK(::i2c_master_read(m_cmd, bytes, length, !ack)); } // read @@ -123,11 +134,11 @@ void I2C::read(uint8_t *byte, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "read(size=1, ack=%d)", ack); } - if (directionKnown == false) { - directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, !ack)); + if (m_directionKnown == false) { + m_directionKnown = true; + ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack)); } - ESP_ERROR_CHECK(::i2c_master_read_byte(cmd, byte, !ack)); + ESP_ERROR_CHECK(::i2c_master_read_byte(m_cmd, byte, !ack)); } // readByte @@ -139,33 +150,35 @@ void I2C::read(uint8_t *byte, bool ack) { * @return N/A. */ void I2C::scan() { - int i; - esp_err_t espRc; - printf("Data Pin: %d, Clock Pin: %d\n", this->sdaPin, this->sclPin); + uint8_t i; + printf("Data Pin: %d, Clock Pin: %d\n", this->m_sdaPin, this->m_sclPin); printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n"); printf("00: "); - for (i=3; i< 0x78; i++) { - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (i << 1) | I2C_MASTER_WRITE, 1 /* expect ack */); - i2c_master_stop(cmd); - - espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 100/portTICK_PERIOD_MS); + for (i=3; i<0x78; i++) { if (i%16 == 0) { printf("\n%.2x:", i); } - if (espRc == 0) { + if (slavePresent(i)) { printf(" %.2x", i); } else { printf(" --"); } - //ESP_LOGD(tag, "i=%d, rc=%d (0x%x)", i, espRc, espRc); - i2c_cmd_link_delete(cmd); } printf("\n"); } // scan +/** + * @brief Set the address of the %I2C slave against which we will be working. + * + * @param [in] address The address of the %I2C slave. + */ +void I2C::setAddress(uint8_t address) +{ + this->m_address = address; +} // setAddress + + /** * @brief enable or disable debugging. * @param [in] enabled Should debugging be enabled or disabled. @@ -176,6 +189,23 @@ void I2C::setDebug(bool enabled) { } // setDebug +/** + * @brief Determine if the slave is present and responding. + * @param [in] The address of the slave. + * @return True if the slave is present and false otherwise. + */ +bool I2C::slavePresent(uint8_t address) { + i2c_cmd_handle_t cmd = ::i2c_cmd_link_create(); + ::i2c_master_start(cmd); + ::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, 1 /* expect ack */); + ::i2c_master_stop(cmd); + + esp_err_t espRc = ::i2c_master_cmd_begin(I2C_NUM_0, cmd, 100/portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + return espRc == 0; // Return true if the slave is present and false otherwise. +} // slavePresent + + /** * @brief Add an %I2C start request to the command stream. * @return N/A. @@ -184,7 +214,7 @@ void I2C::start() { if (debug) { ESP_LOGD(LOG_TAG, "start()"); } - ESP_ERROR_CHECK(::i2c_master_start(cmd)); + ESP_ERROR_CHECK(::i2c_master_start(m_cmd)); } // start @@ -196,8 +226,8 @@ void I2C::stop() { if (debug) { ESP_LOGD(LOG_TAG, "stop()"); } - ESP_ERROR_CHECK(::i2c_master_stop(cmd)); - directionKnown = false; + ESP_ERROR_CHECK(::i2c_master_stop(m_cmd)); + m_directionKnown = false; } // stop @@ -212,11 +242,11 @@ void I2C::write(uint8_t byte, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "write(val=0x%.2x, ack=%d)", byte, ack); } - if (directionKnown == false) { - directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, !ack)); + if (m_directionKnown == false) { + m_directionKnown = true; + ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack)); } - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, byte, !ack)); + ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, byte, !ack)); } // write @@ -232,11 +262,11 @@ void I2C::write(uint8_t *bytes, size_t length, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "write(length=%d, ack=%d)", length, ack); } - if (directionKnown == false) { - directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, !ack)); + if (m_directionKnown == false) { + m_directionKnown = true; + ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack)); } - ESP_ERROR_CHECK(::i2c_master_write(cmd, bytes, length, !ack)); + ESP_ERROR_CHECK(::i2c_master_write(m_cmd, bytes, length, !ack)); } // write diff --git a/cpp_utils/I2C.h b/cpp_utils/I2C.h index 5142723c..5d38af67 100644 --- a/cpp_utils/I2C.h +++ b/cpp_utils/I2C.h @@ -18,54 +18,37 @@ */ class I2C { private: - uint8_t address; - i2c_cmd_handle_t cmd; - bool directionKnown; - gpio_num_t sdaPin; - gpio_num_t sclPin; + uint8_t m_address; + i2c_cmd_handle_t m_cmd; + bool m_directionKnown; + gpio_num_t m_sdaPin; + gpio_num_t m_sclPin; public: - I2C(); - void beginTransaction(); - void endTransaction(); /** - * @brief Get the address of the %I2C slave against which we are working. - * - * @return The address of the %I2C slave. + * @brief The default SDA pin. */ - uint8_t getAddress() const - { - return address; - } - - void init(uint8_t address, gpio_num_t sdaPin = DEFAULT_SDA_PIN, gpio_num_t sclPin = DEFAULT_CLK_PIN); - void read(uint8_t *bytes, size_t length, bool ack=true); - void read(uint8_t *byte, bool ack=true); - + static const gpio_num_t DEFAULT_SDA_PIN = GPIO_NUM_25; /** - * @brief Set the address of the %I2C slave against which we will be working. - * - * @param[in] address The address of the %I2C slave. + * @brief The default Clock pin. */ - void setAddress(uint8_t address) - { - this->address = address; - } + static const gpio_num_t DEFAULT_CLK_PIN = GPIO_NUM_26; - void setDebug(bool enabled); + I2C(); + void beginTransaction(); + void endTransaction(); + uint8_t getAddress() const; + void init(uint8_t address, gpio_num_t sdaPin = DEFAULT_SDA_PIN, gpio_num_t sclPin = DEFAULT_CLK_PIN); + void read(uint8_t* bytes, size_t length, bool ack=true); + void read(uint8_t* byte, bool ack=true); void scan(); + void setAddress(uint8_t address); + void setDebug(bool enabled); + bool slavePresent(uint8_t address); void start(); void stop(); void write(uint8_t byte, bool ack=true); - void write(uint8_t *bytes, size_t length, bool ack=true); - /** - * @brief The default SDA pin. - */ - static const gpio_num_t DEFAULT_SDA_PIN = GPIO_NUM_25; - /** - * @brief The default Clock pin. - */ - static const gpio_num_t DEFAULT_CLK_PIN = GPIO_NUM_26; + void write(uint8_t* bytes, size_t length, bool ack=true); }; #endif /* MAIN_I2C_H_ */ From bdfc8491eba04e24558797eb6579d846192f3acf Mon Sep 17 00:00:00 2001 From: kolban Date: Wed, 4 Oct 2017 09:35:21 -0500 Subject: [PATCH 086/381] Improvements for #98 --- cpp_utils/BLERemoteService.cpp | 13 +++++++++++- cpp_utils/BLEUtils.cpp | 39 +++++++++++++++++++++++++++++++--- cpp_utils/tests/test_rest.cpp | 5 +++++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index 712954d4..b6999cbf 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -154,6 +154,7 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { /** * @brief Retrieve all the characteristics for this service. + * This function will not return until we have all the characteristics. * @return N/A */ void BLERemoteService::retrieveCharacteristics() { @@ -256,7 +257,8 @@ void BLERemoteService::retrieveCharacteristics() { m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic->getUUID().toString(), pNewRemoteCharacteristic)); offset++; // Increment our count of number of descriptors found. - } + } // Loop forever (until we break inside the loop). + m_haveCharacteristics = true; // Remember that we have received the characteristics. ESP_LOGD(LOG_TAG, "<< getCharacteristics()"); } // getCharacteristics @@ -267,6 +269,12 @@ void BLERemoteService::retrieveCharacteristics() { * @return A map of all the characteristics of this service. */ std::map* BLERemoteService::getCharacteristics() { + // If is possible that we have not read the characteristics associated with the service so do that + // now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking + // call and does not return until all the characteristics are available. + if (!m_haveCharacteristics) { + retrieveCharacteristics(); + } return &m_characteristicMap; } // getCharacteristics @@ -275,14 +283,17 @@ BLEClient* BLERemoteService::getClient() { return m_pClient; } + uint16_t BLERemoteService::getEndHandle() { return m_endHandle; } + esp_gatt_id_t* BLERemoteService::getSrvcId() { return &m_srvcId; } + uint16_t BLERemoteService::getStartHandle() { return m_startHandle; } diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index a5fa6239..00bfd470 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -959,8 +959,8 @@ const char* BLEUtils::devTypeToString(esp_bt_dev_type_t type) { * @brief Dump the GAP event to the log. */ void BLEUtils::dumpGapEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t *param) { + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param) { ESP_LOGD(LOG_TAG, "Received a GAP event: %s", gapEventToString(event)); switch(event) { // @@ -1091,14 +1091,19 @@ void BLEUtils::dumpGapEvent( break; } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT + // // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT // + // scan_param_cmpl. + // - esp_bt_status_t status + // case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_param_cmpl.status); break; } // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT + // // ESP_GAP_BLE_SCAN_RESULT_EVT // @@ -1112,6 +1117,7 @@ void BLEUtils::dumpGapEvent( // - ble_adv // - flag // - num_resps + // case ESP_GAP_BLE_SCAN_RESULT_EVT: { switch(param->scan_rst.search_evt) { case ESP_GAP_SEARCH_INQ_RES_EVT: { @@ -1139,9 +1145,23 @@ void BLEUtils::dumpGapEvent( } // ESP_GAP_BLE_SCAN_RESULT_EVT + // + // ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT + // + // scan_rsp_data_cmpl + // - esp_bt_status_t status + // + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: { + ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_cmpl.status); + break; + } // ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT + + // // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT // + // scan_start_cmpl + // - esp_bt_status_t status case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: { ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_start_cmpl.status); break; @@ -1151,6 +1171,9 @@ void BLEUtils::dumpGapEvent( // // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT // + // scan_stop_cmpl + // - esp_bt_status_t status + // case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: { ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status); break; @@ -1158,7 +1181,16 @@ void BLEUtils::dumpGapEvent( // - // ESP_GAP_BLE_SCAN_UPDATE_CONN_PARAMS_EVT + // ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT + // + // update_conn_params + // - esp_bt_status_t status + // - esp_bd_addr_t bda + // - uint16_t min_int + // - uint16_t max_int + // - uint16_t latency + // - uint16_t conn_int + // - uint16_t timeout // case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: { ESP_LOGD(LOG_TAG, "[status: %d, bd_addr: %s, min_int: %d, max_int: %d, latency: %d, conn_int: %d, timeout: %d]", @@ -1182,6 +1214,7 @@ void BLEUtils::dumpGapEvent( break; } // ESP_GAP_BLE_SEC_REQ_EVT + default: { ESP_LOGD(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); break; diff --git a/cpp_utils/tests/test_rest.cpp b/cpp_utils/tests/test_rest.cpp index 4ee38f49..604d3adb 100644 --- a/cpp_utils/tests/test_rest.cpp +++ b/cpp_utils/tests/test_rest.cpp @@ -1,5 +1,10 @@ /* * Test the REST API client. + * This application leverages LibCurl. You must make that package available + * as well as "enable it" from "make menuconfig" and C++ Settings -> libCurl present. + * See also: + * * https://github.com/nkolban/esp32-snippets/issues/108 + * */ #include #include From 1a30b01f1f9e248f7870d37a8dc715f1ef236540 Mon Sep 17 00:00:00 2001 From: kolban Date: Thu, 5 Oct 2017 08:20:00 -0500 Subject: [PATCH 087/381] Fixes for #108 --- cpp_utils/RESTClient.cpp | 6 ++++-- cpp_utils/RESTClient.h | 5 +++-- cpp_utils/component.mk | 3 --- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp_utils/RESTClient.cpp b/cpp_utils/RESTClient.cpp index 5cc259c9..20b5bc3d 100644 --- a/cpp_utils/RESTClient.cpp +++ b/cpp_utils/RESTClient.cpp @@ -4,7 +4,9 @@ * Created on: Mar 12, 2017 * Author: kolban */ -#if defined(ESP_HAVE_CURL) + +#include "sdkconfig.h" +#if defined(CONFIG_LIBCURL_PRESENT) #define _GLIBCXX_USE_C99 // Needed for std::string -> to_string inclusion. @@ -163,4 +165,4 @@ std::string RESTTimings::toString() { "\nTotal: " + std::to_string(m_total); return ret; } // toString -#endif // ESP_HAVE_CURL +#endif // CONFIG_LIBCURL_PRESENT diff --git a/cpp_utils/RESTClient.h b/cpp_utils/RESTClient.h index d9c4e04e..02279c40 100644 --- a/cpp_utils/RESTClient.h +++ b/cpp_utils/RESTClient.h @@ -7,7 +7,8 @@ #ifndef MAIN_RESTCLIENT_H_ #define MAIN_RESTCLIENT_H_ -#if defined(ESP_HAVE_CURL) +#include "sdkconfig.h" +#if defined(CONFIG_LIBCURL_PRESENT) #include #include @@ -120,5 +121,5 @@ class RESTClient { static size_t handleData(void *buffer, size_t size, size_t nmemb, void *userp); void prepForCall(); }; -#endif /* ESP_HAVE_CURL */ +#endif /* CONFIG_LIBCURL_PRESENT */ #endif /* MAIN_RESTCLIENT_H_ */ diff --git a/cpp_utils/component.mk b/cpp_utils/component.mk index 6952ed54..7dacf4a8 100644 --- a/cpp_utils/component.mk +++ b/cpp_utils/component.mk @@ -7,9 +7,6 @@ # please read the ESP-IDF documents if you need to do this. COMPONENT_ADD_INCLUDEDIRS=. -## Uncomment the following line if we have an implementation of libcurl available to us. -##CXXFLAGS+=-DESP_HAVE_CURL - ## Uncomment the following line to enable exception handling #CXXFLAGS+=-fexceptions #CXXFLAGS+= -std=c++11 \ No newline at end of file From 96553747ad699b5652915c9e1d39a02171dee424 Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Fri, 6 Oct 2017 11:03:53 +0300 Subject: [PATCH 088/381] If i opened 192.168.4.1 without any path after, the esp would crash This fixes that --- cpp_utils/WebServer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index ebdd54aa..ba871243 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -590,7 +590,10 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m filePath += httpResponse.getRootPath(); filePath.append(message->uri.p, message->uri.len); ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); - FILE* file = fopen(filePath.c_str(), "rb"); + FILE* file = nullptr; + + if(strcmp(filePath.c_str(), "/") != 0) + file = fopen(filePath.c_str(), "rb"); if (file != nullptr) { auto pData = (uint8_t*)malloc(MAX_CHUNK_LENGTH); size_t read = fread(pData, 1, MAX_CHUNK_LENGTH, file); From c288b0b276f018f26bbf6ff8f1872cba1e5d5df2 Mon Sep 17 00:00:00 2001 From: kolban Date: Fri, 6 Oct 2017 08:58:12 -0500 Subject: [PATCH 089/381] Enhancement for #99 --- cpp_utils/BLERemoteCharacteristic.cpp | 13 ++++++++++--- cpp_utils/BLERemoteCharacteristic.h | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 68fbaf38..25a01cf0 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -41,7 +41,7 @@ BLERemoteCharacteristic::BLERemoteCharacteristic( m_pRemoteService = pRemoteService; m_notifyCallback = nullptr; - getDescriptors(); // Get the descriptors for this characteristic + retrieveCharacteristics(); // Get the descriptors for this characteristic ESP_LOGD(LOG_TAG, "<< BLERemoteCharacteristic"); } // BLERemoteCharacteristic @@ -276,8 +276,8 @@ void BLERemoteCharacteristic::gattClientEventHandler( /** * @brief Populate the descriptors (if any) for this characteristic. */ -void BLERemoteCharacteristic::getDescriptors() { - ESP_LOGD(LOG_TAG, ">> getDescriptors() for characteristic: %s", getUUID().toString().c_str()); +void BLERemoteCharacteristic::retrieveCharacteristics() { + ESP_LOGD(LOG_TAG, ">> retrieveCharacteristics() for characteristic: %s", getUUID().toString().c_str()); removeDescriptors(); // Remove any existing descriptors. @@ -326,6 +326,13 @@ void BLERemoteCharacteristic::getDescriptors() { } // getDescriptors +/** + * @brief Retrieve the map of descriptors keyed by UUID. + */ +std::map* BLERemoteCharacteristic::getDescriptors() { + return &m_descriptorMap; +} // getDescriptors + /** * @brief Get the handle for this characteristic. diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index b34e3649..c06a06b3 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -59,10 +59,11 @@ class BLERemoteCharacteristic { esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); - void getDescriptors(); + std::map* getDescriptors(); uint16_t getHandle(); BLERemoteService* getRemoteService(); void removeDescriptors(); + void retrieveCharacteristics(); // Private properties BLEUUID m_uuid; From 036971d925da09a776a4a5dc23ba20341f592d40 Mon Sep 17 00:00:00 2001 From: kolban Date: Fri, 6 Oct 2017 09:56:04 -0500 Subject: [PATCH 090/381] More chnages for #99 --- cpp_utils/BLERemoteCharacteristic.cpp | 8 ++++---- cpp_utils/BLERemoteCharacteristic.h | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 25a01cf0..2e0fbb0e 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -41,7 +41,7 @@ BLERemoteCharacteristic::BLERemoteCharacteristic( m_pRemoteService = pRemoteService; m_notifyCallback = nullptr; - retrieveCharacteristics(); // Get the descriptors for this characteristic + retrieveDescriptors(); // Get the descriptors for this characteristic ESP_LOGD(LOG_TAG, "<< BLERemoteCharacteristic"); } // BLERemoteCharacteristic @@ -276,8 +276,8 @@ void BLERemoteCharacteristic::gattClientEventHandler( /** * @brief Populate the descriptors (if any) for this characteristic. */ -void BLERemoteCharacteristic::retrieveCharacteristics() { - ESP_LOGD(LOG_TAG, ">> retrieveCharacteristics() for characteristic: %s", getUUID().toString().c_str()); +void BLERemoteCharacteristic::retrieveDescriptors() { + ESP_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str()); removeDescriptors(); // Remove any existing descriptors. @@ -322,7 +322,7 @@ void BLERemoteCharacteristic::retrieveCharacteristics() { offset++; } // while true //m_haveCharacteristics = true; // Remember that we have received the characteristics. - ESP_LOGD(LOG_TAG, "<< getDescriptors(): Found %d descriptors.", offset); + ESP_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", offset); } // getDescriptors diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index c06a06b3..e764fb31 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -37,6 +37,7 @@ class BLERemoteCharacteristic { bool canWrite(); bool canWriteNoResponse(); BLERemoteDescriptor *getDescriptor(BLEUUID uuid); + std::map* getDescriptors(); BLEUUID getUUID(); std::string readValue(void); uint8_t readUInt8(void); @@ -59,11 +60,11 @@ class BLERemoteCharacteristic { esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); - std::map* getDescriptors(); + uint16_t getHandle(); BLERemoteService* getRemoteService(); void removeDescriptors(); - void retrieveCharacteristics(); + void retrieveDescriptors(); // Private properties BLEUUID m_uuid; From a8ff24ddf1190501caadabfd55c715bdfe91f074 Mon Sep 17 00:00:00 2001 From: dentellaluca Date: Sat, 7 Oct 2017 10:40:54 +0200 Subject: [PATCH 091/381] Changed variable name (addr) in struct spi_transaction_t The variable name changed from address to addr in esp-idf, see this commit: https://github.com/espressif/esp-idf/commit/ed1d084aeaef1c7216004354bd5b1a08f4a584cf#diff-c3da310493525a7302b298b32f3ac781 --- hardware/displays/U8G2/u8g2_esp32_hal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardware/displays/U8G2/u8g2_esp32_hal.c b/hardware/displays/U8G2/u8g2_esp32_hal.c index c4ea7875..49f71c4d 100644 --- a/hardware/displays/U8G2/u8g2_esp32_hal.c +++ b/hardware/displays/U8G2/u8g2_esp32_hal.c @@ -75,7 +75,7 @@ uint8_t u8g2_esp32_msg_comms_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void case U8X8_MSG_BYTE_SEND: { spi_transaction_t trans_desc; - trans_desc.address = 0; + trans_desc.addr = 0; trans_desc.command = 0; trans_desc.flags = 0; trans_desc.length = 8 * arg_int; // Number of bits NOT number of bytes. From 2330db843a0286d4aebd6a196abdc867606d70f4 Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 7 Oct 2017 09:34:04 -0500 Subject: [PATCH 092/381] Sync commit 2017-10-07 933CT --- cpp_utils/BLECharacteristic.cpp | 11 ++++++----- cpp_utils/BLERemoteCharacteristic.h | 9 +++++---- cpp_utils/BLERemoteService.cpp | 2 ++ cpp_utils/BLEServer.cpp | 2 +- cpp_utils/BLEService.cpp | 7 +++---- cpp_utils/BLEUUID.cpp | 1 + cpp_utils/BLEUUID.h | 4 ++-- cpp_utils/BLEValue.cpp | 23 +++++++++++++++++++++++ cpp_utils/BLEValue.h | 2 ++ 9 files changed, 45 insertions(+), 16 deletions(-) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index cdc2200e..284032e7 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -94,19 +94,20 @@ void BLECharacteristic::executeCreate(BLEService* pService) { m_semaphoreCreateEvt.take("executeCreate"); - std::string strValue = m_value.getValue(); - + /* esp_attr_value_t value; - value.attr_len = strValue.length(); + value.attr_len = m_value.getLength(); value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; - value.attr_value = (uint8_t*)strValue.data(); + value.attr_value = m_value.getData(); + */ esp_err_t errRc = ::esp_ble_gatts_add_char( m_pService->getHandle(), getUUID().getNative(), static_cast(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), getProperties(), - &value, + //&value, + nullptr, &control); // Whether to auto respond or not. if (errRc != ESP_OK) { diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index e764fb31..0b6750a6 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -36,14 +36,14 @@ class BLERemoteCharacteristic { bool canRead(); bool canWrite(); bool canWriteNoResponse(); - BLERemoteDescriptor *getDescriptor(BLEUUID uuid); + BLERemoteDescriptor* getDescriptor(BLEUUID uuid); std::map* getDescriptors(); BLEUUID getUUID(); std::string readValue(void); uint8_t readUInt8(void); uint16_t readUInt16(void); uint32_t readUInt32(void); - void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)); + void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify)); void writeValue(uint8_t* data, size_t length, bool response = false); void writeValue(std::string newValue, bool response = false); void writeValue(uint8_t newValue, bool response = false); @@ -75,9 +75,10 @@ class BLERemoteCharacteristic { FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt"); FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); std::string m_value; - void (*m_notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify); + void (*m_notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify); + // We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID. - std::map m_descriptorMap; + std::map m_descriptorMap; }; // BLERemoteCharacteristic #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ */ diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index b6999cbf..c312e946 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -269,12 +269,14 @@ void BLERemoteService::retrieveCharacteristics() { * @return A map of all the characteristics of this service. */ std::map* BLERemoteService::getCharacteristics() { + ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); // If is possible that we have not read the characteristics associated with the service so do that // now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking // call and does not return until all the characteristics are available. if (!m_haveCharacteristics) { retrieveCharacteristics(); } + ESP_LOGD(LOG_TAG, "<< getCharacteristics() for service: %s", getUUID().toString().c_str()); return &m_characteristicMap; } // getCharacteristics diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 14e44a0d..a7e61d46 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -168,7 +168,7 @@ void BLEServer::handleGATTServerEvent( esp_ble_gatts_cb_param_t* param) { ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", - BLEUtils::gattServerEventTypeToString(event).c_str()); + BLEUtils::gattServerEventTypeToString(event).c_str()); // Invoke the handler for every Service we have. m_serviceMap.handleGATTServerEvent(event, gatts_if, param); diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 4810f07b..96624f2e 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -54,8 +54,8 @@ BLEService::BLEService(BLEUUID uuid) { * @return N/A. */ void BLEService::executeCreate(BLEServer *pServer) { - ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); - + //ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); + getUUID(); // Needed for a weird bug fix m_pServer = pServer; m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT @@ -70,7 +70,6 @@ void BLEService::executeCreate(BLEServer *pServer) { } m_semaphoreCreateEvt.wait("executeCreate"); - ESP_LOGD(LOG_TAG, "<< executeCreate"); } // executeCreate @@ -171,7 +170,7 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { // to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF). // ESP_LOGD(LOG_TAG, ">> addCharacteristic()"); - ESP_LOGD(LOG_TAG, "Adding characteristic (esp_ble_gatts_add_char): uuid=%s to service: %s", + ESP_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s", pCharacteristic->getUUID().toString().c_str(), toString().c_str()); diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index e02a9895..0cffe041 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -35,6 +35,7 @@ static const char* LOG_TAG = "BLEUUID"; * @param [in] size The number of bytes to copy */ static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { + assert(size > 0); target+=(size-1); // Point target to the last byte of the target data while (size > 0) { *target = *source; diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index cfce7c93..a11220b9 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -30,8 +30,8 @@ class BLEUUID { std::string toString(); private: - esp_bt_uuid_t m_uuid; - bool m_valueSet; // Is there a value set for this instance. + esp_bt_uuid_t m_uuid; // The underlying UUID structure that this class wraps. + bool m_valueSet; // Is there a value set for this instance. }; // BLEUUID #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEUUID_H_ */ diff --git a/cpp_utils/BLEValue.cpp b/cpp_utils/BLEValue.cpp index 1989993a..d36d207a 100644 --- a/cpp_utils/BLEValue.cpp +++ b/cpp_utils/BLEValue.cpp @@ -71,6 +71,24 @@ void BLEValue::commit() { } // commit +/** + * @brief Get a pointer to the data. + * @return A pointer to the data. + */ +uint8_t* BLEValue::getData() { + return (uint8_t*)m_value.data(); +} + + +/** + * @brief Get the length of the data in bytes. + * @return The length of the data in bytes. + */ +size_t BLEValue::getLength() { + return m_value.length(); +} // getLength + + /** * @brief Get the read offset. * @return The read offset into the read. @@ -113,4 +131,9 @@ void BLEValue::setValue(std::string value) { void BLEValue::setValue(uint8_t* pData, size_t length) { m_value = std::string((char*)pData, length); } // setValue + + + + + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEValue.h b/cpp_utils/BLEValue.h index a292c6e1..92a7f9a4 100644 --- a/cpp_utils/BLEValue.h +++ b/cpp_utils/BLEValue.h @@ -21,6 +21,8 @@ class BLEValue { void addPart(uint8_t* pData, size_t length); void cancel(); void commit(); + uint8_t* getData(); + size_t getLength(); uint16_t getReadOffset(); std::string getValue(); void setReadOffset(uint16_t readOffset); From f9d7d31fef93a760580dbef2ebef70dc565cc79e Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 7 Oct 2017 10:32:29 -0500 Subject: [PATCH 093/381] Fixes for #108 --- cpp_utils/tests/test_rest.cpp | 6 +++++- curl/README.md | 2 ++ posix/README.md | 8 ++++++++ posix/posix_shims.c | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 curl/README.md create mode 100644 posix/README.md create mode 100644 posix/posix_shims.c diff --git a/cpp_utils/tests/test_rest.cpp b/cpp_utils/tests/test_rest.cpp index 604d3adb..dc168e64 100644 --- a/cpp_utils/tests/test_rest.cpp +++ b/cpp_utils/tests/test_rest.cpp @@ -2,6 +2,10 @@ * Test the REST API client. * This application leverages LibCurl. You must make that package available * as well as "enable it" from "make menuconfig" and C++ Settings -> libCurl present. + * + * You may also have to include "posix_shims.c" in your compilation to provide resolution + * for Posix calls expected by libcurl that aren't present in ESP-IDF. + * * See also: * * https://github.com/nkolban/esp32-snippets/issues/108 * @@ -37,7 +41,7 @@ class CurlTestTask: public Task { RESTTimings *timings = client.getTimings(); - client.setURL("https://httpbin.org/post"); + client.setURL("http://httpbin.org/post"); client.addHeader("Content-Type", "application/json"); client.post("hello world!"); ESP_LOGD(tag, "Result: %s", client.getResponse().c_str()); diff --git a/curl/README.md b/curl/README.md new file mode 100644 index 00000000..49ea404f --- /dev/null +++ b/curl/README.md @@ -0,0 +1,2 @@ +## Missing Posix functions +LibCurl is a moving target and on occassion, is updated to utilize functions that are not part of the ESP-IDF. For example, at the time of writing, the latest version of LibCurl uses the Posix `access(2)` system call that it previously did not. This call is not present in ESP-IDF. To circumvent the problem, a shim file has been provided in `./posix/posix_shims.c` that provides simple implementations for some missing Posix functions. \ No newline at end of file diff --git a/posix/README.md b/posix/README.md new file mode 100644 index 00000000..f534e952 --- /dev/null +++ b/posix/README.md @@ -0,0 +1,8 @@ +# POSIX +Posix is the specification for Unix like functions. The ESP-IDF provides many implementations for Posix functions but some are omitted and thus should not be used in ESP32 based applications. However there are times when we received 3rd party code that uses a Posix function that we don't have in our environment. Our choices then become: + +* Contact the 3rd party provider and ask them to alter their code to remove it, replace it or make it optional. +* Contact Espressif and ask them to add support for the missing Posix function. +* Provide a shim that satisfies the Posix function using the capabailities that are available to us. + +In the source file called `posix_shims.c` we provide some of those shims. \ No newline at end of file diff --git a/posix/posix_shims.c b/posix/posix_shims.c new file mode 100644 index 00000000..5515e250 --- /dev/null +++ b/posix/posix_shims.c @@ -0,0 +1,18 @@ +#include +#include +#include + +/** + * @brief Provide a shim for the Posix access(2) function. + * @param [in] pathname The file to check. + * @param [in] mode The mode of access requested. + * @return 0 on success, -1 on error with errno set. + */ +int access(const char *pathname, int mode) { + struct stat statBuf; + if (stat(pathname, &statBuf) == -1) { + errno = ENOENT; + return -1; + } + return 0; // Indicate that all we have access to the file. +} // access From 1bc04029909d839e461d67f6f02a3ba2ece38d53 Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 7 Oct 2017 10:47:31 -0500 Subject: [PATCH 094/381] Fixes for #101 --- cpp_utils/BLERemoteCharacteristic.h | 3 ++- cpp_utils/BLERemoteDescriptor.h | 3 ++- cpp_utils/BLERemoteService.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 0b6750a6..6f23f497 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -38,6 +38,7 @@ class BLERemoteCharacteristic { bool canWriteNoResponse(); BLERemoteDescriptor* getDescriptor(BLEUUID uuid); std::map* getDescriptors(); + uint16_t getHandle(); BLEUUID getUUID(); std::string readValue(void); uint8_t readUInt8(void); @@ -61,7 +62,7 @@ class BLERemoteCharacteristic { esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); - uint16_t getHandle(); + BLERemoteService* getRemoteService(); void removeDescriptors(); void retrieveDescriptors(); diff --git a/cpp_utils/BLERemoteDescriptor.h b/cpp_utils/BLERemoteDescriptor.h index 89a2e221..c2cf3836 100644 --- a/cpp_utils/BLERemoteDescriptor.h +++ b/cpp_utils/BLERemoteDescriptor.h @@ -23,6 +23,7 @@ class BLERemoteCharacteristic; */ class BLERemoteDescriptor { public: + uint16_t getHandle(); BLEUUID getUUID(); std::string readValue(void); uint8_t readUInt8(void); @@ -48,7 +49,7 @@ class BLERemoteDescriptor { BLERemoteCharacteristic* m_pRemoteCharacteristic; // Reference to the Remote characteristic of which this descriptor is associated. FreeRTOS::Semaphore m_semaphoreReadDescrEvt = FreeRTOS::Semaphore("ReadDescrEvt"); - uint16_t getHandle(); + }; #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ */ diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 75262868..2007fa22 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -35,6 +35,7 @@ class BLERemoteService { std::map* getCharacteristics(); BLEClient* getClient(void); + uint16_t getHandle(); BLEUUID getUUID(void); std::string toString(void); @@ -48,7 +49,6 @@ class BLERemoteService { // Private methods void retrieveCharacteristics(void); - uint16_t getHandle(); esp_gatt_id_t* getSrvcId(void); uint16_t getStartHandle(); uint16_t getEndHandle(); From d8f4d7ae53ad3fc72882aeedad7a995c6cc49de2 Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 7 Oct 2017 13:16:17 -0500 Subject: [PATCH 095/381] Removal of un-needed semaphore --- cpp_utils/BLEService.cpp | 5 ++--- cpp_utils/BLEService.h | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 96624f2e..208e103f 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -222,7 +222,7 @@ void BLEService::handleGATTServerEvent( switch(event) { - // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. + // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. // add_char: // - esp_gatt_status_t status // - uint16_t attr_handle @@ -238,13 +238,11 @@ void BLEService::handleGATTServerEvent( ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", BLEUUID(param->add_char.char_uuid).toString().c_str()); dump(); - m_semaphoreAddCharEvt.give(); break; } pCharacteristic->setHandle(param->add_char.attr_handle); m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic); //ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str()); - m_semaphoreAddCharEvt.give(); break; } // Reached the correct service. break; @@ -288,6 +286,7 @@ void BLEService::handleGATTServerEvent( } // Default } // Switch + // Invoke the GATTS handler in each of the associated characteristics. m_characteristicMap.handleGATTServerEvent(event, gatts_if, param); } // handleGATTServerEvent diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 86d0776b..1bbf46e0 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -79,7 +79,6 @@ class BLEService { BLECharacteristic* m_lastCreatedCharacteristic; BLEServer* m_pServer; //FreeRTOS::Semaphore m_serializeMutex; - FreeRTOS::Semaphore m_semaphoreAddCharEvt = FreeRTOS::Semaphore("AddCharEvt"); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); BLEUUID m_uuid; From 240d94e6fcd1e2e290a784191f1694207f134259 Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 7 Oct 2017 18:20:37 -0500 Subject: [PATCH 096/381] Changes for enhancement #113 --- cpp_utils/BLEService.cpp | 1 + cpp_utils/BLEUtils.cpp | 40 +++++++++++++++++++++++++++ cpp_utils/BLEUtils.h | 58 ++++++++++++++++++++-------------------- 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 208e103f..9ddf8a79 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -248,6 +248,7 @@ void BLEService::handleGATTServerEvent( break; } // ESP_GATTS_ADD_CHAR_EVT + // ESP_GATTS_START_EVT // // start: diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 00bfd470..402fcc8d 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -288,6 +288,29 @@ static const member_t members_ids[] = { {0, "" } }; +typedef struct { + uint32_t assignedNumber; + std::string name; +} gattdescriptor_t; + +static const gattdescriptor_t g_descriptor_ids[] = { + {0x2905,"Characteristic Aggregate Format"}, + {0x2900,"Characteristic Extended Properties"}, + {0x2904,"Characteristic Presentation Format"}, + {0x2901,"Characteristic User Description"}, + {0x2902,"Client Characteristic Configuration"}, + {0x290B,"Environmental Sensing Configuration"}, + {0x290C,"Environmental Sensing Measurement"}, + {0x290D,"Environmental Sensing Trigger Setting"}, + {0x2907,"External Report Reference"}, + {0x2909,"Number of Digitals"}, + {0x2908,"Report Reference"}, + {0x2903,"Server Characteristic Configuration"}, + {0x290E,"Time Trigger Setting"}, + {0x2906,"Valid Range"}, + {0x290A,"Value Trigger Setting"}, + { 0, "" } +}; typedef struct { uint32_t assignedNumber; @@ -1801,6 +1824,23 @@ std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID } // gattCharacteristicUUIDToString +/** + * @brief Given the UUID for a BLE defined descriptor, return its string representation. + * @param [in] descriptorUUID UUID of the descriptor to be returned as a string. + * @return The string representation of a descriptor UUID. + */ +std::string BLEUtils::gattDescriptorUUIDToString(uint32_t descriptorUUID) { + gattdescriptor_t* p = (gattdescriptor_t *)g_descriptor_ids; + while (p->name.length() > 0) { + if (p->assignedNumber == descriptorUUID) { + return p->name; + } + p++; + } + return ""; +} // gattDescriptorUUIDToString + + /** * @brief Return a string representation of an esp_gattc_service_elem_t. * @return A string representation of an esp_gattc_service_elem_t. diff --git a/cpp_utils/BLEUtils.h b/cpp_utils/BLEUtils.h index 96066af9..5ef6c378 100644 --- a/cpp_utils/BLEUtils.h +++ b/cpp_utils/BLEUtils.h @@ -20,42 +20,42 @@ */ class BLEUtils { public: - static const char* advTypeToString(uint8_t advType); - static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id=0); - + static const char* addressTypeToString(esp_ble_addr_type_t type); + static const char* advTypeToString(uint8_t advType); + static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id=0); static esp_gatt_srvc_id_t buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary=true); - static std::string characteristicPropertiesToString(esp_gatt_char_prop_t prop); - static char* buildHexData(uint8_t *target, uint8_t *source, uint8_t length); - static BLEClient* findByConnId(uint16_t conn_id); - static BLEClient* findByAddress(BLEAddress address); - static std::string gattClientEventTypeToString(esp_gattc_cb_event_t eventType); - static std::string gattServerEventTypeToString(esp_gatts_cb_event_t eventType); - static std::string gattcServiceElementToString(esp_gattc_service_elem_t *pGATTCServiceElement); - static std::string gattServiceIdToString(esp_gatt_srvc_id_t srvcId); - static std::string gattStatusToString(esp_gatt_status_t status); - static std::string gattServiceToString(uint32_t serviceId); - static std::string gattCloseReasonToString(esp_gatt_conn_reason_t reason); - static void registerByAddress(BLEAddress address, BLEClient* pDevice); - static void registerByConnId(uint16_t conn_id, BLEClient* pDevice); - static std::string gattCharacteristicUUIDToString(uint32_t characteristicUUID); - static std::string getMember(uint32_t memberId); - static std::string buildPrintData(uint8_t* source, size_t length); + static char* buildHexData(uint8_t* target, uint8_t* source, uint8_t length); + static std::string buildPrintData(uint8_t* source, size_t length); + static std::string characteristicPropertiesToString(esp_gatt_char_prop_t prop); + static const char* devTypeToString(esp_bt_dev_type_t type); + static void dumpGapEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); static void dumpGattClientEvent( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); static void dumpGattServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* evtParam); - static const char* devTypeToString(esp_bt_dev_type_t type); - static void dumpGapEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param); + static const char* eventTypeToString(esp_ble_evt_type_t eventType); + static BLEClient* findByAddress(BLEAddress address); + static BLEClient* findByConnId(uint16_t conn_id); static const char* gapEventToString(uint32_t eventType); + static std::string gattCharacteristicUUIDToString(uint32_t characteristicUUID); + static std::string gattClientEventTypeToString(esp_gattc_cb_event_t eventType); + static std::string gattCloseReasonToString(esp_gatt_conn_reason_t reason); + static std::string gattcServiceElementToString(esp_gattc_service_elem_t *pGATTCServiceElement); + static std::string gattDescriptorUUIDToString(uint32_t descriptorUUID); + static std::string gattServerEventTypeToString(esp_gatts_cb_event_t eventType); + static std::string gattServiceIdToString(esp_gatt_srvc_id_t srvcId); + static std::string gattServiceToString(uint32_t serviceId); + static std::string gattStatusToString(esp_gatt_status_t status); + static std::string getMember(uint32_t memberId); + static void registerByAddress(BLEAddress address, BLEClient* pDevice); + static void registerByConnId(uint16_t conn_id, BLEClient* pDevice); static const char* searchEventTypeToString(esp_gap_search_evt_t searchEvt); - static const char* addressTypeToString(esp_ble_addr_type_t type); - static const char *eventTypeToString(esp_ble_evt_type_t eventType); }; #endif // CONFIG_BT_ENABLED From adc302638a42e625a1bc91c752e231b0ccaaa02a Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 7 Oct 2017 19:08:20 -0500 Subject: [PATCH 097/381] Fixes for #114 --- cpp_utils/BLEService.cpp | 19 +++++++++++++------ cpp_utils/BLEService.h | 9 +++++---- cpp_utils/BLEUtils.cpp | 24 +++++++++++++++++------- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 9ddf8a79..9e7fa280 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -29,21 +29,24 @@ static const char* LOG_TAG = "BLEService"; // Tag for logging. /** * @brief Construct an instance of the BLEService * @param [in] uuid The UUID of the service. + * @param [in] numHandles The maximum number of handles associated with the service. */ -BLEService::BLEService(const char* uuid) : BLEService(BLEUUID(uuid)) { +BLEService::BLEService(const char* uuid, uint32_t numHandles) : BLEService(BLEUUID(uuid), numHandles) { } /** * @brief Construct an instance of the BLEService * @param [in] uuid The UUID of the service. + * @param [in] numHandles The maximum number of handles associated with the service. */ -BLEService::BLEService(BLEUUID uuid) { - m_uuid = uuid; - m_handle = NULL_HANDLE; - m_pServer = nullptr; +BLEService::BLEService(BLEUUID uuid, uint32_t numHandles) { + m_uuid = uuid; + m_handle = NULL_HANDLE; + m_pServer = nullptr; //m_serializeMutex.setName("BLEService"); m_lastCreatedCharacteristic = nullptr; + m_numHandles = numHandles; } // BLEService @@ -62,7 +65,11 @@ void BLEService::executeCreate(BLEServer *pServer) { esp_gatt_srvc_id_t srvc_id; srvc_id.id.inst_id = 0; srvc_id.id.uuid = *m_uuid.getNative(); - esp_err_t errRc = ::esp_ble_gatts_create_service(getServer()->getGattsIf(), &srvc_id, 10); + esp_err_t errRc = ::esp_ble_gatts_create_service( + getServer()->getGattsIf(), + &srvc_id, + m_numHandles // The maximum number of handles associated with the service. + ); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 1bbf46e0..9a93aff7 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -52,8 +52,8 @@ class BLECharacteristicMap { */ class BLEService { public: - BLEService(const char* uuid); - BLEService(BLEUUID uuid); + BLEService(const char* uuid, uint32_t numHandles=10); + BLEService(BLEUUID uuid, uint32_t numHandles=10); void addCharacteristic(BLECharacteristic* pCharacteristic); BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties); @@ -79,9 +79,10 @@ class BLEService { BLECharacteristic* m_lastCreatedCharacteristic; BLEServer* m_pServer; //FreeRTOS::Semaphore m_serializeMutex; - FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); - FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); BLEUUID m_uuid; + uint32_t m_numHandles; uint16_t getHandle(); BLECharacteristic* getLastCreatedCharacteristic(); diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 402fcc8d..170fbf7b 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -1542,13 +1542,23 @@ void BLEUtils::dumpGattServerEvent( } // ESP_GATTS_ADD_CHAR_DESCR_EVT case ESP_GATTS_ADD_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", - gattStatusToString(evtParam->add_char.status).c_str(), - evtParam->add_char.attr_handle, - evtParam->add_char.attr_handle, - evtParam->add_char.service_handle, - evtParam->add_char.service_handle, - BLEUUID(evtParam->add_char.char_uuid).toString().c_str()); + if (evtParam->add_char.status == ESP_GATT_OK) { + ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", + gattStatusToString(evtParam->add_char.status).c_str(), + evtParam->add_char.attr_handle, + evtParam->add_char.attr_handle, + evtParam->add_char.service_handle, + evtParam->add_char.service_handle, + BLEUUID(evtParam->add_char.char_uuid).toString().c_str()); + } else { + ESP_LOGE(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", + gattStatusToString(evtParam->add_char.status).c_str(), + evtParam->add_char.attr_handle, + evtParam->add_char.attr_handle, + evtParam->add_char.service_handle, + evtParam->add_char.service_handle, + BLEUUID(evtParam->add_char.char_uuid).toString().c_str()); + } break; } // ESP_GATTS_ADD_CHAR_EVT From e2bf94ab10238679d763f20cb271e57ccd0a0f93 Mon Sep 17 00:00:00 2001 From: kolban Date: Sat, 7 Oct 2017 19:15:11 -0500 Subject: [PATCH 098/381] More code for #114 --- cpp_utils/BLEServer.cpp | 5 +++-- cpp_utils/BLEServer.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index a7e61d46..4a8dfd57 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -66,9 +66,10 @@ BLEService* BLEServer::createService(const char* uuid) { * With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition * of a new service. Every service must have a unique UUID. * @param [in] uuid The UUID of the new service. + * @param [in] numHandles The maximum number of handles associated with this service. * @return A reference to the new service object. */ -BLEService* BLEServer::createService(BLEUUID uuid) { +BLEService* BLEServer::createService(BLEUUID uuid, uint32_t numHandles) { ESP_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str()); m_semaphoreCreateEvt.take("createService"); @@ -80,7 +81,7 @@ BLEService* BLEServer::createService(BLEUUID uuid) { return nullptr; } - BLEService* pService = new BLEService(uuid); + BLEService* pService = new BLEService(uuid, numHandles); m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server. pService->executeCreate(this); // Perform the API calls to actually create the service. diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index c7580d24..64b34b41 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -53,7 +53,7 @@ class BLEServer { public: uint32_t getConnectedCount(); BLEService* createService(const char* uuid); - BLEService* createService(BLEUUID uuid); + BLEService* createService(BLEUUID uuid, uint32_t numHandles=15); BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks *pCallbacks); void startAdvertising(); From 1891195b996e87b9cf5e84711dd48fed85d8c295 Mon Sep 17 00:00:00 2001 From: FulminatingGhost Date: Sun, 8 Oct 2017 20:10:58 +0200 Subject: [PATCH 099/381] Added methods to Task class and moved a debug logging line. - Added a method "setPriority" that enables one to set the task priority. - Added a method "setName" that enables one to set the name of the Task outside the constructor. - Moved the task exit ESP_LOG call in front of the stop() call. It would otherwise never be called. --- cpp_utils/Task.cpp | 27 ++++++++++++++++++++++++--- cpp_utils/Task.h | 5 ++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/cpp_utils/Task.cpp b/cpp_utils/Task.cpp index fe6ba2d0..da1a7bb8 100644 --- a/cpp_utils/Task.cpp +++ b/cpp_utils/Task.cpp @@ -24,9 +24,10 @@ static char tag[] = "Task"; * @param [in] stackSize The size of the stack. * @return N/A. */ -Task::Task(std::string taskName, uint16_t stackSize) { +Task::Task(std::string taskName, uint16_t stackSize, uint8_t priority) { m_taskName = taskName; m_stackSize = stackSize; + m_priority = priority; m_taskData = nullptr; m_handle = nullptr; } // Task @@ -55,8 +56,8 @@ void Task::runTask(void* pTaskInstance) { Task* pTask = (Task*)pTaskInstance; ESP_LOGD(tag, ">> runTask: taskName=%s", pTask->m_taskName.c_str()); pTask->run(pTask->m_taskData); - pTask->stop(); ESP_LOGD(tag, "<< runTask: taskName=%s", pTask->m_taskName.c_str()); + pTask->stop(); } // runTask /** @@ -70,7 +71,7 @@ void Task::start(void* taskData) { ESP_LOGW(tag, "Task::start - There might be a task already running!"); } m_taskData = taskData; - ::xTaskCreate(&runTask, m_taskName.c_str(), m_stackSize, this, 5, &m_handle); + ::xTaskCreate(&runTask, m_taskName.c_str(), m_stackSize, this, m_priority, &m_handle); } // start @@ -97,3 +98,23 @@ void Task::stop() { void Task::setStackSize(uint16_t stackSize) { m_stackSize = stackSize; } // setStackSize + +/** + * @brief Set the priority of the task. + * + * @param [in] priority The priority for the task. + * @return N/A. + */ +void Task::setPriority(uint8_t priority) { + m_priority = priority; +} // setPriority + +/** + * @brief Set the name of the task. + * + * @param [in] name The name for the task. + * @return N/A. + */ +void Task::setName(std::string name) { + m_taskName = name; +} // setName diff --git a/cpp_utils/Task.h b/cpp_utils/Task.h index 51e18fc8..21582896 100644 --- a/cpp_utils/Task.h +++ b/cpp_utils/Task.h @@ -33,9 +33,11 @@ */ class Task { public: - Task(std::string taskName="Task", uint16_t stackSize=10000); + Task(std::string taskName="Task", uint16_t stackSize=10000, uint8_t priority=5); virtual ~Task(); void setStackSize(uint16_t stackSize); + void setPriority(uint8_t priority); + void setName(std::string name); void start(void* taskData=nullptr); void stop(); /** @@ -56,6 +58,7 @@ class Task { static void runTask(void *data); std::string m_taskName; uint16_t m_stackSize; + uint8_t m_priority; }; #endif /* COMPONENTS_CPP_UTILS_TASK_H_ */ From 1879d725911f2ec490539e9df4becb59f4b24679 Mon Sep 17 00:00:00 2001 From: FulminatingGhost Date: Sun, 8 Oct 2017 20:27:45 +0200 Subject: [PATCH 100/381] Changed tabs to spaces ... --- cpp_utils/Task.cpp | 2 +- cpp_utils/Task.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/Task.cpp b/cpp_utils/Task.cpp index da1a7bb8..4520478a 100644 --- a/cpp_utils/Task.cpp +++ b/cpp_utils/Task.cpp @@ -27,7 +27,7 @@ static char tag[] = "Task"; Task::Task(std::string taskName, uint16_t stackSize, uint8_t priority) { m_taskName = taskName; m_stackSize = stackSize; - m_priority = priority; + m_priority = priority; m_taskData = nullptr; m_handle = nullptr; } // Task diff --git a/cpp_utils/Task.h b/cpp_utils/Task.h index 21582896..74d7f332 100644 --- a/cpp_utils/Task.h +++ b/cpp_utils/Task.h @@ -58,7 +58,7 @@ class Task { static void runTask(void *data); std::string m_taskName; uint16_t m_stackSize; - uint8_t m_priority; + uint8_t m_priority; }; #endif /* COMPONENTS_CPP_UTILS_TASK_H_ */ From 7a4c880868e1147b6815be6f1393832c4390b3a1 Mon Sep 17 00:00:00 2001 From: kolban Date: Mon, 9 Oct 2017 08:53:05 -0500 Subject: [PATCH 101/381] Change requested for #92 --- cpp_utils/BLEUtils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 170fbf7b..a91bbcc9 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -285,6 +285,7 @@ static const member_t members_ids[] = { {0xFEFD, "Gimbal, Inc."}, {0xFEFE, "GN ReSound A/S"}, {0xFEFF, "GN Netcom"}, + {0xFFFF, "Reserved"}, /*for testing purposes only*/ {0, "" } }; From 2920d11821e0e3b85316730bf609d90449c271ab Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 10 Oct 2017 18:33:20 -0500 Subject: [PATCH 102/381] Changes for #119 and others --- cpp_utils/BLEAdvertising.cpp | 22 +- cpp_utils/BLEAdvertising.h | 1 + cpp_utils/BLEUUID.cpp | 95 +++--- cpp_utils/CPPNVS.cpp | 76 ++++- cpp_utils/CPPNVS.h | 8 +- cpp_utils/GeneralUtils.cpp | 8 +- cpp_utils/GeneralUtils.h | 5 +- cpp_utils/HttpRequest.cpp | 60 ++++ cpp_utils/HttpRequest.h | 2 + cpp_utils/HttpServer.cpp | 33 +- cpp_utils/HttpServer.h | 6 +- cpp_utils/SockServ.cpp | 64 ++-- cpp_utils/SockServ.h | 4 +- cpp_utils/Socket.cpp | 8 +- cpp_utils/Socket.h | 8 + cpp_utils/WiFi.cpp | 67 ++-- cpp_utils/WiFi.h | 7 +- networking/bootwifi/BootWiFi.cpp | 273 +++++++++++++++++ networking/bootwifi/BootWiFi.h | 32 ++ networking/bootwifi/README.md | 6 +- networking/bootwifi/bootwifi.c | 511 ------------------------------- networking/bootwifi/bootwifi.h | 15 - networking/bootwifi/component.mk | 8 +- 23 files changed, 657 insertions(+), 662 deletions(-) create mode 100644 networking/bootwifi/BootWiFi.cpp create mode 100644 networking/bootwifi/BootWiFi.h delete mode 100644 networking/bootwifi/bootwifi.c delete mode 100644 networking/bootwifi/bootwifi.h diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 049aa4dc..c1e0b30b 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -94,24 +94,10 @@ void BLEAdvertising::setServiceUUID(const char* serviceUUID) { void BLEAdvertising::setServiceUUID(BLEUUID serviceUUID) { ESP_LOGD(LOG_TAG, ">> setServiceUUID - %s", serviceUUID.toString().c_str()); m_serviceUUID = serviceUUID; // Save the new service UUID - esp_bt_uuid_t* espUUID = m_serviceUUID.getNative(); - switch(espUUID->len) { - case ESP_UUID_LEN_16: { - m_advDataScanResponse.service_uuid_len = 2; - m_advDataScanResponse.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid16); - break; - } - case ESP_UUID_LEN_32: { - m_advDataScanResponse.service_uuid_len = 4; - m_advDataScanResponse.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid32); - break; - } - case ESP_UUID_LEN_128: { - m_advDataScanResponse.service_uuid_len = 16; - m_advDataScanResponse.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid128); - break; - } - } // switch + m_serviceUUID128 = serviceUUID.to128(); + + m_advDataScanResponse.service_uuid_len = 16; + m_advDataScanResponse.p_service_uuid = reinterpret_cast(&m_serviceUUID128.getNative()->uuid.uuid128); ESP_LOGD(LOG_TAG, "<< setServiceUUID"); } // setServiceUUID diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index afe761d8..810431e7 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -30,6 +30,7 @@ class BLEAdvertising { esp_ble_adv_data_t m_advDataScanResponse; esp_ble_adv_params_t m_advParams; BLEUUID m_serviceUUID; + BLEUUID m_serviceUUID128; }; #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */ diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 0cffe041..19da5185 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -66,15 +66,18 @@ static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { BLEUUID::BLEUUID(std::string value) { m_valueSet = true; if (value.length() == 2) { - m_uuid.len = ESP_UUID_LEN_16; + m_uuid.len = ESP_UUID_LEN_16; m_uuid.uuid.uuid16 = value[0] | (value[1] << 8); - } else if (value.length() == 4) { - m_uuid.len = ESP_UUID_LEN_32; + } + else if (value.length() == 4) { + m_uuid.len = ESP_UUID_LEN_32; m_uuid.uuid.uuid32 = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); - } else if (value.length() == 16) { + } + else if (value.length() == 16) { m_uuid.len = ESP_UUID_LEN_128; memrcpy(m_uuid.uuid.uuid128, (uint8_t*)value.data(), 16); - } else if (value.length() == 36) { + } + else if (value.length() == 36) { // If the length of the string is 36 bytes then we will assume it is a long hex string in // UUID format. m_uuid.len = ESP_UUID_LEN_128; @@ -131,6 +134,7 @@ BLEUUID::BLEUUID(uint8_t* pData, size_t size, bool msbFirst) { m_valueSet = true; } // BLEUUID + /** * @brief Create a UUID from the 16bit value. * @@ -278,50 +282,67 @@ BLEUUID BLEUUID::to128() { } // to128 -//01234567 8901 2345 6789 012345678901 -//0000180d-0000-1000-8000-00805f9b34fb -//0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + /** * @brief Get a string representation of the UUID. * + * The format of a string is: + * 01234567 8901 2345 6789 012345678901 + * 0000180d-0000-1000-8000-00805f9b34fb + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * * @return A string representation of the UUID. */ std::string BLEUUID::toString() { - if (m_valueSet == false) { + if (m_valueSet == false) { // If we have no value, nothing to format. return ""; } - if (m_uuid.len == ESP_UUID_LEN_16) { - std::stringstream ss; - ss << "0000" << std::hex << std::setfill('0') << std::setw(4) << m_uuid.uuid.uuid16 << "-0000-1000-8000-00805f9b34fb"; - return ss.str(); - } - - if (m_uuid.len == ESP_UUID_LEN_32) { - std::stringstream ss; - ss << std::hex << std::setfill('0') << std::setw(8) << m_uuid.uuid.uuid32 << "-0000-1000-8000-00805f9b34fb"; - return ss.str(); - } - + // If the UUIDs are 16 or 32 bit, pad correctly. std::stringstream ss; + + if (m_uuid.len == ESP_UUID_LEN_16) { // If the UUID is 16bit, pad correctly. + ss << "0000" << + std::hex << + std::setfill('0') << + std::setw(4) << + m_uuid.uuid.uuid16 << + "-0000-1000-8000-00805f9b34fb"; + return ss.str(); // Return the string + } // End 16bit UUID + + if (m_uuid.len == ESP_UUID_LEN_32) { // If the UUID is 32bit, pad correctly. + ss << std::hex << + std::setfill('0') << + std::setw(8) << + m_uuid.uuid.uuid32 << + "-0000-1000-8000-00805f9b34fb"; + return ss.str(); // return the string + } // End 32bit UUID + + // The UUID is not 16bit or 32bit which means that it is 128bit. + // + // UUID string format: + // AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP + // ss << std::hex << std::setfill('0') << - std::setw(2) << (int)m_uuid.uuid.uuid128[15] << - std::setw(2) << (int)m_uuid.uuid.uuid128[14] << - std::setw(2) << (int)m_uuid.uuid.uuid128[13] << - std::setw(2) << (int)m_uuid.uuid.uuid128[12] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[11] << - std::setw(2) << (int)m_uuid.uuid.uuid128[10] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[9] << - std::setw(2) << (int)m_uuid.uuid.uuid128[8] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[7] << - std::setw(2) << (int)m_uuid.uuid.uuid128[6] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[5] << - std::setw(2) << (int)m_uuid.uuid.uuid128[4] << - std::setw(2) << (int)m_uuid.uuid.uuid128[3] << - std::setw(2) << (int)m_uuid.uuid.uuid128[2] << - std::setw(2) << (int)m_uuid.uuid.uuid128[1] << - std::setw(2) << (int)m_uuid.uuid.uuid128[0]; + std::setw(2) << (int)m_uuid.uuid.uuid128[15] << + std::setw(2) << (int)m_uuid.uuid.uuid128[14] << + std::setw(2) << (int)m_uuid.uuid.uuid128[13] << + std::setw(2) << (int)m_uuid.uuid.uuid128[12] << "-" << + std::setw(2) << (int)m_uuid.uuid.uuid128[11] << + std::setw(2) << (int)m_uuid.uuid.uuid128[10] << "-" << + std::setw(2) << (int)m_uuid.uuid.uuid128[9] << + std::setw(2) << (int)m_uuid.uuid.uuid128[8] << "-" << + std::setw(2) << (int)m_uuid.uuid.uuid128[7] << + std::setw(2) << (int)m_uuid.uuid.uuid128[6] << "-" << + std::setw(2) << (int)m_uuid.uuid.uuid128[5] << + std::setw(2) << (int)m_uuid.uuid.uuid128[4] << + std::setw(2) << (int)m_uuid.uuid.uuid128[3] << + std::setw(2) << (int)m_uuid.uuid.uuid128[2] << + std::setw(2) << (int)m_uuid.uuid.uuid128[1] << + std::setw(2) << (int)m_uuid.uuid.uuid128[0]; return ss.str(); } // toString #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/CPPNVS.cpp b/cpp_utils/CPPNVS.cpp index 734cc170..99588a43 100644 --- a/cpp_utils/CPPNVS.cpp +++ b/cpp_utils/CPPNVS.cpp @@ -6,9 +6,12 @@ */ #include "CPPNVS.h" - +#include +#include #include +const char* LOG_TAG = "CPPNVS"; + /** * @brief Constructor. * @@ -17,12 +20,12 @@ */ NVS::NVS(std::string name, nvs_open_mode openMode) { m_name = name; - nvs_open(name.c_str(), openMode, &m_handle); + ::nvs_open(name.c_str(), openMode, &m_handle); } // NVS NVS::~NVS() { - nvs_close(m_handle); + ::nvs_close(m_handle); } // ~NVS @@ -30,7 +33,7 @@ NVS::~NVS() { * @brief Commit any work performed in the namespace. */ void NVS::commit() { - nvs_commit(m_handle); + ::nvs_commit(m_handle); } // commit @@ -38,7 +41,7 @@ void NVS::commit() { * @brief Erase ALL the keys in the namespace. */ void NVS::erase() { - nvs_erase_all(m_handle); + ::nvs_erase_all(m_handle); } // erase @@ -48,32 +51,79 @@ void NVS::erase() { * @param [in] key The key to erase from the namespace. */ void NVS::erase(std::string key) { - nvs_erase_key(m_handle, key.c_str()); + ::nvs_erase_key(m_handle, key.c_str()); } // erase /** - * @brief Retrieve a string value by key. + * @brief Retrieve a string/blob value by key. * * @param [in] key The key to read from the namespace. * @param [out] result The string read from the %NVS storage. */ -void NVS::get(std::string key, std::string* result) { +void NVS::get(std::string key, std::string* result, bool isBlob) { size_t length; - nvs_get_str(m_handle, key.c_str(), NULL, &length); + if (isBlob) { + ::nvs_get_blob(m_handle, key.c_str(), NULL, &length); + } else { + ::nvs_get_str(m_handle, key.c_str(), NULL, &length); + } char *data = (char *)malloc(length); - nvs_get_str(m_handle, key.c_str(), data, &length); + if (isBlob) { + ::nvs_get_blob(m_handle, key.c_str(), data, &length); + } else { + ::nvs_get_str(m_handle, key.c_str(), data, &length); + } *result = std::string(data); free(data); } // get +void NVS::get(std::string key, uint32_t& value) { + ::nvs_get_u32(m_handle, key.c_str(), &value); +} // get - uint32_t + + +void NVS::get(std::string key, uint8_t* result, size_t& length) { + ESP_LOGD(LOG_TAG, ">> get: key: %s, blob: inputSize: %d", key.c_str(), length); + esp_err_t rc = ::nvs_get_blob(m_handle, key.c_str(), result, &length); + if (rc != ESP_OK) { + ESP_LOGD(LOG_TAG, "nvs_get_blob: %d", rc); + } + ESP_LOGD(LOG_TAG, "<< get: outputSize: %d", length); +} // get - blob + + + /** - * @brief Set the string value by key. + * @brief Set the string/blob value by key. * * @param [in] key The key to set from the namespace. * @param [in] data The value to set for the key. */ -void NVS::set(std::string key, std::string data) { - nvs_set_str(m_handle, key.c_str(), data.c_str()); +void NVS::set(std::string key, std::string data, bool isBlob) { + ESP_LOGD(LOG_TAG, ">> set: key: %s, string: value=%s", key.c_str(), data.c_str()); + if (isBlob) { + ::nvs_set_blob(m_handle, key.c_str(), data.data(), data.length()); + } else { + ::nvs_set_str(m_handle, key.c_str(), data.c_str()); + } + ESP_LOGD(LOG_TAG, "<< set"); } // set + + +void NVS::set(std::string key, uint32_t value) { + ESP_LOGD(LOG_TAG, ">> set: key: %s, u32: value=%d", key.c_str(), value); + ::nvs_set_u32(m_handle, key.c_str(), value); + ESP_LOGD(LOG_TAG, "<< set"); +} // set - uint32_t + + +void NVS::set(std::string key, uint8_t* data, size_t length) { + ESP_LOGD(LOG_TAG, ">> set: key: %s, blob: length=%d", key.c_str(), length); + esp_err_t rc = ::nvs_set_blob(m_handle, key.c_str(), data, length); + if (rc != ESP_OK) { + ESP_LOGD(LOG_TAG, "nvs_get_blob: %d", rc); + } + ESP_LOGD(LOG_TAG, "<< set"); +} // set (BLOB) diff --git a/cpp_utils/CPPNVS.h b/cpp_utils/CPPNVS.h index 6802ff99..755f551b 100644 --- a/cpp_utils/CPPNVS.h +++ b/cpp_utils/CPPNVS.h @@ -21,8 +21,12 @@ class NVS { void erase(); void erase(std::string key); - void get(std::string key, std::string *result); - void set(std::string key, std::string data); + void get(std::string key, std::string* result, bool isBlob=false); + void get(std::string key, uint8_t* result, size_t &length); + void get(std::string key, uint32_t& value); + void set(std::string key, std::string data, bool isBlob=false); + void set(std::string key, uint32_t value); + void set(std::string key, uint8_t* data, size_t length); private: std::string m_name; nvs_handle m_handle; diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 7f99b972..6bfde84f 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -7,6 +7,7 @@ #include "GeneralUtils.h" #include +#include #include #include #include @@ -403,4 +404,9 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { return "Unknown ESP_ERR error"; } // errorToString - +/** + * @brief Restart the ESP32. + */ +void GeneralUtils::restart() { + esp_restart(); +} // restart diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index 9042d8a7..ce75e43b 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -18,12 +18,13 @@ class GeneralUtils { public: GeneralUtils(); virtual ~GeneralUtils(); - static void hexDump(const uint8_t *pData, uint32_t length); - static std::string ipToString(uint8_t *ip); static bool base64Encode(const std::string &in, std::string *out); static bool base64Decode(const std::string &in, std::string *out); static bool endsWith(std::string str, char c); static const char *errorToString(esp_err_t errCode); + static void hexDump(const uint8_t *pData, uint32_t length); + static std::string ipToString(uint8_t *ip); + static void restart(); }; #endif /* COMPONENTS_CPP_UTILS_GENERALUTILS_H_ */ diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index 4866f4ca..45655893 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -139,6 +139,9 @@ void HttpRequest::close() { } // close_cpp + + + /** * @brief Dump the HttpRequest for debugging purposes. */ @@ -276,6 +279,34 @@ bool HttpRequest::isWebsocket() { } // isWebsocket +/** + * @brief Parse the request message as a form. + * @return A map containing the names/values of the form elements that were found. + */ +std::map HttpRequest::parseForm() { + ESP_LOGD(LOG_TAG, ">> parseForm"); + std::map map; + // A form is composed of name=value pairs where each pair is separated with an "&" character. + // Our algorithm is to split all the pairs by "&" and then split all the name/value pairs by "=". + std::istringstream ss(getBody()); // Get the body of the request. + std::string currentEntry; + while (std::getline(ss, currentEntry, '&')) { // For each form entry. + ESP_LOGD(LOG_TAG, "Processing: %s", currentEntry.c_str()); // Debug + std::istringstream currentPair(currentEntry); // Prepare to parse the name=value string. + std::string name; // Declare the name variable. + std::string value; // Declare the value variable. + + std::getline(currentPair, name, '='); // Parse the current form entry into name/value. + currentPair >> value; // The value is what remains. + map[name] = urlDecode(value); // Decode the field which may have been encoded. + ESP_LOGD(LOG_TAG, " %s = %s", name.c_str(), map[name].c_str()); // Debug + // Add the form entry into the map. + } // Processed all form entries. + ESP_LOGD(LOG_TAG, "<< parseForm"); // Debug + return map; // Return the map of form entries. +} // parseForm + + /** * @brief Return the constituent parts of the path. * If we imagine a path as composed of parts separated by slashes, then this function @@ -309,3 +340,32 @@ std::vector HttpRequest::pathSplit() { return ret; } // pathSplit + +/** + * @brief Decode a URL/form + * @param [in] str + * @return The decoded string. + */ +std::string HttpRequest::urlDecode(std::string str) { + // https://stackoverflow.com/questions/154536/encode-decode-urls-in-c + std::string ret; + char ch; + int i, ii, len = str.length(); + + for (i=0; i < len; i++){ + if (str[i] != '%'){ + if (str[i] == '+') { + ret += ' '; + } + else { + ret += str[i]; + } + } else { + sscanf(str.substr(i + 1, 2).c_str(), "%x", &ii); + ch = static_cast(ii); + ret += ch; + i = i + 2; + } + } + return ret; +} // urlDecode diff --git a/cpp_utils/HttpRequest.h b/cpp_utils/HttpRequest.h index 246da4b4..3f989cbc 100644 --- a/cpp_utils/HttpRequest.h +++ b/cpp_utils/HttpRequest.h @@ -65,7 +65,9 @@ class HttpRequest { WebSocket* getWebSocket(); // Get the WebSocket reference if this is a web socket. bool isClosed(); // Has the connection been closed? bool isWebsocket(); // Is this request to create a web socket? + std::map parseForm(); // Parse the body as a form. std::vector pathSplit(); + std::string urlDecode(std::string str); // Decode a URL. }; #endif /* COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ */ diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index b6da6c1a..168941ad 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -180,18 +180,21 @@ class HttpServerTask: public Task { */ void run(void* data) { m_pHttpServer = (HttpServer*)data; // The passed in data is an instance of an HttpServer. - - SockServ sockServ(m_pHttpServer->getPort()); // Create a socket server on our target port. - sockServ.setSSL(m_pHttpServer->getSSL()); - sockServ.start(); // Start the socket server listening. - + m_pHttpServer->m_sockServ.setPort(m_pHttpServer->m_portNumber); + m_pHttpServer->m_sockServ.setSSL(m_pHttpServer->m_useSSL); + m_pHttpServer->m_sockServ.start(); ESP_LOGD("HttpServerTask", "Listening on port %d", m_pHttpServer->getPort()); - + Socket clientSocket; while(1) { // Loop forever. ESP_LOGD("HttpServerTask", "Waiting for new peer client"); - Socket clientSocket = sockServ.waitForNewClient(); // Block waiting for a new external client connection. + try { + clientSocket = m_pHttpServer->m_sockServ.waitForNewClient(); // Block waiting for a new external client connection. + } + catch(std::exception e) { + return; + } ESP_LOGD("HttpServerTask", "HttpServer listening on port %d received a new client connection; sockFd=%d", m_pHttpServer->getPort(), clientSocket.getFD()); @@ -299,14 +302,30 @@ void HttpServer::setRootPath(std::string path) { * @param [in] useSSL Should we use SSL? */ void HttpServer::start(uint16_t portNumber, bool useSSL) { + // Design: + // The start of the HTTP server should be as fast as possible. ESP_LOGD(LOG_TAG, ">> start: port: %d, useSSL: %d", portNumber, useSSL); m_useSSL = useSSL; m_portNumber = portNumber; + HttpServerTask* pHttpServerTask = new HttpServerTask("HttpServerTask"); pHttpServerTask->start(this); } // start +/** + * @brief Shutdown the HTTP server. + */ +void HttpServer::stop() { + // Shutdown the HTTP Server. The high level is that we will stop the server socket + // that is listening for incoming connections. That will then shutdown all the other + // activities. + ESP_LOGD(LOG_TAG, ">> stop"); + m_sockServ.stop(); + ESP_LOGD(LOG_TAG, "<< stop"); +} // stop + + /** * @brief Construct an instance of a PathHandler. * diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index 0f3e6f5a..35d13383 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -11,11 +11,13 @@ #ifndef COMPONENTS_CPP_UTILS_HTTPSERVER_H_ #define COMPONENTS_CPP_UTILS_HTTPSERVER_H_ #include -#include + #include #include "SockServ.h" #include "HttpRequest.h" #include "HttpResponse.h" +#include "SockServ.h" +#include class HttpServerTask; @@ -61,6 +63,7 @@ class HttpServer { void setDirectoryListing(bool use); // Should we list the content of directories? void setRootPath(std::string path); // Set the root of the file system path. void start(uint16_t portNumber, bool useSSL=false); + void stop(); // Stop a previously started server. private: friend class HttpServerTask; friend class WebSocket; @@ -69,6 +72,7 @@ class HttpServer { std::string m_rootPath; // Root path into the file system. bool m_useSSL; // Is this server listening on an HTTPS port? bool m_directoryListing; // Should we list directory content? + SockServ m_sockServ; // Server socket. }; // HttpServer #endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index d18283a9..7c7bcf99 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -33,30 +33,50 @@ SockServ::SockServ(uint16_t port) : SockServ() { } // SockServ + +/** + * Constructor + */ SockServ::SockServ() { - m_port = 0; - m_clientSemaphore.take("SockServ"); + m_port = 0; // Unknown port. m_acceptQueue = xQueueCreate(10, sizeof(Socket)); + m_useSSL = false; + m_clientSemaphore.take("SockServ"); // Create the queue; deleted in the destructor. } // SockServ + +/** + * Destructor + */ +SockServ::~SockServ() { + vQueueDelete(m_acceptQueue); // Delete the queue created in the constructor. +} // ~SockServ + + /** * @brief Accept an incoming connection. * @private * - * Block waiting for an incoming connection and accept it when it arrives. + * Block waiting for an incoming connection and accept it when it arrives. The new + * socket is placed on a queue and a semaphore signaled that a new client is available. */ -void SockServ::acceptTask(void *data) { - +/* static */ void SockServ::acceptTask(void* data) { SockServ* pSockServ = (SockServ*)data; - while(1) { - Socket tempSock = pSockServ->m_serverSocket.accept(pSockServ->getSSL()); - if (!tempSock.isValid()) { - continue; + try { + while(1) { + Socket tempSock = pSockServ->m_serverSocket.accept(pSockServ->getSSL()); + if (!tempSock.isValid()) { + continue; + } + + pSockServ->m_clientSet.insert(tempSock); + xQueueSendToBack(pSockServ->m_acceptQueue, &tempSock, portMAX_DELAY); + pSockServ->m_clientSemaphore.give(); } - - pSockServ->m_clientSet.insert(tempSock); - xQueueSendToBack(pSockServ->m_acceptQueue, &tempSock, portMAX_DELAY); - pSockServ->m_clientSemaphore.give(); + } catch(std::exception e) { + ESP_LOGD(LOG_TAG, "acceptTask ending"); + pSockServ->m_clientSemaphore.give(); // Wake up any waiting clients. + FreeRTOS::deleteTask(); } } // acceptTask @@ -150,7 +170,7 @@ void SockServ::setSSL(bool use) { void SockServ::start() { assert(m_port != 0); //m_serverSocket.setSSL(m_useSSL); - m_serverSocket.listen(m_port); + m_serverSocket.listen(m_port); // Create a socket and start listening on it. ESP_LOGD(LOG_TAG, "Now listening on port %d", m_port); FreeRTOS::startTask(acceptTask, "acceptTask", this, 16*1024); } // start @@ -160,11 +180,14 @@ void SockServ::start() { * @brief Stop listening for new partner connections. */ void SockServ::stop() { + ESP_LOGD(LOG_TAG, ">> stop"); + // By closing the server socket, the task watching on accept() on that socket + // will throw an exception which will propagate a clean ending. + m_serverSocket.close(); // Close the server socket. + ESP_LOGD(LOG_TAG, "<< stop"); } // stop - - Socket SockServ::waitForData(std::set& socketSet) { fd_set readSet; int maxFd = -1; @@ -206,9 +229,14 @@ Socket SockServ::waitForData(std::set& socketSet) { */ Socket SockServ::waitForNewClient() { ESP_LOGD(LOG_TAG, ">> waitForNewClient") - m_clientSemaphore.wait("waitForNewClient"); + m_clientSemaphore.wait("waitForNewClient"); // Unlocked in acceptTask. + m_clientSemaphore.take("waitForNewClient"); Socket tempSocket; - xQueueReceive(m_acceptQueue, &tempSocket,portMAX_DELAY); + BaseType_t rc = xQueueReceive(m_acceptQueue, &tempSocket, 0); // Read the socket from the queue. + if (rc != pdPASS) { + ESP_LOGE(LOG_TAG, "No new client from SockServ!"); + throw new SocketException(0); + } ESP_LOGD(LOG_TAG, "<< waitForNewClient"); return tempSocket; } // waitForNewClient diff --git a/cpp_utils/SockServ.h b/cpp_utils/SockServ.h index b7e170da..e0ffee76 100644 --- a/cpp_utils/SockServ.h +++ b/cpp_utils/SockServ.h @@ -35,13 +35,15 @@ class SockServ { static void acceptTask(void*); uint16_t m_port; Socket m_serverSocket; - FreeRTOS::Semaphore m_clientSemaphore; + FreeRTOS::Semaphore m_clientSemaphore = FreeRTOS::Semaphore("clientSemaphore"); std::set m_clientSet; QueueHandle_t m_acceptQueue; bool m_useSSL; + public: SockServ(uint16_t port); SockServ(); + ~SockServ(); int connectedCount(); void disconnect(Socket s); bool getSSL(); diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index ce464b45..849c3ef1 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -65,9 +65,9 @@ Socket Socket::accept(bool useSSL) { socklen_t clientAddressLength = sizeof(clientAddress); int clientSockFD = ::lwip_accept_r(m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); if (clientSockFD == -1) { + SocketException se(errno); ESP_LOGE(LOG_TAG, "accept(): %s, m_sock=%d", strerror(errno), m_sock); - Socket newSocket; - return newSocket; + throw se; } ESP_LOGD(LOG_TAG, " - accept: Received new client!: sockFd: %d", clientSockFD); @@ -609,3 +609,7 @@ SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() { setg(m_buffer, m_buffer, m_buffer + bytesRead); return traits_type::to_int_type(*gptr()); } // underflow + +SocketException::SocketException(int myErrno) { + m_errno = myErrno; +} diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 9f046f57..33cce989 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -35,6 +35,14 @@ #include #include #include +#include + +class SocketException: public std::exception { +public: + SocketException(int myErrno); +private: + int m_errno; +}; /** * @brief Encapsulate a socket. diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index ecbb76e0..78571eba 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -53,6 +53,7 @@ WiFi::WiFi() , netmask(0) , m_pWifiEventHandler(nullptr) { + m_eventLoopStarted = false; ::nvs_flash_init(); wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&config); @@ -157,7 +158,17 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password, bool } - ESP_ERROR_CHECK(esp_event_loop_init(WiFi::eventHandler, this)); + + // If the event loop has already started then change the callback else + // start the event loop. + if (m_eventLoopStarted) { + esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. + } else { + ESP_ERROR_CHECK(esp_event_loop_init(WiFi::eventHandler, this)); // Initialze the event handler. + m_eventLoopStarted = true; + } + + //ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); @@ -374,25 +385,37 @@ std::vector WiFi::scan() { * @return N/A. */ void WiFi::startAP(const std::string& ssid, const std::string& password) { - ::nvs_flash_init(); - ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); - ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); - ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_AP) ); - wifi_config_t apConfig; - ::memset(&apConfig, 0, sizeof(apConfig)); - ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); - apConfig.ap.ssid_len = ssid.size(); - ::memcpy(apConfig.ap.password, password.data(), password.size()); - apConfig.ap.channel = 0; - apConfig.ap.authmode = WIFI_AUTH_OPEN; - apConfig.ap.ssid_hidden = 0; - apConfig.ap.max_connection = 4; - apConfig.ap.beacon_interval = 100; - ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &apConfig) ); - ESP_ERROR_CHECK( esp_wifi_start() ); + ESP_LOGD(LOG_TAG, ">> startAP: ssid: %s", ssid.c_str()); + ::nvs_flash_init(); + ::tcpip_adapter_init(); + + // If we have already started the event loop, then change the handler otherwise + // start the event loop. + if (m_eventLoopStarted) { + esp_event_loop_set_cb(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler); + } + else { + ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); + m_eventLoopStarted = true; + } + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_AP) ); + wifi_config_t apConfig; + ::memset(&apConfig, 0, sizeof(apConfig)); + ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); + apConfig.ap.ssid_len = ssid.size(); + ::memcpy(apConfig.ap.password, password.data(), password.size()); + apConfig.ap.channel = 0; + apConfig.ap.authmode = WIFI_AUTH_OPEN; + apConfig.ap.ssid_hidden = 0; + apConfig.ap.max_connection = 4; + apConfig.ap.beacon_interval = 100; + ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &apConfig) ); + ESP_ERROR_CHECK( esp_wifi_start() ); + ESP_LOGD(LOG_TAG, "<< startAP"); } // startAP @@ -400,8 +423,10 @@ void WiFi::startAP(const std::string& ssid, const std::string& password) { * @brief Set the event handler to use to process detected events. * @param[in] wifiEventHandler The class that will be used to process events. */ -void WiFi::setWifiEventHandler(WiFiEventHandler *wifiEventHandler) { +void WiFi::setWifiEventHandler(WiFiEventHandler* wifiEventHandler) { + ESP_LOGD(LOG_TAG, ">> setWifiEventHandler: 0x%d", (uint32_t)wifiEventHandler); this->m_pWifiEventHandler = wifiEventHandler; + ESP_LOGD(LOG_TAG, "<< setWifiEventHandler"); } // setWifiEventHandler diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 3279f284..d087c032 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -105,11 +105,12 @@ class WiFiAPRecord { class WiFi { private: static esp_err_t eventHandler(void* ctx, system_event_t* event); - uint32_t ip; - uint32_t gw; - uint32_t netmask; + uint32_t ip; + uint32_t gw; + uint32_t netmask; WiFiEventHandler* m_pWifiEventHandler; uint8_t m_dnsCount=0; + bool m_eventLoopStarted; FreeRTOS::Semaphore m_gotIpEvt = FreeRTOS::Semaphore("GotIpEvt"); public: diff --git a/networking/bootwifi/BootWiFi.cpp b/networking/bootwifi/BootWiFi.cpp new file mode 100644 index 00000000..ab9f4f3d --- /dev/null +++ b/networking/bootwifi/BootWiFi.cpp @@ -0,0 +1,273 @@ +/** + * Bootwifi - Boot the WiFi environment. + * + * Compile with -DBOOTWIFI_OVERRIDE_GPIO= where is a GPIO pin number + * to use a GPIO override. + * See the README.md for full information. + * + */ +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +#include +#include +#include +#include +#include +#include "BootWiFi.h" +#include "sdkconfig.h" +#include "selectAP.h" + +// If the structure of a record saved for a subsequent reboot changes +// then consider using semver to change the version number or else +// we may try and boot with the wrong data. +#define KEY_VERSION "version" +uint32_t g_version=0x0100; + +#define KEY_CONNECTION_INFO "connectionInfo" // Key used in NVS for connection info +#define BOOTWIFI_NAMESPACE "bootwifi" // Namespace in NVS for bootwifi +#define SSID_SIZE (32) // Maximum SSID size +#define PASSWORD_SIZE (64) // Maximum password size + + +/** + * The information about a WiFi access point connection. + */ +typedef struct { + char ssid[SSID_SIZE]; + char password[PASSWORD_SIZE]; + tcpip_adapter_ip_info_t ipInfo; // Optional static IP information +} connection_info_t; + +static bootwifi_callback_t g_callback = NULL; // Callback function to be invoked when we have finished. + + +// Forward declarations +static void saveConnectionInfo(connection_info_t *pConnectionInfo); + +static const char* LOG_TAG = "bootwifi"; + + +static void dumpConnectionInfo(connection_info_t *pConnectionInfo) { + ESP_LOGD(LOG_TAG, "connection_info.ssid = %.*s", SSID_SIZE, pConnectionInfo->ssid); + ESP_LOGD(LOG_TAG, "connection_info.password = %.*s", PASSWORD_SIZE, pConnectionInfo->password); +} + + +/** + * Retrieve the connection info. A rc==0 means ok. + */ +static int getConnectionInfo(connection_info_t *pConnectionInfo) { + ESP_LOGD(LOG_TAG, ">> getConnectionInfo"); + size_t size; + uint32_t version; + + NVS myNamespace(BOOTWIFI_NAMESPACE); + myNamespace.get(KEY_VERSION, version); + + // Check the versions match + if ((version & 0xff00) != (g_version & 0xff00)) { + ESP_LOGD(LOG_TAG, "Incompatible versions ... current is %x, found is %x", version, g_version); + return -1; + } + + size = sizeof(connection_info_t); + myNamespace.get(KEY_CONNECTION_INFO, (uint8_t*)pConnectionInfo, size); + + // Do a sanity check on the SSID + if (strlen(pConnectionInfo->ssid) == 0) { + ESP_LOGD(LOG_TAG, "NULL ssid detected"); + return -1; + } + dumpConnectionInfo(pConnectionInfo); + ESP_LOGD(LOG_TAG, "<< getConnectionInfo"); + return 0; + +} // getConnectionInfo + + +/** + * Save our connection info for retrieval on a subsequent restart. + */ +static void saveConnectionInfo(connection_info_t *pConnectionInfo) { + dumpConnectionInfo(pConnectionInfo); + NVS myNamespace(BOOTWIFI_NAMESPACE); + myNamespace.set(KEY_CONNECTION_INFO, (uint8_t*)pConnectionInfo, sizeof(connection_info_t)); + myNamespace.set(KEY_VERSION, g_version); + myNamespace.commit(); +} // setConnectionInfo + + +/** + * Retrieve the signal level on the OVERRIDE_GPIO pin. This is used to + * indicate that we should not attempt to connect to any previously saved + * access point we may know about. + */ + +static int checkOverrideGpio() { +#ifdef BOOTWIFI_OVERRIDE_GPIO + gpio_pad_select_gpio(BOOTWIFI_OVERRIDE_GPIO); + gpio_set_direction(BOOTWIFI_OVERRIDE_GPIO, GPIO_MODE_INPUT); + gpio_set_pull_mode(BOOTWIFI_OVERRIDE_GPIO, GPIO_PULLDOWN_ONLY); + return gpio_get_level(BOOTWIFI_OVERRIDE_GPIO); +#else + return 0; // If no boot override, return false +#endif +} // checkOverrideGpio + +static void sendForm(HttpRequest* pRequest, HttpResponse* pResponse) { + pResponse->setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + pResponse->addHeader(HttpRequest::HTTP_HEADER_CONTENT_TYPE, "text/html"); + pResponse->sendData(std::string((char*)selectAP_html, selectAP_html_len)); + pResponse->close(); +} // sendForm + +static void copyData(uint8_t* pTarget, size_t targetLength, std::string source) { + memset(pTarget, 0, targetLength); + size_t copySize = (source.length() > targetLength)? targetLength:source.length(); + memcpy(pTarget, source.data(), copySize); + if (copySize < targetLength) { + pTarget[copySize] = '\0'; + } +} + +static void processForm(HttpRequest* pRequest, HttpResponse* pResponse) { + ESP_LOGD(LOG_TAG, ">> processForm"); + std::map formMap = pRequest->parseForm(); + connection_info_t connectionInfo; + copyData((uint8_t*)connectionInfo.ssid, SSID_SIZE, formMap["ssid"]); + copyData((uint8_t*)connectionInfo.password, PASSWORD_SIZE, formMap["password"]); + + try { + inet_pton(AF_INET, formMap["ip"].c_str(), &connectionInfo.ipInfo.ip); + } catch(std::out_of_range e) { + connectionInfo.ipInfo.ip.addr = 0; + } + + try { + inet_pton(AF_INET, formMap["gw"].c_str(), &connectionInfo.ipInfo.gw); + } catch(std::out_of_range e) { + connectionInfo.ipInfo.gw.addr = 0; + } + + try { + inet_pton(AF_INET, formMap["netmask"].c_str(), &connectionInfo.ipInfo.netmask); + } catch(std::out_of_range e) { + connectionInfo.ipInfo.netmask.addr = 0; + } + + ESP_LOGD(LOG_TAG, "ssid: %s, password: %s", connectionInfo.ssid, connectionInfo.password); + + saveConnectionInfo(&connectionInfo); + + pResponse->setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + pResponse->addHeader(HttpRequest::HTTP_HEADER_CONTENT_TYPE, "text/html"); + //pResponse->sendData(std::string((char*)selectAP_html, selectAP_html_len)); + pResponse->close(); + FreeRTOS::sleep(500); + GeneralUtils::restart(); + ESP_LOGD(LOG_TAG, "<< processForm"); +} // processForm + + +class BootWifiEventHandler: public WiFiEventHandler { +public: + BootWifiEventHandler(BootWiFi *pBootWiFi) { + m_pBootWiFi = pBootWiFi; + } + + /** + * When the access point logic has started and we are able to receive incoming browser + * requests, start the internal HTTP Server. + */ + esp_err_t apStart() { + ESP_LOGD("BootWifiEventHandler", ">> apStart"); + if (m_pBootWiFi->m_httpServerStarted == false) { + m_pBootWiFi->m_httpServerStarted = true; + m_pBootWiFi->m_httpServer.addPathHandler("GET", "/", sendForm); + m_pBootWiFi->m_httpServer.addPathHandler("POST", "/ssidSelected", processForm); + m_pBootWiFi->m_httpServer.start(80); + } + ESP_LOGD("BootWifiEventHandler", "<< apStart"); + return ESP_OK; + } // apStaConnected + + /** + * If we fail to connect as a station, then become an access point and then + * become a web server. + */ + esp_err_t staDisconnected() { + ESP_LOGD("BootWifiEventHandler", ">> staDisconnected"); + m_pBootWiFi->m_wifi.startAP("Duktape", "Duktape"); + ESP_LOGD("BootWifiEventHandler", "<< staDisconnected"); + return ESP_OK; + } + +private: + BootWiFi *m_pBootWiFi; +}; + + +void BootWiFi::bootWiFi2() { + ESP_LOGD(LOG_TAG, ">> bootWiFi2"); + // Check for a GPIO override which occurs when a physical Pin is high + // during the test. This can force the ability to check for new configuration + // even if the existing configured access point is available. + m_wifi.setWifiEventHandler(new BootWifiEventHandler(this)); + if (checkOverrideGpio()) { + ESP_LOGD(LOG_TAG, "- GPIO override detected"); + m_wifi.startAP("Duktape", "Duktape"); + } else { + // There was NO GPIO override, proceed as normal. This means we retrieve + // our stored access point information of the access point we should connect + // against. If that information doesn't exist, then again we become an + // access point ourselves in order to allow a client to connect and bring + // up a browser. + connection_info_t connectionInfo; + int rc = getConnectionInfo(&connectionInfo); + if (rc == 0) { + // We have received connection information, let us now become a station + // and attempt to connect to the access point. + ESP_LOGD(LOG_TAG, "- Connecting to access point \"%s\" ...", connectionInfo.ssid); + assert(strlen(connectionInfo.ssid) > 0); + + m_wifi.setIPInfo( + connectionInfo.ipInfo.ip.addr, + connectionInfo.ipInfo.gw.addr, + connectionInfo.ipInfo.netmask.addr + ); + m_wifi.connectAP(connectionInfo.ssid, connectionInfo.password); // Connect to the access point. + + } else { + // We do NOT have connection information. Let us now become an access + // point that serves up a web server and allow a browser user to specify + // the details that will be eventually used to allow us to connect + // as a station. + m_wifi.startAP("Duktape", "Duktape"); + } // We do NOT have connection info + } + ESP_LOGD(LOG_TAG, "<< bootWiFi2"); +} // bootWiFi2 + + + +void BootWiFi::boot() { + ESP_LOGD(LOG_TAG, ">> boot"); + ESP_LOGD(LOG_TAG, " +----------+"); + ESP_LOGD(LOG_TAG, " | BootWiFi |"); + ESP_LOGD(LOG_TAG, " +----------+"); + m_completeSemaphore.take("boot"); // Take the semaphore which will be unlocked when we complete booting. + bootWiFi2(); + m_completeSemaphore.wait("boot"); // Wait for the semaphore that indicated we have completed booting. + ESP_LOGD(LOG_TAG, "<< boot"); +} + +BootWiFi::BootWiFi() { + m_httpServerStarted = false; +} diff --git a/networking/bootwifi/BootWiFi.h b/networking/bootwifi/BootWiFi.h new file mode 100644 index 00000000..e2b26f94 --- /dev/null +++ b/networking/bootwifi/BootWiFi.h @@ -0,0 +1,32 @@ +/* + * BootWiFi.h + * + * Created on: Nov 25, 2016 + * Author: kolban + */ + +#ifndef MAIN_BOOTWIFI_H_ +#define MAIN_BOOTWIFI_H_ + +#include +#include +#include + +typedef void (*bootwifi_callback_t)(int rc); +class BootWifiEventHandler; + +class BootWiFi { +private: + friend BootWifiEventHandler; + void bootWiFi2(); + WiFi m_wifi; + HttpServer m_httpServer; + bool m_httpServerStarted; + FreeRTOS::Semaphore m_completeSemaphore = FreeRTOS::Semaphore("completeSemaphore"); + +public: + BootWiFi(); + void boot(); +}; + +#endif /* MAIN_BOOTWIFI_H_ */ diff --git a/networking/bootwifi/README.md b/networking/bootwifi/README.md index b6c8bfc2..d46d43d4 100644 --- a/networking/bootwifi/README.md +++ b/networking/bootwifi/README.md @@ -31,13 +31,13 @@ however, [Cesanta](https://www.cesanta.com/), the makers of Mongoose are still w port to the ESP32 which is anticipated to be available before 2017 so we should really wait for that to become available. -##GPIO boot override +## GPIO boot override To enable the ability to specify a GPIO pin to override known station information, compile the code with `-DBOOTWIFI_OVERRIDE_GPIO=` when `` is a GPIO pin number. If the pin is high at startup, then it will override. The pin is configured as pull-down low so it need not be artificially held low. The default is no override pin. -##Future enhancements +## Future enhancements There is always room for enhancements: * Improve the web page shown to the user - Right now it is pretty basic and ideally could be @@ -55,7 +55,7 @@ dramatically improved. Features to be added include - Network SSID to use when being an access point. - Network password to use when being an access point (if any). -##Design and implementation notes +## Design and implementation notes The parameters for Bootwifi are stored in Non-Volatile Storage (NVS). The name space in NVS is "bootwifi". The keys are: diff --git a/networking/bootwifi/bootwifi.c b/networking/bootwifi/bootwifi.c deleted file mode 100644 index 7de7c2fd..00000000 --- a/networking/bootwifi/bootwifi.c +++ /dev/null @@ -1,511 +0,0 @@ -/** - * Bootwifi - Boot the WiFi environment. - * - * Compile with -DBOOTWIFI_OVERRIDE_GPIO= where is a GPIO pin number - * to use a GPIO override. - * See the README.md for full information. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "bootwifi.h" -#include "sdkconfig.h" -#include "selectAP.h" - -// If the structure of a record saved for a subsequent reboot changes -// then consider using semver to change the version number or else -// we may try and boot with the wrong data. -#define KEY_VERSION "version" -uint32_t g_version=0x0100; - -#define KEY_CONNECTION_INFO "connectionInfo" // Key used in NVS for connection info -#define BOOTWIFI_NAMESPACE "bootwifi" // Namespace in NVS for bootwifi -#define SSID_SIZE (32) // Maximum SSID size -#define PASSWORD_SIZE (64) // Maximum password size - -typedef struct { - char ssid[SSID_SIZE]; - char password[PASSWORD_SIZE]; - tcpip_adapter_ip_info_t ipInfo; // Optional static IP information -} connection_info_t; - -static bootwifi_callback_t g_callback = NULL; // Callback function to be invoked when we have finished. - -static int g_mongooseStarted = 0; // Has the mongoose server started? -static int g_mongooseStopRequest = 0; // Request to stop the mongoose server. - -// Forward declarations -static void saveConnectionInfo(connection_info_t *pConnectionInfo); -static void becomeAccessPoint(); -static void bootWiFi2(); - -static char tag[] = "bootwifi"; - - - - - -/** - * Convert a Mongoose event type to a string. Used for debugging. - */ -static char *mongoose_eventToString(int ev) { - static char temp[100]; - switch (ev) { - case MG_EV_CONNECT: - return "MG_EV_CONNECT"; - case MG_EV_ACCEPT: - return "MG_EV_ACCEPT"; - case MG_EV_CLOSE: - return "MG_EV_CLOSE"; - case MG_EV_SEND: - return "MG_EV_SEND"; - case MG_EV_RECV: - return "MG_EV_RECV"; - case MG_EV_HTTP_REQUEST: - return "MG_EV_HTTP_REQUEST"; - case MG_EV_MQTT_CONNACK: - return "MG_EV_MQTT_CONNACK"; - case MG_EV_MQTT_CONNACK_ACCEPTED: - return "MG_EV_MQTT_CONNACK"; - case MG_EV_MQTT_CONNECT: - return "MG_EV_MQTT_CONNECT"; - case MG_EV_MQTT_DISCONNECT: - return "MG_EV_MQTT_DISCONNECT"; - case MG_EV_MQTT_PINGREQ: - return "MG_EV_MQTT_PINGREQ"; - case MG_EV_MQTT_PINGRESP: - return "MG_EV_MQTT_PINGRESP"; - case MG_EV_MQTT_PUBACK: - return "MG_EV_MQTT_PUBACK"; - case MG_EV_MQTT_PUBCOMP: - return "MG_EV_MQTT_PUBCOMP"; - case MG_EV_MQTT_PUBLISH: - return "MG_EV_MQTT_PUBLISH"; - case MG_EV_MQTT_PUBREC: - return "MG_EV_MQTT_PUBREC"; - case MG_EV_MQTT_PUBREL: - return "MG_EV_MQTT_PUBREL"; - case MG_EV_MQTT_SUBACK: - return "MG_EV_MQTT_SUBACK"; - case MG_EV_MQTT_SUBSCRIBE: - return "MG_EV_MQTT_SUBSCRIBE"; - case MG_EV_MQTT_UNSUBACK: - return "MG_EV_MQTT_UNSUBACK"; - case MG_EV_MQTT_UNSUBSCRIBE: - return "MG_EV_MQTT_UNSUBSCRIBE"; - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: - return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: - return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; - case MG_EV_WEBSOCKET_FRAME: - return "MG_EV_WEBSOCKET_FRAME"; - } - sprintf(temp, "Unknown event: %d", ev); - return temp; -} //eventToString - - -// Convert a Mongoose string type to a string. -static char *mgStrToStr(struct mg_str mgStr) { - char *retStr = (char *) malloc(mgStr.len + 1); - memcpy(retStr, mgStr.p, mgStr.len); - retStr[mgStr.len] = 0; - return retStr; -} // mgStrToStr - - -/** - * Handle mongoose events. These are mostly requests to process incoming - * browser requests. The ones we handle are: - * GET / - Send the enter details page. - * GET /set - Set the connection info (REST request). - * POST /ssidSelected - Set the connection info (HTML FORM). - */ -static void mongoose_event_handler(struct mg_connection *nc, int ev, void *evData) { - ESP_LOGD(tag, "- Event: %s", mongoose_eventToString(ev)); - switch (ev) { - case MG_EV_HTTP_REQUEST: { - struct http_message *message = (struct http_message *) evData; - char *uri = mgStrToStr(message->uri); - ESP_LOGD(tag, " - uri: %s", uri); - - if (strcmp(uri, "/set") ==0 ) { - connection_info_t connectionInfo; -//fix - saveConnectionInfo(&connectionInfo); - ESP_LOGD(tag, "- Set the new connection info to ssid: %s, password: %s", - connectionInfo.ssid, connectionInfo.password); - mg_send_head(nc, 200, 0, "Content-Type: text/plain"); - } if (strcmp(uri, "/") == 0) { - mg_send_head(nc, 200, sizeof(selectAP_html), "Content-Type: text/html"); - mg_send(nc, selectAP_html, sizeof(selectAP_html)); - } - // Handle /ssidSelected - // This is an incoming form with properties: - // * ssid - The ssid of the network to connect against. - // * password - the password to use to connect. - // * ip - Static IP address ... may be empty - // * gw - Static GW address ... may be empty - // * netmask - Static netmask ... may be empty - if(strcmp(uri, "/ssidSelected") == 0) { - // We have received a form page containing the details. The form body will - // contain: - // ssid=&password= - ESP_LOGD(tag, "- body: %.*s", message->body.len, message->body.p); - connection_info_t connectionInfo; - mg_get_http_var(&message->body, "ssid", connectionInfo.ssid, SSID_SIZE); - mg_get_http_var(&message->body, "password", connectionInfo.password, PASSWORD_SIZE); - - char ipBuf[20]; - if (mg_get_http_var(&message->body, "ip", ipBuf, sizeof(ipBuf)) > 0) { - inet_pton(AF_INET, ipBuf, &connectionInfo.ipInfo.ip); - } else { - connectionInfo.ipInfo.ip.addr = 0; - } - - if (mg_get_http_var(&message->body, "gw", ipBuf, sizeof(ipBuf)) > 0) { - inet_pton(AF_INET, ipBuf, &connectionInfo.ipInfo.gw); - } - else { - connectionInfo.ipInfo.gw.addr = 0; - } - - if (mg_get_http_var(&message->body, "netmask", ipBuf, sizeof(ipBuf)) > 0) { - inet_pton(AF_INET, ipBuf, &connectionInfo.ipInfo.netmask); - } - else { - connectionInfo.ipInfo.netmask.addr = 0; - } - - ESP_LOGD(tag, "ssid: %s, password: %s", connectionInfo.ssid, connectionInfo.password); - - mg_send_head(nc, 200, 0, "Content-Type: text/plain"); - saveConnectionInfo(&connectionInfo); - bootWiFi2(); - } // url is "/ssidSelected" - // Else ... unknown URL - else { - mg_send_head(nc, 404, 0, "Content-Type: text/plain"); - } - nc->flags |= MG_F_SEND_AND_CLOSE; - free(uri); - break; - } // MG_EV_HTTP_REQUEST - } // End of switch -} // End of mongoose_event_handler - - -// FreeRTOS task to start Mongoose. -static void mongooseTask(void *data) { - struct mg_mgr mgr; - struct mg_connection *connection; - - ESP_LOGD(tag, ">> mongooseTask"); - g_mongooseStopRequest = 0; // Unset the stop request since we are being asked to start. - - mg_mgr_init(&mgr, NULL); - - connection = mg_bind(&mgr, ":80", mongoose_event_handler); - - if (connection == NULL) { - ESP_LOGE(tag, "No connection from the mg_bind()."); - mg_mgr_free(&mgr); - ESP_LOGD(tag, "<< mongooseTask"); - vTaskDelete(NULL); - return; - } - mg_set_protocol_http_websocket(connection); - - // Keep processing until we are flagged that there is a stop request. - while (!g_mongooseStopRequest) { - mg_mgr_poll(&mgr, 1000); - } - - // We have received a stop request, so stop being a web server. - mg_mgr_free(&mgr); - g_mongooseStarted = 0; - - // Since we HAVE ended mongoose, time to invoke the callback. - if (g_callback) { - g_callback(1); - } - - ESP_LOGD(tag, "<< mongooseTask"); - vTaskDelete(NULL); - return; -} // mongooseTask - - -/** - * An ESP32 WiFi event handler. - * The types of events that can be received here are: - * - * SYSTEM_EVENT_AP_PROBEREQRECVED - * SYSTEM_EVENT_AP_STACONNECTED - * SYSTEM_EVENT_AP_STADISCONNECTED - * SYSTEM_EVENT_AP_START - * SYSTEM_EVENT_AP_STOP - * SYSTEM_EVENT_SCAN_DONE - * SYSTEM_EVENT_STA_AUTHMODE_CHANGE - * SYSTEM_EVENT_STA_CONNECTED - * SYSTEM_EVENT_STA_DISCONNECTED - * SYSTEM_EVENT_STA_GOT_IP - * SYSTEM_EVENT_STA_START - * SYSTEM_EVENT_STA_STOP - * SYSTEM_EVENT_WIFI_READY - */ -static esp_err_t esp32_wifi_eventHandler(void *ctx, system_event_t *event) { - // Your event handling code here... - switch(event->event_id) { - // When we have started being an access point, then start being a web server. - case SYSTEM_EVENT_AP_START: { // Handle the AP start event - tcpip_adapter_ip_info_t ip_info; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip_info); - ESP_LOGD(tag, "**********************************************"); - ESP_LOGD(tag, "* We are now an access point and you can point") - ESP_LOGD(tag, "* your browser to http://" IPSTR, IP2STR(&ip_info.ip)); - ESP_LOGD(tag, "**********************************************"); - // Start Mongoose ... - if (!g_mongooseStarted) - { - g_mongooseStarted = 1; - xTaskCreatePinnedToCore(&mongooseTask, "bootwifi_mongoose_task", 8000, NULL, 5, NULL, 0); - } - break; - } // SYSTEM_EVENT_AP_START - - // If we fail to connect to an access point as a station, become an access point. - case SYSTEM_EVENT_STA_DISCONNECTED: { - ESP_LOGD(tag, "Station disconnected started"); - // We think we tried to connect as a station and failed! ... become - // an access point. - becomeAccessPoint(); - break; - } // SYSTEM_EVENT_AP_START - - // If we connected as a station then we are done and we can stop being a - // web server. - case SYSTEM_EVENT_STA_GOT_IP: { - ESP_LOGD(tag, "********************************************"); - ESP_LOGD(tag, "* We are now connected and ready to do work!") - ESP_LOGD(tag, "* - Our IP address is: " IPSTR, IP2STR(&event->event_info.got_ip.ip_info.ip)); - ESP_LOGD(tag, "********************************************"); - g_mongooseStopRequest = 1; // Stop mongoose (if it is running). - // Invoke the callback if Mongoose has NOT been started ... otherwise - // we will invoke the callback when mongoose has ended. - if (!g_mongooseStarted) { - if (g_callback) { - g_callback(1); - } - } // Mongoose was NOT started - break; - } // SYSTEM_EVENT_STA_GOTIP - - default: // Ignore the other event types - break; - } // Switch event - - return ESP_OK; -} // esp32_wifi_eventHandler - - -/** - * Retrieve the connection info. A rc==0 means ok. - */ -static int getConnectionInfo(connection_info_t *pConnectionInfo) { - nvs_handle handle; - size_t size; - esp_err_t err; - uint32_t version; - err = nvs_open(BOOTWIFI_NAMESPACE, NVS_READWRITE, &handle); - if (err != 0) { - ESP_LOGE(tag, "nvs_open: %x", err); - return -1; - } - - // Get the version that the data was saved against. - err = nvs_get_u32(handle, KEY_VERSION, &version); - if (err != ESP_OK) { - ESP_LOGD(tag, "No version record found (%d).", err); - nvs_close(handle); - return -1; - } - - // Check the versions match - if ((version & 0xff00) != (g_version & 0xff00)) { - ESP_LOGD(tag, "Incompatible versions ... current is %x, found is %x", version, g_version); - nvs_close(handle); - return -1; - } - - size = sizeof(connection_info_t); - err = nvs_get_blob(handle, KEY_CONNECTION_INFO, pConnectionInfo, &size); - if (err != ESP_OK) { - ESP_LOGD(tag, "No connection record found (%d).", err); - nvs_close(handle); - return -1; - } - if (err != ESP_OK) { - ESP_LOGE(tag, "nvs_open: %x", err); - nvs_close(handle); - return -1; - } - - // Cleanup - nvs_close(handle); - - // Do a sanity check on the SSID - if (strlen(pConnectionInfo->ssid) == 0) { - ESP_LOGD(tag, "NULL ssid detected"); - return -1; - } - return 0; -} // getConnectionInfo - - -/** - * Save our connection info for retrieval on a subsequent restart. - */ -static void saveConnectionInfo(connection_info_t *pConnectionInfo) { - nvs_handle handle; - ESP_ERROR_CHECK(nvs_open(BOOTWIFI_NAMESPACE, NVS_READWRITE, &handle)); - ESP_ERROR_CHECK(nvs_set_blob(handle, KEY_CONNECTION_INFO, pConnectionInfo, - sizeof(connection_info_t))); - ESP_ERROR_CHECK(nvs_set_u32(handle, KEY_VERSION, g_version)); - ESP_ERROR_CHECK(nvs_commit(handle)); - nvs_close(handle); -} // setConnectionInfo - - -/** - * Become a station connecting to an existing access point. - */ -static void becomeStation(connection_info_t *pConnectionInfo) { - ESP_LOGD(tag, "- Connecting to access point \"%s\" ...", pConnectionInfo->ssid); - assert(strlen(pConnectionInfo->ssid) > 0); - - // If we have a static IP address information, use that. - if (pConnectionInfo->ipInfo.ip.addr != 0) { - ESP_LOGD(tag, " - using a static IP address of " IPSTR, IP2STR(&pConnectionInfo->ipInfo.ip)); - tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &pConnectionInfo->ipInfo); - } else { - tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); - } - - ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA)); - wifi_config_t sta_config; - sta_config.sta.bssid_set = 0; - memcpy(sta_config.sta.ssid, pConnectionInfo->ssid, SSID_SIZE); - memcpy(sta_config.sta.password, pConnectionInfo->password, PASSWORD_SIZE); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config)); - ESP_ERROR_CHECK(esp_wifi_start()); - ESP_ERROR_CHECK(esp_wifi_connect()); -} // becomeStation - - -/** - * Become an access point. - */ -static void becomeAccessPoint() { - ESP_LOGD(tag, "- Starting being an access point ..."); - // We don't have connection info so be an access point! - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); - wifi_config_t apConfig = { - .ap = { - .ssid="ESP32 Duktape", - .ssid_len=0, - .password="Duktape", - .channel=0, - .authmode=WIFI_AUTH_OPEN, - .ssid_hidden=0, - .max_connection=4, - .beacon_interval=100 - } - }; - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &apConfig)); - ESP_ERROR_CHECK(esp_wifi_start()); -} // becomeAccessPoint - - -/** - * Retrieve the signal level on the OVERRIDE_GPIO pin. This is used to - * indicate that we should not attempt to connect to any previously saved - * access point we may know about. - */ - -static int checkOverrideGpio() { -#ifdef BOOTWIFI_OVERRIDE_GPIO - gpio_pad_select_gpio(BOOTWIFI_OVERRIDE_GPIO); - gpio_set_direction(BOOTWIFI_OVERRIDE_GPIO, GPIO_MODE_INPUT); - gpio_set_pull_mode(BOOTWIFI_OVERRIDE_GPIO, GPIO_PULLDOWN_ONLY); - return gpio_get_level(BOOTWIFI_OVERRIDE_GPIO); -#else - return 0; // If no boot override, return false -#endif -} // checkOverrideGpio - - - -static void bootWiFi2() { - ESP_LOGD(tag, ">> bootWiFi2"); - // Check for a GPIO override which occurs when a physical Pin is high - // during the test. This can force the ability to check for new configuration - // even if the existing configured access point is available. - if (checkOverrideGpio()) { - ESP_LOGD(tag, "- GPIO override detected"); - becomeAccessPoint(); - } else { - // There was NO GPIO override, proceed as normal. This means we retrieve - // our stored access point information of the access point we should connect - // against. If that information doesn't exist, then again we become an - // access point ourselves in order to allow a client to connect and bring - // up a browser. - connection_info_t connectionInfo; - int rc = getConnectionInfo(&connectionInfo); - if (rc == 0) { - // We have received connection information, let us now become a station - // and attempt to connect to the access point. - becomeStation(&connectionInfo); - - } else { - // We do NOT have connection information. Let us now become an access - // point that serves up a web server and allow a browser user to specify - // the details that will be eventually used to allow us to connect - // as a station. - becomeAccessPoint(); - } // We do NOT have connection info - } - ESP_LOGD(tag, "<< bootWiFi2"); -} // bootWiFi2 - - -/** - * Main entry into bootWiFi - */ -void bootWiFi(bootwifi_callback_t callback) { - ESP_LOGD(tag, ">> bootWiFi"); - g_callback = callback; - nvs_flash_init(); - tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(esp32_wifi_eventHandler, NULL)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); - - bootWiFi2(); - - ESP_LOGD(tag, "<< bootWiFi"); -} // bootWiFi diff --git a/networking/bootwifi/bootwifi.h b/networking/bootwifi/bootwifi.h deleted file mode 100644 index 885da4a8..00000000 --- a/networking/bootwifi/bootwifi.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * bootwifi.h - * - * Created on: Nov 25, 2016 - * Author: kolban - */ - -#ifndef MAIN_BOOTWIFI_H_ -#define MAIN_BOOTWIFI_H_ - -typedef void (*bootwifi_callback_t)(int rc); -void bootWiFi(); - - -#endif /* MAIN_BOOTWIFI_H_ */ diff --git a/networking/bootwifi/component.mk b/networking/bootwifi/component.mk index e64cac1b..c00ba051 100644 --- a/networking/bootwifi/component.mk +++ b/networking/bootwifi/component.mk @@ -6,10 +6,4 @@ # in the build directory. This behaviour is entirely configurable, # please read the ESP-IDF documents if you need to do this. # -CFLAGS += -DCS_PLATFORM=3 \ - -DMG_DISABLE_DIRECTORY_LISTING=1 \ - -DMG_DISABLE_DAV=1 \ - -DMG_DISABLE_CGI=1 \ - -DMG_DISABLE_FILESYSTEM=1 \ - -DMG_LWIP=1 \ - -DMG_ENABLE_BROADCAST \ No newline at end of file +COMPONENT_ADD_INCLUDEDIRS=. \ No newline at end of file From ff9368469868c090cb1172aad61e985f2e587e5a Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 10 Oct 2017 19:15:06 -0500 Subject: [PATCH 103/381] Code changes for #115 --- cpp_utils/BLEAdvertising.cpp | 105 +++++----- cpp_utils/BLEAdvertising.h | 9 +- cpp_utils/tests/BLETests/Sample1.cpp | 39 +++- .../tests/BLETests/SampleClientAndServer.cpp | 195 ++++++++++++++++++ .../tests/BLETests/SampleClientWithWiFi.cpp | 122 +++++++++++ cpp_utils/tests/BLETests/SampleDisconnect.cpp | 113 ++++++++++ cpp_utils/tests/BLETests/SampleServer.cpp | 3 +- cpp_utils/tests/BLETests/main.cpp | 33 +-- 8 files changed, 537 insertions(+), 82 deletions(-) create mode 100644 cpp_utils/tests/BLETests/SampleClientAndServer.cpp create mode 100644 cpp_utils/tests/BLETests/SampleClientWithWiFi.cpp create mode 100644 cpp_utils/tests/BLETests/SampleDisconnect.cpp diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index c1e0b30b..26479663 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -35,20 +35,6 @@ BLEAdvertising::BLEAdvertising() { m_advData.p_service_uuid = nullptr; m_advData.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT); - m_advDataScanResponse.set_scan_rsp = true; - m_advDataScanResponse.include_name = false; - m_advDataScanResponse.include_txpower = false; - m_advDataScanResponse.min_interval = 0x20; - m_advDataScanResponse.max_interval = 0x40; - m_advDataScanResponse.appearance = 0x00; - m_advDataScanResponse.manufacturer_len = 0; - m_advDataScanResponse.p_manufacturer_data = nullptr; - m_advDataScanResponse.service_data_len = 0; - m_advDataScanResponse.p_service_data = nullptr; - m_advDataScanResponse.service_uuid_len = 0; - m_advDataScanResponse.p_service_uuid = nullptr; - m_advDataScanResponse.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT); - m_advParams.adv_int_min = 0x20; m_advParams.adv_int_max = 0x40; m_advParams.adv_type = ADV_TYPE_IND; @@ -58,6 +44,24 @@ BLEAdvertising::BLEAdvertising() { } // BLEAdvertising +/** + * @brief Add a service uuid to exposed list of services. + * @param [in] serviceUUID The UUID of the service to expose. + */ +void BLEAdvertising::addServiceUUID(BLEUUID serviceUUID) { + m_serviceUUIDs.push_back(serviceUUID); +} // addServiceUUID + + +/** + * @brief Add a service uuid to exposed list of services. + * @param [in] serviceUUID The string representation of the service to expose. + */ +void BLEAdvertising::addServiceUUID(const char* serviceUUID) { + addServiceUUID(BLEUUID(serviceUUID)); +} // addServiceUUID + + /** * @brief Set the device appearance in the advertising data. * The appearance attribute is of type 0x19. The codes for distinct appearances can be found here: @@ -70,38 +74,6 @@ void BLEAdvertising::setAppearance(uint16_t appearance) { } // setAppearance -/** - * @brief Set the service UUID. - * We maintain a class member called m_advData (esp_ble_adv_data_t) that is passed to the - * ESP-IDF advertising functions. In this method, we see two fields within that structure - * namely service_uuid_len and p_service_uuid to be the information supplied in the passed - * in service uuid. - * @param [in] uuid The UUID of the service. - * @return N/A. - */ -void BLEAdvertising::setServiceUUID(const char* serviceUUID) { - return setServiceUUID(BLEUUID(serviceUUID)); -} -/** - * @brief Set the service UUID. - * We maintain a class member called m_advData (esp_ble_adv_data_t) that is passed to the - * ESP-IDF advertising functions. In this method, we see two fields within that structure - * namely service_uuid_len and p_service_uuid to be the information supplied in the passed - * in service uuid. - * @param [in] uuid The UUID of the service. - * @return N/A. - */ -void BLEAdvertising::setServiceUUID(BLEUUID serviceUUID) { - ESP_LOGD(LOG_TAG, ">> setServiceUUID - %s", serviceUUID.toString().c_str()); - m_serviceUUID = serviceUUID; // Save the new service UUID - m_serviceUUID128 = serviceUUID.to128(); - - m_advDataScanResponse.service_uuid_len = 16; - m_advDataScanResponse.p_service_uuid = reinterpret_cast(&m_serviceUUID128.getNative()->uuid.uuid128); - ESP_LOGD(LOG_TAG, "<< setServiceUUID"); -} // setServiceUUID - - /** * @brief Start advertising. * Start advertising. @@ -110,30 +82,49 @@ void BLEAdvertising::setServiceUUID(BLEUUID serviceUUID) { void BLEAdvertising::start() { ESP_LOGD(LOG_TAG, ">> start"); - if (m_advDataScanResponse.service_uuid_len > 0) { - uint8_t hexData[16*2+1]; - BLEUtils::buildHexData(hexData, m_advDataScanResponse.p_service_uuid, m_advDataScanResponse.service_uuid_len); - ESP_LOGD(LOG_TAG, " - Service: service_uuid_len=%d, p_service_uuid=0x%x (data=%s)", - m_advDataScanResponse.service_uuid_len, - (uint32_t)m_advDataScanResponse.p_service_uuid, - (m_advDataScanResponse.service_uuid_len > 0?(char *)hexData:"N/A") - ); - } // We have a service to advertise + // We have a vector of service UUIDs that we wish to advertise. In order to use the + // ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte) + // representations. If we have 1 or more services to advertise then we allocate enough + // storage to host them and then copy them in one at a time into the contiguous storage. + int numServices = m_serviceUUIDs.size(); + if (numServices > 0) { + m_advData.service_uuid_len = 16*numServices; + m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; + uint8_t* p = m_advData.p_service_uuid; + for (int i=0; iuuid.uuid128, 16); + p+=16; + } + } else { + m_advData.service_uuid_len = 0; + ESP_LOGD(LOG_TAG, "- no services advertised"); + } // Set the configuration for advertising. + m_advData.set_scan_rsp = false; esp_err_t errRc = ::esp_ble_gap_config_adv_data(&m_advData); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } - errRc = ::esp_ble_gap_config_adv_data(&m_advDataScanResponse); + m_advData.set_scan_rsp = true; + errRc = ::esp_ble_gap_config_adv_data(&m_advData); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + // If we had services to advertise then we previously allocated some storage for them. + // Here we release that storage. + if (m_advData.service_uuid_len > 0) { + delete[] m_advData.p_service_uuid; + m_advData.p_service_uuid = nullptr; + } + // Start advertising. errRc = ::esp_ble_gap_start_advertising(&m_advParams); if (errRc != ESP_OK) { @@ -158,4 +149,6 @@ void BLEAdvertising::stop() { } ESP_LOGD(LOG_TAG, "<< stop"); } // stop + + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 810431e7..6f315b8c 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -11,6 +11,7 @@ #if defined(CONFIG_BT_ENABLED) #include #include "BLEUUID.h" +#include /** * @brief Perform and manage %BLE advertising. @@ -20,17 +21,15 @@ class BLEAdvertising { public: BLEAdvertising(); + void addServiceUUID(BLEUUID serviceUUID); + void addServiceUUID(const char* serviceUUID); void start(); void stop(); void setAppearance(uint16_t appearance); - void setServiceUUID(const char* serviceUUID); - void setServiceUUID(BLEUUID serviceUUID); private: esp_ble_adv_data_t m_advData; - esp_ble_adv_data_t m_advDataScanResponse; esp_ble_adv_params_t m_advParams; - BLEUUID m_serviceUUID; - BLEUUID m_serviceUUID128; + std::vector m_serviceUUIDs; }; #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */ diff --git a/cpp_utils/tests/BLETests/Sample1.cpp b/cpp_utils/tests/BLETests/Sample1.cpp index ad83ca7f..b551a186 100644 --- a/cpp_utils/tests/BLETests/Sample1.cpp +++ b/cpp_utils/tests/BLETests/Sample1.cpp @@ -2,6 +2,7 @@ #include "BLEServer.h" #include #include +#include #include "BLEDevice.h" #include "sdkconfig.h" @@ -9,30 +10,56 @@ // See the following for generating UUIDs: // https://www.uuidgenerator.net/ -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" +#define CHARACTERISTIC_UUID2 "54059634-9448-404f-9af4-7d14556f3ad8" +#define CHARACTERISTIC_UUID3 "78f8a814-7b20-40ca-b970-0aba448c53b1" +#define CHARACTERISTIC_UUID4 "03a55273-c1ef-4eab-a6c0-7ff11509122f" +#define CHARACTERISTIC_UUID5 "0d19566d-2144-4443-9779-19d42e283439" static void run() { BLEDevice::init("MYDEVICE"); - BLEServer *pServer = BLEDevice::createServer(); + BLEServer* pServer = BLEDevice::createServer(); - BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); + BLEService* pService = pServer->createService(BLEUUID(SERVICE_UUID)); - BLECharacteristic *pCharacteristic = pService->createCharacteristic( + BLECharacteristic* pCharacteristic = pService->createCharacteristic( BLEUUID(CHARACTERISTIC_UUID), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE ); + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID2), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID3), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID4), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID5), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); pService->start(); - BLEAdvertising *pAdvertising = pServer->getAdvertising(); + BLEAdvertising* pAdvertising = pServer->getAdvertising(); pAdvertising->start(); } void Sample1(void) { + //esp_log_level_set("*", ESP_LOG_ERROR); run(); } // app_main diff --git a/cpp_utils/tests/BLETests/SampleClientAndServer.cpp b/cpp_utils/tests/BLETests/SampleClientAndServer.cpp new file mode 100644 index 00000000..ecb028d9 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleClientAndServer.cpp @@ -0,0 +1,195 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include +#include +extern "C" { // See issue 1069: https://github.com/espressif/esp-idf/issues/1069 + #include +} +#include "BLEDevice.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClientAndServer"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +class MyServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting to be a BLE Server"); + + BLEDevice::init("MYDEVICE"); + BLEServer* pServer = BLEDevice::createServer(); + + BLEService* pService = pServer->createService(serviceUUID); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + charUUID, + BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + + pCharacteristic->setValue("Hello World!"); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(pService->getUUID()); + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(1000000); + } +}; + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (!advertisedDevice.haveServiceUUID()) { + ESP_LOGD(LOG_TAG, "Found device is not advertising a service ... ignoring"); + return; + } + if (advertisedDevice.getServiceUUID().equals(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + return; + } // Found our server + ESP_LOGD(LOG_TAG, "Found device advertised %s which is not %s ... ignoring", + advertisedDevice.getServiceUUID().toString().c_str(), + serviceUUID.toString().c_str()); + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClientAndServer(void) { + ESP_LOGD(LOG_TAG, "SampleClientAndServer starting"); + esp_vfs_dev_uart_register(); + int fd = open("/dev/uart/0", O_RDONLY); + if (fd == -1) { + ESP_LOGE(LOG_TAG, "Failed to open file %s", strerror(errno)); + return; + } + + ESP_LOGD(LOG_TAG, "Enter:"); + ESP_LOGD(LOG_TAG, "C - Client"); + ESP_LOGD(LOG_TAG, "S - Server"); + bool isServer; + while(1) { + uint8_t val; + ssize_t rc; + // Read a character from the UART. Since the UART is non-blocking, we loop until we have + // a character available. + do { + rc = read(fd, &val, 1); + if (rc == -1) { + //ESP_LOGE(LOG_TAG, "Failed to read file %s", strerror(errno)); + FreeRTOS::sleep(100); + //return; + } + } while(rc == -1); + + // See if the character is an indication of being a server or a client. + + if (val == 'c' || val == 'C') { + isServer = false; + break; + } + if (val == 's' || val == 'S') { + isServer = true; + break; + } + } + close(fd); // Close the UART file as we don't need it any more. + ESP_LOGD(LOG_TAG, "Chosen: %s", isServer?"Server":"Client"); + + if (isServer) { + MyServer* pMyServer = new MyServer(); + pMyServer->setStackSize(20000); + pMyServer->start(); + } else { + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(15); + } + +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleClientWithWiFi.cpp b/cpp_utils/tests/BLETests/SampleClientWithWiFi.cpp new file mode 100644 index 00000000..152ff130 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleClientWithWiFi.cpp @@ -0,0 +1,122 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" +#include "WiFi.h" +#include "HttpServer.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClientWithWiFi"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClientWithWiFi(void) { + ESP_LOGD(LOG_TAG, "SampleClientWithWiFi starting"); + WiFi wifi; + wifi.setIPInfo("192.168.1.99", "192.168.1.1", "255.255.255.0"); + wifi.connectAP("sweetie", "l16wint!"); + + HttpServer httpServer; + httpServer.start(80); + + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(15); +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleDisconnect.cpp b/cpp_utils/tests/BLETests/SampleDisconnect.cpp new file mode 100644 index 00000000..88869075 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleDisconnect.cpp @@ -0,0 +1,113 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClientDisconnect"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + for (int i=0; i<5; i++) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClientDisconnect(void) { + ESP_LOGD(LOG_TAG, "Scanning SampleClientDisconnect starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(15); +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleServer.cpp b/cpp_utils/tests/BLETests/SampleServer.cpp index 15a52cd6..c7dffd1a 100644 --- a/cpp_utils/tests/BLETests/SampleServer.cpp +++ b/cpp_utils/tests/BLETests/SampleServer.cpp @@ -39,7 +39,8 @@ class MainBLEServer: public Task { pService->start(); BLEAdvertising* pAdvertising = pServer->getAdvertising(); - pAdvertising->setServiceUUID(pService->getUUID().to128()); + pAdvertising->addServiceUUID(pService->getUUID()); + pAdvertising->addServiceUUID(BLEUUID((uint16_t)0x9876)); pAdvertising->start(); ESP_LOGD(LOG_TAG, "Advertising started!"); diff --git a/cpp_utils/tests/BLETests/main.cpp b/cpp_utils/tests/BLETests/main.cpp index 079cf3cf..732ae16d 100644 --- a/cpp_utils/tests/BLETests/main.cpp +++ b/cpp_utils/tests/BLETests/main.cpp @@ -7,30 +7,35 @@ extern "C" { // The list of sample entry points. -void SampleServer(void); +void Sample_MLE_15(void); void Sample1(void); +void SampleClient(void); +void SampleClient_Notify(void); +void SampleClientAndServer(void); +void SampleClientDisconnect(void); +void SampleClientWithWiFi(void); +void SampleNotify(void); void SampleRead(void); -void SampleWrite(void); void SampleScan(void); -void SampleNotify(void); -void SampleClient_Notify(void); -void SampleClient(void); -void Sample_MLE_15(void); void SampleSensorTag(void); - +void SampleServer(void); +void SampleWrite(void); // // Un-comment ONE of the following // --- void app_main(void) { - //SampleServer(); + //Sample_MLE_15(); //Sample1(); - //SampleRead(); - //SampleWrite(); - //SampleScan(); - //SampleNotify(); //SampleClient(); - SampleClient_Notify(); - //Sample_MLE_15(); + //SampleClient_Notify(); + //SampleClientAndServer(); + //SampleClientDisconnect(); + //SampleClientWithWiFi(); + //SampleNotify(); + //SampleRead(); //SampleSensorTag(); + //SampleScan(); + SampleServer(); + //SampleWrite(); } // app_main From 479a786e320c5c351dcf938f77bbb60494b105e7 Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Fri, 13 Oct 2017 20:40:17 +0300 Subject: [PATCH 104/381] Fix Arduino Examples --- cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino | 2 +- cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino | 6 ++---- cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino | 2 +- cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino | 7 ++----- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino index 44506c26..8d329c20 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino @@ -53,7 +53,7 @@ void setup() { BLEDevice::init("MyESP32"); // Create the BLE Server - BLEServer *pServer = new BLEServer(); + BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Create the BLE Service diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino b/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino index 45ebf99f..38224a67 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino @@ -7,8 +7,6 @@ #include #include -BLEDevice ble; - // See the following for generating UUIDs: // https://www.uuidgenerator.net/ @@ -19,8 +17,8 @@ void setup() { Serial.begin(115200); Serial.println("Starting BLE work!"); - ble.init("MyESP32"); - BLEServer *pServer = new BLEServer(); + BLEDevice::init("MyESP32"); + BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(SERVICE_UUID); BLECharacteristic *pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino index a8ab2d7f..a348a666 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino @@ -70,7 +70,7 @@ void setup() { BLEDevice::init("UART Service"); // Create the BLE Server - BLEServer *pServer = new BLEServer(); + BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Create the BLE Service diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino b/cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino index ed5ebc64..24a0cd23 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino @@ -7,8 +7,6 @@ #include #include -BLEDevice ble; - // See the following for generating UUIDs: // https://www.uuidgenerator.net/ @@ -41,9 +39,8 @@ void setup() { Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something"); Serial.println("5- See the magic =)"); - //ble.begin("MyESP32"); - ble.init("MyESP32"); - BLEServer *pServer = new BLEServer(); + BLEDevice::init("MyESP32"); + BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(SERVICE_UUID); From fdafe2f39462ddf256d48c00e257a1259ef5ae4c Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Sat, 14 Oct 2017 00:19:16 +0300 Subject: [PATCH 105/381] Enable Logging in Arduino IDE --- cpp_utils/BLEAddress.cpp | 3 +++ cpp_utils/BLEAdvertisedDevice.cpp | 3 +++ cpp_utils/BLEAdvertising.cpp | 3 +++ cpp_utils/BLECharacteristic.cpp | 3 +++ cpp_utils/BLECharacteristicCallbacks.cpp | 3 +++ cpp_utils/BLECharacteristicMap.cpp | 3 +++ cpp_utils/BLEClient.cpp | 3 +++ cpp_utils/BLEDescriptor.cpp | 3 +++ cpp_utils/BLEDescriptorMap.cpp | 3 +++ cpp_utils/BLEDevice.cpp | 3 +++ cpp_utils/BLERemoteCharacteristic.cpp | 3 +++ cpp_utils/BLERemoteDescriptor.cpp | 3 +++ cpp_utils/BLERemoteService.cpp | 3 +++ cpp_utils/BLEScan.cpp | 3 +++ cpp_utils/BLEServer.cpp | 3 +++ cpp_utils/BLEValue.cpp | 3 +++ 16 files changed, 48 insertions(+) diff --git a/cpp_utils/BLEAddress.cpp b/cpp_utils/BLEAddress.cpp index d2f7f8b0..895fedad 100644 --- a/cpp_utils/BLEAddress.cpp +++ b/cpp_utils/BLEAddress.cpp @@ -13,6 +13,9 @@ #include #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif /** diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 5074b811..83869cf6 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -17,6 +17,9 @@ #include #include "BLEAdvertisedDevice.h" #include "BLEUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG="BLEAdvertisedDevice"; BLEAdvertisedDevice::BLEAdvertisedDevice() { diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 26479663..e5179a41 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -12,6 +12,9 @@ #include #include "BLEUtils.h" #include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLEAdvertising"; diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 284032e7..18ba65dd 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -18,6 +18,9 @@ #include "BLEUtils.h" #include "BLE2902.h" #include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLECharacteristic"; diff --git a/cpp_utils/BLECharacteristicCallbacks.cpp b/cpp_utils/BLECharacteristicCallbacks.cpp index b7338659..46905b51 100644 --- a/cpp_utils/BLECharacteristicCallbacks.cpp +++ b/cpp_utils/BLECharacteristicCallbacks.cpp @@ -8,6 +8,9 @@ #if defined(CONFIG_BT_ENABLED) #include "BLECharacteristic.h" #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLECharacteristicCallbacks"; diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index 6ded0a63..bcf4a75d 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -9,6 +9,9 @@ #include #include #include "BLEService.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif /** diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 645c4aea..93f703a7 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -18,6 +18,9 @@ #include #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif /* * Design diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 4a7fda60..8362af0a 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -16,6 +16,9 @@ #include "BLEService.h" #include "BLEDescriptor.h" #include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLEDescriptor"; diff --git a/cpp_utils/BLEDescriptorMap.cpp b/cpp_utils/BLEDescriptorMap.cpp index b2116521..4e372e1d 100644 --- a/cpp_utils/BLEDescriptorMap.cpp +++ b/cpp_utils/BLEDescriptorMap.cpp @@ -11,6 +11,9 @@ #include "BLECharacteristic.h" #include "BLEDescriptor.h" #include // ESP32 BLE +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif /** * @brief Return the descriptor by UUID. diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index b2777e58..97d02b14 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -26,6 +26,9 @@ #include "BLEClient.h" #include "BLEUtils.h" #include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLEDevice"; diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 2e0fbb0e..ba781d29 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -18,6 +18,9 @@ #include "BLEUtils.h" #include "GeneralUtils.h" #include "BLERemoteDescriptor.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLERemoteCharacteristic"; // The logging tag for this class. diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index a606929d..7a509f86 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -10,6 +10,9 @@ #include "BLERemoteDescriptor.h" #include "GeneralUtils.h" #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLERemoteDescriptor"; diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index c312e946..c0df06c3 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -13,6 +13,9 @@ #include "GeneralUtils.h" #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLERemoteService"; diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 925c09db..d3157b7f 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -17,6 +17,9 @@ #include "BLEScan.h" #include "BLEUtils.h" #include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLEScan"; diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 4a8dfd57..f7b44760 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -20,6 +20,9 @@ #include #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG = "BLEServer"; diff --git a/cpp_utils/BLEValue.cpp b/cpp_utils/BLEValue.cpp index d36d207a..49818e27 100644 --- a/cpp_utils/BLEValue.cpp +++ b/cpp_utils/BLEValue.cpp @@ -10,6 +10,9 @@ #include #include "BLEValue.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif static const char* LOG_TAG="BLEValue"; From e215aa9a9f07e2ef06f450054f69d3fffafa4965 Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 15 Oct 2017 12:42:38 -0500 Subject: [PATCH 106/381] Fixes for #121 --- cpp_utils/ArduinoBLE.md | 8 ++++++++ cpp_utils/Arduino_ESP32_BLE.library.properties | 2 +- cpp_utils/BLEServer.cpp | 1 - cpp_utils/BLEService.cpp | 7 ++++++- cpp_utils/BLEService.h | 6 ++++-- cpp_utils/FreeRTOS.cpp | 8 ++++++-- cpp_utils/FreeRTOS.h | 12 ++++++------ cpp_utils/Makefile.arduino | 6 ++++++ 8 files changed, 37 insertions(+), 13 deletions(-) diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index 07b37fd9..133292e8 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -23,6 +23,14 @@ And here you will find the `ESP32_BLE.zip` that is build from the latest source. ## Installing a new version If you have previously installed a version of the Arduino BLE Support and need to install a new one, follow the steps above to build yourself a new instance of the `ESP32_BLE.zip` that is ready for installation. I recommend removing the old one before installing the new one. To remove the old one, find the directory where the Arduino IDE stores your libraries (on Linux this is commonly `$HOME/Arduino`). In there you will find a directory called `libraries` and, if you have previously installed the BLE support, you will find a directory called `ESP32_BLE`. Remove that directory. +## Replacing the version that comes with Arduino-ESP32 +From October 2017 onwards, a build of the BLE libraries is supplied with the Arduino-ESP32 distribition which means that you should just be able to use the function without performing any additional steps. The intent is to keep the BLE libraries in this project completely in synch with the Arduino-ESP32 distribution. However, there may be times when a bug fix is needed which you may wish to try before an official distribution in Arduino-ESP32. That should be extremely rare. However, just in case it is needed, here is the recipe: + +1. Go to `/hardware/espressif/esp32/libraries` +2. Delete the directory called `BLE` +3. Go to your `/libraries` folder +4. Extract the `ESP32_BLE.zip` file there + ## Switching on debugging The BLE support contains extensive internal diagnostics which can be switched on by editing the file called `sdkconfig.h` and finding the lines which read: diff --git a/cpp_utils/Arduino_ESP32_BLE.library.properties b/cpp_utils/Arduino_ESP32_BLE.library.properties index b9f5972f..fdb01e2f 100644 --- a/cpp_utils/Arduino_ESP32_BLE.library.properties +++ b/cpp_utils/Arduino_ESP32_BLE.library.properties @@ -1,5 +1,5 @@ name=ESP32 BLE Arduino -version=0.4.2 +version=0.4.3 author=Neil Kolban maintainer=Neil Kolban sentence=BLE functions for ESP32 diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index f7b44760..0e66ec0e 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -201,7 +201,6 @@ void BLEServer::handleGATTServerEvent( // - uint16_t app_id case ESP_GATTS_REG_EVT: { m_gatts_if = gatts_if; - m_semaphoreRegisterAppEvt.give(); // Unlock the mutex waiting for the registration of the app. break; } // ESP_GATTS_REG_EVT diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 9e7fa280..669ed261 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -56,9 +56,14 @@ BLEService::BLEService(BLEUUID uuid, uint32_t numHandles) { * @param [in] gatts_if The handle of the GATT server interface. * @return N/A. */ + void BLEService::executeCreate(BLEServer *pServer) { //ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); - getUUID(); // Needed for a weird bug fix + //getUUID(); // Needed for a weird bug fix + //char x[1000]; + //memcpy(x, &m_uuid, sizeof(m_uuid)); + //char x[10]; + //memcpy(x, &deleteMe, 10); m_pServer = pServer; m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 9a93aff7..19b472e9 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -78,10 +78,12 @@ class BLEService { uint16_t m_handle; BLECharacteristic* m_lastCreatedCharacteristic; BLEServer* m_pServer; + BLEUUID m_uuid; + char deleteMe[10]; //FreeRTOS::Semaphore m_serializeMutex; FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); - FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); - BLEUUID m_uuid; + FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); + uint32_t m_numHandles; uint16_t getHandle(); diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index dea73bfe..3cabd91b 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -4,8 +4,9 @@ * Created on: Feb 24, 2017 * Author: kolban */ -#include -#include +#include // Include the base FreeRTOS definitions +#include // Include the task definitions +#include // Include the semaphore definitions #include #include #include @@ -102,6 +103,9 @@ FreeRTOS::Semaphore::~Semaphore() { */ void FreeRTOS::Semaphore::give() { xSemaphoreGive(m_semaphore); +#ifdef ARDUINO_ARCH_ESP32 + FreeRTOS::sleep(10); +#endif ESP_LOGV(LOG_TAG, "Semaphore giving: %s", toString().c_str()); m_owner = ""; } // Semaphore::give diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index 320f4cc6..edaa9df4 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -10,9 +10,9 @@ #include #include -#include -#include -#include +#include // Include the base FreeRTOS definitions +#include // Include the task definitions +#include // Include the semaphore definitions /** @@ -40,9 +40,9 @@ class FreeRTOS { std::string toString(); private: SemaphoreHandle_t m_semaphore; - std::string m_name; - std::string m_owner; - uint32_t m_value; + std::string m_name; + std::string m_owner; + uint32_t m_value; }; }; diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index 609f8450..fc3ccd0f 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -51,6 +51,7 @@ BLE_FILES= \ GeneralUtils.h \ GeneralUtils.cpp +ARDUINO_LIBS=$(HOME)/Arduino/libraries build_ble: rm -rf Arduino/ESP32_BLE @@ -64,3 +65,8 @@ build_ble: rm -rf Arduino/ESP32_BLE @echo "---------------------------------------" @echo "ESP32_BLE.zip Arduino library now built" + +install: build_ble + rm -rf ${ARDUINO_LIBS}/ESP32_BLE + unzip Arduino/ESP32_BLE.zip -d $(ARDUINO_LIBS) + From 3893a18c2ad9d16173e784c9eab54939c14b435f Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 15 Oct 2017 22:30:02 -0500 Subject: [PATCH 107/381] Code changes for #127 --- cpp_utils/BLEClient.cpp | 101 ++++++++++++++++++++++++++++++++++++---- cpp_utils/BLEClient.h | 7 +++ cpp_utils/BLEDevice.cpp | 4 ++ cpp_utils/BLEServer.cpp | 2 +- 4 files changed, 104 insertions(+), 10 deletions(-) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 93f703a7..721e5efb 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -49,6 +49,7 @@ BLEClient::BLEClient() { m_conn_id = 0; m_gattc_if = 0; m_haveServices = false; + m_isConnected = false; // Initially, we are flagged as not connected. } // BLEClient @@ -76,7 +77,8 @@ bool BLEClient::connect(BLEAddress address) { // We need the connection handle that we get from registering the application. We register the app // and then block on its completion. When the event has arrived, we will have the handle. m_semaphoreRegEvt.take("connect"); - esp_err_t errRc = esp_ble_gattc_app_register(0); + + esp_err_t errRc = ::esp_ble_gattc_app_register(0); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return false; @@ -130,6 +132,22 @@ void BLEClient::gattClientEventHandler( // Execute handler code based on the type of event received. switch(event) { + + // + // ESP_GATTC_DISCONNECT_EVT + // + // disconnect: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + case ESP_GATTC_DISCONNECT_EVT: { + // If we receive a disconnect event, set the class flag that indicates that we are + // no longer connected. + m_isConnected = false; + break; + } // ESP_GATTC_DISCONNECT_EVT + + // // ESP_GATTC_OPEN_EVT // @@ -144,6 +162,7 @@ void BLEClient::gattClientEventHandler( if (m_pClientCallbacks != nullptr) { m_pClientCallbacks->onConnect(this); } + m_isConnected = true; // Flag us as connected. m_semaphoreOpenEvt.give(); break; } // ESP_GATTC_OPEN_EVT @@ -211,6 +230,17 @@ void BLEClient::gattClientEventHandler( } // gattClientEventHandler +uint16_t BLEClient::getConnId() { + return m_conn_id; +} // getConnId + + + +esp_gatt_if_t BLEClient::getGattcIf() { + return m_gattc_if; +} // getGattcIf + + /** * @brief Retrieve the address of the peer. * @@ -221,14 +251,29 @@ BLEAddress BLEClient::getPeerAddress() { } // getAddress -uint16_t BLEClient::getConnId() { - return m_conn_id; -} // getConnId - - -esp_gatt_if_t BLEClient::getGattcIf() { - return m_gattc_if; -} // getGattcIf +/** + * @brief Ask the BLE server for the RSSI value. + * @return The RSSI value. + */ +int BLEClient::getRssi() { + ESP_LOGD(LOG_TAG, ">> getRssi()"); + if (!isConnected()) { + ESP_LOGD(LOG_TAG, "<< getRssi(): Not connected"); + return 0; + } + // We make the API call to read the RSSI value which is an asynchronous operation. We expect to receive + // an ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT to indicate completion. + // + m_semaphoreRssiCmplEvt.take("getRssi"); + esp_err_t rc = ::esp_ble_gap_read_rssi(*getPeerAddress().getNative()); + if (rc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< getRssi: esp_ble_gap_read_rssi: rc=%d %s", rc, GeneralUtils::errorToString(rc)); + return 0; + } + int rssiValue = m_semaphoreRssiCmplEvt.wait("getRssi"); + ESP_LOGD(LOG_TAG, "<< getRssi(): %d", rssiValue); + return rssiValue; +} // getRssi /** @@ -301,6 +346,44 @@ std::map* BLEClient::getServices() { return &m_servicesMap; } // getServices +/** + * @brief Handle a received GAP event. + * + * @param [in] event + * @param [in] param + */ +void BLEClient::handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param) { + ESP_LOGD(LOG_TAG, "BLEClient ... handling GAP event!"); + switch(event) { + // + // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT + // + // read_rssi_cmpl + // - esp_bt_status_t status + // - int8_t rssi + // - esp_bd_addr_t remote_addr + // + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: { + m_semaphoreRssiCmplEvt.give((uint32_t)param->read_rssi_cmpl.rssi); + break; + } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT + + default: + break; + } +} // handleGAPEvent + + +/** + * @brief Are we connected to a partner? + * @return True if we are connected and false if we are not connected. + */ +bool BLEClient::isConnected() { + return m_isConnected; +} // isConnected + /** * @brief Set the callbacks that will be invoked. diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 494f51dd..25704a34 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -32,9 +32,14 @@ class BLEClient { bool connect(BLEAddress address); void disconnect(); BLEAddress getPeerAddress(); + int getRssi(); std::map* getServices(); BLERemoteService* getService(const char* uuid); BLERemoteService* getService(BLEUUID uuid); + void handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); + bool isConnected(); void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); std::string toString(); @@ -55,11 +60,13 @@ class BLEClient { uint16_t m_conn_id; // int m_deviceType; esp_gatt_if_t m_gattc_if; + bool m_isConnected; BLEClientCallbacks* m_pClientCallbacks; FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt"); + FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt"); std::map m_servicesMap; bool m_haveServices; // Have we previously obtain the set of services. }; // class BLEDevice diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 97d02b14..e58cac7f 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -147,6 +147,10 @@ void BLEDevice::gapEventHandler( BLEDevice::m_pServer->handleGAPEvent(event, param); } + if (BLEDevice::m_pClient != nullptr) { + BLEDevice::m_pClient->handleGAPEvent(event, param); + } + if (BLEDevice::m_pScan != nullptr) { BLEDevice::getScan()->gapEventHandler(event, param); } diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 0e66ec0e..0d9bd6b5 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -123,7 +123,7 @@ uint16_t BLEServer::getGattsIf() { } /** - * @brief Handle a receiver GAP event. + * @brief Handle a received GAP event. * * @param [in] event * @param [in] param From b40811ffa2843dc37e1ead9b46083a8f482fcb52 Mon Sep 17 00:00:00 2001 From: kolban Date: Tue, 17 Oct 2017 23:37:48 -0500 Subject: [PATCH 108/381] Changes for #132 --- networking/bootwifi/BootWiFi.cpp | 16 ++++++++++++++-- networking/bootwifi/BootWiFi.h | 9 ++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/networking/bootwifi/BootWiFi.cpp b/networking/bootwifi/BootWiFi.cpp index ab9f4f3d..a6f8ebb6 100644 --- a/networking/bootwifi/BootWiFi.cpp +++ b/networking/bootwifi/BootWiFi.cpp @@ -222,7 +222,7 @@ void BootWiFi::bootWiFi2() { m_wifi.setWifiEventHandler(new BootWifiEventHandler(this)); if (checkOverrideGpio()) { ESP_LOGD(LOG_TAG, "- GPIO override detected"); - m_wifi.startAP("Duktape", "Duktape"); + m_wifi.startAP(m_ssid, m_password); } else { // There was NO GPIO override, proceed as normal. This means we retrieve // our stored access point information of the access point we should connect @@ -249,19 +249,30 @@ void BootWiFi::bootWiFi2() { // point that serves up a web server and allow a browser user to specify // the details that will be eventually used to allow us to connect // as a station. - m_wifi.startAP("Duktape", "Duktape"); + m_wifi.startAP(m_ssid, m_password); } // We do NOT have connection info } ESP_LOGD(LOG_TAG, "<< bootWiFi2"); } // bootWiFi2 +/** + * @brief Set the userid/password pair that will be used for the ESP32 access point. + * @param [in] ssid The network id of the ESP32 when it becomes an access point. + * @param [in] password The password for the ESP32 when it becomes an access point. + */ +void BootWiFi::setAccessPointCredentials(std::string ssid, std::string password) { + m_ssid = ssid; + m_password = password; +} // setAccessPointCredentials + void BootWiFi::boot() { ESP_LOGD(LOG_TAG, ">> boot"); ESP_LOGD(LOG_TAG, " +----------+"); ESP_LOGD(LOG_TAG, " | BootWiFi |"); ESP_LOGD(LOG_TAG, " +----------+"); + ESP_LOGD(LOG_TAG, " Access point credentials: %s/%s", m_ssid.c_str(), m_password.c_str()); m_completeSemaphore.take("boot"); // Take the semaphore which will be unlocked when we complete booting. bootWiFi2(); m_completeSemaphore.wait("boot"); // Wait for the semaphore that indicated we have completed booting. @@ -270,4 +281,5 @@ void BootWiFi::boot() { BootWiFi::BootWiFi() { m_httpServerStarted = false; + setAccessPointCredentials("esp32", "password"); } diff --git a/networking/bootwifi/BootWiFi.h b/networking/bootwifi/BootWiFi.h index e2b26f94..3d37dccd 100644 --- a/networking/bootwifi/BootWiFi.h +++ b/networking/bootwifi/BootWiFi.h @@ -19,13 +19,16 @@ class BootWiFi { private: friend BootWifiEventHandler; void bootWiFi2(); - WiFi m_wifi; - HttpServer m_httpServer; - bool m_httpServerStarted; + WiFi m_wifi; + HttpServer m_httpServer; + bool m_httpServerStarted; + std::string m_ssid; + std::string m_password; FreeRTOS::Semaphore m_completeSemaphore = FreeRTOS::Semaphore("completeSemaphore"); public: BootWiFi(); + void setAccessPointCredentials(std::string ssid, std::string password); void boot(); }; From f9a876e19c5ac2e1e7d4160639abd85e84906cee Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 19 Oct 2017 03:22:10 +0200 Subject: [PATCH 109/381] Optimization in display function to speed up drawing --- .../Adafruit_SSD1306.cpp | 60 ++++++++++++------- .../Adafruit_SSD1306.h | 10 +++- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.cpp b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.cpp index 12a7404a..daa4ec22 100644 --- a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.cpp +++ b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.cpp @@ -195,7 +195,7 @@ void Adafruit_SSD1306::begin(uint8_t vccstate, uint8_t i2caddr, bool reset) { dev_config.duty_cycle_pos = 0; dev_config.cs_ena_posttrans = 0; dev_config.cs_ena_pretrans = 0; - dev_config.clock_speed_hz = 100000; // 100KHz + dev_config.clock_speed_hz = 1000000; // 1MHz dev_config.spics_io_num = cs; dev_config.flags = 0; dev_config.queue_size = 1; @@ -393,27 +393,33 @@ void Adafruit_SSD1306::dim(boolean dim) { } void Adafruit_SSD1306::display(void) { - ssd1306_command(SSD1306_COLUMNADDR); - ssd1306_command(0); // Column start address (0 = reset) - ssd1306_command(SSD1306_LCDWIDTH-1); // Column end address (127 = reset) - - ssd1306_command(SSD1306_PAGEADDR); - ssd1306_command(0); // Page start address (0 = reset) - #if SSD1306_LCDHEIGHT == 64 - ssd1306_command(7); // Page end address - #endif - #if SSD1306_LCDHEIGHT == 32 - ssd1306_command(3); // Page end address - #endif - #if SSD1306_LCDHEIGHT == 16 - ssd1306_command(1); // Page end address - #endif + + gpio_set_level((gpio_num_t)dc, 0); + uint8_t cmd_buffer[] = {SSD1306_COLUMNADDR, 0, SSD1306_LCDWIDTH-1, SSD1306_PAGEADDR, 0, (SSD1306_LCDHEIGHT/8-1)}; + spi_transaction_t trans_desc; + trans_desc.addr= 0; + trans_desc.cmd = 0; + trans_desc.flags = 0; + trans_desc.length = 6 * 8; + trans_desc.rxlength = 0; + trans_desc.tx_buffer = cmd_buffer; + trans_desc.rx_buffer = NULL; + + ESP_ERROR_CHECK(spi_device_transmit(spi_handle, &trans_desc)); gpio_set_level((gpio_num_t)dc, 1); - for (uint16_t i=0; i<(SSD1306_LCDWIDTH * SSD1306_LCDHEIGHT/8); i++) { - fastSPIwrite(buffer[i]); - } + //spi_transaction_t trans_desc; + trans_desc.addr= 0; + trans_desc.cmd = 0; + trans_desc.flags = 0; + trans_desc.length = SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH; + trans_desc.rxlength = 0; + trans_desc.tx_buffer = buffer; + trans_desc.rx_buffer = NULL; + + ESP_ERROR_CHECK(spi_device_transmit(spi_handle, &trans_desc)); + } // clear everything @@ -424,8 +430,8 @@ void Adafruit_SSD1306::clearDisplay(void) { inline void Adafruit_SSD1306::fastSPIwrite(uint8_t d) { spi_transaction_t trans_desc; - trans_desc.address = 0; - trans_desc.command = 0; + trans_desc.addr= 0; + trans_desc.cmd = 0; trans_desc.flags = 0; trans_desc.length = 8; trans_desc.rxlength = 0; @@ -651,3 +657,15 @@ void Adafruit_SSD1306::drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h } } } +#ifndef ARDUINO +void Adafruit_SSD1306::println(char* text){ + print(text); + print((char*)"\n"); +} +void Adafruit_SSD1306::print(char* text){ + while(*text != 0) { + write(*text); + text++; + } +} +#endif diff --git a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h index e25c03f8..8a12aafb 100644 --- a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h +++ b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h @@ -44,8 +44,8 @@ All text above, and the splash screen must be included in any redistribution SSD1306_96_16 -----------------------------------------------------------------------*/ -// #define SSD1306_128_64 - #define SSD1306_128_32 + #define SSD1306_128_64 +// #define SSD1306_128_32 // #define SSD1306_96_16 /*=========================================================================*/ @@ -123,7 +123,11 @@ class Adafruit_SSD1306 : public Adafruit_GFX { void begin(uint8_t switchvcc = SSD1306_SWITCHCAPVCC, uint8_t i2caddr = SSD1306_I2C_ADDRESS, bool reset=true); void ssd1306_command(uint8_t c); - + void ssd1306_command(uint8_t *c, uint8_t l); +#ifndef ARDUINO + void print(char*); + void println(char*); +#endif void clearDisplay(void); void invertDisplay(uint8_t i); void display(); From 0acf6a3d566ad72a6f85425b41db43c7762170f4 Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 19 Oct 2017 03:41:06 +0200 Subject: [PATCH 110/381] Optimization in display function to speed up drawing, added esp-idf example --- .../Adafruit_SSD1306.h | 2 +- .../examples/main/component.mk | 8 + .../examples/main/main.cpp | 13 + .../examples/main/tests.cpp | 349 ++++++++++++++++++ 4 files changed, 371 insertions(+), 1 deletion(-) create mode 100644 hardware/displays/Adafruit_SSD1306-Library/examples/main/component.mk create mode 100644 hardware/displays/Adafruit_SSD1306-Library/examples/main/main.cpp create mode 100644 hardware/displays/Adafruit_SSD1306-Library/examples/main/tests.cpp diff --git a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h index 8a12aafb..98ef0227 100644 --- a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h +++ b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h @@ -123,7 +123,7 @@ class Adafruit_SSD1306 : public Adafruit_GFX { void begin(uint8_t switchvcc = SSD1306_SWITCHCAPVCC, uint8_t i2caddr = SSD1306_I2C_ADDRESS, bool reset=true); void ssd1306_command(uint8_t c); - void ssd1306_command(uint8_t *c, uint8_t l); + #ifndef ARDUINO void print(char*); void println(char*); diff --git a/hardware/displays/Adafruit_SSD1306-Library/examples/main/component.mk b/hardware/displays/Adafruit_SSD1306-Library/examples/main/component.mk new file mode 100644 index 00000000..61f8990c --- /dev/null +++ b/hardware/displays/Adafruit_SSD1306-Library/examples/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/hardware/displays/Adafruit_SSD1306-Library/examples/main/main.cpp b/hardware/displays/Adafruit_SSD1306-Library/examples/main/main.cpp new file mode 100644 index 00000000..bc0bd5aa --- /dev/null +++ b/hardware/displays/Adafruit_SSD1306-Library/examples/main/main.cpp @@ -0,0 +1,13 @@ +#include "freertos/FreeRTOS.h" +#include "esp_event.h" + +extern "C"{ + void app_main(void); +} +void test_task(void*); + +void app_main(void) +{ + xTaskCreate(&test_task, "test", 2048, NULL, 5, NULL); +} + diff --git a/hardware/displays/Adafruit_SSD1306-Library/examples/main/tests.cpp b/hardware/displays/Adafruit_SSD1306-Library/examples/main/tests.cpp new file mode 100644 index 00000000..c93fc522 --- /dev/null +++ b/hardware/displays/Adafruit_SSD1306-Library/examples/main/tests.cpp @@ -0,0 +1,349 @@ +/* + * tests.cpp + * + * Created on: Oct 9, 2017 + * Author: chegewara + */ +#define enablePartialUpdate + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +//#include "math.h" +#include "sdkconfig.h" + +#include "Adafruit_SSD1306.h" +#define SCLK_PIN GPIO_NUM_18 +#define DIN_PIN GPIO_NUM_23 +#define DC_PIN GPIO_NUM_16 +#define CS_PIN GPIO_NUM_5 +#define RST_PIN GPIO_NUM_14 + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#define NUMFLAKES 10 +#define XPOS 0 +#define YPOS 1 +#define DELTAY 2 +#define delay(x) vTaskDelay(x/portTICK_PERIOD_MS) + +#define LOGO16_GLCD_HEIGHT 16 +#define LOGO16_GLCD_WIDTH 16 + +static const unsigned char logo16_glcd_bmp[] = +{ 0b00000000, 0b11000000, + 0b00000001, 0b11000000, + 0b00000001, 0b11000000, + 0b00000011, 0b11100000, + 0b11110011, 0b11100000, + 0b11111110, 0b11111000, + 0b01111110, 0b11111111, + 0b00110011, 0b10011111, + 0b00011111, 0b11111100, + 0b00001101, 0b01110000, + 0b00011011, 0b10100000, + 0b00111111, 0b11100000, + 0b00111111, 0b11110000, + 0b01111100, 0b11110000, + 0b01110000, 0b01110000, + 0b00000000, 0b00110000 }; + +Adafruit_SSD1306 display = Adafruit_SSD1306(DIN_PIN, SCLK_PIN, DC_PIN, RST_PIN, CS_PIN); + +void testdrawchar(void) { + display.setTextSize(1); + display.setTextColor(WHITE); + display.setCursor(0,0); + + for (uint8_t i=0; i < 168; i++) { + if (i == '\n') continue; + display.write(i); + //if ((i > 0) && (i % 14 == 0)) + //display.println(); + } + display.display(); +} + +void testdrawcircle(void) { + for (int16_t i=0; i0; i-=5) { + display.fillTriangle(display.width()/2, display.height()/2-i, + display.width()/2-i, display.height()/2+i, + display.width()/2+i, display.height()/2+i, color); + if (color == WHITE) color = BLACK; + else color = WHITE; + display.display(); + } +} + +void testdrawroundrect(void) { + for (int16_t i=0; i=0; i-=4) { + display.drawLine(0, display.height()-1, display.width()-1, i, WHITE); + display.display(); + } + delay(25); + + display.clearDisplay(); + for (int16_t i=display.width()-1; i>=0; i-=4) { + display.drawLine(display.width()-1, display.height()-1, i, 0, WHITE); + display.display(); + } + for (int16_t i=display.height()-1; i>=0; i-=4) { + display.drawLine(display.width()-1, display.height()-1, 0, i, WHITE); + display.display(); + } + delay(25); + + display.clearDisplay(); + for (int16_t i=0; i display.height()) { + icons[f][XPOS] = rand()%(display.width()); + icons[f][YPOS] = 0; + icons[f][DELTAY] = rand()%(5) + 1; + } + } + } +} + +void test_task(void*) { + while(1){ + display.begin(); + //display.setContrast(50); + display.display(); + + delay(2000); + display.clearDisplay(); // clears the screen and buffer + + // draw a single pixel + display.drawPixel(10, 10, WHITE); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw many lines + testdrawline(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw rectangles + testdrawrect(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw multiple rectangles + testfillrect(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw mulitple circles + testdrawcircle(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw a circle, 10 pixel radius + display.fillCircle(display.width()/2, display.height()/2, 10, WHITE); + display.display(); + delay(2000); + display.clearDisplay(); + + testdrawroundrect(); + delay(2000); + display.clearDisplay(); + + testfillroundrect(); + delay(2000); + display.clearDisplay(); + + testdrawtriangle(); + delay(2000); + display.clearDisplay(); + + testfilltriangle(); + delay(2000); + display.clearDisplay(); + + // draw the first ~12 characters in the font + testdrawchar(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw scrolling text + testscrolltext(); + delay(2000); + display.clearDisplay(); + + // text display tests + display.setTextSize(2); + display.setTextColor(WHITE); + display.setCursor(0,0); + display.println((char*)"Hello, world!"); + display.setTextColor(WHITE, BLACK); // 'inverted' text + display.println((char*)"3.141592"); + display.setTextSize(1); + display.setTextColor(WHITE); + display.print((char*)"0x"); + display.println((char*)"DEADBEEF"); + display.display(); + delay(2000); + + // rotation example + display.clearDisplay(); + display.setRotation(1); // rotate 90 degrees counter clockwise, can also use values of 2 and 3 to go further. + display.setTextSize(1); + display.setTextColor(WHITE); + display.setCursor(0,0); + display.println((char*)"Rotation"); + display.setTextSize(1); + display.println((char*)"Example!"); + display.display(); + delay(2000); + + // revert back to no rotation + display.setRotation(0); + + // miniature bitmap display + display.clearDisplay(); + display.drawBitmap(30, 16, logo16_glcd_bmp, 16, 16, WHITE); + display.display(); + + // invert the display + display.invertDisplay(true); + delay(1000); + display.invertDisplay(false); + delay(1000); + + // draw a bitmap icon and 'animate' movement + testdrawbitmap(logo16_glcd_bmp, LOGO16_GLCD_WIDTH, LOGO16_GLCD_HEIGHT); + } +} + + From 7aaa5b4d8088f98163d6bb386a6c075c3a0a191b Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 22 Oct 2017 11:09:53 -0500 Subject: [PATCH 111/381] Work on #134 --- cpp_utils/Apa102.cpp | 46 +++++++++ cpp_utils/Apa102.h | 24 +++++ cpp_utils/SPI.cpp | 21 ++-- cpp_utils/SmartLED.cpp | 218 +++++++++++++++++++++++++++++++++++++++++ cpp_utils/SmartLED.h | 53 ++++++++++ cpp_utils/WS2812.cpp | 6 +- 6 files changed, 355 insertions(+), 13 deletions(-) create mode 100644 cpp_utils/Apa102.cpp create mode 100644 cpp_utils/Apa102.h create mode 100644 cpp_utils/SmartLED.cpp create mode 100644 cpp_utils/SmartLED.h diff --git a/cpp_utils/Apa102.cpp b/cpp_utils/Apa102.cpp new file mode 100644 index 00000000..274ec83f --- /dev/null +++ b/cpp_utils/Apa102.cpp @@ -0,0 +1,46 @@ +/* + * Apa102.cpp + * + * Created on: Oct 22, 2017 + * Author: kolban + */ + +#include "Apa102.h" + +Apa102::Apa102() { +} + +Apa102::~Apa102() { + +} + + +void Apa102::init() { + mySPI.init(); +} + +/** + * @brief Show the pixels on an APA102 device. + * The pixels that have been set are pushed to the APA102 devices. + */ +void Apa102::show() { + // We follow the data sheet for the APA102. To signify a new stream of data + // we send 32bits of 0 value. Following that are 4 bytes of color data. The + // data is 0b111 nnnnn where `nnnnn` is the brightness of the pixels. + // The following data is 8 bits for blue, 8 bits for green and 8 bits for red. + + // Send APA102 start. + mySPI.transferByte(0x0); + mySPI.transferByte(0x0); + mySPI.transferByte(0x0); + mySPI.transferByte(0x0); + + double brigthnessScale = getBrightness() / 100.0; + // Loop over all the pixels in the pixels array to set the colors. + for (int i=0; i 0); #ifdef DEBUG for (auto i=0; i %2d %.2x", i, data[i]); + ESP_LOGD(LOG_TAG, "> %2d %.2x", i, data[i]); } #endif spi_transaction_t trans_desc; @@ -125,7 +126,7 @@ void SPI::transfer(uint8_t* data, size_t dataLen) { //ESP_LOGI(tag, "... Transferring"); esp_err_t rc = ::spi_device_transmit(m_handle, &trans_desc); if (rc != ESP_OK) { - ESP_LOGE(tag, "transfer:spi_device_transmit: %d", rc); + ESP_LOGE(LOG_TAG, "transfer:spi_device_transmit: %d", rc); } } // transmit diff --git a/cpp_utils/SmartLED.cpp b/cpp_utils/SmartLED.cpp new file mode 100644 index 00000000..e0267585 --- /dev/null +++ b/cpp_utils/SmartLED.cpp @@ -0,0 +1,218 @@ +/* + * SmartLED.cpp + * + * Created on: Oct 22, 2017 + * Author: kolban + */ + +#include "SmartLED.h" +#include "string.h" +#include + +const char* LOG_TAG = "SmartLED"; + +SmartLED::SmartLED() { + m_brightness = 100; + m_pixelCount = 0; + m_pixels = nullptr; + m_colorOrder = (char *)"GRB"; +} // SmartLED + + +SmartLED::~SmartLED() { + if (m_pixels != nullptr) { + delete[] m_pixels; // Delete the allocated storage for the pixels. + } +} // ~SmartLED + +/** + * @brief Clear all the pixel colors. + * + * This sets all the pixels to off which is no brightness for all of the color channels. + * The LEDs are not actually updated until a call to show() is subsequently made. + */ +void SmartLED::clear() { + for (auto i=0; im_pixelCount; i++) { + m_pixels[i].red = 0; + m_pixels[i].green = 0; + m_pixels[i].blue = 0; + } // End loop over all the pixel +} // clear + + +/** + * @brief Get the brightness as a percentage. + * @return The brightness as a percentage. + */ +uint32_t SmartLED::getBrightness() { + return m_brightness; +} // getBrightness + +/** + * @brief Return the number of pixels in the chain. + * @return The number of pixels in the chain as previously set by setPixelCount(). + */ +uint16_t SmartLED::getPixelCount() { + return m_pixelCount; +} // getPixelCount + + +/** + * @brief Set the brightness as a percentage. + * @param [in] percent The brightness. + */ +void SmartLED::setBrightness(uint32_t percent) { + m_brightness = percent; +} // setBrightness + +/** + * @brief Set the color order of data sent to the LEDs. + * + * Data is sent to the WS2812s in a serial fashion. There are 8 bits of data for each of the three + * channel colors (red, green and blue). The WS2812 LEDs typically expect the data to arrive in the + * order of "green" then "red" then "blue". However, this has been found to vary between some + * models and manufacturers. What this means is that some want "red", "green", "blue" and still others + * have their own orders. This function can be called to override the default ordering of "GRB". + * We can specify + * an alternate order by supply an alternate three character string made up of 'R', 'G' and 'B' + * for example "RGB". + */ +void SmartLED::setColorOrder(char *colorOrder) { + if (colorOrder != nullptr && strlen(colorOrder) == 3) { + m_colorOrder = colorOrder; + } +} // setColorOrder + + +/** + * @brief Set the given pixel to the specified color. + * + * The LEDs are not actually updated until a call to show() is subsequently made. + * + * @param [in] index The pixel that is to have its color set. + * @param [in] red The amount of red in the pixel. + * @param [in] green The amount of green in the pixel. + * @param [in] blue The amount of blue in the pixel. + */ +void SmartLED::setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue) { + //assert(index < m_pixelCount); + + m_pixels[index].red = red; + m_pixels[index].green = green; + m_pixels[index].blue = blue; +} // setPixel + + +/** + * @brief Set the given pixel to the specified color. + * + * The LEDs are not actually updated until a call to show(). + * + * @param [in] index The pixel that is to have its color set. + * @param [in] pixel The color value of the pixel. + */ +void SmartLED::setPixel(uint16_t index, pixel_t pixel) { + //assert(index < m_pixelCount); + m_pixels[index] = pixel; +} // setPixel + + +/** + * @brief Set the given pixel to the specified color. + * + * The LEDs are not actually updated until a call to show(). + * + * @param [in] index The pixel that is to have its color set. + * @param [in] pixel The color value of the pixel. + */ +void SmartLED::setPixel(uint16_t index, uint32_t pixel) { + //assert(index < m_pixelCount); + + m_pixels[index].red = pixel & 0xff; + m_pixels[index].green = (pixel & 0xff00) >> 8; + m_pixels[index].blue = (pixel & 0xff0000) >> 16; +} // setPixel + +void SmartLED::setPixelCount(uint16_t pixelCount) { + ESP_LOGD(LOG_TAG, ">> setPixelCount: %d", pixelCount); + if (m_pixels != nullptr) { + delete[] m_pixels; + } + m_pixelCount = pixelCount; + m_pixels = new pixel_t[pixelCount]; // Allocate the storage for the pixels. + ESP_LOGD(LOG_TAG, "<< setPixelCount"); +} + +/** + * @brief Set the given pixel to the specified HSB color. + * + * The LEDs are not actually updated until a call to show(). + * + * @param [in] index The pixel that is to have its color set. + * @param [in] hue The amount of hue in the pixel (0-360). + * @param [in] saturation The amount of saturation in the pixel (0-255). + * @param [in] brightness The amount of brightness in the pixel (0-255). + */ +void SmartLED::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness) { + double sat_red; + double sat_green; + double sat_blue; + double ctmp_red; + double ctmp_green; + double ctmp_blue; + double new_red; + double new_green; + double new_blue; + double dSaturation=(double)saturation/255; + double dBrightness=(double)brightness/255; + + //assert(index < pixelCount); + + if (hue < 120) { + sat_red = (120 - hue) / 60.0; + sat_green = hue / 60.0; + sat_blue = 0; + } else if (hue < 240) { + sat_red = 0; + sat_green = (240 - hue) / 60.0; + sat_blue = (hue - 120) / 60.0; + } else { + sat_red = (hue - 240) / 60.0; + sat_green = 0; + sat_blue = (360 - hue) / 60.0; + } + + if (sat_red>1.0) { + sat_red = 1.0; + } + if (sat_green>1.0) { + sat_green = 1.0; + } + if (sat_blue>1.0) { + sat_blue = 1.0; + } + + ctmp_red = 2 * dSaturation * sat_red + (1 - dSaturation); + ctmp_green = 2 * dSaturation * sat_green + (1 - dSaturation); + ctmp_blue = 2 * dSaturation * sat_blue + (1 - dSaturation); + + if (dBrightness < 0.5) { + new_red = dBrightness * ctmp_red; + new_green = dBrightness * ctmp_green; + new_blue = dBrightness * ctmp_blue; + } else { + new_red = (1 - dBrightness) * ctmp_red + 2 * dBrightness - 1; + new_green = (1 - dBrightness) * ctmp_green + 2 * dBrightness - 1; + new_blue = (1 - dBrightness) * ctmp_blue + 2 * dBrightness - 1; + } + + m_pixels[index].red = (uint8_t)(new_red*255); + m_pixels[index].green = (uint8_t)(new_green*255); + m_pixels[index].blue = (uint8_t)(new_blue*255); +} // setHSBPixel + + + + + + diff --git a/cpp_utils/SmartLED.h b/cpp_utils/SmartLED.h new file mode 100644 index 00000000..e70c4843 --- /dev/null +++ b/cpp_utils/SmartLED.h @@ -0,0 +1,53 @@ +/* + * SmartLED.h + * + * Created on: Oct 22, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_SMARTLED_H_ +#define COMPONENTS_SMARTLED_H_ +#include +/** + * @brief A data type representing the color of a pixel. + */ +typedef struct { + /** + * @brief The red component of the pixel. + */ + uint8_t red; + /** + * @brief The green component of the pixel. + */ + uint8_t green; + /** + * @brief The blue component of the pixel. + */ + uint8_t blue; +} pixel_t; + + +class SmartLED { +public: + SmartLED(); + virtual ~SmartLED(); + uint32_t getBrightness(); + uint16_t getPixelCount(); + virtual void init() = 0; + virtual void show() = 0; + void setBrightness(uint32_t percent); + void setColorOrder(char *order); + void setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue); + void setPixel(uint16_t index, pixel_t pixel); + void setPixel(uint16_t index, uint32_t pixel); + void setPixelCount(uint16_t pixelCount); + void setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness); + void clear(); +protected: + uint32_t m_brightness; + char* m_colorOrder; + uint16_t m_pixelCount; + pixel_t* m_pixels; +}; + +#endif /* COMPONENTS_SMARTLED_H_ */ diff --git a/cpp_utils/WS2812.cpp b/cpp_utils/WS2812.cpp index 64f3c825..ca0a5b24 100644 --- a/cpp_utils/WS2812.cpp +++ b/cpp_utils/WS2812.cpp @@ -10,7 +10,7 @@ #include "sdkconfig.h" #include "WS2812.h" -static char tag[] = "WS2812"; +static const char* LOG_TAG = "WS2812"; /** * A NeoPixel is defined by 3 bytes ... red, green and blue. @@ -80,7 +80,7 @@ static uint8_t getChannelValueByType(char type, pixel_t pixel) { case 'G': return pixel.green; } - ESP_LOGW(tag, "Unknown color channel 0x%2x", type); + ESP_LOGW(LOG_TAG, "Unknown color channel 0x%2x", type); return 0; } // getChannelValueByType @@ -153,7 +153,7 @@ void WS2812::show() { (getChannelValueByType(this->colorOrder[1], this->pixels[i]) << 8) | (getChannelValueByType(this->colorOrder[2], this->pixels[i])); - ESP_LOGD(tag, "Pixel value: %x", currentPixel); + ESP_LOGD(LOG_TAG, "Pixel value: %x", currentPixel); for (int j=23; j>=0; j--) { // We have 24 bits of data representing the red, green amd blue channels. The value of the // 24 bits to output is in the variable current_pixel. We now need to stream this value From b59dccb1acdd1a9c5fa2e9e8445e2adf2d9b2482 Mon Sep 17 00:00:00 2001 From: kolban Date: Sun, 22 Oct 2017 11:27:48 -0500 Subject: [PATCH 112/381] Updated docs for #130 --- Documentation/C++ HTTP Server.pdf | Bin 81135 -> 89383 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Documentation/C++ HTTP Server.pdf b/Documentation/C++ HTTP Server.pdf index 1f68419d3ab54c42bf3110cd81405fd5f85fc044..ee5ab10423eca002de7d1c4d194108fc109bd64e 100755 GIT binary patch delta 71502 zcmV(%K;pme`2?rB1&~R9wOUDw>^2a-&#&lnU?!@PB}>pWw0k+^_Fy1~kXshWA&_^; z-%n{TZFawe(2Uz{%cWYsT0G(dPJh4sbrST^fTz{s>Fcj=KYn-m(_Q^o^556r-?q;; zr9aGR1%3Mb<@Eg%PV~sJ^Khrn z%R8ncJt5M0q?vv^F7MiXhu`iN2zP+M&>C(7y4xTx=|Ixk@|fcY6zF(zQRWP*=hX;v zfi*lNwt8#NvFDY41O$`z66#_Y^aBQhWK;Do0;wAgy#vO4pY zOCVi13W_wl_S8mxsK%aU|KJvol>}Sl`z{DQsekFL*Be zdDL+2qTw-={xQY$nS3aDa!?S0( zh?kA7mv?i2X2c^Oj#^?QX*jd8a%pOKvDtnYsU)6o5h_Pr($9g_ltbPI6dxh9RVP_N zEx$Xt9YS1`p(@!fhq(7{>nuV?6|O6K+IrqSs%#lvvUlh#qb@Mn2T5|&u;AKQJR2)` z8+e;DbPH>$APM0+1SU7_W3p z`=R2{M1q+ECkguLL=VQBag2-R49gP;VmRbfbSc{9Q?dH2`uWF*f)sizvPo$m^HzC= zs=cYQN6sov5Y(47dJ-72eAI}I#^&~l&Xv+!$O?YbrQ|FRSTf-8+JH(0LM$f^JJzk+B_FYVWU7MtO zGDH>pAf3 z@vNpDy|5L-v|grOJJ|bT)XUYTd1}to7A1Wa;X0BVr70_JC_`1Y3&~i2(_=tTYrO6c zVwdn5&%_6jA2I!Ud4aZ}!5Ot+@R8$?F}zzQy{F!EGEB+SXk^K0tAjjgpsTs#06n1S z7(fq-52@l~i7%$qNaHP3dEoX!Bc$GOfu# zRNf%4xil|`6Ws4Nc6EAVFj6}on=LJ|`7KQ#1_!?diNfUZICV+HzP$7^A}KDaOfuJX z@@gyTfaUuXRj(K-&SeJ1N^yITN5^}Y8C?u^w{7)iDN`N}%x1ZNB243?djYw{F~re( zi;y=%VxfW`YQd{*_bny5665&QQ?Xe;rJ5w!rYYw6EpOq8vTf!b>}}9H>qIHl@II-T zmt!*x$;DD{msPZsy2cL#ockEZ((LgSKh}|kUrmbAO1}R$K;42B=iMmY=b34%uPVOt zwh{byn)|V|-8-Fs;M@lVC7Qt;=deNjwz4qKFvss4`~<8xsO9chI+Fs)2yq^ndIgmR zx4VLBOqFv=;;VQ9pgc^n_f#Drxdd9e>k~G*!C8CF4c^#la3i%afKz2Xzq+5o5lsKm z16iL{A#FX!iHh^O+1R^3sHV7lFtzhS++9g{F}1YY@xTm!U~OZD*d;q z@vhO`^L+b%K@k=6auv>!*44c)$e~TCMqkr5d5ZKkI_+_nUaH_xzBIJtZ`T+0RVGa| zU(w&g(f2ld5PeA!9*!SjF-=TJv%4P6`2P>sJH)NV2mS`L8v=Cy4l*sUha-A6Uf3}d_T4K)W<|1ql7vX5a@DI|Cd%TEef1>Yef71K_-FEa7 z!(nZIqG68y8^TbeU8ry;KR3h&;?wg%06b{C>hRwG{Q6;S+lap0wd_Cye@LWV1A61= z==J)^?_gb8pf772_#ej$-1RNRP0<7mWF_9VUE&$ruvz%=WWW}?s@z$Mn zIX=-U`58K)(TfLRMIN?+4=7d7l6OJp^WbpEwv}vCM#+>Xmdu`TWHMKAW8r65xQvcu zV^{KUU7))~MczR6*ZCS-a7hNwRh)H^T z_e>Tm7CdIYdiFbJBp7DjXrt`*C64r@r$ZOsf51~^QmEMCDTsVj&bR*t)}!``oVq{_E{A=!mf$S_jtY_@TcS8C?-t;UpSEYe zrrUWf&q>FO3~}m0vk%NlS&nQn>onj!myx&dP1zTT*Pe7qQP1A&5~KX&+>%2{erhO2 zrY!t9%Vy*Af1HH1Xx|aWAQ~~A2E2zcZOR9X4?{ySb2GkGmO#|Lc8r2veAyz1R`fKX>oAm+5WG;P;*uX3Z#;6q&`~O#k(c3nx zivbC1q*z9KFNlfPtig+WduXfK3aw}dMi%`m%G)_jE;tA|G3Np^r-5aKi8k55BrG*Y z(pq|2f6DB!NG)tT-~669QPsB0dt_0F6=5iGsftF!IzYX##j8hs=7-Aumxsn&A@R$TTH-M)8&Vo2=CD6X#f>|aNt4)O10^pffs?-=-w@R8E zGsfEIoe~FpV`ruqu)>7!3StOdFk!a%kgXod9wj6wy1tu43RkZ?bMH~OfbsAVtG8Y+ zf21&4_WSr+nL)D4POZJ`Y?YBCI4hs3JoLy2rm;0s3^>YkC`&g$m18u_GqeP@TQlRY z?Z%9#*PadE6q&`cISb{TGJp{95vO)vHIR%Jb>=XTiWt-aZ6a>b@9(QByhrjzjw9RoHqHIoq!KYz_y%dX=#4Bh7| zye^Pblq^{W0s~2IQlPuG7wDqssxP4E6zFW`_e+U-P_pF2xLp*3n@Q~G#X}w*(h(;Z zz8wA;1pYH1hiR6>=id%Lel`3Zu8QIFABXGvVH!_%nB<59-hUmwe&_+e{{H(}!1Dg* z;r@R3t{O1Qa)25@xPJklg_KHasogta0K^TXxZ+-(OuMd@o3!D87}xuf5*n} z`cKBc2fZ9E-jXU$ZvjBlT_a|k4rZK?)_1eRRoLa{_iy_)6o1qD6k$&^7+@w+?!Yjqx4?p}ieWM4PoNT#T z1sl9AM+nBmd4KWXCfeFz7~<<}byv-+yxu!x4s0gLcG( z@BSKoSe>6{Q4$A~=a*(t2#@1|XbS&cauRTeN^cRjd3qTa<$+X*yb{fU4d}UhQhJWN z$M71xJAZ@(HR64NlV3dt@yWvaat!FBQ(n0fd9=b-{V>E|xKDIRLhuk8ou=V_mkwvb zT58eBL#r@%zC|7;BX&LIKV z1ty2!WWp{(;Ncp<^hE?J+FwpLbR*;dml7>aiGK(iOLS)#>CtigVG7hVi;opXy|XGY zo+LkPA~e8LBBsJ&hRMghj@l(Hc|}T^@%lsD#l?}`Mz$VDTh7lYs_C?WQ}7>nVYNDQ zW(OiG&zyJ%@puC1INS9uyff(Fmr{h)IZH}rV~QX!$@M8Oz3Kq`e2dg>4CHk}tc11V zDSy3@{5$URE9_@jP-fPAOgb@b9>QntR5#$WcZ54iGa( zHjMqY(X|pit?9$kr&ILh2c}oVy0cM`ww#A=bOtN$i{Dk`)JQot0_AS_y_TU^=)|Z} zq5~n1VQPy^;%UuNB+QOKiyFd3UqF@9=zk-F=tiVsBwzQOj3&;kzsbqUuTP8Cd3a4S zI8gwIZrzjDHi>N$L!;NWhcxqLi7S&Rj_%wsaJIe685jrIMBMRNj)|trS%lfP-h(U| zefGXKbq=51#K$4qFpvFGopm{TQruEojNJ`M)wXq=j4QdQh^ynlvK`jM9VuoTZ-2}V zs#V9b03pywS(3IOUMM%n5^0mIKSYm+vR#j=#dC}mBF&Gn@(k7fLY>}!1pGmf>09-5;5?HCU zBdtR#Ikg#uUa?jg*hzwtI!yIDn}4;icCO@;#i`zOMo!gGzicG)3ELnm?)$N5zhoma zYS{2y$le>&y37JN-iT_*Hh3fi4Vp4~qYL_O)%D-HJ>kL1*ok0Q=K@11ps5G1#L4P7{t&%JI z-nk6GbtjfLWpEOn=I0_sb~U1%Q6o0Yw>DA7JbF~+3^J-r+Z*BCU>eswww`J$imX@a zBmmdJV{7a_7A{%N&{3B)NtDrEwWKd8pO9qa&ftYk%$SMT9Qr0ykC)B1n9^cjhnNjm zx5dOA&_+&uZ$t)|YT>G(C4X*F8b{h{1omtBSw8Cuy5|M^j-Lml<1@ENjqVpoLWpwC zy3NhPJLJS9$(UX!^_y=p#z1FX=Ik#yByQllW4;!^v=$ppkHr+mjE?opnnLchW**S* zOMj#vt*dSQfBMm`RldtsY(Tdk4QLN%v5_95t!bZywHPA56fBt zCzQHD9=02sS-+*;X7c-_BrZvZJ`r-x6;_#pw#_=5r37c1leCUovTS|s)lA91WNtJz z>T9-0UH+3yShsIu&%nQsxV*3Mkh!xPS_VwfwkXSXouHZ@+-y`!A58cbGY&v+a3(WL_|fY0)hvVEa3gyB>NsA`!eK@@|F1C%}l zk8n?%%Mi#xT|XEr1#wv@v93s1#^ma3PF-vaAz%rjcV0QW=YL7*z5%fwO&Pwkc0B*=a3=Di0why*=znbEE$Sy^Ovq9<(-tkdPM9^g(6?m5 z?u5xCft$*0(qbvz=HfkqM7a@bPcctaHSYVQ_at{VprNW=M_aw3tS3{>wN};)oJ1>5 z_#{V(O{MzLQh%N07JFVNx@5J=y^>!sQ4i05=K$y?`^=aJbj)b|uPh#$KQhwkJKMi= zV5@z^(A;rjUrv)SYQ@}t3QX!F(BE4<0LE?yRe*bCw*1)zhMI?o4>0-Vq0w$o>ht4IC?Ig z>n5ktxxl`}KH|t8!t-ZN-g&c3VSJiJdgCd0rL0m>)gbSxpvpA*3Mnn90_F) z5KHYXkT?u~ty)Qs?6wiU z&#&lnfa$PEQ4)cM0I9ntKyFS3$ieU_u>%Bg0Q=&<@2cWfTqO0p7>(ESTH;ds@{#Ha zm*3ugxhVXMpf8itmtTK;`^TRy{|ism<=20|ZJ%#;yv~;lTUzb0B=nD@2{OS7} zm_^^zrl#e6ys3Hl;q$M5Z_m%S@2dIJe6@1Exq!m?H}#?JW>XgMqa0}b-vge@=YM*O zz%Z1nBr4Rb%mO#{q_2S}qA!@2_bEK2*IMH^jilml{uD6nc@-805yYyQ@F1e>JuGcd!=pBW;sc z1?~olw)QXQFw`@adCh`aw2k(}kDQn+^ozoTIdM~pl}96UWKM}a-r6QAd09=yP*X%A z@P-fs>er1dRbn20Nr=W}%@vZ*)Zs88bWelzl9AMD@IQ6fb9tX{LR2RX#tIspLIAYPo17+X-Q07!2nt<2A<_Y0MGJL) z;aYje>atUR9Z?~QW6eqkAz>g%R>Jc?$iqBEV+fB>>2c1HIT>59RLlN3moAONR1Uvu z4$DDHcbfTN0~q-G2_GR~uy1WC9>zt?!{8WGqyuf2`G$stkECTSFi|QPXCFOdSRcF{ zlzC022^5}Xjp0{vb!mVoE!&#ip@s%^9V#86-8eyiN)T{PPY!1NOab2H>L3yQl+SoA z?=rknq8c9-wybM~oY|N*s$?MsR%BaiqphaEO4SC2SJvJG_uL7uFzkpUCyX^wZc)3Q zNs))6#QBj(@*=|-k@1Aj&1_F?4MpYCD@9aYK&9-;MbtP^8FFX^+B9s3@YgtgQ@k|x zkgAP;Dh?J!j*nw2Ebby}^H(?wH0EClEML{!8) zcH@|)NldBec&T_PB3CKkqWTw=Cx#n6E>p}mCox-%tuTb@WHcC(8E_!8fh?K#wRE58 z?ooO|LS^>DjDvYM$n}+SwI$Gq!8_*VOF*xGmY`;zH+p(b@han4{4-g6e%L=i6&3k_Ck{N`xt$lI{V;if@<9N zVIKJX3U42W`)QrqZQjou)2B3k_u;=7Y#2-{I$q~PO3K$rnK0K~HwHt9q1bXrer(AE z8NS+^YwcBc$*r28v5A7cdLn1Av4{n!9qhJ#RRV@tv@rpUoJ3ZT1$6ZDrZ-A|ot;nq zu@i$-R&26Ta!oeSZ5ZLIZ$eG-`cD z(WJVvQ{dwkshRjTZp#t*{Knd%71PP?3XfH_*}p@64^A3zTwo zfxqtyPjg%xJr)4&pF;h~^2HaFt%e(DUO|_r8nF1_B1rq^&u{8FUVbYW_}gFJ)a4y6 zKO@OA#vJ_be=mRE(STBQGy*Rsl58mJgPRrrN0rC16K01id?DL$a%vlY@HhPNl;uh^ zr07Hf^@?--4h-Dxpgg_V-h4J$h)mhzMVkdaP$fqRVJxuKX;jLtiBzV2=J8*{VjMkZ z0*&Igo@88VmW&;`h?(0Sw;v=L?m12gXG~ptS%!zu0X7~KK-CaMzY>Q};xJ(56ck{v z6lwE;Q@4|CPJ>=T6}%FE@)1|rz4f|@Mr(VxcPSYiLWeAaIem3Z?4)GyN*u?w^7r(5 zn3G`q!V@Zq!HoGRn2!jBk<-?YB?6dixDt9L3tCL#hdyC>sd(oPzAg3O`rSp4qDb~G zX-(wQvBPM~r3m;5ts^$dyTa>2*Dp@-Zs>|4>_gtfi3WJYfM{)OSPni zG%Vkom4Bs7k>wrxd)7rM>^k`97GEn)d7#3sY7BGd#63p65Wq`BAwZlBtlz20PxG*_ zc}^00Y4})Ips4MyUNXM_8IEUblv+e2igqj;LPCrnK*i940NmXC3RPW-os!~<8Oz0polP6qe6WA}o)YI@GQ@=XskDqP1RuMZBI zTCX^}eun(W(L^g@9{AT1a?w6L$0_urI_LpO0KXqK1%P#bIr&Jd0XkuSVK?idUzcbF ze|Ba%jj_I+`MPGcVl>6!Q@z8J=J`8Zo#=mX@$9@rdJb_BZCqv5C*z?)lY$*6wj#7R zwsx|C{u|?vrn|Cc%{C-QFsH|Ss=3x(cQ2AB**ks)Lb#x@c=WS7%o_iaMU&SRe!@>* z@SGERVRp@b#iL&N)gE|=o-}mXwm5$KrW&x!u)0jJ`PxpMWDm*)ao3?a-xsiQaYLks z7^i~)jMwpkN)PGfyixUE2mMm(JzP5Gr_J;cZ>P@STY4T3>T*j+uo&F&$4VkTvN=8fxGr7ZKCA(8MPVM7PwA%y{)_( zd@hhf^*YxAc=7(Jevzl@j+Dcgl?(!RU=4zv42jgx&h90W2qF)))}I2-~oHJ9%l0T+{yI1GP~WZ4TTBIA4$o?}!F7D8Rr{y* z&UGf)a|6=$w=aXHvE>1EdF4hL7;xc3YUF{Sa)at|6rJ)EXcB*bH*t!VOwhIVR8G-+ zRv~_pnTOmUgN`vo9hD%5CBA1pnnAddR5nS;=cM97p&X2)jReQwD6};xAk^{bliv3f zWnf^`A=MB>U$--Xi;zPUKRC4bF87JiQ)zUG@|V#L$(B~yK&e+=Px3pUNI!nvKmD5t zOLsh+=p#pl|Ezx%b5NjakMaf33b&$0_Kb&^O3k@B4q^~s9AjV^){3V@J(a`>FJWy> z)D@^BcPA{B(j&J!9PB0VLCM(qh#aUvUouW{;>9E;V?Nxjxa%dFganN*V^GFrO0ZYy zrSkbP_0QSq`*H`I{A92>ce<8ZAd!&v!q7=TUIms`x08Q9uHdQ17j0y6%mLg;ZoswW zZ1N?K>61Y#fVpF}%G9Q6P6VkM+~|5Yxj6$J#`SI%cOz?Df{MYp=F;CaU7m&1WuDU9 zWeroGUa9+I>(_e5F_ZKENW))DxFhX)C6!gEm^{dkW)gtWX(TM+bl=c*e$%yb*@Pz- z#N#o{m)H@UF**uL7l1n(er^5&8TglQlb<;pAvQHM3T19&Z(?c+3NbezFd%PYY6?6& zFHB`_XLM*FF*uj+9RVPJG&nRiI6giKb98cLVQmU{ob9~{d{ouhIDF2%clMdgoqeCV zvnG?7WU@>)5|SH2*ddWULWBSTf>PMRj?h(Z@Q0SK>bX>~Fe>ni;Q#5$&c$sX z?VI)@_}?J@Rc)7lcD*hCIg8Mb7lL0{-L|Uz(%QLp@NYsW@kLi>ch5`|Ng>qNMdG{G zws(E$_zegC?FgxV4Ph9*+~X(PXw*ZFYy#<@R`efMA{2^&qSA?OVQA`!}JT(5LsK z-ROCL^alDJwrT?aRk;T!(+p#F#jXIqg6yRhk~3-k5$N{SFGETu3__ zjfFN(MRTCm8_?D05PA{beI5N4eT4o2y~JXFH8w(D2sntR;~97z)Z%gcB_3d`0KZCR zCDX?oV`J>=+#K$Sfqf}UsxS3rDxH2b-JAY(`ZWsQaww+(Akd1sP&b9xekkV!bP|1v zzJfC3*avl;fG0zHZ-@Mk~Sl+2aPV@#SI&2DF3;T+uUscETIsoPU~)5-Kyz%3Sk z$&dpj;X4YjX%=dMynhV6c`tedAhQ>+=WX-_wqq|Y!V~dqJQpv-%W)^}!fWv5_zLLN zhw(mq2)~X0h`(SsM$TBEpJL2n=EuwqW*_rB^EPvmMeGcAF1v=koZZ3hW8Yvu;dn02 z6>(F!h1>@2G8vMwa;x&S?`+?#8dx-caOc3UQ-!Iqsf$xvQ@={Ro%$fHNgqysh~%gU z>ezyoK^=bt?Y|cN6x{>(@d(uMFX&_RSAhB7p)Xjh!cM5MkHW11>OB=|J_onprSM&j zS3v)7!jIxU{4_p-e}!Mcuj1e1_wknuhT0dxw;D#lEM_UR;ZEjJrkD8xe7|CU{>etz zI9tM&vbF3&Xw7E!I%v<&*!S5FIfk=v#oP>TBlnVwl`WCoCfg}{UiPx=GdVAxm!T0e zhtnVSHRe}bExQu!Ld^`ze#ZQsslh*DzQqqR9{ejP&BHdc%}fJRjTn3guzVFVD|X8L zazA56yka5AgSmq#VCQfVR*QOnV7wvbLgrd#A$kBmjlN|j0H$5SzRv7o7O*?Ho4H#2 z4%!H1B1VV5M)jy3*8=R`L~8&-1?+C_w%i<|LXY%#hLuYumELMxbCP=M)T0)Wr6(0}6}V+)MjZvi$TW+~!W9kUo6V_E>} zZ(t)+h_3`ZU4^#dEhvr$@DcPHa}z4Z?d-GPIR^3={M{+Mhn;}-;BUECxK|hsa{3kY zW)V;i5isOlpb@iRWct~E2w+Vmk}+|>|5l)YQ;>oA3SYsjL>>5c_EUU6Q;(WZJKN2S z!?&fr;_BH_=&^%9O&a7?N+he1dAKA%{9{xL*tiUM{&McP?8n5-zR8|Rx1{~41+tvf z`{*+0oe4nQw!&DNfZoGad=b8YOEZ(Xbb2;=l-bR_pSEEw_M_i_rD24n_Tw5HN(*>R zT7xeD48KVJ@W371R_+>Z9d`xrrf-32UW;xwU|vt1Fz4~X*FtLCU%SD0{CKM2Vunhv*h6OC_6(DHb$TYpeLYhn%nNoz5OV3IvN-4#` zl$aN4P)$KxXbcLyua6B1{rJKQ=7RgivB4Ih_Y`$crS9$2tpm5;4=)If_T^)RUc69f z>>YQ>@-2;j3&%osdo=2XU_-mQAddE^HQ>~MtJfCn+JkMim^v6+nC)^z&%Z!kBhRa7o+zxxH*#3n{}8gL21z_S!BxX&>@J=EjD(n}^4`*e#9r z4uN>LY}qXI?z&*^@K`@FTUsCwc$W!}TexK$6nz79;^Y|ul*U}sGPf6B14Rm?C8SkS z``UwzBxKuD>~)BOV`NN0d=GDsI(AQvKI72phCA8^B%2A>uTZ}lz#h;;O-8WwKhN0D&yey=Rd2Zb$PA;5s(sn17-v}Ho% zVg4&boCHq>Q4Qj=?%r6eH$R`Ch@t^t3U#c1rT(&l_$B>JN3e?*z=G~W&Cs`PEu#|9 z{eC|I@Ya42Edo#PrVHjuegQ3V^&v44Yw2Ycl9(ge7|SdYvnd-h`1-;iVDdgXv#|6k zBZEIZZ#6Y8AJvPkKM>t6#ZR6QoP5EBbA`q&3p0H?dFBY86kjdqjNQj0@);;9)*^-ORatAlo!7 z9MiHV1aWf@2ndNZlp&!ckaz^*L6b2cdLKxRB)#i^2(wli%h3SpU)+xo4O3>rFk0Nt zNMXEGP=ppFg6z7upOa$5Y-$d|lv3CxDRo{ZwGv`@5_1s59*}5Ar1T+FqhaQM+~N2@ z2U$x2BqKbSTP6ds3MtUIJ#s}q*6xE0WE^p@NG+Fv<4KlfoGJwgdlDmuvgvYrY#M*2 zX6iuAH2&+Fsr*0n7jzC*4hSEA=_Sk@* z6Sc-)Sf5DjwH{7zYQCSjPt@ptVg{X#H+a-ef%tQH5B9|T9*^MnIO8QW2qkbLTAE0d zlzQSdV@MFMuk+P0b+LxJx-ku&8Wl^klE)aioMk*Jhl%>FQKQvrGI|`bFwIAwhx1~g zyu5J86RR#GK@F%9SH_Z+m1Rj!bui#X7^@s5k$5~NM4aJBEao&toDQacrn*`U+_Tgh zN_s;LqT83er(t&kv#sHH1JlsY91>lPMz7y*@D>5owli!K^9IA{nFUNI!!l2UST`0; z!fzvcj6c)by5>|3e+nFHVl}7uQ_zJxeCxnXf~jdRRwbZQ`GL{kuw(wK)-%te^ZCy2 z`JuPY&ZD>pw_+H}2T&q^eMD60&2?50j6{F>$Wu8c@PHA6PYd=NN=rbhkO%#NNLjMH zwA2vHIHcepAi~)pXT8B*J@DoluhZ#Gy-&;qNuSf{`zLX(VG6puPKWQGBqX^Yo5tbm zV#-rqo5^Um8%;L$Gn>g|BTs4y-j{WMXU)a~&fT0o&Q$}>#nCH&V(42J*14Q6=05d) z^>gYs)hE@mOLDHwxi#m3oEJ53YveX1RvcuWKpb8xT9h28PzEq>R#^;0(_@x7w9$Ti zuW0aAheC>KjFDXHcWBJl!N_=6G{@sgl@Rg2h}^teaCf;6yJhZv=EJ=O`Hus}1yEA-0~ zqQ;T%I2>+Iz-rM`zwzZ9qfws|u!McYUmr+#0|B2m5XQP_LSJohS6i)?oP=Maj{5Nq zPJw*YVR^)VuUBHFvowx!1h3xHr@zKr)ug> zo|0fdM_ARrGlU3G(Bxk2jLE$rprd_x+MY2+-=M+CNH8F`P=Mn6$&amnyW;oVsexzya>HvBc%5`m(6Pea z{pRg=96NT$&yKN+?wB{f=jht~sq|ARIYD}Y{#-QyT&m-yqi<}#>5Zc>1`fa&FmsOs z@%cc^A6+`d)l}MAy54%Nb+hw&*9}#7j8RV%#?>=7`F`npxc;FrZ`nSwePvU)`qN+c zne63%{pml83u0n)bhXp2mzfc+$SElbvV}>#4zUJ}BT`e7G=v*8f=V@JmS!tgxHUH! z_BXH`ldJTraw5uw72yTmPA}tijx&eF;z%$e)^|oXMz=-piSCZdqK?M94`Ls(XQ4Iq z>qri2;8OlKA|FETHhe&cx0_@3i^ z*MHeR^!>}KbGY-}Nv6^}$u-3{-*us{)3wrfrRxUQ9qv24Ps#M_tOwoCv(Fn}alhh~ zD_<};1p#=O!Q;0nIKM%oo$0LJg>V;tU`0RvP_zYvYFxd`j62PT%}340&79fc&wp%a z@w3H?#)8Vog@1~Ap{mZ|_>N$?LcI-Ij#CPBOQf;Z;VKDH2C3CRr zm8Y;g_1{-*ed4hjiGKc3kSQ)?*8_R9h~_JDa9q`-USZr|yxw@5{BD!N9iXJe_j1tZ z3j_mhmxa)GI~GNi*>1O(11=&GJRvsaO{4NaAg0MN(;2c%p@Rv!Ifqw=!qq4ySJ&}= zPGPBb1*+X{ms+nltzZ<+0%R6{Li%8Ha8q!5a98kjP#$!|2YzZFBsXHhw$HlRfO|@zPgi`?4o10 z%xZU<912s=Irq_2l8y(X!NxpI$PPfHnye78i%<|>CDuHOA2mK^Vg`k;~^(aWJ4_u**V%>g;;E&(7GVWpKc3 zGB6kmMuXW1PY`5(li6%CnN3EF)CArDb{Ai*W~!+(5pB74pQ|s<<>GS8%yKVMEOPfKdfc*dMTxsn(deF~kQFH_MpHl? zAB9Jas~t6JbZwwQk_o(m5jR6;o-m#^a>&RVMI&n*H$qI{QGgFHfpHmGp}a^6Eagj? z(s7B>(xOD5e4I!E+mAQkhH-Tb4PsrOAR+fg3JdZ)ZaG$eg5BI_nAWBo4T@d~*S;*V^KFp=+SQ9`Jz6?N7+RC~DgXf0do7*9k! zGV*bZjD2v#Y6h)A0iTkBnp0U82EyO}LQ;q6!`ZP}3LysyVWkw}Y)U~$1&EeHUexYT zYdM)F9OZI;(g!xJ0d6%}DBmS?$!4_}1}6tei8zh$D~1J} zMXf%$cxC+}e`WWmdFA8BQ(1OeY2niPan#vVTo50B-9UpsAcmBJT{Nq^aoo7Z>M0iv z>?eea`I$Jgv3=l8>b|LAjwiZA@{rksgzCVdw*!Zs4OpIs%JCxcf=3kh`5q}`BZ{!E zn(HyGcV6P!WWL6Ev-wu%ql#VT`5XmS{E;2V@opGIZyIMQeK8kL30%0bZpl4>BS8Im4CUPxj}AE6l3id+3< z@(hRpOUfiHpaB&bIU_o+92_Q;8MMqSsmY15)Tf{M4Pa~9v#d{1A0 z7hz&ARR3h+Q_H5UU%Tqi;thA+(RtaEdj7h`rB!OX(V*5l^Y30faEw46|I)xWRZpL? ze9i(w;SK|Z+rfQ|%9#EGC`#~IR~IFCZPrp-Y%*%g$ra`mLCsPV4w}kMOw8OA{X_VF56RENpC`W!f16Z}3Xe*z2(2i4)cj~r9x4k4 z5u>E?iY%d$$&{)9LmAsX!q$AGP%U3aY?0UK>xMD9f>KI9i;Bi2i;7Bj5br`^2+?&VHzgS% zS(IFuWRv~Oy`rbWgpd&n>MqqL6~n6pRS`ILsd`m%m81L+z8h_lm?o7$>GX{!bac(B zGel!T8K|Twk*FzQq?i!Y&0JxBjFKQz`q*A?O`WM9YIQM#NzElmE-Nv_D@YihET+LS)A*BMHhfkN-q8$eva*umKuL9_93R9dkboD9CJ%`G z(Y!p63e_sL;5_cY4uZZQ!TN>b?M1tam?B5j__iT&pIAeze&7tC=KowLe|J8{{=K3< z@N=`vEmJC#awQ{o%Yc2mlpeyvaV#IW_Ymvu^O@n*KkspwC1$;bD3jK-wzf)h1TsB7 zhu4ne$>%67=FI(R%fhD0d6c{UgnHvKkhw$Sgiy#Y1ah%eZx;O3C|7N>SNptPy;4=p>*Z!(moP6zHY!^!mI#cH3U5GqPEnoR00pX~=N_py`awmk1U*rWe?R zwHOD-OP2S;f5<)Ie>>s-YxuKVMaYtC9V<)?PtBbrw1zLtU7=s$=m=l$&|3S`e;2z= z<`&az%f;cPxnDbFa;Jm0IHP>j7Z2+wDPKW~k@QDvS}QAwZWZWdR)ZuTu0a~1#>i?h*cS+y z!!{Glf91PxIrQAyk1Tq1x`j8`+V6e&)zr6G^Xjiyotq4dXMIkaYy76qes=G%3C(7k zAvWe>%)az0f7TN1>P^twHPG7{WWy80N~4vtnr&A075s|kE#{B1KPleQ$S+oO7?^gZ zo$F9`s8{G#8rn@uZAy!u)%#T-7AmxUL_`F=qmJ4+HfoDHOIa_%JSsv95sUhn&7$21 zav&s=i;$)hM8Tu-6Y|q?nY!kCWD)Y058uV zbNb9a-kf8$5gn*0{lQ+H*Wex0f}&Qc2L+xiYOH47Rc9uKfgqDf@2%6A!BDEfP!I#c zKNme9Q7OzCBSeB>H5+WTW@4DkdNWD!yl4c6S`EUGk{ArD_toH-#L9<0Etn`=v)ZO% z>Q$~Le|74)=Tl!``zlJ34GL>?c8vY|-E_F3Egbodyq>?xS*@yq?SdC=2W*x_Bq;-V+n9H5-j{8u| z6_wb+RafCwd_E6(%EpZnrDxDH(6?5Z0T9!THj4QHM~OppOm{4H^f-Rv zP?&W5T(dc#lWSFTWwLUP{}u>{=g3rs)0_(r&S|-2rq#58IUt`I8G*5n#|bj z@LaHQ7*igQcyis>r$%(u@ScJmu>{LZe`oc|;0hMA{fdov5>!! zdMTwOno2-381AR&GRevBTPJBfYdPc)aseT5rqA@{D@*#*-}HHvKzq*g#g#?GifT(q zV1YH!pZ=_J*hiZ0;j+OgzgdyPe`JLZ8=<#lgw-)+Nn>e=Y1j~Euabn*kiBdER4Qit zZPNN&>*lu}|MuI*-`f1@jW_=0H#gq+D)aK4l&c+>IVOHlp3MZLddkH7`tJ^4ynjDN zsmZsz_WF)nUVj}%`9`3?Q@F=bB|0u%T42TrREL@nE3;axvu*9>CDx9@e=c*kwadQG zrmk?87frHGDxYVYSGK~oyzCnH9SL=5v0iWmDF3qBDoTW)m+&y7Cb%zV3|DBja^7&P zg5{VPT@vv}BF<5+h`!iYoG7j<=87Fvn>P$Wg90PF`XMcT@R@ZFsyiNHULy$d0px~h zg29Xqg3RDaf4<*pwYlxqf58@!Z=Htz8a6YO_{FE$t%&c#T`3j~j z7&mvv)2Y8+w7_CD*b?(wgRTjW-ZbaYD=@ni`u`A60y9uqJ8Bh^e~T>Lma8p*#M-%p zssbIIOK7OkZn@Q92-p#jP6Uz^&o}Xhd6suLhL3hK(0^c*pLc}cG;)0Zos4hp<8N1G z$F?-JA+piY^l>%?V|y(An|PhM&Rx$Kr=S+pG4?Uxv5H?*zGix5%%4qfTi>qzB7p6$`#d@RbE+redYDl z_nZ67>YrBc_c0eJW6@xwSR7rGblUYf3X8T1B}o9K>|`~9d|F{{eor_^ZF$(UmDQv5? z_)xX0%7-fiFpOisYpt{Ukj-8@dX%RIW-HF>no3`}51H!?RP{)tY{|e9C#M2&c70=C zwK=JFKb`&_xeclD z)5yR)ij-hu=20m3Kmyp5!-$$M2Q>2onmH2KPO)s@C_`4pf#^4CB>hJy$e{dKyR%U!ku5}n3CftjAP2kW%OXdP) zNpLr#GHeu`3w)jOjdGY;;w$YLGyQEyjs-Jey%WDznoTGnqWj zWFaBdOmVEN%u|?Kfb#i#CO;O5cnW;|c%@k5WN<_i3`Lw}C=&J}q;WDDWgwz=<1gK5 ze>dZD4P3b_5-CIld;wF?k84G1I1JtCRZTY~j9(g08(E{Hd{SqY zs1c5wQ7!O92L=xeQeeh(N=gMBLgHY267g@)ks*838;fNl2E&1M5OSD7Q^VPUpx=;L zCif4e$%Nop%(ZlCz^zM;)JspBN%_0;6yDgv^< z1o-b9p#N@^heLaGCOWm}6K0+_fduHbQthQ9VR1ODAOU(6EY=7SZL6jqFWw*Vs{}s= zmzdAG5n>f8jmNK-AD1(7XMS)d(h3$cnJDVbo#x|a*6fH5-y9+-yH94fht$>8e>nJ) zb|7?)llGI+Lhbpth>Q?}#F;BpxJXkJ8lN{iZ%N+6f%`*G;RBkdy-(&oFMCyajC)^s zQudkBVC9N&iEOl{0XJzTdS~NVvQ|Z_W(i&@Td7&cT&}*{yTNz8_n_~Y!2Ylm_oq+y zY4|)4o%VPsE2o=)TCf2?f}~9)f0nZqd?R<0-2P5KmZ$!)|NR}qRC?^s-@Ezd_sGM2 zJn-g=sjpy4bo#~nhbi%1zUNPWx(6N@L)%dUHyWsc56u&c9rJuXgp}q7`FY%fO8q>Q zO6h(d%~RUu84UJ$Jg>y_6bj}0MOv(N2+HQG>@k$cu#*~TUmLLoP9mzje?2*HQc~l> zAkW~JVURaxQ8P3BxQQwhrKQOp;>JD1&G=IDY1n-P!|*RDW?e+wg!;9^_+P;;LJiE# z;;lY|&&U`n4RZ{Pi_D6BfrWULvD4ofT-fky{A>QV#^3s13%*wJT=KaFy%MC6+XE~r z!NvxIu_4F@g1kRjT7vz_f07_?*)uw7=wZB@Z zb{1EcREMgA)%jzp8>-8ylhxH?LqlCyDr;L#~8%rR5Z}84sBTeVOGq*+WNo2M>$vlp+e`>W;oiDGJe{>Kl zr0q1rSoz68@=7q>G4-S!khm52xC0)t(U8QRbe^*FCrKa)$kbDyiGHp5CY>)k{#5ai3@eLfB^^CG;- ztKl`?3-9n=VA*_NfAB5(njSuqQf}s14pn13_me&?3@o8e-tNhiS;bK`v_et3}qaM zjMzkpv$}gJ0EWdbd{xA(cluI)Cj#u&)Pd9?I!01odYyVx1YebUBxHj456No&670gB zB}BOWki>@Y3#n}it1hFUuS&fn$-6qM0f41Ny@^$j<_!g+qeM~Rj%c$>{KcLtr+pl<0@vh>yQ{hw^Y?AKY>@zdw z`F69}W)B$7*4^`JNq4Wqy7?-dPOS>?lI~t}mfAj8mXOr;l~{{esqKmS}&Aw8zbm*;06pV*ac(^K%sT)0=n9TXg;WiwGv9 zz9i^!(benb*C$pEXUCL309W{S&iKY{O#}ZJ(%8?ttUy>l@cCI&BFwa=zGHU-Hn>o; zm>bpRfAfsYmSa*It#Y}Hw^>b=T9a(5N@d!W6GDgwcJFZi<{*{=sTUIm0M3~l^M{>0}Mu%ecMHj7BIE*>k@Ixa0dpBnA^q|IW zP-}ma>c8*)RR1elwZWkY;7KWjaY5=kW@Dzde^C{qayl4?lOr`&8Ra%BFOx%U)oQ3T z)K?Eo4Y<UF<=lS z&q(zivGBwCu{_fVW@fmusks7-3+7{G>O+geU>D@Eyq8j%pHQc)zM^2__fHU2He_LD>9#I-ID^%HhK?O1X)>Jua8hK!@@-gJ~ zv5cIvC3}uHi(`!s27ZISKwoG%C+D%m3g*Px8JQ-Bv%7N4_AT~X@SVy#H9KWA_8F9Ue|aGi zmY)|Os*`Z~v%cJbpG*~vu{=>39373yJ)=#s#AvxL*pL4p^3kZ3kBnyj;v8LPZL%`f zetb&QlnSHyzr;tY93{Uvh~a+{qZ zE>y%tR3wXt*k|yF*ku8uK$#Fxe=!FJ;T%|+76YlsPIz(F0X&6FeJKNsr9asV+zlG? z$zEFywZ$T~~VQROIen1ktW5TE{gpC_k)ypf;& zyBLhh+&R9SfN_=5>o%f7f1E4R1%f&L(Z={_nNcRwIY*;@=Cvn_L!)yXMP~t(|2n%W zB25zLN|L1hxR5F2BX)<)VRcv>W`|7fc6nT0m(Rt?b0c|?Xe2+v$u(NFR;5*H6dan) z4MZR?tX_a_pg;YsD1sx`4B}3%86=%tBIM+n0qQ|wT0r&0h&~%}2=%7APP6 z<;jWp%kvo}BS{=Y;f`f81_zYV@-pvAWR{oJ)F{!*c;2=l$ABsWL6{Tc9x8|aq1-e&iueR1gdPG+-uE9 z1bcIoMkP_558oY<4`fX+=MoH$q(9ypRE8Ww>u-aL`gC8FBvqE!vLeCop~^ARR;io+ zvi5fyHvF#pfBoC2zw6ywZ+rLM+ira~s}6ne5Jrp`C!_NTuP`E|~pIGJ2!zH*22Rv)9% z)3aLI7_Zgpd5?;K1pKmc3J;IcZY2Ix*qI>mhK>4AU5E*V>?)-) z8rHk9+nFf9UIXta{CY^Ko;j~ZC;2sG?t{n}(T7Bs6NE&S9t@41oNa2!tnvT5=R$BR znUj;%-oE>sMy>9cA;7LXecVJlEd4qNwY zG!BP*h!sm)(kU;3CX8Unrt`7qA!C>Tux*fMV;oBzV}6GVkqVWH_UF*=(Ft@Kap*}7 z|2Ok1^n1O+r(hIMB8or?lNgz!QTe?t6&IzF-p`;IH(2(q|v;N6m-Lrt#W zLH7W5^#FDy(ci?27Rs<*1?U2};&*w=0s)V!v_N0v0~tS-OnM3!CEyMqj?n=)WAHd4 zaTMjFOf(h_d*Xp$#F0edPy`_dgCi;jqf#cr1>p$UUD+IG<3uYL4~2pVNBBSlxda!} zf9%@jdc$?Xb=oC!O*aTUM*JpzJAazzct_dS2e2};R7*CjwXOjPOVZk%k_J0?hctF) z>*&yMU0v(=&@|N#S z;dtb&f-fTmeMD8<=o{z1G=7b5OPm}%dNlsB@QVMVn9h3$$e0T0zn2=6j-UqBL)5@0 zOjp4sb97^@w_d$m0aSoZ%gnlU5E}K&3!G z*?N+$j{xAm&K&7HMdmXJV1R_IoRUs;X11UFn@2wY`jx}xgnd4(Kn|bz>EW|7tB)Mi zN(#k8i>oNJIgo5-Ip0(;Ly~~|e`ZHJ6Uha^JWHHr?W7z0b`7IV-`)MrHFsUO=|++G zy6$?kGxgPntM^{;@P^c@j5;-GB#CR z1RNrPrw|$S>pp!|lxzs8)K@)$9&|ksW~ZYC&LwEIbA`VfUE#c>@M?6UfAiYHJ0f?* ze^&T#ztvq zZ|QvJYz@+erO;W6=#RZXxjW_YsV=&uW9tV`rv@j~>)a09v9=++NH{Jlc`~jn2{|h9l0-O}DHoN(t=>E$2e@#f%;Mi>1WM+LX zUua5*%?~ZqEitVMy=MDw;~CfKkTRYhU{RjhoTD`Rjq&_ky;{bFQ9(f{95RQ)Kzu{t zV94z3z41s+=f4)=HD@E5-qo9Da+&}ZkF}Ts9rYWy8kI8cszVtLeG?YsYu zjnl!Oa@zS*t*3~~j>!7JDSO??x~jo#WaQyCXBMrVR$(81`zT=l(js>l^j2ldCpMkY;raAakHe_5eR84FqBl6?gsX;uqTdG;J&!eIHY?gK zcE)Ox$vMm^^PFaRM9?y-5Mzl-WQ#~gHB?bU+Tao|>U4IxI^CU~>#Wz>j>wLfKe4J7 z@(T?MjSEfOe^CbWR^BFBMH^>lTsE)6=kex6ZRJe4wb(X}8E37xwcvTyxwh+U57}N} zUbenxGw0b=26_+-Oc^~0W*jq%qX)sjp`*w84SX|**kyEy$fGk`{JA958xlfYAqIu` zP;=-==#7vpv^_5r%FXlm^N?0flc@A6pNi3|4y%rd9*T4aFQfu z&jJtjlsP;er^6#S>fz#6r;=E)Xp~Gd^O*<_Ist zNY*6VCOazQWY0is6gA?RnNw`!Flg(VGhdL!e*%6WHn4_F@9ZBr_<=P}`zbovThn@y zETQpV44re6jyw_F>ZyTK=KmNlbP8^AZv=>GJ`mHV zm`#k+=CnF9403X>b8!B|0A*#Aw*K_#Jyt%;K-cwSzYm1lYuPzhnQL^#g+3ni^n7 z0VOR)x9@+;_*Q^X^r!#)l()Ce1~P%CIw6oB41+o$gT=S=5K1>D5$W$QU_LY+*eUSdki1crA zSj&w@k0WUGx||^7D;@o~^GTjJcwwA@Qxtf2!oARa)XnMLb?zqj0(Te4f4kks-AebT z;paN(BxQ}%OOk$i;B4{y9AW(&e+KLyT-wY!nCy_YKTTYV14z|lb* zV~~NI+Jt_~M}_!FF;;E>dDK-KuP7LAoameqA6L+9Y_=|NE{Hc5e3P$_f1&*Rcp+w( z0_ma_t8Sa_9vyRBhjr1+jV%UaFiJ00i9|~CBavvnCzv0X?qbQ2%S$ttu`mVFbu2kn z>+H;ZEJj1XO@b!)kdFvh>>EDpi@JP1x62c7Ii2zRe1Mu6xWCb0@G=Evs6{9k1Yr$f zZ%kk4D`W}_RgQvqM7p2Fe|ZqkMRAz9ne!tqvgD5pSnu-DIUDB!VeQExCSoXv7!TrF zWJn*`YfzIN#YaS*Y!5JCWN0#cX-FG51Ek%XXk0nCN zw=kAcDyMsl`9;z_e?JAdAPRC|5%T04)Y9!hQMwT5F+_vJKR!|#ARF^N(IS}71tZa@ zJz`Wxj0VON=2QW{7voN#4~!l)A+kN#S>kYdl}fTHwo|oHwGCLlqj=QC!>?E`gI!NtHs0&Z(E{tB zM_Ql#@r>)2?UZ;49oMx3WA>b0^wOowvvk5vx8ID}`rMSCFQy^c(Y_oYwVZn#sD%T6 zC#GM~zu;huA6Y-L|C9fx@r?D1L;jNW5BwjDZ(HB7f69MqQ~-}KTC7$(_mc5n`masw z-Ktx)_c0I4e;!udr~QrmH%jHz%nh;|m7BEJo36Lq!t9hOE94c*QdNz16klpAwT`kY z^O=}7!H11uYryMfBJG$g{8_qO+SfmG%7W6wbCVb zDWm#aQ?5lJw>UspHR*L6QrG|o3}H2!6DF&T0xCkgkPNLihZT;!&W6h}OHl)@lKP7> zI4X>^^ z*MD*StS_F~^DS1~|I8Q6xQ9}I*@ath4%Xwjf4fp2Jp4KymwM&>&r9@3QI+NhIY?9# zVI#2&bqVNt1SIdLgfU}^(U?fde?j~Z3NV+6HnF*QVR2V6i;BhK=3@3l@#$iw zxW3{L@Gt?@;d0S1)-M2~Gaw$HhN+IDVbVb=hsTE>jD_jw8Rvi}Xpr(@~l)qb{EfBpl+ zaKxe6b1o}5|Do6b3Bzk?!5}89OT#r>c=ow^t%W3H zCo^_Fv8g;3NR=$Rt}e8&gM|F@C;K+z-=wyjd+XT1w~TD?2C~KZSN7DOrs}SaqxnQ9 zuVx-(f5rVCDM8{|xF<)}&s;02f3aGnf{~+sM|F_74{4ZZMXg{sY&dE-4w8q=a1dJ& z!#ulJiQfV2xo>}wvQx<@pJwg=^!pobmQ?0s_5N$Jo{Z|GgV97L@Uzl+wIK(THBK%# z9D*D#qfWb1ka53HIU_z_1b-yiz#)DPqxcdS#U2oyw4(o~>gzSv8G*Q)e{ZmC^KBEh z_;1YJ65W=s)#Tw^vdsn{BdYGm-S1}_lr|3?NX{s7Iz7l^lg3(^ESec;hC;p1Yqfg3 zHf7AKQZZg76N>2dSg#lKjNVxo_j<7aOqxNCfInu zNg-ct$%}Jaz@aPJ@jBDetyO7!ltNa?KO*koT{Om8yBxQ z(CP?{3Egl(>gY@fedbm!STVJu=c>P7IELWVmS0_T`?S$5&GCtVe;ZI4?kaL;9n4Ow zN(QUk4ma}>gEd(0a$&2B(->%{=R}P;IR>Lg6OKs)E)VBL<9T_pxF@XUXc9#!t59&P zN9{CIzbyvXYBqVCAusU<{G}eh-|O*&Tnsj1ucT|kE@X;D!eMVD6asKw_N2=kfw>b% z>X!ljYSe11^tin+e|d_FE`;J@xJ(~!iZ6(F#<#_f$K`QnA;Wr&E|T12Twv-nZ8M!V zae5OrISPi!Tx-bA=8V)81F7qy?7`{bq8HVbW$2V1D}g3qv%w7p@1i>sTBxA?@9EKh z@Y+L4VEn=Jl9*C_HnHKDl>@g*)Z|rq^qEqV_nDRSq9jR3e;d!)z8ig(H2pFA!k~CY zZU6;nF9&Fs;JXO%`*^R-N7mOr7R!L=KN-NPpsK_Xbd+=iJ4)mi8ZWXg@LcHUl>Q}w zTLSk7xPSP+2?9>5f)>9c$YmL2S%y(ccWLSdFVge}V#N@$mnR2BnCC=INnv4ei6>Tq zvI{I_nStvte-Zj*C20mHYxyCPrZhJe%*_o2J^8^v0P_I@VjZfIps6?#3q@l2LL}em zHyY`D45r(`NOK7;>1Ph_hsH(>FoirU>irR9Y%*>m+uTcsOW{*bosqWVoyttKC1zDa z@58Lg9yJ-pT1H;La1Owg=VM9Vv&2Hw=$RW#!epHtf7uQ8>_HOreG4h)@S3v&m>q*t ze24G7)V31h`~xx zN~N-5hl;I4RjP4#qH3P%V$~)1Qq}d!>s7bmJ5=}MhgDCaC-F=871i7LBmAlAYy6GM zrcq%{e?NYCKdY%l^Hlw~4~FzSFuNgz+E!yvLFc$9lpriCdQr^ zm~U_y9BSrXjXB5QkcIv=H|)@BEwYDfIS#!BVSG#KJGP2yvfdI+`2l=c;5UKK0_-CJ z?2d;Ek|4bv6LY}f34q}vhK-!T02^fCKNH(Zf7D%maCpfJ93kFR@uin}r&^~XK`~FP zBOBkS?khG=Bw8@hBk8^}3NPoN3QX_xIT`2E%#)}TAHuI@7^$T7lB{!8Utpi26ey$q zYRwAOfD~(SB&+ut+~X%{#iXTvd3Ft!URy(zV$AKO2HTioL8Wsznv>%jN?kUaD>>%U ze;E#kR-ILY#cT2M%1Ion(ix5R(y>cYw^KEk$xPK?soO95k=Br-{9J)?C}Q>?79uZT-vv z>pq(jF+8);y3M-V`mFW1HDy)qVtSdQ46Edn7CUFRq%QoV|7_$Ow1ayZFX$Vd(3*s`l9Sb^ZV9M@F%u!m~XA$*bL_`HW;#t4F+kk z;R!K^A3TgAtu#C##=Alog@lk8Vowv-uFxAHwkxzL#E_*2CKS4Zt~&%W>kdzd(FIiX zz>?L6CKdao3h$;%5DsypZse?%a&1o4Du@OTn*{UIPY?6=bOhx8<|B)k3~46Z*2 z!`2^!Ve1blXx_PAl)U zWBacim>f-ypdAjesLUbe#gYy&oC}7>3x>l9hTf2LRErBi<~W2OqzeyQe?^;h79$o{ zB^i>AA?X;AHzb*U{Gh0l2@5UQ@@q4Q{Rg!vRk{61hHsnBUc%Ryk z8L>s9)s~crma>hqZ8C=apxXeCP(@EM>B6|LEYirATt zNF=h?)nu;{qSpxlJvd1mfB)m9igPXtf>E~S+^};4hpks+5A*KNQ#zC!KXjN8uk|Zf zHkUn%%M4$N$cC}kEkDq|{E6rw!@2m@y-WJHtsvX}KO$@=4>Rt8lX%$8eoGniz~{`J z!*BNkgF6C}NhMP$)_vgl$jgjFlPZs(H_)5-UH9)iU!$+_*B*5kf8}~|y^+fC?m6y< zya&9;&@p_>^BMlcqnqo+SZgrC;DR~Be&W#5iMQzy0Ll))$u4fq~`# z6Np9vfpEm*OVGQyH6^9xB_(C$o`gn5{mN2Ksg!XZ4Y|~cWM;?Gy;f0rt(C`Bm?z1@ zv1SPZjZ-{88arPw-O<*$7_ZlM7DQmykVFWdA)8=1QY)x z5Qi%(7}gSTF@=fT@<<|~(P%kSM5~PCR#tkvUQb22H&;YQu`hQ)ZfEZ9+{3xDTroGA z%oUAg`rK`~Z{(iHJq=;~%wI%{$A=eSW*a_=8H_o%n`0Obe`Zh{M5{?)IWy;NG95J? zH+^a1OpdDOG6w+Zo&vh~OpfZ%U+Wt10+O$>^B*~h8k&ZZeMXYZL>4P*>WFrLdIoRG z%HA}o%v^O9v!7pux#oW*OOw|qCarQ1e=h|eCrRD}rFRC5E{OxSC)*CJd{p6~PUtlw1=e<@Ey%9oavaVCE_hjsPi4pA_h zO-z26Q+byb78bw=LC)ITh+gS~iN0g>=FYRO0igP5K6`C=9aZd{9J&smDzlq_UdT6M zYMg!?heSF-IGa&leomf!wWXQ{?n%GzqsMm8x+m_&m?rR|rSJ1m0 zY$lym0T5k;>bWXkoJ}5nW_cc_f9I6d^9=PW{0KdxOwKB2&MEiF55Smz zR8%J*PlpjTT?LQ`4nL@T^3=a^P}zhttDhKWmPZ${H_&s+-+j%hGUt@>zvwyTRCB61 zdrtY+=?^%Vd!b5f*t@b4Bck0c1-fS#6`B~D6mFjGAakM-*y1XCjM73o!>vmVM&QXbjSclxqRZnRgRi!ey z?(>6q)8GL^a=dWr$&=Igj{xtc@H>bx!UF4|s`G^=cU-so9}e=_qn>E3Df9e`F07fFiCr(Bwt zj58wyow{GGHIEL+$dDb#?2$QutN@KTKx2abvYcykug$wQ`as?T(L>sO`5@}6t!3KE zd@dTy_r}b*-n^jJtRZ{s9iJIbe_8)!9I$eE%DjA|eax&FHI>Xk=3ykm?>|MS>(44d z20DpUNCSYvc-}shO09MF^*Ds*Aq`?6T=^Hn=y1J`=|lw}u+2kWNUvd5p*X%N zd$xi9`V2YRa7Nl4Oo$FZ^Ayg|6-09*PyEQCCEPlI)OhJQ)e{DXqh>amI z29K#wb((Z?8Lx@8RAe`)TKr|9idrTMxy(+d?0wa81yT$!*U$~-$1wW)nwh>QE?@nK zL$1>D23yC0w!1%w%)2D@?!lRULO#}A@zEEZ%bW66K5%8LU7@z|MfY9w-j-2q-94%I ze?g>~4*_DIf;8hp?}!Bnf38p#Bo|Vf1xv!6@Yc#owMFJ4OP#yUJ5AQ070se$vb)LC zI6MKeT%f3csBc#e{>Ekngdc`3-p^C z<)RY2RD5Q4{-d!M)h}q?QOmaFZ;9P4>WCrau`)_ojBM~I5^~sJUQUUS>xo9J zxe+DEbHybkRwZLq2E%%-Pn*!zX`8eQw4GXowjWREkGenqyVF#(_HjmzqI}s9-(YhSf!J`8E~5XB^8-%x`xbY0-}FLRR75E zcfWA|)t5Ej-!^cAbaiKRLDT55x2#X?#SdR_X>H5Tx2ArdQ9|#(?9K&=yDpl!brC(@ zSsrw+sA#(8e>>}h6;7N0O z$~4(DDX<(bH(YAEG;p2iy1)U$Gp2)q7X$AEasqahe$1>OH4&X!=SUa2i+UP_u(8Bw zC^1P_yL)xI#NeIou7G!w7klprc)fvuD-a}#%O~~~e-_azZfqINWgu2!Qb}ibWU@P? zlRGL9sLXT*Z7Z^2TY_r515Q&3QC~hGuFB0#Q2k{fKNv8TlnB9qIT#EWj7EV74Mr2f zB@kgWV5IcQj4E;iyvt>-b~^cLHA8NI4^`(ESI1)cImp}WWxBj4yr;t}tt!i_%O6!Rs%m0reC)>1*4(ROKPmi4(e>p!LpyVK#I_gST69PG{pf+% zo3T%;{#EpK^6T<{jdGV)RE^@2rA2H!5&&}ImC(zx)EriT_&K{zvsBTKuNO^h*j5=q zs*-Tj7G;B~oTYIP9a{VFDS<4k*vk@Oe{vK>h!@3o#bxpJW#NdUy5X`xl0^3}@Lvy* zJ$rR^2{JPPMx9+mRiuyfsrY29<)q_7JA~7DU(S!7AKL4X<;irVOS+(OT(HKWuPa>Q zHP*%Qz$hepAIiO8B#BW@&I%@pQSOCw<>ahjk{IRWtYDHDF|*Dv^1x^ekt^S2e~OII zRxz|Q5Rgl{cr3HOIl`2Yoe5-rf@#>^1ZHj5jPkj&YW$yq}SL5N4eHuklD> zn4FR7RImy~lu;;H#-m}d-$5cLdOW3ekH=y6`0X|egRvJn5*ULWxFO6sf6X?V-Db^? z1RN2+Iih7FntN5NRhqS-fKT?=j`NYlg7qG5|#y)!xq*8{=I3Sln3$E z$jZF2H%|Ax(Z8(tsFnU>MeC_nx*#)LOZJ0*TXBve>>MrFc{N~bGXGj35hH&|hWBOu zx#H|l=Kgi5mU^36r!(7de-#jv9Fy%A_*yx>YM0#%9y_j-Y>}%~S+rV_zX?u#QwdM6aDuyre~Rxsc-1gTHI^rfAmg0{_T*99&b@n^O(oM z@pe8{GUTFfeM3(A!Xy9W>tEmV#}zX`pAo}vHaeFz;z2hlBP zE~MR#+R-ege-#(tO8h$d80I=xqP_Ta97Fd&{3i4P3gcVJeraStGthF>g4O8fz+)Gq zkMJ$`>mM9;F_onXJ`m zpV!@-Bj!A=zlLw*cN+fNs57oLHJZ)lwN{TUWV_tqB>*d#=an z9pwuNtpAn3g1{dG--bM)zej}J-Fa-@tI_B3-;EW<{wp3U)F&=0T3qyE@!chtlp2$K z*~IeeDmGW{%=Fkez+INsje%Mw&}^8dd?uTNIPfDhig}j&Q8H5xvNN}2+;Ze*9?Q5DXf^Y4#;rs}Y$l z7V8YxW!xBPjJIao3{n_(W!x+hj1Okq9HeA4?PPrZsP_Lr*H> z*5%l}PU=>Zdd5qrTSMv=ucdA+4WCBcIn;e2b(8C5a^g#=+XQZ7Txyq@rn#27Ei|8- zf2rF_!+%NL4*KpB)a{~ao~CXOP4gOc`>6X}>JHKLC#gH1y8lky1q2fd2xPI6)<5H> zWff?NTPuazshhT?AcP)90xCg8s2Ej%YbK1scCe?SPP7`H9<%{<(U1o4tpztR+aSDy zrYMA%dY}_4!JZCb%i#SU)J=WuV7Ei+e@nnzLR0GCI{|!)AhaE=M`RZk$$d4Hn=NMw zd*Fs=K;t_1^2p>gs2& zCvgqxAeIjcvN2&+0b8n z$?{ye**V!#8e_{udAjLqTNsk}C3cveJlEJKqEd0_jupht>DV$e$zsMib1xX36yYZs zt*!b$#`7MVXi7wd7r8}lNKM?H+4J>f)m}BA-~*)$+V1JNvZ98GZrx&$t;}kjvd1-Z zeYA015wGz1_aAUcMW1vPpE88Xubh=X(cH~g zp7zp|g{Y&x*?QMx)@x4-|FPh|C>cm(^w!U*oN8zYP>D=RR3ZFm_Tlx;Ovp?YUe@BN^P=7dVIl9XKQ_1m&S3b7vB@$baKA}A|r{p zVVIp%CKjhOmm^`=`yrFGM)PfrIPz|qRVpq@*7!!p9t%0tRN@My@tEkm*4pNS>Ti9z zZ~61Jg0I;47KJZhxdWn?oXe!PI7!6@F zpGs_u+`TzPSl?@yumb9Q%jXXSu{O+nq-Iyv-pBGsHbI6Nj9cOZ>ZnW?LMjK)JrV83-pO!g~7~ z5^O2SGcc_axilqn9VH^i7&Z97lVN;B6ju1Re*SC~;j#{k>tr_U^wga&6?rp}7Ut#N zxW;rT+3`hd+NWaC4|LYr``AaZbU>LP4T!)+5x?T7MNa#SZB>((_!kge2O6l%IZ*zjM>J^nHKvtP0;v-raPrM+cE$W$c*P~&t7-aTluq9to z{4M3A&k=vc&B^>tiMGt=9=G2AK?nU_dfGhqcN&HoIY+iX=)ie3x?VO*l%+PV_*V%- zHcKeU9`%Dv}10=>zfW?0JMt5KJxY zZtq=FlI~a#tW7z#8{=;r5h@o(eTKibzru_}HYX3s<{C$L0a@N9I3pgc%5#nNCY9kt z7z>^}rMz~hjn#n*1BMIDt)?bjjb=~jSFOB|2BTd$kfF@b?!&^y#e#^NSNrsDOXb)2 zzo7*tsg%|fc6$9;jb@w`(i)6^$Zm>G+6Y37HG|$~t@L@ndoVduYpR@h7%4SW7TKJS z9h}jK zm|vrT3?01`r$!$d16)wsmRwva&;3jpFLX9ksT-WD(A64kP!@#)yrh=m4f0Akvp)XTR|8K%y=x@xeSf zY$Q}Vji0%6H z`3VJ23;qqjMJNm2g$LrSD2qINA!`^hM3pH_(#W%miFH~?}LQFj{2jzN13ioc$mkzN;G6r;xM&rMU~ zSnfn&OkOou{&m}QD@^dAw-Ku3E>-Z(D zusRV~XOuXs!JzM1vOLLX?OCo;OFZUEkB&SBBxmAh!`l~>{LQ{=g?E6Y^HySUyw=IuGIurQ4F{}g0Z@Q zg&JJTK)#3Y_(e9uM)gJ(I{=U7740x6CsEQ%I482Xnj9)(wFM#HS{)20`_s_VO(!sj z2ku@hfZBn@4kGUjScW(K7Djs_d+UcMQ9x4|`MIDl)r=B3>%f|@IlN-fI3BOVs=FaV z2WKU0%d{9ySnSC-r9VdMh~n{bDgaKH5>rHqL9I5v+hh?oVGfVHZ2v9+u`}dKAySE7R6SOXR$hdvK=K#S29K5?K0Vq8|9Ga`_hkFC^{%_ z_w!uZtruUO4c*;Ns~{2n#M1DK7%}Xhsq`7HZz;7=_o~Fzxcdj7oiKP8;yvftXBGUc zG6oU+8YEL9I)lR-nk;scJyYXDZdvjuE-$L~5T9UvgemX`;@9QRF)J0zmp3!rQ^7Np zPtBB!~w zZ!4S6Q#=cx#j+1jy~6!xs|zhoMqeYJ=q1M}@Hf*X6WJ`Pt$@xa*#v)=;8fDLeB8sM zj=z8O((tFEpz1BfV9QCiZVy-y$7PSbh4LfD9GE`7Mo?!7eDZExO`hLMQey^YKW9$~ zvjKeq`DZ!Hznw!{VKhD*2Fh@tY8Mo^=KN}?66hfk*1bLl1s_rK5om!O4$8qqcpEJC zh>A4NJn5d3%)?Kix^Mc01QGkmVVU#*?NBzbFI89(N; z@gaDjw_`q&Tt%=x{EX~GJom0?Hao{(sq|S8@acqm!|{PN#Up}RbyeSbDvpPoS5_7! zXkNHO>^>_QcI6?mP6@;f2;iX5Z{ulZb%R>2zG z{&Ke{2g7x`U=7cUpCp9k1biTHb=42MvVRwG#WL>9NN-1T%C2FW@_ew#evMqiwSzx|DV$6o_qwX;m-26e{{VYmEumE@$9C#Nz_$)Dzrk;BE z>^(MP@KpRA?$LO3BytMVq26Zj-}l!19V8+|r2V0xCgM+vJJ0tt`?$b7!jy=6+xANT`B?(){k^G_&px7PrKAT&bzQg?BS{R-mSKNpLW%bfY|VXc_}(D6E&LrXNbhC{=ka!<5To&*b(3{bCTpXY zG;6x9EpiT_76Dfxrg^0jYILzu1t8&*iX&X42}zzeCQAW@iN<-1G7I#qTNkRMGrjEU zze?rH#pkxxTbH@Rv%!faio>&)Kcq8QbMGw0YeaJs(I=xuZFQ+tY{e0S&i7F3Kaar5 zJ(JUoN;O3$z}WfS6>^ZuvIlQt^FYrgj9j7Cw9_Gz62qZ$aec>0bm+ z=x&&92Z~}tjf38%>|-I*g8-s%1n7I=>KO1C`S~)+rPNCMG>(|UL5hZIcu5_sl4ak2 zCCL=&jL_gxsHBe+4YD{&jL{#3Q5=PiD05DsB*aDMuxO0rN|!G6E0|JF;RH}U5@|+K zGNE~PWuu-Cox(GoCw(2-)&uPsvw0lic-e_tJ9d%scQ&(fw5l)g*t4Z>m9cF zMw(7u3|XK#N0l!v3w_WL3{|(zB-Ltb4f8rZ+CL5AYscQl^LS;|E-JCxo-b6ilhC~UVVkIv!&8&l10+j0Ks;OEb?x?ZTXcL%q+dT4<1S`g82#bjC86ILF zYL8II=^7cN@8&l6$L^zTq~s|7GNM!-w84M2otJ{%&DI(6hHTxLtYQL~HValU6^Vmg zFl}$w_AIozHfmoYiPuMF39bV;R|aG{JcgDdw@rPZtcQ}E_@B2e8NNUxZVwYHa0W{a zL&eFMIzD%S)#v&k{X;GGZ)Mp3m5g!yTQbJV$;Qe0ANLq{3JD=NdIHf1Mgmi3IP5=N zWGZk10tsDem~j8Lk+HLKCG2z(gA(OqGMG_H z1X6ZC7_Z^3>Mf5AwNsLrzHMAVBBP`By$`NC*$*3{HC2uAB0wAW`cOv^w_TPus(98?(;8wTS;>?R(?iG)rQ;0wkI_49lb49LnJp6aW2RKu<11#p z-TCN>lb{+zp+?y47}K8hw~KhgNH zU%ciW?wDo(DZc@c4tI~Gd&f-n_J5Bj$5*wklAmbr4tLm{zk>{;U7u)FhY+849G)28Oc8=Jl|MdO@ zO~%=v&9!gYzJfNq++x_wKAw+qK&Br{Z)D@g*BX)3#y_vgr#AqEa5~@H;!$q+Nak03 z=1Nb08(MRASEKfR7Q2;Jf9~W{n2Pjc!_)@7#zT9lcidKf4{V`@ap(fXh0_{LE|?&G6G7 zhz$$==AiS(ld25ZvO4tL8U)1X$W56#O%aG;eL}ow#EplQ9_>6r;(h}#f*@o8IKPUC zKR?Z5jSEI9G%7gS`=mdt_;)ju6cWUhZh~mz6^F;Ql{S0gH=upCD9P8LR=+ZY3X%t| z!QONKcmX9a?Gtb#=NRpidp8^*Y){6u4~y>mc260?JmYy^pOEAbH-itb^kEg-)0f^yBnruI98Y@Fxm{@k)~RZEe}3t~10?w-b!PBrZy( z=8l5YN5C5NpsEP(bdPzvJB6-x>?qLJzuawW0YD?ZaTVkxhQB|r$vy%1ab8M*r(A&# z`l%dg03ch!JD_EoU!1QHf3r%~OBjOjMqVB&9izd`Ri9&AqLp!-^VaqO(Hz9`YVrqh z`{BxSf8CqEPzT3M< z!QpG&$9}d{S*cmy)@He~kc4Scr2ZlkzVVFIKJkMC*JOkkj!#nBWLkZztayxPP9&{5 zZ^zR5HQz6EQq;NlyU>EZOoAFOZki{J2?&hZOHd4Vni{e`alXoy7{99OTYvz&&+o|+ zg&F=zL%*x4Tk-ijwtx&=`o!AcuXuV)kxBuV?pf)@S(ePYAXPrh^9o4c1XI<@Y7W!3 zws^VpajXvrLPVY+v+z3q z^39BvhaEU`pnCCpzA(Qq_$RT>EE)(3OBJZNej>fJg(9N|UwJ#+i92 zbGG1)!F;{3e_M6$PKb(;@C{xY%HN~2rIoh?q7WYMBy6!}QB699N)+PzhgAAme|Cu@ zf7Mxy8R-&`w_^_am(0;ZFgG$lm=4DEbSq=`@=h!Tu2Tf}HMtl2Wr0n9aRszMIK+b| z`+R!Jmede5=g$KMp{yI(Gji}<2ypR*hEl1wa>qHzjymgVEguz3MEsu8HFyQZM}wz} zL!PJoUisvg0b!LaVlmHI!&g!!eXA$7vTP3<$D~t_iu4LnR2|8-T&z1?EV&>r&$)rD zY$E1&J@)fI#gM2dUj5k7;?p27?}xkZ)gGyXq%YLVYayjh{Vh&q-_+7Vr%}N27W>AU zO(30@d(bq{qfEx2*yZ@iC86q%F<^9iC51K?ISjC4M6K}oHbd3DK8EA@Uefz#8~9>U z2vO>-W)EA>XW1-uroUSQPb|M!M@#EKd+9CtE|ra&EJ{yWoA(2fJ1Ca~=s9%) zMuJ|UZUD1*nVlcFvriBme@Uazx6&9kx-16XD;$%1|HW}0V%r#zv&a5CjlAWLpESZS zxRKBfeWqDFDJ^6w6>%S@%1x>W!#ohSKm>tboQg6EJVt^A90MF=6BJUNk@3>TzcYjZ z(YK6Gx%v}XBajyxh?bDylTB$4+rZ;y&C*fnL*e;cE+qg@ly(6q{iG79QNs@v+UO=R z%ln3^-!^u2o6imUNotbszQ$ukvRdH(d|F`e-QdS~w@QLP7@oQ}I4@j~;Lh_C2r47B zV!F)axYlS2{fi1Js~PhrY6SNLS5WUQJ+wV~J(tFFrXh<2^VX-!oXVd>d6$>%{h)3f zN}^vu--tASsRDsEhF2a_&i(KtPJl%IorW3YlQ@kw#N#H=kCQKVi+5sf7)!!c@Ov6% z4Yj-wJDfu(Qh0L`+XnO8VB-Av0fv}RE&-L|J_ujfGEvauocm)Eg3(d2k}oV~x3zOP z5-s=!STjp$;~ihr>VQxpZDn^TY7!dwE4YiyMCQH5sPa1%zgC77U{O98!dX}YTd!3FI@$~> z86Pky=r%kDhHL&Wvf{ILq#27eXg7p@I582XndHX`+A{bBvHj+I6MlaxClHhWQ~=NT zCM`8!-KzZ7Nyz0UGOr2mCcTJV4dMeHB>75u{x?Tlv4D*3ZbN!6aU zdT7rB#E;*BDN9vqwn)-_-LpBZOWC$ykDbcR9C+I)5VVwWS<1-lQ+((5iI%*LiZKp_ zv*S98&R}UKb`eP6*CY~t2`(6@brHlH%@iIY6w#EE%ukOURXp7Sn|X?=sAUNcX*hlq z1Qdv>l}r$*Ql2IZc2}X%r=8$yA`12Q!R68ck*Qr1-n{0sbok!yUeFB=F7`R5Imc#D z9YZojgQt(6eSsFPhS&cqkz8+ddl5#9P6QTJ$qHZFggA*(pKUI{hG1li)2h16Lo%R< zkTsEZOOdf9ljfPewWRxg8n=Jr2{GIf?vkJfR@Kxgac#tt8DZoJG`TCZ9ap$8f7y>UaEJU-$WzAHZ)~3f>Cw0+ z3AedcVak~aN!FD$c}P)~19~5FD;ET!a-tuiT@!U*@7lz_+x9m!@8R<@H8nT?HH`T% z{_358(DdSs)~F>Anw=pc@$K3|2h})HE(dT?QS4@84&7j)&PbNUsHr|+GFp!cWcvJ2 zbv=M@h_o#$!7uv^^8RqcuGoEsPt0lUzQq>4%`n?UtwOOJAyw(6DJ>T|9raroXTNVV zZ;8E8MU$6)l-5tkj?GT+rN?dAAdMP>-tW6HJE$K15!9h(8{2n7U@0R&1?r-uGj8@; zpBSNV0L2>VF$SZ`b4 zWqH_`pjMs->z9)wrU3SKN6reljZGB?0AtD)O9t!kMQbHR2_-e-WLQ;b2Gq?pK0tXb z6E(xT^48U_#ieXSN1m&vg8U;o!8E(`4LWptYF)QTPFkErAYxz`l9tBSjI*~G9!K_A znMQ(GsT3#pTxKg`t-#$`3>q}RRKm5Wa{*&xcHMYbG8xW(%*5NZT0NPiIC^r}Avdkw z%>yzS*YfH8WocWPY@+Dk_NZb(7*4;xsIeigKDzmhaCLK_WH)u~gC3i=mS+Su9LO5G z&XzKiN(`i)#TzIzza=u!{h)?Tphu!GBz>g(qy(`q=KvzZ zJa6IQf2l8G;E@MZ7j{@TcACoHkM=^S3oyhX#Jhd>NHb`)&$GLl33=6~eMH*2U*E|2 z?B!SNa#+(huI_MA2Mv!ma-47XgF=INFH66rY&v)c;{kYHQkpZj#L)9D3dkmGV$BN*>19<6V$t6~=d=(>AgzE}@;qsN!6SSI(xhGHSU z`QE*ASLRtNJ1`w$xb3mg!X=7{m5--YRw<$3xQj>+e&Hi(_`=wQr2G5Z&x{l!MDyU5 z^L}-eLEal-E0C31HSKK@&hhs~LNh+l+_mcI@W)Ha(xw%U%1dQ+=6l^0`h@bIfCxaM z?fCC&*N2VsXP39xh@tZ+#kc)GN1Q?pR%sp)g7hDLi8J~I3fE2GB%X2K>XuIjH#Dkl}h6SHutu4A#!qB1UC#*6EvUHXy+(9W%izx2gaX@$$Ei+wVt) zc06N>W9lj+3eq$>BtK45Y3bQT*Q=gnzG6=mc&PmSiA{4tw>kqrlPggo~ zUKH%zeF5={Q}ix|jX7T*8=?eHxizd!HTUtX-9N$e^!oWMevo#o&UmDvH+p@5blu5! zVV4UBqE+%(VE5qh6S@OD{-zr{``5NVi76>bm3l!)Oi6hZtvB#+aPaWmuV3}0wf9!h zwFjfm-LH#{_Ur^dx_GL}o>Y9UmS>A+Acv4lKSB3LTUA+YzfAMJ)+*)M?-dDOX3Kd$ zY)8^(I;|dUblPpt=5l=Y<=^L7ddiF_88@zE;USMBg1`}mO2lNAuvCT~L`0(u%k!p) zalV|hZ*%)G!gYT=G>2k11q$&(Bj6BE8zH)T=9ZJn=VERwa$63DuUk&PH5uKc&$l=NMd2f1c9k`mqT*)zJGG;Q> z&mW^6dFXn1yc>>4E%7IM`ULl+E(929QNxAb!6)8eMl3S^JhtYxapU`9E))((u_20-};XN@ATzw-z z_j9sp@5lI`*Upck8x7~kiPP-&r7@!R&&<6Z=9E{=K03O#zgQgD%)`?L<@hA!JL1ln z$9N+T4)K6P86Kj*Eqd4>k=E`4n1%xR;S=d@U?oZs<1jUaf0pylim?;OLlBizR4_tp zAT0_ruW3n1t`sj9`bU<(GWpN2)JUj!Sg9L$ItY zu#_vrXbffpY;G)>h;rU~wA|MOef>#Ylo=m4X~4VRIc8llfWvkDsZF}y9)_Inx*QHH z(X6X1w@7Zn2YPo823XBQ}z=!MkTRJb%&xmL#=;uM?aa-l5S2AzAX$8xtu`7e8 zfj>M8k;sI!DSNUZQ%J%qIS!L;vt)p?W~1d`l7NjyfJ{1~VS{6hi&Iq;t)3@~;zd&O z-Ax22a7Cmg!aRU&D1o@wgejiqa&EE@)Rkxo?`zm#YY2B*MXex51fsnL&GCtU+fI9eLiK1wHrPb^spbBkLtz(yD$cjKZwJr z6fKD+_vANaewS2kevt@Mw`uO|^_^tov+@}j)kGXL6?M|dS5e^qyUkX3k{1(&m4&$M zy->)t+QUl?c>`GdRo3&2Gwe{HNYf!gIUGnp;v7oFB!uuTispQAE<3{7Q*Ym?i|~L3>q277M=I>-<%@nO+oK)&zKmy0z-{8S_z-@KvoookDHj@VRt&NC@0#wBcX}xGH1w^69|Uxg3au!Y2qrkB)s09CfQr3^qJda%W6t zYa8ucTMADMg^NbjBqdvQNIdRWCdH?y(_01`+HiX zXZ^6RZuPg}T+nwy*YTTb-GZRLIb`ud0}cIV$!JhjDUf2G9L4}zMyLBl9bdOzf5}#w! z@^B$%HkSTZYjumfCL#IeRn>9yr{oH^K!zYtY=;{mxy~(YiBgtz3{q;}<$yFnD*^uN6 z`isE|xWiXoZqpdpLnVZ13ibBA)@TzmkdQt7my$9^3n&D|hP<## zjp5zIg5b~&>A?w7b{B5xTR9SRSIgD*A(@HUUli$+Vs!-3_dWXJjIo39ThWJh)5Q;w zb`N`Op+UXVAFL0rsgVai+*qhI;VX55s(aj)hZ64LbUOoYjtJ&SY_GIge+O#+B!O*) z?zZyhr%t2@Dzx_s5Q(9qN(8BnYKIG9fYT(#H)GE@A{SwzF*MJ>B{|j(7)5aFX*Hxy z9pPshnyq_udrZ>s*f>3&tKMmLnB5KwY{fyJ2uV%2k;MY)%1kr*^EvcLv!Mjt+X!zB zU6sw3?9kkwO-tDIy39cCMvl(7h|j-OTbt~t3ykb&-pb~aE{k`xqrf+DIL&7a1<}; zLNmxz+f&g%eCsso?89?AJ7gs)2E`U?(gXW>()z)BHki{60a4v3lE6u2vvG0R073M| zI#|ORE2GHN2{@cW4WdU^(b&!3XC?XSWP6Ny{Xg-9;}seUulhG_)zGcgMO zfQB-IBxe8TXt2i+;Lv|A7F!Tyf^2WeVrfCk5_W1K{v{<`u2}U}Q7CR4dM!jVHBl_U?w7C~x=`nE=>t3ZS zD#DD8`0mmgAUYkCDLhwPU#q9ec}UJo#nCS2hg77rrL-+oTf_2UvIBRTLBULNR~2yq zL?f5bhrbSBr+(iGLo?YQWgG<_%G|0Vg&77}s43hH3Oc zxa`YP;enVu)@YlkuE#ZL4Y<`ahCysNF{d>_g4W*RA(e#_vpojrmjgFA0TV60V+jaq zI3HdJpeEKQDukxJ7X~{PE@Xp}1AeF@vZ(Lc8Su{Uc8*8)&Xy|s(9P+m9_LpU9Q zGi<08rrfW-u-{L|p*#fmpfJQOj5Eze4uUhhv*;m-g~O~ojV%pU z9zIUbCMSV~s^x2#8dY^B?OIE$tq!LiZZ`_3HY3PTJe*LZq1}Q4(X;YjgF{0p_eaAi zF31o1Nc!xa_U1KqTs#?To{EYjLz`TVS zkZ_|H7ZzdE_h0;Bae2mf21R&;$I2nW=+kS6y(KX`%A`|X+tpAZlKyVs8v`NFWFc+nIYj7aP4T!m;j z@L=K`_k22VE4>H18Q`R#ig;6kFss~)bRs$UDJgVWQ2+3~vELgovGsSBF~%u42qr?g z`~LdzPr@7L6IEX3Vb1nvvUgG`%8!%;Ok+f$33A~!f7{qdp_8Cm+A@>%;ALL8)GtDi z#<+LZP#@BzA>d2U(x23ky=n-jIP;u6?aGhxn%a<^Wx0MpzS!(qsl|pCrvWL~EoZ<>=!xBJdFk#yEn>NVH2cP{FQs2z({3 zc7XK-H*KO2`!e}MBqU?cp(L)@N;I3-iBSVIj(}^}SIdX0x|Jd4mXr%ejMcvSa!H1M zjV{VE_`!0In(&8vg`f>W>Y5l^v{)F#M69EehE2^S!OWvl)Y>tvNhZiqvMpjTq0 zL8Lc=qfAUTS*<0yJ&P=&G=6#5=}!pmGhQcJ4c~PF4t+T;VT|b2WW=LS$9! z*F<8~FiBTDZaFqN&-;0QcZy0?GUDrF#}W#zj78SAvRYtmC+%02zQJPY^tYp>x9ev* zlm2A*G%}n1iK&WsHd+0fOC57^pM$rrU4VVn|GlCj~-%s9Mkj|(RRyh zjPeg-KJ4(L(r1z<5P2Hm!}uIPh9-ZFYZcA&7AHa~-IyJDLivbowNeryu^H-?2 zj!+-!8M}fkkp-UICC=17_go?}`K!U4qqIx%zNMPAEV}NAL#9EFxMYF=P$&m60H{}H zsK=jX2M+Orj!0m2N6iIWHmg>E=Tqophh}X~_K6iZ5m)|d zhVCRSbF?NnK)_?0tZBEq7dPYDjt}}Ajp=Smi58A&mt76jE3_k><~Fzd>hTef!DK-7 z?zh|+YpFmAtl|BN9G%(_(90~BCwb*8aYDZdS}>3KNqpEkcOj{ltxtXmS$U# zY}!`SMvFf1i*zpRyGJo(dEK~gg$(dQLpKey5|YGJk6j)=PE^s62BT(48TsA8lv7PS zO;>|qL?>hcGmWEyFD*-hVKT5rE74+LG|Qsr7jr#i`Ok6J3Y|35iW}h4kE9ff-qEy` z;3TrS&?Npjza)lwOggJ#B05>h&?K}c)2ws(b3J8(;5v92D|2xNpN;Tc;zYtVbqr3B zsB#RGAQ9K5qmG7e>gr{_q#fw`;qUI|;cA1CC`u3q#5Uf!B6N%l5LvKoR^SCCIxW8$K#wxB}wfK zDH$AXzdw9K=1;~T$&+VTgZwqPWfG*k$}JLdN?le8X6C@NFBD~i{<(35rL=|}(KReT z)7G7p>&cnuO|3?aMGc1m#6ZU&!@v#yG5{tXNGi0GXRkz!0dc_IeEx3n``l-R)=R%` zcoQ3QXCfqRX5Ag(h* z_l{9T{)&8QhAeQFH&;Oz|Ey05@RJ9^1?SWU_pE&&gV33-!75J|+3s#G%a{UDm=|Ub zRX=5CM%4;P;B=-dVo#zET&wvZt?q-Zo;RT-y>9>hstN{(dZd|-;=cjt?_Ky{YzkQ*f>r|V@)HLJd>*(=~g%6EMIpw8+dBqLub)0{STAEHiXg|=jl+_;_yAOcOc zeIh)=spI3}kQ~7SSH|1O_^+{nZ+78$mXH77&#;02Cdjxs{vWQ9mF+)V;}mBw^*=eY ze{hZeAup$b&1isgqGI0Hfg|oE1UI z3+Z^TIpMH?o}PMo?OwLn$krPI|;+V6?aljrmkxflp%0BsQlL zUIn!<^D3OyULx}Ql>-MKZc#>?+1NAxdO-jc)?y(nvvCWaMaJ?`-Et{P(8+A07P1bp7uM%^ zs=_2;VeM?<$dvL*2#!%-!~>3p_|FS=KP=%mI9P!R-6NPObcEor|D;|IzC~cL{^L>q z>)}A=`oQmE;1Ixn(~VDRR@|)r#WMo`i)RG>H_r$JHeV5flcOO5|0h$~*g3gVa=w87 zD~c2WX7Eysf8O}NLtt-yVFqXZ8^gZ_HbsjS+>jgiUn2wjuaWt?3W1sUA8-2~s1nHa zFQ}4>n}h9tF_qk2_V{L5U0YRpzA~Ao4zQbWR=Cg%r)?+cNwSDiVksxPqam*B!bMbk+)rCfNIu47p~> zf|BzZYOfxBw0IRP;rgz(K$SNaADdTKw=yGURC`KrbHpOVJb9*>gJ|$z-T*MV0#%P^ zqfWOA$|q94>s@nG>s?hdC#pz{zo!_M-|DXMBPD{i~6 zo9N$Ubf5>ATkJjJnxg5fpHpYd=*6)&r3gO(?%dO^2Gj(2 z1jkovTWfQj)u8g*#ps8oMFUx<*neGr#^eRIsToPYPHFOKw}uoyD6m{=Rr@xSZS2 z>=d7G=_t0yF)gYq5p510Ptd8rsuYVrFP$&HiP)Y7FAiGtoz5S2;%X2SyAH#mptq`a zatDMU;&Z7;gf7YA@zG*c63lCrpaFYe214s81}0>jI$El*Oe17r=t-p3N{vk&_85l2 z4UO!VD!`Ii50hCDf3Z}$fvA^vZ~lss8VR*pXL~iL$K@@HO((|XN2RfN)|sXjvvWKQ zCZWjU(m}JSXsXD;Y|0cHxGY@2TZhVgiM7H8^NdU1%5>Hkt40}FrE)_x-q++eXFZp& zbcas)II~R%8A*@#*+z`-C8>*Rn-EZzBK5&OBuwo(mEX?N(*6%w=M)@Rw07Irw%xI9 z+v&LDj%{PdwvCQ$+jhq`JMI{NpQ?NBf2+>Zdfw|{RqgS8b4(2hyPSxrGH3Wj@<ru7}XEq0Ir!Yj?|PjjkgPcrZGB7zzyXrYruJNqRZ;Fx5eNz5dA zi5G0UJ%*XF55C5xqZLK&)LJc)v+IRv8)IZ?eCniWW|UXOyT1?BU~9^bQThB#OIKN!g|pm0=|x0M!U9Rua{M4Qsd{W|Rdm1_%hX zZsYznC}PVpR4c895H8hw8r<3aB)|LY3#!{S)>{9A8@41 zIuoCBJU2|b*;}-oGtWkJ!8+-6zvWe1NC9s2$QY~Ka5~91J2j;Ct{)`wgh+G+9_cZJ z>K2O<*u=5Sc_{(P=DiE;0JTdJk#s=+6z(!~En8u*E+iYff2L`-1QIMGi+ms_*;q9M zwL<4vO~#6j#ah{J_5@U`Wh;RwHodGW&N?Lzj{W=*j$e!lIezK!ql7hOXkyN>8<56z z$)M}n3|4Qt#bT+YrLuvo?IMplD0;uPIU`e*x@2>D6pyAkAS=2I)=ip83G@nV%Q>Ys?^m&@ZU^FgE?3iTZl;wlWZ zU4_GKqra_1$c+A)q)w*&X~vqcKVW-2#Aqgd7Cue=QLv?(Ig^Ckd1cn~&#S-(oq29` zW?`MFe$(^O2-;Qq@B3#YuS1^@gB9Va*`+cE8ZW}Jd8xo5xYZ5SBu&DnP|V8XP~g+t zbKpVboofQnxruVbcG)M%5J`EdFf3XE3lTR}Kp^&U;IjLEH3e9tg4t#+0?!=qRVD#H zjr-83;gaD@1Ff-e&)8LZWbLsfV9aIf5%e01mPW^&xdn7E955+LWXP1mZ+iMGYoNnK z&1KJ+nxb~bRnR*vbSuz*+q^cSB^%o)3t}ivi4WK`28dS|0A&SE@Xa#qAz&;iR>QJg zGIT1LINb^J`0y_1g;|#3fQsn%ypruFt1-%5;W0U#@A@BlE75;km&=(6Y1vxxmQ)-n zu^Dkqyr>vJxlF*SIUCmym#)t7`D=dV*2z`4a1U+Q4QlAG6Ec>rcqI#1+>?NEea!`R z6Y*b*Ob6xN8ip2vB6E7&JPxEx6a9M2nbQJais8WAHGh)bY;PQx1q8G39WJ0utH*Uo zUs`QO@moFVE{}EQdL^7zAx-~y?d5<+Ac|e}{h1$2F-Cotz8z6&0luv5yWZDcfI+Sq z0d*Be)5bseP)bYxLaIANx667y7c2K)a!l@EE=;;;N>Mu{*%iCn!etn=3WiP^*xKF$ z5}fn%=vc+Biayg301H(Mw__zbY%|~d?oiGw-|d>|bCzP)srX))BLDSJI6Al@1eIVv zsBDbhp!;@)=?ba9MX?VyTQUD1O4C1~wmPkIVfT#U+ z&z5(GSLV$TU9aE8lYQ^)BYv&VUgOut{p<(2_pRs+qb}MfuziCXZ)k3);ON<|jiITo zjz1AONnP&WzG?+swn|Ldqi^|jPRuME7Z_$#ghQKb8*}RZOehNpZxmKYa7#xF9Oi~m z5x>p2x2)K0xDO6Kb~+CbdfWT#g$$+4UfVVx@-8O=UYcA8bJ=Zs5yD`XZsr-o`(6D9 z`dfrlo{I)g=b(BlT3G!rCVCv(3x1EjAU9+@el>Mu!Ha%xf%FqqGK`VK256?c z+X#n>lXc6xGv}cw`ENw}6|Cy3v$9jH)n3=iYtwu!Z>CflJY5@ZYIROoba2cuID)ot zf*w?ONYg8bO&qSSTbu^&@n&-Gwu|u#f?tz#232-|RRxw0c-XT3+NAKH+>%1oC!f|Q zyDH-ZP&EXRlA>Nh`;@t~w6(XiJb|W~Qdq%v>43o_Zf$@gzQM~yD7YG>ncV9KTMQg}Q9Q|h(pkz1JOXgm@l_>`B}v-7q6zQgpk$bf88qwqZF-+IY3PN7l8{pwr!_9eNUc*H$ojTPQJ^}22D_7RlZMtq zvG+v5Pql|l@!k~tBj+WLfHNb~u>Z|KEelrOq zGQOOdCQc%HQ#AERJ0efw}}ks*~@yU44i!P`7zKH9I%`ojEGi~ghJt?Fkt@e zJv%r~mYadJ9*V*y!GR9D&XHGpMw(ij;_{nojeKU8@u1v&8FKNzN)g{lzRpBlqe#uv z&=FdoFz@;l4rsQ2p8MnXbw~bt_){3*o+?Yu{|=%@P2_h*N3*#p_;oo?{B<(_!dCrU zIMw?le%W&e-!Fk~13aXBzYl;877$X5hqcDPr98+#-jY_{`0R7cJjV)lC3wh}m zUR<=o5O@6#cx{56o|BQX`jC{)>u|3^YtQOJ9w*%h83UcV)Wf1!LP!Z212_iUQ~l;E zvHdCrP3shz&K!8Eu!88lz~6vx`!tSPVP^y^$*0V|PaMBb>tHqz{Rlbxpb=yUIS23t z$$4!8V?tGVZ9=&ag_e!rZP@*k!lDdgZn=1M>AQY5oIAj7n!FSvr496xNrX-AI?S^W zGKN)4^jgToe!CyGSaifpNzi!p&b5kx3FVS&55=ok-s<2J0=a*`RXx*y*eCo2~6;*^Gu ze60kDXNy%8tYUpsOu)z0cs1#WWhe?^U&1q!jBjmb&KiqfhWQJeOS9&YjH2IWZxRZT zJrz^^{*8SBU!ZI&S>E8}M3$tWbc=K0G;hP7OJ;5*Pbd>hb#KB@V75f99zBs7ee5Ao zhTv&c6R#ZbA87Dm6dTYz=!( z@~ZzbVqh9=DIYB1PHGeQg%Y?HD`Urf%jP zPx0EW#YhLNK#aFpndTlQ6Ddre}Tx^@tm- zDf;63G1gI2@$_xjSN=?eHRnh7cHw*OfuHK3*zqmH%#_m(Yi-g*rT*yygyX#~r|g5{ zE5VZc7KD27uC@4%rqnZ|!YQtj!$0ZR;yp}WB>T0_L}%*Lvz0oWGh4-M)s!n6M5!lM zlxr1m&~214yAWYhy!FMsoqu#27^ zp!YCvR(QHvnmfC9r-#1uB;QTccs;vp>}cu%nRXp_`JEme!#J(yO;xVb&S~va?^0YS zM?~+vYwYxXu|iX^X9jG8Vt)>27AwS39xeCe_Rxdj!V}4O=$aH+?{cf-=1(60 zzw_hlYvhr#FUXNw@&bba6`dQZG6E~Wjv!QI6W*Iycou|&bG|Xd;jwB|fLX!GJKPU` zuQS4rjj%wYj6FU&E?1DSmb@#~MRA_+<>zn7Uky~<?=N`~|55XXr0Uu4H{|Er-T%97`@ZfX5WDn;Ie*zA8_q0_tKV^)u zWtb1uw)*ygJD>9L`8*ahmd^BeEvJ8)Hwpgn{2?tAlglPxjx`f*;8!tj?0W~SCJmHR zvD{N|3P!Yy9*yD?VY9^@P!a1K-1`2yMGw5Nzu7xrnJtID4Iqkxj+Kgqr-~~);m&=> z@k^$TplMQ=ND>un*d0e?@?bsWR-jvI*>!xagr4?uoTFg$(DKiVTYl4-joX#LBp=if z7@XQAMIfN1wH9!@DF{*`z(58NmOOmKV8jwg_YUWdNc-7>E8kOsBquh7Q>%VS*9yGZ**&L*s1!D+Nds!Us)6 z|1Uhs&YTpi&XI;o0Ez$v|Dklm#`>Nzl!)zHW+wzPxGGTrYSeYJlP0w;tLHz%{4~QX zHoH~4II*Z{E*?iDSzJ6gT%|~Yz)hnt0at|fiB+sKwTXt;_0i&y%_opqLi)gPh;_}m zDj>_YOIX09_)FC*aBH+3*|GP>TL_yKGNcg#MQp(gdh4u@WTr0grWo3Ew;`zY32~hz zQTAQ;xE6-ov0gX-z)N%i12U+@7FUGf>*)LvNAh-DPdQaY8>2K5vWB%a;TnA^Qe5svTW!(tx8&U!Lz;$kQ{e0>Smvgmu+JElp zf9(osNX(#6X&Z!~@c&`TnxI^1L4=^Bs+^phBwYWd4*$)z+5QV7v;P-HX8&)D%>I93 z@vLlI+}!^^!d>$5^if;tety|DeH@?4@nxNw%0ckfV1-4|c7h#8!7}m}W}`L{#X<|A zpa9jCQ*!`iP;qR{d@XBE)r5}WZ_+S^e{5c+>iMZtx%?2({IT@y2hm@e_7~slcD5u~ z!#B_Cr_UFKHs{%w-0QU-VB_DxBWC{;i}jPtH~8E4#g>s8i&e#&dpXg*<)%hW-MR@L zExf=<5$WTa1oYd>|B1+^QJR4vtw)wEX=ei+L+{&*<7~P z{Y1}uZ2GEXtb{LMR_L8@NXF^~a!9n}^eGtGJKbX(JUH67=y0U>2-Dc$(>_+TwC9Q{ zE=j}*+B3>V9#dQ6^trbV)Ut zzL@SQOk)&@OUI)~6soZ5#58jsI~9FmFn7vu==H8Iw?3vQzlhwsyLlb%cUj9_c`?rF zM6NB~Vz%S~N@R*S*2{ZGz3^ZPN2iEwWxYn1UWmZv#btIyp>tyik=l7k-@Wfg`SqM)!!yu?rm=M1$Th-nUi z{25bP-BFiMNuWS6EXfkVWM|dZk{UP=Bf~;US-M}z0-r4{8@}d6R8sU)7FWJ5@_yW> zII1q)r<#|ZCQK00AO8T*0v|59ZbBP^?f$*pEBR$1N@h~fMa5^S!g=$T*sxz*7t@S#! zC_XP6wMKEK?YIfrd^?dIMstPYK1Cy~cKiWo^y!k5zEiC-a@+yXvK5~X;wVM&R8gie zE&#M|875d%Mhd>tFVDZEFMQvA8L@OQ)z{vxYV91?Ss-5X{bc=fQ6b`#q$R-VXmk1U z(PQ%P=&_J#5hS-?UM{slue7ddppj=%B0k(A8NAy&;b2k2L}B9a(?C-`o?e=u$blLt ziZ}XViLe=wZ?Jdm&cq%!+gTK+or$B$2>#BR%;;F7D*L+dE#o|b`OfWS{nb~-Zeros z0r9&JVBBEOCcAvDLRI>oWZajyJTWbCl$kfy#?K z@`U<5a?@Ye889^Dw^opA%{aw2R?|JN$IB+nd)AWO)|eN*1w|NANTY*)85|{m%Xm?? zP+v9feKn2q^R!w-CWGz*KRYWDtAGUpuHxe^vw^>Y&A1qSMRt*jRpvf61gO<8D23@T zn$x4#MQ)Ku#B^xcqL}^FnM}(;aYglxIODiK735sul*W}Tk~N1c9c?=}ViJeluvT70 zZ{uHS=nd<%l3fNI^E_+ATPc$)Ms)%R= zFAEeZ;#!$>&t(kC7`)LfnoYlCRYSJj#S<}1?K;IRDY}|G4Loc=d?I6j$3w3j4p{@v z27=TTbxf>$%Sv9fP#sO;gx%$4{PVoVUj`o5dLN4j%u(p-x8zCnRi8Yl*obidmd(J_ zSbrs|{P+=sOE~-_K!v&>CC3Ao|59QsOf8=l3TnwaQpQ}lKCiZ(AdW-7_Zy8SW|~yK zbl{ab+AkI*#3V5w%_UJEU}Z5IafiT|TZHi>`p^=HHT>^8ijR1~GZIqwKw zjKY=fTc|y13sH+lGKdiyvc2X@GRq1bPg533mRg2iXbm*_h-Ekb%_^8h&@;2(6wAS_ zm?p{Ww}P5MAUDI|9He0*;2JdSXqrMuns(rY_ZvfiUW}nwu=s{CEwYi3*1v5Ws=uDJ zjv~d!Z3$&Ih9JWRAk0DiY--_!qgRcpdx?2o%Y9|H+1bwdv{~oAO&Y-+B}sV!i|{yY zH4KW>{-(M)qx?c(^uu$kJftv8>dWPs`GAMuJ|*p~P0DF<(mA4`Qe-Fg1Kc}Mo=j}v z?~vUSjsj)1)CIEq-N=3tz1vh9e|=m3Q4=YpwH~^8br_ffzzZ@8z6%3_?GHCafFNuS zk_oPi?SBrE3~A^D;?u;93sNEk;tP5TCrk$N9YhhzFdPfPq`$FrnOTs<^=f131(tg- zJTZK(e4n2#IQ*5X5&}!K==TxEZ_hLZ#1qR91jPK;1!8@tY1gg0l$Bcj*1eLgJIsyN zpa=yFHJ^)AVBWhsBt*Z&&o&77T|=QgUpclkQ6IOOB8`tNBYh9KeY)=$RN6SSPnolH zWJp+fP!;82Sa2ACh8#{*=VdqqP9?y+8z6{G@=k-y?m@yq=gMiRC^0e`H#HGZN1Z$! zo7@p{(^Q`B)<#zByqeO8WFLR>{!*<2b$nR+CuQ~>SVX>B|H0x3<@1;}pZ|VzFzmEZ zZY~B#Y@Uy#<)~Cb6(^&||N1Lai$K8Qx4%}lYcA`6BRK-zUOXxio>3gCSInGE@VKM{ zP5_(eFmB|$Jw(YQ3@rIjGzqTJ7>o=#!d%r7Oqmfmdz8nT1$xo<4AVhRRzAj@lL$%| zKdDC~0700&VlKy=A8yN6`pMl9`i$&J8ga<%n7doy`timsO#o}9gPtIBgu}6M$#j97 zDO>u|2pK`;j+~D+l;%i1)==;)fDxI3nmNNP1`bt#S@14mK=ONFs+Va59$Y54c44$W zYOUp1J3kY(b2(0%-9;-&gWRWh>*32pLL~_ZP%cYyFd$VPN83$(g+pkW=(JB{zJaLEyRLqFSuI2s;^CR6c-zIAW;jYh$I<8 z1Oe4HFr>1uiiuzIhoI*Z zaII+j=QlstQuiIUQ=R?nBjT;yi>?d!`<^|>)!a5)?f_HXJ=TCV)29XAeSz=^&LEGZ z3XpA~cNNd+0ZQM_TRMD(>;XG7ga5!vxqQK~W>cfJX~uC|c|fT-!fdh;tpy?tf;Ce1 zmm1a|2gDjgaskQ&>}dh4R53}-BNCqzu$5Qho))3XtdTyud4fhom~R%r0ms z?_*f)Hht_*OuViE-j)Dw*!82gb>6+2$vuXR9_XK(2b+fTQhj+Jx^{Zt$U=^%8PLh? z#|LRK1|rei8}@S5LnaF_fg8i;D-i zl{>QcW2-$(Y~B-yy<(W!3R&FtX|!{B`bB#|rW9XeuuA*}dY+KS&!ne7fKpo%!gOjA84=z2EC8 z4F#^o#E!)I{I6r8G!%7SOcyzaD~Ieu5uguM{rP)c78(TUt#W?Sa`KL*6goQ8-r{6h zGG)JQk(KBMBqC9(ET{o9@=h(p%7nZ$O<{aglvd=^`ScB*dK|U3^G_R5X8asso;M_=dl+r z=3{L~3UW0lSfUm=WTIo^m#btegXlWDGG<|2M7cI0R7Zc=9Y%ooA5DB4YiW*Gs$-nx z-{%xE1|lev(u_+LauUtQbO@igAwd-?C1Btz0wDrU5f~Z5T9#$9>$&S66@o%7Nff~4 zZ8jCVE0d3+S82avkYSeZwX0^UNv7}bv8%BZ*pRA;w(o~&k?0EIH}P1wVHASf^Lv~i zX}7o&zu27heYgYk+RI&>_E*Njb{*||!mg?`Z+Bew1X~vjo4*lr6;>F3ws+z)F>T-e z&_Z%}FNrmo6I1(S?G@eN%^50gh+j^ymQ+t?StR(mbtO!H<*cJkKJ%~HaXPprBR(Cr zK7(W?xYSH^P+p4zePO>MUR&?dK@#)8jMbQ?Bt>eoEa?a^9abJvZrpRDWrV|%3!5Y~ z%rGE)IXQijgEMF7J*Pq*(qUx!@cT*)i!@Jluhn-yo9N_H3l(wq?%jFZQ&@^KP(4Og z=aQ)9L-DcIajfe2X=-OVzyDV)Zr;!8A4Ar@X=0~LXHTLG8GASj2_Z$tr^!*rh?;GX zM^-?S4|xGX>OJNXuOF<~y}FGRbnSCWxJy(qMqSJymZc2cASk8!FhLa{W+ZoAAQUBb zQ(y=p`?^tA2|&Xo@_E6&Va%d}GJ@HXTMqTZJV**h3ga@2G_aw1@PUb;N-uCeis$nr zT8R$Vm&_|l?s`D@p#=Cvx|u>8O9;z9v1tA=Bmn~=V15_BIm~62T22R{o(^6WzgIaC zS9@n7`OAC{W?UC|w2JU&Y(C~Nv4tD4Rc=9U@qLp(=@_jvCpG0kuv|=O(6hWan$fCt zoZYDQ@g_AMaKibs;&m{8q$JJ3_ny|HFW}VNb@dpqnVBhD>JX;e3~Y70W?jXvT(3jA zaV7wOjJAYd%6nQenz2{uLWl6ANdwClb(t)rfC+&D0rQ&?4$>gXG}Qo!X|YP`==3|K zt2(|rhI*WF;J+HU1j3UGjp@!H*b@ck7j*zFKo(uC`mGj%knKLUIEc#c{`^s6npAsz znBAb)dvxL%^F+%9=R}_I`R>by7C5WOA@z=o61e6K3)!K2fOBE^()HC&uzLSx!Qg#?x7DNB1|(=nvSC)tKA5tPg3NfknZG>fI#5BefZ}!sF@s3nvWJ z5;*o>FyiE!>M?2^1(Gl*@5x~E%SQ29a*RWM!wC4N%=@t}wf7B!)js;MP=ikxUSqxi z6;7$-O_?sdX$HpDBHV3JO3u+@_)PFq5yNm1GSe@BRGQ9RvKe!^NV8u18yoJ?(6 zy!_@PY}ePtMP7*S8`U0G=^J36%WOloJ0h}8%wG4Do#?RFw+&WLfce+;rc8ItC-rvj&) z0-u5*D7jawY6vcoRk`0SP6t$SSTC@L7;Y$S7)oeL?5)G*xxHYty|y zFoSnL%4F``>o3gO==mb_JjyDp74*XC36A@`n6Nj!fU^y%1j3TEJzG^iT~Oz60)`n30=F%@<8 zk}+d-jOO6NsvPFvyn(74_nWXUzGuv@I6?$ngx(>zUzk-GU);8hvypF1;!0W~dN8ed zPiKPH&VKAy)^z#ym}^arn<%k-OGf#WO0_-d1EzO>VY$7KSB0ICb$j~z^@BF=Vy=EZ z9&AK(b(miIwqN zI$6w%W!L3Dk5_4H+Q`2BHnUh;N7mK$IbFg69p>id6_d>ylkjGjyOO2TYP{oG_+}sU zQ{nzYtoCDX`Y%vh8=PPqq_|kN@v2iZrb2e&N=Ps(y3lX!;#J8dYjvIt7cDH@VUPz(q=EB zhPxH_>&Q8@IwhitSgiWf^zVSfFaf{Q;u_%|A;?}d!T?o2VQ8Z{WM~>FET-9tdvLhk z=w+3yFhFzB!KRXNvbyb%si{EB?{xN@_8=U^g-x=owSX7j_xDbpLd7X`C2=CVb+HG! zOYPoIyjR!Hnhq4nhVIteX@Vnv0bN1!0lQITS&zW=bs_%8Rrl-mCMyjieHqiWG=m;m z=eM<2?Zpa3qz!}4<+Z=NgRUntp$^x9J~tC8j{tmAebTP4se$oRIzK3rF*ut!+pB>#c4D4lo+m2ko|i2p=#`LMzp zYas8uAVRQxi=f;X{+(cTVLw|J{C$#to%QmkPU(5ox+(;4Gc=V3yi$J5ph3__jez@L$@E=L6k4~_??z{oT;tI zEg|_fhsGwI>Zr+=L;9a8TZ%hwAb{L22|BBzj{j=Fm^%F+f%cBnwl5yDMJ7}8P%iv!<2 z(M7p(K8j>7dH-_iq9zyR`!JpQN2sf<^?H#XL3TfA%$#O`w!hTRyH^{G#m%*EhH}0l z^af(k*2A~j{qTob4Br3cKf~foxq}jx;TBd)dgbUqKFq9(KowCA*dNhx+M)qlRTl_WftNp^=o`5@(>FZ^#!8StT0viKkw)3)N zNNGD8@7Mi$^u6qUmsz4t@zSELYDwEx3rXepI4NGD0rQq!YSy~Dp`Bhd7Enekt^1B2zNLC zvE$0!D`i<9fbs~k@Doi&+Sy0LEOB|9`vbIkTFR-4hxU&TdEEA(BlQ04^{Krur;4p=i;ndz_5pKCGC-xHiRZ^t<1bXHtKV!r z;JHLkvvg8iCovlAPuQ3Cb2B9TV6tr&J+QQ-vUw_W{|2sFQ8`(x-e~7-cU2|2JRjhnEGYFU-vw)$u8knv&m+v2ntE63E^dppv{FWMG*KON_3lSlRkiu!4B$^wJb=QJU^)#r2}UDcKKAWhYo+u;b*`_%g# zFtaNH3uuK8#V|Z)#Z54i)cP}MxfK%Pl*H|N@~0zhev4TaUG@O&@F=W$vKUfTU?Z}f zgyo5M$tt-;h4KqDe-~+7)kS{6$x`2m2W{SPjJ%PhzU_x=pNLz0M5-6M`}teHubSYU z%TN?Wrkz{Q(ukhP-EIhsR=d_yhmn3;kR#NoaISb8Y56=n&9wNY!-uc?w9|bc)Wi00 zzb-yJAw~3ZcCQL}1^ru!Wy(d>BO}h%_S4&W%1y~zb6YEEd7OqlE*T|i-K2ncd}tBs zvO8(HW8iq4oXpdITn|~@@{p%UUN<%8(dVjN#X0z zchc=(m#SRS3ST;hh|w-x69q@k=|qGF+!wg^ z7g6NhUS3fJgF&ie{}STkdr|h(r}i-EmG(^1o$5?X=BZz}7I@24ndK~AskNZ;{D|dP z&%rYkfo(EH3Y?QRoe?7;7AjkpRkQVWpYSVhYMt+n5$rbjE@r+~Fyz%{#8$ORniue@ z;rZmH-O~bSZ+BY@O$DOgl-1VFF#xQu5%^C%4zmn*CmG~uMW}3#s9$8SW=MCMG={qj zNZWfZEWAPOsVrH9Sm3osDxFQK1ckS&tJn()T81V84mG^kYMq7+@Kubvx(*|dLF~FN zbCBM;b8>QW)B~h&Hf|z>FOddoFqn!Hp4l@S>tD#Zjq4PYUpAnDJhy=yA69v|ala z$3+~x%n9A-`e0}!)sbe2+a1>)a3y=j)6x~_o8=p|D&w#FjluQsaJ~5lWf*K^pb!>x zeFZk28)Wqo2(f1bEH}6n!Hg=V@;DvZ7aHH zaLSpLYVG0Oi&LP_48G9z?hzkB(@$#qV;NsB$LaH```+t)0+jP@Ic=7XRDBn}(0Xrh z7G(1-IW3gH?AVx;aB+X-*Ur(owzjs{RUdkH?*Higtrna-iB-1JK|bS714_N+Ui zO@9Mrr4h`6TS08If31ov8>1jf(SbZCY9j8kZ86xGqQj|jR1cR_lRj3m5~f!WJuQ$* zpV0SyesF5vncRH~M01;_mtqGj)Z6bNhi5%xE31&e zRVRR!;8x_gKpBoOO_o%l;J}E2TLAKw^OM+3GK*rgEgd+(wNsGt%qo@4iY?m4XcN9y z!6PX%R;Zs`q!6+0j!;0#QRC;3L#ZODLfBbH&QW%Qu#C^lUdmqKcv2)pkT(K-U zn}pYB6&76a=nA8I08PeVR0THAcPx-X85w0jn-+XiA?W6QFy=%NVZs@SIMh3%v|Z`6 zKOlzhbt%a54>z`&@k(4wH?n&p6FRwLQjtq@1WK0V!7VQXvfgo1MFG&Fs?U8DBEm#) zH$cSn;8m~&pNK8n#k>ZO#N?pQeuGiYq^EllV#Sm_RYBu5zjq6EI}nZy)irk=!pKC? zg;E%kbF!{Gl{X%V72}_eyafas9FUyl zOT5o}N?uxo6*F(fc?-0=g<*3g=u=srKvu6=q#r)X#Y{J6@9ddVzD>R_eVTm{eUW}S ze$jkc_UZ>HN#=v$iSJr|;r12;ilqm-p%i3}W9ZQAenighA}`U}Tb2y#MRlr#KFhtg zG2QUmm_^~Pz^c~dEOydk<%P1H$JBEdx+mV!VH{k6&k0!j^#JF5hvt09<(;mP(m3j~ zB0@%*HPDY@o*#eh$GOIYHldF0q^bBlKN_j*{W)}wnDV<&a-iGP1o!Fbn+84{N*h2X z@`BX0-w&yd$RzCC8q?UQlv}iaeKTHQ;}641Jbn0q2o4oWeyJkBUlZmgF!Z-h7de|~ z=6o;C>$#bzrxpl{m&7WGmQO(`1oH36&-+E_3|Nc8 zL<9&H#PWzb9_FgTcm?E_!RH5Y1+te1X?}RcV;LQ>{)kM8E3IUP6&QM`lSLkG`oZhe z;P@sE{Zsb}Z-y<-a;5#DSpi?J5m6-=kq-GsTgc=g?K`lKJtp!TM4crOHvi1L1SH{f6o0lCzZdw7rpIm}~qP>czAiJ#!GAP%}DDhvXdg@}Tv zE^`cg&yiu*C_K!F=$`~UF$^?vTjJL3eobUGDmZe%Tx3EVLk`=i#*o0_8hoO=rXKwc zt}SN!U#EMLkX#%}lFXb(cfjuRFk`QHEuJ?0#5c~U>q?S$Y8dZ8Lxcg&srPR%lR?Z( zFfSrEDyL#1tHi<9K@PYKw1wREi&jGU4nrASC=ew~7vB@pUGJ%#pO`X23}H(S47KI0 z(mk$HrkB(%4+H6bVsE1s;uCXJadc*9E^nN6*9`z*;|@D|@GDqvqQ&>eJD~r{km{KX z-p-h8gf=+OvRWI(9t?B0HTUdAcq2`u*JHeO(dXlpb5HNz@b>+LDp%l7IUTDyNoUsG zk43DB#P1EQh_a`HfLU;yiJ*Rd?qq1_dqX9np}TxunPg-q!D8YNLVTvflxmvw!{8 zlgM+rBTThX2{%vh7;M1&fu@ zZW{DNutqEw!-AE6`9m!9yrl!Y-*DM8L+9gZjLCRP`)Mn(g5Sj*?T8V@+MqrkA|Ixu z58k;SmCpSZfWmi_N75aQyKNp4wV5OFWjxfP&z`p7$Y}aOH@Z%EOhEb%&%uIe{oDUA zUvR?eJK6$iVMF5nhH25NN0N@zU z=np1Onv{h|;6Ck0dX}b>c36mr>oo(bDDXBg`blY<$U*?>wx83(NszaclwH6F5Nwb_ zmpg@RaE1{FWtUicvM%Ya?9KPc!#{q+tkKlUI1a);SU+{|_&6F_9rT-HRhcL4e#vrV zeZ69{Uo^uTXN48HT}O6Rfhb1iV7mDhV&x!|+t}!@;^y{hyKb4yOh|`r*$fcUpqriz zAFGQq6(ZK|z>NKPC~A*qHw`%?m;^uA-j_WUV#0`UD5}XgmQl~!sOttDoW*rtOarS!SM^CEQAOT#6ZZ+XhGw8HnaZBCxoJ()WHuyMB~l_!(q z&4%fNah6E6rnSqXTlS|dehMJYpNy(dNGbo@7bEw>neE-IF=!|M5_)vzmM>n?>j7O; zdblenozN_9<&RBxXS?|3^bbNr$4XAr*7dpJ%H*;^E_s1&#`CT6Z5ztd+~R5q3=Ksw z+kC6@WM)^BcCkDByRaZl->1twsa}5~i(i(7$AI*)=OMzBN zAU;uR<#aZiD|zR{KbGw^ZjvlCgj9`%wS}(a;v9;=a?+~SP^ukSg@WX!=o0~bgqfog zt^~P&42NS@2f@BFgXAXfBZlPu)<*|MfVt}k>h`^@+clhp43fch?4?&HZ?6pkq@!cQv2VNlzw#5&z7FJGO zU2Limki~+dOFCWAkO9nU6xtl)xVmyZ$i@rTx$doop^j zIU@Jgr?^cN3WP#IR}mLY(S=kq9>ov155liiT7)~pN`Xs9!y~|V)m+(9#XzNQ=gZ72 z`^&388_tBz7EM^01xSn`k}r9OrTK7_6xxW1>6r6q!5yyX+E7$3S~t^Juu7n^vf^v8c~2vDfY5?!71ZY`DzW z!PuRE)yM@blrDOt6A#!rFTkG}g!(G@1HUAJKjmh!jUz#j=&@XT5l@l^e(cT-?Luva zBJpd?UYyR2ay*MZVmym#mSr#CdTjRb|Kz-_h~aBP4ZJyPBgbCTvWGS;+q%}AR{Vk6 zWzV+JNcYhRt{Yl8P_A(x+#H;`q>cduvb6~*<&Z!klwh!99H|iDN7-1ef|Y2&<#QO( zNUd5!)<6RrCRBik2lOivF%corq22TOUwMLH+b*IMPAL|51Ujc^52kXzlzlU)Y zKzIvdoOz?810sf-q2SZQVYvi`Jo{j+rb4;*iqI1kJ!WA)6i=N*=@Ih)|DMo)b6179 zxk~_L`mwlPbDnIg1a@jV@$C1u^L||8Lg`Y1ld=-%e4nuNN5%j_sJFXa7P;0G0igi& zW2F7DWMp@UP5qYFWLo1)_nC8cyg~l`mc*<*c09;gs?|GDoW7xUUM^y!9hKwb5CerY z4tk}o+LPis?voX}#at(XFVx6SkRXwR6bnGjI~}SS{mlQ`)4b(CNIDbcK^@?A*fwIg zqQ7f0B0WTaZQKhI>INQzu&-G(FtK#`o)Wx^Hr(|eV<7H_P8W5wFQSA#hX3UUs;C@h z6E56lpO}&PNme{hv!N2gyywli;*Y);q~3JRi#KUhmzn;p(qOenHh_5?(vC9$`w0XZ zp(CV|7=cZhVNuAQZm!@hgo>|$-BN3+LiG5^2A_d$Sg@=f&F-eM{#_}aAGzBpJ@8W^ z7wA5OKYL>g>fbl>2wl>C*3H`(*GkFC_ezUBn$pL&-aU;`u3mE+gZ>a)JqR)*{{3Q@ zi_A?N{k+?R4CQ+hkL+cs(u_Re;{Yr`kNWznBbD5$x1EARS8D}8COS=p)Px|1`Z6B| z6(PU+%t^q@QeQFKTJ}M+?1ZF6GP*?w8e1&%zr$MVV`-|IYphXel%=+T+{qCqnzO-Q zCC9~l{#`4WHz?=Iwcng%qMl7Dp655$=Umh~1vhE#}7gtz<4(dI5NR@0c)J zu+UWuVnA&@gmA_I&q^|`yFU%dpPZ?1&tiK@8?~5wFMs$r+B&Oy_SWwE8Oigdil=*B zq#0+RlqdeDWJXx*9M35fC5ursE1D_3{nfUTn$Uee1)Q0NJo08qY8E;M88c&%{@F^lSLk$l}|Epo1K!TE9#1h zO#+*TpQj%hzn--c<;A@|aBd?&=s0jbE&QI$@baCLfWEDhMesRE3y2{=UcuS*+` zC45g1vf4HXdIKRZde@Ix!4jSpKN6fYAC3(^?k^BiU%tj6mcES7(%j#y7ymVjt-|tD zbjJYECZFJxe+BC~DOTd`O~fdDD%de6s>K_k$&1pvgQ`qu#}MS-ZnQ&Ra0*@YMDA`K zNv8U1sa!V|iZCawv;m>=!PgfUo!>h2tE5$?v z8>=VJ8Y42_jWbFgqAa;=s4h|*Ek8_%V!e?D-1peZcJ~PGPyu(g;t_IX5Uzo|bi^s9 zHQIN+iHS|YD?h{0cD7gjn`}K{RR2@h zl|Vze{{N;;&5broMU14fWtMlDiBy=8v5z&Aght6Sc7@FE>RK8h7fnQN%VbHS?2_v8 z|D~=jVzRpxN=RgF;s3sGvx2 z3ib06Y8k`TVMQ?-M8`jd>AgI5^WAu zIaTNBNP1RlQT}+L{fl~42p)sDMyYnz0Yz+=J_+Oy>2(GM&WVu*do+sN+}BG5aQp-O z{Nb^}0Dtk#l4-G4$~ljAoRkjwrINqZh^o>87iAQ`Zn^kjOaQ7e{oUOAk2Jh!sIP0Q zSCizvbxKLI@2qdXevBSPr*oa7c~@fFIusyod3$t?TI-gwJK`G5onpHE9?kU~FIwxP zAC8H6+10gdBM6#W>+4h?e;~>aO5)zKWwu_n-IbfkFDqIfrxDcZnPJ`k&STKUgUtx+ zQ+oOrE=tBVi0NM#$%Dqm3P*hXEx`EhuX4*IcM9S~zHQ|+N(QxV2};ywyY>!vuury? zj-KplF5I16usJFJR(X-AMu(tYA;+y*F{igp&@^-`Zjbm4Bb5!7Zk>Y0_LBIRyxjE6 zGCqHqhO3)LTib(zaw(>(Yrlt^Hdns6<#OOI>VK>xd{tW-xQ zACj?5W@B@{)8)rIxBIXIJ)@1ZC(3II^ zVDkIwgoM|{^pAH|>2dN(r!%_!-3)Hzf8Z-VwJE9_vOkw=oIO6iYumOXzjWKLy_jEn znB;Eh613GZn{Pv~HNQ$#AGm$ivC-}ZLU8S*B4A&;Z^&_i>Ix;2Oqm}leVN=WYg|=X z@2X^%@{zW{+_%4+_VnttG0-cDA3LoSrZOpOYZW$mrQyCsa$}vD?>398Rl8-*)w89I zU`8PHqJ^8_cjKf7tz_f0$3Dj_4=1kr*8JGiOfzLaEMvrj-?=xxQ>i?uaatEMWY| z{t_C!^SMR&?vuPgbEjt7==Es&QK^#UXP!CAqz+r_cH^0@$&5Yb30r)Iri-r-b9U1o zaooPWdw!lJ9pgo%f6*Y?7AY8TUZmJWK_^xeNZBb(p1Zkg=arYC*TA>TnnMjoj8npt zqX#Yx^567c%~!qV(-hi$D>p)-A=YaAki5(bs+QhJ9`*9ouqFAABpr7iICvqkcE8}! zw!2HV2OHQ}g$-6+vhJYf{%1tKW9e_|m0yE@%AH^Tc_tRZoTdc_OsUDbjqaLG1|w>I3DeiWf~Oxxgp)HI{oORxNnzJ-+%LBH_8Q^ z(fpYAf1IO}s|>r;argIW!V?{#0WZIWJkscUQbxWPV9*g=-a76vY&T_h^kw;L|4tVe@5t*VLa$LSl>mQDQgK&0;Mg!rFWm))(NYc~xJw){{CGe8W;as6R2>-uVq!4@!9rW);BMDTp{jFK5-#bG}5;@ z{P%yt6-l9EksSA}u^W5V)jeL>O!t>)t(HA%?^sX@=I!?cwf@YD+oVu6ax;Tv>1jEn zJl52d+i+L&x7%ucwt}Mbc3V={XWJ3s6Nfe&X58-@RUA~C^gVaeVYMcA`kD#Vo_m+! zOY^eeuGFs6FL>aW6X}z>+61o z_$pT21ai`5Y&df%_j7FVLCgC*_A-nFNbKHTAre?i8?B{ZxP!|${ctdtzXn6!`w;mo zL=N-Cdu}BrwCv67Il&r@+M#)QN}a9@m46+g@#|K|l2L67-uvi=cXHc#1J?_(Uy-a7O<6&sB=t?^R>=i_s_lJS`ytangCQ^;1b|MgB`udgG_7Cb1FSLn-vB&04ZPrZhqFuJ(qA);53n!Z(WI zx3X++HHK8|e%v%Yo;{{YNi9orH?&I4w(LIgQeV1mR3o2XSQ``g%K#OYE$w-5)+2*8 zP0i2z!@flQE!#$A3jR>R+0U-V1q|4HS^3R4HoW7Wq^zA6N5p7qWq`}DMoMP&pcijP zg_KDTT-(Jyd16?4B&XUwYUiiHtH>kmtLW$YB)M|e;hqdK{FLTxg-%^)P=tJ>sNvf8Ay zr|mGa1b(aTmL%6)`q$bDg^JE6xm}qLn~vs8pTGY81L3Aw!Ykh9$`Mf~Q_eK5$Ay*@ z9J1`H^s*;d7H=sYvjH>5)3;3oqmBW23=vEL2gKtMnnN;eW(9?+S;2YLtTY0S zM3`l1B;pVbNIc5dh{R?N18~4x3&eqQEeJ=PlYOq`L@^zQ*V|(;w36O&j|v=V?!9+ zQe@iWAPlTHs|-g#(}jU9p4m`=^SXd!UMGZ+MHccDgdxe##s-Uo-Q)N!mWP_9xzd8L zQ+Ny*a{)`h6Nv-@5d>jgKaANT58<)M26C~^RlxD&l_ z9zR|J=;B#`;fVk`vV|BxfRH7%03$;n!mzp!!y~5(AfsI%heVh#(-)bXd4^#UvL7rK zLRz4gapY%iW7kCQg%lKo=$fR6IrZzyTQ2)`bZmsDlS75Lt(y z4T2Oc?Ee-)_gh`IlgQi;A z?>$d>C*<`jM4}8(sv{F~!HLp4TX*7W#H(t}!H(#`6tO{saDd`63C06)$N2jGdqF4J zyvtJbp5Nd=wZ@PkV2@tuyZ6%-?WGA#(INQn^Y(VY*X{$?roj2#^?SbKQFa>adO|Aw7g&q1Uvwi_WXVeXh@8wDC_fO6KSm&nq8*z(k~ZEiBWb12L6d+{ z*#v7=q&N$bj1p$yXD%a%lJ^sOd~KJp$r0(?#}p#T3~j4*3xg}Gk_m!w(0jq@yy%ug zd6#48A2U7Ayh4a9Mb~g^4PYwd7a2q$AW<}c5UOl7oY2uzbil;HabHfx5RZ1V%9=VeHyYrdJP>eh7Nb6 z+s_1&grSxKnMu!ef6R~$`mySqwr3iMC5kkQb)64o!^B7^$JI>cBxQi zR?P0IWKH3%Qq*8(i9Lspo_2{gHbp7hnoUF49P8+i5yzDy(vN+o6g%t;!ey}jK@l6t zgKlw~-NXrQcNaM$|HcH7$x4VwWCXN4=wsFp9ImPb(wi3p=3dX(4~imH_e7P(?0H{#?KrpN_txrl34u{{)4>0n>PtRe}wx&?!T>cg!U zWai#Rp70+j;w|+~$LZ69*DIdd$6DycVL|oBK7252Z={&xV_G(!J z%=_$+M1K&7D@%);f9z~_mj1~tMzfa#{X)kgEU&s^w9`ndOT<{BCujC#`x+a zadml>*Hn%wWT{afQv-#J*?3YjB267EDV$OgL*)Y`WeD9cG=@axiD> zEahqV@N#EmeA<_kt39;MzgXdd*XWSwE?aE|EMRcdx3v?e?8VTIXn}l?@(}Urn(-PV z#q@U%NxifKT}~2uD0IO`vNkfU-5oW!h&Xq$`BIL%@L9aKyLk7)bfpOhX&FZ!jRXC< zxJQ#Mr!Tvg_f_L|oSgWgb-U&w{Z2r@hRZ&#*EJaeQo=N{ft* zBFTaGO3p`d+{jkflB?7J5u~_^c+q3xovFnsziQr(Xe+K-T_-ZWEp+zh=ajcCP=}U7)Wk ze+@Hp$+9aGD@RMuU(-~hJ|;|^5O?wj9F=(F!cS`akf=X?*n0Gb8qD7uGLMRq3#x`3 z#2=z$quU4-^z1PMUH+Jy>Q;72Y0O8ztd+I^7gkt9`xx_mmf_eWrzmmNdZu_oU6bUg|z`_T3-7&*u=$Z!E zG5k2WTcWYbi3D|mW@5=e6Lz~>=g;g>y>K{!S5~)H%iDMSD@@Rz7Z^SsK=VPde$&;==vs8=2lgnrIt(faLrJO57fu9Zl#xL%bj($RX`}5)xv+(4qjRJg;?LPUq zL+O{5lC_Mo;Mec8P)GL=WOwLq+Z{UMHe@FgH2<1J!I1)6-j7Ywr2?X}DA2opwp*H- z((l8Tr;P9IyIyYo-+KVxCp5H{qI!x7IXVIx2{VbKi47bpD;rx%0;~|QsXbY_FNw1G zPS=d2by^t!7u&Ds2fnIcGDc>WmIWb7nYcQ2UH+!;BS+*&saI=vPkcsyBj6$GZ23ALgg}k zZ|ZN zF>`L>+YWQewDMJbN@cv))Akj6i#=CVT)Oju9KY`k(cZQDUt6pU>dzwmUpA2tOdMoe?ao44&)tBw~I)P zGT9LKo8;O}zs%YGs}a)egAb%6l!uXsSK;O5W6zVO<74H`yK)TC7g~#SPYtHF81uKC$a$9t=Y8b@UY93#g64w-q(2`m0hQ<#%pxkx^m-!Q-ByYSipW z2wweQfA?L1U+S$B-h!fQJ%iGq>=Iyr$z;(c_{KP$Bf-mH&SZPC$9@pBtwO|bX45XC zN@)aTkm|D4FM>7Hbb)me&G*1{Bb;EQ?7UUJw{Gp|cmvYcv^N?-YT5NVRMJ-!`ju)m z0(*Fr%3ZV1f|L-X+oq^Zf`v;+CS2MXA?-EgVOYqg+K>j|0*#Ujn#vg>gHlBj4DRa? z=IdgFaj+QZa}Ueph#SG%u+WUEC|4i;$?HE0inap&MAIsR?vHCrZO}C~FhUI=xXW*x z&@`)ywgYP8JWxCdSj+=$xoSQPBC}LN+T4(<9WACbWs{lK7)GS+@EiXmb&MqiB1uvbtL|{i`t?j?VkN zaiBVsC;UJZKoQ2XJeb)Zoo}`r98iIp%5qHISPx{`oZR408G(Ic-^FzDE$3$TYYsE@ zrhXJHj{bu}*Aii=66l@ji0KaJd!7*cSUK)D(lYP8?gtZCCp=4#H_DGnlhG($K%t5t zJvAI5oVqwYqmt7iWa(~LmAOHgqCn&*`_Fj#6c{Iu-?#e9Dz8@hmH4b-$LY;J%qe%Av6_4Q`PbM`RJ`I` zAHe|00#B16?u8#+`n~}_pT>NepOHWRUI%zhl1UwtqJ#lEkX2F1R4;NBHnR}D^A>NN zaElC)?~*=fkkMQT*_7}xjUJQL)`~ZCLFRhT7a+e`y20w=u{=(ULdaUR{SaeklE@^5^8<61y&l18S z5E59=n3cx4YJA=|pO5*%m2>7=TksK`VG=VMcxCM4rBF9oDOI6(03p?0K(b{?^+NQN3;!iNNeLtoL2kdslr z{1XlxrTKZHIa%MmB-#D-k>)}bR_x zMh%NoZds8KHKR*+PCC_MO6a6D-xb(=3twR8DVGZi z98W(ry~8vV1ZZ-Z``C0ph#*7<1|IQnuV})n%1Q3|sEk z{q4KVju8A^bM@WBXrExf*W_v2OAYtu?d0g^`_R88J_gnO&}$&?>n)Xv;Oq0c&k&+t zfxCT(Hr8n{`VbD=8R!bghX4FMVHwePD6ug-(p&s{hiViWO>;h=Ab*yazb=1a)~VU$ zg|sd|#?bo_om@++w1lcPR((auw~#|#kHYWT=yBmMI+azCdh;}9dQ9`mu%rlqpT5xT zGhTeEhYZ#I_U;c{?A_6)qE{(Z42bp%+-~LwlJ>9tcSqv&XJAxGOd#^mPDg+pj6=Xdvu>c*eXx6gAcXzLup z=zmCD(j)D z=Uc4|ERi)mPXUF&I#h_vK|*ya&3WUXk&}ZEgl(qc1!E%-(!sBSWYLg-ZA|MFSWVTD`QLsJu}Vb;F-d^HxclLRdjN$4RSw@fIz8?;?EJ*J@A*Gw z`Oi{BZ!mFT7T$60DVSH3u+s>U>_#&~5$*3P19t_qBw5iNhLGpB6qhyukdJ~vwAm`c zwJSOTV}JNj_HHf`N8W;gA8p3mkDyC$eWAn0Yv*|R0%6}m63gmj97hsgS;6s$JSm4m zaijUMPCy__89j_C%I*zya40Y96{f5O$8|w!M1sm-jR?QMPwJRi)D;G#K zQXUnI)!T?vU+SXeE7#m)v##VXvteV@&~^&KQov3W^g7Fw&wMvJL%F0J@f6y*t>$ld zsBhzTefn7L9C~4Ds_ED8Ulv$6e|lT42?lWLP;uN(nyjDuTr#Eaykc!jH3QB>iT}(f za|Q~umYdj-TtlpR42Br+H~UFIQ&RR{!M$3=LGVulRk0u4A&nUklPQiU*?BVJN%;hz zS%Bx9Y?}sYw?r+Qjy&vmTO{(K8x}-pN#ULu(l;FAHH{9PP73tlO3;aDS;dj1R3Y&$ zkXb=mP3oCx^~T5UrdlbC;fh==KbI*9lsJSZZ)hV}6AzQ9RJHpbRQBC)>rEt=c0EMp zDBycD@GWpXe#+jvP9rQmuzR}<);veTYCteo;iKre>Vpn;7G{J{ab1YaZor94$QaB? zlWi)K#?aRsH5LQH_|U@7D7boV{otc)1tYq(V~9N)>mB4Ur^A}!lp;H01vR@`&`e~~v;lg0 zJ?jY|(}-NzzV%ziJKw}~lw8qJ!zV15*3V_p6QNP1Ldu}{U%ug@Ho}&NP&(8%vVTdn zO}qj%c>b|BvhiOp`}38y*T@T%Nt<|bj2q2|na9Wd`$?9sN5j@FqYnA2;-*q|W5qOb zM5o;vNim(X3EY#@FF^HNI_rL})E(%7Q~R^4D$*EV!ca|G#^ujYd(h%7XKT&?(c)ja zff6-9f=!ZsP{a6evuuFzsyQYpdi)Ii@4<21|P>L&X`A~bj5 z%t-kypB6Nzyn}FT#3sz(TihY~V|=bPnZuT7@$M9CQ6t+-iGii|&hy~8xEGMLx$@%7 zOTcdB)Sl2P88;|WUDAxtiC^ru6Nt2-%dSeal-QUMp~CfIKr4mML&~r!n3sZAXCjEj zz!J+sT!QW>7QJKm4tBD-zXW zV2(okR>=kpO#?NfF6+R5CjhL#p(%;;CKPjxAe`s9HRhSb+h?6@EzQ>=W|q#Xl{BAN zPHC>#t?{3jKT@5iN!A7=vxXVdoqa?06=ja;^(tF#iy9qeP7U#eVW&4_RfM+G==MC-uUo$}M7>G3oec`vBDMw11r(e7i8E zA4oA+v!l~$hg8<_nCeZ`kr+>G>V}PrdZczf<$!M#ZxbufA*fj#^a{(3M^SbcW-puZ zAHD3-T)yDZh!*G6!ewJMGTZKFds+P}7i+e9ReRGLa}~e;W@~agU+6E@3}R0ty=`m? zE)ur7sDalpsi;L-5?yb4~$iyrpf#u;;+E@%ztH+M}Hi<#$a%2^jWR2FyZia0RF zl3P4Fgf<;!9*{*WB`yrUPrRqV$~q0t+b z65>KS4U#I*+804Je|@yRddTg>^ZpZF+3FAJ`6&)Z7B_{FN?>L!ts~tiqqRE9KeFHn zjF!{O#6bK_nnAt)cRq4S93Tlj!g7HRRYEiV@NO<#Q$*V29` zkb;i!j0;8b8klDH>UGOG)rDYZ&*_YsVLe+j3xRKon5&exNiV z26@O?U^hfAv5soijOTY#cWc0lDA(CWJrW#iV(H_cJ!h@e#vy+W8)qc+!lC5M%sc-No>2&Flyps%Wsq-wV z4w5*v>d#&?1duSfGcEpoeL4)P$iLa5K#pw+{rlVfeBuU_fu^1?+ep>58Da#YPG$f& zRKVW~aL9%*v**K!dr$Ngi16>pYE^rWcfRTwM(Wz0s=h)A;QhoN8%(kK-ybc-2^SWe zos}oW50@h)ehU_0;b7+B6cmJWb#pN{wuke|J@kclQ(H+p;dwSVHSlMc6bb_Cy&E1O zF$IURgBc!d{TKkXTMdN3kxMt1i?X9@B`A;~&g*VgrU;rk$G1xHpiszJn90e~qw5|y zXpvbfvoAwmYfdS%{_A1hnO(w$-nrK6R#pdoxBk9C2SftnIUIj$pSu=%ee?aFR*!Q( z`J}jIl;npa)8x_fUW$s77U6Ov$U?itVcaFJWhZO-%8)=mp-4aK!qt8h=WF*&h(_+3 zi^2u}7*83R3Fq>Kq4`bu=c&nyp7o*HWlSd01d?!luit8XlS;G;bIo?I_!NERb(?xF z-jt7rDDDBkAZ!+S2eraZkW?(OAdm!IOAyf|iisWhis1F)YR;!BnM=pZm(M5bARyH> z82MThr+2>|9aR!}*9^sp>o2|;fIcd(p1utppq4%OUKzc?>Njr$E6}R=F&sM8gLs1^ zdPV)QKGwO)-!6^c1A3t8D{u_5hKh2Y!l{@Q* z5goKHplq8lgZM|q4pf*vP`oAI;NgZWR~5Ji_QLy9wP&1)ebBH@D~KW2K1OZqo3VAs z6TJY>_#$tHZ$G{ZglxTOGw6 zB={OGm%AOFu8yMbw;RU@j=`^!CZO0K?dbS$6sgWDUY z;@dQT_k-xMew=W|f{BNc8M0zUedK*U1;M|5(I_~3TE zJzBBe$?wa{x3>OK!!X_Duh1n}f4@w)j*s7JUsG4LsjQ~2(INSp+7lC-7tRi=Nd_$r zD(IN{_br{hv33nym1x?+d;{pZECf!HL~K1Rc$NL{SlWoJ%x?7s=;R?T={I(n?QVeF zeT;8>29cxDFlmXdCYoOk`57W8pk4!Z{7ggg+PqnL7?0D+i=SEascWyMQFf@&(7uZ< z?>0hLWIJ|vqp11M6u!+^>?&9ZB=vvY&h*@wek!V73owcxMUJmnb(i)vmbkWw{?OJb zkcYCLgjFWT(ON!esIcEIlB44AT^I540YOWvj3R_`Q;YRIp)CPf7r{`};LsjG&n-;%~My^pSQ5sYL zP3(!U#hGHQcF-!%jh^)@he+KlA~~x;hmA%EC&LX|WcXr(?YV2I6t)N_`+LoTg?T;x9ncverO1;U6#QH*2O_ERAmSGe7kFp(cMQm+@LVppKcfavvEQ* z47%9)Pm-3Twf&Ob0=Bw;jWF;+)`|PchJP+Bhz3PRIX5UE(xNEydsw!K5(EKvGv@@c zk@StZrCowzw?a_1=DuyO(+r^8sANEKrx6&KED5-Z%VKDJ|LxNl7V1Q3V-5e(HIWaC zgjjWG{EQO%RZrmNt8NH0MWhYBlCz;3lYodh zgk&sqqw^A=z*a{MEOe_OsdA0S4A6H%sLDOSoUErKUFJeH2KbZXzokgah2ULpwWK*&c1z8kANYJ0*JS#z2!<@@jxJ?1wTpeV*493c^TMphF7TwP_=NCn%3D7B!3O4D!87I!{Yt>BE%kjxqBNQF)){|@JVQ+$Z3(hbX^^2V(#RjteB{l?QgUuyocE2|! zBw8=iF+kpP)7q85-z-}62(|hg&Vq-K(55tmI@{+t>60PtOvFpV|r|jv0yrUq2N)8vzVp8dAoC4Ip<2y?l$xPpO|T zpgnNcALD{)qBf#1f9FMZOtC`-$B;pL{k`YcdKEQdkzn5bews_ZO`Q(KG13kbQHgAc9F#U1f@L986*I_X&xC1cyik^jXcXi4Y0ZnM z!L6cCy|2#M#0DkX*>T5ChF%@_0L2lHpUtc-&6aExmL`RH5*=>NAn8+RX6p&oq`Uv8 zfFE9i1b1;U1K+=av;;<1sbePV526L# z$?ME;BY-F;F<)1Zou`eatSB5eD#4~e=2q1@F$1G#(tL}xLml@zfR^Wur9TRfr}^iX z?!c8Yn#D0AEtNnFQ3avb_`u7LtI5O)^3;x`LZVxTiX?}FH5|T_B)duExeJQbBNkJa zG!=mV1Jty?!AtFF%IUouK`ork7o(lbDGkO+=7`jp|4tdE6odbvK6+CnB;m?WG`jivgF}Hec`t&nz3>AA&0Ez1)Ob$LG{)_HpQN7C_u za4x?N_pU+~@tpu&66~(s zA_bHKWylpG`9eXdZ0W#=vT>ubC97dzHn;vF`= z5uc%-v7ZuML7A|Jd@ur}bNitl!sWQ!MPu+IoFS#tynN4m+Xbt37s&|f&74~6HKR46 z#f3vogZ;WnC_}IVKEaBp0kVJPP(M+%9rBtEq2?{yoX}iC>v>no(>bR5Wr-q%>V=P~{$aKX9kakep7n%%K-SOs zBhJnkA4A3?z;)W8-6ihMb;aw8*DK(8gKv;Q6kc0|mYeQvB13z>Nvpkf7Y&cT_@i2y zZEt8VKV~*^8x0`G>kxy&%TScIEJ={d&p=pGEnXyrEQMUIekgj#Hlv8p8`aNM-*s}* zPp&Ee+5Sn_ZEMHr#RpXXtnVGN2=RRFeOgMTbLD7X)BjQXw-&xOrnX#v+bv74$< zaXn67_~QZ125fIMLm3xEs4Mg>(CZMb5@PQ-Lzxb(7ske8e(C+B_b<}g$%zvLp>xEJ zQvq)TxH}I)#h7ypWHYy{3M4(;2wye#a$znOQzW*;SY*GCbp+5Xwf8Ev5b373C-GZ; zuVl63m`S_(fRn`FHp`B)pt`DyraqsR_DUE5zEq!A1ZUTE{SrlS5k*f%6DqpHBx72! z>qemyp~fhv<$@r{In|m+PCM^6Y0T_uswy>;L!#d zVnt(A=??M4<$-Mh_fDKP^X*9E5{Q8-17mloZkp-YIke#VuNmX7fkZl2nwwYmn)ZlyMkfaf`D{wn(iI%;rx8qPEv3{G5=v;sT~5^xB3Tmec~9Sh>CE z%lKPlNR81B#`BsUW1>7Oy&jXu^m<)~!C_6AG12Xe@Qa8?UL;>ZJVcQznfpOr84|E5 zBFJRBbYe_8xq}AB21#`L%P!>|z!5^InDrveAE8%z9GKidn}SbYLsG?oES4`GSWD2W z>r59C7eKJ10E8Opc!zs&w&p2g?(vH7O2DC)+{kku&WRbV7fgKzNqW(wO&Ud7yb8*I z>F6tXWv=Cz|4EBdY129GHyq4ib45+L+f3TFHlp{HrW#fH$TC_-rB? zv~4T5FANWh&ec{T0@m>i1dR0C{-VKHwu_UC=^M_jPHu?}q14%=bt5w&Az` zVeQ6b-&jvhSK}{3;Iq5Q*8;O6SD zb~fKID?=J}tTaqhGw_(LpL)^|I@-(HWAMMyC>PvXPAm7LUAy+9S2_q~F>3C8>K65b zdVo_}A+nmnqL5_S7g*Gkax>!|$TN?}%Q z$O(IWLS2ED>}{PZri3)5GY5k$z&8B225w_-nrXc}?j6zD`$kELJ$=rJi%oY^;30fCo#% z^>59p*3txaqRplDp3ie)V2|N*k!9$&DJg}|mS*}ZGxnsPR(w9E@|u<;{Bs&Ee(dE4 zYOxVU{YvC9NEtQCidGSBOXV!gB>}TZP=74P-f7V9Xx3gLdCg+?ZEKexJ#wWK$KI@Ru& zp35<(wZLX*`E)XDRS8g{vsVo!KW#!ob;y2!m3xZ*bs!DXmjAwn(wD@#@kSzuye`A) zMtvSjwQp5Bl13?-faoS`NX>XdfkDWJE~D#dB**nYqVsGB+eN7F8P(wsxIQV{IPb9S zIPbXrt0U`DT=&W4^Mu)59z^`|4HrONOl)MJb;&iSq&A^hXQc}vj$14vb>Ca+;hMlGgZXDH;ZAQd zm@5gaY^ZRfxF;mWwhur3aZ~88%$?L4q`g(W2_bL0rO`Fw*F82RKz@7uF)F_&?;TfP zs$;9}uFl_VDV;drRn9O$5UW(nyk4fubh_tloESc`{9*S=KU*FhuR4ykb<*DFleCU|b+3GG z7E?Z`X8Pu{-UnXKuXFP%y4&9-k#~ueK@ld98G+w40?PIGZGUP$H(!{3DAj4b=$>z^ z-&9>PkW9S+wu2*4-z;tZ^TcK}+HaZt1%F4b(Q@VhB@cMD1NLvR1qQdI%?}}wb%;y6 zM&g6Yad!tQCb+mkjv@GwkP$;R1=CuBHp4L&EdX#Ioc5UBZ!rj5*I(;QWe#d+GS?IrcE?l0DY zWv51YP05g(V!8bX?q6iLHN`hO@L0}$13M*vWi9x;O<>fDZXbx4_mJ$Hv4XDMe&Q^S z3G`SCm^?8_ZLqba6A^eKlgwc49X!G0UN#uD;!1f1Db{4k>WO8vbc>v~&Gd_%e{sb9 zBN)~EXSw=s_LrKpF2~nrti^eo;Y)D0_kNlE-IlnUiJyxfH$z*jQppGv+3BAbt(tCt zFDsP_?Z@py2|sF1tilE+e&uCOB%-3kenWG28y5&NRP9}u=-SV1( z?$N8SZ^o~CBNKW&e6i(!T4rQeO66*NXJ_%mLI!-<*mZI730tv-8G>1O#95IgdsOFG z4@!!qs^j8z(nLhWaw`*sw=~}DE~at+N8J|w zw(69Wj2ZlgN05?F!a_-$Kvi@rLC1%b60Du9CT8^Yx|EPWp)|1+1i|vn&ChsQ|mM+Iwl|?J; zc#Ig+dMTBvsabndhp&rgdC_O-*r>oXHl%cN^Fm+*`|m{!KvZ`;8SY!=0*&z(KlaUn zW0PTIz&NB8Rq>;e*2i&PeFK58UV~&PekgMmux`>XlrZMbD1PYHI ztKzMoEg9s}7zZQc0^yR<>_%k#-C0-R?i~O!-Si}>Q{Yn& z8H@;g6{q`N)$}Hcx#xPx0Gw593d|~n#R3%f6a4ylg`&v-_h%2G;Cf~mM3WT+36g6p zs}dQqlv3j|d9le9^w8vtz?28M7#)}ZRhD1sG>+-LLOoGzf(T9qgmj4dW#e+jfBf#i zQDW9XN$-_Ry`D+bp%Y-yd_#?oY00POF#QkD(sov^5b}(avf-Hf2F)&@StRsQIwk{^ z(l_|lRE|tJ#xrYLu=MYcU52%l(o`K$4~pq$cIg5M(~w}ZMncn8&Z^Y%b7tFPi35v0 zG<6b~4UVJ)E_4rnr7y1K3P1CoW`;b0!Jo_$f-w2yDG5!lRGEOhc)~M&ahqhDx_m-O zH^nuC0t*(CcRqoo3Q5y5p`8VRF6i5cHsu~Y6)pqYER~zV0p+dov`_eBbjtHVMQ z`t(|D3@YbDenlX=9}b>K&!KKe-c)b|%f+LE8c%Nzx;j*cfa>@aRo~J?kc^{C>EtQL zY?Za>S>i}QutVChT<{%Y^65Vk70zWHEoMueZbUTLy0gPmt)ePw zz((Xau0d;(UI>m=2P-xt9QNpyyZIbTyWtAC%(cTOJ`YeDpc>zEvP$}%lO+`xVM%As zDc6!%pszBwGT^s;|s2!^0X?i`L+@w^Lt9u6xyavPKo0xX7OcE@Q`h zImbOPOi`_(XBoTV|U2#7z!s-7-{y>Xusv*^-DPJG$lRS+4^B0Q;5VPS{9-|cIr*6Gurw5cj zb~3S5m1+CSpODHaisIhJ-&L6WX3wSagUw~hJGcRLOCmJL6GZ9Ig5TDUp9vK9e?b5nyo*M zj4k}rU;ZHi9RO+h80)P0f|O9&Wi4%fDY9jkEvP>kzVO4X9dZ%;>JQhz8OsDO&`<5w zE&yW=nDMX!d1$bMX8leH|6*T{OGY*acRlPC9QC1rR#D8vU>QE@{&6VAe^os=M^_ShX zE-M@c1b)7Ard>lOGTC+Sbj|sT0ROzgvG|2&H=j@ZmA0NY{I@%1ofyBYKE}C;6-&@I zI(|HC7-#c}&$7)=AL^uACD7#>Fvz-r-1gJ=6yapZF)vrD{fAgKg}=l zbl&Q{=$#QL&8WV-&6Lf!99)bv^)_u<4$U$1s`g@FEB?J9S7=dH)khrP1L#xz5Fr&l z+WPcm;fa`l+?>*y-uHn=#y}GgzCgr&I3rMI@JtYL5Y&DcBM=l2FA$_aFqA+pz2xK3 z;pAhDu7lTJg}ZK(G=9J~iGQ0-F7G;w;XfzZv1b zq{y)bSqs2P_*kY-$XZK2)Y2h_xCQUO3cC6X>}n$OxSqw+_GGK~@83c+iS+MX@*Wn&(L=~piL zlXHumt|p#cko}h-*q}mgtoz|oX20KrYzghKt;%b1c>ccHGD;T$0yJDJsvA6=*lCn%WWOilpK%E^)f+D8H;PCA4zqewh|MQfMEj0ua5fswXEC2BnsE(8QN zK-J2EVSq!UT%j?MN5M$bGQCpbjs`5@ilKdSyEK!&K1A$vc;tnkI;EHHC zXLbtZtmU(nJC~2+tdsI#^BBvL>z@Fezz%p}-OqwFo8Zmk(r4#BJl7%Iw2)YU9TA4$ zQ)qUoyB^nC{gQ9@?}EKA(<|M}KLkvLlbjFm_e%h42envWdI3`I)2RCtZ zS5p^jCpSkI5|;lF^{Q}8>W&%?)~1eT<|Hia|MyDzPf%=Fd25W8wZ!1N}cUFjD&0V=!3$hkO3dkpE``Pm|$>IB3dg3POrp zI(f?9O;J6%p9DWY)Bm{%D{G3;013d($->S3|E3czdtklPmsYt0 zW_moIvZj9d=XW$&WR3TVin@qN|C0$N5syS65oS&l4vy3j(dC*8w%ydR-jtw#m;<3* zwAqZQXaaB2eipd)-gcc4_zQG7 z`136kPe)4ZEiBn@A{xU7&)UKm-!H`YhK&_<~B&px#o|;OIY|#{` zJ_o#L+dZ{C?{q741TVyr((m{%LpKz}6~uH=`Nlk9s-??T?mlASNnBYBKXW#B-8uve z@`Xqs#CE0G)3efkp(`C69b#i(VP>SJr)kFG8)|bDc1l)Dd<%U$<@dX?-2s7@mKh{M zqQ6b=?v3x@XaspuWm&F;_bgtM1gCF$;P;PxaEGEa(`^+3bkx>4j?7`NFqaVyDwm#?4jNTLS75NpaEjh`~ z=DIqX1g#*9T|facty`dwP>?atQawW9#gBE%@xpRs5>MzuG(r(!2Qol|@y1oy1zLt& zpnu~10QDx2`pOkZ1H|Za#~<&7y-2;-e5m=+1%L>J=l28V7*k#JlLq6T>F>68m#LiY znrEmLD{sr z%Bf|1qw|xy1JU!Jxvdh0)%*|IEZIx>$I^flpSZm?gtOn^2+c(fi()LT$Yk5%5kc4&1cTVTF`3gQvU)U2p-Rk(^_7W7l z!ED7K@3r0L-((<+Qsd(H)6CGD*P1vOKe5iTIkHh{{h2X7Zn9mS{HOb`=K}wtoM4us z9n68UEpvTLzfu_Pr?qCE$C|Ux<6LrKS9n0ml$@fh0g$?p{o#Ihc@ROPf2Mr)qeL8X zZUn0tZ0+U8$v4MI`H4Y6f>dD~ae>mDA?PsxtyQlTvN^X0GOSD3>`U3~N8~Utx!Z+S z2lnDP!sjd$fJZ#iH^Q{9c}slr{z3;Oc7DI33tMc&*hKK=gx!>KYWfAUU6%zkvTzPy zH4NKv09`mft_;3}566}l?-x=8L#yqg4oGcLzA6K>=`;?S8#P`Ax5<016m@ze_mM~Q z9})g>uN<1w9z}!|QO2vYSwhSjFH%#UFQg+80AkDQKg!mq-=?&vA;@59b&@}qHYW;L z$FxxEbmS+DuFPCkz4c<3iVbiaBRc{+!nI?GfImvb*O@fJ*4RyTYW?dK>}J z_QAZUrv2LmI_fc1pq`FJErEr49@x)(szsvHc|WTy5PsWsC}AMHL+^y38&!Hi8U{i; zqnkooX|M`O7~kA`pCv(}pF`fyM&Cl|gu#{keU~d}Zy??W_)No?%Ofge#w>2NbzOqZ@kk$FYlAQ`NK(Sxe(V5CZz_h0A+43 z`~^4gX$c6MY=iQ zo=a9_PNlz5Rzhc>13Gcq4_um|Y9MFcT_14P5ZM;p0!PCV9eH#hcLkWm^uucdZvlo* zP_O>W6OjksBb6fq&N02w?tZ-~z^&kU&i5jwI&xM@>c0`Wf)!G9!F@fX4I?x?B*`}7 zZ?3}s3pqf>zqV}T0q$P{qZH^q_1DzfsfUM-KzR>B+izA*M1NJ*p)Ayhzv0|iAx@mz zEY?&{sHz-4uA;oOq`0WCAU`iR5zooa%8W%L;ZRTr_$G*@x^{nUZUXJr>cFW3SDz!?y&F5KF?BGG`tiFNq&7gK z`rP43^?k1JB+`ODHd^1dtgq?3`Sp`L!C*^nq7TIV60&%e(5DGc46od>YB8Y3y`>M$-Wcq6yT$#fV zKuhL_p-&aWXsTfBuIPr^z5K1Gxd3tPTMKmp;wFUE$n z$sx)p|1}~`LMOwd2JvZkUp(HIlS4>ERR=hQHda%AX>Q`;0j49|%?p2E0r*f8z_zVr zd_I6b7$gYZIv}DY;OX0Z-h9a~pe3GuB<9Ck`k2Ke=5RX3HkZU~PR9&qUmONX-b*zL zTc0{M{4?@)Oa036ec1j3(d|u20#Pm;PE0)W^?iR_6n;ud)XN4`YM@FQf`x`Ye(@B^Y|#dT|7NCv)Jc+yS|i!y zX7-JbkM&oM@sDYqehUi?=3>nB<_orL(T<6001mWeOG8*_*s^#_+dyjblCZ#sx9kTA zB(|lyesLP31E~XBJ$(%~v_L0T;_*PO44Sw*jITRyw}`KsbHRW7{XFP{*EP@Y2ic}> z@x+$h5r~_=UqDEtp$rKnfy5&a51NjF(ECAhBn5u_$>ZZ z)y$!)S^PIuGx?z^R8uunMV`WfVpGr*4Vr>0ICNTI51$ql=sP5EhoP?*L2X^2J=Wpp zM7{Z!_9yc9*bn7%T7H1JU(^}mCWC=D`Lu3<_%nDP_QijLKA#Zuxf4Y+2<7Aa?Be|V zqGDg7Y9a~ZjWvN9rY2rjQ!}y7SEXTTQOY=@RI-duX>bH$Lg560p_6SsWgyUx)_#u8pOIu zXc~SSIph4P*4DLyRs0}0*2b#_`9T0758oPalVEC^%;WL_sQl0ba5yplb?d3;(Ybu* z_WV$;Gs`F}z^xd@@_v+`IxK38)*8DAM*cwR@Kb*o7Vv-(hffO*nu?1+s!)c5p;&22 zS#hx`EIXv&Fc89-A!p=Ze?0W&TEE-vPrgsgMI`~ZJMd59T+8Hk``xaCMZ z+UI|?Z)#6y6&Gh*lW|+dgBicoy{%I^)L3-~GooG{YoJt+Syj5c}k(C~+!lln1 z!1swJe`O@1s>B#6^+A`;dL4|6M?`BPq1Fho;BS$a_X^%_?;)?kJHULjCpYJD!1p(; zCjgUG{HYVDW)M&fxE})iI>wpDVL=GvcZ0Pq>mlO*Im&s@d&fKz|$tQu|l0<}4MW z_!dru0+mr^ENE0?wYxZhG6cWT*XVy+WrK3CC&KXne^#MOjTwGviEcvb667-oZ2 zH75oo9MBOq?%x?g1Sx2GpMK8tz6kKq{w#gLfz&CKn>w{Ot~UtyKB*(G-_SlD}H-M(b%sicw+Jt2Rtk{~YGanq4Ew%_!|5f}seVGLNg z$AS5LDCSHkp5bXMZY|zmzs7#8`+Cm}PN4_Jm5e)2M&2B3zzPR1jwKN{j}?nslyMRaJ>8TBj2Yyegre0hgUF7fbGk$l2H7XXy!ZES7D|=J+w(NVecV#QGUG+adfCI>x z#@5UegMb-|uAw177Qa!LI?$rmzF?M@}+_7EfOy=JFw!upKQH9Z^q)~J8S2)eEgd~{a6As zi8=U-Up&=NUvS6o7cPAB@jkBFOR)2{-|h4?U$d>PXm)YHToCB;bOx^Q+~B#>d#C>?g>k+8fcJU!dGpKO zm;Flh3nsT9z+7hX1sy6bXwvDM-IY5K?gpwDz#oZ@kWh&$cUXUMm-UeKi1nD2v$}#g zkBy9+wKD-1-@sHw+18*u9}SKj40fv$@z{-IeSRm%6c?}?fIZqo>*X0Zp=s2vGH*0rZ@yjmbBoFwqO2wG zQaBI@g+pGCjqrCT7DbKK>9kox9wHJvAvR{sqVhl}uFJ4e4OyWwfP!w#;I)xxC5kJx zHGGg$*(yDuO0UQ_t^K5;<-^!l}c1JdqMSkUHLPa+e^OiYYcsGB);E ztQmH*m!e=TUMq8-(W60S3VuHiVwn5ytDkL{t^yyyWfUbVJk2r{+_=IRP zAOmm6H?W3TRA3BgO=bzrrT_}yz(N_-VML_CC&YgYrE(z^2SXYLdu$}A#%Xj0oQ!jp zOaoSniNRPfo2+Jd!XW!wtX7M~YB6J^CHMxkd-zH%TdCD3!x=zPMUa3wLf;*u!NyMYE&1zfJcT-HEPOJ#(~%>J~SjJ~;m>($&RAUv-VbMJJ2 zComkg;bqG8%I)}8W(U5X>BD;%?E}gOReOIG`&7SGy{kIrR=L$C2SEdGv<9q8Q4d>{>U^E&EhO#P(h(BDKUszgN zR2ZtjLbf-^WoKmpv#3Cvir0cn<~ruY&H?5=Q8yk1g~CGxM+%sN0sPlJ<0iLBoDP3D z9ARsxh)L!fo{Hii9sGO3!4Jsct^bFC3xXc+_9#@!s7K)nV7JN}z(mv|vflJQvplBv zBW3E;K0zO_`t6czL1#LXELfZ+QxI7~1>^i&vc~MDTNW(2e&K}#yUDM}5Wn!p>lfDM zcS?fxmsA+1!Up%r(0B7D*Kcba`bU3SsAkv&m*xr^hQ3G#aMjYtXIhh|*&E*!L!3U zr%9`SB{^{a1IdAx^;(ll7sAt$Fem0FPcxh3)@ExM4XAf6H%D5mF)JN*UZI56YPHa4 zXs;0kpuKM2{Ri;ObbE<>a_U4CRo!Z;#>L9CQJ!8AuJ8k@?&IBy|VCqJ^eOin?`E@^)%ubf4mq1TcH zA+z76w*Xhbuctm_G5Q;{)hlXozgow=t8-<@RRf->6kb>m9a9@KH&W4bUO6%T)>tuq z7I}cf97!ExlOP>U!haL5v(|WP8S@O(f;uKWCOlUD%Zk@5FHii7mClH_viGMMuS|%1bM*sJy=7`pO5a z{Z{QyEB6JM^VIR|aI8?AP*nnQZidRHABRdn(B<;Vj2U`X3$m`Oa>9h5DOjf+z@>Xx zArI#b;M+xStSlHrm8!Ypf{lJ6=&~T_7B+_~vu!~U)Mn9cbk%=|EnV3dB;7g{tBh%b zx(hc;oQF!cn9}|!94F`)mV1fB&(r0QR^mt11Fz0c5Q;5k?ayd`>cOoPH?ZNy`#@E3GA3 z@6)MIkR$a4nv{R~qQBZ&4ies;kiE)VF{0_Tt_5~P7Y!;>Ux*rmy{6I%Mg=4loxBZv zU`(u zGmhT!1RM7OQ{3)%+g(+e731QiRasu6#TmYQ%0&;ghpZWH|KlJG+42GfHJ47Bm7gz^ ztgPzXMD*nCAkCi-^qPgr@DlO7M^*O+9?fH8s%W5+>$PlfU+mdzz1n@V^)~lT)eh_Z z?kDp1sh)q%*lpeG-tT`k<5Zzdi(NPevv-+pbu*XcZOOYk@6n8%c`p>cRrpb%TAKgd z(R_LSGy5!#?6M##%f@W6UXv&rz{kZ4@O4>8TO4F{njlK>iS7iO$gb4uv#j^};i1ULr!N%aC;Jv|J!9&4gL3Pkw;n;sx5LA-*F6F(-L&{@HPU$MoIXI#h z;P}j;j|r)QhD*d;svQvZU@*UxD%YoEl9iOSai%j@IDpDrGL;=ds?@irBy|#%!sAMv z+GAGdsVj!n2uP}fq*h3J5cwgAC3Toex2?D}SgMpKHPGZrrFl>0yV7)#o&(vKOg&T` zVdsB8^~TR0J^s%4>l!z2Ub0)z@D6Rp(tDcj?e8YD;R}^NnflaSAaNdE`pOG!~?Ou~QD=R|6a6>6tBU9m8M{~&)c zLE|d=t1fkB2dmY55$mo(eJX$K5+!aykhh^Ic?<2TnpM?T%-$e^}ZU^mn# zoZwS9!G(znJ876zUt@DZavKS=kuZN73B&PGpB5~$3PL62X$_ys5#^)STr8v9tfk|k z<2!a7zi8FMobhkn{A3TjY&mjd%aW$?pLO1S&#EQ2+>%sv-GBd@bq_xbJmDjti%%(CGX<_(1CK zh)td9&sAsFYQUY9I)yS*|3G$lY^i@d>CMn&s56)YFfRDi-}`+TxghEGWTj9+J zWQ5GC)qbxT<>5?)Ar#ICPB14XD9j3l!94*DFt0sX7@3gaDmVkF{5NDdg-k3||AbK~ zNi2ZW3z$4U=5#q+c9+d%bt!+8UXRb?_XIqgGBcJH%Z}y5IHgXn)ob)>y-Kf8vaygU zB7&v_=#ML-BFY6+_sz>0jVP%t>8_B*Lo<;iz=JWfZ*ueFD=G_4@5EZvh8)Wd*ZDhi>X)sOT#s|tan z<_Ktn&Ezy1#_mKkxS7ZN0KQ>QJZE!SFIr32;ntG%Q$DR44W561->Dy1Ic+5jetMr> z=I?tl)Mhnd*dt3u`+=6f=ohS+gu-K~Pxge>5!W!s$7!Y=rre#3SRy>9)tAvjm187^ zee(zFe!p?!?|a_Ao%*}qz3ujQ-@W~|cezi#TTM9iLoaPSzTuK%m%fDGBg)F@mv+4W z{*HV9{AYme9?*Yb7jQSDFv>+IMJKRokIIh%7LPd)^^|(*J^SO@9CPMC>PwMd@BWFK z$yDd4Z*kuiU^GU$yQGivdcBeNX$VTduPCGV@Tr|<;xDwtO*Wg^F_)7GBY)Iv zjMPM!NW`g8tFxmP zDMF|6?%Th+l8sw|>#l_XxE7ylEl3ImB~lrV28+tb0%;SYM9;`_H2(DQKTR&0 z-ZcLFyrQmbH-AFp|n9-N+_zVP~rpl zI1*sgSbRZFm7SGkhUs6U72L;M*hLl;c)FmlypD9oGx)0V}J6$>U4iuizxz=o?<&$)E%==B5|$1d5_GAY}?b?VSp zBkO+&Etl0@J@mI>|Nivq(m|jQD{wt0Y86YC*m`U~wy{Qyem>8KG>lp?9|O-bJ8id_ zOd%)IsI>?SCZ2EP5AiJTa-Fwn1RX>}9|RyCtpc@bj7`p*`CSDFuEJIwnWw(ryFTrTg35-cqVQ^^Y2 zFb3Y1b1B9n#wgW$+pxCGpl1MLc7+LKQ7_sg=7d~DF3~mHwba$?`iVA#Pn1yJ6|4EUqu72FlTEizFt_KSt} z)^02Fe(=rc7r_(J?}Dcy%8RmB=Pu1(T70SD^6a(6H)L-v{(1II#XGZi6d&+sFlsWm zm(a;lqfn?dAqM&5g-(HY2qqJs;lF>SFeqr_L3B$@rLJU@Sc$WI0v5DdUb92frvb8S z)GX5M(j3t!H15*8ptRiI7v>IykA#nhPlma$t0bpw%*F|wISHC!@`R~#kZwDk_`ZF| z)-$#j4c9 zFzG?;AlopdWJYC5OU%VZV$lwc`s*-uZ9s{K0ACGE zj#yomdmf%~$$kdAWaCKCcq1a>~U@ z)S%1>vhJWp!>Y>5%A!%5){r4M>|&s_z`hw2snao^U&xFRe{6G%iJh)S`9e)KQJ+!u zq3R>mY_)4LIRqdn&hqpSuNt<7kABciqN>5+Wu9^9e{qJRwRP>eCJrS`9CkW!oH>EW zGl-Ct?R07NoI)4P<}!Z+SfO-j9RZxB$O&Ml-c6T`aGbA_Rk_yIQPWDT7HLyoA`Tu^ z>OH9IJ(#H8Ow%Qn^g9)4fxB8cAlalP{a{PNcB{ok-$u5X9Wn2tWZ zsIzuSu%c)Dg0jhU*13CDao+OU2I_1q%uP(Fqro2%W3oIi&Fz1wZ)m8moN>X>KEi#N zpNY-&?L%);_f2*4eA&w+&!{PiK{tV1UIkRHLk>JetT5X-yVYT5U&b%%-eUfw__OLQ zo$?}8hly!t+PMyOhjx{r)6{NR?oiu;tTCuzbsCjEhzK;u>KCyy9Ml#Kw$eU?c~pQF z13MmIt`(i;pi+N?WJ(dzbSV!hk0_5TPbwA40sP?}ClFYAF9Xz>!J*c*MCnTFdz8KC zhS`f9*o!rFYAG=gBaO&(<-cw^GhG`)6jc76YYjjpH z)LJl9#4r)<$_KiZ$_lcnju>{U$x&@3hJ`HrL5k-^GdO>=T0IXr!C+WppbE#)8H!g6 zCI=wK^dyXGN z7{`-Gncw3)q(Q}^^EvbfbR3;T9D0(&|IPdw{lTaTs2J7L_zt8+tFaGBXLN>6AhOB< zOf-n)g(QDW3Vz2EbA?%)H}q~%7zPNDPAXC8IknBnIc-@?HkW0~bX0H^wkg~c+dOWbb-r!B zYkt;pd=a?h@M__S+o~GCR44Z99J)`k@=gW8b zd~R1ra5-;d><(cdHBBtCajalfaGYSXT0z0iLP&7BtWKwk!3>K9rvuy|IWm|PY<5Ut zXAXg^?!djs2{ueWAijh-)QmCP%xN<#gYJJy!BS~ZR_cR-U@+*FT~6%$jSGXiE5^_* zF0r80C1%A-Tw*j644)qimm3VDsl-(&F3!SP2k}EN;vIOa=&;Xa#KLhU3`xh3bd1QG zNCO~D{Vqk>U9gFi&sqxO@(X>A=IG=nEx{E1dKKSVr3&W~OEP^;THC`F$7m_#CH zjl8WXeQQot@h6C5XprnbtAE5l54Ll}L2PM9Iv#iaz#eoys19q_o*i~};PBq`2rt@~ zrFN+~eq7)CrjGk5-)_jHiR8_~Gd9(|qJ9w46xl z@*%09DJZX)rbF;OXRZPmb$);L=R{H;`Vr1`N|U6MDNP2(tN|Fb$zvqP>&fY(^+JM! z1u&1c058qNHc_wT_Su;%J9ev$8mi0d^VNF2MjbLr3tT$SEO~(|Gf4coD1svmLL?Ft zLYdfZvPZ4m;7?HQBsLP#;lY6*+$fkagxUfT}7< zF{`40mDg6zlGy)GM|DIk7KxB0jM?lrTR5d6YT--)WL8?GbDh$rn?)WG&mdds3z(rl zMCR0o@)ii)=Exj%xRajzEFbZvcR8+k?4`@ZIdoy>g_W~j-$@U7enmx`%kHV0zn<~a zVQ}N@RZmM!N!Ow$sQ`Zm(?OQWK_~YGwK`*sjZF3laJ*=HKl;bavq^>?wyVr{XUZ@VbEJo6j3Lg{w#Ha83rbJV?s zznj0^d7JwI+XIj`95b7YE~^J4R-NJUI;7Kt=sHs{Tcz8>DZPKcasRC?u0cM6OEdh9WiW-1!CJ{Y|IsZe$+wFwKy*8T{Fokn?bS_HF!cgFi1pda@h~Iz62jd6H0L+JRM26#_bT(c_>A!h^4)|Tgs53SF0lh$u@CNA--coyj9Cs}d z3P)6GB%R@<1ahvGFL{~NZg-?7B_@^`EgoJloqIQ-gyfD<>!qiE{?66+T(J2@k@&jr z+1Zu+`lB^_&U<8I@>NEgoHlmZ^u-@taBpe#J#>GNV#l+k%}t#Z&3BMfqsc3{M`5&v zQ9hnQ_RYTOH;&6DBeudg?g{je=ZPpg8!d7#Lu=ftf<5SR_r-ZXMmM^z$-6UlPvU2J zkHj8JJeX&?KaB6r-YM+NW~C*PjL}OZ()y~-_L97`DlMhXCQGUL@=_{tXXZL9&ALjn zDIPco0-4aqouuJ0-Hfym_dt7@`%WB<)Il;|=?SUPEz5sV5a6E7_zy@3e z>CJ}Gt0pta{00-;iNP9zZhBnq-)6vPuBP<6t%&TA=BK{sk6IICLp949A4vUqclH^l zwIyC~uD2CT5)+7=v2^m-3v|bU?mHyA2~vMw-TL9v$sx=>dwlEBJMK729_Hmc3Ag?3 zh4d+H{I91l-uGlGIsK+1M{c_5jW>V;f5@oV|KdJDxwu69vDM3mMeo<{ZzGC2*R__- zRyN=XL}op4B5o>=m z3gSj28jg59R54` z*`zWrP^*zDGSefNpCPy(z;}s8wdk2?7F3Xy`)3w8Ogur7oP@%@~C_)#KBY$gkY0J%R5c(}NDyrx~H_``>cwv{j%hToU@?B@Y#&KA2 z*!rnmvzT9OT5Miy;f^qvxAP9sE;=|T<8k<10iQoB+fl}p*$W*FOoP4F(SjG)=R2-* zJnVRxdCC5s!+N&LXHKhpW=VhLyI(Z%O+4mHsn+L{wZ8jBKWKd25e7wwl6M$XK1F0Z z)$@F^o(D-ZM8ji@RJr>~lhP5{F z^*jG4{mws1zw^ubT?{nqi$%XP2C6-4i?N(KGZu7{pCDLb24&2kXYhZsIDrtTN3F;O zC}|aoj<|5ZgFAZt<9rmSsQ_huc*`YKDEb&%Oe z)w~3#d63O6=EDP7?V`KXX#a{HoS46t5o4t=mu~D=2)1XSIxAwKY7Xa$wty8Mwqk3H zS74&IZBrakaG<+^qJV$uar3b5wsx>}?Wr$8F(XRb&|0DxQMGMtT5Vg~N_09W|K-SO zVQG1g$fW;srR}Vf!fXGJ0i*S`7%%ZTNS?{evqnW?fmj1d!~o;mhXnNk(FiUvhy*vvJesFJ$|)iii}k2GKQ9st3P+Kf zcQbBR(ZBZNAIX2}8DVo&bpZ1Oe*(Gk^8v3;_n-@`ROqXY6$ z)>0EWR6x(Efc#(@KBuBmDv9#NlogpB6AJ@cgWJ_uI(cHT*KYTg)Lzo;bQ?56QEe9f zdn}Y&oxF40R0XTigA7qrzZ9=5pU$y%tHsW8)64P73x9v)G@JEWRxzzSc}ESo%Y;ax z`M|eIu~~F43UnzqDM1j=h?~tmUC0|K4TpVRRzp{8js6;9iwUv{c|ODVG*-7GZn5~> zC3(ieKp|5YFD>=uW#*zBK8MMP$6~(RzyR(PtK6Uu>%x(kyA;KueuQ*xMyC$Nj9&be zHw9v1EgOGB8r-DWp?O1dTyqk5Nog#WhjRH`CYNZb0#N5;FAQSOuBBUzglFBJh2`ii{B&I>No43?qL7GVE0?z&b8JUs|aQjvhYTZQtET zk8QUGT&|%bBO?NB1}x79EC*4iSOMrQCFmtf90>V5fZjjJ*eoU3WMVoGK@eH7bB6)$ z7PBk#z{XLp{gRhjA^s zf5-V5|Cw0@W7cf5+nwBt=6@N#v9Lea+@`;uc|`Gu=6?Mv%2(9tA2T;7Zd7m9UvIhI zb}MtYLS3#bR~Kul^yB$rbFqEAQ=P-a_4$8%)Eu?vJ1ZI0)5b%5zq#MiZ|k!kavpH0 zA2&Y1KVbfqZex7hD--sw`;Tk37~_G!);t_zG8@UzY8 zZ0C4mnWfw|&N<6CjjuPWbxN(;qx7h=jhU8An@VYOf$(548aSkKL^+Kqs%0~x zu$3Wlxrqnm+-_EtFyu&<6+C8c<#k_O|7!A8T>9$uUtT}=%V&0fhgApaPos4 zxCLimBc8t_`Qan4VR8W7{^fajxqgh1 zyE-#wR0q_I+BMPvt06?{JZVsQ;Uvw4DQOwyDiOUm>Z?wD7NWhF}T@XwFg zB&yXS9+V~psK|-b=O`PVtkmcGBKD9yY#*aG!31w23nzI@X=#a>$krugXT5?6lx(wd zWJM*-2o!YuE4lo|hZbG?`cE6L|MG@cZ&2AY2y-+!u=4kp^&UL045NP!W?VH-;)?iI zzniz>{^T8{Wlh`rufJ;xR&41iv>M(1X9F&WZ*FJ%Pg^g(^Y`BfS-70;PCG0Hy9&6) zFM#`94BXF$5?C(=f2zG+cbypqy7dOzw!k)FOYp|bE!o?0^tvpZNnRlW@)b3AX6_3z zb!vx?u9>*Ak=yMZuj{e>bPH{Vf<<)5;GdH(I^-hqdPC* z_hSJylGj~w58`oTMR)Xj$+EtS=%NNS&UzpG?3cq!gdD9c!)P$-jXEQzj72k}S<&n$ zrv%l`%qWA=9Bm{I3$}0`jvC{6IAjjwAtW8c%*i3gfm$o*;#Yrk6gQ@l2tD*Qtn$*6 zU*t4BL1Ga5=t3fXe$}qL#%$lZtCwDttfHH$xNz0}R##+V4TKwW=z4dAXPdENx_0|uuk@ZNmyV($?zXY|&18@-FX-QI29UEX6}wfD2= zb6s>xZ>=PmN$(U5ojJ#!wIqI)(x0B@M;y%d_eiIQ{z1(5k`EJ=o_(CYdR;wo_W=8O z^3OzA#T$~>Q#%Y4U?Qg?px-$t4?ii!%gA{2R42-FC!2q#x@ROBa+}Oe_C@YRiKg6d zbBuA6lat8943jHekzqG%Gu&%njv25aTfQs9WDaN3D=lKN;+$A4JI5E!Nl3R~WGI!z z@--Mtu5=MbhTVR?d=rM*6!MawDFGB9i!$sR0UXHo1Oi@bQ?#Zw39A1(q@6ewjJ66;= zB)>gL7v%F>>Eb*Hc|;i+QGJ|0sMq|Dyfn9pud06{kF+t&o0j&qnJ7R7=m!}Am7l5sTE#I5(uT~W63#}XmX)BZ0umdp{J!i|r)W#TD zqXvRCHS(Bu2{#-Yb0kncZbR0m{+$Knv+qec>vY=~VLZ&-ByE%pbyAEA$>YRkZcPif zXJ2DNq>lrPinB1A5R2TR9vx5@wz&#ix` zmb-|X2^4XS7&@fE|bpAOin8GE|bfG`*5EH9D3-<9FV;(2dXK+ zp@Ybq`W6LJ-}VQ1FF7dg0r@nv!E-|SvG?=9KbfzUUwgg{d?#ph45!2%UEpfZ-AW}{ zw7y^Dk&U-8wqm++?K9^TNcRWj;@oVMn_GZrgg*wb*CM4io?6SaE-d$Sc0= zf>=&YIF_C5jG47Dvx)J6I2H(%`6fzyDX4_t00n>FP=amZqfizF_&tDvES7Oj+p}$`(8H#Y5dsTnf{8 zhTf0M=-mD6RdcRiaksR(B2j9uH998u=dqrixHI-*B8$# z;f$epSwl%}>G_4v*Zf92T&wnkJcS*hj>20@cI7@&_G##!x&JKF7EOOdsPN2F_7XqH zKwj)E7JwxPK9_`EyO6s*mni@o=WZ_qBp3Q}3X#Mu=(fh#O)uT>rk8~IrWn~$EQqnO zxJ&_|U~P$5#??#=6#|>|2Se6iFoc4IAx^+R9pP9`cDAc96cPgRK3PS1th%N~t>$AQ z47vg4(!If;Q&Tj6=RbcXOq^JVCdP^m;D=F&xm0wBO@)gKy9-%VC>Ayqvd0Tg7BYpk z6HSwX0^wYU$0rH%s+)Fzt{#rsD66Wff)VH*a)NiQn=o!NC%Ro!0T^`F zxChhw*Ob_#(;ro`VsLFVJ z_yYtx<|N2j#}UWmRAs=#nm(Wy0=3Z9(%WQuR~kz zEi0I2pH{ZOv7mI7V`b^p-aGTP#f3(}6QaB0c1L-U5cZQDa$Y$DFme-dKOxNt{$O;;speCUkJUo!JpSxIEym7r&GFpBegZ*4nYj7`k+ zVt#Mu&L)%1Y>&@=y<@?JQ!c!|=;}|deS-_oyD2^+SLig?w!{OuvlcbXzvb!VUoTu_ zvzr|G3tNA~o+&$Tnz!?E%x(qvgSx|AN$&juJ;APKu*U21GA}Y%hm{@=wtG08iE@FA zY;#72$?Vfb;}WCD!dcmgtgLv#7u9k!iK>`YsW{fBbz7<55r=kLEk1X|PyC@^u`d|( z`+N}(gU#44?X+PJvczN2s6Q5o01LYGNsl!KS`2^4+?Rs1rqgP%+UNCy8j}z`2qna5 zsWH)*Sd{2WY)c$VC=>2HhV`30B)P@B$kJumW;to$j23Kh<&K(P){@mvd4`F@4D)gN zH1X(ZhU)+FcsF7-jBRW+dBNa4^j*moDl-3%g`l&qj-&Jt44-qT7UMGqEyr{Y-6oC7 zSLuIiBXm^0&vequW~2#eGUqrw;S9O>6ZVDCGSgDP_DaBZ5x$2okx%wG0_2UqPsCD- ztLDiN)`T@huCS}9BivD>yuf^+eUa~iAg2y43*8!eAjJJ6_-z=pW=+@@bcMO}gi|U{ zIMRKDhT+QygQ0jKgzVwT1pv%*qOK?}udsi}7cW9-(X>>aXp90OpoqW!n{aFS)H~_^{s`JRTR%CPecw@fI zeuIC*|IYlo;A`R6ik>TZuFj}NPIP}qhy^M$*O|<9VLlY*gC)g9I9O5?=FPl-i>w$I zmDHKdW+7Mt;z9{ii7Snje5KY>X|4=b3YG4{%A(3hWw|sDY`c+@9~>liw2d(o5{KE3%&AxQfFxlaNFCh+!UWil?E#)cmz&LD zVt+_Zxr60XO ziBVVXHPv8Z)RlX9aDh=r-Z{p^2x@&ob%_y-BCE?&&6C?LipX^qV4HtsyKW%${2ty) zmb;%94d4h@fe|FeIhV7^YBnyik6m0CyQokG0n!_>=NdAgcJ9De#;itQsedPxnecq(Q0wLMq|e=4O@Z6X&UfU%>vCunv3xz zn(NiqYi`GPY97FkXr4q*;urDDnz!-C_%qEn_*;!br@^`b{L(&FSB(~E25>)2lnd0) z+KJ!d%D1^(c3a5nSFu{8+B9t=i|AGR#mHR8D6gF)U9VO(_ZG^7`WblAn;6RU(^o7 zda_@~XAPu2-!0uVMK2s}!6tw$k}j~co%y!S*qbZwcxQhAE0h1&_x>#}jN1GB#d|m3 z{2qC@Pln$7ZSw2qo==|q?E@okP@aD2-ar5OUh+ORMu&hu-vZbvWd`=6Z1SFvp(dN` zK3nzF78_v*udqC5VV*C+Io6zLUUp7NR_VA%O>{zbP01?js<3Xk1&1wV7A9_O%>HBa zk0oD3zbJqCF8W=GdVF+z$*Rbz(w)|wVP&K=97fW)H{I}|H+q%aUUHFKfK;mIYXWqc zQfI6gb+ue>F+D9;P|#3PP*7YF$}K6CPRHqnkH$$?%~_1fJ`a!F*}V{j(H+cx~zww-Kj+qRvJZS#tgjcwbuZQIz`ww}HJ z58wCx*3~sLAEtV`s(bo8eknlXcJFz#GDWjFk{a03PUNTYSyRt+eb++CO-L<>8)VG^ z(f{fnE&bQNNUg{zyvX?$i(ip{J`sEM9qj%bPBryu&r0))_c!?mp2Z|-p7oTJ4+*2m z)g5u{-JasY4CK9QE-N|RiA$Yl&Ufm?fhe<$m-lR=x+X&nz9a2luelr(VaL0KGW_Ln zZpJz&m4HvjbnwoiE`t~t+)My{-C?Ju$OVw)Cy(duM<(is!{-6^gI#8k>y8-`=>`K8 z5eHsZ4lUjA_~0K>EXJYtWkhO&`4Q`5L+dz@r|BF)%!c3`UWI>yh41IpmK~<7{f?n% z$;L~E4jfcqb}&i&Ly*Hn{mF905}&6{v=TQtdis|B3KE}HgzUTUhW$1d<^75UKLy~m z8~I{=Afq0EysNzb+vwsX-Y~<@5Bf~BYc$<9TA0fQ4Xxva|EUbA-K;2}++&pdjxV7(||iL&frI?Nigj5TFueph5V z&U31S6PZjnj$If*u2B230huk?VJ2iL+0)Cq4BaoE}S`f zc8oTI6DZ``)fFjntM!TZqKV@8&ZJHfa6Zj!ly|8!o^?mdDX|`>)e|8&GRu+fT}be8 zUc$*n^*A$}i=>)ze_;v3Y{zx&#xl=Sb|*(iXT!u!ON z@cHJ2P)y=ZY4_w+CD*Wr(71b?*O5Ljzaf_M(dBK*iD7*o4h!43W!QpM$LC#8Roere zEhJ6RjTS9p#3#VVtFxn~tgWi7tZ6E>Rz^Cj+g-I^Xd|<)Cun(f!73mue(&k+Gp*jp=$!`X{^L^}O8C&CeTFa^9K^486?1S% z06yt1!YQP~5v z@kDMHy&^@+e9QBC_;2$Mja-gNVd?kEJZ_bt9ak%V?5w9I_tdA6<2`l`J?$@pd@mm= zCO}x_UEA)w^)@b9cP?No$363oWl3gL?-XGnr8$HVA~zJjkQQE@rIi#*9``cdUtyuA z6F|L!u)EGSHCN2jVNB|dog~c=Qfgn86zkKNvLn+=a0>Wjoju+2yx5N2{S@9ztoCc2 zWHY^@bJyd)IR>*|NY8p;-*LUMmPyMd|48|~Obh|f>)?szQ*c$-@h7ND+azZ~ygrk9%udG*7L zxZY0dsvfh>5^C?df0Xq^ZN2GV9-Vo(%#x102xtrB#6%JGA(x|niOZl}lwZ`_&_QzP8M=l?UR9VCPIkbhCS?Md+rUE;S7!~mf58S&}TNa!hRwG@@~^qGrIy#8Qg#hC?^ zZ_plZ=^J5)6r0WS6X1<3h?&7PS|+CPu-FS(UI#Xu2A0QzFG^QVN|&Ut$_*|`)8>G$ zT+QIik96jZcWifnvQAjR&P%u`qS>$8ts5HBDX;)USmt(bJr@Wghk7lFF~bsI^#Q$- z49|%UuRcQArSjSGkhyU^bd&-Hex7a88BSwiq0tC=I-O?^h=xui{<@K7Qk@fFyL0%C zKwd(4D)oiB_p-=lky`z-v*NsHCU_KyG_O^CoSu!au$_{p{11FCh!m8N{30stjt+>~ zs^1^jlZ{h<4qIK~8TQ?s#9NVn>;OYz28~K84`s8%ck=vNtbYsIdqU#B5ZZ+ZiVjd7 zE3nX?Ro}1*033Jci9^zzh=3oC#t1LrN2>|r3KUO~g1}0VS+VIZIZ(EprwYG zH2-D5b6;cN7l}&YH(>1`y|OXI)Bq=Tgh57j^6RE6>_HxbmeQl;wxxh=SR)y23@HOj z*T2}-2r2e{{*BSxw;WRyl`rOuk+%4sMfvxkALysq2QzoOG#X3D7kPI^$lCj-T<#yfllO z=n`>jlw=ej>*SJrD7aK7>V89P(D}nW1&H6KR<-*UcB_nEL`m7kNir-?+J->4PKO~k zjtS{_yvAOtCPH$aEwv+uH;~$<#@UhCwmScYcD2sS_j{>s!2NxCmAVZiawGF!Or)4y>}*ttHc3c4!XmB+bx;H2E zG4DC^Oj}y_FQnH+FqYfW0yku6jbHl6WtNzH$Lkw4$P)>MsIeBg+ygOKpy1GbP4Scqj(X9wF^=h>a!z^iE{xFWf!*k z)IW<4D1^H`8{bLCZ2lXmtH%FUeq)E_%;b~H37Qgi!EJ7%GxQ_NH)iQX#bDL}b~2Cc z0I1SfMaDAoH$Cx>r(ca1`Dv??Cd!1KII-A`1y6cSSAO!WcxheugL=--rU;GHu+2TqYO!2O6BC~&xJA-?<7`Y* ze9?7sDLW5ITjb)bgfZ!!K3T>x^~<*D(r)8?Ij$mgBwkm>f@fjLi{j{Mf~&}}?z$@O zXwX*@?n8p|$L7>U<%fB!5$zKi;E9GLPr96mRdX^$;G*cYA-La^bRTjPv<}f;il1cc zqP!D#Y-m1^b9h!%Lo1ofXGZ1+I8(;`Zmq-Tn~{>DA+o{8U++e9mI<@G{_u*c`MPvd zizxtJKrLtJZt7uWt1av@BrPTsp0%bFu;DRRAm6aY6;WF_y-biR*GR|&IIP*F&nB#t z)Th&j6_IUyhFPd)#IB5`VN>z(33HoJ))r><27QaT)<)?t^AwM=or(Oq+730Oj4eI~ z8Ael(sq*C7`iW0N{FOnQRnlz>7Hbxjr zk+^|sJ;e^y>|pyz)Oy|pWG#c;V4F@!rV_t~mV2THOL<7E$nhjYa38HZ1a|-|O5|0J z+Rd#4+e`N27g?;1<8JAcggSLsVWcOmO}c(rV;=E7r98s}*ok=Q#ugU?2rCNBJ<3xH%`@s*`9S`_qs3JIq zmww10#66lRH67N{yFg-x6FIRfd}_6Wkne) zK0zwob*=z=NkaG}a2xFGc<$ZWGPihTroh+2-=m!2sR_L3n76%Fnfo97dxRB8y3m7t zi+`p@7t)Wmk%9cW9Iy>W_v=U@bj^%ggjIM(s;LyKT=mY>oxrFj;VqIU z{hJYbi)BBZ7p`-CDQSb*Th-FokRx^^wkY9UgC^4z%_(~6mtkSddj?gFq>b>%kdHReerLMKNCvi!3pGl2`FcX}L;8p`)ry(L0o#L@T-6@&IBKa(R-=r!MxQ`Cy1( zcgg@DxXF6PVfus`d*ql@SV_pRe6FiOFKxZKq|)8ky;E_uxu(-vPsd(jHG?<5rBNkq zyWYsuK9;ewyVQ4k_-gVVEXYv$Bf<3wH+bawnQfYEHmW6B@4`D|rUo5fCZt$z8+SFH z7lf)OPct03l9zbO?&4CPO4d@RvB~;O42k>S`t}INaN^yRuc+iW8J|!1O$zD)J>OuN_4l1FpWo1>}P8gr#qYo$m2l>94ggLdjvbEBuEv|IVq z(jId|gTd0ErOG#bjw%FZa>F~N=HiqBzsQx=`o{L+^m4p1h?yrZN$U#NthH8icfAEb z51)8fSP)#5jxV$78lTZ90iL_tQxk@!nZi*ch!8qg!o`v)aT0Re3f+CNkLmf4Rjqm*J1`ppX;sxHA)puaI4$&3ch}Jt>p3Y&Tk{frP5sN zy9j~m>+fhX<|-!^vVr;BKX$3T&~EXwv==541@%NFQ|JhcVH*fd#+T8Ev08^HYcif* zn0cCn3MtmvZ7y*xj9ZSUBX(4EqTCPDrrGZ@o`x~h^H#X&i1~MdoD@Coa?li@rVM4` zDoEFTRSBK8>MB@0*}-0Qc4?)r6I5mD`s z{nlRw_a(`*>}~5t4J8JDSrd{I;(q9inAW>VE3PD2(I(Nhf~VS zDW=(6srKhb#R9DYDvZ-R)dmr9h6k3$HKnoS{&?{g!dY5bTx@PCjGhCGHq;bfXn=}u z6wU9>L&uGikjI==GfEuO*qbe4&{>Cp@R0HP*mE0-NZd%H@J4K*Ft^9Alos;v_{$e} zNNHV=&A_nD35j~8A*s95U1-W*y1~R9?>YKJ zB?yAZaQc>cKS)4h9+}DvQ&=ZBI>93l`EKfwjK1epQqcK+ev6myT+YEG>jO*aax5h)B5O-pt}Ms$8Qj(l zjL7%XR>4{p)KQ|8P3=lV=o4ac)wv=tnOXv!b}$~+zvQ!-n%l{VS!Hc=#3pR~g{?zM zHrA(AE{`Tf5jehFR1WPc6!e4KD$j7qf*-Sy| zYCM?Q0+}tfHTC7D&r9Pf86i~BEELNkdqZ9pT318ZPs(a@U85T_%!4?9yR!h>yjiY0OI%OT`EJik|LREL z8*%eJK;*~fC1C@68qbTBp~^y-umZ@21l;AnBy}j3S7-bij^m~ZQBSZ1N6re~m2sDs zQu3nLfHFp@=XCiCwN<$7VyaGP6V->-JJ+@^SuTk^o*N<8M!yAi5*HRu1U{61)>GeS zdzJvt#ovHNhNoQL@}JG;;v(#G0dJL`pRLSF9Z^<-@Z)Ul>e9|yceF59l9swtl~M=Z4SaO-ylBS4@7aZbb=C;&=hyN#Q( zW%YyfMtbUZ#>(N?BPNCd;HOZ7te2 zO%m^wEjQ3zzLh~mq%mwhtUY{0f`nONHUgaUPxY3z`q>kC;>z2@GoqKaM2$I=+%a9< zmE7T7SI6v(j$@fYmsn8}v!`c(rI`D2M&%3LP<3Jpescuot~IWeFXfEOmpsEkxd-y$ zFQoV9jIb;IsHRMT0K37w{^3ivJjNo1O?<6 zvzj2MW$tkeByiwKN>9lL+T;6JuWOGH3uBWEg@>Dpy{`1NW>kl!LH~<%Pb1M0`_Ke0 zugEK*_E$qs(gi;)2YFN_M%Mu^=}Rd9@euXM*wccG1(}c%>;Y?{0cImx(?ttt2%Lf+ zgJ~h!1U93&54tjMdHw)3s{U8en*sQDwE_okW_tG*Ok@2D%$ekdj|a}a7T7F>$90G~ zCa;w$>2NrANoe`MFjVmr(DRuc6*V={tG~?S&+lY9H}uRaHO7s=vg@b%9-0_ z|1W?l6&Wk6a6?}zxMu^AEk-Z_DC!~}Sr2rM?JZuyfpAiqND`-rqB@jzCZZXd|VJyeW3eXnret_h$xc_l9P%ADpjo}y(yE2P`+b&QIt|Ip+`@a!eX!0`1 zf!mXQI~`~b)g$x@a|<5T0PyQOc6+r8x>4BoCiSuRzftdtan<-#%+gZbQ0pjn(K+ib z?vzGMu0UY6z15qd#b_S0gT%08FW+wF9n23I^ZyZ>9xZQ=$sl3FFYwOa9}*svfrWRc zXWeRe#0SyPC+NgH@55FtC zQZi7>wM3_p*nBoz>+42n=vKK1CC(+gTEO9^V|_Msu_feGIeB0C1d}Ep-QrZkRA8kIOb& zvI=W=Na*gKw)uT$K6@m5>3qe8;_sVfA;+eXF=lpWRT)j65}#R)hOE+i4LZ}y%W~xg znlpbjM-_>H1S@RB1`ZXR#R)DD??<`#m1Gq~qR2>}l%9PWFI?pyRpmDEV9A!I3ogoH zWtt7IvC>#%0MMOq)TL4JlEq!|z%4WK&R?}GKYzqCPG3r}!Ka`9m>bx!-}isavwl8b zcgS394n9Q=K3(0?UT=EcNt^E;lLyujF7LNWCC!bFU@&NPB&^w9fxccjd=&L`+>#l- zG|9}y+;k?1D!?jiMAKK>=k$Ik*ZV5}usUk{w%ICA0&>P}xng6>uisroT8D^IM1JB4MMnM0Ei}{ZPMBIZ8^S{6tHb(aU zfIv&rF&m6%;CsF~1CwV)Nw2?VqJrbFu*fjJl+yVHCE=n~6UKtU?G19hx75!;T>h@5 zGGNjlJ=gI>4J!LZs<^{Naw8fO4%&8%(wC{U!5QaApNH9vYuxYXZMIV`t{)t<1Ya2P zDpaZ|`6;~E$@r9&$_Zo-loovAd?1D06##PlY+}#eJ#tSB_HjnU)P}vP-yP&#`CA`S zpK+wQo_tOhuM9nTOG$s&eRz-aqI9j5JIWDT+pmOeMrA0~)tc2Szx?!q>wj)?YCR8K zuKik=Y?m#q)!I{CC&Zl&;^>@1F3DCg+EXsAO=kHZ+6|w_7!snhe2@Ifd-ppIIRM8R zht(z@pJoh}-6DQYN5~Wo%*An=0y~E~~?|h#b-`Xo@FJ7YVQ$Jd4uMQ>(Bi_) zZC8rluaC1@rN!f2#2yKm-hlPv@4?^BKgASZ(O9Rg35^EKUl_i1k2*|q_DYNM_I1~% zFZ!Y63g9CU&R^zuYVNU2(FJh5Q$!{9SV8X}kW30JG2<5;%b=hi|HTkFCqncf{>MVn zO|=pOhG{HKfdZEK?-~;Gf76hd|EGrZ|7b?c|K%I~|5PH5|5AxsPsD-YA+ebMH!Al9s!?eN>m5IeS(;r@WcXcUaP=rldlHB?wTNp^-cfkrI#y z27m(06vu;kCD9V%z;3mSzzK;`%U)YD-V1fDEb&>r=Cz|?o?BLEc8qi?R~{ppzm_Ge z+I4Fa5^uOarpOZguTFMuZgO{nS5HNMzW1)$!s-~m{j@h*cC1ljjQ!szTc~WVY^+#=^~Xs7hxeQ* zH=8RSwi;aSd_ivtj0|q9cGl3UjLpGS1N>1uo!s7Dnd41N!baXn_chlkHOYv!{2_0H z!F69ypr0!I;fOfovvqj2^XHEch2vR<$W`o}gJ(|OUFkiv03u4Wli-r0W4T-=XLUaz zL!~wkH>si~o0%=oqtt1GItL{{NK=~=^xMxlqNfsk~E{zb4Zg(>xWa3$(NKs#Ge{wD%ga<7-@QI81R*#-vnhYZ3Lm_0dj{ zt9_<-uv#5ucU3-643D}`t?ley*+l@8*KB-mGlPfMsE!p z#ca!JGSrwd=#d!oX#lOau6E}MGt96^7+5pe*~x5-iE#ukJoSD+f+0K3BX@P^i6(?( zK$^dYYv43`HCuH4xuUVHVSwh9WhFIj8N%`qs!?HDA7(0MMRjqWqOovP-lb(Wd#Sg=d^ruL}UQWd~51*-W ziDap^vym5FUDD8aYiKkM0iQ&{&ZpKEO_`%W%^-)SvsJdy2$g@19EA;i-u4tG$~<^0 zIaYK&NPdEDLB6;)u6()bq%)jWn1WLyH`52K1hLplp#@g}Dp22~D`{3@UK))GWF&9f zg(bIMa=}Vb-yajl<3`9t6KOtwDJ4LgNr9}an9~p%z%O|1_GH9qHZt_o-VJH)9>-Az zKjU+xKf9`two5e;WO6b+fBG2|9M5P9hqv~d)*mieuhdAct?DY}{&un+Y7r0K>y1!+ z<~3+>#ryRRkQ)XY4;&JLF*m0%Spx`@WHKe5>ZsHni#4pjpv4co)q|mGNQWwu&pb&> z8B~~bdGQ8YcN*z^^?;>zR&l9DqwVgdoo+^Lxi&X25);Qe?xf;xm^Vfo?PCq2JE~4* zYz~9vhKUYnpP|RuCsBWZWsGyy_V7O$LVBuaY7z_p%#z$HM3(Okd$Vl`Yy?GxSC1>B z6g6_=Dj12`C^V|xJsh#DSw|fEH!4q!CZWLP*Y>otAxC{=C|zq0 zdl|R^gd_H0Munf~m_n}NgjvFTlemnMG|ta8s$i=%8|(e_ortUg7VuJa_=?jNyA^C9 zuo?3wm;XFP#YPhs_yg5Lpyp42zh=)JpWhs zDHnDP$jex%R9hy|wlo&4q~cGi3xt5fVMLAtV(LTXiLGoapq9}vPU8epmz3j#Wnu>& zjeN_<(O{eZKvQYgPRS7?7CR+iB^{49S7av1kYXhKF)?-%#&0J*m2iQKr^)Kl(i%S zG*x*TSm=F$L`1(IQtUoS8?ZNks4uBuVB}d<@)87YuHvQcJ-gywPKhqe1fsfmx+=plhH6v;kGV z>~Bb*<~j7CKdnPnfekDg zJcPb=pe#6lQXpNR2_|Pspl)Cb1VKHZ3J^sce@o!o`M;t-Y2QGP;CVDacR-TBocR94 z+Kh1!3c1>YLqkjL_8KSV!`R|UqQUa$Ct7yVWq`&=Lu65DUHMAxn}sgLm=$h$(6Wxt z(NsNvajVa?ob`%*muagH;OY75x6uLk(T~gKsqfB!FoQ~OO}~JZMncu8+N;&_+OJFM zqUgw>VsQ1H)4%imi&_OtiSiCynxh$57bFClWS)6zpaqa%h*2JUO^^`0b0@&T0%rjb zh-Bvf5=oks->&vXK^)b_1Ru4q%$m?#aH5(D3g*fx5Ttf^G>NgzV`K4-k+(;^ePGEk^Azf|v*2JEIluLdm8g{}m6%5n>*QLq-wD%G2tHqBJCB zjr2IPNNiJ1HyuRF0c7vZP*p)^?Jfyi{3dB;r51hc z8yfq?f-A+WeV;sHHD-aJi(~o-<1IIuJb4$$oO&OC+xm?v3UC|GJxk#?U~-}Z>vN$U z^SPw!x{YgfIC^w)V5}a~YEw1gnz=L9W#G9>46X0%UU3#okcAngu6y)pOVjtx?iD|_?K==*Ym)-r6 zReD=_(iL($5-E^A<6~3o9Q7B^TG217ObN3MN(Fa z1>EYg6`=m_VKxGXgIy92$783`=@yc^$P#=4Zs=B=!ywRfgdNhB;1;7Wad<`ymnVlL z%uWfbBWXIp{;u+(0zEq?E9Vij*lODvv{082HEmo`pzwH}d;)-V?Ac!MYH!eOCsSAbkp8$scK`hEaoa{b*G6*Ikj%Y6ch zDK9U=2o80ne>lruxN|UQ9diT{FBr!;184TVdK?|zj>UCr zs6`)`&!c|9PKUo{Wmb{H2CZkt=DeQ*@&s6L9#A38w%QWYx8!*SZ@XM=qc9-?8D)>e z@&G8RG3nn#E=v1SsWn7&*QHLyDMs^_+APSy0jwVH!k_kT^kHuIJ8Yg874s|KG9exA z`0K$s8V@x*`N4d<>a5QFugA@2noc*;=W`wP3T?4*guW*6Nw%g>`YV}7t0t^N;lPho z{dxOcW$JiotulU+jo}xzRk^r|qn*{tGf&IdlYc1Kpp`3N{%u+W+g~3|ja}N)4DdTGq-^lNv>r|vtW$x@jYPvWFzL(F2mo_c{(Op2 zw&BNya9q=dDpoi#I5k|mQlo;r2A8%i;FP!JJHqA9yf%mD2x|jR#uX>iam4plUc4N& zm2R`Iqsr@bO=OVm>@UB~(NMr2OZfK?I)PLLO|-_Bq}H~|$sHs^0v~%N} zjebF}zsO}WPy2anvkMtvBy(ZgGH;eg{Ms+H_&|wyPUCsySX*^Hd`KZ zy?^I{WUj2F$yxz*Is$w@U0m365!tUb!(Q6({xmGye@eeGP{Dqs&HO$-rX}E5GyYb( zc|8_270}Tir-+N)BFnr?UIC-cnm&HU+6v8z+dS^WU)yJioKIUnda?AiHp)`}J&$2X zrwsKjEGVp~j~BR)1UK))3#P1%*h&QoN-At zAV)4^6`J?j!a~5VMpjv!?^68LbrdDJqz*zdlxEDq* zAo`VeKMlrXs|;V8utc9~f?a9b0G(o#AvwX-Z1!uH+M9Ca#OG11do)ciVzk1erqCTv z#7>yYa^<1dVBMGa%*nkY9Q1q@y6W%lfWor@cwkRRvXtA$K2>1n4 zcSa)BD+xd@3ij4*N-mbVGHQ%W2G9FIrw?k_JprwOm;&%HdnMaLzepxC+(ufP_eNVg z_8!osG30v5Sbx6b=mJrDk45UT*963PjKwWUWOeobcGPC>_JT(-uy0v+K(-MUnHs^N zHG^#^xcW_TITJi13uRv53`A%$Ad`NJ6;d@538Uk;^J z^Vz=wIWX!<4-s;$o0y9!-IH{sPIF}D#8u_3Sn^bG7Mb7SCI+V1RH@|K~$j?sry)g@jx6vvQYP`fB@!ToK$^zKw;pVWPJp{-w5BgKVZF|KMg?v zGW(dYL@-2(pNUP={J>ufqOq`fcEOvaJ{_e#CE^D?^&lJS@p2k+lmpO(++eClXUmu> zZMr>F2(vq|>~mYN8g?#qs|QwZ;_gfzcUMEr9k!PuZ82TZEe#UPDA6M(W>1I){eTTA zv4vI<2L9>#pol;rKLQdzg~?B`L!lROkr&7)hh~He(pqR<*_5o zdN`TW`@dI*@B(ps;>v;wIVHcA)NtfI`M=5~2a5|44nR_wGe>RzP~D>|r(wSIaBnV%ijK^uh zEphD3R}C){ppzymCXsp|EX=de_K||;Dqht%kg;O>?6IO&u;~bQ9@&m$g#Z8(^E4-I zEEH8o2OH%kSkHbwx88icy9e#|Jf+}w+U+bt-b|>!)#mEG*7klqC!d4~e4a_EwaoV$ zI34{Pr}grz^e;05Nk{K*3|Vfv>ewBEJVV-=5kk-+lYnd zrm*r?Lal5bmApZ-Rp$`{2~k*S6uYDvRvoCIzbL#lwxLMRRyKoKBsBU6s55!SH}8T_ za`HN!5n~0uzq}c0v-Hwbbd}zhHEJif{962;A14+EpE>Lt-1r_Pw*es!>!+Q2+#W|b z&oWE(TDSRsSJ(I`6#6FD`li;S{9?DQCIq#d%{0T^AkDJ zdz8%IQ95?V1Hmnd0J{o?DOYnWwpUc=7D7eg;Nu~}*C6xRhEu=wnm9r<%f2_A$*rpynb?y$nt8AcIGv2PNn`K0&4&U5}KQlU5w z3}))-0<$heGXwmO6`!}DN*b!y3s8Q>#aXhys+2FeALw=QlZx_u2v7b0q-bpP`q5Z| zcR$LHoVEkEzunEhgfJXQoatT*;`_!JNWmnph3Ihb305su?-%&bFj-^lA_Zx=h1C)t zJ35dWVj&EkF~Wry2K!-#3Bgr5)n3=`qIK3uY_SgLr~|Z%s4_VFzXkW+qD>HliM+_qxpDH;dU=Y}(uHTIt5Dn3=;v z4>70eHUXC{Ihtz-Zrw-jR6PJHNnyTu5trTA^GGowLG*@X*11SRyf-@=t z-GTI7wk^OMfRMMUb^Jd49kT4NY6V(9Kiru7ON4WA-O=bUB;V$mgpImbY09Sw3%ybu z7l{4tD2##Mu!=Uz&tcyEX48GOKWMY{`jqB^4ho>g{=^VMDBy9@IQn~&rFSZ93RBnU zXP(d9!qxgIqvvMJEoj#nD^0fY^YZ(KC+kPnvi@Hh2GIh^zdea|{($iXZtPNlyyiE_ z(6|7W*|EBHI6kJ#(7OP8b2wg<=sU#4v>q>6kNVz(jGLn3oO0Pe>1hd8;wJv;h0|t; z`W}FpB*;P#=0C=qyqL{OkPtF|re%eE$j_altlh$ZfjEhi^R%wZ|y%KZ023KeTk4)cJU zfSWr>oQ|3rMRktvsB`1LY3c~+ir7-V-3Z{{SVZs=>dr}+E-(EDELPeWe7|lpe_Z!4 zSHdi9nv7xS<1-!M>#uYREspca39g5M{y(j~N~kH~p^!Vt1MU~c}@em zJ9$nQ+BI2G4Z1j)u^I(0xle7tfjp}`IA2=ua1hON*2DxoQFWkQno}lTL0;UhrwlNC z@3J&V^)JK_>S>Y6RZX}HC4%bSBxLIre&Pcq z<6|M*@5tg^;q>G z+Sd1Iy_pT&<_gb&&tW#^=XGap)*5hdR7v3bxP0R59dr}+%#g0tsLIb;c{$Pp@Dum* z+C`sON$u{^eOTn;8LD1ck0j&}I8wD}nPpR=a6jxj8w#0zx364ef8HNh~kpB?(!3i9vHxL*IL&{sO??E*dQ5dbMD889ot-NV)dQWk> zH(&MAv(QKBuX5hVj=+u2KhiARE(gX)65M?qjaF~p9Zy5?~kBI<@AP_r$_weO03t7^vu^ZP7nQpZSie;?F8F3V%e5tkr!NoD4 zPAZ&*l3`2Q1s#A-0-foxiuMac=+_Pc^|iHSkr7mWH^QDowSxL>1iZjmjd za$*0{hnEaUK?%qes(_XwDT0E_8(D+|;8LvlG6&K0l%h087e%3~<|NkO-4c@qD~n5$ zS(ZBVr~%$B+CLNJJ5EgC-Jb;-KtZ@e**#wmI86I9|?q_r!rXFtgIov4td3>IIq$e|{ z!ie(S4OG`2M+3gc|Ak%zXA*RIJ}!4t-~0%9Lq@08q=-fi_VIY6svP+yQVoB>kA2xB zd?=K(?Z#|%^@G!6q5ywi2Dp$gJw{dL z|18{w60sYI=?1ngLC^YNMqc7;$oBCXB6sQN#RF?g=}EpA%DpAsK!!SR?%*|J#(}yc zK}}6D37w`@SSKfAiRdF=OKDWtsM@_uBxl zyYlS)KMMN_sHnCtZV{wQLO>*x?g3_o8M?bcx{>Y%i2;$6Mi?3;r5ouEDd{c|knZkp zeD7cHz3*G!U27Ki{?0zN_db_3`^^3wFSEs;Uq54mno#qab=5qEV12zpH}ZbRav&J{ zt}q#I`!oSog-GCO5UA5+#n!z2&6xF*s2aXOvZ)BqLGzk-pH`spiBfZ+Mdh0YfFh%N(V_1O(kli4dfWhXO zXcY-92a&gN3dswDA5GHw;S+G?(i71H62<&UqGromuK-WuE-Yk&%?nXD$xnmOv7Wpn zFL;)^&YEopCROx1opGA13w5cFp_VcV3Nkr)4M5fOhzJdChF2ia(?Q1La)N!+pYo(7 zCm}f{tSeCJ;qD(nCX{(cKYj$jR##r&S$IJ@JE`$PykjIYqj^l;oq6y;r3Xk3*w}%Z#`GpyTRA_ztpz&;ZTI5m&{~V>A6!zXO=}Hb? z1I5@6|2XhR_DD!zxez)J$_l2Ax~$Vu?xdgeN+>ss8eQISuA7W=&wcRnu3G&0JG!Ls z$s96focLk$F-_>!vY~6_)=zKw35cc`hfDt#ZBMl3Ms+P}vi(-?(S~x4#y9uxkns@* zz@JW%m)&=qSJ`PVx5QIqg}{1+Ka!VHWeaT5SmZp595n|YCA){fPp+C2r@rgfm}kEn zIDkJ-wSl&F4$GgwuZwPn?gj2U@8j>e@2mWpA7=&&KLLieRo%Pzi9tgEPxkOdLF0_I zs%x*kUrWD!IZnvW=jD{ckcX)QU9^BoTzM}HMb<{!gQS~wltN;Tfo zxRPUJ5vO_14v*%;!g}KQXkQgu+gD2`_rpu;QnD!nN3dwWvwbSm@4F|6!*-KOVT{)S)~p9;(eTd*~kzKrN+IZ9^H zvu~YJ=I}m;{<6MkjW=`a2;82h*@ry`};J~~h$do<&-L#UZ! z7c%koZ0Gr2YH7m+8B&zY>_;&uHp`nLv1b@-_vg`6cl+r=ez&P#lLD2Wu&lr5r)_0r zNJ6^GKR$SgCRIxc^Es9}mK)@}x;c)odmr8Tx~~xh?~{Th!*X2so=&7f+3?642nBzI zmXp{Nr-nwRald8gYz;lR)6dN`YF^_R3}nUiDDGzK=Q8@KueE`9TqwBO0-Jcb$+c~Z zT^o0J{GFHm=SXd%#) z7h)h#y=`1VDLjVEo0>)WqWaiO>@Dd;r?J54)@M1qp|@YgJAnfD0%_x&+5%~xb3-hj zYOv?#YF)i4#Doz}i>c!ko#E$As`@0=b}Cgoex67As6bhwk36-MS;oR2om`c&nvsMJ z&T{m@kP&SKk1Lm0?pL&Lx39?XsvT`~6USoq@JnqU$>4Fa!TuG*uUm+JR_N)*@EqlN z4t4~_u6soZ&5s!}bI(ZlBfR*w4S(fYT1+ z0b#-hhcT&iy20~y$R^;5VnvJEie!VTpDRP7sWwBBk-ddH%-12K#u=QI_T)w5o6k*9 z+6pRgEIPL2N&DZ6(_@d!KVuSO6{WE#C=z1Y;!raDvZiB}KhR1^UwU7jDq6{1fNF%l z6xd=4>y->at50q9bJ4?d+zpyor?RgtB-~yTxd%+5HVmZoH5<+pXzDhfrL) z2gmkI*X!p#D{N8_%Qlcp^j(0`j})^QL)jYz?B3@%{0?3BZ1l)tiznD@HgEQD9-}`F0(pIylGE+R!3_hNQMgKxK5tcZe^@8itNUR+#0M|X%r z$U}K28DCm;XO%ldbQC;~ltolCfjjfX2+c%FDx{WII(4^7tc)UMFaA&+rRL*SrPu;) z7De!GOgUoXl%8oCKqoA){`OaQtgY(NfRBe)TwCqh7Kp3Zmwi}rM(-?V6^pC3=LuLXrOqCW=@e0{7h{S2=BL;PxBN&}<;b`Mw%u*s2Xb;>3BMbWhj@NTkS zkX)$yT&LYWiceU5lw>3ZFB8qe{Q(1<-8Ovrny-VuiWX;;h}(zD#Jh-Em=I5%)gQ>m z+DZrG_uRV}e(*Y#+YHdS%3P=V2JKbOr+UJ%=HnZP>pY`&%zaG#Tto`;Iw)sfkaOJ! z=R2EJWks8dCI8H*iY`ESk>@p&I_(`BkA2n9&|&Hjt{=_VI+o|V0V9;+oj4eFTecC` zaPLee^F0|Yl)o`z;PRT4hT2RyYP7ULDy$|}u^`2=rtYQoATRQ^I$NI+v?$`S>#W?3#B3c$OasBkv8s4`@(Pec^wLc&|B0XZKJ@}Ng;LuO zfe30jApRl0OX5;x6TII1mIwBZe1@exe@0K4zBxK)6>4qw^qiM6@w3A~XCOcCr}BW1 zWPVStk&_Ykj1a?Oz$Byr>dtKO%KaW4%Uo~dHE2&O+9)2seZLmDl>EY?mWN_vw zb~2GTQY4t{{0&bf(}#B5j(c_A9oE09=<)ptrNPe?8Oo7IS>LTvd_cCa)~@FC-~wmQ z*lJa@Rh;!ts`^j(<}AtMrnI+NVf16;3g2a<^uVc|n4d-=5yjJ{bVpi&HRJt6Isv(G zl(VME!qJ@|#BU_JnXOUQm34fAvW-7h89>BVnt^f7P9q4YE03MH`ssKe*h0-SiR$=q z{GsSJN%FE|IoK%$yakh7pO{CHvC5daWzc8w&!&oKxt+O;e0SF#gcUg0GkUVh!<^8F z;=tsA5fj|FseL0#K)tA1v%EU0t38!(5tubElP184g+b8M#Vo{pXucQW1xks?(1L#+ zZqvdx0$4T?2n3Mkq2V8?JNorsUBB;xG6|R651RJ3^S%v(&cR|p^T~GU$W(V*ohMYy zo@Eu4rcWRx|6fBmF&!dNDS|~`TCzWFgP9@k$Z3)0`LSGr@g+U;be`r zz{}`RD@Bqwx-qI4{6Jn8i7IX3Af1(z?>u{ zCkw9}cf>JA1F%SdfDt)Pp#D9}f=1yzL0w+O?nQz3n$k;w<~6x3H1@#upOQ53liHW^ zne%<<{U!R%a=p&xWsxAq)AQ0PTfW^uKdRj$kt)1xAHXtN8lI$&;}lw;pyv!)bM=>g zv|K^RUSPan8*_;2C9~dXK%fR|?_(Kg917|@>3Mq$9*jV6^F)M%jWMsTvpN@vO5B&k z@KNIg9%WB5x7#6?j8a~S^|249K2^|5LY{Vg0^Lo^z35%6nzMH)YcHw8^ktdmm>ZF zc=z#?f#IPIDgD7nDSzMS<^iT=y|gM4dKuOTe;y49mcu@5xIciG!_30umU8aE%9 zT+`i(k;!CMsCUkeW!*r>s6zY3oR>D(1-zDi1O1g9G3EyC>+KByptS}}IDL)LU@bDc z%fa2*py5w_AWI(hm3FSDhdAnss3+>=PV3Uxut<5{hG>ZGyKNSV1c zg9^(&{op$|O-YSCH5;l#*FiD!u$pAh_c1zM^aXjxcHFQ}ZTPOnHsfkXzQPW}NxYeT zm));#GokN7?0#tVv?%{5Pq$U>314qKB+J=*p?rxc6fKJ6gLdC#`^K+Nhl7=yRTJ~3SbK`ZPNFts zS9dRZPKhsy_ax1?)T&f-fqXG!aYMl4Eyqdi+k@}4$-3OFhZHC2gZ+akaN;4^%bh#J zCn?C$Xqrz9OHBr9m4X$RKyi4eKqWlT% zhemZrjVnF@JlFxn3fzFc-jORKH2d6922_NV)*!}JP(eC4WU7G~xd!EWyQ(~B*+C7NlYKs8pT41HGiz1@Y%8VS9z z*~Q7(#hLk~S(P_hT*M{fdLI-fz~|3G2Y6G$HEJE~Rh+ur*cSInY;O+3os9Rfo zaa!w2Q{F75XINb-9RWVCbv+G_q!=<;^z5E2DcMtCBbu2QTbvL5mfma!nJDsb{dxiX z%EQ!rMiedHme6`H~8YF%_qdkH1Bm1Dpq(&`nyx|$vv zio87?8F~AOiMYGP74=RoH&f>Zwk0JMjTIP7!EbCFwm%bzh=%G%QUp!%HSjg+IOZ(t zG&nzgiuOgdD&J*CaTliDzHez|rSZ1Q_`r5HJfVEGBt3na@x*FxwP7D1kwZferyLE7@hH1~-j~VVYJoPk+xuOWr6rNgGySz0S+z?`r2`!+`i<_yAbNNn zhGrhRF788f<&j;!82uGyCR$k}W93j0}ECzu<_SUxHm1N~FXE+U)n z>?!HHkNakpCg;^A##?H>RC&vJx(&%rIPSf{5^TQIQ__*9*3*?{@IT>*o+QF-W0u* zikuCJku}+rLkH}chDq~~R?1ST^puJ;-?nz{uV&u?CqiD)rQ-FOo{eOeY&YKWIu zJ)*~L_u9%^z@#`^OGQWRvjoRpNubH3epc_~j48{MLzWFJxx+Tgjs?Mqu@JUZOZ&P^ zGTdYQDRBI4LH(K0FiBZ^ZoMf5#iHWe#yU~ynzyAk-7oLB6ICk>DXGyg<)CMcjIUX!``=9UJji_ExWH$82 z-#lYVf6g=sn@m^=&TS_8im{@5bkLBKW+zto#n(sTO(jm#v(S$^rb$z89ke8j%;CU|Gjx}MZ$SWR7s?>6pj9T z;u=CbWI3CYX=w#q+w7Q3w38n>h{7CX#!hv=FliXTDkT!$Z8a4X_i6AhWmMPlZCa-G zZhTSIZJ$fol}SI+D2y#7GtAWJcVL!CwXz9X7CzCCO3 zf|fLXtOW9T&SkmYaWN}x@s08)1Dd9IECm!L*Lt&%JUO4Jo$L=hVpvF)fozDgWnL<3 zh#+g@edrTKF#Z^8)3v~j47^C%MaM!sv7WY|!%nd91*sA&@LO39wIhc`d>}sR5hh*e zt9)sLBqlA7Y?{KBg{hwC1SqdJzc{J6G)VE8rfzz^JlS{4HMpAEB~o$ls}Ajgqo(ZeV7m#5qL9zsJs(eKtR4;cx4A@_js2Xrk; z3_$|F_r9C$Sw8~hklSw#QYl}wIIIau*28X{GG}#89v25Eym>k1I61znb=cRfSob2| zto?vm(AY;~bvz8AmXQOIQ^cbd(~v3o{*%?kmd2BVmX-ZGj}!f`=ZkYNmiez=VH_uG z-{J0D0;9?8-=l7gddIXy9J;Cu$c?9M%E{MV-^~76AOjttGR^o)Yud8n+UxOh!z`m` zrcNNnxKG(^V@ixPtI~Hx&gnFYbjfj0rhjn1pa|nqRd*#BPsjR43 z*B`h^tQxR3N40@}$I?keg$Ki1>Q4i6GdU)KtU&S>p+Uz;b;56@eT8Qw z9ujFgB}UpD;;cJlZ9!2nXT}3bnn4v>&siJU`fHpzPV4M)H7o=T zo5UD(0172Q6 zH%glo!=puzh(SFAGJbfcpQ5%uCr}%TD4+N*U&)d^{`l6vhUW}_U5;r`lZC0qJ8o#S zf15jj~DN6 z*H9Q=JaPL>zf9>K@;URw!O1q-7`)-PX;K$?F@x%Y3HSR}%&outeJkf-uO_+Dr$9i^ z-ldK*_@qw9?CN5D)-DR~q*xecv0VXD@JAAZFzuNuhQ8WA4D4>bscJm25uF3I^tdoYI?uKw@{3L1{P?cEjiL-Uil-8|^y z@HEW0eT~}ek<-d2fy9Bt1GkYtsxjr}=B7qahf{+90BRmCuHPTb-+zGs2sIcCrUrqz z|M);4(0`ZzA^UIn|Eg`Crp5E%9S$UgiYug+C>Rb&Bma&sl{D?vjrxnX4RBp=W=fCz|+(xFB z$X3R38h3@$scbFf>3do!x%%%zqdTrqFFHXx?``Bm0*DyLQv;Kig!3I^XgWvh>un&P z`16EK7Y}Z#-P4RL^Ic#Mi*w8S2Y2@^ySzH)%&55l_AUqZF+?U6?#| zReCc+=#9uNNoZM^lC_5bjWF>~IDT2Ow8TdU^yOpqBz%rc^?g|w!+Vbr*pvQ83e52p ztH>7*NJo*6=P&)NRSUY<1L-DTgXEEzpmEpciiL%V{hNonHq#NcT{yWIzINZ?b5`B zOOp#Wa(1``2iTL|#*IQFvPgSY@Nem?PX?Oq@E%Yl>Z3=dU~K+;*bP4@A+=wQygPhD zyJK{77Hc1A@pdD3D50zzcZQ~kH!_I4bTInZ+m${rwJ}`RWz0yk)5XiE6UAbSvU9u9 z5xj8o^EqSi$^sfzwVua5jmp)y4^bLWAq%|b#|<0ynb_ro6QI>2@!c;U=9QTaOEW_F zfspdX)-n%Gn9>ECemccoK&{4uqTxW)-n{)%!+IV$kAu$gt`iTawE1(3ePq)d-K56r znc{Ub#3>*_iI1yfy6!=$>F<#nw4_+(h@sRh=?alRWpW1_YwT}(#_7!Jy&(k#`@3tj z+M`xU+h>=`i7}lGcyzs`v8U^Fz8~VcB)_(Ue~IQ@!&vON(rIWK1JS8$;>FZq(VdMD z+rkZLvRCXR)`wrTs)3)rD)m+c0D7d&<-&E>r?i~ZXa=9bskPq4v}h;}$JOwmqrI;Q zD5-i(l*sut$x1kE`>ruC030$vY;C>(DZHg!m|M3N^gTkOPwds(Z>Tp-;Mo*5=|i;x)dJ+-nO*9WRg&k{c5SV! zz!YuHVlaG#!F9iDe=!FbAZNbAsS@-iXGsu2a%=e2TS=K@d(KKv1oOW3TWRWx(r}67 z`!W;c#3vsDiQVh9$S9Le+8JM65AGXL<^#4N$bw2@(j*-bE`9-O0)r54tTc(U@`&a= zN^EmCHGNHWT%&F(<r!?}{8kyd#v3?`)*&xMuKgx}alpi;0el62sPtQoO(_29Bp?`H+# zNrevgE0=pK?=J}uH1(m=4jcWFcMGr?{3ar`gpK>jKuc3P@SPa;=TA5o)f7mxtvCk_ zxuy7U9IWpJwvKo6mN5_`k>{Nw)1$)0Loue8uO)|U({%VqiirV4d)4l|dM`Eat*v21 zF$y@nUsS$@&(*hem|~IzK{89y)4Hs+2ZA$D*OL(ZKI#`|XxjG0{7wCmoSs9G1~~db zY)sD*G4#F}2y1bkoul@S{oJN_VJOa5X6(UMZdI$iPdUi7T4{_AIA*7qLBFvtbUdu` zx3eT0Hn8qw8{%Lp3Mb(DA zkpc-=jfFl=)2I`s!A~YU2I?-U2zaa^BSG`{d%sfKiRu_jQ=VvW8CJk%H8)v~ z6EMx*nI-}d->Pnjmf6G6)7a?Dx;Q`RAgA$5nVWy`iE8S?mc2ZFN=ec3^Q|YXXD!h~ zkozGY?{O7YZ!S1$s0XI;7}$@xzNE}gj$1K=3YvL-OUE1QVU)T?K`Jd$V8GgY`!;OX z;`EuML*FEb&YS!?vdVWR`sjavu3kcSwQjo0UU%nF#rKADeO`PR^I^k^ZHknu zkMep-?>v^0ji&`^cDj8?xVrg^FxMu}Qo;5;3xoU{+suhop1&f$8p8IbNtW)XuXsGp z59Vr{^M{p|nr<7F1Rr)ghXs9hsQfOe9bB)KusH7@~fI(8Rpr(Q#&!Xl@=O-g6mMI97;4wsW$j_*xkKOJ9o9i*?KxQuvult z;;c1xJ(_#H?Y37A^HZg>L_6k2VM0yw=Y*;LxOc>pEN{goCnYAc+^A8q>>}fLo#L1o zIH8=r3|^x$`yK>CGS}%zE%89UJ~Tz=Q5jbT441+++70M6GJ_0XSnpMS&cT@<^r39v zUVc7st0;aMCP&}Bu14ZFth%v$@L2dDsCB7)xnYk`q#wrDa$88C^&T}8Wx}<^vFRwRhOUQD=jXoGM@eFZ&#~&) z%JiI+)0=bOcpE-@`Eu_`=k_p|RD%*^^0~qjrRf&COQyr^J|SCXsf|9j(&YNNFB8#o zT@fBOFb)rcI3K;i?WZ7-)HxFMmI!B?&Y8h_nC8{NkGBm0nhu46BOVmrJ?= zvyYaxs(lM0YNndKV%!x+?+d%Rq~&S1%DqPvZiv|uC%(i*jD42qZmFmcr|k)g)sj?G zhU9j0ZX&BCzkjYEp2RcRoHP(uh;3c5MY@=4sUxpIcNZSs0;v2f&Xo%u3p^61Rm5Xv zj_iR=ba@gaXNPP~@)Ff!{)&^&%Nt#DEq=O6i8$)i^A%L_wLx+dc=Bg>Ph3WXRkFBv zauVcyNveGI6yy1}k?4o))xIQIZ&S1_VQWNYyBl9OBNU&W{SvCFOZpHV6Cdf zPb28diGgP*1FN|T8dg&hm$r{y@OzGwkd}lQSu9x2p1ASvDf612(sUNfE1nwN#2354 z)S6mZ!`e$J=ioip)uxUU_?9_XC-qCsOO63%@;RW%5_P5_dh$P!*EpOgOm>Es`|2O2Ii|RFyWXfrQ2a zTAgrNnle;Mhs@cnETeuBjg_5=;plg7%EjFzfwSK5MoKpKUh1#=#cu{4Up_N8FL3DJ z$vG_C;1dYbiyV-Q5eyItiO}bPg}v^(c@8?C6lsxLmF$UMU=K3zp;V1JCoY{F_SL#Qrio1QDz`y79dkj^_p1j+mjRrR)nYPTqyH$tE*UXOtvvO+Wy4yRn;kZ>AM(W*T*R^|9b*80fWze2`LsLaMK-)88 zx_iI&vouww5=O$y8ET;n!xQ2d>$$BaPspCuGHU!acj6)-1TFjiN+=Se3q%>+Z?p9a z8bQ;FCvaNsv2#ljfMuZds}vgWU}WWy%RL2+kkjVEhW*Sh#oT9Zna#IET7yXNI0JZ85z(BZ~cg zo<5XL47<%Stjyr`k%jn%=P1P{s&$<+`c_}5aNaRqEzuOw4twafut4s^)A$Jz6oteo zcjIXx1cI;9Afh=mYh7Qz8B)^;D%h&I%8qnEWfn8z$2K8BFqYZa_}!&B;}4oI*zsKm zunvzx=jZ+WKI*AUdp<~7331m&U!D^u)KD#X`y7~hcMH(q#96#=llByca2~gZQb)$@ zG$nMJ4=%h4fQpV1J z8d@W^jj!Rx3k$Ysn(QR*;8P;i#j$H>CL_@gJd#4Qm z3$qG{-xWCah*|gfPc`}EWU&rG;+N`ePrTqb3#=E3QZ`N;cN{bkR7JnzUZmXrCV~Tk zIeGp=1jqdc5nQ}b3lV$_4Ji)&clhadKnnf|4T%`zU#x{(z_`|z@FH|192kmwd@UAM zfvid22SN3(RQ|@huO|Hg2A2l|GGqx*3^#Wp`^Zq_6lT{vxL?`v+f zI7$-wKm9JULb}0^alvYyGh1VMpw7BezL&jTw&Lr8?l$#0>{UreH%6F25{67CX-J+O zR3!U;GOC+1;#lo+M!Q?FXWyE#o_&G7Wyl96?ZbG07Zx4F;dSmNdreoe!XWT%@+OJ@ zfLTh>xJKl1F~5p^_V@~wRo49szM_Ccg!Cu<-3|s4HxdX=g^Bbsp7Vqd@(&_B?tc*B z0sl#a2mFHwuL{0|LO=@qcZ3@V;(`9h7KkXJNFw2HTg1;DekA&%jlTp3fxxPFM*t-F zH&G<)c)3K{_@BqdFyKF;iok!61Oopjswl}x%?0>vWe!O$YHrY^fg5medNFoG()>1Wdad2}sHFKc` z^1xM8ko^8{_{#qScTz<%ehh@b0f=L$iGhEpmqXsz1<}X9RSic{L?V76=HQ0tn3F@& z${A4z`m^Uh1VI3}hB}fVD)0|$!CMuPNZ?wpk-$uXm>l9}F0Q(ND2YQ$TL%%^W#^&h z1Vh*nn$+h|Hg-1qJsBvxQx%C8POX82^B6G$4h1208vw)r05CB9PgPH6GjmKtoR}LE@Slqs z3<4p{Vov=h#slF2@W974k?3IDzxg14U)1*hDTII!p7s|Ez{UOB0se|{Lw?)sFBlI5 z3jI3#Lh62F|OZ;6Bf_TUL1>*#A{idw^JNDZj|Aqm7FQUI; zJUj@4{s%+!4*+liq2T`oe`8#n;J*m|3xgsS+3)K=+T!AbK>tyf3sL*;a$G>b-zNfu zaKpg=7w{hq0wD-e*uOOhgmNNw5dJzCAQS}oyZS+#h@FDJ$ssWCpK`GO&IGZm1^Rm@ zAWrCSU;oRDL0p`GzpE7t1|haJ{wjySe($FJr7jrE^PBSXzu0g6gL$|)|87q(56?fH ziwpRV!SHYa|6wK`E-=sE+vDMaLjOJ(M7KP!zl;Vk7#<$TKh%Z5{-v(}YKsQ~;Q6~R z@IbhE{?Q%;1p2$SemD28x(5GoBpxW|KfD!z{bM=tK)Hc`Hxm!S?*Et_6buE!b@h;* z!$8~|styi_R~bSE@sF58#@^h4`u7O_co3)7rRL{^a09^N5+Xd}k|KzmD=~z(ib+6t zz+g^E2~MCSry%D4UxXU*CKGos6|*!mwQ+H?qvjChfpBs{B)KJ_Vj^Oql3=JLwM%wt==&J From a86e1c89a9c72885171ba6b406934ff3a04bde3b Mon Sep 17 00:00:00 2001 From: Ilian Georgiev Date: Wed, 25 Oct 2017 19:11:04 +0300 Subject: [PATCH 113/381] Remove an unneeded inclusion --- cpp_utils/WiFi.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 78571eba..cd614acd 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -25,7 +25,6 @@ #include #include -#include static const char* LOG_TAG = "WiFi"; From ad30b7317c86f916ed0d07f9aeea98b77cb208fc Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 25 Oct 2017 22:40:17 -0500 Subject: [PATCH 114/381] sync --- cpp_utils/FreeRTOS.cpp | 61 +++++++++++++++++++++++++++------ cpp_utils/FreeRTOS.h | 3 ++ cpp_utils/HttpServer.cpp | 2 +- cpp_utils/Memory.cpp | 18 ++++++++++ cpp_utils/Memory.h | 17 +++++++++ cpp_utils/SockServ.cpp | 2 +- cpp_utils/WiFi.cpp | 74 +++++++++++++++++++++++----------------- 7 files changed, 133 insertions(+), 44 deletions(-) create mode 100644 cpp_utils/Memory.cpp create mode 100644 cpp_utils/Memory.h diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 3cabd91b..4097237a 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -77,23 +77,44 @@ uint32_t FreeRTOS::getTimeSinceStart() { */ uint32_t FreeRTOS::Semaphore::wait(std::string owner) { ESP_LOGV(LOG_TAG, "Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); - xSemaphoreTake(m_semaphore, portMAX_DELAY); + if (m_usePthreads) { + pthread_mutex_lock(&m_pthread_mutex); + } else { + xSemaphoreTake(m_semaphore, portMAX_DELAY); + } m_owner = owner; - xSemaphoreGive(m_semaphore); + if (m_usePthreads) { + pthread_mutex_unlock(&m_pthread_mutex); + } else { + xSemaphoreGive(m_semaphore); + } + ESP_LOGV(LOG_TAG, "Semaphore released: %s", toString().c_str()); m_owner = ""; return m_value; } // wait + FreeRTOS::Semaphore::Semaphore(std::string name) { - m_semaphore = xSemaphoreCreateMutex(); + m_usePthreads = true; + if (m_usePthreads) { + pthread_mutex_init(&m_pthread_mutex, nullptr); + } else { + m_semaphore = xSemaphoreCreateMutex(); + } + m_name = name; m_owner = ""; m_value = 0; } + FreeRTOS::Semaphore::~Semaphore() { - vSemaphoreDelete(m_semaphore); + if (m_usePthreads) { + pthread_mutex_destroy(&m_pthread_mutex); + } else { + vSemaphoreDelete(m_semaphore); + } } @@ -102,7 +123,11 @@ FreeRTOS::Semaphore::~Semaphore() { * The Semaphore is given. */ void FreeRTOS::Semaphore::give() { - xSemaphoreGive(m_semaphore); + if (m_usePthreads) { + pthread_mutex_unlock(&m_pthread_mutex); + } else { + xSemaphoreGive(m_semaphore); + } #ifdef ARDUINO_ARCH_ESP32 FreeRTOS::sleep(10); #endif @@ -119,7 +144,7 @@ void FreeRTOS::Semaphore::give() { void FreeRTOS::Semaphore::give(uint32_t value) { m_value = value; give(); -} +} // give /** @@ -127,7 +152,11 @@ void FreeRTOS::Semaphore::give(uint32_t value) { */ void FreeRTOS::Semaphore::giveFromISR() { BaseType_t higherPriorityTaskWoken; - xSemaphoreGiveFromISR(m_semaphore, &higherPriorityTaskWoken); + if (m_usePthreads) { + assert(false); + } else { + xSemaphoreGiveFromISR(m_semaphore, &higherPriorityTaskWoken); + } } // giveFromISR @@ -138,7 +167,11 @@ void FreeRTOS::Semaphore::giveFromISR() { void FreeRTOS::Semaphore::take(std::string owner) { ESP_LOGD(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); - xSemaphoreTake(m_semaphore, portMAX_DELAY); + if (m_usePthreads) { + pthread_mutex_lock(&m_pthread_mutex); + } else { + xSemaphoreTake(m_semaphore, portMAX_DELAY); + } m_owner = owner; ESP_LOGD(LOG_TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take @@ -152,17 +185,23 @@ void FreeRTOS::Semaphore::take(std::string owner) void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); m_owner = owner; - xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); + if (m_usePthreads) { + assert(false); + } else { + xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); + } ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take + std::string FreeRTOS::Semaphore::toString() { std::stringstream stringStream; stringStream << "name: "<< m_name << " (0x" << std::hex << std::setfill('0') << (uint32_t)m_semaphore << "), owner: " << m_owner; return stringStream.str(); -} +} // toString + void FreeRTOS::Semaphore::setName(std::string name) { m_name = name; -} +} // setName diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index edaa9df4..fe318601 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -9,6 +9,7 @@ #define MAIN_FREERTOS_H_ #include #include +#include #include // Include the base FreeRTOS definitions #include // Include the task definitions @@ -40,9 +41,11 @@ class FreeRTOS { std::string toString(); private: SemaphoreHandle_t m_semaphore; + pthread_mutex_t m_pthread_mutex; std::string m_name; std::string m_owner; uint32_t m_value; + bool m_usePthreads; }; }; diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 168941ad..7b081297 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -84,7 +84,7 @@ HttpServer::~HttpServer() { */ class HttpServerTask: public Task { public: - HttpServerTask(std::string name): Task(name, 16*1024) { + HttpServerTask(std::string name): Task(name, 12*1024) { m_pHttpServer = nullptr; }; diff --git a/cpp_utils/Memory.cpp b/cpp_utils/Memory.cpp new file mode 100644 index 00000000..7180c648 --- /dev/null +++ b/cpp_utils/Memory.cpp @@ -0,0 +1,18 @@ +/* + * Memory.cpp + * + * Created on: Oct 24, 2017 + * Author: kolban + */ + +#include "Memory.h" + +Memory::Memory() { + // TODO Auto-generated constructor stub + +} + +Memory::~Memory() { + // TODO Auto-generated destructor stub +} + diff --git a/cpp_utils/Memory.h b/cpp_utils/Memory.h new file mode 100644 index 00000000..59510356 --- /dev/null +++ b/cpp_utils/Memory.h @@ -0,0 +1,17 @@ +/* + * Memory.h + * + * Created on: Oct 24, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_MEMORY_H_ +#define COMPONENTS_CPP_UTILS_MEMORY_H_ + +class Memory { +public: + Memory(); + virtual ~Memory(); +}; + +#endif /* COMPONENTS_CPP_UTILS_MEMORY_H_ */ diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 7c7bcf99..55416823 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -172,7 +172,7 @@ void SockServ::start() { //m_serverSocket.setSSL(m_useSSL); m_serverSocket.listen(m_port); // Create a socket and start listening on it. ESP_LOGD(LOG_TAG, "Now listening on port %d", m_port); - FreeRTOS::startTask(acceptTask, "acceptTask", this, 16*1024); + FreeRTOS::startTask(acceptTask, "acceptTask", this, 8*1024); } // start diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 78571eba..1d275453 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -343,37 +343,49 @@ std::string WiFi::getStaSSID() { * @return A vector of WiFiAPRecord instances. */ std::vector WiFi::scan() { - ::nvs_flash_init(); - ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); - ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK( esp_wifi_start() ); - wifi_scan_config_t conf; - memset(&conf, 0, sizeof(conf)); - conf.show_hidden = true; - esp_err_t rc = ::esp_wifi_scan_start(&conf, true); - if (rc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); - } - uint16_t apCount; - rc = ::esp_wifi_scan_get_ap_num(&apCount); - ESP_LOGD(LOG_TAG, "Count of found access points: %d", apCount); - wifi_ap_record_t *list = - (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); - ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, list)); - std::vector apRecords; - for (auto i=0; i> scan"); + std::vector apRecords; + + ::nvs_flash_init(); + ::tcpip_adapter_init(); + + ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK( esp_wifi_start() ); + + wifi_scan_config_t conf; + memset(&conf, 0, sizeof(conf)); + conf.show_hidden = true; + + esp_err_t rc = ::esp_wifi_scan_start(&conf, true); + if (rc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); + return apRecords; + } + + uint16_t apCount; // Number of access points available. + rc = ::esp_wifi_scan_get_ap_num(&apCount); + ESP_LOGD(LOG_TAG, "Count of found access points: %d", apCount); + + wifi_ap_record_t* list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); + if (list == nullptr) { + ESP_LOGE(LOG_TAG, "Failed to allocate memory"); + return apRecords; + } + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, list)); + + for (auto i=0; i Date: Wed, 25 Oct 2017 22:53:32 -0500 Subject: [PATCH 115/381] Changes for #138 --- cpp_utils/WiFi.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 124ca73e..2332e97f 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "sdkconfig.h" @@ -379,11 +380,15 @@ std::vector WiFi::scan() { for (auto i=0; i rhs.m_rssi;}); return apRecords; } // scan From 8d98d9418ed854973176a6a4ce3e43a7f5d5e6c4 Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 28 Oct 2017 10:19:18 +0200 Subject: [PATCH 116/381] Refactoring BLEAdvertisedDevice + update examples --- cpp_utils/BLEAdvertisedDevice.cpp | 39 +++++++++++-------- cpp_utils/BLEAdvertisedDevice.h | 3 +- cpp_utils/tests/BLETests/SampleClient.cpp | 2 +- .../tests/BLETests/SampleClient_Notify.cpp | 2 +- cpp_utils/tests/BLETests/SampleServer.cpp | 9 ++--- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 83869cf6..a332c7e2 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -108,10 +108,21 @@ BLEScan* BLEAdvertisedDevice::getScan() { * @brief Get the Service UUID. * @return The Service UUID of the advertised device. */ -BLEUUID BLEAdvertisedDevice::getServiceUUID() { - return m_serviceUUID; +BLEUUID BLEAdvertisedDevice::getServiceUUID() { //TODO Remove it eventually, is no longer useful + return m_serviceUUIDs[0]; } // getServiceUUID +/** + * @brief Check advertised serviced for existence required UUID + * @return Return true if service is advertised + */ +bool BLEAdvertisedDevice::isAdvertisingService(BLEUUID uuid){ + for (int i = 0; i < m_serviceUUIDs.size(); ++i) { + if(m_serviceUUIDs[i].equals(uuid)) + return true; + } + return false; +} /** * @brief Get the TX Power. @@ -231,23 +242,19 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { break; } // ESP_BLE_AD_TYPE_FLAG - case ESP_BLE_AD_TYPE_16SRV_CMPL: { // Adv Data Type: 0x03 - setServiceUUID(BLEUUID(*reinterpret_cast(payload))); - break; - } // ESP_BLE_AD_TYPE_16SRV_CMPL - + case ESP_BLE_AD_TYPE_16SRV_CMPL: case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02 - setServiceUUID(BLEUUID(*reinterpret_cast(payload))); + for (int var = 0; var < length/2; ++var) { + setServiceUUID(BLEUUID(*reinterpret_cast(payload+var*2))); + } break; } // ESP_BLE_AD_TYPE_16SRV_PART - case ESP_BLE_AD_TYPE_32SRV_CMPL: { // Adv Data Type: 0x05 - setServiceUUID(BLEUUID(*reinterpret_cast(payload))); - break; - } // ESP_BLE_AD_TYPE_32SRV_CMPL - + case ESP_BLE_AD_TYPE_32SRV_CMPL: case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04 - setServiceUUID(BLEUUID(*reinterpret_cast(payload))); + for (int var = 0; var < length/4; ++var) { + setServiceUUID(BLEUUID(*reinterpret_cast(payload+var*4))); + } break; } // ESP_BLE_AD_TYPE_32SRV_PART @@ -368,9 +375,9 @@ void BLEAdvertisedDevice::setServiceUUID(const char* serviceUUID) { * @param [in] serviceUUID The discovered serviceUUID */ void BLEAdvertisedDevice::setServiceUUID(BLEUUID serviceUUID) { - m_serviceUUID = serviceUUID; + m_serviceUUIDs.push_back(serviceUUID); m_haveServiceUUID = true; - ESP_LOGD(LOG_TAG, "- setServiceUUID(): serviceUUID: %s", serviceUUID.toString().c_str()); + ESP_LOGD(LOG_TAG, "- addServiceUUID(): serviceUUID: %s", serviceUUID.toString().c_str()); } // setRSSI diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index fbdeeec7..aea6da08 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -38,6 +38,7 @@ class BLEAdvertisedDevice { BLEUUID getServiceUUID(); int8_t getTXPower(); + bool isAdvertisingService(BLEUUID uuid); bool haveAppearance(); bool haveManufacturerData(); bool haveName(); @@ -79,7 +80,7 @@ class BLEAdvertisedDevice { std::string m_name; BLEScan* m_pScan; int m_rssi; - BLEUUID m_serviceUUID; + std::vector m_serviceUUIDs; int8_t m_txPower; }; diff --git a/cpp_utils/tests/BLETests/SampleClient.cpp b/cpp_utils/tests/BLETests/SampleClient.cpp index c91942db..3b1c7572 100644 --- a/cpp_utils/tests/BLETests/SampleClient.cpp +++ b/cpp_utils/tests/BLETests/SampleClient.cpp @@ -88,7 +88,7 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); - if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { advertisedDevice.getScan()->stop(); ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); diff --git a/cpp_utils/tests/BLETests/SampleClient_Notify.cpp b/cpp_utils/tests/BLETests/SampleClient_Notify.cpp index 5ebdbf47..aaecbbf7 100644 --- a/cpp_utils/tests/BLETests/SampleClient_Notify.cpp +++ b/cpp_utils/tests/BLETests/SampleClient_Notify.cpp @@ -83,7 +83,7 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); - if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { advertisedDevice.getScan()->stop(); ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); diff --git a/cpp_utils/tests/BLETests/SampleServer.cpp b/cpp_utils/tests/BLETests/SampleServer.cpp index c7dffd1a..70268f46 100644 --- a/cpp_utils/tests/BLETests/SampleServer.cpp +++ b/cpp_utils/tests/BLETests/SampleServer.cpp @@ -18,13 +18,13 @@ class MainBLEServer: public Task { void run(void *data) { ESP_LOGD(LOG_TAG, "Starting BLE work!"); - BLEDevice::init("MYDEVICE"); + BLEDevice::init("ESP32"); BLEServer* pServer = BLEDevice::createServer(); - BLEService* pService = pServer->createService(BLEUUID((uint16_t)0x1234)); + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); BLECharacteristic* pCharacteristic = pService->createCharacteristic( - BLEUUID((uint16_t)0x99AA), + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE @@ -39,8 +39,7 @@ class MainBLEServer: public Task { pService->start(); BLEAdvertising* pAdvertising = pServer->getAdvertising(); - pAdvertising->addServiceUUID(pService->getUUID()); - pAdvertising->addServiceUUID(BLEUUID((uint16_t)0x9876)); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); pAdvertising->start(); ESP_LOGD(LOG_TAG, "Advertising started!"); From e00bf587f40c792167757f5357721b58ac354bcf Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 28 Oct 2017 11:30:36 -0500 Subject: [PATCH 117/381] fixes for #143 --- cpp_utils/WiFi.cpp | 161 ++++++++++++++++++++++++++++++++++++++------- cpp_utils/WiFi.h | 40 +++++------ 2 files changed, 156 insertions(+), 45 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 2332e97f..077a629e 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -19,6 +19,7 @@ #include #include #include +#include "GeneralUtils.h" #include #include #include @@ -164,24 +165,49 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password, bool if (m_eventLoopStarted) { esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. } else { - ESP_ERROR_CHECK(esp_event_loop_init(WiFi::eventHandler, this)); // Initialze the event handler. + esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } m_eventLoopStarted = true; } //ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); - ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); + esp_err_t errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } wifi_config_t sta_config; ::memset(&sta_config, 0, sizeof(sta_config)); ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); ::memcpy(sta_config.sta.password, password.data(), password.size()); sta_config.sta.bssid_set = 0; - ESP_ERROR_CHECK(::esp_wifi_set_config(WIFI_IF_STA, &sta_config)); - ESP_ERROR_CHECK(::esp_wifi_start()); + errRc = ::esp_wifi_set_config(WIFI_IF_STA, &sta_config); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } m_gotIpEvt.take("connectAP"); - ESP_ERROR_CHECK(::esp_wifi_connect()); + errRc = ::esp_wifi_connect(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } m_gotIpEvt.wait("connectAP"); ESP_LOGD(LOG_TAG, "<< connectAP"); } // connectAP @@ -349,12 +375,41 @@ std::vector WiFi::scan() { ::nvs_flash_init(); ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); + // If we have already started the event loop, then change the handler otherwise + // start the event loop. + if (m_eventLoopStarted) { + esp_event_loop_set_cb(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler); + } + else { + esp_err_t errRc = ::esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + m_eventLoopStarted = true; + } + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); - ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK( esp_wifi_start() ); + esp_err_t errRc = ::esp_wifi_init(&cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } wifi_scan_config_t conf; memset(&conf, 0, sizeof(conf)); @@ -375,7 +430,11 @@ std::vector WiFi::scan() { ESP_LOGE(LOG_TAG, "Failed to allocate memory"); return apRecords; } - ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, list)); + errRc = ::esp_wifi_scan_get_ap_records(&apCount, list); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_scan_get_ap_records: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } for (auto i=0; igetEventHandler(), m_pWifiEventHandler); } else { - ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); + esp_err_t errRc = ::esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } m_eventLoopStarted = true; } wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); - ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); - ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_AP) ); + esp_err_t errRc = ::esp_wifi_init(&cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_set_mode(WIFI_MODE_AP); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } wifi_config_t apConfig; ::memset(&apConfig, 0, sizeof(apConfig)); ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); @@ -429,8 +504,16 @@ void WiFi::startAP(const std::string& ssid, const std::string& password) { apConfig.ap.ssid_hidden = 0; apConfig.ap.max_connection = 4; apConfig.ap.beacon_interval = 100; - ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &apConfig) ); - ESP_ERROR_CHECK( esp_wifi_start() ); + errRc = ::esp_wifi_set_config(WIFI_IF_AP, &apConfig); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } ESP_LOGD(LOG_TAG, "<< startAP"); } // startAP @@ -547,7 +630,11 @@ std::string WiFiAPRecord::toString() { } // toString MDNS::MDNS() { - ESP_ERROR_CHECK(mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server)); + esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } MDNS::~MDNS() { @@ -615,22 +702,38 @@ void MDNS::setInstance(const std::string& instance) { * @return N/A. */ void MDNS::serviceAdd(const char* service, const char* proto, uint16_t port) { - ESP_ERROR_CHECK(mdns_service_add(m_mdns_server, service, proto, port)); + esp_err_t errRc = ::mdns_service_add(m_mdns_server, service, proto, port); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_add: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } // serviceAdd void MDNS::serviceInstanceSet(const char* service, const char* proto, const char* instance) { - ESP_ERROR_CHECK(mdns_service_instance_set(m_mdns_server, service, proto, instance)); + esp_err_t errRc = ::mdns_service_instance_set(m_mdns_server, service, proto, instance); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_instance_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } // serviceInstanceSet void MDNS::servicePortSet(const char* service, const char* proto, uint16_t port) { - ESP_ERROR_CHECK(mdns_service_port_set(m_mdns_server, service, proto, port)); + esp_err_t errRc = ::mdns_service_port_set(m_mdns_server, service, proto, port); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_port_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } // servicePortSet void MDNS::serviceRemove(const char* service, const char* proto) { - ESP_ERROR_CHECK(mdns_service_remove(m_mdns_server, service, proto)); + esp_err_t errRc = ::mdns_service_remove(m_mdns_server, service, proto); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_remove: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } // serviceRemove @@ -641,7 +744,11 @@ void MDNS::serviceRemove(const char* service, const char* proto) { * @return N/A. */ void MDNS::setHostname(const char* hostname) { - ESP_ERROR_CHECK(mdns_set_hostname(m_mdns_server,hostname)); + esp_err_t errRc = ::mdns_set_hostname(m_mdns_server,hostname); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_hostname: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } // setHostname @@ -652,5 +759,9 @@ void MDNS::setHostname(const char* hostname) { * @return N/A. */ void MDNS::setInstance(const char* instance) { - ESP_ERROR_CHECK(mdns_set_instance(m_mdns_server, instance)); + esp_err_t errRc = ::mdns_set_instance(m_mdns_server, instance); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_instance: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } // setInstance diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index d087c032..87fd79b5 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -116,29 +116,29 @@ class WiFi { public: WiFi(); ~WiFi(); - void addDNSServer(const std::string& ip); - void addDNSServer(const char* ip); - void addDNSServer(ip_addr_t ip); - void setDNSServer(int numdns, const std::string& ip); - void setDNSServer(int numdns, const char* ip); - void setDNSServer(int numdns, ip_addr_t ip); - struct in_addr getHostByName(const std::string& hostName); - struct in_addr getHostByName(const char* hostName); - void connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); - void dump(); - static std::string getApMac(); + void addDNSServer(const std::string& ip); + void addDNSServer(const char* ip); + void addDNSServer(ip_addr_t ip); + void setDNSServer(int numdns, const std::string& ip); + void setDNSServer(int numdns, const char* ip); + void setDNSServer(int numdns, ip_addr_t ip); + struct in_addr getHostByName(const std::string& hostName); + struct in_addr getHostByName(const char* hostName); + void connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); + void dump(); + static std::string getApMac(); static tcpip_adapter_ip_info_t getApIpInfo(); - static std::string getApSSID(); - static std::string getMode(); + static std::string getApSSID(); + static std::string getMode(); static tcpip_adapter_ip_info_t getStaIpInfo(); - static std::string getStaMac(); - static std::string getStaSSID(); + static std::string getStaMac(); + static std::string getStaSSID(); std::vector scan(); - void startAP(const std::string& ssid, const std::string& passwd); - void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); - void setIPInfo(const char* ip, const char* gw, const char* netmask); - void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); - void setWifiEventHandler(WiFiEventHandler *wifiEventHandler); + void startAP(const std::string& ssid, const std::string& passwd); + void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); + void setIPInfo(const char* ip, const char* gw, const char* netmask); + void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); + void setWifiEventHandler(WiFiEventHandler *wifiEventHandler); }; #endif /* MAIN_WIFI_H_ */ From 27501f2a70181b382641bbb1cfa20c8d9b58a818 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 29 Oct 2017 12:22:22 -0500 Subject: [PATCH 118/381] Fixes for #149 --- cpp_utils/JSON.cpp | 14 ++++++++++++-- cpp_utils/JSON.h | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index f7b296f8..6fa866b5 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -195,10 +195,13 @@ std::size_t JsonArray::size() { return cJSON_GetArraySize(m_node); } // size - +/** + * @brief Constructor + */ JsonObject::JsonObject(cJSON* node) { m_node = node; -} +} // JsonObject + /** * @brief Get the named boolean value from the object. @@ -268,6 +271,13 @@ bool JsonObject::hasItem(std::string name) { } // hasItem +/** + * @brief Determine if this represents a valid JSON node. + * @return True if this is a valid node and false otherwise. + */ +bool JsonObject::isValid() { + return m_node != nullptr; +} // isValid /** * @brief Set the named array property. diff --git a/cpp_utils/JSON.h b/cpp_utils/JSON.h index bca2d647..b41a22fb 100644 --- a/cpp_utils/JSON.h +++ b/cpp_utils/JSON.h @@ -65,6 +65,7 @@ class JsonObject { JsonObject getObject(std::string name); std::string getString(std::string name); bool hasItem(std::string name); + bool isValid(); void setArray(std::string name, JsonArray array); void setBoolean(std::string name, bool value); void setDouble(std::string name, double value); From 36d7e221d114df2c37b611580dcd42da3e676a98 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 29 Oct 2017 12:58:29 -0500 Subject: [PATCH 119/381] Fixes for #143 --- cpp_utils/WiFi.cpp | 304 +++++++++++++++++++---------------- cpp_utils/WiFi.h | 3 +- cpp_utils/WiFiEventHandler.h | 61 ++++--- 3 files changed, 193 insertions(+), 175 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 077a629e..7958f0ba 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -43,8 +43,6 @@ static void setDNSServer(char *ip) { */ - - /** * @brief Creates and uses a default event handler */ @@ -69,6 +67,7 @@ WiFi::~WiFi() { delete m_pWifiEventHandler; } + /** * @brief Add a reference to a DNS server. * @@ -87,22 +86,26 @@ WiFi::~WiFi() { * @return N/A. */ void WiFi::addDNSServer(const std::string& ip) { - addDNSServer(ip.c_str()); + addDNSServer(ip.c_str()); } // addDNSServer + void WiFi::addDNSServer(const char* ip) { - ip_addr_t dns_server; - if(inet_pton(AF_INET, ip, &dns_server)) - addDNSServer(ip); + ip_addr_t dns_server; + if(inet_pton(AF_INET, ip, &dns_server)) { + addDNSServer(ip); + } } // addDNSServer + void WiFi::addDNSServer(ip_addr_t ip) { - ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); - ::dns_setserver(m_dnsCount, &ip); - m_dnsCount++; - m_dnsCount %= 2; + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + ::dns_setserver(m_dnsCount, &ip); + m_dnsCount++; + m_dnsCount %= 2; } // addDNSServer + /** * @brief Set a reference to a DNS server. * @@ -120,20 +123,24 @@ void WiFi::addDNSServer(ip_addr_t ip) { * @return N/A. */ void WiFi::setDNSServer(int numdns, const std::string& ip) { - setDNSServer(numdns, ip.c_str()); + setDNSServer(numdns, ip.c_str()); } // setDNSServer + void WiFi::setDNSServer(int numdns, const char* ip) { - ip_addr_t dns_server; - if(inet_pton(AF_INET, ip, &dns_server)) - setDNSServer(numdns, dns_server); + ip_addr_t dns_server; + if(inet_pton(AF_INET, ip, &dns_server)) { + setDNSServer(numdns, dns_server); + } } // setDNSServer + void WiFi::setDNSServer(int numdns, ip_addr_t ip) { - ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); - ::dns_setserver(numdns, &ip); + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + ::dns_setserver(numdns, &ip); } // setDNSServer + /** * @brief Connect to an external access point. * @@ -158,8 +165,6 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password, bool ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); } - - // If the event loop has already started then change the callback else // start the event loop. if (m_eventLoopStarted) { @@ -172,9 +177,8 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password, bool } m_eventLoopStarted = true; } + // Now, one way or another, the event handler is WiFi::eventHandler. - - //ESP_ERROR_CHECK(esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler)); esp_err_t errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -212,25 +216,34 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password, bool ESP_LOGD(LOG_TAG, "<< connectAP"); } // connectAP + /** * @brief Dump diagnostics to the log. */ void WiFi::dump() { - ESP_LOGD(LOG_TAG, "WiFi Dump"); - ESP_LOGD(LOG_TAG, "---------"); - char ipAddrStr[30]; - ip_addr_t ip = ::dns_getserver(0); - inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); - ESP_LOGD(LOG_TAG, "DNS Server[0]: %s", ipAddrStr); + ESP_LOGD(LOG_TAG, "WiFi Dump"); + ESP_LOGD(LOG_TAG, "---------"); + char ipAddrStr[30]; + ip_addr_t ip = ::dns_getserver(0); + inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); + ESP_LOGD(LOG_TAG, "DNS Server[0]: %s", ipAddrStr); } // dump /** * @brief Primary event handler interface. */ -esp_err_t WiFi::eventHandler(void* ctx, system_event_t* event) { - WiFi *pWiFi = (WiFi *)ctx; +/* STATIC */ esp_err_t WiFi::eventHandler(void* ctx, system_event_t* event) { + // This is the common event handler that we have provided for all event processing. It is called for every event + // that is received by the WiFi subsystem. The "ctx" parameter is an instance of the current WiFi object that we are + // processing. We can then retrieve the specific/custom event handler from within it and invoke that. This then makes this + // an indirection vector to the real caller. + + WiFi *pWiFi = (WiFi *)ctx; // retrieve the WiFi object from the passed in context. + + // Invoke the event handler. esp_err_t rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); + // If the event we received indicates that we now have an IP address then unlock the mutex that // indicates we are waiting for an IP. if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { @@ -245,23 +258,22 @@ esp_err_t WiFi::eventHandler(void* ctx, system_event_t* event) { * @return The AP IP Info. */ tcpip_adapter_ip_info_t WiFi::getApIpInfo() { - tcpip_adapter_ip_info_t ipInfo; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); - return ipInfo; + tcpip_adapter_ip_info_t ipInfo; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); + return ipInfo; } // getApIpInfo - /** * @brief Get the MAC address of the AP interface. * @return The MAC address of the AP interface. */ std::string WiFi::getApMac() { - uint8_t mac[6]; - esp_wifi_get_mac(WIFI_IF_AP, mac); - auto mac_str = (char*) malloc(18); - sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(std::move(mac_str)); + uint8_t mac[6]; + esp_wifi_get_mac(WIFI_IF_AP, mac); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); } // getApMac @@ -270,9 +282,9 @@ std::string WiFi::getApMac() { * @return The AP SSID. */ std::string WiFi::getApSSID() { - wifi_config_t conf; - esp_wifi_get_config(WIFI_IF_AP, &conf); - return std::string((char *)conf.sta.ssid); + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_AP, &conf); + return std::string((char *)conf.sta.ssid); } // getApSSID @@ -284,20 +296,21 @@ std::string WiFi::getApSSID() { * @return The IP address of the host or 0.0.0.0 if not found. */ struct in_addr WiFi::getHostByName(const std::string& hostName) { - return getHostByName(hostName.c_str()); + return getHostByName(hostName.c_str()); } // getHostByName + struct in_addr WiFi::getHostByName(const char* hostName) { - struct in_addr retAddr; - struct hostent *he = gethostbyname(hostName); - if (he == nullptr) { - retAddr.s_addr = 0; - ESP_LOGD(LOG_TAG, "Unable to resolve %s - %d", hostName, h_errno); - } else { - retAddr = *(struct in_addr *)(he->h_addr_list[0]); - ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); - } - return retAddr; + struct in_addr retAddr; + struct hostent *he = gethostbyname(hostName); + if (he == nullptr) { + retAddr.s_addr = 0; + ESP_LOGD(LOG_TAG, "Unable to resolve %s - %d", hostName, h_errno); + } else { + retAddr = *(struct in_addr *)(he->h_addr_list[0]); + ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); + } + return retAddr; } // getHostByName @@ -306,20 +319,20 @@ struct in_addr WiFi::getHostByName(const char* hostName) { * @return The WiFi Mode. */ std::string WiFi::getMode() { - wifi_mode_t mode; - esp_wifi_get_mode(&mode); - switch(mode) { - case WIFI_MODE_NULL: - return "WIFI_MODE_NULL"; - case WIFI_MODE_STA: - return "WIFI_MODE_STA"; - case WIFI_MODE_AP: - return "WIFI_MODE_AP"; - case WIFI_MODE_APSTA: - return "WIFI_MODE_APSTA"; - default: - return "unknown"; - } + wifi_mode_t mode; + esp_wifi_get_mode(&mode); + switch(mode) { + case WIFI_MODE_NULL: + return "WIFI_MODE_NULL"; + case WIFI_MODE_STA: + return "WIFI_MODE_STA"; + case WIFI_MODE_AP: + return "WIFI_MODE_AP"; + case WIFI_MODE_APSTA: + return "WIFI_MODE_APSTA"; + default: + return "unknown"; + } } // getMode @@ -328,9 +341,9 @@ std::string WiFi::getMode() { * @return The STA IP Info. */ tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { - tcpip_adapter_ip_info_t ipInfo; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - return ipInfo; + tcpip_adapter_ip_info_t ipInfo; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + return ipInfo; } // getStaIpInfo @@ -339,11 +352,11 @@ tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { * @return The MAC address of the STA interface. */ std::string WiFi::getStaMac() { - uint8_t mac[6]; - esp_wifi_get_mac(WIFI_IF_STA, mac); - auto mac_str = (char*) malloc(18); - sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(std::move(mac_str)); + uint8_t mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, mac); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); } // getStaMac @@ -352,9 +365,9 @@ std::string WiFi::getStaMac() { * @return The STA SSID. */ std::string WiFi::getStaSSID() { - wifi_config_t conf; - esp_wifi_get_config(WIFI_IF_STA, &conf); - return std::string((char *)conf.ap.ssid); + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_STA, &conf); + return std::string((char *)conf.ap.ssid); } // getStaSSID @@ -378,16 +391,16 @@ std::vector WiFi::scan() { // If we have already started the event loop, then change the handler otherwise // start the event loop. if (m_eventLoopStarted) { - esp_event_loop_set_cb(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler); - } - else { - esp_err_t errRc = ::esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler); + esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. + } else { + esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); } m_eventLoopStarted = true; } + // Now, one way or another, the event handler is WiFi::eventHandler. wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_err_t errRc = ::esp_wifi_init(&cfg); @@ -395,16 +408,19 @@ std::vector WiFi::scan() { ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); } + errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); } + errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); } + errRc = ::esp_wifi_start(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -430,6 +446,7 @@ std::vector WiFi::scan() { ESP_LOGE(LOG_TAG, "Failed to allocate memory"); return apRecords; } + errRc = ::esp_wifi_scan_get_ap_records(&apCount, list); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_scan_get_ap_records: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -437,12 +454,12 @@ std::vector WiFi::scan() { } for (auto i=0; igetEventHandler(), m_pWifiEventHandler); - } - else { - esp_err_t errRc = ::esp_event_loop_init(m_pWifiEventHandler->getEventHandler(), m_pWifiEventHandler); + esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. + } else { + esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); } m_eventLoopStarted = true; } + // Now, one way or another, the event handler is WiFi::eventHandler. + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_err_t errRc = ::esp_wifi_init(&cfg); @@ -547,25 +566,25 @@ void WiFi::setWifiEventHandler(WiFiEventHandler* wifiEventHandler) { * @return N/A. */ void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask) { - setIPInfo(ip.c_str(), gw.c_str(), netmask.c_str()); + setIPInfo(ip.c_str(), gw.c_str(), netmask.c_str()); } // setIPInfo void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { - uint32_t new_ip; - uint32_t new_gw; - uint32_t new_netmask; + uint32_t new_ip; + uint32_t new_gw; + uint32_t new_netmask; - auto success = (bool)inet_pton(AF_INET, ip, &new_ip); - success = success && inet_pton(AF_INET, gw, &new_gw); - success = success && inet_pton(AF_INET, netmask, &new_netmask); + auto success = (bool)inet_pton(AF_INET, ip, &new_ip); + success = success && inet_pton(AF_INET, gw, &new_gw); + success = success && inet_pton(AF_INET, netmask, &new_netmask); - if(!success) { - return; - } + if(!success) { + return; + } - setIPInfo(new_ip, new_gw, new_netmask); + setIPInfo(new_ip, new_gw, new_netmask); } // setIPInfo @@ -576,22 +595,21 @@ void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { * @param [in] netmask Our TCP/IP netmask value. */ void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { - this->ip = ip; - this->gw = gw; - this->netmask = netmask; - - if(ip != 0 && gw != 0 && netmask != 0) { - tcpip_adapter_ip_info_t ipInfo; - ipInfo.ip.addr = ip; - ipInfo.gw.addr = gw; - ipInfo.netmask.addr = netmask; - - ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - } else { - ip = 0; - ::tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); - } + this->ip = ip; + this->gw = gw; + this->netmask = netmask; + + if(ip != 0 && gw != 0 && netmask != 0) { + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + } else { + ip = 0; + ::tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + } } // setIPInfo @@ -601,23 +619,23 @@ void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { * @return A string representation of the WiFi access point record. */ std::string WiFiAPRecord::toString() { - std::string auth; - switch(getAuthMode()) { - case WIFI_AUTH_OPEN: - auth = "WIFI_AUTH_OPEN"; - break; - case WIFI_AUTH_WEP: - auth = "WIFI_AUTH_WEP"; - break; - case WIFI_AUTH_WPA_PSK: - auth = "WIFI_AUTH_WPA_PSK"; - break; - case WIFI_AUTH_WPA2_PSK: - auth = "WIFI_AUTH_WPA2_PSK"; - break; - case WIFI_AUTH_WPA_WPA2_PSK: - auth = "WIFI_AUTH_WPA_WPA2_PSK"; - break; + std::string auth; + switch(getAuthMode()) { + case WIFI_AUTH_OPEN: + auth = "WIFI_AUTH_OPEN"; + break; + case WIFI_AUTH_WEP: + auth = "WIFI_AUTH_WEP"; + break; + case WIFI_AUTH_WPA_PSK: + auth = "WIFI_AUTH_WPA_PSK"; + break; + case WIFI_AUTH_WPA2_PSK: + auth = "WIFI_AUTH_WPA2_PSK"; + break; + case WIFI_AUTH_WPA_WPA2_PSK: + auth = "WIFI_AUTH_WPA_WPA2_PSK"; + break; default: auth = ""; break; @@ -630,18 +648,18 @@ std::string WiFiAPRecord::toString() { } // toString MDNS::MDNS() { - esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } + esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } MDNS::~MDNS() { - if (m_mdns_server != nullptr) { - mdns_free(m_mdns_server); - } - m_mdns_server = nullptr; + if (m_mdns_server != nullptr) { + mdns_free(m_mdns_server); + } + m_mdns_server = nullptr; } /** diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 87fd79b5..37584534 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "FreeRTOS.h" #include "WiFiEventHandler.h" @@ -104,7 +105,7 @@ class WiFiAPRecord { */ class WiFi { private: - static esp_err_t eventHandler(void* ctx, system_event_t* event); + static esp_err_t eventHandler(void* ctx, system_event_t* event); uint32_t ip; uint32_t gw; uint32_t netmask; diff --git a/cpp_utils/WiFiEventHandler.h b/cpp_utils/WiFiEventHandler.h index 0791089a..612dc8f3 100644 --- a/cpp_utils/WiFiEventHandler.h +++ b/cpp_utils/WiFiEventHandler.h @@ -79,40 +79,39 @@ */ class WiFiEventHandler { public: - WiFiEventHandler(); - virtual ~WiFiEventHandler(); - system_event_cb_t getEventHandler(); - virtual esp_err_t apStaConnected(); - virtual esp_err_t apStaDisconnected(); - virtual esp_err_t apStart(); - virtual esp_err_t apStop(); - virtual esp_err_t staConnected(); - virtual esp_err_t staDisconnected(); - virtual esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip); - virtual esp_err_t staStart(); - virtual esp_err_t staStop(); - virtual esp_err_t wifiReady(); + WiFiEventHandler(); + virtual ~WiFiEventHandler(); + virtual esp_err_t apStaConnected(); + virtual esp_err_t apStaDisconnected(); + virtual esp_err_t apStart(); + virtual esp_err_t apStop(); + system_event_cb_t getEventHandler(); + virtual esp_err_t staConnected(); + virtual esp_err_t staDisconnected(); + virtual esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip); + virtual esp_err_t staStart(); + virtual esp_err_t staStop(); + virtual esp_err_t wifiReady(); - /** - * Get the next WiFi event handler in the chain, if there is one. - * @return The next WiFi event handler in the chain or nullptr if there is none. - */ - WiFiEventHandler *getNextHandler() { - return m_nextHandler; - } - - /** - * Set the next WiFi event handler in the chain. - * @param [in] nextHandler The next WiFi event handler in the chain. - */ - void setNextHandler(WiFiEventHandler* nextHandler) { - this->m_nextHandler = nextHandler; - } + /** + * Get the next WiFi event handler in the chain, if there is one. + * @return The next WiFi event handler in the chain or nullptr if there is none. + */ + WiFiEventHandler *getNextHandler() { + return m_nextHandler; + } + /** + * Set the next WiFi event handler in the chain. + * @param [in] nextHandler The next WiFi event handler in the chain. + */ + void setNextHandler(WiFiEventHandler* nextHandler) { + this->m_nextHandler = nextHandler; + } private: - friend class WiFi; - WiFiEventHandler *m_nextHandler; - static esp_err_t eventHandler(void* ctx, system_event_t* event); + friend class WiFi; + WiFiEventHandler *m_nextHandler; + static esp_err_t eventHandler(void* ctx, system_event_t* event); }; #endif /* MAIN_WIFIEVENTHANDLER_H_ */ From 6153b3208e19facfee089a592d84d27f9a19853e Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 29 Oct 2017 17:26:27 -0500 Subject: [PATCH 120/381] Changes for #140 --- cpp_utils/WiFi.cpp | 134 +++++++++++++++++++-------------------------- cpp_utils/WiFi.h | 2 + 2 files changed, 57 insertions(+), 79 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 7958f0ba..bb5c5069 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -53,10 +53,7 @@ WiFi::WiFi() , m_pWifiEventHandler(nullptr) { m_eventLoopStarted = false; - ::nvs_flash_init(); - wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); - esp_wifi_init(&config); - ::tcpip_adapter_init(); + m_initCalled = false; m_pWifiEventHandler = new WiFiEventHandler(); } // WiFi @@ -100,6 +97,7 @@ void WiFi::addDNSServer(const char* ip) { void WiFi::addDNSServer(ip_addr_t ip) { ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + init(); ::dns_setserver(m_dnsCount, &ip); m_dnsCount++; m_dnsCount %= 2; @@ -137,6 +135,7 @@ void WiFi::setDNSServer(int numdns, const char* ip) { void WiFi::setDNSServer(int numdns, ip_addr_t ip) { ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + init(); ::dns_setserver(numdns, &ip); } // setDNSServer @@ -154,6 +153,8 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { void WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ ESP_LOGD(LOG_TAG, ">> connectAP"); + init(); + if (ip != 0 && gw != 0 && netmask != 0) { ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client @@ -165,27 +166,7 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password, bool ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); } - // If the event loop has already started then change the callback else - // start the event loop. - if (m_eventLoopStarted) { - esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. - } else { - esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - m_eventLoopStarted = true; - } - // Now, one way or another, the event handler is WiFi::eventHandler. - - esp_err_t errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); + esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); @@ -217,6 +198,7 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password, bool } // connectAP + /** * @brief Dump diagnostics to the log. */ @@ -258,6 +240,7 @@ void WiFi::dump() { * @return The AP IP Info. */ tcpip_adapter_ip_info_t WiFi::getApIpInfo() { + //init(); tcpip_adapter_ip_info_t ipInfo; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); return ipInfo; @@ -270,6 +253,7 @@ tcpip_adapter_ip_info_t WiFi::getApIpInfo() { */ std::string WiFi::getApMac() { uint8_t mac[6]; + //init(); esp_wifi_get_mac(WIFI_IF_AP, mac); auto mac_str = (char*) malloc(18); sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); @@ -283,6 +267,7 @@ std::string WiFi::getApMac() { */ std::string WiFi::getApSSID() { wifi_config_t conf; + //init(); esp_wifi_get_config(WIFI_IF_AP, &conf); return std::string((char *)conf.sta.ssid); } // getApSSID @@ -372,21 +357,9 @@ std::string WiFi::getStaSSID() { /** - * @brief Perform a WiFi scan looking for access points. - * - * An access point scan is performed and a vector of WiFi access point records - * is built and returned with one record per found scan instance. The scan is - * performed in a blocking fashion and will not return until the set of scanned - * access points has been built. - * - * @return A vector of WiFiAPRecord instances. + * @brief Initialize WiFi. */ -std::vector WiFi::scan() { - ESP_LOGD(LOG_TAG, ">> scan"); - std::vector apRecords; - - ::nvs_flash_init(); - ::tcpip_adapter_init(); +/* PRIVATE */ void WiFi::init() { // If we have already started the event loop, then change the handler otherwise // start the event loop. @@ -402,20 +375,44 @@ std::vector WiFi::scan() { } // Now, one way or another, the event handler is WiFi::eventHandler. - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - esp_err_t errRc = ::esp_wifi_init(&cfg); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } + if (!m_initCalled) { + ::nvs_flash_init(); + ::tcpip_adapter_init(); - errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t errRc = ::esp_wifi_init(&cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } + m_initCalled = true; +} // init + - errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); +/** + * @brief Perform a WiFi scan looking for access points. + * + * An access point scan is performed and a vector of WiFi access point records + * is built and returned with one record per found scan instance. The scan is + * performed in a blocking fashion and will not return until the set of scanned + * access points has been built. + * + * @return A vector of WiFiAPRecord instances. + */ +std::vector WiFi::scan() { + ESP_LOGD(LOG_TAG, ">> scan"); + std::vector apRecords; + + init(); + + esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); @@ -478,41 +475,16 @@ std::vector WiFi::scan() { */ void WiFi::startAP(const std::string& ssid, const std::string& password) { ESP_LOGD(LOG_TAG, ">> startAP: ssid: %s", ssid.c_str()); - ::nvs_flash_init(); - ::tcpip_adapter_init(); - - // If we have already started the event loop, then change the handler otherwise - // start the event loop. - - if (m_eventLoopStarted) { - esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. - } else { - esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - m_eventLoopStarted = true; - } - // Now, one way or another, the event handler is WiFi::eventHandler. + init(); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - esp_err_t errRc = ::esp_wifi_init(&cfg); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - errRc = ::esp_wifi_set_mode(WIFI_MODE_AP); + esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_AP); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); } + + // Build the apConfig structure. wifi_config_t apConfig; ::memset(&apConfig, 0, sizeof(apConfig)); ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); @@ -523,11 +495,13 @@ void WiFi::startAP(const std::string& ssid, const std::string& password) { apConfig.ap.ssid_hidden = 0; apConfig.ap.max_connection = 4; apConfig.ap.beacon_interval = 100; + errRc = ::esp_wifi_set_config(WIFI_IF_AP, &apConfig); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); } + errRc = ::esp_wifi_start(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -595,6 +569,8 @@ void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { * @param [in] netmask Our TCP/IP netmask value. */ void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { + init(); + this->ip = ip; this->gw = gw; this->netmask = netmask; diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 37584534..0a980af0 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -106,12 +106,14 @@ class WiFiAPRecord { class WiFi { private: static esp_err_t eventHandler(void* ctx, system_event_t* event); + void init(); uint32_t ip; uint32_t gw; uint32_t netmask; WiFiEventHandler* m_pWifiEventHandler; uint8_t m_dnsCount=0; bool m_eventLoopStarted; + bool m_initCalled; FreeRTOS::Semaphore m_gotIpEvt = FreeRTOS::Semaphore("GotIpEvt"); public: From b600ac4b2af01b3476479f4149934c3c2676138a Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 30 Oct 2017 19:32:31 -0500 Subject: [PATCH 121/381] #152 --- cpp_utils/GeneralUtils.cpp | 12 ++--- cpp_utils/GeneralUtils.h | 16 +++---- cpp_utils/HttpServer.cpp | 69 +++++++++++++++++++++++++--- cpp_utils/HttpServer.h | 19 +++++++- cpp_utils/Memory.cpp | 92 +++++++++++++++++++++++++++++++++++--- cpp_utils/Memory.h | 22 +++++++-- cpp_utils/WiFi.cpp | 2 +- 7 files changed, 197 insertions(+), 35 deletions(-) diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 6bfde84f..e8eb3a0a 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -24,15 +24,6 @@ static const char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; -GeneralUtils::GeneralUtils() { - // TODO Auto-generated constructor stub - -} - -GeneralUtils::~GeneralUtils() { - // TODO Auto-generated destructor stub -} - static int base64EncodedLength(size_t length) { return (length + 2 - ((length + 2) % 3)) / 3 * 4; } // base64EncodedLength @@ -272,6 +263,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { } */ + /** * @brief Dump a representation of binary data to the console. * @@ -315,6 +307,7 @@ void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { } } // hexDump + /** * @brief Convert an IP address to string. * @param ip The 4 byte IP address. @@ -404,6 +397,7 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { return "Unknown ESP_ERR error"; } // errorToString + /** * @brief Restart the ESP32. */ diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index ce75e43b..2d55abfc 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -16,15 +16,13 @@ */ class GeneralUtils { public: - GeneralUtils(); - virtual ~GeneralUtils(); - static bool base64Encode(const std::string &in, std::string *out); - static bool base64Decode(const std::string &in, std::string *out); - static bool endsWith(std::string str, char c); - static const char *errorToString(esp_err_t errCode); - static void hexDump(const uint8_t *pData, uint32_t length); - static std::string ipToString(uint8_t *ip); - static void restart(); + static bool base64Decode(const std::string& in, std::string* out); + static bool base64Encode(const std::string& in, std::string* out); + static bool endsWith(std::string str, char c); + static const char* errorToString(esp_err_t errCode); + static void hexDump(const uint8_t* pData, uint32_t length); + static std::string ipToString(uint8_t* ip); + static void restart(); }; #endif /* COMPONENTS_CPP_UTILS_GENERALUTILS_H_ */ diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 7b081297..5578993f 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -12,6 +12,7 @@ #include #include "HttpRequest.h" #include "HttpResponse.h" +#include "Memory.h" #include "FileSystem.h" #include "WebSocket.h" #include "GeneralUtils.h" @@ -232,7 +233,7 @@ class HttpServerTask: public Task { */ void HttpServer::addPathHandler( std::string method, - std::string pathExpr, + std::regex* pathExpr, void (*handler)(HttpRequest *pHttpRequest, HttpResponse *pHttpResponse)) { // We are maintaining a C++ vector of PathHandler objects. We add a new entry into that vector. @@ -240,6 +241,36 @@ void HttpServer::addPathHandler( } // addPathHandler +/** + * @brief Register a handler for a path. + * + * When a browser request arrives, the request will contain a method (GET, POST, etc) and a path + * to be accessed. Using this method we can register a regular expression and, if the incoming method + * and path match the expression, the corresponding handler will be called. + * + * Example: + * @code{.cpp} + * static void handle_REST_WiFi(WebServer::HttpRequest *pRequest, WebServer::HttpResponse *pResponse) { + * ... + * } + * + * webServer.addPathHandler("GET", "/ESP32/WiFi", handle_REST_WiFi); + * @endcode + * + * @param [in] method The method being used for access ("GET", "POST" etc). + * @param [in] path The plain path being accessed. + * @param [in] handler The callback function to be invoked when a request arrives. + */ +void HttpServer::addPathHandler( + std::string method, + std::string path, + void (*handler)(HttpRequest *pHttpRequest, HttpResponse *pHttpResponse)) { + + // We are maintaining a C++ vector of PathHandler objects. We add a new entry into that vector. + m_pathHandlers.push_back(PathHandler(method, path, handler)); +} // addPathHandler + + /** * @brief Get the port number on which the HTTP Server is listening. * @return The port number on which the HTTP server is listening. @@ -333,15 +364,36 @@ void HttpServer::stop() { * @param [in] pathPattern The path pattern to be matched. * @param [in] webServerRequestHandler The request handler to be called. */ -PathHandler::PathHandler(std::string method, std::string pathPattern, +PathHandler::PathHandler(std::string method, std::regex *pRegex, + void (*pWebServerRequestHandler) + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ) { + m_method = method; // Save the method we are looking for. + m_pRegex = pRegex; // Save the Regex + m_textPattern = ""; // The plain text of the regex pattern. + m_isRegex = true; + m_pRequestHandler = pWebServerRequestHandler; // The handler to be invoked if the pattern matches. +} // PathHandler + + +/** + * @brief Construct an instance of a PathHandler. + * + * @param [in] method The method to be matched. + * @param [in] pathPattern The path to be matched. Must be an exact match. + * @param [in] webServerRequestHandler The request handler to be called. + */ +PathHandler::PathHandler(std::string method, std::string matchPath, void (*pWebServerRequestHandler) ( HttpRequest* pHttpRequest, HttpResponse* pHttpResponse) ) { m_method = method; // Save the method we are looking for. - m_pattern = std::regex(pathPattern); // Create the Regex pattern. - m_textPattern = pathPattern; // The plain text of the regex pattern. + m_textPattern = matchPath; + m_isRegex = false; m_pRequestHandler = pWebServerRequestHandler; // The handler to be invoked if the pattern matches. } // PathHandler @@ -354,11 +406,16 @@ PathHandler::PathHandler(std::string method, std::string pathPattern, * @return True if the path matches. */ bool PathHandler::match(std::string method, std::string path) { - ESP_LOGD("PathHandler", "matching: %s with %s", m_textPattern.c_str(), path.c_str()); if (method != m_method) { return false; } - return std::regex_search(path, m_pattern); + if (m_isRegex) { + ESP_LOGD("PathHandler", "regex matching: %s with %s", m_textPattern.c_str(), path.c_str()); + + return std::regex_search(path, *m_pRegex); + } + ESP_LOGD("PathHandler", "plain matching: %s with %s", m_textPattern.c_str(), path.c_str()); + return m_textPattern.compare(0, m_textPattern.length(), path) ==0; } // match diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index 35d13383..4b7e3083 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -34,11 +34,20 @@ class PathHandler { HttpRequest* pHttpRequest, HttpResponse* pHttpResponse) ); + PathHandler( + std::string method, // The method in the request to be matched. + std::regex* pathPattern, // The pattern in the request to be matched (regex) + void (*pWebServerRequestHandler) // The handler function to be invoked upon a match. + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ); bool match(std::string method, std::string path); // Does the request method and pattern match? void invokePathHandler(HttpRequest* request, HttpResponse* response); private: std::string m_method; - std::regex m_pattern; + std::regex* m_pRegex; + bool m_isRegex; std::string m_textPattern; void (*m_pRequestHandler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse); }; // PathHandler @@ -57,6 +66,14 @@ class HttpServer { HttpRequest* pHttpRequest, HttpResponse* pHttpResponse) ); + void addPathHandler( + std::string method, + std::regex* pRegex, + void (*webServerRequestHandler) + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ); uint16_t getPort(); // Get the port on which the Http server is listening. std::string getRootPath(); // Get the root of the file system path. bool getSSL(); // Are we using SSL? diff --git a/cpp_utils/Memory.cpp b/cpp_utils/Memory.cpp index 7180c648..c5e7028b 100644 --- a/cpp_utils/Memory.cpp +++ b/cpp_utils/Memory.cpp @@ -4,15 +4,95 @@ * Created on: Oct 24, 2017 * Author: kolban */ - +#include "sdkconfig.h" +#ifdef CONFIG_HEAP_TRACING #include "Memory.h" -Memory::Memory() { - // TODO Auto-generated constructor stub - +#include +#include "GeneralUtils.h" +extern "C" { +#include +#include } +#include + +static const char* LOG_TAG = "Memory"; + +heap_trace_record_t* Memory::m_pRecords = nullptr; +size_t Memory::m_lastHeapSize = 0; + +/** + * @brief Dump the trace records from the heap. + */ +void Memory::dump() { + ::heap_trace_dump(); +} // dump + -Memory::~Memory() { - // TODO Auto-generated destructor stub +/* STATIC */ void Memory::dumpHeapChange(std::string tag) { + size_t currentUsage = heap_caps_get_free_size(MALLOC_CAP_8BIT); + int diff = currentUsage - m_lastHeapSize; + ESP_LOGD(LOG_TAG, "%s: Heap changed by %d bytes (%d to %d)", tag.c_str(), diff, m_lastHeapSize, currentUsage); + m_lastHeapSize = currentUsage; } +/** + * @brief Initialize heap recording. + * @param [in] recordCount The maximum number of records to be recorded. + */ +void Memory::init(uint32_t recordCount) { + assert(recordCount > 0); + if (m_pRecords != nullptr) { + ESP_LOGE(LOG_TAG, "Already initialized"); + return; + } + + m_pRecords = new heap_trace_record_t[recordCount]; // Allocate the maximum number of records to be recorded. + if (m_pRecords == nullptr) { + ESP_LOGE(LOG_TAG, "Unable to create %d heap records", recordCount); + } + + esp_err_t errRc = ::heap_trace_init_standalone(m_pRecords, recordCount); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_init_standalone: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // init + + +void Memory::resumeTrace() { + esp_err_t errRc = ::heap_trace_resume(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_resume: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // resumeTrace + + +void Memory::startTraceAll() { + esp_err_t errRc = ::heap_trace_start(HEAP_TRACE_ALL); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // startTraceAll + + +void Memory::startTraceLeaks() { + esp_err_t errRc = ::heap_trace_start(HEAP_TRACE_LEAKS); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // startTraceLeaks + + +void Memory::stopTrace() { + esp_err_t errRc = ::heap_trace_stop(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // stopTrace + +#endif diff --git a/cpp_utils/Memory.h b/cpp_utils/Memory.h index 59510356..dca5a838 100644 --- a/cpp_utils/Memory.h +++ b/cpp_utils/Memory.h @@ -7,11 +7,27 @@ #ifndef COMPONENTS_CPP_UTILS_MEMORY_H_ #define COMPONENTS_CPP_UTILS_MEMORY_H_ +#include "sdkconfig.h" +#ifdef CONFIG_HEAP_TRACING +#include +extern "C" { +#include +} class Memory { public: - Memory(); - virtual ~Memory(); -}; + static void dump(); + static void dumpHeapChange(std::string tag); + static void init(uint32_t recordCount); + static void resumeTrace(); + static void startTraceAll(); + static void startTraceLeaks(); + static void stopTrace(); + +private: + static heap_trace_record_t* m_pRecords; + static size_t m_lastHeapSize; // Size of last heap recorded. +}; +#endif #endif /* COMPONENTS_CPP_UTILS_MEMORY_H_ */ diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index bb5c5069..94a90271 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -242,7 +242,7 @@ void WiFi::dump() { tcpip_adapter_ip_info_t WiFi::getApIpInfo() { //init(); tcpip_adapter_ip_info_t ipInfo; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); + ::tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); return ipInfo; } // getApIpInfo From 006c88c48a48801c498baf2c969f45388e2e49a1 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Tue, 31 Oct 2017 22:41:20 -0500 Subject: [PATCH 122/381] Aditions for #153 --- cpp_utils/JSON.cpp | 7 +++++++ cpp_utils/JSON.h | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index 6fa866b5..72a8da70 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -6,6 +6,8 @@ */ +// See: https://github.com/DaveGamble/cJSON + #include #include #include "JSON.h" @@ -202,6 +204,11 @@ JsonObject::JsonObject(cJSON* node) { m_node = node; } // JsonObject +JsonArray JsonObject::getArray(std::string name) { + cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + return JsonArray(node); +} + /** * @brief Get the named boolean value from the object. diff --git a/cpp_utils/JSON.h b/cpp_utils/JSON.h index b41a22fb..ed009f8c 100644 --- a/cpp_utils/JSON.h +++ b/cpp_utils/JSON.h @@ -46,6 +46,9 @@ class JsonArray { void addString(std::string value); std::string toString(); std::size_t size(); +private: + friend class JSON; + friend class JsonObject; /** * @brief The underlying cJSON node. */ @@ -59,6 +62,7 @@ class JsonArray { class JsonObject { public: JsonObject(cJSON* node); + JsonArray getArray(std::string name); bool getBoolean(std::string name); double getDouble(std::string name); int getInt(std::string name); @@ -74,6 +78,9 @@ class JsonObject { void setString(std::string name, std::string value); std::string toString(); +private: + friend class JSON; + friend class JsonArray; /** * @brief The underlying cJSON node. */ From 190cfac536f74a782da7571c9f0b77610772ffff Mon Sep 17 00:00:00 2001 From: Philip Ashmore Date: Wed, 1 Nov 2017 16:21:58 +0000 Subject: [PATCH 123/381] Fixes for esp idf commit 79f206be47c3f608615c1de8c491107e6c9194bb and start of esp-idf integration --- Kconfig | 23 +++++++++++++++++++++++ component.mk | 35 +++++++++++++++++++++++++++++++++++ cpp_utils/BLEClient.h | 2 +- cpp_utils/BLEUtils.cpp | 16 ++++++---------- cpp_utils/Doxyfile | 2 +- 5 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 Kconfig create mode 100644 component.mk diff --git a/Kconfig b/Kconfig new file mode 100644 index 00000000..36b07fb6 --- /dev/null +++ b/Kconfig @@ -0,0 +1,23 @@ +menu "nkolban's c++ wrappers" + +menuconfig NKOLBAN + bool "nkolban's c++ wrappers" + default y + help + Select this option to use nkolban's c++ wrappers. + +menuconfig NKOLBAN_BLE + bool "bluetooth wrappers" + depends on BT_ENABLED && NKOLBAN + default y + help + Select this option to use nkolban's c++ bluetooth ble wrappers. + +menuconfig NKOLBAN_BLE2902 + bool "BLE2902" + depends on NKOLBAN_BLE + default y + help + Select this option to use nkolban's c++ bluetooth BLE2902 wrappers. + +endmenu \ No newline at end of file diff --git a/component.mk b/component.mk new file mode 100644 index 00000000..03383fab --- /dev/null +++ b/component.mk @@ -0,0 +1,35 @@ +c := cpp_utils +COMPONENT_EXTRA_INCLUDES := $(realpath $c) +COMPONENT_ADD_INCLUDEDIRS := $c +COMPONENT_SRCDIRS := $c + +$(call compile_only_if,$(CONFIG_NKOLBAN), \ +$c/Task.o \ +$c/FreeRTOS.o \ +$c/GeneralUtils.o \ +) + +$(call compile_only_if,$(CONFIG_NKOLBAN_BLE), \ +$c/BLEAddress.o \ +$c/BLEAdvertisedDevice.o \ +$c/BLEAdvertising.o \ +$c/BLECharacteristic.o \ +$c/BLECharacteristicCallbacks.o \ +$c/BLECharacteristicMap.o \ +$c/BLEClient.o \ +$c/BLEDescriptor.o \ +$c/BLEDescriptorMap.o \ +$c/BLEDevice.o \ +$c/BLERemoteCharacteristic.o \ +$c/BLERemoteDescriptor.o \ +$c/BLERemoteService.o \ +$c/BLEScan.o \ +$c/BLEServer.o \ +$c/BLEService.o \ +$c/BLEServiceMap.o \ +$c/BLEUUID.o \ +$c/BLEUtils.o \ +$c/BLEValue.o \ +) + +$(call compile_only_if,$(CONFIG_NKOLBAN_BLE2902),$c/BLE2902.o) diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 25704a34..b24c71ae 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include "BLERemoteService.h" #include "BLEService.h" #include "BLEAddress.h" diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index a91bbcc9..5d5c4c0a 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -1286,8 +1286,7 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t conn_id // - esp_bd_addr_t remote_bda case ESP_GATTC_CONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[staus: %s, conn_id: %d, remote_bda: %s]", - BLEUtils::gattStatusToString(evtParam->connect.status).c_str(), + ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s]", evtParam->connect.conn_id, BLEAddress(evtParam->connect.remote_bda).toString().c_str() ); @@ -1302,8 +1301,7 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t conn_id // - esp_bd_addr_t remote_bda case ESP_GATTC_DISCONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[staus: %s, conn_id: %d, remote_bda: %s]", - BLEUtils::gattStatusToString(evtParam->disconnect.status).c_str(), + ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s]", evtParam->disconnect.conn_id, BLEAddress(evtParam->disconnect.remote_bda).toString().c_str() ); @@ -1586,10 +1584,9 @@ void BLEUtils::dumpGattServerEvent( } // ESP_GATTS_CONGEST_EVT case ESP_GATTS_CONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, is_connected: %d]", + ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s]", evtParam->connect.conn_id, - BLEAddress(evtParam->connect.remote_bda).toString().c_str(), - evtParam->connect.is_connected); + BLEAddress(evtParam->connect.remote_bda).toString().c_str()); break; } // ESP_GATTS_CONNECT_EVT @@ -1603,10 +1600,9 @@ void BLEUtils::dumpGattServerEvent( } // ESP_GATTS_CREATE_EVT case ESP_GATTS_DISCONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, is_connected: %d]", + ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s]", evtParam->connect.conn_id, - BLEAddress(evtParam->connect.remote_bda).toString().c_str(), - evtParam->connect.is_connected); + BLEAddress(evtParam->connect.remote_bda).toString().c_str()); break; } // ESP_GATTS_DISCONNECT_EVT diff --git a/cpp_utils/Doxyfile b/cpp_utils/Doxyfile index 5ac15096..cd186876 100644 --- a/cpp_utils/Doxyfile +++ b/cpp_utils/Doxyfile @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = /home/kolban/esp32/esptest/apps/workspace/esp32-snippets/cpp_utils/docs +OUTPUT_DIRECTORY = docs # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and From 79d58a0f55ad0efbebbf59b3ec5791b5fc763b87 Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 2 Nov 2017 01:24:18 +0100 Subject: [PATCH 124/381] Fix issue #157 --- .gitignore | 3 +++ cpp_utils/BLEClient.cpp | 1 + cpp_utils/BLEDevice.cpp | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..46b14a0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.project +.cproject +.settings/ diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 721e5efb..ba858287 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -117,6 +117,7 @@ void BLEClient::disconnect() { ESP_LOGE(LOG_TAG, "esp_ble_gattc_close: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + esp_ble_gattc_app_unregister(getGattcIf()); m_peerAddress = BLEAddress("00:00:00:00:00:00"); ESP_LOGD(LOG_TAG, "<< disconnect()"); } // disconnect diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index e58cac7f..199210a2 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -187,7 +187,7 @@ void BLEDevice::init(std::string deviceName) { return; } - errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; From e2555685f5503f39d1c2369df39cd0ddda4896d0 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 1 Nov 2017 20:51:00 -0500 Subject: [PATCH 125/381] Change semaphore usage to FreeRTIS #156 --- cpp_utils/FreeRTOS.cpp | 2 +- cpp_utils/HttpServer.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 4097237a..b8384512 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -96,7 +96,7 @@ uint32_t FreeRTOS::Semaphore::wait(std::string owner) { FreeRTOS::Semaphore::Semaphore(std::string name) { - m_usePthreads = true; + m_usePthreads = false; // Are we using pThreads or FreeRTOS? if (m_usePthreads) { pthread_mutex_init(&m_pthread_mutex, nullptr); } else { diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 5578993f..9149ec47 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -12,7 +12,6 @@ #include #include "HttpRequest.h" #include "HttpResponse.h" -#include "Memory.h" #include "FileSystem.h" #include "WebSocket.h" #include "GeneralUtils.h" From 6aac6e08fef931b5960e35c78182c163b7b790ee Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 2 Nov 2017 18:51:57 -0500 Subject: [PATCH 126/381] Changes for #162 --- cpp_utils/BLEUtils.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 5d5c4c0a..4a08c7c6 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -33,7 +33,7 @@ static std::map g_connIdMap; typedef struct { uint32_t assignedNumber; - std::string name; + const char* name; } member_t; static const member_t members_ids[] = { @@ -291,7 +291,7 @@ static const member_t members_ids[] = { typedef struct { uint32_t assignedNumber; - std::string name; + const char* name; } gattdescriptor_t; static const gattdescriptor_t g_descriptor_ids[] = { @@ -315,7 +315,7 @@ static const gattdescriptor_t g_descriptor_ids[] = { typedef struct { uint32_t assignedNumber; - std::string name; + const char* name; } characteristicMap_t; static const characteristicMap_t g_characteristicsMappings[] = { @@ -542,8 +542,8 @@ static const characteristicMap_t g_characteristicsMappings[] = { * @brief Mapping from service ids to names */ typedef struct { - std::string name; - std::string type; + const char* name; + const char* type; uint32_t assignedNumber; } gattService_t; @@ -1821,9 +1821,9 @@ const char* BLEUtils::gapEventToString(uint32_t eventType) { std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID) { const characteristicMap_t *p = g_characteristicsMappings; - while (p->name.length() > 0) { + while (strlen(p->name) > 0) { if (p->assignedNumber == characteristicUUID) { - return p->name; + return std::string(p->name); } p++; } @@ -1838,9 +1838,9 @@ std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID */ std::string BLEUtils::gattDescriptorUUIDToString(uint32_t descriptorUUID) { gattdescriptor_t* p = (gattdescriptor_t *)g_descriptor_ids; - while (p->name.length() > 0) { + while (strlen(p->name) > 0) { if (p->assignedNumber == descriptorUUID) { - return p->name; + return std::string(p->name); } p++; } @@ -1874,9 +1874,9 @@ std::string BLEUtils::gattServiceIdToString(esp_gatt_srvc_id_t srvcId) { std::string BLEUtils::gattServiceToString(uint32_t serviceId) { gattService_t* p = (gattService_t *)g_gattServices; - while (p->name.length() > 0) { + while (strlen(p->name) > 0) { if (p->assignedNumber == serviceId) { - return p->name; + return std::string(p->name); } p++; } @@ -1988,9 +1988,9 @@ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { std::string BLEUtils::getMember(uint32_t memberId) { member_t* p = (member_t *)members_ids; - while (p->name.length() > 0) { + while (strlen(p->name) > 0) { if (p->assignedNumber == memberId) { - return p->name; + return std::string(p->name); } p++; } From 2e3283297bcd4b0eb1de5f9aa4973e761aa7639d Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 2 Nov 2017 23:18:21 -0500 Subject: [PATCH 127/381] Save ram for #162 --- cpp_utils/BLEDevice.cpp | 3 ++- cpp_utils/HttpRequest.cpp | 48 +++++++++++++++++++-------------------- cpp_utils/HttpRequest.h | 46 ++++++++++++++++++------------------- 3 files changed, 49 insertions(+), 48 deletions(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 199210a2..3ab2c7cf 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -159,7 +159,8 @@ void BLEDevice::gapEventHandler( /** * @brief Retrieve the Scan object that we use for scanning. - * @return The scanning object reference. + * @return The scanning object reference. This is a singleton object. The caller should not + * try and release/delete it. */ BLEScan* BLEDevice::getScan() { if (m_pScan == nullptr) { diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index 45655893..efc8d096 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -45,30 +45,30 @@ static const char* LOG_TAG="HttpRequest"; //static std::string lineTerminator = "\r\n"; -const std::string HttpRequest::HTTP_HEADER_ACCEPT = "Accept"; -const std::string HttpRequest::HTTP_HEADER_ALLOW = "Allow"; -const std::string HttpRequest::HTTP_HEADER_CONNECTION = "Connection"; -const std::string HttpRequest::HTTP_HEADER_CONTENT_LENGTH = "Content-Length"; -const std::string HttpRequest::HTTP_HEADER_CONTENT_TYPE = "Content-Type"; -const std::string HttpRequest::HTTP_HEADER_COOKIE = "Cookie"; -const std::string HttpRequest::HTTP_HEADER_HOST = "Host"; -const std::string HttpRequest::HTTP_HEADER_LAST_MODIFIED = "Last-Modified"; -const std::string HttpRequest::HTTP_HEADER_ORIGIN = "Origin"; -const std::string HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept"; -const std::string HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; -const std::string HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; -const std::string HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version"; -const std::string HttpRequest::HTTP_HEADER_UPGRADE = "Upgrade"; -const std::string HttpRequest::HTTP_HEADER_USER_AGENT = "User-Agent"; - -const std::string HttpRequest::HTTP_METHOD_CONNECT = "CONNECT"; -const std::string HttpRequest::HTTP_METHOD_DELETE = "DELETE"; -const std::string HttpRequest::HTTP_METHOD_GET = "GET"; -const std::string HttpRequest::HTTP_METHOD_HEAD = "HEAD"; -const std::string HttpRequest::HTTP_METHOD_OPTIONS = "OPTIONS"; -const std::string HttpRequest::HTTP_METHOD_PATCH = "PATCH"; -const std::string HttpRequest::HTTP_METHOD_POST = "POST"; -const std::string HttpRequest::HTTP_METHOD_PUT = "PUT"; +const char HttpRequest::HTTP_HEADER_ACCEPT[] = "Accept"; +const char HttpRequest::HTTP_HEADER_ALLOW[] = "Allow"; +const char HttpRequest::HTTP_HEADER_CONNECTION[] = "Connection"; +const char HttpRequest::HTTP_HEADER_CONTENT_LENGTH[] = "Content-Length"; +const char HttpRequest::HTTP_HEADER_CONTENT_TYPE[] = "Content-Type"; +const char HttpRequest::HTTP_HEADER_COOKIE[] = "Cookie"; +const char HttpRequest::HTTP_HEADER_HOST[] = "Host"; +const char HttpRequest::HTTP_HEADER_LAST_MODIFIED[] = "Last-Modified"; +const char HttpRequest::HTTP_HEADER_ORIGIN[] = "Origin"; +const char HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_ACCEPT[] = "Sec-WebSocket-Accept"; +const char HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL[] = "Sec-WebSocket-Protocol"; +const char HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_KEY[] = "Sec-WebSocket-Key"; +const char HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_VERSION[] = "Sec-WebSocket-Version"; +const char HttpRequest::HTTP_HEADER_UPGRADE[] = "Upgrade"; +const char HttpRequest::HTTP_HEADER_USER_AGENT[] = "User-Agent"; + +const char HttpRequest::HTTP_METHOD_CONNECT[] = "CONNECT"; +const char HttpRequest::HTTP_METHOD_DELETE[] = "DELETE"; +const char HttpRequest::HTTP_METHOD_GET[] = "GET"; +const char HttpRequest::HTTP_METHOD_HEAD[] = "HEAD"; +const char HttpRequest::HTTP_METHOD_OPTIONS[] = "OPTIONS"; +const char HttpRequest::HTTP_METHOD_PATCH[] = "PATCH"; +const char HttpRequest::HTTP_METHOD_POST[] = "POST"; +const char HttpRequest::HTTP_METHOD_PUT[] = "PUT"; diff --git a/cpp_utils/HttpRequest.h b/cpp_utils/HttpRequest.h index 3f989cbc..a5a81038 100644 --- a/cpp_utils/HttpRequest.h +++ b/cpp_utils/HttpRequest.h @@ -27,30 +27,30 @@ class HttpRequest { HttpRequest(Socket s); virtual ~HttpRequest(); - static const std::string HTTP_HEADER_ACCEPT; - static const std::string HTTP_HEADER_ALLOW; - static const std::string HTTP_HEADER_CONNECTION; - static const std::string HTTP_HEADER_CONTENT_LENGTH; - static const std::string HTTP_HEADER_CONTENT_TYPE; - static const std::string HTTP_HEADER_COOKIE; - static const std::string HTTP_HEADER_HOST; - static const std::string HTTP_HEADER_LAST_MODIFIED; - static const std::string HTTP_HEADER_ORIGIN; - static const std::string HTTP_HEADER_SEC_WEBSOCKET_ACCEPT; - static const std::string HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL; - static const std::string HTTP_HEADER_SEC_WEBSOCKET_KEY; - static const std::string HTTP_HEADER_SEC_WEBSOCKET_VERSION; - static const std::string HTTP_HEADER_UPGRADE; - static const std::string HTTP_HEADER_USER_AGENT; + static const char HTTP_HEADER_ACCEPT[]; + static const char HTTP_HEADER_ALLOW[]; + static const char HTTP_HEADER_CONNECTION[]; + static const char HTTP_HEADER_CONTENT_LENGTH[]; + static const char HTTP_HEADER_CONTENT_TYPE[]; + static const char HTTP_HEADER_COOKIE[]; + static const char HTTP_HEADER_HOST[]; + static const char HTTP_HEADER_LAST_MODIFIED[]; + static const char HTTP_HEADER_ORIGIN[]; + static const char HTTP_HEADER_SEC_WEBSOCKET_ACCEPT[]; + static const char HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL[]; + static const char HTTP_HEADER_SEC_WEBSOCKET_KEY[]; + static const char HTTP_HEADER_SEC_WEBSOCKET_VERSION[]; + static const char HTTP_HEADER_UPGRADE[]; + static const char HTTP_HEADER_USER_AGENT[]; - static const std::string HTTP_METHOD_CONNECT; - static const std::string HTTP_METHOD_DELETE; - static const std::string HTTP_METHOD_GET; - static const std::string HTTP_METHOD_HEAD; - static const std::string HTTP_METHOD_OPTIONS; - static const std::string HTTP_METHOD_PATCH; - static const std::string HTTP_METHOD_POST; - static const std::string HTTP_METHOD_PUT; + static const char HTTP_METHOD_CONNECT[]; + static const char HTTP_METHOD_DELETE[]; + static const char HTTP_METHOD_GET[]; + static const char HTTP_METHOD_HEAD[]; + static const char HTTP_METHOD_OPTIONS[]; + static const char HTTP_METHOD_PATCH[]; + static const char HTTP_METHOD_POST[]; + static const char HTTP_METHOD_PUT[]; void close(); // Close the connection to the client. void dump(); // Diagnostic dump of the Http request. From 90167ad75f9fa1cbdc93384e92e92416ebfd117c Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Fri, 3 Nov 2017 23:07:49 -0500 Subject: [PATCH 128/381] Fixes for #166 --- cpp_utils/CPPNVS.cpp | 13 ++++++-- cpp_utils/HttpRequest.cpp | 2 +- networking/bootwifi/BootWiFi.cpp | 50 ++++++++++++++++++++++++------- networking/bootwifi/README.md | 51 +++++++++----------------------- 4 files changed, 66 insertions(+), 50 deletions(-) diff --git a/cpp_utils/CPPNVS.cpp b/cpp_utils/CPPNVS.cpp index 99588a43..d0a2a0bb 100644 --- a/cpp_utils/CPPNVS.cpp +++ b/cpp_utils/CPPNVS.cpp @@ -6,24 +6,33 @@ */ #include "CPPNVS.h" +#include "GeneralUtils.h" +#include #include #include #include -const char* LOG_TAG = "CPPNVS"; +static const char LOG_TAG[] = "CPPNVS"; /** * @brief Constructor. * * @param [in] name The namespace to open for access. - * @param [in] openMode + * @param [in] openMode The open mode. One of NVS_READWRITE (default) or NVS_READONLY. */ NVS::NVS(std::string name, nvs_open_mode openMode) { + esp_err_t errRc = ::nvs_flash_init(); // Initialize flash + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } m_name = name; ::nvs_open(name.c_str(), openMode, &m_handle); } // NVS +/** + * @brief Desctructor + */ NVS::~NVS() { ::nvs_close(m_handle); } // ~NVS diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index efc8d096..5347f195 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -299,7 +299,7 @@ std::map HttpRequest::parseForm() { std::getline(currentPair, name, '='); // Parse the current form entry into name/value. currentPair >> value; // The value is what remains. map[name] = urlDecode(value); // Decode the field which may have been encoded. - ESP_LOGD(LOG_TAG, " %s = %s", name.c_str(), map[name].c_str()); // Debug + ESP_LOGD(LOG_TAG, " %s = \"%s\"", name.c_str(), map[name].c_str()); // Debug // Add the form entry into the map. } // Processed all form entries. ESP_LOGD(LOG_TAG, "<< parseForm"); // Debug diff --git a/networking/bootwifi/BootWiFi.cpp b/networking/bootwifi/BootWiFi.cpp index a6f8ebb6..531d1e55 100644 --- a/networking/bootwifi/BootWiFi.cpp +++ b/networking/bootwifi/BootWiFi.cpp @@ -45,18 +45,22 @@ typedef struct { tcpip_adapter_ip_info_t ipInfo; // Optional static IP information } connection_info_t; -static bootwifi_callback_t g_callback = NULL; // Callback function to be invoked when we have finished. +//static bootwifi_callback_t g_callback = NULL; // Callback function to be invoked when we have finished. // Forward declarations static void saveConnectionInfo(connection_info_t *pConnectionInfo); -static const char* LOG_TAG = "bootwifi"; +static const char LOG_TAG[] = "bootwifi"; static void dumpConnectionInfo(connection_info_t *pConnectionInfo) { ESP_LOGD(LOG_TAG, "connection_info.ssid = %.*s", SSID_SIZE, pConnectionInfo->ssid); ESP_LOGD(LOG_TAG, "connection_info.password = %.*s", PASSWORD_SIZE, pConnectionInfo->password); + ESP_LOGD(LOG_TAG, "ip: %s, gw: %s, netmask: %s", + GeneralUtils::ipToString((uint8_t*)&pConnectionInfo->ipInfo.ip).c_str(), + GeneralUtils::ipToString((uint8_t*)&pConnectionInfo->ipInfo.gw).c_str(), + GeneralUtils::ipToString((uint8_t*)&pConnectionInfo->ipInfo.netmask).c_str()); } @@ -128,6 +132,7 @@ static void sendForm(HttpRequest* pRequest, HttpResponse* pResponse) { pResponse->close(); } // sendForm + static void copyData(uint8_t* pTarget, size_t targetLength, std::string source) { memset(pTarget, 0, targetLength); size_t copySize = (source.length() > targetLength)? targetLength:source.length(); @@ -135,8 +140,12 @@ static void copyData(uint8_t* pTarget, size_t targetLength, std::string source) if (copySize < targetLength) { pTarget[copySize] = '\0'; } -} +} // copyData + +/** + * @brief Process the form response. + */ static void processForm(HttpRequest* pRequest, HttpResponse* pResponse) { ESP_LOGD(LOG_TAG, ">> processForm"); std::map formMap = pRequest->parseForm(); @@ -145,20 +154,41 @@ static void processForm(HttpRequest* pRequest, HttpResponse* pResponse) { copyData((uint8_t*)connectionInfo.password, PASSWORD_SIZE, formMap["password"]); try { - inet_pton(AF_INET, formMap["ip"].c_str(), &connectionInfo.ipInfo.ip); - } catch(std::out_of_range e) { + std::string ipStr = formMap.at("ip"); + if (ipStr.empty()) { + ESP_LOGD(LOG_TAG, "No IP address using default 0.0.0.0"); + connectionInfo.ipInfo.ip.addr = 0; + } else { + inet_pton(AF_INET, ipStr.c_str(), &connectionInfo.ipInfo.ip); + } + } catch(std::out_of_range& e) { + ESP_LOGD(LOG_TAG, "No IP address using default 0.0.0.0"); connectionInfo.ipInfo.ip.addr = 0; } try { - inet_pton(AF_INET, formMap["gw"].c_str(), &connectionInfo.ipInfo.gw); - } catch(std::out_of_range e) { + std::string gwStr = formMap.at("gw"); + if (gwStr.empty()) { + ESP_LOGD(LOG_TAG, "No GW address using default 0.0.0.0"); + connectionInfo.ipInfo.gw.addr = 0; + } else { + inet_pton(AF_INET, gwStr.c_str(), &connectionInfo.ipInfo.gw); + } + } catch(std::out_of_range& e) { + ESP_LOGD(LOG_TAG, "No GW address using default 0.0.0.0"); connectionInfo.ipInfo.gw.addr = 0; } try { - inet_pton(AF_INET, formMap["netmask"].c_str(), &connectionInfo.ipInfo.netmask); - } catch(std::out_of_range e) { + std::string netmaskStr = formMap.at("netmask"); + if (netmaskStr.empty()) { + ESP_LOGD(LOG_TAG, "No Netmask address using default 0.0.0.0"); + connectionInfo.ipInfo.netmask.addr = 0; + } else { + inet_pton(AF_INET, netmaskStr.c_str(), &connectionInfo.ipInfo.netmask); + } + } catch(std::out_of_range& e) { + ESP_LOGD(LOG_TAG, "No Netmask address using default 0.0.0.0"); connectionInfo.ipInfo.netmask.addr = 0; } @@ -281,5 +311,5 @@ void BootWiFi::boot() { BootWiFi::BootWiFi() { m_httpServerStarted = false; - setAccessPointCredentials("esp32", "password"); + setAccessPointCredentials("esp32", "password"); // Default access point credentials } diff --git a/networking/bootwifi/README.md b/networking/bootwifi/README.md index d46d43d4..31129bb8 100644 --- a/networking/bootwifi/README.md +++ b/networking/bootwifi/README.md @@ -1,47 +1,25 @@ # Bootwifi -It is common to want to start an ESP32 and have it connect to a WiFi environment but how -does one bootstrap it? To connect to a WiFi environment, we typically need to know the -SSID and password of the network to which we wish to connect. But without network connection -to the ESP32, how do we set it? This component provides a potential solution. +It is common to want to start an ESP32 and have it connect to a WiFi environment but how does one bootstrap it? To connect to a WiFi environment, we typically need to know the SSID and password of the network to which we wish to connect. But without network connection to the ESP32, how do we set it? This component provides a potential solution. -The module exposes a function called `bootwifi` which, when called, will own the connection -of the device to the local WiFi environment. To do this, it looks in its flash storage to see -if it has previously been given an SSID/password pair. If it has, it attempts to connect to that -access point and join the network. +The module exposes a function called `bootwifi` which, when called, will own the connection of the device to the local WiFi environment. To do this, it looks in its flash storage to see if it has previously been given an SSID/password pair. If it has, it attempts to connect to that access point and join the network. -However, let us assume that it has never been given that information. In this case it will become -an access point in its own right. Now you can connect to the ESP32 using your phone or other WiFi -device as it will appear as an access point against which we can connect. Once connected, we can -open a browser to it. In the browser page, we will be prompted for the SSID and password we wish -to subsequently use. This will be saved and used from then on. Now the device will connect to that -network. +However, let us assume that it has never been given that information. In this case it will become an access point in its own right. What this means is that the ESP32 will become a WiFi network to which other WiFi devices can connect. Now you can connect to the ESP32 using your phone or other WiFi device as it will appear as an access point against which we can connect. Once connected, we can open a browser to it. In the browser page, we will be prompted for the SSID and password we wish to subsequently use. This will be saved and used from then on. Now the device will connect to that network. -What if we take our ESP32 to a new environment where the previously saved access point is no longer -accessible or we simply just fail to connect? Again, we will fall back into being an access point -and the user will be able to supply new information. +What if we take our ESP32 to a new environment where the previously saved access point is no longer accessible or we simply just fail to connect? Again, we will fall back into being an access point and the user will be able to supply new information. -What if we want to change the access point to which the ESP32 connects even if that access point has -been previously saved and is still connectable? Simple, the ESP32 can check a GPIO pin at startup and, -if that pin is high (default low) then that can be used as a manual indication that we should become -an access point without even attempting to connect to the network. +What if we want to change the access point to which the ESP32 connects even if that access point has been previously saved and is still connectable? Simple, the ESP32 can check a GPIO pin at startup and, if that pin is high (default low) then that can be used as a manual indication that we should become an access point without even attempting to connect to the network. -This code is supplied in the form of an ESP-IDF module. It depends on a partner module called `mongoose` -that provides a Web Server in order to server up the web pages. A build of `mongoose` is available -however, [Cesanta](https://www.cesanta.com/), the makers of Mongoose are still working on a formal -port to the ESP32 which is anticipated to be available before 2017 so we should really wait for that -to become available. +This code is supplied in the form of an ESP-IDF module. + +The logic uses C++ exception handling and hence C++ exception handling must be enabled in `make menuconfig`. ## GPIO boot override -To enable the ability to specify a GPIO pin to override known station information, compile -the code with `-DBOOTWIFI_OVERRIDE_GPIO=` when `` is a GPIO pin number. If the -pin is high at startup, then it will override. The pin is configured as pull-down low so -it need not be artificially held low. The default is no override pin. +To enable the ability to specify a GPIO pin to override known station information, compile the code with `-DBOOTWIFI_OVERRIDE_GPIO=` when `` is a GPIO pin number. If the pin is high at startup, then it will override. The pin is configured as pull-down low so it need not be artificially held low. The default is no override pin. ## Future enhancements There is always room for enhancements: -* Improve the web page shown to the user - Right now it is pretty basic and ideally could be -dramatically improved. Features to be added include +* Improve the web page shown to the user - Right now it is pretty basic and ideally could be dramatically improved. Features to be added include - listing of available access points for selection * Integrate SSL security. * NeoPixel support for visualization of connection status: @@ -57,13 +35,12 @@ dramatically improved. Features to be added include ## Design and implementation notes The parameters for Bootwifi are stored in Non-Volatile Storage (NVS). The name space in NVS -is "bootwifi". The keys are: +is `bootwifi`. The keys are: -* version - The version of the protocol. -* connectionInfo - The details for connection. +* `version` - The version of the protocol. +* `connectionInfo` - The details for connection. -The form shown to the end user sends back a response as an HTTP POST to "/ssidSelected". -which contains the following form fields: +The form shown to the end user sends back a response as an HTTP POST to `/ssidSelected`. which contains the following form fields: * ssid * password From c1011fdc090a728266ca69a6cddcc8a2a840157a Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 7 Nov 2017 04:18:17 +0100 Subject: [PATCH 129/381] Add fromString to BLEUUID --- cpp_utils/BLEDevice.cpp | 121 +++++++++++++++++++++------------------- cpp_utils/BLEUUID.cpp | 19 +++++++ cpp_utils/BLEUUID.h | 1 + 3 files changed, 85 insertions(+), 56 deletions(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 3ab2c7cf..8d7e04f1 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -39,7 +39,7 @@ static const char* LOG_TAG = "BLEDevice"; BLEServer* BLEDevice::m_pServer = nullptr; BLEScan* BLEDevice::m_pScan = nullptr; BLEClient* BLEDevice::m_pClient = nullptr; - +bool initialized = false; /** * @brief Create a new instance of a client. * @return A new instance of the client. @@ -175,68 +175,77 @@ BLEScan* BLEDevice::getScan() { * @param deviceName The device name of the device. */ void BLEDevice::init(std::string deviceName) { - esp_err_t errRc = ::nvs_flash_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - errRc = esp_bt_controller_init(&bt_cfg); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + if(!initialized){ + initialized = true; + esp_err_t errRc = ::nvs_flash_init(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } - errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + errRc = esp_bt_controller_init(&bt_cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#ifndef CLASSIC_BT_ENABLED + // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue + errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#else + errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif + errRc = esp_bluedroid_init(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } - errRc = esp_bluedroid_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + errRc = esp_bluedroid_enable(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } - errRc = esp_bluedroid_enable(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } - errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } - errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } - errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + errRc = ::esp_ble_gap_set_device_name(deviceName.c_str()); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_device_name: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + }; + + esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; + errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + }; } - - errRc = ::esp_ble_gap_set_device_name(deviceName.c_str()); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_set_device_name: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - }; - - esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; - errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - }; - vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. } // init diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 19da5185..6b2311a7 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -144,6 +144,7 @@ BLEUUID::BLEUUID(uint16_t uuid) { m_uuid.len = ESP_UUID_LEN_16; m_uuid.uuid.uuid16 = uuid; m_valueSet = true; + } // BLEUUID @@ -345,4 +346,22 @@ std::string BLEUUID::toString() { std::setw(2) << (int)m_uuid.uuid.uuid128[0]; return ss.str(); } // toString + +BLEUUID BLEUUID::fromString(std::string _uuid){ + uint8_t start = 0; + if(strstr(_uuid.c_str(), "0x") != nullptr){ + start = 2; + } + uint8_t len = _uuid.length() - start; + if(len == 4 ){ + uint16_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); + return BLEUUID(x); + }else if(len == 8){ + uint32_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); + return BLEUUID(x); + }else if (len == 36){ + return BLEUUID(_uuid); + } + return BLEUUID(); +} #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index a11220b9..bbe9b872 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -28,6 +28,7 @@ class BLEUUID { esp_bt_uuid_t* getNative(); BLEUUID to128(); std::string toString(); + static BLEUUID fromString(std::string uuid); private: esp_bt_uuid_t m_uuid; // The underlying UUID structure that this class wraps. From b22f8250286b7c860336cae2851cb5a9e7773248 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 8 Nov 2017 17:55:39 -0600 Subject: [PATCH 130/381] sync --- cpp_utils/BLEDevice.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 3ab2c7cf..41d928f1 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -163,9 +163,12 @@ void BLEDevice::gapEventHandler( * try and release/delete it. */ BLEScan* BLEDevice::getScan() { + //ESP_LOGD(LOG_TAG, ">> getScan"); if (m_pScan == nullptr) { m_pScan = new BLEScan(); + //ESP_LOGD(LOG_TAG, " - creating a new scan object"); } + //ESP_LOGD(LOG_TAG, "<< getScan: Returning object at 0x%x", (uint32_t)m_pScan); return m_pScan; } // getScan From 20e042548dc87d7058642d745ef9470948bc3975 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 8 Nov 2017 18:07:11 -0600 Subject: [PATCH 131/381] #176 --- cpp_utils/BLEScan.cpp | 5 +- cpp_utils/BLEScan.h | 3 +- cpp_utils/FreeRTOS.cpp | 21 ++++--- cpp_utils/FreeRTOS.h | 15 ++--- cpp_utils/I2C.cpp | 92 +++++++++++++++++++++------- cpp_utils/I2C.h | 9 ++- cpp_utils/JSON.h | 5 +- cpp_utils/System.cpp | 6 +- mongoose/webserver/main/component.mk | 2 +- 9 files changed, 112 insertions(+), 46 deletions(-) diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index d3157b7f..176f5a0c 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -177,7 +177,7 @@ void BLEScan::setWindow(uint16_t windowMSecs) { BLEScanResults BLEScan::start(uint32_t duration) { ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); - m_semaphoreScanEnd.take("start"); + m_semaphoreScanEnd.take(std::string("start")); m_scanResults.m_vectorAdvertisedDevices.clear(); @@ -199,8 +199,7 @@ BLEScanResults BLEScan::start(uint32_t duration) { m_stopped = false; - m_semaphoreScanEnd.take("start"); - m_semaphoreScanEnd.give(); + m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. ESP_LOGD(LOG_TAG, "<< start()"); return m_scanResults; diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index bc7f4314..5f0d5962 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -46,8 +46,6 @@ class BLEScanResults { */ class BLEScan { public: - BLEScan(); - void setActiveScan(bool active); void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks); void setInterval(uint16_t intervalMSecs); @@ -56,6 +54,7 @@ class BLEScan { void stop(); private: + BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. friend class BLEDevice; void gapEventHandler( esp_gap_ble_cb_event_t event, diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index b8384512..3a80c698 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -76,21 +76,25 @@ uint32_t FreeRTOS::getTimeSinceStart() { * @return The value associated with the semaphore. */ uint32_t FreeRTOS::Semaphore::wait(std::string owner) { - ESP_LOGV(LOG_TAG, "Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); + ESP_LOGV(LOG_TAG, ">> wait: Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); + + if (m_usePthreads) { pthread_mutex_lock(&m_pthread_mutex); } else { xSemaphoreTake(m_semaphore, portMAX_DELAY); } + m_owner = owner; + if (m_usePthreads) { pthread_mutex_unlock(&m_pthread_mutex); } else { xSemaphoreGive(m_semaphore); } - ESP_LOGV(LOG_TAG, "Semaphore released: %s", toString().c_str()); - m_owner = ""; + ESP_LOGV(LOG_TAG, "<< wait: Semaphore released: %s", toString().c_str()); + m_owner = std::string(""); return m_value; } // wait @@ -104,7 +108,7 @@ FreeRTOS::Semaphore::Semaphore(std::string name) { } m_name = name; - m_owner = ""; + m_owner = std::string(""); m_value = 0; } @@ -123,6 +127,7 @@ FreeRTOS::Semaphore::~Semaphore() { * The Semaphore is given. */ void FreeRTOS::Semaphore::give() { + ESP_LOGV(LOG_TAG, "Semaphore giving: %s", toString().c_str()); if (m_usePthreads) { pthread_mutex_unlock(&m_pthread_mutex); } else { @@ -131,8 +136,8 @@ void FreeRTOS::Semaphore::give() { #ifdef ARDUINO_ARCH_ESP32 FreeRTOS::sleep(10); #endif - ESP_LOGV(LOG_TAG, "Semaphore giving: %s", toString().c_str()); - m_owner = ""; + + m_owner = std::string(""); } // Semaphore::give @@ -183,13 +188,15 @@ void FreeRTOS::Semaphore::take(std::string owner) * @param [in] timeoutMs Timeout in milliseconds. */ void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { + ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); - m_owner = owner; + if (m_usePthreads) { assert(false); } else { xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); } + m_owner = owner; ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index fe318601..30d33be7 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -31,14 +31,15 @@ class FreeRTOS { public: Semaphore(std::string owner = ""); ~Semaphore(); - void give(); - void giveFromISR(); - void give(uint32_t value); - void setName(std::string name); - void take(std::string owner=""); - void take(uint32_t timeoutMs, std::string owner=""); - uint32_t wait(std::string owner=""); + void give(); + void give(uint32_t value); + void giveFromISR(); + void setName(std::string name); + void take(std::string owner=""); + void take(uint32_t timeoutMs, std::string owner=""); std::string toString(); + uint32_t wait(std::string owner=""); + private: SemaphoreHandle_t m_semaphore; pthread_mutex_t m_pthread_mutex; diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index d6a7121a..6f82e6a1 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -12,6 +12,7 @@ #include "I2C.h" #include "sdkconfig.h" #include +#include "GeneralUtils.h" static const char* LOG_TAG = "I2C"; @@ -27,6 +28,7 @@ I2C::I2C() { m_cmd = 0; m_sdaPin = DEFAULT_SDA_PIN; m_sclPin = DEFAULT_CLK_PIN; + m_portNum = I2C_NUM_0; } // I2C @@ -41,7 +43,10 @@ void I2C::beginTransaction() { ESP_LOGD(LOG_TAG, "beginTransaction()"); } m_cmd = ::i2c_cmd_link_create(); - ESP_ERROR_CHECK(::i2c_master_start(m_cmd)); + esp_err_t errRc = ::i2c_master_start(m_cmd); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } m_directionKnown = false; } // beginTransaction @@ -57,8 +62,15 @@ void I2C::endTransaction() { if (debug) { ESP_LOGD(LOG_TAG, "endTransaction()"); } - ESP_ERROR_CHECK(::i2c_master_stop(m_cmd)); - ESP_ERROR_CHECK(::i2c_master_cmd_begin(I2C_NUM_0, m_cmd, 1000/portTICK_PERIOD_MS)); + esp_err_t errRc = ::i2c_master_stop(m_cmd); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_stop: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + + errRc = ::i2c_master_cmd_begin(m_portNum, m_cmd, 1000/portTICK_PERIOD_MS); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_stop: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } ::i2c_cmd_link_delete(m_cmd); m_directionKnown = false; } // endTransaction @@ -83,21 +95,30 @@ uint8_t I2C::getAddress() const * @param [in] sclPin The pin to use for SCL clock. * @return N/A. */ -void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin) { - ESP_LOGD(LOG_TAG, ">> I2c::init. address=%d, sda=%d, scl=%d", address, sdaPin, sclPin); - this->m_sdaPin = sdaPin; - this->m_sclPin = sclPin; - this->m_address = address; +void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin, uint32_t clockSpeed, i2c_port_t portNum) { + ESP_LOGD(LOG_TAG, ">> I2c::init. address=%d, sda=%d, scl=%d, clockSpeed=%d, portNum=%d", address, sdaPin, sclPin, clockSpeed, portNum); + asser(portNum < I2C_NUM_MAX); + m_portNum = portNum; + m_sdaPin = sdaPin; + m_sclPin = sclPin; + m_address = address; + i2c_config_t conf; - conf.mode = I2C_MODE_MASTER; + conf.mode = I2C_MODE_MASTER; conf.sda_io_num = sdaPin; conf.scl_io_num = sclPin; conf.sda_pullup_en = GPIO_PULLUP_ENABLE; conf.scl_pullup_en = GPIO_PULLUP_ENABLE; - conf.master.clk_speed = 100000; - ESP_ERROR_CHECK(::i2c_param_config(I2C_NUM_0, &conf)); + conf.master.clk_speed = 100000; + esp_err_t errRc = ::i2c_param_config(m_portNum, &conf); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_param_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } if (!driverInstalled) { - ESP_ERROR_CHECK(::i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0)); + errRc = ::i2c_driver_install(m_portNum, I2C_MODE_MASTER, 0, 0, 0); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_driver_install: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } driverInstalled = true; } } // init @@ -117,9 +138,15 @@ void I2C::read(uint8_t* bytes, size_t length, bool ack) { } if (m_directionKnown == false) { m_directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack)); + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } + esp_err_t errRc = ::i2c_master_read(m_cmd, bytes, length, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_read: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - ESP_ERROR_CHECK(::i2c_master_read(m_cmd, bytes, length, !ack)); } // read @@ -136,7 +163,10 @@ void I2C::read(uint8_t *byte, bool ack) { } if (m_directionKnown == false) { m_directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack)); + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } } ESP_ERROR_CHECK(::i2c_master_read_byte(m_cmd, byte, !ack)); } // readByte @@ -200,7 +230,7 @@ bool I2C::slavePresent(uint8_t address) { ::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, 1 /* expect ack */); ::i2c_master_stop(cmd); - esp_err_t espRc = ::i2c_master_cmd_begin(I2C_NUM_0, cmd, 100/portTICK_PERIOD_MS); + esp_err_t espRc = ::i2c_master_cmd_begin(m_portNum, cmd, 100/portTICK_PERIOD_MS); i2c_cmd_link_delete(cmd); return espRc == 0; // Return true if the slave is present and false otherwise. } // slavePresent @@ -214,7 +244,10 @@ void I2C::start() { if (debug) { ESP_LOGD(LOG_TAG, "start()"); } - ESP_ERROR_CHECK(::i2c_master_start(m_cmd)); + esp_err_t errRc = ::i2c_master_start(m_cmd); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } } // start @@ -226,7 +259,10 @@ void I2C::stop() { if (debug) { ESP_LOGD(LOG_TAG, "stop()"); } - ESP_ERROR_CHECK(::i2c_master_stop(m_cmd)); + esp_err_t errRc = ::i2c_master_stop(m_cmd); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_stop: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } m_directionKnown = false; } // stop @@ -244,9 +280,15 @@ void I2C::write(uint8_t byte, bool ack) { } if (m_directionKnown == false) { m_directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack)); + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, byte, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, byte, !ack)); } // write @@ -264,9 +306,15 @@ void I2C::write(uint8_t *bytes, size_t length, bool ack) { } if (m_directionKnown == false) { m_directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack)); + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } + esp_err_t errRc = ::i2c_master_write(m_cmd, bytes, length, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - ESP_ERROR_CHECK(::i2c_master_write(m_cmd, bytes, length, !ack)); } // write diff --git a/cpp_utils/I2C.h b/cpp_utils/I2C.h index 5d38af67..9886d63a 100644 --- a/cpp_utils/I2C.h +++ b/cpp_utils/I2C.h @@ -23,22 +23,29 @@ class I2C { bool m_directionKnown; gpio_num_t m_sdaPin; gpio_num_t m_sclPin; + i2c_port_t m_portNum; public: /** * @brief The default SDA pin. */ static const gpio_num_t DEFAULT_SDA_PIN = GPIO_NUM_25; + /** * @brief The default Clock pin. */ static const gpio_num_t DEFAULT_CLK_PIN = GPIO_NUM_26; + /** + * @brief The default Clock speed. + */ + static const uint32_t DEFAULT_CLK_SPEED = 100000; + I2C(); void beginTransaction(); void endTransaction(); uint8_t getAddress() const; - void init(uint8_t address, gpio_num_t sdaPin = DEFAULT_SDA_PIN, gpio_num_t sclPin = DEFAULT_CLK_PIN); + void init(uint8_t address, gpio_num_t sdaPin = DEFAULT_SDA_PIN, gpio_num_t sclPin = DEFAULT_CLK_PIN, uint32_t clkSpeed = DEFAULT_CLK_SPEED, i2c_port_t portNum = I2C_NUM_0); void read(uint8_t* bytes, size_t length, bool ack=true); void read(uint8_t* byte, bool ack=true); void scan(); diff --git a/cpp_utils/JSON.h b/cpp_utils/JSON.h index ed009f8c..6f4be9b9 100644 --- a/cpp_utils/JSON.h +++ b/cpp_utils/JSON.h @@ -33,7 +33,7 @@ class JSON { */ class JsonArray { public: - JsonArray(cJSON* node); + int getInt(int item); JsonObject getObject(int item); std::string getString(int item); @@ -47,6 +47,7 @@ class JsonArray { std::string toString(); std::size_t size(); private: + JsonArray(cJSON* node); friend class JSON; friend class JsonObject; /** @@ -61,7 +62,6 @@ class JsonArray { */ class JsonObject { public: - JsonObject(cJSON* node); JsonArray getArray(std::string name); bool getBoolean(std::string name); double getDouble(std::string name); @@ -79,6 +79,7 @@ class JsonObject { std::string toString(); private: + JsonObject(cJSON* node); friend class JSON; friend class JsonArray; /** diff --git a/cpp_utils/System.cpp b/cpp_utils/System.cpp index 21a0b9bc..eb8cd35d 100644 --- a/cpp_utils/System.cpp +++ b/cpp_utils/System.cpp @@ -8,6 +8,10 @@ #include "System.h" #include +extern "C" { +#include +} + System::System() { // TODO Auto-generated constructor stub @@ -32,7 +36,7 @@ void System::getChipInfo(esp_chip_info_t *info) { * @return The system wide free heap size. */ uint32_t System::getFreeHeapSize() { - return esp_get_free_heap_size(); + return heap_caps_get_free_size(MALLOC_CAP_8BIT); } // getFreeHeapSize diff --git a/mongoose/webserver/main/component.mk b/mongoose/webserver/main/component.mk index b1671d43..186e7eef 100644 --- a/mongoose/webserver/main/component.mk +++ b/mongoose/webserver/main/component.mk @@ -6,5 +6,5 @@ # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, # please read the ESP-IDF documents if you need to do this. # -CFLAGS += -DCS_PLATFORM=3 -DMG_DISABLE_DIRECTORY_LISTING=1 -DMG_DISABLE_DAV=1 -DMG_DISABLE_CGI=1 +CFLAGS += -DCS_PLATFORM=15 -DMG_DISABLE_DIRECTORY_LISTING=1 -DMG_DISABLE_DAV=1 -DMG_DISABLE_CGI=1 include $(IDF_PATH)/make/component_common.mk From 371d9f7b8a51c2c88a93f03d59d21144c28bd3df Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 8 Nov 2017 20:25:53 -0600 Subject: [PATCH 132/381] #178 --- networking/bootwifi/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/networking/bootwifi/README.md b/networking/bootwifi/README.md index 31129bb8..3d93f557 100644 --- a/networking/bootwifi/README.md +++ b/networking/bootwifi/README.md @@ -9,7 +9,7 @@ What if we take our ESP32 to a new environment where the previously saved access What if we want to change the access point to which the ESP32 connects even if that access point has been previously saved and is still connectable? Simple, the ESP32 can check a GPIO pin at startup and, if that pin is high (default low) then that can be used as a manual indication that we should become an access point without even attempting to connect to the network. -This code is supplied in the form of an ESP-IDF module. +This code is supplied in the form of an ESP-IDF module. In addition, BootWiFi has a pre-requisite of the `cpp_utils` component also distributed as part of this repoistory. The logic uses C++ exception handling and hence C++ exception handling must be enabled in `make menuconfig`. From a7066c3dfd4f9d0643ad8eaec9222f39bc171f0f Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 8 Nov 2017 20:36:56 -0600 Subject: [PATCH 133/381] #179 --- cpp_utils/I2C.cpp | 2 +- cpp_utils/WiFi.cpp | 15 +++++++++------ cpp_utils/WiFi.h | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index 6f82e6a1..3aa740c7 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -97,7 +97,7 @@ uint8_t I2C::getAddress() const */ void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin, uint32_t clockSpeed, i2c_port_t portNum) { ESP_LOGD(LOG_TAG, ">> I2c::init. address=%d, sda=%d, scl=%d, clockSpeed=%d, portNum=%d", address, sdaPin, sclPin, clockSpeed, portNum); - asser(portNum < I2C_NUM_MAX); + assert(portNum < I2C_NUM_MAX); m_portNum = portNum; m_sdaPin = sdaPin; m_sclPin = sclPin; diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 94a90271..65d717c9 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -187,13 +187,15 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password, bool abort(); } - m_gotIpEvt.take("connectAP"); + m_connectFinished.take("connectAP"); // Take the semaphore to wait for a connection. + errRc = ::esp_wifi_connect(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); } - m_gotIpEvt.wait("connectAP"); + + m_connectFinished.wait("connectAP"); // Wait for the completion of the connection. ESP_LOGD(LOG_TAG, "<< connectAP"); } // connectAP @@ -226,11 +228,12 @@ void WiFi::dump() { // Invoke the event handler. esp_err_t rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); - // If the event we received indicates that we now have an IP address then unlock the mutex that - // indicates we are waiting for an IP. - if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { - pWiFi->m_gotIpEvt.give(); + // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that + // indicates we are waiting for a connection complete. + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP || event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { + pWiFi->m_connectFinished.give(); } + return rc; } // eventHandler diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 0a980af0..1dcf87fc 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -114,7 +114,7 @@ class WiFi { uint8_t m_dnsCount=0; bool m_eventLoopStarted; bool m_initCalled; - FreeRTOS::Semaphore m_gotIpEvt = FreeRTOS::Semaphore("GotIpEvt"); + FreeRTOS::Semaphore m_connectFinished = FreeRTOS::Semaphore("ConnectFinished"); public: WiFi(); From 1740cd6dfe6ed17ac4b503674ef09870efb6a3dc Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 8 Nov 2017 20:46:29 -0600 Subject: [PATCH 134/381] #166 --- networking/bootwifi/BootWiFi.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/networking/bootwifi/BootWiFi.cpp b/networking/bootwifi/BootWiFi.cpp index 531d1e55..f56016b8 100644 --- a/networking/bootwifi/BootWiFi.cpp +++ b/networking/bootwifi/BootWiFi.cpp @@ -237,7 +237,15 @@ class BootWifiEventHandler: public WiFiEventHandler { m_pBootWiFi->m_wifi.startAP("Duktape", "Duktape"); ESP_LOGD("BootWifiEventHandler", "<< staDisconnected"); return ESP_OK; - } + } // staDisconnected + + + esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { + ESP_LOGD("BootWifiEventHandler", ">> staGotIP"); + m_pBootWiFi->m_completeSemaphore.give(); // If we got an IP address, then we can end the boot process. + ESP_LOGD("BootWifiEventHandler", "<< staGotIP"); + return ESP_OK; + } // staGotIp private: BootWiFi *m_pBootWiFi; From 571e016f1aa4a0f004d8cc9b36713215d814a104 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 8 Nov 2017 23:00:31 -0600 Subject: [PATCH 135/381] Code changes for #180 --- cpp_utils/System.cpp | 9 ++++++++- cpp_utils/System.h | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cpp_utils/System.cpp b/cpp_utils/System.cpp index eb8cd35d..cd15ac13 100644 --- a/cpp_utils/System.cpp +++ b/cpp_utils/System.cpp @@ -35,7 +35,7 @@ void System::getChipInfo(esp_chip_info_t *info) { * @brief Retrieve the system wide free heap size. * @return The system wide free heap size. */ -uint32_t System::getFreeHeapSize() { +size_t System::getFreeHeapSize() { return heap_caps_get_free_size(MALLOC_CAP_8BIT); } // getFreeHeapSize @@ -50,3 +50,10 @@ std::string System::getIDFVersion() { } // getIDFVersion +/** + * @brief Get the smallest heap size seen. + * @return The smallest heap size seen. + */ +size_t System::getMinimumFreeHeapSize() { + return heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT); +} // getMinimumFreeHeapSize diff --git a/cpp_utils/System.h b/cpp_utils/System.h index 4c084300..915807ef 100644 --- a/cpp_utils/System.h +++ b/cpp_utils/System.h @@ -19,8 +19,9 @@ class System { System(); virtual ~System(); static void getChipInfo(esp_chip_info_t *info); - static uint32_t getFreeHeapSize(); + static size_t getFreeHeapSize(); static std::string getIDFVersion(); + static size_t getMinimumFreeHeapSize(); }; #endif /* COMPONENTS_CPP_UTILS_SYSTEM_H_ */ From 3d17166e1f3f4d6cc0044af5ab9530165f031852 Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 9 Nov 2017 10:00:11 +0100 Subject: [PATCH 136/381] Add multi characteristic functionality to resolve issue #175 --- cpp_utils/BLECharacteristic.cpp | 6 +- cpp_utils/BLECharacteristic.h | 4 +- cpp_utils/BLECharacteristicMap.cpp | 8 +- cpp_utils/BLEDescriptor.h | 2 +- cpp_utils/BLEService.cpp | 18 ++-- cpp_utils/BLEService.h | 5 +- .../BLETests/SampleMultiCharacteristics.cpp | 87 +++++++++++++++++++ cpp_utils/tests/BLETests/main.cpp | 6 +- 8 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 cpp_utils/tests/BLETests/SampleMultiCharacteristics.cpp diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 18ba65dd..3d3b1d6c 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -95,7 +95,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; - m_semaphoreCreateEvt.take("executeCreate"); + //m_semaphoreCreateEvt.take("executeCreate"); /* esp_attr_value_t value; @@ -118,7 +118,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { return; } - m_semaphoreCreateEvt.wait("executeCreate"); + //m_semaphoreCreateEvt.wait("executeCreate"); // Now that we have registered the characteristic, we must also register all the descriptors associated with this // characteristic. We iterate through each of those and invoke the registration call to register them with the @@ -245,7 +245,7 @@ void BLECharacteristic::handleGATTServerEvent( case ESP_GATTS_ADD_CHAR_EVT: { if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) && getService()->getHandle()==param->add_char.service_handle) { - m_semaphoreCreateEvt.give(); + //m_semaphoreCreateEvt.give(); } break; } // ESP_GATTS_ADD_CHAR_EVT diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 24977c00..64891029 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -76,7 +76,9 @@ class BLECharacteristic { void setValue(std::string value); void setWriteProperty(bool value); void setWriteNoResponseProperty(bool value); + void executeCreate(BLEService* pService); std::string toString(); + uint16_t getHandle(); static const uint32_t PROPERTY_READ = 1<<0; @@ -105,8 +107,6 @@ class BLECharacteristic { esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); - void executeCreate(BLEService* pService); - uint16_t getHandle(); esp_gatt_char_prop_t getProperties(); BLEService* getService(); void setHandle(uint16_t handle); diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index bcf4a75d..6efb5980 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -55,8 +55,8 @@ BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) { * @return The first characteristic in the map. */ BLECharacteristic* BLECharacteristicMap::getFirst() { - m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) { + m_iterator = m_handleMap.begin(); + if (m_iterator == m_handleMap.end()) { return nullptr; } BLECharacteristic* pRet = m_iterator->second; @@ -70,7 +70,7 @@ BLECharacteristic* BLECharacteristicMap::getFirst() { * @return The next characteristic in the map. */ BLECharacteristic* BLECharacteristicMap::getNext() { - if (m_iterator == m_uuidMap.end()) { + if (m_iterator == m_handleMap.end()) { return nullptr; } BLECharacteristic* pRet = m_iterator->second; @@ -90,7 +90,7 @@ void BLECharacteristicMap::handleGATTServerEvent( esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every Service we have. - for (auto &myPair : m_uuidMap) { + for (auto &myPair : m_handleMap) { myPair.second->handleGATTServerEvent(event, gatts_if, param); } } // handleGATTServerEvent diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index 1d32d500..45856f59 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -37,6 +37,7 @@ class BLEDescriptor { void setValue(uint8_t* data, size_t size); void setValue(std::string value); std::string toString(); + uint16_t getHandle(); private: friend class BLEDescriptorMap; @@ -46,7 +47,6 @@ class BLEDescriptor { uint16_t m_handle; BLECharacteristic* m_pCharacteristic; void executeCreate(BLECharacteristic* pCharacteristic); - uint16_t getHandle(); void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); }; diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 669ed261..b45bd326 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -124,7 +124,7 @@ void BLEService::start() { return; } - +/* BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); while(pCharacteristic != nullptr) { @@ -134,7 +134,7 @@ void BLEService::start() { pCharacteristic = m_characteristicMap.getNext(); } // Start each of the characteristics ... these are found in the m_characteristicMap. - +*/ m_semaphoreStartEvt.take("start"); esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); @@ -186,14 +186,18 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { pCharacteristic->getUUID().toString().c_str(), toString().c_str()); + p_createCharacteristic = pCharacteristic; + pCharacteristic->executeCreate(this); // Check that we don't add the same characteristic twice. - if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { +/* if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { ESP_LOGE(LOG_TAG, "<< Attempt to add a characteristic but we already have one with this UUID"); return; } - - // Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID - // but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT. +*/ + // Remember this characteristic in our map of characteristics. We have mapped characteristic by handle alread, + // now we need to map all characteristics by uuid. + // TODO Now is stored and mapped only one characteristic with unique UUID. We need to change characteristics map + // to store all characteristics, even if there is few characteristics with the same UUID in one service m_characteristicMap.setByUUID(pCharacteristic->getUUID(), pCharacteristic); ESP_LOGD(LOG_TAG, "<< addCharacteristic()"); @@ -245,7 +249,7 @@ void BLEService::handleGATTServerEvent( // for that characteristic. case ESP_GATTS_ADD_CHAR_EVT: { if (m_handle == param->add_char.service_handle) { - BLECharacteristic *pCharacteristic = getCharacteristic(BLEUUID(param->add_char.char_uuid)); + BLECharacteristic *pCharacteristic = p_createCharacteristic; if (pCharacteristic == nullptr) { ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", BLEUUID(param->add_char.char_uuid).toString().c_str()); diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 19b472e9..895169ae 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -42,7 +42,7 @@ class BLECharacteristicMap { private: std::map m_uuidMap; std::map m_handleMap; - std::map::iterator m_iterator; + std::map::iterator m_iterator; }; @@ -66,6 +66,7 @@ class BLEService { BLEServer* getServer(); void start(); std::string toString(); + uint16_t getHandle(); private: friend class BLEServer; @@ -85,8 +86,8 @@ class BLEService { FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); uint32_t m_numHandles; + BLECharacteristic* p_createCharacteristic; - uint16_t getHandle(); BLECharacteristic* getLastCreatedCharacteristic(); void handleGATTServerEvent( esp_gatts_cb_event_t event, diff --git a/cpp_utils/tests/BLETests/SampleMultiCharacteristics.cpp b/cpp_utils/tests/BLETests/SampleMultiCharacteristics.cpp new file mode 100644 index 00000000..400e567a --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleMultiCharacteristics.cpp @@ -0,0 +1,87 @@ +/** + * Create a new BLE server. + */ +//#include "freertos/FreeRTOS.h" +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include +#include "sdkconfig.h" + +#define SERVICE_UUID_128 "91bad492-b950-4226-aa2b-4ede9fa42f59" +#define CHARACTERISTIC_UUID_128 "0d563a58-196a-48ce-ace2-dfec78acc814" + +static char LOG_TAG[] = "SampleServer"; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + + BLEDevice::init(""); + BLEServer* pServer = BLEDevice::createServer(); + + BLEService* pService = pServer->createService(SERVICE_UUID_128); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID_128), + BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + pCharacteristic->setValue("Hello World! Characteristic no 1!"); + + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID_128), + BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + + pCharacteristic->setValue("Hello World! Characteristic no 2!"); + + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID_128), + BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + + pCharacteristic->setValue("Hello World! Characteristic no 3!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + /* + * To add 128bit service UUID to advertising REMEMBER: + * BLEDevice::init("ESP32"); in this line + * name should be very short, in most cases no longer than 5 characters. + * In this case we have also setup appearance, so we need to leave device name empty + */ + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + /* + * Setting appearance + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml + */ + pAdvertising->setAppearance(0x1280); + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(1000000); + } +}; + + +void SampleMultiCharacteristic(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(4000); + pMainBleServer->start(); + +} // app_main diff --git a/cpp_utils/tests/BLETests/main.cpp b/cpp_utils/tests/BLETests/main.cpp index 732ae16d..bdf60fad 100644 --- a/cpp_utils/tests/BLETests/main.cpp +++ b/cpp_utils/tests/BLETests/main.cpp @@ -20,13 +20,14 @@ void SampleScan(void); void SampleSensorTag(void); void SampleServer(void); void SampleWrite(void); - +void SampleMultiCharacteristic(void); // // Un-comment ONE of the following // --- void app_main(void) { //Sample_MLE_15(); //Sample1(); + SampleMultiCharacteristic(); //SampleClient(); //SampleClient_Notify(); //SampleClientAndServer(); @@ -36,6 +37,7 @@ void app_main(void) { //SampleRead(); //SampleSensorTag(); //SampleScan(); - SampleServer(); + //SampleServer(); //SampleWrite(); } // app_main + From 1d8dfbe41abb361a067974515d60c61c6aaa8cf5 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 9 Nov 2017 23:44:47 -0600 Subject: [PATCH 137/381] Implementation of #185 --- cpp_utils/BLEScan.cpp | 12 ++++++++++++ cpp_utils/BLEScan.h | 1 + 2 files changed, 13 insertions(+) diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 176f5a0c..19d44565 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -228,6 +228,17 @@ void BLEScan::stop() { } // stop +/** + * @brief Dump the scan results to the log. + */ +void BLEScanResults::dump() { + ESP_LOGD(LOG_TAG, ">> Dump scan results:"); + for (int i=0; i Date: Fri, 10 Nov 2017 19:06:31 +0100 Subject: [PATCH 138/381] Implementation of #184 --- cpp_utils/BLERemoteService.cpp | 39 +++++++++++++++++++++++++++++++++- cpp_utils/BLERemoteService.h | 4 ++++ cpp_utils/HttpServer.cpp | 2 +- cpp_utils/SockServ.cpp | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index c0df06c3..ab03292f 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -114,7 +114,7 @@ void BLERemoteService::gattClientEventHandler( } // switch // Send the event to each of the characteristics owned by this service. - for (auto &myPair : m_characteristicMap) { + for (auto &myPair : m_characteristicMapByHandle) { myPair.second->gattClientEventHandler(event, gattc_if, evtParam); } } // gattClientEventHandler @@ -154,6 +154,25 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { return nullptr; } // getCharacteristic +BLERemoteCharacteristic* BLERemoteService::getCharacteristic(uint16_t uuid) { +// Design +// ------ +// We wish to retrieve the characteristic given its handle. It is possible that we have not yet asked the +// device what characteristics it has in which case we have nothing to match against. If we have not +// asked the device about its characteristics, then we do that now. Once we get the results we can then +// examine the characteristics map to see if it has the characteristic we are looking for. + if (!m_haveCharacteristics) { + retrieveCharacteristics(); + } + //std::string v = uuid.toString(); + for (auto &myPair : m_characteristicMapByHandle) { + if (myPair.first == uuid) { + return myPair.second; + } + } + return nullptr; +} // getCharacteristic + /** * @brief Retrieve all the characteristics for this service. @@ -257,6 +276,7 @@ void BLERemoteService::retrieveCharacteristics() { this ); + m_characteristicMapByHandle.insert(std::pair(pNewRemoteCharacteristic->getHandle(), pNewRemoteCharacteristic)); m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic->getUUID().toString(), pNewRemoteCharacteristic)); offset++; // Increment our count of number of descriptors found. @@ -283,6 +303,17 @@ std::map* BLERemoteService::getCharacteri return &m_characteristicMap; } // getCharacteristics +void BLERemoteService::getCharacteristics(std::map* ptr) { + ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %#04x", getHandle()); + // If is possible that we have not read the characteristics associated with the service so do that + // now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking + // call and does not return until all the characteristics are available. + if (!m_haveCharacteristics) { + retrieveCharacteristics(); + } + ESP_LOGD(LOG_TAG, "<< getCharacteristics() for service: %#04x", getHandle()); + *ptr = m_characteristicMapByHandle; +} // getCharacteristics BLEClient* BLERemoteService::getClient() { return m_pClient; @@ -329,6 +360,12 @@ void BLERemoteService::removeCharacteristics() { m_characteristicMap.erase(myPair.first); } m_characteristicMap.clear(); // Clear the map + + for (auto &myPair : m_characteristicMapByHandle) { + delete myPair.second; + m_characteristicMapByHandle.erase(myPair.first); + } + m_characteristicMapByHandle.clear(); // Clear the map } // removeCharacteristics diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 2007fa22..7f8c2f4a 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -32,7 +32,9 @@ class BLERemoteService { // Public methods BLERemoteCharacteristic* getCharacteristic(const char* uuid); BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); + BLERemoteCharacteristic* getCharacteristic(uint16_t uuid); std::map* getCharacteristics(); + void getCharacteristics(std::map* ptr); BLEClient* getClient(void); uint16_t getHandle(); @@ -62,6 +64,8 @@ class BLERemoteService { // We maintain a map of characteristics owned by this service keyed by a string representation of the UUID. std::map m_characteristicMap; + // We maintain a map of characteristics owned by this service keyed by a handle. + std::map m_characteristicMapByHandle; bool m_haveCharacteristics; // Have we previously obtained the characteristics. BLEClient* m_pClient; diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 9149ec47..4fd8ceda 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -84,7 +84,7 @@ HttpServer::~HttpServer() { */ class HttpServerTask: public Task { public: - HttpServerTask(std::string name): Task(name, 12*1024) { + HttpServerTask(std::string name): Task(name, 16*1024) { m_pHttpServer = nullptr; }; diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 55416823..e3b67483 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -39,7 +39,7 @@ SockServ::SockServ(uint16_t port) : SockServ() { */ SockServ::SockServ() { m_port = 0; // Unknown port. - m_acceptQueue = xQueueCreate(10, sizeof(Socket)); + m_acceptQueue = xQueueCreate(1, sizeof(Socket)); m_useSSL = false; m_clientSemaphore.take("SockServ"); // Create the queue; deleted in the destructor. } // SockServ From 1a0528e9c3e3dbddfb8442f5f73bf2a6fcaa9fe8 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 12 Nov 2017 22:03:42 -0600 Subject: [PATCH 139/381] #193 --- cpp_utils/BLEDevice.cpp | 3 +- cpp_utils/BLEScan.cpp | 11 ++- cpp_utils/BLEScan.h | 2 +- cpp_utils/BLEUtils.cpp | 10 ++- tools/sdkconfig_compare/README.md | 1 + tools/sdkconfig_compare/sdkconfig_compare.js | 95 ++++++++++++++++++++ 6 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 tools/sdkconfig_compare/README.md create mode 100644 tools/sdkconfig_compare/sdkconfig_compare.js diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 3f1cca2a..0e4887b9 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -152,7 +152,7 @@ void BLEDevice::gapEventHandler( } if (BLEDevice::m_pScan != nullptr) { - BLEDevice::getScan()->gapEventHandler(event, param); + BLEDevice::getScan()->handleGAPEvent(event, param); } } // gapEventHandler @@ -195,6 +195,7 @@ void BLEDevice::init(std::string deviceName) { #ifndef CLASSIC_BT_ENABLED // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); + //errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 19d44565..7c5b7bd9 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -43,7 +43,7 @@ BLEScan::BLEScan() { * @param [in] event The event type for this event. * @param [in] param Parameter data for this event. */ -void BLEScan::gapEventHandler( +void BLEScan::handleGAPEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) { @@ -66,12 +66,21 @@ void BLEScan::gapEventHandler( case ESP_GAP_BLE_SCAN_RESULT_EVT: { switch(param->scan_rst.search_evt) { + // + // ESP_GAP_SEARCH_INQ_CMPL_EVT + // + // Event that indicates that the duration allowed for the search has completed or that we have been + // asked to stop. case ESP_GAP_SEARCH_INQ_CMPL_EVT: { m_stopped = true; m_semaphoreScanEnd.give(); break; } // ESP_GAP_SEARCH_INQ_CMPL_EVT + // + // ESP_GAP_SEARCH_INQ_RES_EVT + // + // Result that has arrived back from a Scan inquiry. case ESP_GAP_SEARCH_INQ_RES_EVT: { if (m_stopped) { // If we are not scanning, nothing to do with the extra results. break; diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index 481ff4c0..edf79a9a 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -57,7 +57,7 @@ class BLEScan { private: BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. friend class BLEDevice; - void gapEventHandler( + void handleGAPEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 4a08c7c6..da338749 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -634,7 +634,7 @@ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { case BLE_ADDR_TYPE_RPA_RANDOM: return "BLE_ADDR_TYPE_RPA_RANDOM"; default: - return "Unknown esp_ble_addr_type_t"; + return " esp_ble_addr_type_t"; } } // addressTypeToString @@ -700,8 +700,8 @@ const char* BLEUtils::advTypeToString(uint8_t advType) { case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; default: - ESP_LOGD(LOG_TAG, "Unknown adv data type: 0x%x", advType); - return "Unknown"; + ESP_LOGD(LOG_TAG, " adv data type: 0x%x", advType); + return ""; } // End switch } // advTypeToString @@ -785,7 +785,7 @@ std::string BLEUtils::buildPrintData(uint8_t* source, size_t length) { std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) { switch(reason) { - case ESP_GATT_CONN_UNKNOWN: + case ESP_GATT_CONN_: return "ESP_GATT_CONN_UNKNOWN"; case ESP_GATT_CONN_L2C_FAILURE: return "ESP_GATT_CONN_L2C_FAILURE"; @@ -1141,6 +1141,8 @@ void BLEUtils::dumpGapEvent( // - ble_adv // - flag // - num_resps + // - adv_data_len + // - scan_rsp_len // case ESP_GAP_BLE_SCAN_RESULT_EVT: { switch(param->scan_rst.search_evt) { diff --git a/tools/sdkconfig_compare/README.md b/tools/sdkconfig_compare/README.md new file mode 100644 index 00000000..78879264 --- /dev/null +++ b/tools/sdkconfig_compare/README.md @@ -0,0 +1 @@ +This is a node.js application which takes as input two files. Each file should be an ESP-IDF `sdkconfig` file. The tool compares the two files and logs the logical distinctions between them. For example, if one file has an entry while the other doesn't, that will be logged. If one file has an entry with a value that is different from the other, that too will be logged. \ No newline at end of file diff --git a/tools/sdkconfig_compare/sdkconfig_compare.js b/tools/sdkconfig_compare/sdkconfig_compare.js new file mode 100644 index 00000000..7ad29ed2 --- /dev/null +++ b/tools/sdkconfig_compare/sdkconfig_compare.js @@ -0,0 +1,95 @@ +// Node.js application for comparing two ESP-IDF configuration files (sdkconfig) +const fs = require("fs"); // Require the file system processing library +const readline = require("readline"); // Require the readline processing library + +// buildMap +// Read the sdkconfig file specified by fileName and produce a map of the name/value pairs contained +// within. A Promise is returned that is fulfilled when the file has been read. +function buildMap(fileName) { + const promise = new Promise(function(resolve, reject) { + var readStream = fs.createReadStream(fileName); + readStream.on("error", (err) => { + reject(err); + }); + const map = {}; + + const lineReader = readline.createInterface({ + input: readStream, + crlfDelay: Infinity + }); + + // Called when a new line has been read from the file. + lineReader.on("line", (line) => { + line = line.trim(); // Trim whitespace from the line. + + if (line.length == 0) { // Ignore empty lines + return; + } + if (line.startsWith("#")) { // Ignore comment lines + return; + } + + const parts = line.split("="); // Split the line into parts separated by the '=' character. + if (map.hasOwnProperty(parts[0])) { + console.log(`Odd ... we found ${parts[0]} twice.`); + } + map[parts[0]] = parts[1]; // Populate the map element. + }); // on(line) + + // Called when all the lines from the file have been consumed. + lineReader.on("close", () => { + resolve(map); + }); // on(close) + + }); + return promise; +} // buildMap + + +const args = process.argv; +if (args.length != 4) { + console.log("Usage: node sdkconfig_compare file1 file2"); + process.exit(); +} +const file1 = args[2]; +const file2 = args[3]; +buildMap(file1).then((result)=> { + buildMap(file2).then((result2) => { + + // Three passes + // In A and not B + // in B and not A + // value different in A and B + for (const prop in result) { + if (result.hasOwnProperty(prop)) { + if (!result2.hasOwnProperty(prop)) { + console.log(`${prop} in ${file1} but not in ${file2}`); + } + } + } + + for (const prop in result2) { + if (result2.hasOwnProperty(prop)) { + if (!result.hasOwnProperty(prop)) { + console.log(`${prop} in ${file2} but not in ${file1}`); + } + } + } + + for (const prop in result) { + if (result.hasOwnProperty(prop)) { + if (result2.hasOwnProperty(prop)) { + if (result[prop] != result2[prop]) { + console.log(`${prop} values different "${result[prop]}" vs "${result2[prop]}"`); + } + } + } + } + }).catch((err) => { + console.log(err); + process.exit(); + }); +}).catch((err) => { + console.log(err); + process.exit(); +}); \ No newline at end of file From 3b27c1ad7501a5e00156e7534730aace13934cb4 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 12 Nov 2017 22:28:10 -0600 Subject: [PATCH 140/381] Emergency path for clean build --- cpp_utils/BLEUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index da338749..a40485bd 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -785,7 +785,7 @@ std::string BLEUtils::buildPrintData(uint8_t* source, size_t length) { std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) { switch(reason) { - case ESP_GATT_CONN_: + case ESP_GATT_CONN_UNKNOWN: return "ESP_GATT_CONN_UNKNOWN"; case ESP_GATT_CONN_L2C_FAILURE: return "ESP_GATT_CONN_L2C_FAILURE"; From 3ffbf70d946717f1f3a49b70bbf5096835e3654a Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 12 Nov 2017 23:21:47 -0600 Subject: [PATCH 141/381] Addition of adFlagsToString #190 --- cpp_utils/BLEUtils.cpp | 29 ++++++++++++++++++++++++++++- cpp_utils/BLEUtils.h | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index a40485bd..0594575f 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -639,6 +639,32 @@ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { } // addressTypeToString +/** + * @brief Convert the BLE Advertising Data flags to a string. + * @param adFlags The flags to convert + * @return std::string A string representation of the advertising flags. + */ +std::string BLEUtils::adFlagsToString(uint8_t adFlags) { + std::stringstream ss; + if (adFlags & (1<<0)) { + ss << "[LE Limited Discoverable Mode] "; + } + if (adFlags & (1<<1)) { + ss << "[LE General Discoverable Mode] "; + } + if (adFlags & (1<<2)) { + ss << "[BR/EDR Not Supported] "; + } + if (adFlags & (1<<3)) { + ss << "[Simultaneous LE and BR/EDR to Same Device Capable (Controller)] "; + } + if (adFlags & (1<<4)) { + ss << "[Simultaneous LE and BR/EDR to Same Device Capable (Host)] "; + } + return ss.str(); +} // adFlagsToString + + /** * @brief Given an advertising type, return a string representation of the type. * @@ -1147,7 +1173,7 @@ void BLEUtils::dumpGapEvent( case ESP_GAP_BLE_SCAN_RESULT_EVT: { switch(param->scan_rst.search_evt) { case ESP_GAP_SEARCH_INQ_RES_EVT: { - ESP_LOGD(LOG_TAG, "search_evt: %s, bda: %s, dev_type: %s, ble_addr_type: %s, ble_evt_type: %s, rssi: %d, ble_adv: ??, flag: %d, num_resps: %d, adv_data_len: %d, scan_rsp_len: %d", + ESP_LOGD(LOG_TAG, "search_evt: %s, bda: %s, dev_type: %s, ble_addr_type: %s, ble_evt_type: %s, rssi: %d, ble_adv: ??, flag: %d (%s), num_resps: %d, adv_data_len: %d, scan_rsp_len: %d", searchEventTypeToString(param->scan_rst.search_evt), BLEAddress(param->scan_rst.bda).toString().c_str(), devTypeToString(param->scan_rst.dev_type), @@ -1155,6 +1181,7 @@ void BLEUtils::dumpGapEvent( eventTypeToString(param->scan_rst.ble_evt_type), param->scan_rst.rssi, param->scan_rst.flag, + adFlagsToString(param->scan_rst.flag).c_str(), param->scan_rst.num_resps, param->scan_rst.adv_data_len, param->scan_rst.scan_rsp_len diff --git a/cpp_utils/BLEUtils.h b/cpp_utils/BLEUtils.h index 5ef6c378..b2baee53 100644 --- a/cpp_utils/BLEUtils.h +++ b/cpp_utils/BLEUtils.h @@ -21,6 +21,7 @@ class BLEUtils { public: static const char* addressTypeToString(esp_ble_addr_type_t type); + static std::string adFlagsToString(uint8_t adFlags); static const char* advTypeToString(uint8_t advType); static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id=0); static esp_gatt_srvc_id_t buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary=true); From a7982b4ae0e1e81c476ef0f8d73632dc41724f0f Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 13 Nov 2017 19:28:05 -0600 Subject: [PATCH 142/381] Code changes for #197 --- cpp_utils/BLEDevice.cpp | 22 ++++++++++++++++++++++ cpp_utils/BLEDevice.h | 3 +++ 2 files changed, 25 insertions(+) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 0e4887b9..a701459b 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -254,4 +254,26 @@ void BLEDevice::init(std::string deviceName) { } // init +/** + * @brief Set the transmission power. + * The power level can be one of: + * * ESP_PWR_LVL_N14 + * * ESP_PWR_LVL_N11 + * * ESP_PWR_LVL_N8 + * * ESP_PWR_LVL_N5 + * * ESP_PWR_LVL_N2 + * * ESP_PWR_LVL_P1 + * * ESP_PWR_LVL_P4 + * * ESP_PWR_LVL_P7 + * @param [in] powerLevel. + */ +/* STATIC */ void BLEDevice::setPower(esp_power_level_t powerLevel) { + ESP_LOGD(LOG_TAG, ">> setPower: %d", powerLevel); + esp_err_t errRc = ::esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, powerLevel); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + }; + ESP_LOGD(LOG_TAG, "<< setPower"); +} // setPower + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index eb9fdaa7..5a09b5c0 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -13,6 +13,7 @@ #include // ESP32 BLE #include // Part of C++ STL #include +#include #include "BLEServer.h" #include "BLEClient.h" @@ -52,6 +53,8 @@ class BLEDevice { esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); + static void setPower(esp_power_level_t powerLevel); + }; // class BLE #endif // CONFIG_BT_ENABLED From d72aebcaef860c66724358da2b506af3e8c4e642 Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 15 Nov 2017 00:47:02 +0100 Subject: [PATCH 143/381] Revert "Add multiple characteristic functionality to resolve issue #175" --- cpp_utils/BLECharacteristic.cpp | 6 +- cpp_utils/BLECharacteristic.h | 4 +- cpp_utils/BLECharacteristicMap.cpp | 8 +- cpp_utils/BLEDescriptor.h | 2 +- cpp_utils/BLEService.cpp | 18 ++-- cpp_utils/BLEService.h | 5 +- .../BLETests/SampleMultiCharacteristics.cpp | 87 ------------------- cpp_utils/tests/BLETests/main.cpp | 6 +- 8 files changed, 21 insertions(+), 115 deletions(-) delete mode 100644 cpp_utils/tests/BLETests/SampleMultiCharacteristics.cpp diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 3d3b1d6c..18ba65dd 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -95,7 +95,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; - //m_semaphoreCreateEvt.take("executeCreate"); + m_semaphoreCreateEvt.take("executeCreate"); /* esp_attr_value_t value; @@ -118,7 +118,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { return; } - //m_semaphoreCreateEvt.wait("executeCreate"); + m_semaphoreCreateEvt.wait("executeCreate"); // Now that we have registered the characteristic, we must also register all the descriptors associated with this // characteristic. We iterate through each of those and invoke the registration call to register them with the @@ -245,7 +245,7 @@ void BLECharacteristic::handleGATTServerEvent( case ESP_GATTS_ADD_CHAR_EVT: { if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) && getService()->getHandle()==param->add_char.service_handle) { - //m_semaphoreCreateEvt.give(); + m_semaphoreCreateEvt.give(); } break; } // ESP_GATTS_ADD_CHAR_EVT diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 64891029..24977c00 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -76,9 +76,7 @@ class BLECharacteristic { void setValue(std::string value); void setWriteProperty(bool value); void setWriteNoResponseProperty(bool value); - void executeCreate(BLEService* pService); std::string toString(); - uint16_t getHandle(); static const uint32_t PROPERTY_READ = 1<<0; @@ -107,6 +105,8 @@ class BLECharacteristic { esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); + void executeCreate(BLEService* pService); + uint16_t getHandle(); esp_gatt_char_prop_t getProperties(); BLEService* getService(); void setHandle(uint16_t handle); diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index 6efb5980..bcf4a75d 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -55,8 +55,8 @@ BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) { * @return The first characteristic in the map. */ BLECharacteristic* BLECharacteristicMap::getFirst() { - m_iterator = m_handleMap.begin(); - if (m_iterator == m_handleMap.end()) { + m_iterator = m_uuidMap.begin(); + if (m_iterator == m_uuidMap.end()) { return nullptr; } BLECharacteristic* pRet = m_iterator->second; @@ -70,7 +70,7 @@ BLECharacteristic* BLECharacteristicMap::getFirst() { * @return The next characteristic in the map. */ BLECharacteristic* BLECharacteristicMap::getNext() { - if (m_iterator == m_handleMap.end()) { + if (m_iterator == m_uuidMap.end()) { return nullptr; } BLECharacteristic* pRet = m_iterator->second; @@ -90,7 +90,7 @@ void BLECharacteristicMap::handleGATTServerEvent( esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every Service we have. - for (auto &myPair : m_handleMap) { + for (auto &myPair : m_uuidMap) { myPair.second->handleGATTServerEvent(event, gatts_if, param); } } // handleGATTServerEvent diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index 45856f59..1d32d500 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -37,7 +37,6 @@ class BLEDescriptor { void setValue(uint8_t* data, size_t size); void setValue(std::string value); std::string toString(); - uint16_t getHandle(); private: friend class BLEDescriptorMap; @@ -47,6 +46,7 @@ class BLEDescriptor { uint16_t m_handle; BLECharacteristic* m_pCharacteristic; void executeCreate(BLECharacteristic* pCharacteristic); + uint16_t getHandle(); void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); }; diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index b45bd326..669ed261 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -124,7 +124,7 @@ void BLEService::start() { return; } -/* + BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); while(pCharacteristic != nullptr) { @@ -134,7 +134,7 @@ void BLEService::start() { pCharacteristic = m_characteristicMap.getNext(); } // Start each of the characteristics ... these are found in the m_characteristicMap. -*/ + m_semaphoreStartEvt.take("start"); esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); @@ -186,18 +186,14 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { pCharacteristic->getUUID().toString().c_str(), toString().c_str()); - p_createCharacteristic = pCharacteristic; - pCharacteristic->executeCreate(this); // Check that we don't add the same characteristic twice. -/* if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { + if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { ESP_LOGE(LOG_TAG, "<< Attempt to add a characteristic but we already have one with this UUID"); return; } -*/ - // Remember this characteristic in our map of characteristics. We have mapped characteristic by handle alread, - // now we need to map all characteristics by uuid. - // TODO Now is stored and mapped only one characteristic with unique UUID. We need to change characteristics map - // to store all characteristics, even if there is few characteristics with the same UUID in one service + + // Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID + // but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT. m_characteristicMap.setByUUID(pCharacteristic->getUUID(), pCharacteristic); ESP_LOGD(LOG_TAG, "<< addCharacteristic()"); @@ -249,7 +245,7 @@ void BLEService::handleGATTServerEvent( // for that characteristic. case ESP_GATTS_ADD_CHAR_EVT: { if (m_handle == param->add_char.service_handle) { - BLECharacteristic *pCharacteristic = p_createCharacteristic; + BLECharacteristic *pCharacteristic = getCharacteristic(BLEUUID(param->add_char.char_uuid)); if (pCharacteristic == nullptr) { ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", BLEUUID(param->add_char.char_uuid).toString().c_str()); diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 895169ae..19b472e9 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -42,7 +42,7 @@ class BLECharacteristicMap { private: std::map m_uuidMap; std::map m_handleMap; - std::map::iterator m_iterator; + std::map::iterator m_iterator; }; @@ -66,7 +66,6 @@ class BLEService { BLEServer* getServer(); void start(); std::string toString(); - uint16_t getHandle(); private: friend class BLEServer; @@ -86,8 +85,8 @@ class BLEService { FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); uint32_t m_numHandles; - BLECharacteristic* p_createCharacteristic; + uint16_t getHandle(); BLECharacteristic* getLastCreatedCharacteristic(); void handleGATTServerEvent( esp_gatts_cb_event_t event, diff --git a/cpp_utils/tests/BLETests/SampleMultiCharacteristics.cpp b/cpp_utils/tests/BLETests/SampleMultiCharacteristics.cpp deleted file mode 100644 index 400e567a..00000000 --- a/cpp_utils/tests/BLETests/SampleMultiCharacteristics.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Create a new BLE server. - */ -//#include "freertos/FreeRTOS.h" -#include "BLEDevice.h" -#include "BLEServer.h" -#include "BLEUtils.h" -#include "BLE2902.h" -#include -#include -#include -#include "sdkconfig.h" - -#define SERVICE_UUID_128 "91bad492-b950-4226-aa2b-4ede9fa42f59" -#define CHARACTERISTIC_UUID_128 "0d563a58-196a-48ce-ace2-dfec78acc814" - -static char LOG_TAG[] = "SampleServer"; - -class MainBLEServer: public Task { - void run(void *data) { - ESP_LOGD(LOG_TAG, "Starting BLE work!"); - - BLEDevice::init(""); - BLEServer* pServer = BLEDevice::createServer(); - - BLEService* pService = pServer->createService(SERVICE_UUID_128); - - BLECharacteristic* pCharacteristic = pService->createCharacteristic( - BLEUUID(CHARACTERISTIC_UUID_128), - BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | - BLECharacteristic::PROPERTY_INDICATE - ); - pCharacteristic->setValue("Hello World! Characteristic no 1!"); - - pCharacteristic = pService->createCharacteristic( - BLEUUID(CHARACTERISTIC_UUID_128), - BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | - BLECharacteristic::PROPERTY_INDICATE - ); - - pCharacteristic->setValue("Hello World! Characteristic no 2!"); - - pCharacteristic = pService->createCharacteristic( - BLEUUID(CHARACTERISTIC_UUID_128), - BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | - BLECharacteristic::PROPERTY_INDICATE - ); - - pCharacteristic->setValue("Hello World! Characteristic no 3!"); - - BLE2902* p2902Descriptor = new BLE2902(); - p2902Descriptor->setNotifications(true); - pCharacteristic->addDescriptor(p2902Descriptor); - pService->start(); - - BLEAdvertising* pAdvertising = pServer->getAdvertising(); - /* - * To add 128bit service UUID to advertising REMEMBER: - * BLEDevice::init("ESP32"); in this line - * name should be very short, in most cases no longer than 5 characters. - * In this case we have also setup appearance, so we need to leave device name empty - */ - pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); - /* - * Setting appearance - * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml - */ - pAdvertising->setAppearance(0x1280); - pAdvertising->start(); - - ESP_LOGD(LOG_TAG, "Advertising started!"); - delay(1000000); - } -}; - - -void SampleMultiCharacteristic(void) -{ - //esp_log_level_set("*", ESP_LOG_DEBUG); - MainBLEServer* pMainBleServer = new MainBLEServer(); - pMainBleServer->setStackSize(4000); - pMainBleServer->start(); - -} // app_main diff --git a/cpp_utils/tests/BLETests/main.cpp b/cpp_utils/tests/BLETests/main.cpp index bdf60fad..732ae16d 100644 --- a/cpp_utils/tests/BLETests/main.cpp +++ b/cpp_utils/tests/BLETests/main.cpp @@ -20,14 +20,13 @@ void SampleScan(void); void SampleSensorTag(void); void SampleServer(void); void SampleWrite(void); -void SampleMultiCharacteristic(void); + // // Un-comment ONE of the following // --- void app_main(void) { //Sample_MLE_15(); //Sample1(); - SampleMultiCharacteristic(); //SampleClient(); //SampleClient_Notify(); //SampleClientAndServer(); @@ -37,7 +36,6 @@ void app_main(void) { //SampleRead(); //SampleSensorTag(); //SampleScan(); - //SampleServer(); + SampleServer(); //SampleWrite(); } // app_main - From d8299d072188f57e03c6250268475d20caa2ba55 Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 15 Nov 2017 08:23:29 +0100 Subject: [PATCH 144/381] Multiple characteristics feature #175 --- cpp_utils/BLECharacteristic.cpp | 8 ++-- cpp_utils/BLECharacteristic.h | 2 +- cpp_utils/BLECharacteristicMap.cpp | 18 ++++----- cpp_utils/BLEDescriptor.h | 2 +- cpp_utils/BLERemoteCharacteristic.cpp | 1 + cpp_utils/BLERemoteService.cpp | 53 ++++----------------------- cpp_utils/BLERemoteService.h | 4 +- cpp_utils/BLEService.cpp | 6 +-- cpp_utils/BLEService.h | 10 ++--- 9 files changed, 33 insertions(+), 71 deletions(-) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 18ba65dd..054fb55d 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -244,6 +244,7 @@ void BLECharacteristic::handleGATTServerEvent( // - esp_bt_uuid_t char_uuid case ESP_GATTS_ADD_CHAR_EVT: { if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) && + getHandle() == param->add_char.attr_handle && getService()->getHandle()==param->add_char.service_handle) { m_semaphoreCreateEvt.give(); } @@ -418,11 +419,8 @@ void BLECharacteristic::handleGATTServerEvent( // Give each of the descriptors associated with this characteristic the opportunity to handle the // event. - BLEDescriptor *pDescriptor = m_descriptorMap.getFirst(); - while(pDescriptor != nullptr) { - pDescriptor->handleGATTServerEvent(event, gatts_if, param); - pDescriptor = m_descriptorMap.getNext(); - } + + m_descriptorMap.handleGATTServerEvent(event, gatts_if, param); } // handleGATTServerEvent diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 24977c00..cacf1e50 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -77,6 +77,7 @@ class BLECharacteristic { void setWriteProperty(bool value); void setWriteNoResponseProperty(bool value); std::string toString(); + uint16_t getHandle(); static const uint32_t PROPERTY_READ = 1<<0; @@ -106,7 +107,6 @@ class BLECharacteristic { esp_ble_gatts_cb_param_t* param); void executeCreate(BLEService* pService); - uint16_t getHandle(); esp_gatt_char_prop_t getProperties(); BLEService* getService(); void setHandle(uint16_t handle); diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index bcf4a75d..86a97441 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -41,8 +41,8 @@ BLECharacteristic* BLECharacteristicMap::getByUUID(const char* uuid) { */ BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) { for (auto &myPair : m_uuidMap) { - if (myPair.second->getUUID().equals(uuid)) { - return myPair.second; + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; } } //return m_uuidMap.at(uuid.toString()); @@ -59,7 +59,7 @@ BLECharacteristic* BLECharacteristicMap::getFirst() { if (m_iterator == m_uuidMap.end()) { return nullptr; } - BLECharacteristic* pRet = m_iterator->second; + BLECharacteristic* pRet = m_iterator->first; m_iterator++; return pRet; } // getFirst @@ -73,7 +73,7 @@ BLECharacteristic* BLECharacteristicMap::getNext() { if (m_iterator == m_uuidMap.end()) { return nullptr; } - BLECharacteristic* pRet = m_iterator->second; + BLECharacteristic* pRet = m_iterator->first; m_iterator++; return pRet; } // getNext @@ -91,7 +91,7 @@ void BLECharacteristicMap::handleGATTServerEvent( esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every Service we have. for (auto &myPair : m_uuidMap) { - myPair.second->handleGATTServerEvent(event, gatts_if, param); + myPair.first->handleGATTServerEvent(event, gatts_if, param); } } // handleGATTServerEvent @@ -115,9 +115,9 @@ void BLECharacteristicMap::setByHandle(uint16_t handle, * @return N/A. */ void BLECharacteristicMap::setByUUID( - BLEUUID uuid, - BLECharacteristic *pCharacteristic) { - m_uuidMap.insert(std::pair(uuid.toString(), pCharacteristic)); + BLECharacteristic *pCharacteristic, + BLEUUID uuid) { + m_uuidMap.insert(std::pair(pCharacteristic, uuid.toString())); } // setByUUID @@ -134,7 +134,7 @@ std::string BLECharacteristicMap::toString() { stringStream << "\n"; } count++; - stringStream << "handle: 0x" << std::setw(2) << myPair.second->getHandle() << ", uuid: " + myPair.second->getUUID().toString(); + stringStream << "handle: 0x" << std::setw(2) << myPair.first->getHandle() << ", uuid: " + myPair.first->getUUID().toString(); } return stringStream.str(); } // toString diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index 1d32d500..45856f59 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -37,6 +37,7 @@ class BLEDescriptor { void setValue(uint8_t* data, size_t size); void setValue(std::string value); std::string toString(); + uint16_t getHandle(); private: friend class BLEDescriptorMap; @@ -46,7 +47,6 @@ class BLEDescriptor { uint16_t m_handle; BLECharacteristic* m_pCharacteristic; void executeCreate(BLECharacteristic* pCharacteristic); - uint16_t getHandle(); void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); }; diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index ba781d29..64b0a92d 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -311,6 +311,7 @@ void BLERemoteCharacteristic::retrieveDescriptors() { if (count == 0) { break; } + ESP_LOGE(LOG_TAG, ""); ESP_LOGD(LOG_TAG, "Found a descriptor: Handle: %d, UUID: %s", result.handle, BLEUUID(result.uuid).toString().c_str()); // We now have a new characteristic ... let us add that to our set of known characteristics diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index ab03292f..5227964e 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -114,8 +114,8 @@ void BLERemoteService::gattClientEventHandler( } // switch // Send the event to each of the characteristics owned by this service. - for (auto &myPair : m_characteristicMapByHandle) { - myPair.second->gattClientEventHandler(event, gattc_if, evtParam); + for (auto &myPair : m_characteristicMap) { + myPair.first->gattClientEventHandler(event, gattc_if, evtParam); } } // gattClientEventHandler @@ -147,27 +147,8 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { } std::string v = uuid.toString(); for (auto &myPair : m_characteristicMap) { - if (myPair.first == v) { - return myPair.second; - } - } - return nullptr; -} // getCharacteristic - -BLERemoteCharacteristic* BLERemoteService::getCharacteristic(uint16_t uuid) { -// Design -// ------ -// We wish to retrieve the characteristic given its handle. It is possible that we have not yet asked the -// device what characteristics it has in which case we have nothing to match against. If we have not -// asked the device about its characteristics, then we do that now. Once we get the results we can then -// examine the characteristics map to see if it has the characteristic we are looking for. - if (!m_haveCharacteristics) { - retrieveCharacteristics(); - } - //std::string v = uuid.toString(); - for (auto &myPair : m_characteristicMapByHandle) { - if (myPair.first == uuid) { - return myPair.second; + if (myPair.second == v) { + return myPair.first; } } return nullptr; @@ -276,8 +257,7 @@ void BLERemoteService::retrieveCharacteristics() { this ); - m_characteristicMapByHandle.insert(std::pair(pNewRemoteCharacteristic->getHandle(), pNewRemoteCharacteristic)); - m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic->getUUID().toString(), pNewRemoteCharacteristic)); + m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic, pNewRemoteCharacteristic->getUUID().toString())); offset++; // Increment our count of number of descriptors found. } // Loop forever (until we break inside the loop). @@ -291,7 +271,7 @@ void BLERemoteService::retrieveCharacteristics() { * @brief Retrieve a map of all the characteristics of this service. * @return A map of all the characteristics of this service. */ -std::map* BLERemoteService::getCharacteristics() { +std::map* BLERemoteService::getCharacteristics() { ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); // If is possible that we have not read the characteristics associated with the service so do that // now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking @@ -303,17 +283,6 @@ std::map* BLERemoteService::getCharacteri return &m_characteristicMap; } // getCharacteristics -void BLERemoteService::getCharacteristics(std::map* ptr) { - ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %#04x", getHandle()); - // If is possible that we have not read the characteristics associated with the service so do that - // now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking - // call and does not return until all the characteristics are available. - if (!m_haveCharacteristics) { - retrieveCharacteristics(); - } - ESP_LOGD(LOG_TAG, "<< getCharacteristics() for service: %#04x", getHandle()); - *ptr = m_characteristicMapByHandle; -} // getCharacteristics BLEClient* BLERemoteService::getClient() { return m_pClient; @@ -356,16 +325,10 @@ BLEUUID BLERemoteService::getUUID() { */ void BLERemoteService::removeCharacteristics() { for (auto &myPair : m_characteristicMap) { - delete myPair.second; + delete myPair.first; m_characteristicMap.erase(myPair.first); } m_characteristicMap.clear(); // Clear the map - - for (auto &myPair : m_characteristicMapByHandle) { - delete myPair.second; - m_characteristicMapByHandle.erase(myPair.first); - } - m_characteristicMapByHandle.clear(); // Clear the map } // removeCharacteristics @@ -380,7 +343,7 @@ std::string BLERemoteService::toString() { ss << ", start_handle: " << std::dec << m_startHandle << " 0x" << std::hex << m_startHandle << ", end_handle: " << std::dec << m_endHandle << " 0x" << std::hex << m_endHandle; for (auto &myPair : m_characteristicMap) { - ss << "\n" << myPair.second->toString(); + ss << "\n" << myPair.first->toString(); // myPair.second is the value } return ss.str(); diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 7f8c2f4a..521effce 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -33,7 +33,7 @@ class BLERemoteService { BLERemoteCharacteristic* getCharacteristic(const char* uuid); BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); BLERemoteCharacteristic* getCharacteristic(uint16_t uuid); - std::map* getCharacteristics(); + std::map* getCharacteristics(); void getCharacteristics(std::map* ptr); BLEClient* getClient(void); @@ -63,7 +63,7 @@ class BLERemoteService { // Properties // We maintain a map of characteristics owned by this service keyed by a string representation of the UUID. - std::map m_characteristicMap; + std::map m_characteristicMap; // We maintain a map of characteristics owned by this service keyed by a handle. std::map m_characteristicMapByHandle; diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 669ed261..b330a1d2 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -189,12 +189,12 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { // Check that we don't add the same characteristic twice. if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { ESP_LOGE(LOG_TAG, "<< Attempt to add a characteristic but we already have one with this UUID"); - return; + //return; } // Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID // but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT. - m_characteristicMap.setByUUID(pCharacteristic->getUUID(), pCharacteristic); + m_characteristicMap.setByUUID(pCharacteristic, pCharacteristic->getUUID()); ESP_LOGD(LOG_TAG, "<< addCharacteristic()"); } // addCharacteristic @@ -245,7 +245,7 @@ void BLEService::handleGATTServerEvent( // for that characteristic. case ESP_GATTS_ADD_CHAR_EVT: { if (m_handle == param->add_char.service_handle) { - BLECharacteristic *pCharacteristic = getCharacteristic(BLEUUID(param->add_char.char_uuid)); + BLECharacteristic *pCharacteristic = getLastCreatedCharacteristic(); if (pCharacteristic == nullptr) { ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", BLEUUID(param->add_char.char_uuid).toString().c_str()); diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 19b472e9..b37092f2 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -24,8 +24,8 @@ class BLEServer; */ class BLECharacteristicMap { public: - void setByUUID(const char* uuid, BLECharacteristic* pCharacteristic); - void setByUUID(BLEUUID uuid, BLECharacteristic* pCharacteristic); + void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid); + void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid); void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); BLECharacteristic* getByUUID(const char* uuid); BLECharacteristic* getByUUID(BLEUUID uuid); @@ -40,9 +40,9 @@ class BLECharacteristicMap { private: - std::map m_uuidMap; + std::map m_uuidMap; std::map m_handleMap; - std::map::iterator m_iterator; + std::map::iterator m_iterator; }; @@ -66,6 +66,7 @@ class BLEService { BLEServer* getServer(); void start(); std::string toString(); + uint16_t getHandle(); private: friend class BLEServer; @@ -86,7 +87,6 @@ class BLEService { uint32_t m_numHandles; - uint16_t getHandle(); BLECharacteristic* getLastCreatedCharacteristic(); void handleGATTServerEvent( esp_gatts_cb_event_t event, From 2631b4cee30eb90e47645177e8ca0c15d8475673 Mon Sep 17 00:00:00 2001 From: chegewara Date: Fri, 17 Nov 2017 16:55:22 +0100 Subject: [PATCH 145/381] Update BLEDevice.h --- cpp_utils/BLEDevice.h | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 5a09b5c0..acf20f35 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -53,6 +53,7 @@ class BLEDevice { esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); +public: static void setPower(esp_power_level_t powerLevel); }; // class BLE From 19398e6e42a4d664f7a779477f4978624b7e1da6 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 18 Nov 2017 16:46:17 -0600 Subject: [PATCH 146/381] Addition of missing arduino debugs #210 --- Documentation/images/arduino_debug.png | Bin 0 -> 101871 bytes cpp_utils/ArduinoBLE.md | 19 +--- cpp_utils/BLEAdvertisedDevice.cpp | 10 +- cpp_utils/BLEService.cpp | 4 + cpp_utils/BLEUUID.cpp | 3 + cpp_utils/BLEUtils.cpp | 130 ++++++++++++++++++++----- 6 files changed, 120 insertions(+), 46 deletions(-) create mode 100755 Documentation/images/arduino_debug.png diff --git a/Documentation/images/arduino_debug.png b/Documentation/images/arduino_debug.png new file mode 100755 index 0000000000000000000000000000000000000000..0cccfb5fafe2403ed813e076b1dc1727d1fde221 GIT binary patch literal 101871 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 LL+-PTdC30(dvuwf literal 0 HcmV?d00001 diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index 133292e8..59772357 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -32,25 +32,10 @@ From October 2017 onwards, a build of the BLE libraries is supplied with the Ard 4. Extract the `ESP32_BLE.zip` file there ## Switching on debugging -The BLE support contains extensive internal diagnostics which can be switched on by editing the file called `sdkconfig.h` and finding the lines which read: +The BLE support contains extensive internal diagnostics which can be switched on through the "Tools > Core Debug Level" setting: -``` -#define CONFIG_LOG_DEFAULT_LEVEL 1 -``` +![](../../Documentation/images/arduino_debug.png) -Change this to: - -``` -#define CONFIG_LOG_DEFAULT_LEVEL 5 -``` - -and rebuild/deploy your project. - -This file can be found in your Arduino IDE installation directories at: - -``` -/hardware/espressif/esp32/tools/sdk/include/config -``` ## Decoding an exception stack trace While using the BLE C++ classes there is always the unfortunate possibility that something will go wrong and your application crash. Fortunately, this results in some debug information being logged to the console. This is known as a *stack trace*. Included in the stack trace are a sequence of hexadecimal numbers known as the *back trace* which are the list of addresses of functions that were executed just before the crash was detected. If we could decode these we would have a lot of great information that could be used to aid in the resolution. Fortunately there is a fantastic project that makes decoding this information very easy indeed. diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index a332c7e2..4347526f 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -209,7 +209,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { payload++; // Skip to type sizeConsumed += 1 + length; // increase the size consumed. - if (length != 0) { // A length of 0 indicate that we have reached the end. + if (length != 0) { // A length of 0 indicates that we have reached the end. ad_type = *payload; payload++; length--; @@ -253,7 +253,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { case ESP_BLE_AD_TYPE_32SRV_CMPL: case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04 for (int var = 0; var < length/4; ++var) { - setServiceUUID(BLEUUID(*reinterpret_cast(payload+var*4))); + setServiceUUID(BLEUUID(*reinterpret_cast(payload+var*4))); } break; } // ESP_BLE_AD_TYPE_32SRV_PART @@ -362,13 +362,15 @@ void BLEAdvertisedDevice::setScan(BLEScan* pScan) { m_pScan = pScan; } // setScan + /** * @brief Set the Service UUID for this device. * @param [in] serviceUUID The discovered serviceUUID */ void BLEAdvertisedDevice::setServiceUUID(const char* serviceUUID) { return setServiceUUID(BLEUUID(serviceUUID)); -} // setRSSI +} // setServiceUUID + /** * @brief Set the Service UUID for this device. @@ -378,7 +380,7 @@ void BLEAdvertisedDevice::setServiceUUID(BLEUUID serviceUUID) { m_serviceUUIDs.push_back(serviceUUID); m_haveServiceUUID = true; ESP_LOGD(LOG_TAG, "- addServiceUUID(): serviceUUID: %s", serviceUUID.toString().c_str()); -} // setRSSI +} // setServiceUUID /** diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index b330a1d2..32bcc570 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -22,6 +22,10 @@ #include "BLEUtils.h" #include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + #define NULL_HANDLE (0xffff) static const char* LOG_TAG = "BLEService"; // Tag for logging. diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 6b2311a7..f5c6ceee 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -14,6 +14,9 @@ #include "BLEUUID.h" static const char* LOG_TAG = "BLEUUID"; +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif /** * @brief Copy memory from source to target but in reverse order. diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 0594575f..5d1dec99 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -24,6 +24,10 @@ #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + static const char* LOG_TAG = "BLEUtils"; // Tag for logging. /* @@ -675,56 +679,81 @@ std::string BLEUtils::adFlagsToString(uint8_t adFlags) { */ const char* BLEUtils::advTypeToString(uint8_t advType) { switch(advType) { - case ESP_BLE_AD_TYPE_FLAG: + case ESP_BLE_AD_TYPE_FLAG: // 0x01 return "ESP_BLE_AD_TYPE_FLAG"; - case ESP_BLE_AD_TYPE_16SRV_PART: + + case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 return "ESP_BLE_AD_TYPE_16SRV_PART"; - case ESP_BLE_AD_TYPE_16SRV_CMPL: + + case ESP_BLE_AD_TYPE_16SRV_CMPL: // 0x03 return "ESP_BLE_AD_TYPE_16SRV_CMPL"; - case ESP_BLE_AD_TYPE_32SRV_PART: + + case ESP_BLE_AD_TYPE_32SRV_PART: // 0x04 return "ESP_BLE_AD_TYPE_32SRV_PART"; - case ESP_BLE_AD_TYPE_32SRV_CMPL: + + case ESP_BLE_AD_TYPE_32SRV_CMPL: // 0x05 return "ESP_BLE_AD_TYPE_32SRV_CMPL"; - case ESP_BLE_AD_TYPE_128SRV_PART: + + case ESP_BLE_AD_TYPE_128SRV_PART: // 0x06 return "ESP_BLE_AD_TYPE_128SRV_PART"; - case ESP_BLE_AD_TYPE_128SRV_CMPL: + + case ESP_BLE_AD_TYPE_128SRV_CMPL: // 0x07 return "ESP_BLE_AD_TYPE_128SRV_CMPL"; - case ESP_BLE_AD_TYPE_NAME_SHORT: + + case ESP_BLE_AD_TYPE_NAME_SHORT: // 0x08 return "ESP_BLE_AD_TYPE_NAME_SHORT"; - case ESP_BLE_AD_TYPE_NAME_CMPL: + + case ESP_BLE_AD_TYPE_NAME_CMPL: // 0x09 return "ESP_BLE_AD_TYPE_NAME_CMPL"; - case ESP_BLE_AD_TYPE_TX_PWR: + + case ESP_BLE_AD_TYPE_TX_PWR: // 0x0a return "ESP_BLE_AD_TYPE_TX_PWR"; - case ESP_BLE_AD_TYPE_DEV_CLASS: + + case ESP_BLE_AD_TYPE_DEV_CLASS: // 0x0b return "ESP_BLE_AD_TYPE_DEV_CLASS"; - case ESP_BLE_AD_TYPE_SM_TK: + + case ESP_BLE_AD_TYPE_SM_TK: // 0x10 return "ESP_BLE_AD_TYPE_SM_TK"; - case ESP_BLE_AD_TYPE_SM_OOB_FLAG: + + case ESP_BLE_AD_TYPE_SM_OOB_FLAG: // 0x11 return "ESP_BLE_AD_TYPE_SM_OOB_FLAG"; - case ESP_BLE_AD_TYPE_INT_RANGE: + + case ESP_BLE_AD_TYPE_INT_RANGE: // 0x12 return "ESP_BLE_AD_TYPE_INT_RANGE"; - case ESP_BLE_AD_TYPE_SOL_SRV_UUID: + + case ESP_BLE_AD_TYPE_SOL_SRV_UUID: // 0x14 return "ESP_BLE_AD_TYPE_SOL_SRV_UUID"; - case ESP_BLE_AD_TYPE_128SOL_SRV_UUID: + + case ESP_BLE_AD_TYPE_128SOL_SRV_UUID: // 0x15 return "ESP_BLE_AD_TYPE_128SOL_SRV_UUID"; - case ESP_BLE_AD_TYPE_SERVICE_DATA: + + case ESP_BLE_AD_TYPE_SERVICE_DATA: // 0x16 return "ESP_BLE_AD_TYPE_SERVICE_DATA"; - case ESP_BLE_AD_TYPE_PUBLIC_TARGET: + + case ESP_BLE_AD_TYPE_PUBLIC_TARGET: // 0x17 return "ESP_BLE_AD_TYPE_PUBLIC_TARGET"; - case ESP_BLE_AD_TYPE_RANDOM_TARGET: - return "ESP_BLE_Amap1D_TYPE_RANDOM_TARGET"; - case ESP_BLE_AD_TYPE_APPEARANCE: + + case ESP_BLE_AD_TYPE_RANDOM_TARGET: // 0x18 + return "ESP_BLE_AD_TYPE_RANDOM_TARGET"; + + case ESP_BLE_AD_TYPE_APPEARANCE: // 0x19 return "ESP_BLE_AD_TYPE_APPEARANCE"; - case ESP_BLE_AD_TYPE_ADV_INT: + + case ESP_BLE_AD_TYPE_ADV_INT: // 0x1a return "ESP_BLE_AD_TYPE_ADV_INT"; + case ESP_BLE_AD_TYPE_32SOL_SRV_UUID: return "ESP_BLE_AD_TYPE_32SOL_SRV_UUID"; - case ESP_BLE_AD_TYPE_32SERVICE_DATA: + + case ESP_BLE_AD_TYPE_32SERVICE_DATA: // 0x20 return "ESP_BLE_AD_TYPE_32SERVICE_DATA"; - case ESP_BLE_AD_TYPE_128SERVICE_DATA: + + case ESP_BLE_AD_TYPE_128SERVICE_DATA: // 0x21 return "ESP_BLE_AD_TYPE_128SERVICE_DATA"; - case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: + + case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xff return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; + default: ESP_LOGD(LOG_TAG, " adv data type: 0x%x", advType); return ""; @@ -935,52 +964,76 @@ std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType switch(eventType) { case ESP_GATTS_REG_EVT: return "ESP_GATTS_REG_EVT"; + case ESP_GATTS_READ_EVT: return "ESP_GATTS_READ_EVT"; + case ESP_GATTS_WRITE_EVT: return "ESP_GATTS_WRITE_EVT"; + case ESP_GATTS_EXEC_WRITE_EVT: return "ESP_GATTS_EXEC_WRITE_EVT"; + case ESP_GATTS_MTU_EVT: return "ESP_GATTS_MTU_EVT"; + case ESP_GATTS_CONF_EVT: return "ESP_GATTS_CONF_EVT"; + case ESP_GATTS_UNREG_EVT: return "ESP_GATTS_UNREG_EVT"; + case ESP_GATTS_CREATE_EVT: return "ESP_GATTS_CREATE_EVT"; + case ESP_GATTS_ADD_INCL_SRVC_EVT: return "ESP_GATTS_ADD_INCL_SRVC_EVT"; + case ESP_GATTS_ADD_CHAR_EVT: return "ESP_GATTS_ADD_CHAR_EVT"; + case ESP_GATTS_ADD_CHAR_DESCR_EVT: return "ESP_GATTS_ADD_CHAR_DESCR_EVT"; + case ESP_GATTS_DELETE_EVT: return "ESP_GATTS_DELETE_EVT"; + case ESP_GATTS_START_EVT: return "ESP_GATTS_START_EVT"; + case ESP_GATTS_STOP_EVT: return "ESP_GATTS_STOP_EVT"; + case ESP_GATTS_CONNECT_EVT: return "ESP_GATTS_CONNECT_EVT"; + case ESP_GATTS_DISCONNECT_EVT: return "ESP_GATTS_DISCONNECT_EVT"; + case ESP_GATTS_OPEN_EVT: return "ESP_GATTS_OPEN_EVT"; + case ESP_GATTS_CANCEL_OPEN_EVT: return "ESP_GATTS_CANCEL_OPEN_EVT"; + case ESP_GATTS_CLOSE_EVT: return "ESP_GATTS_CLOSE_EVT"; + case ESP_GATTS_LISTEN_EVT: return "ESP_GATTS_LISTEN_EVT"; + case ESP_GATTS_CONGEST_EVT: return "ESP_GATTS_CONGEST_EVT"; + case ESP_GATTS_RESPONSE_EVT: return "ESP_GATTS_RESPONSE_EVT"; + case ESP_GATTS_CREAT_ATTR_TAB_EVT: return "ESP_GATTS_CREAT_ATTR_TAB_EVT"; + case ESP_GATTS_SET_ATTR_VAL_EVT: return "ESP_GATTS_SET_ATTR_VAL_EVT"; + } return "Unknown"; } // gattServerEventTypeToString @@ -1788,59 +1841,86 @@ const char* BLEUtils::gapEventToString(uint32_t eventType) { switch(eventType) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_START_COMPLETE_EVT"; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: /*!< When stop adv complete, the event comes */ return "ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT"; + case ESP_GAP_BLE_AUTH_CMPL_EVT: /* Authentication complete indication. */ return "ESP_GAP_BLE_AUTH_CMPL_EVT"; + case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: return "ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT: return "ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_KEY_EVT: /* BLE key event for peer device keys */ return "ESP_GAP_BLE_KEY_EVT"; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ return "ESP_GAP_BLE_LOCAL_IR_EVT"; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ return "ESP_GAP_BLE_LOCAL_ER_EVT"; + case ESP_GAP_BLE_NC_REQ_EVT: /* Numeric Comparison request event */ return "ESP_GAP_BLE_NC_REQ_EVT"; + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ return "ESP_GAP_BLE_OOB_REQ_EVT"; + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: /* passkey notification event */ return "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ return "ESP_GAP_BLE_PASSKEY_REQ_EVT"; + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: return "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT"; + case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: return "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_RESULT_EVT: return "ESP_GAP_BLE_SCAN_RESULT_EVT"; + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_START_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT"; + case ESP_GAP_BLE_SEC_REQ_EVT: /* BLE security request */ return "ESP_GAP_BLE_SEC_REQ_EVT"; + case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: return "ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT"; + case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: return "ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT"; + case ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT: return "ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT"; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: return "ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT"; + default: ESP_LOGD(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); return "Unknown event type"; From 874a0d3559b65d93d75386dc25aba200f9dd8516 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 19 Nov 2017 09:21:22 -0600 Subject: [PATCH 147/381] Implementation of #211 --- cpp_utils/BLECharacteristic.cpp | 23 ++++++ cpp_utils/BLECharacteristicCallbacks.cpp | 37 ---------- cpp_utils/BLEDescriptor.cpp | 92 +++++++++++++++++++----- cpp_utils/BLEDescriptor.h | 19 ++++- cpp_utils/DesignNotes/BLECPP.md | 0 cpp_utils/FreeRTOS.cpp | 48 +++++++++++++ cpp_utils/FreeRTOS.h | 23 +++++- 7 files changed, 184 insertions(+), 58 deletions(-) delete mode 100644 cpp_utils/BLECharacteristicCallbacks.cpp create mode 100644 cpp_utils/DesignNotes/BLECPP.md diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 054fb55d..6d746cf2 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -554,7 +554,9 @@ void BLECharacteristic::setBroadcastProperty(bool value) { * @param [in] pCallbacks An instance of a callbacks structure used to define any callbacks for the characteristic. */ void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) { + ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallbacks); m_pCallbacks = pCallbacks; + ESP_LOGD(LOG_TAG, "<< setCallbacks"); } // setCallbacks @@ -693,4 +695,25 @@ std::string BLECharacteristic::toString() { return stringstream.str(); } // toString +BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {} + +/** + * @brief Callback function to support a read request. + * @param [in] pCharacteristic The characteristic that is the source of the event. + */ +void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic) { + ESP_LOGD("BLECharacteristicCallbacks", ">> onRead: default"); + ESP_LOGD("BLECharacteristicCallbacks", "<< onRead"); +} // onRead + + +/** + * @brief Callback function to support a write request. + * @param [in] pCharacteristic The characteristic that is the source of the event. + */ +void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic) { + ESP_LOGD("BLECharacteristicCallbacks", ">> onWrite: default"); + ESP_LOGD("BLECharacteristicCallbacks", "<< onWrite"); +} // onWrite + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLECharacteristicCallbacks.cpp b/cpp_utils/BLECharacteristicCallbacks.cpp deleted file mode 100644 index 46905b51..00000000 --- a/cpp_utils/BLECharacteristicCallbacks.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * BLECharacteristicCallbacks.cpp - * - * Created on: Jul 2, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include "BLECharacteristic.h" -#include -#ifdef ARDUINO_ARCH_ESP32 -#include "esp32-hal-log.h" -#endif -static const char* LOG_TAG = "BLECharacteristicCallbacks"; - - -BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {} - -/** - * @brief Callback function to support a read request. - * @param [in] pCharacteristic The characteristic that is the source of the event. - */ -void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic) { - ESP_LOGD(LOG_TAG, ">> onRead: default"); - ESP_LOGD(LOG_TAG, "<< onRead"); -} // onRead - - -/** - * @brief Callback function to support a write request. - * @param [in] pCharacteristic The characteristic that is the source of the event. - */ -void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic) { - ESP_LOGD(LOG_TAG, ">> onWrite: default"); - ESP_LOGD(LOG_TAG, "<< onWrite"); -} // onWrite -#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 8362af0a..054ef420 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -41,7 +41,8 @@ BLEDescriptor::BLEDescriptor(BLEUUID uuid) { m_value.attr_len = 0; m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; m_handle = NULL_HANDLE; - m_pCharacteristic = nullptr; // No initial characteristic. + m_pCharacteristic = nullptr; // No initial characteristic. + m_pCallback = nullptr; // No initial callback. } // BLEDescriptor @@ -177,19 +178,30 @@ void BLEDescriptor::handleGATTServerEvent( // - uint8_t *value case ESP_GATTS_WRITE_EVT: { if (param->write.handle == m_handle) { - setValue(param->write.value, param->write.len); - esp_gatt_rsp_t rsp; + setValue(param->write.value, param->write.len); // Set the value of the descriptor. + + esp_gatt_rsp_t rsp; // Build a response. rsp.attr_value.len = getLength(); rsp.attr_value.handle = m_handle; rsp.attr_value.offset = 0; rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.len); esp_err_t errRc = ::esp_ble_gatts_send_response( - gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, &rsp); - if (errRc != ESP_OK) { + gatts_if, + param->write.conn_id, + param->write.trans_id, + ESP_GATT_OK, + &rsp); + + if (errRc != ESP_OK) { // Check the return code from the send of the response. ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - } + + if (m_pCallback != nullptr) { // We have completed the write, if there is a user supplied callback handler, invoke it now. + m_pCallback->onWrite(this); // Invoke the onWrite callback handler. + } + } // End of ... this is our handle. + break; } // ESP_GATTS_WRITE_EVT @@ -205,25 +217,36 @@ void BLEDescriptor::handleGATTServerEvent( // - bool need_rsp // case ESP_GATTS_READ_EVT: { - ESP_LOGD(LOG_TAG, "- Testing: Sought handle: 0x%.2x == descriptor handle: 0x%.2x ?", param->read.handle, m_handle); - if (param->read.handle == m_handle) { - ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); - if (param->read.need_rsp) { + if (param->read.handle == m_handle) { // If this event is for this descriptor ... process it + + if (m_pCallback != nullptr) { // If we have a user supplied callback, invoke it now. + m_pCallback->onRead(this); // Invoke the onRead callback method in the callback handler. + } + + if (param->read.need_rsp) { // Do we need a response + ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); esp_gatt_rsp_t rsp; - rsp.attr_value.len = getLength(); - rsp.attr_value.handle = param->read.handle; - rsp.attr_value.offset = 0; + rsp.attr_value.len = getLength(); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.offset = 0; rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.len); + esp_err_t errRc = ::esp_ble_gatts_send_response( - gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp); - if (errRc != ESP_OK) { + gatts_if, + param->read.conn_id, + param->read.trans_id, + ESP_GATT_OK, + &rsp); + + if (errRc != ESP_OK) { // Check the return code from the send of the response. ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - } - } // ESP_GATTS_READ_EVT + } // End of need a response. + } // End of this is our handle break; } // ESP_GATTS_READ_EVT + default: { break; } @@ -231,6 +254,17 @@ void BLEDescriptor::handleGATTServerEvent( } // handleGATTServerEvent +/** + * @brief Set the callback handlers for this descriptor. + * @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor. + */ +void BLEDescriptor::setCallbacks(BLEDescriptorCallbacks* pCallback) { + ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallback); + m_pCallback = pCallback; + ESP_LOGD(LOG_TAG, "<< setCallbacks"); +} // setCallbacks + + /** * @brief Set the handle of this descriptor. * Set the handle of this descriptor to be the supplied value. @@ -278,4 +312,28 @@ std::string BLEDescriptor::toString() { stringstream << "UUID: " << m_bleUUID.toString() + ", handle: 0x" << std::setw(2) << m_handle; return stringstream.str(); } // toString + + +BLEDescriptorCallbacks::~BLEDescriptorCallbacks() {} + +/** + * @brief Callback function to support a read request. + * @param [in] pDescriptor The descriptor that is the source of the event. + */ +void BLEDescriptorCallbacks::onRead(BLEDescriptor* pDescriptor) { + ESP_LOGD("BLEDescriptorCallbacks", ">> onRead: default"); + ESP_LOGD("BLEDescriptorCallbacks", "<< onRead"); +} // onRead + + +/** + * @brief Callback function to support a write request. + * @param [in] pDescriptor The descriptor that is the source of the event. + */ +void BLEDescriptorCallbacks::onWrite(BLEDescriptor* pDescriptor) { + ESP_LOGD("BLEDescriptorCallbacks", ">> onWrite: default"); + ESP_LOGD("BLEDescriptorCallbacks", "<< onWrite"); +} // onWrite + + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index 45856f59..4eda4c91 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -17,6 +17,7 @@ class BLEService; class BLECharacteristic; +class BLEDescriptorCallbacks; /** * @brief A model of a %BLE descriptor. @@ -27,6 +28,7 @@ class BLEDescriptor { BLEDescriptor(BLEUUID uuid); virtual ~BLEDescriptor(); + uint16_t getHandle(); size_t getLength(); BLEUUID getUUID(); uint8_t* getValue(); @@ -34,10 +36,10 @@ class BLEDescriptor { esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); + void setCallbacks(BLEDescriptorCallbacks* pCallbacks); void setValue(uint8_t* data, size_t size); void setValue(std::string value); std::string toString(); - uint16_t getHandle(); private: friend class BLEDescriptorMap; @@ -46,9 +48,24 @@ class BLEDescriptor { esp_attr_value_t m_value; uint16_t m_handle; BLECharacteristic* m_pCharacteristic; + BLEDescriptorCallbacks* m_pCallback; void executeCreate(BLECharacteristic* pCharacteristic); void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); }; + +/** + * @brief Callbacks that can be associated with a %BLE descriptors to inform of events. + * + * When a server application creates a %BLE descriptor, we may wish to be informed when there is either + * a read or write request to the descriptors value. An application can register a + * sub-classed instance of this class and will be notified when such an event happens. + */ +class BLEDescriptorCallbacks { +public: + virtual ~BLEDescriptorCallbacks(); + virtual void onRead(BLEDescriptor* pDescriptor); + virtual void onWrite(BLEDescriptor* pDescriptor); +}; #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ */ diff --git a/cpp_utils/DesignNotes/BLECPP.md b/cpp_utils/DesignNotes/BLECPP.md new file mode 100644 index 00000000..e69de29b diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 3a80c698..92390424 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -212,3 +212,51 @@ void FreeRTOS::Semaphore::setName(std::string name) { m_name = name; } // setName + +/** + * @brief Create a ring buffer. + * @param [in] length The amount of storage to allocate for the ring buffer. + * @param [in] type The type of buffer. One of RINGBUF_TYPE_NOSPLIT, RINGBUF_TYPE_ALLOWSPLIT, RINGBUF_TYPE_BYTEBUF. + */ +Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) { + m_handle = ::xRingbufferCreate(length, type); +} // Ringbuffer + + +Ringbuffer::~Ringbuffer() { + ::vRingbufferDelete(m_handle); +} // ~Ringbuffer + + +/** + * @brief Receive data from the buffer. + * @param [out] size On return, the size of data returned. + * @param [in] wait How long to wait. + * @return A pointer to the storage retrieved. + */ +void* Ringbuffer::receive(size_t* size, TickType_t wait) { + return ::xRingbufferReceive(m_handle, size, wait); +} // receive + + +/** + * @brief Return an item. + * @param [in] item The item to be returned/released. + */ +void Ringbuffer::returnItem(void* item) { + ::vRingbufferReturnItem(m_handle, item); +} // returnItem + + +/** + * @brief Send data to the buffer. + * @param [in] data The data to place into the buffer. + * @param [in] length The length of data to place into the buffer. + * @param [in] wait How long to wait before giving up. The default is to wait indefinitely. + * @return + */ +uint32_t Ringbuffer::send(void* data, size_t length, TickType_t wait) { + return ::xRingbufferSend(m_handle, data, length, wait); +} // send + + diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index 30d33be7..43a3b8f4 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -11,9 +11,10 @@ #include #include -#include // Include the base FreeRTOS definitions -#include // Include the task definitions -#include // Include the semaphore definitions +#include // Include the base FreeRTOS definitions. +#include // Include the task definitions. +#include // Include the semaphore definitions. +#include // Include the ringbuffer definitions. /** @@ -50,4 +51,20 @@ class FreeRTOS { }; }; + +/** + * @brief Ringbuffer. + */ +class Ringbuffer { +public: + Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT); + ~Ringbuffer(); + + void* receive(size_t* size, TickType_t wait = portMAX_DELAY); + void returnItem(void* item); + uint32_t send(void* data, size_t length, TickType_t wait = portMAX_DELAY); +private: + RingbufHandle_t m_handle; +}; + #endif /* MAIN_FREERTOS_H_ */ From b53dd77df26a75494406dc63d378443b7a1c56ec Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 19 Nov 2017 10:34:12 -0600 Subject: [PATCH 148/381] Fix for #212 --- cpp_utils/DesignNotes/BLECPP.md | 6 ++++++ cpp_utils/Makefile.arduino | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cpp_utils/DesignNotes/BLECPP.md b/cpp_utils/DesignNotes/BLECPP.md index e69de29b..db920dd3 100644 --- a/cpp_utils/DesignNotes/BLECPP.md +++ b/cpp_utils/DesignNotes/BLECPP.md @@ -0,0 +1,6 @@ +# BLE C++ classes + +# BLE Server +## BLE Characteristic callbacks +When a client connects to us as a BLE Server, it can change a characteristics value or read from a characteristic. The BLECharacteristicCallbacks class provides a handler for such. +# BLE Client \ No newline at end of file diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index fc3ccd0f..df7fe54f 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -18,7 +18,6 @@ BLE_FILES= \ BLEAdvertising.h \ BLECharacteristic.cpp \ BLECharacteristic.h \ - BLECharacteristicCallbacks.cpp \ BLECharacteristicMap.cpp \ BLEClient.cpp \ BLEClient.h \ From d6f9485e719405be9a16d364a2177629cf0d9072 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 20 Nov 2017 14:27:15 -0600 Subject: [PATCH 149/381] Fixes for #213 --- cpp_utils/WebServer.cpp | 82 +++++++++++++++++++++++------------- cpp_utils/WebServer.h | 1 + mongoose/webserver/README.md | 2 +- 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index ba871243..c250a4d5 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -431,7 +431,36 @@ void WebServer::HTTPResponse::addHeader(const std::string& name, const std::stri void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) { m_headers[std::move(name)] = std::move(value); -} +} // addHeader + + +/** + * @brief Build a string representation of the headers. + * @return A string representation of the headers. + */ +std::string WebServer::HTTPResponse::buildHeaders() { + std::string headers; + unsigned long headers_len = 0; + + for(auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + if(iter != m_headers.begin()) + headers_len += 2; + headers_len += iter->first.length(); + headers_len += 2; + headers_len += iter->second.length(); + } + headers_len += 1; + headers.resize(headers_len); // Will not have to resize and recopy during the next loop, we have 2 loops but it still ends up being faster + + for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + if(iter != m_headers.begin()) + headers += "\r\n"; + headers += iter->first; + headers += ": "; + headers += iter->second; + } + return headers; +} // buildHeaders /** * @brief Send data to the HTTP caller. @@ -444,6 +473,9 @@ void WebServer::HTTPResponse::sendData(const std::string& data) { } // sendData + + + /** * @brief Send data to the HTTP caller. * Send the data to the HTTP caller. No further data should be sent after this call. @@ -458,56 +490,48 @@ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { } m_dataSent = true; - std::string headers; - unsigned long headers_len = 0; - - for(auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { - if(iter != m_headers.begin()) - headers_len += 2; - headers_len += iter->first.length(); - headers_len += 2; - headers_len += iter->second.length(); - } - headers_len += 1; - headers.resize(headers_len); // Will not have to resize and recopy during the next loop, we have 2 loops but it still ends up being faster - - for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { - if(iter != m_headers.begin()) - headers += "\r\n"; - headers += iter->first; - headers += ": "; - headers += iter->second; - } - mg_send_head(m_nc, m_status, length, headers.c_str()); + mg_send_head(m_nc, m_status, length, buildHeaders().c_str()); mg_send(m_nc, pData, length); m_nc->flags |= MG_F_SEND_AND_CLOSE; } // sendData + +/** + * + */ void WebServer::HTTPResponse::sendData(const char* pData, size_t length) { sendData((uint8_t*) pData, length); } // sendData + +/** + * + */ void WebServer::HTTPResponse::sendChunkHead() { if(m_dataSent) { ESP_LOGE(tag, "HTTPResponse: Chunk headers already sent! Attempt to send again/more."); } m_dataSent = true; - mg_send_head(m_nc, m_status, -1, m_headers.c_str()); -} + mg_send_head(m_nc, m_status, -1, buildHeaders().c_str()); +} // sendChunkHead + +/** + * + */ void WebServer::HTTPResponse::sendChunk(const char* pData, size_t length) { mg_send_http_chunk(m_nc, pData, length); } // sendChunkHead + +/** + * + */ void WebServer::HTTPResponse::closeConnection() { m_nc->flags |= MG_F_SEND_AND_CLOSE; -} +} // closeConnection -void WebServer::HTTPResponse::sendData(const char* pData, size_t length) { - sendData((uint8_t*) pData, length); -} - /** * @brief Set the headers to be sent in the HTTP response. * @param [in] headers The complete set of headers to send to the caller. diff --git a/cpp_utils/WebServer.h b/cpp_utils/WebServer.h index ef879f91..ae06647b 100644 --- a/cpp_utils/WebServer.h +++ b/cpp_utils/WebServer.h @@ -71,6 +71,7 @@ class WebServer { int m_status; std::map m_headers; bool m_dataSent; + std::string buildHeaders(); }; // HTTPResponse /** diff --git a/mongoose/webserver/README.md b/mongoose/webserver/README.md index 4703176a..07e1ef3b 100644 --- a/mongoose/webserver/README.md +++ b/mongoose/webserver/README.md @@ -1,4 +1,4 @@ -#mongoose/webserver +# mongoose/webserver This snippet provides a simple Webserver using the Mongoose library on the ESP32. It provides two URL endpoints: From 2e08a22d993bd82b4c5eefc4a431386f90238cac Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Tue, 21 Nov 2017 00:02:34 -0600 Subject: [PATCH 150/381] Fixes for #216 --- cpp_utils/BLEClient.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index ba858287..83e778dc 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -163,8 +163,10 @@ void BLEClient::gattClientEventHandler( if (m_pClientCallbacks != nullptr) { m_pClientCallbacks->onConnect(this); } - m_isConnected = true; // Flag us as connected. - m_semaphoreOpenEvt.give(); + if (evtParam->open.status == ESP_GATT_OK) { + m_isConnected = true; // Flag us as connected. + } + m_semaphoreOpenEvt.give(evtParam->open.status); break; } // ESP_GATTC_OPEN_EVT From 17fbacc3413729cc4c87cd5dfe8d6d208f97d89c Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Fri, 24 Nov 2017 21:28:55 -0600 Subject: [PATCH 151/381] Code changes for #221 --- cpp_utils/WiFi.cpp | 14 +++++++++++--- cpp_utils/WiFi.h | 3 ++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 65d717c9..4054dfe5 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -52,9 +52,10 @@ WiFi::WiFi() , netmask(0) , m_pWifiEventHandler(nullptr) { - m_eventLoopStarted = false; - m_initCalled = false; + m_eventLoopStarted = false; + m_initCalled = false; m_pWifiEventHandler = new WiFiEventHandler(); + m_apConnected = false; // Are we connected to an access point? } // WiFi /** @@ -150,9 +151,10 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { * @param [in] waitForConnection Block until the connection has an outcome. * @return N/A. */ -void WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ +bool WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ ESP_LOGD(LOG_TAG, ">> connectAP"); + m_apConnected = false; init(); if (ip != 0 && gw != 0 && netmask != 0) { @@ -197,6 +199,7 @@ void WiFi::connectAP(const std::string& ssid, const std::string& password, bool m_connectFinished.wait("connectAP"); // Wait for the completion of the connection. ESP_LOGD(LOG_TAG, "<< connectAP"); + return m_apConnected; // Return true if we are now connected and false if not. } // connectAP @@ -231,6 +234,11 @@ void WiFi::dump() { // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that // indicates we are waiting for a connection complete. if (event->event_id == SYSTEM_EVENT_STA_GOT_IP || event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { // If we connected and have an IP, change the flag. + pWiFi->m_apConnected = true; + } else { + pWiFi->m_apConnected = false; + } pWiFi->m_connectFinished.give(); } diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 1dcf87fc..e9af3d67 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -114,6 +114,7 @@ class WiFi { uint8_t m_dnsCount=0; bool m_eventLoopStarted; bool m_initCalled; + bool m_apConnected; // Are we connected to an access point? FreeRTOS::Semaphore m_connectFinished = FreeRTOS::Semaphore("ConnectFinished"); public: @@ -127,7 +128,7 @@ class WiFi { void setDNSServer(int numdns, ip_addr_t ip); struct in_addr getHostByName(const std::string& hostName); struct in_addr getHostByName(const char* hostName); - void connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); + bool connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); void dump(); static std::string getApMac(); static tcpip_adapter_ip_info_t getApIpInfo(); From 5fafd4443c0ba81df1a1a607fb89f204eefa6aa7 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 25 Nov 2017 13:25:56 -0600 Subject: [PATCH 152/381] base line for #226 --- cpp_utils/ArduinoBLE.md | 8 +- cpp_utils/GeneralUtils.cpp | 7 +- cpp_utils/GeneralUtils.h | 2 +- cpp_utils/HttpServer.cpp | 12 +- cpp_utils/Memory.cpp | 86 ++++++- cpp_utils/Memory.h | 2 + cpp_utils/SockServ.cpp | 1 + cpp_utils/Socket.cpp | 7 +- cpp_utils/System.cpp | 7 + cpp_utils/System.h | 1 + mongoose/webserver/sdkconfig | 463 ++++++++++++++++++++++++++++++----- 11 files changed, 510 insertions(+), 86 deletions(-) diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index 59772357..d7a446af 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -1,11 +1,13 @@ # Arduino BLE Support -As part of this Github project, we provide libraries for Bluetooth Low Energy (BLE) for the ESP32 Arduino environment. Support for this capability is still in the process of being cooked (as of September 2017). As such you should not build product based on these functions as changes to the API and implementation are extremely likely over the next couple of months. +As part of this Github project, we provide libraries for Bluetooth Low Energy (BLE) for the ESP32 Arduino environment. Support for this capability is still in the process of being cooked (as of November 2017). As such you should not build product based on these functions as changes to the API and implementation are extremely likely over the next couple of months. That said, we now have the ability to produce a driver you can use for testing. This will give you early access to the function while give those who choose to build and maintain the code early feedback on what works, what doesn't and what can be improved from a usage perspective. When complete, the BLE support for ESP32 Arduino will be available as a single ZIP file. The file will be called **ESP32_BLE.zip**. It is this file that will be able to be imported into the Arduino IDE from the `Sketch -> Include Library -> Add .ZIP library`. When initial development of the library has been completed, this ZIP will be placed under some form of release control so that an ordinary Arduino IDE user can simply download this as a unit and install. -A build of the BLE support for Arduino can be found through the Arduino IDE. Visit Sketch -> Include Library -> Manage Libraries. In the library filter, enter "esp32 ble arduino". The search will narrow and you should see "ESP32 BLE Arduino" available for installation or upgrade. +Update: As of 2017-11, the BLE support has been included with the Arduino ESP32 base package. + +A build of the BLE support for Arduino can be found through the Arduino IDE. Visit `Sketch -> Include Library -> Manage Libraries`. In the library filter, enter "esp32 ble arduino". The search will narrow and you should see "ESP32 BLE Arduino" available for installation or upgrade. @@ -32,7 +34,7 @@ From October 2017 onwards, a build of the BLE libraries is supplied with the Ard 4. Extract the `ESP32_BLE.zip` file there ## Switching on debugging -The BLE support contains extensive internal diagnostics which can be switched on through the "Tools > Core Debug Level" setting: +The BLE support contains extensive internal diagnostics which can be switched on through the `Tools > Core Debug Level` setting: ![](../../Documentation/images/arduino_debug.png) diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index e8eb3a0a..fd0c740c 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -398,9 +398,4 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { } // errorToString -/** - * @brief Restart the ESP32. - */ -void GeneralUtils::restart() { - esp_restart(); -} // restart + diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index 2d55abfc..54aae5e3 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -22,7 +22,7 @@ class GeneralUtils { static const char* errorToString(esp_err_t errCode); static void hexDump(const uint8_t* pData, uint32_t length); static std::string ipToString(uint8_t* ip); - static void restart(); + }; #endif /* COMPONENTS_CPP_UTILS_GENERALUTILS_H_ */ diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 4fd8ceda..9ae382b5 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -15,6 +15,7 @@ #include "FileSystem.h" #include "WebSocket.h" #include "GeneralUtils.h" +#include "Memory.h" static const char* LOG_TAG = "HttpServer"; #undef close @@ -65,9 +66,9 @@ static void listDirectory(std::string path, HttpResponse& response) { * Constructor for HTTP Server */ HttpServer::HttpServer() { - m_portNumber = 80; // The default port number. - m_rootPath = ""; // The default path. - m_useSSL = false; // Default SSL is no. + m_portNumber = 80; // The default port number. + m_rootPath = ""; // The default path. + m_useSSL = false; // Default SSL is no. setDirectoryListing(false); // Default directory listing is no. } // HttpServer @@ -188,11 +189,12 @@ class HttpServerTask: public Task { while(1) { // Loop forever. ESP_LOGD("HttpServerTask", "Waiting for new peer client"); - + Memory::checkIntegrity(); try { clientSocket = m_pHttpServer->m_sockServ.waitForNewClient(); // Block waiting for a new external client connection. } - catch(std::exception e) { + catch(std::exception &e) { + ESP_LOGE("HttpServerTask", "Caught an exception waiting for new client!"); return; } diff --git a/cpp_utils/Memory.cpp b/cpp_utils/Memory.cpp index c5e7028b..e3be59db 100644 --- a/cpp_utils/Memory.cpp +++ b/cpp_utils/Memory.cpp @@ -11,20 +11,36 @@ #include #include "GeneralUtils.h" extern "C" { -#include -#include + #include + #include } #include +#include static const char* LOG_TAG = "Memory"; heap_trace_record_t* Memory::m_pRecords = nullptr; size_t Memory::m_lastHeapSize = 0; + +/** + * @brief Check the integrity of memory. + * @return True if all integrity checks passed + */ +/* STATIC */ bool Memory::checkIntegrity() { + bool rc = ::heap_caps_check_integrity_all(true); + if (rc == false && m_pRecords != nullptr) { + dumpRanges(); + abort(); + } + return rc; +} // checkIntegrity + + /** * @brief Dump the trace records from the heap. */ -void Memory::dump() { +/* STATIC */ void Memory::dump() { ::heap_trace_dump(); } // dump @@ -34,13 +50,53 @@ void Memory::dump() { int diff = currentUsage - m_lastHeapSize; ESP_LOGD(LOG_TAG, "%s: Heap changed by %d bytes (%d to %d)", tag.c_str(), diff, m_lastHeapSize, currentUsage); m_lastHeapSize = currentUsage; -} +} // dumpHeapChange + + +/** + * @brief Dump the ranges of allocations found. + */ +/* STATIC */ void Memory::dumpRanges() { + // Each record contained in the Heap trace has the following format: + // + // * uint32_t ccount – Timestamp of record. + // * void* address – Address that was allocated or released. + // * size_t size – Size of the block that was requested and allocated. + // * void* alloced_by[CONFIG_HEAP_TRACING_STACK_DEPTH] – Call stack of allocator + // * void* freed_by[CONFIG_HEAP_TRACING_STACK_DEPTH] – Call stack of releasor + // + if (m_pRecords == nullptr) { + return; + } + esp_log_level_set("*", ESP_LOG_NONE); + size_t count = heap_trace_get_count(); + heap_trace_record_t record; + printf(">>> dumpRanges\n"); + for (size_t i=0; i 0); if (m_pRecords != nullptr) { ESP_LOGE(LOG_TAG, "Already initialized"); @@ -60,7 +116,10 @@ void Memory::init(uint32_t recordCount) { } // init -void Memory::resumeTrace() { +/** + * @brief Resume previously paused trace. + */ +/* STATIC */ void Memory::resumeTrace() { esp_err_t errRc = ::heap_trace_resume(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "heap_trace_resume: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -69,7 +128,10 @@ void Memory::resumeTrace() { } // resumeTrace -void Memory::startTraceAll() { +/** + * @brief Start tracing all allocate and free calls. + */ +/* STATIC */ void Memory::startTraceAll() { esp_err_t errRc = ::heap_trace_start(HEAP_TRACE_ALL); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "heap_trace_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -78,7 +140,10 @@ void Memory::startTraceAll() { } // startTraceAll -void Memory::startTraceLeaks() { +/** + * Start tracing leaks. Matched allocate and free calls are removed. + */ +/* STATIC */ void Memory::startTraceLeaks() { esp_err_t errRc = ::heap_trace_start(HEAP_TRACE_LEAKS); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "heap_trace_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -87,7 +152,10 @@ void Memory::startTraceLeaks() { } // startTraceLeaks -void Memory::stopTrace() { +/** + * @brief Stop recording heap trace. + */ +/* STATIC */ void Memory::stopTrace() { esp_err_t errRc = ::heap_trace_stop(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "heap_trace_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); diff --git a/cpp_utils/Memory.h b/cpp_utils/Memory.h index dca5a838..bcca8c6a 100644 --- a/cpp_utils/Memory.h +++ b/cpp_utils/Memory.h @@ -16,7 +16,9 @@ extern "C" { class Memory { public: + static bool checkIntegrity(); static void dump(); + static void dumpRanges(); static void dumpHeapChange(std::string tag); static void init(uint32_t recordCount); static void resumeTrace(); diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index e3b67483..336ddc90 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -64,6 +64,7 @@ SockServ::~SockServ() { SockServ* pSockServ = (SockServ*)data; try { while(1) { + ESP_LOGD(LOG_TAG, "Waiting on accept") Socket tempSock = pSockServ->m_serverSocket.accept(pSockServ->getSSL()); if (!tempSock.isValid()) { continue; diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 849c3ef1..422d71cc 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -78,7 +78,7 @@ Socket Socket::accept(bool useSSL) { newSocket.m_sslSock.fd = clientSockFD; newSocket.sslHandshake(); ESP_LOGD(LOG_TAG, "DEBUG DEBUG "); - uint8_t x; + uint8_t x; // What is going on here??? newSocket.receive(&x, 1, 0); // FIX FIX FIX } ESP_LOGD(LOG_TAG, "<< accept: sockFd: %d", clientSockFD); @@ -226,7 +226,10 @@ void Socket::getBind(struct sockaddr* pAddr) { ESP_LOGE(LOG_TAG, "getBind: Socket is not initialized."); } socklen_t nameLen = sizeof(struct sockaddr); - ::getsockname(m_sock, pAddr, &nameLen); + int rc = ::getsockname(m_sock, pAddr, &nameLen); + if (rc != 0) { + ESP_LOGE(LOG_TAG, "Error with getsockname in getBind: %s", strerror(errno)); + } } // getBind diff --git a/cpp_utils/System.cpp b/cpp_utils/System.cpp index cd15ac13..1993be4c 100644 --- a/cpp_utils/System.cpp +++ b/cpp_utils/System.cpp @@ -57,3 +57,10 @@ std::string System::getIDFVersion() { size_t System::getMinimumFreeHeapSize() { return heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT); } // getMinimumFreeHeapSize + +/** + * @brief Restart the ESP32. + */ +void System::restart() { + esp_restart(); +} // restart diff --git a/cpp_utils/System.h b/cpp_utils/System.h index 915807ef..6459509d 100644 --- a/cpp_utils/System.h +++ b/cpp_utils/System.h @@ -22,6 +22,7 @@ class System { static size_t getFreeHeapSize(); static std::string getIDFVersion(); static size_t getMinimumFreeHeapSize(); + static void restart(); }; #endif /* COMPONENTS_CPP_UTILS_SYSTEM_H_ */ diff --git a/mongoose/webserver/sdkconfig b/mongoose/webserver/sdkconfig index 36eff3ff..80a51a3e 100644 --- a/mongoose/webserver/sdkconfig +++ b/mongoose/webserver/sdkconfig @@ -8,150 +8,493 @@ # CONFIG_TOOLPREFIX="xtensa-esp32-elf-" CONFIG_PYTHON="python" +CONFIG_MAKE_WARN_UNDEFINED_VARIABLES=y # # Bootloader config # -# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +CONFIG_LOG_BOOTLOADER_LEVEL_NONE= +CONFIG_LOG_BOOTLOADER_LEVEL_ERROR= CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y -# CONFIG_LOG_BOOTLOADER_LEVEL_INFO is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL_INFO= +CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG= +CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE= CONFIG_LOG_BOOTLOADER_LEVEL=2 +CONFIG_BOOTLOADER_VDDSDIO_BOOST=y + +# +# Security features +# +CONFIG_SECURE_BOOT_ENABLED= +CONFIG_FLASH_ENCRYPTION_ENABLED= # # Serial flasher config # CONFIG_ESPTOOLPY_PORT="/dev/ttyUSB0" -# CONFIG_ESPTOOLPY_BAUD_115200B is not set -# CONFIG_ESPTOOLPY_BAUD_230400B is not set +CONFIG_ESPTOOLPY_BAUD_115200B= +CONFIG_ESPTOOLPY_BAUD_230400B= CONFIG_ESPTOOLPY_BAUD_921600B=y -# CONFIG_ESPTOOLPY_BAUD_2MB is not set -# CONFIG_ESPTOOLPY_BAUD_OTHER is not set +CONFIG_ESPTOOLPY_BAUD_2MB= +CONFIG_ESPTOOLPY_BAUD_OTHER= CONFIG_ESPTOOLPY_BAUD_OTHER_VAL=115200 CONFIG_ESPTOOLPY_BAUD=921600 CONFIG_ESPTOOLPY_COMPRESSED=y -# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set -# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set -CONFIG_ESPTOOLPY_FLASHMODE_DIO=y -# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set +CONFIG_FLASHMODE_QIO= +CONFIG_FLASHMODE_QOUT= +CONFIG_FLASHMODE_DIO=y +CONFIG_FLASHMODE_DOUT= CONFIG_ESPTOOLPY_FLASHMODE="dio" -# CONFIG_ESPTOOLPY_FLASHFREQ_80M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_80M= CONFIG_ESPTOOLPY_FLASHFREQ_40M=y -# CONFIG_ESPTOOLPY_FLASHFREQ_26M is not set -# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_26M= +CONFIG_ESPTOOLPY_FLASHFREQ_20M= CONFIG_ESPTOOLPY_FLASHFREQ="40m" -# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_1MB= CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y -# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_4MB= +CONFIG_ESPTOOLPY_FLASHSIZE_8MB= +CONFIG_ESPTOOLPY_FLASHSIZE_16MB= CONFIG_ESPTOOLPY_FLASHSIZE="2MB" +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_BEFORE_RESET=y +CONFIG_ESPTOOLPY_BEFORE_NORESET= +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +CONFIG_ESPTOOLPY_AFTER_NORESET= +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_MONITOR_BAUD_9600B= +CONFIG_MONITOR_BAUD_57600B= +CONFIG_MONITOR_BAUD_115200B=y +CONFIG_MONITOR_BAUD_230400B= +CONFIG_MONITOR_BAUD_921600B= +CONFIG_MONITOR_BAUD_2MB= +CONFIG_MONITOR_BAUD_OTHER= +CONFIG_MONITOR_BAUD_OTHER_VAL=115200 +CONFIG_MONITOR_BAUD=115200 # # Partition Table # CONFIG_PARTITION_TABLE_SINGLE_APP=y -# CONFIG_PARTITION_TABLE_TWO_OTA is not set -# CONFIG_PARTITION_TABLE_CUSTOM is not set +CONFIG_PARTITION_TABLE_TWO_OTA= +CONFIG_PARTITION_TABLE_CUSTOM= CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000 CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" CONFIG_APP_OFFSET=0x10000 + +# +# Compiler options +# CONFIG_OPTIMIZATION_LEVEL_DEBUG=y -# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_LEVEL_RELEASE= +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +CONFIG_OPTIMIZATION_ASSERTIONS_SILENT= +CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED= +CONFIG_CXX_EXCEPTIONS= # # Component config # + +# +# Application Level Tracing +# +CONFIG_ESP32_APPTRACE_DEST_TRAX= +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_ENABLE= +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y + +# +# FreeRTOS SystemView Tracing +# +CONFIG_AWS_IOT_SDK= + +# +# Bluetooth +# +CONFIG_BT_ENABLED= +CONFIG_BTDM_CONTROLLER_PINNED_TO_CORE=0 CONFIG_BT_RESERVE_DRAM=0 # -# ESP32-specific config +# ESP32-specific # -# CONFIG_ESP32_DEFAULT_CPU_FREQ_80 is not set -# CONFIG_ESP32_DEFAULT_CPU_FREQ_160 is not set +CONFIG_ESP32_DEFAULT_CPU_FREQ_80= +CONFIG_ESP32_DEFAULT_CPU_FREQ_160= CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 -CONFIG_ESP32_ENABLE_STACK_WIFI=y -# CONFIG_ESP32_ENABLE_STACK_BT is not set -# CONFIG_ESP32_ENABLE_STACK_NONE is not set CONFIG_MEMMAP_SMP=y -# CONFIG_MEMMAP_TRACEMEM is not set +CONFIG_SPIRAM_SUPPORT= +CONFIG_MEMMAP_TRACEMEM= +CONFIG_MEMMAP_TRACEMEM_TWOBANKS= +CONFIG_ESP32_TRAX= CONFIG_TRACEMEM_RESERVE_DRAM=0x0 -CONFIG_WIFI_ENABLED=y +CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH= +CONFIG_ESP32_ENABLE_COREDUMP_TO_UART= +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_ESP32_ENABLE_COREDUMP= +CONFIG_TWO_UNIVERSAL_MAC_ADDRESS= +CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y +CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS=4 CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2048 CONFIG_MAIN_TASK_STACK_SIZE=4096 -CONFIG_NEWLIB_STDOUT_ADDCR=y -# CONFIG_ULP_COPROC_ENABLED is not set +CONFIG_IPC_TASK_STACK_SIZE=1024 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF= +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_LF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +CONFIG_NEWLIB_NANO_FORMAT= +CONFIG_CONSOLE_UART_DEFAULT=y +CONFIG_CONSOLE_UART_CUSTOM= +CONFIG_CONSOLE_UART_NONE= +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ULP_COPROC_ENABLED= CONFIG_ULP_COPROC_RESERVE_MEM=0 -# CONFIG_ESP32_PANIC_PRINT_HALT is not set +CONFIG_ESP32_PANIC_PRINT_HALT= CONFIG_ESP32_PANIC_PRINT_REBOOT=y -# CONFIG_ESP32_PANIC_SILENT_REBOOT is not set -# CONFIG_ESP32_PANIC_GDBSTUB is not set +CONFIG_ESP32_PANIC_SILENT_REBOOT= +CONFIG_ESP32_PANIC_GDBSTUB= CONFIG_ESP32_DEBUG_OCDAWARE=y CONFIG_INT_WDT=y CONFIG_INT_WDT_TIMEOUT_MS=300 CONFIG_TASK_WDT=y -# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_PANIC= CONFIG_TASK_WDT_TIMEOUT_S=5 -CONFIG_TASK_WDT_CHECK_IDLE_TASK=y -# CONFIG_ESP32_TIME_SYSCALL_USE_RTC is not set +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_0=y +CONFIG_BROWNOUT_DET_LVL_SEL_1= +CONFIG_BROWNOUT_DET_LVL_SEL_2= +CONFIG_BROWNOUT_DET_LVL_SEL_3= +CONFIG_BROWNOUT_DET_LVL_SEL_4= +CONFIG_BROWNOUT_DET_LVL_SEL_5= +CONFIG_BROWNOUT_DET_LVL_SEL_6= +CONFIG_BROWNOUT_DET_LVL_SEL_7= +CONFIG_BROWNOUT_DET_LVL=0 +CONFIG_ESP32_TIME_SYSCALL_USE_RTC= CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y -# CONFIG_ESP32_TIME_SYSCALL_USE_FRC1 is not set -# CONFIG_ESP32_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32_TIME_SYSCALL_USE_FRC1= +CONFIG_ESP32_TIME_SYSCALL_USE_NONE= CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y +CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL= +CONFIG_ESP32_RTC_CLK_CAL_CYCLES=1024 +CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP32_XTAL_FREQ_40=y +CONFIG_ESP32_XTAL_FREQ_26= +CONFIG_ESP32_XTAL_FREQ_AUTO= +CONFIG_ESP32_XTAL_FREQ=40 +CONFIG_DISABLE_BASIC_ROM_CONSOLE= +CONFIG_NO_BLOBS= + +# +# Wi-Fi +# +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER= +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_AMPDU_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +CONFIG_ESP32_WIFI_NVS_ENABLED=y + +# +# PHY +# +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION= +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 + +# +# Power Management +# +CONFIG_PM_ENABLE= + +# +# Ethernet +# +CONFIG_DMA_RX_BUF_NUM=10 +CONFIG_DMA_TX_BUF_NUM=10 +CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE= +CONFIG_EMAC_TASK_PRIORITY=20 + +# +# FAT Filesystem support +# +CONFIG_FATFS_CODEPAGE_ASCII=y +CONFIG_FATFS_CODEPAGE_437= +CONFIG_FATFS_CODEPAGE_720= +CONFIG_FATFS_CODEPAGE_737= +CONFIG_FATFS_CODEPAGE_771= +CONFIG_FATFS_CODEPAGE_775= +CONFIG_FATFS_CODEPAGE_850= +CONFIG_FATFS_CODEPAGE_852= +CONFIG_FATFS_CODEPAGE_855= +CONFIG_FATFS_CODEPAGE_857= +CONFIG_FATFS_CODEPAGE_860= +CONFIG_FATFS_CODEPAGE_861= +CONFIG_FATFS_CODEPAGE_862= +CONFIG_FATFS_CODEPAGE_863= +CONFIG_FATFS_CODEPAGE_864= +CONFIG_FATFS_CODEPAGE_865= +CONFIG_FATFS_CODEPAGE_866= +CONFIG_FATFS_CODEPAGE_869= +CONFIG_FATFS_CODEPAGE_932= +CONFIG_FATFS_CODEPAGE_936= +CONFIG_FATFS_CODEPAGE_949= +CONFIG_FATFS_CODEPAGE_950= +CONFIG_FATFS_CODEPAGE=1 +CONFIG_FATFS_MAX_LFN=255 +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y # # FreeRTOS # CONFIG_FREERTOS_UNICORE=y CONFIG_FREERTOS_CORETIMER_0=y -# CONFIG_FREERTOS_CORETIMER_1 is not set -# CONFIG_FREERTOS_CORETIMER_2 is not set +CONFIG_FREERTOS_CORETIMER_1= CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE= CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL=y -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY= +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK= +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=3 CONFIG_FREERTOS_ASSERT_FAIL_ABORT=y -# CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE is not set -# CONFIG_FREERTOS_ASSERT_DISABLE is not set -CONFIG_FREERTOS_BREAK_ON_SCHEDULER_START_JTAG=y -# CONFIG_ENABLE_MEMORY_DEBUG is not set +CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE= +CONFIG_FREERTOS_ASSERT_DISABLE= +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1024 CONFIG_FREERTOS_ISR_STACKSIZE=1536 -# CONFIG_FREERTOS_DEBUG_INTERNALS is not set +CONFIG_FREERTOS_LEGACY_HOOKS= +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +CONFIG_SUPPORT_STATIC_ALLOCATION= +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_USE_TRACE_FACILITY= +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS= +CONFIG_FREERTOS_DEBUG_INTERNALS= + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +CONFIG_HEAP_POISONING_LIGHT= +CONFIG_HEAP_POISONING_COMPREHENSIVE= +CONFIG_HEAP_TRACING= + +# +# libsodium +# +CONFIG_LIBSODIUM_USE_MBEDTLS_SHA=y # # Log output # -# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set -# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set -# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set -# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set -# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +CONFIG_LOG_DEFAULT_LEVEL_NONE= +CONFIG_LOG_DEFAULT_LEVEL_ERROR= +CONFIG_LOG_DEFAULT_LEVEL_WARN= +CONFIG_LOG_DEFAULT_LEVEL_INFO= +CONFIG_LOG_DEFAULT_LEVEL_DEBUG= CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=y CONFIG_LOG_DEFAULT_LEVEL=5 -# CONFIG_LOG_COLORS is not set +CONFIG_LOG_COLORS= # # LWIP # +CONFIG_L2_TO_L3_COPY= CONFIG_LWIP_MAX_SOCKETS=4 -CONFIG_LWIP_THREAD_LOCAL_STORAGE_INDEX=0 -# CONFIG_LWIP_SO_REUSE is not set +CONFIG_LWIP_SO_REUSE= +CONFIG_LWIP_SO_RCVBUF= CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1 +CONFIG_LWIP_IP_FRAG= +CONFIG_LWIP_IP_REASSEMBLY= +CONFIG_LWIP_STATS= +CONFIG_LWIP_ETHARP_TRUST_IP_MAC=y +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +CONFIG_LWIP_AUTOIP= +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=6 +CONFIG_TCP_MSS=1436 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5744 +CONFIG_TCP_WND_DEFAULT=5744 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +CONFIG_TCP_OVERSIZE_MSS=y +CONFIG_TCP_OVERSIZE_QUARTER_MSS= +CONFIG_TCP_OVERSIZE_DISABLE= + +# +# UDP +# +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=2560 +CONFIG_PPP_SUPPORT= + +# +# ICMP +# +CONFIG_LWIP_MULTICAST_PING= +CONFIG_LWIP_BROADCAST_PING= # # mbedTLS # CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 -# CONFIG_MBEDTLS_DEBUG is not set +CONFIG_MBEDTLS_DEBUG= +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_HARDWARE_MPI= +CONFIG_MBEDTLS_HARDWARE_SHA= +CONFIG_MBEDTLS_HAVE_TIME=y +CONFIG_MBEDTLS_HAVE_TIME_DATE= +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +CONFIG_MBEDTLS_TLS_SERVER_ONLY= +CONFIG_MBEDTLS_TLS_CLIENT_ONLY= +CONFIG_MBEDTLS_TLS_DISABLED= +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +CONFIG_MBEDTLS_PSK_MODES= +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_SSL3= +CONFIG_MBEDTLS_SSL_PROTO_TLS1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +CONFIG_MBEDTLS_SSL_PROTO_DTLS= +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +CONFIG_MBEDTLS_CAMELLIA_C= +CONFIG_MBEDTLS_DES_C= +CONFIG_MBEDTLS_RC4_DISABLED=y +CONFIG_MBEDTLS_RC4_ENABLED_NO_DEFAULT= +CONFIG_MBEDTLS_RC4_ENABLED= +CONFIG_MBEDTLS_BLOWFISH_C= +CONFIG_MBEDTLS_XTEA_C= +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +CONFIG_MBEDTLS_RIPEMD160_C= + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y + +# +# OpenSSL +# +CONFIG_OPENSSL_DEBUG= +CONFIG_OPENSSL_ASSERT_DO_NOTHING=y +CONFIG_OPENSSL_ASSERT_EXIT= + +# +# PThreads +# +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=2048 # # SPI Flash driver # -# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ENABLE_COUNTERS= +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS= +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED= + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +CONFIG_SPIFFS_CACHE_STATS= +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +CONFIG_SPIFFS_GC_STATS= +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y + +# +# Debug Configuration +# +CONFIG_SPIFFS_DBG= +CONFIG_SPIFFS_API_DBG= +CONFIG_SPIFFS_GC_DBG= +CONFIG_SPIFFS_CACHE_DBG= +CONFIG_SPIFFS_CHECK_DBG= +CONFIG_SPIFFS_TEST_VISUALISATION= + +# +# tcpip adapter +# +CONFIG_IP_LOST_TIMER_INTERVAL=120 + +# +# Wear Levelling +# +CONFIG_WL_SECTOR_SIZE_512= +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 From 3db35bdbd02b05fa456d5935102221a6ab2301b4 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 25 Nov 2017 17:26:28 -0600 Subject: [PATCH 153/381] Changes for #217 --- cpp_utils/HttpServer.cpp | 11 +++++------ cpp_utils/HttpServer.h | 5 +++-- cpp_utils/SockServ.cpp | 2 +- cpp_utils/Socket.cpp | 12 +++++------- cpp_utils/Socket.h | 4 ++-- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 9ae382b5..298ab0c3 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -181,17 +181,16 @@ class HttpServerTask: public Task { */ void run(void* data) { m_pHttpServer = (HttpServer*)data; // The passed in data is an instance of an HttpServer. - m_pHttpServer->m_sockServ.setPort(m_pHttpServer->m_portNumber); - m_pHttpServer->m_sockServ.setSSL(m_pHttpServer->m_useSSL); - m_pHttpServer->m_sockServ.start(); + m_pHttpServer->m_socket.setSSL(m_pHttpServer->m_useSSL); + m_pHttpServer->m_socket.listen(m_pHttpServer->m_portNumber); ESP_LOGD("HttpServerTask", "Listening on port %d", m_pHttpServer->getPort()); Socket clientSocket; while(1) { // Loop forever. ESP_LOGD("HttpServerTask", "Waiting for new peer client"); - Memory::checkIntegrity(); + //Memory::checkIntegrity(); try { - clientSocket = m_pHttpServer->m_sockServ.waitForNewClient(); // Block waiting for a new external client connection. + clientSocket = m_pHttpServer->m_socket.accept(); // Block waiting for a new external client connection. } catch(std::exception &e) { ESP_LOGE("HttpServerTask", "Caught an exception waiting for new client!"); @@ -353,7 +352,7 @@ void HttpServer::stop() { // that is listening for incoming connections. That will then shutdown all the other // activities. ESP_LOGD(LOG_TAG, ">> stop"); - m_sockServ.stop(); + m_socket.close(); ESP_LOGD(LOG_TAG, "<< stop"); } // stop diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index 4b7e3083..f07abedd 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -16,7 +16,7 @@ #include "SockServ.h" #include "HttpRequest.h" #include "HttpResponse.h" -#include "SockServ.h" +//#include "SockServ.h" #include class HttpServerTask; @@ -89,7 +89,8 @@ class HttpServer { std::string m_rootPath; // Root path into the file system. bool m_useSSL; // Is this server listening on an HTTPS port? bool m_directoryListing; // Should we list directory content? - SockServ m_sockServ; // Server socket. + //SockServ m_sockServ; // Server socket. + Socket m_socket; }; // HttpServer #endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 336ddc90..6d35a387 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -65,7 +65,7 @@ SockServ::~SockServ() { try { while(1) { ESP_LOGD(LOG_TAG, "Waiting on accept") - Socket tempSock = pSockServ->m_serverSocket.accept(pSockServ->getSSL()); + Socket tempSock = pSockServ->m_serverSocket.accept(); if (!tempSock.isValid()) { continue; } diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 422d71cc..b7c41c1e 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -56,14 +56,12 @@ Socket::~Socket() { /** * @brief Accept a new socket. */ -Socket Socket::accept(bool useSSL) { +Socket Socket::accept() { struct sockaddr addr; getBind(&addr); - ESP_LOGD(LOG_TAG, ">> accept: Accepting on %s; sockFd: %d, using SSL: %d", addressToString(&addr).c_str(), m_sock, useSSL); + ESP_LOGD(LOG_TAG, ">> accept: Accepting on %s; sockFd: %d, using SSL: %d", addressToString(&addr).c_str(), m_sock, getSSL()); - struct sockaddr_in clientAddress; - socklen_t clientAddressLength = sizeof(clientAddress); - int clientSockFD = ::lwip_accept_r(m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); + int clientSockFD = ::lwip_accept_r(m_sock, nullptr, nullptr); if (clientSockFD == -1) { SocketException se(errno); ESP_LOGE(LOG_TAG, "accept(): %s, m_sock=%d", strerror(errno), m_sock); @@ -73,7 +71,7 @@ Socket Socket::accept(bool useSSL) { ESP_LOGD(LOG_TAG, " - accept: Received new client!: sockFd: %d", clientSockFD); Socket newSocket; newSocket.m_sock = clientSockFD; - if (useSSL) { + if (getSSL()) { newSocket.setSSL(true); newSocket.m_sslSock.fd = clientSockFD; newSocket.sslHandshake(); @@ -252,7 +250,7 @@ bool Socket::isValid() { /** * @brief Create a listening socket. * @param [in] port The port number to listen upon. - * @param [in] isDatagram True if we are listening on a datagram. + * @param [in] isDatagram True if we are listening on a datagram. The default is false. */ void Socket::listen(uint16_t port, bool isDatagram) { ESP_LOGD(LOG_TAG, ">> listen: port: %d, isDatagram: %d", port, isDatagram); diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 33cce989..d644dfed 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -56,7 +56,7 @@ class Socket { Socket(); virtual ~Socket(); - Socket accept(bool useSSL=false); + Socket accept(); static std::string addressToString(struct sockaddr* addr); void bind(uint16_t port, uint32_t address); void close(); @@ -99,7 +99,7 @@ class SocketInputRecordStreambuf : public std::streambuf { ~SocketInputRecordStreambuf(); int_type underflow(); private: - char *m_buffer; + char* m_buffer; Socket m_socket; size_t m_dataLength; size_t m_bufferSize; From 313548882fda7643500343bb27a8324eaf907c4e Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 25 Nov 2017 17:34:20 -0600 Subject: [PATCH 154/381] Fix image link --- cpp_utils/ArduinoBLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index d7a446af..fa5bda38 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -36,7 +36,7 @@ From October 2017 onwards, a build of the BLE libraries is supplied with the Ard ## Switching on debugging The BLE support contains extensive internal diagnostics which can be switched on through the `Tools > Core Debug Level` setting: -![](../../Documentation/images/arduino_debug.png) +![](https://github.com/nkolban/esp32-snippets/blob/master/Documentation/images/arduino_debug.png) ## Decoding an exception stack trace From a5e7ace62a338983d71d7965c84c8396d1e3e33d Mon Sep 17 00:00:00 2001 From: Jerzy Mikucki Date: Sun, 26 Nov 2017 02:06:30 +0100 Subject: [PATCH 155/381] Bug fix and cleanup. I2C version of HAL was not functional with current u8g2 version --- hardware/displays/U8G2/test_SSD1306.c | 8 +- hardware/displays/U8G2/test_SSD1306_i2c.c | 6 +- hardware/displays/U8G2/u8g2_esp32_hal.c | 151 ++++++---------------- hardware/displays/U8G2/u8g2_esp32_hal.h | 19 ++- 4 files changed, 59 insertions(+), 125 deletions(-) diff --git a/hardware/displays/U8G2/test_SSD1306.c b/hardware/displays/U8G2/test_SSD1306.c index 62f57cf1..16217017 100644 --- a/hardware/displays/U8G2/test_SSD1306.c +++ b/hardware/displays/U8G2/test_SSD1306.c @@ -40,16 +40,16 @@ void task_test_SSD1306(void *ignore) { u8g2_Setup_ssd1306_128x64_noname_f( &u8g2, U8G2_R0, - u8g2_esp32_msg_comms_cb, - u8g2_esp32_msg_gpio_and_delay_cb); // init u8g2 structure + u8g2_esp32_spi_byte_cb, + u8g2_esp32_gpio_and_delay_cb); // init u8g2 structure u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this, u8g2_SetPowerSave(&u8g2, 0); // wake up display u8g2_ClearBuffer(&u8g2); u8g2_DrawBox(&u8g2, 10,20, 20, 30); - u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr); - u8g2_DrawStr(&u8g2, 0,15,"Hello World!"); + u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr); + u8g2_DrawStr(&u8g2, 0,15,"Hello World!"); u8g2_SendBuffer(&u8g2); ESP_LOGD(tag, "All done!"); diff --git a/hardware/displays/U8G2/test_SSD1306_i2c.c b/hardware/displays/U8G2/test_SSD1306_i2c.c index a26d91fe..2ea8e6b4 100644 --- a/hardware/displays/U8G2/test_SSD1306_i2c.c +++ b/hardware/displays/U8G2/test_SSD1306_i2c.c @@ -26,12 +26,12 @@ void task_test_SSD1306i2c(void *ignore) { u8g2_t u8g2; // a structure which will contain all the data for one display - u8g2_Setup_ssd1306_128x32_univision_f( + u8g2_Setup_ssd1306_i2c_128x32_univision_f( &u8g2, U8G2_R0, //u8x8_byte_sw_i2c, - u8g2_esp32_msg_i2c_cb, - u8g2_esp32_msg_i2c_and_delay_cb); // init u8g2 structure + u8g2_esp32_i2c_byte_cb, + u8g2_esp32_gpio_and_delay_cb); // init u8g2 structure u8x8_SetI2CAddress(&u8g2.u8x8,0x78); ESP_LOGI(TAG, "u8g2_InitDisplay"); diff --git a/hardware/displays/U8G2/u8g2_esp32_hal.c b/hardware/displays/U8G2/u8g2_esp32_hal.c index 49f71c4d..05a2c994 100644 --- a/hardware/displays/U8G2/u8g2_esp32_hal.c +++ b/hardware/displays/U8G2/u8g2_esp32_hal.c @@ -10,9 +10,11 @@ #include "u8g2_esp32_hal.h" static const char *TAG = "u8g2_hal"; +static const unsigned int I2C_TIMEOUT_MS = 1000; -static spi_device_handle_t handle; // SPI handle. -static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data. +static spi_device_handle_t handle_spi; // SPI handle. +static i2c_cmd_handle_t handle_i2c; // I2C handle. +static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data. #undef ESP_ERROR_CHECK #define ESP_ERROR_CHECK(x) do { esp_err_t rc = (x); if (rc != ESP_OK) { ESP_LOGE("err", "esp_err_t = %d", rc); assert(0 && #x);} } while(0); @@ -26,10 +28,10 @@ void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param) { /* * HAL callback function as prescribed by the U8G2 library. This callback is invoked - * to handle callbacks for communications. + * to handle SPI communications. */ -uint8_t u8g2_esp32_msg_comms_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - //ESP_LOGD(tag, "msg_comms_cb: Received a msg: %d: %s", msg, msgToString(msg, arg_int, arg_ptr)); +uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { + ESP_LOGD(TAG, "spi_byte_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr); switch(msg) { case U8X8_MSG_BYTE_SET_DC: if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { @@ -50,7 +52,7 @@ uint8_t u8g2_esp32_msg_comms_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void bus_config.miso_io_num = -1; // MISO bus_config.quadwp_io_num = -1; // Not used bus_config.quadhd_io_num = -1; // Not used - //ESP_LOGI(tag, "... Initializing bus."); + //ESP_LOGI(TAG, "... Initializing bus."); ESP_ERROR_CHECK(spi_bus_initialize(HSPI_HOST, &bus_config, 1)); spi_device_interface_config_t dev_config; @@ -67,8 +69,8 @@ uint8_t u8g2_esp32_msg_comms_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void dev_config.queue_size = 200; dev_config.pre_cb = NULL; dev_config.post_cb = NULL; - //ESP_LOGI(tag, "... Adding device bus."); - ESP_ERROR_CHECK(spi_bus_add_device(HSPI_HOST, &dev_config, &handle)); + //ESP_LOGI(TAG, "... Adding device bus."); + ESP_ERROR_CHECK(spi_bus_add_device(HSPI_HOST, &dev_config, &handle_spi)); break; } @@ -76,36 +78,35 @@ uint8_t u8g2_esp32_msg_comms_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void case U8X8_MSG_BYTE_SEND: { spi_transaction_t trans_desc; trans_desc.addr = 0; - trans_desc.command = 0; + trans_desc.cmd = 0; trans_desc.flags = 0; trans_desc.length = 8 * arg_int; // Number of bits NOT number of bytes. trans_desc.rxlength = 0; trans_desc.tx_buffer = arg_ptr; trans_desc.rx_buffer = NULL; - //ESP_LOGI(tag, "... Transmitting %d bytes.", arg_int); - ESP_ERROR_CHECK(spi_device_transmit(handle, &trans_desc)); + //ESP_LOGI(TAG, "... Transmitting %d bytes.", arg_int); + ESP_ERROR_CHECK(spi_device_transmit(handle_spi, &trans_desc)); break; } } return 0; -} // u8g2_esp32_msg_comms_cb +} // u8g2_esp32_spi_byte_cb /* * HAL callback function as prescribed by the U8G2 library. This callback is invoked - * to handle callbacks for communications. + * to handle I2C communications. */ -uint8_t u8g2_esp32_msg_i2c_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - - ESP_LOGD(TAG, "msg_i2c_cb: Received a msg: %d %d", msg, arg_int); - //ESP_LOGD(tag, "msg_i2c_cb: Received a msg: %d: %s", msg, msgToString(msg, arg_int, arg_ptr)); +uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { + ESP_LOGD(TAG, "i2c_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr); switch(msg) { - case U8X8_MSG_BYTE_SET_DC: + case U8X8_MSG_BYTE_SET_DC: { if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { gpio_set_level(u8g2_esp32_hal.dc, arg_int); } break; + } case U8X8_MSG_BYTE_INIT: { if (u8g2_esp32_hal.sda == U8G2_ESP32_HAL_UNDEFINED || @@ -127,108 +128,47 @@ uint8_t u8g2_esp32_msg_i2c_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void * ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf)); ESP_LOGI(TAG, "i2c_driver_install %d", I2C_MASTER_NUM); ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0)); -/* - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // dummy write - ESP_ERROR_CHECK(i2c_master_start(cmd)); - ESP_ERROR_CHECK(i2c_master_write_byte(cmd, 0x00 | I2C_MASTER_WRITE, ACK_CHECK_DIS)); - ESP_ERROR_CHECK(i2c_master_stop(cmd)); - ESP_LOGI(TAG, "i2c_master_cmd_begin %d", I2C_MASTER_NUM); - ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS)); - i2c_cmd_link_delete(cmd); -*/ - break; } case U8X8_MSG_BYTE_SEND: { - uint8_t *data; - uint8_t cmddata; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - ESP_ERROR_CHECK(i2c_master_start(cmd)); -// ESP_LOGI(TAG, "I2CAddress %02X", u8x8_GetI2CAddress(u8x8)>>1); - ESP_ERROR_CHECK(i2c_master_write_byte(cmd, u8x8_GetI2CAddress(u8x8) | I2C_MASTER_WRITE, ACK_CHECK_EN)); - data = (uint8_t *)arg_ptr; - if (arg_int==1) { - cmddata=0; - ESP_ERROR_CHECK(i2c_master_write(cmd, &cmddata, 1, ACK_CHECK_EN)); -// printf("0x%02X ",zerodata); - } else { - cmddata=0x40; - ESP_ERROR_CHECK(i2c_master_write(cmd, &cmddata, 1, ACK_CHECK_EN)); - //bzero(arg_ptr,arg_int); - //*data=0x40; - } - //ESP_ERROR_CHECK(i2c_master_write(cmd, arg_ptr, arg_int, ACK_CHECK_EN)); + uint8_t* data_ptr = (uint8_t*)arg_ptr; + ESP_LOG_BUFFER_HEXDUMP(TAG, data_ptr, arg_int, ESP_LOG_VERBOSE); - while( arg_int > 0 ) { - ESP_ERROR_CHECK(i2c_master_write_byte(cmd, *data, ACK_CHECK_EN)); -// printf("0x%02X ",*data); - data++; + while( arg_int > 0 ) { + ESP_ERROR_CHECK(i2c_master_write_byte(handle_i2c, *data_ptr, ACK_CHECK_EN)); + data_ptr++; arg_int--; - } -// printf("\n"); - - ESP_ERROR_CHECK(i2c_master_stop(cmd)); -// ESP_LOGI(TAG, "i2c_master_cmd_begin %d", I2C_MASTER_NUM); - ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS)); - i2c_cmd_link_delete(cmd); - break; - } - } - return 0; -} // u8g2_esp32_msg_i2c_cb - -/* - * HAL callback function as prescribed by the U8G2 library. This callback is invoked - * to handle callbacks for GPIO and delay functions. - */ -uint8_t u8g2_esp32_msg_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - //ESP_LOGD(tag, "msg_gpio_and_delay_cb: Received a msg: %d: %s", msg, msgToString(msg, arg_int, arg_ptr)); - switch(msg) { - - // Initialize the GPIO and DELAY HAL functions. If the pins for DC and RESET have been - // specified then we define those pins as GPIO outputs. - case U8X8_MSG_GPIO_AND_DELAY_INIT: { - uint64_t bitmask = 0; - if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { - bitmask = bitmask | (1<>1); + ESP_ERROR_CHECK(i2c_master_start(handle_i2c)); + ESP_ERROR_CHECK(i2c_master_write_byte(handle_i2c, i2c_address | I2C_MASTER_WRITE, ACK_CHECK_EN)); break; + } - // Delay for the number of milliseconds passed in through arg_int. - case U8X8_MSG_DELAY_MILLI: - vTaskDelay(arg_int/portTICK_PERIOD_MS); + case U8X8_MSG_BYTE_END_TRANSFER: { + ESP_LOGD(TAG, "End I2C transfer."); + ESP_ERROR_CHECK(i2c_master_stop(handle_i2c)); + ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, handle_i2c, I2C_TIMEOUT_MS / portTICK_RATE_MS)); + i2c_cmd_link_delete(handle_i2c); break; + } } return 0; -} // u8g2_esp32_msg_gpio_and_delay_cb +} // u8g2_esp32_i2c_byte_cb /* * HAL callback function as prescribed by the U8G2 library. This callback is invoked - * to handle callbacks for I²C and delay functions. + * to handle callbacks for GPIO and delay functions. */ -uint8_t u8g2_esp32_msg_i2c_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - - ESP_LOGD(TAG, "msg_i2c_and_delay_cb: Received a msg: %d", msg); +uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { + ESP_LOGD(TAG, "gpio_and_delay_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr); switch(msg) { // Initialize the GPIO and DELAY HAL functions. If the pins for DC and RESET have been @@ -244,12 +184,7 @@ uint8_t u8g2_esp32_msg_i2c_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_i if (u8g2_esp32_hal.cs != U8G2_ESP32_HAL_UNDEFINED) { bitmask = bitmask | (1< Date: Sun, 26 Nov 2017 16:35:40 -0600 Subject: [PATCH 156/381] Changes for #232 --- cpp_utils/Socket.h | 7 ++++++- cpp_utils/WS2812.cpp | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index d644dfed..6eef11db 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -7,7 +7,7 @@ #ifndef COMPONENTS_CPP_UTILS_SOCKET_H_ #define COMPONENTS_CPP_UTILS_SOCKET_H_ - +#include "sdkconfig.h" #include #include @@ -37,6 +37,11 @@ #include #include + +#if CONFIG_CXX_EXCEPTIONS != 1 +#error "C++ exception handling must be enabled within make menuconfig. See Compiler Options > Enable C++ Exceptions." +#endif + class SocketException: public std::exception { public: SocketException(int myErrno); diff --git a/cpp_utils/WS2812.cpp b/cpp_utils/WS2812.cpp index ca0a5b24..624480f0 100644 --- a/cpp_utils/WS2812.cpp +++ b/cpp_utils/WS2812.cpp @@ -10,6 +10,11 @@ #include "sdkconfig.h" #include "WS2812.h" +#if CONFIG_CXX_EXCEPTIONS != 1 +#error "C++ exception handling must be enabled within make menuconfig. See Compiler Options > Enable C++ Exceptions." +#endif + + static const char* LOG_TAG = "WS2812"; /** From a4eb6dd92139000bd236f37be6e14e4951288609 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 26 Nov 2017 20:55:49 -0600 Subject: [PATCH 157/381] Updates for #196 --- cpp_utils/BLEAdvertising.cpp | 222 ++++++++++++++++++++++++++++++-- cpp_utils/BLEAdvertising.h | 23 ++++ cpp_utils/BLEUUID.cpp | 26 ++++ cpp_utils/BLEUUID.h | 1 + cpp_utils/BLEUtils.cpp | 9 ++ cpp_utils/DesignNotes/BLECPP.md | 57 +++++++- 6 files changed, 325 insertions(+), 13 deletions(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index e5179a41..df4d220b 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -4,6 +4,17 @@ * This class encapsulates advertising a BLE Server. * Created on: Jun 21, 2017 * Author: kolban + * + * The ESP-IDF provides a framework for BLE advertising. It has determined that there are a common set + * of properties that are advertised and has built a data structure that can be populated by the programmer. + * This means that the programmer doesn't have to "mess with" the low level construction of a low level + * BLE advertising frame. Many of the fields are determined for us while others we can set before starting + * to advertise. + * + * Should we wish to construct our own payload, we can use the BLEAdvertisementData class and call the setters + * upon it. Once it is populated, we can then associate it with the advertising and what ever the programmer + * set in the data will be advertised. + * */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) @@ -12,6 +23,7 @@ #include #include "BLEUtils.h" #include "GeneralUtils.h" + #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" #endif @@ -44,6 +56,9 @@ BLEAdvertising::BLEAdvertising() { m_advParams.own_addr_type = BLE_ADDR_TYPE_PUBLIC; m_advParams.channel_map = ADV_CHNL_ALL; m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; + + m_customAdvData = false; // No custom advertising data + m_customScanResponseData = false; // No custom scan response data } // BLEAdvertising @@ -77,13 +92,47 @@ void BLEAdvertising::setAppearance(uint16_t appearance) { } // setAppearance +/** + * @brief Set the advertisement data that is to be published in a regular advertisement. + * @param [in] advertisementData The data to be advertised. + */ +void BLEAdvertising::setAdvertisementData(BLEAdvertisementData& advertisementData) { + ESP_LOGD(LOG_TAG, ">> setAdvertisementData"); + esp_err_t errRc = ::esp_ble_gap_config_adv_data_raw( + (uint8_t*)advertisementData.getPayload().data(), + advertisementData.getPayload().length()); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_config_adv_data_raw: %d %s", errRc, GeneralUtils::errorToString(errRc)); + } + m_customAdvData = true; // Set the flag that indicates we are using custom advertising data. + ESP_LOGD(LOG_TAG, "<< setAdvertisementData"); +} // setAdvertisementData + + +/** + * @brief Set the advertisement data that is to be published in a scan response. + * @param [in] advertisementData The data to be advertised. + */ +void BLEAdvertising::setScanResponseData(BLEAdvertisementData& advertisementData) { + ESP_LOGD(LOG_TAG, ">> setScanResponseData"); + esp_err_t errRc = ::esp_ble_gap_config_scan_rsp_data_raw( + (uint8_t*)advertisementData.getPayload().data(), + advertisementData.getPayload().length()); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_config_scan_rsp_data_raw: %d %s", errRc, GeneralUtils::errorToString(errRc)); + } + m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data. + ESP_LOGD(LOG_TAG, "<< setScanResponseData"); +} // setScanResponseData + /** * @brief Start advertising. * Start advertising. * @return N/A. */ void BLEAdvertising::start() { - ESP_LOGD(LOG_TAG, ">> start"); + ESP_LOGD(LOG_TAG, ">> start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); + // We have a vector of service UUIDs that we wish to advertise. In order to use the // ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte) @@ -92,7 +141,7 @@ void BLEAdvertising::start() { int numServices = m_serviceUUIDs.size(); if (numServices > 0) { m_advData.service_uuid_len = 16*numServices; - m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; + m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; uint8_t* p = m_advData.p_service_uuid; for (int i=0; i ESP_BLE_ADV_DATA_LEN_MAX) { + return; + } + m_payload.append(data); +} // addData + + + +/** + * @brief Set the complete services. + * @param [in] uuid The single service to advertise. + */ +void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { + char cdata[2]; + switch(uuid.bitSize()) { + case 16: { + // [Len] [0x02] [LL] [HH] + cdata[0] = 3; + cdata[1] = ESP_BLE_AD_TYPE_16SRV_CMPL; + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); + break; + } + + case 32: { + // [Len] [0x04] [LL] [LL] [HH] [HH] + cdata[0] = 5; + cdata[1] = ESP_BLE_AD_TYPE_32SRV_CMPL; + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); + break; + } + + case 128: { + // [Len] [0x04] [0] [1] ... [15] + cdata[0] = 17; + cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; + addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); + break; + } + + default: + return; + } +} // setCompleteServices + + +/** + * @brief Set the advertisement flags. + * @param [in] The flags to be set in the advertisement. + * + * * ESP_BLE_ADV_FLAG_LIMIT_DISC + * * ESP_BLE_ADV_FLAG_GEN_DISC + * * ESP_BLE_ADV_FLAG_BREDR_NOT_SPT + * * ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT + * * ESP_BLE_ADV_FLAG_DMT_HOST_SPT + * * ESP_BLE_ADV_FLAG_NON_LIMIT_DISC + */ +void BLEAdvertisementData::setFlags(uint8_t flag) { + char cdata[3]; + cdata[0] = 2; + cdata[1] = ESP_BLE_AD_TYPE_FLAG; + cdata[2] = flag; + addData(std::string(cdata, 3)); +} // setFlag + + +/** + * @brief Set the name. + * @param [in] The complete name of the device. + */ +void BLEAdvertisementData::setName(std::string name) { + ESP_LOGD("BLEAdvertisementData", ">> setName: %s", name.c_str()); + char cdata[2]; + cdata[0] = name.length() + 1; + cdata[1] = ESP_BLE_AD_TYPE_NAME_CMPL; + addData(std::string(cdata, 2) + name); + ESP_LOGD("BLEAdvertisementData", "<< setName"); +} // setName + + +/** + * @brief Set the partial services. + * @param [in] uuid The single service to advertise. + */ +void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { + char cdata[2]; + switch(uuid.bitSize()) { + case 16: { + // [Len] [0x02] [LL] [HH] + cdata[0] = 3; + cdata[1] = ESP_BLE_AD_TYPE_16SRV_PART; + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); + break; + } + + case 32: { + // [Len] [0x04] [LL] [LL] [HH] [HH] + cdata[0] = 5; + cdata[1] = ESP_BLE_AD_TYPE_32SRV_PART; + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); + break; + } + + case 128: { + // [Len] [0x04] [0] [1] ... [15] + cdata[0] = 17; + cdata[1] = ESP_BLE_AD_TYPE_128SRV_PART; + addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); + break; + } + + default: + return; + } +} // setPartialServices + + +/** + * @brief Set the short name. + * @param [in] The short name of the device. + */ +void BLEAdvertisementData::setShortName(std::string name) { + ESP_LOGD("BLEAdvertisementData", ">> setShortName: %s", name.c_str()); + char cdata[2]; + cdata[0] = name.length() + 1; + cdata[1] = ESP_BLE_AD_TYPE_NAME_SHORT; + addData(std::string(cdata, 2) + name); + ESP_LOGD("BLEAdvertisementData", "<< setShortName"); +} // setShortName + + +/** + * @brief Retrieve the payload that is to be advertised. + * @return The payload that is to be advertised. + */ +std::string BLEAdvertisementData::getPayload() { + return m_payload; +} // getPayload + + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 6f315b8c..40a87f92 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -13,6 +13,25 @@ #include "BLEUUID.h" #include + +/** + * @brief Advertisement data set by the programmer to be published by the %BLE server. + */ +class BLEAdvertisementData { +public: + void setCompleteServices(BLEUUID uuid); + void setFlags(uint8_t); + void setName(std::string name); + void setPartialServices(BLEUUID uuid); + void setShortName(std::string name); +private: + friend class BLEAdvertising; + std::string m_payload; // The payload of the advertisement. + void addData(std::string data); // Add data to the payload. + std::string getPayload(); +}; // BLEAdvertisementData + + /** * @brief Perform and manage %BLE advertising. * @@ -26,10 +45,14 @@ class BLEAdvertising { void start(); void stop(); void setAppearance(uint16_t appearance); + void setAdvertisementData(BLEAdvertisementData& advertisementData); + void setScanResponseData(BLEAdvertisementData& advertisementData); private: esp_ble_adv_data_t m_advData; esp_ble_adv_params_t m_advParams; std::vector m_serviceUUIDs; + bool m_customAdvData; // Are we using custom advertising data? + bool m_customScanResponseData; // Are we using custom scan response data? }; #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */ diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index f5c6ceee..4fb48c37 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -188,6 +188,32 @@ BLEUUID::BLEUUID() { } // BLEUUID +/** + * @brief Get the number of bits in this uuid. + * @return The number of bits in the UUID. One of 16, 32 or 128. + */ +int BLEUUID::bitSize() { + if (m_valueSet == false) { + return 0; + } + switch(m_uuid.len) { + case ESP_UUID_LEN_16: { + return 16; + } + case ESP_UUID_LEN_32: { + return 32; + } + case ESP_UUID_LEN_128: { + return 128; + } + default: { + ESP_LOGE(LOG_TAG, "Unknown UUID length: %d", m_uuid.len); + return 0; + } + } // End of switch +} // bitSize + + /** * @brief Compare a UUID against this UUID. * diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index bbe9b872..3089bcb1 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -24,6 +24,7 @@ class BLEUUID { BLEUUID(uint8_t* pData, size_t size, bool msbFirst); BLEUUID(esp_gatt_id_t gattId); BLEUUID(); + int bitSize(); // Get the number of bits in this uuid. bool equals(BLEUUID uuid); esp_bt_uuid_t* getNative(); BLEUUID to128(); diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 5d1dec99..deaf7d2f 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -1263,6 +1263,15 @@ void BLEUtils::dumpGapEvent( } // ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT + // + // ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT + // + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: { + ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_raw_cmpl.status); + break; + } // ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT + + // // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT // diff --git a/cpp_utils/DesignNotes/BLECPP.md b/cpp_utils/DesignNotes/BLECPP.md index db920dd3..1658f9fa 100644 --- a/cpp_utils/DesignNotes/BLECPP.md +++ b/cpp_utils/DesignNotes/BLECPP.md @@ -3,4 +3,59 @@ # BLE Server ## BLE Characteristic callbacks When a client connects to us as a BLE Server, it can change a characteristics value or read from a characteristic. The BLECharacteristicCallbacks class provides a handler for such. -# BLE Client \ No newline at end of file +# BLE Client +# Advertising +When we are being a BLE server we have the opportunity to perform advertising. Advertising means that we broadcast information about ourselves such that interested parties (presumably clients) can receive that information. The BLE advertising is described in the BLE specifications. + +Some key facts: + +* Advertisements can be sent periodically with the period being from 20ms to 10.24 seconds in steps of 0.625 ms. +* An advertisement payload can be up to 31 bytes in length. +* A payload can contain a sequence of records where a record is composed of a length, type and data. + +We can build a BLE advertising structure and specify that it should be sent for an advertisement using `esp_ble_gap_config_adv_data_raw` or sent using a scan response message using `esp_ble_gap_config_scan_rsp_data_raw`. + +Looking at the data structure for advertising we find it consists of records where each record is: + +* 1 byte length +* 1 byte record type +* length-1 bytes of data + +Trailing data (assuming we don't use all 31 bytes) shall be zeros. The advert types that include payload data are: + +* ADV_IND +* ADV\_NONCONN\_IND +* ADV\_SCAN\_IND + +The following advertising types are supported: + +| Type | Description | +|--------|------ +|0x01| Flags | +|0x02|Incomplete list of 16 bit service UUIDs +|0x03|Complete list of 16 bit service UUIDs +|0x04|Incomplete list of 32 bit service UUIDs +|0x05|Complete list of 32 bit UUIDs +|0x06|Incomplete list of 128 bit service UUIDs +|0x07|Complete list of 128 bit service UUIDs +|0x08|Shortened local name +|0x09|Complete local name + + + +See also: + +* BLE Spec 4.2 Vol 3 Part C Chapter 11 - Advertising and Scan response data format. +* [Advertisement record types](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile) +* [A BLE Advertising Primer](http://www.argenox.com/a-ble-advertising-primer/) + +## Exposed functions +|Type|Function +|-|- +|0x01|`setFlags(uint8_t flag)` +|0x02, 0x04, 0x06|`setPartialServices(vector)` +|0x02, 0x04, 0x06|`setPartialServices(BLEUUID`) +|0x03, 0x05, 0x07|`setCompleteServices(vector)` +|0x03, 0x05, 0x07|`setCompleteServices(BLEUUID`) +|0x08|`setShortName(std::string)` +|0x09|`setName(std::string)` \ No newline at end of file From 32e0ee33db4543ae321ad3db8c5742c62e7c11e1 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 26 Nov 2017 22:40:57 -0600 Subject: [PATCH 158/381] More code for #196 --- cpp_utils/BLEAdvertising.cpp | 30 ++++++++++++++++++++++++++++++ cpp_utils/BLEAdvertising.h | 2 ++ cpp_utils/DesignNotes/BLECPP.md | 10 +++++++--- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index df4d220b..7406819b 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -219,6 +219,20 @@ void BLEAdvertisementData::addData(std::string data) { } // addData +/** + * @brief Set the appearance. + * @param [in] appearance The appearance code value. + * + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml + */ +void BLEAdvertisementData::setAppearance(uint16_t appearance) { + char cdata[2]; + cdata[0] = 3; + cdata[1] = ESP_BLE_AD_TYPE_APPEARANCE; + addData(std::string(cdata, 2) + std::string((char *)&appearance,2)); +} // setAppearance + /** * @brief Set the complete services. @@ -277,6 +291,21 @@ void BLEAdvertisementData::setFlags(uint8_t flag) { } // setFlag + +/** + * @brief Set manufacturer specific data. + * @param [in] data Manufacturer data. + */ +void BLEAdvertisementData::setManufacturerData(std::string data) { + ESP_LOGD("BLEAdvertisementData", ">> setManufacturerData"); + char cdata[2]; + cdata[0] = data.length() + 1; + cdata[1] = ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE; + addData(std::string(cdata, 2) + data); + ESP_LOGD("BLEAdvertisementData", "<< setManufacturerData"); +} // setManufacturerData + + /** * @brief Set the name. * @param [in] The complete name of the device. @@ -342,6 +371,7 @@ void BLEAdvertisementData::setShortName(std::string name) { } // setShortName + /** * @brief Retrieve the payload that is to be advertised. * @return The payload that is to be advertised. diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 40a87f92..5c452a1a 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -19,8 +19,10 @@ */ class BLEAdvertisementData { public: + void setAppearance(uint16_t appearance); void setCompleteServices(BLEUUID uuid); void setFlags(uint8_t); + void setManufacturerData(std::string data); void setName(std::string name); void setPartialServices(BLEUUID uuid); void setShortName(std::string name); diff --git a/cpp_utils/DesignNotes/BLECPP.md b/cpp_utils/DesignNotes/BLECPP.md index 1658f9fa..9ce9715a 100644 --- a/cpp_utils/DesignNotes/BLECPP.md +++ b/cpp_utils/DesignNotes/BLECPP.md @@ -40,6 +40,8 @@ The following advertising types are supported: |0x07|Complete list of 128 bit service UUIDs |0x08|Shortened local name |0x09|Complete local name +|0x19|Appearance +|0xFF|Manufacturer data @@ -54,8 +56,10 @@ See also: |-|- |0x01|`setFlags(uint8_t flag)` |0x02, 0x04, 0x06|`setPartialServices(vector)` -|0x02, 0x04, 0x06|`setPartialServices(BLEUUID`) +|0x02, 0x04, 0x06|`setPartialServices(BLEUUID)` |0x03, 0x05, 0x07|`setCompleteServices(vector)` -|0x03, 0x05, 0x07|`setCompleteServices(BLEUUID`) +|0x03, 0x05, 0x07|`setCompleteServices(BLEUUID)` |0x08|`setShortName(std::string)` -|0x09|`setName(std::string)` \ No newline at end of file +|0x09|`setName(std::string)` +|0x19|`setAppearance(uint16_t)` +|0xFF|`setManufacturerData(std::string)` \ No newline at end of file From bf9d53e16c32a2ca54b7171e547ac56d6652b000 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 27 Nov 2017 17:02:38 -0600 Subject: [PATCH 159/381] Changes for #233 --- networking/mqtt/paho_mqtt_embedded_c/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/networking/mqtt/paho_mqtt_embedded_c/README.md b/networking/mqtt/paho_mqtt_embedded_c/README.md index c9b4906a..830981ea 100644 --- a/networking/mqtt/paho_mqtt_embedded_c/README.md +++ b/networking/mqtt/paho_mqtt_embedded_c/README.md @@ -35,3 +35,6 @@ Discussion of the Paho clients takes place on the [Eclipse paho-dev mailing list General questions about the MQTT protocol are discussed in the [MQTT Google Group](https://groups.google.com/forum/?hl=en-US&fromgroups#!forum/mqtt). There is much more information available via the [MQTT community site](http://mqtt.org). + +# ESP32 Specific +The compilation requires that we enable mbedTLS debugging features. Run "make menuconfig" and visit `Component Config -> mbedTLS` and check the `Enable mbedTLS debugging` section. Save and rebuild the environment. From 56790e1b1b05fb0f4c1944ff49e63a11e55d08b8 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 27 Nov 2017 22:40:58 -0600 Subject: [PATCH 160/381] Updates for #228 --- cpp_utils/BLEClient.cpp | 24 +++++++- cpp_utils/BLEClient.h | 3 + cpp_utils/BLEExceptions.cpp | 9 +++ cpp_utils/BLEExceptions.h | 25 ++++++++ cpp_utils/BLERemoteCharacteristic.cpp | 13 ++++ cpp_utils/BLERemoteDescriptor.cpp | 25 +++++++- cpp_utils/BLERemoteDescriptor.h | 2 +- cpp_utils/BLERemoteService.cpp | 86 ++++++--------------------- cpp_utils/BLERemoteService.h | 4 +- cpp_utils/BLEService.h | 3 +- cpp_utils/BLEUtils.cpp | 53 ++++++++++++----- cpp_utils/Makefile.arduino | 2 + 12 files changed, 160 insertions(+), 89 deletions(-) create mode 100644 cpp_utils/BLEExceptions.cpp create mode 100644 cpp_utils/BLEExceptions.h diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 83e778dc..57999006 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -66,6 +66,21 @@ BLEClient::~BLEClient() { } // ~BLEClient +/** + * @brief Clear any existing services. + * + */ +void BLEClient::clearServices() { + ESP_LOGD(LOG_TAG, ">> clearServices"); + // Delete all the services. + for (auto &myPair : m_servicesMap) { + delete myPair.second; + } + m_servicesMap.clear(); + ESP_LOGD(LOG_TAG, "<< clearServices"); +} // clearServices + + /** * @brief Connect to the partner (BLE Server). * @param [in] address The address of the partner. @@ -78,6 +93,8 @@ bool BLEClient::connect(BLEAddress address) { // and then block on its completion. When the event has arrived, we will have the handle. m_semaphoreRegEvt.take("connect"); + clearServices(); // Delete any services that may exist. + esp_err_t errRc = ::esp_ble_gattc_app_register(0); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -144,6 +161,9 @@ void BLEClient::gattClientEventHandler( case ESP_GATTC_DISCONNECT_EVT: { // If we receive a disconnect event, set the class flag that indicates that we are // no longer connected. + if (m_pClientCallbacks != nullptr) { + m_pClientCallbacks->onDisconnect(this); + } m_isConnected = false; break; } // ESP_GATTC_DISCONNECT_EVT @@ -332,7 +352,9 @@ std::map* BLEClient::getServices() { * and will culminate with an ESP_GATTC_SEARCH_CMPL_EVT when all have been received. */ ESP_LOGD(LOG_TAG, ">> getServices"); - m_servicesMap.clear(); + + clearServices(); // Clear any services that may exist. + esp_err_t errRc = esp_ble_gattc_search_service( getGattcIf(), getConnId(), diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index b24c71ae..9ff5f90b 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -15,6 +15,7 @@ #include #include #include +#include "BLEExceptions.h" #include "BLERemoteService.h" #include "BLEService.h" #include "BLEAddress.h" @@ -68,6 +69,7 @@ class BLEClient { FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt"); FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt"); std::map m_servicesMap; + void clearServices(); // Clear any existing services. bool m_haveServices; // Have we previously obtain the set of services. }; // class BLEDevice @@ -79,6 +81,7 @@ class BLEClientCallbacks { public: virtual ~BLEClientCallbacks() {}; virtual void onConnect(BLEClient *pClient) = 0; + virtual void onDisconnect(BLEClient *pClient) = 0; }; #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEExceptions.cpp b/cpp_utils/BLEExceptions.cpp new file mode 100644 index 00000000..b6adfd82 --- /dev/null +++ b/cpp_utils/BLEExceptions.cpp @@ -0,0 +1,9 @@ +/* + * BLExceptions.cpp + * + * Created on: Nov 27, 2017 + * Author: kolban + */ + +#include "BLEExceptions.h" + diff --git a/cpp_utils/BLEExceptions.h b/cpp_utils/BLEExceptions.h new file mode 100644 index 00000000..d03ec6eb --- /dev/null +++ b/cpp_utils/BLEExceptions.h @@ -0,0 +1,25 @@ +/* + * BLExceptions.h + * + * Created on: Nov 27, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEEXCEPTIONS_H_ +#define COMPONENTS_CPP_UTILS_BLEEXCEPTIONS_H_ +#include "sdkconfig.h" + +#if CONFIG_CXX_EXCEPTIONS != 1 +#error "C++ exception handling must be enabled within make menuconfig. See Compiler Options > Enable C++ Exceptions." +#endif + +#include + + +class BLEDisconnectedException : public std::exception { + const char *what() const throw () { + return "BLE Disconnected"; + } +}; + +#endif /* COMPONENTS_CPP_UTILS_BLEEXCEPTIONS_H_ */ diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 64b0a92d..d9c64c91 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -15,6 +15,7 @@ #include #include +#include "BLEExceptions.h" #include "BLEUtils.h" #include "GeneralUtils.h" #include "BLERemoteDescriptor.h" @@ -432,6 +433,12 @@ uint8_t BLERemoteCharacteristic::readUInt8(void) { std::string BLERemoteCharacteristic::readValue() { ESP_LOGD(LOG_TAG, ">> readValue(): uuid: %s, handle: %d 0x%.2x", getUUID().toString().c_str(), getHandle(), getHandle()); + // Check to see that we are connected. + if (!getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + m_semaphoreReadCharEvt.take("readValue"); // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. @@ -543,6 +550,12 @@ std::string BLERemoteCharacteristic::toString() { void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { ESP_LOGD(LOG_TAG, ">> writeValue(), length: %d", newValue.length()); + // Check to see that we are connected. + if (!getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + m_semaphoreWriteCharEvt.take("writeValue"); // Invoke the ESP-IDF API to perform the write. diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index 7a509f86..4d14ba80 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -27,6 +27,7 @@ BLERemoteDescriptor::BLERemoteDescriptor( m_pRemoteCharacteristic = pRemoteCharacteristic; } + /** * @brief Retrieve the handle associated with this remote descriptor. * @return The handle associated with this remote descriptor. @@ -36,6 +37,15 @@ uint16_t BLERemoteDescriptor::getHandle() { } // getHandle +/** + * @brief Get the characteristic that owns this descriptor. + * @return The characteristic that owns this descriptor. + */ +BLERemoteCharacteristic* BLERemoteDescriptor::getRemoteCharacteristic() { + return m_pRemoteCharacteristic; +} // getRemoteCharacteristic + + /** * @brief Retrieve the UUID associated this remote descriptor. * @return The UUID associated this remote descriptor. @@ -46,6 +56,14 @@ BLEUUID BLERemoteDescriptor::getUUID() { std::string BLERemoteDescriptor::readValue(void) { + ESP_LOGD(LOG_TAG, ">> readValue: %s", toString().c_str()); + + // Check to see that we are connected. + if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + m_semaphoreReadDescrEvt.take("readValue"); // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. @@ -66,7 +84,6 @@ std::string BLERemoteDescriptor::readValue(void) { ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length()); return m_value; - return ""; } // readValue @@ -119,6 +136,12 @@ void BLERemoteDescriptor::writeValue( size_t length, bool response) { ESP_LOGD(LOG_TAG, ">> writeValue: %s", toString().c_str()); + // Check to see that we are connected. + if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + esp_err_t errRc = ::esp_ble_gattc_write_char_descr( m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), diff --git a/cpp_utils/BLERemoteDescriptor.h b/cpp_utils/BLERemoteDescriptor.h index c2cf3836..7bbc48f1 100644 --- a/cpp_utils/BLERemoteDescriptor.h +++ b/cpp_utils/BLERemoteDescriptor.h @@ -24,13 +24,13 @@ class BLERemoteCharacteristic; class BLERemoteDescriptor { public: uint16_t getHandle(); + BLERemoteCharacteristic* getRemoteCharacteristic(); BLEUUID getUUID(); std::string readValue(void); uint8_t readUInt8(void); uint16_t readUInt16(void); uint32_t readUInt32(void); std::string toString(void); - //void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)); void writeValue(uint8_t* data, size_t length, bool response = false); void writeValue(std::string newValue, bool response = false); void writeValue(uint8_t newValue, bool response = false); diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index 5227964e..1ad12759 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -115,7 +115,7 @@ void BLERemoteService::gattClientEventHandler( // Send the event to each of the characteristics owned by this service. for (auto &myPair : m_characteristicMap) { - myPair.first->gattClientEventHandler(event, gattc_if, evtParam); + myPair.second->gattClientEventHandler(event, gattc_if, evtParam); } } // gattClientEventHandler @@ -147,8 +147,8 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { } std::string v = uuid.toString(); for (auto &myPair : m_characteristicMap) { - if (myPair.second == v) { - return myPair.first; + if (myPair.first == v) { + return myPair.second; } } return nullptr; @@ -165,60 +165,6 @@ void BLERemoteService::retrieveCharacteristics() { ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); removeCharacteristics(); // Forget any previous characteristics. - /* - m_semaphoreGetCharEvt.take("getCharacteristics"); - - esp_err_t errRc = ::esp_ble_gattc_get_characteristic( - m_pClient->getGattcIf(), - m_pClient->getConnId(), - &m_srvcId, - nullptr); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - m_semaphoreGetCharEvt.wait("getCharacteristics"); // Wait for the characteristics to become available. - - m_haveCharacteristics = true; // Remember that we have received the characteristics. - */ - //ESP_LOGE(LOG_TAG, "!!! NOT IMPLEMENTED !!!"); - //ESP_LOGD(LOG_TAG, "--- test code ---"); - /* - uint16_t count; - esp_gatt_status_t status = ::esp_ble_gattc_get_attr_count( - getClient()->getGattcIf(), - getClient()->getConnId(), - ESP_GATT_DB_CHARACTERISTIC, - m_startHandle, - m_endHandle, - 0, // Characteristic handle ... only used for ESP_GATT_DB_DESCRIPTOR - &count - ); - if (status != ESP_GATT_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_attr_count: %s", BLEUtils::gattStatusToString(status).c_str()); - } else { - ESP_LOGD(LOG_TAG, "Number of characteristics associated with service is %d", count); - } - - count = 1; - esp_gattc_service_elem_t srvcElem; - status = ::esp_ble_gattc_get_service( - getClient()->getGattcIf(), - getClient()->getConnId(), - &m_srvcId.uuid, // UUID of service - &srvcElem, // Records - &count, // records retrieved - 0 // offset - ); - if (status != ESP_GATT_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_service: %s", BLEUtils::gattStatusToString(status).c_str()); - } - else { - ESP_LOGD(LOG_TAG, "%s", BLEUtils::gattcServiceElementToString(&srvcElem).c_str()); - } - */ uint16_t offset = 0; esp_gattc_char_elem_t result; @@ -257,7 +203,7 @@ void BLERemoteService::retrieveCharacteristics() { this ); - m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic, pNewRemoteCharacteristic->getUUID().toString())); + m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic->getUUID().toString(), pNewRemoteCharacteristic)); offset++; // Increment our count of number of descriptors found. } // Loop forever (until we break inside the loop). @@ -271,7 +217,7 @@ void BLERemoteService::retrieveCharacteristics() { * @brief Retrieve a map of all the characteristics of this service. * @return A map of all the characteristics of this service. */ -std::map* BLERemoteService::getCharacteristics() { +std::map * BLERemoteService::getCharacteristics() { ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); // If is possible that we have not read the characteristics associated with the service so do that // now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking @@ -284,31 +230,35 @@ std::map* BLERemoteService::getCharacteri } // getCharacteristics +/** + * @brief Get the client associated with this service. + * @return A reference to the client associated with this service. + */ BLEClient* BLERemoteService::getClient() { return m_pClient; -} +} // getClient uint16_t BLERemoteService::getEndHandle() { return m_endHandle; -} +} // getEndHandle esp_gatt_id_t* BLERemoteService::getSrvcId() { return &m_srvcId; -} +} // getSrvcId uint16_t BLERemoteService::getStartHandle() { return m_startHandle; -} +} // getStartHandle + uint16_t BLERemoteService::getHandle() { ESP_LOGD(LOG_TAG, ">> getHandle: service: %s", getUUID().toString().c_str()); - //ESP_LOGE(LOG_TAG, "!!! getHandle: NOT IMPLEMENTED !!!"); ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", getStartHandle(), getStartHandle()); return getStartHandle(); -} +} // getHandle BLEUUID BLERemoteService::getUUID() { @@ -325,8 +275,8 @@ BLEUUID BLERemoteService::getUUID() { */ void BLERemoteService::removeCharacteristics() { for (auto &myPair : m_characteristicMap) { - delete myPair.first; - m_characteristicMap.erase(myPair.first); + delete myPair.second; + //m_characteristicMap.erase(myPair.first); // Should be no need to delete as it will be deleted by the clear } m_characteristicMap.clear(); // Clear the map } // removeCharacteristics @@ -343,7 +293,7 @@ std::string BLERemoteService::toString() { ss << ", start_handle: " << std::dec << m_startHandle << " 0x" << std::hex << m_startHandle << ", end_handle: " << std::dec << m_endHandle << " 0x" << std::hex << m_endHandle; for (auto &myPair : m_characteristicMap) { - ss << "\n" << myPair.first->toString(); + ss << "\n" << myPair.second->toString(); // myPair.second is the value } return ss.str(); diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 521effce..7f8c2f4a 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -33,7 +33,7 @@ class BLERemoteService { BLERemoteCharacteristic* getCharacteristic(const char* uuid); BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); BLERemoteCharacteristic* getCharacteristic(uint16_t uuid); - std::map* getCharacteristics(); + std::map* getCharacteristics(); void getCharacteristics(std::map* ptr); BLEClient* getClient(void); @@ -63,7 +63,7 @@ class BLERemoteService { // Properties // We maintain a map of characteristics owned by this service keyed by a string representation of the UUID. - std::map m_characteristicMap; + std::map m_characteristicMap; // We maintain a map of characteristics owned by this service keyed by a handle. std::map m_characteristicMapByHandle; diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index b37092f2..fdba54fd 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -80,8 +80,7 @@ class BLEService { BLECharacteristic* m_lastCreatedCharacteristic; BLEServer* m_pServer; BLEUUID m_uuid; - char deleteMe[10]; - //FreeRTOS::Semaphore m_serializeMutex; + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index deaf7d2f..d8d318f2 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -838,28 +838,52 @@ std::string BLEUtils::buildPrintData(uint8_t* source, size_t length) { } // buildPrintData +/** + * @brief Convert a close/disconnect reason to a string. + * @param [in] reason The close reason. + * @return A string representation of the reason. + */ std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) { switch(reason) { - case ESP_GATT_CONN_UNKNOWN: + case ESP_GATT_CONN_UNKNOWN: { return "ESP_GATT_CONN_UNKNOWN"; - case ESP_GATT_CONN_L2C_FAILURE: + } + + case ESP_GATT_CONN_L2C_FAILURE: { return "ESP_GATT_CONN_L2C_FAILURE"; - case ESP_GATT_CONN_TIMEOUT: + } + + case ESP_GATT_CONN_TIMEOUT: { return "ESP_GATT_CONN_TIMEOUT"; - case ESP_GATT_CONN_TERMINATE_PEER_USER: + } + + case ESP_GATT_CONN_TERMINATE_PEER_USER: { return "ESP_GATT_CONN_TERMINATE_PEER_USER"; - case ESP_GATT_CONN_TERMINATE_LOCAL_HOST: + } + + case ESP_GATT_CONN_TERMINATE_LOCAL_HOST: { return "ESP_GATT_CONN_TERMINATE_LOCAL_HOST"; - case ESP_GATT_CONN_FAIL_ESTABLISH: + } + + case ESP_GATT_CONN_FAIL_ESTABLISH: { return "ESP_GATT_CONN_FAIL_ESTABLISH"; - case ESP_GATT_CONN_LMP_TIMEOUT: + } + + case ESP_GATT_CONN_LMP_TIMEOUT: { return "ESP_GATT_CONN_LMP_TIMEOUT"; - case ESP_GATT_CONN_CONN_CANCEL: + } + + case ESP_GATT_CONN_CONN_CANCEL: { return "ESP_GATT_CONN_CONN_CANCEL"; - case ESP_GATT_CONN_NONE: + } + + case ESP_GATT_CONN_NONE: { return "ESP_GATT_CONN_NONE"; - default: + } + + default: { return "Unknown"; + } } } // gattCloseReasonToString @@ -1388,11 +1412,12 @@ void BLEUtils::dumpGattClientEvent( // ESP_GATTC_DISCONNECT_EVT // // disconnect: - // - esp_gatt_status_t status - // - uint16_t conn_id - // - esp_bd_addr_t remote_bda + // - esp_gatt_conn_reason_t reason + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda case ESP_GATTC_DISCONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s]", + ESP_LOGD(LOG_TAG, "[reason: %s, conn_id: %d, remote_bda: %s]", + BLEUtils::gattCloseReasonToString(evtParam->disconnect.reason).c_str(), evtParam->disconnect.conn_id, BLEAddress(evtParam->disconnect.remote_bda).toString().c_str() ); diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index df7fe54f..f9239250 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -26,6 +26,8 @@ BLE_FILES= \ BLEDescriptorMap.cpp \ BLEDevice.cpp \ BLEDevice.h \ + BLEExceptions.cpp \ + BLEExceptions.h \ BLERemoteCharacteristic.cpp \ BLERemoteCharacteristic.h \ BLERemoteDescriptor.cpp \ From ea59ac2d67ce83d6db9f5946b5528001c1e2cb6d Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 29 Nov 2017 23:49:37 -0600 Subject: [PATCH 161/381] Updates for #238 --- cpp_utils/BLEDevice.cpp | 13 +++++++++++++ cpp_utils/BLEDevice.h | 1 + 2 files changed, 14 insertions(+) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index a701459b..77957c60 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -12,6 +12,7 @@ #include #include #include // ESP32 BLE +#include // ESP32 BLE #include // ESP32 BLE #include // ESP32 BLE #include // ESP32 BLE @@ -157,6 +158,18 @@ void BLEDevice::gapEventHandler( } // gapEventHandler +/** + * @brief Get the BLE device address. + * @return The BLE device address. + */ +/* STATIC*/ BLEAddress BLEDevice::getAddress() { + const uint8_t* bdAddr = esp_bt_dev_get_address(); + esp_bd_addr_t addr; + memcpy(addr, bdAddr, sizeof(addr)); + return BLEAddress(addr); +} // getAddress + + /** * @brief Retrieve the Scan object that we use for scanning. * @return The scanning object reference. This is a singleton object. The caller should not diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index acf20f35..de0dfeea 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -29,6 +29,7 @@ class BLEDevice { static BLEClient* createClient(); static BLEServer* createServer(); static void dumpDevices(); + static BLEAddress getAddress(); static BLEScan* getScan(); static void init(std::string deviceName); From e6c816ebcb018745664d90c680edb131b323c2c1 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 2 Dec 2017 10:20:49 -0600 Subject: [PATCH 162/381] Changes for #241 --- cpp_utils/OV7670.cpp | 2 +- cpp_utils/PWM.cpp | 2 +- cpp_utils/PWM.h | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpp_utils/OV7670.cpp b/cpp_utils/OV7670.cpp index 9e4fa7a1..c2de1316 100644 --- a/cpp_utils/OV7670.cpp +++ b/cpp_utils/OV7670.cpp @@ -189,7 +189,7 @@ static esp_err_t camera_enable_out_clock(camera_config_t* config) periph_module_enable(PERIPH_LEDC_MODULE); ledc_timer_config_t timer_conf; - timer_conf.bit_num = (ledc_timer_bit_t)1; + timer_conf.duty_resolution = (ledc_timer_bit_t)1; timer_conf.freq_hz = config->xclk_freq_hz; timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE; timer_conf.timer_num = config->ledc_timer; diff --git a/cpp_utils/PWM.cpp b/cpp_utils/PWM.cpp index 3155b42f..85e0b23f 100644 --- a/cpp_utils/PWM.cpp +++ b/cpp_utils/PWM.cpp @@ -33,7 +33,7 @@ */ PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t bitSize, ledc_timer_t timer, ledc_channel_t channel) { ledc_timer_config_t timer_conf; - timer_conf.bit_num = bitSize; + timer_conf.duty_resolution = bitSize; timer_conf.freq_hz = frequency; timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE; timer_conf.timer_num = timer; diff --git a/cpp_utils/PWM.h b/cpp_utils/PWM.h index fdc1fcf1..b8bfb317 100644 --- a/cpp_utils/PWM.h +++ b/cpp_utils/PWM.h @@ -22,10 +22,10 @@ class PWM { public: PWM( int gpioNum, - uint32_t frequency = 100, + uint32_t frequency = 100, ledc_timer_bit_t bitSize = LEDC_TIMER_10_BIT, - ledc_timer_t timer = LEDC_TIMER_0, - ledc_channel_t channel = LEDC_CHANNEL_0); + ledc_timer_t timer = LEDC_TIMER_0, + ledc_channel_t channel = LEDC_CHANNEL_0); uint32_t getDuty(); uint32_t getFrequency(); From 33c07ab5baccb876468235fd782152c6bef336d4 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 2 Dec 2017 15:16:14 -0600 Subject: [PATCH 163/381] Changes for #242 --- cpp_utils/BLEAdvertising.h | 10 ++- cpp_utils/BLECharacteristic.cpp | 1 - cpp_utils/BLEDevice.cpp | 34 ++++++-- cpp_utils/BLEDevice.h | 16 ++-- cpp_utils/BLEExceptions.h | 6 ++ cpp_utils/BLERemoteService.cpp | 30 ++++++- cpp_utils/BLERemoteService.h | 2 + cpp_utils/BLEServer.cpp | 145 ++++++++++++++------------------ cpp_utils/BLEServer.h | 2 +- cpp_utils/BLEUUID.cpp | 48 +++++++---- cpp_utils/BLEUUID.h | 2 +- cpp_utils/PWM.cpp | 4 +- 12 files changed, 174 insertions(+), 126 deletions(-) diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 5c452a1a..433de603 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -18,6 +18,9 @@ * @brief Advertisement data set by the programmer to be published by the %BLE server. */ class BLEAdvertisementData { + // Only a subset of the possible BLE architected advertisement fields are currently exposed. Others will + // be exposed on demand/request or as time permits. + // public: void setAppearance(uint16_t appearance); void setCompleteServices(BLEUUID uuid); @@ -26,11 +29,13 @@ class BLEAdvertisementData { void setName(std::string name); void setPartialServices(BLEUUID uuid); void setShortName(std::string name); + private: friend class BLEAdvertising; std::string m_payload; // The payload of the advertisement. - void addData(std::string data); // Add data to the payload. - std::string getPayload(); + + void addData(std::string data); // Add data to the payload. + std::string getPayload(); // Retrieve the current advert payload. }; // BLEAdvertisementData @@ -49,6 +54,7 @@ class BLEAdvertising { void setAppearance(uint16_t appearance); void setAdvertisementData(BLEAdvertisementData& advertisementData); void setScanResponseData(BLEAdvertisementData& advertisementData); + private: esp_ble_adv_data_t m_advData; esp_ble_adv_params_t m_advParams; diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 6d746cf2..4c0935a7 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -321,7 +321,6 @@ void BLECharacteristic::handleGATTServerEvent( // - bool need_rsp // case ESP_GATTS_READ_EVT: { - ESP_LOGD(LOG_TAG, "- Testing: 0x%.2x == 0x%.2x", param->read.handle, m_handle); if (param->read.handle == m_handle) { if (m_pCallbacks != nullptr) { diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 77957c60..e1ace955 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -40,12 +40,14 @@ static const char* LOG_TAG = "BLEDevice"; BLEServer* BLEDevice::m_pServer = nullptr; BLEScan* BLEDevice::m_pScan = nullptr; BLEClient* BLEDevice::m_pClient = nullptr; -bool initialized = false; +bool initialized = false; // Have we been initialized? + + /** * @brief Create a new instance of a client. * @return A new instance of the client. */ -BLEClient* BLEDevice::createClient() { +/* STATIC */ BLEClient* BLEDevice::createClient() { m_pClient = new BLEClient(); return m_pClient; } // createClient @@ -55,7 +57,7 @@ BLEClient* BLEDevice::createClient() { * @brief Create a new instance of a server. * @return A new instance of the server. */ -BLEServer* BLEDevice::createServer() { +/* STATIC */ BLEServer* BLEDevice::createServer() { ESP_LOGD(LOG_TAG, ">> createServer"); m_pServer = new BLEServer(); m_pServer->createApp(0); @@ -71,7 +73,7 @@ BLEServer* BLEDevice::createServer() { * @param [in] gatts_if The connection to the GATT interface. * @param [in] param Parameters for the event. */ -void BLEDevice::gattServerEventHandler( +/* STATIC */ void BLEDevice::gattServerEventHandler( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param @@ -97,7 +99,7 @@ void BLEDevice::gattServerEventHandler( * @param [in] gattc_if * @param [in] param */ -void BLEDevice::gattClientEventHandler( +/* STATIC */ void BLEDevice::gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param) { @@ -124,7 +126,7 @@ void BLEDevice::gattClientEventHandler( /** * @brief Handle GAP events. */ -void BLEDevice::gapEventHandler( +/* STATIC */ void BLEDevice::gapEventHandler( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { @@ -175,7 +177,7 @@ void BLEDevice::gapEventHandler( * @return The scanning object reference. This is a singleton object. The caller should not * try and release/delete it. */ -BLEScan* BLEDevice::getScan() { +/* STATIC */ BLEScan* BLEDevice::getScan() { //ESP_LOGD(LOG_TAG, ">> getScan"); if (m_pScan == nullptr) { m_pScan = new BLEScan(); @@ -190,9 +192,10 @@ BLEScan* BLEDevice::getScan() { * @brief Initialize the %BLE environment. * @param deviceName The device name of the device. */ -void BLEDevice::init(std::string deviceName) { +/* STATIC */ void BLEDevice::init(std::string deviceName) { if(!initialized){ - initialized = true; + initialized = true; // Set the initialization flag to ensure we are only initialized once. + esp_err_t errRc = ::nvs_flash_init(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -205,6 +208,7 @@ void BLEDevice::init(std::string deviceName) { ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + #ifndef CLASSIC_BT_ENABLED // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); @@ -220,6 +224,7 @@ void BLEDevice::init(std::string deviceName) { return; } #endif + errRc = esp_bluedroid_init(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -289,4 +294,15 @@ void BLEDevice::init(std::string deviceName) { ESP_LOGD(LOG_TAG, "<< setPower"); } // setPower + +/** + * @brief Return a string representation of the nature of this device. + * @return A string representation of the nature of this device. + */ +/* STATIC */ std::string BLEDevice::toString() { + std::ostringstream oss; + oss << "BD Address: " << getAddress().toString(); + return oss.str(); +} // toString + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index de0dfeea..09a2098f 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -26,12 +26,13 @@ class BLEDevice { public: - static BLEClient* createClient(); - static BLEServer* createServer(); - static void dumpDevices(); - static BLEAddress getAddress(); - static BLEScan* getScan(); - static void init(std::string deviceName); + static BLEClient* createClient(); + static BLEServer* createServer(); + static BLEAddress getAddress(); + static BLEScan* getScan(); + static void init(std::string deviceName); + static void setPower(esp_power_level_t powerLevel); + static std::string toString(); private: static BLEServer *m_pServer; @@ -54,9 +55,6 @@ class BLEDevice { esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); -public: - static void setPower(esp_power_level_t powerLevel); - }; // class BLE #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEExceptions.h b/cpp_utils/BLEExceptions.h index d03ec6eb..369fcaf6 100644 --- a/cpp_utils/BLEExceptions.h +++ b/cpp_utils/BLEExceptions.h @@ -22,4 +22,10 @@ class BLEDisconnectedException : public std::exception { } }; +class BLEUuidNotFoundException : public std::exception { + const char *what() const throw () { + return "No such UUID"; + } +}; + #endif /* COMPONENTS_CPP_UTILS_BLEEXCEPTIONS_H_ */ diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index 1ad12759..78ff9914 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -124,6 +124,7 @@ void BLERemoteService::gattClientEventHandler( * @brief Get the remote characteristic object for the characteristic UUID. * @param [in] uuid Remote characteristic uuid. * @return Reference to the remote characteristic object. + * @throws BLEUuidNotFoundException */ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) { return getCharacteristic(BLEUUID(uuid)); @@ -134,6 +135,7 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) { * @brief Get the characteristic object for the UUID. * @param [in] uuid Characteristic uuid. * @return Reference to the characteristic object. + * @throws BLEUuidNotFoundException */ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { // Design @@ -151,7 +153,7 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { return myPair.second; } } - return nullptr; + throw new BLEUuidNotFoundException(); } // getCharacteristic @@ -265,6 +267,17 @@ BLEUUID BLERemoteService::getUUID() { return m_uuid; } +/** + * @brief Read the value of a characteristic associated with this service. + */ +std::string BLERemoteService::getValue(BLEUUID characteristicUuid) { + ESP_LOGD(LOG_TAG, ">> readValue: uuid: %s", characteristicUuid.toString().c_str()); + std::string ret = getCharacteristic(characteristicUuid)->readValue(); + ESP_LOGD(LOG_TAG, "<< readValue"); + return ret; +} // readValue + + /** * @brief Delete the characteristics in the characteristics map. @@ -282,6 +295,18 @@ void BLERemoteService::removeCharacteristics() { } // removeCharacteristics +/** + * @brief Set the value of a characteristic. + * @param [in] characteristicUuid The characteristic to set. + * @param [in] value The value to set. + * @throws BLEUuidNotFound + */ +void BLERemoteService::setValue(BLEUUID characteristicUuid, std::string value) { + ESP_LOGD(LOG_TAG, ">> setValue: uuid: %s", characteristicUuid.toString().c_str()); + getCharacteristic(characteristicUuid)->writeValue(value); + ESP_LOGD(LOG_TAG, "<< setValue"); +} // setValue + /** * @brief Create a string representation of this remote service. @@ -300,7 +325,4 @@ std::string BLERemoteService::toString() { } // toString - - - #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 7f8c2f4a..b60e4fb3 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -39,6 +39,8 @@ class BLERemoteService { BLEClient* getClient(void); uint16_t getHandle(); BLEUUID getUUID(void); + std::string getValue(BLEUUID characteristicUuid); + void setValue(BLEUUID characteristicUuid, std::string value); std::string toString(void); private: diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 0d9bd6b5..448c8031 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -178,108 +178,114 @@ void BLEServer::handleGATTServerEvent( m_serviceMap.handleGATTServerEvent(event, gatts_if, param); switch(event) { + // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. + // add_char: + // - esp_gatt_status_t status + // - uint16_t attr_handle + // - uint16_t service_handle + // - esp_bt_uuid_t char_uuid + // + case ESP_GATTS_ADD_CHAR_EVT: { + break; + } // ESP_GATTS_ADD_CHAR_EVT // ESP_GATTS_CONNECT_EVT // connect: - // - uint16_t conn_id + // - uint16_t conn_id // - esp_bd_addr_t remote_bda - // - bool is_connected + // - bool is_connected + // case ESP_GATTS_CONNECT_EVT: { m_connId = param->connect.conn_id; // Save the connection id. if (m_pServerCallbacks != nullptr) { m_pServerCallbacks->onConnect(this); } - m_connectedCount++; + m_connectedCount++; // Increment the number of connected devices count. break; } // ESP_GATTS_CONNECT_EVT - // ESP_GATTS_REG_EVT - // reg: - // - esp_gatt_status_t status - // - uint16_t app_id - case ESP_GATTS_REG_EVT: { - m_gatts_if = gatts_if; - m_semaphoreRegisterAppEvt.give(); // Unlock the mutex waiting for the registration of the app. - break; - } // ESP_GATTS_REG_EVT - - // ESP_GATTS_CREATE_EVT // Called when a new service is registered as having been created. // // create: - // * esp_gatt_status_t status - // * uint16_t service_handle + // * esp_gatt_status_t status + // * uint16_t service_handle // * esp_gatt_srvc_id_t service_id // case ESP_GATTS_CREATE_EVT: { BLEService* pService = m_serviceMap.getByUUID(param->create.service_id.id.uuid); m_serviceMap.setByHandle(param->create.service_handle, pService); - //pService->setHandle(param->create.service_handle); m_semaphoreCreateEvt.give(); break; } // ESP_GATTS_CREATE_EVT + // ESP_GATTS_DISCONNECT_EVT + // + // disconnect + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + // - bool is_connected + // + // If we receive a disconnect event then invoke the callback for disconnects (if one is present). + // we also want to start advertising again. + case ESP_GATTS_DISCONNECT_EVT: { + m_connectedCount--; // Decrement the number of connected devices count. + if (m_pServerCallbacks != nullptr) { // If we have callbacks, call now. + m_pServerCallbacks->onDisconnect(this); + } + startAdvertising(); + break; + } // ESP_GATTS_DISCONNECT_EVT + + // ESP_GATTS_READ_EVT - A request to read the value of a characteristic has arrived. // // read: - // - uint16_t conn_id - // - uint32_t trans_id + // - uint16_t conn_id + // - uint32_t trans_id // - esp_bd_addr_t bda - // - uint16_t handle - // - uint16_t offset - // - bool is_long - // - bool need_rsp + // - uint16_t handle + // - uint16_t offset + // - bool is_long + // - bool need_rsp // case ESP_GATTS_READ_EVT: { break; } // ESP_GATTS_READ_EVT + // ESP_GATTS_REG_EVT + // reg: + // - esp_gatt_status_t status + // - uint16_t app_id + // + case ESP_GATTS_REG_EVT: { + m_gatts_if = gatts_if; + m_semaphoreRegisterAppEvt.give(); // Unlock the mutex waiting for the registration of the app. + break; + } // ESP_GATTS_REG_EVT + + // ESP_GATTS_WRITE_EVT - A request to write the value of a characteristic has arrived. // // write: - // - uint16_t conn_id - // - uint16_t trans_id + // - uint16_t conn_id + // - uint16_t trans_id // - esp_bd_addr_t bda - // - uint16_t handle - // - uint16_t offset - // - bool need_rsp - // - bool is_prep - // - uint16_t len - // - uint8_t *value - + // - uint16_t handle + // - uint16_t offset + // - bool need_rsp + // - bool is_prep + // - uint16_t len + // - uint8_t* value + // case ESP_GATTS_WRITE_EVT: { break; } - // ESP_GATTS_DISCONNECT_EVT - // If we receive a disconnect event then invoke the callback for disconnects (if one is present). - // we also want to start advertising again. - case ESP_GATTS_DISCONNECT_EVT: { - m_connectedCount--; - if (m_pServerCallbacks != nullptr) { - m_pServerCallbacks->onDisconnect(this); - } - startAdvertising(); - break; - } // ESP_GATTS_DISCONNECT_EVT - - - // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. - // add_char: - // - esp_gatt_status_t status - // - uint16_t attr_handle - // - uint16_t service_handle - // - esp_bt_uuid_t char_uuid - case ESP_GATTS_ADD_CHAR_EVT: { - break; - } // ESP_GATTS_ADD_CHAR_EVT - - default: { break; } @@ -303,7 +309,7 @@ void BLEServer::registerApp() { /** - * @brief Set the callbacks. + * @brief Set the server callbacks. * * As a %BLE server operates, it will generate server level events such as a new client connecting or a previous client * disconnecting. This function can be called to register a callback handler that will be invoked when these @@ -329,36 +335,15 @@ void BLEServer::startAdvertising() { } // startAdvertising -/* -void BLEServer::addCharacteristic(BLECharacteristic *characteristic, BLEService *pService) { - ESP_LOGD(tag, "Adding characteristic (esp_ble_gatts_add_char): uuid=%s, serviceHandle=0x%.2x", - characteristic->m_bleUUID.toString().c_str(), - pService->getHandle()); - - m_characteristicMap.setByUUID(characteristic->m_bleUUID, characteristic); - - esp_err_t errRc = ::esp_ble_gatts_add_char( - pService->getHandle(), - characteristic->getUUID().getNative(), - (esp_gatt_perm_t)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), - characteristic->getProperties(), - &characteristic->m_value, - NULL); - - if (errRc != ESP_OK) { - ESP_LOGE(tag, "esp_ble_gatts_add_char: rc=%d %s", errRc, espToString(errRc)); - return; - } -} -*/ - void BLEServerCallbacks::onConnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); + ESP_LOGD("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); } // onConnect void BLEServerCallbacks::onDisconnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", ">> onDisconnect(): Default"); + ESP_LOGD("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); ESP_LOGD("BLEServerCallbacks", "<< onDisconnect()"); } // onDisconnect diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 64b34b41..524e88f3 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -55,7 +55,7 @@ class BLEServer { BLEService* createService(const char* uuid); BLEService* createService(BLEUUID uuid, uint32_t numHandles=15); BLEAdvertising* getAdvertising(); - void setCallbacks(BLEServerCallbacks *pCallbacks); + void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 4fb48c37..9ca7cdd7 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "BLEUUID.h" static const char* LOG_TAG = "BLEUUID"; @@ -242,6 +244,35 @@ bool BLEUUID::equals(BLEUUID uuid) { } // equals +/** + * Create a BLEUUID from a string of the form: + * 0xNNNN + * 0xNNNNNNNN + * 0x + * NNNN + * NNNNNNNN + * + */ +BLEUUID BLEUUID::fromString(std::string _uuid){ + uint8_t start = 0; + if (strstr(_uuid.c_str(), "0x") != nullptr) { // If the string starts with 0x, skip those characters. + start = 2; + } + uint8_t len = _uuid.length() - start; // Calculate the length of the string we are going to use. + + if( len == 4) { + uint16_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); + return BLEUUID(x); + } else if (len == 8) { + uint32_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); + return BLEUUID(x); + } else if (len == 36) { + return BLEUUID(_uuid); + } + return BLEUUID(); +} // fromString + + /** * @brief Get the native UUID value. * @@ -376,21 +407,4 @@ std::string BLEUUID::toString() { return ss.str(); } // toString -BLEUUID BLEUUID::fromString(std::string _uuid){ - uint8_t start = 0; - if(strstr(_uuid.c_str(), "0x") != nullptr){ - start = 2; - } - uint8_t len = _uuid.length() - start; - if(len == 4 ){ - uint16_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); - return BLEUUID(x); - }else if(len == 8){ - uint32_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); - return BLEUUID(x); - }else if (len == 36){ - return BLEUUID(_uuid); - } - return BLEUUID(); -} #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index 3089bcb1..5fb7795b 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -29,7 +29,7 @@ class BLEUUID { esp_bt_uuid_t* getNative(); BLEUUID to128(); std::string toString(); - static BLEUUID fromString(std::string uuid); + static BLEUUID fromString(std::string uuid); // Create a BLEUUID from a string private: esp_bt_uuid_t m_uuid; // The underlying UUID structure that this class wraps. diff --git a/cpp_utils/PWM.cpp b/cpp_utils/PWM.cpp index 85e0b23f..67659cb1 100644 --- a/cpp_utils/PWM.cpp +++ b/cpp_utils/PWM.cpp @@ -24,14 +24,14 @@ * * @param [in] gpioNum The GPIO pin to use for output. * @param [in] frequency The frequency of the period. - * @param [in] bitSize The size in bits of the timer. Allowed values are LEDC_TIMER_10_BIT, + * @param [in] dutyResolution The size in bits of the timer. Allowed values are LEDC_TIMER_10_BIT, * LEDC_TIMER_11_BIT, LEDC_TIMER_12_BIT, LEDC_TIMER_13_BIT, LEDC_TIMER_14_BIT, LEDC_TIMER_15_BIT. * @param [in] timer The timer to use. A value of LEDC_TIMER0, LEDC_TIMER1, LEDC_TIMER2 or LEDC_TIMER3. * @param [in] channel The channel to use. A value from LEDC_CHANNEL0 to LEDC_CHANNEL1. * @return N/A. */ -PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t bitSize, ledc_timer_t timer, ledc_channel_t channel) { +PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t dutyResolution, ledc_timer_t timer, ledc_channel_t channel) { ledc_timer_config_t timer_conf; timer_conf.duty_resolution = bitSize; timer_conf.freq_hz = frequency; From 0b91cfea0b904404251ed6def92f25d94227271b Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 2 Dec 2017 15:22:38 -0600 Subject: [PATCH 164/381] More changes for #241 --- cpp_utils/PWM.cpp | 22 +++++++++++----------- cpp_utils/PWM.h | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cpp_utils/PWM.cpp b/cpp_utils/PWM.cpp index 67659cb1..f7119c4a 100644 --- a/cpp_utils/PWM.cpp +++ b/cpp_utils/PWM.cpp @@ -33,7 +33,7 @@ */ PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t dutyResolution, ledc_timer_t timer, ledc_channel_t channel) { ledc_timer_config_t timer_conf; - timer_conf.duty_resolution = bitSize; + timer_conf.duty_resolution = dutyResolution; timer_conf.freq_hz = frequency; timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE; timer_conf.timer_num = timer; @@ -48,9 +48,9 @@ PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t dutyResolution, ledc_ ledc_conf.timer_sel = timer; ESP_ERROR_CHECK(::ledc_channel_config(&ledc_conf)); - this->channel = channel; - this->timer = timer; - this->bitSize = bitSize; + this->m_channel = channel; + this->m_timer = timer; + this->m_dutyResolution = dutyResolution; } // PWM @@ -60,7 +60,7 @@ PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t dutyResolution, ledc_ * @return The duty cycle value. */ uint32_t PWM::getDuty() { - return ::ledc_get_duty(LEDC_HIGH_SPEED_MODE, channel); + return ::ledc_get_duty(LEDC_HIGH_SPEED_MODE, m_channel); } // getDuty @@ -70,7 +70,7 @@ uint32_t PWM::getDuty() { * @return The frequency/period in Hz. */ uint32_t PWM::getFrequency() { - return ::ledc_get_freq(LEDC_HIGH_SPEED_MODE, timer); + return ::ledc_get_freq(LEDC_HIGH_SPEED_MODE, m_timer); } // getFrequency @@ -84,8 +84,8 @@ uint32_t PWM::getFrequency() { * @return N/A. */ void PWM::setDuty(uint32_t duty) { - ESP_ERROR_CHECK(::ledc_set_duty(LEDC_HIGH_SPEED_MODE, channel, duty)); - ESP_ERROR_CHECK(::ledc_update_duty(LEDC_HIGH_SPEED_MODE, channel)); + ESP_ERROR_CHECK(::ledc_set_duty(LEDC_HIGH_SPEED_MODE, m_channel, duty)); + ESP_ERROR_CHECK(::ledc_update_duty(LEDC_HIGH_SPEED_MODE, m_channel)); } // setDuty @@ -97,7 +97,7 @@ void PWM::setDuty(uint32_t duty) { */ void PWM::setDutyPercentage(uint8_t percent) { uint32_t max; - switch(bitSize) { + switch(m_dutyResolution) { case LEDC_TIMER_10_BIT: max = 1 << 10; break; @@ -139,7 +139,7 @@ void PWM::setDutyPercentage(uint8_t percent) { * @return N/A. */ void PWM::setFrequency(uint32_t freq) { - ESP_ERROR_CHECK(::ledc_set_freq(LEDC_HIGH_SPEED_MODE, timer, freq)); + ESP_ERROR_CHECK(::ledc_set_freq(LEDC_HIGH_SPEED_MODE, m_timer, freq)); } // setFrequency @@ -150,5 +150,5 @@ void PWM::setFrequency(uint32_t freq) { * @return N/A. */ void PWM::stop(bool idleLevel) { - ESP_ERROR_CHECK(::ledc_stop(LEDC_HIGH_SPEED_MODE, channel, idleLevel)); + ESP_ERROR_CHECK(::ledc_stop(LEDC_HIGH_SPEED_MODE, m_channel, idleLevel)); } // stop diff --git a/cpp_utils/PWM.h b/cpp_utils/PWM.h index b8bfb317..01c30a48 100644 --- a/cpp_utils/PWM.h +++ b/cpp_utils/PWM.h @@ -34,9 +34,9 @@ class PWM { void setFrequency(uint32_t freq); void stop(bool idleLevel=false); private: - ledc_channel_t channel; - ledc_timer_t timer; - ledc_timer_bit_t bitSize; // Bit size of timer. + ledc_channel_t m_channel; + ledc_timer_t m_timer; + ledc_timer_bit_t m_dutyResolution; // Bit size of timer. }; #endif /* COMPONENTS_CPP_UTILS_PWM_H_ */ From c4d6a3d87a96e792d03891199253fae4de3face3 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 3 Dec 2017 12:08:56 -0600 Subject: [PATCH 165/381] Changes for #242 --- cpp_utils/BLECharacteristic.h | 3 ++- cpp_utils/BLEClient.cpp | 36 +++++++++++++++++++++++++++++++++-- cpp_utils/BLEClient.h | 34 +++++++++++++++++++++------------ cpp_utils/BLEDevice.cpp | 32 +++++++++++++++++++++++++++++++ cpp_utils/BLEDevice.h | 17 ++++++++++------- cpp_utils/BLERemoteService.h | 33 +++++++++++++++++--------------- 6 files changed, 118 insertions(+), 37 deletions(-) diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index cacf1e50..09301834 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -63,10 +63,11 @@ class BLECharacteristic { BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); //size_t getLength(); BLEUUID getUUID(); - std::string getValue(); + void indicate(); void notify(); + std::string getValue(); void setBroadcastProperty(bool value); void setCallbacks(BLECharacteristicCallbacks* pCallbacks); void setIndicateProperty(bool value); diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 57999006..189d5d74 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -117,7 +117,7 @@ bool BLEClient::connect(BLEAddress address) { return false; } - uint32_t rc = m_semaphoreOpenEvt.wait("connect"); + uint32_t rc = m_semaphoreOpenEvt.wait("connect"); // Wait for the connection to complete. ESP_LOGD(LOG_TAG, "<< connect(), rc=%d", rc==ESP_GATT_OK); return rc == ESP_GATT_OK; } // connect @@ -313,6 +313,7 @@ BLERemoteService* BLEClient::getService(const char* uuid) { * @brief Get the service object corresponding to the uuid. * @param [in] uuid The UUID of the service being sought. * @return A reference to the Service or nullptr if don't know about it. + * @throws BLEUuidNotFound */ BLERemoteService* BLEClient::getService(BLEUUID uuid) { ESP_LOGD(LOG_TAG, ">> getService: uuid: %s", uuid.toString().c_str()); @@ -333,7 +334,7 @@ BLERemoteService* BLEClient::getService(BLEUUID uuid) { } } // End of each of the services. ESP_LOGD(LOG_TAG, "<< getService: not found"); - return nullptr; + throw new BLEUuidNotFoundException; } // getService @@ -371,6 +372,21 @@ std::map* BLEClient::getServices() { return &m_servicesMap; } // getServices + +/** + * @brief Get the value of a specific characteristic associated with a specific service. + * @param [in] serviceUUID The service that owns the characteristic. + * @param [in] characteristicUUID The characteristic whose value we wish to read. + * @throws BLEUuidNotFound + */ +std::string BLEClient::getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID) { + ESP_LOGD(LOG_TAG, ">> getValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + std::string ret = getService(serviceUUID)->getCharacteristic(characteristicUUID)->readValue(); + ESP_LOGD(LOG_TAG, "<> setValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + getService(serviceUUID)->getCharacteristic(characteristicUUID)->writeValue(value); + ESP_LOGD(LOG_TAG, "<< setValue"); +} // setValue + + /** * @brief Return a string representation of this client. * @return A string representation of this client. @@ -433,4 +464,5 @@ std::string BLEClient::toString() { return ss.str(); } // toString + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 9ff5f90b..a60ed102 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -30,19 +30,28 @@ class BLEClient { public: BLEClient(); ~BLEClient(); - bool connect(BLEAddress address); - void disconnect(); - BLEAddress getPeerAddress(); - int getRssi(); - std::map* getServices(); - BLERemoteService* getService(const char* uuid); - BLERemoteService* getService(BLEUUID uuid); + + bool connect(BLEAddress address); // Connect to the remote BLE Server + void disconnect(); // Disconnect from the remote BLE Server + BLEAddress getPeerAddress(); // Get the address of the remote BLE Server + int getRssi(); // Get the RSSI of the remote BLE Server + std::map* getServices(); // Get a map of the services offered by the remote BLE Server + BLERemoteService* getService(const char* uuid); // Get a reference to a specified service offered by the remote BLE server. + BLERemoteService* getService(BLEUUID uuid); // Get a reference to a specified service offered by the remote BLE server. + std::string getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID); // Get the value of a given characteristic at a given service. + + void handleGAPEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); - bool isConnected(); + + bool isConnected(); // Return true if we are connected. + void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); - std::string toString(); + void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a given characteristic at a given service. + + std::string toString(); // Return a string representation of this client. + private: friend class BLEDevice; @@ -57,11 +66,12 @@ class BLEClient { uint16_t getConnId(); esp_gatt_if_t getGattcIf(); - BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); + BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); // The BD address of the remote server. uint16_t m_conn_id; // int m_deviceType; esp_gatt_if_t m_gattc_if; - bool m_isConnected; + bool m_haveServices; // Have we previously obtain the set of services from the remote server. + bool m_isConnected; // Are we currently connected. BLEClientCallbacks* m_pClientCallbacks; FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); @@ -70,7 +80,7 @@ class BLEClient { FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt"); std::map m_servicesMap; void clearServices(); // Clear any existing services. - bool m_haveServices; // Have we previously obtain the set of services. + }; // class BLEDevice diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index e1ace955..26b4203e 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -188,6 +188,23 @@ bool initialized = false; // Have we been initialized? } // getScan +/** + * @brief Get the value of a characteristic of a service on a remote device. + * @param [in] bdAddress + * @param [in] serviceUUID + * @param [in] characteristicUUID + */ +/* STATIC */ std::string BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID) { + ESP_LOGD(LOG_TAG, ">> getValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + BLEClient *pClient = createClient(); + pClient->connect(bdAddress); + std::string ret = pClient->getValue(serviceUUID, characteristicUUID); + pClient->disconnect(); + ESP_LOGD(LOG_TAG, "<< getValue"); + return ret; +} // getValue + + /** * @brief Initialize the %BLE environment. * @param deviceName The device name of the device. @@ -295,6 +312,21 @@ bool initialized = false; // Have we been initialized? } // setPower +/** + * @brief Set the value of a characteristic of a service on a remote device. + * @param [in] bdAddress + * @param [in] serviceUUID + * @param [in] characteristicUUID + */ +/* STATIC */ void BLEDevice::setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value) { + ESP_LOGD(LOG_TAG, ">> setValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + BLEClient *pClient = createClient(); + pClient->connect(bdAddress); + pClient->setValue(serviceUUID, characteristicUUID, value); + pClient->disconnect(); +} // setValue + + /** * @brief Return a string representation of the nature of this device. * @return A string representation of the nature of this device. diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 09a2098f..5399ff7c 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -20,19 +20,22 @@ #include "BLEUtils.h" #include "BLEScan.h" #include "BLEAddress.h" + /** * @brief %BLE functions. */ class BLEDevice { public: - static BLEClient* createClient(); - static BLEServer* createServer(); - static BLEAddress getAddress(); - static BLEScan* getScan(); - static void init(std::string deviceName); - static void setPower(esp_power_level_t powerLevel); - static std::string toString(); + static BLEClient* createClient(); // Create a new BLE client. + static BLEServer* createServer(); // Cretae a new BLE server. + static BLEAddress getAddress(); // Retrieve our own local BD address. + static BLEScan* getScan(); // Get the scan object + static std::string getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID); // Get the value of a characteristic of a service on a server. + static void init(std::string deviceName); // Initialize the local BLE environment. + static void setPower(esp_power_level_t powerLevel); // Set our power level. + static void setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a characteristic on a service on a server. + static std::string toString(); // Return a string representation of our device. private: static BLEServer *m_pServer; diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index b60e4fb3..222c6e45 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -30,17 +30,17 @@ class BLERemoteService { virtual ~BLERemoteService(); // Public methods - BLERemoteCharacteristic* getCharacteristic(const char* uuid); - BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); - BLERemoteCharacteristic* getCharacteristic(uint16_t uuid); + BLERemoteCharacteristic* getCharacteristic(const char* uuid); // Get the specified characteristic reference. + BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); // Get the specified characteristic reference. + BLERemoteCharacteristic* getCharacteristic(uint16_t uuid); // Get the specified characteristic reference. std::map* getCharacteristics(); - void getCharacteristics(std::map* ptr); + void getCharacteristics(std::map* pCharacteristicMap); // Get the characteristics map. - BLEClient* getClient(void); - uint16_t getHandle(); - BLEUUID getUUID(void); - std::string getValue(BLEUUID characteristicUuid); - void setValue(BLEUUID characteristicUuid, std::string value); + BLEClient* getClient(void); // Get a reference to the client associated with this service. + uint16_t getHandle(); // Get the handle of this service. + BLEUUID getUUID(void); // Get the UUID of this service. + std::string getValue(BLEUUID characteristicUuid); // Get the value of a characteristic. + void setValue(BLEUUID characteristicUuid, std::string value); // Set the value of a characteristic. std::string toString(void); private: @@ -52,20 +52,23 @@ class BLERemoteService { friend class BLERemoteCharacteristic; // Private methods - void retrieveCharacteristics(void); + void retrieveCharacteristics(void); // Retrieve the characteristics from the BLE Server. esp_gatt_id_t* getSrvcId(void); - uint16_t getStartHandle(); - uint16_t getEndHandle(); + uint16_t getStartHandle(); // Get the start handle for this service. + uint16_t getEndHandle(); // Get the end handle for this service. + void gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); + void removeCharacteristics(); // Properties // We maintain a map of characteristics owned by this service keyed by a string representation of the UUID. std::map m_characteristicMap; + // We maintain a map of characteristics owned by this service keyed by a handle. std::map m_characteristicMapByHandle; @@ -73,9 +76,9 @@ class BLERemoteService { BLEClient* m_pClient; FreeRTOS::Semaphore m_semaphoreGetCharEvt = FreeRTOS::Semaphore("GetCharEvt"); esp_gatt_id_t m_srvcId; - BLEUUID m_uuid; - uint16_t m_startHandle; - uint16_t m_endHandle; + BLEUUID m_uuid; // The UUID of this service. + uint16_t m_startHandle; // The starting handle of this service. + uint16_t m_endHandle; // The ending handle of this service. }; // BLERemoteService #endif /* CONFIG_BT_ENABLED */ From 9b9b24c98abf71131a25e80026ed9a793ca4d287 Mon Sep 17 00:00:00 2001 From: Sepp62 <33748087+Sepp62@users.noreply.github.com> Date: Sun, 3 Dec 2017 23:16:15 +0100 Subject: [PATCH 166/381] Improve stability on BLE notify() - Issue#209 - https://github.com/nkolban/esp32-snippets/issues/209 --- .gitignore | 1 + cpp_utils/BLECharacteristic.cpp | 26 +++++++++++ cpp_utils/BLECharacteristic.h | 6 +-- cpp_utils/BLEServer.cpp | 2 +- cpp_utils/FreeRTOS.cpp | 24 ++++++++-- cpp_utils/FreeRTOS.h | 1 + .../BLETests/Arduino/BLE_uart/BLE_uart.ino | 44 +++++++++++++------ 7 files changed, 84 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 46b14a0a..099e1f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .project .cproject .settings/ +/esp32-snippets/.vs diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 4c0935a7..7343e89d 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -45,6 +45,7 @@ BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { m_handle = NULL_HANDLE; m_properties = (esp_gatt_char_prop_t)0; m_pCallbacks = nullptr; + m_bConnected = false; setBroadcastProperty((properties & PROPERTY_BROADCAST) !=0); setReadProperty((properties & PROPERTY_READ) !=0); @@ -321,6 +322,7 @@ void BLECharacteristic::handleGATTServerEvent( // - bool need_rsp // case ESP_GATTS_READ_EVT: { + ESP_LOGD(LOG_TAG, "- Testing: 0x%.2x == 0x%.2x", param->read.handle, m_handle); if (param->read.handle == m_handle) { if (m_pCallbacks != nullptr) { @@ -410,6 +412,16 @@ void BLECharacteristic::handleGATTServerEvent( break; } + case ESP_GATTS_CONNECT_EVT: + m_semaphoreConfEvt.give(); + m_bConnected = true; + break; + + case ESP_GATTS_DISCONNECT_EVT: + m_semaphoreConfEvt.give(); + m_bConnected = false; + break; + default: { break; } // default @@ -518,6 +530,8 @@ void BLECharacteristic::notify() { length = 20; } + m_semaphoreConfEvt.take("notify"); + esp_err_t errRc = ::esp_ble_gatts_send_indicate( getService()->getServer()->getGattsIf(), getService()->getServer()->getConnId(), @@ -527,6 +541,8 @@ void BLECharacteristic::notify() { return; } + m_semaphoreConfEvt.wait("notify"); + ESP_LOGD(LOG_TAG, "<< notify"); } // Notify @@ -675,6 +691,16 @@ void BLECharacteristic::setWriteProperty(bool value) { } } // setWriteProperty + /** + * @brief, non-blocking check, whether API is ready to receive data + * @return true - if connected and sempahore is not yet taken + */ +bool BLECharacteristic::isReadyForData() +{ + bool bTaken = m_semaphoreConfEvt.isTaken(); + ESP_LOGV(LOG_TAG, " = %d", !bTaken && m_bConnected); + return !bTaken && m_bConnected; +} /** * @brief Return a string representation of the characteristic. diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 09301834..f24cb6ab 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -63,11 +63,10 @@ class BLECharacteristic { BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); //size_t getLength(); BLEUUID getUUID(); - + std::string getValue(); void indicate(); void notify(); - std::string getValue(); void setBroadcastProperty(bool value); void setCallbacks(BLECharacteristicCallbacks* pCallbacks); void setIndicateProperty(bool value); @@ -79,7 +78,7 @@ class BLECharacteristic { void setWriteNoResponseProperty(bool value); std::string toString(); uint16_t getHandle(); - + bool isReadyForData(); static const uint32_t PROPERTY_READ = 1<<0; static const uint32_t PROPERTY_WRITE = 1<<1; @@ -101,6 +100,7 @@ class BLECharacteristic { BLECharacteristicCallbacks* m_pCallbacks; BLEService* m_pService; BLEValue m_value; + volatile bool m_bConnected; void handleGATTServerEvent( esp_gatts_cb_event_t event, diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 448c8031..ce635d98 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -236,7 +236,7 @@ void BLEServer::handleGATTServerEvent( if (m_pServerCallbacks != nullptr) { // If we have callbacks, call now. m_pServerCallbacks->onDisconnect(this); } - startAdvertising(); + // startAdvertising(); - do this with some delay from the loop() break; } // ESP_GATTS_DISCONNECT_EVT diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 92390424..0d59ed6a 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -133,9 +133,9 @@ void FreeRTOS::Semaphore::give() { } else { xSemaphoreGive(m_semaphore); } -#ifdef ARDUINO_ARCH_ESP32 - FreeRTOS::sleep(10); -#endif +// #ifdef ARDUINO_ARCH_ESP32 +// FreeRTOS::sleep(10); +// #endif m_owner = std::string(""); } // Semaphore::give @@ -200,6 +200,24 @@ void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take +/** + * @brief non blocking check, if a semaphore is already taken by another thread + * @return true - if sempahore has already been taken by another thread. + */ +bool FreeRTOS::Semaphore::isTaken() { + bool bTaken = true; + ESP_LOGV(LOG_TAG, "Semaphore taken check" ); + + if (m_usePthreads) { + assert(false); + ESP_LOGV(LOG_TAG, "Semaphore::IsTaken illegal mode usePthreads"); + } else { + if( ( bTaken = xSemaphoreTake(m_semaphore, 0) ) ) + xSemaphoreGive( m_semaphore ); + ESP_LOGV(LOG_TAG, "Semaphore taken: %d", !bTaken); + } + return !bTaken; +} // Semaphore::isTaken std::string FreeRTOS::Semaphore::toString() { std::stringstream stringStream; diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index 43a3b8f4..ae960d39 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -40,6 +40,7 @@ class FreeRTOS { void take(uint32_t timeoutMs, std::string owner=""); std::string toString(); uint32_t wait(std::string owner=""); + bool isTaken(); private: SemaphoreHandle_t m_semaphore; diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino index a348a666..4cbb4a52 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino @@ -24,8 +24,11 @@ #include #include -BLECharacteristic *pCharacteristic; +BLEServer *pServer = NULL; +BLECharacteristic *pTxCharacteristic; +BLECharacteristic *pRxCharacteristic; bool deviceConnected = false; +bool oldDeviceConnected = false; uint8_t txValue = 0; // See the following for generating UUIDs: @@ -70,26 +73,26 @@ void setup() { BLEDevice::init("UART Service"); // Create the BLE Server - BLEServer *pServer = BLEDevice::createServer(); + pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Create the BLE Service BLEService *pService = pServer->createService(SERVICE_UUID); // Create a BLE Characteristic - pCharacteristic = pService->createCharacteristic( + pTxCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY ); - pCharacteristic->addDescriptor(new BLE2902()); + pTxCharacteristic->addDescriptor(new BLE2902()); - BLECharacteristic *pCharacteristic = pService->createCharacteristic( + pRxCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE ); - pCharacteristic->setCallbacks(new MyCallbacks()); + pRxCharacteristic->setCallbacks(new MyCallbacks()); // Start the service pService->start(); @@ -101,11 +104,26 @@ void setup() { void loop() { - if (deviceConnected) { - Serial.printf("*** Sent Value: %d ***\n", txValue); - pCharacteristic->setValue(&txValue, 1); - pCharacteristic->notify(); - txValue++; - } - delay(1000); + if (pTxCharacteristic->isReadyForData()) + { + pTxCharacteristic->setValue(&txValue, 1); + pTxCharacteristic->notify(); + txValue++; + delay(10); // bluetooth stack will go into congestion, if too many packets are sent + } + + // disconnecting + if (!deviceConnected && oldDeviceConnected) + { + delay(500); // give the bluetooth stack the chance to get things ready + pServer->startAdvertising(); // restart advertising + Serial.println("start advertising"); + oldDeviceConnected = deviceConnected; + } + // connecting + if (deviceConnected && !oldDeviceConnected) + { + // do stuff here on connecting + oldDeviceConnected = deviceConnected; + } } From 31860e4c992df3f92ebdf8460fd29e2d7c879018 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 3 Dec 2017 16:53:12 -0600 Subject: [PATCH 167/381] Code changes for #244 --- cpp_utils/TFTP.cpp | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/cpp_utils/TFTP.cpp b/cpp_utils/TFTP.cpp index 4e395591..d2c0254b 100644 --- a/cpp_utils/TFTP.cpp +++ b/cpp_utils/TFTP.cpp @@ -114,18 +114,24 @@ void TFTP::TFTP_Transaction::processRRQ() { return; } - uint8_t buf[TFTP_DATA_SIZE + 2 + 2]; // Buffer data size is packet size (512) + 2 bytes for opcode + 2 bytes for blocknumber. + struct { + uint16_t opCode; + uint16_t blockNumber; + uint8_t buf[TFTP_DATA_SIZE]; + } record; + + record.opCode = htons(TFTP_OPCODE_DATA); // Set the op code to be DATA. - *(uint16_t *)(&buf[0]) = htons(TFTP_OPCODE_DATA); // Set the op code to be DATA. while(!finished) { - *(uint16_t *)(&buf[2]) = htons(blockNumber); - int sizeRead = fread(&buf[4], 1, TFTP_DATA_SIZE, file); + record.blockNumber = htons(blockNumber); + + int sizeRead = fread(record.buf, 1, TFTP_DATA_SIZE, file); ESP_LOGD(tag, "Sending data to %s, blockNumber=%d, size=%d", Socket::addressToString(&m_partnerAddress).c_str(), blockNumber, sizeRead); - m_partnerSocket.sendTo(buf, sizeRead+4, &m_partnerAddress); + m_partnerSocket.sendTo((uint8_t*)&record, sizeRead+4, &m_partnerAddress); if (sizeRead < TFTP_DATA_SIZE) { @@ -327,17 +333,20 @@ uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { * RRQ/ | 01/02 | Filename | 0 | Mode | 0 | * WRQ ----------------------------------------------- */ - uint8_t buf[TFTP_DATA_SIZE]; + union { + uint8_t buf[TFTP_DATA_SIZE]; + uint16_t opCode; + } record; size_t length = 100; ESP_LOGD(tag, "TFTP: Waiting for a request"); - pServerSocket->receiveFrom(buf, length, &m_partnerAddress); + pServerSocket->receiveFrom(record.buf, length, &m_partnerAddress); // Save the filename, mode and op code. - m_filename = std::string((char *)(buf+2)); - m_mode = std::string((char *)(buf + 3 + m_filename.length())); - m_opCode = ntohs(*(uint16_t *)buf); + m_filename = std::string((char *)(record.buf + 2)); + m_mode = std::string((char *)(record.buf + 3 + m_filename.length())); + m_opCode = ntohs(record.opCode); switch(m_opCode) { // Handle the Write Request command. From e86cb07a4d2d585ddc68fe68c3d00e7ed46d8bf7 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Tue, 5 Dec 2017 19:48:05 -0600 Subject: [PATCH 168/381] Fixes for #245 --- networking/bootwifi/BootWiFi.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/networking/bootwifi/BootWiFi.cpp b/networking/bootwifi/BootWiFi.cpp index f56016b8..eaf1a4b3 100644 --- a/networking/bootwifi/BootWiFi.cpp +++ b/networking/bootwifi/BootWiFi.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "BootWiFi.h" #include "sdkconfig.h" @@ -201,7 +202,7 @@ static void processForm(HttpRequest* pRequest, HttpResponse* pResponse) { //pResponse->sendData(std::string((char*)selectAP_html, selectAP_html_len)); pResponse->close(); FreeRTOS::sleep(500); - GeneralUtils::restart(); + System::restart(); ESP_LOGD(LOG_TAG, "<< processForm"); } // processForm From 6e6252d61e4c3b9879629b13a7a5a4790c7e5acc Mon Sep 17 00:00:00 2001 From: Sepp62 <33748087+Sepp62@users.noreply.github.com> Date: Wed, 6 Dec 2017 20:28:58 +0100 Subject: [PATCH 169/381] Improve stability on BLE notify() - Issue#209 - https://github.com/nkolban/esp32-snippets/issues/209 --- .../Arduino/BLE_notify/BLE_notify.ino | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino index 8d329c20..5142c7ce 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino @@ -23,8 +23,10 @@ #include #include +BLEServer *pServer = NULL; BLECharacteristic *pCharacteristic; bool deviceConnected = false; +bool oldDeviceConnected = false; uint8_t value = 0; // See the following for generating UUIDs: @@ -53,7 +55,7 @@ void setup() { BLEDevice::init("MyESP32"); // Create the BLE Server - BLEServer *pServer = BLEDevice::createServer(); + pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Create the BLE Service @@ -81,13 +83,23 @@ void setup() { } void loop() { - - if (deviceConnected) { - Serial.printf("*** NOTIFY: %d ***\n", value); - pCharacteristic->setValue(&value, 1); - pCharacteristic->notify(); - //pCharacteristic->indicate(); - value++; - } - delay(2000); + // notify changed value + if (pCharacteristic->isReadyForData()) { + pCharacteristic->setValue(&value, 1); + pCharacteristic->notify(); + value++; + delay(10); // bluetooth stack will go into congestion, if too many packets are sent + } + // disconnecting + if (!deviceConnected && oldDeviceConnected) { + delay(500); // give the bluetooth stack the chance to get things ready + pServer->startAdvertising(); // restart advertising + Serial.println("start advertising"); + oldDeviceConnected = deviceConnected; + } + // connecting + if (deviceConnected && !oldDeviceConnected) { + // do stuff here on connecting + oldDeviceConnected = deviceConnected; + } } \ No newline at end of file From ea38814083f497b5ac5af4ffd2f40ff0464b8050 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 7 Dec 2017 20:44:20 -0600 Subject: [PATCH 170/381] Fixes for for #248 --- cpp_utils/JSON.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index 72a8da70..5cf1e1ed 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -97,7 +97,7 @@ void JsonArray::addDouble(double value) { * @param [in] value The int value to add to the array. */ void JsonArray::addInt(int value) { - cJSON_AddItemToArray(m_node, cJSON_CreateDouble((double)value, value)); + cJSON_AddItemToArray(m_node, cJSON_CreateNumber((double)value)); } // addInt @@ -326,7 +326,7 @@ void JsonObject::setDouble(std::string name, double value) { * @return N/A. */ void JsonObject::setInt(std::string name, int value) { - cJSON_AddItemToObject(m_node, name.c_str(), cJSON_CreateDouble((double)value, value)); + cJSON_AddItemToObject(m_node, name.c_str(), cJSON_CreateNumber((double)value)); } // setInt From b74fcb065f147563f062b78db0a3bbe78c52b965 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 10 Dec 2017 12:37:32 -0600 Subject: [PATCH 171/381] Fixes for #252 --- cpp_utils/HttpParser.cpp | 7 +++--- cpp_utils/HttpResponse.cpp | 22 ++++++++++++++++++- cpp_utils/HttpResponse.h | 1 + cpp_utils/HttpServer.cpp | 45 +++++++++++++++++++++++++++++++++----- cpp_utils/HttpServer.h | 21 ++++++++++-------- 5 files changed, 77 insertions(+), 19 deletions(-) diff --git a/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp index 9594316a..ab5a0b31 100644 --- a/cpp_utils/HttpParser.cpp +++ b/cpp_utils/HttpParser.cpp @@ -120,12 +120,9 @@ std::pair parseHeader(std::string &line) { HttpParser::HttpParser() { - // TODO Auto-generated constructor stub - } HttpParser::~HttpParser() { - // TODO Auto-generated destructor stub } @@ -191,6 +188,7 @@ bool HttpParser::hasHeader(const std::string& name) { * @param [in] s The socket from which to retrieve data. */ void HttpParser::parse(Socket s) { + ESP_LOGD(LOG_TAG, ">> parse: socket: %s", s.toString().c_str()); std::string line; line = s.readToDelim(lineTerminator); parseRequestLine(line); @@ -201,6 +199,7 @@ void HttpParser::parse(Socket s) { } // Only PUT and POST requests have a body if (getMethod() != "POST" && getMethod() != "PUT") { + ESP_LOGD(LOG_TAG, "<< parse"); return; } @@ -218,7 +217,7 @@ void HttpParser::parse(Socket s) { int rc = s.receive(data, sizeof(data)); m_body = std::string((char *)data, rc); } - ESP_LOGD(LOG_TAG, "Size of body: %d", m_body.length()); + ESP_LOGD(LOG_TAG, "<< parse: Size of body: %d", m_body.length()); } // parse diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp index 70182ca0..b0c60863 100644 --- a/cpp_utils/HttpResponse.cpp +++ b/cpp_utils/HttpResponse.cpp @@ -88,9 +88,10 @@ std::map HttpResponse::getHeaders() { * @param [in] data The data to send to the partner. */ void HttpResponse::sendData(std::string data) { + ESP_LOGD(LOG_TAG, ">> sendData"); // If the request is already closed, nothing further to do. if (m_request->isClosed()) { - ESP_LOGE(LOG_TAG, "Request to send more data but the request/response is already closed"); + ESP_LOGE(LOG_TAG, "<< sendData: Request to send more data but the request/response is already closed"); return; } @@ -101,8 +102,26 @@ void HttpResponse::sendData(std::string data) { // Send the payload data. m_request->getSocket().send(data); + ESP_LOGE(LOG_TAG, "<< sendData"); } // sendData +void HttpResponse::sendData(uint8_t* pData, size_t size) { + ESP_LOGD(LOG_TAG, ">> sendData: 0x%x, size: %d", (uint32_t) pData, size); + // If the request is already closed, nothing further to do. + if (m_request->isClosed()) { + ESP_LOGE(LOG_TAG, "<< sendData: Request to send more data but the request/response is already closed"); + return; + } + + // If we haven't yet sent the header of the data, send that now. + if (m_headerCommitted == false) { + sendHeader(); + } + + // Send the payload data. + m_request->getSocket().send(pData, size); + ESP_LOGD(LOG_TAG, "<< sendData"); +} // sendData /** * @brief Send the header @@ -138,3 +157,4 @@ void HttpResponse::setStatus(const int status, const std::string message) { m_statusMessage = message; } // setStatus + diff --git a/cpp_utils/HttpResponse.h b/cpp_utils/HttpResponse.h index 5c8c5ec0..9e2b4067 100644 --- a/cpp_utils/HttpResponse.h +++ b/cpp_utils/HttpResponse.h @@ -45,6 +45,7 @@ class HttpResponse { std::string getHeader(std::string name); // Get a named header. std::map getHeaders(); // Get all headers. void sendData(std::string data); // Send data to the client. + void sendData(uint8_t* pData, size_t size); // Send data to the client. void setStatus(int status, std::string message); // Set the response status. }; diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 298ab0c3..666e2fc9 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -66,10 +66,11 @@ static void listDirectory(std::string path, HttpResponse& response) { * Constructor for HTTP Server */ HttpServer::HttpServer() { + m_fileBufferSize = 4*1024; // Default size of the file buffer. m_portNumber = 80; // The default port number. m_rootPath = ""; // The default path. m_useSSL = false; // Default SSL is no. - setDirectoryListing(false); // Default directory listing is no. + setDirectoryListing(false); // Default directory listing is disabled. } // HttpServer @@ -100,6 +101,7 @@ class HttpServerTask: public Task { * * If we didn't find a handler, then we are going to behave as a Web Server and try and serve up the * content from the file on the "file system". + * * @param [in] request The HTTP request to process. */ void processRequest(HttpRequest &request) { @@ -134,7 +136,7 @@ class HttpServerTask: public Task { } // Serve up the content from the file on the file system ... if found ... - std::ifstream ifStream; + std::string fileName = m_pHttpServer->getRootPath() + request.getPath(); // Build the absolute file name to read. // If the file name ends with a '/' then remove it ... we are normalizing to NO trailing slashes. @@ -151,6 +153,7 @@ class HttpServerTask: public Task { } // Path was a directory. ESP_LOGD("HttpServerTask", "Opening file: %s", fileName.c_str()); + std::ifstream ifStream; ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); // Attempt to open the file for reading. // If we failed to open the requested file, then it probably didn't exist so return a not found. @@ -163,11 +166,16 @@ class HttpServerTask: public Task { } // We now have an open file and want to push the content of that file through to the browser. + // because of defect #252 we have to do some pretty important re-work here. Specifically, we can't host the whole file in + // RAM at one time. Instead what we have to do is ensure that we only have enough data in RAM to be sent. HttpResponse response(&request); response.setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); - std::stringstream ss; - ss << ifStream.rdbuf(); - response.sendData(ss.str()); + uint8_t *pData = new uint8_t[1000]; + while(!ifStream.eof()) { + ifStream.read((char *)pData, 1000); + response.sendData(pData, ifStream.gcount()); + } + delete[] pData; ifStream.close(); } // processRequest @@ -271,6 +279,18 @@ void HttpServer::addPathHandler( } // addPathHandler +/** + * @brief Get the size of the file buffer. + * When serving up a file from the file system, we can't afford to read the whole file into RAM before + * sending it. As such, we must read the file in chunks. The buffer size is the size of a chunk to be + * transmitted before the next chunk is read. + * @return The file buffer size. + */ +size_t HttpServer::getFileBufferSize() { + return m_fileBufferSize; +} // getFileBufferSize + + /** * @brief Get the port number on which the HTTP Server is listening. * @return The port number on which the HTTP server is listening. @@ -303,6 +323,18 @@ void HttpServer::setDirectoryListing(bool use) { } // setDirectoryListening +/** + * @brief Set the size of the file buffer. + * When serving up a file from the file system, we can't afford to read the whole file into RAM before + * sending it. As such, we must read the file in chunks. The buffer size is the size of a chunk to be + * transmitted before the next chunk is read. + * @param [in] fileBufferSize How large should the file buffer size be? + */ +void HttpServer::setFileBufferSize(size_t fileBufferSize) { + m_fileBufferSize = fileBufferSize; +} // setFileBufferSize + + /** * @brief Set the root path for URL file mapping. * @@ -394,6 +426,7 @@ PathHandler::PathHandler(std::string method, std::string matchPath, m_method = method; // Save the method we are looking for. m_textPattern = matchPath; m_isRegex = false; + m_pRegex = nullptr; m_pRequestHandler = pWebServerRequestHandler; // The handler to be invoked if the pattern matches. } // PathHandler @@ -430,3 +463,5 @@ void PathHandler::invokePathHandler(HttpRequest* request, HttpResponse *response } // invokePathHandler + + diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index f07abedd..adf2d573 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -74,23 +74,26 @@ class HttpServer { HttpRequest* pHttpRequest, HttpResponse* pHttpResponse) ); - uint16_t getPort(); // Get the port on which the Http server is listening. - std::string getRootPath(); // Get the root of the file system path. - bool getSSL(); // Are we using SSL? - void setDirectoryListing(bool use); // Should we list the content of directories? - void setRootPath(std::string path); // Set the root of the file system path. + size_t getFileBufferSize(); // Get the current size of the file buffer. + uint16_t getPort(); // Get the port on which the Http server is listening. + std::string getRootPath(); // Get the root of the file system path. + bool getSSL(); // Are we using SSL? + void setDirectoryListing(bool use); // Should we list the content of directories? + void setFileBufferSize(size_t fileBufferSize); // Set the size of the file buffer + void setRootPath(std::string path); // Set the root of the file system path. void start(uint16_t portNumber, bool useSSL=false); void stop(); // Stop a previously started server. + private: friend class HttpServerTask; friend class WebSocket; - uint16_t m_portNumber; // Port number on which server is listening. + size_t m_fileBufferSize; // Size of the file buffer. + bool m_directoryListing; // Should we list directory content? std::vector m_pathHandlers; // Vector of path handlers. + uint16_t m_portNumber; // Port number on which server is listening. std::string m_rootPath; // Root path into the file system. - bool m_useSSL; // Is this server listening on an HTTPS port? - bool m_directoryListing; // Should we list directory content? - //SockServ m_sockServ; // Server socket. Socket m_socket; + bool m_useSSL; // Is this server listening on an HTTPS port? }; // HttpServer #endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ From 6558d50fc63c7f982395bfe4d349ce4f636bd315 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 10 Dec 2017 16:13:03 -0600 Subject: [PATCH 172/381] More fixes for #252 --- cpp_utils/HttpServer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 666e2fc9..7f4f8b5b 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -170,9 +170,9 @@ class HttpServerTask: public Task { // RAM at one time. Instead what we have to do is ensure that we only have enough data in RAM to be sent. HttpResponse response(&request); response.setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); - uint8_t *pData = new uint8_t[1000]; + uint8_t *pData = new uint8_t[m_pHttpServer->getFileBufferSize()]; while(!ifStream.eof()) { - ifStream.read((char *)pData, 1000); + ifStream.read((char *)pData, m_pHttpServer->getFileBufferSize()); response.sendData(pData, ifStream.gcount()); } delete[] pData; From 94734b54457c534351c6258e6327aed08aa9100c Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 10 Dec 2017 23:19:40 -0600 Subject: [PATCH 173/381] Fixes for #256 --- cpp_utils/HttpResponse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp index b0c60863..adc72648 100644 --- a/cpp_utils/HttpResponse.cpp +++ b/cpp_utils/HttpResponse.cpp @@ -102,7 +102,7 @@ void HttpResponse::sendData(std::string data) { // Send the payload data. m_request->getSocket().send(data); - ESP_LOGE(LOG_TAG, "<< sendData"); + ESP_LOGD(LOG_TAG, "<< sendData"); } // sendData void HttpResponse::sendData(uint8_t* pData, size_t size) { From d52d5b25cd6e835379bcb31a7b733eaa1b8c7dba Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 11 Dec 2017 00:44:45 -0600 Subject: [PATCH 174/381] Fixes for #257 --- cpp_utils/HttpServer.cpp | 105 +++++++++++++++++++++++---------------- cpp_utils/HttpServer.h | 1 + 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 7f4f8b5b..488f05ce 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -20,47 +20,7 @@ static const char* LOG_TAG = "HttpServer"; #undef close -/** - * Send a directory listing back to the browser. - * @param [in] path The path of the directory to list. - * @param [in] response The response object to use to send data back to the browser. - */ -static void listDirectory(std::string path, HttpResponse& response) { - // If path ends with a "/" then remove it. - if (GeneralUtils::endsWith(path, '/')) { - path = path.substr(0, path.length()-1); - } - response.addHeader("Content-Type", "text/html"); - response.setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); - response.sendData(""); - if (!GeneralUtils::endsWith(path, '/')) { - response.sendData(""); - } - response.sendData(""); - response.sendData("

" + path + "

"); - response.sendData("
"); - response.sendData("

[To Parent Directory]

"); - response.sendData(""); - auto files = FileSystem::getDirectoryContents(path); - for (auto it = files.begin(); it != files.end(); ++it) { - std::stringstream ss; - ss << ""; - if (it->isDirectory()) { - ss << ""; - } - else { - ss << ""; - } - ss << ""; - response.sendData(ss.str()); - ESP_LOGD(LOG_TAG, "file: %s", ss.str().c_str()); - } - response.sendData("
" << it->getName() << "<dir>" << it->length() << "
"); - response.sendData("
"); - response.sendData(""); - response.close(); -} // listDirectory /** * Constructor for HTTP Server @@ -109,7 +69,7 @@ class HttpServerTask: public Task { request.getMethod().c_str(), request.getPath().c_str()); // Loop over all the path handlers we have looking for the first one that matches. Note that none of them - // may match. If we find one that does, then invoke the handler and that is the end of processing. + // need to match. If we find one that does, then invoke the handler and that is the end of processing. for (auto pathHandlerIterartor = m_pHttpServer->m_pathHandlers.begin(); pathHandlerIterartor != m_pHttpServer->m_pathHandlers.end(); ++pathHandlerIterartor) { @@ -148,7 +108,7 @@ class HttpServerTask: public Task { if (FileSystem::isDirectory(fileName)) { ESP_LOGD(LOG_TAG, "Path %s is a directory", fileName.c_str()); HttpResponse response(&request); - listDirectory(fileName, response); + m_pHttpServer->listDirectory(fileName, response); // List the contents of the directory. return; } // Path was a directory. @@ -309,11 +269,66 @@ std::string HttpServer::getRootPath() { } // getRootPath +/** + * @brief Return whether or not we are using SSL. + * @return True if we are using SSL. + */ bool HttpServer::getSSL() { return m_useSSL; } // getSSL +/** + * Send a directory listing back to the browser. + * @param [in] path The path of the directory to list. + * @param [in] response The response object to use to send data back to the browser. + */ +void HttpServer::listDirectory(std::string path, HttpResponse& response) { + // If path ends with a "/" then remove it. + if (GeneralUtils::endsWith(path, '/')) { + path = path.substr(0, path.length()-1); + } + + // The url path may not be the same as path which is the path on the file system. + // They will differ if we are using a root path. We check to see if the file system path + // starts with the root path, if it does, we remove the root path from the url path. + std::string urlPath = path; + if (urlPath.compare(0, getRootPath().length(), getRootPath()) == 0) { + urlPath = urlPath.substr(getRootPath().length()); + } + + response.addHeader("Content-Type", "text/html"); + response.setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + response.sendData(""); + if (!GeneralUtils::endsWith(path, '/')) { + response.sendData(""); + } + response.sendData(""); + response.sendData("

" + path + "

"); + response.sendData("
"); + response.sendData("

[To Parent Directory]

"); + response.sendData(""); + auto files = FileSystem::getDirectoryContents(path); + for (auto it = files.begin(); it != files.end(); ++it) { + std::stringstream ss; + ss << ""; + if (it->isDirectory()) { + ss << ""; + } + else { + ss << ""; + } + + ss << ""; + response.sendData(ss.str()); + ESP_LOGD(LOG_TAG, "file: %s", ss.str().c_str()); + } + response.sendData("
" << it->getName() << "<dir>" << it->length() << "
"); + response.sendData("
"); + response.sendData(""); + response.close(); +} // listDirectory + /** * @brief Set whether or not we will list directories. * @param [in] use Set to true to enable directory listing. @@ -353,6 +368,12 @@ void HttpServer::setFileBufferSize(size_t fileBufferSize) { * @return N/A. */ void HttpServer::setRootPath(std::string path) { + + // Should the user have supplied a path that ends in a "/" remove the trailing slash. This also + // means that "/" becomes "". + if (GeneralUtils::endsWith(path, '/')) { + path = path.substr(0, path.length()-1); + } m_rootPath = path; } // setRootPath diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index adf2d573..c814d0c8 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -87,6 +87,7 @@ class HttpServer { private: friend class HttpServerTask; friend class WebSocket; + void listDirectory(std::string path, HttpResponse& response); size_t m_fileBufferSize; // Size of the file buffer. bool m_directoryListing; // Should we list directory content? std::vector m_pathHandlers; // Vector of path handlers. From 3d53194a6c4a2133a094b58439f943bb25dba9dd Mon Sep 17 00:00:00 2001 From: boonkerz Date: Tue, 12 Dec 2017 02:46:28 +0100 Subject: [PATCH 175/381] after an start it should be resend the address as example for the bm280: i2cBus.setAddress(0x77); i2cBus.beginTransaction(); i2cBus.write(BME280_CHIP_ID_ADDR, true); i2cBus.start(); i2cBus.read(&chip_id, false); i2cBus.endTransaction(); --- cpp_utils/I2C.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index 3aa740c7..aac369cf 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -248,6 +248,7 @@ void I2C::start() { if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "i2c_master_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } + m_directionKnown = false; } // start From 7464995182951b5ef38f515a8d567a2f4afbf985 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 11 Dec 2017 23:38:42 -0600 Subject: [PATCH 176/381] changes for #265 --- cpp_utils/GeneralUtils.cpp | 20 +++++++++++++++++++- cpp_utils/GeneralUtils.h | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index fd0c740c..5972101f 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include static const char* LOG_TAG = "GeneralUtils"; @@ -98,6 +100,23 @@ bool GeneralUtils::base64Encode(const std::string &in, std::string *out) { } // base64Encode +/** + * @brief Dump general info to the log. + * Data includes: + * * Amount of free RAM + */ +void GeneralUtils::dumpInfo() { + size_t freeHeap = heap_caps_get_free_size(MALLOC_CAP_8BIT); + esp_chip_info_t chipInfo; + esp_chip_info(&chipInfo); + ESP_LOGD(LOG_TAG, "--- dumpInfo ---"); + ESP_LOGD(LOG_TAG, "Free heap: %d", freeHeap); + ESP_LOGD(LOG_TAG, "Chip Info: Model: %d, cores: %d, revision: %d", chipInfo.model, chipInfo.cores, chipInfo.revision); + ESP_LOGD(LOG_TAG, "ESP-IDF version: %s", esp_get_idf_version()); + ESP_LOGD(LOG_TAG, "---"); +} // dumpInfo + + /** * @brief Does the string end with a specific character? * @param [in] str The string to examine. @@ -398,4 +417,3 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { } // errorToString - diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index 54aae5e3..05262a25 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -18,6 +18,7 @@ class GeneralUtils { public: static bool base64Decode(const std::string& in, std::string* out); static bool base64Encode(const std::string& in, std::string* out); + static void dumpInfo(); static bool endsWith(std::string str, char c); static const char* errorToString(esp_err_t errCode); static void hexDump(const uint8_t* pData, uint32_t length); From 67331f32bfdc5680f3f4d2594e22633ee2cb8562 Mon Sep 17 00:00:00 2001 From: Sepp62 <33748087+Sepp62@users.noreply.github.com> Date: Wed, 13 Dec 2017 20:44:04 +0100 Subject: [PATCH 177/381] Issue #209 - reverted to single threaded operation --- cpp_utils/BLECharacteristic.cpp | 14 ---------- cpp_utils/BLECharacteristic.h | 2 -- cpp_utils/FreeRTOS.cpp | 19 -------------- cpp_utils/FreeRTOS.h | 1 - .../Arduino/BLE_notify/BLE_notify.ino | 5 ++-- .../BLETests/Arduino/BLE_uart/BLE_uart.ino | 26 ++++++++----------- 6 files changed, 13 insertions(+), 54 deletions(-) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 7343e89d..5754c488 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -45,7 +45,6 @@ BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { m_handle = NULL_HANDLE; m_properties = (esp_gatt_char_prop_t)0; m_pCallbacks = nullptr; - m_bConnected = false; setBroadcastProperty((properties & PROPERTY_BROADCAST) !=0); setReadProperty((properties & PROPERTY_READ) !=0); @@ -414,12 +413,10 @@ void BLECharacteristic::handleGATTServerEvent( case ESP_GATTS_CONNECT_EVT: m_semaphoreConfEvt.give(); - m_bConnected = true; break; case ESP_GATTS_DISCONNECT_EVT: m_semaphoreConfEvt.give(); - m_bConnected = false; break; default: { @@ -691,17 +688,6 @@ void BLECharacteristic::setWriteProperty(bool value) { } } // setWriteProperty - /** - * @brief, non-blocking check, whether API is ready to receive data - * @return true - if connected and sempahore is not yet taken - */ -bool BLECharacteristic::isReadyForData() -{ - bool bTaken = m_semaphoreConfEvt.isTaken(); - ESP_LOGV(LOG_TAG, " = %d", !bTaken && m_bConnected); - return !bTaken && m_bConnected; -} - /** * @brief Return a string representation of the characteristic. * @return A string representation of the characteristic. diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index f24cb6ab..1c59247f 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -78,7 +78,6 @@ class BLECharacteristic { void setWriteNoResponseProperty(bool value); std::string toString(); uint16_t getHandle(); - bool isReadyForData(); static const uint32_t PROPERTY_READ = 1<<0; static const uint32_t PROPERTY_WRITE = 1<<1; @@ -100,7 +99,6 @@ class BLECharacteristic { BLECharacteristicCallbacks* m_pCallbacks; BLEService* m_pService; BLEValue m_value; - volatile bool m_bConnected; void handleGATTServerEvent( esp_gatts_cb_event_t event, diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 0d59ed6a..674e6f8e 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -200,25 +200,6 @@ void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); } // Semaphore::take -/** - * @brief non blocking check, if a semaphore is already taken by another thread - * @return true - if sempahore has already been taken by another thread. - */ -bool FreeRTOS::Semaphore::isTaken() { - bool bTaken = true; - ESP_LOGV(LOG_TAG, "Semaphore taken check" ); - - if (m_usePthreads) { - assert(false); - ESP_LOGV(LOG_TAG, "Semaphore::IsTaken illegal mode usePthreads"); - } else { - if( ( bTaken = xSemaphoreTake(m_semaphore, 0) ) ) - xSemaphoreGive( m_semaphore ); - ESP_LOGV(LOG_TAG, "Semaphore taken: %d", !bTaken); - } - return !bTaken; -} // Semaphore::isTaken - std::string FreeRTOS::Semaphore::toString() { std::stringstream stringStream; stringStream << "name: "<< m_name << " (0x" << std::hex << std::setfill('0') << (uint32_t)m_semaphore << "), owner: " << m_owner; diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index ae960d39..43a3b8f4 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -40,7 +40,6 @@ class FreeRTOS { void take(uint32_t timeoutMs, std::string owner=""); std::string toString(); uint32_t wait(std::string owner=""); - bool isTaken(); private: SemaphoreHandle_t m_semaphore; diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino index 5142c7ce..57ad7a7d 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino @@ -24,7 +24,6 @@ #include BLEServer *pServer = NULL; -BLECharacteristic *pCharacteristic; bool deviceConnected = false; bool oldDeviceConnected = false; uint8_t value = 0; @@ -62,7 +61,7 @@ void setup() { BLEService *pService = pServer->createService(SERVICE_UUID); // Create a BLE Characteristic - pCharacteristic = pService->createCharacteristic( + BLECharacteristic * pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | @@ -84,7 +83,7 @@ void setup() { void loop() { // notify changed value - if (pCharacteristic->isReadyForData()) { + if (deviceConnected) { pCharacteristic->setValue(&value, 1); pCharacteristic->notify(); value++; diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino index 4cbb4a52..35b570b9 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino @@ -25,8 +25,7 @@ #include BLEServer *pServer = NULL; -BLECharacteristic *pTxCharacteristic; -BLECharacteristic *pRxCharacteristic; +BLECharacteristic * pTxCharacteristic; bool deviceConnected = false; bool oldDeviceConnected = false; uint8_t txValue = 0; @@ -81,16 +80,16 @@ void setup() { // Create a BLE Characteristic pTxCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID_TX, - BLECharacteristic::PROPERTY_NOTIFY - ); + CHARACTERISTIC_UUID_TX, + BLECharacteristic::PROPERTY_NOTIFY + ); pTxCharacteristic->addDescriptor(new BLE2902()); - pRxCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID_RX, - BLECharacteristic::PROPERTY_WRITE - ); + BLECharacteristic * pRxCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_RX, + BLECharacteristic::PROPERTY_WRITE + ); pRxCharacteristic->setCallbacks(new MyCallbacks()); @@ -104,8 +103,7 @@ void setup() { void loop() { - if (pTxCharacteristic->isReadyForData()) - { + if (deviceConnected) { pTxCharacteristic->setValue(&txValue, 1); pTxCharacteristic->notify(); txValue++; @@ -113,16 +111,14 @@ void loop() { } // disconnecting - if (!deviceConnected && oldDeviceConnected) - { + if (!deviceConnected && oldDeviceConnected) { delay(500); // give the bluetooth stack the chance to get things ready pServer->startAdvertising(); // restart advertising Serial.println("start advertising"); oldDeviceConnected = deviceConnected; } // connecting - if (deviceConnected && !oldDeviceConnected) - { + if (deviceConnected && !oldDeviceConnected) { // do stuff here on connecting oldDeviceConnected = deviceConnected; } From 2adbc1cb90e3dd567a3a0c80f7fed617400c065c Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 13 Dec 2017 20:08:43 -0600 Subject: [PATCH 178/381] Fixes for #276 --- cpp_utils/BLEServer.cpp | 2 +- cpp_utils/GeneralUtils.cpp | 26 +++++++++++++++++++++++ cpp_utils/GeneralUtils.h | 3 +++ cpp_utils/HttpParser.cpp | 42 ++++++++++++++++++-------------------- 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index ce635d98..87c012b3 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -236,7 +236,7 @@ void BLEServer::handleGATTServerEvent( if (m_pServerCallbacks != nullptr) { // If we have callbacks, call now. m_pServerCallbacks->onDisconnect(this); } - // startAdvertising(); - do this with some delay from the loop() + startAdvertising(); //- do this with some delay from the loop() break; } // ESP_GATTS_DISCONNECT_EVT diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 5972101f..42ea682f 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -417,3 +417,29 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { } // errorToString +/** + * @brief Convert a string to lower case. + * @param [in] value The string to convert to lower case. + * @return A lower case representation of the string. + */ +std::string GeneralUtils::toLower(std::string& value) { + // Question: Could this be improved with a signature of: + // std::string& GeneralUtils::toLower(std::string& value) + std::transform(value.begin(), value.end(), value.begin(), ::tolower); + return value; +} // toLower + + +/** + * @brief Remove white space from a string. + */ +std::string GeneralUtils::trim(const std::string& str) +{ + size_t first = str.find_first_not_of(' '); + if (std::string::npos == first) + { + return str; + } + size_t last = str.find_last_not_of(' '); + return str.substr(first, (last - first + 1)); +} // trim diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index 05262a25..9f945071 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -10,6 +10,7 @@ #include #include #include +#include /** * @brief General utilities. @@ -23,6 +24,8 @@ class GeneralUtils { static const char* errorToString(esp_err_t errCode); static void hexDump(const uint8_t* pData, uint32_t length); static std::string ipToString(uint8_t* ip); + static std::string toLower(std::string& value); + static std::string trim(const std::string& str); }; diff --git a/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp index ab5a0b31..9eb6d535 100644 --- a/cpp_utils/HttpParser.cpp +++ b/cpp_utils/HttpParser.cpp @@ -10,6 +10,7 @@ #include #include "HttpParser.h" #include "HttpRequest.h" +#include "GeneralUtils.h" #include @@ -35,21 +36,6 @@ static const char* LOG_TAG = "HttpParser"; static std::string lineTerminator = "\r\n"; -/** - * @brief Remove white space from a string. - */ -static std::string trim(const std::string& str) -{ - size_t first = str.find_first_not_of(' '); - if (std::string::npos == first) - { - return str; - } - size_t last = str.find_last_not_of(' '); - return str.substr(first, (last - first + 1)); -} // trim - - /** * @brief Parse an incoming line of text until we reach a delimiter. * @param [in/out] it The current iterator in the text input. @@ -113,8 +99,10 @@ static std::string toCharToken(std::string::iterator &it, std::string &str, char */ std::pair parseHeader(std::string &line) { auto it = line.begin(); - auto name = toCharToken(it, line, ':'); - auto value = trim(toStringToken(it, line, lineTerminator)); + std::string name = toCharToken(it, line, ':'); // Parse the line until we find a ':' + // We normalize the header name to be lower case. + GeneralUtils::toLower(name); + auto value = GeneralUtils::trim(toStringToken(it, line, lineTerminator)); return std::pair(name, value); } // parseHeader @@ -145,17 +133,25 @@ std::string HttpParser::getBody() { } +/** + * @brief Retrieve the value of the named header. + * @param [in] name The name of the header to retrieve. + * @return The value of the named header or null if not present. + */ std::string HttpParser::getHeader(const std::string& name) { - if (!hasHeader(name)) { + // We normalize the header name to be lower case. + std::string localName = name; + GeneralUtils::toLower(localName); + if (!hasHeader(localName)) { return ""; } - return m_headers.at(name); -} + return m_headers.at(localName); +} // getHeader std::map HttpParser::getHeaders() { return m_headers; -} +} // getHeaders std::string HttpParser::getMethod() { @@ -179,7 +175,9 @@ std::string HttpParser::getVersion() { * @return True if the header is present and false otherwise. */ bool HttpParser::hasHeader(const std::string& name) { - return m_headers.find(name) != m_headers.end(); + // We normalize the header name to be lower case. + std::string localName = name; + return m_headers.find(GeneralUtils::toLower(localName)) != m_headers.end(); } // hasHeader From e71adf0426f9c204946de67f1882f88b24cb4f8b Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 13 Dec 2017 23:40:52 -0600 Subject: [PATCH 179/381] Fixes for #275 --- cpp_utils/BLEClient.cpp | 2 +- cpp_utils/BLEDevice.cpp | 2 +- cpp_utils/BLEDevice.h | 2 +- cpp_utils/BLEScan.cpp | 11 ++++++++--- cpp_utils/BLEScan.h | 5 ++++- cpp_utils/BLEServer.cpp | 2 +- cpp_utils/BLEUtils.cpp | 2 +- cpp_utils/tests/BLETests/SampleWrite.cpp | 2 ++ 8 files changed, 19 insertions(+), 9 deletions(-) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 189d5d74..f3987d08 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -7,7 +7,7 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) #include -#include +#include #include #include #include diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 26b4203e..b89711a1 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -11,7 +11,7 @@ #include #include #include -#include // ESP32 BLE +#include // ESP32 BLE #include // ESP32 BLE #include // ESP32 BLE #include // ESP32 BLE diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 5399ff7c..ecf5e6c7 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -13,7 +13,7 @@ #include // ESP32 BLE #include // Part of C++ STL #include -#include +#include #include "BLEServer.h" #include "BLEClient.h" diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 7c5b7bd9..e450664e 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -33,6 +33,7 @@ BLEScan::BLEScan() { m_scan_params.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; m_pAdvertisedDeviceCallbacks = nullptr; m_stopped = true; + m_wantDuplicates = false; setInterval(100); setWindow(100); } // BLEScan @@ -97,7 +98,7 @@ void BLEScan::handleGAPEvent( break; } } - if (found) { + if (found && !m_wantDuplicates) { // If we found a previous entry AND we don't want duplicates, then we are done. ESP_LOGD(LOG_TAG, "Ignoring %s, already seen it.", advertisedAddress.toString().c_str()); break; } @@ -115,7 +116,9 @@ void BLEScan::handleGAPEvent( m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); } - m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice); + if (!found) { // If we have previously seen this device, don't record it again. + m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice); + } break; } // ESP_GAP_SEARCH_INQ_RES_EVT @@ -154,8 +157,10 @@ void BLEScan::setActiveScan(bool active) { /** * @brief Set the call backs to be invoked. * @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked. + * @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false. */ -void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks) { +void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, bool wantDuplicates) { + m_wantDuplicates = wantDuplicates; m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; } // setAdvertisedDeviceCallbacks diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index edf79a9a..c905c436 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -48,7 +48,9 @@ class BLEScanResults { class BLEScan { public: void setActiveScan(bool active); - void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks); + void setAdvertisedDeviceCallbacks( + BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, + bool wantDuplicates = false); void setInterval(uint16_t intervalMSecs); void setWindow(uint16_t windowMSecs); BLEScanResults start(uint32_t duration); @@ -68,6 +70,7 @@ class BLEScan { bool m_stopped; FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); BLEScanResults m_scanResults; + bool m_wantDuplicates; }; // BLEScan #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 87c012b3..75805135 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -8,7 +8,7 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) #include -#include +#include #include #include #include diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index d8d318f2..f96db1c5 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -14,7 +14,7 @@ #include #include -#include // ESP32 BLE +#include // ESP32 BLE #include // ESP32 BLE #include // ESP32 BLE #include // ESP32 BLE diff --git a/cpp_utils/tests/BLETests/SampleWrite.cpp b/cpp_utils/tests/BLETests/SampleWrite.cpp index 2832f9ec..de8f251a 100644 --- a/cpp_utils/tests/BLETests/SampleWrite.cpp +++ b/cpp_utils/tests/BLETests/SampleWrite.cpp @@ -11,6 +11,7 @@ #include #include #include "BLEDevice.h" +#include "GeneralUtils.h" #include "sdkconfig.h" @@ -35,6 +36,7 @@ class MyCallbacks: public BLECharacteristicCallbacks { static void run() { + GeneralUtils::dumpInfo(); BLEDevice::init("MYDEVICE"); BLEServer *pServer = BLEDevice::createServer(); From e36ba9778cdf46aea5c3419dae61a13da3cec6c9 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 14 Dec 2017 19:58:31 -0600 Subject: [PATCH 180/381] Fixes for #280 --- cpp_utils/BLEScan.cpp | 2 ++ cpp_utils/WiFi.cpp | 15 ++++++++++++++- cpp_utils/WiFi.h | 2 +- cpp_utils/WiFiEventHandler.cpp | 12 ++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index e450664e..e20c7e09 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -105,6 +105,8 @@ void BLEScan::handleGAPEvent( // We now construct a model of the advertised device that we have just found for the first // time. + FreeRTOS::sleep(50); + break; BLEAdvertisedDevice advertisedDevice; advertisedDevice.setAddress(advertisedAddress); advertisedDevice.setRSSI(param->scan_rst.rssi); diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 4054dfe5..d8ba2924 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -482,9 +482,16 @@ std::vector WiFi::scan() { * * @param[in] ssid The SSID to use to advertize for stations. * @param[in] password The password to use for station connections. + * @param[in] auth The authorization mode for access to this access point. Options are: + * * WIFI_AUTH_OPEN + * * WIFI_AUTH_WPA_PSK + * * WIFI_AUTH_WPA2_PSK + * * WIFI_AUTH_WPA_WPA2_PSK + * * WIFI_AUTH_WPA2_ENTERPRISE + * * WIFI_AUTH_WEP * @return N/A. */ -void WiFi::startAP(const std::string& ssid, const std::string& password) { +void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth) { ESP_LOGD(LOG_TAG, ">> startAP: ssid: %s", ssid.c_str()); init(); @@ -518,6 +525,12 @@ void WiFi::startAP(const std::string& ssid, const std::string& password) { ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); } + + errRc = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "tcpip_adapter_dhcps_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< startAP"); } // startAP diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index e9af3d67..eec479d2 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -138,7 +138,7 @@ class WiFi { static std::string getStaMac(); static std::string getStaSSID(); std::vector scan(); - void startAP(const std::string& ssid, const std::string& passwd); + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth = WIFI_AUTH_OPEN); void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); void setIPInfo(const char* ip, const char* gw, const char* netmask); void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); diff --git a/cpp_utils/WiFiEventHandler.cpp b/cpp_utils/WiFiEventHandler.cpp index 405058ad..319e2f25 100644 --- a/cpp_utils/WiFiEventHandler.cpp +++ b/cpp_utils/WiFiEventHandler.cpp @@ -37,10 +37,22 @@ esp_err_t WiFiEventHandler::eventHandler(void* ctx, system_event_t* event) { rc = pWiFiEventHandler->apStart(); break; } + case SYSTEM_EVENT_AP_STOP: { rc = pWiFiEventHandler->apStop(); break; } + + case SYSTEM_EVENT_AP_STACONNECTED: { + rc = pWiFiEventHandler->apStaConnected(); + break; + } + + case SYSTEM_EVENT_AP_STADISCONNECTED: { + rc = pWiFiEventHandler->apStaDisconnected(); + break; + } + case SYSTEM_EVENT_STA_CONNECTED: { rc = pWiFiEventHandler->staConnected(); break; From 1e4c5afcacf6d2318799413c7d343ce6345ae587 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 14 Dec 2017 19:59:49 -0600 Subject: [PATCH 181/381] Fixes for #280 --- cpp_utils/BLEScan.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index e20c7e09..e450664e 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -105,8 +105,6 @@ void BLEScan::handleGAPEvent( // We now construct a model of the advertised device that we have just found for the first // time. - FreeRTOS::sleep(50); - break; BLEAdvertisedDevice advertisedDevice; advertisedDevice.setAddress(advertisedAddress); advertisedDevice.setRSSI(param->scan_rst.rssi); From af58387668375e8620c78e44f55e97f42f0f88e7 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 14 Dec 2017 20:19:20 -0600 Subject: [PATCH 182/381] Fixes for #263 --- cpp_utils/BLECharacteristic.h | 1 + cpp_utils/BLEDescriptor.h | 1 + cpp_utils/BLEService.h | 5 ++--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 1c59247f..7bb54c64 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -87,6 +87,7 @@ class BLECharacteristic { static const uint32_t PROPERTY_WRITE_NR = 1<<5; private: + friend class BLEServer; friend class BLEService; friend class BLEDescriptor; diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index 4eda4c91..ef716d42 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -42,6 +42,7 @@ class BLEDescriptor { std::string toString(); private: + friend class BLEDescriptorMap; friend class BLECharacteristic; BLEUUID m_bleUUID; diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index fdba54fd..2b78e620 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -52,9 +52,6 @@ class BLECharacteristicMap { */ class BLEService { public: - BLEService(const char* uuid, uint32_t numHandles=10); - BLEService(BLEUUID uuid, uint32_t numHandles=10); - void addCharacteristic(BLECharacteristic* pCharacteristic); BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties); BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); @@ -69,6 +66,8 @@ class BLEService { uint16_t getHandle(); private: + BLEService(const char* uuid, uint32_t numHandles); + BLEService(BLEUUID uuid, uint32_t numHandles); friend class BLEServer; friend class BLEServiceMap; friend class BLEDescriptor; From 4cd4ed193961ec23011d0abfdd9be84241eaa9fe Mon Sep 17 00:00:00 2001 From: anio Date: Fri, 15 Dec 2017 15:09:05 +0200 Subject: [PATCH 183/381] Issue #280 fix auth --- cpp_utils/WiFi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index d8ba2924..25f9d1b5 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -509,7 +509,7 @@ void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_au apConfig.ap.ssid_len = ssid.size(); ::memcpy(apConfig.ap.password, password.data(), password.size()); apConfig.ap.channel = 0; - apConfig.ap.authmode = WIFI_AUTH_OPEN; + apConfig.ap.authmode = auth; apConfig.ap.ssid_hidden = 0; apConfig.ap.max_connection = 4; apConfig.ap.beacon_interval = 100; From 4931b218033b09d6ee29f5c96a7978fc1c94a9aa Mon Sep 17 00:00:00 2001 From: anio Date: Fri, 15 Dec 2017 16:58:25 +0200 Subject: [PATCH 184/381] Extend WiFiEventHandler. Add info arguments. --- cpp_utils/NeoPixelWiFiEventHandler.cpp | 6 +- cpp_utils/NeoPixelWiFiEventHandler.h | 6 +- cpp_utils/WiFiEventHandler.cpp | 76 +++++++++++++++++++++++--- cpp_utils/WiFiEventHandler.h | 46 +++++++++++----- cpp_utils/cpp_utils | 1 - 5 files changed, 106 insertions(+), 29 deletions(-) delete mode 120000 cpp_utils/cpp_utils diff --git a/cpp_utils/NeoPixelWiFiEventHandler.cpp b/cpp_utils/NeoPixelWiFiEventHandler.cpp index 9206b567..1632a0f7 100644 --- a/cpp_utils/NeoPixelWiFiEventHandler.cpp +++ b/cpp_utils/NeoPixelWiFiEventHandler.cpp @@ -23,14 +23,14 @@ esp_err_t NeoPixelWiFiEventHandler::apStart() { return ESP_OK; } -esp_err_t NeoPixelWiFiEventHandler::staConnected() { +esp_err_t NeoPixelWiFiEventHandler::staConnected(system_event_sta_connected_t info) { printf("XXX staConnected\n"); ws2812->setPixel(0, 57, 89, 66); ws2812->show(); return ESP_OK; } -esp_err_t NeoPixelWiFiEventHandler::staDisconnected() { +esp_err_t NeoPixelWiFiEventHandler::staDisconnected(system_event_sta_disconnected_t info) { printf("XXX staDisconnected\n"); ws2812->setPixel(0, 64, 0, 0); ws2812->show(); @@ -44,7 +44,7 @@ esp_err_t NeoPixelWiFiEventHandler::staStart() { return ESP_OK; } -esp_err_t NeoPixelWiFiEventHandler::staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { +esp_err_t NeoPixelWiFiEventHandler::staGotIp(system_event_sta_got_ip_t info) { printf("XXX staGotIp\n"); ws2812->setPixel(0, 0, 64, 0); ws2812->show(); diff --git a/cpp_utils/NeoPixelWiFiEventHandler.h b/cpp_utils/NeoPixelWiFiEventHandler.h index c271e3e3..3e9b596e 100644 --- a/cpp_utils/NeoPixelWiFiEventHandler.h +++ b/cpp_utils/NeoPixelWiFiEventHandler.h @@ -23,9 +23,9 @@ class NeoPixelWiFiEventHandler: public WiFiEventHandler { virtual ~NeoPixelWiFiEventHandler(); esp_err_t apStart() override; - esp_err_t staConnected() override; - esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) override; - esp_err_t staDisconnected() override; + esp_err_t staConnected(system_event_sta_connected_t info) override; + esp_err_t staGotIp(system_event_sta_got_ip_t info) override; + esp_err_t staDisconnected(system_event_sta_disconnected_t info) override; esp_err_t wifiReady() override; esp_err_t staStart() override; private: diff --git a/cpp_utils/WiFiEventHandler.cpp b/cpp_utils/WiFiEventHandler.cpp index 319e2f25..c5a4fa2f 100644 --- a/cpp_utils/WiFiEventHandler.cpp +++ b/cpp_utils/WiFiEventHandler.cpp @@ -44,22 +44,32 @@ esp_err_t WiFiEventHandler::eventHandler(void* ctx, system_event_t* event) { } case SYSTEM_EVENT_AP_STACONNECTED: { - rc = pWiFiEventHandler->apStaConnected(); + rc = pWiFiEventHandler->apStaConnected(event->event_info.sta_connected); break; } case SYSTEM_EVENT_AP_STADISCONNECTED: { - rc = pWiFiEventHandler->apStaDisconnected(); + rc = pWiFiEventHandler->apStaDisconnected(event->event_info.sta_disconnected); + break; + } + + case SYSTEM_EVENT_SCAN_DONE: { + rc = pWiFiEventHandler->staScanDone(event->event_info.scan_done); + break; + } + + case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: { + rc = pWiFiEventHandler->staAuthChange(event->event_info.auth_change); break; } case SYSTEM_EVENT_STA_CONNECTED: { - rc = pWiFiEventHandler->staConnected(); + rc = pWiFiEventHandler->staConnected(event->event_info.connected); break; } case SYSTEM_EVENT_STA_DISCONNECTED: { - rc = pWiFiEventHandler->staDisconnected(); + rc = pWiFiEventHandler->staDisconnected(event->event_info.disconnected); break; } @@ -123,7 +133,7 @@ system_event_cb_t WiFiEventHandler::getEventHandler() { * @param [in] event_sta_got_ip The Station Got IP event. * @return An indication of whether or not we processed the event successfully. */ -esp_err_t WiFiEventHandler::staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { +esp_err_t WiFiEventHandler::staGotIp(system_event_sta_got_ip_t info) { ESP_LOGD(LOG_TAG, "default staGotIp"); return ESP_OK; } // staGotIp @@ -169,29 +179,77 @@ esp_err_t WiFiEventHandler::staStop() { } // staStop -esp_err_t WiFiEventHandler::staConnected() { +/** + * @brief Handle the Station Connected event. + * Handle having connected to remote AP. + * @param [in] event_connected system_event_sta_connected_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::staConnected(system_event_sta_connected_t info) { ESP_LOGD(LOG_TAG, "default staConnected"); return ESP_OK; } // staConnected -esp_err_t WiFiEventHandler::staDisconnected() { +/** + * @brief Handle the Station Disconnected event. + * Handle having disconnected from remote AP. + * @param [in] event_disconnected system_event_sta_disconnected_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::staDisconnected(system_event_sta_disconnected_t info) { ESP_LOGD(LOG_TAG, "default staDisconnected"); return ESP_OK; } // staDisconnected -esp_err_t WiFiEventHandler::apStaConnected() { +/** + * @brief Handle a Station Connected to AP event. + * Handle having a station connected to ESP32 soft-AP. + * @param [in] event_sta_connected system_event_ap_staconnected_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::apStaConnected(system_event_ap_staconnected_t info) { ESP_LOGD(LOG_TAG, "default apStaConnected"); return ESP_OK; } // apStaConnected -esp_err_t WiFiEventHandler::apStaDisconnected() { +/** + * @brief Handle a Station Disconnected from AP event. + * Handle having a station disconnected from ESP32 soft-AP. + * @param [in] event_sta_disconnected system_event_ap_stadisconnected_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::apStaDisconnected(system_event_ap_stadisconnected_t info) { ESP_LOGD(LOG_TAG, "default apStaDisconnected"); return ESP_OK; } // apStaDisconnected +/** + * @brief Handle a Scan for APs done event. + * Handle having an ESP32 station scan (APs) done. + * @param [in] event_scan_done system_event_sta_scan_done_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::staScanDone(system_event_sta_scan_done_t info) { + ESP_LOGD(LOG_TAG, "default staScanDone"); + return ESP_OK; +} // staScanDone + + +/** + * @brief Handle the auth mode of APs change event. + * Handle having the auth mode of AP ESP32 station connected to changed. + * @param [in] event_auth_change system_event_sta_authmode_change_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::staAuthChange(system_event_sta_authmode_change_t info) { + ESP_LOGD(LOG_TAG, "default staAuthChange"); + return ESP_OK; +} // staAuthChange + + WiFiEventHandler::~WiFiEventHandler() { } // ~WiFiEventHandler diff --git a/cpp_utils/WiFiEventHandler.h b/cpp_utils/WiFiEventHandler.h index 612dc8f3..4dd46dba 100644 --- a/cpp_utils/WiFiEventHandler.h +++ b/cpp_utils/WiFiEventHandler.h @@ -22,13 +22,15 @@ * wifi.startAP("MYSSID", "password"); * * The overridable functions are: - * * esp_err_t apStaConnected() - * * esp_err_t apStaDisconnected() + * * esp_err_t apStaConnected(system_event_ap_staconnected_t info) + * * esp_err_t apStaDisconnected(system_event_ap_stadisconnected_t info) * * esp_err_t apStart() * * esp_err_t apStop() - * * esp_err_t staConnected() - * * esp_err_t staDisconnected() - * * esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) + * * esp_err_t staConnected(system_event_sta_connected_t info) + * * esp_err_t staDisconnected(system_event_sta_disconnected_t info) + * * esp_err_t staGotIp(system_event_sta_got_ip_t info) + * * esp_err_t staScanDone(system_event_sta_scan_done_t info) + * * esp_err_t staAuthChange(system_event_sta_authmode_change_t info) * * esp_err_t staStart() * * esp_err_t staStop() * * esp_err_t wifiReady() @@ -56,11 +58,19 @@ * return ESP_OK; * } * - * esp_err_t MyHandler::staConnected() { + * esp_err_t MyHandler::staConnected(system_event_sta_connected_t info) { * return ESP_OK; * } * - * esp_err_t MyHandler::staDisconnected() { + * esp_err_t MyHandler::staDisconnected(system_event_sta_disconnected_t info) { + * return ESP_OK; + * } + * + * esp_err_t MyHandler::apStaConnected(system_event_ap_staconnected_t info) { + * return ESP_OK; + * } + * + * esp_err_t MyHandler::apStaDisconnected(system_event_ap_stadisconnected_t info) { * return ESP_OK; * } * @@ -68,7 +78,15 @@ * return ESP_OK; * } * - * esp_err_t MyHandler::staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { + * esp_err_t MyHandler::staGotIp(system_event_sta_got_ip_t info) { + * return ESP_OK; + * } + * + * esp_err_t MyHandler::staScanDone(system_event_sta_scan_done_t info) { + * return ESP_OK; + * } + * + * esp_err_t MyHandler::staAuthChange(system_event_sta_authmode_change_t info) { * return ESP_OK; * } * @@ -81,14 +99,16 @@ class WiFiEventHandler { public: WiFiEventHandler(); virtual ~WiFiEventHandler(); - virtual esp_err_t apStaConnected(); - virtual esp_err_t apStaDisconnected(); + virtual esp_err_t apStaConnected(system_event_ap_staconnected_t info); + virtual esp_err_t apStaDisconnected(system_event_ap_stadisconnected_t info); virtual esp_err_t apStart(); virtual esp_err_t apStop(); system_event_cb_t getEventHandler(); - virtual esp_err_t staConnected(); - virtual esp_err_t staDisconnected(); - virtual esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip); + virtual esp_err_t staConnected(system_event_sta_connected_t info); + virtual esp_err_t staDisconnected(system_event_sta_disconnected_t info); + virtual esp_err_t staGotIp(system_event_sta_got_ip_t info); + virtual esp_err_t staScanDone(system_event_sta_scan_done_t info); + virtual esp_err_t staAuthChange(system_event_sta_authmode_change_t info); virtual esp_err_t staStart(); virtual esp_err_t staStop(); virtual esp_err_t wifiReady(); diff --git a/cpp_utils/cpp_utils b/cpp_utils/cpp_utils deleted file mode 120000 index 5d96b78d..00000000 --- a/cpp_utils/cpp_utils +++ /dev/null @@ -1 +0,0 @@ -/home/kolban/esp32/esptest/apps/workspace/esp32-snippets/cpp_utils \ No newline at end of file From cfaa5c7f86d3e69d5de69f11265eb6784fc57d16 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Fri, 15 Dec 2017 22:31:00 -0600 Subject: [PATCH 185/381] Changes for #196 --- cpp_utils/BLEAdvertising.cpp | 34 ++++++++++++++++++++++++++++++++++ cpp_utils/BLEAdvertising.h | 27 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 7406819b..e8d9bf4a 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -381,5 +381,39 @@ std::string BLEAdvertisementData::getPayload() { } // getPayload +BLEBeacon::BLEBeacon() { + m_beaconData.subType = 0x02; + m_beaconData.subTypeLength = 0x15; + m_beaconData.major = 0; + m_beaconData.minor = 0; + m_beaconData.signalPower = 0; + memset(m_beaconData.proximityUUID, 0, sizeof(m_beaconData.proximityUUID)); +} // BLEBeacon + +std::string BLEBeacon::getData() { + return std::string((char*)&m_beaconData, sizeof(m_beaconData)); +} // getData + +void BLEBeacon::setMajor(uint16_t major) { + m_beaconData.major = major; +} // setMajor + +void BLEBeacon::setManufacturerId(uint16_t manufacturerId) { + m_beaconData.manufacturerId = manufacturerId; +} // setManufacturerId + +void BLEBeacon::setMinor(uint16_t minor) { + m_beaconData.minor = minor; +} // setMinior + +void BLEBeacon::setProximityUUID(BLEUUID uuid) { + uuid = uuid.to128(); + memcpy(m_beaconData.proximityUUID, uuid.getNative()->uuid.uuid128, 16); +} // setProximityUUID + +void BLEBeacon::setSignalPower(uint16_t signalPower) { + m_beaconData.signalPower = signalPower; +} // setSignalPower + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 433de603..1965227d 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -13,6 +13,33 @@ #include "BLEUUID.h" #include +/** + * @brief Representation of a beacon. + * See: + * * https://en.wikipedia.org/wiki/IBeacon + */ +class BLEBeacon { +private: + struct { + uint16_t manufacturerId; + uint8_t subType; + uint8_t subTypeLength; + uint8_t proximityUUID[16]; + uint16_t major; + uint16_t minor; + uint8_t signalPower; + } m_beaconData; +public: + BLEBeacon(); + void setManufacturerId(uint16_t manufacturerId); + //void setSubType(uint8_t subType); + void setProximityUUID(BLEUUID uuid); + void setMajor(uint16_t major); + void setMinor(uint16_t minor); + void setSignalPower(uint16_t signalPower); + std::string getData(); +}; // BLEBeacon + /** * @brief Advertisement data set by the programmer to be published by the %BLE server. From e2257d9c9ff0b37ccb84e4ff6455e34fbd62748e Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 17 Dec 2017 14:14:27 -0600 Subject: [PATCH 186/381] Fixes for #64 --- cpp_utils/BLEAdvertising.cpp | 11 ++++++----- cpp_utils/GeneralUtils.cpp | 20 ++++++++++++++++++++ cpp_utils/GeneralUtils.h | 2 ++ cpp_utils/HttpRequest.cpp | 16 +++++++++++++++- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index e8d9bf4a..6331fa10 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -382,11 +382,12 @@ std::string BLEAdvertisementData::getPayload() { BLEBeacon::BLEBeacon() { - m_beaconData.subType = 0x02; - m_beaconData.subTypeLength = 0x15; - m_beaconData.major = 0; - m_beaconData.minor = 0; - m_beaconData.signalPower = 0; + m_beaconData.manufacturerId = 0x4c00; + m_beaconData.subType = 0x02; + m_beaconData.subTypeLength = 0x15; + m_beaconData.major = 0; + m_beaconData.minor = 0; + m_beaconData.signalPower = 0; memset(m_beaconData.proximityUUID, 0, sizeof(m_beaconData.proximityUUID)); } // BLEBeacon diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 42ea682f..ccf74f79 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -339,6 +339,24 @@ std::string GeneralUtils::ipToString(uint8_t *ip) { } // ipToString +/** + * @brief Split a string into parts based on a delimiter. + * @param [in] source The source string to split. + * @param [in] delimiter The delimiter characters. + * @return A vector of strings that are the split of the input. + */ +std::vector GeneralUtils::split(std::string source, char delimiter) { + // See also: https://stackoverflow.com/questions/5167625/splitting-a-c-stdstring-using-tokens-e-g + std::vector strings; + std::istringstream iss(source); + std::string s; + while(std::getline(iss, s, delimiter)) { + strings.push_back(trim(s)); + } + return strings; +} // split + + /** * @brief Convert an ESP error code to a string. * @param [in] errCode The errCode to be converted. @@ -443,3 +461,5 @@ std::string GeneralUtils::trim(const std::string& str) size_t last = str.find_last_not_of(' '); return str.substr(first, (last - first + 1)); } // trim + + diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index 9f945071..013953dc 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -11,6 +11,7 @@ #include #include #include +#include /** * @brief General utilities. @@ -24,6 +25,7 @@ class GeneralUtils { static const char* errorToString(esp_err_t errCode); static void hexDump(const uint8_t* pData, uint32_t length); static std::string ipToString(uint8_t* ip); + static std::vector split(std::string source, char delimiter); static std::string toLower(std::string& value); static std::string trim(const std::string& str); diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index 5347f195..bbbdce6d 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -33,6 +33,7 @@ #include #include +#include #include "HttpResponse.h" #include "HttpRequest.h" #include "GeneralUtils.h" @@ -96,11 +97,24 @@ HttpRequest::HttpRequest(Socket clientSocket) { m_parser.parse(clientSocket); // Parse the socket stream to build the HTTP data. + // We have to take some special action on the Connection header. We want to know if it contains "Upgrade" + // however it has come to light that the Connection header can contain multiple parts. For example, it has + // been reported that it can contain "keep-alive,Upgrade". Because of this we can't simply examine the string + // to see if it equals "Upgrade". Our solution is to get the value of Connection string, split it by "," as + // a delimiter and then examine each of the parts to see if any of those are "Upgrade". + std::vector parts = GeneralUtils::split(getHeader(HTTP_HEADER_CONNECTION), ','); + bool upgradeFound = false; + if (std::find(parts.begin(), parts.end(), "Upgrade") != parts.end()) + { + upgradeFound = true; + } + // Is this a Web Socket? if (getMethod() == HTTP_METHOD_GET && !getHeader(HTTP_HEADER_HOST).empty() && getHeader(HTTP_HEADER_UPGRADE) == "websocket" && - getHeader(HTTP_HEADER_CONNECTION) == "Upgrade" && + //getHeader(HTTP_HEADER_CONNECTION) == "Upgrade" && + upgradeFound == true && !getHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY).empty() && !getHeader(HTTP_HEADER_SEC_WEBSOCKET_VERSION).empty()) { ESP_LOGD(LOG_TAG, "Websocket detected!"); From 3d39b222179d8ea2f8e981a90eb998da53a12320 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 17 Dec 2017 23:13:13 -0600 Subject: [PATCH 187/381] Code changes for #291 --- cpp_utils/BLEAdvertising.cpp | 31 +++++++++++++++++++++++++++++++ cpp_utils/BLEAdvertising.h | 1 + cpp_utils/BLEDevice.cpp | 28 ++++++++++++++++++++++++++++ cpp_utils/BLEDevice.h | 2 ++ 4 files changed, 62 insertions(+) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 6331fa10..d7b20bb6 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -92,6 +92,36 @@ void BLEAdvertising::setAppearance(uint16_t appearance) { } // setAppearance +/** + * @brief Set the filtering for the scan filter. + * @param [in] scanRequestWhitelistOnly If true, only allow scan requests from those on the white list. + * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. + */ +void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { + ESP_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly); + if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { + m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; + ESP_LOGD(LOG_TAG, "<< setScanFilter"); + return; + } + if (scanRequestWhitelistOnly && !connectWhitelistOnly) { + m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY; + ESP_LOGD(LOG_TAG, "<< setScanFilter"); + return; + } + if (!scanRequestWhitelistOnly && connectWhitelistOnly) { + m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_WLST; + ESP_LOGD(LOG_TAG, "<< setScanFilter"); + return; + } + if (scanRequestWhitelistOnly && connectWhitelistOnly) { + m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST; + ESP_LOGD(LOG_TAG, "<< setScanFilter"); + return; + } +} // setScanFilter + + /** * @brief Set the advertisement data that is to be published in a regular advertisement. * @param [in] advertisementData The data to be advertised. @@ -417,4 +447,5 @@ void BLEBeacon::setSignalPower(uint16_t signalPower) { } // setSignalPower + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 1965227d..04f5924d 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -80,6 +80,7 @@ class BLEAdvertising { void stop(); void setAppearance(uint16_t appearance); void setAdvertisementData(BLEAdvertisementData& advertisementData); + void setScanFilter(bool scanRequertWhitelistOnly, bool connectWhitelistOnly); void setScanResponseData(BLEAdvertisementData& advertisementData); private: diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index b89711a1..5e09069d 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -337,4 +337,32 @@ bool initialized = false; // Have we been initialized? return oss.str(); } // toString + +/** + * @brief Add an entry to the BLE white list. + * @param [in] address The address to add to the white list. + */ +void BLEDevice::whiteListAdd(BLEAddress address) { + ESP_LOGD(LOG_TAG, ">> whiteListAdd: %s", address.toString().c_str()); + esp_err_t errRc = esp_ble_gap_update_whitelist(true, *address.getNative()); // True to add an entry. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< whiteListAdd"); +} // whiteListAdd + + +/** + * @brief Remove an entry from the BLE white list. + * @param [in] address The address to remove from the white list. + */ +void BLEDevice::whiteListRemove(BLEAddress address) { + ESP_LOGD(LOG_TAG, ">> whiteListRemove: %s", address.toString().c_str()); + esp_err_t errRc = esp_ble_gap_update_whitelist(false, *address.getNative()); // False to remove an entry. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< whiteListRemove"); +} // whiteListRemove + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index ecf5e6c7..c6383720 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -36,6 +36,8 @@ class BLEDevice { static void setPower(esp_power_level_t powerLevel); // Set our power level. static void setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a characteristic on a service on a server. static std::string toString(); // Return a string representation of our device. + static void whiteListAdd(BLEAddress address); // Add an entry to the BLE white list. + static void whiteListRemove(BLEAddress address); // Remove an entry from the BLE white list. private: static BLEServer *m_pServer; From dcfdd26ba998f52cf08ac69b595773c0aa117bd8 Mon Sep 17 00:00:00 2001 From: chegewara Date: Mon, 18 Dec 2017 20:04:50 +0100 Subject: [PATCH 188/381] Introduced BLE security --- cpp_utils/BLECharacteristic.cpp | 27 ++++-- cpp_utils/BLECharacteristic.h | 4 + cpp_utils/BLEClient.cpp | 50 +++++++++++ cpp_utils/BLEClient.h | 4 + cpp_utils/BLESecurity.cpp | 91 ++++++++++++++++++++ cpp_utils/BLESecurity.h | 60 +++++++++++++ cpp_utils/BLEServer.cpp | 64 +++++++++++++- cpp_utils/BLEServer.h | 8 ++ cpp_utils/SampleSecureClient.cpp | 141 +++++++++++++++++++++++++++++++ cpp_utils/SampleSecureServer.cpp | 88 +++++++++++++++++++ 10 files changed, 529 insertions(+), 8 deletions(-) create mode 100644 cpp_utils/BLESecurity.cpp create mode 100644 cpp_utils/BLESecurity.h create mode 100644 cpp_utils/SampleSecureClient.cpp create mode 100644 cpp_utils/SampleSecureServer.cpp diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 5754c488..9e9aa5c6 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -107,7 +107,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { esp_err_t errRc = ::esp_ble_gatts_add_char( m_pService->getHandle(), getUUID().getNative(), - static_cast(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), + static_cast(m_permissions), getProperties(), //&value, nullptr, @@ -163,6 +163,9 @@ uint16_t BLECharacteristic::getHandle() { return m_handle; } // getHandle +void BLECharacteristic::setAccessPermissions(esp_gatt_perm_t perm) { + m_permissions = perm; +} esp_gatt_char_prop_t BLECharacteristic::getProperties() { return m_properties; @@ -348,12 +351,17 @@ void BLECharacteristic::handleGATTServerEvent( // The following code has deliberately not been factored to make it fewer statements because this would cloud the // the logic flow comprehension. // + uint16_t maxOffset = m_mtu - 1; + if (m_mtu > 512) + maxOffset = 512; + ESP_LOGI(LOG_TAG, "%d", m_mtu); + ESP_LOGI(LOG_TAG, "%d", maxOffset); if (param->read.need_rsp) { ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); esp_gatt_rsp_t rsp; std::string value = m_value.getValue(); if (param->read.is_long) { - if (value.length() - m_value.getReadOffset() < 22) { + if (value.length() - m_value.getReadOffset() < maxOffset) { // This is the last in the chain rsp.attr_value.len = value.length() - m_value.getReadOffset(); rsp.attr_value.offset = m_value.getReadOffset(); @@ -361,16 +369,16 @@ void BLECharacteristic::handleGATTServerEvent( m_value.setReadOffset(0); } else { // There will be more to come. - rsp.attr_value.len = 22; + rsp.attr_value.len = maxOffset; rsp.attr_value.offset = m_value.getReadOffset(); memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len); - m_value.setReadOffset(rsp.attr_value.offset + 22); + m_value.setReadOffset(rsp.attr_value.offset + maxOffset); } } else { - if (value.length() > 21) { + if (value.length()+1 > maxOffset) { // Too big for a single shot entry. - m_value.setReadOffset(22); - rsp.attr_value.len = 22; + m_value.setReadOffset(maxOffset); + rsp.attr_value.len = maxOffset; rsp.attr_value.offset = 0; memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len); } else { @@ -412,6 +420,7 @@ void BLECharacteristic::handleGATTServerEvent( } case ESP_GATTS_CONNECT_EVT: + m_mtu = 23; m_semaphoreConfEvt.give(); break; @@ -419,6 +428,10 @@ void BLECharacteristic::handleGATTServerEvent( m_semaphoreConfEvt.give(); break; + case ESP_GATTS_MTU_EVT : + m_mtu = param->mtu.mtu; + break; + default: { break; } // default diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 7bb54c64..a65bf121 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -13,6 +13,7 @@ #include #include "BLEUUID.h" #include +#include #include "BLEDescriptor.h" #include "BLEValue.h" #include "FreeRTOS.h" @@ -78,6 +79,7 @@ class BLECharacteristic { void setWriteNoResponseProperty(bool value); std::string toString(); uint16_t getHandle(); + void setAccessPermissions(esp_gatt_perm_t perm); static const uint32_t PROPERTY_READ = 1<<0; static const uint32_t PROPERTY_WRITE = 1<<1; @@ -100,6 +102,8 @@ class BLECharacteristic { BLECharacteristicCallbacks* m_pCallbacks; BLEService* m_pService; BLEValue m_value; + esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + uint16_t m_mtu = 23; void handleGATTServerEvent( esp_gatts_cb_event_t event, diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index f3987d08..3db0be64 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -168,6 +168,14 @@ void BLEClient::gattClientEventHandler( break; } // ESP_GATTC_DISCONNECT_EVT + case ESP_GATTS_CONNECT_EVT: { + //m_connId = param->connect.conn_id; // Save the connection id. + if(m_securityLevel){ + esp_ble_set_encryption(evtParam->connect.remote_bda, m_securityLevel); + //memcpy(m_remote_bda, param->connect.remote_bda, sizeof(m_remote_bda)); + } + break; + } // ESP_GATTS_CONNECT_EVT // // ESP_GATTC_OPEN_EVT @@ -411,6 +419,41 @@ void BLEClient::handleGAPEvent( break; } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); + // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); + assert(m_securityCallbacks!=nullptr); + // esp_ble_passkey_reply(m_remote_bda, true, m_securityCallbacks->onPassKeyRequest()); + break; + + /* + * TODO should we add white/black list comparison? + */ + case ESP_GAP_BLE_SEC_REQ_EVT: + /* send the positive(true) security response to the peer device to accept the security request. + If not accept the security request, should sent the security response with negative(false) accept value*/ + if(m_securityCallbacks!=nullptr) + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, m_securityCallbacks->onSecurityRequest()); + else + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, false); + break; + /* + * + */ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + ///show the passkey number to the user to input it in the peer deivce. + if(m_securityCallbacks!=nullptr) + m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); + break; + case ESP_GAP_BLE_KEY_EVT: + //shows the ble key info share with peer device to the user. + ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: + if(m_securityCallbacks!=nullptr) + m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); + break; + default: break; } @@ -448,6 +491,13 @@ void BLEClient::setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::s ESP_LOGD(LOG_TAG, "<< setValue"); } // setValue +void BLEClient::setEncryptionLevel(esp_ble_sec_act_t level) { + m_securityLevel = level; +} + +void BLEClient::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { + m_securityCallbacks = callbacks; +} /** * @brief Return a string representation of this client. diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index a60ed102..1a630dfe 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -49,6 +49,8 @@ class BLEClient { void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a given characteristic at a given service. + void setEncryptionLevel(esp_ble_sec_act_t level); + void setSecurityCallbacks(BLESecurityCallbacks* pCallbacks); std::string toString(); // Return a string representation of this client. @@ -80,6 +82,8 @@ class BLEClient { FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt"); std::map m_servicesMap; void clearServices(); // Clear any existing services. + esp_ble_sec_act_t m_securityLevel = (esp_ble_sec_act_t)0; + BLESecurityCallbacks* m_securityCallbacks = 0; }; // class BLEDevice diff --git a/cpp_utils/BLESecurity.cpp b/cpp_utils/BLESecurity.cpp new file mode 100644 index 00000000..f3e5bbe9 --- /dev/null +++ b/cpp_utils/BLESecurity.cpp @@ -0,0 +1,91 @@ +/* + * BLESecurity.cpp + * + * Created on: Dec 17, 2017 + * Author: chegewara + */ + +#include + +BLESecurity::BLESecurity() { +} + +BLESecurity::~BLESecurity() { +} +/* + * @brief Set requested authentication mode + */ +void BLESecurity::setAuthenticationMode(esp_ble_auth_req_t auth_req) { + m_authReq = auth_req; + esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &m_authReq, sizeof(uint8_t)); // <--- setup requested authentication mode +} +/* + * @brief Set our device IO capability to let end user perform authorization + * either by displaying or entering generated 6-digits pin code + */ +void BLESecurity::setCapability(esp_ble_io_cap_t iocap) { + m_iocap = iocap; + esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); +} +/* + * @brief Init encryption key by server + * @param key_size is value between 7 and 16 + */ +void BLESecurity::setInitEncryptionKey(uint8_t init_key, uint8_t key_size) { + m_initKey = init_key; + m_keySize = key_size; + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &m_initKey, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t)); // <--- setup encryption key max size +} + +/* + * @brief Init encryption key by client + * @param key_size is value between 7 and 16 + */ +void BLESecurity::setRespEncryptionKey(uint8_t resp_key, uint8_t key_size) { + m_respKey = resp_key; + m_keySize = key_size; + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &m_respKey, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t)); // <--- setup encryption key max size +} +/* + * @brief Debug function to display what keys are exchanged by peers + */ +char* BLESecurity::esp_key_type_to_str(esp_ble_key_type_t key_type) +{ + char *key_str = NULL; + switch(key_type) { + case ESP_LE_KEY_NONE: + key_str = (char*)"ESP_LE_KEY_NONE"; + break; + case ESP_LE_KEY_PENC: + key_str = (char*)"ESP_LE_KEY_PENC"; + break; + case ESP_LE_KEY_PID: + key_str = (char*)"ESP_LE_KEY_PID"; + break; + case ESP_LE_KEY_PCSRK: + key_str = (char*)"ESP_LE_KEY_PCSRK"; + break; + case ESP_LE_KEY_PLK: + key_str = (char*)"ESP_LE_KEY_PLK"; + break; + case ESP_LE_KEY_LLK: + key_str = (char*)"ESP_LE_KEY_LLK"; + break; + case ESP_LE_KEY_LENC: + key_str = (char*)"ESP_LE_KEY_LENC"; + break; + case ESP_LE_KEY_LID: + key_str = (char*)"ESP_LE_KEY_LID"; + break; + case ESP_LE_KEY_LCSRK: + key_str = (char*)"ESP_LE_KEY_LCSRK"; + break; + default: + key_str = (char*)"INVALID BLE KEY TYPE"; + break; + + } + return key_str; +} diff --git a/cpp_utils/BLESecurity.h b/cpp_utils/BLESecurity.h new file mode 100644 index 00000000..494a1b7c --- /dev/null +++ b/cpp_utils/BLESecurity.h @@ -0,0 +1,60 @@ +/* + * BLESecurity.h + * + * Created on: Dec 17, 2017 + * Author: chegewara + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESECURITY_H_ +#define COMPONENTS_CPP_UTILS_BLESECURITY_H_ +#include + +class BLESecurity { +public: + BLESecurity(); + virtual ~BLESecurity(); + void setAuthenticationMode(esp_ble_auth_req_t auth_req); + void setCapability(esp_ble_io_cap_t iocap); + void setInitEncryptionKey(uint8_t init_key, uint8_t key_size = 16); + void setRespEncryptionKey(uint8_t resp_key, uint8_t key_size = 16); + static char* esp_key_type_to_str(esp_ble_key_type_t key_type); + +private: + esp_ble_auth_req_t m_authReq; + esp_ble_io_cap_t m_iocap; + uint8_t m_initKey; + uint8_t m_respKey; + uint8_t m_keySize; +}; + +/* + * @brief Callbacks to handle GAP events related to authorization + */ +class BLESecurityCallbacks { +public: + virtual ~BLESecurityCallbacks() {}; + + /* + * @brief Its request from peer device to input authentication pin code displayed on peer device. + * It requires that our device is capable to input 6-digits code by end user + * @return Return 6-digits integer value from input device + */ + virtual uint32_t onPassKeyRequest() = 0; + /* + * @brief Provide us 6-digits code to perform authentication. + * It requires that our device is capable to display this code to end user + * @param + */ + virtual void onPassKeyNotify(uint32_t pass_key); + /* + * @brief Here we can make decision if we want to let negotiate authorization with peer device or not + * return Return true if we accept this peer device request + */ + virtual bool onSecurityRequest(); + /* + * Provide us information when authentication process is completed + */ + virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t); +}; + +#endif /* COMPONENTS_CPP_UTILS_BLESECURITY_H_ */ diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 75805135..a3ea7681 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +//#include #include "BLEDevice.h" #include "BLEServer.h" #include "BLEService.h" @@ -151,6 +151,53 @@ void BLEServer::handleGAPEvent( */ break; } + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + break; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); + break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); + break; + case ESP_GAP_BLE_NC_REQ_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); + esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); + assert(m_securityCallbacks!=nullptr); + esp_ble_passkey_reply(m_remote_bda, true, m_securityCallbacks->onPassKeyRequest()); + break; + + /* + * TODO should we add white/black list comparison? + */ + case ESP_GAP_BLE_SEC_REQ_EVT: + /* send the positive(true) security response to the peer device to accept the security request. + If not accept the security request, should sent the security response with negative(false) accept value*/ + if(m_securityCallbacks!=nullptr) + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, m_securityCallbacks->onSecurityRequest()); + else + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, false); + break; + /* + * + */ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + ///show the passkey number to the user to input it in the peer deivce. + if(m_securityCallbacks!=nullptr) + m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); + break; + case ESP_GAP_BLE_KEY_EVT: + //shows the ble key info share with peer device to the user. + ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: + if(m_securityCallbacks!=nullptr) + m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); + break; + default: break; } @@ -198,6 +245,10 @@ void BLEServer::handleGATTServerEvent( // case ESP_GATTS_CONNECT_EVT: { m_connId = param->connect.conn_id; // Save the connection id. + if(m_securityLevel){ + esp_ble_set_encryption(param->connect.remote_bda, m_securityLevel); + memcpy(m_remote_bda, param->connect.remote_bda, sizeof(m_remote_bda)); + } if (m_pServerCallbacks != nullptr) { m_pServerCallbacks->onConnect(this); } @@ -334,6 +385,17 @@ void BLEServer::startAdvertising() { ESP_LOGD(LOG_TAG, "<< startAdvertising"); } // startAdvertising +void BLEServer::setEncryptionLevel(esp_ble_sec_act_t level) { + m_securityLevel = level; +} + +uint32_t BLEServer::getPassKey() { + return m_securityPassKey; +} + +void BLEServer::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { + m_securityCallbacks = callbacks; +} void BLEServerCallbacks::onConnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 524e88f3..8d4554d2 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -18,6 +18,7 @@ #include "BLEAdvertising.h" #include "BLECharacteristic.h" #include "BLEService.h" +#include "BLESecurity.h" #include "FreeRTOS.h" class BLEServerCallbacks; @@ -57,6 +58,9 @@ class BLEServer { BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); + void setEncryptionLevel(esp_ble_sec_act_t level); + uint32_t getPassKey(); + void setSecurityCallbacks(BLESecurityCallbacks* pCallbacks); private: @@ -74,6 +78,10 @@ class BLEServer { FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); BLEServiceMap m_serviceMap; BLEServerCallbacks* m_pServerCallbacks; + esp_ble_sec_act_t m_securityLevel = (esp_ble_sec_act_t)0; + esp_bd_addr_t m_remote_bda; + uint32_t m_securityPassKey; + BLESecurityCallbacks* m_securityCallbacks; void createApp(uint16_t appId); uint16_t getConnId(); diff --git a/cpp_utils/SampleSecureClient.cpp b/cpp_utils/SampleSecureClient.cpp new file mode 100644 index 00000000..457988ed --- /dev/null +++ b/cpp_utils/SampleSecureClient.cpp @@ -0,0 +1,141 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onSecurityRequest(){ + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + pClient->setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + pClient->setSecurityCallbacks(new MySecurity()); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleSecureClient(void) { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(15); +} // SampleClient diff --git a/cpp_utils/SampleSecureServer.cpp b/cpp_utils/SampleSecureServer.cpp new file mode 100644 index 00000000..97c9037f --- /dev/null +++ b/cpp_utils/SampleSecureServer.cpp @@ -0,0 +1,88 @@ +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onSecurityRequest(){ + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); + pServer->setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + pServer->setSecurityCallbacks(new MySecurity()); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); + pSecurity->setCapability(ESP_IO_CAP_NONE); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(1000000); + } +}; + + +void SampleSecureServer(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main From d05c381022300f497df0d66cb6f8f9b1f72dbf9a Mon Sep 17 00:00:00 2001 From: chegewara Date: Mon, 18 Dec 2017 22:54:47 +0100 Subject: [PATCH 189/381] Introduce BLE HID class --- cpp_utils/BLEHIDDevice.cpp | 154 +++++++ cpp_utils/BLEHIDDevice.h | 87 ++++ cpp_utils/HIDTypes.h | 91 ++++ cpp_utils/tests/BLETests/SampleHIDDevice.cpp | 220 ++++++++++ .../tests/BLETests/SampleKeyboardTypes.h | 402 ++++++++++++++++++ .../BLETests}/SampleSecureClient.cpp | 2 +- .../BLETests}/SampleSecureServer.cpp | 0 7 files changed, 955 insertions(+), 1 deletion(-) create mode 100644 cpp_utils/BLEHIDDevice.cpp create mode 100644 cpp_utils/BLEHIDDevice.h create mode 100644 cpp_utils/HIDTypes.h create mode 100644 cpp_utils/tests/BLETests/SampleHIDDevice.cpp create mode 100644 cpp_utils/tests/BLETests/SampleKeyboardTypes.h rename cpp_utils/{ => tests/BLETests}/SampleSecureClient.cpp (98%) rename cpp_utils/{ => tests/BLETests}/SampleSecureServer.cpp (100%) diff --git a/cpp_utils/BLEHIDDevice.cpp b/cpp_utils/BLEHIDDevice.cpp new file mode 100644 index 00000000..c5485a81 --- /dev/null +++ b/cpp_utils/BLEHIDDevice.cpp @@ -0,0 +1,154 @@ +/* + * BLEHIDDevice.cpp + * + * Created on: Dec 18, 2017 + * Author: chegewara + */ +//#include "BLEUUID.h" +#include "BLEHIDDevice.h" + +BLEHIDDevice::BLEHIDDevice(BLEServer* server) { + m_deviceInfoService = server->createService(BLEUUID((uint16_t) 0x180a)); + m_hidService = server->createService(BLEUUID((uint16_t) 0x1812), 40); + //m_batteryService = server->createService(BLEUUID((uint16_t) 0x180f)); + createDescriptors(); + createCharacteristics(); +} + +BLEHIDDevice::~BLEHIDDevice() { + // TODO Auto-generated destructor stub +} + +void BLEHIDDevice::setReportMap(uint8_t* map, uint16_t size) { + m_reportMapCharacteristic->setValue(map, size); +} + +void BLEHIDDevice::createDescriptors() { + m_inputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + const uint8_t desc1_val[] = {0x01}; + m_inputReportDescriptor->setValue((uint8_t*)desc1_val, 1); + m_inputReportNotifications = new BLE2902(); + + m_outputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + const uint8_t desc2_val[] = {0x02}; + m_outputReportDescriptor->setValue((uint8_t*)desc2_val, 1); + + m_featureReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + const uint8_t desc3_val[] = {0x03}; + m_featureReportDescriptor->setValue((uint8_t*)desc3_val, 1); + + m_bootInputNotifications = new BLE2902(); + + if(m_batteryService != nullptr){ + m_batteryLevelDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2904)); + m_batteryLevelNotifications = new BLE2902(); + } +} + +void BLEHIDDevice::createCharacteristics() { + m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a29, BLECharacteristic::PROPERTY_READ); + m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a50, BLECharacteristic::PROPERTY_READ); + + m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4a, BLECharacteristic::PROPERTY_READ); + m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4b, BLECharacteristic::PROPERTY_READ); + m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4c, BLECharacteristic::PROPERTY_WRITE_NR); + m_inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + m_outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + m_featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); + m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4e, BLECharacteristic::PROPERTY_WRITE_NR); + m_bootInputCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a22, BLECharacteristic::PROPERTY_NOTIFY); + m_bootOutputCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a32, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); + + m_inputReportCharacteristic->addDescriptor(m_inputReportDescriptor); + m_inputReportCharacteristic->addDescriptor(m_inputReportNotifications); + m_outputReportCharacteristic->addDescriptor(m_outputReportDescriptor); + m_featureReportCharacteristic->addDescriptor(m_featureReportDescriptor); + m_bootInputCharacteristic->addDescriptor(m_bootInputNotifications); + if(m_batteryService != nullptr){ + m_batteryLevelCharacteristic->addDescriptor(m_batteryLevelDescriptor); //OPTIONAL? + m_batteryLevelCharacteristic->addDescriptor(m_batteryLevelNotifications); //OPTIONAL? + } +} + +void BLEHIDDevice::startServices() { + m_deviceInfoService->start(); + m_hidService->start(); + if(m_batteryService!=nullptr) + m_batteryService->start(); +} + +BLEService* BLEHIDDevice::deviceInfo() { + return m_deviceInfoService; +} + +BLEService* BLEHIDDevice::hidService() { + return m_hidService; +} + +BLEService* BLEHIDDevice::batteryService() { + return m_batteryService; +} + +BLECharacteristic* BLEHIDDevice::manufacturer() { + return m_manufacturerCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::pnp() { + return m_pnpCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::hidInfo() { + return m_hidInfoCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::reportMap() { + return m_reportMapCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::hidControl() { + return m_hidControlCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::inputReport(void*) { + return m_inputReportCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::outputReport(void*) { + return m_outputReportCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::featureReport(void*) { + return m_featureReportCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::protocolMode() { + return m_protocolModeCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::bootInput() { + return m_bootInputCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::bootOutput() { + return m_bootOutputCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::batteryLevel(void*) { + return m_batteryLevelCharacteristic; +} + +BLEDescriptor* BLEHIDDevice::inputReport() { + return m_inputReportDescriptor; +} + +BLEDescriptor* BLEHIDDevice::outputReport() { + return m_outputReportDescriptor; +} + +BLEDescriptor* BLEHIDDevice::featureReport() { + return m_featureReportDescriptor; +} + +BLEDescriptor* BLEHIDDevice::batteryLevel() { + return m_batteryLevelDescriptor; +} diff --git a/cpp_utils/BLEHIDDevice.h b/cpp_utils/BLEHIDDevice.h new file mode 100644 index 00000000..43e4ccdc --- /dev/null +++ b/cpp_utils/BLEHIDDevice.h @@ -0,0 +1,87 @@ +/* + * BLEHIDDevice.h + * + * Created on: Dec 18, 2017 + * Author: chegewara + */ + +#ifndef _BLEHIDDEVICE_H_ +#define _BLEHIDDEVICE_H_ +#include "BLECharacteristic.h" +#include "BLEService.h" +#include "BLEDescriptor.h" +#include "BLE2902.h" +#include "HIDTypes.h" + +#define GENERIC_HID 960 +#define HID_KEYBOARD 961 +#define HID_MOUSE 962 +#define HID_JOYSTICK 963 +#define HID_GAMEPAD 964 +#define HID_TABLET 965 +#define HID_CARD_READER 966 +#define HID_DIGITAL_PEN 967 +#define HID_BARCODE 968 + +class BLEHIDDevice { +public: + BLEHIDDevice(BLEServer*); + virtual ~BLEHIDDevice(); + + void setReportMap(uint8_t* map, uint16_t); + void startServices(); + + BLEService* deviceInfo(); + BLEService* hidService(); + BLEService* batteryService(); + + BLECharacteristic* manufacturer(); + BLECharacteristic* pnp(); + BLECharacteristic* hidInfo(); + BLECharacteristic* reportMap(); + BLECharacteristic* hidControl(); + BLECharacteristic* inputReport(void*); + BLECharacteristic* outputReport(void*); + BLECharacteristic* featureReport(void*); + BLECharacteristic* protocolMode(); + BLECharacteristic* bootInput(); + BLECharacteristic* bootOutput(); + BLECharacteristic* batteryLevel(void*); + + BLEDescriptor* inputReport(); + BLEDescriptor* outputReport(); + BLEDescriptor* featureReport(); + BLEDescriptor* batteryLevel(); + +private: + void createCharacteristics(); + void createDescriptors(); + + BLEService* m_deviceInfoService; //0x180a + BLEService* m_hidService; //0x1812 + BLEService* m_batteryService = 0; //0x180f + + BLECharacteristic* m_manufacturerCharacteristic; //0x2a29 + BLECharacteristic* m_pnpCharacteristic; //0x2a50 + BLECharacteristic* m_hidInfoCharacteristic; //0x2a4a + BLECharacteristic* m_reportMapCharacteristic; //0x2a4b + BLECharacteristic* m_hidControlCharacteristic; //0x2a4c + BLECharacteristic* m_inputReportCharacteristic; //0x2a4d + BLECharacteristic* m_outputReportCharacteristic; //0x2a4d + BLECharacteristic* m_featureReportCharacteristic; //0x2a4d + BLECharacteristic* m_protocolModeCharacteristic; //0x2a4e + BLECharacteristic* m_bootInputCharacteristic; //0x2a22 + BLECharacteristic* m_bootOutputCharacteristic; //0x2a32 + BLECharacteristic* m_batteryLevelCharacteristic; //0x2a19 + + BLEDescriptor* m_inputReportDescriptor; //0x2908 + BLEDescriptor* m_outputReportDescriptor; //0x2908 + BLEDescriptor* m_featureReportDescriptor; //0x2908 + BLE2902* m_inputReportNotifications; //0x2902 + BLE2902* m_bootInputNotifications; //0x2902 + BLEDescriptor* m_batteryLevelDescriptor; //0x2904 + BLE2902* m_batteryLevelNotifications; //0x2902 + +}; + +#endif /* _BLEHIDDEVICE_H_ */ diff --git a/cpp_utils/HIDTypes.h b/cpp_utils/HIDTypes.h new file mode 100644 index 00000000..b8b181be --- /dev/null +++ b/cpp_utils/HIDTypes.h @@ -0,0 +1,91 @@ +/* Copyright (c) 2010-2011 mbed.org, MIT License +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of this software +* and associated documentation files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all copies or +* substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef USBCLASS_HID_TYPES +#define USBCLASS_HID_TYPES + +#include + +/* */ +#define HID_VERSION_1_11 (0x0111) + +/* HID Class */ +#define HID_CLASS (3) +#define HID_SUBCLASS_NONE (0) +#define HID_PROTOCOL_NONE (0) + +/* Descriptors */ +#define HID_DESCRIPTOR (33) +#define HID_DESCRIPTOR_LENGTH (0x09) +#define REPORT_DESCRIPTOR (34) + +/* Class requests */ +#define GET_REPORT (0x1) +#define GET_IDLE (0x2) +#define SET_REPORT (0x9) +#define SET_IDLE (0xa) + +/* HID Class Report Descriptor */ +/* Short items: size is 0, 1, 2 or 3 specifying 0, 1, 2 or 4 (four) bytes */ +/* of data as per HID Class standard */ + +/* Main items */ +#define INPUT(size) (0x80 | size) +#define OUTPUT(size) (0x90 | size) +#define FEATURE(size) (0xb0 | size) +#define COLLECTION(size) (0xa0 | size) +#define END_COLLECTION(size) (0xc0 | size) + +/* Global items */ +#define USAGE_PAGE(size) (0x04 | size) +#define LOGICAL_MINIMUM(size) (0x14 | size) +#define LOGICAL_MAXIMUM(size) (0x24 | size) +#define PHYSICAL_MINIMUM(size) (0x34 | size) +#define PHYSICAL_MAXIMUM(size) (0x44 | size) +#define UNIT_EXPONENT(size) (0x54 | size) +#define UNIT(size) (0x64 | size) +#define REPORT_SIZE(size) (0x74 | size) +#define REPORT_ID(size) (0x84 | size) +#define REPORT_COUNT(size) (0x94 | size) +#define PUSH(size) (0xa4 | size) +#define POP(size) (0xb4 | size) + +/* Local items */ +#define USAGE(size) (0x08 | size) +#define USAGE_MINIMUM(size) (0x18 | size) +#define USAGE_MAXIMUM(size) (0x28 | size) +#define DESIGNATOR_INDEX(size) (0x38 | size) +#define DESIGNATOR_MINIMUM(size) (0x48 | size) +#define DESIGNATOR_MAXIMUM(size) (0x58 | size) +#define STRING_INDEX(size) (0x78 | size) +#define STRING_MINIMUM(size) (0x88 | size) +#define STRING_MAXIMUM(size) (0x98 | size) +#define DELIMITER(size) (0xa8 | size) + +/* HID Report */ +/* Where report IDs are used the first byte of 'data' will be the */ +/* report ID and 'length' will include this report ID byte. */ + +#define MAX_HID_REPORT_SIZE (64) + +typedef struct { + uint32_t length; + uint8_t data[MAX_HID_REPORT_SIZE]; +} HID_REPORT; + +#endif diff --git a/cpp_utils/tests/BLETests/SampleHIDDevice.cpp b/cpp_utils/tests/BLETests/SampleHIDDevice.cpp new file mode 100644 index 00000000..c3a4df1a --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleHIDDevice.cpp @@ -0,0 +1,220 @@ +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include "BLEHIDDevice.h" +#include "SampleKeyboardTypes.h" +#include +#include +#include + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleHIDDevice"; + +static BLEHIDDevice* hid; + +class MyTask : public Task { + void run(void*){ + vTaskDelay(10000); + const char* hello = "Hello world from esp32 hid keyboard!!!"; + while(*hello){ + KEYMAP map = keymap[(uint8_t)*hello]; + uint8_t a[] = {map.modifier, 0x0, map.usage, 0x0,0x0,0x0,0x0,0x0}; + hid->inputReport(NULL)->setValue(a,sizeof(a)); + hid->inputReport(NULL)->notify(); + + hello++; + } + uint8_t v[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0}; + hid->inputReport(NULL)->setValue(v, sizeof(v)); + hid->inputReport(NULL)->notify(); + vTaskDelete(NULL); + } +}; + MyTask *task; + + class MyCallbacks : public BLEServerCallbacks { + void onConnect(BLEServer* pServer){ + task->start(); + } + + void onDisconnect(BLEServer* pServer){ + + } + }; +uint32_t passKey = 0; + class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGE(LOG_TAG, "The passkey request %d", passKey); + + vTaskDelay(25000); + return passKey; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + passKey = pass_key; + } + bool onSecurityRequest(){ + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } + }; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + + task = new MyTask(); + BLEDevice::init("ESP32"); + BLEServer *pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyCallbacks()); + pServer->setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); + pServer->setSecurityCallbacks(new MySecurity()); + + /* + * Instantiate hid device + */ + hid = new BLEHIDDevice(pServer); + + /* + * Set manufacturer name + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.manufacturer_name_string.xml + */ + std::string name = "esp-community"; + hid->manufacturer()->setValue(name); + + /* + * Set pnp parameters + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.pnp_id.xml + */ + const uint8_t pnp[] = {0x01,0x02,0xe5,0xab,0xcd,0x01,0x10}; + hid->pnp()->setValue((uint8_t*)pnp, sizeof(pnp)); + + /* + * Set hid informations + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.hid_information.xml + */ + const uint8_t val1[] = {0x01,0x11,0x00,0x03}; + hid->hidInfo()->setValue((uint8_t*)val1, 4); + + /* + * Mouse + */ + const uint8_t reportMap2[] = { + USAGE_PAGE(1), 0x01, + USAGE(1), 0x02, + COLLECTION(1), 0x01, + USAGE(1), 0x01, + COLLECTION(1), 0x00, + USAGE_PAGE(1), 0x09, + USAGE_MINIMUM(1), 0x1, + USAGE_MAXIMUM(1), 0x3, + LOGICAL_MINIMUM(1), 0x0, + LOGICAL_MAXIMUM(1), 0x1, + REPORT_COUNT(1), 0x3, + REPORT_SIZE(1), 0x1, + INPUT(1), 0x2, // (Data, Variable, Absolute), ;3 button bits + REPORT_COUNT(1), 0x1, + REPORT_SIZE(1), 0x5, + INPUT(1), 0x1, //(Constant), ;5 bit padding + USAGE_PAGE(1), 0x1, //(Generic Desktop), + USAGE(1), 0x30, + USAGE(1), 0x31, + LOGICAL_MINIMUM(1), 0x81, + LOGICAL_MAXIMUM(1), 0x7f, + REPORT_SIZE(1), 0x8, + REPORT_COUNT(1), 0x2, + INPUT(1), 0x6, //(Data, Variable, Relative), ;2 position bytes (X & Y) + END_COLLECTION(0), + END_COLLECTION(0) + }; + /* + * Keyboard + */ + const uint8_t reportMap[] = { + USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls + USAGE(1), 0x06, // Keyboard + COLLECTION(1), 0x01, // Application + USAGE_PAGE(1), 0x07, // Kbrd/Keypad + USAGE_MINIMUM(1), 0xE0, + USAGE_MAXIMUM(1), 0xE7, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x01, + REPORT_SIZE(1), 0x01, // 1 byte (Modifier) + REPORT_COUNT(1), 0x08, + INPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position + REPORT_COUNT(1), 0x01, // 1 byte (Reserved) + REPORT_SIZE(1), 0x08, + INPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position + REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana) + REPORT_SIZE(1), 0x01, + USAGE_PAGE(1), 0x08, // LEDs + USAGE_MINIMUM(1), 0x01, // Num Lock + USAGE_MAXIMUM(1), 0x05, // Kana + OUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile + REPORT_COUNT(1), 0x01, // 3 bits (Padding) + REPORT_SIZE(1), 0x03, + OUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile + REPORT_COUNT(1), 0x06, // 6 bytes (Keys) + REPORT_SIZE(1), 0x08, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x65, // 101 keys + USAGE_PAGE(1), 0x07, // Kbrd/Keypad + USAGE_MINIMUM(1), 0x00, + USAGE_MAXIMUM(1), 0x65, + INPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position + END_COLLECTION(0) + }; + /* + * Set report map (here is initialized device driver on client side) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.report_map.xml + */ + hid->setReportMap((uint8_t*)reportMap, sizeof(reportMap)); + + /* + * We are prepared to start hid device services. Before this point we can change all values and/or set parameters we need. + * Also before we start, if we want to provide battery info, we need to prepare battery service. + * We can setup characteristics authorization + */ + hid->startServices(); + + /* + * Its good to setup advertising by providing appearance and advertised service. This will let clients find our device by type + */ + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->setAppearance(HID_KEYBOARD); + pAdvertising->addServiceUUID(hid->hidService()->getUUID()); + pAdvertising->start(); + + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); + pSecurity->setCapability(ESP_IO_CAP_NONE); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(1000000); + } +}; + + +void SampleHID(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main diff --git a/cpp_utils/tests/BLETests/SampleKeyboardTypes.h b/cpp_utils/tests/BLETests/SampleKeyboardTypes.h new file mode 100644 index 00000000..ef48a526 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleKeyboardTypes.h @@ -0,0 +1,402 @@ +/* Copyright (c) 2015 mbed.org, MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Note: this file was pulled from different parts of the USBHID library, in mbed SDK + */ + +#ifndef KEYBOARD_DEFS_H +#define KEYBOARD_DEFS_H + +#define REPORT_ID_KEYBOARD 1 +#define REPORT_ID_VOLUME 3 + +/* Modifiers */ +enum MODIFIER_KEY { + KEY_CTRL = 1, + KEY_SHIFT = 2, + KEY_ALT = 4, +}; + + +enum MEDIA_KEY { + KEY_NEXT_TRACK, /*!< next Track Button */ + KEY_PREVIOUS_TRACK, /*!< Previous track Button */ + KEY_STOP, /*!< Stop Button */ + KEY_PLAY_PAUSE, /*!< Play/Pause Button */ + KEY_MUTE, /*!< Mute Button */ + KEY_VOLUME_UP, /*!< Volume Up Button */ + KEY_VOLUME_DOWN, /*!< Volume Down Button */ +}; + +enum FUNCTION_KEY { + KEY_F1 = 128, /* F1 key */ + KEY_F2, /* F2 key */ + KEY_F3, /* F3 key */ + KEY_F4, /* F4 key */ + KEY_F5, /* F5 key */ + KEY_F6, /* F6 key */ + KEY_F7, /* F7 key */ + KEY_F8, /* F8 key */ + KEY_F9, /* F9 key */ + KEY_F10, /* F10 key */ + KEY_F11, /* F11 key */ + KEY_F12, /* F12 key */ + + KEY_PRINT_SCREEN, /* Print Screen key */ + KEY_SCROLL_LOCK, /* Scroll lock */ + KEY_CAPS_LOCK, /* caps lock */ + KEY_NUM_LOCK, /* num lock */ + KEY_INSERT, /* Insert key */ + KEY_HOME, /* Home key */ + KEY_PAGE_UP, /* Page Up key */ + KEY_PAGE_DOWN, /* Page Down key */ + + RIGHT_ARROW, /* Right arrow */ + LEFT_ARROW, /* Left arrow */ + DOWN_ARROW, /* Down arrow */ + UP_ARROW, /* Up arrow */ +}; + +typedef struct { + unsigned char usage; + unsigned char modifier; +} KEYMAP; + +#ifdef US_KEYBOARD +/* US keyboard (as HID standard) */ +#define KEYMAP_SIZE (152) +const KEYMAP keymap[KEYMAP_SIZE] = { + {0, 0}, /* NUL */ + {0, 0}, /* SOH */ + {0, 0}, /* STX */ + {0, 0}, /* ETX */ + {0, 0}, /* EOT */ + {0, 0}, /* ENQ */ + {0, 0}, /* ACK */ + {0, 0}, /* BEL */ + {0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */ + {0x2b, 0}, /* TAB */ /* Keyboard Tab */ + {0x28, 0}, /* LF */ /* Keyboard Return (Enter) */ + {0, 0}, /* VT */ + {0, 0}, /* FF */ + {0, 0}, /* CR */ + {0, 0}, /* SO */ + {0, 0}, /* SI */ + {0, 0}, /* DEL */ + {0, 0}, /* DC1 */ + {0, 0}, /* DC2 */ + {0, 0}, /* DC3 */ + {0, 0}, /* DC4 */ + {0, 0}, /* NAK */ + {0, 0}, /* SYN */ + {0, 0}, /* ETB */ + {0, 0}, /* CAN */ + {0, 0}, /* EM */ + {0, 0}, /* SUB */ + {0, 0}, /* ESC */ + {0, 0}, /* FS */ + {0, 0}, /* GS */ + {0, 0}, /* RS */ + {0, 0}, /* US */ + {0x2c, 0}, /* */ + {0x1e, KEY_SHIFT}, /* ! */ + {0x34, KEY_SHIFT}, /* " */ + {0x20, KEY_SHIFT}, /* # */ + {0x21, KEY_SHIFT}, /* $ */ + {0x22, KEY_SHIFT}, /* % */ + {0x24, KEY_SHIFT}, /* & */ + {0x34, 0}, /* ' */ + {0x26, KEY_SHIFT}, /* ( */ + {0x27, KEY_SHIFT}, /* ) */ + {0x25, KEY_SHIFT}, /* * */ + {0x2e, KEY_SHIFT}, /* + */ + {0x36, 0}, /* , */ + {0x2d, 0}, /* - */ + {0x37, 0}, /* . */ + {0x38, 0}, /* / */ + {0x27, 0}, /* 0 */ + {0x1e, 0}, /* 1 */ + {0x1f, 0}, /* 2 */ + {0x20, 0}, /* 3 */ + {0x21, 0}, /* 4 */ + {0x22, 0}, /* 5 */ + {0x23, 0}, /* 6 */ + {0x24, 0}, /* 7 */ + {0x25, 0}, /* 8 */ + {0x26, 0}, /* 9 */ + {0x33, KEY_SHIFT}, /* : */ + {0x33, 0}, /* ; */ + {0x36, KEY_SHIFT}, /* < */ + {0x2e, 0}, /* = */ + {0x37, KEY_SHIFT}, /* > */ + {0x38, KEY_SHIFT}, /* ? */ + {0x1f, KEY_SHIFT}, /* @ */ + {0x04, KEY_SHIFT}, /* A */ + {0x05, KEY_SHIFT}, /* B */ + {0x06, KEY_SHIFT}, /* C */ + {0x07, KEY_SHIFT}, /* D */ + {0x08, KEY_SHIFT}, /* E */ + {0x09, KEY_SHIFT}, /* F */ + {0x0a, KEY_SHIFT}, /* G */ + {0x0b, KEY_SHIFT}, /* H */ + {0x0c, KEY_SHIFT}, /* I */ + {0x0d, KEY_SHIFT}, /* J */ + {0x0e, KEY_SHIFT}, /* K */ + {0x0f, KEY_SHIFT}, /* L */ + {0x10, KEY_SHIFT}, /* M */ + {0x11, KEY_SHIFT}, /* N */ + {0x12, KEY_SHIFT}, /* O */ + {0x13, KEY_SHIFT}, /* P */ + {0x14, KEY_SHIFT}, /* Q */ + {0x15, KEY_SHIFT}, /* R */ + {0x16, KEY_SHIFT}, /* S */ + {0x17, KEY_SHIFT}, /* T */ + {0x18, KEY_SHIFT}, /* U */ + {0x19, KEY_SHIFT}, /* V */ + {0x1a, KEY_SHIFT}, /* W */ + {0x1b, KEY_SHIFT}, /* X */ + {0x1c, KEY_SHIFT}, /* Y */ + {0x1d, KEY_SHIFT}, /* Z */ + {0x2f, 0}, /* [ */ + {0x31, 0}, /* \ */ + {0x30, 0}, /* ] */ + {0x23, KEY_SHIFT}, /* ^ */ + {0x2d, KEY_SHIFT}, /* _ */ + {0x35, 0}, /* ` */ + {0x04, 0}, /* a */ + {0x05, 0}, /* b */ + {0x06, 0}, /* c */ + {0x07, 0}, /* d */ + {0x08, 0}, /* e */ + {0x09, 0}, /* f */ + {0x0a, 0}, /* g */ + {0x0b, 0}, /* h */ + {0x0c, 0}, /* i */ + {0x0d, 0}, /* j */ + {0x0e, 0}, /* k */ + {0x0f, 0}, /* l */ + {0x10, 0}, /* m */ + {0x11, 0}, /* n */ + {0x12, 0}, /* o */ + {0x13, 0}, /* p */ + {0x14, 0}, /* q */ + {0x15, 0}, /* r */ + {0x16, 0}, /* s */ + {0x17, 0}, /* t */ + {0x18, 0}, /* u */ + {0x19, 0}, /* v */ + {0x1a, 0}, /* w */ + {0x1b, 0}, /* x */ + {0x1c, 0}, /* y */ + {0x1d, 0}, /* z */ + {0x2f, KEY_SHIFT}, /* { */ + {0x31, KEY_SHIFT}, /* | */ + {0x30, KEY_SHIFT}, /* } */ + {0x35, KEY_SHIFT}, /* ~ */ + {0,0}, /* DEL */ + + {0x3a, 0}, /* F1 */ + {0x3b, 0}, /* F2 */ + {0x3c, 0}, /* F3 */ + {0x3d, 0}, /* F4 */ + {0x3e, 0}, /* F5 */ + {0x3f, 0}, /* F6 */ + {0x40, 0}, /* F7 */ + {0x41, 0}, /* F8 */ + {0x42, 0}, /* F9 */ + {0x43, 0}, /* F10 */ + {0x44, 0}, /* F11 */ + {0x45, 0}, /* F12 */ + + {0x46, 0}, /* PRINT_SCREEN */ + {0x47, 0}, /* SCROLL_LOCK */ + {0x39, 0}, /* CAPS_LOCK */ + {0x53, 0}, /* NUM_LOCK */ + {0x49, 0}, /* INSERT */ + {0x4a, 0}, /* HOME */ + {0x4b, 0}, /* PAGE_UP */ + {0x4e, 0}, /* PAGE_DOWN */ + + {0x4f, 0}, /* RIGHT_ARROW */ + {0x50, 0}, /* LEFT_ARROW */ + {0x51, 0}, /* DOWN_ARROW */ + {0x52, 0}, /* UP_ARROW */ +}; + +#else +/* UK keyboard */ +#define KEYMAP_SIZE (152) +const KEYMAP keymap[KEYMAP_SIZE] = { + {0, 0}, /* NUL */ + {0, 0}, /* SOH */ + {0, 0}, /* STX */ + {0, 0}, /* ETX */ + {0, 0}, /* EOT */ + {0, 0}, /* ENQ */ + {0, 0}, /* ACK */ + {0, 0}, /* BEL */ + {0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */ + {0x2b, 0}, /* TAB */ /* Keyboard Tab */ + {0x28, 0}, /* LF */ /* Keyboard Return (Enter) */ + {0, 0}, /* VT */ + {0, 0}, /* FF */ + {0, 0}, /* CR */ + {0, 0}, /* SO */ + {0, 0}, /* SI */ + {0, 0}, /* DEL */ + {0, 0}, /* DC1 */ + {0, 0}, /* DC2 */ + {0, 0}, /* DC3 */ + {0, 0}, /* DC4 */ + {0, 0}, /* NAK */ + {0, 0}, /* SYN */ + {0, 0}, /* ETB */ + {0, 0}, /* CAN */ + {0, 0}, /* EM */ + {0, 0}, /* SUB */ + {0, 0}, /* ESC */ + {0, 0}, /* FS */ + {0, 0}, /* GS */ + {0, 0}, /* RS */ + {0, 0}, /* US */ + {0x2c, 0}, /* */ + {0x1e, KEY_SHIFT}, /* ! */ + {0x1f, KEY_SHIFT}, /* " */ + {0x32, 0}, /* # */ + {0x21, KEY_SHIFT}, /* $ */ + {0x22, KEY_SHIFT}, /* % */ + {0x24, KEY_SHIFT}, /* & */ + {0x34, 0}, /* ' */ + {0x26, KEY_SHIFT}, /* ( */ + {0x27, KEY_SHIFT}, /* ) */ + {0x25, KEY_SHIFT}, /* * */ + {0x2e, KEY_SHIFT}, /* + */ + {0x36, 0}, /* , */ + {0x2d, 0}, /* - */ + {0x37, 0}, /* . */ + {0x38, 0}, /* / */ + {0x27, 0}, /* 0 */ + {0x1e, 0}, /* 1 */ + {0x1f, 0}, /* 2 */ + {0x20, 0}, /* 3 */ + {0x21, 0}, /* 4 */ + {0x22, 0}, /* 5 */ + {0x23, 0}, /* 6 */ + {0x24, 0}, /* 7 */ + {0x25, 0}, /* 8 */ + {0x26, 0}, /* 9 */ + {0x33, KEY_SHIFT}, /* : */ + {0x33, 0}, /* ; */ + {0x36, KEY_SHIFT}, /* < */ + {0x2e, 0}, /* = */ + {0x37, KEY_SHIFT}, /* > */ + {0x38, KEY_SHIFT}, /* ? */ + {0x34, KEY_SHIFT}, /* @ */ + {0x04, KEY_SHIFT}, /* A */ + {0x05, KEY_SHIFT}, /* B */ + {0x06, KEY_SHIFT}, /* C */ + {0x07, KEY_SHIFT}, /* D */ + {0x08, KEY_SHIFT}, /* E */ + {0x09, KEY_SHIFT}, /* F */ + {0x0a, KEY_SHIFT}, /* G */ + {0x0b, KEY_SHIFT}, /* H */ + {0x0c, KEY_SHIFT}, /* I */ + {0x0d, KEY_SHIFT}, /* J */ + {0x0e, KEY_SHIFT}, /* K */ + {0x0f, KEY_SHIFT}, /* L */ + {0x10, KEY_SHIFT}, /* M */ + {0x11, KEY_SHIFT}, /* N */ + {0x12, KEY_SHIFT}, /* O */ + {0x13, KEY_SHIFT}, /* P */ + {0x14, KEY_SHIFT}, /* Q */ + {0x15, KEY_SHIFT}, /* R */ + {0x16, KEY_SHIFT}, /* S */ + {0x17, KEY_SHIFT}, /* T */ + {0x18, KEY_SHIFT}, /* U */ + {0x19, KEY_SHIFT}, /* V */ + {0x1a, KEY_SHIFT}, /* W */ + {0x1b, KEY_SHIFT}, /* X */ + {0x1c, KEY_SHIFT}, /* Y */ + {0x1d, KEY_SHIFT}, /* Z */ + {0x2f, 0}, /* [ */ + {0x64, 0}, /* \ */ + {0x30, 0}, /* ] */ + {0x23, KEY_SHIFT}, /* ^ */ + {0x2d, KEY_SHIFT}, /* _ */ + {0x35, 0}, /* ` */ + {0x04, 0}, /* a */ + {0x05, 0}, /* b */ + {0x06, 0}, /* c */ + {0x07, 0}, /* d */ + {0x08, 0}, /* e */ + {0x09, 0}, /* f */ + {0x0a, 0}, /* g */ + {0x0b, 0}, /* h */ + {0x0c, 0}, /* i */ + {0x0d, 0}, /* j */ + {0x0e, 0}, /* k */ + {0x0f, 0}, /* l */ + {0x10, 0}, /* m */ + {0x11, 0}, /* n */ + {0x12, 0}, /* o */ + {0x13, 0}, /* p */ + {0x14, 0}, /* q */ + {0x15, 0}, /* r */ + {0x16, 0}, /* s */ + {0x17, 0}, /* t */ + {0x18, 0}, /* u */ + {0x19, 0}, /* v */ + {0x1a, 0}, /* w */ + {0x1b, 0}, /* x */ + {0x1c, 0}, /* y */ + {0x1d, 0}, /* z */ + {0x2f, KEY_SHIFT}, /* { */ + {0x64, KEY_SHIFT}, /* | */ + {0x30, KEY_SHIFT}, /* } */ + {0x32, KEY_SHIFT}, /* ~ */ + {0,0}, /* DEL */ + + {0x3a, 0}, /* F1 */ + {0x3b, 0}, /* F2 */ + {0x3c, 0}, /* F3 */ + {0x3d, 0}, /* F4 */ + {0x3e, 0}, /* F5 */ + {0x3f, 0}, /* F6 */ + {0x40, 0}, /* F7 */ + {0x41, 0}, /* F8 */ + {0x42, 0}, /* F9 */ + {0x43, 0}, /* F10 */ + {0x44, 0}, /* F11 */ + {0x45, 0}, /* F12 */ + + {0x46, 0}, /* PRINT_SCREEN */ + {0x47, 0}, /* SCROLL_LOCK */ + {0x39, 0}, /* CAPS_LOCK */ + {0x53, 0}, /* NUM_LOCK */ + {0x49, 0}, /* INSERT */ + {0x4a, 0}, /* HOME */ + {0x4b, 0}, /* PAGE_UP */ + {0x4e, 0}, /* PAGE_DOWN */ + + {0x4f, 0}, /* RIGHT_ARROW */ + {0x50, 0}, /* LEFT_ARROW */ + {0x51, 0}, /* DOWN_ARROW */ + {0x52, 0}, /* UP_ARROW */ +}; +#endif + +#endif diff --git a/cpp_utils/SampleSecureClient.cpp b/cpp_utils/tests/BLETests/SampleSecureClient.cpp similarity index 98% rename from cpp_utils/SampleSecureClient.cpp rename to cpp_utils/tests/BLETests/SampleSecureClient.cpp index 457988ed..859291cb 100644 --- a/cpp_utils/SampleSecureClient.cpp +++ b/cpp_utils/tests/BLETests/SampleSecureClient.cpp @@ -56,7 +56,7 @@ class MyClient: public Task { void run(void* data) { BLESecurity *pSecurity = new BLESecurity(); pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM); - pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setCapability(ESP_IO_CAP_KBDISP); pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); BLEAddress* pAddress = (BLEAddress*)data; diff --git a/cpp_utils/SampleSecureServer.cpp b/cpp_utils/tests/BLETests/SampleSecureServer.cpp similarity index 100% rename from cpp_utils/SampleSecureServer.cpp rename to cpp_utils/tests/BLETests/SampleSecureServer.cpp From c5ac7f751b30d506fc5eb4d4019a28958093dcd6 Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 19 Dec 2017 05:03:54 +0100 Subject: [PATCH 190/381] IBeacon fix --- cpp_utils/BLEAdvertising.cpp | 8 ++++---- cpp_utils/BLEAdvertising.h | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index d7b20bb6..e026cbec 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -426,15 +426,15 @@ std::string BLEBeacon::getData() { } // getData void BLEBeacon::setMajor(uint16_t major) { - m_beaconData.major = major; + m_beaconData.major = ENDIAN_CHANGE_U16(major); } // setMajor void BLEBeacon::setManufacturerId(uint16_t manufacturerId) { - m_beaconData.manufacturerId = manufacturerId; + m_beaconData.manufacturerId = ENDIAN_CHANGE_U16(manufacturerId); } // setManufacturerId void BLEBeacon::setMinor(uint16_t minor) { - m_beaconData.minor = minor; + m_beaconData.minor = ENDIAN_CHANGE_U16(minor); } // setMinior void BLEBeacon::setProximityUUID(BLEUUID uuid) { @@ -442,7 +442,7 @@ void BLEBeacon::setProximityUUID(BLEUUID uuid) { memcpy(m_beaconData.proximityUUID, uuid.getNative()->uuid.uuid128, 16); } // setProximityUUID -void BLEBeacon::setSignalPower(uint16_t signalPower) { +void BLEBeacon::setSignalPower(int8_t signalPower) { m_beaconData.signalPower = signalPower; } // setSignalPower diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 04f5924d..8d53b64e 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -12,6 +12,7 @@ #include #include "BLEUUID.h" #include +#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) /** * @brief Representation of a beacon. @@ -27,8 +28,8 @@ class BLEBeacon { uint8_t proximityUUID[16]; uint16_t major; uint16_t minor; - uint8_t signalPower; - } m_beaconData; + int8_t signalPower; + } __attribute__((packed))m_beaconData; public: BLEBeacon(); void setManufacturerId(uint16_t manufacturerId); @@ -36,7 +37,7 @@ class BLEBeacon { void setProximityUUID(BLEUUID uuid); void setMajor(uint16_t major); void setMinor(uint16_t minor); - void setSignalPower(uint16_t signalPower); + void setSignalPower(int8_t signalPower); std::string getData(); }; // BLEBeacon From 189c42c41d9654ba8dab9e3beb347adf7c7500e6 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Tue, 19 Dec 2017 20:16:02 -0600 Subject: [PATCH 191/381] Changes for #298 --- cpp_utils/JSON.cpp | 23 +++++++++++++++++++++++ cpp_utils/JSON.h | 2 ++ 2 files changed, 25 insertions(+) diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index 5cf1e1ed..1f752c63 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -189,6 +189,18 @@ std::string JsonArray::toString() { } // toString +/** + * @brief Build an unformatted string representation. + * @return A string representation. + */ +std::string JsonArray::toStringUnformatted() { + char *data = cJSON_PrintUnformatted(m_node); + std::string ret(data); + free(data); + return ret; +} // toStringUnformatted + + /** * @brief Get the number of elements from the array. * @return The int value that represents the number of elements. @@ -363,3 +375,14 @@ std::string JsonObject::toString() { return ret; } // toString + +/** + * @brief Build an unformatted string representation. + * @return A string representation. + */ +std::string JsonObject::toStringUnformatted() { + char *data = cJSON_PrintUnformatted(m_node); + std::string ret(data); + free(data); + return ret; +} // toStringUnformatted diff --git a/cpp_utils/JSON.h b/cpp_utils/JSON.h index 6f4be9b9..13f8f0a2 100644 --- a/cpp_utils/JSON.h +++ b/cpp_utils/JSON.h @@ -45,6 +45,7 @@ class JsonArray { void addObject(JsonObject value); void addString(std::string value); std::string toString(); + std::string toStringUnformatted(); std::size_t size(); private: JsonArray(cJSON* node); @@ -77,6 +78,7 @@ class JsonObject { void setObject(std::string name, JsonObject value); void setString(std::string name, std::string value); std::string toString(); + std::string toStringUnformatted(); private: JsonObject(cJSON* node); From 756f2dcfa14398e222b73125b4401b93bcf4305c Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 21 Dec 2017 10:59:02 +0100 Subject: [PATCH 192/381] Add ServiceData GAP event --- cpp_utils/BLEAdvertisedDevice.cpp | 20 ++++++++++++++++++++ cpp_utils/BLEAdvertisedDevice.h | 3 +++ 2 files changed, 23 insertions(+) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 4347526f..c948e552 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -132,6 +132,14 @@ int8_t BLEAdvertisedDevice::getTXPower() { return m_txPower; } // getTXPower +/** + * @brief Get the service data. + * @return The ServiceData of the advertised device. + */ +uint8_t* BLEAdvertisedDevice::getServiceData() { + return m_serviceData; +} //getServiceData + /** * @brief Does this advertisement have an appearance value? * @return True if there is an appearance value present. @@ -274,6 +282,11 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { break; } // ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE + case ESP_BLE_AD_TYPE_SERVICE_DATA: { + setServiceData(payload); + break; + } //ESP_BLE_AD_TYPE_SERVICE_DATA + default: { ESP_LOGD(LOG_TAG, "Unhandled type: adType: %d - 0x%.2x", ad_type, ad_type); break; @@ -393,6 +406,13 @@ void BLEAdvertisedDevice::setTXPower(int8_t txPower) { ESP_LOGD(LOG_TAG, "- txPower: %d", m_txPower); } // setTXPower +/** + * @brief Set the ServiceData value. + * @param [in] data ServiceData value. + */ +void BLEAdvertisedDevice::setServiceData(uint8_t* data) { + m_serviceData = data; +} //setServiceData /** * @brief Create a string representation of this device. diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index aea6da08..25cc54e1 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -37,6 +37,7 @@ class BLEAdvertisedDevice { BLEScan* getScan(); BLEUUID getServiceUUID(); int8_t getTXPower(); + uint8_t* getServiceData(); bool isAdvertisingService(BLEUUID uuid); bool haveAppearance(); @@ -63,6 +64,7 @@ class BLEAdvertisedDevice { void setServiceUUID(const char* serviceUUID); void setServiceUUID(BLEUUID serviceUUID); void setTXPower(int8_t txPower); + void setServiceData(uint8_t* data); bool m_haveAppearance; bool m_haveManufacturerData; @@ -82,6 +84,7 @@ class BLEAdvertisedDevice { int m_rssi; std::vector m_serviceUUIDs; int8_t m_txPower; + uint8_t* m_serviceData; }; /** From bcc7f6942a06e0a627007e45d23bb7fee1d0fc04 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 21 Dec 2017 15:50:15 -0600 Subject: [PATCH 193/381] Updates for #282 --- cpp_utils/BLEAdvertisedDevice.cpp | 69 +++++++++++++++++++------------ cpp_utils/BLEAdvertisedDevice.h | 11 +++-- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index c948e552..eb546ee9 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -29,6 +29,7 @@ BLEAdvertisedDevice::BLEAdvertisedDevice() { m_manufacturerData = ""; m_name = ""; m_rssi = -9999; + m_serviceData = ""; m_txPower = 0; m_pScan = nullptr; @@ -36,6 +37,7 @@ BLEAdvertisedDevice::BLEAdvertisedDevice() { m_haveManufacturerData = false; m_haveName = false; m_haveRSSI = false; + m_haveServiceData = false; m_haveServiceUUID = false; m_haveTXPower = false; @@ -65,7 +67,7 @@ BLEAddress BLEAdvertisedDevice::getAddress() { */ uint16_t BLEAdvertisedDevice::getAppearance() { return m_appearance; -} +} // getAppearance /** @@ -74,7 +76,7 @@ uint16_t BLEAdvertisedDevice::getAppearance() { */ std::string BLEAdvertisedDevice::getManufacturerData() { return m_manufacturerData; -} +} // getManufacturerData /** @@ -104,6 +106,15 @@ BLEScan* BLEAdvertisedDevice::getScan() { } // getScan +/** + * @brief Get the service data. + * @return The ServiceData of the advertised device. + */ +std::string BLEAdvertisedDevice::getServiceData() { + return m_serviceData; +} //getServiceData + + /** * @brief Get the Service UUID. * @return The Service UUID of the advertised device. @@ -132,13 +143,7 @@ int8_t BLEAdvertisedDevice::getTXPower() { return m_txPower; } // getTXPower -/** - * @brief Get the service data. - * @return The ServiceData of the advertised device. - */ -uint8_t* BLEAdvertisedDevice::getServiceData() { - return m_serviceData; -} //getServiceData + /** * @brief Does this advertisement have an appearance value? @@ -176,6 +181,15 @@ bool BLEAdvertisedDevice::haveRSSI() { } // haveRSSI +/** + * @brief Does this advertisement have a service data value? + * @return True if there is a service data value present. + */ +bool BLEAdvertisedDevice::haveServiceData() { + return m_haveServiceData; +} // haveServiceData + + /** * @brief Does this advertisement have a service UUID value? * @return True if there is a service UUID value present. @@ -213,8 +227,8 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { bool finished = false; while(!finished) { - length = *payload; // Retrieve the length of the record. - payload++; // Skip to type + length = *payload; // Retrieve the length of the record. + payload++; // Skip to type sizeConsumed += 1 + length; // increase the size consumed. if (length != 0) { // A length of 0 indicates that we have reached the end. @@ -227,15 +241,13 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { ad_type, BLEUtils::advTypeToString(ad_type), length, pHex); free(pHex); - - switch(ad_type) { - case ESP_BLE_AD_TYPE_NAME_CMPL: { // Adv Data Type: 0x09 + case ESP_BLE_AD_TYPE_NAME_CMPL: { // Adv Data Type: 0x09 setName(std::string(reinterpret_cast(payload), length)); break; } // ESP_BLE_AD_TYPE_NAME_CMPL - case ESP_BLE_AD_TYPE_TX_PWR: { // Adv Data Type: 0x0A + case ESP_BLE_AD_TYPE_TX_PWR: { // Adv Data Type: 0x0A setTXPower(*payload); break; } // ESP_BLE_AD_TYPE_TX_PWR @@ -245,13 +257,13 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { break; } // ESP_BLE_AD_TYPE_APPEARANCE - case ESP_BLE_AD_TYPE_FLAG: { // Adv Data Type: 0x01 + case ESP_BLE_AD_TYPE_FLAG: { // Adv Data Type: 0x01 setAdFlag(*payload); break; } // ESP_BLE_AD_TYPE_FLAG case ESP_BLE_AD_TYPE_16SRV_CMPL: - case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02 + case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02 for (int var = 0; var < length/2; ++var) { setServiceUUID(BLEUUID(*reinterpret_cast(payload+var*2))); } @@ -259,7 +271,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { } // ESP_BLE_AD_TYPE_16SRV_PART case ESP_BLE_AD_TYPE_32SRV_CMPL: - case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04 + case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04 for (int var = 0; var < length/4; ++var) { setServiceUUID(BLEUUID(*reinterpret_cast(payload+var*4))); } @@ -282,8 +294,8 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { break; } // ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE - case ESP_BLE_AD_TYPE_SERVICE_DATA: { - setServiceData(payload); + case ESP_BLE_AD_TYPE_SERVICE_DATA: { // Adv Data Type: 0x16 (Service Data) + setServiceData(std::string(reinterpret_cast(payload), length)); break; } //ESP_BLE_AD_TYPE_SERVICE_DATA @@ -396,6 +408,16 @@ void BLEAdvertisedDevice::setServiceUUID(BLEUUID serviceUUID) { } // setServiceUUID +/** + * @brief Set the ServiceData value. + * @param [in] data ServiceData value. + */ +void BLEAdvertisedDevice::setServiceData(std::string serviceData) { + m_haveServiceData = true; // Set the flag that indicates we have service data. + m_serviceData = serviceData; // Save the service data that we received. +} //setServiceData + + /** * @brief Set the power level for this device. * @param [in] txPower The discovered power level. @@ -406,13 +428,6 @@ void BLEAdvertisedDevice::setTXPower(int8_t txPower) { ESP_LOGD(LOG_TAG, "- txPower: %d", m_txPower); } // setTXPower -/** - * @brief Set the ServiceData value. - * @param [in] data ServiceData value. - */ -void BLEAdvertisedDevice::setServiceData(uint8_t* data) { - m_serviceData = data; -} //setServiceData /** * @brief Create a string representation of this device. diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index 25cc54e1..14afff90 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -35,15 +35,17 @@ class BLEAdvertisedDevice { std::string getName(); int getRSSI(); BLEScan* getScan(); + std::string getServiceData(); BLEUUID getServiceUUID(); int8_t getTXPower(); - uint8_t* getServiceData(); - bool isAdvertisingService(BLEUUID uuid); + + bool isAdvertisingService(BLEUUID uuid); bool haveAppearance(); bool haveManufacturerData(); bool haveName(); bool haveRSSI(); + bool haveServiceData(); bool haveServiceUUID(); bool haveTXPower(); @@ -64,12 +66,13 @@ class BLEAdvertisedDevice { void setServiceUUID(const char* serviceUUID); void setServiceUUID(BLEUUID serviceUUID); void setTXPower(int8_t txPower); - void setServiceData(uint8_t* data); + void setServiceData(std::string data); bool m_haveAppearance; bool m_haveManufacturerData; bool m_haveName; bool m_haveRSSI; + bool m_haveServiceData; bool m_haveServiceUUID; bool m_haveTXPower; @@ -84,7 +87,7 @@ class BLEAdvertisedDevice { int m_rssi; std::vector m_serviceUUIDs; int8_t m_txPower; - uint8_t* m_serviceData; + std::string m_serviceData; }; /** From 95a20ebae4c8f0b161c36e6be51b57ecfc7ae63b Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 21 Dec 2017 17:57:45 -0600 Subject: [PATCH 194/381] More changes for #282 --- cpp_utils/BLEAdvertisedDevice.cpp | 60 +++++++++++++++++++++++++++++-- cpp_utils/BLEAdvertisedDevice.h | 6 +++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index eb546ee9..351c5e10 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -115,6 +115,15 @@ std::string BLEAdvertisedDevice::getServiceData() { } //getServiceData +/** + * @brief Get the service data UUID. + * @return The service data UUID. + */ +BLEUUID BLEAdvertisedDevice::getServiceDataUUID() { + return m_serviceDataUUID; +} // getServiceDataUUID + + /** * @brief Get the Service UUID. * @return The Service UUID of the advertised device. @@ -294,11 +303,45 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { break; } // ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE - case ESP_BLE_AD_TYPE_SERVICE_DATA: { // Adv Data Type: 0x16 (Service Data) - setServiceData(std::string(reinterpret_cast(payload), length)); + case ESP_BLE_AD_TYPE_SERVICE_DATA: { // Adv Data Type: 0x16 (Service Data) - 2 byte UUID + if (length < 2) { + ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_SERVICE_DATA"); + break; + } + uint16_t uuid = *(uint16_t *)payload; + setServiceDataUUID(BLEUUID(uuid)); + if (length > 2) { + setServiceData(std::string(reinterpret_cast(payload+2), length-2)); + } break; } //ESP_BLE_AD_TYPE_SERVICE_DATA + case ESP_BLE_AD_TYPE_32SERVICE_DATA: { // Adv Data Type: 0x20 (Service Data) - 4 byte UUID + if (length < 4) { + ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA"); + break; + } + uint32_t uuid = *(uint32_t *)payload; + setServiceDataUUID(BLEUUID(uuid)); + if (length > 4) { + setServiceData(std::string(reinterpret_cast(payload+4), length-4)); + } + break; + } //ESP_BLE_AD_TYPE_32SERVICE_DATA + + case ESP_BLE_AD_TYPE_128SERVICE_DATA: { // Adv Data Type: 0x21 (Service Data) - 16 byte UUID + if (length < 16) { + ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA"); + break; + } + + setServiceDataUUID(BLEUUID(payload, (size_t)16, false)); + if (length > 16) { + setServiceData(std::string(reinterpret_cast(payload+16), length-16)); + } + break; + } //ESP_BLE_AD_TYPE_32SERVICE_DATA + default: { ESP_LOGD(LOG_TAG, "Unhandled type: adType: %d - 0x%.2x", ad_type, ad_type); break; @@ -418,6 +461,16 @@ void BLEAdvertisedDevice::setServiceData(std::string serviceData) { } //setServiceData +/** + * @brief Set the ServiceDataUUID value. + * @param [in] data ServiceDataUUID value. + */ +void BLEAdvertisedDevice::setServiceDataUUID(BLEUUID uuid) { + m_haveServiceData = true; // Set the flag that indicates we have service data. + m_serviceDataUUID = uuid; +} // setServiceDataUUID + + /** * @brief Set the power level for this device. * @param [in] txPower The discovered power level. @@ -453,5 +506,8 @@ std::string BLEAdvertisedDevice::toString() { return ss.str(); } // toString + + + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index 14afff90..41bc4c66 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -36,6 +36,7 @@ class BLEAdvertisedDevice { int getRSSI(); BLEScan* getScan(); std::string getServiceData(); + BLEUUID getServiceDataUUID(); BLEUUID getServiceUUID(); int8_t getTXPower(); @@ -63,10 +64,12 @@ class BLEAdvertisedDevice { void setName(std::string name); void setRSSI(int rssi); void setScan(BLEScan* pScan); + void setServiceData(std::string data); + void setServiceDataUUID(BLEUUID uuid); void setServiceUUID(const char* serviceUUID); void setServiceUUID(BLEUUID serviceUUID); void setTXPower(int8_t txPower); - void setServiceData(std::string data); + bool m_haveAppearance; bool m_haveManufacturerData; @@ -88,6 +91,7 @@ class BLEAdvertisedDevice { std::vector m_serviceUUIDs; int8_t m_txPower; std::string m_serviceData; + BLEUUID m_serviceDataUUID; }; /** From ba3d7ac1faa3d23c0fd8426b4a19b37f3bb0831a Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Fri, 22 Dec 2017 11:46:32 -0600 Subject: [PATCH 195/381] Fixes for #308 --- cpp_utils/BLEDevice.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 5e09069d..0f50148b 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -48,7 +48,13 @@ bool initialized = false; // Have we been initialized? * @return A new instance of the client. */ /* STATIC */ BLEClient* BLEDevice::createClient() { + ESP_LOGD(LOG_TAG, ">> createClient"); +#ifndef CONFIG_GATTC_ENABLE // Check that BLE GATTC is enabled in make menuconfig + ESP_LOGE(LOG_TAG, "BLE GATTC is not enabled - CONFIG_GATTC_ENABLE not defined"); + abort(); +#endif // CONFIG_GATTC_ENABLE m_pClient = new BLEClient(); + ESP_LOGD(LOG_TAG, "<< createClient"); return m_pClient; } // createClient @@ -59,6 +65,10 @@ bool initialized = false; // Have we been initialized? */ /* STATIC */ BLEServer* BLEDevice::createServer() { ESP_LOGD(LOG_TAG, ">> createServer"); +#ifndef CONFIG_GATTS_ENABLE // Check that BLE GATTS is enabled in make menuconfig + ESP_LOGE(LOG_TAG, "BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined"); + abort(); +#endif // CONFIG_GATTS_ENABLE m_pServer = new BLEServer(); m_pServer->createApp(0); ESP_LOGD(LOG_TAG, "<< createServer"); @@ -260,17 +270,21 @@ bool initialized = false; // Have we been initialized? return; } +#ifdef CONFIG_GATTC_ENABLE // Check that BLE client is configured in make menuconfig errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } +#endif // CONFIG_GATTC_ENABLE +#ifdef CONFIG_GATTS_ENABLE // Check that BLE server is configured in make menuconfig errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } +#endif // CONFIG_GATTS_ENABLE errRc = ::esp_ble_gap_set_device_name(deviceName.c_str()); if (errRc != ESP_OK) { From a6cc232e59f0e89a9b05dfacc76a8e0ad7d06a0d Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 19:35:39 +0100 Subject: [PATCH 196/381] Security redesign + examples --- cpp_utils/BLEClient.cpp | 51 ------ cpp_utils/BLEClient.h | 4 - cpp_utils/BLEDescriptor.cpp | 3 + cpp_utils/BLEDescriptor.h | 2 + cpp_utils/BLEDevice.cpp | 102 ++++++++++-- cpp_utils/BLEDevice.h | 4 + cpp_utils/BLESecurity.cpp | 18 ++- cpp_utils/BLESecurity.h | 7 +- cpp_utils/BLEServer.cpp | 61 ------- cpp_utils/BLEServer.h | 7 - .../security/SampleClient_Encryption.cpp | 122 ++++++++++++++ ...nt_authentication_numeric_confirmation.cpp | 153 ++++++++++++++++++ .../SampleClient_authentication_passkey.cpp | 152 +++++++++++++++++ .../BLETests/security/SampleSecureClient.cpp | 147 +++++++++++++++++ .../BLETests/security/SampleSecureServer.cpp | 117 ++++++++++++++ .../security/SampleServer_Encryption.cpp | 85 ++++++++++ ...er_authentication_numeric_confirmation.cpp | 129 +++++++++++++++ .../SampleServer_authentication_passkey.cpp | 132 +++++++++++++++ .../security/SampleServer_authorization.cpp | 132 +++++++++++++++ .../BLETests/security/app_main_security.cpp | 33 ++++ 20 files changed, 1321 insertions(+), 140 deletions(-) create mode 100644 cpp_utils/tests/BLETests/security/SampleClient_Encryption.cpp create mode 100644 cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp create mode 100644 cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp create mode 100644 cpp_utils/tests/BLETests/security/SampleSecureClient.cpp create mode 100644 cpp_utils/tests/BLETests/security/SampleSecureServer.cpp create mode 100644 cpp_utils/tests/BLETests/security/SampleServer_Encryption.cpp create mode 100644 cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp create mode 100644 cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp create mode 100644 cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp create mode 100644 cpp_utils/tests/BLETests/security/app_main_security.cpp diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 3db0be64..55af054c 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -168,15 +168,6 @@ void BLEClient::gattClientEventHandler( break; } // ESP_GATTC_DISCONNECT_EVT - case ESP_GATTS_CONNECT_EVT: { - //m_connId = param->connect.conn_id; // Save the connection id. - if(m_securityLevel){ - esp_ble_set_encryption(evtParam->connect.remote_bda, m_securityLevel); - //memcpy(m_remote_bda, param->connect.remote_bda, sizeof(m_remote_bda)); - } - break; - } // ESP_GATTS_CONNECT_EVT - // // ESP_GATTC_OPEN_EVT // @@ -419,41 +410,6 @@ void BLEClient::handleGAPEvent( break; } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT - case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); - // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); - assert(m_securityCallbacks!=nullptr); - // esp_ble_passkey_reply(m_remote_bda, true, m_securityCallbacks->onPassKeyRequest()); - break; - - /* - * TODO should we add white/black list comparison? - */ - case ESP_GAP_BLE_SEC_REQ_EVT: - /* send the positive(true) security response to the peer device to accept the security request. - If not accept the security request, should sent the security response with negative(false) accept value*/ - if(m_securityCallbacks!=nullptr) - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, m_securityCallbacks->onSecurityRequest()); - else - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, false); - break; - /* - * - */ - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. - ///show the passkey number to the user to input it in the peer deivce. - if(m_securityCallbacks!=nullptr) - m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); - break; - case ESP_GAP_BLE_KEY_EVT: - //shows the ble key info share with peer device to the user. - ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); - break; - case ESP_GAP_BLE_AUTH_CMPL_EVT: - if(m_securityCallbacks!=nullptr) - m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); - break; - default: break; } @@ -491,13 +447,6 @@ void BLEClient::setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::s ESP_LOGD(LOG_TAG, "<< setValue"); } // setValue -void BLEClient::setEncryptionLevel(esp_ble_sec_act_t level) { - m_securityLevel = level; -} - -void BLEClient::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { - m_securityCallbacks = callbacks; -} /** * @brief Return a string representation of this client. diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 1a630dfe..a60ed102 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -49,8 +49,6 @@ class BLEClient { void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a given characteristic at a given service. - void setEncryptionLevel(esp_ble_sec_act_t level); - void setSecurityCallbacks(BLESecurityCallbacks* pCallbacks); std::string toString(); // Return a string representation of this client. @@ -82,8 +80,6 @@ class BLEClient { FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt"); std::map m_servicesMap; void clearServices(); // Clear any existing services. - esp_ble_sec_act_t m_securityLevel = (esp_ble_sec_act_t)0; - BLESecurityCallbacks* m_securityCallbacks = 0; }; // class BLEDevice diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 054ef420..ebccf0d9 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -301,6 +301,9 @@ void BLEDescriptor::setValue(std::string value) { setValue((uint8_t *)value.data(), value.length()); } // setValue +void BLEDescriptor::setAccessPermissions(esp_gatt_perm_t perm) { + m_permissions = perm; +} /** * @brief Return a string representation of the descriptor. diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index ef716d42..9ec408e5 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -39,6 +39,7 @@ class BLEDescriptor { void setCallbacks(BLEDescriptorCallbacks* pCallbacks); void setValue(uint8_t* data, size_t size); void setValue(std::string value); + void setAccessPermissions(esp_gatt_perm_t perm); std::string toString(); private: @@ -53,6 +54,7 @@ class BLEDescriptor { void executeCreate(BLECharacteristic* pCharacteristic); void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; }; /** diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 0f50148b..62d7b910 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -33,7 +33,6 @@ static const char* LOG_TAG = "BLEDevice"; - /** * Singletons for the BLEDevice. */ @@ -41,6 +40,8 @@ BLEServer* BLEDevice::m_pServer = nullptr; BLEScan* BLEDevice::m_pScan = nullptr; BLEClient* BLEDevice::m_pClient = nullptr; bool initialized = false; // Have we been initialized? +esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; +BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; /** @@ -94,6 +95,20 @@ bool initialized = false; // Have we been initialized? BLEUtils::dumpGattServerEvent(event, gatts_if, param); + switch(event) { + case ESP_GATTS_CONNECT_EVT: { + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } + break; + } // ESP_GATTS_CONNECT_EVT + + default: { + break; + } + } // switch + + if (BLEDevice::m_pServer != nullptr) { BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); } @@ -117,13 +132,20 @@ bool initialized = false; // Have we been initialized? ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); BLEUtils::dumpGattClientEvent(event, gattc_if, param); -/* + switch(event) { + case ESP_GATTC_CONNECT_EVT: { + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } + break; + } // ESP_GATTC_CONNECT_EVT + default: { break; } } // switch - */ + // If we have a client registered, call it. if (BLEDevice::m_pClient != nullptr) { @@ -143,14 +165,63 @@ bool initialized = false; // Have we been initialized? BLEUtils::dumpGapEvent(event, param); switch(event) { - case ESP_GAP_BLE_SEC_REQ_EVT: { - esp_err_t errRc = ::esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_security_rsp: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + break; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); + break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); + break; + case ESP_GAP_BLE_NC_REQ_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); + } + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); + // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); } break; - } - + /* + * TODO should we add white/black list comparison? + */ + case ESP_GAP_BLE_SEC_REQ_EVT: + /* send the positive(true) security response to the peer device to accept the security request. + If not accept the security request, should sent the security response with negative(false) accept value*/ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); + } + else{ + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + } + break; + /* + * + */ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + ///show the passkey number to the user to input it in the peer deivce. + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); + if(BLEDevice::m_securityCallbacks!=nullptr){ + ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); + BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); + } + break; + case ESP_GAP_BLE_KEY_EVT: + //shows the ble key type info share with peer device to the user. + ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: + if(BLEDevice::m_securityCallbacks!=nullptr){ + BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); + } + break; default: { break; } @@ -167,6 +238,12 @@ bool initialized = false; // Have we been initialized? if (BLEDevice::m_pScan != nullptr) { BLEDevice::getScan()->handleGAPEvent(event, param); } + + /* + * Security events: + */ + + } // gapEventHandler @@ -379,4 +456,11 @@ void BLEDevice::whiteListRemove(BLEAddress address) { ESP_LOGD(LOG_TAG, "<< whiteListRemove"); } // whiteListRemove +void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { + BLEDevice::m_securityLevel = level; +} + +void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { + BLEDevice::m_securityCallbacks = callbacks; +} #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index c6383720..28889611 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -38,11 +38,15 @@ class BLEDevice { static std::string toString(); // Return a string representation of our device. static void whiteListAdd(BLEAddress address); // Add an entry to the BLE white list. static void whiteListRemove(BLEAddress address); // Remove an entry from the BLE white list. + static void setEncryptionLevel(esp_ble_sec_act_t level); + static void setSecurityCallbacks(BLESecurityCallbacks* pCallbacks); private: static BLEServer *m_pServer; static BLEScan *m_pScan; static BLEClient *m_pClient; + static esp_ble_sec_act_t m_securityLevel; + static BLESecurityCallbacks* m_securityCallbacks; static esp_gatt_if_t getGattcIF(); diff --git a/cpp_utils/BLESecurity.cpp b/cpp_utils/BLESecurity.cpp index f3e5bbe9..273ec77f 100644 --- a/cpp_utils/BLESecurity.cpp +++ b/cpp_utils/BLESecurity.cpp @@ -31,23 +31,29 @@ void BLESecurity::setCapability(esp_ble_io_cap_t iocap) { * @brief Init encryption key by server * @param key_size is value between 7 and 16 */ -void BLESecurity::setInitEncryptionKey(uint8_t init_key, uint8_t key_size) { +void BLESecurity::setInitEncryptionKey(uint8_t init_key) { m_initKey = init_key; - m_keySize = key_size; esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &m_initKey, sizeof(uint8_t)); - esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t)); // <--- setup encryption key max size } /* * @brief Init encryption key by client * @param key_size is value between 7 and 16 */ -void BLESecurity::setRespEncryptionKey(uint8_t resp_key, uint8_t key_size) { +void BLESecurity::setRespEncryptionKey(uint8_t resp_key) { m_respKey = resp_key; - m_keySize = key_size; esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &m_respKey, sizeof(uint8_t)); - esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t)); // <--- setup encryption key max size } + +/* + * + * + */ +void BLESecurity::setKeySize(uint8_t key_size) { + m_keySize = key_size; + esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &m_keySize, sizeof(uint8_t)); +} + /* * @brief Debug function to display what keys are exchanged by peers */ diff --git a/cpp_utils/BLESecurity.h b/cpp_utils/BLESecurity.h index 494a1b7c..1a649283 100644 --- a/cpp_utils/BLESecurity.h +++ b/cpp_utils/BLESecurity.h @@ -15,8 +15,9 @@ class BLESecurity { virtual ~BLESecurity(); void setAuthenticationMode(esp_ble_auth_req_t auth_req); void setCapability(esp_ble_io_cap_t iocap); - void setInitEncryptionKey(uint8_t init_key, uint8_t key_size = 16); - void setRespEncryptionKey(uint8_t resp_key, uint8_t key_size = 16); + void setInitEncryptionKey(uint8_t init_key); + void setRespEncryptionKey(uint8_t resp_key); + void setKeySize(uint8_t key_size = 16); static char* esp_key_type_to_str(esp_ble_key_type_t key_type); private: @@ -55,6 +56,8 @@ class BLESecurityCallbacks { * Provide us information when authentication process is completed */ virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t); + + virtual bool onConfirmPIN(uint32_t pin); }; #endif /* COMPONENTS_CPP_UTILS_BLESECURITY_H_ */ diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index a3ea7681..8dd2a210 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -151,52 +151,6 @@ void BLEServer::handleGAPEvent( */ break; } - case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); - break; - case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); - break; - case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); - break; - case ESP_GAP_BLE_NC_REQ_EVT: - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); - break; - case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); - esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); - assert(m_securityCallbacks!=nullptr); - esp_ble_passkey_reply(m_remote_bda, true, m_securityCallbacks->onPassKeyRequest()); - break; - - /* - * TODO should we add white/black list comparison? - */ - case ESP_GAP_BLE_SEC_REQ_EVT: - /* send the positive(true) security response to the peer device to accept the security request. - If not accept the security request, should sent the security response with negative(false) accept value*/ - if(m_securityCallbacks!=nullptr) - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, m_securityCallbacks->onSecurityRequest()); - else - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, false); - break; - /* - * - */ - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. - ///show the passkey number to the user to input it in the peer deivce. - if(m_securityCallbacks!=nullptr) - m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); - break; - case ESP_GAP_BLE_KEY_EVT: - //shows the ble key info share with peer device to the user. - ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); - break; - case ESP_GAP_BLE_AUTH_CMPL_EVT: - if(m_securityCallbacks!=nullptr) - m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); - break; default: break; @@ -245,10 +199,6 @@ void BLEServer::handleGATTServerEvent( // case ESP_GATTS_CONNECT_EVT: { m_connId = param->connect.conn_id; // Save the connection id. - if(m_securityLevel){ - esp_ble_set_encryption(param->connect.remote_bda, m_securityLevel); - memcpy(m_remote_bda, param->connect.remote_bda, sizeof(m_remote_bda)); - } if (m_pServerCallbacks != nullptr) { m_pServerCallbacks->onConnect(this); } @@ -385,17 +335,6 @@ void BLEServer::startAdvertising() { ESP_LOGD(LOG_TAG, "<< startAdvertising"); } // startAdvertising -void BLEServer::setEncryptionLevel(esp_ble_sec_act_t level) { - m_securityLevel = level; -} - -uint32_t BLEServer::getPassKey() { - return m_securityPassKey; -} - -void BLEServer::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { - m_securityCallbacks = callbacks; -} void BLEServerCallbacks::onConnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 8d4554d2..bc0ef05d 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -58,9 +58,6 @@ class BLEServer { BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); - void setEncryptionLevel(esp_ble_sec_act_t level); - uint32_t getPassKey(); - void setSecurityCallbacks(BLESecurityCallbacks* pCallbacks); private: @@ -78,10 +75,6 @@ class BLEServer { FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); BLEServiceMap m_serviceMap; BLEServerCallbacks* m_pServerCallbacks; - esp_ble_sec_act_t m_securityLevel = (esp_ble_sec_act_t)0; - esp_bd_addr_t m_remote_bda; - uint32_t m_securityPassKey; - BLESecurityCallbacks* m_securityCallbacks; void createApp(uint16_t appId); uint16_t getConnId(); diff --git a/cpp_utils/tests/BLETests/security/SampleClient_Encryption.cpp b/cpp_utils/tests/BLETests/security/SampleClient_Encryption.cpp new file mode 100644 index 00000000..2ab4c909 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleClient_Encryption.cpp @@ -0,0 +1,122 @@ +/* + * SampleClient_Encryption.cpp + * + * Created on: Dec 23, 2017 + * Author: chegewara + */ + +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClient_Encryption(void) { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(5); +} // SampleClient + + + diff --git a/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp b/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp new file mode 100644 index 00000000..6d00dc78 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp @@ -0,0 +1,153 @@ +/* + * SampleClient_authentication_numeric_confirmation.cpp + * + * Created on: Dec 23, 2017 + * Author: esp32 + */ + + +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); +// pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClient_authentication_numeric_confirmation(void) { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(5); +} // SampleClient + + diff --git a/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp b/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp new file mode 100644 index 00000000..8ab3edfd --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp @@ -0,0 +1,152 @@ +/* + * SampleClient_authentication_passkey.cpp + * + * Created on: Dec 23, 2017 + * Author: esp32 + */ + +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return false; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); +// pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleSecureClient(void) { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(5); +} // SampleClient + + + diff --git a/cpp_utils/tests/BLETests/security/SampleSecureClient.cpp b/cpp_utils/tests/BLETests/security/SampleSecureClient.cpp new file mode 100644 index 00000000..88764720 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleSecureClient.cpp @@ -0,0 +1,147 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return false; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); +// pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleSecureClient(void) { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(5); +} // SampleClient diff --git a/cpp_utils/tests/BLETests/security/SampleSecureServer.cpp b/cpp_utils/tests/BLETests/security/SampleSecureServer.cpp new file mode 100644 index 00000000..d1ed296a --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleSecureServer.cpp @@ -0,0 +1,117 @@ +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "SecurityRequest"); + vTaskDelay(5000); + + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +class bleCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } + + void onRead(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } +}; +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setCallbacks(new bleCharacteristicCallback()); + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + p2902Descriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(1000000); + } +}; + + +void SampleSecureServer(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main diff --git a/cpp_utils/tests/BLETests/security/SampleServer_Encryption.cpp b/cpp_utils/tests/BLETests/security/SampleServer_Encryption.cpp new file mode 100644 index 00000000..10c16218 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleServer_Encryption.cpp @@ -0,0 +1,85 @@ +/* + * SampleServer_Encryption.cpp + * + * Created on: Dec 23, 2017 + * Author: chegewara + */ + +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MyCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); + } + + void onRead(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); + } +}; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + + pCharacteristic->setCallbacks(new MyCharacteristicCallback()); + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(portMAX_DELAY); + } +}; + + +void SampleServer_Encryption(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main + + + diff --git a/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp b/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp new file mode 100644 index 00000000..71728619 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp @@ -0,0 +1,129 @@ +/* + * SampleServer_authentication_numeric_confirmation.cpp + * + * Created on: Dec 23, 2017 + * Author: esp32 + */ + + +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "SecurityRequest"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +class bleCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } + + void onRead(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } +}; +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setCallbacks(new bleCharacteristicCallback()); + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + p2902Descriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + //pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(portMAX_DELAY); + } +}; + + +void SampleServer_authentication_numeric_confirmation(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main + + diff --git a/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp b/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp new file mode 100644 index 00000000..af9615c3 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp @@ -0,0 +1,132 @@ +/* + * SampleServer_authentication_passkey.cpp + * + * Created on: Dec 23, 2017 + * Author: esp32 + */ + +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MySecurity : public BLESecurityCallbacks { + + /* + * @return value is a pass key from input panel and displayed on peer device + */ + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + /* + * pass_key should be displayed to end user to authenticate with peer device + */ + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); + } + /* + * here we can + */ + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "On Security Request"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +class MyCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } + + void onRead(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } +}; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setCallbacks(new MyCharacteristicCallback()); + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(portMAX_DELAY); + } +}; + + +void SampleSecureServer(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main + + + diff --git a/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp b/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp new file mode 100644 index 00000000..127ed81d --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp @@ -0,0 +1,132 @@ +/* + * SampleServer_authorization.cpp + * + * Created on: Dec 23, 2017 + * Author: esp32 + */ + + +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t){ + return false; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "On Security Request"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +class bleCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } + + void onRead(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } +}; +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); +// BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + /* + * Authorized permission to read/write characteristic. Require also protecting descriptor 2902 to protect notify/indicate requests + */ + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setCallbacks(new bleCharacteristicCallback()); + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + /* + * Authorized permission to read/write descriptor to protect notify/indicate requests + */ + p2902Descriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + BLESecurity *pSecurity = new BLESecurity(); +// pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); +/* pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); +*/ + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(portMAX_DELAY); + } +}; + + +void SampleServer_Authorization(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main + + diff --git a/cpp_utils/tests/BLETests/security/app_main_security.cpp b/cpp_utils/tests/BLETests/security/app_main_security.cpp new file mode 100644 index 00000000..7b1bbca7 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/app_main_security.cpp @@ -0,0 +1,33 @@ +/** + * Main file for running the BLE samples. + */ +extern "C" { + void app_main(void); +} + + +// The list of sample entry points. + +void SampleClient_Encryption(void); +void SampleServer_Encryption(void); +void SampleClient_authentication_passkey(void); +void SampleServer_authentication_passkey(void); +void SampleClient_authentication_numeric_confirmation(void); +void SampleServer_authentication_numeric_confirmation(void); + +void SampleServer_Authorization(void); + +// +// Un-comment ONE of the following +// --- +void app_main(void) { + + SampleClient_Encryption(); +// SampleServer_Encryption(); +// SampleClient_authentication_passkey(); +// SampleServer_authentication_passkey(); +// SampleClient_authentication_numeric_confirmation(); +// SampleServer_authentication_numeric_confirmation(); +// +// SampleServer_Authorization(); +} // app_main From 6057871db7af7270d2dffa4c95764c23349f1fea Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 23 Dec 2017 12:38:56 -0600 Subject: [PATCH 197/381] Fixes for #310 --- cpp_utils/HttpRequest.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index bbbdce6d..9261af60 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -205,6 +205,7 @@ std::string HttpRequest::getPath() { #define STATE_NAME 0 #define STATE_VALUE 1 + /** * @brief Get the query part of the request. * The query is a set of name = value pairs. The return is a map keyed by the name items. @@ -215,7 +216,15 @@ std::map HttpRequest::getQuery() { // Walk through all the characters in the query string maintaining a simple state machine // that lets us know what we are parsing. std::map queryMap; - std::string queryString = ""; + + std::string possibleQueryString = getPath(); + int qindex = possibleQueryString.find_first_of("?") ; + if (qindex < 0) { + ESP_LOGD(LOG_TAG, "No query string present") ; + return queryMap ; + } + std::string queryString = possibleQueryString.substr(qindex + 1, -1) ; + ESP_LOGD(LOG_TAG, "query string: %s", queryString.c_str()) ; /* * We maintain a simple state machine with states of: From 67671174daa136c2e7cdb6dc5c316ec1b5e79495 Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 21:44:50 +0100 Subject: [PATCH 198/381] add arduino examples --- cpp_utils/HIDKeyboardTypes.h | 402 ++++++++++++++++++ cpp_utils/Makefile.arduino | 8 +- .../BLE_client/BLE_client_encrypted.ino | 138 ++++++ .../BLE_client_encrypted.ino | 138 ++++++ .../BLE_client_numeric_confirmation.ino | 169 ++++++++ .../BLE_client_numeric_confirmation.ino | 169 ++++++++ .../BLE_client/BLE_client_passkey.ino | 168 ++++++++ .../BLE_client_passkey/BLE_client_passkey.ino | 168 ++++++++ .../BLE_server/BLE_server_authorization.ino | 44 ++ .../BLE_server_authorization.ino | 44 ++ .../BLE_server/BLE_server_encrypted.ino | 43 ++ .../BLE_server_encrypted.ino | 43 ++ .../BLE_server_numeric_confirmation.ino | 75 ++++ .../BLE_server_numeric_confirmation.ino | 75 ++++ .../BLE_server/BLE_server_passkey.ino | 74 ++++ .../BLE_server_passkey/BLE_server_passkey.ino | 74 ++++ ...nt_authentication_numeric_confirmation.cpp | 4 +- .../SampleClient_authentication_passkey.cpp | 6 +- .../BLETests/security/SampleSecureClient.cpp | 147 ------- .../BLETests/security/SampleSecureServer.cpp | 117 ----- ...er_authentication_numeric_confirmation.cpp | 2 +- .../SampleServer_authentication_passkey.cpp | 14 +- .../security/SampleServer_authorization.cpp | 13 +- 23 files changed, 1846 insertions(+), 289 deletions(-) create mode 100644 cpp_utils/HIDKeyboardTypes.h create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted/BLE_client_encrypted.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation/BLE_client_numeric_confirmation.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey/BLE_client_passkey.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino delete mode 100644 cpp_utils/tests/BLETests/security/SampleSecureClient.cpp delete mode 100644 cpp_utils/tests/BLETests/security/SampleSecureServer.cpp diff --git a/cpp_utils/HIDKeyboardTypes.h b/cpp_utils/HIDKeyboardTypes.h new file mode 100644 index 00000000..ef48a526 --- /dev/null +++ b/cpp_utils/HIDKeyboardTypes.h @@ -0,0 +1,402 @@ +/* Copyright (c) 2015 mbed.org, MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Note: this file was pulled from different parts of the USBHID library, in mbed SDK + */ + +#ifndef KEYBOARD_DEFS_H +#define KEYBOARD_DEFS_H + +#define REPORT_ID_KEYBOARD 1 +#define REPORT_ID_VOLUME 3 + +/* Modifiers */ +enum MODIFIER_KEY { + KEY_CTRL = 1, + KEY_SHIFT = 2, + KEY_ALT = 4, +}; + + +enum MEDIA_KEY { + KEY_NEXT_TRACK, /*!< next Track Button */ + KEY_PREVIOUS_TRACK, /*!< Previous track Button */ + KEY_STOP, /*!< Stop Button */ + KEY_PLAY_PAUSE, /*!< Play/Pause Button */ + KEY_MUTE, /*!< Mute Button */ + KEY_VOLUME_UP, /*!< Volume Up Button */ + KEY_VOLUME_DOWN, /*!< Volume Down Button */ +}; + +enum FUNCTION_KEY { + KEY_F1 = 128, /* F1 key */ + KEY_F2, /* F2 key */ + KEY_F3, /* F3 key */ + KEY_F4, /* F4 key */ + KEY_F5, /* F5 key */ + KEY_F6, /* F6 key */ + KEY_F7, /* F7 key */ + KEY_F8, /* F8 key */ + KEY_F9, /* F9 key */ + KEY_F10, /* F10 key */ + KEY_F11, /* F11 key */ + KEY_F12, /* F12 key */ + + KEY_PRINT_SCREEN, /* Print Screen key */ + KEY_SCROLL_LOCK, /* Scroll lock */ + KEY_CAPS_LOCK, /* caps lock */ + KEY_NUM_LOCK, /* num lock */ + KEY_INSERT, /* Insert key */ + KEY_HOME, /* Home key */ + KEY_PAGE_UP, /* Page Up key */ + KEY_PAGE_DOWN, /* Page Down key */ + + RIGHT_ARROW, /* Right arrow */ + LEFT_ARROW, /* Left arrow */ + DOWN_ARROW, /* Down arrow */ + UP_ARROW, /* Up arrow */ +}; + +typedef struct { + unsigned char usage; + unsigned char modifier; +} KEYMAP; + +#ifdef US_KEYBOARD +/* US keyboard (as HID standard) */ +#define KEYMAP_SIZE (152) +const KEYMAP keymap[KEYMAP_SIZE] = { + {0, 0}, /* NUL */ + {0, 0}, /* SOH */ + {0, 0}, /* STX */ + {0, 0}, /* ETX */ + {0, 0}, /* EOT */ + {0, 0}, /* ENQ */ + {0, 0}, /* ACK */ + {0, 0}, /* BEL */ + {0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */ + {0x2b, 0}, /* TAB */ /* Keyboard Tab */ + {0x28, 0}, /* LF */ /* Keyboard Return (Enter) */ + {0, 0}, /* VT */ + {0, 0}, /* FF */ + {0, 0}, /* CR */ + {0, 0}, /* SO */ + {0, 0}, /* SI */ + {0, 0}, /* DEL */ + {0, 0}, /* DC1 */ + {0, 0}, /* DC2 */ + {0, 0}, /* DC3 */ + {0, 0}, /* DC4 */ + {0, 0}, /* NAK */ + {0, 0}, /* SYN */ + {0, 0}, /* ETB */ + {0, 0}, /* CAN */ + {0, 0}, /* EM */ + {0, 0}, /* SUB */ + {0, 0}, /* ESC */ + {0, 0}, /* FS */ + {0, 0}, /* GS */ + {0, 0}, /* RS */ + {0, 0}, /* US */ + {0x2c, 0}, /* */ + {0x1e, KEY_SHIFT}, /* ! */ + {0x34, KEY_SHIFT}, /* " */ + {0x20, KEY_SHIFT}, /* # */ + {0x21, KEY_SHIFT}, /* $ */ + {0x22, KEY_SHIFT}, /* % */ + {0x24, KEY_SHIFT}, /* & */ + {0x34, 0}, /* ' */ + {0x26, KEY_SHIFT}, /* ( */ + {0x27, KEY_SHIFT}, /* ) */ + {0x25, KEY_SHIFT}, /* * */ + {0x2e, KEY_SHIFT}, /* + */ + {0x36, 0}, /* , */ + {0x2d, 0}, /* - */ + {0x37, 0}, /* . */ + {0x38, 0}, /* / */ + {0x27, 0}, /* 0 */ + {0x1e, 0}, /* 1 */ + {0x1f, 0}, /* 2 */ + {0x20, 0}, /* 3 */ + {0x21, 0}, /* 4 */ + {0x22, 0}, /* 5 */ + {0x23, 0}, /* 6 */ + {0x24, 0}, /* 7 */ + {0x25, 0}, /* 8 */ + {0x26, 0}, /* 9 */ + {0x33, KEY_SHIFT}, /* : */ + {0x33, 0}, /* ; */ + {0x36, KEY_SHIFT}, /* < */ + {0x2e, 0}, /* = */ + {0x37, KEY_SHIFT}, /* > */ + {0x38, KEY_SHIFT}, /* ? */ + {0x1f, KEY_SHIFT}, /* @ */ + {0x04, KEY_SHIFT}, /* A */ + {0x05, KEY_SHIFT}, /* B */ + {0x06, KEY_SHIFT}, /* C */ + {0x07, KEY_SHIFT}, /* D */ + {0x08, KEY_SHIFT}, /* E */ + {0x09, KEY_SHIFT}, /* F */ + {0x0a, KEY_SHIFT}, /* G */ + {0x0b, KEY_SHIFT}, /* H */ + {0x0c, KEY_SHIFT}, /* I */ + {0x0d, KEY_SHIFT}, /* J */ + {0x0e, KEY_SHIFT}, /* K */ + {0x0f, KEY_SHIFT}, /* L */ + {0x10, KEY_SHIFT}, /* M */ + {0x11, KEY_SHIFT}, /* N */ + {0x12, KEY_SHIFT}, /* O */ + {0x13, KEY_SHIFT}, /* P */ + {0x14, KEY_SHIFT}, /* Q */ + {0x15, KEY_SHIFT}, /* R */ + {0x16, KEY_SHIFT}, /* S */ + {0x17, KEY_SHIFT}, /* T */ + {0x18, KEY_SHIFT}, /* U */ + {0x19, KEY_SHIFT}, /* V */ + {0x1a, KEY_SHIFT}, /* W */ + {0x1b, KEY_SHIFT}, /* X */ + {0x1c, KEY_SHIFT}, /* Y */ + {0x1d, KEY_SHIFT}, /* Z */ + {0x2f, 0}, /* [ */ + {0x31, 0}, /* \ */ + {0x30, 0}, /* ] */ + {0x23, KEY_SHIFT}, /* ^ */ + {0x2d, KEY_SHIFT}, /* _ */ + {0x35, 0}, /* ` */ + {0x04, 0}, /* a */ + {0x05, 0}, /* b */ + {0x06, 0}, /* c */ + {0x07, 0}, /* d */ + {0x08, 0}, /* e */ + {0x09, 0}, /* f */ + {0x0a, 0}, /* g */ + {0x0b, 0}, /* h */ + {0x0c, 0}, /* i */ + {0x0d, 0}, /* j */ + {0x0e, 0}, /* k */ + {0x0f, 0}, /* l */ + {0x10, 0}, /* m */ + {0x11, 0}, /* n */ + {0x12, 0}, /* o */ + {0x13, 0}, /* p */ + {0x14, 0}, /* q */ + {0x15, 0}, /* r */ + {0x16, 0}, /* s */ + {0x17, 0}, /* t */ + {0x18, 0}, /* u */ + {0x19, 0}, /* v */ + {0x1a, 0}, /* w */ + {0x1b, 0}, /* x */ + {0x1c, 0}, /* y */ + {0x1d, 0}, /* z */ + {0x2f, KEY_SHIFT}, /* { */ + {0x31, KEY_SHIFT}, /* | */ + {0x30, KEY_SHIFT}, /* } */ + {0x35, KEY_SHIFT}, /* ~ */ + {0,0}, /* DEL */ + + {0x3a, 0}, /* F1 */ + {0x3b, 0}, /* F2 */ + {0x3c, 0}, /* F3 */ + {0x3d, 0}, /* F4 */ + {0x3e, 0}, /* F5 */ + {0x3f, 0}, /* F6 */ + {0x40, 0}, /* F7 */ + {0x41, 0}, /* F8 */ + {0x42, 0}, /* F9 */ + {0x43, 0}, /* F10 */ + {0x44, 0}, /* F11 */ + {0x45, 0}, /* F12 */ + + {0x46, 0}, /* PRINT_SCREEN */ + {0x47, 0}, /* SCROLL_LOCK */ + {0x39, 0}, /* CAPS_LOCK */ + {0x53, 0}, /* NUM_LOCK */ + {0x49, 0}, /* INSERT */ + {0x4a, 0}, /* HOME */ + {0x4b, 0}, /* PAGE_UP */ + {0x4e, 0}, /* PAGE_DOWN */ + + {0x4f, 0}, /* RIGHT_ARROW */ + {0x50, 0}, /* LEFT_ARROW */ + {0x51, 0}, /* DOWN_ARROW */ + {0x52, 0}, /* UP_ARROW */ +}; + +#else +/* UK keyboard */ +#define KEYMAP_SIZE (152) +const KEYMAP keymap[KEYMAP_SIZE] = { + {0, 0}, /* NUL */ + {0, 0}, /* SOH */ + {0, 0}, /* STX */ + {0, 0}, /* ETX */ + {0, 0}, /* EOT */ + {0, 0}, /* ENQ */ + {0, 0}, /* ACK */ + {0, 0}, /* BEL */ + {0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */ + {0x2b, 0}, /* TAB */ /* Keyboard Tab */ + {0x28, 0}, /* LF */ /* Keyboard Return (Enter) */ + {0, 0}, /* VT */ + {0, 0}, /* FF */ + {0, 0}, /* CR */ + {0, 0}, /* SO */ + {0, 0}, /* SI */ + {0, 0}, /* DEL */ + {0, 0}, /* DC1 */ + {0, 0}, /* DC2 */ + {0, 0}, /* DC3 */ + {0, 0}, /* DC4 */ + {0, 0}, /* NAK */ + {0, 0}, /* SYN */ + {0, 0}, /* ETB */ + {0, 0}, /* CAN */ + {0, 0}, /* EM */ + {0, 0}, /* SUB */ + {0, 0}, /* ESC */ + {0, 0}, /* FS */ + {0, 0}, /* GS */ + {0, 0}, /* RS */ + {0, 0}, /* US */ + {0x2c, 0}, /* */ + {0x1e, KEY_SHIFT}, /* ! */ + {0x1f, KEY_SHIFT}, /* " */ + {0x32, 0}, /* # */ + {0x21, KEY_SHIFT}, /* $ */ + {0x22, KEY_SHIFT}, /* % */ + {0x24, KEY_SHIFT}, /* & */ + {0x34, 0}, /* ' */ + {0x26, KEY_SHIFT}, /* ( */ + {0x27, KEY_SHIFT}, /* ) */ + {0x25, KEY_SHIFT}, /* * */ + {0x2e, KEY_SHIFT}, /* + */ + {0x36, 0}, /* , */ + {0x2d, 0}, /* - */ + {0x37, 0}, /* . */ + {0x38, 0}, /* / */ + {0x27, 0}, /* 0 */ + {0x1e, 0}, /* 1 */ + {0x1f, 0}, /* 2 */ + {0x20, 0}, /* 3 */ + {0x21, 0}, /* 4 */ + {0x22, 0}, /* 5 */ + {0x23, 0}, /* 6 */ + {0x24, 0}, /* 7 */ + {0x25, 0}, /* 8 */ + {0x26, 0}, /* 9 */ + {0x33, KEY_SHIFT}, /* : */ + {0x33, 0}, /* ; */ + {0x36, KEY_SHIFT}, /* < */ + {0x2e, 0}, /* = */ + {0x37, KEY_SHIFT}, /* > */ + {0x38, KEY_SHIFT}, /* ? */ + {0x34, KEY_SHIFT}, /* @ */ + {0x04, KEY_SHIFT}, /* A */ + {0x05, KEY_SHIFT}, /* B */ + {0x06, KEY_SHIFT}, /* C */ + {0x07, KEY_SHIFT}, /* D */ + {0x08, KEY_SHIFT}, /* E */ + {0x09, KEY_SHIFT}, /* F */ + {0x0a, KEY_SHIFT}, /* G */ + {0x0b, KEY_SHIFT}, /* H */ + {0x0c, KEY_SHIFT}, /* I */ + {0x0d, KEY_SHIFT}, /* J */ + {0x0e, KEY_SHIFT}, /* K */ + {0x0f, KEY_SHIFT}, /* L */ + {0x10, KEY_SHIFT}, /* M */ + {0x11, KEY_SHIFT}, /* N */ + {0x12, KEY_SHIFT}, /* O */ + {0x13, KEY_SHIFT}, /* P */ + {0x14, KEY_SHIFT}, /* Q */ + {0x15, KEY_SHIFT}, /* R */ + {0x16, KEY_SHIFT}, /* S */ + {0x17, KEY_SHIFT}, /* T */ + {0x18, KEY_SHIFT}, /* U */ + {0x19, KEY_SHIFT}, /* V */ + {0x1a, KEY_SHIFT}, /* W */ + {0x1b, KEY_SHIFT}, /* X */ + {0x1c, KEY_SHIFT}, /* Y */ + {0x1d, KEY_SHIFT}, /* Z */ + {0x2f, 0}, /* [ */ + {0x64, 0}, /* \ */ + {0x30, 0}, /* ] */ + {0x23, KEY_SHIFT}, /* ^ */ + {0x2d, KEY_SHIFT}, /* _ */ + {0x35, 0}, /* ` */ + {0x04, 0}, /* a */ + {0x05, 0}, /* b */ + {0x06, 0}, /* c */ + {0x07, 0}, /* d */ + {0x08, 0}, /* e */ + {0x09, 0}, /* f */ + {0x0a, 0}, /* g */ + {0x0b, 0}, /* h */ + {0x0c, 0}, /* i */ + {0x0d, 0}, /* j */ + {0x0e, 0}, /* k */ + {0x0f, 0}, /* l */ + {0x10, 0}, /* m */ + {0x11, 0}, /* n */ + {0x12, 0}, /* o */ + {0x13, 0}, /* p */ + {0x14, 0}, /* q */ + {0x15, 0}, /* r */ + {0x16, 0}, /* s */ + {0x17, 0}, /* t */ + {0x18, 0}, /* u */ + {0x19, 0}, /* v */ + {0x1a, 0}, /* w */ + {0x1b, 0}, /* x */ + {0x1c, 0}, /* y */ + {0x1d, 0}, /* z */ + {0x2f, KEY_SHIFT}, /* { */ + {0x64, KEY_SHIFT}, /* | */ + {0x30, KEY_SHIFT}, /* } */ + {0x32, KEY_SHIFT}, /* ~ */ + {0,0}, /* DEL */ + + {0x3a, 0}, /* F1 */ + {0x3b, 0}, /* F2 */ + {0x3c, 0}, /* F3 */ + {0x3d, 0}, /* F4 */ + {0x3e, 0}, /* F5 */ + {0x3f, 0}, /* F6 */ + {0x40, 0}, /* F7 */ + {0x41, 0}, /* F8 */ + {0x42, 0}, /* F9 */ + {0x43, 0}, /* F10 */ + {0x44, 0}, /* F11 */ + {0x45, 0}, /* F12 */ + + {0x46, 0}, /* PRINT_SCREEN */ + {0x47, 0}, /* SCROLL_LOCK */ + {0x39, 0}, /* CAPS_LOCK */ + {0x53, 0}, /* NUM_LOCK */ + {0x49, 0}, /* INSERT */ + {0x4a, 0}, /* HOME */ + {0x4b, 0}, /* PAGE_UP */ + {0x4e, 0}, /* PAGE_DOWN */ + + {0x4f, 0}, /* RIGHT_ARROW */ + {0x50, 0}, /* LEFT_ARROW */ + {0x51, 0}, /* DOWN_ARROW */ + {0x52, 0}, /* UP_ARROW */ +}; +#endif + +#endif diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index f9239250..82d7da3e 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -28,6 +28,8 @@ BLE_FILES= \ BLEDevice.h \ BLEExceptions.cpp \ BLEExceptions.h \ + BLEHIDDevice.cpp \ + BLEHIDDevice.h \ BLERemoteCharacteristic.cpp \ BLERemoteCharacteristic.h \ BLERemoteDescriptor.cpp \ @@ -41,6 +43,8 @@ BLE_FILES= \ BLEService.cpp \ BLEService.h \ BLEServiceMap.cpp \ + BLESecurity.cpp \ + BLESecurity.h \ BLEUtils.cpp \ BLEUtils.h \ BLEUUID.cpp \ @@ -50,7 +54,9 @@ BLE_FILES= \ FreeRTOS.h \ FreeRTOS.cpp \ GeneralUtils.h \ - GeneralUtils.cpp + GeneralUtils.cpp \ + HIDTypes.h \ + HIDKeyboardTypes.h ARDUINO_LIBS=$(HOME)/Arduino/libraries diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted.ino new file mode 100644 index 00000000..e77d774f --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted.ino @@ -0,0 +1,138 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + Serial.println(" - Connected to server"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our service"); + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = true; + + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +void setup() { + Serial.begin(115200); + Serial.println("Starting Arduino BLE Client application..."); + BLEDevice::init(""); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // End of setup. + + +// This is the Arduino main loop function. +void loop() { + + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. + if (doConnect == true) { + if (connectToServer(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } else { + Serial.println("We have failed to connect to the server; there is nothin more we will do."); + } + doConnect = false; + } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. + if (connected) { + String newValue = "Time since boot: " + String(millis()/1000); + Serial.println("Setting new characteristic value to \"" + newValue + "\""); + + // Set the characteristic's value to be the array of bytes that is actually a string. + pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted/BLE_client_encrypted.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted/BLE_client_encrypted.ino new file mode 100644 index 00000000..e77d774f --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted/BLE_client_encrypted.ino @@ -0,0 +1,138 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + Serial.println(" - Connected to server"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our service"); + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = true; + + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +void setup() { + Serial.begin(115200); + Serial.println("Starting Arduino BLE Client application..."); + BLEDevice::init(""); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // End of setup. + + +// This is the Arduino main loop function. +void loop() { + + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. + if (doConnect == true) { + if (connectToServer(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } else { + Serial.println("We have failed to connect to the server; there is nothin more we will do."); + } + doConnect = false; + } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. + if (connected) { + String newValue = "Time since boot: " + String(millis()/1000); + Serial.println("Setting new characteristic value to \"" + newValue + "\""); + + // Set the characteristic's value to be the array of bytes that is actually a string. + pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation.ino new file mode 100644 index 00000000..846a664b --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation.ino @@ -0,0 +1,169 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + Serial.println(" - Connected to server"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our service"); + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = true; + + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +void setup() { + Serial.begin(115200); + Serial.println("Starting Arduino BLE Client application..."); + BLEDevice::init(""); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // End of setup. + + +// This is the Arduino main loop function. +void loop() { + + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. + if (doConnect == true) { + if (connectToServer(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } else { + Serial.println("We have failed to connect to the server; there is nothin more we will do."); + } + doConnect = false; + } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. + if (connected) { + String newValue = "Time since boot: " + String(millis()/1000); + Serial.println("Setting new characteristic value to \"" + newValue + "\""); + + // Set the characteristic's value to be the array of bytes that is actually a string. + pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation/BLE_client_numeric_confirmation.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation/BLE_client_numeric_confirmation.ino new file mode 100644 index 00000000..846a664b --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation/BLE_client_numeric_confirmation.ino @@ -0,0 +1,169 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + Serial.println(" - Connected to server"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our service"); + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = true; + + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +void setup() { + Serial.begin(115200); + Serial.println("Starting Arduino BLE Client application..."); + BLEDevice::init(""); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // End of setup. + + +// This is the Arduino main loop function. +void loop() { + + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. + if (doConnect == true) { + if (connectToServer(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } else { + Serial.println("We have failed to connect to the server; there is nothin more we will do."); + } + doConnect = false; + } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. + if (connected) { + String newValue = "Time since boot: " + String(millis()/1000); + Serial.println("Setting new characteristic value to \"" + newValue + "\""); + + // Set the characteristic's value to be the array of bytes that is actually a string. + pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey.ino new file mode 100644 index 00000000..763a87d2 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey.ino @@ -0,0 +1,168 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + Serial.println(" - Connected to server"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our service"); + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = true; + + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +void setup() { + Serial.begin(115200); + Serial.println("Starting Arduino BLE Client application..."); + BLEDevice::init(""); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // End of setup. + + +// This is the Arduino main loop function. +void loop() { + + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. + if (doConnect == true) { + if (connectToServer(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } else { + Serial.println("We have failed to connect to the server; there is nothin more we will do."); + } + doConnect = false; + } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. + if (connected) { + String newValue = "Time since boot: " + String(millis()/1000); + Serial.println("Setting new characteristic value to \"" + newValue + "\""); + + // Set the characteristic's value to be the array of bytes that is actually a string. + pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey/BLE_client_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey/BLE_client_passkey.ino new file mode 100644 index 00000000..763a87d2 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey/BLE_client_passkey.ino @@ -0,0 +1,168 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + Serial.println(" - Connected to server"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our service"); + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = true; + + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +void setup() { + Serial.begin(115200); + Serial.println("Starting Arduino BLE Client application..."); + BLEDevice::init(""); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // End of setup. + + +// This is the Arduino main loop function. +void loop() { + + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. + if (doConnect == true) { + if (connectToServer(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } else { + Serial.println("We have failed to connect to the server; there is nothin more we will do."); + } + doConnect = false; + } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. + if (connected) { + String newValue = "Time since boot: " + String(millis()/1000); + Serial.println("Setting new characteristic value to \"" + newValue + "\""); + + // Set the characteristic's value to be the array of bytes that is actually a string. + pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization.ino new file mode 100644 index 00000000..7da509b4 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization.ino @@ -0,0 +1,44 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("MyESP32"); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + /* + * Authorized permission to read/write characteristic. Require also protecting descriptor 2902 to protect notify/indicate requests + */ + + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino new file mode 100644 index 00000000..7da509b4 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino @@ -0,0 +1,44 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("MyESP32"); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + /* + * Authorized permission to read/write characteristic. Require also protecting descriptor 2902 to protect notify/indicate requests + */ + + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted.ino new file mode 100644 index 00000000..e712794b --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted.ino @@ -0,0 +1,43 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("MyESP32"); + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino new file mode 100644 index 00000000..e712794b --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino @@ -0,0 +1,43 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("MyESP32"); + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation.ino new file mode 100644 index 00000000..d726104b --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation.ino @@ -0,0 +1,75 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "SecurityRequest"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("MyESP32"); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino new file mode 100644 index 00000000..d726104b --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino @@ -0,0 +1,75 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "SecurityRequest"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("MyESP32"); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey.ino new file mode 100644 index 00000000..a79a305a --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey.ino @@ -0,0 +1,74 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); + } + + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "On Security Request"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("MyESP32"); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino new file mode 100644 index 00000000..a79a305a --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino @@ -0,0 +1,74 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); + } + + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "On Security Request"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("MyESP32"); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp b/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp index 6d00dc78..2791f3d1 100644 --- a/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp +++ b/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp @@ -2,7 +2,7 @@ * SampleClient_authentication_numeric_confirmation.cpp * * Created on: Dec 23, 2017 - * Author: esp32 + * Author: chegewara */ @@ -71,7 +71,7 @@ class MyClient: public Task { BLESecurity *pSecurity = new BLESecurity(); pSecurity->setKeySize(); -// pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); pSecurity->setCapability(ESP_IO_CAP_IO); pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); // Connect to the remove BLE Server. diff --git a/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp b/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp index 8ab3edfd..4848385d 100644 --- a/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp +++ b/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp @@ -2,7 +2,7 @@ * SampleClient_authentication_passkey.cpp * * Created on: Dec 23, 2017 - * Author: esp32 + * Author: chegewara */ #include @@ -69,8 +69,8 @@ class MyClient: public Task { BLEDevice::setSecurityCallbacks(new MySecurity()); BLESecurity *pSecurity = new BLESecurity(); -// pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); - pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); // Connect to the remove BLE Server. pClient->connect(*pAddress); diff --git a/cpp_utils/tests/BLETests/security/SampleSecureClient.cpp b/cpp_utils/tests/BLETests/security/SampleSecureClient.cpp deleted file mode 100644 index 88764720..00000000 --- a/cpp_utils/tests/BLETests/security/SampleSecureClient.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Create a sample BLE client that connects to a BLE server and then retrieves the current - * characteristic value. It will then periodically update the value of the characteristic on the - * remote server with the current time since boot. - */ -#include -#include -#include -#include -#include "BLEDevice.h" - -#include "BLEAdvertisedDevice.h" -#include "BLEClient.h" -#include "BLEScan.h" -#include "BLEUtils.h" -#include "Task.h" - -#include "sdkconfig.h" - -static const char* LOG_TAG = "SampleClient"; - -// See the following for generating UUIDs: -// https://www.uuidgenerator.net/ -// The remote service we wish to connect to. -static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); -// The characteristic of the remote service we are interested in. -static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); - -class MySecurity : public BLESecurityCallbacks { - - uint32_t onPassKeyRequest(){ - return 123456; - } - void onPassKeyNotify(uint32_t pass_key){ - ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); - } - bool onConfirmPIN(uint32_t pass_key){ - ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); - vTaskDelay(5000); - return false; - } - bool onSecurityRequest(){ - ESP_LOGI(LOG_TAG, "Security Request"); - return true; - } - void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ - if(auth_cmpl.success){ - ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); - esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); - ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); - } - ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); - } -}; - - -/** - * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server - * as the input parameter when the task is created. - */ -class MyClient: public Task { - void run(void* data) { - - BLEAddress* pAddress = (BLEAddress*)data; - BLEClient* pClient = BLEDevice::createClient(); - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - BLEDevice::setSecurityCallbacks(new MySecurity()); - - BLESecurity *pSecurity = new BLESecurity(); -// pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); - pSecurity->setCapability(ESP_IO_CAP_IO); - pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - // Connect to the remove BLE Server. - pClient->connect(*pAddress); - - // Obtain a reference to the service we are after in the remote BLE server. - BLERemoteService* pRemoteService = pClient->getService(serviceUUID); - if (pRemoteService == nullptr) { - ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); - return; - } - - - // Obtain a reference to the characteristic in the service of the remote BLE server. - BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); - if (pRemoteCharacteristic == nullptr) { - ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); - return; - } - - // Read the value of the characteristic. - std::string value = pRemoteCharacteristic->readValue(); - ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); - - while(1) { - // Set a new value of the characteristic - ESP_LOGD(LOG_TAG, "Setting the new value"); - std::ostringstream stringStream; - struct timeval tv; - gettimeofday(&tv, nullptr); - stringStream << "Time since boot: " << tv.tv_sec; - pRemoteCharacteristic->writeValue(stringStream.str()); - - FreeRTOS::sleep(1000); - } - - pClient->disconnect(); - - ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); - ESP_LOGD(LOG_TAG, "-- End of task"); - } // run -}; // MyClient - - -/** - * Scan for BLE servers and find the first one that advertises the service we are looking for. - */ -class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { - /** - * Called for each advertising BLE server. - */ - void onResult(BLEAdvertisedDevice advertisedDevice) { - ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); - - if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { - advertisedDevice.getScan()->stop(); - - ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); - MyClient* pMyClient = new MyClient(); - pMyClient->setStackSize(18000); - pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); - } // Found our server - } // onResult -}; // MyAdvertisedDeviceCallbacks - - -/** - * Perform the work of a sample BLE client. - */ -void SampleSecureClient(void) { - ESP_LOGD(LOG_TAG, "Scanning sample starting"); - BLEDevice::init(""); - BLEScan *pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); - pBLEScan->setActiveScan(true); - pBLEScan->start(5); -} // SampleClient diff --git a/cpp_utils/tests/BLETests/security/SampleSecureServer.cpp b/cpp_utils/tests/BLETests/security/SampleSecureServer.cpp deleted file mode 100644 index d1ed296a..00000000 --- a/cpp_utils/tests/BLETests/security/SampleSecureServer.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Create a new BLE server. - */ -#include "BLEDevice.h" -#include "BLEServer.h" -#include "BLEUtils.h" -#include "BLE2902.h" -#include -#include -#include - - -#include "sdkconfig.h" - -static char LOG_TAG[] = "SampleServer"; - -class MySecurity : public BLESecurityCallbacks { - - uint32_t onPassKeyRequest(){ - ESP_LOGI(LOG_TAG, "PassKeyRequest"); - return 123456; - } - void onPassKeyNotify(uint32_t pass_key){ - ESP_LOGI(LOG_TAG, "The passkey Notify number:%d", pass_key); - } - bool onConfirmPIN(uint32_t pass_key){ - ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); - vTaskDelay(5000); - return true; - } - bool onSecurityRequest(){ - ESP_LOGI(LOG_TAG, "SecurityRequest"); - vTaskDelay(5000); - - return true; - } - - void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ - ESP_LOGI(LOG_TAG, "Starting BLE work!"); - if(cmpl.success){ - uint16_t length; - esp_ble_gap_get_whitelist_size(&length); - ESP_LOGD(LOG_TAG, "size: %d", length); - } - } -}; - -class bleCharacteristicCallback: public BLECharacteristicCallbacks { - void onWrite(BLECharacteristic* pCharacteristic) { - std::string msg = pCharacteristic->getValue(); - ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); - esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); - esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); - } - - void onRead(BLECharacteristic* pCharacteristic) { - std::string msg = pCharacteristic->getValue(); - ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); - esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); - esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); - } -}; -class MainBLEServer: public Task { - void run(void *data) { - ESP_LOGD(LOG_TAG, "Starting BLE work!"); - esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); - esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); - BLEDevice::init("ESP32"); - BLEServer* pServer = BLEDevice::createServer(); - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); - BLEDevice::setSecurityCallbacks(new MySecurity()); - - BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); - - BLECharacteristic* pCharacteristic = pService->createCharacteristic( - BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_NOTIFY | - BLECharacteristic::PROPERTY_WRITE | - BLECharacteristic::PROPERTY_INDICATE - ); - pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - pCharacteristic->setCallbacks(new bleCharacteristicCallback()); - pCharacteristic->setValue("Hello World!"); - - BLE2902* p2902Descriptor = new BLE2902(); - p2902Descriptor->setNotifications(true); - pCharacteristic->addDescriptor(p2902Descriptor); - p2902Descriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - - pService->start(); - - BLEAdvertising* pAdvertising = pServer->getAdvertising(); - pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); - - BLESecurity *pSecurity = new BLESecurity(); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); - pSecurity->setCapability(ESP_IO_CAP_IO); - pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - - pAdvertising->start(); - - ESP_LOGD(LOG_TAG, "Advertising started!"); - delay(1000000); - } -}; - - -void SampleSecureServer(void) -{ - //esp_log_level_set("*", ESP_LOG_DEBUG); - MainBLEServer* pMainBleServer = new MainBLEServer(); - pMainBleServer->setStackSize(20000); - pMainBleServer->start(); - -} // app_main diff --git a/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp b/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp index 71728619..3231b37d 100644 --- a/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp +++ b/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp @@ -2,7 +2,7 @@ * SampleServer_authentication_numeric_confirmation.cpp * * Created on: Dec 23, 2017 - * Author: esp32 + * Author: chegewara */ diff --git a/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp b/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp index af9615c3..24d45868 100644 --- a/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp +++ b/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp @@ -2,7 +2,7 @@ * SampleServer_authentication_passkey.cpp * * Created on: Dec 23, 2017 - * Author: esp32 + * Author: chegewara */ /** @@ -23,22 +23,15 @@ static char LOG_TAG[] = "SampleServer"; class MySecurity : public BLESecurityCallbacks { - /* - * @return value is a pass key from input panel and displayed on peer device - */ uint32_t onPassKeyRequest(){ ESP_LOGI(LOG_TAG, "PassKeyRequest"); return 123456; } - /* - * pass_key should be displayed to end user to authenticate with peer device - */ + void onPassKeyNotify(uint32_t pass_key){ ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); } - /* - * here we can - */ + bool onSecurityRequest(){ ESP_LOGI(LOG_TAG, "On Security Request"); return true; @@ -109,7 +102,6 @@ class MainBLEServer: public Task { pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); pSecurity->setCapability(ESP_IO_CAP_OUT); pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); pAdvertising->start(); diff --git a/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp b/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp index 127ed81d..02e47e4d 100644 --- a/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp +++ b/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp @@ -2,7 +2,7 @@ * SampleServer_authorization.cpp * * Created on: Dec 23, 2017 - * Author: esp32 + * Author: chegewara */ @@ -32,7 +32,7 @@ class MySecurity : public BLESecurityCallbacks { ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); } bool onConfirmPIN(uint32_t){ - return false; + return true; } bool onSecurityRequest(){ ESP_LOGI(LOG_TAG, "On Security Request"); @@ -107,11 +107,10 @@ class MainBLEServer: public Task { pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); BLESecurity *pSecurity = new BLESecurity(); -// pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); - pSecurity->setCapability(ESP_IO_CAP_OUT); -/* pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); -*/ + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_NONE); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + pAdvertising->start(); ESP_LOGD(LOG_TAG, "Advertising started!"); From 5251054d02e79ca811b3e822542efd8d3e46f45c Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 21:47:56 +0100 Subject: [PATCH 199/381] Delete BLE_server_passkey.ino --- .../BLE_server/BLE_server_passkey.ino | 74 ------------------- 1 file changed, 74 deletions(-) delete mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey.ino diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey.ino deleted file mode 100644 index a79a305a..00000000 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey.ino +++ /dev/null @@ -1,74 +0,0 @@ -/* - Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp - Ported to Arduino ESP32 by Evandro Copercini -*/ - -#include -#include -#include - -// See the following for generating UUIDs: -// https://www.uuidgenerator.net/ - -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" - -class MySecurity : public BLESecurityCallbacks { - - uint32_t onPassKeyRequest(){ - ESP_LOGI(LOG_TAG, "PassKeyRequest"); - return 123456; - } - - void onPassKeyNotify(uint32_t pass_key){ - ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); - } - - bool onSecurityRequest(){ - ESP_LOGI(LOG_TAG, "On Security Request"); - return true; - } - - void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ - ESP_LOGI(LOG_TAG, "Starting BLE work!"); - if(cmpl.success){ - uint16_t length; - esp_ble_gap_get_whitelist_size(&length); - ESP_LOGD(LOG_TAG, "size: %d", length); - } - } -}; - -void setup() { - Serial.begin(115200); - Serial.println("Starting BLE work!"); - - BLEDevice::init("MyESP32"); - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - /* - * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation - */ - BLEDevice::setSecurityCallbacks(new MySecurity()); - BLEServer *pServer = BLEDevice::createServer(); - BLEService *pService = pServer->createService(SERVICE_UUID); - BLECharacteristic *pCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID, - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_WRITE - ); - - pCharacteristic->setValue("Hello World says Neil"); - pService->start(); - BLEAdvertising *pAdvertising = pServer->getAdvertising(); - pAdvertising->start(); - BLESecurity *pSecurity = new BLESecurity(); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); - pSecurity->setCapability(ESP_IO_CAP_OUT); - pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - Serial.println("Characteristic defined! Now you can read it in your phone!"); -} - -void loop() { - // put your main code here, to run repeatedly: - delay(2000); -} \ No newline at end of file From f85d4d4eee1d01c7de27a1eb2121d091fa0ad8f0 Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 21:48:04 +0100 Subject: [PATCH 200/381] Delete BLE_server_numeric_confirmation.ino --- .../BLE_server_numeric_confirmation.ino | 75 ------------------- 1 file changed, 75 deletions(-) delete mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation.ino diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation.ino deleted file mode 100644 index d726104b..00000000 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation.ino +++ /dev/null @@ -1,75 +0,0 @@ -/* - Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp - Ported to Arduino ESP32 by Evandro Copercini -*/ - -#include -#include -#include - -// See the following for generating UUIDs: -// https://www.uuidgenerator.net/ - -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" - -class MySecurity : public BLESecurityCallbacks { - - uint32_t onPassKeyRequest(){ - ESP_LOGI(LOG_TAG, "PassKeyRequest"); - return 123456; - } - void onPassKeyNotify(uint32_t pass_key){ - ESP_LOGI(LOG_TAG, "The passkey Notify number:%d", pass_key); - } - bool onConfirmPIN(uint32_t pass_key){ - ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); - vTaskDelay(5000); - return true; - } - bool onSecurityRequest(){ - ESP_LOGI(LOG_TAG, "SecurityRequest"); - return true; - } - - void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ - ESP_LOGI(LOG_TAG, "Starting BLE work!"); - } -}; - -void setup() { - Serial.begin(115200); - Serial.println("Starting BLE work!"); - - BLEDevice::init("MyESP32"); - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - /* - * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation - */ - BLEDevice::setSecurityCallbacks(new MySecurity()); - BLEServer *pServer = BLEDevice::createServer(); - BLEService *pService = pServer->createService(SERVICE_UUID); - BLECharacteristic *pCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID, - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_WRITE - ); - - pCharacteristic->setValue("Hello World says Neil"); - pService->start(); - BLEAdvertising *pAdvertising = pServer->getAdvertising(); - pAdvertising->start(); - - BLESecurity *pSecurity = new BLESecurity(); - pSecurity->setKeySize(); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); - pSecurity->setCapability(ESP_IO_CAP_IO); - pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - - Serial.println("Characteristic defined! Now you can read it in your phone!"); -} - -void loop() { - // put your main code here, to run repeatedly: - delay(2000); -} \ No newline at end of file From 924b0d2d603c955f6e55611d298c9dcce50d690d Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 21:48:12 +0100 Subject: [PATCH 201/381] Delete BLE_server_encrypted.ino --- .../BLE_server/BLE_server_encrypted.ino | 43 ------------------- 1 file changed, 43 deletions(-) delete mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted.ino diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted.ino deleted file mode 100644 index e712794b..00000000 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted.ino +++ /dev/null @@ -1,43 +0,0 @@ -/* - Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp - Ported to Arduino ESP32 by Evandro Copercini -*/ - -#include -#include -#include - -// See the following for generating UUIDs: -// https://www.uuidgenerator.net/ - -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" - -void setup() { - Serial.begin(115200); - Serial.println("Starting BLE work!"); - - BLEDevice::init("MyESP32"); - /* - * Here we have implemented simplest security. This kind security does not provide authentication - */ - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - BLEServer *pServer = BLEDevice::createServer(); - BLEService *pService = pServer->createService(SERVICE_UUID); - BLECharacteristic *pCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID, - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_WRITE - ); - - pCharacteristic->setValue("Hello World says Neil"); - pService->start(); - BLEAdvertising *pAdvertising = pServer->getAdvertising(); - pAdvertising->start(); - Serial.println("Characteristic defined! Now you can read it in your phone!"); -} - -void loop() { - // put your main code here, to run repeatedly: - delay(2000); -} \ No newline at end of file From 38d712e7afe8201a9cfd7feb756f949702f8b8f8 Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 21:48:20 +0100 Subject: [PATCH 202/381] Delete BLE_server_authorization.ino --- .../BLE_server/BLE_server_authorization.ino | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization.ino diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization.ino deleted file mode 100644 index 7da509b4..00000000 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization.ino +++ /dev/null @@ -1,44 +0,0 @@ -/* - Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp - Ported to Arduino ESP32 by Evandro Copercini -*/ - -#include -#include -#include - -// See the following for generating UUIDs: -// https://www.uuidgenerator.net/ - -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" - -void setup() { - Serial.begin(115200); - Serial.println("Starting BLE work!"); - - BLEDevice::init("MyESP32"); - BLEServer *pServer = BLEDevice::createServer(); - BLEService *pService = pServer->createService(SERVICE_UUID); - BLECharacteristic *pCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID, - BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_WRITE - ); - - /* - * Authorized permission to read/write characteristic. Require also protecting descriptor 2902 to protect notify/indicate requests - */ - - pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - pCharacteristic->setValue("Hello World says Neil"); - pService->start(); - BLEAdvertising *pAdvertising = pServer->getAdvertising(); - pAdvertising->start(); - Serial.println("Characteristic defined! Now you can read it in your phone!"); -} - -void loop() { - // put your main code here, to run repeatedly: - delay(2000); -} \ No newline at end of file From 2d9422c035c077dfe3ce0bb56dceeb6282d5ef9a Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 21:48:58 +0100 Subject: [PATCH 203/381] Update BLE_server_passkey.ino --- .../BLE_server/BLE_server_passkey/BLE_server_passkey.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino index a79a305a..a48ad482 100644 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino @@ -43,7 +43,7 @@ void setup() { Serial.begin(115200); Serial.println("Starting BLE work!"); - BLEDevice::init("MyESP32"); + BLEDevice::init("ESP32"); BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); /* * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation @@ -71,4 +71,4 @@ void setup() { void loop() { // put your main code here, to run repeatedly: delay(2000); -} \ No newline at end of file +} From 48d9031be9a817910769ad541e711611a6083ced Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 21:49:25 +0100 Subject: [PATCH 204/381] Update BLE_server_numeric_confirmation.ino --- .../BLE_server_numeric_confirmation.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino index d726104b..186019e8 100644 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino @@ -41,7 +41,7 @@ void setup() { Serial.begin(115200); Serial.println("Starting BLE work!"); - BLEDevice::init("MyESP32"); + BLEDevice::init("ESP32"); BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); /* * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation @@ -72,4 +72,4 @@ void setup() { void loop() { // put your main code here, to run repeatedly: delay(2000); -} \ No newline at end of file +} From 9d4e62fefce34cb30cee4567d5cefd8a8f56e98d Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 21:49:47 +0100 Subject: [PATCH 205/381] Update BLE_server_encrypted.ino --- .../BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino index e712794b..43ffd2c9 100644 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino @@ -17,7 +17,7 @@ void setup() { Serial.begin(115200); Serial.println("Starting BLE work!"); - BLEDevice::init("MyESP32"); + BLEDevice::init("ESP32"); /* * Here we have implemented simplest security. This kind security does not provide authentication */ @@ -40,4 +40,4 @@ void setup() { void loop() { // put your main code here, to run repeatedly: delay(2000); -} \ No newline at end of file +} From 8f0d70774d755b387209c657002417bceb4c655a Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 23 Dec 2017 21:50:08 +0100 Subject: [PATCH 206/381] Update BLE_server_authorization.ino --- .../BLE_server_authorization/BLE_server_authorization.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino index 7da509b4..75a461af 100644 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino @@ -17,7 +17,7 @@ void setup() { Serial.begin(115200); Serial.println("Starting BLE work!"); - BLEDevice::init("MyESP32"); + BLEDevice::init("ESP32"); BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(SERVICE_UUID); BLECharacteristic *pCharacteristic = pService->createCharacteristic( @@ -41,4 +41,4 @@ void setup() { void loop() { // put your main code here, to run repeatedly: delay(2000); -} \ No newline at end of file +} From f28769c8d2e7fd313ed78644d477fcd918c42203 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 23 Dec 2017 16:25:55 -0600 Subject: [PATCH 207/381] Code changes for #314 --- cpp_utils/BLE2904.cpp | 54 ++++++++++++++++++++++++ cpp_utils/BLE2904.h | 74 +++++++++++++++++++++++++++++++++ cpp_utils/BLECharacteristic.cpp | 6 +-- cpp_utils/BLEDescriptor.cpp | 14 +++---- cpp_utils/BLEDescriptor.h | 37 +++++++++-------- 5 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 cpp_utils/BLE2904.cpp create mode 100644 cpp_utils/BLE2904.h diff --git a/cpp_utils/BLE2904.cpp b/cpp_utils/BLE2904.cpp new file mode 100644 index 00000000..f0305cb4 --- /dev/null +++ b/cpp_utils/BLE2904.cpp @@ -0,0 +1,54 @@ +/* + * BLE2904.cpp + * + * Created on: Dec 23, 2017 + * Author: kolban + */ + +/* + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "BLE2904.h" + + +BLE2904::BLE2904() : BLEDescriptor(BLEUUID((uint16_t) 0x2904)) { + m_data.m_format = 0; + m_data.m_exponent = 0; + m_data.m_namespace = 1; // 1 = Bluetooth SIG Assigned Numbers + m_data.m_unit = 0; + m_data.m_description = 0; + setValue((uint8_t*)&m_data, sizeof(m_data)); +} // BLE2902 + + +void BLE2904::setDescription(uint16_t description) { + m_data.m_description = description; + setValue((uint8_t*)&m_data, sizeof(m_data)); +} + + +void BLE2904::setExponent(int8_t exponent) { + m_data.m_exponent = exponent; + setValue((uint8_t*)&m_data, sizeof(m_data)); +} // setExponent + +void BLE2904::setFormat(uint8_t format) { + m_data.m_format = format; + setValue((uint8_t*)&m_data, sizeof(m_data)); +} // setFormat + +void BLE2904::setNamespace(uint8_t namespace_value) { + m_data.m_namespace = namespace_value; + setValue((uint8_t*)&m_data, sizeof(m_data)); +} // setNamespace + +void BLE2904::setUnit(uint16_t unit) { + m_data.m_unit = unit; + setValue((uint8_t*)&m_data, sizeof(m_data)); +} // setUnit + +#endif diff --git a/cpp_utils/BLE2904.h b/cpp_utils/BLE2904.h new file mode 100644 index 00000000..ef9a770a --- /dev/null +++ b/cpp_utils/BLE2904.h @@ -0,0 +1,74 @@ +/* + * BLE2904.h + * + * Created on: Dec 23, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLE2904_H_ +#define COMPONENTS_CPP_UTILS_BLE2904_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "BLEDescriptor.h" + +struct BLE2904_Data { + uint8_t m_format; + int8_t m_exponent; + uint16_t m_unit; + uint8_t m_namespace; + uint16_t m_description; + +} __attribute__((packed)); + +/** + * @brief Descriptor for Characteristic Presentation Format. + * + * This is a convenience descriptor for the Characteristic Presentation Format which has a UUID of 0x2904. + * + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + */ +class BLE2904: public BLEDescriptor { +public: + BLE2904(); + static const uint8_t FORMAT_BOOLEAN = 1; + static const uint8_t FORMAT_UINT2 = 2; + static const uint8_t FORMAT_UINT4 = 3; + static const uint8_t FORMAT_UINT8 = 4; + static const uint8_t FORMAT_UINT12 = 5; + static const uint8_t FORMAT_UINT16 = 6; + static const uint8_t FORMAT_UINT24 = 7; + static const uint8_t FORMAT_UINT32 = 8; + static const uint8_t FORMAT_UINT48 = 9; + static const uint8_t FORMAT_UINT64 = 10; + static const uint8_t FORMAT_UINT128 = 11; + static const uint8_t FORMAT_SINT8 = 12; + static const uint8_t FORMAT_SINT12 = 13; + static const uint8_t FORMAT_SINT16 = 14; + static const uint8_t FORMAT_SINT24 = 15; + static const uint8_t FORMAT_SINT32 = 16; + static const uint8_t FORMAT_SINT48 = 17; + static const uint8_t FORMAT_SINT64 = 18; + static const uint8_t FORMAT_SINT128 = 19; + static const uint8_t FORMAT_FLOAT32 = 20; + static const uint8_t FORMAT_FLOAT64 = 21; + static const uint8_t FORMAT_SFLOAT16 = 22; + static const uint8_t FORMAT_SFLOAT32 = 23; + static const uint8_t FORMAT_IEEE20601 = 24; + static const uint8_t FORMAT_UTF8 = 25; + static const uint8_t FORMAT_UTF16 = 26; + static const uint8_t FORMAT_OPAQUE = 27; + + void setDescription(uint16_t); + void setExponent(int8_t exponent); + void setFormat(uint8_t format); + void setNamespace(uint8_t namespace_value); + void setUnit(uint16_t unit); + +private: + BLE2904_Data m_data; +}; // BLE2904 + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLE2904_H_ */ diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 9e9aa5c6..3f8460ac 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -324,7 +324,6 @@ void BLECharacteristic::handleGATTServerEvent( // - bool need_rsp // case ESP_GATTS_READ_EVT: { - ESP_LOGD(LOG_TAG, "- Testing: 0x%.2x == 0x%.2x", param->read.handle, m_handle); if (param->read.handle == m_handle) { if (m_pCallbacks != nullptr) { @@ -352,10 +351,9 @@ void BLECharacteristic::handleGATTServerEvent( // the logic flow comprehension. // uint16_t maxOffset = m_mtu - 1; - if (m_mtu > 512) + if (m_mtu > 512) { maxOffset = 512; - ESP_LOGI(LOG_TAG, "%d", m_mtu); - ESP_LOGI(LOG_TAG, "%d", maxOffset); + } if (param->read.need_rsp) { ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); esp_gatt_rsp_t rsp; diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index ebccf0d9..1a72ef30 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -37,12 +37,12 @@ BLEDescriptor::BLEDescriptor(const char* uuid) : BLEDescriptor(BLEUUID(uuid)) { */ BLEDescriptor::BLEDescriptor(BLEUUID uuid) { m_bleUUID = uuid; - m_value.attr_value = (uint8_t *)malloc(ESP_GATT_MAX_ATTR_LEN); // Allocate storage for the value. - m_value.attr_len = 0; - m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; - m_handle = NULL_HANDLE; - m_pCharacteristic = nullptr; // No initial characteristic. - m_pCallback = nullptr; // No initial callback. + m_value.attr_value = (uint8_t *)malloc(ESP_GATT_MAX_ATTR_LEN); // Allocate storage for the value. + m_value.attr_len = 0; // Initial length is 0. + m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; // Maximum length of the data. + m_handle = NULL_HANDLE; // Handle is initially unknown. + m_pCharacteristic = nullptr; // No initial characteristic. + m_pCallback = nullptr; // No initial callback. } // BLEDescriptor @@ -51,7 +51,7 @@ BLEDescriptor::BLEDescriptor(BLEUUID uuid) { * @brief BLEDescriptor destructor. */ BLEDescriptor::~BLEDescriptor() { - free(m_value.attr_value); + free(m_value.attr_value); // Release the storage we created in the constructor. } // ~BLEDescriptor diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index 9ec408e5..d9e0aefb 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -28,34 +28,37 @@ class BLEDescriptor { BLEDescriptor(BLEUUID uuid); virtual ~BLEDescriptor(); - uint16_t getHandle(); - size_t getLength(); - BLEUUID getUUID(); - uint8_t* getValue(); + uint16_t getHandle(); // Get the handle of the descriptor. + size_t getLength(); // Get the length of the value of the descriptor. + BLEUUID getUUID(); // Get the UUID of the descriptor. + uint8_t* getValue(); // Get a pointer to the value of the descriptor. void handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); - void setCallbacks(BLEDescriptorCallbacks* pCallbacks); - void setValue(uint8_t* data, size_t size); - void setValue(std::string value); - void setAccessPermissions(esp_gatt_perm_t perm); - std::string toString(); -private: + void setAccessPermissions(esp_gatt_perm_t perm); // Set the permissions of the descriptor. + void setCallbacks(BLEDescriptorCallbacks* pCallbacks); // Set callbacks to be invoked for the descriptor. + void setValue(uint8_t* data, size_t size); // Set the value of the descriptor as a pointer to data. + void setValue(std::string value); // Set the value of the descriptor as a data buffer. + + std::string toString(); // Convert the descriptor to a string representation. +private: friend class BLEDescriptorMap; friend class BLECharacteristic; - BLEUUID m_bleUUID; - esp_attr_value_t m_value; - uint16_t m_handle; - BLECharacteristic* m_pCharacteristic; + BLEUUID m_bleUUID; + uint16_t m_handle; BLEDescriptorCallbacks* m_pCallback; + BLECharacteristic* m_pCharacteristic; + esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + esp_attr_value_t m_value; + void executeCreate(BLECharacteristic* pCharacteristic); void setHandle(uint16_t handle); - FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); - esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; -}; +}; // BLEDescriptor + /** * @brief Callbacks that can be associated with a %BLE descriptors to inform of events. From 7f9ccb51a6994ef2b1d1051b14a08747fee215f5 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 25 Dec 2017 16:24:07 -0600 Subject: [PATCH 208/381] Added reference to assigned numbers for units #314 --- cpp_utils/BLE2904.cpp | 20 ++++++++++++++++++++ cpp_utils/BLE2904.h | 2 +- cpp_utils/BLEUtils.cpp | 6 ++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLE2904.cpp b/cpp_utils/BLE2904.cpp index f0305cb4..4423ed82 100644 --- a/cpp_utils/BLE2904.cpp +++ b/cpp_utils/BLE2904.cpp @@ -25,27 +25,47 @@ BLE2904::BLE2904() : BLEDescriptor(BLEUUID((uint16_t) 0x2904)) { } // BLE2902 +/** + * @brief Set the description. + */ void BLE2904::setDescription(uint16_t description) { m_data.m_description = description; setValue((uint8_t*)&m_data, sizeof(m_data)); } +/** + * @brief Set the exponent. + */ void BLE2904::setExponent(int8_t exponent) { m_data.m_exponent = exponent; setValue((uint8_t*)&m_data, sizeof(m_data)); } // setExponent + +/** + * @brief Set the format. + */ void BLE2904::setFormat(uint8_t format) { m_data.m_format = format; setValue((uint8_t*)&m_data, sizeof(m_data)); } // setFormat + +/** + * @brief Set the namespace. + */ void BLE2904::setNamespace(uint8_t namespace_value) { m_data.m_namespace = namespace_value; setValue((uint8_t*)&m_data, sizeof(m_data)); } // setNamespace + +/** + * @brief Set the units for this value. It should be one of the encoded values defined here: + * https://www.bluetooth.com/specifications/assigned-numbers/units + * @param [in] uint The type of units of this characteristic as defined by assigned numbers. + */ void BLE2904::setUnit(uint16_t unit) { m_data.m_unit = unit; setValue((uint8_t*)&m_data, sizeof(m_data)); diff --git a/cpp_utils/BLE2904.h b/cpp_utils/BLE2904.h index ef9a770a..cb337e22 100644 --- a/cpp_utils/BLE2904.h +++ b/cpp_utils/BLE2904.h @@ -15,7 +15,7 @@ struct BLE2904_Data { uint8_t m_format; int8_t m_exponent; - uint16_t m_unit; + uint16_t m_unit; // See https://www.bluetooth.com/specifications/assigned-numbers/units uint8_t m_namespace; uint16_t m_description; diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index f96db1c5..ff4ebfaf 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -1611,13 +1611,15 @@ void BLEUtils::dumpGattClientEvent( // - esp_gatt_status_t status // - uint16_t conn_id // - uint16_t handle + // - uint16_t offset // case ESP_GATTC_WRITE_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x]", + ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x, offset: %d]", BLEUtils::gattStatusToString(evtParam->write.status).c_str(), evtParam->write.conn_id, evtParam->write.handle, - evtParam->write.handle + evtParam->write.handle, + evtParam->write.offset ); break; } // ESP_GATTC_WRITE_CHAR_EVT From 37a5652be6ff57d427dcf3fb61634fdca66b07f0 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 27 Dec 2017 22:08:35 -0600 Subject: [PATCH 209/381] Fixes for #321 --- cpp_utils/I2C.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index aac369cf..7c8f5056 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -143,7 +143,7 @@ void I2C::read(uint8_t* bytes, size_t length, bool ack) { ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } } - esp_err_t errRc = ::i2c_master_read(m_cmd, bytes, length, !ack); + esp_err_t errRc = ::i2c_master_read(m_cmd, bytes, length, ack?I2C_MASTER_ACK:I2C_MASTER_NACK); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "i2c_master_read: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } @@ -168,7 +168,7 @@ void I2C::read(uint8_t *byte, bool ack) { ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } } - ESP_ERROR_CHECK(::i2c_master_read_byte(m_cmd, byte, !ack)); + ESP_ERROR_CHECK(::i2c_master_read_byte(m_cmd, byte, ack?I2C_MASTER_ACK:I2C_MASTER_NACK)); } // readByte From da4394cd46f622e153fb39735dd732da647904d8 Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 28 Dec 2017 09:44:35 +0100 Subject: [PATCH 210/381] Some mtu related bug fixes --- cpp_utils/BLECharacteristic.cpp | 25 +--- cpp_utils/BLECharacteristic.h | 1 - cpp_utils/BLEDevice.cpp | 42 +++++- cpp_utils/BLEDevice.h | 3 + .../tests/BLETests/SampleSecureClient.cpp | 141 ------------------ .../tests/BLETests/SampleSecureServer.cpp | 88 ----------- 6 files changed, 52 insertions(+), 248 deletions(-) delete mode 100644 cpp_utils/tests/BLETests/SampleSecureClient.cpp delete mode 100644 cpp_utils/tests/BLETests/SampleSecureServer.cpp diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 3f8460ac..52310d7a 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -15,6 +15,7 @@ #include #include "BLECharacteristic.h" #include "BLEService.h" +#include "BLEDevice.h" #include "BLEUtils.h" #include "BLE2902.h" #include "GeneralUtils.h" @@ -350,8 +351,9 @@ void BLECharacteristic::handleGATTServerEvent( // The following code has deliberately not been factored to make it fewer statements because this would cloud the // the logic flow comprehension. // - uint16_t maxOffset = m_mtu - 1; - if (m_mtu > 512) { + // TODO requires some more research to confirm that 512 is max PDU like in bluetooth specs + uint16_t maxOffset = BLEDevice::getMTU() - 1; + if (BLEDevice::getMTU() > 512) { maxOffset = 512; } if (param->read.need_rsp) { @@ -418,7 +420,6 @@ void BLECharacteristic::handleGATTServerEvent( } case ESP_GATTS_CONNECT_EVT: - m_mtu = 23; m_semaphoreConfEvt.give(); break; @@ -426,10 +427,6 @@ void BLECharacteristic::handleGATTServerEvent( m_semaphoreConfEvt.give(); break; - case ESP_GATTS_MTU_EVT : - m_mtu = param->mtu.mtu; - break; - default: { break; } // default @@ -473,14 +470,11 @@ void BLECharacteristic::indicate() { return; } - if (m_value.getValue().length() > 20) { - ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)"); + if (m_value.getValue().length() > (BLEDevice::getMTU() - 3)) { + ESP_LOGI(LOG_TAG, "- Truncating to %d bytes (maximum indicate size)", BLEDevice::getMTU() - 3); } size_t length = m_value.getValue().length(); - if (length > 20) { - length = 20; - } m_semaphoreConfEvt.take("indicate"); @@ -529,14 +523,11 @@ void BLECharacteristic::notify() { return; } - if (m_value.getValue().length() > 20) { - ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)"); + if (m_value.getValue().length() > (BLEDevice::getMTU() - 3)) { + ESP_LOGI(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", BLEDevice::getMTU() - 3); } size_t length = m_value.getValue().length(); - if (length > 20) { - length = 20; - } m_semaphoreConfEvt.take("notify"); diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index a65bf121..fefe59a0 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -103,7 +103,6 @@ class BLECharacteristic { BLEService* m_pService; BLEValue m_value; esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; - uint16_t m_mtu = 23; void handleGATTServerEvent( esp_gatts_cb_event_t event, diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 62d7b910..a2dd80b4 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -17,6 +17,7 @@ #include // ESP32 BLE #include // ESP32 BLE #include // ESP32 BLE +#include // ESP32 BLE #include // ESP32 ESP-IDF #include // ESP32 ESP-IDF #include // Part of C++ Standard library @@ -42,7 +43,7 @@ BLEClient* BLEDevice::m_pClient = nullptr; bool initialized = false; // Have we been initialized? esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; - +uint16_t BLEDevice::m_localMTU = 23; /** * @brief Create a new instance of a client. @@ -103,6 +104,11 @@ BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; break; } // ESP_GATTS_CONNECT_EVT + case ESP_GATTS_MTU_EVT: { + BLEDevice::m_localMTU = param->mtu.mtu; + ESP_LOGI(LOG_TAG, "ESP_GATTS_MTU_EVT, MTU %d", BLEDevice::m_localMTU); + break; + } default: { break; } @@ -135,6 +141,9 @@ BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; switch(event) { case ESP_GATTC_CONNECT_EVT: { + if(BLEDevice::getMTU() != 23){ + esp_err_t err = esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); + } if(BLEDevice::m_securityLevel){ esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); } @@ -456,11 +465,42 @@ void BLEDevice::whiteListRemove(BLEAddress address) { ESP_LOGD(LOG_TAG, "<< whiteListRemove"); } // whiteListRemove +/* + * @brief Set encryption level that will be negotiated with peer device durng connection + * @param [in] level Requested encryption level + */ void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { BLEDevice::m_securityLevel = level; } +/* + * @brief Set callbacks that will be used to handle encryption negotiation events and authentication events + * @param [in] cllbacks Pointer to BLESecurityCallbacks class callback + */ void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { BLEDevice::m_securityCallbacks = callbacks; } + +/* + * @brief Setup local mtu that will be used to negotiate mtu during request from client peer + * @param [in] mtu Value to set local mtu, should be larger than 23 and lower or equal to 517 + */ +esp_err_t BLEDevice::setMTU(uint16_t mtu) { + ESP_LOGD(LOG_TAG, ">> setLocalMTU: %d", mtu); + esp_err_t err = esp_ble_gatt_set_local_mtu(mtu); + if(err == ESP_OK){ + m_localMTU = mtu; + } else { + ESP_LOGE(LOG_TAG, "can't set local mtu value: %d", mtu); + } + ESP_LOGD(LOG_TAG, "<< setLocalMTU"); + return err; +} + +/* + * @brief Get local MTU value set during mtu request or default value + */ +uint16_t BLEDevice::getMTU() { + return m_localMTU; +} #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 28889611..7f331435 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -40,6 +40,8 @@ class BLEDevice { static void whiteListRemove(BLEAddress address); // Remove an entry from the BLE white list. static void setEncryptionLevel(esp_ble_sec_act_t level); static void setSecurityCallbacks(BLESecurityCallbacks* pCallbacks); + static esp_err_t setMTU(uint16_t mtu); + static uint16_t getMTU(); private: static BLEServer *m_pServer; @@ -47,6 +49,7 @@ class BLEDevice { static BLEClient *m_pClient; static esp_ble_sec_act_t m_securityLevel; static BLESecurityCallbacks* m_securityCallbacks; + static uint16_t m_localMTU; static esp_gatt_if_t getGattcIF(); diff --git a/cpp_utils/tests/BLETests/SampleSecureClient.cpp b/cpp_utils/tests/BLETests/SampleSecureClient.cpp deleted file mode 100644 index 859291cb..00000000 --- a/cpp_utils/tests/BLETests/SampleSecureClient.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Create a sample BLE client that connects to a BLE server and then retrieves the current - * characteristic value. It will then periodically update the value of the characteristic on the - * remote server with the current time since boot. - */ -#include -#include -#include -#include -#include "BLEDevice.h" - -#include "BLEAdvertisedDevice.h" -#include "BLEClient.h" -#include "BLEScan.h" -#include "BLEUtils.h" -#include "Task.h" - -#include "sdkconfig.h" - -static const char* LOG_TAG = "SampleClient"; - -// See the following for generating UUIDs: -// https://www.uuidgenerator.net/ -// The remote service we wish to connect to. -static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); -// The characteristic of the remote service we are interested in. -static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); - -class MySecurity : public BLESecurityCallbacks { - - uint32_t onPassKeyRequest(){ - return 123456; - } - void onPassKeyNotify(uint32_t pass_key){ - ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); - } - bool onSecurityRequest(){ - return true; - } - void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ - if(auth_cmpl.success){ - ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); - esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); - ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); - } - ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); - } -}; - - -/** - * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server - * as the input parameter when the task is created. - */ -class MyClient: public Task { - void run(void* data) { - BLESecurity *pSecurity = new BLESecurity(); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM); - pSecurity->setCapability(ESP_IO_CAP_KBDISP); - pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - - BLEAddress* pAddress = (BLEAddress*)data; - BLEClient* pClient = BLEDevice::createClient(); - pClient->setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - pClient->setSecurityCallbacks(new MySecurity()); - - // Connect to the remove BLE Server. - pClient->connect(*pAddress); - - // Obtain a reference to the service we are after in the remote BLE server. - BLERemoteService* pRemoteService = pClient->getService(serviceUUID); - if (pRemoteService == nullptr) { - ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); - return; - } - - - // Obtain a reference to the characteristic in the service of the remote BLE server. - BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); - if (pRemoteCharacteristic == nullptr) { - ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); - return; - } - - // Read the value of the characteristic. - std::string value = pRemoteCharacteristic->readValue(); - ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); - - while(1) { - // Set a new value of the characteristic - ESP_LOGD(LOG_TAG, "Setting the new value"); - std::ostringstream stringStream; - struct timeval tv; - gettimeofday(&tv, nullptr); - stringStream << "Time since boot: " << tv.tv_sec; - pRemoteCharacteristic->writeValue(stringStream.str()); - - FreeRTOS::sleep(1000); - } - - pClient->disconnect(); - - ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); - ESP_LOGD(LOG_TAG, "-- End of task"); - } // run -}; // MyClient - - -/** - * Scan for BLE servers and find the first one that advertises the service we are looking for. - */ -class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { - /** - * Called for each advertising BLE server. - */ - void onResult(BLEAdvertisedDevice advertisedDevice) { - ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); - - if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { - advertisedDevice.getScan()->stop(); - - ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); - MyClient* pMyClient = new MyClient(); - pMyClient->setStackSize(18000); - pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); - } // Found our server - } // onResult -}; // MyAdvertisedDeviceCallbacks - - -/** - * Perform the work of a sample BLE client. - */ -void SampleSecureClient(void) { - ESP_LOGD(LOG_TAG, "Scanning sample starting"); - BLEDevice::init(""); - BLEScan *pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); - pBLEScan->setActiveScan(true); - pBLEScan->start(15); -} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleSecureServer.cpp b/cpp_utils/tests/BLETests/SampleSecureServer.cpp deleted file mode 100644 index 97c9037f..00000000 --- a/cpp_utils/tests/BLETests/SampleSecureServer.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Create a new BLE server. - */ -#include "BLEDevice.h" -#include "BLEServer.h" -#include "BLEUtils.h" -#include "BLE2902.h" -#include -#include -#include - - -#include "sdkconfig.h" - -static char LOG_TAG[] = "SampleServer"; - -class MySecurity : public BLESecurityCallbacks { - - uint32_t onPassKeyRequest(){ - return 123456; - } - void onPassKeyNotify(uint32_t pass_key){ - ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); - } - bool onSecurityRequest(){ - return true; - } - void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ - if(auth_cmpl.success){ - ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); - esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); - ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); - } - ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); - } -}; - -class MainBLEServer: public Task { - void run(void *data) { - ESP_LOGD(LOG_TAG, "Starting BLE work!"); - - BLEDevice::init("ESP32"); - BLEServer* pServer = BLEDevice::createServer(); - pServer->setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - pServer->setSecurityCallbacks(new MySecurity()); - - BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); - - BLECharacteristic* pCharacteristic = pService->createCharacteristic( - BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), - BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | - BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | - BLECharacteristic::PROPERTY_INDICATE - ); - pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - - pCharacteristic->setValue("Hello World!"); - - BLE2902* p2902Descriptor = new BLE2902(); - p2902Descriptor->setNotifications(true); - pCharacteristic->addDescriptor(p2902Descriptor); - - pService->start(); - - BLEAdvertising* pAdvertising = pServer->getAdvertising(); - pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); - - BLESecurity *pSecurity = new BLESecurity(); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); - pSecurity->setCapability(ESP_IO_CAP_NONE); - pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - - pAdvertising->start(); - - ESP_LOGD(LOG_TAG, "Advertising started!"); - delay(1000000); - } -}; - - -void SampleSecureServer(void) -{ - //esp_log_level_set("*", ESP_LOG_DEBUG); - MainBLEServer* pMainBleServer = new MainBLEServer(); - pMainBleServer->setStackSize(20000); - pMainBleServer->start(); - -} // app_main From 2fbca3c6b6fbe229a6dfaf348a1aaab1636c2c1d Mon Sep 17 00:00:00 2001 From: anio Date: Thu, 28 Dec 2017 14:36:20 +0200 Subject: [PATCH 211/381] Add timeout related to https://github.com/nkolban/esp32-snippets/issues/322 --- cpp_utils/HttpServer.cpp | 18 ++++++++++++++++++ cpp_utils/HttpServer.h | 3 +++ cpp_utils/Socket.cpp | 30 ++++++++++++++++++++++++++++-- cpp_utils/Socket.h | 2 ++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 488f05ce..3d68650b 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -28,6 +28,7 @@ static const char* LOG_TAG = "HttpServer"; HttpServer::HttpServer() { m_fileBufferSize = 4*1024; // Default size of the file buffer. m_portNumber = 80; // The default port number. + m_clientTimeout = 5; // The default timeout 5 seconds. m_rootPath = ""; // The default path. m_useSSL = false; // Default SSL is no. setDirectoryListing(false); // Default directory listing is disabled. @@ -159,6 +160,7 @@ class HttpServerTask: public Task { //Memory::checkIntegrity(); try { clientSocket = m_pHttpServer->m_socket.accept(); // Block waiting for a new external client connection. + clientSocket.setTimeout(m_pHttpServer->getClientTimeout()); } catch(std::exception &e) { ESP_LOGE("HttpServerTask", "Caught an exception waiting for new client!"); @@ -329,6 +331,22 @@ void HttpServer::listDirectory(std::string path, HttpResponse& response) { response.close(); } // listDirectory +/** + * @brief Set different socket timeout for new connections. + * @param [in] use Set to true to enable directory listing. + */ +void HttpServer::setClientTimeout(uint32_t timeout) { + m_clientTimeout = timeout; +} + +/** + * @brief Get current socket's timeout for new connections. + * @param [in] use Set to true to enable directory listing. + */ +uint32_t HttpServer::getClientTimeout() { + return m_clientTimeout; +} + /** * @brief Set whether or not we will list directories. * @param [in] use Set to true to enable directory listing. diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index c814d0c8..2d92d76f 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -81,6 +81,8 @@ class HttpServer { void setDirectoryListing(bool use); // Should we list the content of directories? void setFileBufferSize(size_t fileBufferSize); // Set the size of the file buffer void setRootPath(std::string path); // Set the root of the file system path. + void setClientTimeout(uint32_t timeout); // Set client's socket timeout + uint32_t getClientTimeout(); // Get client's socket timeout void start(uint16_t portNumber, bool useSSL=false); void stop(); // Stop a previously started server. @@ -95,6 +97,7 @@ class HttpServer { std::string m_rootPath; // Root path into the file system. Socket m_socket; bool m_useSSL; // Is this server listening on an HTTPS port? + uint32_t m_clientTimeout; // Default Timeout }; // HttpServer #endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index b7c41c1e..8f350094 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -60,8 +60,10 @@ Socket Socket::accept() { struct sockaddr addr; getBind(&addr); ESP_LOGD(LOG_TAG, ">> accept: Accepting on %s; sockFd: %d, using SSL: %d", addressToString(&addr).c_str(), m_sock, getSSL()); - - int clientSockFD = ::lwip_accept_r(m_sock, nullptr, nullptr); + struct sockaddr_in client_addr; + socklen_t sin_size; + int clientSockFD = ::lwip_accept_r(m_sock, (struct sockaddr *)&client_addr, &sin_size); + printf("------> new connection client %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); if (clientSockFD == -1) { SocketException se(errno); ESP_LOGE(LOG_TAG, "accept(): %s, m_sock=%d", strerror(errno), m_sock); @@ -272,6 +274,30 @@ bool Socket::operator <(const Socket& other) const { return m_sock < other.m_sock; } +int Socket::setSocketOption(int option, char* value, size_t len) +{ + int res = setsockopt(m_sock, SOL_SOCKET, option, value, len); + if(res < 0) { + ESP_LOGE(LOG_TAG, "%X : %d", option, errno); + } + return res; +} + +/** + * @brief Socket timeout. + * @param [in] seconds to wait. + */ +int Socket::setTimeout(uint32_t seconds) +{ + struct timeval tv; + tv.tv_sec = seconds; + tv.tv_usec = 0; + if(setSocketOption(SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)) < 0) { + return -1; + } + return setSocketOption(SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); +} + std::string Socket::readToDelim(std::string delim) { std::string ret; diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 6eef11db..a4a71398 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -68,6 +68,8 @@ class Socket { int connect(struct in_addr address, uint16_t port); int connect(char* address, uint16_t port); int createSocket(bool isDatagram = false); + int setSocketOption(int option, char* value, size_t len); + int setTimeout(uint32_t seconds); void getBind(struct sockaddr* pAddr); int getFD() const; bool getSSL() const; From 744af7b2e27d2f63eee6a591eddee141d39b70aa Mon Sep 17 00:00:00 2001 From: anio Date: Fri, 29 Dec 2017 17:31:40 +0200 Subject: [PATCH 212/381] Disable timeout for WebSocket's #322 --- cpp_utils/HttpServer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 3d68650b..c6f4b419 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -171,6 +171,9 @@ class HttpServerTask: public Task { HttpRequest request(clientSocket); // Build the HTTP Request from the socket. + if (request.isWebsocket()) { // If this is a WebSocket + clientSocket.setTimeout(0); // Clear the timeout. + } request.dump(); // debug. processRequest(request); // Process the request. if (!request.isWebsocket()) { // If this is NOT a WebSocket, then close it as the request From 20214be69235b5e7da3321d599282b64e37b9028 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 30 Dec 2017 12:31:53 -0600 Subject: [PATCH 213/381] Fixes for #328 --- cpp_utils/BLECharacteristic.cpp | 23 ++++++++++++++++------- cpp_utils/HttpServer.cpp | 10 +++++++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 52310d7a..2e1a1b6a 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -327,17 +327,16 @@ void BLECharacteristic::handleGATTServerEvent( case ESP_GATTS_READ_EVT: { if (param->read.handle == m_handle) { - if (m_pCallbacks != nullptr) { - m_pCallbacks->onRead(this); // Invoke the read callback. - } + // Here's an interesting thing. The read request has the option of saying whether we need a response // or not. What would it "mean" to receive a read request and NOT send a response back? That feels like // a very strange read. // // We have to handle the case where the data we wish to send back to the client is greater than the maximum -// packet size of 22 bytes. In this case, we become responsible for chunking the data into uints of 22 bytes. -// The apparent algorithm is as follows. +// packet size of 22 bytes. In this case, we become responsible for chunking the data into units of 22 bytes. +// The apparent algorithm is as follows: +// // If the is_long flag is set then this is a follow on from an original read and we will already have sent at least 22 bytes. // If the is_long flag is not set then we need to check how much data we are going to send. If we are sending LESS than // 22 bytes, then we "just" send it and thats the end of the story. @@ -359,8 +358,10 @@ void BLECharacteristic::handleGATTServerEvent( if (param->read.need_rsp) { ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); esp_gatt_rsp_t rsp; - std::string value = m_value.getValue(); + if (param->read.is_long) { + std::string value = m_value.getValue(); + if (value.length() - m_value.getReadOffset() < maxOffset) { // This is the last in the chain rsp.attr_value.len = value.length() - m_value.getReadOffset(); @@ -374,7 +375,14 @@ void BLECharacteristic::handleGATTServerEvent( memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len); m_value.setReadOffset(rsp.attr_value.offset + maxOffset); } - } else { + } else { // read.is_long == false + + if (m_pCallbacks != nullptr) { // If is.long is false then this is the first (or only) request to read data, so invoke the callback + m_pCallbacks->onRead(this); // Invoke the read callback. + } + + std::string value = m_value.getValue(); + if (value.length()+1 > maxOffset) { // Too big for a single shot entry. m_value.setReadOffset(maxOffset); @@ -408,6 +416,7 @@ void BLECharacteristic::handleGATTServerEvent( break; } // ESP_GATTS_READ_EVT + // ESP_GATTS_CONF_EVT // // conf: diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index c6f4b419..fe31af83 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -1,5 +1,10 @@ /* * HttpServer.cpp + * Design: + * This class represents an HTTP server. We create an instance of the class and then it is configured. + * When the user has completed the configuration, they execute the start() method which starts it running. + * A subsequent call to stop() will stop it. When start() is called, a new task is created which listens + * for incoming messages. * * Created on: Aug 30, 2017 * Author: kolban @@ -157,7 +162,7 @@ class HttpServerTask: public Task { while(1) { // Loop forever. ESP_LOGD("HttpServerTask", "Waiting for new peer client"); - //Memory::checkIntegrity(); + try { clientSocket = m_pHttpServer->m_socket.accept(); // Block waiting for a new external client connection. clientSocket.setTimeout(m_pHttpServer->getClientTimeout()); @@ -167,8 +172,7 @@ class HttpServerTask: public Task { return; } - ESP_LOGD("HttpServerTask", "HttpServer listening on port %d received a new client connection; sockFd=%d", m_pHttpServer->getPort(), clientSocket.getFD()); - + ESP_LOGD("HttpServerTask", "HttpServer listening on port %d has received a new client connection; sockFd=%d", m_pHttpServer->getPort(), clientSocket.getFD()); HttpRequest request(clientSocket); // Build the HTTP Request from the socket. if (request.isWebsocket()) { // If this is a WebSocket From 3943f99a5dba851e6e62be0ed753619361ed79a0 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 30 Dec 2017 19:20:22 -0600 Subject: [PATCH 214/381] Code changes for #329 Note that this is an interface breaking change. --- cpp_utils/WebSocket.cpp | 13 ++++++++----- cpp_utils/WebSocket.h | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp index 791be38c..14642c34 100644 --- a/cpp_utils/WebSocket.cpp +++ b/cpp_utils/WebSocket.cpp @@ -158,7 +158,7 @@ class WebSocketReader: public Task { case OPCODE_BINARY: { if (pWebSocketHandler != nullptr) { WebSocketInputStreambuf streambuf(pWebSocket->getSocket(), payloadLen, frame.mask==1?mask:nullptr); - pWebSocketHandler->onMessage(&streambuf); + pWebSocketHandler->onMessage(&streambuf, pWebSocket); //streambuf.discard(); } break; @@ -203,7 +203,8 @@ class WebSocketReader: public Task { * If no over-riding handler is provided for the "close" event, this method is called. */ void WebSocketHandler::onClose() { - ESP_LOGD("WebSocketHandler", ">> WebSocketHandler:onClose()"); + ESP_LOGD("WebSocketHandler", ">> onClose"); + ESP_LOGD("WebSocketHandler", "<< onClose"); } // onClose @@ -217,8 +218,9 @@ void WebSocketHandler::onClose() { * ``` * This will read the whole message into the string stream. */ -void WebSocketHandler::onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf) { - ESP_LOGD("WebSocketHandler", ">> onMessage") +void WebSocketHandler::onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf, WebSocket *pWebSocket) { + ESP_LOGD("WebSocketHandler", ">> onMessage"); + ESP_LOGD("WebSocketHandler", "<< onMessage"); } // onData @@ -227,7 +229,8 @@ void WebSocketHandler::onMessage(WebSocketInputStreambuf* pWebSocketInputStreamb * If no over-riding handler is provided for the "error" event, this method is called. */ void WebSocketHandler::onError(std::string error) { - ESP_LOGD("WebSocketHandler", ">> WebSocketHandler:onError()"); + ESP_LOGD("WebSocketHandler", ">> onError: %s", error.c_str()); + ESP_LOGD("WebSocketHandler", "<< onError"); } // onError diff --git a/cpp_utils/WebSocket.h b/cpp_utils/WebSocket.h index d1308f15..80c72f0c 100644 --- a/cpp_utils/WebSocket.h +++ b/cpp_utils/WebSocket.h @@ -13,6 +13,7 @@ #undef close #undef send class WebSocketReader; +class WebSocket; // +-------------------------------+ // | WebSocketInputStreambuf | @@ -45,7 +46,7 @@ class WebSocketHandler { public: virtual ~WebSocketHandler(); virtual void onClose(); - virtual void onMessage(WebSocketInputStreambuf *pWebSocketInputStreambuf); + virtual void onMessage(WebSocketInputStreambuf *pWebSocketInputStreambuf, WebSocket *pWebSocket); virtual void onError(std::string error); }; From 5afeced69002e6ffffd7cf1676ad11891217de3f Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 31 Dec 2017 11:05:56 -0600 Subject: [PATCH 215/381] Fixes for #332 --- cpp_utils/JSON.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index 1f752c63..2c1de785 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -229,10 +229,7 @@ JsonArray JsonObject::getArray(std::string name) { */ bool JsonObject::getBoolean(std::string name) { cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); - if (node->valueint == 0) { - return false; - } - return true; + return cJSON_IsTrue(node); } // getBoolean @@ -316,7 +313,7 @@ void JsonObject::setArray(std::string name, JsonArray array) { * @return N/A. */ void JsonObject::setBoolean(std::string name, bool value) { - cJSON_AddItemToObject(m_node, name.c_str(), cJSON_CreateBool(value)); + cJSON_AddItemToObject(m_node, name.c_str(), value?cJSON_CreateTrue():cJSON_CreateFalse()); } // setBoolean From 82ba6d77b7b156685dd300297c240c13db8ff3bb Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 1 Jan 2018 09:17:57 -0600 Subject: [PATCH 216/381] Fixes for #335 --- cpp_utils/BLEDevice.cpp | 5 ++- cpp_utils/BLEHIDDevice.cpp | 6 +++ cpp_utils/BLEHIDDevice.h | 6 ++- cpp_utils/BLESecurity.cpp | 90 +++++++++++++++++++++----------------- cpp_utils/BLESecurity.h | 22 +++++++--- cpp_utils/Socket.cpp | 2 +- 6 files changed, 80 insertions(+), 51 deletions(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index a2dd80b4..427666c1 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -142,7 +142,10 @@ uint16_t BLEDevice::m_localMTU = 23; switch(event) { case ESP_GATTC_CONNECT_EVT: { if(BLEDevice::getMTU() != 23){ - esp_err_t err = esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); + esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } } if(BLEDevice::m_securityLevel){ esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); diff --git a/cpp_utils/BLEHIDDevice.cpp b/cpp_utils/BLEHIDDevice.cpp index c5485a81..3e7fbc89 100644 --- a/cpp_utils/BLEHIDDevice.cpp +++ b/cpp_utils/BLEHIDDevice.cpp @@ -4,6 +4,10 @@ * Created on: Dec 18, 2017 * Author: chegewara */ + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + //#include "BLEUUID.h" #include "BLEHIDDevice.h" @@ -152,3 +156,5 @@ BLEDescriptor* BLEHIDDevice::featureReport() { BLEDescriptor* BLEHIDDevice::batteryLevel() { return m_batteryLevelDescriptor; } + +#endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEHIDDevice.h b/cpp_utils/BLEHIDDevice.h index 43e4ccdc..bf2b29e0 100644 --- a/cpp_utils/BLEHIDDevice.h +++ b/cpp_utils/BLEHIDDevice.h @@ -7,6 +7,10 @@ #ifndef _BLEHIDDEVICE_H_ #define _BLEHIDDEVICE_H_ + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + #include "BLECharacteristic.h" #include "BLEService.h" #include "BLEDescriptor.h" @@ -83,5 +87,5 @@ class BLEHIDDevice { BLE2902* m_batteryLevelNotifications; //0x2902 }; - +#endif // CONFIG_BT_ENABLED #endif /* _BLEHIDDEVICE_H_ */ diff --git a/cpp_utils/BLESecurity.cpp b/cpp_utils/BLESecurity.cpp index 273ec77f..4cf964a7 100644 --- a/cpp_utils/BLESecurity.cpp +++ b/cpp_utils/BLESecurity.cpp @@ -6,6 +6,8 @@ */ #include +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) BLESecurity::BLESecurity() { } @@ -19,79 +21,85 @@ void BLESecurity::setAuthenticationMode(esp_ble_auth_req_t auth_req) { m_authReq = auth_req; esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &m_authReq, sizeof(uint8_t)); // <--- setup requested authentication mode } -/* + +/** * @brief Set our device IO capability to let end user perform authorization * either by displaying or entering generated 6-digits pin code */ void BLESecurity::setCapability(esp_ble_io_cap_t iocap) { m_iocap = iocap; esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); -} -/* +} // setCapability + + +/** * @brief Init encryption key by server * @param key_size is value between 7 and 16 */ void BLESecurity::setInitEncryptionKey(uint8_t init_key) { m_initKey = init_key; esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &m_initKey, sizeof(uint8_t)); -} +} // setInitEncryptionKey -/* + +/** * @brief Init encryption key by client * @param key_size is value between 7 and 16 */ void BLESecurity::setRespEncryptionKey(uint8_t resp_key) { m_respKey = resp_key; esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &m_respKey, sizeof(uint8_t)); -} +} // setRespEncryptionKey -/* + +/** * * */ void BLESecurity::setKeySize(uint8_t key_size) { m_keySize = key_size; esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &m_keySize, sizeof(uint8_t)); -} +} //setKeySize -/* + +/** * @brief Debug function to display what keys are exchanged by peers */ char* BLESecurity::esp_key_type_to_str(esp_ble_key_type_t key_type) { - char *key_str = NULL; - switch(key_type) { - case ESP_LE_KEY_NONE: - key_str = (char*)"ESP_LE_KEY_NONE"; - break; - case ESP_LE_KEY_PENC: - key_str = (char*)"ESP_LE_KEY_PENC"; - break; - case ESP_LE_KEY_PID: - key_str = (char*)"ESP_LE_KEY_PID"; - break; - case ESP_LE_KEY_PCSRK: - key_str = (char*)"ESP_LE_KEY_PCSRK"; - break; - case ESP_LE_KEY_PLK: - key_str = (char*)"ESP_LE_KEY_PLK"; - break; - case ESP_LE_KEY_LLK: - key_str = (char*)"ESP_LE_KEY_LLK"; - break; + char* key_str = nullptr; + switch(key_type) { + case ESP_LE_KEY_NONE: + key_str = (char*)"ESP_LE_KEY_NONE"; + break; + case ESP_LE_KEY_PENC: + key_str = (char*)"ESP_LE_KEY_PENC"; + break; + case ESP_LE_KEY_PID: + key_str = (char*)"ESP_LE_KEY_PID"; + break; + case ESP_LE_KEY_PCSRK: + key_str = (char*)"ESP_LE_KEY_PCSRK"; + break; + case ESP_LE_KEY_PLK: + key_str = (char*)"ESP_LE_KEY_PLK"; + break; + case ESP_LE_KEY_LLK: + key_str = (char*)"ESP_LE_KEY_LLK"; + break; case ESP_LE_KEY_LENC: - key_str = (char*)"ESP_LE_KEY_LENC"; - break; + key_str = (char*)"ESP_LE_KEY_LENC"; + break; case ESP_LE_KEY_LID: - key_str = (char*)"ESP_LE_KEY_LID"; - break; + key_str = (char*)"ESP_LE_KEY_LID"; + break; case ESP_LE_KEY_LCSRK: - key_str = (char*)"ESP_LE_KEY_LCSRK"; - break; + key_str = (char*)"ESP_LE_KEY_LCSRK"; + break; default: - key_str = (char*)"INVALID BLE KEY TYPE"; - break; - - } - return key_str; -} + key_str = (char*)"INVALID BLE KEY TYPE"; + break; + } + return key_str; +} // esp_key_type_to_str +#endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLESecurity.h b/cpp_utils/BLESecurity.h index 1a649283..67c41efb 100644 --- a/cpp_utils/BLESecurity.h +++ b/cpp_utils/BLESecurity.h @@ -7,6 +7,9 @@ #ifndef COMPONENTS_CPP_UTILS_BLESECURITY_H_ #define COMPONENTS_CPP_UTILS_BLESECURITY_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + #include class BLESecurity { @@ -26,7 +29,8 @@ class BLESecurity { uint8_t m_initKey; uint8_t m_respKey; uint8_t m_keySize; -}; +}; // BLESecurity + /* * @brief Callbacks to handle GAP events related to authorization @@ -35,29 +39,33 @@ class BLESecurityCallbacks { public: virtual ~BLESecurityCallbacks() {}; - /* + /** * @brief Its request from peer device to input authentication pin code displayed on peer device. * It requires that our device is capable to input 6-digits code by end user * @return Return 6-digits integer value from input device */ virtual uint32_t onPassKeyRequest() = 0; - /* + + /** * @brief Provide us 6-digits code to perform authentication. * It requires that our device is capable to display this code to end user * @param */ virtual void onPassKeyNotify(uint32_t pass_key); - /* + + /** * @brief Here we can make decision if we want to let negotiate authorization with peer device or not * return Return true if we accept this peer device request */ + virtual bool onSecurityRequest(); - /* + /** * Provide us information when authentication process is completed */ virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t); virtual bool onConfirmPIN(uint32_t pin); -}; +}; // BLESecurityCallbacks -#endif /* COMPONENTS_CPP_UTILS_BLESECURITY_H_ */ +#endif // CONFIG_BT_ENABLED +#endif // COMPONENTS_CPP_UTILS_BLESECURITY_H_ diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 8f350094..a0b668fc 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -63,7 +63,7 @@ Socket Socket::accept() { struct sockaddr_in client_addr; socklen_t sin_size; int clientSockFD = ::lwip_accept_r(m_sock, (struct sockaddr *)&client_addr, &sin_size); - printf("------> new connection client %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); + //printf("------> new connection client %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); if (clientSockFD == -1) { SocketException se(errno); ESP_LOGE(LOG_TAG, "accept(): %s, m_sock=%d", strerror(errno), m_sock); From ac27ff1e7fc22f87e23e3383953152fcb77a8799 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 1 Jan 2018 13:22:05 -0600 Subject: [PATCH 217/381] Updates for #337 --- Documentation/BLE C++ Guide.pdf | Bin 283509 -> 286129 bytes 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Documentation/BLE C++ Guide.pdf diff --git a/Documentation/BLE C++ Guide.pdf b/Documentation/BLE C++ Guide.pdf old mode 100644 new mode 100755 index e68e0398173ea68d4dc5817a7e3f16b01ef18740..cc4ca4bbfeace1c3fbb35883e33efdba230f6b9d GIT binary patch delta 77268 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%0AP&}BM!AQ4DJZsF>Z2FIQe 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 delta 74878 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 z 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

Jo_x$JDAFV_~cU(P;gyT?Aj9I)-PD-mO0w%B&s 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 Date: Mon, 1 Jan 2018 14:52:12 -0600 Subject: [PATCH 218/381] Fixes for #331 --- cpp_utils/FreeRTOS.cpp | 41 ++++++++++++++++++++++++++++++++-------- cpp_utils/FreeRTOS.h | 4 ++-- cpp_utils/HttpServer.cpp | 15 +++++++++++++-- cpp_utils/HttpServer.h | 9 +++++---- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 674e6f8e..1ae01d73 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -168,17 +168,25 @@ void FreeRTOS::Semaphore::giveFromISR() { /** * @brief Take a semaphore. * Take a semaphore and wait indefinitely. + * @param [in] owner The new owner (for debugging) + * @return True if we took the semaphore. */ -void FreeRTOS::Semaphore::take(std::string owner) +bool FreeRTOS::Semaphore::take(std::string owner) { ESP_LOGD(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); + bool rc = false; if (m_usePthreads) { pthread_mutex_lock(&m_pthread_mutex); } else { - xSemaphoreTake(m_semaphore, portMAX_DELAY); + rc = ::xSemaphoreTake(m_semaphore, portMAX_DELAY); } m_owner = owner; - ESP_LOGD(LOG_TAG, "Semaphore taken: %s", toString().c_str()); + if (rc) { + ESP_LOGD(LOG_TAG, "Semaphore taken: %s", toString().c_str()); + } else { + ESP_LOGE(LOG_TAG, "Semaphore NOT taken: %s", toString().c_str()); + } + return rc; } // Semaphore::take @@ -186,20 +194,33 @@ void FreeRTOS::Semaphore::take(std::string owner) * @brief Take a semaphore. * Take a semaphore but return if we haven't obtained it in the given period of milliseconds. * @param [in] timeoutMs Timeout in milliseconds. + * @param [in] owner The new owner (for debugging) + * @return True if we took the semaphore. */ -void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { +bool FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); - + bool rc = false; if (m_usePthreads) { - assert(false); + assert(false); // We apparently don't have a timed wait for pthreads. } else { - xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); + rc = ::xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); } m_owner = owner; - ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); + if (rc) { + ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); + } else { + ESP_LOGE(LOG_TAG, "Semaphore NOT taken: %s", toString().c_str()); + } + return rc; } // Semaphore::take + + +/** + * @brief Create a string representation of the semaphore. + * @return A string representation of the semaphore. + */ std::string FreeRTOS::Semaphore::toString() { std::stringstream stringStream; stringStream << "name: "<< m_name << " (0x" << std::hex << std::setfill('0') << (uint32_t)m_semaphore << "), owner: " << m_owner; @@ -207,6 +228,10 @@ std::string FreeRTOS::Semaphore::toString() { } // toString +/** + * @brief Set the name of the semaphore. + * @param [in] name The name of the semaphore. + */ void FreeRTOS::Semaphore::setName(std::string name) { m_name = name; } // setName diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index 43a3b8f4..ab0e83d8 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -36,8 +36,8 @@ class FreeRTOS { void give(uint32_t value); void giveFromISR(); void setName(std::string name); - void take(std::string owner=""); - void take(uint32_t timeoutMs, std::string owner=""); + bool take(std::string owner=""); + bool take(uint32_t timeoutMs, std::string owner=""); std::string toString(); uint32_t wait(std::string owner=""); diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index fe31af83..15a78248 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -169,10 +169,11 @@ class HttpServerTask: public Task { } catch(std::exception &e) { ESP_LOGE("HttpServerTask", "Caught an exception waiting for new client!"); + m_pHttpServer->m_semaphoreServerStarted.give(); // Release the semaphore .. we are now no longer running. return; } - ESP_LOGD("HttpServerTask", "HttpServer listening on port %d has received a new client connection; sockFd=%d", m_pHttpServer->getPort(), clientSocket.getFD()); + ESP_LOGD("HttpServerTask", "HttpServer that was listening on port %d has received a new client connection; sockFd=%d", m_pHttpServer->getPort(), clientSocket.getFD()); HttpRequest request(clientSocket); // Build the HTTP Request from the socket. if (request.isWebsocket()) { // If this is a WebSocket @@ -414,11 +415,20 @@ void HttpServer::start(uint16_t portNumber, bool useSSL) { // Design: // The start of the HTTP server should be as fast as possible. ESP_LOGD(LOG_TAG, ">> start: port: %d, useSSL: %d", portNumber, useSSL); + + // Take the semaphore that says that we are now running. If we are already running, then end here as + // there is nothing further to do. + if (m_semaphoreServerStarted.take(100, "start") == false) { + ESP_LOGD(LOG_TAG, "<< start: Already running"); + return; + } + m_useSSL = useSSL; m_portNumber = portNumber; HttpServerTask* pHttpServerTask = new HttpServerTask("HttpServerTask"); pHttpServerTask->start(this); + ESP_LOGD(LOG_TAG, "<< start"); } // start @@ -430,7 +440,8 @@ void HttpServer::stop() { // that is listening for incoming connections. That will then shutdown all the other // activities. ESP_LOGD(LOG_TAG, ">> stop"); - m_socket.close(); + m_socket.close(); // Close the socket that is being used to watch for incoming requests. + m_semaphoreServerStarted.wait("stop"); // Wait for the server to stop. ESP_LOGD(LOG_TAG, "<< stop"); } // stop diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index 2d92d76f..95558c05 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -16,7 +16,7 @@ #include "SockServ.h" #include "HttpRequest.h" #include "HttpResponse.h" -//#include "SockServ.h" +#include "FreeRTOS.h" #include class HttpServerTask; @@ -74,15 +74,15 @@ class HttpServer { HttpRequest* pHttpRequest, HttpResponse* pHttpResponse) ); + uint32_t getClientTimeout(); // Get client's socket timeout size_t getFileBufferSize(); // Get the current size of the file buffer. uint16_t getPort(); // Get the port on which the Http server is listening. std::string getRootPath(); // Get the root of the file system path. bool getSSL(); // Are we using SSL? + void setClientTimeout(uint32_t timeout); // Set client's socket timeout void setDirectoryListing(bool use); // Should we list the content of directories? void setFileBufferSize(size_t fileBufferSize); // Set the size of the file buffer void setRootPath(std::string path); // Set the root of the file system path. - void setClientTimeout(uint32_t timeout); // Set client's socket timeout - uint32_t getClientTimeout(); // Get client's socket timeout void start(uint16_t portNumber, bool useSSL=false); void stop(); // Stop a previously started server. @@ -97,7 +97,8 @@ class HttpServer { std::string m_rootPath; // Root path into the file system. Socket m_socket; bool m_useSSL; // Is this server listening on an HTTPS port? - uint32_t m_clientTimeout; // Default Timeout + uint32_t m_clientTimeout; // Default Timeout + FreeRTOS::Semaphore m_semaphoreServerStarted = FreeRTOS::Semaphore("ServerStarted"); }; // HttpServer #endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ From 1ce09382cdb0a5f3d9e7ffb83d5e8699bd95402f Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 1 Jan 2018 15:19:02 -0600 Subject: [PATCH 219/381] Changes for #337 --- cpp_utils/BLEAdvertising.cpp | 60 +++++++++++++++++++++++++++------ cpp_utils/BLEAdvertising.h | 1 + cpp_utils/DesignNotes/BLECPP.md | 4 +++ 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index e026cbec..3f22f119 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -259,7 +259,7 @@ void BLEAdvertisementData::addData(std::string data) { void BLEAdvertisementData::setAppearance(uint16_t appearance) { char cdata[2]; cdata[0] = 3; - cdata[1] = ESP_BLE_AD_TYPE_APPEARANCE; + cdata[1] = ESP_BLE_AD_TYPE_APPEARANCE; // 0x19 addData(std::string(cdata, 2) + std::string((char *)&appearance,2)); } // setAppearance @@ -274,7 +274,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; - cdata[1] = ESP_BLE_AD_TYPE_16SRV_CMPL; + cdata[1] = ESP_BLE_AD_TYPE_16SRV_CMPL; // 0x03 addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); break; } @@ -282,7 +282,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { case 32: { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; - cdata[1] = ESP_BLE_AD_TYPE_32SRV_CMPL; + cdata[1] = ESP_BLE_AD_TYPE_32SRV_CMPL; // 0x05 addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); break; } @@ -290,7 +290,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { case 128: { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; - cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; + cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; // 0x07 addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); break; } @@ -315,7 +315,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { void BLEAdvertisementData::setFlags(uint8_t flag) { char cdata[3]; cdata[0] = 2; - cdata[1] = ESP_BLE_AD_TYPE_FLAG; + cdata[1] = ESP_BLE_AD_TYPE_FLAG; // 0x01 cdata[2] = flag; addData(std::string(cdata, 3)); } // setFlag @@ -330,7 +330,7 @@ void BLEAdvertisementData::setManufacturerData(std::string data) { ESP_LOGD("BLEAdvertisementData", ">> setManufacturerData"); char cdata[2]; cdata[0] = data.length() + 1; - cdata[1] = ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE; + cdata[1] = ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE; // 0xff addData(std::string(cdata, 2) + data); ESP_LOGD("BLEAdvertisementData", "<< setManufacturerData"); } // setManufacturerData @@ -344,7 +344,7 @@ void BLEAdvertisementData::setName(std::string name) { ESP_LOGD("BLEAdvertisementData", ">> setName: %s", name.c_str()); char cdata[2]; cdata[0] = name.length() + 1; - cdata[1] = ESP_BLE_AD_TYPE_NAME_CMPL; + cdata[1] = ESP_BLE_AD_TYPE_NAME_CMPL; // 0x09 addData(std::string(cdata, 2) + name); ESP_LOGD("BLEAdvertisementData", "<< setName"); } // setName @@ -360,7 +360,7 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; - cdata[1] = ESP_BLE_AD_TYPE_16SRV_PART; + cdata[1] = ESP_BLE_AD_TYPE_16SRV_PART; // 0x02 addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); break; } @@ -368,7 +368,7 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { case 32: { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; - cdata[1] = ESP_BLE_AD_TYPE_32SRV_PART; + cdata[1] = ESP_BLE_AD_TYPE_32SRV_PART; // 0x04 addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); break; } @@ -376,7 +376,7 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { case 128: { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; - cdata[1] = ESP_BLE_AD_TYPE_128SRV_PART; + cdata[1] = ESP_BLE_AD_TYPE_128SRV_PART; // 0x06 addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); break; } @@ -387,6 +387,44 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { } // setPartialServices +/** + * @brief Set the service data (UUID + data) + * @param [in] uuid The UUID to set with the service data. Size of UUID will be used. + * @param [in] data The data to be associated with the service data advert. + */ +void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { + char cdata[2]; + switch(uuid.bitSize()) { + case 16: { + // [Len] [0x16] [UUID16] data + cdata[0] = data.length() + 3; + cdata[1] = ESP_BLE_AD_TYPE_SERVICE_DATA; // 0x16 + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2) + data); + break; + } + + case 32: { + // [Len] [0x20] [UUID32] data + cdata[0] = data.length() + 5; + cdata[1] = ESP_BLE_AD_TYPE_32SERVICE_DATA; // 0x20 + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4) + data); + break; + } + + case 128: { + // [Len] [0x21] [UUID128] data + cdata[0] = data.length() + 17; + cdata[1] = ESP_BLE_AD_TYPE_128SERVICE_DATA; // 0x21 + addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16) + data); + break; + } + + default: + return; + } +} // setServiceData + + /** * @brief Set the short name. * @param [in] The short name of the device. @@ -395,7 +433,7 @@ void BLEAdvertisementData::setShortName(std::string name) { ESP_LOGD("BLEAdvertisementData", ">> setShortName: %s", name.c_str()); char cdata[2]; cdata[0] = name.length() + 1; - cdata[1] = ESP_BLE_AD_TYPE_NAME_SHORT; + cdata[1] = ESP_BLE_AD_TYPE_NAME_SHORT; // 0x08 addData(std::string(cdata, 2) + name); ESP_LOGD("BLEAdvertisementData", "<< setShortName"); } // setShortName diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 8d53b64e..c9d5ba98 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -56,6 +56,7 @@ class BLEAdvertisementData { void setManufacturerData(std::string data); void setName(std::string name); void setPartialServices(BLEUUID uuid); + void setServiceData(BLEUUID uuid, std::string data); void setShortName(std::string name); private: diff --git a/cpp_utils/DesignNotes/BLECPP.md b/cpp_utils/DesignNotes/BLECPP.md index 9ce9715a..64e7a04c 100644 --- a/cpp_utils/DesignNotes/BLECPP.md +++ b/cpp_utils/DesignNotes/BLECPP.md @@ -40,7 +40,10 @@ The following advertising types are supported: |0x07|Complete list of 128 bit service UUIDs |0x08|Shortened local name |0x09|Complete local name +|0x16|Service data (16 bit) |0x19|Appearance +|0x20|Service data (32 bit) +|0x21|Service data (128 bit) |0xFF|Manufacturer data @@ -61,5 +64,6 @@ See also: |0x03, 0x05, 0x07|`setCompleteServices(BLEUUID)` |0x08|`setShortName(std::string)` |0x09|`setName(std::string)` +|0x16, 0x20, 0x21|`setServiceData(BLEUUID, std::string)` |0x19|`setAppearance(uint16_t)` |0xFF|`setManufacturerData(std::string)` \ No newline at end of file From 59f667e5614c611ad68c8d1766b731e911de43bf Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 3 Jan 2018 19:31:29 -0600 Subject: [PATCH 220/381] Changes for #333 --- cpp_utils/Socket.cpp | 35 +++++++++++++++++++++++------------ cpp_utils/Socket.h | 6 +++--- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index a0b668fc..602bdc74 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -107,9 +107,9 @@ std::string Socket::addressToString(struct sockaddr* addr) { * Specify an address of INADDR_ANY to use the local server IP. * @param [in] port Port number to bind. * @param [in] address Address to bind. - * @return N/A + * @return Returns 0 on success. */ -void Socket::bind(uint16_t port, uint32_t address) { +int Socket::bind(uint16_t port, uint32_t address) { ESP_LOGD(LOG_TAG, ">> bind: port=%d, address=0x%x", port, address); if (m_sock == -1) { @@ -120,35 +120,39 @@ void Socket::bind(uint16_t port, uint32_t address) { serverAddress.sin_addr.s_addr = htonl(address); serverAddress.sin_port = htons(port); int rc = ::lwip_bind_r(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); - if (rc == -1) { + if (rc != 0) { ESP_LOGE(LOG_TAG, "<< bind: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); - return; + return rc; } ESP_LOGD(LOG_TAG, "<< bind"); + return rc; } // bind /** * @brief Close the socket. * - * @return N/A. + * @return Returns 0 on success. */ -void Socket::close() { +int Socket::close() { ESP_LOGD(LOG_TAG, "close: m_sock=%d, ssl: %d", m_sock, getSSL()); + int rc; if (getSSL()) { - int rc = mbedtls_ssl_close_notify(&m_sslContext); + rc = mbedtls_ssl_close_notify(&m_sslContext); if (rc < 0) { ESP_LOGD(LOG_TAG, "mbedtls_ssl_close_notify: %d", rc); } } + rc = 0; if (m_sock != -1) { ESP_LOGD(LOG_TAG, "Calling lwip_close on %d", m_sock); - int rc = lwip_close_r(m_sock); + rc = ::lwip_close_r(m_sock); if (rc != 0) { - ESP_LOGE(LOG_TAG, "Error with lwip_close"); + ESP_LOGE(LOG_TAG, "Error with lwip_close: %d", rc); } } m_sock = -1; + return rc; } // close @@ -253,20 +257,27 @@ bool Socket::isValid() { * @brief Create a listening socket. * @param [in] port The port number to listen upon. * @param [in] isDatagram True if we are listening on a datagram. The default is false. + * @return Returns 0 on success. */ -void Socket::listen(uint16_t port, bool isDatagram) { +int Socket::listen(uint16_t port, bool isDatagram) { ESP_LOGD(LOG_TAG, ">> listen: port: %d, isDatagram: %d", port, isDatagram); createSocket(isDatagram); - bind(port, 0); + int rc = bind(port, 0); + if (rc != 0) { + ESP_LOGE(LOG_TAG, "<< listen: Error in bind: %s", strerror(errno)); + return rc; + } // For a datagram socket, we don't execute a listen call. That is is only for connection oriented // sockets. if (!isDatagram) { - int rc = ::lwip_listen_r(m_sock, 5); + rc = ::lwip_listen_r(m_sock, 5); if (rc == -1) { ESP_LOGE(LOG_TAG, "<< listen: %s", strerror(errno)); + return rc; } } ESP_LOGD(LOG_TAG, "<< listen"); + return 0; } // listen diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index a4a71398..7685bf7c 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -63,8 +63,8 @@ class Socket { Socket accept(); static std::string addressToString(struct sockaddr* addr); - void bind(uint16_t port, uint32_t address); - void close(); + int bind(uint16_t port, uint32_t address); + int close(); int connect(struct in_addr address, uint16_t port); int connect(char* address, uint16_t port); int createSocket(bool isDatagram = false); @@ -74,7 +74,7 @@ class Socket { int getFD() const; bool getSSL() const; bool isValid(); - void listen(uint16_t port, bool isDatagram=false); + int listen(uint16_t port, bool isDatagram=false); bool operator<(const Socket& other) const; std::string readToDelim(std::string delim); size_t receive(uint8_t* data, size_t length, bool exact=false); From 52cb88c6e2a046a58b9bceac0861066988469d97 Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 4 Jan 2018 03:46:30 +0100 Subject: [PATCH 221/381] Fully functional HID library with keyboard example --- cpp_utils/BLEDevice.cpp | 1 + cpp_utils/BLEHIDDevice.cpp | 260 +++++++++++------- cpp_utils/BLEHIDDevice.h | 46 +--- cpp_utils/HIDTypes.h | 9 +- .../tests/BLETests/SampleHIDKeyboard.cpp | 192 +++++++++++++ 5 files changed, 382 insertions(+), 126 deletions(-) create mode 100644 cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 427666c1..b333ed7c 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -98,6 +98,7 @@ uint16_t BLEDevice::m_localMTU = 23; switch(event) { case ESP_GATTS_CONNECT_EVT: { + BLEDevice::m_localMTU = 23; if(BLEDevice::m_securityLevel){ esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); } diff --git a/cpp_utils/BLEHIDDevice.cpp b/cpp_utils/BLEHIDDevice.cpp index 3e7fbc89..29376f3a 100644 --- a/cpp_utils/BLEHIDDevice.cpp +++ b/cpp_utils/BLEHIDDevice.cpp @@ -1,160 +1,234 @@ /* * BLEHIDDevice.cpp * - * Created on: Dec 18, 2017 + * Created on: Jan 03, 2018 * Author: chegewara */ - #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -//#include "BLEUUID.h" #include "BLEHIDDevice.h" +#include "BLE2904.h" + BLEHIDDevice::BLEHIDDevice(BLEServer* server) { + /* + * Here we create mandatory services described in bluetooth specification + */ m_deviceInfoService = server->createService(BLEUUID((uint16_t) 0x180a)); m_hidService = server->createService(BLEUUID((uint16_t) 0x1812), 40); - //m_batteryService = server->createService(BLEUUID((uint16_t) 0x180f)); - createDescriptors(); - createCharacteristics(); -} + m_batteryService = server->createService(BLEUUID((uint16_t) 0x180f)); -BLEHIDDevice::~BLEHIDDevice() { - // TODO Auto-generated destructor stub -} - -void BLEHIDDevice::setReportMap(uint8_t* map, uint16_t size) { - m_reportMapCharacteristic->setValue(map, size); -} - -void BLEHIDDevice::createDescriptors() { - m_inputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); - const uint8_t desc1_val[] = {0x01}; - m_inputReportDescriptor->setValue((uint8_t*)desc1_val, 1); - m_inputReportNotifications = new BLE2902(); + /* + * Mandatory characteristic for device info service + */ + m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a50, BLECharacteristic::PROPERTY_READ); - m_outputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); - const uint8_t desc2_val[] = {0x02}; - m_outputReportDescriptor->setValue((uint8_t*)desc2_val, 1); + /* + * Mandatory characteristics for HID service + */ + m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4a, BLECharacteristic::PROPERTY_READ); + m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4b, BLECharacteristic::PROPERTY_READ); + m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4c, BLECharacteristic::PROPERTY_WRITE_NR); + m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4e, BLECharacteristic::PROPERTY_WRITE_NR | BLECharacteristic::PROPERTY_READ); - m_featureReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); - const uint8_t desc3_val[] = {0x03}; - m_featureReportDescriptor->setValue((uint8_t*)desc3_val, 1); + /* + * Mandatory battery level characteristic with notification and presence descriptor + */ + BLE2904* batteryLevelDescriptor = new BLE2904(); + batteryLevelDescriptor->setFormat(BLE2904::FORMAT_UINT8); + batteryLevelDescriptor->setNamespace(1); + batteryLevelDescriptor->setUnit(0x27ad); - m_bootInputNotifications = new BLE2902(); + m_batteryLevelCharacteristic = m_batteryService->createCharacteristic((uint16_t)0x2a19, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + m_batteryLevelCharacteristic->addDescriptor(batteryLevelDescriptor); + m_batteryLevelCharacteristic->addDescriptor(new BLE2902()); - if(m_batteryService != nullptr){ - m_batteryLevelDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2904)); - m_batteryLevelNotifications = new BLE2902(); - } + /* + * This value is setup here because its default value in most usage cases, its very rare to use boot mode + * and we want to simplify library using as much as possible + */ + const uint8_t pMode[] = {0x01}; + protocolMode()->setValue((uint8_t*)pMode, 1); } -void BLEHIDDevice::createCharacteristics() { - m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a29, BLECharacteristic::PROPERTY_READ); - m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a50, BLECharacteristic::PROPERTY_READ); +BLEHIDDevice::~BLEHIDDevice() { +} - m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4a, BLECharacteristic::PROPERTY_READ); - m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4b, BLECharacteristic::PROPERTY_READ); - m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4c, BLECharacteristic::PROPERTY_WRITE_NR); - m_inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); - m_outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); - m_featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); - m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4e, BLECharacteristic::PROPERTY_WRITE_NR); - m_bootInputCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a22, BLECharacteristic::PROPERTY_NOTIFY); - m_bootOutputCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a32, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); - - m_inputReportCharacteristic->addDescriptor(m_inputReportDescriptor); - m_inputReportCharacteristic->addDescriptor(m_inputReportNotifications); - m_outputReportCharacteristic->addDescriptor(m_outputReportDescriptor); - m_featureReportCharacteristic->addDescriptor(m_featureReportDescriptor); - m_bootInputCharacteristic->addDescriptor(m_bootInputNotifications); - if(m_batteryService != nullptr){ - m_batteryLevelCharacteristic->addDescriptor(m_batteryLevelDescriptor); //OPTIONAL? - m_batteryLevelCharacteristic->addDescriptor(m_batteryLevelNotifications); //OPTIONAL? - } +/* + * @brief + */ +void BLEHIDDevice::reportMap(uint8_t* map, uint16_t size) { + m_reportMapCharacteristic->setValue(map, size); } +/* + * @brief This function suppose to be called at the end, when we have created all characteristics we need to build HID service + */ void BLEHIDDevice::startServices() { m_deviceInfoService->start(); m_hidService->start(); - if(m_batteryService!=nullptr) - m_batteryService->start(); + m_batteryService->start(); } -BLEService* BLEHIDDevice::deviceInfo() { - return m_deviceInfoService; +/* + * @brief Create manufacturer characteristic (this characteristic is optional) + */ +BLECharacteristic* BLEHIDDevice::manufacturer() { + m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a29, BLECharacteristic::PROPERTY_READ); + return m_manufacturerCharacteristic; } -BLEService* BLEHIDDevice::hidService() { - return m_hidService; +/* + * @brief Set manufacturer name + * @param [in] name manufacturer name + */ +void BLEHIDDevice::manufacturer(std::string name) { + m_manufacturerCharacteristic->setValue(name); } -BLEService* BLEHIDDevice::batteryService() { - return m_batteryService; +/* + * @brief + */ +void BLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) { + uint8_t pnp[] = {sig, (uint8_t)(vid>>8), (uint8_t)vid, (uint8_t)(pid>>8), (uint8_t)pid, (uint8_t)(version>>8), (uint8_t)version}; + m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); } -BLECharacteristic* BLEHIDDevice::manufacturer() { - return m_manufacturerCharacteristic; +/* + * @brief + */ +void BLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) { + uint8_t info[] = {0x11,0x1, country, flags}; + m_hidInfoCharacteristic->setValue(info, sizeof(info));; } -BLECharacteristic* BLEHIDDevice::pnp() { - return m_pnpCharacteristic; -} +/* + * @brief Create input report characteristic that need to be saved as new characteristic object so can be further used + * @param [in] reportID input report ID, the same as in report map for input object related to created characteristic + * @return pointer to new input report characteristic + */ +BLECharacteristic* BLEHIDDevice::inputReport(uint8_t reportID) { + BLECharacteristic* inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor* inputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); -BLECharacteristic* BLEHIDDevice::hidInfo() { - return m_hidInfoCharacteristic; + uint8_t desc1_val[] = {reportID, 0x01}; + inputReportDescriptor->setValue((uint8_t*)desc1_val, 2); + inputReportCharacteristic->addDescriptor(new BLE2902()); + inputReportCharacteristic->addDescriptor(inputReportDescriptor); + + return inputReportCharacteristic; } -BLECharacteristic* BLEHIDDevice::reportMap() { - return m_reportMapCharacteristic; +/* + * @brief Create output report characteristic that need to be saved as new characteristic object so can be further used + * @param [in] reportID Output report ID, the same as in report map for output object related to created characteristic + * @return Pointer to new output report characteristic + */ +BLECharacteristic* BLEHIDDevice::outputReport(uint8_t reportID) { + BLECharacteristic* outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); + BLEDescriptor* outputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + + uint8_t desc1_val[] = {reportID, 0x02}; + outputReportDescriptor->setValue((uint8_t*)desc1_val, 2); + outputReportCharacteristic->addDescriptor(outputReportDescriptor); + + return outputReportCharacteristic; } -BLECharacteristic* BLEHIDDevice::hidControl() { - return m_hidControlCharacteristic; +/* + * @brief Create feature report characteristic that need to be saved as new characteristic object so can be further used + * @param [in] reportID Feature report ID, the same as in report map for feature object related to created characteristic + * @return Pointer to new feature report characteristic + */ +BLECharacteristic* BLEHIDDevice::featureReport(uint8_t reportID) { + BLECharacteristic* featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + BLEDescriptor* featureReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + + uint8_t desc1_val[] = {reportID, 0x03}; + featureReportDescriptor->setValue((uint8_t*)desc1_val, 2); + featureReportCharacteristic->addDescriptor(featureReportDescriptor); + + return featureReportCharacteristic; } -BLECharacteristic* BLEHIDDevice::inputReport(void*) { - return m_inputReportCharacteristic; +/* + * @brief + */ +BLECharacteristic* BLEHIDDevice::bootInput() { + BLECharacteristic* bootInputCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a22, BLECharacteristic::PROPERTY_NOTIFY); + bootInputCharacteristic->addDescriptor(new BLE2902()); + + return bootInputCharacteristic; } -BLECharacteristic* BLEHIDDevice::outputReport(void*) { - return m_outputReportCharacteristic; +/* + * @brief + */ +BLECharacteristic* BLEHIDDevice::bootOutput() { + return m_hidService->createCharacteristic((uint16_t)0x2a32, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); } -BLECharacteristic* BLEHIDDevice::featureReport(void*) { - return m_featureReportCharacteristic; +/* + * @brief + */ +BLECharacteristic* BLEHIDDevice::hidControl() { + return m_hidControlCharacteristic; } +/* + * @brief + */ BLECharacteristic* BLEHIDDevice::protocolMode() { return m_protocolModeCharacteristic; } -BLECharacteristic* BLEHIDDevice::bootInput() { - return m_bootInputCharacteristic; +void BLEHIDDevice::setBatteryLevel(uint8_t level) { + m_batteryLevelCharacteristic->setValue(&level, 1); } - -BLECharacteristic* BLEHIDDevice::bootOutput() { - return m_bootOutputCharacteristic; +/* + * @brief Returns battery level characteristic + * @ return battery level characteristic + *//* +BLECharacteristic* BLEHIDDevice::batteryLevel() { + return m_batteryLevelCharacteristic; } -BLECharacteristic* BLEHIDDevice::batteryLevel(void*) { - return m_batteryLevelCharacteristic; + + +BLECharacteristic* BLEHIDDevice::reportMap() { + return m_reportMapCharacteristic; } -BLEDescriptor* BLEHIDDevice::inputReport() { - return m_inputReportDescriptor; +BLECharacteristic* BLEHIDDevice::pnp() { + return m_pnpCharacteristic; } -BLEDescriptor* BLEHIDDevice::outputReport() { - return m_outputReportDescriptor; + +BLECharacteristic* BLEHIDDevice::hidInfo() { + return m_hidInfoCharacteristic; +} +*/ +/* + * @brief + */ +BLEService* BLEHIDDevice::deviceInfo() { + return m_deviceInfoService; } -BLEDescriptor* BLEHIDDevice::featureReport() { - return m_featureReportDescriptor; +/* + * @brief + */ +BLEService* BLEHIDDevice::hidService() { + return m_hidService; } -BLEDescriptor* BLEHIDDevice::batteryLevel() { - return m_batteryLevelDescriptor; +/* + * @brief + */ +BLEService* BLEHIDDevice::batteryService() { + return m_batteryService; } #endif // CONFIG_BT_ENABLED + diff --git a/cpp_utils/BLEHIDDevice.h b/cpp_utils/BLEHIDDevice.h index bf2b29e0..319fd42a 100644 --- a/cpp_utils/BLEHIDDevice.h +++ b/cpp_utils/BLEHIDDevice.h @@ -1,7 +1,7 @@ /* * BLEHIDDevice.h * - * Created on: Dec 18, 2017 + * Created on: Jan 03, 2018 * Author: chegewara */ @@ -32,7 +32,7 @@ class BLEHIDDevice { BLEHIDDevice(BLEServer*); virtual ~BLEHIDDevice(); - void setReportMap(uint8_t* map, uint16_t); + void reportMap(uint8_t* map, uint16_t); void startServices(); BLEService* deviceInfo(); @@ -40,27 +40,25 @@ class BLEHIDDevice { BLEService* batteryService(); BLECharacteristic* manufacturer(); - BLECharacteristic* pnp(); - BLECharacteristic* hidInfo(); - BLECharacteristic* reportMap(); + void manufacturer(std::string name); + //BLECharacteristic* pnp(); + void pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version); + //BLECharacteristic* hidInfo(); + void hidInfo(uint8_t country, uint8_t flags); + //BLECharacteristic* batteryLevel(); + void setBatteryLevel(uint8_t level); + + + //BLECharacteristic* reportMap(); BLECharacteristic* hidControl(); - BLECharacteristic* inputReport(void*); - BLECharacteristic* outputReport(void*); - BLECharacteristic* featureReport(void*); + BLECharacteristic* inputReport(uint8_t reportID); + BLECharacteristic* outputReport(uint8_t reportID); + BLECharacteristic* featureReport(uint8_t reportID); BLECharacteristic* protocolMode(); BLECharacteristic* bootInput(); BLECharacteristic* bootOutput(); - BLECharacteristic* batteryLevel(void*); - - BLEDescriptor* inputReport(); - BLEDescriptor* outputReport(); - BLEDescriptor* featureReport(); - BLEDescriptor* batteryLevel(); private: - void createCharacteristics(); - void createDescriptors(); - BLEService* m_deviceInfoService; //0x180a BLEService* m_hidService; //0x1812 BLEService* m_batteryService = 0; //0x180f @@ -70,22 +68,8 @@ class BLEHIDDevice { BLECharacteristic* m_hidInfoCharacteristic; //0x2a4a BLECharacteristic* m_reportMapCharacteristic; //0x2a4b BLECharacteristic* m_hidControlCharacteristic; //0x2a4c - BLECharacteristic* m_inputReportCharacteristic; //0x2a4d - BLECharacteristic* m_outputReportCharacteristic; //0x2a4d - BLECharacteristic* m_featureReportCharacteristic; //0x2a4d BLECharacteristic* m_protocolModeCharacteristic; //0x2a4e - BLECharacteristic* m_bootInputCharacteristic; //0x2a22 - BLECharacteristic* m_bootOutputCharacteristic; //0x2a32 BLECharacteristic* m_batteryLevelCharacteristic; //0x2a19 - - BLEDescriptor* m_inputReportDescriptor; //0x2908 - BLEDescriptor* m_outputReportDescriptor; //0x2908 - BLEDescriptor* m_featureReportDescriptor; //0x2908 - BLE2902* m_inputReportNotifications; //0x2902 - BLE2902* m_bootInputNotifications; //0x2902 - BLEDescriptor* m_batteryLevelDescriptor; //0x2904 - BLE2902* m_batteryLevelNotifications; //0x2902 - }; #endif // CONFIG_BT_ENABLED #endif /* _BLEHIDDEVICE_H_ */ diff --git a/cpp_utils/HIDTypes.h b/cpp_utils/HIDTypes.h index b8b181be..726b84be 100644 --- a/cpp_utils/HIDTypes.h +++ b/cpp_utils/HIDTypes.h @@ -45,8 +45,13 @@ /* of data as per HID Class standard */ /* Main items */ +#ifdef ARDUINO_ARCH_ESP32 +#define HIDINPUT(size) (0x80 | size) +#define HIDOUTPUT(size) (0x90 | size) +#else #define INPUT(size) (0x80 | size) #define OUTPUT(size) (0x90 | size) +#endif #define FEATURE(size) (0xb0 | size) #define COLLECTION(size) (0xa0 | size) #define END_COLLECTION(size) (0xc0 | size) @@ -59,9 +64,9 @@ #define PHYSICAL_MAXIMUM(size) (0x44 | size) #define UNIT_EXPONENT(size) (0x54 | size) #define UNIT(size) (0x64 | size) -#define REPORT_SIZE(size) (0x74 | size) +#define REPORT_SIZE(size) (0x74 | size) //bits #define REPORT_ID(size) (0x84 | size) -#define REPORT_COUNT(size) (0x94 | size) +#define REPORT_COUNT(size) (0x94 | size) //bytes #define PUSH(size) (0xa4 | size) #define POP(size) (0xb4 | size) diff --git a/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp b/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp new file mode 100644 index 00000000..7564cd96 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp @@ -0,0 +1,192 @@ +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include "BLEHIDDevice.h" +#include "HIDKeyboardTypes.h" +#include +#include +#include + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleHIDDevice"; + +static BLEHIDDevice* hid; +BLECharacteristic* input; +BLECharacteristic* output; + +/* + * This callback is connect with output report. In keyboard output report report special keys changes, like CAPSLOCK, NUMLOCK + * We can add digital pins with LED to show status + * bit 1 - NUM LOCK + * bit 2 - CAPS LOCK + * bit 3 - SCROLL LOCK + */ +class MyOutputCallbacks : public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* me){ + uint8_t* value = (uint8_t*)(me->getValue().c_str()); + ESP_LOGI(LOG_TAG, "special keys: %d", *value); + } +}; + +class MyTask : public Task { + void run(void*){ + vTaskDelay(5000/portTICK_PERIOD_MS); // wait 5 seconds before send first message + const char* hello = "Hello world from esp32 hid keyboard!!!"; + while(1){ + vTaskDelay(2000/portTICK_PERIOD_MS); // simulate write message every 2 seconds + while(*hello){ + KEYMAP map = keymap[(uint8_t)*hello]; + /* + * simulate keydown, we can send up to 6 keys + */ + uint8_t a[] = {map.modifier, 0x0, map.usage, 0x0,0x0,0x0,0x0,0x0}; + input->setValue(a,sizeof(a)); + input->notify(); + + /* + * simulate keyup + */ + uint8_t v[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0}; + input->setValue(v, sizeof(v)); + input->notify(); + hello++; + } + } + vTaskDelete(NULL); + } +}; + +MyTask *task; +class MyCallbacks : public BLEServerCallbacks { + void onConnect(BLEServer* pServer){ + task->start(); + } + + void onDisconnect(BLEServer* pServer){ + task->stop(); + } +}; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + + task = new MyTask(); + BLEDevice::init("ESP32"); + BLEServer *pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyCallbacks()); + + /* + * Instantiate hid device + */ + hid = new BLEHIDDevice(pServer); + + + input = hid->inputReport(1); // <-- input REPORTID from report map + output = hid->outputReport(1); // <-- output REPORTID from report map + + output->setCallbacks(new MyOutputCallbacks()); + + /* + * Set manufacturer name (OPTIONAL) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.manufacturer_name_string.xml + */ + std::string name = "esp-community"; + hid->manufacturer()->setValue(name); + + /* + * Set pnp parameters (MANDATORY) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.pnp_id.xml + */ + + hid->pnp(0x02, 0xe502, 0xa111, 0x0210); + + /* + * Set hid informations (MANDATORY) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.hid_information.xml + */ + hid->hidInfo(0x00,0x01); + + + /* + * Keyboard + */ + const uint8_t reportMap[] = { + USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls + USAGE(1), 0x06, // Keyboard + COLLECTION(1), 0x01, // Application + REPORT_ID(1), 0x01, // REPORTID + USAGE_PAGE(1), 0x07, // Kbrd/Keypad + USAGE_MINIMUM(1), 0xE0, + USAGE_MAXIMUM(1), 0xE7, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x01, + REPORT_SIZE(1), 0x01, // 1 byte (Modifier) + REPORT_COUNT(1), 0x08, + INPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position + REPORT_COUNT(1), 0x01, // 1 byte (Reserved) + REPORT_SIZE(1), 0x08, + INPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position + REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana) + REPORT_SIZE(1), 0x01, + USAGE_PAGE(1), 0x08, // LEDs + USAGE_MINIMUM(1), 0x01, // Num Lock + USAGE_MAXIMUM(1), 0x05, // Kana + OUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile + REPORT_COUNT(1), 0x01, // 3 bits (Padding) + REPORT_SIZE(1), 0x03, + OUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile + REPORT_COUNT(1), 0x06, // 6 bytes (Keys) + REPORT_SIZE(1), 0x08, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x65, // 101 keys + USAGE_PAGE(1), 0x07, // Kbrd/Keypad + USAGE_MINIMUM(1), 0x00, + USAGE_MAXIMUM(1), 0x65, + INPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position + END_COLLECTION(0) + }; + /* + * Set report map (here is initialized device driver on client side) (MANDATORY) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.report_map.xml + */ + hid->reportMap((uint8_t*)reportMap, sizeof(reportMap)); + + /* + * We are prepared to start hid device services. Before this point we can change all values and/or set parameters we need. + * Also before we start, if we want to provide battery info, we need to prepare battery service. + * We can setup characteristics authorization + */ + hid->startServices(); + + /* + * Its good to setup advertising by providing appearance and advertised service. This will let clients find our device by type + */ + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->setAppearance(HID_KEYBOARD); + pAdvertising->addServiceUUID(hid->hidService()->getUUID()); + pAdvertising->start(); + + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(1000000); + } +}; + + +void SampleHID(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main From 9112aebed4ef86cfccccfdbf3aedf8fe44ec08e4 Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 4 Jan 2018 04:07:09 +0100 Subject: [PATCH 222/381] modified: cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp --- cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp b/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp index 7564cd96..f0385c52 100644 --- a/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp +++ b/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp @@ -36,9 +36,8 @@ class MyOutputCallbacks : public BLECharacteristicCallbacks { class MyTask : public Task { void run(void*){ vTaskDelay(5000/portTICK_PERIOD_MS); // wait 5 seconds before send first message - const char* hello = "Hello world from esp32 hid keyboard!!!"; while(1){ - vTaskDelay(2000/portTICK_PERIOD_MS); // simulate write message every 2 seconds + const char* hello = "Hello world from esp32 hid keyboard!!!\n"; while(*hello){ KEYMAP map = keymap[(uint8_t)*hello]; /* @@ -55,7 +54,10 @@ class MyTask : public Task { input->setValue(v, sizeof(v)); input->notify(); hello++; + + vTaskDelay(10/portTICK_PERIOD_MS); } + vTaskDelay(2000/portTICK_PERIOD_MS); // simulate write message every 2 seconds } vTaskDelete(NULL); } From dd08e7f2bf699bd4c64c9ae20034b7ac46a46008 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 3 Jan 2018 22:49:24 -0600 Subject: [PATCH 223/381] Changes for #319 --- cpp_utils/BLECharacteristic.cpp | 27 +++++++++++++++++++++------ cpp_utils/BLECharacteristic.h | 2 +- cpp_utils/BLEServer.cpp | 1 + 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 2e1a1b6a..5e5aa2ac 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -199,17 +199,27 @@ std::string BLECharacteristic::getValue() { } // getValue +/** + * Handle a GATT server event. + */ void BLECharacteristic::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { + ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); + switch(event) { // Events handled: + // // ESP_GATTS_ADD_CHAR_EVT - // ESP_GATTS_WRITE_EVT + // ESP_GATTS_CONF_EVT + // ESP_GATTS_CONNECT_EVT + // ESP_GATTS_DISCONNECT_EVT + // ESP_GATTS_EXEC_WRITE_EVT // ESP_GATTS_READ_EVT - // + // ESP_GATTS_WRITE_EVT + // // ESP_GATTS_EXEC_WRITE_EVT // When we receive this event it is an indication that a previous write long needs to be committed. // @@ -217,7 +227,7 @@ void BLECharacteristic::handleGATTServerEvent( // - uint16_t conn_id // - uint32_t trans_id // - esp_bd_addr_t bda - // - uint8_t exec_write_flag + // - uint8_t exec_write_flag - Either ESP_GATT_PREP_WRITE_EXEC or ESP_GATT_PREP_WRITE_CANCEL // case ESP_GATTS_EXEC_WRITE_EVT: { if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { @@ -428,13 +438,15 @@ void BLECharacteristic::handleGATTServerEvent( break; } - case ESP_GATTS_CONNECT_EVT: + case ESP_GATTS_CONNECT_EVT: { m_semaphoreConfEvt.give(); break; + } - case ESP_GATTS_DISCONNECT_EVT: + case ESP_GATTS_DISCONNECT_EVT: { m_semaphoreConfEvt.give(); break; + } default: { break; @@ -446,7 +458,7 @@ void BLECharacteristic::handleGATTServerEvent( // event. m_descriptorMap.handleGATTServerEvent(event, gatts_if, param); - + ESP_LOGD(LOG_TAG, "<< handleGATTServerEvent"); } // handleGATTServerEvent @@ -699,6 +711,7 @@ void BLECharacteristic::setWriteProperty(bool value) { } } // setWriteProperty + /** * @brief Return a string representation of the characteristic. * @return A string representation of the characteristic. @@ -717,8 +730,10 @@ std::string BLECharacteristic::toString() { return stringstream.str(); } // toString + BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {} + /** * @brief Callback function to support a read request. * @param [in] pCharacteristic The characteristic that is the source of the event. diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index fefe59a0..10dc787f 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -102,7 +102,7 @@ class BLECharacteristic { BLECharacteristicCallbacks* m_pCallbacks; BLEService* m_pService; BLEValue m_value; - esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; void handleGATTServerEvent( esp_gatts_cb_event_t event, diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 8dd2a210..9d26eb50 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -342,6 +342,7 @@ void BLEServerCallbacks::onConnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); } // onConnect + void BLEServerCallbacks::onDisconnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", ">> onDisconnect(): Default"); ESP_LOGD("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); From c759f217f2ebc8ba9ae0d745a9fdfb52f9168abd Mon Sep 17 00:00:00 2001 From: anio Date: Thu, 4 Jan 2018 10:01:55 +0200 Subject: [PATCH 224/381] Move file serving to HttpResponse #342 --- cpp_utils/HttpResponse.cpp | 32 ++++++++++++++++++++++++++++++++ cpp_utils/HttpResponse.h | 1 + cpp_utils/HttpServer.cpp | 31 +++---------------------------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp index adc72648..4c5c1a4e 100644 --- a/cpp_utils/HttpResponse.cpp +++ b/cpp_utils/HttpResponse.cpp @@ -5,6 +5,7 @@ * Author: kolban */ #include +#include #include "HttpRequest.h" #include "HttpResponse.h" #include @@ -123,6 +124,37 @@ void HttpResponse::sendData(uint8_t* pData, size_t size) { ESP_LOGD(LOG_TAG, "<< sendData"); } // sendData +void HttpResponse::sendFile(std::string fileName, size_t bufSize) +{ + ESP_LOGI(LOG_TAG, "Opening file: %s", fileName.c_str()); + std::ifstream ifStream; + ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); // Attempt to open the file for reading. + + // If we failed to open the requested file, then it probably didn't exist so return a not found. + if (!ifStream.is_open()) { + ESP_LOGE(LOG_TAG, "Unable to open file %s for reading", fileName.c_str()); + setStatus(HttpResponse::HTTP_STATUS_NOT_FOUND, "Not Found"); + addHeader(HttpRequest::HTTP_HEADER_CONTENT_TYPE, "text/plain"); + sendData("Not Found"); + close(); + return; // Since we failed to open the file, no further work to be done. + } + + // We now have an open file and want to push the content of that file through to the browser. + // because of defect #252 we have to do some pretty important re-work here. Specifically, we can't host the whole file in + // RAM at one time. Instead what we have to do is ensure that we only have enough data in RAM to be sent. + + setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + uint8_t *pData = new uint8_t[bufSize]; + while(!ifStream.eof()) { + ifStream.read((char *)pData, bufSize); + sendData(pData, ifStream.gcount()); + } + delete[] pData; + ifStream.close(); + close(); +} // sendFile + /** * @brief Send the header */ diff --git a/cpp_utils/HttpResponse.h b/cpp_utils/HttpResponse.h index 9e2b4067..7080ea90 100644 --- a/cpp_utils/HttpResponse.h +++ b/cpp_utils/HttpResponse.h @@ -46,6 +46,7 @@ class HttpResponse { std::map getHeaders(); // Get all headers. void sendData(std::string data); // Send data to the client. void sendData(uint8_t* pData, size_t size); // Send data to the client. + void sendFile(std::string fileName, size_t bufSize=4*1024); // Send file contents if exists. void setStatus(int status, std::string message); // Set the response status. }; diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 15a78248..a6d16e99 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -109,41 +109,16 @@ class HttpServerTask: public Task { if (GeneralUtils::endsWith(fileName, '/')) { fileName = fileName.substr(0, fileName.length()-1); } - + + HttpResponse response(&request); // Test if the path is a directory. if (FileSystem::isDirectory(fileName)) { ESP_LOGD(LOG_TAG, "Path %s is a directory", fileName.c_str()); - HttpResponse response(&request); m_pHttpServer->listDirectory(fileName, response); // List the contents of the directory. return; } // Path was a directory. - ESP_LOGD("HttpServerTask", "Opening file: %s", fileName.c_str()); - std::ifstream ifStream; - ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); // Attempt to open the file for reading. - - // If we failed to open the requested file, then it probably didn't exist so return a not found. - if (!ifStream.is_open()) { - ESP_LOGE("HttpServerTask", "Unable to open file %s for reading", fileName.c_str()); - HttpResponse response(&request); - response.setStatus(HttpResponse::HTTP_STATUS_NOT_FOUND, "Not Found"); - response.sendData(""); - return; // Since we failed to open the file, no further work to be done. - } - - // We now have an open file and want to push the content of that file through to the browser. - // because of defect #252 we have to do some pretty important re-work here. Specifically, we can't host the whole file in - // RAM at one time. Instead what we have to do is ensure that we only have enough data in RAM to be sent. - HttpResponse response(&request); - response.setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); - uint8_t *pData = new uint8_t[m_pHttpServer->getFileBufferSize()]; - while(!ifStream.eof()) { - ifStream.read((char *)pData, m_pHttpServer->getFileBufferSize()); - response.sendData(pData, ifStream.gcount()); - } - delete[] pData; - ifStream.close(); - + response.sendFile(fileName, m_pHttpServer->getFileBufferSize()); } // processRequest From b603dd5507d7c404cf705a784f34f5e5d4b87a86 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 4 Jan 2018 18:22:21 -0600 Subject: [PATCH 225/381] Changes for #271 --- cpp_utils/BLEAdvertising.cpp | 37 ---------------- cpp_utils/BLEAdvertising.h | 29 ------------- cpp_utils/BLEBeacon.cpp | 84 ++++++++++++++++++++++++++++++++++++ cpp_utils/BLEBeacon.h | 43 ++++++++++++++++++ 4 files changed, 127 insertions(+), 66 deletions(-) create mode 100644 cpp_utils/BLEBeacon.cpp create mode 100644 cpp_utils/BLEBeacon.h diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 3f22f119..9e01ab78 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -449,41 +449,4 @@ std::string BLEAdvertisementData::getPayload() { } // getPayload -BLEBeacon::BLEBeacon() { - m_beaconData.manufacturerId = 0x4c00; - m_beaconData.subType = 0x02; - m_beaconData.subTypeLength = 0x15; - m_beaconData.major = 0; - m_beaconData.minor = 0; - m_beaconData.signalPower = 0; - memset(m_beaconData.proximityUUID, 0, sizeof(m_beaconData.proximityUUID)); -} // BLEBeacon - -std::string BLEBeacon::getData() { - return std::string((char*)&m_beaconData, sizeof(m_beaconData)); -} // getData - -void BLEBeacon::setMajor(uint16_t major) { - m_beaconData.major = ENDIAN_CHANGE_U16(major); -} // setMajor - -void BLEBeacon::setManufacturerId(uint16_t manufacturerId) { - m_beaconData.manufacturerId = ENDIAN_CHANGE_U16(manufacturerId); -} // setManufacturerId - -void BLEBeacon::setMinor(uint16_t minor) { - m_beaconData.minor = ENDIAN_CHANGE_U16(minor); -} // setMinior - -void BLEBeacon::setProximityUUID(BLEUUID uuid) { - uuid = uuid.to128(); - memcpy(m_beaconData.proximityUUID, uuid.getNative()->uuid.uuid128, 16); -} // setProximityUUID - -void BLEBeacon::setSignalPower(int8_t signalPower) { - m_beaconData.signalPower = signalPower; -} // setSignalPower - - - #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index c9d5ba98..003ad1a8 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -12,35 +12,6 @@ #include #include "BLEUUID.h" #include -#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) - -/** - * @brief Representation of a beacon. - * See: - * * https://en.wikipedia.org/wiki/IBeacon - */ -class BLEBeacon { -private: - struct { - uint16_t manufacturerId; - uint8_t subType; - uint8_t subTypeLength; - uint8_t proximityUUID[16]; - uint16_t major; - uint16_t minor; - int8_t signalPower; - } __attribute__((packed))m_beaconData; -public: - BLEBeacon(); - void setManufacturerId(uint16_t manufacturerId); - //void setSubType(uint8_t subType); - void setProximityUUID(BLEUUID uuid); - void setMajor(uint16_t major); - void setMinor(uint16_t minor); - void setSignalPower(int8_t signalPower); - std::string getData(); -}; // BLEBeacon - /** * @brief Advertisement data set by the programmer to be published by the %BLE server. diff --git a/cpp_utils/BLEBeacon.cpp b/cpp_utils/BLEBeacon.cpp new file mode 100644 index 00000000..a63197ca --- /dev/null +++ b/cpp_utils/BLEBeacon.cpp @@ -0,0 +1,84 @@ +/* + * BLEBeacon.cpp + * + * Created on: Jan 4, 2018 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEBeacon.h" + +#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) + +static const char LOG_TAG[] = "BLEBeacon"; + +BLEBeacon::BLEBeacon() { + m_beaconData.manufacturerId = 0x4c00; + m_beaconData.subType = 0x02; + m_beaconData.subTypeLength = 0x15; + m_beaconData.major = 0; + m_beaconData.minor = 0; + m_beaconData.signalPower = 0; + memset(m_beaconData.proximityUUID, 0, sizeof(m_beaconData.proximityUUID)); +} // BLEBeacon + +std::string BLEBeacon::getData() { + return std::string((char*)&m_beaconData, sizeof(m_beaconData)); +} // getData + +uint16_t BLEBeacon::getMajor() { + return m_beaconData.major; +} + +uint16_t BLEBeacon::getManufacturerId() { + return m_beaconData.manufacturerId; +} + +uint16_t BLEBeacon::getMinor() { + return m_beaconData.minor; +} + +BLEUUID BLEBeacon::getProximityUUID() { + return BLEUUID(m_beaconData.proximityUUID, 16, false); +} + +int8_t BLEBeacon::getSignalPower() { + return m_beaconData.signalPower; +} + +/** + * Set the raw data for the beacon record. + */ +void BLEBeacon::setData(std::string data) { + if (data.length() != sizeof(m_beaconData)) { + ESP_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_beaconData)); + return; + } + memcpy(&m_beaconData, data.data(), sizeof(m_beaconData)); +} // setData + +void BLEBeacon::setMajor(uint16_t major) { + m_beaconData.major = ENDIAN_CHANGE_U16(major); +} // setMajor + +void BLEBeacon::setManufacturerId(uint16_t manufacturerId) { + m_beaconData.manufacturerId = ENDIAN_CHANGE_U16(manufacturerId); +} // setManufacturerId + +void BLEBeacon::setMinor(uint16_t minor) { + m_beaconData.minor = ENDIAN_CHANGE_U16(minor); +} // setMinior + +void BLEBeacon::setProximityUUID(BLEUUID uuid) { + uuid = uuid.to128(); + memcpy(m_beaconData.proximityUUID, uuid.getNative()->uuid.uuid128, 16); +} // setProximityUUID + +void BLEBeacon::setSignalPower(int8_t signalPower) { + m_beaconData.signalPower = signalPower; +} // setSignalPower + + +#endif diff --git a/cpp_utils/BLEBeacon.h b/cpp_utils/BLEBeacon.h new file mode 100644 index 00000000..0b02e2bd --- /dev/null +++ b/cpp_utils/BLEBeacon.h @@ -0,0 +1,43 @@ +/* + * BLEBeacon2.h + * + * Created on: Jan 4, 2018 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEBEACON_H_ +#define COMPONENTS_CPP_UTILS_BLEBEACON_H_ +#include "BLEUUID.h" +/** + * @brief Representation of a beacon. + * See: + * * https://en.wikipedia.org/wiki/IBeacon + */ +class BLEBeacon { +private: + struct { + uint16_t manufacturerId; + uint8_t subType; + uint8_t subTypeLength; + uint8_t proximityUUID[16]; + uint16_t major; + uint16_t minor; + int8_t signalPower; + } __attribute__((packed))m_beaconData; +public: + BLEBeacon(); + std::string getData(); + uint16_t getMajor(); + uint16_t getMinor(); + uint16_t getManufacturerId(); + BLEUUID getProximityUUID(); + int8_t getSignalPower(); + void setData(std::string data); + void setMajor(uint16_t major); + void setMinor(uint16_t minor); + void setManufacturerId(uint16_t manufacturerId); + void setProximityUUID(BLEUUID uuid); + void setSignalPower(int8_t signalPower); +}; // BLEBeacon + +#endif /* COMPONENTS_CPP_UTILS_BLEBEACON_H_ */ From c909117ea3baaba5c8e88f8d3d02310dfe97ce44 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 6 Jan 2018 16:14:05 -0600 Subject: [PATCH 226/381] Fixes for #344 --- cpp_utils/WebSocket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp index 14642c34..285a0f1f 100644 --- a/cpp_utils/WebSocket.cpp +++ b/cpp_utils/WebSocket.cpp @@ -337,7 +337,7 @@ void WebSocket::send(std::string data, uint8_t sendType) { } else { frame.len = 126; m_socket.send((uint8_t *)&frame, sizeof(frame)); - m_socket.send((uint16_t)data.length()); + m_socket.send(htons((uint16_t)data.length())); // Convert to network byte order from host byte order } m_socket.send((uint8_t*)data.data(), data.length()); ESP_LOGD(LOG_TAG, "<< send"); From 137f3d997fde8fe15e66bb98984c1f187ebb2c54 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Tue, 9 Jan 2018 23:07:25 -0600 Subject: [PATCH 227/381] Fixes for #355 --- cpp_utils/HttpServer.cpp | 2 +- cpp_utils/Socket.cpp | 28 ++++++++++++++++++++++++---- cpp_utils/Socket.h | 5 +++-- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index a6d16e99..1b1845f1 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -131,7 +131,7 @@ class HttpServerTask: public Task { void run(void* data) { m_pHttpServer = (HttpServer*)data; // The passed in data is an instance of an HttpServer. m_pHttpServer->m_socket.setSSL(m_pHttpServer->m_useSSL); - m_pHttpServer->m_socket.listen(m_pHttpServer->m_portNumber); + m_pHttpServer->m_socket.listen(m_pHttpServer->m_portNumber, false /* is datagram */, true /* Allow address reuse */); ESP_LOGD("HttpServerTask", "Listening on port %d", m_pHttpServer->getPort()); Socket clientSocket; while(1) { // Loop forever. diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 602bdc74..89340ce6 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -259,9 +259,10 @@ bool Socket::isValid() { * @param [in] isDatagram True if we are listening on a datagram. The default is false. * @return Returns 0 on success. */ -int Socket::listen(uint16_t port, bool isDatagram) { +int Socket::listen(uint16_t port, bool isDatagram, bool reuseAddress) { ESP_LOGD(LOG_TAG, ">> listen: port: %d, isDatagram: %d", port, isDatagram); createSocket(isDatagram); + setReuseAddress(reuseAddress); int rc = bind(port, 0); if (rc != 0) { ESP_LOGE(LOG_TAG, "<< listen: Error in bind: %s", strerror(errno)); @@ -285,14 +286,19 @@ bool Socket::operator <(const Socket& other) const { return m_sock < other.m_sock; } -int Socket::setSocketOption(int option, char* value, size_t len) + +/** + * @brief Set the socket option. + */ +int Socket::setSocketOption(int option, void* value, size_t len) { - int res = setsockopt(m_sock, SOL_SOCKET, option, value, len); + int res = ::setsockopt(m_sock, SOL_SOCKET, option, value, len); if(res < 0) { ESP_LOGE(LOG_TAG, "%X : %d", option, errno); } return res; -} +} // setSocketOption + /** * @brief Socket timeout. @@ -479,6 +485,18 @@ void Socket::sendTo(const uint8_t* data, size_t length, struct sockaddr* pAddr) } // sendTo +/** + * @brief Flag the socket address as re-usable. + * @param [in] value True to mark the address as re-usable, false otherwise. + */ +void Socket::setReuseAddress(bool value) { + ESP_LOGD(LOG_TAG, ">> setReuseAddress: %d", value); + int val = value?1:0; + setSocketOption(SO_REUSEADDR, &val, sizeof(val)); + ESP_LOGD(LOG_TAG, "<< setReuseAddress"); +} // setReuseAddress + + /** * @brief Flag the socket as using SSL * @param [in] sslValue True if we wish to use SSL. @@ -651,3 +669,5 @@ SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() { SocketException::SocketException(int myErrno) { m_errno = myErrno; } + + diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 7685bf7c..6804ab93 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -68,13 +68,14 @@ class Socket { int connect(struct in_addr address, uint16_t port); int connect(char* address, uint16_t port); int createSocket(bool isDatagram = false); - int setSocketOption(int option, char* value, size_t len); + void setReuseAddress(bool value); + int setSocketOption(int option, void* value, size_t len); int setTimeout(uint32_t seconds); void getBind(struct sockaddr* pAddr); int getFD() const; bool getSSL() const; bool isValid(); - int listen(uint16_t port, bool isDatagram=false); + int listen(uint16_t port, bool isDatagram=false, bool reuseAddress=false); bool operator<(const Socket& other) const; std::string readToDelim(std::string delim); size_t receive(uint8_t* data, size_t length, bool exact=false); From 21264f79456aefcdb66540ab607151b6a9bb0bcd Mon Sep 17 00:00:00 2001 From: Alexander Sparkowsky Date: Sun, 14 Jan 2018 07:24:47 +0100 Subject: [PATCH 228/381] Use uart given as parameter to read line from GPS Previously the uart paramter has been ignored and UART1 has always been used to read a line from GPS. --- hardware/gps/Fragments/log_to_console.c | 2 +- hardware/gps/gps/main/gps.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hardware/gps/Fragments/log_to_console.c b/hardware/gps/Fragments/log_to_console.c index aa5ef034..be4717fd 100644 --- a/hardware/gps/Fragments/log_to_console.c +++ b/hardware/gps/Fragments/log_to_console.c @@ -8,7 +8,7 @@ char *readLine(uart_port_t uart) { int size; char *ptr = line; while(1) { - size = uart_read_bytes(UART_NUM_1, (unsigned char *)ptr, 1, portMAX_DELAY); + size = uart_read_bytes(uart, (unsigned char *)ptr, 1, portMAX_DELAY); if (size == 1) { if (*ptr == '\n') { *ptr = 0; diff --git a/hardware/gps/gps/main/gps.c b/hardware/gps/gps/main/gps.c index 917f412c..c54ae2bc 100644 --- a/hardware/gps/gps/main/gps.c +++ b/hardware/gps/gps/main/gps.c @@ -11,7 +11,7 @@ char *readLine(uart_port_t uart) { int size; char *ptr = line; while(1) { - size = uart_read_bytes(UART_NUM_1, (unsigned char *)ptr, 1, portMAX_DELAY); + size = uart_read_bytes(uart, (unsigned char *)ptr, 1, portMAX_DELAY); if (size == 1) { if (*ptr == '\n') { ptr++; From 59f8eb8048bfd5c94787d5593f1211c93e84f460 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Wed, 17 Jan 2018 18:05:17 -0600 Subject: [PATCH 229/381] Changes for #363 --- cpp_utils/WiFi.cpp | 18 ++++++++++++++---- cpp_utils/WiFi.h | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 25f9d1b5..6eeddc06 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -647,6 +647,7 @@ std::string WiFiAPRecord::toString() { return std::string(std::move(info_str)); } // toString +/* MDNS::MDNS() { esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); if (errRc != ESP_OK) { @@ -661,6 +662,7 @@ MDNS::~MDNS() { } m_mdns_server = nullptr; } +*/ /** * @brief Define the service for mDNS. @@ -670,6 +672,7 @@ MDNS::~MDNS() { * @param [in] port * @return N/A. */ +/* void MDNS::serviceAdd(const std::string& service, const std::string& proto, uint16_t port) { serviceAdd(service.c_str(), proto.c_str(), port); } // serviceAdd @@ -688,7 +691,7 @@ void MDNS::servicePortSet(const std::string& service, const std::string& proto, void MDNS::serviceRemove(const std::string& service, const std::string& proto) { serviceRemove(service.c_str(), proto.c_str()); } // serviceRemove - +*/ /** * @brief Set the mDNS hostname. @@ -696,10 +699,11 @@ void MDNS::serviceRemove(const std::string& service, const std::string& proto) { * @param [in] hostname The host name to set against the mDNS. * @return N/A. */ +/* void MDNS::setHostname(const std::string& hostname) { setHostname(hostname.c_str()); } // setHostname - +*/ /** * @brief Set the mDNS instance. @@ -707,9 +711,11 @@ void MDNS::setHostname(const std::string& hostname) { * @param [in] instance The instance name to set against the mDNS. * @return N/A. */ +/* void MDNS::setInstance(const std::string& instance) { setInstance(instance.c_str()); } // setInstance +*/ /** * @brief Define the service for mDNS. @@ -719,6 +725,7 @@ void MDNS::setInstance(const std::string& instance) { * @param [in] port * @return N/A. */ +/* void MDNS::serviceAdd(const char* service, const char* proto, uint16_t port) { esp_err_t errRc = ::mdns_service_add(m_mdns_server, service, proto, port); if (errRc != ESP_OK) { @@ -754,13 +761,14 @@ void MDNS::serviceRemove(const char* service, const char* proto) { } } // serviceRemove - +*/ /** * @brief Set the mDNS hostname. * * @param [in] hostname The host name to set against the mDNS. * @return N/A. */ +/* void MDNS::setHostname(const char* hostname) { esp_err_t errRc = ::mdns_set_hostname(m_mdns_server,hostname); if (errRc != ESP_OK) { @@ -768,7 +776,7 @@ void MDNS::setHostname(const char* hostname) { abort(); } } // setHostname - +*/ /** * @brief Set the mDNS instance. @@ -776,6 +784,7 @@ void MDNS::setHostname(const char* hostname) { * @param [in] instance The instance name to set against the mDNS. * @return N/A. */ +/* void MDNS::setInstance(const char* instance) { esp_err_t errRc = ::mdns_set_instance(m_mdns_server, instance); if (errRc != ESP_OK) { @@ -783,3 +792,4 @@ void MDNS::setInstance(const char* instance) { abort(); } } // setInstance +*/ diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index eec479d2..5d45d0d8 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -19,6 +19,7 @@ /** * @brief Manage mDNS server. */ +/* class MDNS { public: MDNS(); @@ -40,6 +41,7 @@ class MDNS { private: mdns_server_t *m_mdns_server = nullptr; }; +*/ class WiFiAPRecord { public: From 9d53b4211f991d5db2c3ee217ac5b699c7af0cf6 Mon Sep 17 00:00:00 2001 From: reaper7 Date: Thu, 18 Jan 2018 07:46:59 +0000 Subject: [PATCH 230/381] add missing BLE2904.* files for arduino build --- cpp_utils/Makefile.arduino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index 82d7da3e..07408ea9 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -10,6 +10,8 @@ BLE_FILES= \ BLE2902.cpp \ BLE2902.h \ + BLE2904.cpp \ + BLE2904.h \ BLEAddress.cpp \ BLEAddress.h \ BLEAdvertisedDevice.cpp \ From 2cb73c57a7fae282383929d96760f2bde72b92ed Mon Sep 17 00:00:00 2001 From: reaper7 Date: Thu, 18 Jan 2018 08:15:35 +0000 Subject: [PATCH 231/381] add missing BLEBeacon.* files --- cpp_utils/Makefile.arduino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index 07408ea9..b9524dcf 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -18,6 +18,8 @@ BLE_FILES= \ BLEAdvertisedDevice.h \ BLEAdvertising.cpp \ BLEAdvertising.h \ + BLEBeacon.cpp \ + BLEBeacon.h \ BLECharacteristic.cpp \ BLECharacteristic.h \ BLECharacteristicMap.cpp \ From bf2da87c2c91e976a52ff8b4302644e59e761ce5 Mon Sep 17 00:00:00 2001 From: "U-THINKPAD-T530\\springob" Date: Sat, 20 Jan 2018 15:39:58 +0100 Subject: [PATCH 232/381] Added Task::setCore(BaseType_t coreId) for setting the core number the task has to run on. --- cpp_utils/Task.cpp | 14 +++++++++++++- cpp_utils/Task.h | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cpp_utils/Task.cpp b/cpp_utils/Task.cpp index 4520478a..a8ba9a37 100644 --- a/cpp_utils/Task.cpp +++ b/cpp_utils/Task.cpp @@ -30,6 +30,7 @@ Task::Task(std::string taskName, uint16_t stackSize, uint8_t priority) { m_priority = priority; m_taskData = nullptr; m_handle = nullptr; + m_coreId = tskNO_AFFINITY; } // Task Task::~Task() { @@ -71,7 +72,7 @@ void Task::start(void* taskData) { ESP_LOGW(tag, "Task::start - There might be a task already running!"); } m_taskData = taskData; - ::xTaskCreate(&runTask, m_taskName.c_str(), m_stackSize, this, m_priority, &m_handle); + ::xTaskCreatePinnedToCore(&runTask, m_taskName.c_str(), m_stackSize, this, m_priority, &m_handle, m_coreId); } // start @@ -118,3 +119,14 @@ void Task::setPriority(uint8_t priority) { void Task::setName(std::string name) { m_taskName = name; } // setName + +/** + * @brief Set the core number the task has to be executed on. + * If the core number is not set, tskNO_AFFINITY will be used + * + * @param [in] coreId The id of the core. + * @return N/A. + */ +void Task::setCore(BaseType_t coreId) { + m_coreId = coreId; +} diff --git a/cpp_utils/Task.h b/cpp_utils/Task.h index 74d7f332..0d58f222 100644 --- a/cpp_utils/Task.h +++ b/cpp_utils/Task.h @@ -38,6 +38,7 @@ class Task { void setStackSize(uint16_t stackSize); void setPriority(uint8_t priority); void setName(std::string name); + void setCore(BaseType_t coreId); void start(void* taskData=nullptr); void stop(); /** @@ -59,6 +60,7 @@ class Task { std::string m_taskName; uint16_t m_stackSize; uint8_t m_priority; + BaseType_t m_coreId; }; #endif /* COMPONENTS_CPP_UTILS_TASK_H_ */ From dee95597943c347636a2c7e3301ea4aa5fb05b58 Mon Sep 17 00:00:00 2001 From: Han Date: Thu, 25 Jan 2018 22:32:01 +0100 Subject: [PATCH 233/381] Handle partial writes for issue #377 --- cpp_utils/Socket.cpp | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 89340ce6..bc5a064c 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -429,15 +429,36 @@ int Socket::receiveFrom(uint8_t* data, size_t length, struct sockaddr *pAddr) { int Socket::send(const uint8_t* data, size_t length) const { ESP_LOGD(LOG_TAG, "send: Raw binary of length: %d", length); //GeneralUtils::hexDump(data, length); - int rc; - if (getSSL()) { - rc = mbedtls_ssl_write((mbedtls_ssl_context*)&m_sslContext, data, length); - } else { - rc = ::lwip_send_r(m_sock, data, length, 0); - } - if (rc == -1) { - ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); - } + int rc = ERR_OK; + while (length > 0) + { + if (getSSL()) { + rc = mbedtls_ssl_write((mbedtls_ssl_context*)&m_sslContext, data, length); + // retry with same parameters if MBEDTLS_ERR_SSL_WANT_WRITE or MBEDTLS_ERR_SSL_WANT_READ + if ((rc != MBEDTLS_ERR_SSL_WANT_WRITE) && (rc != MBEDTLS_ERR_SSL_WANT_READ)) { + if (rc < 0) { + // no cure for other errors - log and exit + ESP_LOGE(LOG_TAG, "send: SSL write error %d", rc); + return rc; + } else { + // not all data was written, try again for the remainder + length -= rc; + data += rc; + } + } + } else { + rc = ::lwip_send_r(m_sock, data, length, 0); + if ((rc < 0) && (errno != EAGAIN)) { + // no cure for errors other than EAGAIN - log and exit + ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); + return rc; + } else if (rc > 0) { + // not all data was written, try again for the remainder + length -= rc; + data += rc; + } + } + } return rc; } // send From d650cfc7e0ba7bdec01cbe9693bfde56373204c2 Mon Sep 17 00:00:00 2001 From: Han Date: Thu, 25 Jan 2018 23:21:34 +0100 Subject: [PATCH 234/381] Added WebSocket::send for binary data (issue #380) --- cpp_utils/WebSocket.cpp | 29 +++++++++++++++++++++++++++++ cpp_utils/WebSocket.h | 1 + 2 files changed, 30 insertions(+) diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp index 285a0f1f..e7fc05fb 100644 --- a/cpp_utils/WebSocket.cpp +++ b/cpp_utils/WebSocket.cpp @@ -344,6 +344,35 @@ void WebSocket::send(std::string data, uint8_t sendType) { } // send_cpp +/** + * @brief Send data down the web socket + * See the WebSocket spec (RFC6455) section "6.1 Sending Data". + * We build a WebSocket frame, send the frame followed by the data. + * @param [in] data The data to send down the WebSocket. + * @param [in] sendType The type of payload. Either SEND_TYPE_TEXT or SEND_TYPE_BINARY. + */ +void WebSocket::send(uint8_t* data, uint16_t length, uint8_t sendType) { + ESP_LOGD(LOG_TAG, ">> send: Length: %d", length); + Frame frame; + frame.fin = 1; + frame.rsv1 = 0; + frame.rsv2 = 0; + frame.rsv3 = 0; + frame.opCode = sendType==SEND_TYPE_TEXT?OPCODE_TEXT:OPCODE_BINARY; + frame.mask = 0; + if (length < 126) { + frame.len = length; + m_socket.send((uint8_t *)&frame, sizeof(frame)); + } else { + frame.len = 126; + m_socket.send((uint8_t *)&frame, sizeof(frame)); + m_socket.send(htons(length)); // Convert to network byte order from host byte order + } + m_socket.send(data, length); + ESP_LOGD(LOG_TAG, "<< send"); +} + + /** * @brief Set the Web socket handler associated with this Websocket. * diff --git a/cpp_utils/WebSocket.h b/cpp_utils/WebSocket.h index 80c72f0c..d3bfda49 100644 --- a/cpp_utils/WebSocket.h +++ b/cpp_utils/WebSocket.h @@ -91,6 +91,7 @@ class WebSocket { WebSocketHandler* getHandler(); Socket getSocket(); void send(std::string data, uint8_t sendType = SEND_TYPE_BINARY); + void send(uint8_t* data, uint16_t length, uint8_t sendType = SEND_TYPE_BINARY); void setHandler(WebSocketHandler *handler); }; // WebSocket From b1b56c7ef0b3cdfc6cbd1b109b2b24c48c8c52e2 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 27 Jan 2018 15:35:46 -0600 Subject: [PATCH 235/381] Changes for #270 --- cpp_utils/PCF8575.cpp | 123 ++++++++++++++++++++++++++++++++++++++++++ cpp_utils/PCF8575.h | 37 +++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 cpp_utils/PCF8575.cpp create mode 100644 cpp_utils/PCF8575.h diff --git a/cpp_utils/PCF8575.cpp b/cpp_utils/PCF8575.cpp new file mode 100644 index 00000000..3714abb1 --- /dev/null +++ b/cpp_utils/PCF8575.cpp @@ -0,0 +1,123 @@ +/* + * PCF8575.cpp + * + * Created on: Jan 27, 2018 + * Author: kolban + */ + +#include "PCF8575.h" +#include "I2C.h" + +/** + * @brief Class constructor. + * + * The address is the address of the device on the %I2C bus. This is the value 0x20 plus + * the value of the device input pins `A0`, `A1` and `A2`. This means that the address should + * be between 0x20 and 0x27. + * + * @param [in] address The %I2C address of the device on the %I2C bus. + */ +PCF8575::PCF8575(uint8_t address) { + i2c.setAddress(address); + m_lastWrite = 0; +} + +/** + * @brief Class instance destructor. + */ +PCF8575::~PCF8575() { +} + + +/** + * @brief Read all the input bits from the device. + * @return A 16 bit value representing the values on each of the input pins. + */ +uint16_t PCF8575::read() { + uint16_t value; + i2c.beginTransaction(); + i2c.read((uint8_t*)&value,true); + i2c.read(((uint8_t*)&value) + 1,true); + i2c.endTransaction(); + return value; +} // read + + +/** + * @brief Read the logic level on a given pin. + * + * @param [in] bit The input pin of the device to read. Values are 0-15. + * @return True if the pin is high, false otherwise. Undefined if there is no signal on the pin. + */ +bool PCF8575::readBit(uint16_t bit) { + if (bit > 7) { + return false; + } + uint16_t value = read(); + return (value & (1<> 8) & 0xff, true); + i2c.endTransaction(); + m_lastWrite = value; +} // write + + +/** + * @brief Change the output value of a specific pin. + * + * The other bits beyond the one setting retain their values from the previous call to write() or + * previous calls to writeBit(). + * + * @param [in] bit The pin to have its value changed. The pin may be 0-15. + * @param [in] value The logic level to appear on the identified output pin. + */ +void PCF8575::writeBit(uint16_t bit, bool value) { + if (bit > 15) { + return; + } + if (invert) { + value = !value; + } + if (value) { + m_lastWrite |= (1<invert = value; +} // setInvert + + +/** + * @brief Initialize the PCF8575 device. + * + * @param [in] sdaPin The pin to use for the %I2C SDA functions. + * @param [in] clkPin The pin to use for the %I2C CLK functions. + */ +void PCF8575::init(gpio_num_t sdaPin, gpio_num_t clkPin) { + i2c.init(0, sdaPin, clkPin); +} // init diff --git a/cpp_utils/PCF8575.h b/cpp_utils/PCF8575.h new file mode 100644 index 00000000..467fae6c --- /dev/null +++ b/cpp_utils/PCF8575.h @@ -0,0 +1,37 @@ +/* + * PCF8575.h + * + * Created on: Jan 27, 2018 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_PCF8575_H_ +#define COMPONENTS_CPP_UTILS_PCF8575_H_ +#include "I2C.h" + +/** + * @brief Encapsulate a %PCF8575 device. + * + * The %PCF8575 is a 16 bit %GPIO expander attached to %I2C. It can read and write 16 bits of data + * and hence has 16 pins that can be used for input or output. + * + * @see [PCF8575 home page](http://www.ti.com/product/PCF8575) + */ +class PCF8575 { +public: + PCF8575(uint8_t address); + virtual ~PCF8575(); + void init(gpio_num_t sdaPin=I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin=I2C::DEFAULT_CLK_PIN); + uint16_t read(); + bool readBit(uint16_t bit); + void setInvert(bool value); + void write(uint16_t value); + void writeBit(uint16_t bit, bool value); + +private: + I2C i2c = I2C(); + uint8_t m_lastWrite; + bool invert = false; +}; + +#endif /* COMPONENTS_CPP_UTILS_PCF8575_H_ */ From c0a57d95272ef07fa70cecb2baf1e2a4b61ea1b7 Mon Sep 17 00:00:00 2001 From: Jeff Edson Date: Wed, 31 Jan 2018 15:37:01 -0800 Subject: [PATCH 236/381] Added Example main.c to networking/bootwifi/README.md based on comment by @chegewara https://github.com/nkolban/esp32-snippets/issues/245#issuecomment-350034132 --- networking/bootwifi/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/networking/bootwifi/README.md b/networking/bootwifi/README.md index 3d93f557..2f2a7c6c 100644 --- a/networking/bootwifi/README.md +++ b/networking/bootwifi/README.md @@ -13,6 +13,27 @@ This code is supplied in the form of an ESP-IDF module. In addition, BootWiFi h The logic uses C++ exception handling and hence C++ exception handling must be enabled in `make menuconfig`. +## Example +Below is a sample of a minimal main.cpp. +* If you run it on an esp32, it should create a new open wifi network with SSID: "ESP32". +* Connect to that network. +* Then open a browser and go to http://192.168.4.1. +```C++ +#include "BootWiFi.h" +#include "sdkconfig.h" + +extern "C" { + void app_main(void); +} + +BootWiFi *boot; + +void app_main(void) { + boot = new BootWiFi(); + boot->boot(); +} +``` + ## GPIO boot override To enable the ability to specify a GPIO pin to override known station information, compile the code with `-DBOOTWIFI_OVERRIDE_GPIO=` when `` is a GPIO pin number. If the pin is high at startup, then it will override. The pin is configured as pull-down low so it need not be artificially held low. The default is no override pin. From f323d99245a68904106f8f9b3abaa29dfbb634ec Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 4 Feb 2018 18:42:41 -0600 Subject: [PATCH 237/381] Changes for #389 --- cpp_utils/WiFi.cpp | 17 +++++++++++++---- networking/bootwifi/BootWiFi.cpp | 15 ++++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 6eeddc06..b4ed0edb 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -54,16 +54,20 @@ WiFi::WiFi() { m_eventLoopStarted = false; m_initCalled = false; - m_pWifiEventHandler = new WiFiEventHandler(); + //m_pWifiEventHandler = new WiFiEventHandler(); m_apConnected = false; // Are we connected to an access point? } // WiFi + /** * @brief Deletes the event handler that was used by the class */ WiFi::~WiFi() { - delete m_pWifiEventHandler; -} + if (m_pWifiEventHandler != nullptr) { + delete m_pWifiEventHandler; + m_pWifiEventHandler = nullptr; + } +} // ~WiFi /** @@ -229,7 +233,12 @@ void WiFi::dump() { WiFi *pWiFi = (WiFi *)ctx; // retrieve the WiFi object from the passed in context. // Invoke the event handler. - esp_err_t rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); + esp_err_t rc; + if (pWiFi->m_pWifiEventHandler != nullptr) { + rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); + } else { + rc = ESP_OK; + } // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that // indicates we are waiting for a connection complete. diff --git a/networking/bootwifi/BootWiFi.cpp b/networking/bootwifi/BootWiFi.cpp index eaf1a4b3..aadc6d34 100644 --- a/networking/bootwifi/BootWiFi.cpp +++ b/networking/bootwifi/BootWiFi.cpp @@ -253,8 +253,12 @@ class BootWifiEventHandler: public WiFiEventHandler { }; +/** + * Boot WiFi + */ void BootWiFi::bootWiFi2() { ESP_LOGD(LOG_TAG, ">> bootWiFi2"); + // Check for a GPIO override which occurs when a physical Pin is high // during the test. This can force the ability to check for new configuration // even if the existing configured access point is available. @@ -306,17 +310,22 @@ void BootWiFi::setAccessPointCredentials(std::string ssid, std::string password) } // setAccessPointCredentials + +/** + * @brief Main entry point into booting WiFi + */ void BootWiFi::boot() { ESP_LOGD(LOG_TAG, ">> boot"); ESP_LOGD(LOG_TAG, " +----------+"); ESP_LOGD(LOG_TAG, " | BootWiFi |"); ESP_LOGD(LOG_TAG, " +----------+"); ESP_LOGD(LOG_TAG, " Access point credentials: %s/%s", m_ssid.c_str(), m_password.c_str()); - m_completeSemaphore.take("boot"); // Take the semaphore which will be unlocked when we complete booting. + m_completeSemaphore.take("boot"); // Take the semaphore which will be unlocked when we complete booting. bootWiFi2(); - m_completeSemaphore.wait("boot"); // Wait for the semaphore that indicated we have completed booting. + m_completeSemaphore.wait("boot"); // Wait for the semaphore that indicated we have completed booting. + m_wifi.setWifiEventHandler(nullptr); // Remove the WiFi boot handler when we have completed booting. ESP_LOGD(LOG_TAG, "<< boot"); -} +} // boot BootWiFi::BootWiFi() { m_httpServerStarted = false; From c14df1aa58acccabb2cc365fd341837d22b50d75 Mon Sep 17 00:00:00 2001 From: Jack Rickard Date: Tue, 13 Feb 2018 22:44:09 -0600 Subject: [PATCH 238/381] Update BLEAdvertising.cpp Apple iBeacon requires a max and min advertising interval of 100ms. It appears to me we are kind of hard coded at 32ms min and 64ms max. We could probably do an iBeacon using setManufacturersData() and setFlags() but we need to be able to modify advertising interval to truly accommodate iBeacon spec. I think this code does it. --- cpp_utils/BLEAdvertising.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 9e01ab78..4b3cb8d6 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -91,6 +91,16 @@ void BLEAdvertising::setAppearance(uint16_t appearance) { m_advData.appearance = appearance; } // setAppearance +void BLEAdvertising::setMinInterval(uint16_t mininterval) { + m_advData.min_interval = mininterval; + m_advParams.adv_int_min = mininterval; +} // setMinInterval + +void BLEAdvertising::setMaxInterval(uint16_t maxinterval) { + m_advData.max_interval = maxinterval; + m_advParams.adv_int_max = maxinterval; +} // setMaxInterval + /** * @brief Set the filtering for the scan filter. From d84d43ac4e50adf3e926c1efbdec3e722b4291c8 Mon Sep 17 00:00:00 2001 From: Jack Rickard Date: Tue, 13 Feb 2018 22:46:17 -0600 Subject: [PATCH 239/381] Update BLEAdvertising.h Add public methods to set minimum and maximum advertising packet transmission interval. --- cpp_utils/BLEAdvertising.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 003ad1a8..d1fa3c73 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -52,6 +52,8 @@ class BLEAdvertising { void start(); void stop(); void setAppearance(uint16_t appearance); + void setMaxInterval(uint16_t maxinterval); + void setMinInterval(uint16_t mininterval); void setAdvertisementData(BLEAdvertisementData& advertisementData); void setScanFilter(bool scanRequertWhitelistOnly, bool connectWhitelistOnly); void setScanResponseData(BLEAdvertisementData& advertisementData); From 0bd09ae2c2a8bb03c9e1eaac2c6b51318cc0cd17 Mon Sep 17 00:00:00 2001 From: Marcel Seerig Date: Fri, 16 Feb 2018 19:57:04 +0100 Subject: [PATCH 240/381] little changes for a better handling with the wifi class --- cpp_utils/WiFi.cpp | 94 ++++++++++++++++++++++++++++++++++++++++++++-- cpp_utils/WiFi.h | 7 ++++ 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index b4ed0edb..f3176530 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -292,6 +292,38 @@ std::string WiFi::getApSSID() { return std::string((char *)conf.sta.ssid); } // getApSSID +/** + * @brief Get the current ESP32 IP form AP. + * @return The ESP32 IP. + */ +std::string WiFi::getApIp(){ + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaIp + +/** + * @brief Get the current AP netmask. + * @return The Netmask IP. + */ +std::string WiFi::getApNetmask(){ + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaNetmask + +/** + * @brief Get the current AP Gateway IP. + * @return The Gateway IP. + */ +std::string WiFi::getApGateway(){ + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaGateway /** * @brief Lookup an IP address by host name. @@ -351,6 +383,40 @@ tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { return ipInfo; } // getStaIpInfo +/** + * @brief Get the current ESP32 IP form STA. + * @return The ESP32 IP. + */ +std::string WiFi::getStaIp(){ + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaIp + + +/** + * @brief Get the current STA netmask. + * @return The Netmask IP. + */ +std::string WiFi::getStaNetmask(){ + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaNetmask + + +/** + * @brief Get the current STA Gateway IP. + * @return The Gateway IP. + */ +std::string WiFi::getStaGateway(){ + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaGateway /** * @brief Get the MAC address of the STA interface. @@ -501,6 +567,27 @@ std::vector WiFi::scan() { * @return N/A. */ void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth) { + startAP(ssid, password, auth, 0, false, 4); +} // startAP + +/** + * @brief Start being an access point. + * + * @param[in] ssid The SSID to use to advertize for stations. + * @param[in] password The password to use for station connections. + * @param[in] auth The authorization mode for access to this access point. Options are: + * * WIFI_AUTH_OPEN + * * WIFI_AUTH_WPA_PSK + * * WIFI_AUTH_WPA2_PSK + * * WIFI_AUTH_WPA_WPA2_PSK + * * WIFI_AUTH_WPA2_ENTERPRISE + * * WIFI_AUTH_WEP + * @param[in] channel from the access point. + * @param[in] is the ssid hidden, ore not. + * @param[in] limiting number of clients. + * @return N/A. + */ +void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection) { ESP_LOGD(LOG_TAG, ">> startAP: ssid: %s", ssid.c_str()); init(); @@ -517,10 +604,10 @@ void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_au ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); apConfig.ap.ssid_len = ssid.size(); ::memcpy(apConfig.ap.password, password.data(), password.size()); - apConfig.ap.channel = 0; + apConfig.ap.channel = channel; apConfig.ap.authmode = auth; - apConfig.ap.ssid_hidden = 0; - apConfig.ap.max_connection = 4; + apConfig.ap.ssid_hidden = (uint8_t) ssid_hidden; + apConfig.ap.max_connection = max_connection; apConfig.ap.beacon_interval = 100; errRc = ::esp_wifi_set_config(WIFI_IF_AP, &apConfig); @@ -543,7 +630,6 @@ void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_au ESP_LOGD(LOG_TAG, "<< startAP"); } // startAP - /** * @brief Set the event handler to use to process detected events. * @param[in] wifiEventHandler The class that will be used to process events. diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 5d45d0d8..b06635d9 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -135,12 +135,19 @@ class WiFi { static std::string getApMac(); static tcpip_adapter_ip_info_t getApIpInfo(); static std::string getApSSID(); + static std::string getApIp(); + static std::string getApNetmask(); + static std::string getApGateway(); static std::string getMode(); static tcpip_adapter_ip_info_t getStaIpInfo(); static std::string getStaMac(); static std::string getStaSSID(); + static std::string getStaIp(); + static std::string getStaNetmask(); + static std::string getStaGateway(); std::vector scan(); void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth = WIFI_AUTH_OPEN); + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection); void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); void setIPInfo(const char* ip, const char* gw, const char* netmask); void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); From bcf522e483c3029b098eb89d9ddad989fd6f247a Mon Sep 17 00:00:00 2001 From: Marcel Seerig Date: Mon, 19 Feb 2018 11:40:50 +0100 Subject: [PATCH 241/381] add the PubSubClient class. (alternative to AWS) --- cpp_utils/PubSubClient.cpp | 853 +++++++++++++++++++++++++++++++++++++ cpp_utils/PubSubClient.h | 172 ++++++++ 2 files changed, 1025 insertions(+) create mode 100644 cpp_utils/PubSubClient.cpp create mode 100644 cpp_utils/PubSubClient.h diff --git a/cpp_utils/PubSubClient.cpp b/cpp_utils/PubSubClient.cpp new file mode 100644 index 00000000..e3542cb0 --- /dev/null +++ b/cpp_utils/PubSubClient.cpp @@ -0,0 +1,853 @@ +/* + PubSubClient.cpp - A simple client for MQTT. + Nick O'Leary + http://knolleary.net + + edit by marcel.seerig + */ +#include "esp_log.h" + +#include "PubSubClient.h" +#include "Task.h" +#include "FreeRTOS.h" +#include "FreeRTOSTimer.h" + +#include "sdkconfig.h" + +static const char* TAG = "PubSubClient"; + +#define pgm_read_byte_near(x) *(x) + +/** + * @brief A task that will handle the PubSubClient. + * + * This Task is started, when we have a valid connection. + * If the connection breaks, we stop this Task. + */ +class PubSubClientTask: public Task { +public: + PubSubClientTask(std::string name) : + Task(name, 16 * 1024) { + taskName = name; + }; +private: + std::string taskName; + /** + * @brief Loop over the PubSubClient. + * @param [in] data A pointer to an instance of the PubSubClient. + */ + void run(void* data) { + PubSubClient* pPubSubClient = (PubSubClient*) data; + ESP_LOGD("PubSubClientTask", "PubSubClientTask Task started!"); + + while(1) { + if (pPubSubClient->connected()) { + + uint16_t len = pPubSubClient->readPacket(); + + if (len > 0) { // if there was data + + pPubSubClient->keepAliveTimer->reset(0); //lastInActivity = t; + + mqtt_message *msg = new mqtt_message; + pPubSubClient->parseData(msg, len); + + //pPubSubClient->dumpData(msg); + ESP_LOGD(TAG, "Message type (%s)!", pPubSubClient->messageType_toString(msg->type).c_str()); + + + if (msg->type == PUBLISH) { + + if (pPubSubClient->callback) { + + if (msg->qos == QOS0) { + + pPubSubClient->callback(msg->topic, msg->payload); + + } else if (msg->qos == QOS1) { + pPubSubClient->callback(msg->topic, msg->payload); + + pPubSubClient->buffer[0] = PUBACK; + pPubSubClient->buffer[1] = 2; + pPubSubClient->buffer[2] = (msg->msgId >> 8); + pPubSubClient->buffer[3] = (msg->msgId & 0xFF); + + int rc = pPubSubClient->_client->send(pPubSubClient->buffer, 4); + if(rc < 0) pPubSubClient->_state = CONNECTION_LOST; + + pPubSubClient->keepAliveTimer->reset(0); //lastOutActivity = t; + + }else if(msg->qos == QOS2) { + ESP_LOGD(TAG, "QOS2 is not supported!"); + }else{ + ESP_LOGD(TAG, "QOS-Level unkonwon yet!"); + } + + } + } else if (msg->type == PINGREQ) { + pPubSubClient->buffer[0] = PINGRESP; + pPubSubClient->buffer[1] = 0; + int rc = pPubSubClient->_client->send(pPubSubClient->buffer, 2); + if(rc < 0) pPubSubClient->_state = CONNECTION_LOST; + + } else if (msg->type == PINGRESP) { + pPubSubClient->PING_outstanding = false; + + }else if (msg->type == SUBACK) { + pPubSubClient->SUBACK_outstanding = false; + pPubSubClient->timeoutTimer->stop(0); + + }else if (msg->type == UNSUBACK) { + pPubSubClient->UNSUBACK_Outstanding = false; + pPubSubClient->timeoutTimer->stop(0); + } + + delete(msg); + } + } + } // While (1) + } // run +}; // PubSubClientTask + +PubSubClient::PubSubClient() { + setup(); + this->_state = DISCONNECTED; + this->_client = new Socket; + setCallback(NULL); +} + +PubSubClient::PubSubClient(Socket& client) { + setup(); + this->_state = DISCONNECTED; + setClient(client); +} + +PubSubClient::PubSubClient(std::string addr, uint16_t port) { + setup(); + this->_state = DISCONNECTED; + this->_client = new Socket; + setServer(addr, port); +} + +PubSubClient::PubSubClient(std::string addr, uint16_t port, Socket& client) { + setup(); + this->_state = DISCONNECTED; + setServer(addr, port); + setClient(client); +} + +PubSubClient::PubSubClient(std::string addr, uint16_t port, + MQTT_CALLBACK_SIGNATURE, Socket& client) { + setup(); + this->_state = DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); +} + +PubSubClient::~PubSubClient() { + _client->close(); + keepAliveTimer->stop(0); + timeoutTimer->stop(0); + m_task->stop(); + delete (_client); + delete (keepAliveTimer); + delete (timeoutTimer); + delete (m_task); +} + +/** + * @brief This is a Timer called routine mapping routine, which calls + * the PubSubClient member function keepAliveChecker. + * @param The FreeRTOSTimer root instance for this callback function. + * @return N/A. + */ +void keepAliveTimerMapper(FreeRTOSTimer *pTimer) { + PubSubClient* m_pubSubClient = (PubSubClient*) pTimer->getData(); + m_pubSubClient->keepAliveChecker(); +} //keepAliveChecker + +/** + * @brief This is a Timer called routine mapping routine, which calls + * the PubSubClient member function timeoutChecker. + * @param The FreeRTOSTimer root instance for this callback function. + * @return N/A. + */ +void timeoutTimerMapper(FreeRTOSTimer *pTimer) { + PubSubClient* m_pubSubClient = (PubSubClient*) pTimer->getData(); + m_pubSubClient->timeoutChecker(); +} //keepAliveChecker + +/** + * @brief This is a internal setup routine for the PubSubClient. + * @param N/A. + * @return N/A. + */ +void PubSubClient::setup(void) { + PING_outstanding = false; + SUBACK_outstanding = false; + UNSUBACK_Outstanding = false; + + + keepAliveTimer = new FreeRTOSTimer((char*) "keepAliveTimer", + (MQTT_KEEPALIVE * 1000) / portTICK_PERIOD_MS, true, this, + keepAliveTimerMapper); + timeoutTimer = new FreeRTOSTimer((char*) "timeoutTimer", + (MQTT_KEEPALIVE * 1000) / portTICK_PERIOD_MS, true, this, + timeoutTimerMapper); + m_task = new PubSubClientTask("PubSubClientTask"); +} // setup + +/** + * @brief This is a Timer called routine, which checks the PING_outstanding flag. + * This flag is set in this function. We send a Keep alive message to the + * server here. If there is a data in, or output, or we receive the MQTT + * ping request, the flag will be set to false by other functions. + * This function is called every MQTT_KEEPALIVE interval. If the flag is + * still true, we have a error with the connection. + * @param N/A. + * @return N/A. + */ +void PubSubClient::keepAliveChecker(void){ + + if (PING_outstanding && connected()) { + _state = CONNECTION_TIMEOUT; + //_client->close(); + ESP_LOGD(TAG, "KeepAlive TIMEOUT!"); + } else { + buffer[0] = PINGREQ; + buffer[1] = 0; + int rc = _client->send(buffer, 2); + if(rc < 0) _state = CONNECTION_LOST; + ESP_LOGD(TAG, "send KeepAlive REQUEST!"); + PING_outstanding = true; + } +} //keepAliveChecker + +/** + * @brief This is a Timer called routine, which is called, when we reach the timeout. + * Used is this function for all ACK commands, which comes over MQTT. Notice, + * that the KeepAlivePing has his own Timer, to prevent conflicts. Basically, if + * this function is called, we have detected a MQTT timeout! + * @param N/A. + * @return N/A. + */ +void PubSubClient::timeoutChecker(void){ + + if (connected() && (SUBACK_outstanding || UNSUBACK_Outstanding)) { + _state = CONNECTION_TIMEOUT; + //_client->close(); + ESP_LOGD(TAG, "MQTT TIMEOUT!"); + } +} //keepAliveChecker + +/** + * @brief Connect to a MQTT server. + * @param [in] Device id to identify this device. + * @return success (true), or no success (false). + */ +bool PubSubClient::connect(const char *id) { + return connect(id, NULL, NULL, 0, 0, 0, 0); +} + +/** + * @brief Connect to a MQTT server. + * @param [in] Device id to identify this device. + * [in] my user name. + * [in] my password. + * @return success (true), or no success (false). + */ +bool PubSubClient::connect(const char *id, const char *user, const char *pass) { + return connect(id, user, pass, 0, 0, 0, 0); +} + +/** + * @brief Connect to a MQTT server. + * @param [in] Device id to identify this device. + * [in] last will: topic name. + * [in] last will: qos-level. + * [in] last will: as retained message. + * [in] last will: payload. + * @return success (true), or no success (false). + */ +bool PubSubClient::connect(const char *id, const char* willTopic, + uint8_t willQos, bool willRetain, const char* willMessage) { + return connect(id, NULL, NULL, willTopic, willQos, willRetain, willMessage); +} + +/** + * @brief Connect to a MQTT server. + * @param [in] Device id to identify this device. + * [in] my user name. + * [in] my password. + * [in] last will: topic name. + * [in] last will: qos-level. + * [in] last will: as retained message. + * [in] last will: payload. + * @return success (true), or no success (false). + */ +bool PubSubClient::connect(const char *id, const char *user, const char *pass, + const char* willTopic, uint8_t willQos, bool willRetain, + const char* willMessage) { + _config.id = id; + + _config.user = user; + _config.pass = pass; + + _config.willTopic = willTopic; + _config.willQos = willQos; + _config.willRetain = willRetain; + _config.willMessage = willMessage; + + return connect(); +} + +/** + * @brief Connect to a MQTT server with the with the previous settings. + * Note: do not call this function without settings, this will not work! + * For the very first connect process, use the connect function with parameters. + * @param N/A + * @return success (true), or no success (false). + */ +bool PubSubClient::connect(){ + + if (!connected()) { + + ESP_LOGD(TAG, "Connect to mqtt server..."); + + ESP_LOGD(TAG, "ip: %s port: %d", _config.ip.c_str(), _config.port) + int result = _client->connect((char *)_config.ip.c_str(), _config.port); + + if (result == 0) { + + nextMsgId = 1; + // Leave room in the buffer for header and variable length field + uint16_t length = 5; + unsigned int j; + +#if MQTT_VERSION == MQTT_VERSION_3_1 + uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 9 +#elif MQTT_VERSION == MQTT_VERSION_3_1_1 + uint8_t d[7] = { 0x00, 0x04, 'M', 'Q', 'T', 'T', MQTT_VERSION }; +#define MQTT_HEADER_VERSION_LENGTH 7 +#endif + for (j = 0; j < MQTT_HEADER_VERSION_LENGTH; j++) { + buffer[length++] = d[j]; + } + + uint8_t v; + if (_config.willTopic) { + v = 0x06 | (_config.willQos << 3) | (_config.willRetain << 5); + } else { + v = 0x02; + } + + if (_config.user != NULL) { + v = v | 0x80; + + if (_config.pass != NULL) { + v = v | (0x80 >> 1); + } + } + + buffer[length++] = v; + + buffer[length++] = ((MQTT_KEEPALIVE) >> 8); + buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF); + length = writeString(_config.id, buffer, length); + if (_config.willTopic) { + length = writeString(_config.willTopic, buffer, length); + length = writeString(_config.willMessage, buffer, length); + } + + if (_config.user != NULL) { + length = writeString(_config.user, buffer, length); + if (_config.pass != NULL) { + length = writeString(_config.pass, buffer, length); + } + } + + write(CONNECT, buffer, length - 5); + + // start keepAliveTimer in 1ms... + keepAliveTimer->start(0); //lastInActivity = lastOutActivity = millis(); + + readPacket(); + uint8_t type = buffer[0] & 0xF0; + + if (type == CONNACK) { + ESP_LOGD(TAG, "Connected to mqtt server!"); + + keepAliveTimer->reset(0); //lastInActivity = millis(); + PING_outstanding = false; + _state = CONNECTED; + + m_task->start(this); + return true; + } else { + _state = (mqtt_state) buffer[3]; + ESP_LOGD(TAG, "Error: %d", _state); + } + + } else { + keepAliveTimer->stop(0); + _state = CONNECT_FAILED; + } + return false; + } + return true; +} + +/** + * @brief Receiving a MQTT packet and store it in the buffer to parse it. + * @param N/A. + * @return Number of received bytes. + */ +uint16_t PubSubClient::readPacket() { + + size_t res = _client->receive(buffer, MQTT_MAX_PACKET_SIZE); + + if (res > MQTT_MAX_PACKET_SIZE) { + res = 0; // This will cause the packet to be ignored. + } + + return res; +} + +/** + * @brief Publish a MQTT message. + * @param [in] my topic. + * [in] my payload. + * @return success (true), or no success (false). + */ +bool PubSubClient::publish(const char* topic, const char* payload) { + return publish(topic, (const uint8_t*) payload, strlen(payload), false); +} + +/** + * @brief Publish a MQTT message. + * @param [in] my topic. + * [in] my payload. + * [in] is this a retained message (true/false) + * @return success (true), or no success (false). + */ +bool PubSubClient::publish(const char* topic, const char* payload, + bool retained) { + return publish(topic, (const uint8_t*) payload, strlen(payload), retained); +} + +/** + * @brief Publish a MQTT message. + * @param [in] my topic. + * [in] my payload. + * [in] length of the message + * @return success (true), or no success (false). + */ +bool PubSubClient::publish(const char* topic, const uint8_t* payload, + unsigned int plength) { + return publish(topic, payload, plength, false); +} + +/** + * @brief Publish a MQTT message. + * @param [in] my topic. + * [in] my payload. + * [in] length of the message + * [in] is this a retained message (true/false) + * @return success (true), or no success (false). + */ +bool PubSubClient::publish(const char* topic, const uint8_t* payload, + unsigned int plength, bool retained) { + if (connected()) { + if (MQTT_MAX_PACKET_SIZE < 5 + 2 + strlen(topic) + plength) { + // Too long + return false; + } + // Leave room in the buffer for header and variable length field + uint16_t length = 5; + length = writeString(topic, buffer, length); + uint16_t i; + for (i = 0; i < plength; i++) { + buffer[length++] = payload[i]; + } + uint8_t header = PUBLISH; + if (retained) { + header |= 1; + } + return write(header, buffer, length - 5); + } + return false; +} + +//bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, +// unsigned int plength, bool retained) { +// uint8_t llen = 0; +// uint8_t digit; +// unsigned int rc = 0; +// uint16_t tlen; +// unsigned int pos = 0; +// unsigned int i; +// uint8_t header; +// unsigned int len; +// +// if (!connected()) { +// return false; +// } +// +// tlen = strlen(topic); +// +// header = PUBLISH; +// if (retained) { +// header |= 1; +// } +// buffer[pos++] = header; +// len = plength + 2 + tlen; +// do { +// digit = len % 128; +// len = len / 128; +// if (len > 0) { +// digit |= 0x80; +// } +// buffer[pos++] = digit; +// llen++; +// } while (len > 0); +// +// pos = writeString(topic, buffer, pos); +// +// rc += _client->send(buffer, pos); +// +// for (i = 0; i < plength; i++) { +// //rc += _client->send((char)pgm_read_byte_near(payload + i)); +// //rc += _client->send((uint8_t*) pgm_read_byte_near(payload + i), 1); +// } +// +// keepAliveTimer->reset(0); //lastOutActivity = millis(); +// +// return rc == tlen + 4 + plength; +//} + +/** + * @brief Send a MQTT message over socket. + * @param [in] MQTT header. + * [in] Message buffer. + * [in] total length. + * @return success (true), or no success (false). + */ +bool PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { + uint8_t lenBuf[4]; + uint8_t llen = 0; + uint8_t digit; + uint8_t pos = 0; + int rc; + uint16_t len = length; + do { + digit = len % 128; + len = len / 128; + if (len > 0) { + digit |= 0x80; + } + lenBuf[pos++] = digit; + llen++; + } while (len > 0); + + buf[4 - llen] = header; + for (int i = 0; i < llen; i++) { + buf[5 - llen + i] = lenBuf[i]; + } + +//#ifdef MQTT_MAX_TRANSFER_SIZE +// uint8_t* writeBuf = buf+(4-llen); +// uint16_t bytesRemaining = length+1+llen; //Match the length type +// uint8_t bytesToWrite; +// bool result = true; +// while((bytesRemaining > 0) && result) { +// bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; +// rc = _client->write(writeBuf,bytesToWrite); +// result = (rc == bytesToWrite); +// bytesRemaining -= rc; +// writeBuf += rc; +// } +// return result; +//#else + rc = _client->send(buf + (4 - llen), length + 1 + llen); + if(rc < 0) _state = CONNECTION_LOST; + keepAliveTimer->reset(0); //lastOutActivity = millis(); + return (rc == 1 + llen + length); +//#endif +} + +/** + * @brief Subscribe a MQTT topic. + * @param [in] my topic + * [in] qos of subscription + * @return request transmitted with success (true), or no success (false). + */ +bool PubSubClient::subscribe(const char* topic, bool ack) { + + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { + // Too long + return false; + } + if (connected()) { + // Leave room in the buffer for header and variable length field + uint16_t length = 5; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, buffer, length); + buffer[length++] = QOS1; + + if(write(SUBSCRIBE | QOS1, buffer, length - 5)){ + SUBACK_outstanding = true; + if(ack) timeoutTimer->start(0); + return true; + } + } + return false; +} + +/** + * @brief Check the state of subscription. If there was received a subscription + * ACK, we return a true here. + * @return Is subscription validated with ACK (true/false) + */ +bool PubSubClient::isSubscribeDone(void){ + return !SUBACK_outstanding; +} + +/** + * @brief Unsubscribe a MQTT topic. + * @param [in] my topic + * [in] qos of unsubscription + * @return request transmitted with success (true), or no success (false). + */ +bool PubSubClient::unsubscribe(const char* topic, bool ack) { + + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { + // Too long + return false; + } + if (connected()) { + uint16_t length = 5; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, buffer, length); + + if(write(UNSUBSCRIBE | QOS1, buffer, length - 5)){ + UNSUBACK_Outstanding = true; + if(ack) timeoutTimer->start(0); + return true; + } + } + return false; +} + +/** + * @brief Check the state of unsubscription. If there was received a unsubscription + * ACK, we return a true here. + * @return Is unsubscription validated with ACK (true/false) + */ +bool PubSubClient::isUnsubscribeDone(void){ + return !UNSUBACK_Outstanding; +} + +/** + * @brief Disconnect form MQTT server and close the socket. + * @return N/A. + */ +void PubSubClient::disconnect() { + buffer[0] = DISCONNECT; + buffer[1] = 0; + _client->send(buffer, 2); + _state = DISCONNECTED; + _client->close(); + keepAliveTimer->stop(0); //lastInActivity = lastOutActivity = millis(); + timeoutTimer->stop(0); +} + +/** + * @brief calculation help to send a string. + */ +uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, + uint16_t pos) { + const char* idp = string; + uint16_t i = 0; + pos += 2; + while (*idp) { + buf[pos++] = *idp++; + i++; + } + buf[pos - i - 2] = (i >> 8); + buf[pos - i - 1] = (i & 0xFF); + return pos; +} + +/** + * @brief Check the connection to the MQTT server. + * @return connected (true/false) + */ +bool PubSubClient::connected() { + + if (this->_state == CONNECTED) { + + bool rc = true; + + if (_client == nullptr) { + rc = false; + } else if (!_client->isValid()) { + rc = false; + + this->_state = CONNECTION_LOST; + + if (_client->isValid()) _client->close(); + keepAliveTimer->stop(0); + timeoutTimer->stop(0); + } + return rc; + } + return false; + ESP_LOGD(TAG, "_state = %d", _state); + +} + +/** + * @brief Set server ip and Port of my MQTT server. + * @param [in] ip of the distant MQTT server. + * [in] the port of the distant MQTT port. + * @return My instance. + */ +PubSubClient& PubSubClient::setServer(std::string ip, uint16_t port) { + _config.ip = ip; + _config.port = port; + return *this; +} + +/** + * @brief Set the callback function for incoming data. + * @param [in] callback function + * @return My instance. + */ +PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { + this->callback = callback; + return *this; +} + +/** + * @brief Set the socket, which we want to use for our MQTT communication. + * @param [in] the new socket instance + * @return My instance. + */ +PubSubClient& PubSubClient::setClient(Socket& client) { + this->_client = &client; + return *this; +} + +/** + * @brief Get the current MYTT state form the instance. + * @param N/A. + * @return current MQTT state. + */ +int PubSubClient::state() { + return this->_state; +} + +/** + * @brief Parsing the received data in to the internal message struct. + */ +void PubSubClient::parseData(mqtt_message* msg, uint16_t len){ + + /********* Parse Fixed header *********/ + msg->type = buffer[0] & 0xF0; + + /* read DUP-Flag */ + if(msg->type == PUBLISH) + msg->dup = (bool)(buffer[0] & 0x18)>>3; + + /* read QoS-Level */ + if(msg->type == PUBLISH) + msg->qos = (buffer[0] & 0x06); + + /* read RETAIN-Frag */ + if(msg->type == PUBLISH) + msg->retained = (bool)(buffer[0] & 0x01); + + uint8_t remainingLength = buffer[1]; + + /********* Parse Variable header *********/ + int pos = 2; + + /* read topic name */ + if(msg->type == PUBLISH){ + uint16_t topicLen = (buffer[2] << 8) + buffer[3]; + + msg->topic = ""; + for(int i = 4; i<(topicLen+4); i++){ + msg->topic += (char) buffer[i]; + pos++; + } + } + + /* read Message ID */ + if(msg->type == PUBLISH || msg->type == PUBACK || msg->type == PUBREC || msg->type == PUBCOMP || msg->type == SUBACK || msg->type == UNSUBACK){ + msg->msgId = (buffer[pos]<<8) + (buffer[pos+1]); + pos += 2; + } + + /********* read Payload *********/ + if(msg->type == PUBLISH){ + msg->payload = ""; + for(int i = pos; i payload += (char)buffer[i]; + } + } +} + +/** + * @brief Dump the message struct. + */ +void PubSubClient::dumpData(mqtt_message* msg){ + + ESP_LOGD(TAG, "mqtt_message_type: %s", messageType_toString(msg->type).c_str()); + ESP_LOGD(TAG, "mqtt_qos: %d", msg->qos); + ESP_LOGD(TAG, "retained: %d", msg->retained); + ESP_LOGD(TAG, "dup: %d", msg->dup); + ESP_LOGD(TAG, "topic: %s", msg->topic.c_str()); + ESP_LOGD(TAG, "payload: %s", msg->payload.c_str()); + ESP_LOGD(TAG, "msgId: %d", msg->msgId); +} + +/** + * @brief Convert the MQTT message type to string. + * @param [in] message type byte. + * @return message type as std::string. + */ +std::string PubSubClient::messageType_toString(uint8_t type){ + std::string str = "Not in list!"; + switch(type){ + case CONNECT : str = "CONNECT"; break; + case CONNACK : str = "CONNACK"; break; + case PUBLISH : str = "PUBLISH"; break; + case PUBACK : str = "PUBACK"; break; + case PUBREC : str = "PUBREC"; break; + case PUBREL : str = "PUBREL"; break; + case PUBCOMP : str = "PUBCOMP"; break; + case SUBSCRIBE : str = "SUBSCRIBE"; break; + case SUBACK : str = "SUBACK"; break; + case UNSUBSCRIBE: str = "UNSUBSCRIBE"; break; + case UNSUBACK : str = "UNSUBACK"; break; + case PINGREQ : str = "PINGREQ"; break; + case PINGRESP : str = "PINGRESP"; break; + case DISCONNECT : str = "DISCONNECT"; break; + case Reserved : str = "Reserved"; break; + } + + return str; +} diff --git a/cpp_utils/PubSubClient.h b/cpp_utils/PubSubClient.h new file mode 100644 index 00000000..8e56ca59 --- /dev/null +++ b/cpp_utils/PubSubClient.h @@ -0,0 +1,172 @@ +/* + PubSubClient.h - A simple client for MQTT. + Nick O'Leary + http://knolleary.net + + edit by marcel.seerig +*/ + +#ifndef PubSubClient_h +#define PubSubClient_h + +#include +#include "Socket.h" +#include "FreeRTOSTimer.h" + +#define MQTT_VERSION_3_1 3 +#define MQTT_VERSION_3_1_1 4 + +// MQTT_VERSION : Pick the version +//#define MQTT_VERSION MQTT_VERSION_3_1 +#ifndef MQTT_VERSION +#define MQTT_VERSION MQTT_VERSION_3_1_1 +#endif + +// MQTT_MAX_PACKET_SIZE : Maximum packet size +#ifndef MQTT_MAX_PACKET_SIZE +#define MQTT_MAX_PACKET_SIZE 128 +#endif + +// MQTT_KEEPALIVE : keepAlive interval in Seconds +#ifndef MQTT_KEEPALIVE +#define MQTT_KEEPALIVE 15 +#endif + +// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds +#ifndef MQTT_SOCKET_TIMEOUT +#define MQTT_SOCKET_TIMEOUT 15 +#endif + +// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client +// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to +// pass the entire MQTT packet in each write call. +//#define MQTT_MAX_TRANSFER_SIZE 80 + +// Possible values for client.state() + +struct mqtt_InitTypeDef{ + std::string ip; + uint16_t port; + + const char* user; + const char* pass; + + const char * id; + const char* willTopic; + uint8_t willQos; + bool willRetain; + const char* willMessage; +}; + +typedef enum{ + CONNECTION_TIMEOUT = -4, + CONNECTION_LOST = -3, + CONNECT_FAILED = -2, + DISCONNECTED = -1, + CONNECTED = 0, + CONNECT_BAD_PROTOCOL = 1, + CONNECT_BAD_CLIENT_ID = 2, + CONNECT_UNAVAILABLE = 3, + CONNECT_BAD_CREDENTIALS = 4, + CONNECT_UNAUTHORIZED = 5, +}mqtt_state; + +typedef enum{ + CONNECT = 1 << 4, // Client request to connect to Server + CONNACK = 2 << 4, // Connect Acknowledgment + PUBLISH = 3 << 4, // Publish message + PUBACK = 4 << 4, // Publish Acknowledgment + PUBREC = 5 << 4, // Publish Received (assured delivery part 1) + PUBREL = 6 << 4, // Publish Release (assured delivery part 2) + PUBCOMP = 7 << 4, // Publish Complete (assured delivery part 3) + SUBSCRIBE = 8 << 4, // Client Subscribe request + SUBACK = 9 << 4, // Subscribe Acknowledgment + UNSUBSCRIBE = 10 << 4, // Client Unsubscribe request + UNSUBACK = 11 << 4, // Unsubscribe Acknowledgment + PINGREQ = 12 << 4, // PING Request + PINGRESP = 13 << 4, // PING Response + DISCONNECT = 14 << 4, // Client is Disconnecting + Reserved = 15 << 4, // Reserved +}mqtt_message_type; + +typedef enum{ + QOS0 = (0 << 1), + QOS1 = (1 << 1), + QOS2 = (2 << 1), +}mqtt_qos; + +struct mqtt_message{ + uint8_t type; + uint8_t qos; + bool retained; + bool dup; + std::string topic; + std::string payload; + uint16_t msgId; +}; + +#define MQTT_CALLBACK_SIGNATURE void (*callback)(std::string, std::string) + +class PubSubClientTask; + +class PubSubClient { +public: + PubSubClient(); + PubSubClient(Socket& client); + PubSubClient(std::string ip, uint16_t port); + PubSubClient(std::string ip, uint16_t port, Socket& client); + PubSubClient(std::string ip, uint16_t port, MQTT_CALLBACK_SIGNATURE,Socket& client); + ~PubSubClient(); + + PubSubClient& setServer(std::string ip, uint16_t port); + PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); + PubSubClient& setClient(Socket& client); + + bool connect(const char* id); + bool connect(const char* id, const char* user, const char* pass); + bool connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage); + bool connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage); + bool connect(); + void disconnect(); + bool publish(const char* topic, const char* payload); + bool publish(const char* topic, const char* payload, bool retained); + bool publish(const char* topic, const uint8_t * payload, unsigned int plength); + bool publish(const char* topic, const uint8_t * payload, unsigned int plength, bool retained); + //bool publish_P(const char* topic, const uint8_t * payload, unsigned int plength, bool retained); + + bool subscribe (const char* topic, bool ack=false); + bool unsubscribe (const char* topic, bool ack=false); + bool isSubscribeDone (void); + bool isUnsubscribeDone (void); + + bool connected (void); + int state (void); + void keepAliveChecker (void); + void timeoutChecker (void); + +private: + friend class PubSubClientTask; + PubSubClientTask* m_task; + Socket* _client; + mqtt_InitTypeDef _config; + mqtt_state _state; + uint8_t buffer[MQTT_MAX_PACKET_SIZE]; + uint16_t nextMsgId; + bool PING_outstanding; + bool SUBACK_outstanding; + bool UNSUBACK_Outstanding; + FreeRTOSTimer* keepAliveTimer; + FreeRTOSTimer* timeoutTimer; + + MQTT_CALLBACK_SIGNATURE; + void setup (void); + uint16_t readPacket(); + bool write (uint8_t header, uint8_t* buf, uint16_t length); + uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); + void parseData (mqtt_message* msg, uint16_t len); + void dumpData (mqtt_message* msg); + std::string messageType_toString(uint8_t type); + +}; + +#endif From 94cc9439d1cf4feed758c14a7aa2223408cc66d9 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 24 Feb 2018 09:05:37 -0600 Subject: [PATCH 242/381] Addition of bootloaderExamine tool source --- tools/bootloaderExamine/main.cpp | 147 +++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 tools/bootloaderExamine/main.cpp diff --git a/tools/bootloaderExamine/main.cpp b/tools/bootloaderExamine/main.cpp new file mode 100644 index 00000000..aaf0f458 --- /dev/null +++ b/tools/bootloaderExamine/main.cpp @@ -0,0 +1,147 @@ +#include +#include + +/* Main header of binary image */ +typedef struct { + uint8_t magic; + uint8_t segment_count; + /* flash read mode (esp_image_spi_mode_t as uint8_t) */ + uint8_t spi_mode; + /* flash frequency (esp_image_spi_freq_t as uint8_t) */ + uint8_t spi_speed: 4; + /* flash chip size (esp_image_flash_size_t as uint8_t) */ + uint8_t spi_size: 4; + uint32_t entry_addr; + /* WP pin when SPI pins set via efuse (read by ROM bootloader, the IDF bootloader uses software to configure the WP + * pin and sets this field to 0xEE=disabled) */ + uint8_t wp_pin; + /* Drive settings for the SPI flash pins (read by ROM bootloader) */ + uint8_t spi_pin_drv[3]; + /* Reserved bytes in ESP32 additional header space, currently unused */ + uint8_t reserved[11]; + /* If 1, a SHA256 digest "simple hash" (of the entire image) is appended after the checksum. Included in image length. This digest + * is separate to secure boot and only used for detecting corruption. For secure boot signed images, the signature + * is appended after this (and the simple hash is included in the signed data). */ + uint8_t hash_appended; +} __attribute__((packed)) esp_image_header_t; + +typedef struct { + uint32_t load_addr; + uint32_t data_len; +} esp_image_segment_header_t; + + +/** + * @brief Return a memory area name given an address. + * @param addr The address to decode. + * @return A string description of the memory area. + */ +static const char* area(uint32_t addr) { + if (addr >= 0x3FF80000 && addr <= 0x3FF81FFF) { + return "RTS Fast (8K)"; + } + if (addr >= 0x3FF82000 && addr <= 0x3FF8FFFF) { + return "Reserved"; + } + if (addr >= 0x3FF90000 && addr <= 0x3FF9FFFF) { + return "Internal ROM 1 (64K)"; + } + if (addr >= 0x3FFA0000 && addr <= 0x3FFADFFF) { + return "Reserved"; + } + if (addr >= 0x3FFAE000 && addr <= 0x3FFDFFFF) { + return "Internal SRAM 2 (200K)"; + } + if (addr >= 0x3FFE0000 && addr <= 0x3FFFFFFF) { + return "Internal SRAM 1 (128K)"; + } + if (addr >= 0x40000000 && addr <= 0x40007FFF) { + return "Internal ROM 0 0 (32K)"; + } + if (addr >= 0x40008000 && addr <= 0x4005FFFF) { + return "Internal ROM 0 (352K)"; + } + if (addr >= 0x40060000 && addr <= 0x4006FFFF) { + return "Reserved"; + } + if (addr >= 0x40070000 && addr <= 0x4007FFFF) { + return "Internal SRAM 0 (64K)"; + } + if (addr >= 0x40080000 && addr <= 0x4009FFFF) { + return "Internal SRAM 0 (128K)"; + } + if (addr >= 0x400A0000 && addr <= 0x400AFFFF) { + return "Internal SRAM 1 (64K)"; + } + if (addr >= 0x400B0000 && addr <= 0x400B7FFF) { + return "Internal SRAM 1 (32K)"; + } + if (addr >= 0x400B8000 && addr <= 0x400BFFFF) { + return "Internal SRAM 1 (32K)"; + } + if (addr >= 0x400C0000 && addr <= 0x400C1FFF) { + return "RTC FAST Memory (8K)"; + } + if (addr >= 0x50000000 && addr <= 0x50001FFF) { + return "RTC SLOW Memory (8K)"; + } + if (addr >= 0x3F400000 && addr <= 0x3F7FFFFF) { + return "External Memory (Data)"; + } + if (addr >= 0x3F800000 && addr <= 0x3FBFFFFF) { + return "External Memory (Data)"; + } + if (addr >= 0x400C2000 && addr <= 0x40BFFFFF) { + return "External Memory (Instruction)"; + } + return "Un-described"; +} //area + + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: %s ESP32_BIN_FILE.bin\n", argv[0]); + return 0; + } + + const char* fileName = argv[1]; + esp_image_header_t header; + esp_image_segment_header_t imageSegmentHeader; + + FILE* file = fopen(fileName, "rb"); + if (file == nullptr) { + printf("Failed to open file %s\n", fileName); + return 0; + } + + size_t count = fread(&header, sizeof(esp_image_header_t), 1, file); + if (count != 1) { + printf("Failed to read esp_image_header_t\n"); + return 0; + } + + printf("Dump of ESP32 binary file: %s\n", fileName); + printf("magic: 0x%x, segment_count: %d, entry_addr: 0x%x - %s\n", + header.magic, header.segment_count, header.entry_addr, area(header.entry_addr)); + + printf("\n"); + printf("Seg | Start | End | Length | Area\n"); + printf("----+------------+------------+-------------------+-------------------------------\n"); + for (int i=0; i Date: Wed, 28 Feb 2018 11:08:23 +0100 Subject: [PATCH 243/381] Allow HttpParser to parse response messages as well as request messages --- cpp_utils/HttpParser.cpp | 52 ++++++++++++++++++++++++++++++++++++++++ cpp_utils/HttpParser.h | 6 +++++ 2 files changed, 58 insertions(+) diff --git a/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp index 9eb6d535..cc7b686b 100644 --- a/cpp_utils/HttpParser.cpp +++ b/cpp_utils/HttpParser.cpp @@ -168,6 +168,13 @@ std::string HttpParser::getVersion() { return m_version; } // getVersion +std::string HttpParser::getStatus() { + return m_status; +} // getStatus + +std::string HttpParser::getReason() { + return m_reason; +} // getReason /** * @brief Determine if we have a header of the given name. @@ -261,3 +268,48 @@ void HttpParser::parseRequestLine(std::string &line) { m_version = toCharToken(it, line, ' '); ESP_LOGD(LOG_TAG, "<< parseRequestLine: method: %s, url: %s, version: %s", m_method.c_str(), m_url.c_str(), m_version.c_str()); } // parseRequestLine + +/** + * @brief Parse a response message. + * @param [in] line The response to parse. + */ +// A response is built from: +// A status line, any number of header lines, a body +// +void HttpParser::parseResponse(std::string message) +{ + auto it = message.begin(); + auto line = toStringToken(it, message, lineTerminator); + parseStatusLine(line); + + line = toStringToken(it, message, lineTerminator); + while(!line.empty()) { + ESP_LOGD(LOG_TAG, "Header: \"%s\"", line.c_str()); + m_headers.insert(parseHeader(line)); + line = toStringToken(it, message, lineTerminator); + } + + m_body = message.substr(std::distance(message.begin(), it)); +} // parse + +/** + * @brief Parse A status line. + * @param [in] line The status line to parse. + */ +// A status Line is built from: +// +// +void HttpParser::parseStatusLine(std::string &line) +{ + ESP_LOGD(LOG_TAG, ">> ParseStatusLine: \"%s\" [%d]", line.c_str(), line.length()); + std::string::iterator it = line.begin(); + // Get the version + m_version = toCharToken(it, line, ' '); + // Get the version + m_status = toCharToken(it, line, ' '); + // Get the status code + m_reason = toStringToken(it, line, lineTerminator); + + ESP_LOGD(LOG_TAG, "<< ParseStatusLine: method: %s, version: %s, status: %s", m_method.c_str(), m_version.c_str(), m_status.c_str()); +} // parseRequestLine + diff --git a/cpp_utils/HttpParser.h b/cpp_utils/HttpParser.h index 77191163..47aafcea 100644 --- a/cpp_utils/HttpParser.h +++ b/cpp_utils/HttpParser.h @@ -17,9 +17,12 @@ class HttpParser { std::string m_url; std::string m_version; std::string m_body; + std::string m_status; + std::string m_reason; std::map m_headers; void dump(); void parseRequestLine(std::string &line); + void parseStatusLine(std::string &line); public: HttpParser(); virtual ~HttpParser(); @@ -29,9 +32,12 @@ class HttpParser { std::string getMethod(); std::string getURL(); std::string getVersion(); + std::string getStatus(); + std::string getReason(); bool hasHeader(const std::string& name); void parse(std::string message); void parse(Socket s); + void parseResponse(std::string message); }; #endif /* CPP_UTILS_HTTPPARSER_H_ */ From 280aa8469a3387e4c3b211809395ced0870f5a8d Mon Sep 17 00:00:00 2001 From: Han Date: Fri, 2 Mar 2018 17:41:04 +0100 Subject: [PATCH 244/381] Retry if esp_wifi_connect fails to connect --- cpp_utils/WiFi.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index f3176530..5fdaede1 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -194,14 +194,17 @@ bool WiFi::connectAP(const std::string& ssid, const std::string& password, bool } m_connectFinished.take("connectAP"); // Take the semaphore to wait for a connection. + do { + ESP_LOGD(LOG_TAG, "esp_wifi_connect"); + errRc = ::esp_wifi_connect(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + } + while (!m_connectFinished.take(5000, "connectAP")); // retry if not connected within 5s + m_connectFinished.give(); - errRc = ::esp_wifi_connect(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - m_connectFinished.wait("connectAP"); // Wait for the completion of the connection. ESP_LOGD(LOG_TAG, "<< connectAP"); return m_apConnected; // Return true if we are now connected and false if not. } // connectAP From f182335222617611ac1c24229993e47ace2a9cf9 Mon Sep 17 00:00:00 2001 From: "R. Main" Date: Sat, 3 Mar 2018 12:58:48 -0500 Subject: [PATCH 245/381] Update u8g2_esp32_hal.c To ensure use of GPIO32 & GPIO33 are supported (34-39 are input only), must define literal constant as 'unsigned long long', or the compiler produces code that fails to perform the required shift, resulting in corruption of the GPIO matrix. --- hardware/displays/U8G2/u8g2_esp32_hal.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hardware/displays/U8G2/u8g2_esp32_hal.c b/hardware/displays/U8G2/u8g2_esp32_hal.c index 05a2c994..fdab51a3 100644 --- a/hardware/displays/U8G2/u8g2_esp32_hal.c +++ b/hardware/displays/U8G2/u8g2_esp32_hal.c @@ -176,13 +176,13 @@ uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, case U8X8_MSG_GPIO_AND_DELAY_INIT: { uint64_t bitmask = 0; if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { - bitmask = bitmask | (1< Date: Sun, 4 Mar 2018 11:15:42 -0600 Subject: [PATCH 246/381] Addition of MMU tool --- tools/MMUTool/MMUTool.html | 134 +++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 tools/MMUTool/MMUTool.html diff --git a/tools/MMUTool/MMUTool.html b/tools/MMUTool/MMUTool.html new file mode 100644 index 00000000..b50337ab --- /dev/null +++ b/tools/MMUTool/MMUTool.html @@ -0,0 +1,134 @@ + + + + +MMUTool + + + + + +

ESP32 MMU Tool

+

Storage address:

+

MMU entry:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0x3F40 00000x3F40 FFFF0Data
0x3F41 00000x3F41 FFFF1Data
.........Data
0x3F7F 00000x3F7F FFFF63Data
0x4000 00000x4000 FFFF64Instruction
0x4001 00000x4001 FFFF65Instruction
.........Instruction
0x403F 00000x403F FFFF127Instruction
0x4040 00000x4040 FFFF128Instruction
.........Instruction
0x407F 00000x407F FFFF255Instruction
+ + + \ No newline at end of file From dbad017d0449d662e78a75b72c3700a5868163d3 Mon Sep 17 00:00:00 2001 From: Damon Smith Date: Mon, 5 Mar 2018 21:36:05 +1100 Subject: [PATCH 247/381] stop NVS get from crashing if the value hasn't been set yet, also expose a function in wifi to get connected state. --- cpp_utils/CPPNVS.cpp | 18 ++++++++++++------ cpp_utils/CPPNVS.h | 6 +++--- cpp_utils/WiFi.cpp | 9 +++++++++ cpp_utils/WiFi.h | 1 + 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/cpp_utils/CPPNVS.cpp b/cpp_utils/CPPNVS.cpp index d0a2a0bb..f363439a 100644 --- a/cpp_utils/CPPNVS.cpp +++ b/cpp_utils/CPPNVS.cpp @@ -70,12 +70,16 @@ void NVS::erase(std::string key) { * @param [in] key The key to read from the namespace. * @param [out] result The string read from the %NVS storage. */ -void NVS::get(std::string key, std::string* result, bool isBlob) { +int NVS::get(std::string key, std::string* result, bool isBlob) { size_t length; if (isBlob) { - ::nvs_get_blob(m_handle, key.c_str(), NULL, &length); + esp_err_t rc = ::nvs_get_blob(m_handle, key.c_str(), NULL, &length); + ESP_LOGD(LOG_TAG, "Error getting key: %i", rc); + return rc; } else { - ::nvs_get_str(m_handle, key.c_str(), NULL, &length); + esp_err_t rc = ::nvs_get_str(m_handle, key.c_str(), NULL, &length); + ESP_LOGD(LOG_TAG, "Error getting key: %i", rc); + return rc; } char *data = (char *)malloc(length); if (isBlob) { @@ -85,21 +89,23 @@ void NVS::get(std::string key, std::string* result, bool isBlob) { } *result = std::string(data); free(data); + return ESP_OK; } // get -void NVS::get(std::string key, uint32_t& value) { - ::nvs_get_u32(m_handle, key.c_str(), &value); +int NVS::get(std::string key, uint32_t& value) { + return ::nvs_get_u32(m_handle, key.c_str(), &value); } // get - uint32_t -void NVS::get(std::string key, uint8_t* result, size_t& length) { +int NVS::get(std::string key, uint8_t* result, size_t& length) { ESP_LOGD(LOG_TAG, ">> get: key: %s, blob: inputSize: %d", key.c_str(), length); esp_err_t rc = ::nvs_get_blob(m_handle, key.c_str(), result, &length); if (rc != ESP_OK) { ESP_LOGD(LOG_TAG, "nvs_get_blob: %d", rc); } ESP_LOGD(LOG_TAG, "<< get: outputSize: %d", length); + return rc; } // get - blob diff --git a/cpp_utils/CPPNVS.h b/cpp_utils/CPPNVS.h index 755f551b..6afbc166 100644 --- a/cpp_utils/CPPNVS.h +++ b/cpp_utils/CPPNVS.h @@ -21,9 +21,9 @@ class NVS { void erase(); void erase(std::string key); - void get(std::string key, std::string* result, bool isBlob=false); - void get(std::string key, uint8_t* result, size_t &length); - void get(std::string key, uint32_t& value); + int get(std::string key, std::string* result, bool isBlob=false); + int get(std::string key, uint8_t* result, size_t &length); + int get(std::string key, uint32_t& value); void set(std::string key, std::string data, bool isBlob=false); void set(std::string key, uint32_t value); void set(std::string key, uint8_t* data, size_t length); diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 5fdaede1..bfe9477d 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -224,6 +224,15 @@ void WiFi::dump() { } // dump +/** + * @brief Returns whether wifi is connected to an access point + */ +bool WiFi::isConnectedToAP() { + return m_apConnected; +} // isConnected + + + /** * @brief Primary event handler interface. */ diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index b06635d9..e12994ef 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -132,6 +132,7 @@ class WiFi { struct in_addr getHostByName(const char* hostName); bool connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); void dump(); + bool isConnectedToAP(); static std::string getApMac(); static tcpip_adapter_ip_info_t getApIpInfo(); static std::string getApSSID(); From 3436c8735202bc6f7a479772df635e76d3d29c4e Mon Sep 17 00:00:00 2001 From: pcbreflux Date: Tue, 6 Mar 2018 13:07:22 +0100 Subject: [PATCH 248/381] BLE_iBeacon --- .../Arduino/BLE_iBeacon/BLE_iBeacon.ino | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino b/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino new file mode 100644 index 00000000..5f6ed002 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino @@ -0,0 +1,104 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp + Ported to Arduino ESP32 by pcbreflux +*/ + + +/* + Create a BLE server that will send periodic iBeacon frames. + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create advertising data + 3. Start advertising. + 4. wait + 5. Stop advertising. + 6. deep sleep + +*/ +#include "sys/time.h" + +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLEBeacon.h" +#include "esp_sleep.h" + +#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up +RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory +RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory + +#ifdef __cplusplus +extern "C" { +#endif + +uint8_t temprature_sens_read(); +//uint8_t g_phyFuns; + +#ifdef __cplusplus +} +#endif + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +BLEAdvertising *pAdvertising; +struct timeval now; + +#define BEACON_UUID "8ec76ea3-6668-48da-9866-75be8bc86f4d" // UUID 1 128-Bit (may use linux tool uuidgen or random numbers via https://www.uuidgenerator.net/) + +void setBeacon() { + + BLEBeacon oBeacon = BLEBeacon(); + oBeacon.setManufacturerId(0x4C00); // fake Apple 0x004C LSB (ENDIAN_CHANGE_U16!) + oBeacon.setProximityUUID(BLEUUID(BEACON_UUID)); + oBeacon.setMajor((bootcount & 0xFFFF0000) >> 16); + oBeacon.setMinor(bootcount&0xFFFF); + BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); + BLEAdvertisementData oScanResponseData = BLEAdvertisementData(); + + oAdvertisementData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED 0x04 + + std::string strServiceData = ""; + + strServiceData += (char)26; // Len + strServiceData += (char)0xFF; // Type + strServiceData += oBeacon.getData(); + oAdvertisementData.addData(strServiceData); + + pAdvertising->setAdvertisementData(oAdvertisementData); + pAdvertising->setScanResponseData(oScanResponseData); + +} + +void setup() { + + + Serial.begin(115200); + gettimeofday(&now, NULL); + + Serial.printf("start ESP32 %d\n",bootcount++); + + Serial.printf("deep sleep (%lds since last reset, %lds since last boot)\n",now.tv_sec,now.tv_sec-last); + + last = now.tv_sec; + + // Create the BLE Device + BLEDevice::init(""); + + // Create the BLE Server + BLEServer *pServer = BLEDevice::createServer(); + + pAdvertising = pServer->getAdvertising(); + + setBeacon(); + // Start advertising + pAdvertising->start(); + Serial.println("Advertizing started..."); + delay(100); + pAdvertising->stop(); + Serial.printf("enter deep sleep\n"); + esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION); + Serial.printf("in deep sleep\n"); +} + +void loop() { +} From b1c5399325fdd3065b11c0715bdad63e1c3d4a4c Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 10 Mar 2018 18:41:21 -0600 Subject: [PATCH 249/381] Change for this forum post: https://esp32.com/viewtopic.php?f=2&t=3756 --- .../paho_mqtt_embedded_c/MQTTClient-C/src/linux/MQTTLinux.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/networking/mqtt/paho_mqtt_embedded_c/MQTTClient-C/src/linux/MQTTLinux.c b/networking/mqtt/paho_mqtt_embedded_c/MQTTClient-C/src/linux/MQTTLinux.c index f46a0fde..38de27c9 100644 --- a/networking/mqtt/paho_mqtt_embedded_c/MQTTClient-C/src/linux/MQTTLinux.c +++ b/networking/mqtt/paho_mqtt_embedded_c/MQTTClient-C/src/linux/MQTTLinux.c @@ -215,7 +215,9 @@ static int NetworkConnectSSL(Network *n, char *addr, int port) { mbedtls_entropy_init(&mqtt_ssl_context->entropy); mbedtls_ssl_conf_dbg(&mqtt_ssl_context->conf, my_debug, stdout); +#ifdef CONFIG_MBEDTLS_DEBUG mbedtls_debug_set_threshold(4); // Log at verbose only +#endif // CONFIG_MBEDTLS_DEBUG int ret = mbedtls_ctr_drbg_seed( &mqtt_ssl_context->ctr_drbg, From aef10f41f339be05d2e7e3b5fb1b2b7bcf80adf1 Mon Sep 17 00:00:00 2001 From: Damon Smith Date: Tue, 13 Mar 2018 00:12:11 +1100 Subject: [PATCH 250/381] only return NVS error code if it is not OK --- cpp_utils/CPPNVS.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cpp_utils/CPPNVS.cpp b/cpp_utils/CPPNVS.cpp index f363439a..b204045b 100644 --- a/cpp_utils/CPPNVS.cpp +++ b/cpp_utils/CPPNVS.cpp @@ -74,12 +74,16 @@ int NVS::get(std::string key, std::string* result, bool isBlob) { size_t length; if (isBlob) { esp_err_t rc = ::nvs_get_blob(m_handle, key.c_str(), NULL, &length); - ESP_LOGD(LOG_TAG, "Error getting key: %i", rc); - return rc; + if (rc != ESP_OK) { + ESP_LOGI(LOG_TAG, "Error getting key: %i", rc); + return rc; + } } else { esp_err_t rc = ::nvs_get_str(m_handle, key.c_str(), NULL, &length); - ESP_LOGD(LOG_TAG, "Error getting key: %i", rc); - return rc; + if (rc != ESP_OK) { + ESP_LOGI(LOG_TAG, "Error getting key: %i", rc); + return rc; + } } char *data = (char *)malloc(length); if (isBlob) { From 5c893018ba951c3025f492130ce38296fbba7fe0 Mon Sep 17 00:00:00 2001 From: pcbreflux Date: Tue, 13 Mar 2018 09:27:34 +0100 Subject: [PATCH 251/381] BLEEddystone --- cpp_utils/BLEEddystoneTLM.cpp | 134 ++++++++++++++++++++++++++++++ cpp_utils/BLEEddystoneTLM.h | 50 ++++++++++++ cpp_utils/BLEEddystoneURL.cpp | 149 ++++++++++++++++++++++++++++++++++ cpp_utils/BLEEddystoneURL.h | 42 ++++++++++ 4 files changed, 375 insertions(+) create mode 100644 cpp_utils/BLEEddystoneTLM.cpp create mode 100644 cpp_utils/BLEEddystoneTLM.h create mode 100644 cpp_utils/BLEEddystoneURL.cpp create mode 100644 cpp_utils/BLEEddystoneURL.h diff --git a/cpp_utils/BLEEddystoneTLM.cpp b/cpp_utils/BLEEddystoneTLM.cpp new file mode 100644 index 00000000..0c61ac1b --- /dev/null +++ b/cpp_utils/BLEEddystoneTLM.cpp @@ -0,0 +1,134 @@ +/* + * BLEEddystoneTLM.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ +#include "Arduino.h" +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEEddystoneTLM.h" + +static const char LOG_TAG[] = "BLEEddystoneTLM"; +#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) +#define ENDIAN_CHANGE_U32(x) ((((x)&0xFF000000)>>24) + (((x)&0x00FF0000)>>8)) + ((((x)&0xFF00)<<8) + (((x)&0xFF)<<24)) + +BLEEddystoneTLM::BLEEddystoneTLM() { + beconUUID = 0xFEAA; + m_eddystoneData.frameType = EDDYSTONE_TLM_FRAME_TYPE; + m_eddystoneData.version = 0; + m_eddystoneData.volt = 3300; // 3300mV = 3.3V + m_eddystoneData.temp = (uint16_t)((float)23.00); + m_eddystoneData.advCount = 0; + m_eddystoneData.tmil = 0; +} // BLEEddystoneTLM + +std::string BLEEddystoneTLM::getData() { + return std::string((char*)&m_eddystoneData, sizeof(m_eddystoneData)); +} // getData + +BLEUUID BLEEddystoneTLM::getUUID() { + return BLEUUID(beconUUID); +} // getUUID + +uint8_t BLEEddystoneTLM::getVersion() { + return m_eddystoneData.version; +} // getVersion + +uint16_t BLEEddystoneTLM::getVolt() { + return m_eddystoneData.volt; +} // getVolt + +float BLEEddystoneTLM::getTemp() { + return (float)m_eddystoneData.temp; +} // getTemp + +uint32_t BLEEddystoneTLM::getCount() { + return m_eddystoneData.advCount; +} // getCount + +uint32_t BLEEddystoneTLM::getTime() { + return m_eddystoneData.tmil; +} // getTime + +std::string BLEEddystoneTLM::toString() { + std::string out = ""; + String buff; + uint32_t rawsec; + + out += "Version "; + buff = String(m_eddystoneData.version, DEC); + out += buff.c_str(); + out += "\n"; + + out += "Battery Voltage "; + buff = String(ENDIAN_CHANGE_U16(m_eddystoneData.volt), DEC); + out += buff.c_str(); + out += " mV\n"; + + out += "Temperature "; + buff = String((float)m_eddystoneData.temp, 1); + out += buff.c_str(); + out += " °C\n"; + + out += "Adv. Count "; + buff = String(ENDIAN_CHANGE_U32(m_eddystoneData.advCount), DEC); + out += buff.c_str(); + out += "\n"; + + out += "Time "; + rawsec = ENDIAN_CHANGE_U32(m_eddystoneData.tmil); + buff = "0000"+String(rawsec/864000, DEC); + out += buff.substring(buff.length()-4,buff.length()).c_str(); + out += "."; + buff = "00"+String((rawsec/36000)%24, DEC); + out += buff.substring(buff.length()-2,buff.length()).c_str(); + out += ":"; + buff = "00"+String((rawsec/600)%60, DEC); + out += buff.substring(buff.length()-2,buff.length()).c_str(); + out += ":"; + buff = "00"+String((rawsec/10)%60, DEC); + out += buff.substring(buff.length()-2,buff.length()).c_str(); + out += "\n"; + + return out; +} // toString + +/** + * Set the raw data for the beacon record. + */ +void BLEEddystoneTLM::setData(std::string data) { + if (data.length() != sizeof(m_eddystoneData)) { + ESP_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_eddystoneData)); + return; + } + memcpy(&m_eddystoneData, data.data(), data.length()); +} // setData + +void BLEEddystoneTLM::setUUID(BLEUUID l_uuid) { + beconUUID = l_uuid.getNative()->uuid.uuid16; +} // setUUID + +void BLEEddystoneTLM::setVersion(uint8_t version) { + m_eddystoneData.version = version; +} // setVersion + +void BLEEddystoneTLM::setVolt(uint16_t volt) { + m_eddystoneData.volt = volt; +} // setVolt + +void BLEEddystoneTLM::setTemp(float temp) { + m_eddystoneData.temp = (uint16_t)temp; +} // setTemp + +void BLEEddystoneTLM::setCount(uint32_t advCount) { + m_eddystoneData.advCount = advCount; +} // setCount + +void BLEEddystoneTLM::setTime(uint32_t tmil) { + m_eddystoneData.tmil = tmil; +} // setTime + +#endif diff --git a/cpp_utils/BLEEddystoneTLM.h b/cpp_utils/BLEEddystoneTLM.h new file mode 100644 index 00000000..76bd6a43 --- /dev/null +++ b/cpp_utils/BLEEddystoneTLM.h @@ -0,0 +1,50 @@ +/* + * BLEEddystoneTLM.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ + +#ifndef _BLEEddystoneTLM_H_ +#define _BLEEddystoneTLM_H_ +#include "BLEUUID.h" + +#define EDDYSTONE_TLM_FRAME_TYPE 0x20 + +/** + * @brief Representation of a beacon. + * See: + * * https://github.com/google/eddystone + */ +class BLEEddystoneTLM { +private: + uint16_t beconUUID; + struct { + uint8_t frameType; + int8_t version; + uint16_t volt; + uint16_t temp; + uint32_t advCount; + uint32_t tmil; + } __attribute__((packed))m_eddystoneData; +public: + BLEEddystoneTLM(); + std::string getData(); + BLEUUID getUUID(); + uint8_t getVersion(); + uint16_t getVolt(); + float getTemp(); + uint32_t getCount(); + uint32_t getTime(); + std::string toString(); + void setData(std::string data); + void setUUID(BLEUUID l_uuid); + void setVersion(uint8_t version); + void setVolt(uint16_t volt); + void setTemp(float temp); + void setCount(uint32_t advCount); + void setTime(uint32_t tmil); + +}; // BLEEddystoneTLM + +#endif /* _BLEEddystoneTLM_H_ */ diff --git a/cpp_utils/BLEEddystoneURL.cpp b/cpp_utils/BLEEddystoneURL.cpp new file mode 100644 index 00000000..6c12b246 --- /dev/null +++ b/cpp_utils/BLEEddystoneURL.cpp @@ -0,0 +1,149 @@ +/* + * BLEEddystoneURL.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEEddystoneURL.h" + +static const char LOG_TAG[] = "BLEEddystoneURL"; + +BLEEddystoneURL::BLEEddystoneURL() { + beconUUID = 0xFEAA; + lengthURL = 0; + m_eddystoneData.frameType = EDDYSTONE_URL_FRAME_TYPE; + m_eddystoneData.advertisedTxPower = 0; + memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); +} // BLEEddystoneURL + +std::string BLEEddystoneURL::getData() { + return std::string((char*)&m_eddystoneData, sizeof(m_eddystoneData)); +} // getData + +BLEUUID BLEEddystoneURL::getUUID() { + return BLEUUID(beconUUID); +} // getUUID + +int8_t BLEEddystoneURL::getPower() { + return m_eddystoneData.advertisedTxPower; +} // getPower + +std::string BLEEddystoneURL::getURL() { + return std::string((char*)&m_eddystoneData.url, sizeof(m_eddystoneData.url)); +} // getURL + +std::string BLEEddystoneURL::getDecodedURL() { + std::string decodedURL = ""; + + switch (m_eddystoneData.url[0]) { + case 0x00: + decodedURL += "http://www."; + break; + case 0x01: + decodedURL += "https://www."; + break; + case 0x02: + decodedURL += "http://"; + break; + case 0x03: + decodedURL += "https://"; + break; + default: + decodedURL += m_eddystoneData.url[0]; + } + + for (int i=1;i33&&m_eddystoneData.url[i]<127) { + decodedURL += m_eddystoneData.url[i]; + } else { + switch (m_eddystoneData.url[i]) { + case 0x00: + decodedURL += ".com/"; + break; + case 0x01: + decodedURL += ".org/"; + break; + case 0x02: + decodedURL += ".edu/"; + break; + case 0x03: + decodedURL += ".net/"; + break; + case 0x04: + decodedURL += ".info/"; + break; + case 0x05: + decodedURL += ".biz/"; + break; + case 0x06: + decodedURL += ".gov/"; + break; + case 0x07: + decodedURL += ".com"; + break; + case 0x08: + decodedURL += ".org"; + break; + case 0x09: + decodedURL += ".edu"; + break; + case 0x0A: + decodedURL += ".net"; + break; + case 0x0B: + decodedURL += ".info"; + break; + case 0x0C: + decodedURL += ".biz"; + break; + case 0x0D: + decodedURL += ".gov"; + break; + } + } + } + + + return decodedURL; +} // getDecodedURL + + + +/** + * Set the raw data for the beacon record. + */ +void BLEEddystoneURL::setData(std::string data) { + if (data.length() > sizeof(m_eddystoneData)) { + ESP_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and max expected %d", data.length(), sizeof(m_eddystoneData)); + return; + } + memset(&m_eddystoneData, 0, sizeof(m_eddystoneData)); + memcpy(&m_eddystoneData, data.data(), data.length()); + lengthURL=data.length()-(sizeof(m_eddystoneData)-sizeof(m_eddystoneData.url)); + +} // setData + +void BLEEddystoneURL::setUUID(BLEUUID l_uuid) { + beconUUID = l_uuid.getNative()->uuid.uuid16; +} // setUUID + +void BLEEddystoneURL::setPower(int8_t advertisedTxPower) { + m_eddystoneData.advertisedTxPower = advertisedTxPower; +} // setPower + +void BLEEddystoneURL::setURL(std::string url) { + if (url.length() > sizeof(m_eddystoneData.url)) { + ESP_LOGE(LOG_TAG, "Unable to set the url ... length passed in was %d and max expected %d", url.length(), sizeof(m_eddystoneData.url)); + return; + } + memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); + memcpy(m_eddystoneData.url, url.data(), url.length()); + lengthURL=url.length(); +} // setURL + + +#endif diff --git a/cpp_utils/BLEEddystoneURL.h b/cpp_utils/BLEEddystoneURL.h new file mode 100644 index 00000000..2025cb19 --- /dev/null +++ b/cpp_utils/BLEEddystoneURL.h @@ -0,0 +1,42 @@ +/* + * BLEEddystoneURL.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ + +#ifndef _BLEEddystoneURL_H_ +#define _BLEEddystoneURL_H_ +#include "BLEUUID.h" + +#define EDDYSTONE_URL_FRAME_TYPE 0x10 + +/** + * @brief Representation of a beacon. + * See: + * * https://github.com/google/eddystone + */ +class BLEEddystoneURL { +private: + uint16_t beconUUID; + uint8_t lengthURL; + struct { + uint8_t frameType; + int8_t advertisedTxPower; + uint8_t url[16]; + } __attribute__((packed))m_eddystoneData; +public: + BLEEddystoneURL(); + std::string getData(); + BLEUUID getUUID(); + int8_t getPower(); + std::string getURL(); + std::string getDecodedURL(); + void setData(std::string data); + void setUUID(BLEUUID l_uuid); + void setPower(int8_t advertisedTxPower); + void setURL(std::string url); + +}; // BLEEddystoneURL + +#endif /* _BLEEddystoneURL_H_ */ From 247d66da92827c9bd6a6f586dd70f8cef6eec5e0 Mon Sep 17 00:00:00 2001 From: Thomas van de Wege Date: Sat, 17 Mar 2018 14:45:38 +0100 Subject: [PATCH 252/381] Replaced error message for double characteristics Made the fix that @chegewara proposed. I was confused by the error and found this github issue in the repository: https://github.com/nkolban/esp32-snippets/issues/175 --- cpp_utils/BLEService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 32bcc570..64f60a9a 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -192,7 +192,7 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { // Check that we don't add the same characteristic twice. if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { - ESP_LOGE(LOG_TAG, "<< Attempt to add a characteristic but we already have one with this UUID"); + ESP_LOGW(LOG_TAG, "<< Adding a new characteristic with the same UUID as a previous one"); //return; } From 8d3b0f93dded47e2f8b9f8e02e5c9376bf28082a Mon Sep 17 00:00:00 2001 From: mws-rmain <30533684+mws-rmain@users.noreply.github.com> Date: Mon, 19 Mar 2018 13:59:26 -0400 Subject: [PATCH 253/381] Update BLEDevice.cpp Fixes to allow BLEDevice to compile with security disabled (CONFIG_BLE_SMP_ENABLE undefined) --- cpp_utils/BLEDevice.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index b333ed7c..f686f1a5 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -99,9 +99,11 @@ uint16_t BLEDevice::m_localMTU = 23; switch(event) { case ESP_GATTS_CONNECT_EVT: { BLEDevice::m_localMTU = 23; +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig if(BLEDevice::m_securityLevel){ esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); } +#endif // CONFIG_BLE_SMP_ENABLE break; } // ESP_GATTS_CONNECT_EVT @@ -148,9 +150,11 @@ uint16_t BLEDevice::m_localMTU = 23; ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } } +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig if(BLEDevice::m_securityLevel){ esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); } +#endif // CONFIG_BLE_SMP_ENABLE break; } // ESP_GATTC_CONNECT_EVT @@ -190,16 +194,20 @@ uint16_t BLEDevice::m_localMTU = 23; break; case ESP_GAP_BLE_NC_REQ_EVT: ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig if(BLEDevice::m_securityCallbacks!=nullptr){ esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); } +#endif // CONFIG_BLE_SMP_ENABLE break; case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig if(BLEDevice::m_securityCallbacks!=nullptr){ esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); } +#endif // CONFIG_BLE_SMP_ENABLE break; /* * TODO should we add white/black list comparison? @@ -208,12 +216,14 @@ uint16_t BLEDevice::m_localMTU = 23; /* send the positive(true) security response to the peer device to accept the security request. If not accept the security request, should sent the security response with negative(false) accept value*/ ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig if(BLEDevice::m_securityCallbacks!=nullptr){ esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); } else{ esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); } +#endif // CONFIG_BLE_SMP_ENABLE break; /* * @@ -221,19 +231,27 @@ uint16_t BLEDevice::m_localMTU = 23; case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. ///show the passkey number to the user to input it in the peer deivce. ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig if(BLEDevice::m_securityCallbacks!=nullptr){ ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); } +#endif // CONFIG_BLE_SMP_ENABLE break; case ESP_GAP_BLE_KEY_EVT: //shows the ble key type info share with peer device to the user. + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); +#endif // CONFIG_BLE_SMP_ENABLE break; case ESP_GAP_BLE_AUTH_CMPL_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig if(BLEDevice::m_securityCallbacks!=nullptr){ BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); } +#endif // CONFIG_BLE_SMP_ENABLE break; default: { break; @@ -382,12 +400,14 @@ uint16_t BLEDevice::m_localMTU = 23; return; }; +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; }; +#endif // CONFIG_BLE_SMP_ENABLE } vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. } // init From 6a9c83f7a9b640eb83e6fc85ae8ba3d9c86b556e Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 22 Mar 2018 07:16:44 +0100 Subject: [PATCH 254/381] Fix gatts_add_char_descr_evt_param stuct refactored --- cpp_utils/BLEDescriptor.cpp | 2 +- cpp_utils/BLEUtils.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 1a72ef30..58ff78b4 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -155,7 +155,7 @@ void BLEDescriptor::handleGATTServerEvent( (uint32_t)m_pCharacteristic->getService()->getLastCreatedCharacteristic()); */ if (m_pCharacteristic != nullptr && - m_bleUUID.equals(BLEUUID(param->add_char_descr.char_uuid)) && + m_bleUUID.equals(BLEUUID(param->add_char_descr.descr_uuid)) && m_pCharacteristic->getService()->getHandle() == param->add_char_descr.service_handle && m_pCharacteristic == m_pCharacteristic->getService()->getLastCreatedCharacteristic()) { setHandle(param->add_char_descr.attr_handle); diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index ff4ebfaf..a33ee27a 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -1654,7 +1654,7 @@ void BLEUtils::dumpGattServerEvent( evtParam->add_char_descr.attr_handle, evtParam->add_char_descr.service_handle, evtParam->add_char_descr.service_handle, - BLEUUID(evtParam->add_char_descr.char_uuid).toString().c_str()); + BLEUUID(evtParam->add_char_descr.descr_uuid).toString().c_str()); break; } // ESP_GATTS_ADD_CHAR_DESCR_EVT From 7500a986afab3e662a51f8d567aa5d62a8d35bdf Mon Sep 17 00:00:00 2001 From: Thomas van de Wege Date: Thu, 22 Mar 2018 19:23:58 +0100 Subject: [PATCH 255/381] Adding getter for the initialized variable --- cpp_utils/BLEDevice.h | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 7f331435..7a1b833d 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -42,6 +42,7 @@ class BLEDevice { static void setSecurityCallbacks(BLESecurityCallbacks* pCallbacks); static esp_err_t setMTU(uint16_t mtu); static uint16_t getMTU(); + static bool getInitialized(); // Returns the state of the device, is it initialized or not? private: static BLEServer *m_pServer; From a8f98eaaac1867f4ddd0f6d8c83c0b304572ccda Mon Sep 17 00:00:00 2001 From: Thomas van de Wege Date: Thu, 22 Mar 2018 19:26:38 +0100 Subject: [PATCH 256/381] Added getter for the initialized variable --- cpp_utils/BLEDevice.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index f686f1a5..1c6e6bb8 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -527,4 +527,8 @@ esp_err_t BLEDevice::setMTU(uint16_t mtu) { uint16_t BLEDevice::getMTU() { return m_localMTU; } + +bool BLEDevice::getInitialized() { + return initialized; +} #endif // CONFIG_BT_ENABLED From 32729aeac9c8eafcf022826a63908543cbc2fe5c Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 28 Mar 2018 00:49:20 +0200 Subject: [PATCH 257/381] Fix Compiler option->Optimization level->Debug error --- cpp_utils/BLEService.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 64f60a9a..4e59c4a4 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -72,6 +72,7 @@ void BLEService::executeCreate(BLEServer *pServer) { m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT esp_gatt_srvc_id_t srvc_id; + srvc_id.is_primary = true; srvc_id.id.inst_id = 0; srvc_id.id.uuid = *m_uuid.getNative(); esp_err_t errRc = ::esp_ble_gatts_create_service( From 17cd0532f74ff46dc5272e1d09016edad34713bf Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 29 Mar 2018 18:46:10 -0500 Subject: [PATCH 258/381] Moved BLEEddystonefiles to onhold as per #447 --- .cproject | 100 ++++++++++++++++++++- cpp_utils/I2C.cpp | 2 +- cpp_utils/{ => onhold}/BLEEddystoneTLM.cpp | 0 cpp_utils/{ => onhold}/BLEEddystoneTLM.h | 0 cpp_utils/{ => onhold}/BLEEddystoneURL.cpp | 0 cpp_utils/{ => onhold}/BLEEddystoneURL.h | 0 tools/bootloaderExamine/main.cpp | 4 +- 7 files changed, 99 insertions(+), 7 deletions(-) rename cpp_utils/{ => onhold}/BLEEddystoneTLM.cpp (100%) rename cpp_utils/{ => onhold}/BLEEddystoneTLM.h (100%) rename cpp_utils/{ => onhold}/BLEEddystoneURL.cpp (100%) rename cpp_utils/{ => onhold}/BLEEddystoneURL.h (100%) diff --git a/.cproject b/.cproject index 379a8d68..54db1eb5 100644 --- a/.cproject +++ b/.cproject @@ -26,15 +26,46 @@ + + + diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index 7c8f5056..96618bc2 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -69,7 +69,7 @@ void I2C::endTransaction() { errRc = ::i2c_master_cmd_begin(m_portNum, m_cmd, 1000/portTICK_PERIOD_MS); if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "i2c_master_stop: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + ESP_LOGE(LOG_TAG, "i2c_master_cmd_begin: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } ::i2c_cmd_link_delete(m_cmd); m_directionKnown = false; diff --git a/cpp_utils/BLEEddystoneTLM.cpp b/cpp_utils/onhold/BLEEddystoneTLM.cpp similarity index 100% rename from cpp_utils/BLEEddystoneTLM.cpp rename to cpp_utils/onhold/BLEEddystoneTLM.cpp diff --git a/cpp_utils/BLEEddystoneTLM.h b/cpp_utils/onhold/BLEEddystoneTLM.h similarity index 100% rename from cpp_utils/BLEEddystoneTLM.h rename to cpp_utils/onhold/BLEEddystoneTLM.h diff --git a/cpp_utils/BLEEddystoneURL.cpp b/cpp_utils/onhold/BLEEddystoneURL.cpp similarity index 100% rename from cpp_utils/BLEEddystoneURL.cpp rename to cpp_utils/onhold/BLEEddystoneURL.cpp diff --git a/cpp_utils/BLEEddystoneURL.h b/cpp_utils/onhold/BLEEddystoneURL.h similarity index 100% rename from cpp_utils/BLEEddystoneURL.h rename to cpp_utils/onhold/BLEEddystoneURL.h diff --git a/tools/bootloaderExamine/main.cpp b/tools/bootloaderExamine/main.cpp index aaf0f458..22e8a5aa 100644 --- a/tools/bootloaderExamine/main.cpp +++ b/tools/bootloaderExamine/main.cpp @@ -121,8 +121,8 @@ int main(int argc, char *argv[]) { } printf("Dump of ESP32 binary file: %s\n", fileName); - printf("magic: 0x%x, segment_count: %d, entry_addr: 0x%x - %s\n", - header.magic, header.segment_count, header.entry_addr, area(header.entry_addr)); + printf("magic: 0x%x, segment_count: %d, entry_addr: 0x%x - %s, hash_appended: %d\n", + header.magic, header.segment_count, header.entry_addr, area(header.entry_addr), header.hash_appended); printf("\n"); printf("Seg | Start | End | Length | Area\n"); From ab08a657d380460e6389e592dde963780a323080 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 29 Mar 2018 23:41:23 -0500 Subject: [PATCH 259/381] Check for member not present #454 --- cpp_utils/JSON.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index 2c1de785..c55f3a78 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -229,6 +229,9 @@ JsonArray JsonObject::getArray(std::string name) { */ bool JsonObject::getBoolean(std::string name) { cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) { + return false; + } return cJSON_IsTrue(node); } // getBoolean @@ -240,6 +243,9 @@ bool JsonObject::getBoolean(std::string name) { */ double JsonObject::getDouble(std::string name) { cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) { + return 0.0; + } return node->valuedouble; } // getDouble @@ -251,6 +257,9 @@ double JsonObject::getDouble(std::string name) { */ int JsonObject::getInt(std::string name) { cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) { + return 0; + } return node->valueint; } // getInt @@ -269,10 +278,13 @@ JsonObject JsonObject::getObject(std::string name) { /** * @brief Get the named string value from the object. * @param [in] name The name of the object property. - * @return The string value from the object. + * @return The string value from the object. A zero length string is returned when the object is not present. */ std::string JsonObject::getString(std::string name) { cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) { + return ""; + } return std::string(node->valuestring); } // getString From 054a87b1731a4404c06b9839fbf31a104493d25c Mon Sep 17 00:00:00 2001 From: Testato Date: Mon, 2 Apr 2018 08:31:18 +0200 Subject: [PATCH 260/381] Update espToError.c --- error handling/fragments/espToError.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/error handling/fragments/espToError.c b/error handling/fragments/espToError.c index 66e90eb9..72127583 100644 --- a/error handling/fragments/espToError.c +++ b/error handling/fragments/espToError.c @@ -13,7 +13,7 @@ char *espToString(esp_err_t value) { case ESP_ERR_INVALID_ARG: return "Invalid argument"; case ESP_ERR_INVALID_SIZE: - return "Invalid state"; + return "Invalid size"; case ESP_ERR_INVALID_STATE: return "Invalid state"; case ESP_ERR_NOT_FOUND: From 9c796bbe0038d65e6ae2b19a8bc2791497a04d8c Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Tue, 3 Apr 2018 10:31:38 -0500 Subject: [PATCH 261/381] Changes for esp_ble_gattc_open in ESP-IDF --- cpp_utils/BLEClient.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 55af054c..57ff4d21 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -110,6 +110,7 @@ bool BLEClient::connect(BLEAddress address) { errRc = ::esp_ble_gattc_open( getGattcIf(), *getPeerAddress().getNative(), // address + BLE_ADDR_TYPE_PUBLIC, // Note: This was added on 2018-04-03 when the latest ESP-IDF was detected to have changed the signature. 1 // direct connection ); if (errRc != ESP_OK) { From f42b82430de669ffbc6f6bf930339cecda60985b Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Fri, 6 Apr 2018 15:09:36 -0500 Subject: [PATCH 262/381] Made addData and getPayload public --- cpp_utils/BLEAdvertising.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index d1fa3c73..e3165298 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -29,13 +29,12 @@ class BLEAdvertisementData { void setPartialServices(BLEUUID uuid); void setServiceData(BLEUUID uuid, std::string data); void setShortName(std::string name); + void addData(std::string data); // Add data to the payload. + std::string getPayload(); // Retrieve the current advert payload. private: friend class BLEAdvertising; std::string m_payload; // The payload of the advertisement. - - void addData(std::string data); // Add data to the payload. - std::string getPayload(); // Retrieve the current advert payload. }; // BLEAdvertisementData From 7aaed6d3355bd0b251bc9386b742ee0699ee5a71 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Fri, 6 Apr 2018 16:09:54 -0500 Subject: [PATCH 263/381] Fixes on BLE_Notify --- cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino index 57ad7a7d..5e915bea 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino @@ -23,7 +23,8 @@ #include #include -BLEServer *pServer = NULL; +BLEServer* pServer = NULL; +BLECharacteristic* pCharacteristic = NULL; bool deviceConnected = false; bool oldDeviceConnected = false; uint8_t value = 0; @@ -61,7 +62,7 @@ void setup() { BLEService *pService = pServer->createService(SERVICE_UUID); // Create a BLE Characteristic - BLECharacteristic * pCharacteristic = pService->createCharacteristic( + pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | From bbec0b9793dce4bb8ca89c529aa7d86428af58b6 Mon Sep 17 00:00:00 2001 From: jrickard Date: Sat, 7 Apr 2018 14:10:04 -0500 Subject: [PATCH 264/381] BLECharacterstic.setValue() overrides to allow additional data types Revised BLECharacterstic.setValue() to allow additional data types when setting characterstic values. Added for uint16_t, uint32_t, int, float, and double. These data types appear to carry through to iPhone examination of ESP32 BLE transmission. --- cpp_utils/BLECharacteristic.cpp | 37 +++++++++++++++++++++++++++++++++ cpp_utils/BLECharacteristic.h | 5 +++++ 2 files changed, 42 insertions(+) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 5e5aa2ac..931c753d 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -683,6 +683,43 @@ void BLECharacteristic::setValue(std::string value) { setValue((uint8_t*)(value.data()), value.length()); } // setValue +void BLECharacteristic::setValue(uint16_t& data16) { + uint8_t temp[2]; + temp[0]=data16; + temp[1]=data16>>8; + setValue(temp, 2); +} // setValue + +void BLECharacteristic::setValue(uint32_t& data32) { + uint8_t temp[4]; + temp[0]=data32; + temp[1]=data32>>8; + temp[2]=data32>>16; + temp[3]=data32>>24; + setValue(temp, 4); +} // setValue + +void BLECharacteristic::setValue(int& data32) { + uint8_t temp[4]; + temp[0]=data32; + temp[1]=data32>>8; + temp[2]=data32>>16; + temp[3]=data32>>24; + setValue(temp, 4); +} // setValue + +void BLECharacteristic::setValue(float& data32) { + uint8_t temp[4]; + *((float *)temp) = data32; + setValue(temp, 4); +} // setValue + +void BLECharacteristic::setValue(double& data64) { + uint8_t temp[8]; + *((double *)temp) = data64; + setValue(temp, 8); +} // setValue + /** * @brief Set the Write No Response property value. diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 10dc787f..b3f8d2e9 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -75,6 +75,11 @@ class BLECharacteristic { void setReadProperty(bool value); void setValue(uint8_t* data, size_t size); void setValue(std::string value); + void setValue(uint16_t& data16); + void setValue(uint32_t& data32); + void setValue(int& data32); + void setValue(float& data32); + void setValue(double& data64); void setWriteProperty(bool value); void setWriteNoResponseProperty(bool value); std::string toString(); From 6fa380ba2d9924943c796d9a3780d51cd917943c Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 9 Apr 2018 22:23:15 -0500 Subject: [PATCH 265/381] Addition of esptool libs --- tools/esptool_libs/README.md | 4 + tools/esptool_libs/nodejs/espjs.js | 1029 ++++++++++++++++++++++++++++ 2 files changed, 1033 insertions(+) create mode 100644 tools/esptool_libs/README.md create mode 100644 tools/esptool_libs/nodejs/espjs.js diff --git a/tools/esptool_libs/README.md b/tools/esptool_libs/README.md new file mode 100644 index 00000000..b337ac56 --- /dev/null +++ b/tools/esptool_libs/README.md @@ -0,0 +1,4 @@ +# Esptool libraries +The Espressif awesome esptool performs just about anything you may want to do relating to ESP32 programming over UART. It can write to flash, write to memory, erase flash and a world of other features. The specification of the protocol can be found in the `esptool` github project Wiki. + +This section of this repository hosts libraries for working with this protocol. There is an implementation in Node.js and more to come including C and C++. \ No newline at end of file diff --git a/tools/esptool_libs/nodejs/espjs.js b/tools/esptool_libs/nodejs/espjs.js new file mode 100644 index 00000000..5285e265 --- /dev/null +++ b/tools/esptool_libs/nodejs/espjs.js @@ -0,0 +1,1029 @@ +/** + * Requires: + * * hexy + * * serialport - https://www.npmjs.com/package/serialport + * + * To debug, run: + * node --inspect-brk espjs.js + * + * In chrome run, + * + * chrome://inspect + * + */ + + /** + * We want to provide processing to both transmitted requests and received responses. A request is a message that is then encapsulated + * in a SLIP packet and transmitted via UART. A response is a message received via UART that is wrapped in a SLIP packet. + * + * We will logically define a DataPacket as the raw packet unit that is either transmitted or received. This encapsulates a + * Buffer that contains the data. + */ +const hexy = require("hexy"); +const zlib = require('zlib'); +const fs = require("fs"); +const assert = require("assert"); +const PORT = "/dev/ttyUSB1"; +const argv = require("minimist")(process.argv.slice(2)); + +const COMMAND_FLASH_BEGIN = 0x02; +const COMMAND_FLASH_DATA = 0x03; +const COMMAND_FLASH_END = 0x04; +const COMMAND_MEM_BEGIN = 0x05; +const COMMAND_MEM_END = 0x06; +const COMMAND_MEM_DATA = 0x07; +const COMMAND_SYNC = 0x08; +const COMMAND_WRITE_REG = 0x09; +const COMMAND_READ_REG = 0x0a; +const COMMAND_SPI_SET_PARAMS = 0x0b; +const COMMAND_SPI_FLASH_MD5 = 0x13; + +const EFUSE_REG_BASE = 0x6001a000; + +var esp32r0Delay = false; +var response = null; +var serialData = SerialData(); // The buffer holding the received serial data. + +var SerialPort = require("serialport"); +var port = new SerialPort(PORT, { + baudRate: 115200, + autoOpen: false +}); + +/** + * Maintain received serial data. + * We are receiving serial data asynchronosly from the serial processor. This means that when data arrives + * on the UART, it is passed to an instance of this object for accumulation. The data is passed in through a + * call to append. + */ +function SerialData() { + var serialDataBuffer; // The serial data we are accumulating. + + function empty() { + serialDataBuffer = Buffer.allocUnsafe(0); + } + + var resolveMoreData = null; + /** + * Append data received from the serial port into our buffer. + * @param {Buffer} data The data received from our serial port. + * @returns Nothing. + */ + function append(data) { + console.log("SerialData.append:\n%s", hexy.hexy(data)); + serialDataBuffer = Buffer.concat([serialDataBuffer, data]); // Append the new data into the serial buffer. + if (resolveMoreData != null) { // If we have a promise resolution that is waiting for more data .. + resolveMoreData(); // resolve the promise. + resolveModeData = null; + } + } // append + + /** + * Create a promise that is resolved when we have more data. + * @returns A promise that is resolved when we have more data. + */ + async function moreData() { + return new Promise((resolveA, rejectA) => { + resolveMoreData = resolveA; // The promise is resolved when append is called to provide more data. + }); + } // moreData + + + /** + * Assume that the serial data received is a sequence of packets. This function returns a promise that + * is resolved when a packet is available for processing. + */ + async function getNextDataPacket() { + var p = new Promise(async (resolve, reject) => { + console.log("getNextDataPacket>>"); + while(true) { // Keep looping until we have a data packet. + if (DataPacket().containsSLIPPacket(serialDataBuffer)) { // Does the buffer now contain a SLIP packet? + var dp = DataPacket(); // Create a new data packet + serialDataBuffer = dp.setSLIPPacket(serialDataBuffer); // Load the data packet with the raw serial data. + console.log("Got a new data packet: %s\n%s", dp.toString(), hexy.hexy(dp.getData())); + resolve(dp); + return; + } + await moreData(); // We didn't find a data packet in our data, so wait for more data. + } // We will loop forever waiting for packets. + }); + return p; // Return the promise that is resolved when we have a data packet. + } // getNextDataPacket + + empty(); // Initialize / empty the serial data. + return { + "append": append, + "empty": empty, + "getNextDataPacket": getNextDataPacket + } +} // SerialData + + +/** + * A data packet encapsulates a command or response. + */ +function DataPacket() { + var dataPacket = Buffer.allocUnsafe(0); + /** + * Examine the data buffer looking for a slip trailer (0xc0). If found, we return the index + * at which it was found. If not found, we return -1. + * @param {Buffer} data The data in which we are looking for the slip trailer. + * @returns The original of the slip trailer or -1 if not found. + */ + function getSLIPTrailer(data) { + for (i=1; i 0; + } // containsSLIPPacket + + + /** + * Process the data passed in that is SLIP encoded and build a parsed data packet. + * @param {Buffer} data The data to build a packet from. This will be raw data received from UART. + * @returns The unprocessed data. + */ + function setSLIPPacket(data) { + console.log("setSLIPPacket:\n%s", hexy.hexy(data)); + assert(data); + assert(data[0] == 0xc0); + assert(data.length > 0); + var end = getSLIPTrailer(data); + if (end == -1) { + throw "No SLIP trailer in data we are to process."; + } + assert(end > 0); + dataPacket = Buffer.allocUnsafe(data.length); // Our data will be no MORE than data.length bytes in size. + for (var i=1, j=0; i 0xc0 + dataPacket[j] = 0xc0; + i++ + } else if (data[i] == 0xdb && data[i+1] == 0xdd) { // 0xdb 0xdd -> 0xdb + dataPacket[j] = 0xdb; + i++; + } else { + dataPacket[j] = data[i] + } + } // End loop over data. + + // We now have a buffer (dataPacket) that contains our decoded data but will be too large. However, j contains the number of bytes we want. + var temp = Buffer.allocUnsafe(j); + dataPacket.copy(temp, 0, 0, j); // Copy dataPacket[0..j-1] -> temp + dataPacket = temp; + + // We have now processed what we wanted from the incoming data and now we must return the unused data. + i++; + var unprocessedLength = data.length - i; + temp = Buffer.allocUnsafe(unprocessedLength); + data.copy(temp, 0, i); // copy data[i..] -> temp + + console.log("setSLIPPacket: New data has length: %d, returning unused buffer of size: %d", dataPacket.length, temp.length); + console.log("Unused:\n%s", hexy.hexy(temp)); + return temp; + } // setSLIPPacket + + + /** + * Internally we have a dataPacket. We want to return a SLIP encoded buffer from that packet. + * @returns A SLIP encoded representation of our data Packet. + */ + function getSLIPPacket() { + var temp = Buffer.allocUnsafe(dataPacket.length * 2); + var i = 0; // The variable called "i" will be the current byte being written into the slip encoded output. + temp[i] = 0xc0; + i++; + for (j=0; j temp2 + temp2[i] = 0xc0; // Terminate with a slip trailder. + return temp2; + } // getSLIPPacket + + /** + * Set the data. + * @param {*} data + */ + function setData(data) { + dataPacket = data; + } + + function getData() { + return dataPacket; + } + + function toString() { + var result; + if (dataPacket[0] == 0) { + result = "Request: "; + result += "command: " + commandToString(dataPacket[1]); + result += ", size: " + dataPacket.readUInt16LE(2); + result += ", calculatedSize: " + (dataPacket.length - 8); + } else if (dataPacket[0] == 1) { + result = "Response: "; + result += "command: " + commandToString(dataPacket[1]); + result += ", size: " + dataPacket.readUInt16LE(2); + result += ", calculatedSize: " + (dataPacket.length - 8); + result += ", value: " + dataPacket.readUInt32LE(4).toString(16); + // For the ESP32 + // Size - 4 = status + // Size - 3 = error + result += ", status: " + dataPacket[dataPacket.length-4]; + result += ", errorCode: " + dataPacket[dataPacket.length-3]; + } else { + result = "Unknown command: " + dataPacket[0]; + } + return result; + } // toString + + return { + "containsSLIPPacket": containsSLIPPacket, // Does the data passed in define contain a SLIP packet? + "getSLIPPacket": getSLIPPacket, + "setSLIPPacket": setSLIPPacket, + "getData": getData, + "setData": setData, + "toString": toString + } // End of return +} // DataPacket + + +/** + * Convert a command code to a string representation. + * @param {String} command The command to be converted. + * @returns A string representation of the command. + */ +function commandToString(command) { + switch(command) { + case COMMAND_FLASH_BEGIN: + return "COMMAND_FLASH_BEGIN"; + case COMMAND_FLASH_DATA: + return "COMMAND_FLASH_DATA"; + case COMMAND_FLASH_END: + return "COMMAND_FLASH_END"; + case COMMAND_MEM_BEGIN: + return "COMMAND_MEM_BEGIN"; + case COMMAND_MEM_END: + return "COMMAND_MEM_END"; + case COMMAND_MEM_DATA: + return "COMMAND_MEM_DATA"; + case COMMAND_SYNC: + return "COMMAND_SYNC"; + case COMMAND_WRITE_REG: + return "COMMAND_WRITE_REG"; + case COMMAND_READ_REG: + return "COMMAND_READ_REG"; + case COMMAND_SPI_SET_PARAMS: + return "COMMAND_SPI_SET_PARAMS"; + case COMMAND_SPI_FLASH_MD5: + return "COMMAND_SPI_FLASH_MD5"; + } + return "Unknown command: " + command; +} // commandToString + + +function setFlags(options) { + return new Promise((resolve, reject) => { + port.set(options, () => { + resolve(); + }); + }); +} // setFlags + + +function delay(msecs) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, msecs); + }); +} // delay + + +/** + * Write the command header + * @param command // The ESP32 flash loader command + * @param data // The data to send. + * @param checksum // [Optional] The checksum value + * @returns + */ +function buildCommand(command, data, checksum = 0) { + console.log("buildCommand: %s, checksum: 0x%s, dataLength: %d", commandToString(command), checksum.toString(16), data.length); + var buf = Buffer.allocUnsafe(8 + data.length); // Create a buffer used to hold the command. + buf.writeUInt8(0, 0); // Flag as a request [0] + buf.writeUInt8(command, 1); // Write the command [1] + buf.writeUInt16LE(data.length, 2); // Write the size [2-3] + buf.writeUInt32LE(checksum, 4); // Write the checksum [4-7] + data.copy(buf, 8); // Copy data[0..] to buf[8..] + var dataPacket = DataPacket(); // Create a new data packet object + dataPacket.setData(buf); // Populate the data packet + return dataPacket; +} // writeCommand + + +/** + * Promise to flush all pending transmitting data before resolution. + * @returns A promise that is resolved when the transmission data has all been sent. + */ +async function drain() { + return new Promise((resolve, reject) => { + port.drain(() => {resolve()}); + }); +} // drain + + +/** + * Promise to discard all un-transmitted output data and discard all unread input data. + * @returns A promise that is resolved when input/output data is discarded. + */ +function flush() { + return; + console.log("Flushing..."); + return new Promise((resolve, reject) => { + port.flush(()=>{resolve()}); + }); +} // flush + +// We send a UART command request. The thinking is that we will now start to receive a UART command response. +// The response won't arrive as a unit but will instead be dribbled in over time asynchronously. As such, we need to maintain +// state to process it. A response is composed of a response header and response payload. The response header (as seen by +// UART) will contain: +// 0xC0 - SLIP header +// 0x01 - Direction. Should be 0x01 for a response. +// 0x?? - Command +// 0x?? 0x?? - Size of payload data. +// 0x?? 0x?? 0x?? 0x?? - Value. Used by some commands (NOT ALL)!! +// 0x?? ... - Data as indicated by the size command. +// 0x?? 0x?? 0x?? 0x?? - Outcome +// 0xC0 - SLIP trailer +function ProcessResponse() { + var command; + var data; + var dataSize; + var value; + var status; + var errorCode; + var resolve; + var reject = null; + var promise = null; + + var timeoutId; + + + async function getResponse() { + console.log(">> ProcessResponse.getResponse()") + + promise = new Promise(async (resolveA, rejectA) => { + resolve = resolveA; + reject = rejectA; + timeoutId = setTimeout(()=>{ + console.log("getResponse timeout!"); + reject(); + }, 500); + var dp = await serialData.getNextDataPacket(); + console.log(" .. getResponse ... we think we have a new datapacket!"); + processPacket(dp.getData()); + resolve(); + }); + return promise; + } + + function processPacket(inputData) { + assert(inputData); + assert(inputData.length > 0); + console.log("Received a packet, length %d:\n", inputData.length, hexy.hexy(inputData)); + // 0 - direction + // 1 - command + // [2-3] - size + // [4-7] - value + // [8-(length-3)] - data + // [length-2] - status + // [length-1] - error code + if (inputData.readUInt8(0) != 0x01) { + throw "Expected direction to be 0x01 for a response."; + } + command = inputData.readUInt8(1); + dataSize = inputData.readUInt16LE(2); + value = inputData.readUInt32LE(4); + data = Buffer.allocUnsafe(inputData.length - 8); + inputData.copy(data, 0, 8, inputData.length-2); + status = inputData.readUInt8(inputData.length - 2); + erorCode = inputData.readUInt8(inputData.length-1); + clearTimeout(timeoutId); + } // processPacket + + return { + getResponse: getResponse, + processPacket: processPacket + }; +} // ProcessResponse + + +/** + * Send a SYNC command and wait for the response. + */ +async function doSync() { + console.log("Sending sync"); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(36); // The sync message is 0x07 0x07 0x12 0x20 0x55 (repeated 32 times) + buf.writeUInt8(0x07, 0); + buf.writeUInt8(0x07, 1); + buf.writeUInt8(0x12, 2); + buf.writeUInt8(0x20, 3); + for (var i=4; i<36; i++) { + buf.writeUInt8(0x55, i); + } + var dataPacket = buildCommand(COMMAND_SYNC, buf); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); + console.log("response: " + response); + try { + await response.getResponse(); + resolve(); + } + catch(e) { + console.log("Failed to get SYNC command response: " + e); + reject(); + } + }); + return p; +} // doSync + + +/** + * Calculate the checksum of the data. + * The current algorithm is the seed 0xEF XORed with each of the bytes of data. + * @param {Buffer} data The data against which the checksum is to be calculated. + * @returns The checksum value. + */ +function calculateChecksum(data) { + var checksum = 0xef; + for (var i=0; i> memEnd: executeFlag: %d, entrypointAddress: 0x%s", executeFlag, entrypointAddress.toString(16)); + var p = new Promise(async function (resolve, reject) { + await flush(); // Discard any pending input or output. + var buf = Buffer.allocUnsafe(2*4); // Allocate the payload which is 2 * 32bit words + buf.writeUInt32LE(executeFlag, 0); // Execute flag. + buf.writeUInt32LE(entrypointAddress, 4); // Entry point address. + var dataPacket = buildCommand(COMMAND_MEM_END, buf); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); // Drain the transmit queue. + await response.getResponse(); // Wait for a response. + resolve(); // Resolve the promise. + }); + return p; + } // memEnd + + /** + * Determine if we have sent all the data for the given MEM_BEGIN transaction. + * @returns True if we have sent all the data we need to send. + */ + function allDataSent() { + // We maintain state in a number of variables. The variable called sizeWritten is the number of bytes that we have written to + // the ESP32. The variable called totalSize is the number of bytes that we expect to write. The notion of all data sent + // will occur when the sizeWritten is equal to totalSize. + return sizeWritten >= totalSize; + } // allDataSent + + + /** + * Write a MEM_DATA command. + * This command should follow a MEM_BEGIN command. If transmits a unit of data that is written directly into + * the ESP32 RAM. The address in RAM is supplied by the MEM_BEGIN command and previous MEM_DATA calls. + * @param {Buffer} data + */ + async function memData(data) { + console.log(">> memData: sequenceId: %d", sequenceNumber); + var sizeRemaining = totalSize - sizeWritten; // Calculate the amount of data still expected. + var offsetIntoData = sequenceNumber * MEM_PACKET_SIZE; // Calculate the offset into the data from which to send. + var sizeToWrite = sizeRemaining > MEM_PACKET_SIZE?MEM_PACKET_SIZE:sizeRemaining; // Calculate how large this unit should be. + var buf2 = Buffer.allocUnsafe(4*4 + data.length); // Allocate storage for the command preamble. + buf2.writeUInt32LE(sizeToWrite, 0); // Size of data to be sent. + buf2.writeUInt32LE(sequenceNumber, 4); // Current sequence number. + buf2.writeUInt32LE(0, 8); // Zero. + buf2.writeUInt32LE(0, 12); // Zero. + data.copy(buf2, 16); + //var buf = Buffer.concat([buf2, data]); + //console.log("Here is the data we are sending:\n%s", hexy.hexy(buf)); + + var p = new Promise(async function(resolve, reject) { + console.log("+----------+"); + console.log("| MEM_DATA |"); + console.log("+----------+"); + await flush(); // Discard any untransmitted and unreceived data. // End the SLIP communication. + var dataPacket = buildCommand(COMMAND_MEM_DATA, buf2, calculateChecksum(data)); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); // Drain the transmit queue. + sequenceNumber ++; // Increment the sequence number. + sizeWritten += sizeToWrite; // Updae the size of the data that we have now written. // Ensure that all the transmitted data is now outbound. + await response.getResponse(); // Await a response. + resolve(); // Indicate that the command has been completed. + }); + return p; + }; // memData + + + /** + * Transmit a buffer of data to a given address within ESP32 RAM. + * @param {Buffer} data The data to be written into ESP32 RAM. + * @param {integer} address The address that data will be written to. + */ + async function send(data, address) { + // If the data is not 32bit aligned, expand to 32 bits. + if (data.length % 4 != 0) { + let buf = Buffer.allocUnsafe((data.length + 4) & 0xFFFFFFFC).fill(0xff); + data.copy(buf); + data = buf; + } + console.log(">> send data of size: %d, to RAM address 0x%s", data.length, address.toString(16)); + await memBegin(data.length, address); + while(!allDataSent()) { + await memData(data); + } + }; // send + + + return { + "send": send, + "end": memEnd + }; +} // MemCommands + +function portWrite(data) { + console.log("Writing data down port, length: %d", data.length) + return new Promise((resolve, reject) => { + port.write(data, ()=> {resolve()}); + }); +} + +/** + * Process the handling of flash commands. + */ +function FlashCommands() { + var sequenceNumber; // The current sequence number. + var totalSize; // The total number of bytes we expect to send. + var sizeWritten; // The number of bytes we have written so far. + + const FLASH_PACKET_SIZE = 0x4000; // 16K = 16384 bytes + + + async function flashBegin(size, address) { + console.log("Flash Begin: size: %d, address: 0x%s", size, address.toString(16)); + var p = new Promise(async function (resolve, reject) { + console.log("+-------------+"); + console.log("| FLASH_BEGIN |"); + console.log("+-------------+"); + await flush(); + var buf = Buffer.allocUnsafe(4*4); + buf.writeUInt32LE(size, 0); // Size of data to be sent/erased. + buf.writeUInt32LE(Math.ceil(size/FLASH_PACKET_SIZE), 4); // Number of data packets to be sent. + buf.writeUInt32LE(FLASH_PACKET_SIZE, 8); // Size of each data packet. + buf.writeUInt32LE(address, 12); // Offset in memory to start writing. + var dataPacket = buildCommand(COMMAND_FLASH_BEGIN, buf); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); + await response.getResponse(); + resolve(); + }); + return p; + } // flashBegin + + + async function flashData(data, sequenceNumber) { + console.log(">> flashData: sequenceId: %d, size: %d", sequenceNumber, data.length); + if (data.length < 16*1024) { + var temp = Buffer.alloc(16*1024, 0xff); + data.copy(temp); + data = temp; + } + var buf2 = Buffer.alloc(4*4 + data.length); // Allocate storage for the command preamble. + buf2.writeUInt32LE(data.length, 0); // Size of data to be sent. + buf2.writeUInt32LE(sequenceNumber, 4); // Current sequence number. + buf2.writeUInt32LE(0, 8); + buf2.writeUInt32LE(0, 12); + data.copy(buf2, 16); + //var buf = Buffer.concat([buf2, data]); + //console.log("Here is the data we are sending:\n%s", hexy.hexy(buf)); + + var p = new Promise(async function(resolve, reject) { + console.log("+------------+"); + console.log("| FLASH_DATA |"); + console.log("+------------+"); + await flush(); // Discard any untransmitted and unreceived data. + var dataPacket = buildCommand(COMMAND_FLASH_DATA, buf2, calculateChecksum(data)); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); // Ensure that all the transmitted data is now outbound. + + await response.getResponse(); // Await a response. + resolve(); // Indicate that the command has been completed. + }); + return p; + }; // flashData + + + /** + * Send the FLASH_END command. + * @param {Integer} command + * @returns A promise that is resolved when the FLASH_END command completes. + */ + function flashEnd(command) { + console.log("+-----------+"); + console.log("| FLASH_END |"); + console.log("+-----------+"); + console.log(">> memEnd: command: %d", command); + var p = new Promise(async function (resolve, reject) { + await flush(); // Discard any pending input or output. + var buf = Buffer.allocUnsafe(4); // Allocate the payload which is one 32bit word. + buf.writeUInt32LE(command, 0); // Execute flag. + var dataPacket = buildCommand(COMMAND_FLASH_END, buf); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); // Drain the transmit queue. + await response.getResponse(); // Wait for a response. + resolve(); // Resolve the promise. + }); + return p; + } // flashEnd + + /** + * Transmit a buffer of data to a given address within ESP32 FLASH. + * @param {Buffer} data The data to be written into ESP32 FLASH. + * @param {integer} address The address that data will be written to. + */ + async function send(data, address) { + return new Promise(async (resolve, reject) => { + // If the data is not 32bit aligned, expand to 32 bits. + if (data.length % 4 != 0) { + let buf = Buffer.allocUnsafe((data.length + 4) & 0xFFFFFFFC).fill(0xff); + data.copy(buf); + data = buf; + } + console.log(">> send flash data of size: %d, to flash address 0x%s", data.length, address.toString(16)); + await flashBegin(data.length, address); + + sequenceNumber = 0; // The sequence number of the next FLASH_DATA transmission. + totalSize = data.length; // The size of the data we are going to be sending. + sizeWritten = 0; // The size of the data we have written so far. + + while(sizeWritten < totalSize) { + var sizeRemaining = totalSize - sizeWritten; // Calculate the amount of data still expected. + var offsetIntoData = sequenceNumber * FLASH_PACKET_SIZE; // Calculate the offset into the data from which to send. + var sizeToWrite = sizeRemaining > FLASH_PACKET_SIZE?FLASH_PACKET_SIZE:sizeRemaining; // Calculate how large this unit should be. + var tempBuf = Buffer.allocUnsafe(sizeToWrite); + data.copy(tempBuf, 0, offsetIntoData, offsetIntoData+sizeToWrite); + await flashData(tempBuf, sequenceNumber); + sequenceNumber++; + sizeWritten += sizeToWrite; + } + await flashEnd(1); + resolve(); + }); + }; // send + + return { + "send": send, + "end": flashEnd + }; + +} // FlashCommands + + +/** + * Upload the flasher application. + * @returns A Promise that is fulfilled when the flasher has been uploaded. + */ +async function uploadFlasher() { + // We read a JSON file that has been prepared to contain the flasher application. This JSON contains: + // { + // textAddress: - A string representation (hex) of the memory address into which the text data should be loaded. + // dataAddress: - A string representation (hex) of the memory address into which the data data should be loaded. + // textData: - A base64 encoded representation of the text data. + // dataData: - A base64 encoded representation of the data data + // entryPoint: - A string representation of the entry point (hex) for execution. + // } + // + return new Promise(async (resolve, reject)=>{ + console.log(">> uploadFlasher"); + // Load the flasher from the local file. + var flasherDataRaw = fs.readFileSync("esptool/flasher_stub/build/flasher_stub.json", "utf8"); + var flasherData = JSON.parse(flasherDataRaw); + + flasherData.textData = Buffer.from(flasherData.textData, "base64"); + flasherData.dataData = Buffer.from(flasherData.dataData, "base64"); + console.log("Text data length: %d, Data data length: %d", flasherData.textData.length, flasherData.dataData.length); + + await MemCommands().send(flasherData.textData, parseInt(flasherData.textAddress, 16)); + await MemCommands().send(flasherData.dataData, parseInt(flasherData.dataAddress, 16)); + await MemCommands().end(0, parseInt(flasherData.entryPoint, 16)); + console.log("<< uploadFlasher complete"); + resolve(); + }); // End of promise +} // uploadFlasher + + +/** + * Write a value to an ESP32 register defined. + * @param {Integer} address The address of the register. + * @param {Integer} value The value to be written to the register. + * @returns A promise that is fulfilled when the register has been written. + */ +function writeReg(address, value) { + console.log("+-----------+"); + console.log("| WRITE_REG |"); + console.log("+-----------+"); + console.log(">> Writing register: Command: 0x%s, Address: 0x%s, value: 0x%s", + COMMAND_WRITE_REG.toString(16), address.toString(16), value.toString(16)); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(4*4); + buf.writeUInt32LE(address, 0); + buf.writeUInt32LE(value, 4); + buf.writeUInt32LE(0xffffffff, 8); // Mask + buf.writeUInt32LE(0, 12); // Delay + var dataPacket = buildCommand(COMMAND_WRITE_REG, buf); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); + await response.getResponse(); + resolve(); + }); + return p; +} // writeReg + +/** + * Set the SPI configuration parameters. + * @param {Integer} size The size of SPI flash. + * @returns A promise that is fulfilled when the SPI parameters have been set. + */ +function spiSetParams(size) { + console.log("+----------------+"); + console.log("| SPI_SET_PARAMS |"); + console.log("+----------------+"); + console.log(">> SPI Set Params: Command: 0x%s, size:%d", + COMMAND_SPI_SET_PARAMS.toString(16), size); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(6*4); + buf.writeUInt32LE(0, 0); // id + buf.writeUInt32LE(size, 4); // Total size + buf.writeUInt32LE(64*1024, 8); // block size + buf.writeUInt32LE(4*1024, 12); // sector size + buf.writeUInt32LE(256, 16); // page size + buf.writeUInt32LE(0xffff, 20); // status mask + var dataPacket = buildCommand(COMMAND_SPI_SET_PARAMS, buf); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); + await response.getResponse(); + resolve(); + }); + return p; +} // writeReg + + +/** + * Read an ESP32 register. + * @param {Integer} address The address of the register to be read. + * @returns The result of reading the register. + */ +function readReg(address) { + console.log(">> Reading register: Command: 0x%s, Address: 0x%s", COMMAND_READ_REG.toString(16), address.toString(16)); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(4); + buf.writeUInt32LE(address, 0); + var dataPacket = buildCommand(COMMAND_READ_REG, buf); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); + var result = await response.getResponse(); + console.log("readReg: %O", result); + resolve(result); + }); + return p; +} // readReg + + + +/** + * Get the MD5 hash of an area of flash memory. + * @param {Integer} address The address of the flash memory. + * @param {Integer} size The size of the flasg memory. + * @returns The MD5 hash value. + */ +function spiFlashMD5(address, size) { + console.log(">> spiFlashMD5: Command: 0x%s, Address: 0x%s, Size: %d", COMMAND_SPI_FLASH_MD5.toString(16), address.toString(16), size); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(4 * 4); + buf.writeUInt32LE(address, 0); + buf.writeUInt32LE(size, 0); + buf.writeUInt32LE(0, 0); + buf.writeUInt32LE(0, 0); + var dataPacket = buildCommand(COMMAND_SPI_FLASH_MD5, buf); + await portWrite(dataPacket.getSLIPPacket()); + await drain(); + var result = await response.getResponse(); + console.log("spiFlashMD5 result: %O", result); + resolve(result); + }); + return p; +} // spiFlashMD5 + + +/** + * Read the value of an eFuse. + * @param {Integer} fuseNumber + * @returns The value of the eFuse. + */ +function readEfuse(fuseNumber) { + console.log(">> readEFuse: %d", fuseNumber); + return readReg(EFUSE_REG_BASE + (4 * fuseNumber)); +} // readEfuse + + +/** + * The ESP32 needs to be in a "download" mode in order to process commands. For many boards, a sequence of + * DTR/RTS UART twiddles can be sends to achieve that goal. This function performs that task. + * + * @returns A Promise that is fulfilled when the ESP32 is in download mode. + */ +function enterDownloadMode() { + console.log(">> enterDownloadMode: Enter download mode, esp32r0Delay = " + esp32r0Delay); + return new Promise((resolve, reject) => { + setFlags({ dtr: false, rts: true }).then(()=>{ + return delay(100); + }).then(() => { + if (esp32r0Delay) { + return delay(1200); + } + return Promise.resolve(); + }).then(()=> { + return setFlags({ dtr: true, rts: false }); + }).then(() => { + if (esp32r0Delay) { + return delay(400); + } + return Promise.resolve(); + }).then(()=> { + return delay(50); + }).then(()=> { + return setFlags({ dtr: false, rts: false }); + }).then(() => { + resolve(); + }); + }); +} // enterDownloadMode + + +/** + * Flash the content of the file supplied by filename into ESP32 flash specified by the address. + * @param {Integer} address The address in flash to write the file. + * @param {String} fileName The name of the file to read from. + */ +async function flashFile(address, fileName) { + console.log("Flashing file %s to address 0x%s", fileName, address.toString(16)); + var fileData = fs.readFileSync(fileName); + console.log("Read file ... size is %d", fileData.length); + await FlashCommands().send(fileData, address); + console.log("Flash File complete"); +} // flashFile + + +async function sleep(interval) { + return new Promise((resolve) => { + setTimeout(() => {resolve()}, interval); + }); +} + + +console.log("Start!"); +if (argv._.length%2 != 0) { + console.log("Wrong number of args"); + return; +} +response = ProcessResponse(); +console.log("Response created: " + response); +port.open(async (err)=>{ + if (err) { + console.log("Error opening: " + err); + return; + } + console.log("Open!"); + await enterDownloadMode(); + //var p = Promise.resolve(); + console.log("We should now be in ESP32 download mode!"); + serialData.empty(); + while(1) { + try { + await doSync(); + break; + } + catch(e) { + console.log(e); + } + } + await sleep(1000); + serialData.empty(); + console.log("+----------------+") + console.log("| Sync Complete! |"); + console.log("+----------------+") + + await readEfuse(3); + console.log("Read fuse completed!"); + await uploadFlasher(); + serialData.empty(); + console.log("Flasher upload completed and running!"); + /* + await writeReg(0x6000202c, 0x00000017); + await writeReg(0x6000201c, 0x90000000); + await writeReg(0x60002024, 0x9f000070); + await writeReg(0x60002080, 0x00000000); + await writeReg(0x60002000, 0x00040000); + await writeReg(0x6000201c, 0x80000040); + await writeReg(0x60002024, 0x00000000); + */ + await spiSetParams(0x400000); + + // Walk through each of the files presented and upload them. The input on the command line should be of the form: + // address file [address file]* + + for (var i=0; i { + serialData.append(data); // Append the new data into the serial buffer. +}); + +console.log("Init done"); From a60e6dead22393ec9ea717782dd32775689ab1e8 Mon Sep 17 00:00:00 2001 From: Jeff Edson Date: Thu, 12 Apr 2018 12:10:53 -0700 Subject: [PATCH 266/381] Added (long)response_code return value to get and post methods so that you can test for success --- cpp_utils/RESTClient.cpp | 10 ++++++++-- cpp_utils/RESTClient.h | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cpp_utils/RESTClient.cpp b/cpp_utils/RESTClient.cpp index 20b5bc3d..7160dca6 100644 --- a/cpp_utils/RESTClient.cpp +++ b/cpp_utils/RESTClient.cpp @@ -36,13 +36,16 @@ RESTClient::~RESTClient() { /** * @brief Perform an HTTP GET request. */ -void RESTClient::get() { +long RESTClient::get() { + long response_code; // Added return response_code 2018_4_12 prepForCall(); ::curl_easy_setopt(m_curlHandle, CURLOPT_HTTPGET, 1); int rc = ::curl_easy_perform(m_curlHandle); if (rc != CURLE_OK) { ESP_LOGE(tag, "get(): %s", getErrorMessage().c_str()); } + curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &response_code); // Added return response_code 2018_4_12 + return response_code; // Added return response_code 2018_4_12 } // get @@ -52,13 +55,16 @@ void RESTClient::get() { * @param [in] body The body of the payload to send with the post request. * */ -void RESTClient::post(std::string body) { +long RESTClient::post(std::string body) { + long response_code; // Added return response_code 2018_4_12 prepForCall(); ::curl_easy_setopt(m_curlHandle, CURLOPT_POSTFIELDS, body.c_str()); int rc = ::curl_easy_perform(m_curlHandle); if (rc != CURLE_OK) { ESP_LOGE(tag, "post(): %s", getErrorMessage().c_str()); } + curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &response_code);// Added return response_code 2018_4_12 + return response_code;// Added return response_code 2018_4_12 } // post diff --git a/cpp_utils/RESTClient.h b/cpp_utils/RESTClient.h index 02279c40..4ad91d5a 100644 --- a/cpp_utils/RESTClient.h +++ b/cpp_utils/RESTClient.h @@ -68,7 +68,7 @@ class RESTClient { RESTClient(); virtual ~RESTClient(); void addHeader(std::string name, std::string value); - void get(); + long get(); // Added return response_code 2018_4_12 std::string getErrorMessage(); /** * @brief Get the response payload data from the last REST call. @@ -86,7 +86,7 @@ class RESTClient { return m_timings; } - void post(std::string body); + long post(std::string body); // Added return response_code 2018_4_12 /** * @brief Set the URL for the target. From b87e0ad857fa6006430ed09e4aa990853c4987dd Mon Sep 17 00:00:00 2001 From: Jeff Edson Date: Thu, 12 Apr 2018 20:48:17 -0700 Subject: [PATCH 267/381] Modified bootWiFi2 method to call m_wifi.connectAP forever or until it successfully connects --- networking/bootwifi/BootWiFi.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/networking/bootwifi/BootWiFi.cpp b/networking/bootwifi/BootWiFi.cpp index aadc6d34..8a62786a 100644 --- a/networking/bootwifi/BootWiFi.cpp +++ b/networking/bootwifi/BootWiFi.cpp @@ -285,7 +285,11 @@ void BootWiFi::bootWiFi2() { connectionInfo.ipInfo.gw.addr, connectionInfo.ipInfo.netmask.addr ); - m_wifi.connectAP(connectionInfo.ssid, connectionInfo.password); // Connect to the access point. + + // Connect to the access point. + while(!m_wifi.connectAP(connectionInfo.ssid, connectionInfo.password)){ + ESP_LOGE(LOG_TAG, "Unable to connect to access point \"%s\" - trying again...", connectionInfo.ssid); + }; } else { // We do NOT have connection information. Let us now become an access From 472d1517519065d7b218f5aaa49b59344195abb2 Mon Sep 17 00:00:00 2001 From: Han Date: Fri, 13 Apr 2018 21:29:39 +0200 Subject: [PATCH 268/381] Prevents exception when parsing HTTP POST message without a body --- cpp_utils/HttpParser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp index cc7b686b..d9040659 100644 --- a/cpp_utils/HttpParser.cpp +++ b/cpp_utils/HttpParser.cpp @@ -220,7 +220,9 @@ void HttpParser::parse(Socket s) { } else { uint8_t data[512]; int rc = s.receive(data, sizeof(data)); - m_body = std::string((char *)data, rc); + if (rc > 0) { + m_body = std::string((char *)data, rc); + } } ESP_LOGD(LOG_TAG, "<< parse: Size of body: %d", m_body.length()); } // parse From 7a33f4bf8ade6f6da31a2ae86d2a98fd28c9d290 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Tue, 17 Apr 2018 22:54:09 -0500 Subject: [PATCH 269/381] Refresh of ESP32 flash tool libraries --- tools/esptool_libs/nodejs/README.md | 3 + tools/esptool_libs/nodejs/build.sh | 25 +++ tools/esptool_libs/nodejs/espjs.js | 284 +++++++++++++++++----------- 3 files changed, 198 insertions(+), 114 deletions(-) create mode 100644 tools/esptool_libs/nodejs/README.md create mode 100755 tools/esptool_libs/nodejs/build.sh diff --git a/tools/esptool_libs/nodejs/README.md b/tools/esptool_libs/nodejs/README.md new file mode 100644 index 00000000..95d560e6 --- /dev/null +++ b/tools/esptool_libs/nodejs/README.md @@ -0,0 +1,3 @@ +# NodeJS ESP Tools + +The `build.js` tool is used to take the `stub_flasher_32.elf` and break it apart into the separate files that need to be individually pushed to ESP32 RAM for execution. \ No newline at end of file diff --git a/tools/esptool_libs/nodejs/build.sh b/tools/esptool_libs/nodejs/build.sh new file mode 100755 index 00000000..8bcf71eb --- /dev/null +++ b/tools/esptool_libs/nodejs/build.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Extract the text and data sections to their own files. +xtensa-esp32-elf-objcopy \ + --dump-section .text=text.dat \ + --dump-section .data=data.dat \ + stub_flasher_32.elf + +# Parse out the text load address, the data load address and the entry point from the ELF. +text_address=`xtensa-esp32-elf-objdump --section-headers --wide --section=.text stub_flasher_32.elf | sed -n '$s/^\s*[[:alnum:]]*\s*\S*\s*[[:alnum:]]*\s*[[:alnum:]]*\s*\([[:alnum:]]*\).*/\1/p'` +#echo "Text address: 0x${text_address}" +data_address=`xtensa-esp32-elf-objdump --section-headers --wide --section=.data stub_flasher_32.elf | sed -n '$s/^\s*[[:alnum:]]*\s*\S*\s*[[:alnum:]]*\s*[[:alnum:]]*\s*\([[:alnum:]]*\).*/\1/p'` +#echo "Data address: 0x${data_address}" +entry_point=`xtensa-esp32-elf-objdump --wide --file-headers stub_flasher_32.elf | sed -n '/start address/s/start address 0x\([[:alnum:]]\)/\1/p'` +#echo "Entry point: 0x${entry_point}" + +echo "{" +echo " \"textAddress\": \"${text_address}\"," +echo " \"dataAddress\": \"${data_address}\"," +echo " \"entryPoint\": \"${entry_point}\"," +echo " \"textData\": \"$(base64 --wrap=0 text.dat)\"," +echo " \"dataData\": \"$(base64 --wrap=0 data.dat)\"" +echo "}" + +# Remove the temporary text and data files. +#rm text.dat data.dat diff --git a/tools/esptool_libs/nodejs/espjs.js b/tools/esptool_libs/nodejs/espjs.js index 5285e265..33b62e68 100644 --- a/tools/esptool_libs/nodejs/espjs.js +++ b/tools/esptool_libs/nodejs/espjs.js @@ -12,6 +12,23 @@ * */ + /** + * High level commands: + * doSync - SYNC + * MemCommands + * - MEM_BEGIN + * - MEM_END + * FlashCommands + * - FLASH_BEGIN + * - FLASH_END + * writeReg - WRITE_REG + * readReg - READ_REG + * enterDownloadMode + * spiSetParams - SPI_SET_PARAMS + * flashFile - Flash a specific file to a specific address. + * spiFlashMD5 - Calculate the hash of a region of flash. + */ + /** * We want to provide processing to both transmitted requests and received responses. A request is a message that is then encapsulated * in a SLIP packet and transmitted via UART. A response is a message received via UART that is wrapped in a SLIP packet. @@ -19,32 +36,45 @@ * We will logically define a DataPacket as the raw packet unit that is either transmitted or received. This encapsulates a * Buffer that contains the data. */ -const hexy = require("hexy"); -const zlib = require('zlib'); -const fs = require("fs"); -const assert = require("assert"); -const PORT = "/dev/ttyUSB1"; -const argv = require("minimist")(process.argv.slice(2)); - -const COMMAND_FLASH_BEGIN = 0x02; -const COMMAND_FLASH_DATA = 0x03; -const COMMAND_FLASH_END = 0x04; -const COMMAND_MEM_BEGIN = 0x05; -const COMMAND_MEM_END = 0x06; -const COMMAND_MEM_DATA = 0x07; -const COMMAND_SYNC = 0x08; -const COMMAND_WRITE_REG = 0x09; -const COMMAND_READ_REG = 0x0a; -const COMMAND_SPI_SET_PARAMS = 0x0b; -const COMMAND_SPI_FLASH_MD5 = 0x13; - -const EFUSE_REG_BASE = 0x6001a000; +const hexy = require("hexy"); // Dumping hex data utility. +const zlib = require('zlib'); // Compression/decompression. +const fs = require("fs"); // File system access. +const assert = require("assert"); // Internal assertion. +const argv = require("minimist")(process.argv.slice(2)); // command line processing. +const SerialPort = require("serialport"); // Serial / UART access. +const md5 = require("md5"); // Hash checksum. + +const PORT = "/dev/ttyUSB1"; // The serial port to work with. + +// These are the commands exposed by the Flasher stub. +const COMMAND_FLASH_BEGIN = 0x02; +const COMMAND_FLASH_DATA = 0x03; +const COMMAND_FLASH_END = 0x04; +const COMMAND_MEM_BEGIN = 0x05; +const COMMAND_MEM_END = 0x06; +const COMMAND_MEM_DATA = 0x07; +const COMMAND_SYNC = 0x08; +const COMMAND_WRITE_REG = 0x09; +const COMMAND_READ_REG = 0x0a; +const COMMAND_SPI_SET_PARAMS = 0x0b; +const COMMAND_SPI_ATTACH = 0x0d; +const COMMAND_CHANGE_BAUDRATE = 0x0f; +const COMMAND_FLASH_DEFL_BEGIN = 0x10; +const COMMAND_FLASH_DEFL_DATA = 0x11; +const COMMAND_FLASH_DEFL_END = 0x12; +const COMMAND_SPI_FLASH_MD5 = 0x13; +const COMMAND_ERASE_FLASH = 0xd0; +const COMMAND_ERASE_REGION = 0xd1; +const COMMAND_READ_FLASH = 0xd2; +const COMMAND_RUN_USER_CODE = 0xd3; + +const EFUSE_REG_BASE = 0x6001a000; var esp32r0Delay = false; var response = null; var serialData = SerialData(); // The buffer holding the received serial data. -var SerialPort = require("serialport"); + var port = new SerialPort(PORT, { baudRate: 115200, autoOpen: false @@ -52,18 +82,24 @@ var port = new SerialPort(PORT, { /** * Maintain received serial data. + * * We are receiving serial data asynchronosly from the serial processor. This means that when data arrives * on the UART, it is passed to an instance of this object for accumulation. The data is passed in through a - * call to append. + * call to append(). */ function SerialData() { - var serialDataBuffer; // The serial data we are accumulating. + var serialDataBuffer; // The serial data we are accumulating. + var resolveMoreData = null; // A resolve function for a promise that will be resolved when more data has arrived. + + /** + * Purge and reset any existing unprocessed serial data. + */ function empty() { serialDataBuffer = Buffer.allocUnsafe(0); - } + } // empty + - var resolveMoreData = null; /** * Append data received from the serial port into our buffer. * @param {Buffer} data The data received from our serial port. @@ -71,13 +107,14 @@ function SerialData() { */ function append(data) { console.log("SerialData.append:\n%s", hexy.hexy(data)); - serialDataBuffer = Buffer.concat([serialDataBuffer, data]); // Append the new data into the serial buffer. + serialDataBuffer = Buffer.concat([serialDataBuffer, data]); // Append the new data into the accumulating serial buffer. if (resolveMoreData != null) { // If we have a promise resolution that is waiting for more data .. resolveMoreData(); // resolve the promise. resolveModeData = null; } } // append + /** * Create a promise that is resolved when we have more data. * @returns A promise that is resolved when we have more data. @@ -90,18 +127,20 @@ function SerialData() { /** + * Get the next SLIP data packet. * Assume that the serial data received is a sequence of packets. This function returns a promise that * is resolved when a packet is available for processing. */ async function getNextDataPacket() { var p = new Promise(async (resolve, reject) => { console.log("getNextDataPacket>>"); - while(true) { // Keep looping until we have a data packet. - if (DataPacket().containsSLIPPacket(serialDataBuffer)) { // Does the buffer now contain a SLIP packet? - var dp = DataPacket(); // Create a new data packet - serialDataBuffer = dp.setSLIPPacket(serialDataBuffer); // Load the data packet with the raw serial data. - console.log("Got a new data packet: %s\n%s", dp.toString(), hexy.hexy(dp.getData())); - resolve(dp); + while(true) { // Keep looping until we have a data packet. + if (DataPacket().containsSLIPPacket(serialDataBuffer)) { // Does the buffer now contain a SLIP packet? + var newDataPacket = DataPacket(); // Create a new data packet + serialDataBuffer = newDataPacket.setSLIPPacket(serialDataBuffer); // Load the data packet with the raw serial data. + console.log("Got a new data packet: %s\n%s", + newDataPacket.toString(), hexy.hexy(newDataPacket.getData())); + resolve(newDataPacket); return; } await moreData(); // We didn't find a data packet in our data, so wait for more data. @@ -110,10 +149,11 @@ function SerialData() { return p; // Return the promise that is resolved when we have a data packet. } // getNextDataPacket + empty(); // Initialize / empty the serial data. return { - "append": append, - "empty": empty, + "append": append, + "empty": empty, "getNextDataPacket": getNextDataPacket } } // SerialData @@ -124,6 +164,7 @@ function SerialData() { */ function DataPacket() { var dataPacket = Buffer.allocUnsafe(0); + /** * Examine the data buffer looking for a slip trailer (0xc0). If found, we return the index * at which it was found. If not found, we return -1. @@ -157,15 +198,17 @@ function DataPacket() { */ function setSLIPPacket(data) { console.log("setSLIPPacket:\n%s", hexy.hexy(data)); - assert(data); - assert(data[0] == 0xc0); - assert(data.length > 0); - var end = getSLIPTrailer(data); + assert(data); // Assert if we have been presented no data. + assert(data.length > 0); // Assert that the data length is not 0. + assert(data[0] == 0xc0); // Assert if the first bytes isn't a SLIP marker. + + var end = getSLIPTrailer(data); // Get the end marker for the SLIP packet. if (end == -1) { throw "No SLIP trailer in data we are to process."; } assert(end > 0); - dataPacket = Buffer.allocUnsafe(data.length); // Our data will be no MORE than data.length bytes in size. + + dataPacket = Buffer.allocUnsafe(data.length); // Our data will be no MORE (but likely less) than data.length bytes in size. for (var i=1, j=0; i 0xc0 dataPacket[j] = 0xc0; @@ -181,7 +224,7 @@ function DataPacket() { // We now have a buffer (dataPacket) that contains our decoded data but will be too large. However, j contains the number of bytes we want. var temp = Buffer.allocUnsafe(j); dataPacket.copy(temp, 0, 0, j); // Copy dataPacket[0..j-1] -> temp - dataPacket = temp; + dataPacket = temp; // Set datapacket to the shrunk buffer. // We have now processed what we wanted from the incoming data and now we must return the unused data. i++; @@ -190,7 +233,9 @@ function DataPacket() { data.copy(temp, 0, i); // copy data[i..] -> temp console.log("setSLIPPacket: New data has length: %d, returning unused buffer of size: %d", dataPacket.length, temp.length); - console.log("Unused:\n%s", hexy.hexy(temp)); + if (temp.length > 0) { + console.log("Unused:\n%s", hexy.hexy(temp)); + } return temp; } // setSLIPPacket @@ -251,8 +296,8 @@ function DataPacket() { // For the ESP32 // Size - 4 = status // Size - 3 = error - result += ", status: " + dataPacket[dataPacket.length-4]; - result += ", errorCode: " + dataPacket[dataPacket.length-3]; + result += ", status: " + dataPacket[dataPacket.length-2]; + result += ", errorCode: " + dataPacket[dataPacket.length-1]; } else { result = "Unknown command: " + dataPacket[0]; } @@ -329,18 +374,18 @@ function delay(msecs) { * @param checksum // [Optional] The checksum value * @returns */ -function buildCommand(command, data, checksum = 0) { - console.log("buildCommand: %s, checksum: 0x%s, dataLength: %d", commandToString(command), checksum.toString(16), data.length); +async function buildAndSendRequest(command, data, checksum = 0) { + console.log("buildAndSendRequest: %s, checksum: 0x%s, dataLength: %d", commandToString(command), checksum.toString(16), data.length); var buf = Buffer.allocUnsafe(8 + data.length); // Create a buffer used to hold the command. - buf.writeUInt8(0, 0); // Flag as a request [0] - buf.writeUInt8(command, 1); // Write the command [1] + buf.writeUInt8(0, 0); // Flag as a request [0] + buf.writeUInt8(command, 1); // Write the command [1] buf.writeUInt16LE(data.length, 2); // Write the size [2-3] - buf.writeUInt32LE(checksum, 4); // Write the checksum [4-7] + buf.writeUInt32LE(checksum, 4); // Write the checksum [4-7] data.copy(buf, 8); // Copy data[0..] to buf[8..] var dataPacket = DataPacket(); // Create a new data packet object dataPacket.setData(buf); // Populate the data packet - return dataPacket; -} // writeCommand + await portWrite(dataPacket.getSLIPPacket()); // Send the data packet to the ESP32. +} // buildAndSendRequest /** @@ -401,7 +446,7 @@ function ProcessResponse() { timeoutId = setTimeout(()=>{ console.log("getResponse timeout!"); reject(); - }, 500); + }, 1000); var dp = await serialData.getNextDataPacket(); console.log(" .. getResponse ... we think we have a new datapacket!"); processPacket(dp.getData()); @@ -413,7 +458,7 @@ function ProcessResponse() { function processPacket(inputData) { assert(inputData); assert(inputData.length > 0); - console.log("Received a packet, length %d:\n", inputData.length, hexy.hexy(inputData)); + console.log("Received a packet, length %d:\n%s", inputData.length, hexy.hexy(inputData)); // 0 - direction // 1 - command // [2-3] - size @@ -427,16 +472,18 @@ function ProcessResponse() { command = inputData.readUInt8(1); dataSize = inputData.readUInt16LE(2); value = inputData.readUInt32LE(4); - data = Buffer.allocUnsafe(inputData.length - 8); - inputData.copy(data, 0, 8, inputData.length-2); + data = Buffer.allocUnsafe(inputData.length - 8 - 2); + inputData.copy(data, 0, 8, inputData.length - 2); status = inputData.readUInt8(inputData.length - 2); - erorCode = inputData.readUInt8(inputData.length-1); + erorCode = inputData.readUInt8(inputData.length - 1); clearTimeout(timeoutId); } // processPacket return { getResponse: getResponse, - processPacket: processPacket + getData: function() { + return data; + } }; } // ProcessResponse @@ -456,9 +503,7 @@ async function doSync() { for (var i=4; i<36; i++) { buf.writeUInt8(0x55, i); } - var dataPacket = buildCommand(COMMAND_SYNC, buf); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); + await buildAndSendRequest(COMMAND_SYNC, buf); console.log("response: " + response); try { await response.getResponse(); @@ -508,9 +553,7 @@ function MemCommands() { buf.writeUInt32LE(Math.ceil(size/MEM_PACKET_SIZE), 4); // Number of data packets to be sent. buf.writeUInt32LE(MEM_PACKET_SIZE, 8); // Size of each data packet. buf.writeUInt32LE(address, 12); // Offset in memory to start writing. - var dataPacket = buildCommand(COMMAND_MEM_BEGIN, buf); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); + await buildAndSendRequest(COMMAND_MEM_BEGIN, buf); await response.getResponse(); resolve(); }); @@ -531,9 +574,7 @@ function MemCommands() { var buf = Buffer.allocUnsafe(2*4); // Allocate the payload which is 2 * 32bit words buf.writeUInt32LE(executeFlag, 0); // Execute flag. buf.writeUInt32LE(entrypointAddress, 4); // Entry point address. - var dataPacket = buildCommand(COMMAND_MEM_END, buf); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); // Drain the transmit queue. + await buildAndSendRequest(COMMAND_MEM_END, buf); await response.getResponse(); // Wait for a response. resolve(); // Resolve the promise. }); @@ -577,9 +618,7 @@ function MemCommands() { console.log("| MEM_DATA |"); console.log("+----------+"); await flush(); // Discard any untransmitted and unreceived data. // End the SLIP communication. - var dataPacket = buildCommand(COMMAND_MEM_DATA, buf2, calculateChecksum(data)); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); // Drain the transmit queue. + await buildAndSendRequest(COMMAND_MEM_DATA, buf2, calculateChecksum(data)); sequenceNumber ++; // Increment the sequence number. sizeWritten += sizeToWrite; // Updae the size of the data that we have now written. // Ensure that all the transmitted data is now outbound. await response.getResponse(); // Await a response. @@ -615,12 +654,17 @@ function MemCommands() { }; } // MemCommands + function portWrite(data) { console.log("Writing data down port, length: %d", data.length) return new Promise((resolve, reject) => { - port.write(data, ()=> {resolve()}); + port.write(data, async ()=> { + await drain; + resolve() + }); }); -} +} // portWrite + /** * Process the handling of flash commands. @@ -645,9 +689,7 @@ function FlashCommands() { buf.writeUInt32LE(Math.ceil(size/FLASH_PACKET_SIZE), 4); // Number of data packets to be sent. buf.writeUInt32LE(FLASH_PACKET_SIZE, 8); // Size of each data packet. buf.writeUInt32LE(address, 12); // Offset in memory to start writing. - var dataPacket = buildCommand(COMMAND_FLASH_BEGIN, buf); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); + await buildAndSendRequest(COMMAND_FLASH_BEGIN, buf); await response.getResponse(); resolve(); }); @@ -657,16 +699,16 @@ function FlashCommands() { async function flashData(data, sequenceNumber) { console.log(">> flashData: sequenceId: %d, size: %d", sequenceNumber, data.length); - if (data.length < 16*1024) { - var temp = Buffer.alloc(16*1024, 0xff); + if (data.length < FLASH_PACKET_SIZE) { + var temp = Buffer.alloc(FLASH_PACKET_SIZE, 0xff); data.copy(temp); data = temp; } var buf2 = Buffer.alloc(4*4 + data.length); // Allocate storage for the command preamble. buf2.writeUInt32LE(data.length, 0); // Size of data to be sent. buf2.writeUInt32LE(sequenceNumber, 4); // Current sequence number. - buf2.writeUInt32LE(0, 8); - buf2.writeUInt32LE(0, 12); + buf2.writeUInt32LE(0, 8); + buf2.writeUInt32LE(0, 12); data.copy(buf2, 16); //var buf = Buffer.concat([buf2, data]); //console.log("Here is the data we are sending:\n%s", hexy.hexy(buf)); @@ -676,10 +718,7 @@ function FlashCommands() { console.log("| FLASH_DATA |"); console.log("+------------+"); await flush(); // Discard any untransmitted and unreceived data. - var dataPacket = buildCommand(COMMAND_FLASH_DATA, buf2, calculateChecksum(data)); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); // Ensure that all the transmitted data is now outbound. - + await buildAndSendRequest(COMMAND_FLASH_DATA, buf2, calculateChecksum(data)); await response.getResponse(); // Await a response. resolve(); // Indicate that the command has been completed. }); @@ -701,9 +740,7 @@ function FlashCommands() { await flush(); // Discard any pending input or output. var buf = Buffer.allocUnsafe(4); // Allocate the payload which is one 32bit word. buf.writeUInt32LE(command, 0); // Execute flag. - var dataPacket = buildCommand(COMMAND_FLASH_END, buf); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); // Drain the transmit queue. + await buildAndSendRequest(COMMAND_FLASH_END, buf); await response.getResponse(); // Wait for a response. resolve(); // Resolve the promise. }); @@ -734,7 +771,7 @@ function FlashCommands() { var sizeRemaining = totalSize - sizeWritten; // Calculate the amount of data still expected. var offsetIntoData = sequenceNumber * FLASH_PACKET_SIZE; // Calculate the offset into the data from which to send. var sizeToWrite = sizeRemaining > FLASH_PACKET_SIZE?FLASH_PACKET_SIZE:sizeRemaining; // Calculate how large this unit should be. - var tempBuf = Buffer.allocUnsafe(sizeToWrite); + var tempBuf = Buffer.allocUnsafe(sizeToWrite); data.copy(tempBuf, 0, offsetIntoData, offsetIntoData+sizeToWrite); await flashData(tempBuf, sequenceNumber); sequenceNumber++; @@ -803,17 +840,16 @@ function writeReg(address, value) { var buf = Buffer.allocUnsafe(4*4); buf.writeUInt32LE(address, 0); buf.writeUInt32LE(value, 4); - buf.writeUInt32LE(0xffffffff, 8); // Mask - buf.writeUInt32LE(0, 12); // Delay - var dataPacket = buildCommand(COMMAND_WRITE_REG, buf); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); + buf.writeUInt32LE(0xffffffff, 8); // Mask + buf.writeUInt32LE(0, 12); // Delay + await buildAndSendRequest(COMMAND_WRITE_REG, buf); await response.getResponse(); resolve(); }); return p; } // writeReg + /** * Set the SPI configuration parameters. * @param {Integer} size The size of SPI flash. @@ -828,20 +864,38 @@ function spiSetParams(size) { var p = new Promise(async function(resolve, reject) { await flush(); var buf = Buffer.allocUnsafe(6*4); - buf.writeUInt32LE(0, 0); // id - buf.writeUInt32LE(size, 4); // Total size - buf.writeUInt32LE(64*1024, 8); // block size - buf.writeUInt32LE(4*1024, 12); // sector size - buf.writeUInt32LE(256, 16); // page size - buf.writeUInt32LE(0xffff, 20); // status mask - var dataPacket = buildCommand(COMMAND_SPI_SET_PARAMS, buf); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); + buf.writeUInt32LE(0, 0); // id + buf.writeUInt32LE(size, 4); // Total size + buf.writeUInt32LE(64*1024, 8); // block size + buf.writeUInt32LE(4*1024, 12); // sector size + buf.writeUInt32LE(256, 16); // page size + buf.writeUInt32LE(0xffff, 20); // status mask + await buildAndSendRequest(COMMAND_SPI_SET_PARAMS, buf); await response.getResponse(); resolve(); }); return p; -} // writeReg +} // spiSetParams + + +/** + * Erase the flash memory. + * @returns A promise that is fulfilled when the flash memory has been erased. + */ +function eraseFlash() { + console.log("+-------------+"); + console.log("| ERASE_FLASH |"); + console.log("+-------------+"); + console.log(">> Erase Flash: Command: 0x%s", COMMAND_ERASE_FLASH.toString(16)); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(0); + await buildAndSendRequest(COMMAND_ERASE_FLASH, buf); + await response.getResponse(); + resolve(); + }); + return p; +} // spiSetParams /** @@ -855,9 +909,7 @@ function readReg(address) { await flush(); var buf = Buffer.allocUnsafe(4); buf.writeUInt32LE(address, 0); - var dataPacket = buildCommand(COMMAND_READ_REG, buf); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); + await buildAndSendRequest(COMMAND_READ_REG, buf); var result = await response.getResponse(); console.log("readReg: %O", result); resolve(result); @@ -874,20 +926,21 @@ function readReg(address) { * @returns The MD5 hash value. */ function spiFlashMD5(address, size) { + console.log("+---------------+"); + console.log("| SPI_FLASH_MD5 |"); + console.log("+---------------+"); console.log(">> spiFlashMD5: Command: 0x%s, Address: 0x%s, Size: %d", COMMAND_SPI_FLASH_MD5.toString(16), address.toString(16), size); var p = new Promise(async function(resolve, reject) { await flush(); var buf = Buffer.allocUnsafe(4 * 4); buf.writeUInt32LE(address, 0); - buf.writeUInt32LE(size, 0); - buf.writeUInt32LE(0, 0); - buf.writeUInt32LE(0, 0); - var dataPacket = buildCommand(COMMAND_SPI_FLASH_MD5, buf); - await portWrite(dataPacket.getSLIPPacket()); - await drain(); - var result = await response.getResponse(); - console.log("spiFlashMD5 result: %O", result); - resolve(result); + buf.writeUInt32LE(size, 4); + buf.writeUInt32LE(0, 8); + buf.writeUInt32LE(0, 12); + await buildAndSendRequest(COMMAND_SPI_FLASH_MD5, buf); + await response.getResponse(); + console.log("spiFlashMD5 result:\n%s", hexy.hexy(response.getData())); + resolve(response.getData()); }); return p; } // spiFlashMD5 @@ -946,9 +999,10 @@ function enterDownloadMode() { async function flashFile(address, fileName) { console.log("Flashing file %s to address 0x%s", fileName, address.toString(16)); var fileData = fs.readFileSync(fileName); - console.log("Read file ... size is %d", fileData.length); + console.log("Read file ... size is %d, md5: %s", fileData.length, md5(fileData)); await FlashCommands().send(fileData, address); console.log("Flash File complete"); + return fileData.length; } // flashFile @@ -1015,7 +1069,9 @@ port.open(async (err)=>{ i++; var fileName = argv._[i]; // Obtain the file name. i++; - await flashFile(address, fileName); // Invoke the processor to upload the file into flash. + var size = await flashFile(address, fileName); // Invoke the processor to upload the file into flash. + console.log("File size written: %d", size); + await spiFlashMD5(address, size); } // End of process each file. From 2ae46aa1353bc0e2ffd75c8842c48ec18d999a32 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Tue, 17 Apr 2018 23:02:57 -0500 Subject: [PATCH 270/381] Upload of sample flasher --- tools/esptool_libs/nodejs/SampleFlasher/flasher_stub.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tools/esptool_libs/nodejs/SampleFlasher/flasher_stub.json diff --git a/tools/esptool_libs/nodejs/SampleFlasher/flasher_stub.json b/tools/esptool_libs/nodejs/SampleFlasher/flasher_stub.json new file mode 100644 index 00000000..248949cf --- /dev/null +++ b/tools/esptool_libs/nodejs/SampleFlasher/flasher_stub.json @@ -0,0 +1,7 @@ +{ + "textAddress": "40090000", + "dataAddress": "3ffd2ba4", + "entryPoint": "40090564", + "textData": "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", + "dataData": "CAD8Pw==" +} From 6817fee198f017f746ea7fc2c283f8a38bd01a7b Mon Sep 17 00:00:00 2001 From: Jeff Edson Date: Mon, 23 Apr 2018 16:30:55 -0700 Subject: [PATCH 271/381] Added GeneralUtils::wifiErrorToString - Convert a wifi_err_reason_t code to a string --- cpp_utils/GeneralUtils.cpp | 76 ++++++++++++++++++++++++++++++++++++++ cpp_utils/GeneralUtils.h | 1 + 2 files changed, 77 insertions(+) diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index ccf74f79..960f3172 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -434,6 +434,82 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { return "Unknown ESP_ERR error"; } // errorToString +/** + * @brief Convert a wifi_err_reason_t code to a string. + * @param [in] errCode The errCode to be converted. + * @return A string representation of the error code. + * + * @note: wifi_err_reason_t values as of April 2018 are: (1-24, 200-204) and are defined in ~/esp-idf/components/esp32/include/esp_wifi_types.h. + */ +const char* GeneralUtils::wifiErrorToString(uint8_t errCode) { + if (errCode == ESP_OK) + return "ESP_OK (received SYSTEM_EVENT_STA_GOT_IP event)"; + if (errCode == UINT8_MAX) + return "Not Connected (default value)"; + + switch((wifi_err_reason_t) errCode) { + case WIFI_REASON_UNSPECIFIED: + return "WIFI_REASON_UNSPECIFIED"; + case WIFI_REASON_AUTH_EXPIRE: + return "WIFI_REASON_AUTH_EXPIRE"; + case WIFI_REASON_AUTH_LEAVE: + return "WIFI_REASON_AUTH_LEAVE"; + case WIFI_REASON_ASSOC_EXPIRE: + return "WIFI_REASON_ASSOC_EXPIRE"; + case WIFI_REASON_ASSOC_TOOMANY: + return "WIFI_REASON_ASSOC_TOOMANY"; + case WIFI_REASON_NOT_AUTHED: + return "WIFI_REASON_NOT_AUTHED"; + case WIFI_REASON_NOT_ASSOCED: + return "WIFI_REASON_NOT_ASSOCED"; + case WIFI_REASON_ASSOC_LEAVE: + return "WIFI_REASON_ASSOC_LEAVE"; + case WIFI_REASON_ASSOC_NOT_AUTHED: + return "WIFI_REASON_ASSOC_NOT_AUTHED"; + case WIFI_REASON_DISASSOC_PWRCAP_BAD: + return "WIFI_REASON_DISASSOC_PWRCAP_BAD"; + case WIFI_REASON_DISASSOC_SUPCHAN_BAD: + return "WIFI_REASON_DISASSOC_SUPCHAN_BAD"; + case WIFI_REASON_IE_INVALID: + return "WIFI_REASON_IE_INVALID"; + case WIFI_REASON_MIC_FAILURE: + return "WIFI_REASON_MIC_FAILURE"; + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + return "WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT"; + case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: + return "WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT"; + case WIFI_REASON_IE_IN_4WAY_DIFFERS: + return "WIFI_REASON_IE_IN_4WAY_DIFFERS"; + case WIFI_REASON_GROUP_CIPHER_INVALID: + return "WIFI_REASON_GROUP_CIPHER_INVALID"; + case WIFI_REASON_PAIRWISE_CIPHER_INVALID: + return "WIFI_REASON_PAIRWISE_CIPHER_INVALID"; + case WIFI_REASON_AKMP_INVALID: + return "WIFI_REASON_AKMP_INVALID"; + case WIFI_REASON_UNSUPP_RSN_IE_VERSION: + return "WIFI_REASON_UNSUPP_RSN_IE_VERSION"; + case WIFI_REASON_INVALID_RSN_IE_CAP: + return "WIFI_REASON_INVALID_RSN_IE_CAP"; + case WIFI_REASON_802_1X_AUTH_FAILED: + return "WIFI_REASON_802_1X_AUTH_FAILED"; + case WIFI_REASON_CIPHER_SUITE_REJECTED: + return "WIFI_REASON_CIPHER_SUITE_REJECTED"; + + case WIFI_REASON_BEACON_TIMEOUT: + return "WIFI_REASON_BEACON_TIMEOUT"; + case WIFI_REASON_NO_AP_FOUND: + return "WIFI_REASON_NO_AP_FOUND"; + case WIFI_REASON_AUTH_FAIL: + return "WIFI_REASON_AUTH_FAIL"; + case WIFI_REASON_ASSOC_FAIL: + return "WIFI_REASON_ASSOC_FAIL"; + case WIFI_REASON_HANDSHAKE_TIMEOUT: + return "WIFI_REASON_HANDSHAKE_TIMEOUT"; + } + return "Unknown ESP_ERR error"; +} // wifiErrorToString + + /** * @brief Convert a string to lower case. diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index 013953dc..3706040e 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -23,6 +23,7 @@ class GeneralUtils { static void dumpInfo(); static bool endsWith(std::string str, char c); static const char* errorToString(esp_err_t errCode); + static const char* wifiErrorToString(uint8_t value); static void hexDump(const uint8_t* pData, uint32_t length); static std::string ipToString(uint8_t* ip); static std::vector split(std::string source, char delimiter); From 6014b063810b3cd53a8dc3b3f2c8e582259d2b79 Mon Sep 17 00:00:00 2001 From: Jeff Edson Date: Mon, 23 Apr 2018 16:37:15 -0700 Subject: [PATCH 272/381] Modified WiFi::connectAP so that it returns ESP_OK if successfully receives a SYSTEM_EVENT_STA_GOT_IP event. Otherwise returns wifi_err_reason_t --- cpp_utils/WiFi.cpp | 19 ++++++++++--------- cpp_utils/WiFi.h | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index bfe9477d..36590314 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -55,7 +55,7 @@ WiFi::WiFi() m_eventLoopStarted = false; m_initCalled = false; //m_pWifiEventHandler = new WiFiEventHandler(); - m_apConnected = false; // Are we connected to an access point? + m_apConnectionStatus = UINT8_MAX; // Are we connected to an access point? } // WiFi @@ -153,12 +153,12 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { * @param [in] ssid The network SSID of the access point to which we wish to connect. * @param [in] password The password of the access point to which we wish to connect. * @param [in] waitForConnection Block until the connection has an outcome. - * @return N/A. + * @returns ESP_OK if successfully connected to an access point. Otherwise returns wifi_err_reason_t - use GeneralUtils::wifiErrorToString(uint8_t errCode) to print the error. */ -bool WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ +uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ ESP_LOGD(LOG_TAG, ">> connectAP"); - m_apConnected = false; + m_apConnectionStatus = UINT8_MAX; init(); if (ip != 0 && gw != 0 && netmask != 0) { @@ -206,7 +206,7 @@ bool WiFi::connectAP(const std::string& ssid, const std::string& password, bool m_connectFinished.give(); ESP_LOGD(LOG_TAG, "<< connectAP"); - return m_apConnected; // Return true if we are now connected and false if not. + return m_apConnectionStatus; // Return ESP_OK if we are now connected and wifi_err_reason_t if not. } // connectAP @@ -228,7 +228,7 @@ void WiFi::dump() { * @brief Returns whether wifi is connected to an access point */ bool WiFi::isConnectedToAP() { - return m_apConnected; + return m_apConnectionStatus; } // isConnected @@ -255,10 +255,11 @@ bool WiFi::isConnectedToAP() { // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that // indicates we are waiting for a connection complete. if (event->event_id == SYSTEM_EVENT_STA_GOT_IP || event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { - if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { // If we connected and have an IP, change the flag. - pWiFi->m_apConnected = true; + + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { // If we connected and have an IP, change the status to ESP_OK. Otherwise, change it to the reason code. + pWiFi->m_apConnectionStatus = ESP_OK; } else { - pWiFi->m_apConnected = false; + pWiFi->m_apConnectionStatus = event->event_info.disconnected.reason; } pWiFi->m_connectFinished.give(); } diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index e12994ef..a57360ca 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -116,7 +116,7 @@ class WiFi { uint8_t m_dnsCount=0; bool m_eventLoopStarted; bool m_initCalled; - bool m_apConnected; // Are we connected to an access point? + uint8_t m_apConnectionStatus; // ESP_OK = we are connected to an access point. Otherwise receives wifi_err_reason_t. FreeRTOS::Semaphore m_connectFinished = FreeRTOS::Semaphore("ConnectFinished"); public: @@ -130,7 +130,7 @@ class WiFi { void setDNSServer(int numdns, ip_addr_t ip); struct in_addr getHostByName(const std::string& hostName); struct in_addr getHostByName(const char* hostName); - bool connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); + uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); void dump(); bool isConnectedToAP(); static std::string getApMac(); From 2438e6d2bcf8216101a1800412696f5f8d78074f Mon Sep 17 00:00:00 2001 From: Jeff Edson Date: Mon, 23 Apr 2018 16:37:44 -0700 Subject: [PATCH 273/381] Modified WiFi::connectAP so that it returns ESP_OK if successfully receives a SYSTEM_EVENT_STA_GOT_IP event. Otherwise returns wifi_err_reason_t --- cpp_utils/WiFi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 36590314..503e9235 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -153,7 +153,7 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { * @param [in] ssid The network SSID of the access point to which we wish to connect. * @param [in] password The password of the access point to which we wish to connect. * @param [in] waitForConnection Block until the connection has an outcome. - * @returns ESP_OK if successfully connected to an access point. Otherwise returns wifi_err_reason_t - use GeneralUtils::wifiErrorToString(uint8_t errCode) to print the error. + * @returns ESP_OK if successfully receives a SYSTEM_EVENT_STA_GOT_IP event. Otherwise returns wifi_err_reason_t - use GeneralUtils::wifiErrorToString(uint8_t errCode) to print the error. */ uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ ESP_LOGD(LOG_TAG, ">> connectAP"); From af664f01e706dce7c781f660d867eff8f8c83000 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 23 Apr 2018 19:34:36 -0500 Subject: [PATCH 274/381] Implementation of async BLEScan #496 --- cpp_utils/BLEScan.cpp | 15 ++++- cpp_utils/BLEScan.h | 3 +- cpp_utils/tests/BLETests/SampleAsyncScan.cpp | 58 ++++++++++++++++++++ cpp_utils/tests/BLETests/main.cpp | 2 + tools/bootloaderExamine/.gitignore | 2 + 5 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 cpp_utils/tests/BLETests/SampleAsyncScan.cpp create mode 100644 tools/bootloaderExamine/.gitignore diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index e450664e..73086c6e 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -74,6 +74,9 @@ void BLEScan::handleGAPEvent( // asked to stop. case ESP_GAP_SEARCH_INQ_CMPL_EVT: { m_stopped = true; + if (m_scanCompleteCB != nullptr) { + m_scanCompleteCB(m_scanResults); + } m_semaphoreScanEnd.give(); break; } // ESP_GAP_SEARCH_INQ_CMPL_EVT @@ -186,10 +189,14 @@ void BLEScan::setWindow(uint16_t windowMSecs) { /** * @brief Start scanning. * @param [in] duration The duration in seconds for which to scan. - * @return N/A. + * @param [in] scanCompleteCB A function to be called when scanning has completed. This can + * be supplied as nullptr (the default) in which case the call to start will block until scanning has + * been completed. + * @return The BLEScanResults. Only applicable if we are waiting for results. */ -BLEScanResults BLEScan::start(uint32_t duration) { +BLEScanResults BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)) { ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); + m_scanCompleteCB = scanCompleteCB; // Save the callback to be invoked when the scan completes. m_semaphoreScanEnd.take(std::string("start")); @@ -213,7 +220,9 @@ BLEScanResults BLEScan::start(uint32_t duration) { m_stopped = false; - m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. + if (m_scanCompleteCB == nullptr) { + m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. + } ESP_LOGD(LOG_TAG, "<< start()"); return m_scanResults; diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index c905c436..3e53ce64 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -53,7 +53,7 @@ class BLEScan { bool wantDuplicates = false); void setInterval(uint16_t intervalMSecs); void setWindow(uint16_t windowMSecs); - BLEScanResults start(uint32_t duration); + BLEScanResults start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults) = nullptr); void stop(); private: @@ -71,6 +71,7 @@ class BLEScan { FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); BLEScanResults m_scanResults; bool m_wantDuplicates; + void (*m_scanCompleteCB)(BLEScanResults scanResults); }; // BLEScan #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/tests/BLETests/SampleAsyncScan.cpp b/cpp_utils/tests/BLETests/SampleAsyncScan.cpp new file mode 100644 index 00000000..2973b652 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleAsyncScan.cpp @@ -0,0 +1,58 @@ +/** + * Perform an async scanning for BLE advertised servers. + */ +#include "BLEUtils.h" +#include "BLEScan.h" +#include + +#include "BLEDevice.h" +#include "BLEAdvertisedDevice.h" +#include "sdkconfig.h" + +/** + * Callback for each detected advertised device. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice advertisedDevice) { + printf("Advertised Device: %s\n", advertisedDevice.toString().c_str()); + } +}; + + +/** + * Callback invoked when scanning has completed. + */ +static void scanCompleteCB(BLEScanResults scanResults) { + printf("Scan complete!\n"); + printf("We found %d devices\n", scanResults.getCount()); + scanResults.dump(); +} // scanCompleteCB + +/** + * Run the sample. + */ +static void run() { + printf("Async Scanning sample starting\n"); + BLEDevice::init(""); + + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), false); + pBLEScan->setActiveScan(true); + printf("About to start scanning for 10 seconds\n"); + pBLEScan->start(10, scanCompleteCB); + printf("Now scanning in the background ... scanCompleteCB() will be called when done.\n"); + + // + // Now going into a loop logging that we are still alive. + // + while(1) { + printf("Tick! - still alive\n"); + FreeRTOS::sleep(1000); + } + printf("Scanning sample ended\n"); +} + +void SampleAsyncScan(void) +{ + run(); +} // app_main diff --git a/cpp_utils/tests/BLETests/main.cpp b/cpp_utils/tests/BLETests/main.cpp index 732ae16d..77c3567b 100644 --- a/cpp_utils/tests/BLETests/main.cpp +++ b/cpp_utils/tests/BLETests/main.cpp @@ -9,6 +9,7 @@ extern "C" { // The list of sample entry points. void Sample_MLE_15(void); void Sample1(void); +void SampleAsyncScan(void); void SampleClient(void); void SampleClient_Notify(void); void SampleClientAndServer(void); @@ -27,6 +28,7 @@ void SampleWrite(void); void app_main(void) { //Sample_MLE_15(); //Sample1(); + //SampleAsyncScan(); //SampleClient(); //SampleClient_Notify(); //SampleClientAndServer(); diff --git a/tools/bootloaderExamine/.gitignore b/tools/bootloaderExamine/.gitignore new file mode 100644 index 00000000..5db6b6fc --- /dev/null +++ b/tools/bootloaderExamine/.gitignore @@ -0,0 +1,2 @@ +/a.out +/app-template.bin From 0eda8c66b20639e9d7f1cc62dd42f8ea594923a0 Mon Sep 17 00:00:00 2001 From: Jeff Edson Date: Mon, 23 Apr 2018 17:57:02 -0700 Subject: [PATCH 275/381] Prior to this change, BootWiFi::boot would hang on m_completeSemaphore.wait("boot") if WiFi::connectAP received SYSTEM_EVENT_STA_DISCONNECTED. This commit changes 2 items 1) after calling m_wifi.connectAP, calls m_completeSemaphore.give() to ensure that we dont hang 2) BootWiFi::boot returns ESP_OK if it successfully receives a SYSTEM_EVENT_STA_GOT_IP event. Otherwise it returns the wifi_err_reason_t so that the programmer can then handle it in a distinct manner. --- networking/bootwifi/BootWiFi.cpp | 24 +++++++++++++++++------- networking/bootwifi/BootWiFi.h | 3 ++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/networking/bootwifi/BootWiFi.cpp b/networking/bootwifi/BootWiFi.cpp index 8a62786a..cc07c898 100644 --- a/networking/bootwifi/BootWiFi.cpp +++ b/networking/bootwifi/BootWiFi.cpp @@ -243,6 +243,7 @@ class BootWifiEventHandler: public WiFiEventHandler { esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { ESP_LOGD("BootWifiEventHandler", ">> staGotIP"); + m_pBootWiFi->m_apConnectionStatus = ESP_OK; // Set the status to ESP_OK m_pBootWiFi->m_completeSemaphore.give(); // If we got an IP address, then we can end the boot process. ESP_LOGD("BootWifiEventHandler", "<< staGotIP"); return ESP_OK; @@ -255,6 +256,11 @@ class BootWifiEventHandler: public WiFiEventHandler { /** * Boot WiFi + * + * @brief Get connected to WiFi + * + * @detailed If SSID & Password were previously saved, connect to the AP. Otherwise become an AP and start an HTTP server so that the user can set SSID & Password - then save it. + * */ void BootWiFi::bootWiFi2() { ESP_LOGD(LOG_TAG, ">> bootWiFi2"); @@ -286,10 +292,8 @@ void BootWiFi::bootWiFi2() { connectionInfo.ipInfo.netmask.addr ); - // Connect to the access point. - while(!m_wifi.connectAP(connectionInfo.ssid, connectionInfo.password)){ - ESP_LOGE(LOG_TAG, "Unable to connect to access point \"%s\" - trying again...", connectionInfo.ssid); - }; + m_apConnectionStatus = m_wifi.connectAP(connectionInfo.ssid, connectionInfo.password); // Try to connect to the access point. + m_completeSemaphore.give(); // end the boot process so we don't hang... } else { // We do NOT have connection information. Let us now become an access @@ -316,22 +320,28 @@ void BootWiFi::setAccessPointCredentials(std::string ssid, std::string password) /** - * @brief Main entry point into booting WiFi + * @brief Main entry point into booting WiFi - see BootWiFi2 for more detail. + * + * The event handler will be called back with the outcome of the connection. + * + * @returns ESP_OK if successfully connected to an access point. Otherwise returns wifi_err_reason_t - to print use GeneralUtils::wifiErrorToString */ -void BootWiFi::boot() { +uint8_t BootWiFi::boot() { ESP_LOGD(LOG_TAG, ">> boot"); ESP_LOGD(LOG_TAG, " +----------+"); ESP_LOGD(LOG_TAG, " | BootWiFi |"); ESP_LOGD(LOG_TAG, " +----------+"); ESP_LOGD(LOG_TAG, " Access point credentials: %s/%s", m_ssid.c_str(), m_password.c_str()); - m_completeSemaphore.take("boot"); // Take the semaphore which will be unlocked when we complete booting. + m_completeSemaphore.take("boot"); // Take the semaphore which will be unlocked when we complete booting. bootWiFi2(); m_completeSemaphore.wait("boot"); // Wait for the semaphore that indicated we have completed booting. m_wifi.setWifiEventHandler(nullptr); // Remove the WiFi boot handler when we have completed booting. ESP_LOGD(LOG_TAG, "<< boot"); + return m_apConnectionStatus; } // boot BootWiFi::BootWiFi() { m_httpServerStarted = false; + m_apConnectionStatus = UINT8_MAX; setAccessPointCredentials("esp32", "password"); // Default access point credentials } diff --git a/networking/bootwifi/BootWiFi.h b/networking/bootwifi/BootWiFi.h index 3d37dccd..9c4a17b6 100644 --- a/networking/bootwifi/BootWiFi.h +++ b/networking/bootwifi/BootWiFi.h @@ -24,12 +24,13 @@ class BootWiFi { bool m_httpServerStarted; std::string m_ssid; std::string m_password; + uint8_t m_apConnectionStatus; // receives the connection status. ESP_OK = received SYSTEM_EVENT_STA_GOT_IP event. FreeRTOS::Semaphore m_completeSemaphore = FreeRTOS::Semaphore("completeSemaphore"); public: BootWiFi(); void setAccessPointCredentials(std::string ssid, std::string password); - void boot(); + uint8_t boot(); }; #endif /* MAIN_BOOTWIFI_H_ */ From 5ae9b98309030d8fedde4addcea2f9d2cdfff4ed Mon Sep 17 00:00:00 2001 From: chegewara Date: Fri, 27 Apr 2018 13:18:27 +0200 Subject: [PATCH 276/381] Add retrieving raw advertising data --- cpp_utils/BLEAdvertisedDevice.cpp | 7 +++++++ cpp_utils/BLEAdvertisedDevice.h | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 351c5e10..67603dfb 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -234,6 +234,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { uint8_t ad_type; uint8_t sizeConsumed = 0; bool finished = false; + setPayload(payload); while(!finished) { length = *payload; // Retrieve the length of the record. @@ -506,7 +507,13 @@ std::string BLEAdvertisedDevice::toString() { return ss.str(); } // toString +uint8_t* BLEAdvertisedDevice::getPayload() { + return m_payload; +} +void BLEAdvertisedDevice::setPayload(uint8_t* payload) { + m_payload = payload; +} #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index 41bc4c66..a3b1e6e2 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -39,9 +39,10 @@ class BLEAdvertisedDevice { BLEUUID getServiceDataUUID(); BLEUUID getServiceUUID(); int8_t getTXPower(); + uint8_t* getPayload(); - bool isAdvertisingService(BLEUUID uuid); + bool isAdvertisingService(BLEUUID uuid); bool haveAppearance(); bool haveManufacturerData(); bool haveName(); @@ -69,6 +70,7 @@ class BLEAdvertisedDevice { void setServiceUUID(const char* serviceUUID); void setServiceUUID(BLEUUID serviceUUID); void setTXPower(int8_t txPower); + void setPayload(uint8_t* payload); bool m_haveAppearance; @@ -92,6 +94,7 @@ class BLEAdvertisedDevice { int8_t m_txPower; std::string m_serviceData; BLEUUID m_serviceDataUUID; + uint8_t* m_payload; }; /** From e00a6c79cdad38bec2a64c07d7ab6a7a9cfed62e Mon Sep 17 00:00:00 2001 From: mws-rmain <30533684+mws-rmain@users.noreply.github.com> Date: Fri, 27 Apr 2018 10:13:26 -0400 Subject: [PATCH 277/381] Update PubSubClient.cpp --- cpp_utils/PubSubClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/PubSubClient.cpp b/cpp_utils/PubSubClient.cpp index e3542cb0..8aaca5eb 100644 --- a/cpp_utils/PubSubClient.cpp +++ b/cpp_utils/PubSubClient.cpp @@ -315,7 +315,7 @@ bool PubSubClient::connect(){ ESP_LOGD(TAG, "Connect to mqtt server..."); - ESP_LOGD(TAG, "ip: %s port: %d", _config.ip.c_str(), _config.port) + ESP_LOGD(TAG, "ip: %s port: %d", _config.ip.c_str(), _config.port); int result = _client->connect((char *)_config.ip.c_str(), _config.port); if (result == 0) { From c1485348ef286b3452f068a86d731ec8db4b31a3 Mon Sep 17 00:00:00 2001 From: mws-rmain <30533684+mws-rmain@users.noreply.github.com> Date: Fri, 27 Apr 2018 10:18:15 -0400 Subject: [PATCH 278/381] Update BLEServer.cpp --- cpp_utils/BLEServer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 9d26eb50..c33afdf2 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -18,7 +18,6 @@ #include "BLEUtils.h" #include #include -#include #include #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" From fac6baad4bf9e1a5f982bf568fda2a5929328dae Mon Sep 17 00:00:00 2001 From: mws-rmain <30533684+mws-rmain@users.noreply.github.com> Date: Fri, 27 Apr 2018 10:20:22 -0400 Subject: [PATCH 279/381] Update SockServ.cpp --- cpp_utils/SockServ.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 6d35a387..296b3a4b 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -64,7 +64,7 @@ SockServ::~SockServ() { SockServ* pSockServ = (SockServ*)data; try { while(1) { - ESP_LOGD(LOG_TAG, "Waiting on accept") + ESP_LOGD(LOG_TAG, "Waiting on accept"); Socket tempSock = pSockServ->m_serverSocket.accept(); if (!tempSock.isValid()) { continue; @@ -229,7 +229,7 @@ Socket SockServ::waitForData(std::set& socketSet) { * or can return immediately is there is already a client connection in existence. */ Socket SockServ::waitForNewClient() { - ESP_LOGD(LOG_TAG, ">> waitForNewClient") + ESP_LOGD(LOG_TAG, ">> waitForNewClient"); m_clientSemaphore.wait("waitForNewClient"); // Unlocked in acceptTask. m_clientSemaphore.take("waitForNewClient"); Socket tempSocket; From 7a33655165211b601c702b6705a708786333aa56 Mon Sep 17 00:00:00 2001 From: Benjamin Aigner Date: Mon, 7 May 2018 16:57:24 +0200 Subject: [PATCH 280/381] Removed vTaskDelete, avoiding crashes with Task::stop() called --- cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp b/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp index f0385c52..29bff125 100644 --- a/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp +++ b/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp @@ -59,7 +59,6 @@ class MyTask : public Task { } vTaskDelay(2000/portTICK_PERIOD_MS); // simulate write message every 2 seconds } - vTaskDelete(NULL); } }; From 2a7358ce2930fe255eb8988a8b23a8a7206c17be Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 13 May 2018 18:58:11 -0500 Subject: [PATCH 281/381] Initial implementation of FTP Server --- cpp_utils/FTPCallbacks.cpp | 155 +++++++ cpp_utils/FTPServer.cpp | 823 +++++++++++++++++++++++++++++++++++++ cpp_utils/FTPServer.h | 137 ++++++ cpp_utils/GPIO.cpp | 1 + 4 files changed, 1116 insertions(+) create mode 100644 cpp_utils/FTPCallbacks.cpp create mode 100644 cpp_utils/FTPServer.cpp create mode 100644 cpp_utils/FTPServer.h diff --git a/cpp_utils/FTPCallbacks.cpp b/cpp_utils/FTPCallbacks.cpp new file mode 100644 index 00000000..d88c2300 --- /dev/null +++ b/cpp_utils/FTPCallbacks.cpp @@ -0,0 +1,155 @@ +#include "FTPServer.h" +#include +#include +#include +#include + +static const char* LOG_TAG = "FTPCallbacks"; +/** + * Called at the start of a STOR request. The file name is the name of the file the client would like to + * save. + */ +void FTPFileCallbacks::onStoreStart(std::string fileName) { + ESP_LOGD(LOG_TAG, ">> FTPFileCallbacks::onStoreStart: fileName=%s", fileName.c_str()); + m_storeFile.open(fileName, std::ios::binary); // Open the file for writing. + if (m_storeFile.fail()) { + throw FTPServer::FileException(); + } + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onStoreStart"); +} // FTPFileCallbacks#onStoreStart + + +/** + * Called when the client presents a new chunk of data to be saved. + */ +size_t FTPFileCallbacks::onStoreData(uint8_t* data, size_t size) { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onStoreData: size=%d", size); + m_storeFile.write((char *)data, size); // Store data received. + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onStoreData: size=%d", size); + return size; +} // FTPFileCallbacks#onStoreData + + +/** + * Called at the end of a STOR request. This indicates that the client has completed its transmission of the + * file. + */ +void FTPFileCallbacks::onStoreEnd() { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onStoreEnd"); + m_storeFile.close(); // Close the open file. + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onStoreEnd"); +} // FTPFileCallbacks#onStoreEnd + + +/** + * Called when the client requests retrieval of a file. + */ +void FTPFileCallbacks::onRetrieveStart(std::string fileName) { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onRetrieveStart: fileName=%s", fileName.c_str()); + m_byteCount = 0; + m_retrieveFile.open(fileName, std::ios::binary); + if (m_retrieveFile.fail()) { + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onRetrieveStart: ***FileException***"); + throw FTPServer::FileException(); + } + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onRetrieveStart"); +} // FTPFileCallbacks#onRetrieveStart + + +/** + * Called when the client is ready to receive the next piece of the file. To indicate that there + * is no more data to send, return a size of 0. + * @param data The data buffer that we can fill to return data back to the client. + * @param size The maximum size of the data buffer that we can populate. + * @return The size of data being returned. Return 0 to indicate that there is no more data to return. + */ +size_t FTPFileCallbacks::onRetrieveData(uint8_t* data, size_t size) { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onRetrieveData"); + m_retrieveFile.read((char *)data, size); + size_t readSize = m_retrieveFile.gcount(); + m_byteCount += readSize; + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onRetrieveData: sizeRead=%d", readSize); + return m_retrieveFile.gcount(); // Return the number of bytes read. +} // FTPFileCallbacks#onRetrieveData + + +/** + * Called when the retrieval has been completed. + */ +void FTPFileCallbacks::onRetrieveEnd() { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onRetrieveEnd"); + m_retrieveFile.close(); + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onRetrieveEnd: bytesTransmitted=%d", m_byteCount); +} // FTPFileCallbacks#onRetrieveEnd + + +/** + * Return a list of files in the file system. + * @return a list of files in the file system. + */ +std::string FTPFileCallbacks::onDir() { + + DIR* dir = opendir(FTPServer::getCurrentDirectory().c_str()); + std::stringstream ss; + while(1) { + struct dirent* pDirentry = readdir(dir); + if (pDirentry == nullptr) { + break; + } + ss << pDirentry->d_name << "\r\n"; + } + closedir(dir); + return ss.str(); +} // FTPFileCallbacks#onDir + + +/// ---- END OF FTPFileCallbacks + + +void FTPCallbacks::onStoreStart(std::string fileName) { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onStoreStart: fileName=%s", fileName.c_str()); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onStoreStart"); +} // FTPCallbacks#onStoreStart + + +size_t FTPCallbacks::onStoreData(uint8_t* data, size_t size) { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onStoreData: size=%d", size); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onStoreData"); + return 0; +} // FTPCallbacks#onStoreData + + +void FTPCallbacks::onStoreEnd() { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onStoreEnd"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onStoreEnd"); +} // FTPCallbacks#onStoreEnd + + +void FTPCallbacks::onRetrieveStart(std::string fileName) { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onRetrieveStart"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onRetrieveStart"); +} // FTPCallbacks#onRetrieveStart + + +size_t FTPCallbacks::onRetrieveData(uint8_t *data, size_t size) { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onRetrieveData"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onRetrieveData: 0"); + return 0; +} // FTPCallbacks#onRetrieveData + + +void FTPCallbacks::onRetrieveEnd() { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onRetrieveEnd"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onRetrieveEnd"); +} // FTPCallbacks#onRetrieveEnd + + +std::string FTPCallbacks::onDir() { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onDir"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onDir"); + return ""; +} // FTPCallbacks#onDir + +FTPCallbacks::~FTPCallbacks() { + +} // FTPCallbacks#~FTPCallbacks diff --git a/cpp_utils/FTPServer.cpp b/cpp_utils/FTPServer.cpp new file mode 100644 index 00000000..f8d006fd --- /dev/null +++ b/cpp_utils/FTPServer.cpp @@ -0,0 +1,823 @@ +/* + * FTPServer.cpp + * + * Created on: May 6, 2018 + * Author: kolban + */ + +#include "FTPServer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char* LOG_TAG = "FTPServer"; + +// trim from start (in place) +static void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); +} // ltrim + + +// trim from end (in place) +static void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} // rtrim + + +// trim from both ends (in place) +static void trim(std::string &s) { + ltrim(s); + rtrim(s); +} // trim + + +FTPServer::FTPServer() { + ESP_LOGD(LOG_TAG,">> FTPServer()"); + + m_dataSocket = -1; + m_clientSocket = -1; + m_dataPort = -1; + m_dataIp = -1; + m_passiveSocket = -1; + m_serverSocket = -1; + + m_callbacks = nullptr; + m_isPassive = false; + m_isImage = true; + m_chunkSize = 4096; + m_port = 21; // The default Server-PI port + m_loginRequired = false; + m_isAuthenticated = false; + m_userid = ""; + m_password = ""; + + ESP_LOGD(LOG_TAG,"<< FTPServer()"); +} // FTPServer#FTPServer + + +FTPServer::~FTPServer() { + // Nothing to do here by default. +} // FTPServer#~FTPServer + + +/** + * Close the connection to the FTP client. + */ +void FTPServer::closeConnection() { + ESP_LOGD(LOG_TAG,">> closeConnection"); + close(m_clientSocket); + ESP_LOGD(LOG_TAG,"<< closeConnection"); +} // FTPServer#closeConnection + + +/** + * Close a previously opened data connection. + */ +void FTPServer::closeData() { + ESP_LOGD(LOG_TAG,">> closeData"); + close(m_dataSocket); + m_dataSocket = -1; + ESP_LOGD(LOG_TAG,"<< closeData"); +} // FTPServer#closeData + + +/** + * Close the passive listening socket that was opened by listenPassive. + */ +void FTPServer::closePassive() { + ESP_LOGD(LOG_TAG,">> closePassive"); + close(m_passiveSocket); + m_passiveSocket = -1; + ESP_LOGD(LOG_TAG, "<< closePassive"); +} // FTPServer#closePassive + + +/** + * Retrieve the current directory. + */ +/* STATIC */ std::string FTPServer::getCurrentDirectory() { + char maxDirectory[256]; + std::string currentDirectory = getcwd(maxDirectory, sizeof(maxDirectory)); + return currentDirectory; +} // FTPServer#getCurrentDirectory + + +/** + * Create a listening socket for the new passive connection. + * @return a String for the passive parameters. + */ +std::string FTPServer::listenPassive() { + ESP_LOGD(LOG_TAG, ">> listenPassive"); + + m_passiveSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_passiveSocket == -1) { + ESP_LOGD(LOG_TAG, "socket: %s", strerror(errno)); + } + + struct sockaddr_in clientAddrInfo; + unsigned int addrInfoSize = sizeof(clientAddrInfo); + getsockname(m_clientSocket, (struct sockaddr*)&clientAddrInfo, &addrInfoSize); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(0); + int rc = bind(m_passiveSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "bind: %s", strerror(errno)); + } + + rc = listen(m_passiveSocket, 5); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "listen: %s", strerror(errno)); + } + + unsigned int addrLen = sizeof(serverAddress); + rc = getsockname(m_passiveSocket, (struct sockaddr*)&serverAddress, &addrLen); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "getsockname: %s", strerror(errno)); + } + + + std::stringstream ss; + ss << ((clientAddrInfo.sin_addr.s_addr >> 0) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 8) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 16) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 24) & 0xff) << + "," << ((serverAddress.sin_port >> 0) & 0xff) << + "," << ((serverAddress.sin_port >> 8) & 0xff); + std::string retStr = ss.str(); + + ESP_LOGD(LOG_TAG, "<< listenPassive: %s", retStr.c_str()); + return retStr; +} // FTPServer#listenPassive + + +/** + * Handle the AUTH command. + */ +void FTPServer::onAuth(std::istringstream& ss) { + std::string param; + ss >> param; + ESP_LOGD(LOG_TAG, ">> onAuth: %s", param.c_str()); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); // Syntax error, command unrecognized. + ESP_LOGD(LOG_TAG, "<< onAuth"); +} // FTPServer#onAuth + + +/** + * Change the current working directory. + * @param ss A string stream where the first parameter is the directory to change to. + */ +void FTPServer::onCwd(std::istringstream& ss) { + std::string path; + ss >> path; + ESP_LOGD(LOG_TAG, ">> onCwd: path=%s", path.c_str()); + chdir(path.c_str()); + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); + ESP_LOGD(LOG_TAG, "<< onCwd"); +} // FTPServer#onCwd + + +/** + * Process the client transmitted LIST request. + */ +void FTPServer::onList(std::istringstream& ss) { + std::string directory; + ss >> directory; + ESP_LOGD(LOG_TAG, ">> onList: directory=%s", directory.c_str()); + + openData(); + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + if (m_callbacks != nullptr) { + std::string dirString = m_callbacks->onDir(); + sendData((uint8_t *)dirString.data(), dirString.length()); + } + closeData(); + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + ESP_LOGD(LOG_TAG, "<< onList"); +} // FTPServer#onList + + +void FTPServer::onMkd(std::istringstream &ss) { + std::string path; + ss >> path; + ESP_LOGD(LOG_TAG, ">> onMkd: path=%s", path.c_str()); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + ESP_LOGD(LOG_TAG, "<< onMkd"); +} // FTPServer#onMkd + + +/** + * Process a NOOP operation. + */ +void FTPServer::onNoop(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onNoop"); + sendResponse(RESPONSE_200_COMMAND_OK); // Command okay. + ESP_LOGD(LOG_TAG, "<< onNoop"); +} // FTPServer#onNoop + + +/** + * Process PORT request. The information provided is encoded in the parameter as + * h1,h2,h3,h4,p1,p2 where h1,h2,h3,h4 is the IP address we should connect to + * and p1,p2 is the port number. The data is MSB + * + * Our logic does not form any connection but remembers the ip address and port number + * to be used for a subsequence data connection. + * + * Possible responses: + * 200 + * 500, 501, 421, 530 + */ +void FTPServer::onPort(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onPort"); + char c; + uint16_t h1, h2, h3, h4, p1, p2; + ss >> h1 >> c >> h2 >> c >> h3 >> c >> h4 >> c >> p1 >> c >> p2; + m_dataPort = p1*256 + p2; + ESP_LOGD(LOG_TAG, "%d.%d.%d.%d %d", h1, h2, h3, h4, m_dataPort); + m_dataIp = h1<<24 | h2<<16 | h3<<8 | h4; + sendResponse(RESPONSE_200_COMMAND_OK); // Command okay. + m_isPassive = false; + + ESP_LOGD(LOG_TAG, "<< onPort"); +} // FTPServer#onPort + + +/** + * Process the PASS command. + * Possible responses: + * 230 + * 202 + * 530 + * 500, 501, 503, 421 + * 332 + */ +void FTPServer::onPass(std::istringstream& ss) { + std::string password; + ss >> password; + ESP_LOGD(LOG_TAG, ">> onPass: password=%s", password.c_str()); + + // If the immediate last command wasn't USER then don't try and process PASS. + if (m_lastCommand != "USER") { + sendResponse(RESPONSE_503_BAD_SEQUENCE); + ESP_LOGD(LOG_TAG, "<< onPass"); + return; + } + + // Compare the supplied userid and passwords. + if (m_userid == m_suppliedUserid && password == m_password) { + sendResponse(RESPONSE_230_USER_LOGGED_IN); + m_isAuthenticated = true; + } else { + sendResponse(RESPONSE_530_NOT_LOGGED_IN); + closeConnection(); + m_isAuthenticated = false; + } + ESP_LOGD(LOG_TAG, "<< onPass"); +} // FTPServer#onPass + + +/** + * Process the PASV command. + * Possible responses: + * 227 + * 500, 501, 502, 421, 530 + */ +void FTPServer::onPasv(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onPasv"); + std::string ipInfo = listenPassive(); + std::ostringstream responseTextSS; + responseTextSS << "Entering Passive Mode (" << ipInfo << ")."; + std::string responseText; + responseText = responseTextSS.str(); + sendResponse(RESPONSE_227_ENTERING_PASSIVE_MODE, responseText.c_str()); + m_isPassive = true; + + ESP_LOGD(LOG_TAG, "<< onPasv"); +} // FTPServer#onPasv + + +/** + * Process the PWD command to determine our current working directory. + * Possible responses: + * 257 + * 500, 501, 502, 421, 550 + */ +void FTPServer::onPWD(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onPWD"); + sendResponse(257, "\"" + getCurrentDirectory() + "\""); + ESP_LOGD(LOG_TAG, "<< onPWD: %s", getCurrentDirectory().c_str()); +} // FTPServer#onPWD + + +/** + * Possible responses: + * 221 + * 500 + */ +void FTPServer::onQuit(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onQuit"); + sendResponse(FTPServer::RESPONSE_221_CLOSING_CONTROL_CONNECTION); // Service closing control connection. + closeConnection(); // Close the connection to the client. + ESP_LOGD(LOG_TAG, "<< onQuit"); +} // FTPServer#onQuit + + +/** + * Process a RETR command. The client sends this command to retrieve the content of a file. + * The name of the file is the first parameter in the input stream. + * + * Possible responses: + * 125, 150 + * (110) + * 226, 250 + * 425, 426, 451 + * 450, 550 + * 500, 501, 421, 530 + * @param ss The parameter stream. + */ +void FTPServer::onRetr(std::istringstream& ss) { + + // We open a data connection back to the client. We then invoke the callback to indicate that we have + // started a retrieve operation. We call the retrieve callback to request the next chunk of data and + // transmit this down the data connection. We repeat this until there is no more data to send at which + // point we close the data connection and we are done. + ESP_LOGD(LOG_TAG, ">> onRetr"); + std::string fileName; + + ss >> fileName; + uint8_t data[m_chunkSize]; + + + if (m_callbacks != nullptr) { + try { + m_callbacks->onRetrieveStart(fileName); + } catch(FTPServer::FileException& e) { + sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. + ESP_LOGD(LOG_TAG, "<< onRetr: Returned 550 to client."); + return; + } + } + + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + openData(); + if (m_callbacks != nullptr) { + int readSize = m_callbacks->onRetrieveData(data, m_chunkSize); + while(readSize > 0) { + sendData(data, readSize); + readSize = m_callbacks->onRetrieveData(data, m_chunkSize); + } + } + closeData(); + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveEnd(); + } + ESP_LOGD(LOG_TAG, "<< onRetr"); +} // FTPServer#onRetr + + +void FTPServer::onRmd(std::istringstream &ss) { + ESP_LOGD(LOG_TAG, ">> onRmd"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + ESP_LOGD(LOG_TAG, "<< onRmd"); +} // FTPServer#onRmd + + +/** + * Called to process a STOR request. This means that the client wishes to store a file + * on the server. The name of the file is found in the parameter. + */ +void FTPServer::onStor(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onStor"); + std::string fileName; + ss >> fileName; + + receiveFile(fileName); + ESP_LOGD(LOG_TAG, "<< onStor"); +} // FTPServer#onStor + + +void FTPServer::onSyst(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onSyst"); + sendResponse(215, "UNIX Type: L8"); + ESP_LOGD(LOG_TAG, "<< onSyst"); +} // FTPServer#onSyst + + +/** + * Process a TYPE request. The parameter that follows is the type of transfer we wish + * to process. Types include: + * I and A. + * + * Possible responses: + * 200 + * 500, 501, 504, 421, 530 + */ +void FTPServer::onType(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onType"); + std::string type; + ss >> type; + if (type.compare("I") == 0) { + m_isImage = true; + } else { + m_isImage = false; + } + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); // Command okay. + ESP_LOGD(LOG_TAG, "<< onType: isImage=%d", m_isImage); +} // FTPServer#onType + + +/** + * Process a USER request. The parameter that follows is the identity of the user. + * + * Possible responses: + * 230 + * 530 + * 500, 501, 421 + * 331, 332 + * + */ +void FTPServer::onUser(std::istringstream& ss) { + // When we receive a user command, we next want to know if we should ask for a password. If the m_loginRequired + // flag is set then we do indeed want a password and will send the response that we wish one. + + std::string userName; + ss >> userName; + ESP_LOGD(LOG_TAG, ">> onUser: userName=%s", userName.c_str()); + if (m_loginRequired) { + sendResponse(FTPServer::RESPONSE_331_PASSWORD_REQUIRED); + } else { + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); // Command okay. + } + m_suppliedUserid = userName; // Save the username that was supplied. + ESP_LOGD(LOG_TAG, "<< onUser"); +} // FTPServer#onUser + + +void FTPServer::onXmkd(std::istringstream &ss) { + ESP_LOGD(LOG_TAG, ">> onXmkd"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + ESP_LOGD(LOG_TAG, "<< onXmkd"); +} // FTPServer#onXmkd + + +void FTPServer::onXrmd(std::istringstream &ss) { + ESP_LOGD(LOG_TAG, ">> onXrmd"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + ESP_LOGD(LOG_TAG, "<< onXrmd"); +} // FTPServer#onXrmd + + +/** + * Open a data connection with the client. + * We will use closeData() to close the connection. + * @return True if the data connection succeeded. + */ +bool FTPServer::openData() { + if (m_isPassive) { + // Handle a passive connection ... here we receive a connection from the client from the passive socket. + struct sockaddr_in clientAddress; + socklen_t clientAddressLength = sizeof(clientAddress); + m_dataSocket = accept(m_passiveSocket, (struct sockaddr *)&clientAddress, &clientAddressLength); + if (m_dataSocket == -1) { + ESP_LOGD(LOG_TAG, "FTPServer::openData: accept(): %s", strerror(errno)); + closePassive(); + return false; + } + closePassive(); + } else { + // Handle an active connection ... here we connect to the client. + m_dataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(m_dataIp); + serverAddress.sin_port = htons(m_dataPort); + + int rc = connect(m_dataSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "FTPServer::openData: connect(): %s", strerror(errno)); + return false; + } + } + return true; +} // FTPServer#openData + + +/** + * Process commands received from the client. + */ +void FTPServer::processCommand() { + sendResponse(FTPServer::RESPONSE_220_SERVICE_READY); // Service ready. + ESP_LOGD(LOG_TAG, ">> FTPServer::processCommand"); + m_lastCommand = ""; + while(1) { + std::string line = ""; + char currentChar; + char lastChar = '\0'; + int rc = recv(m_clientSocket, ¤tChar, 1, 0); + while(rc != -1 && rc!=0) { + line += currentChar; + if (lastChar == '\r' && currentChar == '\n') { + break; + } + //printf("%c\n", currentChar); + lastChar = currentChar; + rc = recv(m_clientSocket, ¤tChar, 1, 0); + } // End while we are waiting for a line. + + if (rc == 0 || rc == -1) { // If we didn't get a line or an error, then we have finished processing commands. + break; + } + + std::string command; + std::istringstream ss(line); + getline(ss, command, ' '); + trim(command); + + // We now have a command to process. + + ESP_LOGD(LOG_TAG, "Command: \"%s\"", command.c_str()); + if (command.compare("USER")==0) { + onUser(ss); + } + else if (command.compare("PASS")==0) { + onPass(ss); + } + else if (m_loginRequired && !m_isAuthenticated) { + sendResponse(RESPONSE_530_NOT_LOGGED_IN); + } + else if (command.compare("PASV")==0) { + onPasv(ss); + } + else if (command.compare("SYST")==0) { + onSyst(ss); + } + else if (command.compare("PORT")==0) { + onPort(ss); + } + else if (command.compare("LIST")==0) { + onList(ss); + } + else if (command.compare("TYPE")==0) { + onType(ss); + } + else if (command.compare("RETR")==0) { + onRetr(ss); + } + else if (command.compare("QUIT")==0) { + onQuit(ss); + } + else if (command.compare("AUTH")==0) { + onAuth(ss); + } + else if (command.compare("STOR")==0) { + onStor(ss); + } + else if (command.compare("PWD")==0) { + onPWD(ss); + } + else if (command.compare("MKD")==0) { + onMkd(ss); + } + else if (command.compare("XMKD")==0) { + onXmkd(ss); + } + else if (command.compare("RMD")==0) { + onRmd(ss); + } + else if (command.compare("XRMD")==0) { + onXrmd(ss); + } + else if (command.compare("CWD")==0) { + onCwd(ss); + } + else { + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); // Syntax error, command unrecognized. + } + m_lastCommand = command; + } // End loop processing commands. + + close(m_clientSocket); // We won't be processing any further commands from this client. + ESP_LOGD(LOG_TAG, "<< FTPServer::processCommand"); +} // FTPServer::processCommand + + +/** + * Receive a file from the FTP client (STOR). The name of the file to be created is passed as a + * parameter. + */ +void FTPServer::receiveFile(std::string fileName) { + ESP_LOGD(LOG_TAG, ">> receiveFile: %s", fileName.c_str()); + if (m_callbacks != nullptr) { + try { + m_callbacks->onStoreStart(fileName); + } catch(FTPServer::FileException& e) { + ESP_LOGD(LOG_TAG, "Caught a file exception!"); + sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. + return; + } + } + openData(); + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + uint8_t buf[m_chunkSize]; + uint32_t totalSizeRead = 0; + while(1) { + int rc = recv(m_dataSocket, &buf, m_chunkSize, 0); + if (rc <= 0) { + break; + } + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveData(buf, rc); + } + totalSizeRead += rc; + } + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + closeData(); + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveEnd(); + } + ESP_LOGD(LOG_TAG, "<< receiveFile: totalSizeRead=%d", totalSizeRead); +} // FTPServer#receiveFile + + +/** + * Send data to the client over the data connection previously opened with a call to openData(). + * @param pData A pointer to the data to send. + * @param size The number of bytes to send. + */ +void FTPServer::sendData(uint8_t* pData, uint32_t size) { + ESP_LOGD(LOG_TAG, ">> FTPServer::sendData: size=%d", size); + int rc = send(m_dataSocket, pData, size, 0); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "FTPServer::sendData: send(): %s", strerror(errno)); + } + ESP_LOGD(LOG_TAG, "<< FTPServer::sendData"); +} // FTPServer#sendData + + +/** + * Send a response to the client. A response is composed of two parts. The first is a code as architected in the + * FTP specification. The second is a piece of text. + */ +void FTPServer::sendResponse(int code, std::string text) { + ESP_LOGD(LOG_TAG, ">> sendResponse: (%d) %s", code, text.c_str()); + std::ostringstream ss; + ss << code << " " << text << "\r\n"; + int rc = send(m_clientSocket, ss.str().data(), ss.str().length(), 0); + if (rc == -1) { + ESP_LOGE(LOG_TAG,"send: %s", strerror(errno)); + } + ESP_LOGD(LOG_TAG, "<< sendResponse"); +} // FTPServer#sendResponse + + +/** + * Send a response to the client. A response is composed of two parts. The first is a code as architected in the + * FTP specification. The second is a piece of text. In this function, a standard piece of text is used based on + * the code. + */ +void FTPServer::sendResponse(int code) { + std::string text = "unknown"; + + switch(code) { // Map the code to a text string. + case RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION: + text = "File status okay; about to open data connection."; + break; + case RESPONSE_200_COMMAND_OK: + text = "Command okay."; + break; + case RESPONSE_220_SERVICE_READY: + text = "Service ready."; + break; + case RESPONSE_221_CLOSING_CONTROL_CONNECTION: + text = "Service closing control connection."; + break; + case RESPONSE_226_CLOSING_DATA_CONNECTION: + text = "Closing data connection."; + break; + case RESPONSE_230_USER_LOGGED_IN: + text = "User logged in, proceed."; + break; + case RESPONSE_331_PASSWORD_REQUIRED: + text = "Password required."; + break; + case RESPONSE_500_COMMAND_UNRECOGNIZED: + text = "Syntax error, command unrecognized."; + break; + case RESPONSE_502_COMMAND_NOT_IMPLEMENTED: + text = "Command not implemented."; + break; + case RESPONSE_503_BAD_SEQUENCE: + text = "Bad sequence of commands."; + break; + case RESPONSE_530_NOT_LOGGED_IN: + text = "Not logged in."; + break; + case RESPONSE_550_ACTION_NOT_TAKEN: + text = "Requested action not taken."; + break; + default: + break; + } + sendResponse(code, text); // Send the code AND the text to the FTP client. +} // FTPServer#sendResponse + + +/** + * Set the callbacks that are to be invoked to perform work. + * @param pCallbacks An instance of an FTPCallbacks based class. + */ +void FTPServer::setCallbacks(FTPCallbacks* pCallbacks) { + m_callbacks = pCallbacks; +} // FTPServer#setCallbacks + + +void FTPServer::setCredentials(std::string userid, std::string password) { + ESP_LOGD(LOG_TAG, ">> setCredentials: userid=%s", userid.c_str()); + m_loginRequired = true; + m_userid = userid; + m_password = password; + ESP_LOGD(LOG_TAG, "<< setCredentials"); +} // FTPServer#setCredentials + + +/** + * Set the TCP port we should listen on for FTP client requests. + */ +void FTPServer::setPort(uint16_t port) { + m_port = port; +} // FTPServer#setPort + + +/** + * Start being an FTP Server. + */ +void FTPServer::start() { + m_serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_serverSocket == -1) { + ESP_LOGD(LOG_TAG, "socket: %s", strerror(errno)); + } + + int enable = 1; + setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(m_port); + int rc = bind(m_serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "bind: %s", strerror(errno)); + } + rc = listen(m_serverSocket, 5); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "listen: %s", strerror(errno)); + } + while(1) { + waitForFTPClient(); + processCommand(); + } +} // FTPServer#start + + +/** + * Wait for a new client to connect. + */ +int FTPServer::waitForFTPClient() { + ESP_LOGD(LOG_TAG, ">> FTPServer::waitForFTPClient"); + + struct sockaddr_in clientAddress; + socklen_t clientAddressLength = sizeof(clientAddress); + m_clientSocket = accept(m_serverSocket, (struct sockaddr *)&clientAddress, &clientAddressLength); + + char ipAddr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &clientAddress.sin_addr, ipAddr, sizeof(ipAddr)); + ESP_LOGD(LOG_TAG, "Received connection from %s [%d]", ipAddr, clientAddress.sin_port); + + struct sockaddr_in socketAddressInfo; + unsigned int socketAddressInfoSize = sizeof(socketAddressInfo); + getsockname(m_clientSocket, (struct sockaddr*)&socketAddressInfo, &socketAddressInfoSize); + + inet_ntop(AF_INET, &socketAddressInfo.sin_addr, ipAddr, sizeof(ipAddr)); + ESP_LOGD(LOG_TAG, "Connected at %s [%d]", ipAddr, socketAddressInfo.sin_port); + ESP_LOGD(LOG_TAG, "<< FTPServer::waitForFTPClient: fd=%d\n", m_clientSocket); + + return m_clientSocket; +} // FTPServer::waitForFTPClient + diff --git a/cpp_utils/FTPServer.h b/cpp_utils/FTPServer.h new file mode 100644 index 00000000..7dc56f13 --- /dev/null +++ b/cpp_utils/FTPServer.h @@ -0,0 +1,137 @@ +/* + * FTPServer.h + * + * Created on: May 6, 2018 + * Author: kolban + */ + +#ifndef NETWORKING_FTPSERVER_FTPSERVER_H_ +#define NETWORKING_FTPSERVER_FTPSERVER_H_ +#include +#include +#include +#include +#include + + +class FTPCallbacks { +public: + virtual void onStoreStart(std::string fileName); + virtual size_t onStoreData(uint8_t* data, size_t size); + virtual void onStoreEnd(); + virtual void onRetrieveStart(std::string fileName); + virtual size_t onRetrieveData(uint8_t *data, size_t size); + virtual void onRetrieveEnd(); + virtual std::string onDir(); + virtual ~FTPCallbacks(); +}; + +/** + * An implementation of FTPCallbacks that uses Posix File I/O to perform file access. + */ +class FTPFileCallbacks : public FTPCallbacks { +private: + std::ofstream m_storeFile; // File used to store data from the client. + std::ifstream m_retrieveFile; // File used to retrieve data for the client. + uint32_t m_byteCount; // Count of bytes sent over wire. +public: + void onStoreStart(std::string fileName) override; // Called for a STOR request. + size_t onStoreData(uint8_t* data, size_t size) override; // Called when a chunk of STOR data becomes available. + void onStoreEnd() override; // Called at the end of a STOR request. + void onRetrieveStart(std::string fileName) override; // Called at the start of a RETR request. + size_t onRetrieveData(uint8_t* data, size_t size) override; // Called to retrieve a chunk of RETR data. + void onRetrieveEnd() override; // Called when we have retrieved all the data. + std::string onDir() override; // Called to retrieve all the directory entries. +}; + + +class FTPServer { +private: + int m_serverSocket; // The socket the FTP server is listening on. + int m_clientSocket; // The current client socket. + int m_dataSocket; // The data socket. + int m_passiveSocket; // The socket on which the server is listening for passive FTP connections. + uint16_t m_port; // The port the FTP server will use. + uint16_t m_dataPort; // The port for data connections. + uint32_t m_dataIp; // The ip address for data connections. + bool m_isPassive; // Are we in passive mode? If not, then we are in active mode. + bool m_isImage; // Are we in image mode? + size_t m_chunkSize; // The maximum chunk size. + std::string m_userid; // The required userid. + std::string m_password; // The required password. + std::string m_suppliedUserid; // The userid supplied from the USER command. + bool m_loginRequired; // Do we required a login? + bool m_isAuthenticated; // Have we authenticated? + std::string m_lastCommand; // The last command that was processed. + + FTPCallbacks* m_callbacks; // The callbacks for processing. + + void closeConnection(); + void closeData(); + void closePassive(); + void onAuth(std::istringstream& ss); + void onCwd(std::istringstream& ss); + void onList(std::istringstream& ss); + void onMkd(std::istringstream& ss); + void onNoop(std::istringstream& ss); + void onPass(std::istringstream& ss); + void onPasv(std::istringstream& ss); + void onPort(std::istringstream& ss); + void onPWD(std::istringstream& ss); + void onQuit(std::istringstream& ss); + void onRetr(std::istringstream& ss); + void onRmd(std::istringstream& ss); + void onStor(std::istringstream& ss); + void onSyst(std::istringstream& ss); + void onType(std::istringstream& ss); + void onUser(std::istringstream& ss); + void onXmkd(std::istringstream& ss); + void onXrmd(std::istringstream& ss); + + bool openData(); + + void receiveFile(std::string fileName); + void sendResponse(int code); + void sendResponse(int code, std::string text); + void sendData(uint8_t* pData, uint32_t size); + std::string listenPassive(); + int waitForFTPClient(); + void processCommand(); + +public: + FTPServer(); + virtual ~FTPServer(); + void setCredentials(std::string userid, std::string password); + void start(); + void setPort(uint16_t port); + void setCallbacks(FTPCallbacks* pFTPCallbacks); + static std::string getCurrentDirectory(); + class FileException: public std::exception { + + }; + + // Response codes. + static const int RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION = 150; + static const int RESPONSE_200_COMMAND_OK = 200; + static const int RESPONSE_202_COMMAND_NOT_IMPLEMENTED = 202; + static const int RESPONSE_212_DIRECTORY_STATUS = 212; + static const int RESPONSE_213_FILE_STATUS = 213; + static const int RESPONSE_214_HELP_MESSAGE = 214; + static const int RESPONSE_220_SERVICE_READY = 220; + static const int RESPONSE_221_CLOSING_CONTROL_CONNECTION = 221; + static const int RESPONSE_230_USER_LOGGED_IN = 230; + static const int RESPONSE_226_CLOSING_DATA_CONNECTION = 226; + static const int RESPONSE_227_ENTERING_PASSIVE_MODE = 227; + static const int RESPONSE_331_PASSWORD_REQUIRED = 331; + static const int RESPONSE_332_NEED_ACCOUNT = 332; + static const int RESPONSE_500_COMMAND_UNRECOGNIZED = 500; + static const int RESPONSE_502_COMMAND_NOT_IMPLEMENTED = 502; + static const int RESPONSE_503_BAD_SEQUENCE = 503; + static const int RESPONSE_530_NOT_LOGGED_IN = 530; + static const int RESPONSE_550_ACTION_NOT_TAKEN = 550; + static const int RESPONSE_553_FILE_NAME_NOT_ALLOWED = 553; +}; + + + +#endif /* NETWORKING_FTPSERVER_FTPSERVER_H_ */ diff --git a/cpp_utils/GPIO.cpp b/cpp_utils/GPIO.cpp index 4188d307..23cf6fb7 100644 --- a/cpp_utils/GPIO.cpp +++ b/cpp_utils/GPIO.cpp @@ -9,6 +9,7 @@ #include #include "sdkconfig.h" #include +#include #include "GeneralUtils.h" static const char* LOG_TAG = "GPIO"; From 631b53cc790816565a1def3f76962594a0cdb7ea Mon Sep 17 00:00:00 2001 From: FedericoBusero <35894905+FedericoBusero@users.noreply.github.com> Date: Thu, 17 May 2018 22:45:59 +0200 Subject: [PATCH 282/381] Add Service UUID advertising (iOS support) Lots of apps (e.g. Blynk, RemoteXY) do not find the device during scanning on iOS, whereas other devices with the same service UUID are found. The origin of the problem is that in the advertisment, the service UUID is missing. This extra line fixes this problem. --- cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino index 35b570b9..ec014db7 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino @@ -97,6 +97,7 @@ void setup() { pService->start(); // Start advertising + pServer->getAdvertising()->addServiceUUID(pService->getUUID()); pServer->getAdvertising()->start(); Serial.println("Waiting a client connection to notify..."); } From 984a7aba4253fb29e25f2022e909f17adc447d22 Mon Sep 17 00:00:00 2001 From: olehs Date: Sun, 20 May 2018 15:59:04 +0300 Subject: [PATCH 283/381] reset m_haveServices in clearServices() #522 --- cpp_utils/BLEClient.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 57ff4d21..f4524980 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -77,6 +77,7 @@ void BLEClient::clearServices() { delete myPair.second; } m_servicesMap.clear(); + m_haveServices = false; ESP_LOGD(LOG_TAG, "<< clearServices"); } // clearServices From 70dbad12b9ca48b9763c851bf48cb4e337bc8355 Mon Sep 17 00:00:00 2001 From: Oleg Date: Sun, 20 May 2018 21:10:13 +0300 Subject: [PATCH 284/381] Call give() to all m_semaphore***CmplEvt on disconnect This allows to avoid infinit lock in situations, where DISCONNECT occurs during long calls --- cpp_utils/BLEClient.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index f4524980..141cf0f5 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -167,6 +167,8 @@ void BLEClient::gattClientEventHandler( m_pClientCallbacks->onDisconnect(this); } m_isConnected = false; + m_semaphoreRssiCmplEvt.give(); + m_semaphoreSearchCmplEvt.give(1); break; } // ESP_GATTC_DISCONNECT_EVT @@ -214,7 +216,7 @@ void BLEClient::gattClientEventHandler( // - uint16_t conn_id // case ESP_GATTC_SEARCH_CMPL_EVT: { - m_semaphoreSearchCmplEvt.give(); + m_semaphoreSearchCmplEvt.give(0); break; } // ESP_GATTC_SEARCH_CMPL_EVT @@ -367,8 +369,8 @@ std::map* BLEClient::getServices() { ESP_LOGE(LOG_TAG, "esp_ble_gattc_search_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return &m_servicesMap; } - m_semaphoreSearchCmplEvt.wait("getServices"); - m_haveServices = true; // Remember that we now have services. + // If sucessfull, remember that we now have services. + m_haveServices = (m_semaphoreSearchCmplEvt.wait("getServices") == 0); ESP_LOGD(LOG_TAG, "<< getServices"); return &m_servicesMap; } // getServices From 0d6744244c5701d9d3acdfdfc0a5bf0441ae0b86 Mon Sep 17 00:00:00 2001 From: chegewara Date: Fri, 25 May 2018 23:54:50 +0200 Subject: [PATCH 285/381] multiple services with the same uuid --- cpp_utils/BLEServer.cpp | 9 +++++---- cpp_utils/BLEServer.h | 5 ++++- cpp_utils/BLEService.cpp | 5 +++-- cpp_utils/BLEService.h | 1 + cpp_utils/BLEServiceMap.cpp | 36 ++++++++++++++++++++++++++++++++---- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index c33afdf2..ad323477 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -75,15 +75,16 @@ BLEService* BLEServer::createService(BLEUUID uuid, uint32_t numHandles) { ESP_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str()); m_semaphoreCreateEvt.take("createService"); + BLEService* pService = new BLEService(uuid, numHandles); // Check that a service with the supplied UUID does not already exist. if (m_serviceMap.getByUUID(uuid) != nullptr) { - ESP_LOGE(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.", + ESP_LOGW(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.", uuid.toString().c_str()); - m_semaphoreCreateEvt.give(); - return nullptr; + //m_semaphoreCreateEvt.give(); + //return nullptr; + pService->m_id = 1; } - BLEService* pService = new BLEService(uuid, numHandles); m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server. pService->executeCreate(this); // Perform the API calls to actually create the service. diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index bc0ef05d..e21deeef 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -40,10 +40,13 @@ class BLEServiceMap { void setByUUID(const char* uuid, BLEService* service); void setByUUID(BLEUUID uuid, BLEService* service); std::string toString(); + BLEService* getFirst(); + BLEService* getNext(); private: - std::map m_uuidMap; std::map m_handleMap; + std::map m_uuidMap; + std::map::iterator m_iterator; }; diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 4e59c4a4..4a427fbb 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -73,7 +73,7 @@ void BLEService::executeCreate(BLEServer *pServer) { esp_gatt_srvc_id_t srvc_id; srvc_id.is_primary = true; - srvc_id.id.inst_id = 0; + srvc_id.id.inst_id = m_id; srvc_id.id.uuid = *m_uuid.getNative(); esp_err_t errRc = ::esp_ble_gatts_create_service( getServer()->getGattsIf(), @@ -292,7 +292,8 @@ void BLEService::handleGATTServerEvent( // * - bool is_primary // case ESP_GATTS_CREATE_EVT: { - if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid))) { + ESP_LOGE(LOG_TAG, "%d", param->create.service_handle); + if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid)) && m_id == param->create.service_id.id.inst_id) { setHandle(param->create.service_handle); m_semaphoreCreateEvt.give(); } diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 2b78e620..79958f3d 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -64,6 +64,7 @@ class BLEService { void start(); std::string toString(); uint16_t getHandle(); + uint8_t m_id = 0; private: BLEService(const char* uuid, uint32_t numHandles); diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index 8fdbd5ac..96811ad2 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -27,8 +27,8 @@ BLEService* BLEServiceMap::getByUUID(const char* uuid) { */ BLEService* BLEServiceMap::getByUUID(BLEUUID uuid) { for (auto &myPair : m_uuidMap) { - if (myPair.second->getUUID().equals(uuid)) { - return myPair.second; + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; } } //return m_uuidMap.at(uuid.toString()); @@ -54,7 +54,7 @@ BLEService* BLEServiceMap::getByHandle(uint16_t handle) { */ void BLEServiceMap::setByUUID(BLEUUID uuid, BLEService *service) { - m_uuidMap.insert(std::pair(uuid.toString(), service)); + m_uuidMap.insert(std::pair(service, uuid.toString())); } // setByUUID @@ -89,7 +89,35 @@ void BLEServiceMap::handleGATTServerEvent( esp_ble_gatts_cb_param_t *param) { // Invoke the handler for every Service we have. for (auto &myPair : m_uuidMap) { - myPair.second->handleGATTServerEvent(event, gatts_if, param); + myPair.first->handleGATTServerEvent(event, gatts_if, param); } } + +/** + * @brief Get the first service in the map. + * @return The first service in the map. + */ +BLEService* BLEServiceMap::getFirst() { + m_iterator = m_uuidMap.begin(); + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } + BLEService* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getFirst + +/** + * @brief Get the next service in the map. + * @return The next service in the map. + */ +BLEService* BLEServiceMap::getNext() { + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } + BLEService* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getNext + #endif /* CONFIG_BT_ENABLED */ From eb836aaff47607b32da1d6de07942568a9748ee9 Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 26 May 2018 02:59:22 +0200 Subject: [PATCH 286/381] multiple services with the same uuid --- cpp_utils/BLEServer.cpp | 7 ++++--- cpp_utils/BLEServer.h | 4 ++-- cpp_utils/BLEService.cpp | 1 - 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index ad323477..bc079813 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -69,22 +69,23 @@ BLEService* BLEServer::createService(const char* uuid) { * of a new service. Every service must have a unique UUID. * @param [in] uuid The UUID of the new service. * @param [in] numHandles The maximum number of handles associated with this service. + * @param [in] inst_id With multiple services with the same UUID we need to provide inst_id value different for each service. * @return A reference to the new service object. */ -BLEService* BLEServer::createService(BLEUUID uuid, uint32_t numHandles) { +BLEService* BLEServer::createService(BLEUUID uuid, uint32_t numHandles, uint8_t inst_id) { ESP_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str()); m_semaphoreCreateEvt.take("createService"); - BLEService* pService = new BLEService(uuid, numHandles); // Check that a service with the supplied UUID does not already exist. if (m_serviceMap.getByUUID(uuid) != nullptr) { ESP_LOGW(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.", uuid.toString().c_str()); //m_semaphoreCreateEvt.give(); //return nullptr; - pService->m_id = 1; } + BLEService* pService = new BLEService(uuid, numHandles); + pService->m_id = inst_id; m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server. pService->executeCreate(this); // Perform the API calls to actually create the service. diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index e21deeef..d53510d6 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -57,10 +57,11 @@ class BLEServer { public: uint32_t getConnectedCount(); BLEService* createService(const char* uuid); - BLEService* createService(BLEUUID uuid, uint32_t numHandles=15); + BLEService* createService(BLEUUID uuid, uint32_t numHandles=15, uint8_t inst_id=0); BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); + uint16_t getGattsIf(); private: @@ -81,7 +82,6 @@ class BLEServer { void createApp(uint16_t appId); uint16_t getConnId(); - uint16_t getGattsIf(); void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); void registerApp(); diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 4a427fbb..6636ca5c 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -292,7 +292,6 @@ void BLEService::handleGATTServerEvent( // * - bool is_primary // case ESP_GATTS_CREATE_EVT: { - ESP_LOGE(LOG_TAG, "%d", param->create.service_handle); if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid)) && m_id == param->create.service_id.id.inst_id) { setHandle(param->create.service_handle); m_semaphoreCreateEvt.give(); From b059dad94f861620dc6a7cda1bb1f9ed331a61e6 Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 26 May 2018 03:03:16 +0200 Subject: [PATCH 287/381] multiple services with the same uuid --- cpp_utils/BLEServer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index d53510d6..6c71679a 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -61,7 +61,6 @@ class BLEServer { BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); - uint16_t getGattsIf(); private: @@ -82,6 +81,7 @@ class BLEServer { void createApp(uint16_t appId); uint16_t getConnId(); + uint16_t getGattsIf(); void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); void registerApp(); From db1d45e19fcf0e3e6c32bd1fa232fb79aeebbde2 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 26 May 2018 15:22:14 -0500 Subject: [PATCH 288/381] Addition of console samples. --- console/main.cpp | 14 + console/test_argtable.cpp | 69 +++ console/test_console.cpp | 95 +++ console/test_linenoise.cpp | 40 ++ eclipse/c_includes.xml | 1 + networking/FTPServer/FTPCallbacks.cpp | 154 +++++ networking/FTPServer/FTPServer.cpp | 819 ++++++++++++++++++++++++++ networking/FTPServer/FTPServer.h | 137 +++++ networking/FTPServer/README.md | 76 +++ networking/FTPServer/main.cpp | 12 + 10 files changed, 1417 insertions(+) create mode 100644 console/main.cpp create mode 100644 console/test_argtable.cpp create mode 100644 console/test_console.cpp create mode 100644 console/test_linenoise.cpp create mode 100644 networking/FTPServer/FTPCallbacks.cpp create mode 100644 networking/FTPServer/FTPServer.cpp create mode 100644 networking/FTPServer/FTPServer.h create mode 100644 networking/FTPServer/README.md create mode 100644 networking/FTPServer/main.cpp diff --git a/console/main.cpp b/console/main.cpp new file mode 100644 index 00000000..f5715f5d --- /dev/null +++ b/console/main.cpp @@ -0,0 +1,14 @@ +extern "C" { + void app_main(void); +} + +extern void test_linenoise(); +extern void test_argtable(); +extern void test_console(); + +void app_main(void) +{ + //test_linenoise(); + //test_argtable(); + test_console(); +} diff --git a/console/test_argtable.cpp b/console/test_argtable.cpp new file mode 100644 index 00000000..160b728c --- /dev/null +++ b/console/test_argtable.cpp @@ -0,0 +1,69 @@ +/** + * Test of argtable + * + * @author Neil Kolban + * @date 2018-05-26 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" + +void test_argtable(void) +{ + // Boiler plate setup for using linenoise + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + uart_driver_install((uart_port_t)CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + + linenoiseClearScreen(); + linenoiseSetMultiLine(0); + + struct arg_lit* help = arg_lit0("h", "help", "Generate some help!"); + struct arg_lit* test = arg_lit1("t", "test", "Generate some help!"); + struct arg_end* end = arg_end(20); + + void *argtable[] = { + help, + test, + end + }; + + char *argv[10]; + + while(1) { + char* line = linenoise("Enter command > "); + if (line != NULL) { + printf("Got: %s\n", line); + size_t argc = esp_console_split_argv(line, argv, 10); + printf("Parsed to %d argc count\n", argc); + for (int i=0; i0) { + printf("Number of errors: %d\n", numErrors); + arg_print_errors(stdout, end, "myprog"); + arg_print_syntaxv(stdout, argtable, "Here:"); + } + else { + if (help->count > 0) { + printf("Found help!\n"); + } + } + linenoiseHistoryAdd(line); + linenoiseFree(line); + } // Line is not null + printf("--------------\n"); + } // End while loop +} diff --git a/console/test_console.cpp b/console/test_console.cpp new file mode 100644 index 00000000..20c240ad --- /dev/null +++ b/console/test_console.cpp @@ -0,0 +1,95 @@ +/** + * Test of console + * + * @author Neil Kolban + * @date 2018-05-26 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" + + +struct arg_lit* show_arg_show = arg_lit0("s", "show", "generate show"); +struct arg_end* show_arg_end = arg_end(10); +void *show_argtable[] = { + show_arg_show, + show_arg_end +}; + +int runShow(int argc, char *argv[]) { + printf("Found show!\n"); + return 0; +} + +struct arg_str* greet_arg_name = arg_str1("n", "name", "", "generate help"); +struct arg_end* greet_arg_end = arg_end(10); +void *greet_argtable[] = { + greet_arg_name, + greet_arg_end +}; + +int runGreet(int argc, char *argv[]) { + printf("Found Greet!\n"); + int numErrors = arg_parse(argc, argv, greet_argtable); + if (numErrors > 0) { + arg_print_errors(stdout, greet_arg_end, "greet"); + } else { + printf("Hello %s\n", greet_arg_name->sval[0]); + } + return 0; +} + +void test_console(void) +{ + // Boiler plate setup for using linenoise + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + uart_driver_install((uart_port_t)CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + esp_console_config_t consoleConfig; + consoleConfig.max_cmdline_args = 5; + consoleConfig.max_cmdline_length = 100; + + esp_console_init(&consoleConfig); + esp_console_register_help_command(); + + esp_console_cmd_t consoleCmd; + consoleCmd.command = "show"; + consoleCmd.func = runShow; + consoleCmd.help = "Show something"; + consoleCmd.argtable = show_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "greet"; + consoleCmd.func = runGreet; + consoleCmd.help = "Greet someone"; + consoleCmd.argtable = greet_argtable; + esp_console_cmd_register(&consoleCmd); + + + linenoiseClearScreen(); + linenoiseSetMultiLine(0); + + + while(1) { + char* line = linenoise("Enter command > "); + if (line != NULL) { + printf("Got: %s\n", line); + int ret; + esp_console_run(line, &ret); + linenoiseHistoryAdd(line); + linenoiseFree(line); + } // Line is not null + printf("--------------\n"); + } // End while loop +} diff --git a/console/test_linenoise.cpp b/console/test_linenoise.cpp new file mode 100644 index 00000000..b2d36cf4 --- /dev/null +++ b/console/test_linenoise.cpp @@ -0,0 +1,40 @@ +/** + * Test of linenoise + * + * @author Neil Kolban + * @date 2018-05-26 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" + +void test_linenoise(void) +{ + // Boiler plate setup for using linenoise + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + uart_driver_install((uart_port_t)CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + + linenoiseClearScreen(); + linenoiseSetMultiLine(0); + + while(1) { + char* line = linenoise("Enter command > "); + if (line != NULL) { + printf("Got: %s\n", line); + linenoiseHistoryAdd(line); + linenoiseFree(line); + } // Line is not null + printf("--------------\n"); + } // End while loop +} diff --git a/eclipse/c_includes.xml b/eclipse/c_includes.xml index ee8feb85..eaa53ba2 100644 --- a/eclipse/c_includes.xml +++ b/eclipse/c_includes.xml @@ -74,6 +74,7 @@ ${IDF_PATH}/components/aws_iot/aws-iot-device-sdk-embedded-C/include ${IDF_PATH}/components/fatfs/src ${IDF_PATH}/components/wear_levelling/include +${IDF_PATH}/components/console diff --git a/networking/FTPServer/FTPCallbacks.cpp b/networking/FTPServer/FTPCallbacks.cpp new file mode 100644 index 00000000..99a6e111 --- /dev/null +++ b/networking/FTPServer/FTPCallbacks.cpp @@ -0,0 +1,154 @@ +#include "FTPServer.h" +#include +#include +#include + + +/** + * Called at the start of a STOR request. The file name is the name of the file the client would like to + * save. + */ +void FTPFileCallbacks::onStoreStart(std::string fileName) { + printf(">> FTPFileCallbacks::onStoreStart: fileName=%s\n", fileName.c_str()); + m_storeFile.open(fileName, std::ios::binary); // Open the file for writing. + if (m_storeFile.fail()) { + throw FTPServer::FileException(); + } + printf("<< FTPFileCallbacks::onStoreStart\n"); +} // FTPFileCallbacks#onStoreStart + + +/** + * Called when the client presents a new chunk of data to be saved. + */ +size_t FTPFileCallbacks::onStoreData(uint8_t* data, size_t size) { + printf(">> FTPFileCallbacks::onStoreData: size=%ld\n", size); + m_storeFile.write((char *)data, size); // Store data received. + printf("<< FTPFileCallbacks::onStoreData: size=%ld\n", size); + return size; +} // FTPFileCallbacks#onStoreData + + +/** + * Called at the end of a STOR request. This indicates that the client has completed its transmission of the + * file. + */ +void FTPFileCallbacks::onStoreEnd() { + printf(">> FTPFileCallbacks::onStoreEnd\n"); + m_storeFile.close(); // Close the open file. + printf("<< FTPFileCallbacks::onStoreEnd\n"); +} // FTPFileCallbacks#onStoreEnd + + +/** + * Called when the client requests retrieval of a file. + */ +void FTPFileCallbacks::onRetrieveStart(std::string fileName) { + printf(">> FTPFileCallbacks::onRetrieveStart: fileName=%s\n", fileName.c_str()); + m_byteCount = 0; + m_retrieveFile.open(fileName, std::ios::binary); + if (m_retrieveFile.fail()) { + printf("<< FTPFileCallbacks::onRetrieveStart: ***FileException***\n"); + throw FTPServer::FileException(); + } + printf("<< FTPFileCallbacks::onRetrieveStart\n"); +} // FTPFileCallbacks#onRetrieveStart + + +/** + * Called when the client is ready to receive the next piece of the file. To indicate that there + * is no more data to send, return a size of 0. + * @param data The data buffer that we can fill to return data back to the client. + * @param size The maximum size of the data buffer that we can populate. + * @return The size of data being returned. Return 0 to indicate that there is no more data to return. + */ +size_t FTPFileCallbacks::onRetrieveData(uint8_t* data, size_t size) { + printf(">> FTPFileCallbacks::onRetrieveData\n"); + m_retrieveFile.read((char *)data, size); + size_t readSize = m_retrieveFile.gcount(); + m_byteCount += readSize; + printf("<< FTPFileCallbacks::onRetrieveData: sizeRead=%ld\n", readSize); + return m_retrieveFile.gcount(); // Return the number of bytes read. +} // FTPFileCallbacks#onRetrieveData + + +/** + * Called when the retrieval has been completed. + */ +void FTPFileCallbacks::onRetrieveEnd() { + printf(">> FTPFileCallbacks::onRetrieveEnd\n"); + m_retrieveFile.close(); + printf("<< FTPFileCallbacks::onRetrieveEnd: bytesTransmitted=%d\n", m_byteCount); +} // FTPFileCallbacks#onRetrieveEnd + + +/** + * Return a list of files in the file system. + * @return a list of files in the file system. + */ +std::string FTPFileCallbacks::onDir() { + + DIR* dir = opendir(FTPServer::getCurrentDirectory().c_str()); + std::stringstream ss; + while(1) { + struct dirent* pDirentry = readdir(dir); + if (pDirentry == nullptr) { + break; + } + ss << pDirentry->d_name << "\r\n"; + } + closedir(dir); + return ss.str(); +} // FTPFileCallbacks#onDir + + +/// ---- END OF FTPFileCallbacks + + +void FTPCallbacks::onStoreStart(std::string fileName) { + printf(">> FTPCallbacks::onStoreStart: fileName=%s\n", fileName.c_str()); + printf("<< FTPCallbacks::onStoreStart\n"); +} // FTPCallbacks#onStoreStart + + +size_t FTPCallbacks::onStoreData(uint8_t* data, size_t size) { + printf(">> FTPCallbacks::onStoreData: size=%ld\n", size); + printf("<< FTPCallbacks::onStoreData\n"); + return 0; +} // FTPCallbacks#onStoreData + + +void FTPCallbacks::onStoreEnd() { + printf(">> FTPCallbacks::onStoreEnd\n"); + printf("<< FTPCallbacks::onStoreEnd\n"); +} // FTPCallbacks#onStoreEnd + + +void FTPCallbacks::onRetrieveStart(std::string fileName) { + printf(">> FTPCallbacks::onRetrieveStart\n"); + printf("<< FTPCallbacks::onRetrieveStart\n"); +} // FTPCallbacks#onRetrieveStart + + +size_t FTPCallbacks::onRetrieveData(uint8_t *data, size_t size) { + printf(">> FTPCallbacks::onRetrieveData\n"); + printf("<< FTPCallbacks::onRetrieveData: 0\n"); + return 0; +} // FTPCallbacks#onRetrieveData + + +void FTPCallbacks::onRetrieveEnd() { +printf(">> FTPCallbacks::onRetrieveEnd\n"); +printf("<< FTPCallbacks::onRetrieveEnd\n"); +} // FTPCallbacks#onRetrieveEnd + + +std::string FTPCallbacks::onDir() { + printf(">> FTPCallbacks::onDir\n"); + printf("<< FTPCallbacks::onDir\n"); + return ""; +} // FTPCallbacks#onDir + +FTPCallbacks::~FTPCallbacks() { + +} // FTPCallbacks#~FTPCallbacks diff --git a/networking/FTPServer/FTPServer.cpp b/networking/FTPServer/FTPServer.cpp new file mode 100644 index 00000000..87899c3b --- /dev/null +++ b/networking/FTPServer/FTPServer.cpp @@ -0,0 +1,819 @@ +/* + * FTPServer.cpp + * + * Created on: May 6, 2018 + * Author: kolban + */ + +#include "FTPServer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// trim from start (in place) +static void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); +} // ltrim + + +// trim from end (in place) +static void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} // rtrim + + +// trim from both ends (in place) +static void trim(std::string &s) { + ltrim(s); + rtrim(s); +} // trim + + +FTPServer::FTPServer() { + printf(">> FTPServer()\n"); + + m_dataSocket = -1; + m_clientSocket = -1; + m_dataPort = -1; + m_dataIp = -1; + m_passiveSocket = -1; + m_serverSocket = -1; + + m_callbacks = nullptr; + m_isPassive = false; + m_isImage = true; + m_chunkSize = 4096; + m_port = 21; // The default Server-PI port + m_loginRequired = false; + m_isAuthenticated = false; + m_userid = ""; + m_password = ""; + + printf("<< FTPServer()\n"); +} // FTPServer#FTPServer + + +FTPServer::~FTPServer() { + // Nothing to do here by default. +} // FTPServer#~FTPServer + + +/** + * Close the connection to the FTP client. + */ +void FTPServer::closeConnection() { + printf(">> closeConnection\n"); + close(m_clientSocket); + printf("<< closeConnection\n"); +} // FTPServer#closeConnection + + +/** + * Close a previously opened data connection. + */ +void FTPServer::closeData() { + printf(">> closeData\n"); + close(m_dataSocket); + m_dataSocket = -1; + printf("<< closeData\n"); +} // FTPServer#closeData + + +/** + * Close the passive listening socket that was opened by listenPassive. + */ +void FTPServer::closePassive() { + printf(">> closePassive\n"); + close(m_passiveSocket); + m_passiveSocket = -1; + printf("<< closePassive\n"); +} // FTPServer#closePassive + + +/** + * Retrieve the current directory. + */ +/* STATIC */ std::string FTPServer::getCurrentDirectory() { + char maxDirectory[256]; + std::string currentDirectory = getcwd(maxDirectory, sizeof(maxDirectory)); + return currentDirectory; +} // FTPServer#getCurrentDirectory + + +/** + * Create a listening socket for the new passive connection. + * @return a String for the passive parameters. + */ +std::string FTPServer::listenPassive() { + printf(">> listenPassive\n"); + + m_passiveSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_passiveSocket == -1) { + printf("socket: %s", strerror(errno)); + } + + struct sockaddr_in clientAddrInfo; + unsigned int addrInfoSize = sizeof(clientAddrInfo); + getsockname(m_clientSocket, (struct sockaddr*)&clientAddrInfo, &addrInfoSize); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(0); + int rc = bind(m_passiveSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + if (rc == -1) { + printf("bind: %s\n", strerror(errno)); + } + + rc = listen(m_passiveSocket, 5); + if (rc == -1) { + printf("listen: %s", strerror(errno)); + } + + unsigned int addrLen = sizeof(serverAddress); + rc = getsockname(m_passiveSocket, (struct sockaddr*)&serverAddress, &addrLen); + if (rc == -1) { + printf("getsockname: %s\n", strerror(errno)); + } + + + std::stringstream ss; + ss << ((clientAddrInfo.sin_addr.s_addr >> 0) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 8) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 16) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 24) & 0xff) << + "," << ((serverAddress.sin_port >> 0) & 0xff) << + "," << ((serverAddress.sin_port >> 8) & 0xff); + std::string retStr = ss.str(); + + printf("<< listenPassive: %s\n", retStr.c_str()); + return retStr; +} // FTPServer#listenPassive + + +/** + * Handle the AUTH command. + */ +void FTPServer::onAuth(std::istringstream& ss) { + std::string param; + ss >> param; + printf(">> onAuth: %s\n", param.c_str()); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); // Syntax error, command unrecognized. + printf("<< onAuth\n"); +} // FTPServer#onAuth + + +/** + * Change the current working directory. + * @param ss A string stream where the first parameter is the directory to change to. + */ +void FTPServer::onCwd(std::istringstream& ss) { + std::string path; + ss >> path; + printf(">> onCwd: path=%s\n", path.c_str()); + chdir(path.c_str()); + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); + printf("<< onCwd\n"); +} // FTPServer#onCwd + + +/** + * Process the client transmitted LIST request. + */ +void FTPServer::onList(std::istringstream& ss) { + std::string directory; + ss >> directory; + printf(">> onList: directory=%s\n", directory.c_str()); + + openData(); + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + if (m_callbacks != nullptr) { + std::string dirString = m_callbacks->onDir(); + sendData((uint8_t *)dirString.data(), dirString.length()); + } + closeData(); + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + printf("<< onList\n"); +} // FTPServer#onList + + +void FTPServer::onMkd(std::istringstream &ss) { + std::string path; + ss >> path; + printf(">> onMkd: path=%s\n", path.c_str()); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + printf("<< onMkd\n"); +} // FTPServer#onMkd + + +/** + * Process a NOOP operation. + */ +void FTPServer::onNoop(std::istringstream& ss) { + printf(">> onNoop\n"); + sendResponse(RESPONSE_200_COMMAND_OK); // Command okay. + printf("<< onNoop\n"); +} // FTPServer#onNoop + + +/** + * Process PORT request. The information provided is encoded in the parameter as + * h1,h2,h3,h4,p1,p2 where h1,h2,h3,h4 is the IP address we should connect to + * and p1,p2 is the port number. The data is MSB + * + * Our logic does not form any connection but remembers the ip address and port number + * to be used for a subsequence data connection. + * + * Possible responses: + * 200 + * 500, 501, 421, 530 + */ +void FTPServer::onPort(std::istringstream& ss) { + printf(">> onPort\n"); + char c; + uint16_t h1, h2, h3, h4, p1, p2; + ss >> h1 >> c >> h2 >> c >> h3 >> c >> h4 >> c >> p1 >> c >> p2; + m_dataPort = p1*256 + p2; + printf("%d.%d.%d.%d %d\n", h1, h2, h3, h4, m_dataPort); + m_dataIp = h1<<24 | h2<<16 | h3<<8 | h4; + sendResponse(RESPONSE_200_COMMAND_OK); // Command okay. + m_isPassive = false; + + printf("<< onPort\n"); +} // FTPServer#onPort + + +/** + * Process the PASS command. + * Possible responses: + * 230 + * 202 + * 530 + * 500, 501, 503, 421 + * 332 + */ +void FTPServer::onPass(std::istringstream& ss) { + std::string password; + ss >> password; + printf(">> onPass: password=%s\n", password.c_str()); + + // If the immediate last command wasn't USER then don't try and process PASS. + if (m_lastCommand != "USER") { + sendResponse(RESPONSE_503_BAD_SEQUENCE); + printf("<< onPass\n"); + return; + } + + // Compare the supplied userid and passwords. + if (m_userid == m_suppliedUserid && password == m_password) { + sendResponse(RESPONSE_230_USER_LOGGED_IN); + m_isAuthenticated = true; + } else { + sendResponse(RESPONSE_530_NOT_LOGGED_IN); + closeConnection(); + m_isAuthenticated = false; + } + + + printf("<< onPass\n"); +} // FTPServer#onPass + + +/** + * Process the PASV command. + * Possible responses: + * 227 + * 500, 501, 502, 421, 530 + */ +void FTPServer::onPasv(std::istringstream& ss) { + printf(">> onPasv\n"); + std::string ipInfo = listenPassive(); + std::ostringstream responseTextSS; + responseTextSS << "Entering Passive Mode (" << ipInfo << ")."; + std::string responseText; + responseText = responseTextSS.str(); + sendResponse(RESPONSE_227_ENTERING_PASSIVE_MODE, responseText.c_str()); + m_isPassive = true; + + printf("<< onPasv\n"); +} // FTPServer#onPasv + + +/** + * Process the PWD command to determine our current working directory. + * Possible responses: + * 257 + * 500, 501, 502, 421, 550 + */ +void FTPServer::onPWD(std::istringstream& ss) { + printf(">> onPWD\n"); + sendResponse(257, "\"" + getCurrentDirectory() + "\""); + printf("<< onPWD: %s\n", getCurrentDirectory().c_str()); +} // FTPServer#onPWD + + +/** + * Possible responses: + * 221 + * 500 + */ +void FTPServer::onQuit(std::istringstream& ss) { + printf(">> onQuit\n"); + sendResponse(FTPServer::RESPONSE_221_CLOSING_CONTROL_CONNECTION); // Service closing control connection. + closeConnection(); // Close the connection to the client. + printf("<< onQuit\n"); +} // FTPServer#onQuit + + +/** + * Process a RETR command. The client sends this command to retrieve the content of a file. + * The name of the file is the first parameter in the input stream. + * + * Possible responses: + * 125, 150 + * (110) + * 226, 250 + * 425, 426, 451 + * 450, 550 + * 500, 501, 421, 530 + * @param ss The parameter stream. + */ +void FTPServer::onRetr(std::istringstream& ss) { + + // We open a data connection back to the client. We then invoke the callback to indicate that we have + // started a retrieve operation. We call the retrieve callback to request the next chunk of data and + // transmit this down the data connection. We repeat this until there is no more data to send at which + // point we close the data connection and we are done. + printf(">> onRetr\n"); + std::string fileName; + + ss >> fileName; + uint8_t data[m_chunkSize]; + + + if (m_callbacks != nullptr) { + try { + m_callbacks->onRetrieveStart(fileName); + } catch(FTPServer::FileException& e) { + sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. + printf("<< onRetr: Returned 550 to client.\n"); + return; + } + } + + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + openData(); + if (m_callbacks != nullptr) { + int readSize = m_callbacks->onRetrieveData(data, m_chunkSize); + while(readSize > 0) { + sendData(data, readSize); + readSize = m_callbacks->onRetrieveData(data, m_chunkSize); + } + } + closeData(); + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveEnd(); + } + printf("<< onRetr\n"); +} // FTPServer#onRetr + + +void FTPServer::onRmd(std::istringstream &ss) { + printf(">> onRmd\n"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + printf("<< onRmd\n"); +} // FTPServer#onRmd + + +/** + * Called to process a STOR request. This means that the client wishes to store a file + * on the server. The name of the file is found in the parameter. + */ +void FTPServer::onStor(std::istringstream& ss) { + printf(">> onStor\n"); + std::string fileName; + ss >> fileName; + + receiveFile(fileName); + printf("<< onStor\n"); +} // FTPServer#onStor + + +void FTPServer::onSyst(std::istringstream& ss) { + printf(">> onSyst\n"); + sendResponse(215, "UNIX Type: L8"); + printf("<< onSyst\n"); +} // FTPServer#onSyst + + +/** + * Process a TYPE request. The parameter that follows is the type of transfer we wish + * to process. Types include: + * I and A. + * + * Possible responses: + * 200 + * 500, 501, 504, 421, 530 + */ +void FTPServer::onType(std::istringstream& ss) { + printf(">> onType\n"); + std::string type; + ss >> type; + if (type.compare("I") == 0) { + m_isImage = true; + } else { + m_isImage = false; + } + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); // Command okay. + printf("<< onType: isImage=%d\n", m_isImage); +} // FTPServer#onType + + +/** + * Process a USER request. The parameter that follows is the identity of the user. + * + * Possible responses: + * 230 + * 530 + * 500, 501, 421 + * 331, 332 + * + */ +void FTPServer::onUser(std::istringstream& ss) { + // When we receive a user command, we next want to know if we should ask for a password. If the m_loginRequired + // flag is set then we do indeed want a password and will send the response that we wish one. + + std::string userName; + ss >> userName; + printf(">> onUser: userName=%s\n", userName.c_str()); + if (m_loginRequired) { + sendResponse(FTPServer::RESPONSE_331_PASSWORD_REQUIRED); + } else { + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); // Command okay. + } + m_suppliedUserid = userName; // Save the username that was supplied. + printf("<< onUser\n"); +} // FTPServer#onUser + + +void FTPServer::onXmkd(std::istringstream &ss) { + printf(">> onXmkd\n"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + printf("<< onXmkd\n"); +} // FTPServer#onXmkd + + +void FTPServer::onXrmd(std::istringstream &ss) { + printf(">> onXrmd\n"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + printf("<< onXrmd\n"); +} // FTPServer#onXrmd + + +/** + * Open a data connection with the client. + * We will use closeData() to close the connection. + * @return True if the data connection succeeded. + */ +bool FTPServer::openData() { + if (m_isPassive) { + // Handle a passive connection ... here we receive a connection from the client from the passive socket. + struct sockaddr_in clientAddress; + socklen_t clientAddressLength = sizeof(clientAddress); + m_dataSocket = accept(m_passiveSocket, (struct sockaddr *)&clientAddress, &clientAddressLength); + if (m_dataSocket == -1) { + printf("FTPServer::openData: accept(): %s\n", strerror(errno)); + closePassive(); + return false; + } + closePassive(); + } else { + // Handle an active connection ... here we connect to the client. + m_dataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(m_dataIp); + serverAddress.sin_port = htons(m_dataPort); + + int rc = connect(m_dataSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); + if (rc == -1) { + printf("FTPServer::openData: connect(): %s\n", strerror(errno)); + return false; + } + } + return true; +} // FTPServer#openData + + +/** + * Process commands received from the client. + */ +void FTPServer::processCommand() { + sendResponse(FTPServer::RESPONSE_220_SERVICE_READY); // Service ready. + printf(">> FTPServer::processCommand\n"); + m_lastCommand = ""; + while(1) { + std::string line = ""; + char currentChar; + char lastChar = '\0'; + int rc = recv(m_clientSocket, ¤tChar, 1, 0); + while(rc != -1 && rc!=0) { + line += currentChar; + if (lastChar == '\r' && currentChar == '\n') { + break; + } + printf("%c\n", currentChar); + lastChar = currentChar; + rc = recv(m_clientSocket, ¤tChar, 1, 0); + } // End while we are waiting for a line. + + if (rc == 0 || rc == -1) { // If we didn't get a line or an error, then we have finished processing commands. + break; + } + + std::string command; + std::istringstream ss(line); + getline(ss, command, ' '); + trim(command); + + // We now have a command to process. + + printf("Command: \"%s\"\n", command.c_str()); + if (command.compare("USER")==0) { + onUser(ss); + } + else if (command.compare("PASS")==0) { + onPass(ss); + } + else if (m_loginRequired && !m_isAuthenticated) { + sendResponse(RESPONSE_530_NOT_LOGGED_IN); + } + else if (command.compare("PASV")==0) { + onPasv(ss); + } + else if (command.compare("SYST")==0) { + onSyst(ss); + } + else if (command.compare("PORT")==0) { + onPort(ss); + } + else if (command.compare("LIST")==0) { + onList(ss); + } + else if (command.compare("TYPE")==0) { + onType(ss); + } + else if (command.compare("RETR")==0) { + onRetr(ss); + } + else if (command.compare("QUIT")==0) { + onQuit(ss); + } + else if (command.compare("AUTH")==0) { + onAuth(ss); + } + else if (command.compare("STOR")==0) { + onStor(ss); + } + else if (command.compare("PWD")==0) { + onPWD(ss); + } + else if (command.compare("MKD")==0) { + onMkd(ss); + } + else if (command.compare("XMKD")==0) { + onXmkd(ss); + } + else if (command.compare("RMD")==0) { + onRmd(ss); + } + else if (command.compare("XRMD")==0) { + onXrmd(ss); + } + else if (command.compare("CWD")==0) { + onCwd(ss); + } + else { + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); // Syntax error, command unrecognized. + } + m_lastCommand = command; + } // End loop processing commands. + + close(m_clientSocket); // We won't be processing any further commands from this client. + printf("<< FTPServer::processCommand\n"); +} // FTPServer::processCommand + + +/** + * Receive a file from the FTP client (STOR). The name of the file to be created is passed as a + * parameter. + */ +void FTPServer::receiveFile(std::string fileName) { + printf(">> receiveFile: %s\n", fileName.c_str()); + if (m_callbacks != nullptr) { + try { + m_callbacks->onStoreStart(fileName); + } catch(FTPServer::FileException& e) { + printf("Caught a file exception!\n"); + sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. + return; + } + } + openData(); + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + uint8_t buf[m_chunkSize]; + uint32_t totalSizeRead = 0; + while(1) { + int rc = recv(m_dataSocket, &buf, m_chunkSize, 0); + if (rc <= 0) { + break; + } + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveData(buf, rc); + } + totalSizeRead += rc; + } + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + closeData(); + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveEnd(); + } + printf("<< receiveFile: totalSizeRead=%d\n", totalSizeRead); +} // FTPServer#receiveFile + + +/** + * Send data to the client over the data connection previously opened with a call to openData(). + * @param pData A pointer to the data to send. + * @param size The number of bytes to send. + */ +void FTPServer::sendData(uint8_t* pData, uint32_t size) { + printf(">> FTPServer::sendData: size=%d\n", size); + int rc = send(m_dataSocket, pData, size, 0); + if (rc == -1) { + printf("FTPServer::sendData: send(): %s\n", strerror(errno)); + } + printf("<< FTPServer::sendData\n"); +} // FTPServer#sendData + + +/** + * Send a response to the client. A response is composed of two parts. The first is a code as architected in the + * FTP specification. The second is a piece of text. + */ +void FTPServer::sendResponse(int code, std::string text) { + printf(">> sendResponse: (%d) %s\n", code, text.c_str()); + std::ostringstream ss; + ss << code << " " << text << "\r\n"; + int rc = send(m_clientSocket, ss.str().data(), ss.str().length(), 0); + printf("<< sendResponse\n"); +} // FTPServer#sendResponse + + +/** + * Send a response to the client. A response is composed of two parts. The first is a code as architected in the + * FTP specification. The second is a piece of text. In this function, a standard piece of text is used based on + * the code. + */ +void FTPServer::sendResponse(int code) { + std::string text = "unknown"; + + switch(code) { // Map the code to a text string. + case RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION: + text = "File status okay; about to open data connection."; + break; + case RESPONSE_200_COMMAND_OK: + text = "Command okay."; + break; + case RESPONSE_220_SERVICE_READY: + text = "Service ready."; + break; + case RESPONSE_221_CLOSING_CONTROL_CONNECTION: + text = "Service closing control connection."; + break; + case RESPONSE_226_CLOSING_DATA_CONNECTION: + text = "Closing data connection."; + break; + case RESPONSE_230_USER_LOGGED_IN: + text = "User logged in, proceed."; + break; + case RESPONSE_331_PASSWORD_REQUIRED: + text = "Password required."; + break; + case RESPONSE_500_COMMAND_UNRECOGNIZED: + text = "Syntax error, command unrecognized."; + break; + case RESPONSE_502_COMMAND_NOT_IMPLEMENTED: + text = "Command not implemented."; + break; + case RESPONSE_503_BAD_SEQUENCE: + text = "Bad sequence of commands."; + break; + case RESPONSE_530_NOT_LOGGED_IN: + text = "Not logged in."; + break; + case RESPONSE_550_ACTION_NOT_TAKEN: + text = "Requested action not taken."; + break; + default: + break; + } + sendResponse(code, text); // Send the code AND the text to the FTP client. +} // FTPServer#sendResponse + + +/** + * Set the callbacks that are to be invoked to perform work. + * @param pCallbacks An instance of an FTPCallbacks based class. + */ +void FTPServer::setCallbacks(FTPCallbacks* pCallbacks) { + m_callbacks = pCallbacks; +} // FTPServer#setCallbacks + + +void FTPServer::setCredentials(std::string userid, std::string password) { + printf(">> setCredentials: userid=%s\n", userid.c_str()); + m_loginRequired = true; + m_userid = userid; + m_password = password; + printf("<< setCredentials\n"); +} // FTPServer#setCredentials + + +/** + * Set the TCP port we should listen on for FTP client requests. + */ +void FTPServer::setPort(uint16_t port) { + m_port = port; +} // FTPServer#setPort + + +/** + * Start being an FTP Server. + */ +void FTPServer::start() { + m_serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_serverSocket == -1) { + printf("socket: %s", strerror(errno)); + } + + int enable = 1; + setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(m_port); + int rc = bind(m_serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + if (rc == -1) { + printf("bind: %s\n", strerror(errno)); + } + rc = listen(m_serverSocket, 5); + if (rc == -1) { + printf("listen: %s", strerror(errno)); + } + while(1) { + waitForFTPClient(); + processCommand(); + } +} // FTPServer#start + + +/** + * Wait for a new client to connect. + */ +int FTPServer::waitForFTPClient() { + printf(">> FTPServer::waitForFTPClient\n"); + + struct sockaddr_in clientAddress; + socklen_t clientAddressLength = sizeof(clientAddress); + m_clientSocket = accept(m_serverSocket, (struct sockaddr *)&clientAddress, &clientAddressLength); + + char ipAddr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &clientAddress.sin_addr, ipAddr, sizeof(ipAddr)); + printf("Received connection from %s [%d]\n", ipAddr, clientAddress.sin_port); + + struct sockaddr_in socketAddressInfo; + unsigned int socketAddressInfoSize = sizeof(socketAddressInfo); + getsockname(m_clientSocket, (struct sockaddr*)&socketAddressInfo, &socketAddressInfoSize); + + inet_ntop(AF_INET, &socketAddressInfo.sin_addr, ipAddr, sizeof(ipAddr)); + printf("Connected at %s [%d]\n", ipAddr, socketAddressInfo.sin_port); + printf("<< FTPServer::waitForFTPClient: fd=%d\n", m_clientSocket); + + return m_clientSocket; +} // FTPServer::waitForFTPClient + diff --git a/networking/FTPServer/FTPServer.h b/networking/FTPServer/FTPServer.h new file mode 100644 index 00000000..7dc56f13 --- /dev/null +++ b/networking/FTPServer/FTPServer.h @@ -0,0 +1,137 @@ +/* + * FTPServer.h + * + * Created on: May 6, 2018 + * Author: kolban + */ + +#ifndef NETWORKING_FTPSERVER_FTPSERVER_H_ +#define NETWORKING_FTPSERVER_FTPSERVER_H_ +#include +#include +#include +#include +#include + + +class FTPCallbacks { +public: + virtual void onStoreStart(std::string fileName); + virtual size_t onStoreData(uint8_t* data, size_t size); + virtual void onStoreEnd(); + virtual void onRetrieveStart(std::string fileName); + virtual size_t onRetrieveData(uint8_t *data, size_t size); + virtual void onRetrieveEnd(); + virtual std::string onDir(); + virtual ~FTPCallbacks(); +}; + +/** + * An implementation of FTPCallbacks that uses Posix File I/O to perform file access. + */ +class FTPFileCallbacks : public FTPCallbacks { +private: + std::ofstream m_storeFile; // File used to store data from the client. + std::ifstream m_retrieveFile; // File used to retrieve data for the client. + uint32_t m_byteCount; // Count of bytes sent over wire. +public: + void onStoreStart(std::string fileName) override; // Called for a STOR request. + size_t onStoreData(uint8_t* data, size_t size) override; // Called when a chunk of STOR data becomes available. + void onStoreEnd() override; // Called at the end of a STOR request. + void onRetrieveStart(std::string fileName) override; // Called at the start of a RETR request. + size_t onRetrieveData(uint8_t* data, size_t size) override; // Called to retrieve a chunk of RETR data. + void onRetrieveEnd() override; // Called when we have retrieved all the data. + std::string onDir() override; // Called to retrieve all the directory entries. +}; + + +class FTPServer { +private: + int m_serverSocket; // The socket the FTP server is listening on. + int m_clientSocket; // The current client socket. + int m_dataSocket; // The data socket. + int m_passiveSocket; // The socket on which the server is listening for passive FTP connections. + uint16_t m_port; // The port the FTP server will use. + uint16_t m_dataPort; // The port for data connections. + uint32_t m_dataIp; // The ip address for data connections. + bool m_isPassive; // Are we in passive mode? If not, then we are in active mode. + bool m_isImage; // Are we in image mode? + size_t m_chunkSize; // The maximum chunk size. + std::string m_userid; // The required userid. + std::string m_password; // The required password. + std::string m_suppliedUserid; // The userid supplied from the USER command. + bool m_loginRequired; // Do we required a login? + bool m_isAuthenticated; // Have we authenticated? + std::string m_lastCommand; // The last command that was processed. + + FTPCallbacks* m_callbacks; // The callbacks for processing. + + void closeConnection(); + void closeData(); + void closePassive(); + void onAuth(std::istringstream& ss); + void onCwd(std::istringstream& ss); + void onList(std::istringstream& ss); + void onMkd(std::istringstream& ss); + void onNoop(std::istringstream& ss); + void onPass(std::istringstream& ss); + void onPasv(std::istringstream& ss); + void onPort(std::istringstream& ss); + void onPWD(std::istringstream& ss); + void onQuit(std::istringstream& ss); + void onRetr(std::istringstream& ss); + void onRmd(std::istringstream& ss); + void onStor(std::istringstream& ss); + void onSyst(std::istringstream& ss); + void onType(std::istringstream& ss); + void onUser(std::istringstream& ss); + void onXmkd(std::istringstream& ss); + void onXrmd(std::istringstream& ss); + + bool openData(); + + void receiveFile(std::string fileName); + void sendResponse(int code); + void sendResponse(int code, std::string text); + void sendData(uint8_t* pData, uint32_t size); + std::string listenPassive(); + int waitForFTPClient(); + void processCommand(); + +public: + FTPServer(); + virtual ~FTPServer(); + void setCredentials(std::string userid, std::string password); + void start(); + void setPort(uint16_t port); + void setCallbacks(FTPCallbacks* pFTPCallbacks); + static std::string getCurrentDirectory(); + class FileException: public std::exception { + + }; + + // Response codes. + static const int RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION = 150; + static const int RESPONSE_200_COMMAND_OK = 200; + static const int RESPONSE_202_COMMAND_NOT_IMPLEMENTED = 202; + static const int RESPONSE_212_DIRECTORY_STATUS = 212; + static const int RESPONSE_213_FILE_STATUS = 213; + static const int RESPONSE_214_HELP_MESSAGE = 214; + static const int RESPONSE_220_SERVICE_READY = 220; + static const int RESPONSE_221_CLOSING_CONTROL_CONNECTION = 221; + static const int RESPONSE_230_USER_LOGGED_IN = 230; + static const int RESPONSE_226_CLOSING_DATA_CONNECTION = 226; + static const int RESPONSE_227_ENTERING_PASSIVE_MODE = 227; + static const int RESPONSE_331_PASSWORD_REQUIRED = 331; + static const int RESPONSE_332_NEED_ACCOUNT = 332; + static const int RESPONSE_500_COMMAND_UNRECOGNIZED = 500; + static const int RESPONSE_502_COMMAND_NOT_IMPLEMENTED = 502; + static const int RESPONSE_503_BAD_SEQUENCE = 503; + static const int RESPONSE_530_NOT_LOGGED_IN = 530; + static const int RESPONSE_550_ACTION_NOT_TAKEN = 550; + static const int RESPONSE_553_FILE_NAME_NOT_ALLOWED = 553; +}; + + + +#endif /* NETWORKING_FTPSERVER_FTPSERVER_H_ */ diff --git a/networking/FTPServer/README.md b/networking/FTPServer/README.md new file mode 100644 index 00000000..d5d570c3 --- /dev/null +++ b/networking/FTPServer/README.md @@ -0,0 +1,76 @@ +# ESP32 FTP Server +The world is awash in excellent FTP servers both as stand-alone applications as well as libraries that can be linked with to build ones own integrated FTP server. This project is yet another. + +The design intent is that it will be available to ESP32 based applications to server files from a Posix file system. + +## FTP Protocol +We will be concentrating exclsuively on the FTP Server protocol. This will allow an ESP32 to serve up files to FTP clients and allow FTP clients to deposit new files. + +The FTP Protocol is described in [RFC 959](https://tools.ietf.org/html/rfc959). + +Our server engine will implement the Server-PI (Server Protocol Interpreter) and the Server-DTP (Server Data Transfer Process). + +## Commands + +### PASV +The client has requested that the server enter passive mode. The response must be: + +``` +227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). +``` + +### PORT +Client requests that the server form a data connection to the given host and port. This is used in FTP *active* mode. +``` +PORT {h1,h2,h3,h4,p1,p2} +``` +port number is p1*256 + p2 + +### RETR +This command represents a request *from the client* to retrieve a file held by the server. + +### STOR +This command represents a request *from the client* to stor a file on the server. + +### SYST +Determine the System type of the FTP server. +``` +SYST +``` +The recommended response is: +``` +215 UNIX Type: L8 +``` + +### TYPE +Define the type of data to be transmitted. Codes include: + +* `A` - ASCII +* `E` - EBCDIC +* `I` - Image (binary) + +Example: +``` +TYPE I +``` + +### USER +Supply the user name that the client wishes to present to the FTP server. +``` +USER {name} +``` + +A response of `331` will request a password and should then be followed by the `PASS` command. + +## Active vs Passive +The FTP server can communicate data to the client in one of two modes called *active* and *passive*. In active mode, the FTP client is responsible for setting up a listening socket. When the FTP client is ready to receive data, it sends a `PORT` command informing the server of the IP address and port against which the FTP server should form the connection. +In passive mode, the FTP client sends a `PASV` command to the FTP server. The FTP server then responds with a host and port pair which will be listened upon to receive a connection request *from* the client. Once received, the data can then flow over this connection. + +See also: + +* [Active FTP vs. Passive FTP, a Definitive Explanation](http://slacksite.com/other/ftp.html) + +# References + +* [FTP: File Transfer Protocol](https://cr.yp.to/ftp.html) +* [Wikipedia: File Transfer Protocol](https://en.wikipedia.org/wiki/File_Transfer_Protocol) \ No newline at end of file diff --git a/networking/FTPServer/main.cpp b/networking/FTPServer/main.cpp new file mode 100644 index 00000000..45725051 --- /dev/null +++ b/networking/FTPServer/main.cpp @@ -0,0 +1,12 @@ +#include "FTPServer.h" +#include + + +int main(int argc, char* argv[]) { + printf("FTPServer starting!\n"); + FTPServer *ftpServer = new FTPServer(); + ftpServer->setCallbacks(new FTPFileCallbacks()); + //ftpServer->setCredentials("user", "pass"); + ftpServer->setPort(9876); + ftpServer->start(); +} From 937a81c18d742e86305df5184ccfb70f35b77c85 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Fri, 8 Jun 2018 11:35:44 -0500 Subject: [PATCH 289/381] Work in progress for DMA driver --- memory/dma/DMA.cpp | 411 +++++++++++++++++++++++++++++++++++++ memory/dma/DMA.h | 26 +++ memory/dma/DMA_GPIO.h | 13 ++ memory/dma/GPIODriver.cpp | 88 ++++++++ memory/dma/GPIODriver.h | 18 ++ memory/dma/dma_console.cpp | 215 +++++++++++++++++++ memory/dma/main.cpp | 14 ++ 7 files changed, 785 insertions(+) create mode 100644 memory/dma/DMA.cpp create mode 100644 memory/dma/DMA.h create mode 100644 memory/dma/DMA_GPIO.h create mode 100644 memory/dma/GPIODriver.cpp create mode 100644 memory/dma/GPIODriver.h create mode 100644 memory/dma/dma_console.cpp create mode 100644 memory/dma/main.cpp diff --git a/memory/dma/DMA.cpp b/memory/dma/DMA.cpp new file mode 100644 index 00000000..a72a92e5 --- /dev/null +++ b/memory/dma/DMA.cpp @@ -0,0 +1,411 @@ +/* + * DMA.cpp + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#include "DMA.h" +#include +#include +#include +#include + +#include // Inclusions for WRITE_PERI_xxx and family. +#include // Inclusions for I2S registers. +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#define D0 GPIO_NUM_4 +#define D1 GPIO_NUM_5 +#define D2 GPIO_NUM_18 +#define D3 GPIO_NUM_19 +#define D4 GPIO_NUM_36 +#define D5 GPIO_NUM_39 +#define D6 GPIO_NUM_34 +#define D7 GPIO_NUM_35 +#define VSYNC GPIO_NUM_25 +#define HREF GPIO_NUM_23 +#define PCLK GPIO_NUM_22 + +lldesc_t ll1; + +QueueHandle_t queueHandle; +intr_handle_t intHandle; + +class LogInterruptTask : public Task { + void run(void *data) { + printf("Starting interrupt log\n"); + uint32_t queueItem; + while(1) { + xQueueReceive(queueHandle, &queueItem, portMAX_DELAY); + printf("New interrupt value: 0x%x\n", queueItem); + } // End while 1 + } // LogInterruptTask#run +}; // LogInterruptTask + +static void IRAM_ATTR dmaInt(void *arg) { + uint32_t value = 0x99; + xQueueSendToBackFromISR(queueHandle, &value, nullptr); + I2S0.int_clr.val = I2S0.int_raw.val; +} + + +DMA::DMA() { + +} + +DMA::~DMA() { +} + + +void DMA::clearInterupts() { + + // Set 1 bits in the flags for I2S_INT_CLR_REG to clear interrupts. + I2S0.int_clr.val = 0xFFFFFFFF; // 32 bits of 1 to clear all flags. + +} // DMA#clearInterupts + + +void DMA::reset() { + + clearInterupts(); + ll1.eof = 1; + ll1.length = 0; // Number of valid bytes. + ll1.size = 1024; // Size of buffer. + ll1.offset = 0; + ll1.sosf = 0; + ll1.empty = 0; // No next link + ll1.owner = 1; // 1 = DMA, 2 = software + memset((void *)ll1.buf, 0xff, ll1.size); + + + + const uint32_t lc_conf_reset_flags = I2S_IN_RST_M | I2S_AHBM_RST_M | I2S_AHBM_FIFO_RST_M; + I2S0.lc_conf.val |= lc_conf_reset_flags; + I2S0.lc_conf.val &= ~lc_conf_reset_flags; + + + // See TRM 12.4.2. + const uint32_t conf_reset_flags = I2S_RX_RESET_M | I2S_RX_FIFO_RESET_M | I2S_TX_RESET_M | I2S_TX_FIFO_RESET_M; + I2S0.conf.val |= conf_reset_flags; + I2S0.conf.val &= ~conf_reset_flags; + + while (I2S0.state.rx_fifo_reset_back) { // Bit is 1 while not ready + ; + } + + I2S0.in_link.addr = (uint32_t)&ll1; +} // DMA#reset + + +void DMA::dumpBuffer() { + printf(">> dumpBuffer\n"); + GeneralUtils::hexDump((uint8_t*)ll1.buf, 64); + printf("<< dumpBuffer\n"); +} + +void DMA::setCameraMode() { + printf(">> setCameraMode\n"); + + gpio_pad_select_gpio(D0); + gpio_pad_select_gpio(D1); + gpio_pad_select_gpio(D2); + gpio_pad_select_gpio(D3); + gpio_pad_select_gpio(D4); + gpio_pad_select_gpio(D5); + gpio_pad_select_gpio(D6); + gpio_pad_select_gpio(D7); + gpio_pad_select_gpio(VSYNC); + gpio_pad_select_gpio(HREF); + gpio_pad_select_gpio(PCLK); + + gpio_set_direction(D0, GPIO_MODE_INPUT); + gpio_set_direction(D1, GPIO_MODE_INPUT); + gpio_set_direction(D2, GPIO_MODE_INPUT); + gpio_set_direction(D3, GPIO_MODE_INPUT); + gpio_set_direction(D4, GPIO_MODE_INPUT); + gpio_set_direction(D5, GPIO_MODE_INPUT); + gpio_set_direction(D6, GPIO_MODE_INPUT); + gpio_set_direction(D7, GPIO_MODE_INPUT); + gpio_set_direction(VSYNC, GPIO_MODE_INPUT); + gpio_set_direction(HREF, GPIO_MODE_INPUT); + gpio_set_direction(PCLK, GPIO_MODE_INPUT); + + // Map sources / sinks of data to their logical counter parts. + gpio_matrix_in(D0, I2S0I_DATA_IN0_IDX, false); + gpio_matrix_in(D1, I2S0I_DATA_IN1_IDX, false); + gpio_matrix_in(D2, I2S0I_DATA_IN2_IDX, false); + gpio_matrix_in(D3, I2S0I_DATA_IN3_IDX, false); + gpio_matrix_in(D4, I2S0I_DATA_IN4_IDX, false); + gpio_matrix_in(D5, I2S0I_DATA_IN5_IDX, false); + gpio_matrix_in(D6, I2S0I_DATA_IN6_IDX, false); + gpio_matrix_in(D7, I2S0I_DATA_IN7_IDX, false); + + // Set constants for the bits [15:8] alternating 1 and 0. This results in 0xAA. + gpio_matrix_in(0x30, I2S0I_DATA_IN8_IDX, false); + gpio_matrix_in(0x38, I2S0I_DATA_IN9_IDX, false); + gpio_matrix_in(0x30, I2S0I_DATA_IN10_IDX, false); + gpio_matrix_in(0x38, I2S0I_DATA_IN11_IDX, false); + gpio_matrix_in(0x30, I2S0I_DATA_IN12_IDX, false); + gpio_matrix_in(0x38, I2S0I_DATA_IN13_IDX, false); + gpio_matrix_in(0x30, I2S0I_DATA_IN14_IDX, false); + gpio_matrix_in(0x38, I2S0I_DATA_IN15_IDX, false); + + gpio_matrix_in(0x38 /*VSYNC*/, I2S0I_V_SYNC_IDX, false); + gpio_matrix_in(0x38, I2S0I_H_SYNC_IDX, false); // Constant high + gpio_matrix_in(0x38 /* HREF */, I2S0I_H_ENABLE_IDX, false); + gpio_matrix_in(PCLK, I2S0I_WS_IN_IDX, false); + + //SET_PERI_REG_BITS(I2SCONF2_REG, I2S_CAMERA_EN, 1, I2S_CAMERA_EN_S); + + + // We must enable I2S0 peripheral + periph_module_enable(PERIPH_I2S0_MODULE); + + reset(); + + + /* + rx_msb_right | result | +---------------+--------------------------------------------------------------------------------+ + 0 | 00 00 v1low v1high 00 00 v2low v2high 00 00 v3low v3high 00 00 v4low v4high | + 1 | 00 00 v1low v1high 00 00 v2low ?? 00 00 v2low ?? 00 00 v3 low ?? | + */ + + // We enable I2S_CONF2_REG:I2S_CAMERA_EN + I2S0.conf2.camera_en = 1; // 1 for camera mode (TRM). + + // We enable I2S_CONF_REG:I2S_LCD_EN + I2S0.conf2.lcd_en = 1; // 1 for camera mode (TRM). + + // I2S_CONF_REG:I2S_RX_SLAVE_MOD + I2S0.conf.rx_slave_mod = 1; // 1 for slave receiving mode. Required for camera mode (TRM). + + // I2S_CONF_REG:I2S_RX_MSB_RIGHT + I2S0.conf.rx_msb_right = 0; // 0 required for camera mode (TRM). + + // I2S_CONF_REG:I2S_RX_RIGHT_FIRST + I2S0.conf.rx_right_first = 0; // 0 required for camera mode (TRM). + + // I2S_CONF_CHANG_REG:I2S_RX_CHAN_MOD + I2S0.conf_chan.rx_chan_mod = 1; // 1 required for camera mode (TRM). + + // I2S_FIFO_CONF_REG:I2S_RX_FIFO_MOD + I2S0.fifo_conf.rx_fifo_mod = 1; // 1 = 16bit single channel data. Required for camera mode (TRM). + + // Configure clock divider + I2S0.clkm_conf.clkm_div_a = 1; + I2S0.clkm_conf.clkm_div_b = 0; + I2S0.clkm_conf.clkm_div_num = 2; + + // FIFO will sink data to DMA + I2S0.fifo_conf.dscr_en = 1; + + // FIFO configuration + I2S0.fifo_conf.rx_fifo_mod = 2; + I2S0.fifo_conf.rx_fifo_mod_force_en = 1; + I2S0.conf_chan.rx_chan_mod = 1; + + // Clear flags which are used in I2S serial mode + I2S0.sample_rate_conf.rx_bits_mod = 0; + I2S0.conf.rx_right_first = 0; + I2S0.conf.rx_msb_right = 0; + I2S0.conf.rx_msb_shift = 0; + I2S0.conf.rx_mono = 0; + I2S0.conf.rx_short_sync = 0; + I2S0.timing.val = 0; + + + esp_err_t errRc; + errRc = esp_intr_alloc( + ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, + &dmaInt, + NULL, + &intHandle); + if (errRc != ESP_OK) { + printf("esp_intr_alloc: %d\n", errRc); + } + + errRc = esp_intr_enable(intHandle); + if (errRc != ESP_OK) { + printf("esp_intr_enable: %d\n", errRc); + } + + I2S0.rx_eof_num = 8; + + + ll1.eof = 1; + ll1.length = 0; // Number of valid bytes. + ll1.size = 1024; // Size of buffer. + ll1.offset = 0; + ll1.sosf = 0; + ll1.empty = 0; // No next link + ll1.owner = 1; // 1 = DMA, 0 = software + + memset((void *)ll1.buf, 0xff, ll1.size); + + // The address of the first in-link descriptor. + I2S0.in_link.addr = (uint32_t)&ll1; + + // Set to 1 to start in-link descriptor. + //I2S0.in_link.start = 1; + + startRX(); + + clearInterupts(); + + printf("<< setCameraMode\n"); +} // DMA#setCameraMode + + +void DMA::stopRX() { + // Set to 0 to stop receiving data. + I2S0.conf.rx_start = 0; + I2S0.conf.rx_reset = 1; +} // DMA#stopRX + + +void DMA::start() { + ll1.buf = (uint8_t*)malloc(1024); // Allocate storage for the linked list buffer. + queueHandle = xQueueCreate(50, sizeof(uint32_t)); + + LogInterruptTask* pLogInterruptTask = new LogInterruptTask(); + pLogInterruptTask->start(); +} // DMA#start + + +void DMA::startRX() { + // Set to 1 to start receiving data. + + printf(">> DMA::startRX\n"); + + I2S0.int_clr.val = I2S0.int_raw.val; + + I2S0.rx_eof_num = 8; + + I2S0.in_link.addr = (uint32_t)&ll1; + //I2S0.in_link.stop = 1; + I2S0.in_link.start = 1; + //I2S0.in_link.restart = 1; + I2S0.conf.rx_start = 1; + + // Enable interrupt generation + I2S0.int_ena.in_suc_eof = true; + //I2S0.int_ena.in_done = true; + /* + I2S0.int_ena.in_dscr_empty = true; + I2S0.int_ena.in_dscr_err = true; + I2S0.int_ena.in_err_eof = true; + + I2S0.int_ena.rx_hung = true; + I2S0.int_ena.rx_rempty = true; + I2S0.int_ena.rx_take_data = true; + I2S0.int_ena.rx_wfull = true; + */ + //I2S0.int_ena.rx_take_data = true; + + + printf("<< DMA::startRX\n"); +} // DMA#startRX + + +void DMA::dumpStatus() { + printf("conf\n"); + printf("%-20s: %d\n", "I2S_RX_SLAVE_MOD", I2S0.conf.rx_slave_mod); + printf("%-20s: %d\n", "I2S_RX_MSB_RIGHT", I2S0.conf.rx_msb_right); + printf("%-20s: %d\n", "I2S_RX_RIGHT_FIRST", I2S0.conf.rx_right_first); + printf("%-20s: %d\n", "I2S_RX_START", I2S0.conf.rx_start); + printf("%-20s: %d\n", "I2S_RX_RESET", I2S0.conf.rx_reset); + printf("-----\n"); + + printf("conf_chan\n"); + printf("%-20s: %d\n", "I2S_RX_CHAN_MOD", I2S0.conf_chan.rx_chan_mod); + printf("-----\n"); + + printf("fifo_conf (I2S_FIFO_CONF_REG)\n"); + printf("%-26s: %d\n", "I2S_RX_FIFO_MOD_FORCE_EN", I2S0.fifo_conf.rx_fifo_mod_force_en); + printf("%-26s: %d\n", "I2S_RX_FIFO_MOD", I2S0.fifo_conf.rx_fifo_mod); + printf("%-26s: %d\n", "I2S_RX_DSCR_EN", I2S0.fifo_conf.dscr_en); + printf("%-26s: %d\n", "I2S_RX_DATA_NUM", I2S0.fifo_conf.rx_data_num); + printf("-----\n"); + + printf("conf2\n"); + printf("%-20s: %d\n", "I2S_CAMERA_EN", I2S0.conf2.camera_en); + printf("%-20s: %d\n", "I2S_LCD_EN", I2S0.conf2.lcd_en); + printf("-----\n"); + + + printf("state\n"); + printf("%-20s: %d\n", "I2S_RX_FIFO_RESET_BACK", I2S0.state.rx_fifo_reset_back); + printf("-----\n"); + + printf("rx_eof_num (I2S_RXEOF_NUM_REG)\n"); + printf("%-20s: %d\n", "I2S_RXEOF_NUM_REG", I2S0.rx_eof_num); + printf("-----\n"); + + printf("in_eof_des_addr (I2S_IN_EOF_DES_ADDR_REG)\n"); + printf("%-20s: 0x%x\n", "I2S_IN_EOF_DES_ADDR_REG", I2S0.in_eof_des_addr); + printf("-----\n"); + + printf("in_link_dscr (I2S_INLINK_DSCR_REG)\n"); + printf("%-20s: 0x%x\n", "I2S_INLINK_DSCR_REG", I2S0.in_link_dscr); + printf("-----\n"); + + printf("in_link_dscr_bf0 (I2S_INLINK_DSCR_BF0_REG)\n"); + printf("%-20s: 0x%x\n", "I2S_INLINK_DSCR_BF0_REG", I2S0.in_link_dscr_bf0); + printf("-----\n"); + + printf("in_link_dscr_bf1 (I2S_INLINK_DSCR_BF1_REG)\n"); + printf("%-20s: 0x%x\n", "I2S_INLINK_DSCR_BF1_REG", I2S0.in_link_dscr_bf1); + printf("-----\n"); + + printf("int_raw\n"); + printf("%-25s: %d\n", "I2S_IN_DSCR_EMPTY_INT_RAW", I2S0.int_raw.in_dscr_empty); + printf("%-25s: %d\n", "I2S_IN_DSCR_ERR_INT_RAW", I2S0.int_raw.in_dscr_err); + printf("%-25s: %d\n", "I2S_IN_SUC_EOF_INT_RAW", I2S0.int_raw.in_suc_eof); + printf("%-25s: %d\n", "I2S_IN_ERR_EOF_INT_RAW", I2S0.int_raw.in_err_eof); + printf("%-25s: %d\n", "I2S_IN_DONE_INT_RAW", I2S0.int_raw.in_done); + printf("%-25s: %d\n", "I2S_RX_HUNG_INT_RAW", I2S0.int_raw.rx_hung); + printf("%-25s: %d\n", "I2S_RX_REMPTY_INT_RAW", I2S0.int_raw.rx_rempty); + printf("%-25s: %d\n", "I2S_RX_WFULL_INT_RAW", I2S0.int_raw.rx_wfull); + printf("%-25s: %d\n", "I2S_RX_TAKE_DATA_INT_RAW", I2S0.int_raw.rx_take_data); + printf("-----\n"); + + printf("int_ena\n"); + printf("%-25s: %d\n", "I2S_IN_DSCR_EMPTY_INT_RAW", I2S0.int_ena.in_dscr_empty); + printf("%-25s: %d\n", "I2S_IN_DSCR_ERR_INT_RAW", I2S0.int_ena.in_dscr_err); + printf("%-25s: %d\n", "I2S_IN_SUC_EOF_INT_RAW", I2S0.int_ena.in_suc_eof); + printf("%-25s: %d\n", "I2S_IN_ERR_EOF_INT_RAW", I2S0.int_ena.in_err_eof); + printf("%-25s: %d\n", "I2S_IN_DONE_INT_RAW", I2S0.int_ena.in_done); + printf("%-25s: %d\n", "I2S_RX_HUNG_INT_RAW", I2S0.int_ena.rx_hung); + printf("%-25s: %d\n", "I2S_RX_REMPTY_INT_RAW", I2S0.int_ena.rx_rempty); + printf("%-25s: %d\n", "I2S_RX_WFULL_INT_RAW", I2S0.int_ena.rx_wfull); + printf("%-25s: %d\n", "I2S_RX_TAKE_DATA_INT_RAW", I2S0.int_ena.rx_take_data); + printf("-----\n"); + + printf("in_link\n"); + printf("%-20s: %d\n", "I2S_INLINK_PARK", I2S0.in_link.park); + printf("%-20s: %d\n", "I2S_INLINK_RESTART", I2S0.in_link.restart); + printf("%-20s: %d\n", "I2S_INLINK_START", I2S0.in_link.start); + printf("%-20s: %d\n", "I2S_INLINK_STOP", I2S0.in_link.stop); + printf("%-20s: 0x%x\n", "I2S_INLINK_ADDR", (I2S0.in_link.addr & 0xFFFFF)); + printf("-----\n"); + + printf("lldesc 1: 0x%x\n", (uint32_t)&ll1); + printf("%-20s: %d\n", "length", ll1.length); + printf("%-20s: %d\n", "size", ll1.size); + printf("%-20s: %d\n", "owner", ll1.owner); + printf("%-20s: %d\n", "empty", ll1.empty); + printf("%-20s: 0x%x\n", "&buf", (uint32_t)ll1.buf); +} // DMA#dumpStatus diff --git a/memory/dma/DMA.h b/memory/dma/DMA.h new file mode 100644 index 00000000..2be79458 --- /dev/null +++ b/memory/dma/DMA.h @@ -0,0 +1,26 @@ +/* + * DMA.h + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#ifndef MAIN_DMA_H_ +#define MAIN_DMA_H_ + +class DMA { +public: + DMA(); + virtual ~DMA(); + void clearInterupts(); + void dumpBuffer(); + void dumpStatus(); + void reset(); + void setCameraMode(); + void start(); + void startRX(); + void stopRX(); + +}; + +#endif /* MAIN_DMA_H_ */ diff --git a/memory/dma/DMA_GPIO.h b/memory/dma/DMA_GPIO.h new file mode 100644 index 00000000..124adc28 --- /dev/null +++ b/memory/dma/DMA_GPIO.h @@ -0,0 +1,13 @@ +/* + * DMA_GPIO.h + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#ifndef MAIN_DMA_GPIO_H_ +#define MAIN_DMA_GPIO_H_ + + + +#endif /* MAIN_DMA_GPIO_H_ */ diff --git a/memory/dma/GPIODriver.cpp b/memory/dma/GPIODriver.cpp new file mode 100644 index 00000000..78021c0d --- /dev/null +++ b/memory/dma/GPIODriver.cpp @@ -0,0 +1,88 @@ +/* + * GPIODriver.cpp + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#include "GPIODriver.h" +#include "DMA_GPIO.h" +#include +#include +#include + +#define D0 GPIO_NUM_4 +#define D1 GPIO_NUM_5 +#define D2 GPIO_NUM_18 +#define D3 GPIO_NUM_19 +#define D4 GPIO_NUM_13 // 36 +#define D5 GPIO_NUM_14 // 39 +#define D6 GPIO_NUM_15 // 34 +#define D7 GPIO_NUM_16 // 35 +#define VSYNC GPIO_NUM_25 +#define HREF GPIO_NUM_23 +#define PCLK GPIO_NUM_22 + +GPIODriver::GPIODriver() { +} + +GPIODriver::~GPIODriver() { +} + +void GPIODriver::run() { + printf(">> GPIODriver::run\n"); + // Setup the GPIO pins as output. + gpio_pad_select_gpio(D0); + gpio_pad_select_gpio(D1); + gpio_pad_select_gpio(D2); + gpio_pad_select_gpio(D3); + gpio_pad_select_gpio(D4); + gpio_pad_select_gpio(D5); + gpio_pad_select_gpio(D6); + gpio_pad_select_gpio(D7); + gpio_pad_select_gpio(VSYNC); + gpio_pad_select_gpio(HREF); + gpio_pad_select_gpio(PCLK); + + gpio_set_direction(D0, GPIO_MODE_OUTPUT); + gpio_set_direction(D1, GPIO_MODE_OUTPUT); + gpio_set_direction(D2, GPIO_MODE_OUTPUT); + gpio_set_direction(D3, GPIO_MODE_OUTPUT); + gpio_set_direction(D4, GPIO_MODE_OUTPUT); + gpio_set_direction(D5, GPIO_MODE_OUTPUT); + gpio_set_direction(D6, GPIO_MODE_OUTPUT); + gpio_set_direction(D7, GPIO_MODE_OUTPUT); + gpio_set_direction(VSYNC, GPIO_MODE_OUTPUT); + gpio_set_direction(HREF, GPIO_MODE_OUTPUT); + gpio_set_direction(PCLK, GPIO_MODE_OUTPUT); + + gpio_set_level(VSYNC, 1); + gpio_set_level(HREF, 1); + // Initialize the counter. + uint8_t counter = 0; + // Loop + + gpio_set_level(PCLK, 0); + while(1) { + + // Write the counter value to the GPIOs + gpio_set_level(D0, (counter & 0b00000001) != 0); + gpio_set_level(D1, (counter & 0b00000010) != 0); + gpio_set_level(D2, (counter & 0b00000100) != 0); + gpio_set_level(D3, (counter & 0b00001000) != 0); + gpio_set_level(D4, (counter & 0b00010000) != 0); + gpio_set_level(D5, (counter & 0b00100000) != 0); + gpio_set_level(D6, (counter & 0b01000000) != 0); + gpio_set_level(D7, (counter & 0b10000000) != 0); + + + // Pulse the clock + gpio_set_level(PCLK, 1); + vTaskDelay(10/portTICK_PERIOD_MS); + gpio_set_level(PCLK, 0); + + // Delay + vTaskDelay(990/portTICK_PERIOD_MS); + counter++; + } // End while 1 +} // GPIODriver#run diff --git a/memory/dma/GPIODriver.h b/memory/dma/GPIODriver.h new file mode 100644 index 00000000..03f8d7ec --- /dev/null +++ b/memory/dma/GPIODriver.h @@ -0,0 +1,18 @@ +/* + * GPIODriver.h + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#ifndef MAIN_GPIODRIVER_H_ +#define MAIN_GPIODRIVER_H_ + +class GPIODriver { +public: + GPIODriver(); + virtual ~GPIODriver(); + void run(); +}; + +#endif /* MAIN_GPIODRIVER_H_ */ diff --git a/memory/dma/dma_console.cpp b/memory/dma/dma_console.cpp new file mode 100644 index 00000000..bc3bd05b --- /dev/null +++ b/memory/dma/dma_console.cpp @@ -0,0 +1,215 @@ +/** + * DMA Driver + * + * @author Neil Kolban + * @date 2018-05-26 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" +#include "DMA.h" +#include "GPIODriver.h" +#include + +DMA dma; + + +struct arg_end* reset_arg_end = arg_end(10); +void *reset_argtable[] = { + reset_arg_end +}; + +int runReset(int argc, char *argv[]) { + printf("Reset\n"); + dma.reset(); + return 0; +} + + +struct arg_end* start_arg_end = arg_end(10); +void *start_argtable[] = { + start_arg_end +}; + +int runStart(int argc, char *argv[]) { + printf("Started\n"); + return 0; +} + + +struct arg_end* startRX_arg_end = arg_end(10); +void *startRX_argtable[] = { + startRX_arg_end +}; + +int runStartRX(int argc, char *argv[]) { + printf("StartRX\n"); + return 0; +} + + +struct arg_end* stopRX_arg_end = arg_end(10); +void *stopRX_argtable[] = { + stopRX_arg_end +}; + +int runStopRX(int argc, char *argv[]) { + printf("StopRX\n"); + return 0; +} + + +struct arg_end* hexdump_arg_end = arg_end(10); +void *hexdump_argtable[] = { + hexdump_arg_end +}; + +int runHexdump(int argc, char *argv[]) { + printf("Hexdump\n"); + dma.dumpBuffer(); + return 0; +} + +struct arg_end* clientStart_arg_end = arg_end(10); +void *clientStart_arg_end_argtable[] = { + clientStart_arg_end +}; + +int runClientStart(int argc, char *argv[]) { + printf("runClientStart\n"); + GPIODriver gpioDriver; + gpioDriver.run(); + return 0; +} + + +struct arg_end* camera_arg_end = arg_end(10); +void *camera_argtable[] = { + camera_arg_end +}; + +int runCamera(int argc, char *argv[]) { + printf("camera\n"); + dma.setCameraMode(); + return 0; +} + +struct arg_end* stop_arg_end = arg_end(10); +void *stop_argtable[] = { + stop_arg_end +}; + +int runStop(int argc, char *argv[]) { + printf("Stopped\n"); + return 0; +} + + +struct arg_end* status_arg_end = arg_end(10); +void *status_argtable[] = { + stop_arg_end +}; + +int runStatus(int argc, char *argv[]) { + printf("Status\n"); + dma.dumpStatus(); + return 0; +} + + +void test_console(void) +{ + dma.start(); + // Boiler plate setup for using linenoise + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + uart_driver_install((uart_port_t)CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + esp_console_config_t consoleConfig; + consoleConfig.max_cmdline_args = 5; + consoleConfig.max_cmdline_length = 100; + + esp_console_init(&consoleConfig); + esp_console_register_help_command(); + + esp_console_cmd_t consoleCmd; + consoleCmd.command = "start"; + consoleCmd.func = runStart; + consoleCmd.help = "Start processing"; + consoleCmd.argtable = start_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "stop"; + consoleCmd.func = runStop; + consoleCmd.help = "Stop processing"; + consoleCmd.argtable = stop_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "status"; + consoleCmd.func = runStatus; + consoleCmd.help = "Status processing"; + consoleCmd.argtable = status_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "camera"; + consoleCmd.func = runCamera; + consoleCmd.help = "camera processing"; + consoleCmd.argtable = camera_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "clientStart"; + consoleCmd.func = runClientStart; + consoleCmd.help = "clientStart processing"; + consoleCmd.argtable = clientStart_arg_end_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "hexdump"; + consoleCmd.func = runHexdump; + consoleCmd.help = "hexdump processing"; + consoleCmd.argtable = hexdump_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "startRX"; + consoleCmd.func = runStartRX; + consoleCmd.help = "startRX processing"; + consoleCmd.argtable = startRX_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "stopRX"; + consoleCmd.func = runStopRX; + consoleCmd.help = "stopRX processing"; + consoleCmd.argtable = stopRX_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "reset"; + consoleCmd.func = runReset; + consoleCmd.help = "reset processing"; + consoleCmd.argtable = reset_argtable; + esp_console_cmd_register(&consoleCmd); + + + //linenoiseClearScreen(); + linenoiseSetMultiLine(0); + + + while(1) { + char* line = linenoise("Enter command > "); + if (line != NULL) { + int ret; + esp_console_run(line, &ret); + linenoiseHistoryAdd(line); + linenoiseFree(line); + } // Line is not null + printf("--------------\n"); + } // End while loop +} diff --git a/memory/dma/main.cpp b/memory/dma/main.cpp new file mode 100644 index 00000000..f5715f5d --- /dev/null +++ b/memory/dma/main.cpp @@ -0,0 +1,14 @@ +extern "C" { + void app_main(void); +} + +extern void test_linenoise(); +extern void test_argtable(); +extern void test_console(); + +void app_main(void) +{ + //test_linenoise(); + //test_argtable(); + test_console(); +} From 75d405cac70c1e046445ec098d4654bc1b47f8c2 Mon Sep 17 00:00:00 2001 From: Han Date: Thu, 14 Jun 2018 22:10:52 +0200 Subject: [PATCH 290/381] Set spi_bus_config_t and spi_device_interface_config_t flags --- cpp_utils/SPI.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp_utils/SPI.cpp b/cpp_utils/SPI.cpp index 56b87502..5ce45d15 100644 --- a/cpp_utils/SPI.cpp +++ b/cpp_utils/SPI.cpp @@ -54,6 +54,7 @@ void SPI::init(int mosiPin, int misoPin, int clkPin, int csPin) { bus_config.quadwp_io_num = -1; // Not used bus_config.quadhd_io_num = -1; // Not used bus_config.max_transfer_sz = 0; // 0 means use default. + bus_config.flags = (SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO); ESP_LOGI(LOG_TAG, "... Initializing bus; host=%d", m_host); @@ -78,7 +79,7 @@ void SPI::init(int mosiPin, int misoPin, int clkPin, int csPin) { dev_config.cs_ena_pretrans = 0; dev_config.clock_speed_hz = 100000; dev_config.spics_io_num = csPin; - dev_config.flags = 0; + dev_config.flags = SPI_DEVICE_NO_DUMMY; dev_config.queue_size = 1; dev_config.pre_cb = NULL; dev_config.post_cb = NULL; From f77854155291af610e8f430f85c1cbb5e989cbf2 Mon Sep 17 00:00:00 2001 From: Oleg Date: Sun, 17 Jun 2018 22:05:19 +0300 Subject: [PATCH 291/381] splitted BLEScan::start to blocking & non-blocking oveloads --- cpp_utils/BLEScan.cpp | 29 ++++++++++++++++++----------- cpp_utils/BLEScan.h | 3 ++- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 73086c6e..3046b7c8 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -189,16 +189,14 @@ void BLEScan::setWindow(uint16_t windowMSecs) { /** * @brief Start scanning. * @param [in] duration The duration in seconds for which to scan. - * @param [in] scanCompleteCB A function to be called when scanning has completed. This can - * be supplied as nullptr (the default) in which case the call to start will block until scanning has - * been completed. - * @return The BLEScanResults. Only applicable if we are waiting for results. + * @param [in] scanCompleteCB A function to be called when scanning has completed. + * @return True if scan started or false if there was an error. */ -BLEScanResults BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)) { +bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)) { ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); - m_scanCompleteCB = scanCompleteCB; // Save the callback to be invoked when the scan completes. m_semaphoreScanEnd.take(std::string("start")); + m_scanCompleteCB = scanCompleteCB; // Save the callback to be invoked when the scan completes. m_scanResults.m_vectorAdvertisedDevices.clear(); @@ -207,7 +205,7 @@ BLEScanResults BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanR if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); m_semaphoreScanEnd.give(); - return m_scanResults; + return false; } errRc = ::esp_ble_gap_start_scanning(duration); @@ -215,16 +213,25 @@ BLEScanResults BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanR if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); m_semaphoreScanEnd.give(); - return m_scanResults; + return false; } m_stopped = false; - if (m_scanCompleteCB == nullptr) { + ESP_LOGD(LOG_TAG, "<< start()"); + return true; +} // start + + +/** + * @brief Start scanning and block until scanning has been completed. + * @param [in] duration The duration in seconds for which to scan. + * @return The BLEScanResults. + */ +BLEScanResults BLEScan::start(uint32_t duration) { + if(start(duration, nullptr)) { m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. } - - ESP_LOGD(LOG_TAG, "<< start()"); return m_scanResults; } // start diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index 3e53ce64..76c7c7cc 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -53,7 +53,8 @@ class BLEScan { bool wantDuplicates = false); void setInterval(uint16_t intervalMSecs); void setWindow(uint16_t windowMSecs); - BLEScanResults start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults) = nullptr); + bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)); + BLEScanResults start(uint32_t duration); void stop(); private: From eead14aff146a23f74f40308d3048e2b2b3249f6 Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 19 Jun 2018 08:36:58 +0200 Subject: [PATCH 292/381] Add remove service --- cpp_utils/BLEServer.cpp | 8 ++++++++ cpp_utils/BLEServer.h | 3 ++- cpp_utils/BLEServiceMap.cpp | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index bc079813..03696ea5 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -323,6 +323,14 @@ void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { m_pServerCallbacks = pCallbacks; } // setCallbacks +/* + * Remove service + */ +void BLEServer::removeService(BLEService *service) { + esp_ble_gatts_delete_service(handle); + uint16_t handle = service->getHandle(); + m_serviceMap->removeService(service); +} /** * @brief Start advertising. diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 6c71679a..585fdf89 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -42,6 +42,7 @@ class BLEServiceMap { std::string toString(); BLEService* getFirst(); BLEService* getNext(); + void removeService(BLEService *service); private: std::map m_handleMap; @@ -61,7 +62,7 @@ class BLEServer { BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); - + void removeService(BLEService *service); private: BLEServer(); diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index 96811ad2..0184f0da 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -120,4 +120,9 @@ BLEService* BLEServiceMap::getNext() { return pRet; } // getNext +void BLEServiceMap::removeService(BLEService *service){ + m_handleMap->erase(serice->getHandle()); + m_uuidMap->erase(service); +} + #endif /* CONFIG_BT_ENABLED */ From 21ba4a8df9e4c535193b768d8acf7dee5b2ca61d Mon Sep 17 00:00:00 2001 From: James Bergin Date: Wed, 20 Jun 2018 16:18:36 -0500 Subject: [PATCH 293/381] - Added way to remove services. --- cpp_utils/BLEServer.cpp | 8 +++++ cpp_utils/BLEServer.h | 2 ++ cpp_utils/BLEService.cpp | 60 +++++++++++++++++++++++++++++++++++++ cpp_utils/BLEService.h | 4 +++ cpp_utils/BLEServiceMap.cpp | 5 ++++ 5 files changed, 79 insertions(+) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index bc079813..d5c9de68 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -323,6 +323,14 @@ void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { m_pServerCallbacks = pCallbacks; } // setCallbacks +/* + * Remove service + */ +void BLEServer::removeService(BLEService *service) { + service->stop(); + service->executeDelete(); + m_serviceMap.removeService(service); +} /** * @brief Start advertising. diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 6c71679a..95c55d53 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -42,6 +42,7 @@ class BLEServiceMap { std::string toString(); BLEService* getFirst(); BLEService* getNext(); + void removeService(BLEService *service); private: std::map m_handleMap; @@ -61,6 +62,7 @@ class BLEServer { BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); + void removeService(BLEService *service); private: diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 6636ca5c..d7a4da27 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -91,6 +91,29 @@ void BLEService::executeCreate(BLEServer *pServer) { } // executeCreate +/** + * @brief Delete the service. + * Delete the service. + * @param [in] gatts_if The handle of the GATT server interface. + * @return N/A. + */ + +void BLEService::executeDelete() { + ESP_LOGD(LOG_TAG, ">> executeDelete()"); + m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT + + esp_err_t errRc = ::esp_ble_gatts_delete_service( getHandle() ); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_delete_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreDeleteEvt.wait("executeDelete"); + ESP_LOGD(LOG_TAG, "<< executeDelete"); +} // executeDelete + + /** * @brief Dump details of this BLE GATT service. * @return N/A. @@ -153,6 +176,34 @@ void BLEService::start() { } // start +/** + * @brief Stop the service. + * @return Stop the service. + */ +void BLEService::stop() { +// We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). +// + ESP_LOGD(LOG_TAG, ">> stop(): Stopping service (esp_ble_gatts_stop_service): %s", toString().c_str()); + if (m_handle == NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "<< !!! We attempted to stop a service but don't know its handle!"); + return; + } + + m_semaphoreStopEvt.take("stop"); + esp_err_t errRc = ::esp_ble_gatts_stop_service(m_handle); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_stop_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreStopEvt.wait("stop"); + + ESP_LOGD(LOG_TAG, "<< stop()"); +} // start + + /** * @brief Set the handle associated with this service. * @param [in] handle The handle associated with the service. @@ -278,6 +329,10 @@ void BLEService::handleGATTServerEvent( break; } // ESP_GATTS_START_EVT + case ESP_GATTS_STOP_EVT: + m_semaphoreStopEvt.give(); + break; + // ESP_GATTS_CREATE_EVT // Called when a new service is registered as having been created. @@ -299,6 +354,11 @@ void BLEService::handleGATTServerEvent( break; } // ESP_GATTS_CREATE_EVT + case ESP_GATTS_DELETE_EVT: { + m_semaphoreDeleteEvt.give(); + break; + } + default: { break; } // Default diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 79958f3d..93b4b2c6 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -57,11 +57,13 @@ class BLEService { BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); void dump(); void executeCreate(BLEServer* pServer); + void executeDelete(); BLECharacteristic* getCharacteristic(const char* uuid); BLECharacteristic* getCharacteristic(BLEUUID uuid); BLEUUID getUUID(); BLEServer* getServer(); void start(); + void stop(); std::string toString(); uint16_t getHandle(); uint8_t m_id = 0; @@ -82,7 +84,9 @@ class BLEService { BLEUUID m_uuid; FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreDeleteEvt = FreeRTOS::Semaphore("DeleteEvt"); FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); + FreeRTOS::Semaphore m_semaphoreStopEvt = FreeRTOS::Semaphore("StopEvt"); uint32_t m_numHandles; diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index 96811ad2..78f00feb 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -120,4 +120,9 @@ BLEService* BLEServiceMap::getNext() { return pRet; } // getNext +void BLEServiceMap::removeService(BLEService *service){ + m_handleMap.erase(service->getHandle()); + m_uuidMap.erase(service); +} + #endif /* CONFIG_BT_ENABLED */ From 07c4b8220e6c5240acc1492ba286d25dbaf993e0 Mon Sep 17 00:00:00 2001 From: James Bergin Date: Thu, 21 Jun 2018 10:46:36 -0500 Subject: [PATCH 294/381] - Updated comments for documentation. --- cpp_utils/BLEService.cpp | 30 ++++++++++++++++++++++++------ cpp_utils/BLEServiceMap.cpp | 6 +++++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index d7a4da27..340ea560 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -94,13 +94,12 @@ void BLEService::executeCreate(BLEServer *pServer) { /** * @brief Delete the service. * Delete the service. - * @param [in] gatts_if The handle of the GATT server interface. * @return N/A. */ void BLEService::executeDelete() { ESP_LOGD(LOG_TAG, ">> executeDelete()"); - m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT + m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_DELETE_EVT esp_err_t errRc = ::esp_ble_gatts_delete_service( getHandle() ); @@ -329,9 +328,18 @@ void BLEService::handleGATTServerEvent( break; } // ESP_GATTS_START_EVT - case ESP_GATTS_STOP_EVT: - m_semaphoreStopEvt.give(); + // ESP_GATTS_STOP_EVT + // + // stop: + // esp_gatt_status_t status + // uint16_t service_handle + // + case ESP_GATTS_STOP_EVT: { + if (param->stop.service_handle == getHandle()) { + m_semaphoreStopEvt.give(); + } break; + } // ESP_GATTS_STOP_EVT // ESP_GATTS_CREATE_EVT @@ -354,10 +362,20 @@ void BLEService::handleGATTServerEvent( break; } // ESP_GATTS_CREATE_EVT + + // ESP_GATTS_DELETE_EVT + // Called when a service is deleted. + // + // delete: + // * esp_gatt_status_t status + // * uint16_t service_handle + // case ESP_GATTS_DELETE_EVT: { - m_semaphoreDeleteEvt.give(); + if (param->del.service_handle == getHandle()) { + m_semaphoreDeleteEvt.give(); + } break; - } + } // ESP_GATTS_DELETE_EVT default: { break; diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index 78f00feb..dd828fae 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -120,9 +120,13 @@ BLEService* BLEServiceMap::getNext() { return pRet; } // getNext +/** + * @brief Removes service from maps. + * @return N/A. + */ void BLEServiceMap::removeService(BLEService *service){ m_handleMap.erase(service->getHandle()); m_uuidMap.erase(service); -} +} // removeService #endif /* CONFIG_BT_ENABLED */ From de9d41386b046bd260ee8e42189fde2e67995c63 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Thu, 21 Jun 2018 20:27:57 +0200 Subject: [PATCH 295/381] Add Arduino compatibility to BLEDevice::init --- cpp_utils/BLEDevice.cpp | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 1c6e6bb8..6aa7c6e3 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -331,14 +331,21 @@ uint16_t BLEDevice::m_localMTU = 23; if(!initialized){ initialized = true; // Set the initialization flag to ensure we are only initialized once. - esp_err_t errRc = ::nvs_flash_init(); + esp_err_t errRc = ESP_OK; +#ifdef ARDUINO_ARCH_ESP32 + if (!btStart()) { + errRc = ESP_FAIL; + return; + } +#else + errRc = ::nvs_flash_init(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } - esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - errRc = esp_bt_controller_init(&bt_cfg); + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + errRc = esp_bt_controller_init(&bt_cfg); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; @@ -358,18 +365,24 @@ uint16_t BLEDevice::m_localMTU = 23; ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } +#endif #endif - errRc = esp_bluedroid_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); + if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){ + errRc = esp_bluedroid_init(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } } - errRc = esp_bluedroid_enable(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){ + errRc = esp_bluedroid_enable(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } } errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler); From 4743dfc22daa447f45b6416b4e872c0ac7bb6e6b Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 23 Jun 2018 11:34:30 -0500 Subject: [PATCH 296/381] Addition of missing header for Arduino --- cpp_utils/BLEDevice.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 6aa7c6e3..a7db454b 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -30,6 +30,7 @@ #include "GeneralUtils.h" #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" +#include "esp32-hal-bt.h" #endif static const char* LOG_TAG = "BLEDevice"; From 7e1cf238767d5456235afba7c37830633968dc89 Mon Sep 17 00:00:00 2001 From: Damon Smith Date: Tue, 26 Jun 2018 22:33:59 +1000 Subject: [PATCH 297/381] added a way to connect to a Station while keeping the AP running --- cpp_utils/WiFi.cpp | 16 ++++++++++++---- cpp_utils/WiFi.h | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 503e9235..2372dea6 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -146,16 +146,24 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { /** - * @brief Connect to an external access point. + * @see WiFi::connectWithMode - this one defaults the mode to WIFI_MODE_AP + */ +uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ + return this->connectWithMode(ssid, password, waitForConnection, WIFI_MODE_AP); +} + +/** + * @brief Connect to an external access point and specify the mode (WIFI_MODE_AP or WIFI_MODE_APSTA). * * The event handler will be called back with the outcome of the connection. * * @param [in] ssid The network SSID of the access point to which we wish to connect. * @param [in] password The password of the access point to which we wish to connect. * @param [in] waitForConnection Block until the connection has an outcome. - * @returns ESP_OK if successfully receives a SYSTEM_EVENT_STA_GOT_IP event. Otherwise returns wifi_err_reason_t - use GeneralUtils::wifiErrorToString(uint8_t errCode) to print the error. + * @param [in] mode WIFI_MODE_AP for normal or WIFI_MODE_APSTA if you want to keep an Access Point running while you connect + * @return N/A. */ -uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ +uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection, wifi_mode_t mode){ ESP_LOGD(LOG_TAG, ">> connectAP"); m_apConnectionStatus = UINT8_MAX; @@ -172,7 +180,7 @@ uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bo ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); } - esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); + esp_err_t errRc = ::esp_wifi_set_mode(mode); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); abort(); diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index a57360ca..09f3d56d 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -131,6 +131,7 @@ class WiFi { struct in_addr getHostByName(const std::string& hostName); struct in_addr getHostByName(const char* hostName); uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); + uint8_t connectWithMode(const std::string& ssid, const std::string& password, bool waitForConnection, wifi_mode_t mode); void dump(); bool isConnectedToAP(); static std::string getApMac(); From 18a03f9a20d55bfc55648f43964222725cab3e7e Mon Sep 17 00:00:00 2001 From: Damon Smith Date: Tue, 26 Jun 2018 22:42:01 +1000 Subject: [PATCH 298/381] fixed to just use default value params --- cpp_utils/WiFi.cpp | 7 ------- cpp_utils/WiFi.h | 3 +-- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 2372dea6..f1a07217 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -145,13 +145,6 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { } // setDNSServer -/** - * @see WiFi::connectWithMode - this one defaults the mode to WIFI_MODE_AP - */ -uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection){ - return this->connectWithMode(ssid, password, waitForConnection, WIFI_MODE_AP); -} - /** * @brief Connect to an external access point and specify the mode (WIFI_MODE_AP or WIFI_MODE_APSTA). * diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 09f3d56d..c1b6bedc 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -130,8 +130,7 @@ class WiFi { void setDNSServer(int numdns, ip_addr_t ip); struct in_addr getHostByName(const std::string& hostName); struct in_addr getHostByName(const char* hostName); - uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true); - uint8_t connectWithMode(const std::string& ssid, const std::string& password, bool waitForConnection, wifi_mode_t mode); + uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true, wifi_mode_t mode=WIFI_MODE_STA); void dump(); bool isConnectedToAP(); static std::string getApMac(); From cc56443c45344fa48101403fb62cc1249c2e1f01 Mon Sep 17 00:00:00 2001 From: Damon Smith Date: Tue, 26 Jun 2018 22:43:51 +1000 Subject: [PATCH 299/381] fix up comments on connectAP --- cpp_utils/WiFi.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index f1a07217..09d59d55 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -146,7 +146,7 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { /** - * @brief Connect to an external access point and specify the mode (WIFI_MODE_AP or WIFI_MODE_APSTA). + * @brief Connect to an external access point. * * The event handler will be called back with the outcome of the connection. * @@ -154,7 +154,7 @@ void WiFi::setDNSServer(int numdns, ip_addr_t ip) { * @param [in] password The password of the access point to which we wish to connect. * @param [in] waitForConnection Block until the connection has an outcome. * @param [in] mode WIFI_MODE_AP for normal or WIFI_MODE_APSTA if you want to keep an Access Point running while you connect - * @return N/A. + * @return ESP_OK if we are now connected and wifi_err_reason_t if not. */ uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection, wifi_mode_t mode){ ESP_LOGD(LOG_TAG, ">> connectAP"); From 8dbdaa504148f2207fad4e6e7b9974886f894685 Mon Sep 17 00:00:00 2001 From: Friedemann Date: Tue, 26 Jun 2018 15:36:05 +0200 Subject: [PATCH 300/381] added getServiceByUUID() to BLEServer --- cpp_utils/BLEServer.cpp | 18 ++++++++++++++++++ cpp_utils/BLEServer.h | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index d5c9de68..164d1c46 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -96,6 +96,24 @@ BLEService* BLEServer::createService(BLEUUID uuid, uint32_t numHandles, uint8_t } // createService +/** + * @brief Get a %BLE Service by its UUID + * @param [in] uuid The UUID of the new service. + * @return A reference to the service object. + */ +BLEService* BLEServer::getServiceByUUID(const char* uuid) { + return m_serviceMap.getByUUID(uuid); +} + +/** + * @brief Get a %BLE Service by its UUID + * @param [in] uuid The UUID of the new service. + * @return A reference to the service object. + */ +BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { + return m_serviceMap.getByUUID(uuid); +} + /** * @brief Retrieve the advertising object that can be used to advertise the existence of the server. * diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 95c55d53..8c140c44 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -63,7 +63,8 @@ class BLEServer { void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); void removeService(BLEService *service); - + BLEService* getServiceByUUID(const char* uuid); + BLEService* getServiceByUUID(BLEUUID uuid); private: BLEServer(); From d6ef14932ecd95b34fa9d066e179e10365ebb7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cabral?= Date: Thu, 5 Jul 2018 10:32:04 +0100 Subject: [PATCH 301/381] FIX: BLESecurity.h -> BLESecurityCallbacks. Make all the methods pure virtual. Some of the methods were virtual, but without local definitions, this creates an error during linking. --- cpp_utils/BLESecurity.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp_utils/BLESecurity.h b/cpp_utils/BLESecurity.h index 67c41efb..2d52b015 100644 --- a/cpp_utils/BLESecurity.h +++ b/cpp_utils/BLESecurity.h @@ -51,20 +51,20 @@ class BLESecurityCallbacks { * It requires that our device is capable to display this code to end user * @param */ - virtual void onPassKeyNotify(uint32_t pass_key); + virtual void onPassKeyNotify(uint32_t pass_key) = 0; /** * @brief Here we can make decision if we want to let negotiate authorization with peer device or not * return Return true if we accept this peer device request */ - virtual bool onSecurityRequest(); + virtual bool onSecurityRequest() = 0 ; /** * Provide us information when authentication process is completed */ - virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t); + virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t) = 0; - virtual bool onConfirmPIN(uint32_t pin); + virtual bool onConfirmPIN(uint32_t pin) = 0; }; // BLESecurityCallbacks #endif // CONFIG_BT_ENABLED From 74b056fdb4256614c0e548bb2954131b22b97f32 Mon Sep 17 00:00:00 2001 From: Robert Klep Date: Fri, 6 Jul 2018 12:24:37 +0200 Subject: [PATCH 302/381] Handle `response` argument for BLERemoteDescriptor#writeValue() --- cpp_utils/BLERemoteDescriptor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index 4d14ba80..2cdc7db1 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -148,7 +148,7 @@ void BLERemoteDescriptor::writeValue( getHandle(), length, // Data length data, // Data - ESP_GATT_WRITE_TYPE_NO_RSP, + response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE ); if (errRc != ESP_OK) { From caa546abd4c0685191aa7b047449cbe99584626f Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 10 Jul 2018 18:12:42 +0200 Subject: [PATCH 303/381] Update remote characteristic to register for notify/indications (write to 0x2902) --- cpp_utils/BLERemoteCharacteristic.cpp | 12 +++++++++++- cpp_utils/BLERemoteCharacteristic.h | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index d9c64c91..907e0251 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -475,7 +475,7 @@ void BLERemoteCharacteristic::registerForNotify( BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, - bool isNotify)) { + bool isNotify), bool notifications) { ESP_LOGD(LOG_TAG, ">> registerForNotify(): %s", toString().c_str()); m_notifyCallback = notifyCallback; // Save the notification callback. @@ -492,6 +492,12 @@ void BLERemoteCharacteristic::registerForNotify( if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } + + uint8_t val[] = {0x01, 0x00}; + if(!notifications) + val[0] = 0x02; + BLERemoteDescriptor *desc = getDescriptorByUUID("0x2902"); + desc->writeValue(val, 2); } // End Register else { // If we weren't passed a callback function, then this is an unregistration. esp_err_t errRc = ::esp_ble_gattc_unregister_for_notify( @@ -503,6 +509,10 @@ void BLERemoteCharacteristic::registerForNotify( if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_unregister_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } + + uint8_t val[] = {0x00, 0x00}; + BLERemoteDescriptor *desc = getDescriptorByUUID("0x2902"); + desc->writeValue(val, 2); } // End Unregister m_semaphoreRegForNotifyEvt.wait("registerForNotify"); diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 6f23f497..10a2ec4c 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -44,7 +44,7 @@ class BLERemoteCharacteristic { uint8_t readUInt8(void); uint16_t readUInt16(void); uint32_t readUInt32(void); - void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify)); + void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify), bool notifications = true); void writeValue(uint8_t* data, size_t length, bool response = false); void writeValue(std::string newValue, bool response = false); void writeValue(uint8_t newValue, bool response = false); From 49d6abababc0b00d75c78a100ec15cf23d5c13b3 Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 10 Jul 2018 18:22:45 +0200 Subject: [PATCH 304/381] Add onConnect callback with parameter to allow retrieve client MAC --- cpp_utils/BLEServer.cpp | 1 + cpp_utils/BLEServer.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index d5c9de68..272c1f0a 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -202,6 +202,7 @@ void BLEServer::handleGATTServerEvent( m_connId = param->connect.conn_id; // Save the connection id. if (m_pServerCallbacks != nullptr) { m_pServerCallbacks->onConnect(this); + m_pServerCallbacks->onConnect(this, param); } m_connectedCount++; // Increment the number of connected devices count. break; diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 585fdf89..417c129f 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -64,6 +64,7 @@ class BLEServer { void startAdvertising(); void removeService(BLEService *service); + private: BLEServer(); friend class BLEService; @@ -103,7 +104,7 @@ class BLEServerCallbacks { * @param [in] pServer A reference to the %BLE server that received the client connection. */ virtual void onConnect(BLEServer* pServer); - + virtual void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param); /** * @brief Handle an existing client disconnection. * From 1012efc4e658c490ee7052fad9cdddff370e372b Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 10 Jul 2018 18:41:53 +0200 Subject: [PATCH 305/381] Add read raw data from remote characteristic --- cpp_utils/BLERemoteCharacteristic.cpp | 13 +++++++++++++ cpp_utils/BLERemoteCharacteristic.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 907e0251..1830c0cf 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -204,6 +204,11 @@ void BLERemoteCharacteristic::gattClientEventHandler( // and unlock the semaphore to ensure that the requestor of the data can continue. if (evtParam->read.status == ESP_GATT_OK) { m_value = std::string((char*)evtParam->read.value, evtParam->read.value_len); + if(m_rawData != nullptr) + free(m_rawData); + + m_rawData = calloc(evtParam->read.value_len, sizeof(uint8_t)); + memcpy(m_rawData, evtParam->read.value, evtParam->read.value_len); } else { m_value = ""; } @@ -613,4 +618,12 @@ void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool resp writeValue(std::string((char *)data, length), response); } // writeValue +/** + * @brief Read raw data from remote characteristic as hex bytes + * @return return pointer data read + */ +uint8_t* BLERemoteCharacteristic::readRawData() { + return m_rawData; +} + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 10a2ec4c..044f8f62 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -49,6 +49,7 @@ class BLERemoteCharacteristic { void writeValue(std::string newValue, bool response = false); void writeValue(uint8_t newValue, bool response = false); std::string toString(void); + uint8_t* readRawData(); private: BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); @@ -76,6 +77,7 @@ class BLERemoteCharacteristic { FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt"); FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); std::string m_value; + uint8_t *m_rawData; void (*m_notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify); // We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID. From 8cf68913ef4e4d82106544ed7f333c1b1fe9cbb4 Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 10 Jul 2018 23:45:06 +0200 Subject: [PATCH 306/381] Quick fix --- cpp_utils/BLERemoteCharacteristic.cpp | 6 +++--- cpp_utils/BLEServer.cpp | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 1830c0cf..33d43eee 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -207,7 +207,7 @@ void BLERemoteCharacteristic::gattClientEventHandler( if(m_rawData != nullptr) free(m_rawData); - m_rawData = calloc(evtParam->read.value_len, sizeof(uint8_t)); + m_rawData = (uint8_t*) calloc(evtParam->read.value_len, sizeof(uint8_t)); memcpy(m_rawData, evtParam->read.value, evtParam->read.value_len); } else { m_value = ""; @@ -501,7 +501,7 @@ void BLERemoteCharacteristic::registerForNotify( uint8_t val[] = {0x01, 0x00}; if(!notifications) val[0] = 0x02; - BLERemoteDescriptor *desc = getDescriptorByUUID("0x2902"); + BLERemoteDescriptor *desc = getDescriptor(BLEUUID("0x2902")); desc->writeValue(val, 2); } // End Register else { // If we weren't passed a callback function, then this is an unregistration. @@ -516,7 +516,7 @@ void BLERemoteCharacteristic::registerForNotify( } uint8_t val[] = {0x00, 0x00}; - BLERemoteDescriptor *desc = getDescriptorByUUID("0x2902"); + BLERemoteDescriptor *desc = getDescriptor(BLEUUID("0x2902")); desc->writeValue(val, 2); } // End Unregister diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 272c1f0a..a124abcb 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -352,6 +352,12 @@ void BLEServerCallbacks::onConnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); } // onConnect +void BLEServerCallbacks::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) { + ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); + ESP_LOGD("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); + ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); +} // onConnect + void BLEServerCallbacks::onDisconnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", ">> onDisconnect(): Default"); From db780952f1d0f43cc30c28bb19e1bbc2f8f19b4f Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 5 Aug 2018 19:08:29 -0500 Subject: [PATCH 307/381] Sync --- cpp_utils/AWS.cpp | 123 +++++++++ cpp_utils/AWS.h | 1 + cpp_utils/Console.cpp | 200 ++++++++++++++ cpp_utils/Console.h | 149 +++++++++++ cpp_utils/GeneralUtils.cpp | 21 +- cpp_utils/MMU.cpp | 127 +++++++++ cpp_utils/MMU.h | 22 ++ cpp_utils/System.cpp | 433 +++++++++++++++++++++++++++++++ cpp_utils/System.h | 2 + cpp_utils/Task.cpp | 2 +- cpp_utils/Task.h | 2 +- tasks/watchdogs/README.md | 6 + tools/bootloaderExamine/main.cpp | 6 + 13 files changed, 1082 insertions(+), 12 deletions(-) create mode 100644 cpp_utils/Console.cpp create mode 100644 cpp_utils/Console.h create mode 100644 cpp_utils/MMU.cpp create mode 100644 cpp_utils/MMU.h create mode 100644 tasks/watchdogs/README.md diff --git a/cpp_utils/AWS.cpp b/cpp_utils/AWS.cpp index 561162c3..7687be21 100644 --- a/cpp_utils/AWS.cpp +++ b/cpp_utils/AWS.cpp @@ -19,6 +19,129 @@ AWS::AWS() { AWS::~AWS() { } +/** + * Convert an AWS IoT error code to a string representation. + * @param err The error code to be mapped. + * @return A string representation of the error code. + */ +/* static */ std::string AWS::errorToString(IoT_Error_t err) { + switch(err) { + case NETWORK_PHYSICAL_LAYER_CONNECTED : + return "NETWORK_PHYSICAL_LAYER_CONNECTED"; + case NETWORK_MANUALLY_DISCONNECTED : + return "NETWORK_MANUALLY_DISCONNECTED"; + case NETWORK_ATTEMPTING_RECONNECT: + return "NETWORK_ATTEMPTING_RECONNECT"; + case NETWORK_RECONNECTED: + return "NETWORK_RECONNECTED"; + case MQTT_NOTHING_TO_READ : + return "MQTT_NOTHING_TO_READ"; + case MQTT_CONNACK_CONNECTION_ACCEPTED: + return "MQTT_CONNACK_CONNECTION_ACCEPTED"; + case SUCCESS : + return "SUCCESS"; + case FAILURE: + return "FAILURE"; + case NULL_VALUE_ERROR : + return "NULL_VALUE_ERROR"; + case TCP_CONNECTION_ERROR : + return "TCP_CONNECTION_ERROR"; + case SSL_CONNECTION_ERROR: + return "SSL_CONNECTION_ERROR"; + case TCP_SETUP_ERROR : + return "TCP_SETUP_ERROR"; + case NETWORK_SSL_CONNECT_TIMEOUT_ERROR : + return "NETWORK_SSL_CONNECT_TIMEOUT_ERROR"; + case NETWORK_SSL_WRITE_ERROR : + return "NETWORK_SSL_WRITE_ERROR"; + case NETWORK_SSL_INIT_ERROR : + return "NETWORK_SSL_INIT_ERROR"; + case NETWORK_SSL_CERT_ERROR : + return "NETWORK_SSL_CERT_ERROR"; + case NETWORK_SSL_WRITE_TIMEOUT_ERROR : + return "NETWORK_SSL_WRITE_TIMEOUT_ERROR"; + case NETWORK_SSL_READ_TIMEOUT_ERROR : + return "NETWORK_SSL_READ_TIMEOUT_ERROR"; + case NETWORK_SSL_READ_ERROR : + return "NETWORK_SSL_READ_ERROR"; + case NETWORK_DISCONNECTED_ERROR : + return "NETWORK_DISCONNECTED_ERROR"; + case NETWORK_RECONNECT_TIMED_OUT_ERROR: + return "NETWORK_RECONNECT_TIMED_OUT_ERROR"; + case NETWORK_ALREADY_CONNECTED_ERROR : + return "NETWORK_ALREADY_CONNECTED_ERROR"; + case NETWORK_MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED : + return "NETWORK_MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED"; + case NETWORK_SSL_UNKNOWN_ERROR : + return "NETWORK_SSL_UNKNOWN_ERROR"; + case NETWORK_PHYSICAL_LAYER_DISCONNECTED : + return "NETWORK_PHYSICAL_LAYER_DISCONNECTED"; + case NETWORK_X509_ROOT_CRT_PARSE_ERROR : + return "NETWORK_X509_ROOT_CRT_PARSE_ERROR"; + case NETWORK_X509_DEVICE_CRT_PARSE_ERROR : + return "NETWORK_X509_DEVICE_CRT_PARSE_ERROR"; + case NETWORK_PK_PRIVATE_KEY_PARSE_ERROR : + return "NETWORK_PK_PRIVATE_KEY_PARSE_ERROR"; + case NETWORK_ERR_NET_SOCKET_FAILED : + return "NETWORK_ERR_NET_SOCKET_FAILED"; + case NETWORK_ERR_NET_UNKNOWN_HOST : + return "NETWORK_ERR_NET_UNKNOWN_HOST"; + case NETWORK_ERR_NET_CONNECT_FAILED : + return "NETWORK_ERR_NET_CONNECT_FAILED"; + case NETWORK_SSL_NOTHING_TO_READ : + return "NETWORK_SSL_NOTHING_TO_READ"; + case MQTT_CONNECTION_ERROR : + return "MQTT_CONNECTION_ERROR"; + case MQTT_CONNECT_TIMEOUT_ERROR : + return "MQTT_CONNECT_TIMEOUT_ERROR"; + case MQTT_REQUEST_TIMEOUT_ERROR: + return "MQTT_REQUEST_TIMEOUT_ERROR"; + case MQTT_UNEXPECTED_CLIENT_STATE_ERROR : + return "MQTT_UNEXPECTED_CLIENT_STATE_ERROR"; + case MQTT_CLIENT_NOT_IDLE_ERROR : + return "MQTT_CLIENT_NOT_IDLE_ERROR"; + case MQTT_RX_MESSAGE_PACKET_TYPE_INVALID_ERROR : + return "MQTT_RX_MESSAGE_PACKET_TYPE_INVALID_ERROR"; + case MQTT_RX_BUFFER_TOO_SHORT_ERROR : + return "MQTT_RX_BUFFER_TOO_SHORT_ERROR"; + case MQTT_TX_BUFFER_TOO_SHORT_ERROR : + return "MQTT_TX_BUFFER_TOO_SHORT_ERROR"; + case MQTT_MAX_SUBSCRIPTIONS_REACHED_ERROR : + return "MQTT_MAX_SUBSCRIPTIONS_REACHED_ERROR"; + case MQTT_DECODE_REMAINING_LENGTH_ERROR : + return "MQTT_DECODE_REMAINING_LENGTH_ERROR"; + case MQTT_CONNACK_UNKNOWN_ERROR : + return "MQTT_CONNACK_UNKNOWN_ERROR"; + case MQTT_CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR : + return "MQTT_CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR"; + case MQTT_CONNACK_IDENTIFIER_REJECTED_ERROR: + return "MQTT_CONNACK_IDENTIFIER_REJECTED_ERROR"; + case MQTT_CONNACK_SERVER_UNAVAILABLE_ERROR : + return "MQTT_CONNACK_SERVER_UNAVAILABLE_ERROR"; + case MQTT_CONNACK_BAD_USERDATA_ERROR: + return "MQTT_CONNACK_BAD_USERDATA_ERROR"; + case MQTT_CONNACK_NOT_AUTHORIZED_ERROR : + return "MQTT_CONNACK_NOT_AUTHORIZED_ERROR"; + case JSON_PARSE_ERROR : + return "JSON_PARSE_ERROR"; + case SHADOW_WAIT_FOR_PUBLISH : + return "SHADOW_WAIT_FOR_PUBLISH"; + case SHADOW_JSON_BUFFER_TRUNCATED : + return "SHADOW_JSON_BUFFER_TRUNCATED"; + case SHADOW_JSON_ERROR : + return "SHADOW_JSON_ERROR"; + case MUTEX_INIT_ERROR : + return "MUTEX_INIT_ERROR"; + case MUTEX_LOCK_ERROR: + return "MUTEX_LOCK_ERROR"; + case MUTEX_UNLOCK_ERROR : + return "MUTEX_UNLOCK_ERROR"; + case MUTEX_DESTROY_ERROR : + return "MUTEX_DESTROY_ERROR"; + default: + return "Unknown error!"; + } +} // AWS#errorToString /** * @brief Connect to the AWS IoT service. diff --git a/cpp_utils/AWS.h b/cpp_utils/AWS.h index 5d2c891c..abbd0613 100644 --- a/cpp_utils/AWS.h +++ b/cpp_utils/AWS.h @@ -24,6 +24,7 @@ class AWS { void connect(std::string clientId); void disconnect(); + static std::string errorToString(IoT_Error_t err); // Convert an AWS IoT error code to a string representation. void init(std::string host=CONFIG_AWS_IOT_MQTT_HOST, uint16_t port=CONFIG_AWS_IOT_MQTT_PORT); void publish(std::string topic, std::string payload, QoS qos = QOS0); void subscribe(std::string topic); diff --git a/cpp_utils/Console.cpp b/cpp_utils/Console.cpp new file mode 100644 index 00000000..c88d8372 --- /dev/null +++ b/cpp_utils/Console.cpp @@ -0,0 +1,200 @@ +/* + * Console.cpp + * + * Created on: Jun 15, 2018 + * Author: kolban + */ + +/** + * Example: + * Argtable argtable; + * argtable.addString("myparam", "l", "list", "Get Listings"); + * argtable.parse(argc, argv); + * ArgTableEntry_String* pStr = (ArgTableEntry_String*)argTable.get("myparam"); + * pStr->getValue(); + */ +#include "Console.h" + +/** + * Argtable instance constructor. + */ +ArgTable::ArgTable() { + m_argtable = nullptr; + m_argEnd = nullptr; +} // ArgTable#ArgTable + + +/** + * Argtable instance destructor. + */ +ArgTable::~ArgTable() { + freeArgtable(); // Release any resources associated with the argtable. +} // ArgTable#~ArgTable + + +/** + * Build the ArgTable that will be used for parsing. + */ +/* private */ void ArgTable::build() { + /* + * The m_argTableEntries is a std::list that contains the argtable entries in the form of a std::pair. We + * allocate storage for the ArgTable and then populate it. The last entry of the argtable must be an end marker. + */ + int size = m_argTableEntries.size(); + m_argtable = new void*[size + 1]; + int i=0; + for (auto it = m_argTableEntries.begin(); it != m_argTableEntries.end(); ++it) { + m_argtable[i] = it->second->getEntry(); + i++; + } + m_argEnd = arg_end(10); + m_argtable[i] = m_argEnd; +} // ArgTable#build + + +ArgTableEntry_Date ArgTable::addDate(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_Date* pDate = new ArgTableEntry_Date(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pDate)); + return *pDate; +} // ArgTable#addDate + + +ArgTableEntry_Double ArgTable::addDouble(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_Double* pDouble = new ArgTableEntry_Double(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pDouble)); + return *pDouble; +} // ArgTable#addDouble + + +ArgTableEntry_File ArgTable::addFile(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_File* pFile = new ArgTableEntry_File(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pFile)); + return *pFile; +} // ArgTable#addFile + + +ArgTableEntry_Int ArgTable::addInt(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_Int* pInt = new ArgTableEntry_Int(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pInt)); + return *pInt; +} // ArgTable#addInt + + +ArgTableEntry_Lit ArgTable::addLit(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_Lit* pLit = new ArgTableEntry_Lit(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pLit)); + return *pLit; +} // ArgTable#addLit + + +ArgTableEntry_String ArgTable::addString(std::string name, std::string shortopts, std::string longopts, std::string glossary, int min, int max) { + ArgTableEntry_String* pStr = new ArgTableEntry_String(shortopts, longopts, glossary, min, max); + m_argTableEntries.push_back(std::make_pair(name, pStr)); + return *pStr; +} // ArgTable#addString + + +/** + * Parse the input and output parameters against this argtable. + * @param argc A count of the number of parameters. + * @param argv An array of string parameters. + * @return The number of errors detected. + */ +int ArgTable::parse(int argc, char* argv[]) { + if (m_argtable == nullptr) { // If we don't have an argtable, build it. + build(); + } + int nErrors = arg_parse(argc, argv, m_argtable); + return nErrors; +} // ArgTable#parse + + +/** + * Print any errors associated with the parsing. + */ +void ArgTable::printErrors(FILE* fp, std::string progName) { + if (m_argEnd != nullptr) { + arg_print_errors(fp, m_argEnd, progName.c_str()); + } +} // ArgTable#printErrors + + +/** + * Release the argtable data. + */ +/* private */void ArgTable::freeArgtable() { + if (m_argtable != nullptr) { + arg_free(m_argtable); + m_argtable = nullptr; + m_argEnd = nullptr; + } +} // ArgTable#freeArgtable + + +ArgTableEntry_Date::ArgTableEntry_Date(std::string shortopts, std::string longopts, std::string glossary) { + m_argDate = arg_daten(shortopts.c_str(), longopts.c_str(), "", "", 0, 1, glossary.c_str()); + m_type = ArgType_t::DATE; +} // ArgTableEntry_Date#ArgTableEntry_Date + + +int ArgTableEntry_Date::getCount() { + return m_argDate->count; +} // ArgTableEntry_Date#getCount + + +ArgTableEntry_Double::ArgTableEntry_Double(std::string shortopts, std::string longopts, std::string glossary) { + m_argDbl = arg_dbln(shortopts.c_str(), longopts.c_str(), "", 0, 1, glossary.c_str()); + m_type = ArgType_t::DBL; +} + +int ArgTableEntry_Double::getCount() { + return m_argDbl->count; +} + +ArgTableEntry_File::ArgTableEntry_File(std::string shortopts, std::string longopts, std::string glossary) { + m_argFile = arg_filen(shortopts.c_str(), longopts.c_str(), "", 0, 1, glossary.c_str()); + m_type = ArgType_t::FILE; +} + +int ArgTableEntry_File::getCount() { + return m_argFile->count; +} + +ArgTableEntry_Int::ArgTableEntry_Int(std::string shortopts, std::string longopts, std::string glossary) { + m_argInt = arg_intn(shortopts.c_str(), longopts.c_str(), "", 0, 1, glossary.c_str()); + m_type = ArgType_t::INT; +} + +int ArgTableEntry_Int::getCount() { + return m_argInt->count; +} + +ArgTableEntry_Lit::ArgTableEntry_Lit(std::string shortopts, std::string longopts, std::string glossary) { + m_argLit = arg_litn(shortopts.c_str(), longopts.c_str(), 0, 1, glossary.c_str()); + m_type = ArgType_t::LIT; +} + +int ArgTableEntry_Lit::getCount() { + return m_argLit->count; +} + +int ArgTableEntry_Regex::getCount() { + return m_argRex->count; +} + +ArgTableEntry_String::ArgTableEntry_String(std::string shortopts, std::string longopts, std::string glossary, int min, int max) { + m_argStr = arg_strn(shortopts.c_str(), longopts.c_str(), "", min, max, glossary.c_str()); + m_type = ArgType_t::STR; +} + +int ArgTableEntry_String::getCount() { + return m_argStr->count; +} + + +Console::Console() { +} + +Console::~Console() { +} + diff --git a/cpp_utils/Console.h b/cpp_utils/Console.h new file mode 100644 index 00000000..2ce40fa2 --- /dev/null +++ b/cpp_utils/Console.h @@ -0,0 +1,149 @@ +/* + * Console.h + * + * Created on: Jun 15, 2018 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_CONSOLE_H_ +#define COMPONENTS_CPP_UTILS_CONSOLE_H_ + +#include +#include +#include +#include + +class Console { +public: + Console(); + virtual ~Console(); +}; + + +enum class ArgType_t { LIT, INT, DBL, STR, REX, FILE, DATE }; + + +class ArgTableEntry_Generic { +protected: + ArgType_t m_type; +public: + virtual int getCount(); + bool hasValue() { + return getCount() > 0; + } + virtual void* getEntry() = 0; +}; + + +class ArgTableEntry_Lit : public ArgTableEntry_Generic { +private: + struct arg_lit* m_argLit; +public: + int getCount(); + ArgTableEntry_Lit(std::string shortopts, std::string longopts, std::string glossary); + void* getEntry() { + return m_argLit; + } +}; + +class ArgTableEntry_Int : public ArgTableEntry_Generic { +private: + struct arg_int* m_argInt; +public: + ArgTableEntry_Int(std::string shortopts, std::string longopts, std::string glossary); + int getCount(); + int getValue(int index=0); + void* getEntry() { + return m_argInt; + } +}; + + +class ArgTableEntry_Double : public ArgTableEntry_Generic { +private: + struct arg_dbl* m_argDbl; +public: + ArgTableEntry_Double(std::string shortopts, std::string longopts, std::string glossary); + int getCount(); + double getValue(int index=0); + void* getEntry() { + return m_argDbl; + } +}; + + +class ArgTableEntry_String : public ArgTableEntry_Generic { +private: + struct arg_str* m_argStr; +public: + ArgTableEntry_String(std::string shortopts, std::string longopts, std::string glossary, int min, int max); + int getCount(); + std::string getValue(int index=0); + void* getEntry() { + return m_argStr; + } +}; + + +class ArgTableEntry_Regex : public ArgTableEntry_Generic { +private: + struct arg_rex* m_argRex; +public: + int getCount(); + std::string getValue(int index=0); + void* getEntry() { + return m_argRex; + } +}; + + +class ArgTableEntry_File : public ArgTableEntry_Generic { +private: + struct arg_file* m_argFile; +public: + ArgTableEntry_File(std::string shortopts, std::string longopts, std::string glossary); + int getCount(); + std::string getFilename(int index=0); + std::string getBasename(int index=0); + std::string getExtension(int index=0); + void* getEntry() { + return m_argFile; + } +}; + + +class ArgTableEntry_Date : public ArgTableEntry_Generic { +private: + struct arg_date* m_argDate; +public: + ArgTableEntry_Date(std::string shortopts, std::string longopts, std::string glossary); + int getCount(); + struct tm* getValue(int index=0); + void* getEntry() { + return m_argDate; + } +}; + + +class ArgTable { +private: + void** m_argtable; + struct arg_end* m_argEnd; + std::list> m_argTableEntries; + void build(); + void freeArgtable(); + +public: + ArgTable(); + ~ArgTable(); + ArgTableEntry_Date addDate(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_Double addDouble(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_File addFile(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_Int addInt(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_Lit addLit(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_String addString(std::string name, std::string shortopts, std::string longopts, std::string glossary, int min, int max); + int parse(int argc, char* argv[]); + void printErrors(FILE* fp, std::string progName=""); +}; + +#endif /* COMPONENTS_CPP_UTILS_CONSOLE_H_ */ diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 960f3172..e26d84c9 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -296,7 +296,8 @@ void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { char tempBuf[80]; uint32_t lineNumber = 0; - ESP_LOGD(LOG_TAG, " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ----------------"); + ESP_LOGD(LOG_TAG, " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"); + ESP_LOGD(LOG_TAG, " -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); strcpy(ascii, ""); strcpy(hex, ""); uint32_t index=0; @@ -365,23 +366,23 @@ std::vector GeneralUtils::split(std::string source, char delimiter) const char* GeneralUtils::errorToString(esp_err_t errCode) { switch(errCode) { case ESP_OK: - return "OK"; + return "ESP_OK"; case ESP_FAIL: - return "Fail"; + return "ESP_FAIL"; case ESP_ERR_NO_MEM: - return "No memory"; + return "ESP_ERR_NO_MEM"; case ESP_ERR_INVALID_ARG: - return "Invalid argument"; + return "ESP_ERR_INVALID_ARG"; case ESP_ERR_INVALID_SIZE: - return "Invalid state"; + return "ESP_ERR_INVALID_SIZE"; case ESP_ERR_INVALID_STATE: - return "Invalid state"; + return "ESP_ERR_INVALID_STATE"; case ESP_ERR_NOT_FOUND: - return "Not found"; + return "ESP_ERR_NOT_FOUND"; case ESP_ERR_NOT_SUPPORTED: - return "Not supported"; + return "ESP_ERR_NOT_SUPPORTED"; case ESP_ERR_TIMEOUT: - return "Timeout"; + return "ESP_ERR_TIMEOUT"; case ESP_ERR_NVS_NOT_INITIALIZED: return "ESP_ERR_NVS_NOT_INITIALIZED"; case ESP_ERR_NVS_NOT_FOUND: diff --git a/cpp_utils/MMU.cpp b/cpp_utils/MMU.cpp new file mode 100644 index 00000000..e43328fb --- /dev/null +++ b/cpp_utils/MMU.cpp @@ -0,0 +1,127 @@ +/* + * MMU.cpp + * + * Created on: Jun 30, 2018 + * Author: kolban + */ + +#include "MMU.h" +#include +#include +#include + +// The following functions are provided by spi_flash.h +extern "C" { + extern void spi_flash_disable_interrupts_caches_and_other_cpu(); + + // Enable cache, enable interrupts (to be added in future), resume scheduler + extern void spi_flash_enable_interrupts_caches_and_other_cpu(); +} + + +typedef struct { + uint32_t low; + uint32_t high; +} addressRange_t; + +static addressRange_t entryNumberToAddressRange(uint32_t entryNumber) { + addressRange_t ret; + if (entryNumber < 64) { + ret.low = 0x3F400000 + 64*1024*entryNumber; + ret.high = 0x3F400000 + 64*1024*(entryNumber+1)-1; + return ret; + } + ret.low = 0x40000000 + 64*1024*(entryNumber-64); + ret.high = 0x40000000 + 64*1024*(entryNumber+1-64)-1; + return ret; +} + + +static uint32_t flashPageToOffset(uint32_t page) { + return page*64*1024; +} + + + +/* static */ void MMU::dump() { + const uint32_t mappingInvalid = 1 << 8; + + printf("PRO CPU MMU\n"); + for (int i=0; i<256; i++) { + if (!(DPORT_PRO_FLASH_MMU_TABLE[i] & mappingInvalid)) { + addressRange_t addressRange = entryNumberToAddressRange(i); + printf("Entry: %2d (0x%8.8x - 0x%8.8x), Page: %d - offset: 0x%x\n", + i, + addressRange.low, addressRange.high, + DPORT_PRO_FLASH_MMU_TABLE[i] & 0xff, + flashPageToOffset(DPORT_PRO_FLASH_MMU_TABLE[i] & 0xff)); + } + } + printf("\n"); + printf("APP CPU MMU\n"); + for (int i=0; i<256; i++) { + if (!(DPORT_APP_FLASH_MMU_TABLE[i] & mappingInvalid)) { + addressRange_t addressRange = entryNumberToAddressRange(i); + printf("Entry: %2d (0x%8.8x - 0x%8.8x), Page: %d - offset: 0x%x\n", + i, + addressRange.low, addressRange.high, + DPORT_APP_FLASH_MMU_TABLE[i] & 0xff, + flashPageToOffset(DPORT_APP_FLASH_MMU_TABLE[i] & 0xff)); + } + } +} // MMU#dumpMMU + +extern "C" { + static void IRAM_ATTR mapFlashToVMA_Internal(uint32_t flashOffset, void* vma, size_t size); +} + +static void IRAM_ATTR mapFlashToVMA_Internal(uint32_t flashOffset, void* vma, size_t size) { + printf(">> MMU::mapFlashToVMA: flash offset: 0x%x, VMA: 0x%x, size: %d\n", flashOffset, (uint32_t)vma, size); + uint32_t mmuEntryStart; // The MMU table entry to start mapping. + uint32_t mmuEntryEnd; // The MMU table entry to end mapping. + + if ((uint32_t)vma >= 0x40000000 && (uint32_t)vma < 0x40C00000) { + mmuEntryStart = (((uint32_t)vma - 0x40000000)/(64*1024)) + 64; + mmuEntryEnd = (((uint32_t)vma - 0x40000000 + size)/(64*1024)) + 64; + } + else if ((uint32_t)vma >= 0x3F400000 && (uint32_t)vma < 0x3F800000) { + mmuEntryStart = (((uint32_t)vma - 0x3F400000)/(64*1024)); + mmuEntryEnd = (((uint32_t)vma - 0x3F400000 + size)/(64*1024)); + } + else { + printf(" - Unable to map from flash to VMA."); + return; + } + + // At this point we have populated mmuEntryStart and mmuEntryEnd which are the MMU table entries. + uint32_t pFlashStart = flashOffset; + uint32_t pFlashEnd = flashOffset + size; + + printf(" - Mapping flash to VMA via MMU. MMU entries start: %d, end: %d, mapping flash 0x%x (flash page: %d) to 0x%x (flash page: %d)\n", + mmuEntryStart, mmuEntryEnd, pFlashStart, pFlashStart/(64*1024), pFlashEnd, pFlashEnd/(64*1024)); + + uint32_t flashRegion = pFlashStart / (64*1024); // Determine the 64K chunk of flash to be mapped (we map in units of 64K). + + spi_flash_disable_interrupts_caches_and_other_cpu(); + + // For each of the mapping entries, map it to the corresponding flash region. + for (uint32_t i=mmuEntryStart; i<=mmuEntryEnd; i++) { + DPORT_PRO_FLASH_MMU_TABLE[i] = flashRegion; // There are two tables. One for the PRO CPU and one for the APP CPU. + DPORT_APP_FLASH_MMU_TABLE[i] = flashRegion; // Map both of them to the flash region. + flashRegion++; + } + + Cache_Flush(0); + Cache_Flush(1); + spi_flash_enable_interrupts_caches_and_other_cpu(); +} // mapFlashToVMA + +/** + * Map an area of flash memory into VMA. + * @param flashOffset The offset in flash of the start of data. + * @param vma Where in VMA the data should appear. + * @size How much data to map. + */ +void IRAM_ATTR MMU::mapFlashToVMA(uint32_t flashOffset, void* vma, size_t size) { + mapFlashToVMA_Internal(flashOffset, vma, size); +} diff --git a/cpp_utils/MMU.h b/cpp_utils/MMU.h new file mode 100644 index 00000000..d8ba6b64 --- /dev/null +++ b/cpp_utils/MMU.h @@ -0,0 +1,22 @@ +/* + * MMU.h + * + * Created on: Jun 30, 2018 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_MMU_H_ +#define COMPONENTS_CPP_UTILS_MMU_H_ +#include +#include +#include + +class MMU { +public: + MMU(); + virtual ~MMU(); + static void dump(); + static void mapFlashToVMA(uint32_t flashOffset, void* vma, size_t size); +}; + +#endif /* COMPONENTS_CPP_UTILS_MMU_H_ */ diff --git a/cpp_utils/System.cpp b/cpp_utils/System.cpp index 1993be4c..e9e7c525 100644 --- a/cpp_utils/System.cpp +++ b/cpp_utils/System.cpp @@ -7,11 +7,133 @@ #include "System.h" #include +#include +#include extern "C" { #include } +typedef volatile struct { + union { + struct { + uint32_t mcu_oe: 1; + uint32_t slp_sel: 1; + uint32_t mcu_wpd: 1; + uint32_t mcu_wpu: 1; + uint32_t mcu_ie: 1; + uint32_t mcu_drb: 2; + uint32_t func_wpd: 1; + uint32_t func_wpu: 1; + uint32_t func_ie: 1; + uint32_t func_drv: 2; + uint32_t mcu_sel: 3; + uint32_t reserved15: 17; + }; + uint32_t val; + }; +} io_mux_reg_t; + + +typedef volatile struct { + union { + struct { + uint32_t clk1: 4; + uint32_t clk2: 4; + uint32_t clk3: 4; + uint32_t reserved12: 20; + }; + uint32_t val; + } pin_ctrl; + + // The 36 exposed pads. + io_mux_reg_t pad_gpio36; // GPIO36 + io_mux_reg_t pad_gpio37; // GPIO37 + io_mux_reg_t pad_gpio38; // GPIO38 + io_mux_reg_t pad_gpio39; // GPIO39 + io_mux_reg_t pad_gpio34; // GPIO34 + io_mux_reg_t pad_gpio35; // GPIO35 + io_mux_reg_t pad_gpio32; // GPIO32 + io_mux_reg_t pad_gpio33; // GPIO33 + io_mux_reg_t pad_gpio25; // GPIO25 + io_mux_reg_t pad_gpio26; // GPIO26 + io_mux_reg_t pad_gpio27; // GPIO27 + io_mux_reg_t pad_mtms; // GPIO14 + io_mux_reg_t pad_mtdi; // GPIO12 + io_mux_reg_t pad_mtck; // GPIO13 + io_mux_reg_t pad_mtdo; // GPIO15 + io_mux_reg_t pad_gpio2; // GPIO2 + io_mux_reg_t pad_gpio0; // GPIO0 + io_mux_reg_t pad_gpio4; // GPIO4 + io_mux_reg_t pad_gpio16; // GPIO16 + io_mux_reg_t pad_gpio17; // GPIO17 + io_mux_reg_t pad_sd_data2; // GPIO9 + io_mux_reg_t pad_sd_data3; // GPIO10 + io_mux_reg_t pad_sd_cmd; // GPIO11 + io_mux_reg_t pad_sd_clk; // GPIO6 + io_mux_reg_t pad_sd_data0; // GPIO7 + io_mux_reg_t pad_sd_data1; // GPIO8 + io_mux_reg_t pad_gpio5; // GPIO5 + io_mux_reg_t pad_gpio18; // GPIO18 + io_mux_reg_t pad_gpio19; // GPIO19 + io_mux_reg_t pad_gpio20; // GPIO20 + io_mux_reg_t pad_gpio21; // GPIO21 + io_mux_reg_t pad_gpio22; // GPIO22 + io_mux_reg_t pad_u0rxd; // GPIO3 + io_mux_reg_t pad_u0txd; // GPIO1 + io_mux_reg_t pad_gpio23; // GPIO23 + io_mux_reg_t pad_gpio24; // GPIO24 +} io_mux_dev_t; + +static io_mux_dev_t* IO_MUX = (io_mux_dev_t*)0x3ff49000; + +static const io_mux_reg_t* io_mux_translate[] = { + &IO_MUX->pad_gpio0, // 0 + &IO_MUX->pad_u0txd, // 1 + &IO_MUX->pad_gpio2, // 2 + &IO_MUX->pad_u0rxd, // 3 + &IO_MUX->pad_gpio4, // 4 + &IO_MUX->pad_gpio5, // 5 + &IO_MUX->pad_sd_clk, // 6 + &IO_MUX->pad_sd_data0, // 7 + &IO_MUX->pad_sd_data1, // 8 + &IO_MUX->pad_sd_data2, // 9 + &IO_MUX->pad_sd_data3, // 10 + &IO_MUX->pad_sd_cmd, // 11 + &IO_MUX->pad_mtdi, // 12 + &IO_MUX->pad_mtck, // 13 + &IO_MUX->pad_mtms, // 14 + &IO_MUX->pad_mtdo, // 15 + &IO_MUX->pad_gpio16, // 16 + &IO_MUX->pad_gpio17, // 17 + &IO_MUX->pad_gpio18, // 18 + &IO_MUX->pad_gpio19, // 19 + &IO_MUX->pad_gpio20, // 20 + &IO_MUX->pad_gpio21, // 21 + &IO_MUX->pad_gpio22, // 22 + &IO_MUX->pad_gpio23, // 23 + &IO_MUX->pad_gpio24, // 24 + &IO_MUX->pad_gpio25, // 25 + &IO_MUX->pad_gpio26, // 26 + &IO_MUX->pad_gpio27, // 27 + nullptr, // 28 + nullptr, // 29 + nullptr, // 30 + nullptr, // 31 + &IO_MUX->pad_gpio32, // 32 + &IO_MUX->pad_gpio33, // 33 + &IO_MUX->pad_gpio34, // 34 + &IO_MUX->pad_gpio35, // 35 + &IO_MUX->pad_gpio36, // 36 + &IO_MUX->pad_gpio37, // 37 + &IO_MUX->pad_gpio38, // 38 + &IO_MUX->pad_gpio39 // 39 +}; + +static const io_mux_reg_t* gpioToIoMux(int gpio) { + return io_mux_translate[gpio]; +} + System::System() { // TODO Auto-generated constructor stub @@ -21,6 +143,317 @@ System::~System() { // TODO Auto-generated destructor stub } +const static char* outSignalStrings[] = { + "SPICLK_out", // 0 + "SPIQ_out", // 1 + "SPID_out", // 2 + "SPIHD_out", //3 + "SPIWP_out", // 4 + "SPICS0_out", // 5 + "SPICS1_out", // 6 + "SPICS2_out", // 7 + "HSPICLK_out", // 8 + "HSPIQ_out", // 9 + "HSPID_out", // 10 + "HSPICS0_out", // 11 + "HSPIHD_out", // 12 + "HSPIWP_out", // 13 + "U0TXD_out", // 14 + "U0RTS_out", // 15 + "U0DTR_out", // 16 + "U1TXD_out", // 17 + "U1RTS_out", // 18 + "", // 19 + "", // 20 + "", // 21 + "", // 22 + "I2S0O_BCK_out", // 23 + "I2S1O_BCK_out", // 24 + "I2S0O_WS_out", // 25 + "I2S1O_WS_out", // 26 + "I2S0I_BCK_out", // 27 + "I2S0I_WS_out", // 28 + "I2CEXT0_SCL_out", // 29 + "I2CEXT0_SDA_out", // 30 + "sdio_tohost_int_out", // 31 + "pwm0_out0a", // 32 + "pwm0_out0b", // 33 + "pwm0_out1a", // 34 + "pwm0_out1b", // 35 + "pwm0_out2a", // 36 + "pwm0_out2b", //37 + "", // 38 + "", // 39 + "", // 40 + "", // 41 + "", // 42 + "", // 43 + "", // 44 + "", // 45 + "", // 46 + "", // 47 + "", // 48 + "", // 49 + "", // 50 + "", // 51 + "", // 52 + "", // 53 + "", // 54 + "", // 55 + "", // 56 + "", // 57 + "", // 58 + "", // 59 + "", // 60 + "HSPICS1_out", // 61 + "HSPICS2_out", // 62 + "VSPICLK_out_mux", // 63 + "VSPIQ_out", // 64 + "VSPID_out", // 65 + "VSPIHD_out", // 66 + "VSPIWP_out", // 67 + "VSPICS0_out", // 68 + "VSPICS1_out", // 69 + "VSPICS2_out", // 70 + "ledc_hs_sig_out0", // 71 + "ledc_hs_sig_out1", // 72 + "ledc_hs_sig_out2", // 73 + "ledc_hs_sig_out3", // 74 + "ledc_hs_sig_out4", // 75 + "ledc_hs_sig_out5", // 76 + "ledc_hs_sig_out6", // 77 + "ledc_hs_sig_out7", // 78 + "edc_ls_sig_out0", // 79 + "ledc_ls_sig_out1", // 80 + "ledc_ls_sig_out2", // 81 + "ledc_ls_sig_out3", // 82 + "ledc_ls_sig_out4", // 83 + "ledc_ls_sig_out5", // 84 + "ledc_ls_sig_out6", // 85 + "ledc_ls_sig_out7", // 86 + "rmt_sig_out0", // 87 + "rmt_sig_out1", // 88 + "rmt_sig_out2", // 89 + "rmt_sig_out3", // 90 + "rmt_sig_out4", // 91 + "rmt_sig_out5", // 92 + "rmt_sig_out6", // 93 + "rmt_sig_out7", // 94 + "I2CEXT1_SCL_out", // 95 + "I2CEXT1_SCL_out", // 96 + "host_ccmd_od_pullup_en_n", // 97 + "host_rst_n_1", // 98 + "host_rst_n_2", // 99 + "gpio_sd0_out", // 100 + "gpio_sd1_out", // 101 + "gpio_sd2_out", // 102 + "gpio_sd3_out", // 103 + "gpio_sd4_out", // 104 + "gpio_sd5_out", // 105 + "gpio_sd6_out", // 106 + "gpio_sd7_out", // 107 + "pwm1_out0a", // 108 + "pwm1_out0b", // 109 + "pwm1_out1a", // 110 + "pwm1_out1b", // 111 + "pwm1_out2a", // 112 + "pwm1_out2b", // 113 + "pwm2_out1h", // 114 + "pwm2_out1l", // 115 + "pwm2_out2h", // 116 + "pwm2_out2l", // 117 + "pwm2_out3h", // 118 + "pwm2_out3l", // 119 + "pwm2_out4h", // 120 + "pwm2_out4l", // 121 + "", // 122 + "", // 123 + "", // 124 + "", // 125 + "", // 126 + "", // 127 + "", // 128 + "", // 129 + "", // 130 + "", // 131 + "", // 132 + "", // 133 + "", // 134 + "", // 135 + "", // 136 + "", // 137 + "", // 138 + "", // 139 + "I2S0O_DATA_out0", // 140 + "I2S0O_DATA_out1", // 141 + "I2S0O_DATA_out2", // 142 + "I2S0O_DATA_out3", // 143 + "I2S0O_DATA_out4", // 144 + "I2S0O_DATA_out5", // 145 + "I2S0O_DATA_out6", // 146 + "I2S0O_DATA_out7", // 147 + "I2S0O_DATA_out8", // 148 + "I2S0O_DATA_out9", // 149 + "I2S0O_DATA_out10", // 150 + "I2S0O_DATA_out11", // 151 + "I2S0O_DATA_out12", // 152 + "I2S0O_DATA_out13", // 153 + "I2S0O_DATA_out14", // 154 + "I2S0O_DATA_out15", // 155 + "I2S0O_DATA_out16", // 156 + "I2S0O_DATA_out17", // 157 + "I2S0O_DATA_out18", // 158 + "I2S0O_DATA_out19", // 159 + "I2S0O_DATA_out20", // 160 + "I2S0O_DATA_out21", // 161 + "I2S0O_DATA_out22", // 162 + "I2S0O_DATA_out23", // 163 + "I2S1I_BCK_out", // 164 + "I2S1I_WS_out", // 165 + "I2S1O_DATA_out0", // 166 + "I2S1O_DATA_out1", // 167 + "I2S1O_DATA_out2", // 168 + "I2S1O_DATA_out3", // 169 + "I2S1O_DATA_out4", // 170 + "I2S1O_DATA_out5", // 171 + "I2S1O_DATA_out6", // 172 + "I2S1O_DATA_out7", // 173 + "I2S1O_DATA_out8", // 174 + "I2S1O_DATA_out9", // 175 + "I2S1O_DATA_out10", // 176 + "I2S1O_DATA_out11", // 177 + "I2S1O_DATA_out12", // 178 + "I2S1O_DATA_out13", // 179 + "I2S1O_DATA_out14", // 180 + "I2S1O_DATA_out15", // 181 + "I2S1O_DATA_out16", // 182 + "I2S1O_DATA_out17", // 183 + "I2S1O_DATA_out18", // 184 + "I2S1O_DATA_out19", // 185 + "I2S1O_DATA_out20", // 186 + "I2S1O_DATA_out21", // 187 + "I2S1O_DATA_out22", // 188 + "I2S1O_DATA_out23", // 189 + "pwm3_out1h", // 190 + "pwm3_out1l", // 191 + "pwm3_out2h", // 192 + "pwm3_out2l", // 193 + "pwm3_out3h", // 194 + "pwm3_out3l", // 195 + "pwm3_out4h", // 196 + "pwm3_out4l", // 197 + "U2TXD_out", // 198 + "U2RTS_out", // 199 + "emac_mdc_o", // 200 + "emac_mdo_o", // 201 + "emac_crs_o", // 202 + "emac_col_o", // 203 + "bt_audio0_irq", // 204 + "bt_audio1_irq", // 205 + "bt_audio2_irq", // 206 + "ble_audio0_irq", // 207 + "ble_audio1_irq", // 208 + "ble_audio2_irq", // 209 + "pcmfsync_out", // 210 + "pcmclk_out", // 211 + "pcmdout", // 212 + "ble_audio_sync0_p", // 213 + "ble_audio_sync1_p", // 214 + "ble_audio_sync2_p", // 215 + "", // 216 + "", // 217 + "", // 218 + "", // 219 + "", // 220 + "", // 221 + "", // 222 + "", // 223 + "sig_in_func224", // 224 + "sig_in_func225", // 225 + "sig_in_func226", // 226 + "sig_in_func227", // 227 + "sig_in_func228", // 228 + "", // 229 + "", // 230 + "", // 231 + "", // 232 + "", // 233 + "", // 234 + "", // 235 + "", // 236 + "", // 237 + "", // 238 + "", // 239 + "", // 240 + "", // 241 + "", // 242 + "", // 243 + "", // 244 + "", // 245 + "", // 246 + "", // 247 + "", // 248 + "", // 249 + "", // 250 + "", // 251 + "", // 252 + "", // 253 + "", // 254 + "", // 255 +}; + + +/** + * Dump the mappings for GPIO pins. + */ +/* static */ void System::dumpPinMapping() { + const int numPins = 40; + printf("GPIO_FUNCn_OUT_SEL_CFG_REG\n"); + printf("--------------------------\n"); + printf("%3s %4s\n", "Pin", "Func"); + for (int i=0; imcu_sel + 1, io_mux->func_ie); + } + } + +} // System#dumpPinMapping + + +/** + * Dump the storage stats for the heap. + */ +/* static */ void System::dumpHeapInfo() { + multi_heap_info_t heapInfo; + + printf(" %10s %10s %10s %10s %13s %11s %12s\n", "Free", "Allocated", "Largest", "Minimum", "Alloc Blocks", "Free Blocks", "Total Blocks"); + heap_caps_get_info(&heapInfo, MALLOC_CAP_EXEC); + printf("EXEC %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_32BIT); + printf("32BIT %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_8BIT); + printf("8BIT %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_DMA); + printf("DMA %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_SPIRAM); + printf("SPISRAM %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_INTERNAL); + printf("INTERNAL %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_DEFAULT); + printf("DEFAULT %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); +} // System#dumpHeapInfo + + /** * @brief Get the information about the device. * @param [out] info The structure to be populated on return. diff --git a/cpp_utils/System.h b/cpp_utils/System.h index 6459509d..50337c32 100644 --- a/cpp_utils/System.h +++ b/cpp_utils/System.h @@ -18,6 +18,8 @@ class System { public: System(); virtual ~System(); + static void dumpPinMapping(); // Dump the mappings of pins to functions. + static void dumpHeapInfo(); static void getChipInfo(esp_chip_info_t *info); static size_t getFreeHeapSize(); static std::string getIDFVersion(); diff --git a/cpp_utils/Task.cpp b/cpp_utils/Task.cpp index a8ba9a37..2177b888 100644 --- a/cpp_utils/Task.cpp +++ b/cpp_utils/Task.cpp @@ -43,7 +43,7 @@ Task::~Task() { * @return N/A. */ -void Task::delay(int ms) { +/* static */ void Task::delay(int ms) { ::vTaskDelay(ms/portTICK_PERIOD_MS); } // delay diff --git a/cpp_utils/Task.h b/cpp_utils/Task.h index 0d58f222..f7d88754 100644 --- a/cpp_utils/Task.h +++ b/cpp_utils/Task.h @@ -51,7 +51,7 @@ class Task { * @param [in] data The data passed in to the newly started task. */ virtual void run(void *data) = 0; // Make run pure virtual - void delay(int ms); + static void delay(int ms); private: xTaskHandle m_handle; diff --git a/tasks/watchdogs/README.md b/tasks/watchdogs/README.md new file mode 100644 index 00000000..85c7c5c9 --- /dev/null +++ b/tasks/watchdogs/README.md @@ -0,0 +1,6 @@ +#Watchdogs +This is a sample application that illustrates the capabilities of watchdogs and watchdog processing. + +A related YouTube video is available here: + +https://www.youtube.com/watch?v=C2xF3O6qkbg \ No newline at end of file diff --git a/tools/bootloaderExamine/main.cpp b/tools/bootloaderExamine/main.cpp index 22e8a5aa..60472e30 100644 --- a/tools/bootloaderExamine/main.cpp +++ b/tools/bootloaderExamine/main.cpp @@ -1,5 +1,6 @@ #include #include +#include /* Main header of binary image */ typedef struct { @@ -120,6 +121,11 @@ int main(int argc, char *argv[]) { return 0; } + if (header.magic != 0xE9) { + printf("Failed to find magic number (0xE9) in BIN file header.\n"); + return 0; + } + printf("Dump of ESP32 binary file: %s\n", fileName); printf("magic: 0x%x, segment_count: %d, entry_addr: 0x%x - %s, hash_appended: %d\n", header.magic, header.segment_count, header.entry_addr, area(header.entry_addr), header.hash_appended); From f50bcb0731c783b0676efa30cfcefb5a7954895f Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 5 Aug 2018 19:09:53 -0500 Subject: [PATCH 308/381] sync --- Documentation/ideas/FastFlash/README.md | 8 ++ tasks/watchdogs/README.md | 2 +- tasks/watchdogs/main.cpp | 155 ++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 Documentation/ideas/FastFlash/README.md create mode 100644 tasks/watchdogs/main.cpp diff --git a/Documentation/ideas/FastFlash/README.md b/Documentation/ideas/FastFlash/README.md new file mode 100644 index 00000000..d599b4aa --- /dev/null +++ b/Documentation/ideas/FastFlash/README.md @@ -0,0 +1,8 @@ +# FastFlash +As we build ESP32 applications, we typically perform a compile, flash, test loop cycle. We compile an app, we flash the ESP32 with that app and then we test whether it works. We perform these actions over and over again. When we look at the time taken in each step, we see there is compile time on our PC and then the time taken to flash the PC. This story talks about the time taken to flash the PC. + +The ESP32 is typically configured to flash at 115200 kbps. This is 115200 bits per second. If we think that a typical ESP32 application is 800KBytes then this requires a transmission of: + +800000 * 9 = 7.2 million bits = 62.5 seconds + +we can increase our baud rate up to 921600 = 7.8 seconds \ No newline at end of file diff --git a/tasks/watchdogs/README.md b/tasks/watchdogs/README.md index 85c7c5c9..052bc868 100644 --- a/tasks/watchdogs/README.md +++ b/tasks/watchdogs/README.md @@ -1,4 +1,4 @@ -#Watchdogs +# Watchdogs This is a sample application that illustrates the capabilities of watchdogs and watchdog processing. A related YouTube video is available here: diff --git a/tasks/watchdogs/main.cpp b/tasks/watchdogs/main.cpp new file mode 100644 index 00000000..de4484c5 --- /dev/null +++ b/tasks/watchdogs/main.cpp @@ -0,0 +1,155 @@ +#include "freertos/FreeRTOS.h" +#include "esp_wifi.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_event_loop.h" +#include "nvs_flash.h" +#include "driver/gpio.h" +#include + +esp_err_t event_handler(void *ctx, system_event_t *event) +{ + return ESP_OK; +} + +extern "C" +{ + void app_main(void); +} + +void highPriorityTask(void *myData) +{ + printf("High priority task started and now looping for 10 seconds. Our priority is %d.\n", uxTaskPriorityGet(nullptr)); + TickType_t startTicks = xTaskGetTickCount(); + while (xTaskGetTickCount() - startTicks < (10 * 1000 / portTICK_PERIOD_MS)) + { + // Do nothing but loop + } + printf("High priority task ended\n"); + vTaskDelete(nullptr); +} + +void hardLoopTask(void *myData) +{ + printf("Hard loop task started ...\n"); + while (1) + { + // do nothing but burn CPU + } +} + +void hardLoopTaskNoInterrupts(void *myData) +{ + printf("Hard loop task disabling interrupts started ...\n"); + taskDISABLE_INTERRUPTS(); + while (1) + { + // do nothing but burn CPU + } +} + +void myTask(void *myData) +{ + printf("# Running in myTask\n"); + printf("# Registering our new task with the task watchdog.\n"); + esp_task_wdt_add(nullptr); + + printf("# Looping 5 times with a delay of 1 second and not feeding the watchdog.\n"); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + } + + printf("# Looping 5 times with a delay of 1 second and positively feeding the watchdog.\n"); + esp_task_wdt_reset(); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + esp_task_wdt_reset(); + } + + printf("# Removing our watchdog registration so we can do something expensive.\n"); + esp_task_wdt_delete(nullptr); + + printf("# Looping 5 times with a delay of 1 second and not feeding the watchdog.\n"); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + } + + printf("# Re-registering our task with the task watchdog.\n"); + esp_task_wdt_add(nullptr); + printf("# Looping 5 times with a delay of 1 second and not feeding the watchdog.\n"); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + } + + printf("# Our current task priority is %d.\n", uxTaskPriorityGet(nullptr)); + printf("# Spwaning a higher priority task\n"); + xTaskCreate(highPriorityTask, // Task code + "Priority task", // Name of task + 16 * 1024, // Stack size + nullptr, // Task data + 5, // Priority + nullptr // task handle + ); + + printf("# Looping 5 times with a delay of 1 second and positively feeding the watchdog.\n"); + esp_task_wdt_reset(); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + esp_task_wdt_reset(); + } + + printf("Spawning a hard-loop function!\n"); + xTaskCreate(hardLoopTaskNoInterrupts, // Task code + "Hard Loop", // Name of task + 16 * 1024, // Stack size + nullptr, // Task data + 5, // Priority + nullptr // task handle + ); + + printf("# Looping 5 times with a delay of 1 second and positively feeding the watchdog.\n"); + esp_task_wdt_reset(); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + esp_task_wdt_reset(); + } + + + printf("# Removing our watchdog registration before we end the task.\n"); + esp_task_wdt_delete(nullptr); + + printf("# Ending myTask\n"); + vTaskDelete(nullptr); +} // myTask + +void app_main(void) +{ + xTaskHandle handle; + printf("App starting\n"); + printf("Initializing the task watchdog subsystem with an interval of 2 seconds.\n"); + esp_task_wdt_init(2, false); + + printf("Creatign a new task.\n"); + // Now let us create a new task. + xTaskCreate(myTask, // Task code + "My Task", // Name of task + 16 * 1024, // Stack size + nullptr, // Task data + 0, // Priority + &handle // task handle + ); + + //printf("App Ended!\n"); +} // app_main From 5f0a1685aa803e58b94b1b398cb8452ae10e91b7 Mon Sep 17 00:00:00 2001 From: Friedemann Stoffregen Date: Tue, 7 Aug 2018 16:10:21 +0200 Subject: [PATCH 309/381] fixes #447 --- .DS_Store | Bin 0 -> 6148 bytes cpp_utils/{onhold => }/BLEEddystoneTLM.cpp | 84 ++++++++++++--------- cpp_utils/{onhold => }/BLEEddystoneTLM.h | 0 cpp_utils/{onhold => }/BLEEddystoneURL.cpp | 0 cpp_utils/{onhold => }/BLEEddystoneURL.h | 0 5 files changed, 50 insertions(+), 34 deletions(-) create mode 100644 .DS_Store rename cpp_utils/{onhold => }/BLEEddystoneTLM.cpp (66%) mode change 100644 => 100755 rename cpp_utils/{onhold => }/BLEEddystoneTLM.h (100%) mode change 100644 => 100755 rename cpp_utils/{onhold => }/BLEEddystoneURL.cpp (100%) mode change 100644 => 100755 rename cpp_utils/{onhold => }/BLEEddystoneURL.h (100%) mode change 100644 => 100755 diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a1b1628574996553bb72515f29175d569efcf42f GIT binary patch literal 6148 zcmeHK&2G~`5S~p^u+>O4QV&Jquo4F@ImA>g;%0^BR;dU_^uSNBvDLV;?8tVKLlnt< zfj&U#725ZJhv3LdaA#(BgQ(J8s}{{zv)}IQ%>I0~cDw)}dXvZnZ~(wUC2ZJOTqD#^ zx*`o@Arxwi2~?0k3S~H3@n*waWPskC4WD6*&k+83f8{t#^ALM~hvPIUt8VwFXf#`E z4<1^!z23Il8|@GND4+RNP)*8SP`sno>!2EiK{0fm_(>FF?J$X|(IC$HX*`XygJM6Z zV(mJ}^AsIa*dPesCh=_OY`)5~DoC=pP-E*c-XA7el+SwkB#TPrZw9xlr{2*oTqEwm zvWL#re7^JIWk>F~i;kST-EFrccb~h9g>5~4w*BVg@yY4gm-DaRE-t^TAQ1T3DtTmZ z2|uuM4J}i@$kIGJ!ro=%GJ=sAU +#include #include #include "BLEEddystoneTLM.h" @@ -54,46 +54,62 @@ uint32_t BLEEddystoneTLM::getTime() { } // getTime std::string BLEEddystoneTLM::toString() { + std::stringstream ss; std::string out = ""; - String buff; uint32_t rawsec; + ss << "Version "; + ss << std::dec << m_eddystoneData.version; + ss << "\n"; - out += "Version "; - buff = String(m_eddystoneData.version, DEC); - out += buff.c_str(); - out += "\n"; + ss << "Battery Voltage "; + ss << std::dec << ENDIAN_CHANGE_U16(m_eddystoneData.volt); + ss << " mV\n"; - out += "Battery Voltage "; - buff = String(ENDIAN_CHANGE_U16(m_eddystoneData.volt), DEC); - out += buff.c_str(); - out += " mV\n"; + ss << "Temperature "; + ss << (float)m_eddystoneData.temp; + ss << " °C\n"; - out += "Temperature "; - buff = String((float)m_eddystoneData.temp, 1); - out += buff.c_str(); - out += " °C\n"; - - out += "Adv. Count "; - buff = String(ENDIAN_CHANGE_U32(m_eddystoneData.advCount), DEC); - out += buff.c_str(); - out += "\n"; + ss << "Adv. Count "; + ss << std::dec << ENDIAN_CHANGE_U32(m_eddystoneData.advCount); + + ss << "\n"; - out += "Time "; + ss << "Time "; + rawsec = ENDIAN_CHANGE_U32(m_eddystoneData.tmil); - buff = "0000"+String(rawsec/864000, DEC); - out += buff.substring(buff.length()-4,buff.length()).c_str(); - out += "."; - buff = "00"+String((rawsec/36000)%24, DEC); - out += buff.substring(buff.length()-2,buff.length()).c_str(); - out += ":"; - buff = "00"+String((rawsec/600)%60, DEC); - out += buff.substring(buff.length()-2,buff.length()).c_str(); - out += ":"; - buff = "00"+String((rawsec/10)%60, DEC); - out += buff.substring(buff.length()-2,buff.length()).c_str(); - out += "\n"; - - return out; + std::stringstream buffstream; + buffstream << "0000"; + buffstream << std::dec << rawsec/864000; + std::string buff = buffstream.str(); + + ss << buff.substr(buff.length()-4, buff.length()); + ss << "."; + + buffstream.str(""); + buffstream.clear(); + buffstream << "00"; + buffstream << std::dec << (rawsec/36000)%24; + buff = buffstream.str(); + ss << buff.substr(buff.length()-2, buff.length()); + ss << ":"; + + buffstream.str(""); + buffstream.clear(); + buffstream << "00"; + buffstream << std::dec << (rawsec/600)%60; + buff = buffstream.str(); + ss << buff.substr(buff.length()-2, buff.length()); + ss << ":"; + + buffstream.str(""); + buffstream.clear(); + buffstream << "00"; + buffstream << std::dec << (rawsec/10)%60; + buff = buffstream.str(); + ss << buff.substr(buff.length()-2, buff.length()); + ss << "\n"; + + return ss.str(); } // toString /** diff --git a/cpp_utils/onhold/BLEEddystoneTLM.h b/cpp_utils/BLEEddystoneTLM.h old mode 100644 new mode 100755 similarity index 100% rename from cpp_utils/onhold/BLEEddystoneTLM.h rename to cpp_utils/BLEEddystoneTLM.h diff --git a/cpp_utils/onhold/BLEEddystoneURL.cpp b/cpp_utils/BLEEddystoneURL.cpp old mode 100644 new mode 100755 similarity index 100% rename from cpp_utils/onhold/BLEEddystoneURL.cpp rename to cpp_utils/BLEEddystoneURL.cpp diff --git a/cpp_utils/onhold/BLEEddystoneURL.h b/cpp_utils/BLEEddystoneURL.h old mode 100644 new mode 100755 similarity index 100% rename from cpp_utils/onhold/BLEEddystoneURL.h rename to cpp_utils/BLEEddystoneURL.h From b1922a86aff31da7ad2526fdcdfdd363ed0be511 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 16 Aug 2018 15:57:31 -0500 Subject: [PATCH 310/381] Initial samples for VSCode --- VisualStudioCode/README.md | 4 + VisualStudioCode/c_cpp_properties.json | 266 +++++++++++++++++++++++++ VisualStudioCode/launch.json | 43 ++++ VisualStudioCode/settings.json | 2 + VisualStudioCode/tasks.json | 41 ++++ eclipse/c_includes.xml | 19 +- 6 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 VisualStudioCode/README.md create mode 100644 VisualStudioCode/c_cpp_properties.json create mode 100644 VisualStudioCode/launch.json create mode 100644 VisualStudioCode/settings.json create mode 100644 VisualStudioCode/tasks.json diff --git a/VisualStudioCode/README.md b/VisualStudioCode/README.md new file mode 100644 index 00000000..ff5ab2f2 --- /dev/null +++ b/VisualStudioCode/README.md @@ -0,0 +1,4 @@ +These are file for Microsoft Visual Studio Code and can be copied into your `.vscode` project folder. For more information on this area, see: + +* [VSCode JTAG Debugging of ESP32 - Part 2](https://gojimmypi.blogspot.com/2017/05/vscode-remote-jtag-debugging-of-esp32.html) +* [Deous/VSC-Guide-for-esp32](https://github.com/Deous/VSC-Guide-for-esp32) \ No newline at end of file diff --git a/VisualStudioCode/c_cpp_properties.json b/VisualStudioCode/c_cpp_properties.json new file mode 100644 index 00000000..9ef83b14 --- /dev/null +++ b/VisualStudioCode/c_cpp_properties.json @@ -0,0 +1,266 @@ +{ + "configurations": [ + { + "name": "ESP32-Linux", + "includePath": [ + "${workspaceRoot}", + "${workspaceRoot}/components", + "${workspaceRoot}/build", + "${workspaceRoot}/build/include", + "${env:IDF_PATH}/components/bt/bluedroid/utils/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/smp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/sdp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/rfcomm/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/l2cap/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/gatt/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/gap/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avrc/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avdt/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avct/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/a2dp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/include", + "${env:IDF_PATH}/components/bt/bluedroid/osi/include", + "${env:IDF_PATH}/components/bt/bluedroid/hci/include", + "${env:IDF_PATH}/components/bt/bluedroid/gki/include", + "${env:IDF_PATH}/components/bt/bluedroid/external/sbc/encoder/include", + "${env:IDF_PATH}/components/bt/bluedroid/external/sbc/decoder/include", + "${env:IDF_PATH}/components/bt/bluedroid/device/include", + "${env:IDF_PATH}/components/bt/bluedroid/btcore/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/smp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/hid/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/dis/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/battery/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/a2dp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/esp/blufi/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/esp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/include", + "${env:IDF_PATH}/components/bt/bluedroid/bta/sys/include", + "${env:IDF_PATH}/components/bt/bluedroid/bta/include", + "${env:IDF_PATH}/components/bt/bluedroid/api/include", + "${env:IDF_PATH}/components/bt/bluedroid/include", + "${env:IDF_PATH}/components/aws_iot/include", + "${env:IDF_PATH}/components/aws_iot/aws-iot-device-sdk-embedded-C/include", + "${env:IDF_PATH}/components/app_trace/include", + "${env:IDF_PATH}/components/app_update/include", + "${env:IDF_PATH}/components/xtensa-debug-module/include", + "${env:IDF_PATH}/components/bootloader_support/include", + "${env:IDF_PATH}/components/bootloader_support/include_priv", + "${env:IDF_PATH}/components/bt/include", + "${env:IDF_PATH}/components/coap/port/include", + "${env:IDF_PATH}/components/coap/port/include/coap", + "${env:IDF_PATH}/components/coap/libcoap/include", + "${env:IDF_PATH}/components/coap/libcoap/include/coap", + "${env:IDF_PATH}/components/cxx/include", + "${env:IDF_PATH}/components/driver/include", + "${env:IDF_PATH}/components/driver/include/driver", + "${env:IDF_PATH}/components/esp32/include", + "${env:IDF_PATH}/components/ethernet/include", + "${env:IDF_PATH}/components/expat/include/expat", + "${env:IDF_PATH}/components/expat/port/include", + "${env:IDF_PATH}/components/fatfs/src", + "${env:IDF_PATH}/components/freertos/include", + "${env:IDF_PATH}/components/heap/include", + "${env:IDF_PATH}/components/jsmn/include", + "${env:IDF_PATH}/components/json/include", + "${env:IDF_PATH}/components/json/port/include", + "${env:IDF_PATH}/components/json/cJSON", + "${env:IDF_PATH}/components/libsodium/libsodium/src/libsodium/include", + "${env:IDF_PATH}/components/libsodium/libsodium/src/libsodium/include/sodium", + "${env:IDF_PATH}/components/log/include", + "${env:IDF_PATH}/components/lwip/include/lwip", + "${env:IDF_PATH}/components/lwip/include/lwip/port", + "${env:IDF_PATH}/components/lwip/include/lwip/posix", + "${env:IDF_PATH}/components/lwip/apps/ping", + "${env:IDF_PATH}/components/lwip/include/lwip/apps", + "${env:IDF_PATH}/components/lwip/include/lwip/apps/sntp", + "${env:IDF_PATH}/components/lwip/include/lwip/lwip", + "${env:IDF_PATH}/components/lwip/include/lwip/lwip/priv", + "${env:IDF_PATH}/components/lwip/include/lwip/netif", + "${env:IDF_PATH}/components/lwip/include/lwip/netif/ppp", + "${env:IDF_PATH}/components/lwip/include/lwip/netif/ppp/polarssl", + "${env:IDF_PATH}/components/lwip/include/lwip/port", + "${env:IDF_PATH}/components/lwip/include/lwip/port/arch", + "${env:IDF_PATH}/components/lwip/include/lwip/port/arpa", + "${env:IDF_PATH}/components/lwip/include/lwip/port/netif", + "${env:IDF_PATH}/components/lwip/include/lwip/port/netinet", + "${env:IDF_PATH}/components/lwip/include/lwip/posix", + "${env:IDF_PATH}/components/lwip/include/lwip/posix/sys", + "${env:IDF_PATH}/components/mbedtls/port/include", + "${env:IDF_PATH}/components/mbedtls/mbedtls/include", + "${env:IDF_PATH}/components/mbedtls/port/include/mbedtls", + "${env:IDF_PATH}/components/mdns/include", + "${env:IDF_PATH}/components/micro-ecc/micro-ecc", + "${env:IDF_PATH}/components/newlib/include", + "${env:IDF_PATH}/components/newlib/include/sys", + "${env:IDF_PATH}/components/newlib/platform_include", + "${env:IDF_PATH}/components/nghttp/include", + "${env:IDF_PATH}/components/nghttp/port/include", + "${env:IDF_PATH}/components/nvs_flash/include", + "${env:IDF_PATH}/components/openssl/include", + "${env:IDF_PATH}/components/openssl/include/internal", + "${env:IDF_PATH}/components/openssl/include/platform", + "${env:IDF_PATH}/components/openssl/include/openssl", + "${env:IDF_PATH}/components/pthread/include", + "${env:IDF_PATH}/components/sdmmc/include", + "${env:IDF_PATH}/components/spi_flash/include", + "${env:IDF_PATH}/components/tcpip_adapter/include", + "${env:IDF_PATH}/components/soc/esp32/include", + "${env:IDF_PATH}/components/soc/include", + "${env:IDF_PATH}/components/soc/esp32/include/soc", + "${env:IDF_PATH}/components/spi_flash", + "${env:IDF_PATH}/components/spiffs/include", + "${env:IDF_PATH}/components/tcpip_adapter/include", + "${env:IDF_PATH}/components/heap/include", + "${env:IDF_PATH}/components/ulp/include", + "${env:IDF_PATH}/components/ulp/include/esp32", + "${env:IDF_PATH}/components/vfs/include", + "${env:IDF_PATH}/components/vfs/include/sys", + "${env:IDF_PATH}/components/wear_levelling/include", + "${env:IDF_PATH}/components/wpa_supplicant/include", + "${env:IDF_PATH}/components/wpa_supplicant/port/include", + "${env:IDF_PATH}/components/wpa_supplicant/include/crypto", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/eap_peer", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/tls", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/utils", + "${env:IDF_PATH}/components/xtensa-debug-module/include", + "C:/Program Files/Espressif/ESP-IDF Tools/toolchain/lib/gcc/xtensa-esp32-elf/5.2.0/include" + ], + "intelliSenseMode": "clang-x64", + "browse": { + "path": [ + "${workspaceRoot}", + "${workspaceRoot}/components", + "${workspaceRoot}/build", + "${workspaceRoot}/build/include", + "${env:IDF_PATH}/components/bt/bluedroid/utils/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/smp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/sdp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/rfcomm/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/l2cap/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/gatt/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/gap/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avrc/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avdt/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avct/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/a2dp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/include", + "${env:IDF_PATH}/components/bt/bluedroid/osi/include", + "${env:IDF_PATH}/components/bt/bluedroid/hci/include", + "${env:IDF_PATH}/components/bt/bluedroid/gki/include", + "${env:IDF_PATH}/components/bt/bluedroid/external/sbc/encoder/include", + "${env:IDF_PATH}/components/bt/bluedroid/external/sbc/decoder/include", + "${env:IDF_PATH}/components/bt/bluedroid/device/include", + "${env:IDF_PATH}/components/bt/bluedroid/btcore/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/smp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/hid/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/dis/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/battery/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/a2dp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/esp/blufi/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/esp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/include", + "${env:IDF_PATH}/components/bt/bluedroid/bta/sys/include", + "${env:IDF_PATH}/components/bt/bluedroid/bta/include", + "${env:IDF_PATH}/components/bt/bluedroid/api/include", + "${env:IDF_PATH}/components/bt/bluedroid/include", + "${env:IDF_PATH}/components/aws_iot/include", + "${env:IDF_PATH}/components/aws_iot/aws-iot-device-sdk-embedded-C/include", + "${env:IDF_PATH}/components/app_trace/include", + "${env:IDF_PATH}/components/app_update/include", + "${env:IDF_PATH}/components/xtensa-debug-module/include", + "${env:IDF_PATH}/components/bootloader_support/include", + "${env:IDF_PATH}/components/bootloader_support/include_priv", + "${env:IDF_PATH}/components/bt/include", + "${env:IDF_PATH}/components/coap/port/include", + "${env:IDF_PATH}/components/coap/port/include/coap", + "${env:IDF_PATH}/components/coap/libcoap/include", + "${env:IDF_PATH}/components/coap/libcoap/include/coap", + "${env:IDF_PATH}/components/cxx/include", + "${env:IDF_PATH}/components/driver/include", + "${env:IDF_PATH}/components/driver/include/driver", + "${env:IDF_PATH}/components/esp32/include", + "${env:IDF_PATH}/components/ethernet/include", + "${env:IDF_PATH}/components/expat/include/expat", + "${env:IDF_PATH}/components/expat/port/include", + "${env:IDF_PATH}/components/fatfs/src", + "${env:IDF_PATH}/components/freertos/include", + "${env:IDF_PATH}/components/heap/include", + "${env:IDF_PATH}/components/jsmn/include", + "${env:IDF_PATH}/components/json/include", + "${env:IDF_PATH}/components/json/port/include", + "${env:IDF_PATH}/components/json/cJSON", + "${env:IDF_PATH}/components/libsodium/libsodium/src/libsodium/include", + "${env:IDF_PATH}/components/libsodium/libsodium/src/libsodium/include/sodium", + "${env:IDF_PATH}/components/log/include", + "${env:IDF_PATH}/components/lwip/include/lwip", + "${env:IDF_PATH}/components/lwip/include/lwip/port", + "${env:IDF_PATH}/components/lwip/include/lwip/posix", + "${env:IDF_PATH}/components/lwip/apps/ping", + "${env:IDF_PATH}/components/lwip/include/lwip/apps", + "${env:IDF_PATH}/components/lwip/include/lwip/apps/sntp", + "${env:IDF_PATH}/components/lwip/include/lwip/lwip", + "${env:IDF_PATH}/components/lwip/include/lwip/lwip/priv", + "${env:IDF_PATH}/components/lwip/include/lwip/netif", + "${env:IDF_PATH}/components/lwip/include/lwip/netif/ppp", + "${env:IDF_PATH}/components/lwip/include/lwip/netif/ppp/polarssl", + "${env:IDF_PATH}/components/lwip/include/lwip/port", + "${env:IDF_PATH}/components/lwip/include/lwip/port/arch", + "${env:IDF_PATH}/components/lwip/include/lwip/port/arpa", + "${env:IDF_PATH}/components/lwip/include/lwip/port/netif", + "${env:IDF_PATH}/components/lwip/include/lwip/port/netinet", + "${env:IDF_PATH}/components/lwip/include/lwip/posix", + "${env:IDF_PATH}/components/lwip/include/lwip/posix/sys", + "${env:IDF_PATH}/components/mbedtls/port/include", + "${env:IDF_PATH}/components/mbedtls/include", + "${env:IDF_PATH}/components/mbedtls/port/include/mbedtls", + "${env:IDF_PATH}/components/mdns/include", + "${env:IDF_PATH}/components/micro-ecc/micro-ecc", + "${env:IDF_PATH}/components/newlib/include", + "${env:IDF_PATH}/components/newlib/include/sys", + "${env:IDF_PATH}/components/newlib/platform_include", + "${env:IDF_PATH}/components/nghttp/include", + "${env:IDF_PATH}/components/nghttp/port/include", + "${env:IDF_PATH}/components/nvs_flash/include", + "${env:IDF_PATH}/components/openssl/include", + "${env:IDF_PATH}/components/openssl/include/internal", + "${env:IDF_PATH}/components/openssl/include/platform", + "${env:IDF_PATH}/components/openssl/include/openssl", + "${env:IDF_PATH}/components/pthread/include", + "${env:IDF_PATH}/components/sdmmc/include", + "${env:IDF_PATH}/components/spi_flash/include", + "${env:IDF_PATH}/components/tcpip_adapter/include", + "${env:IDF_PATH}/components/soc/esp32/include", + "${env:IDF_PATH}/components/soc/include", + "${env:IDF_PATH}/components/soc/esp32/include/soc", + "${env:IDF_PATH}/components/spi_flash", + "${env:IDF_PATH}/components/spiffs/include", + "${env:IDF_PATH}/components/tcpip_adapter/include", + "${env:IDF_PATH}/components/heap/include", + "${env:IDF_PATH}/components/ulp/include", + "${env:IDF_PATH}/components/ulp/include/esp32", + "${env:IDF_PATH}/components/vfs/include", + "${env:IDF_PATH}/components/vfs/include/sys", + "${env:IDF_PATH}/components/wear_levelling/include", + "${env:IDF_PATH}/components/wpa_supplicant/include", + "${env:IDF_PATH}/components/wpa_supplicant/port/include", + "${env:IDF_PATH}/components/wpa_supplicant/include/crypto", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/eap_peer", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/tls", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/utils", + "${env:IDF_PATH}/components/xtensa-debug-module/include", + "C:/Program Files/Espressif/ESP-IDF Tools/toolchain/lib/gcc/xtensa-esp32-elf/5.2.0/include" + + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "${workspaceRoot}/.vscode/browse.vc.db" + }, + "cStandard": "c11", + "cppStandard": "c++17" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/VisualStudioCode/launch.json b/VisualStudioCode/launch.json new file mode 100644 index 00000000..257de4a0 --- /dev/null +++ b/VisualStudioCode/launch.json @@ -0,0 +1,43 @@ +{ + "version": "0.2.0", + "configurations": [ + + { + "name": "ESP32 OpenOCD launch", + "type": "cppdbg", + "request": "launch", + "program": "./build/app-template.elf", + "args": [], + "stopAtEntry": true, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "/opt/xtensa-esp32-elf/bin/xtensa-esp32-elf-gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "text": "target remote localhost:3333" + }, + { + "text": "monitor reset halt" + }, + { + "text": "flushregs" + }, + { + "text": "thb app_main" + } + ], + "logging": { + "trace": true, + "traceResponse": true, + "engineLogging": true + } + } + ] +} \ No newline at end of file diff --git a/VisualStudioCode/settings.json b/VisualStudioCode/settings.json new file mode 100644 index 00000000..7a73a41b --- /dev/null +++ b/VisualStudioCode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/VisualStudioCode/tasks.json b/VisualStudioCode/tasks.json new file mode 100644 index 00000000..d989b0f2 --- /dev/null +++ b/VisualStudioCode/tasks.json @@ -0,0 +1,41 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "menuconfig", + "type": "shell", + "command": "make menuconfig", + "problemMatcher": [] + }, + { + "label": "make", + "type": "shell", + "command": "make -j5", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "flash", + "type": "shell", + "command": "make flash monitor", + "problemMatcher": [] + }, + { + "label": "monitor", + "type": "shell", + "command": "make monitor", + "problemMatcher": [] + }, + { + "label": "clean", + "type": "shell", + "command": "make clean", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/eclipse/c_includes.xml b/eclipse/c_includes.xml index eaa53ba2..60400ad5 100644 --- a/eclipse/c_includes.xml +++ b/eclipse/c_includes.xml @@ -50,7 +50,6 @@ ${IDF_PATH}/components/spi_flash/include ${IDF_PATH}/components/mbedtls/include ${IDF_PATH}/components/mdns/include -${IDF_PATH}/components/json/include ${IDF_PATH}/components/bt/include ${IDF_PATH}/components/bt/bluedroid/bta/include ${IDF_PATH}/components/bt/bluedroid/bta/sys/include @@ -128,18 +127,36 @@ ESP_PLATFORM1 + +PLATFORM_ID11 + + +DEBUG_BUILD1 + ESP_PLATFORM1 + +PLATFORM_ID11 + + +DEBUG_BUILD1 + ESP_PLATFORM1 + +PLATFORM_ID11 + + +DEBUG_BUILD1 + From ed1de4b54a585e126649e19a19bc39566fb50a88 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sat, 25 Aug 2018 19:21:50 -0500 Subject: [PATCH 311/381] Addition of GCP JWT sample --- cloud/GCP/JWT/base64url.cpp | 136 ++++++++++++++++++++++++++ cloud/GCP/JWT/base64url.h | 15 +++ cloud/GCP/JWT/main.cpp | 189 ++++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 cloud/GCP/JWT/base64url.cpp create mode 100644 cloud/GCP/JWT/base64url.h create mode 100644 cloud/GCP/JWT/main.cpp diff --git a/cloud/GCP/JWT/base64url.cpp b/cloud/GCP/JWT/base64url.cpp new file mode 100644 index 00000000..94221181 --- /dev/null +++ b/cloud/GCP/JWT/base64url.cpp @@ -0,0 +1,136 @@ +// https://raw.githubusercontent.com/zhicheng/base64/master/base64.c +/* This is a public domain base64 implementation written by WEI Zhicheng. */ + +#include + +#include "base64url.h" + +/* BASE 64 encode table */ +static const char base64en[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '_', +}; + +#define BASE64_PAD '=' + + +#define BASE64DE_FIRST '+' +#define BASE64DE_LAST 'z' +/* ASCII order for BASE 64 decode, -1 in unused character */ +static const signed char base64de[] = { + /* '+', ',', '-', '.', '/', '0', '1', '2', */ + 62, -1, -1, -1, 63, 52, 53, 54, + + /* '3', '4', '5', '6', '7', '8', '9', ':', */ + 55, 56, 57, 58, 59, 60, 61, -1, + + /* ';', '<', '=', '>', '?', '@', 'A', 'B', */ + -1, -1, -1, -1, -1, -1, 0, 1, + + /* 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', */ + 2, 3, 4, 5, 6, 7, 8, 9, + + /* 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', */ + 10, 11, 12, 13, 14, 15, 16, 17, + + /* 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', */ + 18, 19, 20, 21, 22, 23, 24, 25, + + /* '[', '\', ']', '^', '_', '`', 'a', 'b', */ + -1, -1, -1, -1, -1, -1, 26, 27, + + /* 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', */ + 28, 29, 30, 31, 32, 33, 34, 35, + + /* 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', */ + 36, 37, 38, 39, 40, 41, 42, 43, + + /* 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', */ + 44, 45, 46, 47, 48, 49, 50, 51, +}; + +int base64url_encode(const unsigned char *in, unsigned int inlen, char *out) +{ + unsigned int i, j; + + for (i = j = 0; i < inlen; i++) { + int s = i % 3; /* from 6/gcd(6, 8) */ + + switch (s) { + case 0: + out[j++] = base64en[(in[i] >> 2) & 0x3F]; + continue; + case 1: + out[j++] = base64en[((in[i-1] & 0x3) << 4) + ((in[i] >> 4) & 0xF)]; + continue; + case 2: + out[j++] = base64en[((in[i-1] & 0xF) << 2) + ((in[i] >> 6) & 0x3)]; + out[j++] = base64en[in[i] & 0x3F]; + } + } + + /* move back */ + i -= 1; + + /* check the last and add padding */ + + if ((i % 3) == 0) { + out[j++] = base64en[(in[i] & 0x3) << 4]; + //out[j++] = BASE64_PAD; + //out[j++] = BASE64_PAD; + } else if ((i % 3) == 1) { + out[j++] = base64en[(in[i] & 0xF) << 2]; + //out[j++] = BASE64_PAD; + } + + out[j++] = 0; + + return BASE64_OK; +} + +int base64url_decode(const char *in, unsigned int inlen, unsigned char *out) +{ + unsigned int i, j; + + for (i = j = 0; i < inlen; i++) { + int c; + int s = i % 4; /* from 8/gcd(6, 8) */ + + if (in[i] == '=') + return BASE64_OK; + + if (in[i] < BASE64DE_FIRST || in[i] > BASE64DE_LAST || + (c = base64de[in[i] - BASE64DE_FIRST]) == -1) + return BASE64_INVALID; + + switch (s) { + case 0: + out[j] = ((unsigned int)c << 2) & 0xFF; + continue; + case 1: + out[j++] += ((unsigned int)c >> 4) & 0x3; + + /* if not last char with padding */ + if (i < (inlen - 3) || in[inlen - 2] != '=') + out[j] = ((unsigned int)c & 0xF) << 4; + continue; + case 2: + out[j++] += ((unsigned int)c >> 2) & 0xF; + + /* if not last char with padding */ + if (i < (inlen - 2) || in[inlen - 1] != '=') + out[j] = ((unsigned int)c & 0x3) << 6; + continue; + case 3: + out[j++] += (unsigned char)c; + } + } + + return BASE64_OK; +} diff --git a/cloud/GCP/JWT/base64url.h b/cloud/GCP/JWT/base64url.h new file mode 100644 index 00000000..7a8c80ca --- /dev/null +++ b/cloud/GCP/JWT/base64url.h @@ -0,0 +1,15 @@ +// https://raw.githubusercontent.com/zhicheng/base64/master/base64.h +#ifndef __BASE64URL_H__ +#define __BASE64URL_H__ + +enum {BASE64_OK = 0, BASE64_INVALID}; + +#define BASE64_ENCODE_OUT_SIZE(s) (((s) + 2) / 3 * 4) +#define BASE64_DECODE_OUT_SIZE(s) (((s)) / 4 * 3) + +int base64url_encode(const unsigned char *in, unsigned int inlen, char *out); + +int base64url_decode(const char *in, unsigned int inlen, unsigned char *out); + + +#endif /* __BASE64URL_H__ */ diff --git a/cloud/GCP/JWT/main.cpp b/cloud/GCP/JWT/main.cpp new file mode 100644 index 00000000..fcf8ec8b --- /dev/null +++ b/cloud/GCP/JWT/main.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "passwords.h" +#include "base64url.h" + +// This is an "xxd" file of the PEM of the private key. +#include "device1_private_pem.h" + + +extern "C" { + void app_main(); +} + +/** + * Return a string representation of an mbedtls error code + */ +static char* mbedtlsError(int errnum) { + static char buffer[200]; + mbedtls_strerror(errnum, buffer, sizeof(buffer)); + return buffer; +} // mbedtlsError + + +/** + * Create a JWT token for GCP. + * For full details, perform a Google search on JWT. However, in summary, we build two strings. One that represents the + * header and one that represents the payload. Both are JSON and are as described in the GCP and JWT documentation. Next + * we base64url encode both strings. Note that is distinct from normal/simple base64 encoding. Once we have a string for + * the base64url encoding of both header and payload, we concatenate both strings together separated by a ".". This resulting + * string is then signed using RSASSA which basically produces an SHA256 message digest that is then signed. The resulting + * binary is then itself converted into base64url and concatenated with the previously built base64url combined header and + * payload and that is our resulting JWT token. + * @param projectId The GCP project. + * @param privateKey The PEM or DER of the private key. + * @param privateKeySize The size in bytes of the private key. + * @returns A JWT token for transmission to GCP. + */ +char* createGCPJWT(const char* projectId, uint8_t* privateKey, size_t privateKeySize) { + char base64Header[100]; + const char header[] = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}"; + base64url_encode( + (unsigned char *)header, // Data to encode. + strlen(header), // Length of data to encode. + base64Header); // Base64 encoded data. + + time_t now; + time(&now); + uint32_t iat = now; // Set the time now. + uint32_t exp = iat + 60*60; // Set the expiry time. + + char payload[100]; + sprintf(payload, "{\"iat\":%d,\"exp\":%d,\"aud\":\"%s\"}", iat, exp, projectId); + + char base64Payload[100]; + base64url_encode( + (unsigned char *)payload, // Data to encode. + strlen(payload), // Length of data to encode. + base64Payload); // Base64 encoded data. + + uint8_t headerAndPayload[800]; + sprintf((char*)headerAndPayload, "%s.%s", base64Header, base64Payload); + + // At this point we have created the header and payload parts, converted both to base64 and concatenated them + // together as a single string. Now we need to sign them using RSASSA + + mbedtls_pk_context pk_context; + mbedtls_pk_init(&pk_context); + int rc = mbedtls_pk_parse_key(&pk_context, privateKey, privateKeySize, NULL, 0); + if (rc != 0) { + printf("Failed to mbedtls_pk_parse_key: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc)); + return nullptr; + } + + uint8_t oBuf[5000]; + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + + const char* pers="MyEntropy"; + + mbedtls_ctr_drbg_seed( + &ctr_drbg, + mbedtls_entropy_func, + &entropy, + (const unsigned char*)pers, + strlen(pers)); + + + uint8_t digest[32]; + rc = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), headerAndPayload, strlen((char*)headerAndPayload), digest); + if (rc != 0) { + printf("Failed to mbedtls_md: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc)); + return nullptr; + } + + size_t retSize; + rc = mbedtls_pk_sign(&pk_context, MBEDTLS_MD_SHA256, digest, sizeof(digest), oBuf, &retSize, mbedtls_ctr_drbg_random, &ctr_drbg); + if (rc != 0) { + printf("Failed to mbedtls_pk_sign: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc)); + return nullptr; + } + + + char base64Signature[600]; + base64url_encode((unsigned char *)oBuf, retSize, base64Signature); + + char* retData = (char*)malloc(strlen((char*)headerAndPayload) + 1 + strlen((char*)base64Signature) + 1); + + sprintf(retData, "%s.%s", headerAndPayload, base64Signature); + + mbedtls_pk_free(&pk_context); + return retData; +} + +void run(void *) { + printf("Task starting!\n"); + const char* projectId = "test-214415"; + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setservername(0, "time-a-g.nist.gov"); + sntp_init(); + // https://www.epochconverter.com/ + time_t now = 0; + time(&now); + while(now < 5000) { + vTaskDelay(1000 * portTICK_RATE_MS); + time(&now); + } + + char* jwt = createGCPJWT(projectId, device1_private_pem, device1_private_pem_len); + if (jwt != nullptr) { + printf("JWT: %s\n", jwt); + free(jwt); + } + vTaskDelete(nullptr); +} + +esp_err_t event_handler(void *ctx, system_event_t *event) +{ + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { + printf("Our IP address is " IPSTR "\n", + IP2STR(&event->event_info.got_ip.ip_info.ip)); + printf("We have now connected to a station and can do things...\n"); + xTaskCreate(run, "run", 16000, nullptr, 0, nullptr); + + } + + if (event->event_id == SYSTEM_EVENT_STA_START) { + ESP_ERROR_CHECK(esp_wifi_connect()); + } + return ESP_OK; +} + +void app_main(void) +{ + printf("Starting\n"); + nvs_flash_init(); + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + wifi_config_t sta_config; + memset(&sta_config, 0, sizeof(sta_config)); + strcpy((char*)sta_config.sta.ssid, SSID); + strcpy((char*)sta_config.sta.password, SSID_PASSWORD); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config)); + ESP_ERROR_CHECK(esp_wifi_start()); +} + From 1d976d21e95eacb8773ebc45c6c0b3f7f8897c77 Mon Sep 17 00:00:00 2001 From: menesesleonardo <33615982+menesesleonardo@users.noreply.github.com> Date: Tue, 28 Aug 2018 23:55:46 -0500 Subject: [PATCH 312/381] Create pcf8523 it is very similar to ds1307 and people could use it. --- hardware/rtc/pcf8523.c | 200 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 hardware/rtc/pcf8523.c diff --git a/hardware/rtc/pcf8523.c b/hardware/rtc/pcf8523.c new file mode 100644 index 00000000..2d1e310e --- /dev/null +++ b/hardware/rtc/pcf8523.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "errorhandle_func.h" + +#include "sdkconfig.h" + +#define SDA_PIN 23 +#define SCL_PIN 22 +#define RTC_ADDRESS 0x68 // most I2C rtcs have their address on 0x68. any doubt check with i2c scanner snippet + +static char tag[] = "RTC"; + +static uint8_t intToBCD(uint8_t num) { + return ((num / 10) << 4) | (num%10); +} + +static uint8_t bcdToInt(uint8_t bcd) { + // 0x10 + return ((bcd >> 4) * 10) + (bcd & 0x0f);; +} + + +void initI2C() { + i2c_config_t conf; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = SDA_PIN; + conf.scl_io_num = SCL_PIN; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = 100000; + ESP_ERROR_CHECK(i2c_param_config(I2C_NUM_0, &conf)); + ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0)); +} + +/* + * PCF8523 slightly changed its 7 bytes encoded in BCD: + * 03h - Seconds - 00-59 + * 04h - Minutes - 00-59 + * 05h - Hours - 00-23 + * 06h - monthday - 01-31 + * 07h - weekday - 00-06 + * 08h - Month - 01-12 + * 09h - Year - 00-99 + * + */ +time_t rtc_readValue() { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + ESP_ERROR_CHECK(i2c_master_start(cmd)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (RTC_ADDRESS << 1) | I2C_MASTER_WRITE, true /* expect ack */)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, 0x03, 1)); // start address + ESP_ERROR_CHECK(i2c_master_start(cmd)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (RTC_ADDRESS << 1) | I2C_MASTER_READ, true /* expect ack */)); + uint8_t data[7]; + ESP_ERROR_CHECK(i2c_master_read(cmd, data, 7, false)); + ESP_ERROR_CHECK(i2c_master_stop(cmd)); + COMMANDCHECKOKERR(i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS),"RTC COMMAND"); + i2c_cmd_link_delete(cmd); + + int i; + for (i=0; i<7; i++) { + ESP_LOGD(tag, "%d: 0x%.2x", i, data[i]); + } + + struct tm tm; + tm.tm_sec = bcdToInt(data[0]); + tm.tm_min = bcdToInt(data[1]); + tm.tm_hour = bcdToInt(data[2]); + tm.tm_mday = bcdToInt(data[3]); + tm.tm_mon = bcdToInt(data[5]) - 1; // 0-11 - Note: The month on the PCF8523 is 1-12. + tm.tm_year = bcdToInt(data[6]) + 100; // Years since 1900 + time_t readTime = mktime(&tm); + return readTime; +} + +void rtc_writeValue(time_t newTime) { + ESP_LOGD(tag, ">> writeValue: %ld", newTime); + struct tm tm; + gmtime_r(&newTime, &tm); + char buf[30]; + ESP_LOGD(tag, " - %s", asctime_r(&tm, buf)); + + esp_err_t errRc; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + ESP_ERROR_CHECK(i2c_master_start(cmd)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (RTC_ADDRESS << 1) | I2C_MASTER_WRITE, 1 /* expect ack */)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, 0x03, 1)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_sec), 1)); // seconds + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_min), 1 )); // minutes + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_hour), 1 )); // hours + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_mday), 1)); // date of month + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_wday+1), 1 )); // week day + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_mon+1), 1)); // month + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_year-100), 1)); // year + ESP_ERROR_CHECK(i2c_master_stop(cmd)); + errRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS); + if (errRc != 0) { + ESP_LOGE(tag, "i2c_master_cmd_begin: %d", errRc); + } + i2c_cmd_link_delete(cmd); +} + + +/* + implement in your time function + @Kolban created a nice one, here is my contribution. + + esp_err_t sntp_update(){ + + static const char *tag = "TIME_SETUP"; + esp_err_t ret; + char buffer[20]; + EventBits_t bitreturn; + TickType_t waittime = 10000/portTICK_PERIOD_MS; + + + bitreturn = xEventGroupWaitBits(event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, waittime); + + if((bitreturn & BIT0) != 0){ + + printf("going online\n"); + + } + else printf("going offline\n"); + + + // initialize the SNTP service + sntp_setoperatingmode(SNTP_OPMODE_POLL); + // to create SNTP server variable make a #define statement i.e. "pool.ntp.org" + sntp_setservername(0, CONFIG_SNTP_SERVER); + sntp_init(); + + initI2C(); + + time_t t; + struct tm timertc; + t = rtc_readValue(); + localtime_r(&t, &timertc); + + strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M:%S", &timertc); + ESP_LOGI(tag,"Current time in rtc (GTM) is: %s\n\n", buffer); + + // wait for the service to set the time + time_t now; + struct tm timeinfo; + time(&now); + localtime_r(&now, &timeinfo); + int counter = 0; + + // try for a minute to get time from network + while((timeinfo.tm_year < (2018 - 1900)) && (counter < 12)) + { + + ESP_LOGW(tag,"Time outdated, waiting...\n"); + vTaskDelay(5000 / portTICK_PERIOD_MS); + time(&now); + localtime_r(&now, &timeinfo); + counter ++; + } + + if((timeinfo.tm_year < (2018 - 1900)) && (counter == 12)){ + ESP_LOGE(tag, "TIMEOUT"); + + + // stick to rtc time + now = rtc_readValue(); + + } + else{ + // update rtc time + rtc_writeValue(now); + } + + + // to create timezone variable make a #define statement i.e. "COT+5" + setenv("TZ",CONFIG_TIMEZONE_TZ, 1); + tzset(); + + // print the actual time in location + localtime_r(&now, &timeinfo); + strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M:%S", &timeinfo); + ESP_LOGI(tag,"Current time in your Location: %s\n\n", buffer); + + // check time set in rtc in case it was updated from network + t = rtc_readValue(); + localtime_r(&t, &timertc); + strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M:%S", &timertc); + ESP_LOGI(tag,"Current time in rtc (GTM) is: %s\n\n", buffer); + + ret = ESP_OK; + + return ret; +} + + */ From f8d3438005b0db8c1db6b7d092569902b92ae8d9 Mon Sep 17 00:00:00 2001 From: SolipsistD Date: Mon, 3 Sep 2018 13:16:02 +0100 Subject: [PATCH 313/381] Fix params passed to i2c.init() to include DEVICE_ADDRESS in the first position. Otherwise SDA becomes SCL_PIN and SCL becomes Default. --- cpp_utils/tests/task_i2c_scanner.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp_utils/tests/task_i2c_scanner.cpp b/cpp_utils/tests/task_i2c_scanner.cpp index b1bde2e0..061c3b08 100644 --- a/cpp_utils/tests/task_i2c_scanner.cpp +++ b/cpp_utils/tests/task_i2c_scanner.cpp @@ -6,6 +6,7 @@ #include "sdkconfig.h" +#define DEVICE_ADDRESS 0 #define SDA_PIN 25 #define SCL_PIN 26 @@ -14,7 +15,7 @@ class I2CScanner: public Task { void run(void *data) override { I2C i2c; - i2c.init((gpio_num_t)SDA_PIN, (gpio_num_t)SCL_PIN); + i2c.init((uint8_t)DEVICE_ADDRESS, (gpio_num_t)SDA_PIN, (gpio_num_t)SCL_PIN); i2c.scan(); } // End run }; From b0503a6489da57bb40557b86c282d9ab4b225238 Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 5 Sep 2018 14:54:42 +0200 Subject: [PATCH 314/381] add multiple servers support --- cpp_utils/BLEClient.cpp | 8 +- cpp_utils/BLEClient.h | 2 +- cpp_utils/BLEDevice.cpp | 1106 +++++++++--------- cpp_utils/BLEDevice.h | 5 +- cpp_utils/BLERemoteDescriptor.cpp | 370 +++--- cpp_utils/BLEScan.cpp | 584 +++++----- cpp_utils/BLEScan.h | 158 +-- cpp_utils/BLESecurity.h | 142 +-- cpp_utils/BLEService.cpp | 870 +++++++------- cpp_utils/BLEService.h | 208 ++-- cpp_utils/BLEServiceMap.cpp | 264 ++--- cpp_utils/WiFi.cpp | 1808 ++++++++++++++--------------- cpp_utils/WiFi.h | 316 ++--- 13 files changed, 2928 insertions(+), 2913 deletions(-) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 141cf0f5..14198d18 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -21,6 +21,7 @@ #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" #endif +#include "BLEDevice.h" /* * Design @@ -44,9 +45,9 @@ */ static const char* LOG_TAG = "BLEClient"; -BLEClient::BLEClient() { +BLEClient::BLEClient(uint16_t connID) { m_pClientCallbacks = nullptr; - m_conn_id = 0; + m_conn_id = connID; m_gattc_if = 0; m_haveServices = false; m_isConnected = false; // Initially, we are flagged as not connected. @@ -96,7 +97,7 @@ bool BLEClient::connect(BLEAddress address) { clearServices(); // Delete any services that may exist. - esp_err_t errRc = ::esp_ble_gattc_app_register(0); + esp_err_t errRc = ::esp_ble_gattc_app_register(m_conn_id); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return false; @@ -138,6 +139,7 @@ void BLEClient::disconnect() { } esp_ble_gattc_app_unregister(getGattcIf()); m_peerAddress = BLEAddress("00:00:00:00:00:00"); + BLEDevice::removeClient(m_conn_id); ESP_LOGD(LOG_TAG, "<< disconnect()"); } // disconnect diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index a60ed102..f010b05f 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -28,7 +28,7 @@ class BLEClientCallbacks; */ class BLEClient { public: - BLEClient(); + BLEClient(uint16_t connID); ~BLEClient(); bool connect(BLEAddress address); // Connect to the remote BLE Server diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index a7db454b..369b1285 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -1,548 +1,558 @@ -/* - * BLE.cpp - * - * Created on: Mar 16, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include -#include -#include -#include -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 ESP-IDF -#include // ESP32 ESP-IDF -#include // Part of C++ Standard library -#include // Part of C++ Standard library -#include // Part of C++ Standard library - -#include "BLEDevice.h" -#include "BLEClient.h" -#include "BLEUtils.h" -#include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 -#include "esp32-hal-log.h" -#include "esp32-hal-bt.h" -#endif - -static const char* LOG_TAG = "BLEDevice"; - -/** - * Singletons for the BLEDevice. - */ -BLEServer* BLEDevice::m_pServer = nullptr; -BLEScan* BLEDevice::m_pScan = nullptr; -BLEClient* BLEDevice::m_pClient = nullptr; -bool initialized = false; // Have we been initialized? -esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; -BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; -uint16_t BLEDevice::m_localMTU = 23; - -/** - * @brief Create a new instance of a client. - * @return A new instance of the client. - */ -/* STATIC */ BLEClient* BLEDevice::createClient() { - ESP_LOGD(LOG_TAG, ">> createClient"); -#ifndef CONFIG_GATTC_ENABLE // Check that BLE GATTC is enabled in make menuconfig - ESP_LOGE(LOG_TAG, "BLE GATTC is not enabled - CONFIG_GATTC_ENABLE not defined"); - abort(); -#endif // CONFIG_GATTC_ENABLE - m_pClient = new BLEClient(); - ESP_LOGD(LOG_TAG, "<< createClient"); - return m_pClient; -} // createClient - - -/** - * @brief Create a new instance of a server. - * @return A new instance of the server. - */ -/* STATIC */ BLEServer* BLEDevice::createServer() { - ESP_LOGD(LOG_TAG, ">> createServer"); -#ifndef CONFIG_GATTS_ENABLE // Check that BLE GATTS is enabled in make menuconfig - ESP_LOGE(LOG_TAG, "BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined"); - abort(); -#endif // CONFIG_GATTS_ENABLE - m_pServer = new BLEServer(); - m_pServer->createApp(0); - ESP_LOGD(LOG_TAG, "<< createServer"); - return m_pServer; -} // createServer - - -/** - * @brief Handle GATT server events. - * - * @param [in] event The event that has been newly received. - * @param [in] gatts_if The connection to the GATT interface. - * @param [in] param Parameters for the event. - */ -/* STATIC */ void BLEDevice::gattServerEventHandler( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param -) { - ESP_LOGD(LOG_TAG, "gattServerEventHandler [esp_gatt_if: %d] ... %s", - gatts_if, - BLEUtils::gattServerEventTypeToString(event).c_str()); - - BLEUtils::dumpGattServerEvent(event, gatts_if, param); - - switch(event) { - case ESP_GATTS_CONNECT_EVT: { - BLEDevice::m_localMTU = 23; -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityLevel){ - esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - } // ESP_GATTS_CONNECT_EVT - - case ESP_GATTS_MTU_EVT: { - BLEDevice::m_localMTU = param->mtu.mtu; - ESP_LOGI(LOG_TAG, "ESP_GATTS_MTU_EVT, MTU %d", BLEDevice::m_localMTU); - break; - } - default: { - break; - } - } // switch - - - if (BLEDevice::m_pServer != nullptr) { - BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); - } -} // gattServerEventHandler - - -/** - * @brief Handle GATT client events. - * - * Handler for the GATT client events. - * - * @param [in] event - * @param [in] gattc_if - * @param [in] param - */ -/* STATIC */ void BLEDevice::gattClientEventHandler( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t* param) { - - ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", - gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); - BLEUtils::dumpGattClientEvent(event, gattc_if, param); - - switch(event) { - case ESP_GATTC_CONNECT_EVT: { - if(BLEDevice::getMTU() != 23){ - esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - } -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityLevel){ - esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - } // ESP_GATTC_CONNECT_EVT - - default: { - break; - } - } // switch - - - // If we have a client registered, call it. - if (BLEDevice::m_pClient != nullptr) { - BLEDevice::m_pClient->gattClientEventHandler(event, gattc_if, param); - } - -} // gattClientEventHandler - - -/** - * @brief Handle GAP events. - */ -/* STATIC */ void BLEDevice::gapEventHandler( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t *param) { - - BLEUtils::dumpGapEvent(event, param); - - switch(event) { - - case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); - break; - case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); - break; - case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); - break; - case ESP_GAP_BLE_NC_REQ_EVT: - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); - // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - /* - * TODO should we add white/black list comparison? - */ - case ESP_GAP_BLE_SEC_REQ_EVT: - /* send the positive(true) security response to the peer device to accept the security request. - If not accept the security request, should sent the security response with negative(false) accept value*/ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); - } - else{ - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - /* - * - */ - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. - ///show the passkey number to the user to input it in the peer deivce. - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); - BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_KEY_EVT: - //shows the ble key type info share with peer device to the user. - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); -#endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_AUTH_CMPL_EVT: - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - default: { - break; - } - } // switch - - if (BLEDevice::m_pServer != nullptr) { - BLEDevice::m_pServer->handleGAPEvent(event, param); - } - - if (BLEDevice::m_pClient != nullptr) { - BLEDevice::m_pClient->handleGAPEvent(event, param); - } - - if (BLEDevice::m_pScan != nullptr) { - BLEDevice::getScan()->handleGAPEvent(event, param); - } - - /* - * Security events: - */ - - -} // gapEventHandler - - -/** - * @brief Get the BLE device address. - * @return The BLE device address. - */ -/* STATIC*/ BLEAddress BLEDevice::getAddress() { - const uint8_t* bdAddr = esp_bt_dev_get_address(); - esp_bd_addr_t addr; - memcpy(addr, bdAddr, sizeof(addr)); - return BLEAddress(addr); -} // getAddress - - -/** - * @brief Retrieve the Scan object that we use for scanning. - * @return The scanning object reference. This is a singleton object. The caller should not - * try and release/delete it. - */ -/* STATIC */ BLEScan* BLEDevice::getScan() { - //ESP_LOGD(LOG_TAG, ">> getScan"); - if (m_pScan == nullptr) { - m_pScan = new BLEScan(); - //ESP_LOGD(LOG_TAG, " - creating a new scan object"); - } - //ESP_LOGD(LOG_TAG, "<< getScan: Returning object at 0x%x", (uint32_t)m_pScan); - return m_pScan; -} // getScan - - -/** - * @brief Get the value of a characteristic of a service on a remote device. - * @param [in] bdAddress - * @param [in] serviceUUID - * @param [in] characteristicUUID - */ -/* STATIC */ std::string BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID) { - ESP_LOGD(LOG_TAG, ">> getValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - BLEClient *pClient = createClient(); - pClient->connect(bdAddress); - std::string ret = pClient->getValue(serviceUUID, characteristicUUID); - pClient->disconnect(); - ESP_LOGD(LOG_TAG, "<< getValue"); - return ret; -} // getValue - - -/** - * @brief Initialize the %BLE environment. - * @param deviceName The device name of the device. - */ -/* STATIC */ void BLEDevice::init(std::string deviceName) { - if(!initialized){ - initialized = true; // Set the initialization flag to ensure we are only initialized once. - - esp_err_t errRc = ESP_OK; -#ifdef ARDUINO_ARCH_ESP32 - if (!btStart()) { - errRc = ESP_FAIL; - return; - } -#else - errRc = ::nvs_flash_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - errRc = esp_bt_controller_init(&bt_cfg); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - -#ifndef CLASSIC_BT_ENABLED - // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue - errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); - //errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } -#else - errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } -#endif -#endif - - esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); - if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){ - errRc = esp_bluedroid_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - } - - if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){ - errRc = esp_bluedroid_enable(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - } - - errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - -#ifdef CONFIG_GATTC_ENABLE // Check that BLE client is configured in make menuconfig - errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } -#endif // CONFIG_GATTC_ENABLE - -#ifdef CONFIG_GATTS_ENABLE // Check that BLE server is configured in make menuconfig - errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } -#endif // CONFIG_GATTS_ENABLE - - errRc = ::esp_ble_gap_set_device_name(deviceName.c_str()); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_set_device_name: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - }; - -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; - errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - }; -#endif // CONFIG_BLE_SMP_ENABLE - } - vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. -} // init - - -/** - * @brief Set the transmission power. - * The power level can be one of: - * * ESP_PWR_LVL_N14 - * * ESP_PWR_LVL_N11 - * * ESP_PWR_LVL_N8 - * * ESP_PWR_LVL_N5 - * * ESP_PWR_LVL_N2 - * * ESP_PWR_LVL_P1 - * * ESP_PWR_LVL_P4 - * * ESP_PWR_LVL_P7 - * @param [in] powerLevel. - */ -/* STATIC */ void BLEDevice::setPower(esp_power_level_t powerLevel) { - ESP_LOGD(LOG_TAG, ">> setPower: %d", powerLevel); - esp_err_t errRc = ::esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, powerLevel); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - }; - ESP_LOGD(LOG_TAG, "<< setPower"); -} // setPower - - -/** - * @brief Set the value of a characteristic of a service on a remote device. - * @param [in] bdAddress - * @param [in] serviceUUID - * @param [in] characteristicUUID - */ -/* STATIC */ void BLEDevice::setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value) { - ESP_LOGD(LOG_TAG, ">> setValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - BLEClient *pClient = createClient(); - pClient->connect(bdAddress); - pClient->setValue(serviceUUID, characteristicUUID, value); - pClient->disconnect(); -} // setValue - - -/** - * @brief Return a string representation of the nature of this device. - * @return A string representation of the nature of this device. - */ -/* STATIC */ std::string BLEDevice::toString() { - std::ostringstream oss; - oss << "BD Address: " << getAddress().toString(); - return oss.str(); -} // toString - - -/** - * @brief Add an entry to the BLE white list. - * @param [in] address The address to add to the white list. - */ -void BLEDevice::whiteListAdd(BLEAddress address) { - ESP_LOGD(LOG_TAG, ">> whiteListAdd: %s", address.toString().c_str()); - esp_err_t errRc = esp_ble_gap_update_whitelist(true, *address.getNative()); // True to add an entry. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - ESP_LOGD(LOG_TAG, "<< whiteListAdd"); -} // whiteListAdd - - -/** - * @brief Remove an entry from the BLE white list. - * @param [in] address The address to remove from the white list. - */ -void BLEDevice::whiteListRemove(BLEAddress address) { - ESP_LOGD(LOG_TAG, ">> whiteListRemove: %s", address.toString().c_str()); - esp_err_t errRc = esp_ble_gap_update_whitelist(false, *address.getNative()); // False to remove an entry. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - ESP_LOGD(LOG_TAG, "<< whiteListRemove"); -} // whiteListRemove - -/* - * @brief Set encryption level that will be negotiated with peer device durng connection - * @param [in] level Requested encryption level - */ -void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { - BLEDevice::m_securityLevel = level; -} - -/* - * @brief Set callbacks that will be used to handle encryption negotiation events and authentication events - * @param [in] cllbacks Pointer to BLESecurityCallbacks class callback - */ -void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { - BLEDevice::m_securityCallbacks = callbacks; -} - -/* - * @brief Setup local mtu that will be used to negotiate mtu during request from client peer - * @param [in] mtu Value to set local mtu, should be larger than 23 and lower or equal to 517 - */ -esp_err_t BLEDevice::setMTU(uint16_t mtu) { - ESP_LOGD(LOG_TAG, ">> setLocalMTU: %d", mtu); - esp_err_t err = esp_ble_gatt_set_local_mtu(mtu); - if(err == ESP_OK){ - m_localMTU = mtu; - } else { - ESP_LOGE(LOG_TAG, "can't set local mtu value: %d", mtu); - } - ESP_LOGD(LOG_TAG, "<< setLocalMTU"); - return err; -} - -/* - * @brief Get local MTU value set during mtu request or default value - */ -uint16_t BLEDevice::getMTU() { - return m_localMTU; -} - -bool BLEDevice::getInitialized() { - return initialized; -} -#endif // CONFIG_BT_ENABLED +/* + * BLE.cpp + * + * Created on: Mar 16, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include +#include +#include +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 ESP-IDF +#include // ESP32 ESP-IDF +#include // Part of C++ Standard library +#include // Part of C++ Standard library +#include // Part of C++ Standard library + +#include "BLEDevice.h" +#include "BLEClient.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#include "esp32-hal-bt.h" +#endif + +static const char* LOG_TAG = "BLEDevice"; + +/** + * Singletons for the BLEDevice. + */ +BLEServer* BLEDevice::m_pServer = nullptr; +BLEScan* BLEDevice::m_pScan = nullptr; +BLEClient* BLEDevice::m_pClient = nullptr; +bool initialized = false; // Have we been initialized? +esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; +BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; +uint16_t BLEDevice::m_localMTU = 23; + +/** + * @brief Create a new instance of a client. + * @return A new instance of the client. + */ +/* STATIC */ BLEClient* BLEDevice::createClient(uint16_t connID) { + ESP_LOGD(LOG_TAG, ">> createClient"); +#ifndef CONFIG_GATTC_ENABLE // Check that BLE GATTC is enabled in make menuconfig + ESP_LOGE(LOG_TAG, "BLE GATTC is not enabled - CONFIG_GATTC_ENABLE not defined"); + abort(); +#endif // CONFIG_GATTC_ENABLE + m_pClient = new BLEClient(connID); + addClient(connID, m_pClient); + ESP_LOGD(LOG_TAG, "<< createClient"); + return m_pClient; +} // createClient + + +/** + * @brief Create a new instance of a server. + * @return A new instance of the server. + */ +/* STATIC */ BLEServer* BLEDevice::createServer() { + ESP_LOGD(LOG_TAG, ">> createServer"); +#ifndef CONFIG_GATTS_ENABLE // Check that BLE GATTS is enabled in make menuconfig + ESP_LOGE(LOG_TAG, "BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined"); + abort(); +#endif // CONFIG_GATTS_ENABLE + m_pServer = new BLEServer(); + m_pServer->createApp(0); + ESP_LOGD(LOG_TAG, "<< createServer"); + return m_pServer; +} // createServer + + +/** + * @brief Handle GATT server events. + * + * @param [in] event The event that has been newly received. + * @param [in] gatts_if The connection to the GATT interface. + * @param [in] param Parameters for the event. + */ +/* STATIC */ void BLEDevice::gattServerEventHandler( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param +) { + ESP_LOGD(LOG_TAG, "gattServerEventHandler [esp_gatt_if: %d] ... %s", + gatts_if, + BLEUtils::gattServerEventTypeToString(event).c_str()); + + BLEUtils::dumpGattServerEvent(event, gatts_if, param); + + switch(event) { + case ESP_GATTS_CONNECT_EVT: { + BLEDevice::m_localMTU = 23; +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTS_CONNECT_EVT + + case ESP_GATTS_MTU_EVT: { + BLEDevice::m_localMTU = param->mtu.mtu; + ESP_LOGI(LOG_TAG, "ESP_GATTS_MTU_EVT, MTU %d", BLEDevice::m_localMTU); + break; + } + default: { + break; + } + } // switch + + + if (BLEDevice::m_pServer != nullptr) { + BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); + } +} // gattServerEventHandler + + +/** + * @brief Handle GATT client events. + * + * Handler for the GATT client events. + * + * @param [in] event + * @param [in] gattc_if + * @param [in] param + */ +/* STATIC */ void BLEDevice::gattClientEventHandler( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* param) { + + ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", + gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); + BLEUtils::dumpGattClientEvent(event, gattc_if, param); + + switch(event) { + case ESP_GATTC_CONNECT_EVT: { + if(BLEDevice::getMTU() != 23){ + esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTC_CONNECT_EVT + + default: { + break; + } + } // switch + + + // If we have a client registered, call it. + if (BLEDevice::m_pClient != nullptr) { + BLEDevice::m_pClient->gattClientEventHandler(event, gattc_if, param); + } + +} // gattClientEventHandler + + +/** + * @brief Handle GAP events. + */ +/* STATIC */ void BLEDevice::gapEventHandler( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t *param) { + + BLEUtils::dumpGapEvent(event, param); + + switch(event) { + + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + break; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); + break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); + break; + case ESP_GAP_BLE_NC_REQ_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); + // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + /* + * TODO should we add white/black list comparison? + */ + case ESP_GAP_BLE_SEC_REQ_EVT: + /* send the positive(true) security response to the peer device to accept the security request. + If not accept the security request, should sent the security response with negative(false) accept value*/ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); + } + else{ + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + /* + * + */ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + ///show the passkey number to the user to input it in the peer deivce. + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); + BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_KEY_EVT: + //shows the ble key type info share with peer device to the user. + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + default: { + break; + } + } // switch + + if (BLEDevice::m_pServer != nullptr) { + BLEDevice::m_pServer->handleGAPEvent(event, param); + } + + if (BLEDevice::m_pClient != nullptr) { + BLEDevice::m_pClient->handleGAPEvent(event, param); + } + + if (BLEDevice::m_pScan != nullptr) { + BLEDevice::getScan()->handleGAPEvent(event, param); + } + + /* + * Security events: + */ + + +} // gapEventHandler + + +/** + * @brief Get the BLE device address. + * @return The BLE device address. + */ +/* STATIC*/ BLEAddress BLEDevice::getAddress() { + const uint8_t* bdAddr = esp_bt_dev_get_address(); + esp_bd_addr_t addr; + memcpy(addr, bdAddr, sizeof(addr)); + return BLEAddress(addr); +} // getAddress + + +/** + * @brief Retrieve the Scan object that we use for scanning. + * @return The scanning object reference. This is a singleton object. The caller should not + * try and release/delete it. + */ +/* STATIC */ BLEScan* BLEDevice::getScan() { + //ESP_LOGD(LOG_TAG, ">> getScan"); + if (m_pScan == nullptr) { + m_pScan = new BLEScan(); + //ESP_LOGD(LOG_TAG, " - creating a new scan object"); + } + //ESP_LOGD(LOG_TAG, "<< getScan: Returning object at 0x%x", (uint32_t)m_pScan); + return m_pScan; +} // getScan + + +/** + * @brief Get the value of a characteristic of a service on a remote device. + * @param [in] bdAddress + * @param [in] serviceUUID + * @param [in] characteristicUUID + */ +/* STATIC */ std::string BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID) { + ESP_LOGD(LOG_TAG, ">> getValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + BLEClient *pClient = createClient(); + pClient->connect(bdAddress); + std::string ret = pClient->getValue(serviceUUID, characteristicUUID); + pClient->disconnect(); + ESP_LOGD(LOG_TAG, "<< getValue"); + return ret; +} // getValue + + +/** + * @brief Initialize the %BLE environment. + * @param deviceName The device name of the device. + */ +/* STATIC */ void BLEDevice::init(std::string deviceName) { + if(!initialized){ + initialized = true; // Set the initialization flag to ensure we are only initialized once. + + esp_err_t errRc = ESP_OK; +#ifdef ARDUINO_ARCH_ESP32 + if (!btStart()) { + errRc = ESP_FAIL; + return; + } +#else + errRc = ::nvs_flash_init(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + errRc = esp_bt_controller_init(&bt_cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + +#ifndef CLASSIC_BT_ENABLED + // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue + errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); + //errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#else + errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif +#endif + + esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); + if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){ + errRc = esp_bluedroid_init(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + } + + if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){ + errRc = esp_bluedroid_enable(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + } + + errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + +#ifdef CONFIG_GATTC_ENABLE // Check that BLE client is configured in make menuconfig + errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif // CONFIG_GATTC_ENABLE + +#ifdef CONFIG_GATTS_ENABLE // Check that BLE server is configured in make menuconfig + errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif // CONFIG_GATTS_ENABLE + + errRc = ::esp_ble_gap_set_device_name(deviceName.c_str()); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_device_name: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + }; + +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; + errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + }; +#endif // CONFIG_BLE_SMP_ENABLE + } + vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. +} // init + + +/** + * @brief Set the transmission power. + * The power level can be one of: + * * ESP_PWR_LVL_N14 + * * ESP_PWR_LVL_N11 + * * ESP_PWR_LVL_N8 + * * ESP_PWR_LVL_N5 + * * ESP_PWR_LVL_N2 + * * ESP_PWR_LVL_P1 + * * ESP_PWR_LVL_P4 + * * ESP_PWR_LVL_P7 + * @param [in] powerLevel. + */ +/* STATIC */ void BLEDevice::setPower(esp_power_level_t powerLevel) { + ESP_LOGD(LOG_TAG, ">> setPower: %d", powerLevel); + esp_err_t errRc = ::esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, powerLevel); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + }; + ESP_LOGD(LOG_TAG, "<< setPower"); +} // setPower + + +/** + * @brief Set the value of a characteristic of a service on a remote device. + * @param [in] bdAddress + * @param [in] serviceUUID + * @param [in] characteristicUUID + */ +/* STATIC */ void BLEDevice::setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value) { + ESP_LOGD(LOG_TAG, ">> setValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + BLEClient *pClient = createClient(); + pClient->connect(bdAddress); + pClient->setValue(serviceUUID, characteristicUUID, value); + pClient->disconnect(); +} // setValue + + +/** + * @brief Return a string representation of the nature of this device. + * @return A string representation of the nature of this device. + */ +/* STATIC */ std::string BLEDevice::toString() { + std::ostringstream oss; + oss << "BD Address: " << getAddress().toString(); + return oss.str(); +} // toString + + +/** + * @brief Add an entry to the BLE white list. + * @param [in] address The address to add to the white list. + */ +void BLEDevice::whiteListAdd(BLEAddress address) { + ESP_LOGD(LOG_TAG, ">> whiteListAdd: %s", address.toString().c_str()); + esp_err_t errRc = esp_ble_gap_update_whitelist(true, *address.getNative()); // True to add an entry. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< whiteListAdd"); +} // whiteListAdd + + +/** + * @brief Remove an entry from the BLE white list. + * @param [in] address The address to remove from the white list. + */ +void BLEDevice::whiteListRemove(BLEAddress address) { + ESP_LOGD(LOG_TAG, ">> whiteListRemove: %s", address.toString().c_str()); + esp_err_t errRc = esp_ble_gap_update_whitelist(false, *address.getNative()); // False to remove an entry. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< whiteListRemove"); +} // whiteListRemove + +/* + * @brief Set encryption level that will be negotiated with peer device durng connection + * @param [in] level Requested encryption level + */ +void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { + BLEDevice::m_securityLevel = level; +} + +/* + * @brief Set callbacks that will be used to handle encryption negotiation events and authentication events + * @param [in] cllbacks Pointer to BLESecurityCallbacks class callback + */ +void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { + BLEDevice::m_securityCallbacks = callbacks; +} + +/* + * @brief Setup local mtu that will be used to negotiate mtu during request from client peer + * @param [in] mtu Value to set local mtu, should be larger than 23 and lower or equal to 517 + */ +esp_err_t BLEDevice::setMTU(uint16_t mtu) { + ESP_LOGD(LOG_TAG, ">> setLocalMTU: %d", mtu); + esp_err_t err = esp_ble_gatt_set_local_mtu(mtu); + if(err == ESP_OK){ + m_localMTU = mtu; + } else { + ESP_LOGE(LOG_TAG, "can't set local mtu value: %d", mtu); + } + ESP_LOGD(LOG_TAG, "<< setLocalMTU"); + return err; +} + +/* + * @brief Get local MTU value set during mtu request or default value + */ +uint16_t BLEDevice::getMTU() { + return m_localMTU; +} + +bool BLEDevice::getInitialized() { + return initialized; +} + +void BLEDevice::addClient(uint16_t connID, BLEClient* client) { + // m_clientList.insert(std::pair(connID, client)); +} + +void BLEDevice::removeClient(uint16_t connID) { + m_clientList.erase(connID); +} + +#endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 7a1b833d..e4f4ff5a 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -27,7 +27,7 @@ class BLEDevice { public: - static BLEClient* createClient(); // Create a new BLE client. + static BLEClient* createClient(uint16_t connID = 0); // Create a new BLE client. static BLEServer* createServer(); // Cretae a new BLE server. static BLEAddress getAddress(); // Retrieve our own local BD address. static BLEScan* getScan(); // Get the scan object @@ -43,6 +43,8 @@ class BLEDevice { static esp_err_t setMTU(uint16_t mtu); static uint16_t getMTU(); static bool getInitialized(); // Returns the state of the device, is it initialized or not? + static void addClient(uint16_t connID, BLEClient* client); + static void removeClient(uint16_t connID); private: static BLEServer *m_pServer; @@ -51,6 +53,7 @@ class BLEDevice { static esp_ble_sec_act_t m_securityLevel; static BLESecurityCallbacks* m_securityCallbacks; static uint16_t m_localMTU; + static std::map m_clientList; static esp_gatt_if_t getGattcIF(); diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index 2cdc7db1..36492360 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -1,185 +1,185 @@ -/* - * BLERemoteDescriptor.cpp - * - * Created on: Jul 8, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include "BLERemoteDescriptor.h" -#include "GeneralUtils.h" -#include -#ifdef ARDUINO_ARCH_ESP32 -#include "esp32-hal-log.h" -#endif - -static const char* LOG_TAG = "BLERemoteDescriptor"; - - -BLERemoteDescriptor::BLERemoteDescriptor( - uint16_t handle, - BLEUUID uuid, - BLERemoteCharacteristic* pRemoteCharacteristic) { - - m_handle = handle; - m_uuid = uuid; - m_pRemoteCharacteristic = pRemoteCharacteristic; -} - - -/** - * @brief Retrieve the handle associated with this remote descriptor. - * @return The handle associated with this remote descriptor. - */ -uint16_t BLERemoteDescriptor::getHandle() { - return m_handle; -} // getHandle - - -/** - * @brief Get the characteristic that owns this descriptor. - * @return The characteristic that owns this descriptor. - */ -BLERemoteCharacteristic* BLERemoteDescriptor::getRemoteCharacteristic() { - return m_pRemoteCharacteristic; -} // getRemoteCharacteristic - - -/** - * @brief Retrieve the UUID associated this remote descriptor. - * @return The UUID associated this remote descriptor. - */ -BLEUUID BLERemoteDescriptor::getUUID() { - return m_uuid; -} // getUUID - - -std::string BLERemoteDescriptor::readValue(void) { - ESP_LOGD(LOG_TAG, ">> readValue: %s", toString().c_str()); - - // Check to see that we are connected. - if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { - ESP_LOGE(LOG_TAG, "Disconnected"); - throw BLEDisconnectedException(); - } - - m_semaphoreReadDescrEvt.take("readValue"); - - // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. - esp_err_t errRc = ::esp_ble_gattc_read_char_descr( - m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), - m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), // The connection ID to the BLE server - getHandle(), // The handle of this characteristic - ESP_GATT_AUTH_REQ_NONE); // Security - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return ""; - } - - // Block waiting for the event that indicates that the read has completed. When it has, the std::string found - // in m_value will contain our data. - m_semaphoreReadDescrEvt.wait("readValue"); - - ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length()); - return m_value; -} // readValue - - -uint8_t BLERemoteDescriptor::readUInt8(void) { - std::string value = readValue(); - if (value.length() >= 1) { - return (uint8_t)value[0]; - } - return 0; -} // readUInt8 - - -uint16_t BLERemoteDescriptor::readUInt16(void) { - std::string value = readValue(); - if (value.length() >= 2) { - return *(uint16_t*)(value.data()); - } - return 0; -} // readUInt16 - - -uint32_t BLERemoteDescriptor::readUInt32(void) { - std::string value = readValue(); - if (value.length() >= 4) { - return *(uint32_t*)(value.data()); - } - return 0; -} // readUInt32 - - -/** - * @brief Return a string representation of this BLE Remote Descriptor. - * @retun A string representation of this BLE Remote Descriptor. - */ -std::string BLERemoteDescriptor::toString(void) { - std::stringstream ss; - ss << "handle: " << getHandle() << ", uuid: " << getUUID().toString(); - return ss.str(); -} // toString - - -/** - * @brief Write data to the BLE Remote Descriptor. - * @param [in] data The data to send to the remote descriptor. - * @param [in] length The length of the data to send. - * @param [in] response True if we expect a response. - */ -void BLERemoteDescriptor::writeValue( - uint8_t* data, - size_t length, - bool response) { - ESP_LOGD(LOG_TAG, ">> writeValue: %s", toString().c_str()); - // Check to see that we are connected. - if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { - ESP_LOGE(LOG_TAG, "Disconnected"); - throw BLEDisconnectedException(); - } - - esp_err_t errRc = ::esp_ble_gattc_write_char_descr( - m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), - m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), - getHandle(), - length, // Data length - data, // Data - response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE - ); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char_descr: %d", errRc); - } - ESP_LOGD(LOG_TAG, "<< writeValue"); -} // writeValue - - -/** - * @brief Write data represented as a string to the BLE Remote Descriptor. - * @param [in] newValue The data to send to the remote descriptor. - * @param [in] response True if we expect a response. - */ -void BLERemoteDescriptor::writeValue( - std::string newValue, - bool response) { - writeValue(newValue.data(), newValue.length()); -} // writeValue - - -/** - * @brief Write a byte value to the Descriptor. - * @param [in] The single byte to write. - * @param [in] True if we expect a response. - */ -void BLERemoteDescriptor::writeValue( - uint8_t newValue, - bool response) { - writeValue(&newValue, 1, response); -} // writeValue - - -#endif /* CONFIG_BT_ENABLED */ +/* + * BLERemoteDescriptor.cpp + * + * Created on: Jul 8, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include "BLERemoteDescriptor.h" +#include "GeneralUtils.h" +#include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + +static const char* LOG_TAG = "BLERemoteDescriptor"; + + +BLERemoteDescriptor::BLERemoteDescriptor( + uint16_t handle, + BLEUUID uuid, + BLERemoteCharacteristic* pRemoteCharacteristic) { + + m_handle = handle; + m_uuid = uuid; + m_pRemoteCharacteristic = pRemoteCharacteristic; +} + + +/** + * @brief Retrieve the handle associated with this remote descriptor. + * @return The handle associated with this remote descriptor. + */ +uint16_t BLERemoteDescriptor::getHandle() { + return m_handle; +} // getHandle + + +/** + * @brief Get the characteristic that owns this descriptor. + * @return The characteristic that owns this descriptor. + */ +BLERemoteCharacteristic* BLERemoteDescriptor::getRemoteCharacteristic() { + return m_pRemoteCharacteristic; +} // getRemoteCharacteristic + + +/** + * @brief Retrieve the UUID associated this remote descriptor. + * @return The UUID associated this remote descriptor. + */ +BLEUUID BLERemoteDescriptor::getUUID() { + return m_uuid; +} // getUUID + + +std::string BLERemoteDescriptor::readValue(void) { + ESP_LOGD(LOG_TAG, ">> readValue: %s", toString().c_str()); + + // Check to see that we are connected. + if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + + m_semaphoreReadDescrEvt.take("readValue"); + + // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. + esp_err_t errRc = ::esp_ble_gattc_read_char_descr( + m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), + m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), // The connection ID to the BLE server + getHandle(), // The handle of this characteristic + ESP_GATT_AUTH_REQ_NONE); // Security + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return ""; + } + + // Block waiting for the event that indicates that the read has completed. When it has, the std::string found + // in m_value will contain our data. + m_semaphoreReadDescrEvt.wait("readValue"); + + ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length()); + return m_value; +} // readValue + + +uint8_t BLERemoteDescriptor::readUInt8(void) { + std::string value = readValue(); + if (value.length() >= 1) { + return (uint8_t)value[0]; + } + return 0; +} // readUInt8 + + +uint16_t BLERemoteDescriptor::readUInt16(void) { + std::string value = readValue(); + if (value.length() >= 2) { + return *(uint16_t*)(value.data()); + } + return 0; +} // readUInt16 + + +uint32_t BLERemoteDescriptor::readUInt32(void) { + std::string value = readValue(); + if (value.length() >= 4) { + return *(uint32_t*)(value.data()); + } + return 0; +} // readUInt32 + + +/** + * @brief Return a string representation of this BLE Remote Descriptor. + * @retun A string representation of this BLE Remote Descriptor. + */ +std::string BLERemoteDescriptor::toString(void) { + std::stringstream ss; + ss << "handle: " << getHandle() << ", uuid: " << getUUID().toString(); + return ss.str(); +} // toString + + +/** + * @brief Write data to the BLE Remote Descriptor. + * @param [in] data The data to send to the remote descriptor. + * @param [in] length The length of the data to send. + * @param [in] response True if we expect a response. + */ +void BLERemoteDescriptor::writeValue( + uint8_t* data, + size_t length, + bool response) { + ESP_LOGD(LOG_TAG, ">> writeValue: %s", toString().c_str()); + // Check to see that we are connected. + if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + + esp_err_t errRc = ::esp_ble_gattc_write_char_descr( + m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), + m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), + getHandle(), + length, // Data length + data, // Data + response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE + ); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char_descr: %d", errRc); + } + ESP_LOGD(LOG_TAG, "<< writeValue"); +} // writeValue + + +/** + * @brief Write data represented as a string to the BLE Remote Descriptor. + * @param [in] newValue The data to send to the remote descriptor. + * @param [in] response True if we expect a response. + */ +void BLERemoteDescriptor::writeValue( + std::string newValue, + bool response) { + writeValue(newValue.data(), newValue.length()); +} // writeValue + + +/** + * @brief Write a byte value to the Descriptor. + * @param [in] The single byte to write. + * @param [in] True if we expect a response. + */ +void BLERemoteDescriptor::writeValue( + uint8_t newValue, + bool response) { + writeValue(&newValue, 1, response); +} // writeValue + + +#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 3046b7c8..87046670 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -1,292 +1,292 @@ -/* - * BLEScan.cpp - * - * Created on: Jul 1, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - - -#include -#include - -#include - -#include "BLEAdvertisedDevice.h" -#include "BLEScan.h" -#include "BLEUtils.h" -#include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 -#include "esp32-hal-log.h" -#endif - -static const char* LOG_TAG = "BLEScan"; - - -/** - * Constructor - */ -BLEScan::BLEScan() { - m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan. - m_scan_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; - m_scan_params.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; - m_pAdvertisedDeviceCallbacks = nullptr; - m_stopped = true; - m_wantDuplicates = false; - setInterval(100); - setWindow(100); -} // BLEScan - - -/** - * @brief Handle GAP events related to scans. - * @param [in] event The event type for this event. - * @param [in] param Parameter data for this event. - */ -void BLEScan::handleGAPEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param) { - - switch(event) { - - // ESP_GAP_BLE_SCAN_RESULT_EVT - // --------------------------- - // scan_rst: - // esp_gap_search_evt_t search_evt - // esp_bd_addr_t bda - // esp_bt_dev_type_t dev_type - // esp_ble_addr_type_t ble_addr_type - // esp_ble_evt_type_t ble_evt_type - // int rssi - // uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX] - // int flag - // int num_resps - // uint8_t adv_data_len - // uint8_t scan_rsp_len - case ESP_GAP_BLE_SCAN_RESULT_EVT: { - - switch(param->scan_rst.search_evt) { - // - // ESP_GAP_SEARCH_INQ_CMPL_EVT - // - // Event that indicates that the duration allowed for the search has completed or that we have been - // asked to stop. - case ESP_GAP_SEARCH_INQ_CMPL_EVT: { - m_stopped = true; - if (m_scanCompleteCB != nullptr) { - m_scanCompleteCB(m_scanResults); - } - m_semaphoreScanEnd.give(); - break; - } // ESP_GAP_SEARCH_INQ_CMPL_EVT - - // - // ESP_GAP_SEARCH_INQ_RES_EVT - // - // Result that has arrived back from a Scan inquiry. - case ESP_GAP_SEARCH_INQ_RES_EVT: { - if (m_stopped) { // If we are not scanning, nothing to do with the extra results. - break; - } - -// Examine our list of previously scanned addresses and, if we found this one already, -// ignore it. - BLEAddress advertisedAddress(param->scan_rst.bda); - bool found = false; - - for (int i=0; iscan_rst.rssi); - advertisedDevice.setAdFlag(param->scan_rst.flag); - advertisedDevice.parseAdvertisement((uint8_t*)param->scan_rst.ble_adv); - advertisedDevice.setScan(this); - - if (m_pAdvertisedDeviceCallbacks) { - m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); - } - - if (!found) { // If we have previously seen this device, don't record it again. - m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice); - } - - break; - } // ESP_GAP_SEARCH_INQ_RES_EVT - - default: { - break; - } - } // switch - search_evt - - - break; - } // ESP_GAP_BLE_SCAN_RESULT_EVT - - default: { - break; - } // default - } // End switch -} // gapEventHandler - - -/** - * @brief Should we perform an active or passive scan? - * The default is a passive scan. An active scan means that we will wish a scan response. - * @param [in] active If true, we perform an active scan otherwise a passive scan. - * @return N/A. - */ -void BLEScan::setActiveScan(bool active) { - if (active) { - m_scan_params.scan_type = BLE_SCAN_TYPE_ACTIVE; - } else { - m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; - } -} // setActiveScan - - -/** - * @brief Set the call backs to be invoked. - * @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked. - * @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false. - */ -void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, bool wantDuplicates) { - m_wantDuplicates = wantDuplicates; - m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; -} // setAdvertisedDeviceCallbacks - - -/** - * @brief Set the interval to scan. - * @param [in] The interval in msecs. - */ -void BLEScan::setInterval(uint16_t intervalMSecs) { - m_scan_params.scan_interval = intervalMSecs / 0.625; -} // setInterval - - -/** - * @brief Set the window to actively scan. - * @param [in] windowMSecs How long to actively scan. - */ -void BLEScan::setWindow(uint16_t windowMSecs) { - m_scan_params.scan_window = windowMSecs / 0.625; -} // setWindow - - -/** - * @brief Start scanning. - * @param [in] duration The duration in seconds for which to scan. - * @param [in] scanCompleteCB A function to be called when scanning has completed. - * @return True if scan started or false if there was an error. - */ -bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)) { - ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); - - m_semaphoreScanEnd.take(std::string("start")); - m_scanCompleteCB = scanCompleteCB; // Save the callback to be invoked when the scan completes. - - m_scanResults.m_vectorAdvertisedDevices.clear(); - - esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); - m_semaphoreScanEnd.give(); - return false; - } - - errRc = ::esp_ble_gap_start_scanning(duration); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); - m_semaphoreScanEnd.give(); - return false; - } - - m_stopped = false; - - ESP_LOGD(LOG_TAG, "<< start()"); - return true; -} // start - - -/** - * @brief Start scanning and block until scanning has been completed. - * @param [in] duration The duration in seconds for which to scan. - * @return The BLEScanResults. - */ -BLEScanResults BLEScan::start(uint32_t duration) { - if(start(duration, nullptr)) { - m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. - } - return m_scanResults; -} // start - - -/** - * @brief Stop an in progress scan. - * @return N/A. - */ -void BLEScan::stop() { - ESP_LOGD(LOG_TAG, ">> stop()"); - - esp_err_t errRc = ::esp_ble_gap_stop_scanning(); - - m_stopped = true; - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - m_semaphoreScanEnd.give(); - - ESP_LOGD(LOG_TAG, "<< stop()"); -} // stop - - -/** - * @brief Dump the scan results to the log. - */ -void BLEScanResults::dump() { - ESP_LOGD(LOG_TAG, ">> Dump scan results:"); - for (int i=0; i +#include + +#include + +#include "BLEAdvertisedDevice.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + +static const char* LOG_TAG = "BLEScan"; + + +/** + * Constructor + */ +BLEScan::BLEScan() { + m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan. + m_scan_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; + m_scan_params.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; + m_pAdvertisedDeviceCallbacks = nullptr; + m_stopped = true; + m_wantDuplicates = false; + setInterval(100); + setWindow(100); +} // BLEScan + + +/** + * @brief Handle GAP events related to scans. + * @param [in] event The event type for this event. + * @param [in] param Parameter data for this event. + */ +void BLEScan::handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param) { + + switch(event) { + + // ESP_GAP_BLE_SCAN_RESULT_EVT + // --------------------------- + // scan_rst: + // esp_gap_search_evt_t search_evt + // esp_bd_addr_t bda + // esp_bt_dev_type_t dev_type + // esp_ble_addr_type_t ble_addr_type + // esp_ble_evt_type_t ble_evt_type + // int rssi + // uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX] + // int flag + // int num_resps + // uint8_t adv_data_len + // uint8_t scan_rsp_len + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + + switch(param->scan_rst.search_evt) { + // + // ESP_GAP_SEARCH_INQ_CMPL_EVT + // + // Event that indicates that the duration allowed for the search has completed or that we have been + // asked to stop. + case ESP_GAP_SEARCH_INQ_CMPL_EVT: { + m_stopped = true; + if (m_scanCompleteCB != nullptr) { + m_scanCompleteCB(m_scanResults); + } + m_semaphoreScanEnd.give(); + break; + } // ESP_GAP_SEARCH_INQ_CMPL_EVT + + // + // ESP_GAP_SEARCH_INQ_RES_EVT + // + // Result that has arrived back from a Scan inquiry. + case ESP_GAP_SEARCH_INQ_RES_EVT: { + if (m_stopped) { // If we are not scanning, nothing to do with the extra results. + break; + } + +// Examine our list of previously scanned addresses and, if we found this one already, +// ignore it. + BLEAddress advertisedAddress(param->scan_rst.bda); + bool found = false; + + for (int i=0; iscan_rst.rssi); + advertisedDevice.setAdFlag(param->scan_rst.flag); + advertisedDevice.parseAdvertisement((uint8_t*)param->scan_rst.ble_adv); + advertisedDevice.setScan(this); + + if (m_pAdvertisedDeviceCallbacks) { + m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); + } + + if (!found) { // If we have previously seen this device, don't record it again. + m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice); + } + + break; + } // ESP_GAP_SEARCH_INQ_RES_EVT + + default: { + break; + } + } // switch - search_evt + + + break; + } // ESP_GAP_BLE_SCAN_RESULT_EVT + + default: { + break; + } // default + } // End switch +} // gapEventHandler + + +/** + * @brief Should we perform an active or passive scan? + * The default is a passive scan. An active scan means that we will wish a scan response. + * @param [in] active If true, we perform an active scan otherwise a passive scan. + * @return N/A. + */ +void BLEScan::setActiveScan(bool active) { + if (active) { + m_scan_params.scan_type = BLE_SCAN_TYPE_ACTIVE; + } else { + m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; + } +} // setActiveScan + + +/** + * @brief Set the call backs to be invoked. + * @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked. + * @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false. + */ +void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, bool wantDuplicates) { + m_wantDuplicates = wantDuplicates; + m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; +} // setAdvertisedDeviceCallbacks + + +/** + * @brief Set the interval to scan. + * @param [in] The interval in msecs. + */ +void BLEScan::setInterval(uint16_t intervalMSecs) { + m_scan_params.scan_interval = intervalMSecs / 0.625; +} // setInterval + + +/** + * @brief Set the window to actively scan. + * @param [in] windowMSecs How long to actively scan. + */ +void BLEScan::setWindow(uint16_t windowMSecs) { + m_scan_params.scan_window = windowMSecs / 0.625; +} // setWindow + + +/** + * @brief Start scanning. + * @param [in] duration The duration in seconds for which to scan. + * @param [in] scanCompleteCB A function to be called when scanning has completed. + * @return True if scan started or false if there was an error. + */ +bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)) { + ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); + + m_semaphoreScanEnd.take(std::string("start")); + m_scanCompleteCB = scanCompleteCB; // Save the callback to be invoked when the scan completes. + + m_scanResults.m_vectorAdvertisedDevices.clear(); + + esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); + m_semaphoreScanEnd.give(); + return false; + } + + errRc = ::esp_ble_gap_start_scanning(duration); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); + m_semaphoreScanEnd.give(); + return false; + } + + m_stopped = false; + + ESP_LOGD(LOG_TAG, "<< start()"); + return true; +} // start + + +/** + * @brief Start scanning and block until scanning has been completed. + * @param [in] duration The duration in seconds for which to scan. + * @return The BLEScanResults. + */ +BLEScanResults BLEScan::start(uint32_t duration) { + if(start(duration, nullptr)) { + m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. + } + return m_scanResults; +} // start + + +/** + * @brief Stop an in progress scan. + * @return N/A. + */ +void BLEScan::stop() { + ESP_LOGD(LOG_TAG, ">> stop()"); + + esp_err_t errRc = ::esp_ble_gap_stop_scanning(); + + m_stopped = true; + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreScanEnd.give(); + + ESP_LOGD(LOG_TAG, "<< stop()"); +} // stop + + +/** + * @brief Dump the scan results to the log. + */ +void BLEScanResults::dump() { + ESP_LOGD(LOG_TAG, ">> Dump scan results:"); + for (int i=0; i - -#include -#include "BLEAdvertisedDevice.h" -#include "BLEClient.h" -#include "FreeRTOS.h" - -class BLEAdvertisedDevice; -class BLEAdvertisedDeviceCallbacks; -class BLEClient; -class BLEScan; - - -/** - * @brief The result of having performed a scan. - * When a scan completes, we have a set of found devices. Each device is described - * by a BLEAdvertisedDevice object. The number of items in the set is given by - * getCount(). We can retrieve a device by calling getDevice() passing in the - * index (starting at 0) of the desired device. - */ -class BLEScanResults { -public: - void dump(); - int getCount(); - BLEAdvertisedDevice getDevice(uint32_t i); - -private: - friend BLEScan; - std::vector m_vectorAdvertisedDevices; -}; - -/** - * @brief Perform and manage %BLE scans. - * - * Scanning is associated with a %BLE client that is attempting to locate BLE servers. - */ -class BLEScan { -public: - void setActiveScan(bool active); - void setAdvertisedDeviceCallbacks( - BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, - bool wantDuplicates = false); - void setInterval(uint16_t intervalMSecs); - void setWindow(uint16_t windowMSecs); - bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)); - BLEScanResults start(uint32_t duration); - void stop(); - -private: - BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. - friend class BLEDevice; - void handleGAPEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param); - void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); - - - esp_ble_scan_params_t m_scan_params; - BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks; - bool m_stopped; - FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); - BLEScanResults m_scanResults; - bool m_wantDuplicates; - void (*m_scanCompleteCB)(BLEScanResults scanResults); -}; // BLEScan - -#endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLESCAN_H_ */ +/* + * BLEScan.h + * + * Created on: Jul 1, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESCAN_H_ +#define COMPONENTS_CPP_UTILS_BLESCAN_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include + +#include +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "FreeRTOS.h" + +class BLEAdvertisedDevice; +class BLEAdvertisedDeviceCallbacks; +class BLEClient; +class BLEScan; + + +/** + * @brief The result of having performed a scan. + * When a scan completes, we have a set of found devices. Each device is described + * by a BLEAdvertisedDevice object. The number of items in the set is given by + * getCount(). We can retrieve a device by calling getDevice() passing in the + * index (starting at 0) of the desired device. + */ +class BLEScanResults { +public: + void dump(); + int getCount(); + BLEAdvertisedDevice getDevice(uint32_t i); + +private: + friend BLEScan; + std::vector m_vectorAdvertisedDevices; +}; + +/** + * @brief Perform and manage %BLE scans. + * + * Scanning is associated with a %BLE client that is attempting to locate BLE servers. + */ +class BLEScan { +public: + void setActiveScan(bool active); + void setAdvertisedDeviceCallbacks( + BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, + bool wantDuplicates = false); + void setInterval(uint16_t intervalMSecs); + void setWindow(uint16_t windowMSecs); + bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)); + BLEScanResults start(uint32_t duration); + void stop(); + +private: + BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. + friend class BLEDevice; + void handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); + void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); + + + esp_ble_scan_params_t m_scan_params; + BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks; + bool m_stopped; + FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); + BLEScanResults m_scanResults; + bool m_wantDuplicates; + void (*m_scanCompleteCB)(BLEScanResults scanResults); +}; // BLEScan + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLESCAN_H_ */ diff --git a/cpp_utils/BLESecurity.h b/cpp_utils/BLESecurity.h index 2d52b015..e35a398a 100644 --- a/cpp_utils/BLESecurity.h +++ b/cpp_utils/BLESecurity.h @@ -1,71 +1,71 @@ -/* - * BLESecurity.h - * - * Created on: Dec 17, 2017 - * Author: chegewara - */ - -#ifndef COMPONENTS_CPP_UTILS_BLESECURITY_H_ -#define COMPONENTS_CPP_UTILS_BLESECURITY_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - -#include - -class BLESecurity { -public: - BLESecurity(); - virtual ~BLESecurity(); - void setAuthenticationMode(esp_ble_auth_req_t auth_req); - void setCapability(esp_ble_io_cap_t iocap); - void setInitEncryptionKey(uint8_t init_key); - void setRespEncryptionKey(uint8_t resp_key); - void setKeySize(uint8_t key_size = 16); - static char* esp_key_type_to_str(esp_ble_key_type_t key_type); - -private: - esp_ble_auth_req_t m_authReq; - esp_ble_io_cap_t m_iocap; - uint8_t m_initKey; - uint8_t m_respKey; - uint8_t m_keySize; -}; // BLESecurity - - -/* - * @brief Callbacks to handle GAP events related to authorization - */ -class BLESecurityCallbacks { -public: - virtual ~BLESecurityCallbacks() {}; - - /** - * @brief Its request from peer device to input authentication pin code displayed on peer device. - * It requires that our device is capable to input 6-digits code by end user - * @return Return 6-digits integer value from input device - */ - virtual uint32_t onPassKeyRequest() = 0; - - /** - * @brief Provide us 6-digits code to perform authentication. - * It requires that our device is capable to display this code to end user - * @param - */ - virtual void onPassKeyNotify(uint32_t pass_key) = 0; - - /** - * @brief Here we can make decision if we want to let negotiate authorization with peer device or not - * return Return true if we accept this peer device request - */ - - virtual bool onSecurityRequest() = 0 ; - /** - * Provide us information when authentication process is completed - */ - virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t) = 0; - - virtual bool onConfirmPIN(uint32_t pin) = 0; -}; // BLESecurityCallbacks - -#endif // CONFIG_BT_ENABLED -#endif // COMPONENTS_CPP_UTILS_BLESECURITY_H_ +/* + * BLESecurity.h + * + * Created on: Dec 17, 2017 + * Author: chegewara + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESECURITY_H_ +#define COMPONENTS_CPP_UTILS_BLESECURITY_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include + +class BLESecurity { +public: + BLESecurity(); + virtual ~BLESecurity(); + void setAuthenticationMode(esp_ble_auth_req_t auth_req); + void setCapability(esp_ble_io_cap_t iocap); + void setInitEncryptionKey(uint8_t init_key); + void setRespEncryptionKey(uint8_t resp_key); + void setKeySize(uint8_t key_size = 16); + static char* esp_key_type_to_str(esp_ble_key_type_t key_type); + +private: + esp_ble_auth_req_t m_authReq; + esp_ble_io_cap_t m_iocap; + uint8_t m_initKey; + uint8_t m_respKey; + uint8_t m_keySize; +}; // BLESecurity + + +/* + * @brief Callbacks to handle GAP events related to authorization + */ +class BLESecurityCallbacks { +public: + virtual ~BLESecurityCallbacks() {}; + + /** + * @brief Its request from peer device to input authentication pin code displayed on peer device. + * It requires that our device is capable to input 6-digits code by end user + * @return Return 6-digits integer value from input device + */ + virtual uint32_t onPassKeyRequest() = 0; + + /** + * @brief Provide us 6-digits code to perform authentication. + * It requires that our device is capable to display this code to end user + * @param + */ + virtual void onPassKeyNotify(uint32_t pass_key) = 0; + + /** + * @brief Here we can make decision if we want to let negotiate authorization with peer device or not + * return Return true if we accept this peer device request + */ + + virtual bool onSecurityRequest() = 0 ; + /** + * Provide us information when authentication process is completed + */ + virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t) = 0; + + virtual bool onConfirmPIN(uint32_t pin) = 0; +}; // BLESecurityCallbacks + +#endif // CONFIG_BT_ENABLED +#endif // COMPONENTS_CPP_UTILS_BLESECURITY_H_ diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 340ea560..863eb66f 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -1,435 +1,435 @@ -/* - * BLEService.cpp - * - * Created on: Mar 25, 2017 - * Author: kolban - */ - -// A service is identified by a UUID. A service is also the container for one or more characteristics. - -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include -#include - -#include -#include -#include - -#include "BLEServer.h" -#include "BLEService.h" -#include "BLEUtils.h" -#include "GeneralUtils.h" - -#ifdef ARDUINO_ARCH_ESP32 -#include "esp32-hal-log.h" -#endif - -#define NULL_HANDLE (0xffff) - -static const char* LOG_TAG = "BLEService"; // Tag for logging. - -/** - * @brief Construct an instance of the BLEService - * @param [in] uuid The UUID of the service. - * @param [in] numHandles The maximum number of handles associated with the service. - */ -BLEService::BLEService(const char* uuid, uint32_t numHandles) : BLEService(BLEUUID(uuid), numHandles) { -} - - -/** - * @brief Construct an instance of the BLEService - * @param [in] uuid The UUID of the service. - * @param [in] numHandles The maximum number of handles associated with the service. - */ -BLEService::BLEService(BLEUUID uuid, uint32_t numHandles) { - m_uuid = uuid; - m_handle = NULL_HANDLE; - m_pServer = nullptr; - //m_serializeMutex.setName("BLEService"); - m_lastCreatedCharacteristic = nullptr; - m_numHandles = numHandles; -} // BLEService - - -/** - * @brief Create the service. - * Create the service. - * @param [in] gatts_if The handle of the GATT server interface. - * @return N/A. - */ - -void BLEService::executeCreate(BLEServer *pServer) { - //ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); - //getUUID(); // Needed for a weird bug fix - //char x[1000]; - //memcpy(x, &m_uuid, sizeof(m_uuid)); - //char x[10]; - //memcpy(x, &deleteMe, 10); - m_pServer = pServer; - m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT - - esp_gatt_srvc_id_t srvc_id; - srvc_id.is_primary = true; - srvc_id.id.inst_id = m_id; - srvc_id.id.uuid = *m_uuid.getNative(); - esp_err_t errRc = ::esp_ble_gatts_create_service( - getServer()->getGattsIf(), - &srvc_id, - m_numHandles // The maximum number of handles associated with the service. - ); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - m_semaphoreCreateEvt.wait("executeCreate"); - ESP_LOGD(LOG_TAG, "<< executeCreate"); -} // executeCreate - - -/** - * @brief Delete the service. - * Delete the service. - * @return N/A. - */ - -void BLEService::executeDelete() { - ESP_LOGD(LOG_TAG, ">> executeDelete()"); - m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_DELETE_EVT - - esp_err_t errRc = ::esp_ble_gatts_delete_service( getHandle() ); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gatts_delete_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - m_semaphoreDeleteEvt.wait("executeDelete"); - ESP_LOGD(LOG_TAG, "<< executeDelete"); -} // executeDelete - - -/** - * @brief Dump details of this BLE GATT service. - * @return N/A. - */ -void BLEService::dump() { - ESP_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%.2x", - m_uuid.toString().c_str(), - m_handle); - ESP_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str()); -} // dump - - -/** - * @brief Get the UUID of the service. - * @return the UUID of the service. - */ -BLEUUID BLEService::getUUID() { - return m_uuid; -} // getUUID - - -/** - * @brief Start the service. - * Here we wish to start the service which means that we will respond to partner requests about it. - * Starting a service also means that we can create the corresponding characteristics. - * @return Start the service. - */ -void BLEService::start() { -// We ask the BLE runtime to start the service and then create each of the characteristics. -// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event -// obtained as a result of calling esp_ble_gatts_create_service(). -// - ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); - if (m_handle == NULL_HANDLE) { - ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!"); - return; - } - - - BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); - - while(pCharacteristic != nullptr) { - m_lastCreatedCharacteristic = pCharacteristic; - pCharacteristic->executeCreate(this); - - pCharacteristic = m_characteristicMap.getNext(); - } - // Start each of the characteristics ... these are found in the m_characteristicMap. - - m_semaphoreStartEvt.take("start"); - esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - m_semaphoreStartEvt.wait("start"); - - ESP_LOGD(LOG_TAG, "<< start()"); -} // start - - -/** - * @brief Stop the service. - * @return Stop the service. - */ -void BLEService::stop() { -// We ask the BLE runtime to start the service and then create each of the characteristics. -// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event -// obtained as a result of calling esp_ble_gatts_create_service(). -// - ESP_LOGD(LOG_TAG, ">> stop(): Stopping service (esp_ble_gatts_stop_service): %s", toString().c_str()); - if (m_handle == NULL_HANDLE) { - ESP_LOGE(LOG_TAG, "<< !!! We attempted to stop a service but don't know its handle!"); - return; - } - - m_semaphoreStopEvt.take("stop"); - esp_err_t errRc = ::esp_ble_gatts_stop_service(m_handle); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_stop_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - m_semaphoreStopEvt.wait("stop"); - - ESP_LOGD(LOG_TAG, "<< stop()"); -} // start - - -/** - * @brief Set the handle associated with this service. - * @param [in] handle The handle associated with the service. - */ -void BLEService::setHandle(uint16_t handle) { - ESP_LOGD(LOG_TAG, ">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str()); - if (m_handle != NULL_HANDLE) { - ESP_LOGE(LOG_TAG, "!!! Handle is already set %.2x", m_handle); - return; - } - m_handle = handle; - ESP_LOGD(LOG_TAG, "<< setHandle"); -} // setHandle - - -/** - * @brief Get the handle associated with this service. - * @return The handle associated with this service. - */ -uint16_t BLEService::getHandle() { - return m_handle; -} // getHandle - - -/** - * @brief Add a characteristic to the service. - * @param [in] pCharacteristic A pointer to the characteristic to be added. - */ -void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { -// We maintain a mapping of characteristics owned by this service. These are managed by the -// BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic -// to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF). -// - ESP_LOGD(LOG_TAG, ">> addCharacteristic()"); - ESP_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s", - pCharacteristic->getUUID().toString().c_str(), - toString().c_str()); - - // Check that we don't add the same characteristic twice. - if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { - ESP_LOGW(LOG_TAG, "<< Adding a new characteristic with the same UUID as a previous one"); - //return; - } - - // Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID - // but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT. - m_characteristicMap.setByUUID(pCharacteristic, pCharacteristic->getUUID()); - - ESP_LOGD(LOG_TAG, "<< addCharacteristic()"); -} // addCharacteristic - - -/** - * @brief Create a new BLE Characteristic associated with this service. - * @param [in] uuid - The UUID of the characteristic. - * @param [in] properties - The properties of the characteristic. - * @return The new BLE characteristic. - */ -BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) { - return createCharacteristic(BLEUUID(uuid), properties); -} - - -/** - * @brief Create a new BLE Characteristic associated with this service. - * @param [in] uuid - The UUID of the characteristic. - * @param [in] properties - The properties of the characteristic. - * @return The new BLE characteristic. - */ -BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) { - BLECharacteristic *pCharacteristic = new BLECharacteristic(uuid, properties); - addCharacteristic(pCharacteristic); - return pCharacteristic; -} // createCharacteristic - - -/** - * @brief Handle a GATTS server event. - */ -void BLEService::handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { - - - switch(event) { - // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. - // add_char: - // - esp_gatt_status_t status - // - uint16_t attr_handle - // - uint16_t service_handle - // - esp_bt_uuid_t char_uuid - - // If we have reached the correct service, then locate the characteristic and remember the handle - // for that characteristic. - case ESP_GATTS_ADD_CHAR_EVT: { - if (m_handle == param->add_char.service_handle) { - BLECharacteristic *pCharacteristic = getLastCreatedCharacteristic(); - if (pCharacteristic == nullptr) { - ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", - BLEUUID(param->add_char.char_uuid).toString().c_str()); - dump(); - break; - } - pCharacteristic->setHandle(param->add_char.attr_handle); - m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic); - //ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str()); - break; - } // Reached the correct service. - break; - } // ESP_GATTS_ADD_CHAR_EVT - - - // ESP_GATTS_START_EVT - // - // start: - // esp_gatt_status_t status - // uint16_t service_handle - case ESP_GATTS_START_EVT: { - if (param->start.service_handle == getHandle()) { - m_semaphoreStartEvt.give(); - } - break; - } // ESP_GATTS_START_EVT - - // ESP_GATTS_STOP_EVT - // - // stop: - // esp_gatt_status_t status - // uint16_t service_handle - // - case ESP_GATTS_STOP_EVT: { - if (param->stop.service_handle == getHandle()) { - m_semaphoreStopEvt.give(); - } - break; - } // ESP_GATTS_STOP_EVT - - - // ESP_GATTS_CREATE_EVT - // Called when a new service is registered as having been created. - // - // create: - // * esp_gatt_status_t status - // * uint16_t service_handle - // * esp_gatt_srvc_id_t service_id - // * - esp_gatt_id id - // * - esp_bt_uuid uuid - // * - uint8_t inst_id - // * - bool is_primary - // - case ESP_GATTS_CREATE_EVT: { - if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid)) && m_id == param->create.service_id.id.inst_id) { - setHandle(param->create.service_handle); - m_semaphoreCreateEvt.give(); - } - break; - } // ESP_GATTS_CREATE_EVT - - - // ESP_GATTS_DELETE_EVT - // Called when a service is deleted. - // - // delete: - // * esp_gatt_status_t status - // * uint16_t service_handle - // - case ESP_GATTS_DELETE_EVT: { - if (param->del.service_handle == getHandle()) { - m_semaphoreDeleteEvt.give(); - } - break; - } // ESP_GATTS_DELETE_EVT - - default: { - break; - } // Default - } // Switch - - // Invoke the GATTS handler in each of the associated characteristics. - m_characteristicMap.handleGATTServerEvent(event, gatts_if, param); -} // handleGATTServerEvent - - -BLECharacteristic* BLEService::getCharacteristic(const char* uuid) { - return getCharacteristic(BLEUUID(uuid)); -} - - -BLECharacteristic* BLEService::getCharacteristic(BLEUUID uuid) { - return m_characteristicMap.getByUUID(uuid); -} - - -/** - * @brief Return a string representation of this service. - * A service is defined by: - * * Its UUID - * * Its handle - * @return A string representation of this service. - */ -std::string BLEService::toString() { - std::stringstream stringStream; - stringStream << "UUID: " << getUUID().toString() << - ", handle: 0x" << std::hex << std::setfill('0') << std::setw(2) << getHandle(); - return stringStream.str(); -} // toString - - -/** - * @brief Get the last created characteristic. - * It is lamentable that this function has to exist. It returns the last created characteristic. - * We need this because the descriptor API is built around the notion that a new descriptor, when created, - * is associated with the last characteristics created and we need that information. - * @return The last created characteristic. - */ -BLECharacteristic* BLEService::getLastCreatedCharacteristic() { - return m_lastCreatedCharacteristic; -} // getLastCreatedCharacteristic - - -/** - * @brief Get the BLE server associated with this service. - * @return The BLEServer associated with this service. - */ -BLEServer* BLEService::getServer() { - return m_pServer; -} // getServer - -#endif // CONFIG_BT_ENABLED +/* + * BLEService.cpp + * + * Created on: Mar 25, 2017 + * Author: kolban + */ + +// A service is identified by a UUID. A service is also the container for one or more characteristics. + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include + +#include +#include +#include + +#include "BLEServer.h" +#include "BLEService.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" + +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + +#define NULL_HANDLE (0xffff) + +static const char* LOG_TAG = "BLEService"; // Tag for logging. + +/** + * @brief Construct an instance of the BLEService + * @param [in] uuid The UUID of the service. + * @param [in] numHandles The maximum number of handles associated with the service. + */ +BLEService::BLEService(const char* uuid, uint32_t numHandles) : BLEService(BLEUUID(uuid), numHandles) { +} + + +/** + * @brief Construct an instance of the BLEService + * @param [in] uuid The UUID of the service. + * @param [in] numHandles The maximum number of handles associated with the service. + */ +BLEService::BLEService(BLEUUID uuid, uint32_t numHandles) { + m_uuid = uuid; + m_handle = NULL_HANDLE; + m_pServer = nullptr; + //m_serializeMutex.setName("BLEService"); + m_lastCreatedCharacteristic = nullptr; + m_numHandles = numHandles; +} // BLEService + + +/** + * @brief Create the service. + * Create the service. + * @param [in] gatts_if The handle of the GATT server interface. + * @return N/A. + */ + +void BLEService::executeCreate(BLEServer *pServer) { + //ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); + //getUUID(); // Needed for a weird bug fix + //char x[1000]; + //memcpy(x, &m_uuid, sizeof(m_uuid)); + //char x[10]; + //memcpy(x, &deleteMe, 10); + m_pServer = pServer; + m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT + + esp_gatt_srvc_id_t srvc_id; + srvc_id.is_primary = true; + srvc_id.id.inst_id = m_id; + srvc_id.id.uuid = *m_uuid.getNative(); + esp_err_t errRc = ::esp_ble_gatts_create_service( + getServer()->getGattsIf(), + &srvc_id, + m_numHandles // The maximum number of handles associated with the service. + ); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreCreateEvt.wait("executeCreate"); + ESP_LOGD(LOG_TAG, "<< executeCreate"); +} // executeCreate + + +/** + * @brief Delete the service. + * Delete the service. + * @return N/A. + */ + +void BLEService::executeDelete() { + ESP_LOGD(LOG_TAG, ">> executeDelete()"); + m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_DELETE_EVT + + esp_err_t errRc = ::esp_ble_gatts_delete_service( getHandle() ); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_delete_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreDeleteEvt.wait("executeDelete"); + ESP_LOGD(LOG_TAG, "<< executeDelete"); +} // executeDelete + + +/** + * @brief Dump details of this BLE GATT service. + * @return N/A. + */ +void BLEService::dump() { + ESP_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%.2x", + m_uuid.toString().c_str(), + m_handle); + ESP_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str()); +} // dump + + +/** + * @brief Get the UUID of the service. + * @return the UUID of the service. + */ +BLEUUID BLEService::getUUID() { + return m_uuid; +} // getUUID + + +/** + * @brief Start the service. + * Here we wish to start the service which means that we will respond to partner requests about it. + * Starting a service also means that we can create the corresponding characteristics. + * @return Start the service. + */ +void BLEService::start() { +// We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). +// + ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); + if (m_handle == NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!"); + return; + } + + + BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); + + while(pCharacteristic != nullptr) { + m_lastCreatedCharacteristic = pCharacteristic; + pCharacteristic->executeCreate(this); + + pCharacteristic = m_characteristicMap.getNext(); + } + // Start each of the characteristics ... these are found in the m_characteristicMap. + + m_semaphoreStartEvt.take("start"); + esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreStartEvt.wait("start"); + + ESP_LOGD(LOG_TAG, "<< start()"); +} // start + + +/** + * @brief Stop the service. + * @return Stop the service. + */ +void BLEService::stop() { +// We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). +// + ESP_LOGD(LOG_TAG, ">> stop(): Stopping service (esp_ble_gatts_stop_service): %s", toString().c_str()); + if (m_handle == NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "<< !!! We attempted to stop a service but don't know its handle!"); + return; + } + + m_semaphoreStopEvt.take("stop"); + esp_err_t errRc = ::esp_ble_gatts_stop_service(m_handle); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_stop_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreStopEvt.wait("stop"); + + ESP_LOGD(LOG_TAG, "<< stop()"); +} // start + + +/** + * @brief Set the handle associated with this service. + * @param [in] handle The handle associated with the service. + */ +void BLEService::setHandle(uint16_t handle) { + ESP_LOGD(LOG_TAG, ">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str()); + if (m_handle != NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "!!! Handle is already set %.2x", m_handle); + return; + } + m_handle = handle; + ESP_LOGD(LOG_TAG, "<< setHandle"); +} // setHandle + + +/** + * @brief Get the handle associated with this service. + * @return The handle associated with this service. + */ +uint16_t BLEService::getHandle() { + return m_handle; +} // getHandle + + +/** + * @brief Add a characteristic to the service. + * @param [in] pCharacteristic A pointer to the characteristic to be added. + */ +void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { +// We maintain a mapping of characteristics owned by this service. These are managed by the +// BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic +// to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF). +// + ESP_LOGD(LOG_TAG, ">> addCharacteristic()"); + ESP_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s", + pCharacteristic->getUUID().toString().c_str(), + toString().c_str()); + + // Check that we don't add the same characteristic twice. + if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { + ESP_LOGW(LOG_TAG, "<< Adding a new characteristic with the same UUID as a previous one"); + //return; + } + + // Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID + // but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT. + m_characteristicMap.setByUUID(pCharacteristic, pCharacteristic->getUUID()); + + ESP_LOGD(LOG_TAG, "<< addCharacteristic()"); +} // addCharacteristic + + +/** + * @brief Create a new BLE Characteristic associated with this service. + * @param [in] uuid - The UUID of the characteristic. + * @param [in] properties - The properties of the characteristic. + * @return The new BLE characteristic. + */ +BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) { + return createCharacteristic(BLEUUID(uuid), properties); +} + + +/** + * @brief Create a new BLE Characteristic associated with this service. + * @param [in] uuid - The UUID of the characteristic. + * @param [in] properties - The properties of the characteristic. + * @return The new BLE characteristic. + */ +BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) { + BLECharacteristic *pCharacteristic = new BLECharacteristic(uuid, properties); + addCharacteristic(pCharacteristic); + return pCharacteristic; +} // createCharacteristic + + +/** + * @brief Handle a GATTS server event. + */ +void BLEService::handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param) { + + + switch(event) { + // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. + // add_char: + // - esp_gatt_status_t status + // - uint16_t attr_handle + // - uint16_t service_handle + // - esp_bt_uuid_t char_uuid + + // If we have reached the correct service, then locate the characteristic and remember the handle + // for that characteristic. + case ESP_GATTS_ADD_CHAR_EVT: { + if (m_handle == param->add_char.service_handle) { + BLECharacteristic *pCharacteristic = getLastCreatedCharacteristic(); + if (pCharacteristic == nullptr) { + ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", + BLEUUID(param->add_char.char_uuid).toString().c_str()); + dump(); + break; + } + pCharacteristic->setHandle(param->add_char.attr_handle); + m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic); + //ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str()); + break; + } // Reached the correct service. + break; + } // ESP_GATTS_ADD_CHAR_EVT + + + // ESP_GATTS_START_EVT + // + // start: + // esp_gatt_status_t status + // uint16_t service_handle + case ESP_GATTS_START_EVT: { + if (param->start.service_handle == getHandle()) { + m_semaphoreStartEvt.give(); + } + break; + } // ESP_GATTS_START_EVT + + // ESP_GATTS_STOP_EVT + // + // stop: + // esp_gatt_status_t status + // uint16_t service_handle + // + case ESP_GATTS_STOP_EVT: { + if (param->stop.service_handle == getHandle()) { + m_semaphoreStopEvt.give(); + } + break; + } // ESP_GATTS_STOP_EVT + + + // ESP_GATTS_CREATE_EVT + // Called when a new service is registered as having been created. + // + // create: + // * esp_gatt_status_t status + // * uint16_t service_handle + // * esp_gatt_srvc_id_t service_id + // * - esp_gatt_id id + // * - esp_bt_uuid uuid + // * - uint8_t inst_id + // * - bool is_primary + // + case ESP_GATTS_CREATE_EVT: { + if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid)) && m_id == param->create.service_id.id.inst_id) { + setHandle(param->create.service_handle); + m_semaphoreCreateEvt.give(); + } + break; + } // ESP_GATTS_CREATE_EVT + + + // ESP_GATTS_DELETE_EVT + // Called when a service is deleted. + // + // delete: + // * esp_gatt_status_t status + // * uint16_t service_handle + // + case ESP_GATTS_DELETE_EVT: { + if (param->del.service_handle == getHandle()) { + m_semaphoreDeleteEvt.give(); + } + break; + } // ESP_GATTS_DELETE_EVT + + default: { + break; + } // Default + } // Switch + + // Invoke the GATTS handler in each of the associated characteristics. + m_characteristicMap.handleGATTServerEvent(event, gatts_if, param); +} // handleGATTServerEvent + + +BLECharacteristic* BLEService::getCharacteristic(const char* uuid) { + return getCharacteristic(BLEUUID(uuid)); +} + + +BLECharacteristic* BLEService::getCharacteristic(BLEUUID uuid) { + return m_characteristicMap.getByUUID(uuid); +} + + +/** + * @brief Return a string representation of this service. + * A service is defined by: + * * Its UUID + * * Its handle + * @return A string representation of this service. + */ +std::string BLEService::toString() { + std::stringstream stringStream; + stringStream << "UUID: " << getUUID().toString() << + ", handle: 0x" << std::hex << std::setfill('0') << std::setw(2) << getHandle(); + return stringStream.str(); +} // toString + + +/** + * @brief Get the last created characteristic. + * It is lamentable that this function has to exist. It returns the last created characteristic. + * We need this because the descriptor API is built around the notion that a new descriptor, when created, + * is associated with the last characteristics created and we need that information. + * @return The last created characteristic. + */ +BLECharacteristic* BLEService::getLastCreatedCharacteristic() { + return m_lastCreatedCharacteristic; +} // getLastCreatedCharacteristic + + +/** + * @brief Get the BLE server associated with this service. + * @return The BLEServer associated with this service. + */ +BLEServer* BLEService::getServer() { + return m_pServer; +} // getServer + +#endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 93b4b2c6..d2ab0759 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -1,104 +1,104 @@ -/* - * BLEService.h - * - * Created on: Mar 25, 2017 - * Author: kolban - */ - -#ifndef COMPONENTS_CPP_UTILS_BLESERVICE_H_ -#define COMPONENTS_CPP_UTILS_BLESERVICE_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - -#include - -#include "BLECharacteristic.h" -#include "BLEServer.h" -#include "BLEUUID.h" -#include "FreeRTOS.h" - -class BLEServer; - -/** - * @brief A data mapping used to manage the set of %BLE characteristics known to the server. - */ -class BLECharacteristicMap { -public: - void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid); - void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid); - void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); - BLECharacteristic* getByUUID(const char* uuid); - BLECharacteristic* getByUUID(BLEUUID uuid); - BLECharacteristic* getByHandle(uint16_t handle); - BLECharacteristic* getFirst(); - BLECharacteristic* getNext(); - std::string toString(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param); - - -private: - std::map m_uuidMap; - std::map m_handleMap; - std::map::iterator m_iterator; -}; - - -/** - * @brief The model of a %BLE service. - * - */ -class BLEService { -public: - void addCharacteristic(BLECharacteristic* pCharacteristic); - BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties); - BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); - void dump(); - void executeCreate(BLEServer* pServer); - void executeDelete(); - BLECharacteristic* getCharacteristic(const char* uuid); - BLECharacteristic* getCharacteristic(BLEUUID uuid); - BLEUUID getUUID(); - BLEServer* getServer(); - void start(); - void stop(); - std::string toString(); - uint16_t getHandle(); - uint8_t m_id = 0; - -private: - BLEService(const char* uuid, uint32_t numHandles); - BLEService(BLEUUID uuid, uint32_t numHandles); - friend class BLEServer; - friend class BLEServiceMap; - friend class BLEDescriptor; - friend class BLECharacteristic; - friend class BLEDevice; - - BLECharacteristicMap m_characteristicMap; - uint16_t m_handle; - BLECharacteristic* m_lastCreatedCharacteristic; - BLEServer* m_pServer; - BLEUUID m_uuid; - - FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); - FreeRTOS::Semaphore m_semaphoreDeleteEvt = FreeRTOS::Semaphore("DeleteEvt"); - FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); - FreeRTOS::Semaphore m_semaphoreStopEvt = FreeRTOS::Semaphore("StopEvt"); - - uint32_t m_numHandles; - - BLECharacteristic* getLastCreatedCharacteristic(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param); - void setHandle(uint16_t handle); - //void setService(esp_gatt_srvc_id_t srvc_id); -}; // BLEService - - -#endif // CONFIG_BT_ENABLED -#endif /* COMPONENTS_CPP_UTILS_BLESERVICE_H_ */ +/* + * BLEService.h + * + * Created on: Mar 25, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESERVICE_H_ +#define COMPONENTS_CPP_UTILS_BLESERVICE_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include + +#include "BLECharacteristic.h" +#include "BLEServer.h" +#include "BLEUUID.h" +#include "FreeRTOS.h" + +class BLEServer; + +/** + * @brief A data mapping used to manage the set of %BLE characteristics known to the server. + */ +class BLECharacteristicMap { +public: + void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid); + void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid); + void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); + BLECharacteristic* getByUUID(const char* uuid); + BLECharacteristic* getByUUID(BLEUUID uuid); + BLECharacteristic* getByHandle(uint16_t handle); + BLECharacteristic* getFirst(); + BLECharacteristic* getNext(); + std::string toString(); + void handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param); + + +private: + std::map m_uuidMap; + std::map m_handleMap; + std::map::iterator m_iterator; +}; + + +/** + * @brief The model of a %BLE service. + * + */ +class BLEService { +public: + void addCharacteristic(BLECharacteristic* pCharacteristic); + BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties); + BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); + void dump(); + void executeCreate(BLEServer* pServer); + void executeDelete(); + BLECharacteristic* getCharacteristic(const char* uuid); + BLECharacteristic* getCharacteristic(BLEUUID uuid); + BLEUUID getUUID(); + BLEServer* getServer(); + void start(); + void stop(); + std::string toString(); + uint16_t getHandle(); + uint8_t m_id = 0; + +private: + BLEService(const char* uuid, uint32_t numHandles); + BLEService(BLEUUID uuid, uint32_t numHandles); + friend class BLEServer; + friend class BLEServiceMap; + friend class BLEDescriptor; + friend class BLECharacteristic; + friend class BLEDevice; + + BLECharacteristicMap m_characteristicMap; + uint16_t m_handle; + BLECharacteristic* m_lastCreatedCharacteristic; + BLEServer* m_pServer; + BLEUUID m_uuid; + + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreDeleteEvt = FreeRTOS::Semaphore("DeleteEvt"); + FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); + FreeRTOS::Semaphore m_semaphoreStopEvt = FreeRTOS::Semaphore("StopEvt"); + + uint32_t m_numHandles; + + BLECharacteristic* getLastCreatedCharacteristic(); + void handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param); + void setHandle(uint16_t handle); + //void setService(esp_gatt_srvc_id_t srvc_id); +}; // BLEService + + +#endif // CONFIG_BT_ENABLED +#endif /* COMPONENTS_CPP_UTILS_BLESERVICE_H_ */ diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index dd828fae..179de7a8 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -1,132 +1,132 @@ -/* - * BLEServiceMap.cpp - * - * Created on: Jun 22, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include -#include "BLEService.h" - - -/** - * @brief Return the service by UUID. - * @param [in] UUID The UUID to look up the service. - * @return The characteristic. - */ -BLEService* BLEServiceMap::getByUUID(const char* uuid) { - return getByUUID(BLEUUID(uuid)); -} - -/** - * @brief Return the service by UUID. - * @param [in] UUID The UUID to look up the service. - * @return The characteristic. - */ -BLEService* BLEServiceMap::getByUUID(BLEUUID uuid) { - for (auto &myPair : m_uuidMap) { - if (myPair.first->getUUID().equals(uuid)) { - return myPair.first; - } - } - //return m_uuidMap.at(uuid.toString()); - return nullptr; -} // getByUUID - - -/** - * @brief Return the service by handle. - * @param [in] handle The handle to look up the service. - * @return The service. - */ -BLEService* BLEServiceMap::getByHandle(uint16_t handle) { - return m_handleMap.at(handle); -} // getByHandle - - -/** - * @brief Set the service by UUID. - * @param [in] uuid The uuid of the service. - * @param [in] characteristic The service to cache. - * @return N/A. - */ -void BLEServiceMap::setByUUID(BLEUUID uuid, - BLEService *service) { - m_uuidMap.insert(std::pair(service, uuid.toString())); -} // setByUUID - - -/** - * @brief Set the service by handle. - * @param [in] handle The handle of the service. - * @param [in] service The service to cache. - * @return N/A. - */ -void BLEServiceMap::setByHandle(uint16_t handle, - BLEService* service) { - m_handleMap.insert(std::pair(handle, service)); -} // setByHandle - - -/** - * @brief Return a string representation of the service map. - * @return A string representation of the service map. - */ -std::string BLEServiceMap::toString() { - std::stringstream stringStream; - stringStream << std::hex << std::setfill('0'); - for (auto &myPair: m_handleMap) { - stringStream << "handle: 0x" << std::setw(2) << myPair.first << ", uuid: " + myPair.second->getUUID().toString() << "\n"; - } - return stringStream.str(); -} // toString - -void BLEServiceMap::handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { - // Invoke the handler for every Service we have. - for (auto &myPair : m_uuidMap) { - myPair.first->handleGATTServerEvent(event, gatts_if, param); - } -} - -/** - * @brief Get the first service in the map. - * @return The first service in the map. - */ -BLEService* BLEServiceMap::getFirst() { - m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLEService* pRet = m_iterator->first; - m_iterator++; - return pRet; -} // getFirst - -/** - * @brief Get the next service in the map. - * @return The next service in the map. - */ -BLEService* BLEServiceMap::getNext() { - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLEService* pRet = m_iterator->first; - m_iterator++; - return pRet; -} // getNext - -/** - * @brief Removes service from maps. - * @return N/A. - */ -void BLEServiceMap::removeService(BLEService *service){ - m_handleMap.erase(service->getHandle()); - m_uuidMap.erase(service); -} // removeService - -#endif /* CONFIG_BT_ENABLED */ +/* + * BLEServiceMap.cpp + * + * Created on: Jun 22, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEService.h" + + +/** + * @brief Return the service by UUID. + * @param [in] UUID The UUID to look up the service. + * @return The characteristic. + */ +BLEService* BLEServiceMap::getByUUID(const char* uuid) { + return getByUUID(BLEUUID(uuid)); +} + +/** + * @brief Return the service by UUID. + * @param [in] UUID The UUID to look up the service. + * @return The characteristic. + */ +BLEService* BLEServiceMap::getByUUID(BLEUUID uuid) { + for (auto &myPair : m_uuidMap) { + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; + } + } + //return m_uuidMap.at(uuid.toString()); + return nullptr; +} // getByUUID + + +/** + * @brief Return the service by handle. + * @param [in] handle The handle to look up the service. + * @return The service. + */ +BLEService* BLEServiceMap::getByHandle(uint16_t handle) { + return m_handleMap.at(handle); +} // getByHandle + + +/** + * @brief Set the service by UUID. + * @param [in] uuid The uuid of the service. + * @param [in] characteristic The service to cache. + * @return N/A. + */ +void BLEServiceMap::setByUUID(BLEUUID uuid, + BLEService *service) { + m_uuidMap.insert(std::pair(service, uuid.toString())); +} // setByUUID + + +/** + * @brief Set the service by handle. + * @param [in] handle The handle of the service. + * @param [in] service The service to cache. + * @return N/A. + */ +void BLEServiceMap::setByHandle(uint16_t handle, + BLEService* service) { + m_handleMap.insert(std::pair(handle, service)); +} // setByHandle + + +/** + * @brief Return a string representation of the service map. + * @return A string representation of the service map. + */ +std::string BLEServiceMap::toString() { + std::stringstream stringStream; + stringStream << std::hex << std::setfill('0'); + for (auto &myPair: m_handleMap) { + stringStream << "handle: 0x" << std::setw(2) << myPair.first << ", uuid: " + myPair.second->getUUID().toString() << "\n"; + } + return stringStream.str(); +} // toString + +void BLEServiceMap::handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param) { + // Invoke the handler for every Service we have. + for (auto &myPair : m_uuidMap) { + myPair.first->handleGATTServerEvent(event, gatts_if, param); + } +} + +/** + * @brief Get the first service in the map. + * @return The first service in the map. + */ +BLEService* BLEServiceMap::getFirst() { + m_iterator = m_uuidMap.begin(); + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } + BLEService* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getFirst + +/** + * @brief Get the next service in the map. + * @return The next service in the map. + */ +BLEService* BLEServiceMap::getNext() { + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } + BLEService* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getNext + +/** + * @brief Removes service from maps. + * @return N/A. + */ +void BLEServiceMap::removeService(BLEService *service){ + m_handleMap.erase(service->getHandle()); + m_uuidMap.erase(service); +} // removeService + +#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 09d59d55..81d37c36 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -1,904 +1,904 @@ -/* - * WiFi.cpp - * - * Created on: Feb 25, 2017 - * Author: kolban - */ - -//#define _GLIBCXX_USE_C99 -#include -#include -#include -#include -#include "sdkconfig.h" - - -#include "WiFi.h" -#include -#include -#include -#include -#include -#include "GeneralUtils.h" -#include -#include -#include -#include -#include - -#include - - -static const char* LOG_TAG = "WiFi"; - - -/* -static void setDNSServer(char *ip) { - ip_addr_t dnsserver; - ESP_LOGD(tag, "Setting DNS[%d] to %s", 0, ip); - inet_pton(AF_INET, ip, &dnsserver); - ESP_LOGD(tag, "ip of DNS is %.8x", *(uint32_t *)&dnsserver); - dns_setserver(0, &dnsserver); -} -*/ - - -/** - * @brief Creates and uses a default event handler - */ -WiFi::WiFi() - : ip(0) - , gw(0) - , netmask(0) - , m_pWifiEventHandler(nullptr) -{ - m_eventLoopStarted = false; - m_initCalled = false; - //m_pWifiEventHandler = new WiFiEventHandler(); - m_apConnectionStatus = UINT8_MAX; // Are we connected to an access point? -} // WiFi - - -/** - * @brief Deletes the event handler that was used by the class - */ -WiFi::~WiFi() { - if (m_pWifiEventHandler != nullptr) { - delete m_pWifiEventHandler; - m_pWifiEventHandler = nullptr; - } -} // ~WiFi - - -/** - * @brief Add a reference to a DNS server. - * - * Here we define a server that will act as a DNS server. We can add two DNS - * servers in total. The first will be the primary, the second will be the backup. - * The public Google DNS servers are "8.8.8.8" and "8.8.4.4". - * - * For example: - * - * @code{.cpp} - * wifi.addDNSServer("8.8.8.8"); - * wifi.addDNSServer("8.8.4.4"); - * @endcode - * - * @param [in] ip The IP address of the DNS Server. - * @return N/A. - */ -void WiFi::addDNSServer(const std::string& ip) { - addDNSServer(ip.c_str()); -} // addDNSServer - - -void WiFi::addDNSServer(const char* ip) { - ip_addr_t dns_server; - if(inet_pton(AF_INET, ip, &dns_server)) { - addDNSServer(ip); - } -} // addDNSServer - - -void WiFi::addDNSServer(ip_addr_t ip) { - ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); - init(); - ::dns_setserver(m_dnsCount, &ip); - m_dnsCount++; - m_dnsCount %= 2; -} // addDNSServer - - -/** - * @brief Set a reference to a DNS server. - * - * Here we define a server that will act as a DNS server. We use numdns to specify which DNS server to set - * - * For example: - * - * @code{.cpp} - * wifi.setDNSServer(0, "8.8.8.8"); - * wifi.setDNSServer(1, "8.8.4.4"); - * @endcode - * - * @param [in] numdns The DNS number we wish to set - * @param [in] ip The IP address of the DNS Server. - * @return N/A. - */ -void WiFi::setDNSServer(int numdns, const std::string& ip) { - setDNSServer(numdns, ip.c_str()); -} // setDNSServer - - -void WiFi::setDNSServer(int numdns, const char* ip) { - ip_addr_t dns_server; - if(inet_pton(AF_INET, ip, &dns_server)) { - setDNSServer(numdns, dns_server); - } -} // setDNSServer - - -void WiFi::setDNSServer(int numdns, ip_addr_t ip) { - ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); - init(); - ::dns_setserver(numdns, &ip); -} // setDNSServer - - -/** - * @brief Connect to an external access point. - * - * The event handler will be called back with the outcome of the connection. - * - * @param [in] ssid The network SSID of the access point to which we wish to connect. - * @param [in] password The password of the access point to which we wish to connect. - * @param [in] waitForConnection Block until the connection has an outcome. - * @param [in] mode WIFI_MODE_AP for normal or WIFI_MODE_APSTA if you want to keep an Access Point running while you connect - * @return ESP_OK if we are now connected and wifi_err_reason_t if not. - */ -uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection, wifi_mode_t mode){ - ESP_LOGD(LOG_TAG, ">> connectAP"); - - m_apConnectionStatus = UINT8_MAX; - init(); - - if (ip != 0 && gw != 0 && netmask != 0) { - ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client - - tcpip_adapter_ip_info_t ipInfo; - ipInfo.ip.addr = ip; - ipInfo.gw.addr = gw; - ipInfo.netmask.addr = netmask; - - ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - } - - esp_err_t errRc = ::esp_wifi_set_mode(mode); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - wifi_config_t sta_config; - ::memset(&sta_config, 0, sizeof(sta_config)); - ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); - ::memcpy(sta_config.sta.password, password.data(), password.size()); - sta_config.sta.bssid_set = 0; - errRc = ::esp_wifi_set_config(WIFI_IF_STA, &sta_config); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - errRc = ::esp_wifi_start(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - m_connectFinished.take("connectAP"); // Take the semaphore to wait for a connection. - do { - ESP_LOGD(LOG_TAG, "esp_wifi_connect"); - errRc = ::esp_wifi_connect(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - } - while (!m_connectFinished.take(5000, "connectAP")); // retry if not connected within 5s - m_connectFinished.give(); - - ESP_LOGD(LOG_TAG, "<< connectAP"); - return m_apConnectionStatus; // Return ESP_OK if we are now connected and wifi_err_reason_t if not. -} // connectAP - - - -/** - * @brief Dump diagnostics to the log. - */ -void WiFi::dump() { - ESP_LOGD(LOG_TAG, "WiFi Dump"); - ESP_LOGD(LOG_TAG, "---------"); - char ipAddrStr[30]; - ip_addr_t ip = ::dns_getserver(0); - inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); - ESP_LOGD(LOG_TAG, "DNS Server[0]: %s", ipAddrStr); -} // dump - - -/** - * @brief Returns whether wifi is connected to an access point - */ -bool WiFi::isConnectedToAP() { - return m_apConnectionStatus; -} // isConnected - - - -/** - * @brief Primary event handler interface. - */ -/* STATIC */ esp_err_t WiFi::eventHandler(void* ctx, system_event_t* event) { - // This is the common event handler that we have provided for all event processing. It is called for every event - // that is received by the WiFi subsystem. The "ctx" parameter is an instance of the current WiFi object that we are - // processing. We can then retrieve the specific/custom event handler from within it and invoke that. This then makes this - // an indirection vector to the real caller. - - WiFi *pWiFi = (WiFi *)ctx; // retrieve the WiFi object from the passed in context. - - // Invoke the event handler. - esp_err_t rc; - if (pWiFi->m_pWifiEventHandler != nullptr) { - rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); - } else { - rc = ESP_OK; - } - - // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that - // indicates we are waiting for a connection complete. - if (event->event_id == SYSTEM_EVENT_STA_GOT_IP || event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { - - if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { // If we connected and have an IP, change the status to ESP_OK. Otherwise, change it to the reason code. - pWiFi->m_apConnectionStatus = ESP_OK; - } else { - pWiFi->m_apConnectionStatus = event->event_info.disconnected.reason; - } - pWiFi->m_connectFinished.give(); - } - - return rc; -} // eventHandler - - -/** - * @brief Get the AP IP Info. - * @return The AP IP Info. - */ -tcpip_adapter_ip_info_t WiFi::getApIpInfo() { - //init(); - tcpip_adapter_ip_info_t ipInfo; - ::tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); - return ipInfo; -} // getApIpInfo - - -/** - * @brief Get the MAC address of the AP interface. - * @return The MAC address of the AP interface. - */ -std::string WiFi::getApMac() { - uint8_t mac[6]; - //init(); - esp_wifi_get_mac(WIFI_IF_AP, mac); - auto mac_str = (char*) malloc(18); - sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(std::move(mac_str)); -} // getApMac - - -/** - * @brief Get the AP SSID. - * @return The AP SSID. - */ -std::string WiFi::getApSSID() { - wifi_config_t conf; - //init(); - esp_wifi_get_config(WIFI_IF_AP, &conf); - return std::string((char *)conf.sta.ssid); -} // getApSSID - -/** - * @brief Get the current ESP32 IP form AP. - * @return The ESP32 IP. - */ -std::string WiFi::getApIp(){ - tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaIp - -/** - * @brief Get the current AP netmask. - * @return The Netmask IP. - */ -std::string WiFi::getApNetmask(){ - tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaNetmask - -/** - * @brief Get the current AP Gateway IP. - * @return The Gateway IP. - */ -std::string WiFi::getApGateway(){ - tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaGateway - -/** - * @brief Lookup an IP address by host name. - * - * @param [in] hostName The hostname to resolve. - * - * @return The IP address of the host or 0.0.0.0 if not found. - */ -struct in_addr WiFi::getHostByName(const std::string& hostName) { - return getHostByName(hostName.c_str()); -} // getHostByName - - -struct in_addr WiFi::getHostByName(const char* hostName) { - struct in_addr retAddr; - struct hostent *he = gethostbyname(hostName); - if (he == nullptr) { - retAddr.s_addr = 0; - ESP_LOGD(LOG_TAG, "Unable to resolve %s - %d", hostName, h_errno); - } else { - retAddr = *(struct in_addr *)(he->h_addr_list[0]); - ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); - } - return retAddr; -} // getHostByName - - -/** - * @brief Get the WiFi Mode. - * @return The WiFi Mode. - */ -std::string WiFi::getMode() { - wifi_mode_t mode; - esp_wifi_get_mode(&mode); - switch(mode) { - case WIFI_MODE_NULL: - return "WIFI_MODE_NULL"; - case WIFI_MODE_STA: - return "WIFI_MODE_STA"; - case WIFI_MODE_AP: - return "WIFI_MODE_AP"; - case WIFI_MODE_APSTA: - return "WIFI_MODE_APSTA"; - default: - return "unknown"; - } -} // getMode - - -/** - * @brief Get the STA IP Info. - * @return The STA IP Info. - */ -tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { - tcpip_adapter_ip_info_t ipInfo; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - return ipInfo; -} // getStaIpInfo - -/** - * @brief Get the current ESP32 IP form STA. - * @return The ESP32 IP. - */ -std::string WiFi::getStaIp(){ - tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaIp - - -/** - * @brief Get the current STA netmask. - * @return The Netmask IP. - */ -std::string WiFi::getStaNetmask(){ - tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaNetmask - - -/** - * @brief Get the current STA Gateway IP. - * @return The Gateway IP. - */ -std::string WiFi::getStaGateway(){ - tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaGateway - -/** - * @brief Get the MAC address of the STA interface. - * @return The MAC address of the STA interface. - */ -std::string WiFi::getStaMac() { - uint8_t mac[6]; - esp_wifi_get_mac(WIFI_IF_STA, mac); - auto mac_str = (char*) malloc(18); - sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(std::move(mac_str)); -} // getStaMac - - -/** - * @brief Get the STA SSID. - * @return The STA SSID. - */ -std::string WiFi::getStaSSID() { - wifi_config_t conf; - esp_wifi_get_config(WIFI_IF_STA, &conf); - return std::string((char *)conf.ap.ssid); -} // getStaSSID - - -/** - * @brief Initialize WiFi. - */ -/* PRIVATE */ void WiFi::init() { - - // If we have already started the event loop, then change the handler otherwise - // start the event loop. - if (m_eventLoopStarted) { - esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. - } else { - esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - m_eventLoopStarted = true; - } - // Now, one way or another, the event handler is WiFi::eventHandler. - - if (!m_initCalled) { - ::nvs_flash_init(); - ::tcpip_adapter_init(); - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - esp_err_t errRc = ::esp_wifi_init(&cfg); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - } - m_initCalled = true; -} // init - - -/** - * @brief Perform a WiFi scan looking for access points. - * - * An access point scan is performed and a vector of WiFi access point records - * is built and returned with one record per found scan instance. The scan is - * performed in a blocking fashion and will not return until the set of scanned - * access points has been built. - * - * @return A vector of WiFiAPRecord instances. - */ -std::vector WiFi::scan() { - ESP_LOGD(LOG_TAG, ">> scan"); - std::vector apRecords; - - init(); - - esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - errRc = ::esp_wifi_start(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - wifi_scan_config_t conf; - memset(&conf, 0, sizeof(conf)); - conf.show_hidden = true; - - esp_err_t rc = ::esp_wifi_scan_start(&conf, true); - if (rc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); - return apRecords; - } - - uint16_t apCount; // Number of access points available. - rc = ::esp_wifi_scan_get_ap_num(&apCount); - ESP_LOGD(LOG_TAG, "Count of found access points: %d", apCount); - - wifi_ap_record_t* list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); - if (list == nullptr) { - ESP_LOGE(LOG_TAG, "Failed to allocate memory"); - return apRecords; - } - - errRc = ::esp_wifi_scan_get_ap_records(&apCount, list); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_scan_get_ap_records: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - for (auto i=0; i rhs.m_rssi;}); - return apRecords; -} // scan - - -/** - * @brief Start being an access point. - * - * @param[in] ssid The SSID to use to advertize for stations. - * @param[in] password The password to use for station connections. - * @param[in] auth The authorization mode for access to this access point. Options are: - * * WIFI_AUTH_OPEN - * * WIFI_AUTH_WPA_PSK - * * WIFI_AUTH_WPA2_PSK - * * WIFI_AUTH_WPA_WPA2_PSK - * * WIFI_AUTH_WPA2_ENTERPRISE - * * WIFI_AUTH_WEP - * @return N/A. - */ -void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth) { - startAP(ssid, password, auth, 0, false, 4); -} // startAP - -/** - * @brief Start being an access point. - * - * @param[in] ssid The SSID to use to advertize for stations. - * @param[in] password The password to use for station connections. - * @param[in] auth The authorization mode for access to this access point. Options are: - * * WIFI_AUTH_OPEN - * * WIFI_AUTH_WPA_PSK - * * WIFI_AUTH_WPA2_PSK - * * WIFI_AUTH_WPA_WPA2_PSK - * * WIFI_AUTH_WPA2_ENTERPRISE - * * WIFI_AUTH_WEP - * @param[in] channel from the access point. - * @param[in] is the ssid hidden, ore not. - * @param[in] limiting number of clients. - * @return N/A. - */ -void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection) { - ESP_LOGD(LOG_TAG, ">> startAP: ssid: %s", ssid.c_str()); - - init(); - - esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_AP); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - // Build the apConfig structure. - wifi_config_t apConfig; - ::memset(&apConfig, 0, sizeof(apConfig)); - ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); - apConfig.ap.ssid_len = ssid.size(); - ::memcpy(apConfig.ap.password, password.data(), password.size()); - apConfig.ap.channel = channel; - apConfig.ap.authmode = auth; - apConfig.ap.ssid_hidden = (uint8_t) ssid_hidden; - apConfig.ap.max_connection = max_connection; - apConfig.ap.beacon_interval = 100; - - errRc = ::esp_wifi_set_config(WIFI_IF_AP, &apConfig); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - errRc = ::esp_wifi_start(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - errRc = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "tcpip_adapter_dhcps_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - - ESP_LOGD(LOG_TAG, "<< startAP"); -} // startAP - -/** - * @brief Set the event handler to use to process detected events. - * @param[in] wifiEventHandler The class that will be used to process events. - */ -void WiFi::setWifiEventHandler(WiFiEventHandler* wifiEventHandler) { - ESP_LOGD(LOG_TAG, ">> setWifiEventHandler: 0x%d", (uint32_t)wifiEventHandler); - this->m_pWifiEventHandler = wifiEventHandler; - ESP_LOGD(LOG_TAG, "<< setWifiEventHandler"); -} // setWifiEventHandler - - -/** - * @brief Set the IP info and enable DHCP if ip != 0. If called with ip == 0 then DHCP is enabled. - * If called with bad values it will do nothing. - * - * Do not call this method if we are being an access point ourselves. - * - * For example, prior to calling `connectAP()` we could invoke: - * - * @code{.cpp} - * myWifi.setIPInfo("192.168.1.99", "192.168.1.1", "255.255.255.0"); - * @endcode - * - * @param [in] ip IP address value. - * @param [in] gw Gateway value. - * @param [in] netmask Netmask value. - * @return N/A. - */ -void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask) { - setIPInfo(ip.c_str(), gw.c_str(), netmask.c_str()); -} // setIPInfo - - - -void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { - uint32_t new_ip; - uint32_t new_gw; - uint32_t new_netmask; - - auto success = (bool)inet_pton(AF_INET, ip, &new_ip); - success = success && inet_pton(AF_INET, gw, &new_gw); - success = success && inet_pton(AF_INET, netmask, &new_netmask); - - if(!success) { - return; - } - - setIPInfo(new_ip, new_gw, new_netmask); -} // setIPInfo - - -/** - * @brief Set the IP Info based on the IP address, gateway and netmask. - * @param [in] ip The IP address of our ESP32. - * @param [in] gw The gateway we should use. - * @param [in] netmask Our TCP/IP netmask value. - */ -void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { - init(); - - this->ip = ip; - this->gw = gw; - this->netmask = netmask; - - if(ip != 0 && gw != 0 && netmask != 0) { - tcpip_adapter_ip_info_t ipInfo; - ipInfo.ip.addr = ip; - ipInfo.gw.addr = gw; - ipInfo.netmask.addr = netmask; - ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - } else { - ip = 0; - ::tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); - } -} // setIPInfo - - -/** - * @brief Return a string representation of the WiFi access point record. - * - * @return A string representation of the WiFi access point record. - */ -std::string WiFiAPRecord::toString() { - std::string auth; - switch(getAuthMode()) { - case WIFI_AUTH_OPEN: - auth = "WIFI_AUTH_OPEN"; - break; - case WIFI_AUTH_WEP: - auth = "WIFI_AUTH_WEP"; - break; - case WIFI_AUTH_WPA_PSK: - auth = "WIFI_AUTH_WPA_PSK"; - break; - case WIFI_AUTH_WPA2_PSK: - auth = "WIFI_AUTH_WPA2_PSK"; - break; - case WIFI_AUTH_WPA_WPA2_PSK: - auth = "WIFI_AUTH_WPA_WPA2_PSK"; - break; - default: - auth = ""; - break; - } -// std::stringstream s; -// s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; - auto info_str = (char*) malloc(6 + 32 + 8 + 22 + 8 + 3 + 1); - sprintf(info_str, "ssid: %s, auth: %s, rssi: %d", m_ssid.c_str(), auth.c_str(), (int) m_rssi); - return std::string(std::move(info_str)); -} // toString - -/* -MDNS::MDNS() { - esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} - -MDNS::~MDNS() { - if (m_mdns_server != nullptr) { - mdns_free(m_mdns_server); - } - m_mdns_server = nullptr; -} -*/ - -/** - * @brief Define the service for mDNS. - * - * @param [in] service - * @param [in] proto - * @param [in] port - * @return N/A. - */ -/* -void MDNS::serviceAdd(const std::string& service, const std::string& proto, uint16_t port) { - serviceAdd(service.c_str(), proto.c_str(), port); -} // serviceAdd - - -void MDNS::serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance) { - serviceInstanceSet(service.c_str(), proto.c_str(), instance.c_str()); -} // serviceInstanceSet - - -void MDNS::servicePortSet(const std::string& service, const std::string& proto, uint16_t port) { - servicePortSet(service.c_str(), proto.c_str(), port); -} // servicePortSet - - -void MDNS::serviceRemove(const std::string& service, const std::string& proto) { - serviceRemove(service.c_str(), proto.c_str()); -} // serviceRemove -*/ - -/** - * @brief Set the mDNS hostname. - * - * @param [in] hostname The host name to set against the mDNS. - * @return N/A. - */ -/* -void MDNS::setHostname(const std::string& hostname) { - setHostname(hostname.c_str()); -} // setHostname -*/ - -/** - * @brief Set the mDNS instance. - * - * @param [in] instance The instance name to set against the mDNS. - * @return N/A. - */ -/* -void MDNS::setInstance(const std::string& instance) { - setInstance(instance.c_str()); -} // setInstance -*/ - -/** - * @brief Define the service for mDNS. - * - * @param [in] service - * @param [in] proto - * @param [in] port - * @return N/A. - */ -/* -void MDNS::serviceAdd(const char* service, const char* proto, uint16_t port) { - esp_err_t errRc = ::mdns_service_add(m_mdns_server, service, proto, port); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_service_add: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // serviceAdd - - -void MDNS::serviceInstanceSet(const char* service, const char* proto, const char* instance) { - esp_err_t errRc = ::mdns_service_instance_set(m_mdns_server, service, proto, instance); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_service_instance_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // serviceInstanceSet - - -void MDNS::servicePortSet(const char* service, const char* proto, uint16_t port) { - esp_err_t errRc = ::mdns_service_port_set(m_mdns_server, service, proto, port); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_service_port_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // servicePortSet - - -void MDNS::serviceRemove(const char* service, const char* proto) { - esp_err_t errRc = ::mdns_service_remove(m_mdns_server, service, proto); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_service_remove: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // serviceRemove - -*/ -/** - * @brief Set the mDNS hostname. - * - * @param [in] hostname The host name to set against the mDNS. - * @return N/A. - */ -/* -void MDNS::setHostname(const char* hostname) { - esp_err_t errRc = ::mdns_set_hostname(m_mdns_server,hostname); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_set_hostname: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // setHostname -*/ - -/** - * @brief Set the mDNS instance. - * - * @param [in] instance The instance name to set against the mDNS. - * @return N/A. - */ -/* -void MDNS::setInstance(const char* instance) { - esp_err_t errRc = ::mdns_set_instance(m_mdns_server, instance); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_set_instance: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // setInstance -*/ +/* + * WiFi.cpp + * + * Created on: Feb 25, 2017 + * Author: kolban + */ + +//#define _GLIBCXX_USE_C99 +#include +#include +#include +#include +#include "sdkconfig.h" + + +#include "WiFi.h" +#include +#include +#include +#include +#include +#include "GeneralUtils.h" +#include +#include +#include +#include +#include + +#include + + +static const char* LOG_TAG = "WiFi"; + + +/* +static void setDNSServer(char *ip) { + ip_addr_t dnsserver; + ESP_LOGD(tag, "Setting DNS[%d] to %s", 0, ip); + inet_pton(AF_INET, ip, &dnsserver); + ESP_LOGD(tag, "ip of DNS is %.8x", *(uint32_t *)&dnsserver); + dns_setserver(0, &dnsserver); +} +*/ + + +/** + * @brief Creates and uses a default event handler + */ +WiFi::WiFi() + : ip(0) + , gw(0) + , netmask(0) + , m_pWifiEventHandler(nullptr) +{ + m_eventLoopStarted = false; + m_initCalled = false; + //m_pWifiEventHandler = new WiFiEventHandler(); + m_apConnectionStatus = UINT8_MAX; // Are we connected to an access point? +} // WiFi + + +/** + * @brief Deletes the event handler that was used by the class + */ +WiFi::~WiFi() { + if (m_pWifiEventHandler != nullptr) { + delete m_pWifiEventHandler; + m_pWifiEventHandler = nullptr; + } +} // ~WiFi + + +/** + * @brief Add a reference to a DNS server. + * + * Here we define a server that will act as a DNS server. We can add two DNS + * servers in total. The first will be the primary, the second will be the backup. + * The public Google DNS servers are "8.8.8.8" and "8.8.4.4". + * + * For example: + * + * @code{.cpp} + * wifi.addDNSServer("8.8.8.8"); + * wifi.addDNSServer("8.8.4.4"); + * @endcode + * + * @param [in] ip The IP address of the DNS Server. + * @return N/A. + */ +void WiFi::addDNSServer(const std::string& ip) { + addDNSServer(ip.c_str()); +} // addDNSServer + + +void WiFi::addDNSServer(const char* ip) { + ip_addr_t dns_server; + if(inet_pton(AF_INET, ip, &dns_server)) { + addDNSServer(ip); + } +} // addDNSServer + + +void WiFi::addDNSServer(ip_addr_t ip) { + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + init(); + ::dns_setserver(m_dnsCount, &ip); + m_dnsCount++; + m_dnsCount %= 2; +} // addDNSServer + + +/** + * @brief Set a reference to a DNS server. + * + * Here we define a server that will act as a DNS server. We use numdns to specify which DNS server to set + * + * For example: + * + * @code{.cpp} + * wifi.setDNSServer(0, "8.8.8.8"); + * wifi.setDNSServer(1, "8.8.4.4"); + * @endcode + * + * @param [in] numdns The DNS number we wish to set + * @param [in] ip The IP address of the DNS Server. + * @return N/A. + */ +void WiFi::setDNSServer(int numdns, const std::string& ip) { + setDNSServer(numdns, ip.c_str()); +} // setDNSServer + + +void WiFi::setDNSServer(int numdns, const char* ip) { + ip_addr_t dns_server; + if(inet_pton(AF_INET, ip, &dns_server)) { + setDNSServer(numdns, dns_server); + } +} // setDNSServer + + +void WiFi::setDNSServer(int numdns, ip_addr_t ip) { + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + init(); + ::dns_setserver(numdns, &ip); +} // setDNSServer + + +/** + * @brief Connect to an external access point. + * + * The event handler will be called back with the outcome of the connection. + * + * @param [in] ssid The network SSID of the access point to which we wish to connect. + * @param [in] password The password of the access point to which we wish to connect. + * @param [in] waitForConnection Block until the connection has an outcome. + * @param [in] mode WIFI_MODE_AP for normal or WIFI_MODE_APSTA if you want to keep an Access Point running while you connect + * @return ESP_OK if we are now connected and wifi_err_reason_t if not. + */ +uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection, wifi_mode_t mode){ + ESP_LOGD(LOG_TAG, ">> connectAP"); + + m_apConnectionStatus = UINT8_MAX; + init(); + + if (ip != 0 && gw != 0 && netmask != 0) { + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client + + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; + + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + } + + esp_err_t errRc = ::esp_wifi_set_mode(mode); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + wifi_config_t sta_config; + ::memset(&sta_config, 0, sizeof(sta_config)); + ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); + ::memcpy(sta_config.sta.password, password.data(), password.size()); + sta_config.sta.bssid_set = 0; + errRc = ::esp_wifi_set_config(WIFI_IF_STA, &sta_config); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + m_connectFinished.take("connectAP"); // Take the semaphore to wait for a connection. + do { + ESP_LOGD(LOG_TAG, "esp_wifi_connect"); + errRc = ::esp_wifi_connect(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + } + while (!m_connectFinished.take(5000, "connectAP")); // retry if not connected within 5s + m_connectFinished.give(); + + ESP_LOGD(LOG_TAG, "<< connectAP"); + return m_apConnectionStatus; // Return ESP_OK if we are now connected and wifi_err_reason_t if not. +} // connectAP + + + +/** + * @brief Dump diagnostics to the log. + */ +void WiFi::dump() { + ESP_LOGD(LOG_TAG, "WiFi Dump"); + ESP_LOGD(LOG_TAG, "---------"); + char ipAddrStr[30]; + ip_addr_t ip = ::dns_getserver(0); + inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); + ESP_LOGD(LOG_TAG, "DNS Server[0]: %s", ipAddrStr); +} // dump + + +/** + * @brief Returns whether wifi is connected to an access point + */ +bool WiFi::isConnectedToAP() { + return m_apConnectionStatus; +} // isConnected + + + +/** + * @brief Primary event handler interface. + */ +/* STATIC */ esp_err_t WiFi::eventHandler(void* ctx, system_event_t* event) { + // This is the common event handler that we have provided for all event processing. It is called for every event + // that is received by the WiFi subsystem. The "ctx" parameter is an instance of the current WiFi object that we are + // processing. We can then retrieve the specific/custom event handler from within it and invoke that. This then makes this + // an indirection vector to the real caller. + + WiFi *pWiFi = (WiFi *)ctx; // retrieve the WiFi object from the passed in context. + + // Invoke the event handler. + esp_err_t rc; + if (pWiFi->m_pWifiEventHandler != nullptr) { + rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); + } else { + rc = ESP_OK; + } + + // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that + // indicates we are waiting for a connection complete. + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP || event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { + + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { // If we connected and have an IP, change the status to ESP_OK. Otherwise, change it to the reason code. + pWiFi->m_apConnectionStatus = ESP_OK; + } else { + pWiFi->m_apConnectionStatus = event->event_info.disconnected.reason; + } + pWiFi->m_connectFinished.give(); + } + + return rc; +} // eventHandler + + +/** + * @brief Get the AP IP Info. + * @return The AP IP Info. + */ +tcpip_adapter_ip_info_t WiFi::getApIpInfo() { + //init(); + tcpip_adapter_ip_info_t ipInfo; + ::tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); + return ipInfo; +} // getApIpInfo + + +/** + * @brief Get the MAC address of the AP interface. + * @return The MAC address of the AP interface. + */ +std::string WiFi::getApMac() { + uint8_t mac[6]; + //init(); + esp_wifi_get_mac(WIFI_IF_AP, mac); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); +} // getApMac + + +/** + * @brief Get the AP SSID. + * @return The AP SSID. + */ +std::string WiFi::getApSSID() { + wifi_config_t conf; + //init(); + esp_wifi_get_config(WIFI_IF_AP, &conf); + return std::string((char *)conf.sta.ssid); +} // getApSSID + +/** + * @brief Get the current ESP32 IP form AP. + * @return The ESP32 IP. + */ +std::string WiFi::getApIp(){ + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaIp + +/** + * @brief Get the current AP netmask. + * @return The Netmask IP. + */ +std::string WiFi::getApNetmask(){ + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaNetmask + +/** + * @brief Get the current AP Gateway IP. + * @return The Gateway IP. + */ +std::string WiFi::getApGateway(){ + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaGateway + +/** + * @brief Lookup an IP address by host name. + * + * @param [in] hostName The hostname to resolve. + * + * @return The IP address of the host or 0.0.0.0 if not found. + */ +struct in_addr WiFi::getHostByName(const std::string& hostName) { + return getHostByName(hostName.c_str()); +} // getHostByName + + +struct in_addr WiFi::getHostByName(const char* hostName) { + struct in_addr retAddr; + struct hostent *he = gethostbyname(hostName); + if (he == nullptr) { + retAddr.s_addr = 0; + ESP_LOGD(LOG_TAG, "Unable to resolve %s - %d", hostName, h_errno); + } else { + retAddr = *(struct in_addr *)(he->h_addr_list[0]); + ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); + } + return retAddr; +} // getHostByName + + +/** + * @brief Get the WiFi Mode. + * @return The WiFi Mode. + */ +std::string WiFi::getMode() { + wifi_mode_t mode; + esp_wifi_get_mode(&mode); + switch(mode) { + case WIFI_MODE_NULL: + return "WIFI_MODE_NULL"; + case WIFI_MODE_STA: + return "WIFI_MODE_STA"; + case WIFI_MODE_AP: + return "WIFI_MODE_AP"; + case WIFI_MODE_APSTA: + return "WIFI_MODE_APSTA"; + default: + return "unknown"; + } +} // getMode + + +/** + * @brief Get the STA IP Info. + * @return The STA IP Info. + */ +tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { + tcpip_adapter_ip_info_t ipInfo; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + return ipInfo; +} // getStaIpInfo + +/** + * @brief Get the current ESP32 IP form STA. + * @return The ESP32 IP. + */ +std::string WiFi::getStaIp(){ + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaIp + + +/** + * @brief Get the current STA netmask. + * @return The Netmask IP. + */ +std::string WiFi::getStaNetmask(){ + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaNetmask + + +/** + * @brief Get the current STA Gateway IP. + * @return The Gateway IP. + */ +std::string WiFi::getStaGateway(){ + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaGateway + +/** + * @brief Get the MAC address of the STA interface. + * @return The MAC address of the STA interface. + */ +std::string WiFi::getStaMac() { + uint8_t mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, mac); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); +} // getStaMac + + +/** + * @brief Get the STA SSID. + * @return The STA SSID. + */ +std::string WiFi::getStaSSID() { + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_STA, &conf); + return std::string((char *)conf.ap.ssid); +} // getStaSSID + + +/** + * @brief Initialize WiFi. + */ +/* PRIVATE */ void WiFi::init() { + + // If we have already started the event loop, then change the handler otherwise + // start the event loop. + if (m_eventLoopStarted) { + esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. + } else { + esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + m_eventLoopStarted = true; + } + // Now, one way or another, the event handler is WiFi::eventHandler. + + if (!m_initCalled) { + ::nvs_flash_init(); + ::tcpip_adapter_init(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t errRc = ::esp_wifi_init(&cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + } + m_initCalled = true; +} // init + + +/** + * @brief Perform a WiFi scan looking for access points. + * + * An access point scan is performed and a vector of WiFi access point records + * is built and returned with one record per found scan instance. The scan is + * performed in a blocking fashion and will not return until the set of scanned + * access points has been built. + * + * @return A vector of WiFiAPRecord instances. + */ +std::vector WiFi::scan() { + ESP_LOGD(LOG_TAG, ">> scan"); + std::vector apRecords; + + init(); + + esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + wifi_scan_config_t conf; + memset(&conf, 0, sizeof(conf)); + conf.show_hidden = true; + + esp_err_t rc = ::esp_wifi_scan_start(&conf, true); + if (rc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); + return apRecords; + } + + uint16_t apCount; // Number of access points available. + rc = ::esp_wifi_scan_get_ap_num(&apCount); + ESP_LOGD(LOG_TAG, "Count of found access points: %d", apCount); + + wifi_ap_record_t* list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); + if (list == nullptr) { + ESP_LOGE(LOG_TAG, "Failed to allocate memory"); + return apRecords; + } + + errRc = ::esp_wifi_scan_get_ap_records(&apCount, list); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_scan_get_ap_records: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + for (auto i=0; i rhs.m_rssi;}); + return apRecords; +} // scan + + +/** + * @brief Start being an access point. + * + * @param[in] ssid The SSID to use to advertize for stations. + * @param[in] password The password to use for station connections. + * @param[in] auth The authorization mode for access to this access point. Options are: + * * WIFI_AUTH_OPEN + * * WIFI_AUTH_WPA_PSK + * * WIFI_AUTH_WPA2_PSK + * * WIFI_AUTH_WPA_WPA2_PSK + * * WIFI_AUTH_WPA2_ENTERPRISE + * * WIFI_AUTH_WEP + * @return N/A. + */ +void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth) { + startAP(ssid, password, auth, 0, false, 4); +} // startAP + +/** + * @brief Start being an access point. + * + * @param[in] ssid The SSID to use to advertize for stations. + * @param[in] password The password to use for station connections. + * @param[in] auth The authorization mode for access to this access point. Options are: + * * WIFI_AUTH_OPEN + * * WIFI_AUTH_WPA_PSK + * * WIFI_AUTH_WPA2_PSK + * * WIFI_AUTH_WPA_WPA2_PSK + * * WIFI_AUTH_WPA2_ENTERPRISE + * * WIFI_AUTH_WEP + * @param[in] channel from the access point. + * @param[in] is the ssid hidden, ore not. + * @param[in] limiting number of clients. + * @return N/A. + */ +void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection) { + ESP_LOGD(LOG_TAG, ">> startAP: ssid: %s", ssid.c_str()); + + init(); + + esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_AP); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + // Build the apConfig structure. + wifi_config_t apConfig; + ::memset(&apConfig, 0, sizeof(apConfig)); + ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); + apConfig.ap.ssid_len = ssid.size(); + ::memcpy(apConfig.ap.password, password.data(), password.size()); + apConfig.ap.channel = channel; + apConfig.ap.authmode = auth; + apConfig.ap.ssid_hidden = (uint8_t) ssid_hidden; + apConfig.ap.max_connection = max_connection; + apConfig.ap.beacon_interval = 100; + + errRc = ::esp_wifi_set_config(WIFI_IF_AP, &apConfig); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "tcpip_adapter_dhcps_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + + ESP_LOGD(LOG_TAG, "<< startAP"); +} // startAP + +/** + * @brief Set the event handler to use to process detected events. + * @param[in] wifiEventHandler The class that will be used to process events. + */ +void WiFi::setWifiEventHandler(WiFiEventHandler* wifiEventHandler) { + ESP_LOGD(LOG_TAG, ">> setWifiEventHandler: 0x%d", (uint32_t)wifiEventHandler); + this->m_pWifiEventHandler = wifiEventHandler; + ESP_LOGD(LOG_TAG, "<< setWifiEventHandler"); +} // setWifiEventHandler + + +/** + * @brief Set the IP info and enable DHCP if ip != 0. If called with ip == 0 then DHCP is enabled. + * If called with bad values it will do nothing. + * + * Do not call this method if we are being an access point ourselves. + * + * For example, prior to calling `connectAP()` we could invoke: + * + * @code{.cpp} + * myWifi.setIPInfo("192.168.1.99", "192.168.1.1", "255.255.255.0"); + * @endcode + * + * @param [in] ip IP address value. + * @param [in] gw Gateway value. + * @param [in] netmask Netmask value. + * @return N/A. + */ +void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask) { + setIPInfo(ip.c_str(), gw.c_str(), netmask.c_str()); +} // setIPInfo + + + +void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { + uint32_t new_ip; + uint32_t new_gw; + uint32_t new_netmask; + + auto success = (bool)inet_pton(AF_INET, ip, &new_ip); + success = success && inet_pton(AF_INET, gw, &new_gw); + success = success && inet_pton(AF_INET, netmask, &new_netmask); + + if(!success) { + return; + } + + setIPInfo(new_ip, new_gw, new_netmask); +} // setIPInfo + + +/** + * @brief Set the IP Info based on the IP address, gateway and netmask. + * @param [in] ip The IP address of our ESP32. + * @param [in] gw The gateway we should use. + * @param [in] netmask Our TCP/IP netmask value. + */ +void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { + init(); + + this->ip = ip; + this->gw = gw; + this->netmask = netmask; + + if(ip != 0 && gw != 0 && netmask != 0) { + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + } else { + ip = 0; + ::tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + } +} // setIPInfo + + +/** + * @brief Return a string representation of the WiFi access point record. + * + * @return A string representation of the WiFi access point record. + */ +std::string WiFiAPRecord::toString() { + std::string auth; + switch(getAuthMode()) { + case WIFI_AUTH_OPEN: + auth = "WIFI_AUTH_OPEN"; + break; + case WIFI_AUTH_WEP: + auth = "WIFI_AUTH_WEP"; + break; + case WIFI_AUTH_WPA_PSK: + auth = "WIFI_AUTH_WPA_PSK"; + break; + case WIFI_AUTH_WPA2_PSK: + auth = "WIFI_AUTH_WPA2_PSK"; + break; + case WIFI_AUTH_WPA_WPA2_PSK: + auth = "WIFI_AUTH_WPA_WPA2_PSK"; + break; + default: + auth = ""; + break; + } +// std::stringstream s; +// s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; + auto info_str = (char*) malloc(6 + 32 + 8 + 22 + 8 + 3 + 1); + sprintf(info_str, "ssid: %s, auth: %s, rssi: %d", m_ssid.c_str(), auth.c_str(), (int) m_rssi); + return std::string(std::move(info_str)); +} // toString + +/* +MDNS::MDNS() { + esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} + +MDNS::~MDNS() { + if (m_mdns_server != nullptr) { + mdns_free(m_mdns_server); + } + m_mdns_server = nullptr; +} +*/ + +/** + * @brief Define the service for mDNS. + * + * @param [in] service + * @param [in] proto + * @param [in] port + * @return N/A. + */ +/* +void MDNS::serviceAdd(const std::string& service, const std::string& proto, uint16_t port) { + serviceAdd(service.c_str(), proto.c_str(), port); +} // serviceAdd + + +void MDNS::serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance) { + serviceInstanceSet(service.c_str(), proto.c_str(), instance.c_str()); +} // serviceInstanceSet + + +void MDNS::servicePortSet(const std::string& service, const std::string& proto, uint16_t port) { + servicePortSet(service.c_str(), proto.c_str(), port); +} // servicePortSet + + +void MDNS::serviceRemove(const std::string& service, const std::string& proto) { + serviceRemove(service.c_str(), proto.c_str()); +} // serviceRemove +*/ + +/** + * @brief Set the mDNS hostname. + * + * @param [in] hostname The host name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setHostname(const std::string& hostname) { + setHostname(hostname.c_str()); +} // setHostname +*/ + +/** + * @brief Set the mDNS instance. + * + * @param [in] instance The instance name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setInstance(const std::string& instance) { + setInstance(instance.c_str()); +} // setInstance +*/ + +/** + * @brief Define the service for mDNS. + * + * @param [in] service + * @param [in] proto + * @param [in] port + * @return N/A. + */ +/* +void MDNS::serviceAdd(const char* service, const char* proto, uint16_t port) { + esp_err_t errRc = ::mdns_service_add(m_mdns_server, service, proto, port); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_add: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // serviceAdd + + +void MDNS::serviceInstanceSet(const char* service, const char* proto, const char* instance) { + esp_err_t errRc = ::mdns_service_instance_set(m_mdns_server, service, proto, instance); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_instance_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // serviceInstanceSet + + +void MDNS::servicePortSet(const char* service, const char* proto, uint16_t port) { + esp_err_t errRc = ::mdns_service_port_set(m_mdns_server, service, proto, port); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_port_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // servicePortSet + + +void MDNS::serviceRemove(const char* service, const char* proto) { + esp_err_t errRc = ::mdns_service_remove(m_mdns_server, service, proto); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_remove: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // serviceRemove + +*/ +/** + * @brief Set the mDNS hostname. + * + * @param [in] hostname The host name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setHostname(const char* hostname) { + esp_err_t errRc = ::mdns_set_hostname(m_mdns_server,hostname); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_hostname: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // setHostname +*/ + +/** + * @brief Set the mDNS instance. + * + * @param [in] instance The instance name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setInstance(const char* instance) { + esp_err_t errRc = ::mdns_set_instance(m_mdns_server, instance); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_instance: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // setInstance +*/ diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index c1b6bedc..a4d5d4d5 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -1,158 +1,158 @@ -/* - * WiFi.h - * - * Created on: Feb 25, 2017 - * Author: kolban - */ - -#ifndef MAIN_WIFI_H_ -#define MAIN_WIFI_H_ -#include "sdkconfig.h" - -#include -#include -#include -#include -#include "FreeRTOS.h" -#include "WiFiEventHandler.h" - -/** - * @brief Manage mDNS server. - */ -/* -class MDNS { -public: - MDNS(); - ~MDNS(); - void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); - void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); - void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); - void serviceRemove(const std::string& service, const std::string& proto); - void setHostname(const std::string& hostname); - void setInstance(const std::string& instance); - // If we the above functions with a basic char*, a copy would be created into an std::string, - // making the whole thing require twice as much processing power and speed - void serviceAdd(const char* service, const char* proto, uint16_t port); - void serviceInstanceSet(const char* service, const char* proto, const char* instance); - void servicePortSet(const char* service, const char* proto, uint16_t port); - void serviceRemove(const char* service, const char* proto); - void setHostname(const char* hostname); - void setInstance(const char* instance); -private: - mdns_server_t *m_mdns_server = nullptr; -}; -*/ - -class WiFiAPRecord { -public: - friend class WiFi; - - /** - * @brief Get the auth mode. - * @return The auth mode. - */ - wifi_auth_mode_t getAuthMode() { - return m_authMode; - } - - /** - * @brief Get the RSSI. - * @return the RSSI. - */ - int8_t getRSSI() { - return m_rssi; - } - - /** - * @brief Get the SSID. - * @return the SSID. - */ - std::string getSSID() { - return m_ssid; - } - - std::string toString(); - -private: - uint8_t m_bssid[6]; - int8_t m_rssi; - std::string m_ssid; - wifi_auth_mode_t m_authMode; -}; - -/** - * @brief %WiFi driver. - * - * Encapsulate control of %WiFi functions. - * - * Here is an example fragment that illustrates connecting to an access point. - * @code{.cpp} - * #include - * #include - * - * class TargetWiFiEventHandler: public WiFiEventHandler { - * esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { - * ESP_LOGD(tag, "MyWiFiEventHandler(Class): staGotIp"); - * // Do something ... - * return ESP_OK; - * } - * }; - * - * WiFi wifi; - * - * TargetWiFiEventHandler *eventHandler = new TargetWiFiEventHandler(); - * wifi.setWifiEventHandler(eventHandler); - * wifi.connectAP("myssid", "mypassword"); - * @endcode - */ -class WiFi { -private: - static esp_err_t eventHandler(void* ctx, system_event_t* event); - void init(); - uint32_t ip; - uint32_t gw; - uint32_t netmask; - WiFiEventHandler* m_pWifiEventHandler; - uint8_t m_dnsCount=0; - bool m_eventLoopStarted; - bool m_initCalled; - uint8_t m_apConnectionStatus; // ESP_OK = we are connected to an access point. Otherwise receives wifi_err_reason_t. - FreeRTOS::Semaphore m_connectFinished = FreeRTOS::Semaphore("ConnectFinished"); - -public: - WiFi(); - ~WiFi(); - void addDNSServer(const std::string& ip); - void addDNSServer(const char* ip); - void addDNSServer(ip_addr_t ip); - void setDNSServer(int numdns, const std::string& ip); - void setDNSServer(int numdns, const char* ip); - void setDNSServer(int numdns, ip_addr_t ip); - struct in_addr getHostByName(const std::string& hostName); - struct in_addr getHostByName(const char* hostName); - uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true, wifi_mode_t mode=WIFI_MODE_STA); - void dump(); - bool isConnectedToAP(); - static std::string getApMac(); - static tcpip_adapter_ip_info_t getApIpInfo(); - static std::string getApSSID(); - static std::string getApIp(); - static std::string getApNetmask(); - static std::string getApGateway(); - static std::string getMode(); - static tcpip_adapter_ip_info_t getStaIpInfo(); - static std::string getStaMac(); - static std::string getStaSSID(); - static std::string getStaIp(); - static std::string getStaNetmask(); - static std::string getStaGateway(); - std::vector scan(); - void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth = WIFI_AUTH_OPEN); - void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection); - void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); - void setIPInfo(const char* ip, const char* gw, const char* netmask); - void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); - void setWifiEventHandler(WiFiEventHandler *wifiEventHandler); -}; - -#endif /* MAIN_WIFI_H_ */ +/* + * WiFi.h + * + * Created on: Feb 25, 2017 + * Author: kolban + */ + +#ifndef MAIN_WIFI_H_ +#define MAIN_WIFI_H_ +#include "sdkconfig.h" + +#include +#include +#include +#include +#include "FreeRTOS.h" +#include "WiFiEventHandler.h" + +/** + * @brief Manage mDNS server. + */ +/* +class MDNS { +public: + MDNS(); + ~MDNS(); + void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); + void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); + void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); + void serviceRemove(const std::string& service, const std::string& proto); + void setHostname(const std::string& hostname); + void setInstance(const std::string& instance); + // If we the above functions with a basic char*, a copy would be created into an std::string, + // making the whole thing require twice as much processing power and speed + void serviceAdd(const char* service, const char* proto, uint16_t port); + void serviceInstanceSet(const char* service, const char* proto, const char* instance); + void servicePortSet(const char* service, const char* proto, uint16_t port); + void serviceRemove(const char* service, const char* proto); + void setHostname(const char* hostname); + void setInstance(const char* instance); +private: + mdns_server_t *m_mdns_server = nullptr; +}; +*/ + +class WiFiAPRecord { +public: + friend class WiFi; + + /** + * @brief Get the auth mode. + * @return The auth mode. + */ + wifi_auth_mode_t getAuthMode() { + return m_authMode; + } + + /** + * @brief Get the RSSI. + * @return the RSSI. + */ + int8_t getRSSI() { + return m_rssi; + } + + /** + * @brief Get the SSID. + * @return the SSID. + */ + std::string getSSID() { + return m_ssid; + } + + std::string toString(); + +private: + uint8_t m_bssid[6]; + int8_t m_rssi; + std::string m_ssid; + wifi_auth_mode_t m_authMode; +}; + +/** + * @brief %WiFi driver. + * + * Encapsulate control of %WiFi functions. + * + * Here is an example fragment that illustrates connecting to an access point. + * @code{.cpp} + * #include + * #include + * + * class TargetWiFiEventHandler: public WiFiEventHandler { + * esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { + * ESP_LOGD(tag, "MyWiFiEventHandler(Class): staGotIp"); + * // Do something ... + * return ESP_OK; + * } + * }; + * + * WiFi wifi; + * + * TargetWiFiEventHandler *eventHandler = new TargetWiFiEventHandler(); + * wifi.setWifiEventHandler(eventHandler); + * wifi.connectAP("myssid", "mypassword"); + * @endcode + */ +class WiFi { +private: + static esp_err_t eventHandler(void* ctx, system_event_t* event); + void init(); + uint32_t ip; + uint32_t gw; + uint32_t netmask; + WiFiEventHandler* m_pWifiEventHandler; + uint8_t m_dnsCount=0; + bool m_eventLoopStarted; + bool m_initCalled; + uint8_t m_apConnectionStatus; // ESP_OK = we are connected to an access point. Otherwise receives wifi_err_reason_t. + FreeRTOS::Semaphore m_connectFinished = FreeRTOS::Semaphore("ConnectFinished"); + +public: + WiFi(); + ~WiFi(); + void addDNSServer(const std::string& ip); + void addDNSServer(const char* ip); + void addDNSServer(ip_addr_t ip); + void setDNSServer(int numdns, const std::string& ip); + void setDNSServer(int numdns, const char* ip); + void setDNSServer(int numdns, ip_addr_t ip); + struct in_addr getHostByName(const std::string& hostName); + struct in_addr getHostByName(const char* hostName); + uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true, wifi_mode_t mode=WIFI_MODE_STA); + void dump(); + bool isConnectedToAP(); + static std::string getApMac(); + static tcpip_adapter_ip_info_t getApIpInfo(); + static std::string getApSSID(); + static std::string getApIp(); + static std::string getApNetmask(); + static std::string getApGateway(); + static std::string getMode(); + static tcpip_adapter_ip_info_t getStaIpInfo(); + static std::string getStaMac(); + static std::string getStaSSID(); + static std::string getStaIp(); + static std::string getStaNetmask(); + static std::string getStaGateway(); + std::vector scan(); + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth = WIFI_AUTH_OPEN); + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection); + void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); + void setIPInfo(const char* ip, const char* gw, const char* netmask); + void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); + void setWifiEventHandler(WiFiEventHandler *wifiEventHandler); +}; + +#endif /* MAIN_WIFI_H_ */ From fc0a957708fce0442da178c5509b653f0de3e2c8 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sat, 15 Sep 2018 16:47:09 -0600 Subject: [PATCH 315/381] I2C: fix ignored clockSpeed --- cpp_utils/I2C.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index 96618bc2..dfcfeff0 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -109,7 +109,7 @@ void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin, uint32_t c conf.scl_io_num = sclPin; conf.sda_pullup_en = GPIO_PULLUP_ENABLE; conf.scl_pullup_en = GPIO_PULLUP_ENABLE; - conf.master.clk_speed = 100000; + conf.master.clk_speed = clockSpeed; esp_err_t errRc = ::i2c_param_config(m_portNum, &conf); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "i2c_param_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -279,7 +279,7 @@ void I2C::write(uint8_t byte, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "write(val=0x%.2x, ack=%d)", byte, ack); } - if (m_directionKnown == false) { + if (!m_directionKnown) { m_directionKnown = true; esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack); if (errRc != ESP_OK) { From de865db4af61119cbbb0147205373ee582f65324 Mon Sep 17 00:00:00 2001 From: Aodzip Date: Sun, 23 Sep 2018 15:29:21 +0800 Subject: [PATCH 316/381] Fix a bug in U8G2 HAL SPI initialize if we don't set bus_config to all zero, a wrong bus_config.flag may have some random bug --- hardware/displays/U8G2/u8g2_esp32_hal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hardware/displays/U8G2/u8g2_esp32_hal.c b/hardware/displays/U8G2/u8g2_esp32_hal.c index fdab51a3..caaa78d9 100644 --- a/hardware/displays/U8G2/u8g2_esp32_hal.c +++ b/hardware/displays/U8G2/u8g2_esp32_hal.c @@ -47,6 +47,7 @@ uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void } spi_bus_config_t bus_config; + memset(&bus_config, 0, sizeof(spi_bus_config_t)); bus_config.sclk_io_num = u8g2_esp32_hal.clk; // CLK bus_config.mosi_io_num = u8g2_esp32_hal.mosi; // MOSI bus_config.miso_io_num = -1; // MISO From 83ca169ef0be97dbe87628878f0ab1b240a41ebd Mon Sep 17 00:00:00 2001 From: hetlelid Date: Wed, 26 Sep 2018 23:41:27 +0200 Subject: [PATCH 317/381] Update GeneralUtils.cpp Changed ESP_LOGD to ESP_LOGV to lower logging level --- cpp_utils/GeneralUtils.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index e26d84c9..2c0f0846 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -109,11 +109,11 @@ void GeneralUtils::dumpInfo() { size_t freeHeap = heap_caps_get_free_size(MALLOC_CAP_8BIT); esp_chip_info_t chipInfo; esp_chip_info(&chipInfo); - ESP_LOGD(LOG_TAG, "--- dumpInfo ---"); - ESP_LOGD(LOG_TAG, "Free heap: %d", freeHeap); - ESP_LOGD(LOG_TAG, "Chip Info: Model: %d, cores: %d, revision: %d", chipInfo.model, chipInfo.cores, chipInfo.revision); - ESP_LOGD(LOG_TAG, "ESP-IDF version: %s", esp_get_idf_version()); - ESP_LOGD(LOG_TAG, "---"); + ESP_LOGV(LOG_TAG, "--- dumpInfo ---"); + ESP_LOGV(LOG_TAG, "Free heap: %d", freeHeap); + ESP_LOGV(LOG_TAG, "Chip Info: Model: %d, cores: %d, revision: %d", chipInfo.model, chipInfo.cores, chipInfo.revision); + ESP_LOGV(LOG_TAG, "ESP-IDF version: %s", esp_get_idf_version()); + ESP_LOGV(LOG_TAG, "---"); } // dumpInfo @@ -231,7 +231,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { if (index % 16 == 0) { strcpy(hexBuf, hex.str().c_str()); strcpy(asciiBuf, ascii.str().c_str()); - ESP_LOGD(tag, "%s %s", hexBuf, asciiBuf); + ESP_LOGV(tag, "%s %s", hexBuf, asciiBuf); hex.str(""); ascii.str(""); } @@ -243,8 +243,8 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { } strcpy(hexBuf, hex.str().c_str()); strcpy(asciiBuf, ascii.str().c_str()); - ESP_LOGD(tag, "%s %s", hexBuf, asciiBuf); - //ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); + ESP_LOGV(tag, "%s %s", hexBuf, asciiBuf); + //ESP_LOGV(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); } FreeRTOS::sleep(1000); } @@ -266,7 +266,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { } index++; if (index % 16 == 0) { - ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); + ESP_LOGV(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); hex.str(""); ascii.str(""); } @@ -276,7 +276,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { hex << " "; index++; } - ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); + ESP_LOGV(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); } FreeRTOS::sleep(1000); } @@ -296,8 +296,8 @@ void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { char tempBuf[80]; uint32_t lineNumber = 0; - ESP_LOGD(LOG_TAG, " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"); - ESP_LOGD(LOG_TAG, " -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); + ESP_LOGV(LOG_TAG, " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"); + ESP_LOGV(LOG_TAG, " -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); strcpy(ascii, ""); strcpy(hex, ""); uint32_t index=0; @@ -312,7 +312,7 @@ void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { strcat(ascii, tempBuf); index++; if (index % 16 == 0) { - ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); + ESP_LOGV(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); strcpy(ascii, ""); strcpy(hex, ""); lineNumber++; @@ -323,7 +323,7 @@ void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { strcat(hex, " "); index++; } - ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); + ESP_LOGV(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); } } // hexDump From 5ce0c5025c76c6f79d53b98d28420d24b88e345f Mon Sep 17 00:00:00 2001 From: Mert Akengin Date: Thu, 27 Sep 2018 23:45:53 +0300 Subject: [PATCH 318/381] update BLECharacteristic.h to include `getData` call --- cpp_utils/BLECharacteristic.h | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index b3f8d2e9..e627628e 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -65,6 +65,7 @@ class BLECharacteristic { //size_t getLength(); BLEUUID getUUID(); std::string getValue(); + uint8_t* getData(); void indicate(); void notify(); From 7e64a01419f5e0b8deead6e4a8f11e621eb0845e Mon Sep 17 00:00:00 2001 From: Mert Akengin Date: Thu, 27 Sep 2018 23:48:06 +0300 Subject: [PATCH 319/381] update BLECharacteristic.cpp to proxy `getData` function --- cpp_utils/BLECharacteristic.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 931c753d..56a27ddb 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -198,6 +198,14 @@ std::string BLECharacteristic::getValue() { return m_value.getValue(); } // getValue +/** + * @brief Retrieve the current raw data of the characteristic. + * @return A pointer to storage containing the current characteristic data. + */ +uint8_t* BLECharacteristic::getData() { + return m_value.getData(); +} // getData + /** * Handle a GATT server event. From cc85e037cd617d3a050bba2b68307698f12e9eab Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 30 Sep 2018 20:08:00 -0600 Subject: [PATCH 320/381] Added switch case to fix compilation --- cpp_utils/BLEUtils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index a33ee27a..53f1a8f2 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -1058,6 +1058,8 @@ std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType case ESP_GATTS_SET_ATTR_VAL_EVT: return "ESP_GATTS_SET_ATTR_VAL_EVT"; + case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: + return "ESP_GATTS_SEND_SERVICE_CHANGE_EVT"; } return "Unknown"; } // gattServerEventTypeToString From bb251b7ec39cb0138633469404da2026fed57c18 Mon Sep 17 00:00:00 2001 From: chegewara Date: Mon, 1 Oct 2018 19:39:32 +0200 Subject: [PATCH 321/381] move m_advertising to BLEDevice --- cpp_utils/BLEDevice.cpp | 24 ++++++++++++++++-------- cpp_utils/BLEDevice.h | 8 ++++---- cpp_utils/BLEServer.cpp | 8 +++++--- cpp_utils/BLEServer.h | 2 +- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 369b1285..4da8965a 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -41,6 +41,7 @@ static const char* LOG_TAG = "BLEDevice"; BLEServer* BLEDevice::m_pServer = nullptr; BLEScan* BLEDevice::m_pScan = nullptr; BLEClient* BLEDevice::m_pClient = nullptr; +BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; bool initialized = false; // Have we been initialized? esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; @@ -50,14 +51,13 @@ uint16_t BLEDevice::m_localMTU = 23; * @brief Create a new instance of a client. * @return A new instance of the client. */ -/* STATIC */ BLEClient* BLEDevice::createClient(uint16_t connID) { +/* STATIC */ BLEClient* BLEDevice::createClient() { ESP_LOGD(LOG_TAG, ">> createClient"); #ifndef CONFIG_GATTC_ENABLE // Check that BLE GATTC is enabled in make menuconfig ESP_LOGE(LOG_TAG, "BLE GATTC is not enabled - CONFIG_GATTC_ENABLE not defined"); abort(); #endif // CONFIG_GATTC_ENABLE - m_pClient = new BLEClient(connID); - addClient(connID, m_pClient); + m_pClient = new BLEClient(); ESP_LOGD(LOG_TAG, "<< createClient"); return m_pClient; } // createClient @@ -547,12 +547,20 @@ bool BLEDevice::getInitialized() { return initialized; } -void BLEDevice::addClient(uint16_t connID, BLEClient* client) { - // m_clientList.insert(std::pair(connID, client)); +BLEAdvertising* BLEDevice::getAdvertising() { + if(m_bleAdvertising == nullptr) + { + m_bleAdvertising = new BLEAdvertising(); + ESP_LOGI(LOG_TAG, "create advertising"); + } + ESP_LOGD(LOG_TAG, "get advertising"); + return m_bleAdvertising; } -void BLEDevice::removeClient(uint16_t connID) { - m_clientList.erase(connID); -} +void BLEDevice::startAdvertising() { + ESP_LOGD(LOG_TAG, ">> startAdvertising"); + getAdvertising()->start(); + ESP_LOGD(LOG_TAG, "<< startAdvertising"); +} // startAdvertising #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index e4f4ff5a..a37ac960 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -26,8 +26,11 @@ */ class BLEDevice { public: + static BLEAdvertising* getAdvertising(); + static void startAdvertising(); + static BLEAdvertising *m_bleAdvertising; - static BLEClient* createClient(uint16_t connID = 0); // Create a new BLE client. + static BLEClient* createClient(); // Create a new BLE client. static BLEServer* createServer(); // Cretae a new BLE server. static BLEAddress getAddress(); // Retrieve our own local BD address. static BLEScan* getScan(); // Get the scan object @@ -43,8 +46,6 @@ class BLEDevice { static esp_err_t setMTU(uint16_t mtu); static uint16_t getMTU(); static bool getInitialized(); // Returns the state of the device, is it initialized or not? - static void addClient(uint16_t connID, BLEClient* client); - static void removeClient(uint16_t connID); private: static BLEServer *m_pServer; @@ -53,7 +54,6 @@ class BLEDevice { static esp_ble_sec_act_t m_securityLevel; static BLESecurityCallbacks* m_securityCallbacks; static uint16_t m_localMTU; - static std::map m_clientList; static esp_gatt_if_t getGattcIF(); diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 6a60f1db..56b1e0c0 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -120,7 +120,8 @@ BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { * @return An advertising object. */ BLEAdvertising* BLEServer::getAdvertising() { - return &m_bleAdvertising; + // return &m_bleAdvertising; + return BLEDevice::getAdvertising(); } uint16_t BLEServer::getConnId() { @@ -257,7 +258,7 @@ void BLEServer::handleGATTServerEvent( if (m_pServerCallbacks != nullptr) { // If we have callbacks, call now. m_pServerCallbacks->onDisconnect(this); } - startAdvertising(); //- do this with some delay from the loop() + // startAdvertising(); //- do this with some delay from the loop() break; } // ESP_GATTS_DISCONNECT_EVT @@ -359,7 +360,8 @@ void BLEServer::removeService(BLEService *service) { */ void BLEServer::startAdvertising() { ESP_LOGD(LOG_TAG, ">> startAdvertising"); - m_bleAdvertising.start(); + // m_bleAdvertising.start(); + BLEDevice::startAdvertising(); ESP_LOGD(LOG_TAG, "<< startAdvertising"); } // startAdvertising diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index a58b0e9b..7f40faef 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -73,7 +73,7 @@ class BLEServer { friend class BLEDevice; esp_ble_adv_data_t m_adv_data; uint16_t m_appId; - BLEAdvertising m_bleAdvertising; + // BLEAdvertising m_bleAdvertising; uint16_t m_connId; uint32_t m_connectedCount; uint16_t m_gatts_if; From b5ab56a60aea1881ebf5257fd4de09fb4f949c84 Mon Sep 17 00:00:00 2001 From: chegewara Date: Mon, 1 Oct 2018 21:44:32 +0200 Subject: [PATCH 322/381] Revert "Move m_advertising to BLEDevice" --- cpp_utils/BLEClient.cpp | 8 +- cpp_utils/BLEClient.h | 2 +- cpp_utils/BLEDevice.cpp | 1114 +++++++++--------- cpp_utils/BLEDevice.h | 3 - cpp_utils/BLERemoteDescriptor.cpp | 370 +++--- cpp_utils/BLEScan.cpp | 584 +++++----- cpp_utils/BLEScan.h | 158 +-- cpp_utils/BLESecurity.h | 142 +-- cpp_utils/BLEServer.cpp | 8 +- cpp_utils/BLEServer.h | 2 +- cpp_utils/BLEService.cpp | 870 +++++++------- cpp_utils/BLEService.h | 208 ++-- cpp_utils/BLEServiceMap.cpp | 264 ++--- cpp_utils/WiFi.cpp | 1808 ++++++++++++++--------------- cpp_utils/WiFi.h | 316 ++--- 15 files changed, 2916 insertions(+), 2941 deletions(-) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 14198d18..141cf0f5 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -21,7 +21,6 @@ #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" #endif -#include "BLEDevice.h" /* * Design @@ -45,9 +44,9 @@ */ static const char* LOG_TAG = "BLEClient"; -BLEClient::BLEClient(uint16_t connID) { +BLEClient::BLEClient() { m_pClientCallbacks = nullptr; - m_conn_id = connID; + m_conn_id = 0; m_gattc_if = 0; m_haveServices = false; m_isConnected = false; // Initially, we are flagged as not connected. @@ -97,7 +96,7 @@ bool BLEClient::connect(BLEAddress address) { clearServices(); // Delete any services that may exist. - esp_err_t errRc = ::esp_ble_gattc_app_register(m_conn_id); + esp_err_t errRc = ::esp_ble_gattc_app_register(0); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return false; @@ -139,7 +138,6 @@ void BLEClient::disconnect() { } esp_ble_gattc_app_unregister(getGattcIf()); m_peerAddress = BLEAddress("00:00:00:00:00:00"); - BLEDevice::removeClient(m_conn_id); ESP_LOGD(LOG_TAG, "<< disconnect()"); } // disconnect diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index f010b05f..a60ed102 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -28,7 +28,7 @@ class BLEClientCallbacks; */ class BLEClient { public: - BLEClient(uint16_t connID); + BLEClient(); ~BLEClient(); bool connect(BLEAddress address); // Connect to the remote BLE Server diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 4da8965a..a7db454b 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -1,566 +1,548 @@ -/* - * BLE.cpp - * - * Created on: Mar 16, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include -#include -#include -#include -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 ESP-IDF -#include // ESP32 ESP-IDF -#include // Part of C++ Standard library -#include // Part of C++ Standard library -#include // Part of C++ Standard library - -#include "BLEDevice.h" -#include "BLEClient.h" -#include "BLEUtils.h" -#include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 -#include "esp32-hal-log.h" -#include "esp32-hal-bt.h" -#endif - -static const char* LOG_TAG = "BLEDevice"; - -/** - * Singletons for the BLEDevice. - */ -BLEServer* BLEDevice::m_pServer = nullptr; -BLEScan* BLEDevice::m_pScan = nullptr; -BLEClient* BLEDevice::m_pClient = nullptr; -BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; -bool initialized = false; // Have we been initialized? -esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; -BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; -uint16_t BLEDevice::m_localMTU = 23; - -/** - * @brief Create a new instance of a client. - * @return A new instance of the client. - */ -/* STATIC */ BLEClient* BLEDevice::createClient() { - ESP_LOGD(LOG_TAG, ">> createClient"); -#ifndef CONFIG_GATTC_ENABLE // Check that BLE GATTC is enabled in make menuconfig - ESP_LOGE(LOG_TAG, "BLE GATTC is not enabled - CONFIG_GATTC_ENABLE not defined"); - abort(); -#endif // CONFIG_GATTC_ENABLE - m_pClient = new BLEClient(); - ESP_LOGD(LOG_TAG, "<< createClient"); - return m_pClient; -} // createClient - - -/** - * @brief Create a new instance of a server. - * @return A new instance of the server. - */ -/* STATIC */ BLEServer* BLEDevice::createServer() { - ESP_LOGD(LOG_TAG, ">> createServer"); -#ifndef CONFIG_GATTS_ENABLE // Check that BLE GATTS is enabled in make menuconfig - ESP_LOGE(LOG_TAG, "BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined"); - abort(); -#endif // CONFIG_GATTS_ENABLE - m_pServer = new BLEServer(); - m_pServer->createApp(0); - ESP_LOGD(LOG_TAG, "<< createServer"); - return m_pServer; -} // createServer - - -/** - * @brief Handle GATT server events. - * - * @param [in] event The event that has been newly received. - * @param [in] gatts_if The connection to the GATT interface. - * @param [in] param Parameters for the event. - */ -/* STATIC */ void BLEDevice::gattServerEventHandler( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param -) { - ESP_LOGD(LOG_TAG, "gattServerEventHandler [esp_gatt_if: %d] ... %s", - gatts_if, - BLEUtils::gattServerEventTypeToString(event).c_str()); - - BLEUtils::dumpGattServerEvent(event, gatts_if, param); - - switch(event) { - case ESP_GATTS_CONNECT_EVT: { - BLEDevice::m_localMTU = 23; -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityLevel){ - esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - } // ESP_GATTS_CONNECT_EVT - - case ESP_GATTS_MTU_EVT: { - BLEDevice::m_localMTU = param->mtu.mtu; - ESP_LOGI(LOG_TAG, "ESP_GATTS_MTU_EVT, MTU %d", BLEDevice::m_localMTU); - break; - } - default: { - break; - } - } // switch - - - if (BLEDevice::m_pServer != nullptr) { - BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); - } -} // gattServerEventHandler - - -/** - * @brief Handle GATT client events. - * - * Handler for the GATT client events. - * - * @param [in] event - * @param [in] gattc_if - * @param [in] param - */ -/* STATIC */ void BLEDevice::gattClientEventHandler( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t* param) { - - ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", - gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); - BLEUtils::dumpGattClientEvent(event, gattc_if, param); - - switch(event) { - case ESP_GATTC_CONNECT_EVT: { - if(BLEDevice::getMTU() != 23){ - esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - } -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityLevel){ - esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - } // ESP_GATTC_CONNECT_EVT - - default: { - break; - } - } // switch - - - // If we have a client registered, call it. - if (BLEDevice::m_pClient != nullptr) { - BLEDevice::m_pClient->gattClientEventHandler(event, gattc_if, param); - } - -} // gattClientEventHandler - - -/** - * @brief Handle GAP events. - */ -/* STATIC */ void BLEDevice::gapEventHandler( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t *param) { - - BLEUtils::dumpGapEvent(event, param); - - switch(event) { - - case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); - break; - case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); - break; - case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); - break; - case ESP_GAP_BLE_NC_REQ_EVT: - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); - // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - /* - * TODO should we add white/black list comparison? - */ - case ESP_GAP_BLE_SEC_REQ_EVT: - /* send the positive(true) security response to the peer device to accept the security request. - If not accept the security request, should sent the security response with negative(false) accept value*/ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); - } - else{ - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - /* - * - */ - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. - ///show the passkey number to the user to input it in the peer deivce. - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); - BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_KEY_EVT: - //shows the ble key type info share with peer device to the user. - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); -#endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_AUTH_CMPL_EVT: - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - default: { - break; - } - } // switch - - if (BLEDevice::m_pServer != nullptr) { - BLEDevice::m_pServer->handleGAPEvent(event, param); - } - - if (BLEDevice::m_pClient != nullptr) { - BLEDevice::m_pClient->handleGAPEvent(event, param); - } - - if (BLEDevice::m_pScan != nullptr) { - BLEDevice::getScan()->handleGAPEvent(event, param); - } - - /* - * Security events: - */ - - -} // gapEventHandler - - -/** - * @brief Get the BLE device address. - * @return The BLE device address. - */ -/* STATIC*/ BLEAddress BLEDevice::getAddress() { - const uint8_t* bdAddr = esp_bt_dev_get_address(); - esp_bd_addr_t addr; - memcpy(addr, bdAddr, sizeof(addr)); - return BLEAddress(addr); -} // getAddress - - -/** - * @brief Retrieve the Scan object that we use for scanning. - * @return The scanning object reference. This is a singleton object. The caller should not - * try and release/delete it. - */ -/* STATIC */ BLEScan* BLEDevice::getScan() { - //ESP_LOGD(LOG_TAG, ">> getScan"); - if (m_pScan == nullptr) { - m_pScan = new BLEScan(); - //ESP_LOGD(LOG_TAG, " - creating a new scan object"); - } - //ESP_LOGD(LOG_TAG, "<< getScan: Returning object at 0x%x", (uint32_t)m_pScan); - return m_pScan; -} // getScan - - -/** - * @brief Get the value of a characteristic of a service on a remote device. - * @param [in] bdAddress - * @param [in] serviceUUID - * @param [in] characteristicUUID - */ -/* STATIC */ std::string BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID) { - ESP_LOGD(LOG_TAG, ">> getValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - BLEClient *pClient = createClient(); - pClient->connect(bdAddress); - std::string ret = pClient->getValue(serviceUUID, characteristicUUID); - pClient->disconnect(); - ESP_LOGD(LOG_TAG, "<< getValue"); - return ret; -} // getValue - - -/** - * @brief Initialize the %BLE environment. - * @param deviceName The device name of the device. - */ -/* STATIC */ void BLEDevice::init(std::string deviceName) { - if(!initialized){ - initialized = true; // Set the initialization flag to ensure we are only initialized once. - - esp_err_t errRc = ESP_OK; -#ifdef ARDUINO_ARCH_ESP32 - if (!btStart()) { - errRc = ESP_FAIL; - return; - } -#else - errRc = ::nvs_flash_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - errRc = esp_bt_controller_init(&bt_cfg); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - -#ifndef CLASSIC_BT_ENABLED - // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue - errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); - //errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } -#else - errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } -#endif -#endif - - esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); - if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){ - errRc = esp_bluedroid_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - } - - if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){ - errRc = esp_bluedroid_enable(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - } - - errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - -#ifdef CONFIG_GATTC_ENABLE // Check that BLE client is configured in make menuconfig - errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } -#endif // CONFIG_GATTC_ENABLE - -#ifdef CONFIG_GATTS_ENABLE // Check that BLE server is configured in make menuconfig - errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } -#endif // CONFIG_GATTS_ENABLE - - errRc = ::esp_ble_gap_set_device_name(deviceName.c_str()); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_set_device_name: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - }; - -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; - errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - }; -#endif // CONFIG_BLE_SMP_ENABLE - } - vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. -} // init - - -/** - * @brief Set the transmission power. - * The power level can be one of: - * * ESP_PWR_LVL_N14 - * * ESP_PWR_LVL_N11 - * * ESP_PWR_LVL_N8 - * * ESP_PWR_LVL_N5 - * * ESP_PWR_LVL_N2 - * * ESP_PWR_LVL_P1 - * * ESP_PWR_LVL_P4 - * * ESP_PWR_LVL_P7 - * @param [in] powerLevel. - */ -/* STATIC */ void BLEDevice::setPower(esp_power_level_t powerLevel) { - ESP_LOGD(LOG_TAG, ">> setPower: %d", powerLevel); - esp_err_t errRc = ::esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, powerLevel); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - }; - ESP_LOGD(LOG_TAG, "<< setPower"); -} // setPower - - -/** - * @brief Set the value of a characteristic of a service on a remote device. - * @param [in] bdAddress - * @param [in] serviceUUID - * @param [in] characteristicUUID - */ -/* STATIC */ void BLEDevice::setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value) { - ESP_LOGD(LOG_TAG, ">> setValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - BLEClient *pClient = createClient(); - pClient->connect(bdAddress); - pClient->setValue(serviceUUID, characteristicUUID, value); - pClient->disconnect(); -} // setValue - - -/** - * @brief Return a string representation of the nature of this device. - * @return A string representation of the nature of this device. - */ -/* STATIC */ std::string BLEDevice::toString() { - std::ostringstream oss; - oss << "BD Address: " << getAddress().toString(); - return oss.str(); -} // toString - - -/** - * @brief Add an entry to the BLE white list. - * @param [in] address The address to add to the white list. - */ -void BLEDevice::whiteListAdd(BLEAddress address) { - ESP_LOGD(LOG_TAG, ">> whiteListAdd: %s", address.toString().c_str()); - esp_err_t errRc = esp_ble_gap_update_whitelist(true, *address.getNative()); // True to add an entry. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - ESP_LOGD(LOG_TAG, "<< whiteListAdd"); -} // whiteListAdd - - -/** - * @brief Remove an entry from the BLE white list. - * @param [in] address The address to remove from the white list. - */ -void BLEDevice::whiteListRemove(BLEAddress address) { - ESP_LOGD(LOG_TAG, ">> whiteListRemove: %s", address.toString().c_str()); - esp_err_t errRc = esp_ble_gap_update_whitelist(false, *address.getNative()); // False to remove an entry. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - ESP_LOGD(LOG_TAG, "<< whiteListRemove"); -} // whiteListRemove - -/* - * @brief Set encryption level that will be negotiated with peer device durng connection - * @param [in] level Requested encryption level - */ -void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { - BLEDevice::m_securityLevel = level; -} - -/* - * @brief Set callbacks that will be used to handle encryption negotiation events and authentication events - * @param [in] cllbacks Pointer to BLESecurityCallbacks class callback - */ -void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { - BLEDevice::m_securityCallbacks = callbacks; -} - -/* - * @brief Setup local mtu that will be used to negotiate mtu during request from client peer - * @param [in] mtu Value to set local mtu, should be larger than 23 and lower or equal to 517 - */ -esp_err_t BLEDevice::setMTU(uint16_t mtu) { - ESP_LOGD(LOG_TAG, ">> setLocalMTU: %d", mtu); - esp_err_t err = esp_ble_gatt_set_local_mtu(mtu); - if(err == ESP_OK){ - m_localMTU = mtu; - } else { - ESP_LOGE(LOG_TAG, "can't set local mtu value: %d", mtu); - } - ESP_LOGD(LOG_TAG, "<< setLocalMTU"); - return err; -} - -/* - * @brief Get local MTU value set during mtu request or default value - */ -uint16_t BLEDevice::getMTU() { - return m_localMTU; -} - -bool BLEDevice::getInitialized() { - return initialized; -} - -BLEAdvertising* BLEDevice::getAdvertising() { - if(m_bleAdvertising == nullptr) - { - m_bleAdvertising = new BLEAdvertising(); - ESP_LOGI(LOG_TAG, "create advertising"); - } - ESP_LOGD(LOG_TAG, "get advertising"); - return m_bleAdvertising; -} - -void BLEDevice::startAdvertising() { - ESP_LOGD(LOG_TAG, ">> startAdvertising"); - getAdvertising()->start(); - ESP_LOGD(LOG_TAG, "<< startAdvertising"); -} // startAdvertising - -#endif // CONFIG_BT_ENABLED +/* + * BLE.cpp + * + * Created on: Mar 16, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include +#include +#include +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 ESP-IDF +#include // ESP32 ESP-IDF +#include // Part of C++ Standard library +#include // Part of C++ Standard library +#include // Part of C++ Standard library + +#include "BLEDevice.h" +#include "BLEClient.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#include "esp32-hal-bt.h" +#endif + +static const char* LOG_TAG = "BLEDevice"; + +/** + * Singletons for the BLEDevice. + */ +BLEServer* BLEDevice::m_pServer = nullptr; +BLEScan* BLEDevice::m_pScan = nullptr; +BLEClient* BLEDevice::m_pClient = nullptr; +bool initialized = false; // Have we been initialized? +esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; +BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; +uint16_t BLEDevice::m_localMTU = 23; + +/** + * @brief Create a new instance of a client. + * @return A new instance of the client. + */ +/* STATIC */ BLEClient* BLEDevice::createClient() { + ESP_LOGD(LOG_TAG, ">> createClient"); +#ifndef CONFIG_GATTC_ENABLE // Check that BLE GATTC is enabled in make menuconfig + ESP_LOGE(LOG_TAG, "BLE GATTC is not enabled - CONFIG_GATTC_ENABLE not defined"); + abort(); +#endif // CONFIG_GATTC_ENABLE + m_pClient = new BLEClient(); + ESP_LOGD(LOG_TAG, "<< createClient"); + return m_pClient; +} // createClient + + +/** + * @brief Create a new instance of a server. + * @return A new instance of the server. + */ +/* STATIC */ BLEServer* BLEDevice::createServer() { + ESP_LOGD(LOG_TAG, ">> createServer"); +#ifndef CONFIG_GATTS_ENABLE // Check that BLE GATTS is enabled in make menuconfig + ESP_LOGE(LOG_TAG, "BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined"); + abort(); +#endif // CONFIG_GATTS_ENABLE + m_pServer = new BLEServer(); + m_pServer->createApp(0); + ESP_LOGD(LOG_TAG, "<< createServer"); + return m_pServer; +} // createServer + + +/** + * @brief Handle GATT server events. + * + * @param [in] event The event that has been newly received. + * @param [in] gatts_if The connection to the GATT interface. + * @param [in] param Parameters for the event. + */ +/* STATIC */ void BLEDevice::gattServerEventHandler( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param +) { + ESP_LOGD(LOG_TAG, "gattServerEventHandler [esp_gatt_if: %d] ... %s", + gatts_if, + BLEUtils::gattServerEventTypeToString(event).c_str()); + + BLEUtils::dumpGattServerEvent(event, gatts_if, param); + + switch(event) { + case ESP_GATTS_CONNECT_EVT: { + BLEDevice::m_localMTU = 23; +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTS_CONNECT_EVT + + case ESP_GATTS_MTU_EVT: { + BLEDevice::m_localMTU = param->mtu.mtu; + ESP_LOGI(LOG_TAG, "ESP_GATTS_MTU_EVT, MTU %d", BLEDevice::m_localMTU); + break; + } + default: { + break; + } + } // switch + + + if (BLEDevice::m_pServer != nullptr) { + BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); + } +} // gattServerEventHandler + + +/** + * @brief Handle GATT client events. + * + * Handler for the GATT client events. + * + * @param [in] event + * @param [in] gattc_if + * @param [in] param + */ +/* STATIC */ void BLEDevice::gattClientEventHandler( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* param) { + + ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", + gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); + BLEUtils::dumpGattClientEvent(event, gattc_if, param); + + switch(event) { + case ESP_GATTC_CONNECT_EVT: { + if(BLEDevice::getMTU() != 23){ + esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTC_CONNECT_EVT + + default: { + break; + } + } // switch + + + // If we have a client registered, call it. + if (BLEDevice::m_pClient != nullptr) { + BLEDevice::m_pClient->gattClientEventHandler(event, gattc_if, param); + } + +} // gattClientEventHandler + + +/** + * @brief Handle GAP events. + */ +/* STATIC */ void BLEDevice::gapEventHandler( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t *param) { + + BLEUtils::dumpGapEvent(event, param); + + switch(event) { + + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + break; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); + break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); + break; + case ESP_GAP_BLE_NC_REQ_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); + // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + /* + * TODO should we add white/black list comparison? + */ + case ESP_GAP_BLE_SEC_REQ_EVT: + /* send the positive(true) security response to the peer device to accept the security request. + If not accept the security request, should sent the security response with negative(false) accept value*/ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); + } + else{ + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + /* + * + */ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + ///show the passkey number to the user to input it in the peer deivce. + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); + BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_KEY_EVT: + //shows the ble key type info share with peer device to the user. + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + default: { + break; + } + } // switch + + if (BLEDevice::m_pServer != nullptr) { + BLEDevice::m_pServer->handleGAPEvent(event, param); + } + + if (BLEDevice::m_pClient != nullptr) { + BLEDevice::m_pClient->handleGAPEvent(event, param); + } + + if (BLEDevice::m_pScan != nullptr) { + BLEDevice::getScan()->handleGAPEvent(event, param); + } + + /* + * Security events: + */ + + +} // gapEventHandler + + +/** + * @brief Get the BLE device address. + * @return The BLE device address. + */ +/* STATIC*/ BLEAddress BLEDevice::getAddress() { + const uint8_t* bdAddr = esp_bt_dev_get_address(); + esp_bd_addr_t addr; + memcpy(addr, bdAddr, sizeof(addr)); + return BLEAddress(addr); +} // getAddress + + +/** + * @brief Retrieve the Scan object that we use for scanning. + * @return The scanning object reference. This is a singleton object. The caller should not + * try and release/delete it. + */ +/* STATIC */ BLEScan* BLEDevice::getScan() { + //ESP_LOGD(LOG_TAG, ">> getScan"); + if (m_pScan == nullptr) { + m_pScan = new BLEScan(); + //ESP_LOGD(LOG_TAG, " - creating a new scan object"); + } + //ESP_LOGD(LOG_TAG, "<< getScan: Returning object at 0x%x", (uint32_t)m_pScan); + return m_pScan; +} // getScan + + +/** + * @brief Get the value of a characteristic of a service on a remote device. + * @param [in] bdAddress + * @param [in] serviceUUID + * @param [in] characteristicUUID + */ +/* STATIC */ std::string BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID) { + ESP_LOGD(LOG_TAG, ">> getValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + BLEClient *pClient = createClient(); + pClient->connect(bdAddress); + std::string ret = pClient->getValue(serviceUUID, characteristicUUID); + pClient->disconnect(); + ESP_LOGD(LOG_TAG, "<< getValue"); + return ret; +} // getValue + + +/** + * @brief Initialize the %BLE environment. + * @param deviceName The device name of the device. + */ +/* STATIC */ void BLEDevice::init(std::string deviceName) { + if(!initialized){ + initialized = true; // Set the initialization flag to ensure we are only initialized once. + + esp_err_t errRc = ESP_OK; +#ifdef ARDUINO_ARCH_ESP32 + if (!btStart()) { + errRc = ESP_FAIL; + return; + } +#else + errRc = ::nvs_flash_init(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + errRc = esp_bt_controller_init(&bt_cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + +#ifndef CLASSIC_BT_ENABLED + // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue + errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); + //errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#else + errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif +#endif + + esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); + if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){ + errRc = esp_bluedroid_init(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + } + + if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){ + errRc = esp_bluedroid_enable(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + } + + errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + +#ifdef CONFIG_GATTC_ENABLE // Check that BLE client is configured in make menuconfig + errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif // CONFIG_GATTC_ENABLE + +#ifdef CONFIG_GATTS_ENABLE // Check that BLE server is configured in make menuconfig + errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif // CONFIG_GATTS_ENABLE + + errRc = ::esp_ble_gap_set_device_name(deviceName.c_str()); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_device_name: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + }; + +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; + errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + }; +#endif // CONFIG_BLE_SMP_ENABLE + } + vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. +} // init + + +/** + * @brief Set the transmission power. + * The power level can be one of: + * * ESP_PWR_LVL_N14 + * * ESP_PWR_LVL_N11 + * * ESP_PWR_LVL_N8 + * * ESP_PWR_LVL_N5 + * * ESP_PWR_LVL_N2 + * * ESP_PWR_LVL_P1 + * * ESP_PWR_LVL_P4 + * * ESP_PWR_LVL_P7 + * @param [in] powerLevel. + */ +/* STATIC */ void BLEDevice::setPower(esp_power_level_t powerLevel) { + ESP_LOGD(LOG_TAG, ">> setPower: %d", powerLevel); + esp_err_t errRc = ::esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, powerLevel); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + }; + ESP_LOGD(LOG_TAG, "<< setPower"); +} // setPower + + +/** + * @brief Set the value of a characteristic of a service on a remote device. + * @param [in] bdAddress + * @param [in] serviceUUID + * @param [in] characteristicUUID + */ +/* STATIC */ void BLEDevice::setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value) { + ESP_LOGD(LOG_TAG, ">> setValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + BLEClient *pClient = createClient(); + pClient->connect(bdAddress); + pClient->setValue(serviceUUID, characteristicUUID, value); + pClient->disconnect(); +} // setValue + + +/** + * @brief Return a string representation of the nature of this device. + * @return A string representation of the nature of this device. + */ +/* STATIC */ std::string BLEDevice::toString() { + std::ostringstream oss; + oss << "BD Address: " << getAddress().toString(); + return oss.str(); +} // toString + + +/** + * @brief Add an entry to the BLE white list. + * @param [in] address The address to add to the white list. + */ +void BLEDevice::whiteListAdd(BLEAddress address) { + ESP_LOGD(LOG_TAG, ">> whiteListAdd: %s", address.toString().c_str()); + esp_err_t errRc = esp_ble_gap_update_whitelist(true, *address.getNative()); // True to add an entry. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< whiteListAdd"); +} // whiteListAdd + + +/** + * @brief Remove an entry from the BLE white list. + * @param [in] address The address to remove from the white list. + */ +void BLEDevice::whiteListRemove(BLEAddress address) { + ESP_LOGD(LOG_TAG, ">> whiteListRemove: %s", address.toString().c_str()); + esp_err_t errRc = esp_ble_gap_update_whitelist(false, *address.getNative()); // False to remove an entry. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< whiteListRemove"); +} // whiteListRemove + +/* + * @brief Set encryption level that will be negotiated with peer device durng connection + * @param [in] level Requested encryption level + */ +void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { + BLEDevice::m_securityLevel = level; +} + +/* + * @brief Set callbacks that will be used to handle encryption negotiation events and authentication events + * @param [in] cllbacks Pointer to BLESecurityCallbacks class callback + */ +void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { + BLEDevice::m_securityCallbacks = callbacks; +} + +/* + * @brief Setup local mtu that will be used to negotiate mtu during request from client peer + * @param [in] mtu Value to set local mtu, should be larger than 23 and lower or equal to 517 + */ +esp_err_t BLEDevice::setMTU(uint16_t mtu) { + ESP_LOGD(LOG_TAG, ">> setLocalMTU: %d", mtu); + esp_err_t err = esp_ble_gatt_set_local_mtu(mtu); + if(err == ESP_OK){ + m_localMTU = mtu; + } else { + ESP_LOGE(LOG_TAG, "can't set local mtu value: %d", mtu); + } + ESP_LOGD(LOG_TAG, "<< setLocalMTU"); + return err; +} + +/* + * @brief Get local MTU value set during mtu request or default value + */ +uint16_t BLEDevice::getMTU() { + return m_localMTU; +} + +bool BLEDevice::getInitialized() { + return initialized; +} +#endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index a37ac960..7a1b833d 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -26,9 +26,6 @@ */ class BLEDevice { public: - static BLEAdvertising* getAdvertising(); - static void startAdvertising(); - static BLEAdvertising *m_bleAdvertising; static BLEClient* createClient(); // Create a new BLE client. static BLEServer* createServer(); // Cretae a new BLE server. diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index 36492360..2cdc7db1 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -1,185 +1,185 @@ -/* - * BLERemoteDescriptor.cpp - * - * Created on: Jul 8, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include "BLERemoteDescriptor.h" -#include "GeneralUtils.h" -#include -#ifdef ARDUINO_ARCH_ESP32 -#include "esp32-hal-log.h" -#endif - -static const char* LOG_TAG = "BLERemoteDescriptor"; - - -BLERemoteDescriptor::BLERemoteDescriptor( - uint16_t handle, - BLEUUID uuid, - BLERemoteCharacteristic* pRemoteCharacteristic) { - - m_handle = handle; - m_uuid = uuid; - m_pRemoteCharacteristic = pRemoteCharacteristic; -} - - -/** - * @brief Retrieve the handle associated with this remote descriptor. - * @return The handle associated with this remote descriptor. - */ -uint16_t BLERemoteDescriptor::getHandle() { - return m_handle; -} // getHandle - - -/** - * @brief Get the characteristic that owns this descriptor. - * @return The characteristic that owns this descriptor. - */ -BLERemoteCharacteristic* BLERemoteDescriptor::getRemoteCharacteristic() { - return m_pRemoteCharacteristic; -} // getRemoteCharacteristic - - -/** - * @brief Retrieve the UUID associated this remote descriptor. - * @return The UUID associated this remote descriptor. - */ -BLEUUID BLERemoteDescriptor::getUUID() { - return m_uuid; -} // getUUID - - -std::string BLERemoteDescriptor::readValue(void) { - ESP_LOGD(LOG_TAG, ">> readValue: %s", toString().c_str()); - - // Check to see that we are connected. - if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { - ESP_LOGE(LOG_TAG, "Disconnected"); - throw BLEDisconnectedException(); - } - - m_semaphoreReadDescrEvt.take("readValue"); - - // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. - esp_err_t errRc = ::esp_ble_gattc_read_char_descr( - m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), - m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), // The connection ID to the BLE server - getHandle(), // The handle of this characteristic - ESP_GATT_AUTH_REQ_NONE); // Security - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return ""; - } - - // Block waiting for the event that indicates that the read has completed. When it has, the std::string found - // in m_value will contain our data. - m_semaphoreReadDescrEvt.wait("readValue"); - - ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length()); - return m_value; -} // readValue - - -uint8_t BLERemoteDescriptor::readUInt8(void) { - std::string value = readValue(); - if (value.length() >= 1) { - return (uint8_t)value[0]; - } - return 0; -} // readUInt8 - - -uint16_t BLERemoteDescriptor::readUInt16(void) { - std::string value = readValue(); - if (value.length() >= 2) { - return *(uint16_t*)(value.data()); - } - return 0; -} // readUInt16 - - -uint32_t BLERemoteDescriptor::readUInt32(void) { - std::string value = readValue(); - if (value.length() >= 4) { - return *(uint32_t*)(value.data()); - } - return 0; -} // readUInt32 - - -/** - * @brief Return a string representation of this BLE Remote Descriptor. - * @retun A string representation of this BLE Remote Descriptor. - */ -std::string BLERemoteDescriptor::toString(void) { - std::stringstream ss; - ss << "handle: " << getHandle() << ", uuid: " << getUUID().toString(); - return ss.str(); -} // toString - - -/** - * @brief Write data to the BLE Remote Descriptor. - * @param [in] data The data to send to the remote descriptor. - * @param [in] length The length of the data to send. - * @param [in] response True if we expect a response. - */ -void BLERemoteDescriptor::writeValue( - uint8_t* data, - size_t length, - bool response) { - ESP_LOGD(LOG_TAG, ">> writeValue: %s", toString().c_str()); - // Check to see that we are connected. - if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { - ESP_LOGE(LOG_TAG, "Disconnected"); - throw BLEDisconnectedException(); - } - - esp_err_t errRc = ::esp_ble_gattc_write_char_descr( - m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), - m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), - getHandle(), - length, // Data length - data, // Data - response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, - ESP_GATT_AUTH_REQ_NONE - ); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char_descr: %d", errRc); - } - ESP_LOGD(LOG_TAG, "<< writeValue"); -} // writeValue - - -/** - * @brief Write data represented as a string to the BLE Remote Descriptor. - * @param [in] newValue The data to send to the remote descriptor. - * @param [in] response True if we expect a response. - */ -void BLERemoteDescriptor::writeValue( - std::string newValue, - bool response) { - writeValue(newValue.data(), newValue.length()); -} // writeValue - - -/** - * @brief Write a byte value to the Descriptor. - * @param [in] The single byte to write. - * @param [in] True if we expect a response. - */ -void BLERemoteDescriptor::writeValue( - uint8_t newValue, - bool response) { - writeValue(&newValue, 1, response); -} // writeValue - - -#endif /* CONFIG_BT_ENABLED */ +/* + * BLERemoteDescriptor.cpp + * + * Created on: Jul 8, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include "BLERemoteDescriptor.h" +#include "GeneralUtils.h" +#include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + +static const char* LOG_TAG = "BLERemoteDescriptor"; + + +BLERemoteDescriptor::BLERemoteDescriptor( + uint16_t handle, + BLEUUID uuid, + BLERemoteCharacteristic* pRemoteCharacteristic) { + + m_handle = handle; + m_uuid = uuid; + m_pRemoteCharacteristic = pRemoteCharacteristic; +} + + +/** + * @brief Retrieve the handle associated with this remote descriptor. + * @return The handle associated with this remote descriptor. + */ +uint16_t BLERemoteDescriptor::getHandle() { + return m_handle; +} // getHandle + + +/** + * @brief Get the characteristic that owns this descriptor. + * @return The characteristic that owns this descriptor. + */ +BLERemoteCharacteristic* BLERemoteDescriptor::getRemoteCharacteristic() { + return m_pRemoteCharacteristic; +} // getRemoteCharacteristic + + +/** + * @brief Retrieve the UUID associated this remote descriptor. + * @return The UUID associated this remote descriptor. + */ +BLEUUID BLERemoteDescriptor::getUUID() { + return m_uuid; +} // getUUID + + +std::string BLERemoteDescriptor::readValue(void) { + ESP_LOGD(LOG_TAG, ">> readValue: %s", toString().c_str()); + + // Check to see that we are connected. + if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + + m_semaphoreReadDescrEvt.take("readValue"); + + // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. + esp_err_t errRc = ::esp_ble_gattc_read_char_descr( + m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), + m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), // The connection ID to the BLE server + getHandle(), // The handle of this characteristic + ESP_GATT_AUTH_REQ_NONE); // Security + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return ""; + } + + // Block waiting for the event that indicates that the read has completed. When it has, the std::string found + // in m_value will contain our data. + m_semaphoreReadDescrEvt.wait("readValue"); + + ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length()); + return m_value; +} // readValue + + +uint8_t BLERemoteDescriptor::readUInt8(void) { + std::string value = readValue(); + if (value.length() >= 1) { + return (uint8_t)value[0]; + } + return 0; +} // readUInt8 + + +uint16_t BLERemoteDescriptor::readUInt16(void) { + std::string value = readValue(); + if (value.length() >= 2) { + return *(uint16_t*)(value.data()); + } + return 0; +} // readUInt16 + + +uint32_t BLERemoteDescriptor::readUInt32(void) { + std::string value = readValue(); + if (value.length() >= 4) { + return *(uint32_t*)(value.data()); + } + return 0; +} // readUInt32 + + +/** + * @brief Return a string representation of this BLE Remote Descriptor. + * @retun A string representation of this BLE Remote Descriptor. + */ +std::string BLERemoteDescriptor::toString(void) { + std::stringstream ss; + ss << "handle: " << getHandle() << ", uuid: " << getUUID().toString(); + return ss.str(); +} // toString + + +/** + * @brief Write data to the BLE Remote Descriptor. + * @param [in] data The data to send to the remote descriptor. + * @param [in] length The length of the data to send. + * @param [in] response True if we expect a response. + */ +void BLERemoteDescriptor::writeValue( + uint8_t* data, + size_t length, + bool response) { + ESP_LOGD(LOG_TAG, ">> writeValue: %s", toString().c_str()); + // Check to see that we are connected. + if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + + esp_err_t errRc = ::esp_ble_gattc_write_char_descr( + m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), + m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), + getHandle(), + length, // Data length + data, // Data + response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE + ); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char_descr: %d", errRc); + } + ESP_LOGD(LOG_TAG, "<< writeValue"); +} // writeValue + + +/** + * @brief Write data represented as a string to the BLE Remote Descriptor. + * @param [in] newValue The data to send to the remote descriptor. + * @param [in] response True if we expect a response. + */ +void BLERemoteDescriptor::writeValue( + std::string newValue, + bool response) { + writeValue(newValue.data(), newValue.length()); +} // writeValue + + +/** + * @brief Write a byte value to the Descriptor. + * @param [in] The single byte to write. + * @param [in] True if we expect a response. + */ +void BLERemoteDescriptor::writeValue( + uint8_t newValue, + bool response) { + writeValue(&newValue, 1, response); +} // writeValue + + +#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 87046670..3046b7c8 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -1,292 +1,292 @@ -/* - * BLEScan.cpp - * - * Created on: Jul 1, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - - -#include -#include - -#include - -#include "BLEAdvertisedDevice.h" -#include "BLEScan.h" -#include "BLEUtils.h" -#include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 -#include "esp32-hal-log.h" -#endif - -static const char* LOG_TAG = "BLEScan"; - - -/** - * Constructor - */ -BLEScan::BLEScan() { - m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan. - m_scan_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; - m_scan_params.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; - m_pAdvertisedDeviceCallbacks = nullptr; - m_stopped = true; - m_wantDuplicates = false; - setInterval(100); - setWindow(100); -} // BLEScan - - -/** - * @brief Handle GAP events related to scans. - * @param [in] event The event type for this event. - * @param [in] param Parameter data for this event. - */ -void BLEScan::handleGAPEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param) { - - switch(event) { - - // ESP_GAP_BLE_SCAN_RESULT_EVT - // --------------------------- - // scan_rst: - // esp_gap_search_evt_t search_evt - // esp_bd_addr_t bda - // esp_bt_dev_type_t dev_type - // esp_ble_addr_type_t ble_addr_type - // esp_ble_evt_type_t ble_evt_type - // int rssi - // uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX] - // int flag - // int num_resps - // uint8_t adv_data_len - // uint8_t scan_rsp_len - case ESP_GAP_BLE_SCAN_RESULT_EVT: { - - switch(param->scan_rst.search_evt) { - // - // ESP_GAP_SEARCH_INQ_CMPL_EVT - // - // Event that indicates that the duration allowed for the search has completed or that we have been - // asked to stop. - case ESP_GAP_SEARCH_INQ_CMPL_EVT: { - m_stopped = true; - if (m_scanCompleteCB != nullptr) { - m_scanCompleteCB(m_scanResults); - } - m_semaphoreScanEnd.give(); - break; - } // ESP_GAP_SEARCH_INQ_CMPL_EVT - - // - // ESP_GAP_SEARCH_INQ_RES_EVT - // - // Result that has arrived back from a Scan inquiry. - case ESP_GAP_SEARCH_INQ_RES_EVT: { - if (m_stopped) { // If we are not scanning, nothing to do with the extra results. - break; - } - -// Examine our list of previously scanned addresses and, if we found this one already, -// ignore it. - BLEAddress advertisedAddress(param->scan_rst.bda); - bool found = false; - - for (int i=0; iscan_rst.rssi); - advertisedDevice.setAdFlag(param->scan_rst.flag); - advertisedDevice.parseAdvertisement((uint8_t*)param->scan_rst.ble_adv); - advertisedDevice.setScan(this); - - if (m_pAdvertisedDeviceCallbacks) { - m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); - } - - if (!found) { // If we have previously seen this device, don't record it again. - m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice); - } - - break; - } // ESP_GAP_SEARCH_INQ_RES_EVT - - default: { - break; - } - } // switch - search_evt - - - break; - } // ESP_GAP_BLE_SCAN_RESULT_EVT - - default: { - break; - } // default - } // End switch -} // gapEventHandler - - -/** - * @brief Should we perform an active or passive scan? - * The default is a passive scan. An active scan means that we will wish a scan response. - * @param [in] active If true, we perform an active scan otherwise a passive scan. - * @return N/A. - */ -void BLEScan::setActiveScan(bool active) { - if (active) { - m_scan_params.scan_type = BLE_SCAN_TYPE_ACTIVE; - } else { - m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; - } -} // setActiveScan - - -/** - * @brief Set the call backs to be invoked. - * @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked. - * @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false. - */ -void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, bool wantDuplicates) { - m_wantDuplicates = wantDuplicates; - m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; -} // setAdvertisedDeviceCallbacks - - -/** - * @brief Set the interval to scan. - * @param [in] The interval in msecs. - */ -void BLEScan::setInterval(uint16_t intervalMSecs) { - m_scan_params.scan_interval = intervalMSecs / 0.625; -} // setInterval - - -/** - * @brief Set the window to actively scan. - * @param [in] windowMSecs How long to actively scan. - */ -void BLEScan::setWindow(uint16_t windowMSecs) { - m_scan_params.scan_window = windowMSecs / 0.625; -} // setWindow - - -/** - * @brief Start scanning. - * @param [in] duration The duration in seconds for which to scan. - * @param [in] scanCompleteCB A function to be called when scanning has completed. - * @return True if scan started or false if there was an error. - */ -bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)) { - ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); - - m_semaphoreScanEnd.take(std::string("start")); - m_scanCompleteCB = scanCompleteCB; // Save the callback to be invoked when the scan completes. - - m_scanResults.m_vectorAdvertisedDevices.clear(); - - esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); - m_semaphoreScanEnd.give(); - return false; - } - - errRc = ::esp_ble_gap_start_scanning(duration); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); - m_semaphoreScanEnd.give(); - return false; - } - - m_stopped = false; - - ESP_LOGD(LOG_TAG, "<< start()"); - return true; -} // start - - -/** - * @brief Start scanning and block until scanning has been completed. - * @param [in] duration The duration in seconds for which to scan. - * @return The BLEScanResults. - */ -BLEScanResults BLEScan::start(uint32_t duration) { - if(start(duration, nullptr)) { - m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. - } - return m_scanResults; -} // start - - -/** - * @brief Stop an in progress scan. - * @return N/A. - */ -void BLEScan::stop() { - ESP_LOGD(LOG_TAG, ">> stop()"); - - esp_err_t errRc = ::esp_ble_gap_stop_scanning(); - - m_stopped = true; - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - m_semaphoreScanEnd.give(); - - ESP_LOGD(LOG_TAG, "<< stop()"); -} // stop - - -/** - * @brief Dump the scan results to the log. - */ -void BLEScanResults::dump() { - ESP_LOGD(LOG_TAG, ">> Dump scan results:"); - for (int i=0; i +#include + +#include + +#include "BLEAdvertisedDevice.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + +static const char* LOG_TAG = "BLEScan"; + + +/** + * Constructor + */ +BLEScan::BLEScan() { + m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan. + m_scan_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; + m_scan_params.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; + m_pAdvertisedDeviceCallbacks = nullptr; + m_stopped = true; + m_wantDuplicates = false; + setInterval(100); + setWindow(100); +} // BLEScan + + +/** + * @brief Handle GAP events related to scans. + * @param [in] event The event type for this event. + * @param [in] param Parameter data for this event. + */ +void BLEScan::handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param) { + + switch(event) { + + // ESP_GAP_BLE_SCAN_RESULT_EVT + // --------------------------- + // scan_rst: + // esp_gap_search_evt_t search_evt + // esp_bd_addr_t bda + // esp_bt_dev_type_t dev_type + // esp_ble_addr_type_t ble_addr_type + // esp_ble_evt_type_t ble_evt_type + // int rssi + // uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX] + // int flag + // int num_resps + // uint8_t adv_data_len + // uint8_t scan_rsp_len + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + + switch(param->scan_rst.search_evt) { + // + // ESP_GAP_SEARCH_INQ_CMPL_EVT + // + // Event that indicates that the duration allowed for the search has completed or that we have been + // asked to stop. + case ESP_GAP_SEARCH_INQ_CMPL_EVT: { + m_stopped = true; + if (m_scanCompleteCB != nullptr) { + m_scanCompleteCB(m_scanResults); + } + m_semaphoreScanEnd.give(); + break; + } // ESP_GAP_SEARCH_INQ_CMPL_EVT + + // + // ESP_GAP_SEARCH_INQ_RES_EVT + // + // Result that has arrived back from a Scan inquiry. + case ESP_GAP_SEARCH_INQ_RES_EVT: { + if (m_stopped) { // If we are not scanning, nothing to do with the extra results. + break; + } + +// Examine our list of previously scanned addresses and, if we found this one already, +// ignore it. + BLEAddress advertisedAddress(param->scan_rst.bda); + bool found = false; + + for (int i=0; iscan_rst.rssi); + advertisedDevice.setAdFlag(param->scan_rst.flag); + advertisedDevice.parseAdvertisement((uint8_t*)param->scan_rst.ble_adv); + advertisedDevice.setScan(this); + + if (m_pAdvertisedDeviceCallbacks) { + m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); + } + + if (!found) { // If we have previously seen this device, don't record it again. + m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice); + } + + break; + } // ESP_GAP_SEARCH_INQ_RES_EVT + + default: { + break; + } + } // switch - search_evt + + + break; + } // ESP_GAP_BLE_SCAN_RESULT_EVT + + default: { + break; + } // default + } // End switch +} // gapEventHandler + + +/** + * @brief Should we perform an active or passive scan? + * The default is a passive scan. An active scan means that we will wish a scan response. + * @param [in] active If true, we perform an active scan otherwise a passive scan. + * @return N/A. + */ +void BLEScan::setActiveScan(bool active) { + if (active) { + m_scan_params.scan_type = BLE_SCAN_TYPE_ACTIVE; + } else { + m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; + } +} // setActiveScan + + +/** + * @brief Set the call backs to be invoked. + * @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked. + * @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false. + */ +void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, bool wantDuplicates) { + m_wantDuplicates = wantDuplicates; + m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; +} // setAdvertisedDeviceCallbacks + + +/** + * @brief Set the interval to scan. + * @param [in] The interval in msecs. + */ +void BLEScan::setInterval(uint16_t intervalMSecs) { + m_scan_params.scan_interval = intervalMSecs / 0.625; +} // setInterval + + +/** + * @brief Set the window to actively scan. + * @param [in] windowMSecs How long to actively scan. + */ +void BLEScan::setWindow(uint16_t windowMSecs) { + m_scan_params.scan_window = windowMSecs / 0.625; +} // setWindow + + +/** + * @brief Start scanning. + * @param [in] duration The duration in seconds for which to scan. + * @param [in] scanCompleteCB A function to be called when scanning has completed. + * @return True if scan started or false if there was an error. + */ +bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)) { + ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); + + m_semaphoreScanEnd.take(std::string("start")); + m_scanCompleteCB = scanCompleteCB; // Save the callback to be invoked when the scan completes. + + m_scanResults.m_vectorAdvertisedDevices.clear(); + + esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); + m_semaphoreScanEnd.give(); + return false; + } + + errRc = ::esp_ble_gap_start_scanning(duration); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); + m_semaphoreScanEnd.give(); + return false; + } + + m_stopped = false; + + ESP_LOGD(LOG_TAG, "<< start()"); + return true; +} // start + + +/** + * @brief Start scanning and block until scanning has been completed. + * @param [in] duration The duration in seconds for which to scan. + * @return The BLEScanResults. + */ +BLEScanResults BLEScan::start(uint32_t duration) { + if(start(duration, nullptr)) { + m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. + } + return m_scanResults; +} // start + + +/** + * @brief Stop an in progress scan. + * @return N/A. + */ +void BLEScan::stop() { + ESP_LOGD(LOG_TAG, ">> stop()"); + + esp_err_t errRc = ::esp_ble_gap_stop_scanning(); + + m_stopped = true; + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreScanEnd.give(); + + ESP_LOGD(LOG_TAG, "<< stop()"); +} // stop + + +/** + * @brief Dump the scan results to the log. + */ +void BLEScanResults::dump() { + ESP_LOGD(LOG_TAG, ">> Dump scan results:"); + for (int i=0; i - -#include -#include "BLEAdvertisedDevice.h" -#include "BLEClient.h" -#include "FreeRTOS.h" - -class BLEAdvertisedDevice; -class BLEAdvertisedDeviceCallbacks; -class BLEClient; -class BLEScan; - - -/** - * @brief The result of having performed a scan. - * When a scan completes, we have a set of found devices. Each device is described - * by a BLEAdvertisedDevice object. The number of items in the set is given by - * getCount(). We can retrieve a device by calling getDevice() passing in the - * index (starting at 0) of the desired device. - */ -class BLEScanResults { -public: - void dump(); - int getCount(); - BLEAdvertisedDevice getDevice(uint32_t i); - -private: - friend BLEScan; - std::vector m_vectorAdvertisedDevices; -}; - -/** - * @brief Perform and manage %BLE scans. - * - * Scanning is associated with a %BLE client that is attempting to locate BLE servers. - */ -class BLEScan { -public: - void setActiveScan(bool active); - void setAdvertisedDeviceCallbacks( - BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, - bool wantDuplicates = false); - void setInterval(uint16_t intervalMSecs); - void setWindow(uint16_t windowMSecs); - bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)); - BLEScanResults start(uint32_t duration); - void stop(); - -private: - BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. - friend class BLEDevice; - void handleGAPEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param); - void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); - - - esp_ble_scan_params_t m_scan_params; - BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks; - bool m_stopped; - FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); - BLEScanResults m_scanResults; - bool m_wantDuplicates; - void (*m_scanCompleteCB)(BLEScanResults scanResults); -}; // BLEScan - -#endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLESCAN_H_ */ +/* + * BLEScan.h + * + * Created on: Jul 1, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESCAN_H_ +#define COMPONENTS_CPP_UTILS_BLESCAN_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include + +#include +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "FreeRTOS.h" + +class BLEAdvertisedDevice; +class BLEAdvertisedDeviceCallbacks; +class BLEClient; +class BLEScan; + + +/** + * @brief The result of having performed a scan. + * When a scan completes, we have a set of found devices. Each device is described + * by a BLEAdvertisedDevice object. The number of items in the set is given by + * getCount(). We can retrieve a device by calling getDevice() passing in the + * index (starting at 0) of the desired device. + */ +class BLEScanResults { +public: + void dump(); + int getCount(); + BLEAdvertisedDevice getDevice(uint32_t i); + +private: + friend BLEScan; + std::vector m_vectorAdvertisedDevices; +}; + +/** + * @brief Perform and manage %BLE scans. + * + * Scanning is associated with a %BLE client that is attempting to locate BLE servers. + */ +class BLEScan { +public: + void setActiveScan(bool active); + void setAdvertisedDeviceCallbacks( + BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, + bool wantDuplicates = false); + void setInterval(uint16_t intervalMSecs); + void setWindow(uint16_t windowMSecs); + bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)); + BLEScanResults start(uint32_t duration); + void stop(); + +private: + BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. + friend class BLEDevice; + void handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); + void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); + + + esp_ble_scan_params_t m_scan_params; + BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks; + bool m_stopped; + FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); + BLEScanResults m_scanResults; + bool m_wantDuplicates; + void (*m_scanCompleteCB)(BLEScanResults scanResults); +}; // BLEScan + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLESCAN_H_ */ diff --git a/cpp_utils/BLESecurity.h b/cpp_utils/BLESecurity.h index e35a398a..2d52b015 100644 --- a/cpp_utils/BLESecurity.h +++ b/cpp_utils/BLESecurity.h @@ -1,71 +1,71 @@ -/* - * BLESecurity.h - * - * Created on: Dec 17, 2017 - * Author: chegewara - */ - -#ifndef COMPONENTS_CPP_UTILS_BLESECURITY_H_ -#define COMPONENTS_CPP_UTILS_BLESECURITY_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - -#include - -class BLESecurity { -public: - BLESecurity(); - virtual ~BLESecurity(); - void setAuthenticationMode(esp_ble_auth_req_t auth_req); - void setCapability(esp_ble_io_cap_t iocap); - void setInitEncryptionKey(uint8_t init_key); - void setRespEncryptionKey(uint8_t resp_key); - void setKeySize(uint8_t key_size = 16); - static char* esp_key_type_to_str(esp_ble_key_type_t key_type); - -private: - esp_ble_auth_req_t m_authReq; - esp_ble_io_cap_t m_iocap; - uint8_t m_initKey; - uint8_t m_respKey; - uint8_t m_keySize; -}; // BLESecurity - - -/* - * @brief Callbacks to handle GAP events related to authorization - */ -class BLESecurityCallbacks { -public: - virtual ~BLESecurityCallbacks() {}; - - /** - * @brief Its request from peer device to input authentication pin code displayed on peer device. - * It requires that our device is capable to input 6-digits code by end user - * @return Return 6-digits integer value from input device - */ - virtual uint32_t onPassKeyRequest() = 0; - - /** - * @brief Provide us 6-digits code to perform authentication. - * It requires that our device is capable to display this code to end user - * @param - */ - virtual void onPassKeyNotify(uint32_t pass_key) = 0; - - /** - * @brief Here we can make decision if we want to let negotiate authorization with peer device or not - * return Return true if we accept this peer device request - */ - - virtual bool onSecurityRequest() = 0 ; - /** - * Provide us information when authentication process is completed - */ - virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t) = 0; - - virtual bool onConfirmPIN(uint32_t pin) = 0; -}; // BLESecurityCallbacks - -#endif // CONFIG_BT_ENABLED -#endif // COMPONENTS_CPP_UTILS_BLESECURITY_H_ +/* + * BLESecurity.h + * + * Created on: Dec 17, 2017 + * Author: chegewara + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESECURITY_H_ +#define COMPONENTS_CPP_UTILS_BLESECURITY_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include + +class BLESecurity { +public: + BLESecurity(); + virtual ~BLESecurity(); + void setAuthenticationMode(esp_ble_auth_req_t auth_req); + void setCapability(esp_ble_io_cap_t iocap); + void setInitEncryptionKey(uint8_t init_key); + void setRespEncryptionKey(uint8_t resp_key); + void setKeySize(uint8_t key_size = 16); + static char* esp_key_type_to_str(esp_ble_key_type_t key_type); + +private: + esp_ble_auth_req_t m_authReq; + esp_ble_io_cap_t m_iocap; + uint8_t m_initKey; + uint8_t m_respKey; + uint8_t m_keySize; +}; // BLESecurity + + +/* + * @brief Callbacks to handle GAP events related to authorization + */ +class BLESecurityCallbacks { +public: + virtual ~BLESecurityCallbacks() {}; + + /** + * @brief Its request from peer device to input authentication pin code displayed on peer device. + * It requires that our device is capable to input 6-digits code by end user + * @return Return 6-digits integer value from input device + */ + virtual uint32_t onPassKeyRequest() = 0; + + /** + * @brief Provide us 6-digits code to perform authentication. + * It requires that our device is capable to display this code to end user + * @param + */ + virtual void onPassKeyNotify(uint32_t pass_key) = 0; + + /** + * @brief Here we can make decision if we want to let negotiate authorization with peer device or not + * return Return true if we accept this peer device request + */ + + virtual bool onSecurityRequest() = 0 ; + /** + * Provide us information when authentication process is completed + */ + virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t) = 0; + + virtual bool onConfirmPIN(uint32_t pin) = 0; +}; // BLESecurityCallbacks + +#endif // CONFIG_BT_ENABLED +#endif // COMPONENTS_CPP_UTILS_BLESECURITY_H_ diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 56b1e0c0..6a60f1db 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -120,8 +120,7 @@ BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { * @return An advertising object. */ BLEAdvertising* BLEServer::getAdvertising() { - // return &m_bleAdvertising; - return BLEDevice::getAdvertising(); + return &m_bleAdvertising; } uint16_t BLEServer::getConnId() { @@ -258,7 +257,7 @@ void BLEServer::handleGATTServerEvent( if (m_pServerCallbacks != nullptr) { // If we have callbacks, call now. m_pServerCallbacks->onDisconnect(this); } - // startAdvertising(); //- do this with some delay from the loop() + startAdvertising(); //- do this with some delay from the loop() break; } // ESP_GATTS_DISCONNECT_EVT @@ -360,8 +359,7 @@ void BLEServer::removeService(BLEService *service) { */ void BLEServer::startAdvertising() { ESP_LOGD(LOG_TAG, ">> startAdvertising"); - // m_bleAdvertising.start(); - BLEDevice::startAdvertising(); + m_bleAdvertising.start(); ESP_LOGD(LOG_TAG, "<< startAdvertising"); } // startAdvertising diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 7f40faef..a58b0e9b 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -73,7 +73,7 @@ class BLEServer { friend class BLEDevice; esp_ble_adv_data_t m_adv_data; uint16_t m_appId; - // BLEAdvertising m_bleAdvertising; + BLEAdvertising m_bleAdvertising; uint16_t m_connId; uint32_t m_connectedCount; uint16_t m_gatts_if; diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 863eb66f..340ea560 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -1,435 +1,435 @@ -/* - * BLEService.cpp - * - * Created on: Mar 25, 2017 - * Author: kolban - */ - -// A service is identified by a UUID. A service is also the container for one or more characteristics. - -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include -#include - -#include -#include -#include - -#include "BLEServer.h" -#include "BLEService.h" -#include "BLEUtils.h" -#include "GeneralUtils.h" - -#ifdef ARDUINO_ARCH_ESP32 -#include "esp32-hal-log.h" -#endif - -#define NULL_HANDLE (0xffff) - -static const char* LOG_TAG = "BLEService"; // Tag for logging. - -/** - * @brief Construct an instance of the BLEService - * @param [in] uuid The UUID of the service. - * @param [in] numHandles The maximum number of handles associated with the service. - */ -BLEService::BLEService(const char* uuid, uint32_t numHandles) : BLEService(BLEUUID(uuid), numHandles) { -} - - -/** - * @brief Construct an instance of the BLEService - * @param [in] uuid The UUID of the service. - * @param [in] numHandles The maximum number of handles associated with the service. - */ -BLEService::BLEService(BLEUUID uuid, uint32_t numHandles) { - m_uuid = uuid; - m_handle = NULL_HANDLE; - m_pServer = nullptr; - //m_serializeMutex.setName("BLEService"); - m_lastCreatedCharacteristic = nullptr; - m_numHandles = numHandles; -} // BLEService - - -/** - * @brief Create the service. - * Create the service. - * @param [in] gatts_if The handle of the GATT server interface. - * @return N/A. - */ - -void BLEService::executeCreate(BLEServer *pServer) { - //ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); - //getUUID(); // Needed for a weird bug fix - //char x[1000]; - //memcpy(x, &m_uuid, sizeof(m_uuid)); - //char x[10]; - //memcpy(x, &deleteMe, 10); - m_pServer = pServer; - m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT - - esp_gatt_srvc_id_t srvc_id; - srvc_id.is_primary = true; - srvc_id.id.inst_id = m_id; - srvc_id.id.uuid = *m_uuid.getNative(); - esp_err_t errRc = ::esp_ble_gatts_create_service( - getServer()->getGattsIf(), - &srvc_id, - m_numHandles // The maximum number of handles associated with the service. - ); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - m_semaphoreCreateEvt.wait("executeCreate"); - ESP_LOGD(LOG_TAG, "<< executeCreate"); -} // executeCreate - - -/** - * @brief Delete the service. - * Delete the service. - * @return N/A. - */ - -void BLEService::executeDelete() { - ESP_LOGD(LOG_TAG, ">> executeDelete()"); - m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_DELETE_EVT - - esp_err_t errRc = ::esp_ble_gatts_delete_service( getHandle() ); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gatts_delete_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - m_semaphoreDeleteEvt.wait("executeDelete"); - ESP_LOGD(LOG_TAG, "<< executeDelete"); -} // executeDelete - - -/** - * @brief Dump details of this BLE GATT service. - * @return N/A. - */ -void BLEService::dump() { - ESP_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%.2x", - m_uuid.toString().c_str(), - m_handle); - ESP_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str()); -} // dump - - -/** - * @brief Get the UUID of the service. - * @return the UUID of the service. - */ -BLEUUID BLEService::getUUID() { - return m_uuid; -} // getUUID - - -/** - * @brief Start the service. - * Here we wish to start the service which means that we will respond to partner requests about it. - * Starting a service also means that we can create the corresponding characteristics. - * @return Start the service. - */ -void BLEService::start() { -// We ask the BLE runtime to start the service and then create each of the characteristics. -// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event -// obtained as a result of calling esp_ble_gatts_create_service(). -// - ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); - if (m_handle == NULL_HANDLE) { - ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!"); - return; - } - - - BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); - - while(pCharacteristic != nullptr) { - m_lastCreatedCharacteristic = pCharacteristic; - pCharacteristic->executeCreate(this); - - pCharacteristic = m_characteristicMap.getNext(); - } - // Start each of the characteristics ... these are found in the m_characteristicMap. - - m_semaphoreStartEvt.take("start"); - esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - m_semaphoreStartEvt.wait("start"); - - ESP_LOGD(LOG_TAG, "<< start()"); -} // start - - -/** - * @brief Stop the service. - * @return Stop the service. - */ -void BLEService::stop() { -// We ask the BLE runtime to start the service and then create each of the characteristics. -// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event -// obtained as a result of calling esp_ble_gatts_create_service(). -// - ESP_LOGD(LOG_TAG, ">> stop(): Stopping service (esp_ble_gatts_stop_service): %s", toString().c_str()); - if (m_handle == NULL_HANDLE) { - ESP_LOGE(LOG_TAG, "<< !!! We attempted to stop a service but don't know its handle!"); - return; - } - - m_semaphoreStopEvt.take("stop"); - esp_err_t errRc = ::esp_ble_gatts_stop_service(m_handle); - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_stop_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - m_semaphoreStopEvt.wait("stop"); - - ESP_LOGD(LOG_TAG, "<< stop()"); -} // start - - -/** - * @brief Set the handle associated with this service. - * @param [in] handle The handle associated with the service. - */ -void BLEService::setHandle(uint16_t handle) { - ESP_LOGD(LOG_TAG, ">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str()); - if (m_handle != NULL_HANDLE) { - ESP_LOGE(LOG_TAG, "!!! Handle is already set %.2x", m_handle); - return; - } - m_handle = handle; - ESP_LOGD(LOG_TAG, "<< setHandle"); -} // setHandle - - -/** - * @brief Get the handle associated with this service. - * @return The handle associated with this service. - */ -uint16_t BLEService::getHandle() { - return m_handle; -} // getHandle - - -/** - * @brief Add a characteristic to the service. - * @param [in] pCharacteristic A pointer to the characteristic to be added. - */ -void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { -// We maintain a mapping of characteristics owned by this service. These are managed by the -// BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic -// to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF). -// - ESP_LOGD(LOG_TAG, ">> addCharacteristic()"); - ESP_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s", - pCharacteristic->getUUID().toString().c_str(), - toString().c_str()); - - // Check that we don't add the same characteristic twice. - if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { - ESP_LOGW(LOG_TAG, "<< Adding a new characteristic with the same UUID as a previous one"); - //return; - } - - // Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID - // but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT. - m_characteristicMap.setByUUID(pCharacteristic, pCharacteristic->getUUID()); - - ESP_LOGD(LOG_TAG, "<< addCharacteristic()"); -} // addCharacteristic - - -/** - * @brief Create a new BLE Characteristic associated with this service. - * @param [in] uuid - The UUID of the characteristic. - * @param [in] properties - The properties of the characteristic. - * @return The new BLE characteristic. - */ -BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) { - return createCharacteristic(BLEUUID(uuid), properties); -} - - -/** - * @brief Create a new BLE Characteristic associated with this service. - * @param [in] uuid - The UUID of the characteristic. - * @param [in] properties - The properties of the characteristic. - * @return The new BLE characteristic. - */ -BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) { - BLECharacteristic *pCharacteristic = new BLECharacteristic(uuid, properties); - addCharacteristic(pCharacteristic); - return pCharacteristic; -} // createCharacteristic - - -/** - * @brief Handle a GATTS server event. - */ -void BLEService::handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { - - - switch(event) { - // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. - // add_char: - // - esp_gatt_status_t status - // - uint16_t attr_handle - // - uint16_t service_handle - // - esp_bt_uuid_t char_uuid - - // If we have reached the correct service, then locate the characteristic and remember the handle - // for that characteristic. - case ESP_GATTS_ADD_CHAR_EVT: { - if (m_handle == param->add_char.service_handle) { - BLECharacteristic *pCharacteristic = getLastCreatedCharacteristic(); - if (pCharacteristic == nullptr) { - ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", - BLEUUID(param->add_char.char_uuid).toString().c_str()); - dump(); - break; - } - pCharacteristic->setHandle(param->add_char.attr_handle); - m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic); - //ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str()); - break; - } // Reached the correct service. - break; - } // ESP_GATTS_ADD_CHAR_EVT - - - // ESP_GATTS_START_EVT - // - // start: - // esp_gatt_status_t status - // uint16_t service_handle - case ESP_GATTS_START_EVT: { - if (param->start.service_handle == getHandle()) { - m_semaphoreStartEvt.give(); - } - break; - } // ESP_GATTS_START_EVT - - // ESP_GATTS_STOP_EVT - // - // stop: - // esp_gatt_status_t status - // uint16_t service_handle - // - case ESP_GATTS_STOP_EVT: { - if (param->stop.service_handle == getHandle()) { - m_semaphoreStopEvt.give(); - } - break; - } // ESP_GATTS_STOP_EVT - - - // ESP_GATTS_CREATE_EVT - // Called when a new service is registered as having been created. - // - // create: - // * esp_gatt_status_t status - // * uint16_t service_handle - // * esp_gatt_srvc_id_t service_id - // * - esp_gatt_id id - // * - esp_bt_uuid uuid - // * - uint8_t inst_id - // * - bool is_primary - // - case ESP_GATTS_CREATE_EVT: { - if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid)) && m_id == param->create.service_id.id.inst_id) { - setHandle(param->create.service_handle); - m_semaphoreCreateEvt.give(); - } - break; - } // ESP_GATTS_CREATE_EVT - - - // ESP_GATTS_DELETE_EVT - // Called when a service is deleted. - // - // delete: - // * esp_gatt_status_t status - // * uint16_t service_handle - // - case ESP_GATTS_DELETE_EVT: { - if (param->del.service_handle == getHandle()) { - m_semaphoreDeleteEvt.give(); - } - break; - } // ESP_GATTS_DELETE_EVT - - default: { - break; - } // Default - } // Switch - - // Invoke the GATTS handler in each of the associated characteristics. - m_characteristicMap.handleGATTServerEvent(event, gatts_if, param); -} // handleGATTServerEvent - - -BLECharacteristic* BLEService::getCharacteristic(const char* uuid) { - return getCharacteristic(BLEUUID(uuid)); -} - - -BLECharacteristic* BLEService::getCharacteristic(BLEUUID uuid) { - return m_characteristicMap.getByUUID(uuid); -} - - -/** - * @brief Return a string representation of this service. - * A service is defined by: - * * Its UUID - * * Its handle - * @return A string representation of this service. - */ -std::string BLEService::toString() { - std::stringstream stringStream; - stringStream << "UUID: " << getUUID().toString() << - ", handle: 0x" << std::hex << std::setfill('0') << std::setw(2) << getHandle(); - return stringStream.str(); -} // toString - - -/** - * @brief Get the last created characteristic. - * It is lamentable that this function has to exist. It returns the last created characteristic. - * We need this because the descriptor API is built around the notion that a new descriptor, when created, - * is associated with the last characteristics created and we need that information. - * @return The last created characteristic. - */ -BLECharacteristic* BLEService::getLastCreatedCharacteristic() { - return m_lastCreatedCharacteristic; -} // getLastCreatedCharacteristic - - -/** - * @brief Get the BLE server associated with this service. - * @return The BLEServer associated with this service. - */ -BLEServer* BLEService::getServer() { - return m_pServer; -} // getServer - -#endif // CONFIG_BT_ENABLED +/* + * BLEService.cpp + * + * Created on: Mar 25, 2017 + * Author: kolban + */ + +// A service is identified by a UUID. A service is also the container for one or more characteristics. + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include + +#include +#include +#include + +#include "BLEServer.h" +#include "BLEService.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" + +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + +#define NULL_HANDLE (0xffff) + +static const char* LOG_TAG = "BLEService"; // Tag for logging. + +/** + * @brief Construct an instance of the BLEService + * @param [in] uuid The UUID of the service. + * @param [in] numHandles The maximum number of handles associated with the service. + */ +BLEService::BLEService(const char* uuid, uint32_t numHandles) : BLEService(BLEUUID(uuid), numHandles) { +} + + +/** + * @brief Construct an instance of the BLEService + * @param [in] uuid The UUID of the service. + * @param [in] numHandles The maximum number of handles associated with the service. + */ +BLEService::BLEService(BLEUUID uuid, uint32_t numHandles) { + m_uuid = uuid; + m_handle = NULL_HANDLE; + m_pServer = nullptr; + //m_serializeMutex.setName("BLEService"); + m_lastCreatedCharacteristic = nullptr; + m_numHandles = numHandles; +} // BLEService + + +/** + * @brief Create the service. + * Create the service. + * @param [in] gatts_if The handle of the GATT server interface. + * @return N/A. + */ + +void BLEService::executeCreate(BLEServer *pServer) { + //ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); + //getUUID(); // Needed for a weird bug fix + //char x[1000]; + //memcpy(x, &m_uuid, sizeof(m_uuid)); + //char x[10]; + //memcpy(x, &deleteMe, 10); + m_pServer = pServer; + m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT + + esp_gatt_srvc_id_t srvc_id; + srvc_id.is_primary = true; + srvc_id.id.inst_id = m_id; + srvc_id.id.uuid = *m_uuid.getNative(); + esp_err_t errRc = ::esp_ble_gatts_create_service( + getServer()->getGattsIf(), + &srvc_id, + m_numHandles // The maximum number of handles associated with the service. + ); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreCreateEvt.wait("executeCreate"); + ESP_LOGD(LOG_TAG, "<< executeCreate"); +} // executeCreate + + +/** + * @brief Delete the service. + * Delete the service. + * @return N/A. + */ + +void BLEService::executeDelete() { + ESP_LOGD(LOG_TAG, ">> executeDelete()"); + m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_DELETE_EVT + + esp_err_t errRc = ::esp_ble_gatts_delete_service( getHandle() ); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_delete_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreDeleteEvt.wait("executeDelete"); + ESP_LOGD(LOG_TAG, "<< executeDelete"); +} // executeDelete + + +/** + * @brief Dump details of this BLE GATT service. + * @return N/A. + */ +void BLEService::dump() { + ESP_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%.2x", + m_uuid.toString().c_str(), + m_handle); + ESP_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str()); +} // dump + + +/** + * @brief Get the UUID of the service. + * @return the UUID of the service. + */ +BLEUUID BLEService::getUUID() { + return m_uuid; +} // getUUID + + +/** + * @brief Start the service. + * Here we wish to start the service which means that we will respond to partner requests about it. + * Starting a service also means that we can create the corresponding characteristics. + * @return Start the service. + */ +void BLEService::start() { +// We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). +// + ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); + if (m_handle == NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!"); + return; + } + + + BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); + + while(pCharacteristic != nullptr) { + m_lastCreatedCharacteristic = pCharacteristic; + pCharacteristic->executeCreate(this); + + pCharacteristic = m_characteristicMap.getNext(); + } + // Start each of the characteristics ... these are found in the m_characteristicMap. + + m_semaphoreStartEvt.take("start"); + esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreStartEvt.wait("start"); + + ESP_LOGD(LOG_TAG, "<< start()"); +} // start + + +/** + * @brief Stop the service. + * @return Stop the service. + */ +void BLEService::stop() { +// We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). +// + ESP_LOGD(LOG_TAG, ">> stop(): Stopping service (esp_ble_gatts_stop_service): %s", toString().c_str()); + if (m_handle == NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "<< !!! We attempted to stop a service but don't know its handle!"); + return; + } + + m_semaphoreStopEvt.take("stop"); + esp_err_t errRc = ::esp_ble_gatts_stop_service(m_handle); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_stop_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreStopEvt.wait("stop"); + + ESP_LOGD(LOG_TAG, "<< stop()"); +} // start + + +/** + * @brief Set the handle associated with this service. + * @param [in] handle The handle associated with the service. + */ +void BLEService::setHandle(uint16_t handle) { + ESP_LOGD(LOG_TAG, ">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str()); + if (m_handle != NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "!!! Handle is already set %.2x", m_handle); + return; + } + m_handle = handle; + ESP_LOGD(LOG_TAG, "<< setHandle"); +} // setHandle + + +/** + * @brief Get the handle associated with this service. + * @return The handle associated with this service. + */ +uint16_t BLEService::getHandle() { + return m_handle; +} // getHandle + + +/** + * @brief Add a characteristic to the service. + * @param [in] pCharacteristic A pointer to the characteristic to be added. + */ +void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { +// We maintain a mapping of characteristics owned by this service. These are managed by the +// BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic +// to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF). +// + ESP_LOGD(LOG_TAG, ">> addCharacteristic()"); + ESP_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s", + pCharacteristic->getUUID().toString().c_str(), + toString().c_str()); + + // Check that we don't add the same characteristic twice. + if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { + ESP_LOGW(LOG_TAG, "<< Adding a new characteristic with the same UUID as a previous one"); + //return; + } + + // Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID + // but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT. + m_characteristicMap.setByUUID(pCharacteristic, pCharacteristic->getUUID()); + + ESP_LOGD(LOG_TAG, "<< addCharacteristic()"); +} // addCharacteristic + + +/** + * @brief Create a new BLE Characteristic associated with this service. + * @param [in] uuid - The UUID of the characteristic. + * @param [in] properties - The properties of the characteristic. + * @return The new BLE characteristic. + */ +BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) { + return createCharacteristic(BLEUUID(uuid), properties); +} + + +/** + * @brief Create a new BLE Characteristic associated with this service. + * @param [in] uuid - The UUID of the characteristic. + * @param [in] properties - The properties of the characteristic. + * @return The new BLE characteristic. + */ +BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) { + BLECharacteristic *pCharacteristic = new BLECharacteristic(uuid, properties); + addCharacteristic(pCharacteristic); + return pCharacteristic; +} // createCharacteristic + + +/** + * @brief Handle a GATTS server event. + */ +void BLEService::handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param) { + + + switch(event) { + // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. + // add_char: + // - esp_gatt_status_t status + // - uint16_t attr_handle + // - uint16_t service_handle + // - esp_bt_uuid_t char_uuid + + // If we have reached the correct service, then locate the characteristic and remember the handle + // for that characteristic. + case ESP_GATTS_ADD_CHAR_EVT: { + if (m_handle == param->add_char.service_handle) { + BLECharacteristic *pCharacteristic = getLastCreatedCharacteristic(); + if (pCharacteristic == nullptr) { + ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", + BLEUUID(param->add_char.char_uuid).toString().c_str()); + dump(); + break; + } + pCharacteristic->setHandle(param->add_char.attr_handle); + m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic); + //ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str()); + break; + } // Reached the correct service. + break; + } // ESP_GATTS_ADD_CHAR_EVT + + + // ESP_GATTS_START_EVT + // + // start: + // esp_gatt_status_t status + // uint16_t service_handle + case ESP_GATTS_START_EVT: { + if (param->start.service_handle == getHandle()) { + m_semaphoreStartEvt.give(); + } + break; + } // ESP_GATTS_START_EVT + + // ESP_GATTS_STOP_EVT + // + // stop: + // esp_gatt_status_t status + // uint16_t service_handle + // + case ESP_GATTS_STOP_EVT: { + if (param->stop.service_handle == getHandle()) { + m_semaphoreStopEvt.give(); + } + break; + } // ESP_GATTS_STOP_EVT + + + // ESP_GATTS_CREATE_EVT + // Called when a new service is registered as having been created. + // + // create: + // * esp_gatt_status_t status + // * uint16_t service_handle + // * esp_gatt_srvc_id_t service_id + // * - esp_gatt_id id + // * - esp_bt_uuid uuid + // * - uint8_t inst_id + // * - bool is_primary + // + case ESP_GATTS_CREATE_EVT: { + if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid)) && m_id == param->create.service_id.id.inst_id) { + setHandle(param->create.service_handle); + m_semaphoreCreateEvt.give(); + } + break; + } // ESP_GATTS_CREATE_EVT + + + // ESP_GATTS_DELETE_EVT + // Called when a service is deleted. + // + // delete: + // * esp_gatt_status_t status + // * uint16_t service_handle + // + case ESP_GATTS_DELETE_EVT: { + if (param->del.service_handle == getHandle()) { + m_semaphoreDeleteEvt.give(); + } + break; + } // ESP_GATTS_DELETE_EVT + + default: { + break; + } // Default + } // Switch + + // Invoke the GATTS handler in each of the associated characteristics. + m_characteristicMap.handleGATTServerEvent(event, gatts_if, param); +} // handleGATTServerEvent + + +BLECharacteristic* BLEService::getCharacteristic(const char* uuid) { + return getCharacteristic(BLEUUID(uuid)); +} + + +BLECharacteristic* BLEService::getCharacteristic(BLEUUID uuid) { + return m_characteristicMap.getByUUID(uuid); +} + + +/** + * @brief Return a string representation of this service. + * A service is defined by: + * * Its UUID + * * Its handle + * @return A string representation of this service. + */ +std::string BLEService::toString() { + std::stringstream stringStream; + stringStream << "UUID: " << getUUID().toString() << + ", handle: 0x" << std::hex << std::setfill('0') << std::setw(2) << getHandle(); + return stringStream.str(); +} // toString + + +/** + * @brief Get the last created characteristic. + * It is lamentable that this function has to exist. It returns the last created characteristic. + * We need this because the descriptor API is built around the notion that a new descriptor, when created, + * is associated with the last characteristics created and we need that information. + * @return The last created characteristic. + */ +BLECharacteristic* BLEService::getLastCreatedCharacteristic() { + return m_lastCreatedCharacteristic; +} // getLastCreatedCharacteristic + + +/** + * @brief Get the BLE server associated with this service. + * @return The BLEServer associated with this service. + */ +BLEServer* BLEService::getServer() { + return m_pServer; +} // getServer + +#endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index d2ab0759..93b4b2c6 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -1,104 +1,104 @@ -/* - * BLEService.h - * - * Created on: Mar 25, 2017 - * Author: kolban - */ - -#ifndef COMPONENTS_CPP_UTILS_BLESERVICE_H_ -#define COMPONENTS_CPP_UTILS_BLESERVICE_H_ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) - -#include - -#include "BLECharacteristic.h" -#include "BLEServer.h" -#include "BLEUUID.h" -#include "FreeRTOS.h" - -class BLEServer; - -/** - * @brief A data mapping used to manage the set of %BLE characteristics known to the server. - */ -class BLECharacteristicMap { -public: - void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid); - void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid); - void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); - BLECharacteristic* getByUUID(const char* uuid); - BLECharacteristic* getByUUID(BLEUUID uuid); - BLECharacteristic* getByHandle(uint16_t handle); - BLECharacteristic* getFirst(); - BLECharacteristic* getNext(); - std::string toString(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param); - - -private: - std::map m_uuidMap; - std::map m_handleMap; - std::map::iterator m_iterator; -}; - - -/** - * @brief The model of a %BLE service. - * - */ -class BLEService { -public: - void addCharacteristic(BLECharacteristic* pCharacteristic); - BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties); - BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); - void dump(); - void executeCreate(BLEServer* pServer); - void executeDelete(); - BLECharacteristic* getCharacteristic(const char* uuid); - BLECharacteristic* getCharacteristic(BLEUUID uuid); - BLEUUID getUUID(); - BLEServer* getServer(); - void start(); - void stop(); - std::string toString(); - uint16_t getHandle(); - uint8_t m_id = 0; - -private: - BLEService(const char* uuid, uint32_t numHandles); - BLEService(BLEUUID uuid, uint32_t numHandles); - friend class BLEServer; - friend class BLEServiceMap; - friend class BLEDescriptor; - friend class BLECharacteristic; - friend class BLEDevice; - - BLECharacteristicMap m_characteristicMap; - uint16_t m_handle; - BLECharacteristic* m_lastCreatedCharacteristic; - BLEServer* m_pServer; - BLEUUID m_uuid; - - FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); - FreeRTOS::Semaphore m_semaphoreDeleteEvt = FreeRTOS::Semaphore("DeleteEvt"); - FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); - FreeRTOS::Semaphore m_semaphoreStopEvt = FreeRTOS::Semaphore("StopEvt"); - - uint32_t m_numHandles; - - BLECharacteristic* getLastCreatedCharacteristic(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param); - void setHandle(uint16_t handle); - //void setService(esp_gatt_srvc_id_t srvc_id); -}; // BLEService - - -#endif // CONFIG_BT_ENABLED -#endif /* COMPONENTS_CPP_UTILS_BLESERVICE_H_ */ +/* + * BLEService.h + * + * Created on: Mar 25, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESERVICE_H_ +#define COMPONENTS_CPP_UTILS_BLESERVICE_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include + +#include "BLECharacteristic.h" +#include "BLEServer.h" +#include "BLEUUID.h" +#include "FreeRTOS.h" + +class BLEServer; + +/** + * @brief A data mapping used to manage the set of %BLE characteristics known to the server. + */ +class BLECharacteristicMap { +public: + void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid); + void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid); + void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); + BLECharacteristic* getByUUID(const char* uuid); + BLECharacteristic* getByUUID(BLEUUID uuid); + BLECharacteristic* getByHandle(uint16_t handle); + BLECharacteristic* getFirst(); + BLECharacteristic* getNext(); + std::string toString(); + void handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param); + + +private: + std::map m_uuidMap; + std::map m_handleMap; + std::map::iterator m_iterator; +}; + + +/** + * @brief The model of a %BLE service. + * + */ +class BLEService { +public: + void addCharacteristic(BLECharacteristic* pCharacteristic); + BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties); + BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); + void dump(); + void executeCreate(BLEServer* pServer); + void executeDelete(); + BLECharacteristic* getCharacteristic(const char* uuid); + BLECharacteristic* getCharacteristic(BLEUUID uuid); + BLEUUID getUUID(); + BLEServer* getServer(); + void start(); + void stop(); + std::string toString(); + uint16_t getHandle(); + uint8_t m_id = 0; + +private: + BLEService(const char* uuid, uint32_t numHandles); + BLEService(BLEUUID uuid, uint32_t numHandles); + friend class BLEServer; + friend class BLEServiceMap; + friend class BLEDescriptor; + friend class BLECharacteristic; + friend class BLEDevice; + + BLECharacteristicMap m_characteristicMap; + uint16_t m_handle; + BLECharacteristic* m_lastCreatedCharacteristic; + BLEServer* m_pServer; + BLEUUID m_uuid; + + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreDeleteEvt = FreeRTOS::Semaphore("DeleteEvt"); + FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); + FreeRTOS::Semaphore m_semaphoreStopEvt = FreeRTOS::Semaphore("StopEvt"); + + uint32_t m_numHandles; + + BLECharacteristic* getLastCreatedCharacteristic(); + void handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param); + void setHandle(uint16_t handle); + //void setService(esp_gatt_srvc_id_t srvc_id); +}; // BLEService + + +#endif // CONFIG_BT_ENABLED +#endif /* COMPONENTS_CPP_UTILS_BLESERVICE_H_ */ diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index 179de7a8..dd828fae 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -1,132 +1,132 @@ -/* - * BLEServiceMap.cpp - * - * Created on: Jun 22, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include -#include -#include "BLEService.h" - - -/** - * @brief Return the service by UUID. - * @param [in] UUID The UUID to look up the service. - * @return The characteristic. - */ -BLEService* BLEServiceMap::getByUUID(const char* uuid) { - return getByUUID(BLEUUID(uuid)); -} - -/** - * @brief Return the service by UUID. - * @param [in] UUID The UUID to look up the service. - * @return The characteristic. - */ -BLEService* BLEServiceMap::getByUUID(BLEUUID uuid) { - for (auto &myPair : m_uuidMap) { - if (myPair.first->getUUID().equals(uuid)) { - return myPair.first; - } - } - //return m_uuidMap.at(uuid.toString()); - return nullptr; -} // getByUUID - - -/** - * @brief Return the service by handle. - * @param [in] handle The handle to look up the service. - * @return The service. - */ -BLEService* BLEServiceMap::getByHandle(uint16_t handle) { - return m_handleMap.at(handle); -} // getByHandle - - -/** - * @brief Set the service by UUID. - * @param [in] uuid The uuid of the service. - * @param [in] characteristic The service to cache. - * @return N/A. - */ -void BLEServiceMap::setByUUID(BLEUUID uuid, - BLEService *service) { - m_uuidMap.insert(std::pair(service, uuid.toString())); -} // setByUUID - - -/** - * @brief Set the service by handle. - * @param [in] handle The handle of the service. - * @param [in] service The service to cache. - * @return N/A. - */ -void BLEServiceMap::setByHandle(uint16_t handle, - BLEService* service) { - m_handleMap.insert(std::pair(handle, service)); -} // setByHandle - - -/** - * @brief Return a string representation of the service map. - * @return A string representation of the service map. - */ -std::string BLEServiceMap::toString() { - std::stringstream stringStream; - stringStream << std::hex << std::setfill('0'); - for (auto &myPair: m_handleMap) { - stringStream << "handle: 0x" << std::setw(2) << myPair.first << ", uuid: " + myPair.second->getUUID().toString() << "\n"; - } - return stringStream.str(); -} // toString - -void BLEServiceMap::handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { - // Invoke the handler for every Service we have. - for (auto &myPair : m_uuidMap) { - myPair.first->handleGATTServerEvent(event, gatts_if, param); - } -} - -/** - * @brief Get the first service in the map. - * @return The first service in the map. - */ -BLEService* BLEServiceMap::getFirst() { - m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLEService* pRet = m_iterator->first; - m_iterator++; - return pRet; -} // getFirst - -/** - * @brief Get the next service in the map. - * @return The next service in the map. - */ -BLEService* BLEServiceMap::getNext() { - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLEService* pRet = m_iterator->first; - m_iterator++; - return pRet; -} // getNext - -/** - * @brief Removes service from maps. - * @return N/A. - */ -void BLEServiceMap::removeService(BLEService *service){ - m_handleMap.erase(service->getHandle()); - m_uuidMap.erase(service); -} // removeService - -#endif /* CONFIG_BT_ENABLED */ +/* + * BLEServiceMap.cpp + * + * Created on: Jun 22, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEService.h" + + +/** + * @brief Return the service by UUID. + * @param [in] UUID The UUID to look up the service. + * @return The characteristic. + */ +BLEService* BLEServiceMap::getByUUID(const char* uuid) { + return getByUUID(BLEUUID(uuid)); +} + +/** + * @brief Return the service by UUID. + * @param [in] UUID The UUID to look up the service. + * @return The characteristic. + */ +BLEService* BLEServiceMap::getByUUID(BLEUUID uuid) { + for (auto &myPair : m_uuidMap) { + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; + } + } + //return m_uuidMap.at(uuid.toString()); + return nullptr; +} // getByUUID + + +/** + * @brief Return the service by handle. + * @param [in] handle The handle to look up the service. + * @return The service. + */ +BLEService* BLEServiceMap::getByHandle(uint16_t handle) { + return m_handleMap.at(handle); +} // getByHandle + + +/** + * @brief Set the service by UUID. + * @param [in] uuid The uuid of the service. + * @param [in] characteristic The service to cache. + * @return N/A. + */ +void BLEServiceMap::setByUUID(BLEUUID uuid, + BLEService *service) { + m_uuidMap.insert(std::pair(service, uuid.toString())); +} // setByUUID + + +/** + * @brief Set the service by handle. + * @param [in] handle The handle of the service. + * @param [in] service The service to cache. + * @return N/A. + */ +void BLEServiceMap::setByHandle(uint16_t handle, + BLEService* service) { + m_handleMap.insert(std::pair(handle, service)); +} // setByHandle + + +/** + * @brief Return a string representation of the service map. + * @return A string representation of the service map. + */ +std::string BLEServiceMap::toString() { + std::stringstream stringStream; + stringStream << std::hex << std::setfill('0'); + for (auto &myPair: m_handleMap) { + stringStream << "handle: 0x" << std::setw(2) << myPair.first << ", uuid: " + myPair.second->getUUID().toString() << "\n"; + } + return stringStream.str(); +} // toString + +void BLEServiceMap::handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param) { + // Invoke the handler for every Service we have. + for (auto &myPair : m_uuidMap) { + myPair.first->handleGATTServerEvent(event, gatts_if, param); + } +} + +/** + * @brief Get the first service in the map. + * @return The first service in the map. + */ +BLEService* BLEServiceMap::getFirst() { + m_iterator = m_uuidMap.begin(); + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } + BLEService* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getFirst + +/** + * @brief Get the next service in the map. + * @return The next service in the map. + */ +BLEService* BLEServiceMap::getNext() { + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } + BLEService* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getNext + +/** + * @brief Removes service from maps. + * @return N/A. + */ +void BLEServiceMap::removeService(BLEService *service){ + m_handleMap.erase(service->getHandle()); + m_uuidMap.erase(service); +} // removeService + +#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 81d37c36..09d59d55 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -1,904 +1,904 @@ -/* - * WiFi.cpp - * - * Created on: Feb 25, 2017 - * Author: kolban - */ - -//#define _GLIBCXX_USE_C99 -#include -#include -#include -#include -#include "sdkconfig.h" - - -#include "WiFi.h" -#include -#include -#include -#include -#include -#include "GeneralUtils.h" -#include -#include -#include -#include -#include - -#include - - -static const char* LOG_TAG = "WiFi"; - - -/* -static void setDNSServer(char *ip) { - ip_addr_t dnsserver; - ESP_LOGD(tag, "Setting DNS[%d] to %s", 0, ip); - inet_pton(AF_INET, ip, &dnsserver); - ESP_LOGD(tag, "ip of DNS is %.8x", *(uint32_t *)&dnsserver); - dns_setserver(0, &dnsserver); -} -*/ - - -/** - * @brief Creates and uses a default event handler - */ -WiFi::WiFi() - : ip(0) - , gw(0) - , netmask(0) - , m_pWifiEventHandler(nullptr) -{ - m_eventLoopStarted = false; - m_initCalled = false; - //m_pWifiEventHandler = new WiFiEventHandler(); - m_apConnectionStatus = UINT8_MAX; // Are we connected to an access point? -} // WiFi - - -/** - * @brief Deletes the event handler that was used by the class - */ -WiFi::~WiFi() { - if (m_pWifiEventHandler != nullptr) { - delete m_pWifiEventHandler; - m_pWifiEventHandler = nullptr; - } -} // ~WiFi - - -/** - * @brief Add a reference to a DNS server. - * - * Here we define a server that will act as a DNS server. We can add two DNS - * servers in total. The first will be the primary, the second will be the backup. - * The public Google DNS servers are "8.8.8.8" and "8.8.4.4". - * - * For example: - * - * @code{.cpp} - * wifi.addDNSServer("8.8.8.8"); - * wifi.addDNSServer("8.8.4.4"); - * @endcode - * - * @param [in] ip The IP address of the DNS Server. - * @return N/A. - */ -void WiFi::addDNSServer(const std::string& ip) { - addDNSServer(ip.c_str()); -} // addDNSServer - - -void WiFi::addDNSServer(const char* ip) { - ip_addr_t dns_server; - if(inet_pton(AF_INET, ip, &dns_server)) { - addDNSServer(ip); - } -} // addDNSServer - - -void WiFi::addDNSServer(ip_addr_t ip) { - ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); - init(); - ::dns_setserver(m_dnsCount, &ip); - m_dnsCount++; - m_dnsCount %= 2; -} // addDNSServer - - -/** - * @brief Set a reference to a DNS server. - * - * Here we define a server that will act as a DNS server. We use numdns to specify which DNS server to set - * - * For example: - * - * @code{.cpp} - * wifi.setDNSServer(0, "8.8.8.8"); - * wifi.setDNSServer(1, "8.8.4.4"); - * @endcode - * - * @param [in] numdns The DNS number we wish to set - * @param [in] ip The IP address of the DNS Server. - * @return N/A. - */ -void WiFi::setDNSServer(int numdns, const std::string& ip) { - setDNSServer(numdns, ip.c_str()); -} // setDNSServer - - -void WiFi::setDNSServer(int numdns, const char* ip) { - ip_addr_t dns_server; - if(inet_pton(AF_INET, ip, &dns_server)) { - setDNSServer(numdns, dns_server); - } -} // setDNSServer - - -void WiFi::setDNSServer(int numdns, ip_addr_t ip) { - ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); - init(); - ::dns_setserver(numdns, &ip); -} // setDNSServer - - -/** - * @brief Connect to an external access point. - * - * The event handler will be called back with the outcome of the connection. - * - * @param [in] ssid The network SSID of the access point to which we wish to connect. - * @param [in] password The password of the access point to which we wish to connect. - * @param [in] waitForConnection Block until the connection has an outcome. - * @param [in] mode WIFI_MODE_AP for normal or WIFI_MODE_APSTA if you want to keep an Access Point running while you connect - * @return ESP_OK if we are now connected and wifi_err_reason_t if not. - */ -uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection, wifi_mode_t mode){ - ESP_LOGD(LOG_TAG, ">> connectAP"); - - m_apConnectionStatus = UINT8_MAX; - init(); - - if (ip != 0 && gw != 0 && netmask != 0) { - ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client - - tcpip_adapter_ip_info_t ipInfo; - ipInfo.ip.addr = ip; - ipInfo.gw.addr = gw; - ipInfo.netmask.addr = netmask; - - ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - } - - esp_err_t errRc = ::esp_wifi_set_mode(mode); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - wifi_config_t sta_config; - ::memset(&sta_config, 0, sizeof(sta_config)); - ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); - ::memcpy(sta_config.sta.password, password.data(), password.size()); - sta_config.sta.bssid_set = 0; - errRc = ::esp_wifi_set_config(WIFI_IF_STA, &sta_config); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - errRc = ::esp_wifi_start(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - m_connectFinished.take("connectAP"); // Take the semaphore to wait for a connection. - do { - ESP_LOGD(LOG_TAG, "esp_wifi_connect"); - errRc = ::esp_wifi_connect(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - } - while (!m_connectFinished.take(5000, "connectAP")); // retry if not connected within 5s - m_connectFinished.give(); - - ESP_LOGD(LOG_TAG, "<< connectAP"); - return m_apConnectionStatus; // Return ESP_OK if we are now connected and wifi_err_reason_t if not. -} // connectAP - - - -/** - * @brief Dump diagnostics to the log. - */ -void WiFi::dump() { - ESP_LOGD(LOG_TAG, "WiFi Dump"); - ESP_LOGD(LOG_TAG, "---------"); - char ipAddrStr[30]; - ip_addr_t ip = ::dns_getserver(0); - inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); - ESP_LOGD(LOG_TAG, "DNS Server[0]: %s", ipAddrStr); -} // dump - - -/** - * @brief Returns whether wifi is connected to an access point - */ -bool WiFi::isConnectedToAP() { - return m_apConnectionStatus; -} // isConnected - - - -/** - * @brief Primary event handler interface. - */ -/* STATIC */ esp_err_t WiFi::eventHandler(void* ctx, system_event_t* event) { - // This is the common event handler that we have provided for all event processing. It is called for every event - // that is received by the WiFi subsystem. The "ctx" parameter is an instance of the current WiFi object that we are - // processing. We can then retrieve the specific/custom event handler from within it and invoke that. This then makes this - // an indirection vector to the real caller. - - WiFi *pWiFi = (WiFi *)ctx; // retrieve the WiFi object from the passed in context. - - // Invoke the event handler. - esp_err_t rc; - if (pWiFi->m_pWifiEventHandler != nullptr) { - rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); - } else { - rc = ESP_OK; - } - - // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that - // indicates we are waiting for a connection complete. - if (event->event_id == SYSTEM_EVENT_STA_GOT_IP || event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { - - if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { // If we connected and have an IP, change the status to ESP_OK. Otherwise, change it to the reason code. - pWiFi->m_apConnectionStatus = ESP_OK; - } else { - pWiFi->m_apConnectionStatus = event->event_info.disconnected.reason; - } - pWiFi->m_connectFinished.give(); - } - - return rc; -} // eventHandler - - -/** - * @brief Get the AP IP Info. - * @return The AP IP Info. - */ -tcpip_adapter_ip_info_t WiFi::getApIpInfo() { - //init(); - tcpip_adapter_ip_info_t ipInfo; - ::tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); - return ipInfo; -} // getApIpInfo - - -/** - * @brief Get the MAC address of the AP interface. - * @return The MAC address of the AP interface. - */ -std::string WiFi::getApMac() { - uint8_t mac[6]; - //init(); - esp_wifi_get_mac(WIFI_IF_AP, mac); - auto mac_str = (char*) malloc(18); - sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(std::move(mac_str)); -} // getApMac - - -/** - * @brief Get the AP SSID. - * @return The AP SSID. - */ -std::string WiFi::getApSSID() { - wifi_config_t conf; - //init(); - esp_wifi_get_config(WIFI_IF_AP, &conf); - return std::string((char *)conf.sta.ssid); -} // getApSSID - -/** - * @brief Get the current ESP32 IP form AP. - * @return The ESP32 IP. - */ -std::string WiFi::getApIp(){ - tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaIp - -/** - * @brief Get the current AP netmask. - * @return The Netmask IP. - */ -std::string WiFi::getApNetmask(){ - tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaNetmask - -/** - * @brief Get the current AP Gateway IP. - * @return The Gateway IP. - */ -std::string WiFi::getApGateway(){ - tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaGateway - -/** - * @brief Lookup an IP address by host name. - * - * @param [in] hostName The hostname to resolve. - * - * @return The IP address of the host or 0.0.0.0 if not found. - */ -struct in_addr WiFi::getHostByName(const std::string& hostName) { - return getHostByName(hostName.c_str()); -} // getHostByName - - -struct in_addr WiFi::getHostByName(const char* hostName) { - struct in_addr retAddr; - struct hostent *he = gethostbyname(hostName); - if (he == nullptr) { - retAddr.s_addr = 0; - ESP_LOGD(LOG_TAG, "Unable to resolve %s - %d", hostName, h_errno); - } else { - retAddr = *(struct in_addr *)(he->h_addr_list[0]); - ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); - } - return retAddr; -} // getHostByName - - -/** - * @brief Get the WiFi Mode. - * @return The WiFi Mode. - */ -std::string WiFi::getMode() { - wifi_mode_t mode; - esp_wifi_get_mode(&mode); - switch(mode) { - case WIFI_MODE_NULL: - return "WIFI_MODE_NULL"; - case WIFI_MODE_STA: - return "WIFI_MODE_STA"; - case WIFI_MODE_AP: - return "WIFI_MODE_AP"; - case WIFI_MODE_APSTA: - return "WIFI_MODE_APSTA"; - default: - return "unknown"; - } -} // getMode - - -/** - * @brief Get the STA IP Info. - * @return The STA IP Info. - */ -tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { - tcpip_adapter_ip_info_t ipInfo; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - return ipInfo; -} // getStaIpInfo - -/** - * @brief Get the current ESP32 IP form STA. - * @return The ESP32 IP. - */ -std::string WiFi::getStaIp(){ - tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaIp - - -/** - * @brief Get the current STA netmask. - * @return The Netmask IP. - */ -std::string WiFi::getStaNetmask(){ - tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaNetmask - - -/** - * @brief Get the current STA Gateway IP. - * @return The Gateway IP. - */ -std::string WiFi::getStaGateway(){ - tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); - char ipAddrStr[30]; - inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); - return std::string(ipAddrStr); -} // getStaGateway - -/** - * @brief Get the MAC address of the STA interface. - * @return The MAC address of the STA interface. - */ -std::string WiFi::getStaMac() { - uint8_t mac[6]; - esp_wifi_get_mac(WIFI_IF_STA, mac); - auto mac_str = (char*) malloc(18); - sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return std::string(std::move(mac_str)); -} // getStaMac - - -/** - * @brief Get the STA SSID. - * @return The STA SSID. - */ -std::string WiFi::getStaSSID() { - wifi_config_t conf; - esp_wifi_get_config(WIFI_IF_STA, &conf); - return std::string((char *)conf.ap.ssid); -} // getStaSSID - - -/** - * @brief Initialize WiFi. - */ -/* PRIVATE */ void WiFi::init() { - - // If we have already started the event loop, then change the handler otherwise - // start the event loop. - if (m_eventLoopStarted) { - esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. - } else { - esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - m_eventLoopStarted = true; - } - // Now, one way or another, the event handler is WiFi::eventHandler. - - if (!m_initCalled) { - ::nvs_flash_init(); - ::tcpip_adapter_init(); - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - esp_err_t errRc = ::esp_wifi_init(&cfg); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - } - m_initCalled = true; -} // init - - -/** - * @brief Perform a WiFi scan looking for access points. - * - * An access point scan is performed and a vector of WiFi access point records - * is built and returned with one record per found scan instance. The scan is - * performed in a blocking fashion and will not return until the set of scanned - * access points has been built. - * - * @return A vector of WiFiAPRecord instances. - */ -std::vector WiFi::scan() { - ESP_LOGD(LOG_TAG, ">> scan"); - std::vector apRecords; - - init(); - - esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - errRc = ::esp_wifi_start(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - wifi_scan_config_t conf; - memset(&conf, 0, sizeof(conf)); - conf.show_hidden = true; - - esp_err_t rc = ::esp_wifi_scan_start(&conf, true); - if (rc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); - return apRecords; - } - - uint16_t apCount; // Number of access points available. - rc = ::esp_wifi_scan_get_ap_num(&apCount); - ESP_LOGD(LOG_TAG, "Count of found access points: %d", apCount); - - wifi_ap_record_t* list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); - if (list == nullptr) { - ESP_LOGE(LOG_TAG, "Failed to allocate memory"); - return apRecords; - } - - errRc = ::esp_wifi_scan_get_ap_records(&apCount, list); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_scan_get_ap_records: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - for (auto i=0; i rhs.m_rssi;}); - return apRecords; -} // scan - - -/** - * @brief Start being an access point. - * - * @param[in] ssid The SSID to use to advertize for stations. - * @param[in] password The password to use for station connections. - * @param[in] auth The authorization mode for access to this access point. Options are: - * * WIFI_AUTH_OPEN - * * WIFI_AUTH_WPA_PSK - * * WIFI_AUTH_WPA2_PSK - * * WIFI_AUTH_WPA_WPA2_PSK - * * WIFI_AUTH_WPA2_ENTERPRISE - * * WIFI_AUTH_WEP - * @return N/A. - */ -void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth) { - startAP(ssid, password, auth, 0, false, 4); -} // startAP - -/** - * @brief Start being an access point. - * - * @param[in] ssid The SSID to use to advertize for stations. - * @param[in] password The password to use for station connections. - * @param[in] auth The authorization mode for access to this access point. Options are: - * * WIFI_AUTH_OPEN - * * WIFI_AUTH_WPA_PSK - * * WIFI_AUTH_WPA2_PSK - * * WIFI_AUTH_WPA_WPA2_PSK - * * WIFI_AUTH_WPA2_ENTERPRISE - * * WIFI_AUTH_WEP - * @param[in] channel from the access point. - * @param[in] is the ssid hidden, ore not. - * @param[in] limiting number of clients. - * @return N/A. - */ -void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection) { - ESP_LOGD(LOG_TAG, ">> startAP: ssid: %s", ssid.c_str()); - - init(); - - esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_AP); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - // Build the apConfig structure. - wifi_config_t apConfig; - ::memset(&apConfig, 0, sizeof(apConfig)); - ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); - apConfig.ap.ssid_len = ssid.size(); - ::memcpy(apConfig.ap.password, password.data(), password.size()); - apConfig.ap.channel = channel; - apConfig.ap.authmode = auth; - apConfig.ap.ssid_hidden = (uint8_t) ssid_hidden; - apConfig.ap.max_connection = max_connection; - apConfig.ap.beacon_interval = 100; - - errRc = ::esp_wifi_set_config(WIFI_IF_AP, &apConfig); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - errRc = ::esp_wifi_start(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - - errRc = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "tcpip_adapter_dhcps_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - - ESP_LOGD(LOG_TAG, "<< startAP"); -} // startAP - -/** - * @brief Set the event handler to use to process detected events. - * @param[in] wifiEventHandler The class that will be used to process events. - */ -void WiFi::setWifiEventHandler(WiFiEventHandler* wifiEventHandler) { - ESP_LOGD(LOG_TAG, ">> setWifiEventHandler: 0x%d", (uint32_t)wifiEventHandler); - this->m_pWifiEventHandler = wifiEventHandler; - ESP_LOGD(LOG_TAG, "<< setWifiEventHandler"); -} // setWifiEventHandler - - -/** - * @brief Set the IP info and enable DHCP if ip != 0. If called with ip == 0 then DHCP is enabled. - * If called with bad values it will do nothing. - * - * Do not call this method if we are being an access point ourselves. - * - * For example, prior to calling `connectAP()` we could invoke: - * - * @code{.cpp} - * myWifi.setIPInfo("192.168.1.99", "192.168.1.1", "255.255.255.0"); - * @endcode - * - * @param [in] ip IP address value. - * @param [in] gw Gateway value. - * @param [in] netmask Netmask value. - * @return N/A. - */ -void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask) { - setIPInfo(ip.c_str(), gw.c_str(), netmask.c_str()); -} // setIPInfo - - - -void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { - uint32_t new_ip; - uint32_t new_gw; - uint32_t new_netmask; - - auto success = (bool)inet_pton(AF_INET, ip, &new_ip); - success = success && inet_pton(AF_INET, gw, &new_gw); - success = success && inet_pton(AF_INET, netmask, &new_netmask); - - if(!success) { - return; - } - - setIPInfo(new_ip, new_gw, new_netmask); -} // setIPInfo - - -/** - * @brief Set the IP Info based on the IP address, gateway and netmask. - * @param [in] ip The IP address of our ESP32. - * @param [in] gw The gateway we should use. - * @param [in] netmask Our TCP/IP netmask value. - */ -void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { - init(); - - this->ip = ip; - this->gw = gw; - this->netmask = netmask; - - if(ip != 0 && gw != 0 && netmask != 0) { - tcpip_adapter_ip_info_t ipInfo; - ipInfo.ip.addr = ip; - ipInfo.gw.addr = gw; - ipInfo.netmask.addr = netmask; - ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); - } else { - ip = 0; - ::tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); - } -} // setIPInfo - - -/** - * @brief Return a string representation of the WiFi access point record. - * - * @return A string representation of the WiFi access point record. - */ -std::string WiFiAPRecord::toString() { - std::string auth; - switch(getAuthMode()) { - case WIFI_AUTH_OPEN: - auth = "WIFI_AUTH_OPEN"; - break; - case WIFI_AUTH_WEP: - auth = "WIFI_AUTH_WEP"; - break; - case WIFI_AUTH_WPA_PSK: - auth = "WIFI_AUTH_WPA_PSK"; - break; - case WIFI_AUTH_WPA2_PSK: - auth = "WIFI_AUTH_WPA2_PSK"; - break; - case WIFI_AUTH_WPA_WPA2_PSK: - auth = "WIFI_AUTH_WPA_WPA2_PSK"; - break; - default: - auth = ""; - break; - } -// std::stringstream s; -// s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; - auto info_str = (char*) malloc(6 + 32 + 8 + 22 + 8 + 3 + 1); - sprintf(info_str, "ssid: %s, auth: %s, rssi: %d", m_ssid.c_str(), auth.c_str(), (int) m_rssi); - return std::string(std::move(info_str)); -} // toString - -/* -MDNS::MDNS() { - esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} - -MDNS::~MDNS() { - if (m_mdns_server != nullptr) { - mdns_free(m_mdns_server); - } - m_mdns_server = nullptr; -} -*/ - -/** - * @brief Define the service for mDNS. - * - * @param [in] service - * @param [in] proto - * @param [in] port - * @return N/A. - */ -/* -void MDNS::serviceAdd(const std::string& service, const std::string& proto, uint16_t port) { - serviceAdd(service.c_str(), proto.c_str(), port); -} // serviceAdd - - -void MDNS::serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance) { - serviceInstanceSet(service.c_str(), proto.c_str(), instance.c_str()); -} // serviceInstanceSet - - -void MDNS::servicePortSet(const std::string& service, const std::string& proto, uint16_t port) { - servicePortSet(service.c_str(), proto.c_str(), port); -} // servicePortSet - - -void MDNS::serviceRemove(const std::string& service, const std::string& proto) { - serviceRemove(service.c_str(), proto.c_str()); -} // serviceRemove -*/ - -/** - * @brief Set the mDNS hostname. - * - * @param [in] hostname The host name to set against the mDNS. - * @return N/A. - */ -/* -void MDNS::setHostname(const std::string& hostname) { - setHostname(hostname.c_str()); -} // setHostname -*/ - -/** - * @brief Set the mDNS instance. - * - * @param [in] instance The instance name to set against the mDNS. - * @return N/A. - */ -/* -void MDNS::setInstance(const std::string& instance) { - setInstance(instance.c_str()); -} // setInstance -*/ - -/** - * @brief Define the service for mDNS. - * - * @param [in] service - * @param [in] proto - * @param [in] port - * @return N/A. - */ -/* -void MDNS::serviceAdd(const char* service, const char* proto, uint16_t port) { - esp_err_t errRc = ::mdns_service_add(m_mdns_server, service, proto, port); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_service_add: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // serviceAdd - - -void MDNS::serviceInstanceSet(const char* service, const char* proto, const char* instance) { - esp_err_t errRc = ::mdns_service_instance_set(m_mdns_server, service, proto, instance); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_service_instance_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // serviceInstanceSet - - -void MDNS::servicePortSet(const char* service, const char* proto, uint16_t port) { - esp_err_t errRc = ::mdns_service_port_set(m_mdns_server, service, proto, port); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_service_port_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // servicePortSet - - -void MDNS::serviceRemove(const char* service, const char* proto) { - esp_err_t errRc = ::mdns_service_remove(m_mdns_server, service, proto); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_service_remove: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // serviceRemove - -*/ -/** - * @brief Set the mDNS hostname. - * - * @param [in] hostname The host name to set against the mDNS. - * @return N/A. - */ -/* -void MDNS::setHostname(const char* hostname) { - esp_err_t errRc = ::mdns_set_hostname(m_mdns_server,hostname); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_set_hostname: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // setHostname -*/ - -/** - * @brief Set the mDNS instance. - * - * @param [in] instance The instance name to set against the mDNS. - * @return N/A. - */ -/* -void MDNS::setInstance(const char* instance) { - esp_err_t errRc = ::mdns_set_instance(m_mdns_server, instance); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_set_instance: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } -} // setInstance -*/ +/* + * WiFi.cpp + * + * Created on: Feb 25, 2017 + * Author: kolban + */ + +//#define _GLIBCXX_USE_C99 +#include +#include +#include +#include +#include "sdkconfig.h" + + +#include "WiFi.h" +#include +#include +#include +#include +#include +#include "GeneralUtils.h" +#include +#include +#include +#include +#include + +#include + + +static const char* LOG_TAG = "WiFi"; + + +/* +static void setDNSServer(char *ip) { + ip_addr_t dnsserver; + ESP_LOGD(tag, "Setting DNS[%d] to %s", 0, ip); + inet_pton(AF_INET, ip, &dnsserver); + ESP_LOGD(tag, "ip of DNS is %.8x", *(uint32_t *)&dnsserver); + dns_setserver(0, &dnsserver); +} +*/ + + +/** + * @brief Creates and uses a default event handler + */ +WiFi::WiFi() + : ip(0) + , gw(0) + , netmask(0) + , m_pWifiEventHandler(nullptr) +{ + m_eventLoopStarted = false; + m_initCalled = false; + //m_pWifiEventHandler = new WiFiEventHandler(); + m_apConnectionStatus = UINT8_MAX; // Are we connected to an access point? +} // WiFi + + +/** + * @brief Deletes the event handler that was used by the class + */ +WiFi::~WiFi() { + if (m_pWifiEventHandler != nullptr) { + delete m_pWifiEventHandler; + m_pWifiEventHandler = nullptr; + } +} // ~WiFi + + +/** + * @brief Add a reference to a DNS server. + * + * Here we define a server that will act as a DNS server. We can add two DNS + * servers in total. The first will be the primary, the second will be the backup. + * The public Google DNS servers are "8.8.8.8" and "8.8.4.4". + * + * For example: + * + * @code{.cpp} + * wifi.addDNSServer("8.8.8.8"); + * wifi.addDNSServer("8.8.4.4"); + * @endcode + * + * @param [in] ip The IP address of the DNS Server. + * @return N/A. + */ +void WiFi::addDNSServer(const std::string& ip) { + addDNSServer(ip.c_str()); +} // addDNSServer + + +void WiFi::addDNSServer(const char* ip) { + ip_addr_t dns_server; + if(inet_pton(AF_INET, ip, &dns_server)) { + addDNSServer(ip); + } +} // addDNSServer + + +void WiFi::addDNSServer(ip_addr_t ip) { + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + init(); + ::dns_setserver(m_dnsCount, &ip); + m_dnsCount++; + m_dnsCount %= 2; +} // addDNSServer + + +/** + * @brief Set a reference to a DNS server. + * + * Here we define a server that will act as a DNS server. We use numdns to specify which DNS server to set + * + * For example: + * + * @code{.cpp} + * wifi.setDNSServer(0, "8.8.8.8"); + * wifi.setDNSServer(1, "8.8.4.4"); + * @endcode + * + * @param [in] numdns The DNS number we wish to set + * @param [in] ip The IP address of the DNS Server. + * @return N/A. + */ +void WiFi::setDNSServer(int numdns, const std::string& ip) { + setDNSServer(numdns, ip.c_str()); +} // setDNSServer + + +void WiFi::setDNSServer(int numdns, const char* ip) { + ip_addr_t dns_server; + if(inet_pton(AF_INET, ip, &dns_server)) { + setDNSServer(numdns, dns_server); + } +} // setDNSServer + + +void WiFi::setDNSServer(int numdns, ip_addr_t ip) { + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + init(); + ::dns_setserver(numdns, &ip); +} // setDNSServer + + +/** + * @brief Connect to an external access point. + * + * The event handler will be called back with the outcome of the connection. + * + * @param [in] ssid The network SSID of the access point to which we wish to connect. + * @param [in] password The password of the access point to which we wish to connect. + * @param [in] waitForConnection Block until the connection has an outcome. + * @param [in] mode WIFI_MODE_AP for normal or WIFI_MODE_APSTA if you want to keep an Access Point running while you connect + * @return ESP_OK if we are now connected and wifi_err_reason_t if not. + */ +uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection, wifi_mode_t mode){ + ESP_LOGD(LOG_TAG, ">> connectAP"); + + m_apConnectionStatus = UINT8_MAX; + init(); + + if (ip != 0 && gw != 0 && netmask != 0) { + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client + + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; + + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + } + + esp_err_t errRc = ::esp_wifi_set_mode(mode); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + wifi_config_t sta_config; + ::memset(&sta_config, 0, sizeof(sta_config)); + ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); + ::memcpy(sta_config.sta.password, password.data(), password.size()); + sta_config.sta.bssid_set = 0; + errRc = ::esp_wifi_set_config(WIFI_IF_STA, &sta_config); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + m_connectFinished.take("connectAP"); // Take the semaphore to wait for a connection. + do { + ESP_LOGD(LOG_TAG, "esp_wifi_connect"); + errRc = ::esp_wifi_connect(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + } + while (!m_connectFinished.take(5000, "connectAP")); // retry if not connected within 5s + m_connectFinished.give(); + + ESP_LOGD(LOG_TAG, "<< connectAP"); + return m_apConnectionStatus; // Return ESP_OK if we are now connected and wifi_err_reason_t if not. +} // connectAP + + + +/** + * @brief Dump diagnostics to the log. + */ +void WiFi::dump() { + ESP_LOGD(LOG_TAG, "WiFi Dump"); + ESP_LOGD(LOG_TAG, "---------"); + char ipAddrStr[30]; + ip_addr_t ip = ::dns_getserver(0); + inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); + ESP_LOGD(LOG_TAG, "DNS Server[0]: %s", ipAddrStr); +} // dump + + +/** + * @brief Returns whether wifi is connected to an access point + */ +bool WiFi::isConnectedToAP() { + return m_apConnectionStatus; +} // isConnected + + + +/** + * @brief Primary event handler interface. + */ +/* STATIC */ esp_err_t WiFi::eventHandler(void* ctx, system_event_t* event) { + // This is the common event handler that we have provided for all event processing. It is called for every event + // that is received by the WiFi subsystem. The "ctx" parameter is an instance of the current WiFi object that we are + // processing. We can then retrieve the specific/custom event handler from within it and invoke that. This then makes this + // an indirection vector to the real caller. + + WiFi *pWiFi = (WiFi *)ctx; // retrieve the WiFi object from the passed in context. + + // Invoke the event handler. + esp_err_t rc; + if (pWiFi->m_pWifiEventHandler != nullptr) { + rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); + } else { + rc = ESP_OK; + } + + // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that + // indicates we are waiting for a connection complete. + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP || event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { + + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { // If we connected and have an IP, change the status to ESP_OK. Otherwise, change it to the reason code. + pWiFi->m_apConnectionStatus = ESP_OK; + } else { + pWiFi->m_apConnectionStatus = event->event_info.disconnected.reason; + } + pWiFi->m_connectFinished.give(); + } + + return rc; +} // eventHandler + + +/** + * @brief Get the AP IP Info. + * @return The AP IP Info. + */ +tcpip_adapter_ip_info_t WiFi::getApIpInfo() { + //init(); + tcpip_adapter_ip_info_t ipInfo; + ::tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); + return ipInfo; +} // getApIpInfo + + +/** + * @brief Get the MAC address of the AP interface. + * @return The MAC address of the AP interface. + */ +std::string WiFi::getApMac() { + uint8_t mac[6]; + //init(); + esp_wifi_get_mac(WIFI_IF_AP, mac); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); +} // getApMac + + +/** + * @brief Get the AP SSID. + * @return The AP SSID. + */ +std::string WiFi::getApSSID() { + wifi_config_t conf; + //init(); + esp_wifi_get_config(WIFI_IF_AP, &conf); + return std::string((char *)conf.sta.ssid); +} // getApSSID + +/** + * @brief Get the current ESP32 IP form AP. + * @return The ESP32 IP. + */ +std::string WiFi::getApIp(){ + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaIp + +/** + * @brief Get the current AP netmask. + * @return The Netmask IP. + */ +std::string WiFi::getApNetmask(){ + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaNetmask + +/** + * @brief Get the current AP Gateway IP. + * @return The Gateway IP. + */ +std::string WiFi::getApGateway(){ + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaGateway + +/** + * @brief Lookup an IP address by host name. + * + * @param [in] hostName The hostname to resolve. + * + * @return The IP address of the host or 0.0.0.0 if not found. + */ +struct in_addr WiFi::getHostByName(const std::string& hostName) { + return getHostByName(hostName.c_str()); +} // getHostByName + + +struct in_addr WiFi::getHostByName(const char* hostName) { + struct in_addr retAddr; + struct hostent *he = gethostbyname(hostName); + if (he == nullptr) { + retAddr.s_addr = 0; + ESP_LOGD(LOG_TAG, "Unable to resolve %s - %d", hostName, h_errno); + } else { + retAddr = *(struct in_addr *)(he->h_addr_list[0]); + ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); + } + return retAddr; +} // getHostByName + + +/** + * @brief Get the WiFi Mode. + * @return The WiFi Mode. + */ +std::string WiFi::getMode() { + wifi_mode_t mode; + esp_wifi_get_mode(&mode); + switch(mode) { + case WIFI_MODE_NULL: + return "WIFI_MODE_NULL"; + case WIFI_MODE_STA: + return "WIFI_MODE_STA"; + case WIFI_MODE_AP: + return "WIFI_MODE_AP"; + case WIFI_MODE_APSTA: + return "WIFI_MODE_APSTA"; + default: + return "unknown"; + } +} // getMode + + +/** + * @brief Get the STA IP Info. + * @return The STA IP Info. + */ +tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { + tcpip_adapter_ip_info_t ipInfo; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + return ipInfo; +} // getStaIpInfo + +/** + * @brief Get the current ESP32 IP form STA. + * @return The ESP32 IP. + */ +std::string WiFi::getStaIp(){ + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaIp + + +/** + * @brief Get the current STA netmask. + * @return The Netmask IP. + */ +std::string WiFi::getStaNetmask(){ + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaNetmask + + +/** + * @brief Get the current STA Gateway IP. + * @return The Gateway IP. + */ +std::string WiFi::getStaGateway(){ + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaGateway + +/** + * @brief Get the MAC address of the STA interface. + * @return The MAC address of the STA interface. + */ +std::string WiFi::getStaMac() { + uint8_t mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, mac); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); +} // getStaMac + + +/** + * @brief Get the STA SSID. + * @return The STA SSID. + */ +std::string WiFi::getStaSSID() { + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_STA, &conf); + return std::string((char *)conf.ap.ssid); +} // getStaSSID + + +/** + * @brief Initialize WiFi. + */ +/* PRIVATE */ void WiFi::init() { + + // If we have already started the event loop, then change the handler otherwise + // start the event loop. + if (m_eventLoopStarted) { + esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. + } else { + esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + m_eventLoopStarted = true; + } + // Now, one way or another, the event handler is WiFi::eventHandler. + + if (!m_initCalled) { + ::nvs_flash_init(); + ::tcpip_adapter_init(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t errRc = ::esp_wifi_init(&cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + } + m_initCalled = true; +} // init + + +/** + * @brief Perform a WiFi scan looking for access points. + * + * An access point scan is performed and a vector of WiFi access point records + * is built and returned with one record per found scan instance. The scan is + * performed in a blocking fashion and will not return until the set of scanned + * access points has been built. + * + * @return A vector of WiFiAPRecord instances. + */ +std::vector WiFi::scan() { + ESP_LOGD(LOG_TAG, ">> scan"); + std::vector apRecords; + + init(); + + esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + wifi_scan_config_t conf; + memset(&conf, 0, sizeof(conf)); + conf.show_hidden = true; + + esp_err_t rc = ::esp_wifi_scan_start(&conf, true); + if (rc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); + return apRecords; + } + + uint16_t apCount; // Number of access points available. + rc = ::esp_wifi_scan_get_ap_num(&apCount); + ESP_LOGD(LOG_TAG, "Count of found access points: %d", apCount); + + wifi_ap_record_t* list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); + if (list == nullptr) { + ESP_LOGE(LOG_TAG, "Failed to allocate memory"); + return apRecords; + } + + errRc = ::esp_wifi_scan_get_ap_records(&apCount, list); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_scan_get_ap_records: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + for (auto i=0; i rhs.m_rssi;}); + return apRecords; +} // scan + + +/** + * @brief Start being an access point. + * + * @param[in] ssid The SSID to use to advertize for stations. + * @param[in] password The password to use for station connections. + * @param[in] auth The authorization mode for access to this access point. Options are: + * * WIFI_AUTH_OPEN + * * WIFI_AUTH_WPA_PSK + * * WIFI_AUTH_WPA2_PSK + * * WIFI_AUTH_WPA_WPA2_PSK + * * WIFI_AUTH_WPA2_ENTERPRISE + * * WIFI_AUTH_WEP + * @return N/A. + */ +void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth) { + startAP(ssid, password, auth, 0, false, 4); +} // startAP + +/** + * @brief Start being an access point. + * + * @param[in] ssid The SSID to use to advertize for stations. + * @param[in] password The password to use for station connections. + * @param[in] auth The authorization mode for access to this access point. Options are: + * * WIFI_AUTH_OPEN + * * WIFI_AUTH_WPA_PSK + * * WIFI_AUTH_WPA2_PSK + * * WIFI_AUTH_WPA_WPA2_PSK + * * WIFI_AUTH_WPA2_ENTERPRISE + * * WIFI_AUTH_WEP + * @param[in] channel from the access point. + * @param[in] is the ssid hidden, ore not. + * @param[in] limiting number of clients. + * @return N/A. + */ +void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection) { + ESP_LOGD(LOG_TAG, ">> startAP: ssid: %s", ssid.c_str()); + + init(); + + esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_AP); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + // Build the apConfig structure. + wifi_config_t apConfig; + ::memset(&apConfig, 0, sizeof(apConfig)); + ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); + apConfig.ap.ssid_len = ssid.size(); + ::memcpy(apConfig.ap.password, password.data(), password.size()); + apConfig.ap.channel = channel; + apConfig.ap.authmode = auth; + apConfig.ap.ssid_hidden = (uint8_t) ssid_hidden; + apConfig.ap.max_connection = max_connection; + apConfig.ap.beacon_interval = 100; + + errRc = ::esp_wifi_set_config(WIFI_IF_AP, &apConfig); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "tcpip_adapter_dhcps_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + + ESP_LOGD(LOG_TAG, "<< startAP"); +} // startAP + +/** + * @brief Set the event handler to use to process detected events. + * @param[in] wifiEventHandler The class that will be used to process events. + */ +void WiFi::setWifiEventHandler(WiFiEventHandler* wifiEventHandler) { + ESP_LOGD(LOG_TAG, ">> setWifiEventHandler: 0x%d", (uint32_t)wifiEventHandler); + this->m_pWifiEventHandler = wifiEventHandler; + ESP_LOGD(LOG_TAG, "<< setWifiEventHandler"); +} // setWifiEventHandler + + +/** + * @brief Set the IP info and enable DHCP if ip != 0. If called with ip == 0 then DHCP is enabled. + * If called with bad values it will do nothing. + * + * Do not call this method if we are being an access point ourselves. + * + * For example, prior to calling `connectAP()` we could invoke: + * + * @code{.cpp} + * myWifi.setIPInfo("192.168.1.99", "192.168.1.1", "255.255.255.0"); + * @endcode + * + * @param [in] ip IP address value. + * @param [in] gw Gateway value. + * @param [in] netmask Netmask value. + * @return N/A. + */ +void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask) { + setIPInfo(ip.c_str(), gw.c_str(), netmask.c_str()); +} // setIPInfo + + + +void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { + uint32_t new_ip; + uint32_t new_gw; + uint32_t new_netmask; + + auto success = (bool)inet_pton(AF_INET, ip, &new_ip); + success = success && inet_pton(AF_INET, gw, &new_gw); + success = success && inet_pton(AF_INET, netmask, &new_netmask); + + if(!success) { + return; + } + + setIPInfo(new_ip, new_gw, new_netmask); +} // setIPInfo + + +/** + * @brief Set the IP Info based on the IP address, gateway and netmask. + * @param [in] ip The IP address of our ESP32. + * @param [in] gw The gateway we should use. + * @param [in] netmask Our TCP/IP netmask value. + */ +void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { + init(); + + this->ip = ip; + this->gw = gw; + this->netmask = netmask; + + if(ip != 0 && gw != 0 && netmask != 0) { + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + } else { + ip = 0; + ::tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + } +} // setIPInfo + + +/** + * @brief Return a string representation of the WiFi access point record. + * + * @return A string representation of the WiFi access point record. + */ +std::string WiFiAPRecord::toString() { + std::string auth; + switch(getAuthMode()) { + case WIFI_AUTH_OPEN: + auth = "WIFI_AUTH_OPEN"; + break; + case WIFI_AUTH_WEP: + auth = "WIFI_AUTH_WEP"; + break; + case WIFI_AUTH_WPA_PSK: + auth = "WIFI_AUTH_WPA_PSK"; + break; + case WIFI_AUTH_WPA2_PSK: + auth = "WIFI_AUTH_WPA2_PSK"; + break; + case WIFI_AUTH_WPA_WPA2_PSK: + auth = "WIFI_AUTH_WPA_WPA2_PSK"; + break; + default: + auth = ""; + break; + } +// std::stringstream s; +// s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; + auto info_str = (char*) malloc(6 + 32 + 8 + 22 + 8 + 3 + 1); + sprintf(info_str, "ssid: %s, auth: %s, rssi: %d", m_ssid.c_str(), auth.c_str(), (int) m_rssi); + return std::string(std::move(info_str)); +} // toString + +/* +MDNS::MDNS() { + esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} + +MDNS::~MDNS() { + if (m_mdns_server != nullptr) { + mdns_free(m_mdns_server); + } + m_mdns_server = nullptr; +} +*/ + +/** + * @brief Define the service for mDNS. + * + * @param [in] service + * @param [in] proto + * @param [in] port + * @return N/A. + */ +/* +void MDNS::serviceAdd(const std::string& service, const std::string& proto, uint16_t port) { + serviceAdd(service.c_str(), proto.c_str(), port); +} // serviceAdd + + +void MDNS::serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance) { + serviceInstanceSet(service.c_str(), proto.c_str(), instance.c_str()); +} // serviceInstanceSet + + +void MDNS::servicePortSet(const std::string& service, const std::string& proto, uint16_t port) { + servicePortSet(service.c_str(), proto.c_str(), port); +} // servicePortSet + + +void MDNS::serviceRemove(const std::string& service, const std::string& proto) { + serviceRemove(service.c_str(), proto.c_str()); +} // serviceRemove +*/ + +/** + * @brief Set the mDNS hostname. + * + * @param [in] hostname The host name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setHostname(const std::string& hostname) { + setHostname(hostname.c_str()); +} // setHostname +*/ + +/** + * @brief Set the mDNS instance. + * + * @param [in] instance The instance name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setInstance(const std::string& instance) { + setInstance(instance.c_str()); +} // setInstance +*/ + +/** + * @brief Define the service for mDNS. + * + * @param [in] service + * @param [in] proto + * @param [in] port + * @return N/A. + */ +/* +void MDNS::serviceAdd(const char* service, const char* proto, uint16_t port) { + esp_err_t errRc = ::mdns_service_add(m_mdns_server, service, proto, port); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_add: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // serviceAdd + + +void MDNS::serviceInstanceSet(const char* service, const char* proto, const char* instance) { + esp_err_t errRc = ::mdns_service_instance_set(m_mdns_server, service, proto, instance); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_instance_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // serviceInstanceSet + + +void MDNS::servicePortSet(const char* service, const char* proto, uint16_t port) { + esp_err_t errRc = ::mdns_service_port_set(m_mdns_server, service, proto, port); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_port_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // servicePortSet + + +void MDNS::serviceRemove(const char* service, const char* proto) { + esp_err_t errRc = ::mdns_service_remove(m_mdns_server, service, proto); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_remove: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // serviceRemove + +*/ +/** + * @brief Set the mDNS hostname. + * + * @param [in] hostname The host name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setHostname(const char* hostname) { + esp_err_t errRc = ::mdns_set_hostname(m_mdns_server,hostname); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_hostname: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // setHostname +*/ + +/** + * @brief Set the mDNS instance. + * + * @param [in] instance The instance name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setInstance(const char* instance) { + esp_err_t errRc = ::mdns_set_instance(m_mdns_server, instance); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_instance: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // setInstance +*/ diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index a4d5d4d5..c1b6bedc 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -1,158 +1,158 @@ -/* - * WiFi.h - * - * Created on: Feb 25, 2017 - * Author: kolban - */ - -#ifndef MAIN_WIFI_H_ -#define MAIN_WIFI_H_ -#include "sdkconfig.h" - -#include -#include -#include -#include -#include "FreeRTOS.h" -#include "WiFiEventHandler.h" - -/** - * @brief Manage mDNS server. - */ -/* -class MDNS { -public: - MDNS(); - ~MDNS(); - void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); - void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); - void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); - void serviceRemove(const std::string& service, const std::string& proto); - void setHostname(const std::string& hostname); - void setInstance(const std::string& instance); - // If we the above functions with a basic char*, a copy would be created into an std::string, - // making the whole thing require twice as much processing power and speed - void serviceAdd(const char* service, const char* proto, uint16_t port); - void serviceInstanceSet(const char* service, const char* proto, const char* instance); - void servicePortSet(const char* service, const char* proto, uint16_t port); - void serviceRemove(const char* service, const char* proto); - void setHostname(const char* hostname); - void setInstance(const char* instance); -private: - mdns_server_t *m_mdns_server = nullptr; -}; -*/ - -class WiFiAPRecord { -public: - friend class WiFi; - - /** - * @brief Get the auth mode. - * @return The auth mode. - */ - wifi_auth_mode_t getAuthMode() { - return m_authMode; - } - - /** - * @brief Get the RSSI. - * @return the RSSI. - */ - int8_t getRSSI() { - return m_rssi; - } - - /** - * @brief Get the SSID. - * @return the SSID. - */ - std::string getSSID() { - return m_ssid; - } - - std::string toString(); - -private: - uint8_t m_bssid[6]; - int8_t m_rssi; - std::string m_ssid; - wifi_auth_mode_t m_authMode; -}; - -/** - * @brief %WiFi driver. - * - * Encapsulate control of %WiFi functions. - * - * Here is an example fragment that illustrates connecting to an access point. - * @code{.cpp} - * #include - * #include - * - * class TargetWiFiEventHandler: public WiFiEventHandler { - * esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { - * ESP_LOGD(tag, "MyWiFiEventHandler(Class): staGotIp"); - * // Do something ... - * return ESP_OK; - * } - * }; - * - * WiFi wifi; - * - * TargetWiFiEventHandler *eventHandler = new TargetWiFiEventHandler(); - * wifi.setWifiEventHandler(eventHandler); - * wifi.connectAP("myssid", "mypassword"); - * @endcode - */ -class WiFi { -private: - static esp_err_t eventHandler(void* ctx, system_event_t* event); - void init(); - uint32_t ip; - uint32_t gw; - uint32_t netmask; - WiFiEventHandler* m_pWifiEventHandler; - uint8_t m_dnsCount=0; - bool m_eventLoopStarted; - bool m_initCalled; - uint8_t m_apConnectionStatus; // ESP_OK = we are connected to an access point. Otherwise receives wifi_err_reason_t. - FreeRTOS::Semaphore m_connectFinished = FreeRTOS::Semaphore("ConnectFinished"); - -public: - WiFi(); - ~WiFi(); - void addDNSServer(const std::string& ip); - void addDNSServer(const char* ip); - void addDNSServer(ip_addr_t ip); - void setDNSServer(int numdns, const std::string& ip); - void setDNSServer(int numdns, const char* ip); - void setDNSServer(int numdns, ip_addr_t ip); - struct in_addr getHostByName(const std::string& hostName); - struct in_addr getHostByName(const char* hostName); - uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true, wifi_mode_t mode=WIFI_MODE_STA); - void dump(); - bool isConnectedToAP(); - static std::string getApMac(); - static tcpip_adapter_ip_info_t getApIpInfo(); - static std::string getApSSID(); - static std::string getApIp(); - static std::string getApNetmask(); - static std::string getApGateway(); - static std::string getMode(); - static tcpip_adapter_ip_info_t getStaIpInfo(); - static std::string getStaMac(); - static std::string getStaSSID(); - static std::string getStaIp(); - static std::string getStaNetmask(); - static std::string getStaGateway(); - std::vector scan(); - void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth = WIFI_AUTH_OPEN); - void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection); - void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); - void setIPInfo(const char* ip, const char* gw, const char* netmask); - void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); - void setWifiEventHandler(WiFiEventHandler *wifiEventHandler); -}; - -#endif /* MAIN_WIFI_H_ */ +/* + * WiFi.h + * + * Created on: Feb 25, 2017 + * Author: kolban + */ + +#ifndef MAIN_WIFI_H_ +#define MAIN_WIFI_H_ +#include "sdkconfig.h" + +#include +#include +#include +#include +#include "FreeRTOS.h" +#include "WiFiEventHandler.h" + +/** + * @brief Manage mDNS server. + */ +/* +class MDNS { +public: + MDNS(); + ~MDNS(); + void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); + void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); + void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); + void serviceRemove(const std::string& service, const std::string& proto); + void setHostname(const std::string& hostname); + void setInstance(const std::string& instance); + // If we the above functions with a basic char*, a copy would be created into an std::string, + // making the whole thing require twice as much processing power and speed + void serviceAdd(const char* service, const char* proto, uint16_t port); + void serviceInstanceSet(const char* service, const char* proto, const char* instance); + void servicePortSet(const char* service, const char* proto, uint16_t port); + void serviceRemove(const char* service, const char* proto); + void setHostname(const char* hostname); + void setInstance(const char* instance); +private: + mdns_server_t *m_mdns_server = nullptr; +}; +*/ + +class WiFiAPRecord { +public: + friend class WiFi; + + /** + * @brief Get the auth mode. + * @return The auth mode. + */ + wifi_auth_mode_t getAuthMode() { + return m_authMode; + } + + /** + * @brief Get the RSSI. + * @return the RSSI. + */ + int8_t getRSSI() { + return m_rssi; + } + + /** + * @brief Get the SSID. + * @return the SSID. + */ + std::string getSSID() { + return m_ssid; + } + + std::string toString(); + +private: + uint8_t m_bssid[6]; + int8_t m_rssi; + std::string m_ssid; + wifi_auth_mode_t m_authMode; +}; + +/** + * @brief %WiFi driver. + * + * Encapsulate control of %WiFi functions. + * + * Here is an example fragment that illustrates connecting to an access point. + * @code{.cpp} + * #include + * #include + * + * class TargetWiFiEventHandler: public WiFiEventHandler { + * esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { + * ESP_LOGD(tag, "MyWiFiEventHandler(Class): staGotIp"); + * // Do something ... + * return ESP_OK; + * } + * }; + * + * WiFi wifi; + * + * TargetWiFiEventHandler *eventHandler = new TargetWiFiEventHandler(); + * wifi.setWifiEventHandler(eventHandler); + * wifi.connectAP("myssid", "mypassword"); + * @endcode + */ +class WiFi { +private: + static esp_err_t eventHandler(void* ctx, system_event_t* event); + void init(); + uint32_t ip; + uint32_t gw; + uint32_t netmask; + WiFiEventHandler* m_pWifiEventHandler; + uint8_t m_dnsCount=0; + bool m_eventLoopStarted; + bool m_initCalled; + uint8_t m_apConnectionStatus; // ESP_OK = we are connected to an access point. Otherwise receives wifi_err_reason_t. + FreeRTOS::Semaphore m_connectFinished = FreeRTOS::Semaphore("ConnectFinished"); + +public: + WiFi(); + ~WiFi(); + void addDNSServer(const std::string& ip); + void addDNSServer(const char* ip); + void addDNSServer(ip_addr_t ip); + void setDNSServer(int numdns, const std::string& ip); + void setDNSServer(int numdns, const char* ip); + void setDNSServer(int numdns, ip_addr_t ip); + struct in_addr getHostByName(const std::string& hostName); + struct in_addr getHostByName(const char* hostName); + uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true, wifi_mode_t mode=WIFI_MODE_STA); + void dump(); + bool isConnectedToAP(); + static std::string getApMac(); + static tcpip_adapter_ip_info_t getApIpInfo(); + static std::string getApSSID(); + static std::string getApIp(); + static std::string getApNetmask(); + static std::string getApGateway(); + static std::string getMode(); + static tcpip_adapter_ip_info_t getStaIpInfo(); + static std::string getStaMac(); + static std::string getStaSSID(); + static std::string getStaIp(); + static std::string getStaNetmask(); + static std::string getStaGateway(); + std::vector scan(); + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth = WIFI_AUTH_OPEN); + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection); + void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); + void setIPInfo(const char* ip, const char* gw, const char* netmask); + void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); + void setWifiEventHandler(WiFiEventHandler *wifiEventHandler); +}; + +#endif /* MAIN_WIFI_H_ */ From 874ce79815817a4796612716c95ea6d639c9df0b Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 2 Oct 2018 01:37:06 +0200 Subject: [PATCH 323/381] move m_bleAdvertising to BLEDevice --- cpp_utils/BLEDevice.cpp | 18 ++++++++++++++++++ cpp_utils/BLEDevice.h | 3 +++ cpp_utils/BLEServer.cpp | 4 ++-- cpp_utils/BLEServer.h | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index a7db454b..157074d4 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -45,6 +45,7 @@ bool initialized = false; // Have we been initialized? esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; uint16_t BLEDevice::m_localMTU = 23; +BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; /** * @brief Create a new instance of a client. @@ -545,4 +546,21 @@ uint16_t BLEDevice::getMTU() { bool BLEDevice::getInitialized() { return initialized; } + +BLEAdvertising* BLEDevice::getAdvertising() { + if(m_bleAdvertising == nullptr) + { + m_bleAdvertising = new BLEAdvertising(); + ESP_LOGI(LOG_TAG, "create advertising"); + } + ESP_LOGD(LOG_TAG, "get advertising"); + return m_bleAdvertising; +} + +void BLEDevice::startAdvertising() { + ESP_LOGD(LOG_TAG, ">> startAdvertising"); + getAdvertising()->start(); + ESP_LOGD(LOG_TAG, "<< startAdvertising"); +} // startAdvertising + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 7a1b833d..63183d0b 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -43,6 +43,8 @@ class BLEDevice { static esp_err_t setMTU(uint16_t mtu); static uint16_t getMTU(); static bool getInitialized(); // Returns the state of the device, is it initialized or not? + static BLEAdvertising* getAdvertising(); + static void startAdvertising(); private: static BLEServer *m_pServer; @@ -51,6 +53,7 @@ class BLEDevice { static esp_ble_sec_act_t m_securityLevel; static BLESecurityCallbacks* m_securityCallbacks; static uint16_t m_localMTU; + static BLEAdvertising *m_bleAdvertising; static esp_gatt_if_t getGattcIF(); diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 6a60f1db..1881c14b 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -120,7 +120,7 @@ BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { * @return An advertising object. */ BLEAdvertising* BLEServer::getAdvertising() { - return &m_bleAdvertising; + return BLEDevice::getAdvertising(); } uint16_t BLEServer::getConnId() { @@ -359,7 +359,7 @@ void BLEServer::removeService(BLEService *service) { */ void BLEServer::startAdvertising() { ESP_LOGD(LOG_TAG, ">> startAdvertising"); - m_bleAdvertising.start(); + BLEDevice::startAdvertising(); ESP_LOGD(LOG_TAG, "<< startAdvertising"); } // startAdvertising diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index a58b0e9b..7f40faef 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -73,7 +73,7 @@ class BLEServer { friend class BLEDevice; esp_ble_adv_data_t m_adv_data; uint16_t m_appId; - BLEAdvertising m_bleAdvertising; + // BLEAdvertising m_bleAdvertising; uint16_t m_connId; uint32_t m_connectedCount; uint16_t m_gatts_if; From 482f7b4062893095650167e9feb7f85663459e66 Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 2 Oct 2018 19:45:12 +0200 Subject: [PATCH 324/381] some small bugfixes --- cpp_utils/BLEAdvertisedDevice.cpp | 2 +- cpp_utils/BLERemoteService.cpp | 14 ++++++++++++-- .../BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino | 8 ++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 67603dfb..d2ba1d91 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -137,7 +137,7 @@ BLEUUID BLEAdvertisedDevice::getServiceUUID() { //TODO Remove it eventually, is * @return Return true if service is advertised */ bool BLEAdvertisedDevice::isAdvertisingService(BLEUUID uuid){ - for (int i = 0; i < m_serviceUUIDs.size(); ++i) { + for (int i = 0; i < m_serviceUUIDs.size(); i++) { if(m_serviceUUIDs[i].equals(uuid)) return true; } diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index 78ff9914..ef0588bc 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -114,7 +114,7 @@ void BLERemoteService::gattClientEventHandler( } // switch // Send the event to each of the characteristics owned by this service. - for (auto &myPair : m_characteristicMap) { + for (auto &myPair : m_characteristicMapByHandle) { myPair.second->gattClientEventHandler(event, gattc_if, evtParam); } } // gattClientEventHandler @@ -206,7 +206,7 @@ void BLERemoteService::retrieveCharacteristics() { ); m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic->getUUID().toString(), pNewRemoteCharacteristic)); - + m_characteristicMapByHandle.insert(std::pair(result.char_handle, pNewRemoteCharacteristic)); offset++; // Increment our count of number of descriptors found. } // Loop forever (until we break inside the loop). @@ -231,6 +231,12 @@ std::map * BLERemoteService::getCharacte return &m_characteristicMap; } // getCharacteristics +/** + * @brief This function is designed to get characteristics map when we have multiple characteristics with the same UUID + */ +void BLERemoteService::getCharacteristics(std::map* pCharacteristicMap){ + pCharacteristicMap = &m_characteristicMapByHandle; +} // Get the characteristics map. /** * @brief Get the client associated with this service. @@ -292,6 +298,10 @@ void BLERemoteService::removeCharacteristics() { //m_characteristicMap.erase(myPair.first); // Should be no need to delete as it will be deleted by the clear } m_characteristicMap.clear(); // Clear the map + for (auto &myPair : m_characteristicMapByHandle) { + delete myPair.second; + } + m_characteristicMapByHandle.clear(); // Clear the map } // removeCharacteristics diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino b/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino index 5f6ed002..2c2ec6a3 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino @@ -49,7 +49,7 @@ void setBeacon() { BLEBeacon oBeacon = BLEBeacon(); oBeacon.setManufacturerId(0x4C00); // fake Apple 0x004C LSB (ENDIAN_CHANGE_U16!) - oBeacon.setProximityUUID(BLEUUID(BEACON_UUID)); + oBeacon.setProximityUUID(BLEUUID(BEACON_UUID)); oBeacon.setMajor((bootcount & 0xFFFF0000) >> 16); oBeacon.setMinor(bootcount&0xFFFF); BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); @@ -84,10 +84,10 @@ void setup() { // Create the BLE Device BLEDevice::init(""); - // Create the BLE Server - BLEServer *pServer = BLEDevice::createServer(); + // Create the BLE Server is no longer required to crete beacon + // BLEServer *pServer = BLEDevice::createServer(); - pAdvertising = pServer->getAdvertising(); + pAdvertising = BLEDevice::getAdvertising(); setBeacon(); // Start advertising From b1ef951052c3be844ee3a9a1be8254b3b4fa6f2c Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 4 Oct 2018 00:44:26 +0200 Subject: [PATCH 325/381] fix macOS compatibility --- cpp_utils/BLEHIDDevice.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cpp_utils/BLEHIDDevice.cpp b/cpp_utils/BLEHIDDevice.cpp index 29376f3a..944d179e 100644 --- a/cpp_utils/BLEHIDDevice.cpp +++ b/cpp_utils/BLEHIDDevice.cpp @@ -111,10 +111,14 @@ void BLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) { BLECharacteristic* BLEHIDDevice::inputReport(uint8_t reportID) { BLECharacteristic* inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); BLEDescriptor* inputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + BLE2902* p2902 = new BLE2902(); + inputReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + inputReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + p2902->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); uint8_t desc1_val[] = {reportID, 0x01}; inputReportDescriptor->setValue((uint8_t*)desc1_val, 2); - inputReportCharacteristic->addDescriptor(new BLE2902()); + inputReportCharacteristic->addDescriptor(p2902; inputReportCharacteristic->addDescriptor(inputReportDescriptor); return inputReportCharacteristic; @@ -128,6 +132,8 @@ BLECharacteristic* BLEHIDDevice::inputReport(uint8_t reportID) { BLECharacteristic* BLEHIDDevice::outputReport(uint8_t reportID) { BLECharacteristic* outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); BLEDescriptor* outputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + outputReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + outputReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); uint8_t desc1_val[] = {reportID, 0x02}; outputReportDescriptor->setValue((uint8_t*)desc1_val, 2); @@ -144,6 +150,8 @@ BLECharacteristic* BLEHIDDevice::outputReport(uint8_t reportID) { BLECharacteristic* BLEHIDDevice::featureReport(uint8_t reportID) { BLECharacteristic* featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); BLEDescriptor* featureReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + featureReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + featureReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); uint8_t desc1_val[] = {reportID, 0x03}; featureReportDescriptor->setValue((uint8_t*)desc1_val, 2); From bac1b24dee019a14949509b67155be1797b59757 Mon Sep 17 00:00:00 2001 From: chegewara Date: Thu, 4 Oct 2018 00:50:27 +0200 Subject: [PATCH 326/381] fix macOS HID compatibility --- cpp_utils/BLEHIDDevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/BLEHIDDevice.cpp b/cpp_utils/BLEHIDDevice.cpp index 944d179e..cc6c531d 100644 --- a/cpp_utils/BLEHIDDevice.cpp +++ b/cpp_utils/BLEHIDDevice.cpp @@ -118,7 +118,7 @@ BLECharacteristic* BLEHIDDevice::inputReport(uint8_t reportID) { uint8_t desc1_val[] = {reportID, 0x01}; inputReportDescriptor->setValue((uint8_t*)desc1_val, 2); - inputReportCharacteristic->addDescriptor(p2902; + inputReportCharacteristic->addDescriptor(p2902); inputReportCharacteristic->addDescriptor(inputReportDescriptor); return inputReportCharacteristic; From b17e34656bc5e712096ec1e2a3bd2198f23f9641 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 16:27:09 -0600 Subject: [PATCH 327/381] Common code style --- cpp_utils/Apa102.cpp | 2 +- cpp_utils/BLE2902.cpp | 17 +- cpp_utils/BLE2904.cpp | 14 +- cpp_utils/BLEAddress.cpp | 29 +- cpp_utils/BLEAdvertisedDevice.cpp | 32 +- cpp_utils/BLEAdvertisedDevice.h | 3 +- cpp_utils/BLEAdvertising.cpp | 50 +- cpp_utils/BLEAdvertising.h | 1 - cpp_utils/BLEBeacon.cpp | 2 +- cpp_utils/BLEBeacon.h | 2 +- cpp_utils/BLECharacteristic.cpp | 105 +-- cpp_utils/BLECharacteristic.h | 70 +- cpp_utils/BLECharacteristicMap.cpp | 18 +- cpp_utils/BLEClient.cpp | 11 +- cpp_utils/BLEClient.h | 5 +- cpp_utils/BLEDescriptor.cpp | 15 +- cpp_utils/BLEDescriptorMap.cpp | 33 +- cpp_utils/BLEDevice.cpp | 137 ++- cpp_utils/BLEDevice.h | 12 +- cpp_utils/BLEEddystoneTLM.cpp | 50 +- cpp_utils/BLEEddystoneTLM.h | 41 +- cpp_utils/BLEEddystoneURL.cpp | 133 ++- cpp_utils/BLEEddystoneURL.h | 29 +- cpp_utils/BLEExceptions.h | 4 +- cpp_utils/BLEHIDDevice.cpp | 63 +- cpp_utils/BLEHIDDevice.h | 18 +- cpp_utils/BLERemoteCharacteristic.cpp | 93 +- cpp_utils/BLERemoteCharacteristic.h | 25 +- cpp_utils/BLERemoteDescriptor.cpp | 31 +- cpp_utils/BLERemoteService.cpp | 20 +- cpp_utils/BLERemoteService.h | 5 +- cpp_utils/BLEScan.cpp | 18 +- cpp_utils/BLEScan.h | 2 +- cpp_utils/BLESecurity.cpp | 45 +- cpp_utils/BLESecurity.h | 1 + cpp_utils/BLEServer.cpp | 33 +- cpp_utils/BLEServer.h | 12 +- cpp_utils/BLEService.cpp | 52 +- cpp_utils/BLEService.h | 20 +- cpp_utils/BLEServiceMap.cpp | 22 +- cpp_utils/BLEUUID.cpp | 86 +- cpp_utils/BLEUtils.cpp | 455 +++------ cpp_utils/BLEUtils.h | 6 +- cpp_utils/BLEValue.cpp | 13 +- cpp_utils/BLEValue.h | 15 +- cpp_utils/BLEXML/Characteristics/code/run.sh | 4 +- cpp_utils/BLEXML/Services/code/run.sh | 4 +- cpp_utils/CPPNVS.cpp | 2 +- cpp_utils/CPPNVS.h | 8 +- cpp_utils/Console.cpp | 27 +- cpp_utils/Console.h | 16 +- cpp_utils/FTPCallbacks.cpp | 13 +- cpp_utils/FTPServer.cpp | 116 +-- cpp_utils/FTPServer.h | 89 +- cpp_utils/File.cpp | 34 +- cpp_utils/File.h | 2 +- cpp_utils/FileSystem.cpp | 43 +- cpp_utils/FreeRTOS.cpp | 29 +- cpp_utils/FreeRTOS.h | 9 +- cpp_utils/FreeRTOSTimer.cpp | 17 +- cpp_utils/FreeRTOSTimer.h | 17 +- cpp_utils/GPIO.cpp | 22 +- cpp_utils/GPIO.h | 3 +- cpp_utils/GeneralUtils.cpp | 182 ++-- cpp_utils/GeneralUtils.h | 2 +- cpp_utils/HIDKeyboardTypes.h | 58 +- cpp_utils/HIDTypes.h | 4 +- cpp_utils/HttpParser.cpp | 56 +- cpp_utils/HttpParser.h | 30 +- cpp_utils/HttpRequest.cpp | 37 +- cpp_utils/HttpRequest.h | 13 +- cpp_utils/HttpResponse.cpp | 32 +- cpp_utils/HttpResponse.h | 20 +- cpp_utils/HttpServer.cpp | 50 +- cpp_utils/HttpServer.h | 2 +- cpp_utils/I2C.cpp | 25 +- cpp_utils/I2C.h | 25 +- cpp_utils/I2S.cpp | 181 ++-- cpp_utils/IFTTT.cpp | 2 +- cpp_utils/JSON.cpp | 55 +- cpp_utils/JSON.h | 7 +- cpp_utils/MAX7219.cpp | 106 +-- cpp_utils/MAX7219.h | 43 +- cpp_utils/MFRC522.cpp | 563 +++++------- cpp_utils/MFRC522.h | 46 +- cpp_utils/MFRC522Debug.h | 3 +- cpp_utils/MMU.cpp | 37 +- cpp_utils/MPU6050.cpp | 13 +- cpp_utils/MPU6050.h | 34 +- cpp_utils/MRFC522Debug.cpp | 40 +- cpp_utils/Makefile.arduino | 1 - cpp_utils/Memory.cpp | 23 +- cpp_utils/Memory.h | 1 - cpp_utils/NeoPixelWiFiEventHandler.h | 3 +- cpp_utils/OV7670.cpp | 229 +++-- cpp_utils/OV7670.h | 4 +- cpp_utils/PCF8574.cpp | 11 +- cpp_utils/PCF8574.h | 3 +- cpp_utils/PCF8575.cpp | 20 +- cpp_utils/PCF8575.h | 2 +- cpp_utils/PWM.cpp | 11 +- cpp_utils/PWM.h | 4 +- cpp_utils/PubSubClient.cpp | 248 ++--- cpp_utils/PubSubClient.h | 48 +- cpp_utils/RESTClient.cpp | 26 +- cpp_utils/RESTClient.h | 17 +- cpp_utils/RMT.cpp | 16 +- cpp_utils/RMT.h | 4 +- cpp_utils/SOC.cpp | 8 +- cpp_utils/SPI.cpp | 5 +- cpp_utils/SPI.h | 45 +- cpp_utils/SSLUtils.cpp | 4 +- cpp_utils/SmartLED.cpp | 130 ++- cpp_utils/SmartLED.h | 3 +- cpp_utils/SockServ.cpp | 25 +- cpp_utils/SockServ.h | 3 +- cpp_utils/Socket.cpp | 244 +++-- cpp_utils/Socket.h | 30 +- cpp_utils/System.cpp | 17 +- cpp_utils/System.h | 2 +- cpp_utils/TFTP.cpp | 88 +- cpp_utils/TFTP.h | 8 +- cpp_utils/Task.cpp | 16 +- cpp_utils/Task.h | 9 +- cpp_utils/U8G2.h | 13 +- cpp_utils/WS2812.cpp | 154 ++-- cpp_utils/WS2812.h | 12 +- cpp_utils/WebServer.cpp | 919 +++++++++---------- cpp_utils/WebServer.h | 365 ++++---- cpp_utils/WebSocket.cpp | 63 +- cpp_utils/WebSocket.h | 32 +- cpp_utils/WebSocketFileTransfer.cpp | 33 +- cpp_utils/WebSocketFileTransfer.h | 2 +- cpp_utils/WiFi.cpp | 188 ++-- cpp_utils/WiFi.h | 174 ++-- cpp_utils/WiFiEventHandler.cpp | 72 +- cpp_utils/WiFiEventHandler.h | 4 +- cpp_utils/mainpage.dox | 2 +- 138 files changed, 3402 insertions(+), 3910 deletions(-) diff --git a/cpp_utils/Apa102.cpp b/cpp_utils/Apa102.cpp index 274ec83f..c35c9150 100644 --- a/cpp_utils/Apa102.cpp +++ b/cpp_utils/Apa102.cpp @@ -37,7 +37,7 @@ void Apa102::show() { double brigthnessScale = getBrightness() / 100.0; // Loop over all the pixels in the pixels array to set the colors. - for (int i=0; i(payload), length)); break; @@ -274,16 +273,16 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { case ESP_BLE_AD_TYPE_16SRV_CMPL: case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02 - for (int var = 0; var < length/2; ++var) { - setServiceUUID(BLEUUID(*reinterpret_cast(payload+var*2))); + for (int var = 0; var < length / 2; ++var) { + setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 2))); } break; } // ESP_BLE_AD_TYPE_16SRV_PART case ESP_BLE_AD_TYPE_32SRV_CMPL: case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04 - for (int var = 0; var < length/4; ++var) { - setServiceUUID(BLEUUID(*reinterpret_cast(payload+var*4))); + for (int var = 0; var < length / 4; ++var) { + setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 4))); } break; } // ESP_BLE_AD_TYPE_32SRV_PART @@ -309,10 +308,10 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_SERVICE_DATA"); break; } - uint16_t uuid = *(uint16_t *)payload; + uint16_t uuid = *(uint16_t*) payload; setServiceDataUUID(BLEUUID(uuid)); if (length > 2) { - setServiceData(std::string(reinterpret_cast(payload+2), length-2)); + setServiceData(std::string(reinterpret_cast(payload + 2), length - 2)); } break; } //ESP_BLE_AD_TYPE_SERVICE_DATA @@ -322,10 +321,10 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA"); break; } - uint32_t uuid = *(uint32_t *)payload; + uint32_t uuid = *(uint32_t*) payload; setServiceDataUUID(BLEUUID(uuid)); if (length > 4) { - setServiceData(std::string(reinterpret_cast(payload+4), length-4)); + setServiceData(std::string(reinterpret_cast(payload + 4), length - 4)); } break; } //ESP_BLE_AD_TYPE_32SERVICE_DATA @@ -336,9 +335,9 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { break; } - setServiceDataUUID(BLEUUID(payload, (size_t)16, false)); + setServiceDataUUID(BLEUUID(payload, (size_t) 16, false)); if (length > 16) { - setServiceData(std::string(reinterpret_cast(payload+16), length-16)); + setServiceData(std::string(reinterpret_cast(payload + 16), length - 16)); } break; } //ESP_BLE_AD_TYPE_32SERVICE_DATA @@ -352,7 +351,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { } // Length <> 0 - if (sizeConsumed >=31 || length == 0) { + if (sizeConsumed >= 31 || length == 0) { finished = true; } } // !finished @@ -395,7 +394,7 @@ void BLEAdvertisedDevice::setAppearance(uint16_t appearance) { void BLEAdvertisedDevice::setManufacturerData(std::string manufacturerData) { m_manufacturerData = manufacturerData; m_haveManufacturerData = true; - char* pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)m_manufacturerData.data(), (uint8_t)m_manufacturerData.length()); + char* pHex = BLEUtils::buildHexData(nullptr, (uint8_t*) m_manufacturerData.data(), (uint8_t) m_manufacturerData.length()); ESP_LOGD(LOG_TAG, "- manufacturer data: %s", pHex); free(pHex); } // setManufacturerData @@ -517,4 +516,3 @@ void BLEAdvertisedDevice::setPayload(uint8_t* payload) { #endif /* CONFIG_BT_ENABLED */ - diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index a3b1e6e2..eebdd6ef 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -72,7 +72,6 @@ class BLEAdvertisedDevice { void setTXPower(int8_t txPower); void setPayload(uint8_t* payload); - bool m_haveAppearance; bool m_haveManufacturerData; bool m_haveName; @@ -82,7 +81,6 @@ class BLEAdvertisedDevice { bool m_haveTXPower; - BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); uint8_t m_adFlag; uint16_t m_appearance; int m_deviceType; @@ -95,6 +93,7 @@ class BLEAdvertisedDevice { std::string m_serviceData; BLEUUID m_serviceDataUUID; uint8_t* m_payload; + BLEAddress m_address = BLEAddress((uint8_t*) "\0\0\0\0\0\0"); }; /** diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 4b3cb8d6..ea3825ac 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -107,7 +107,7 @@ void BLEAdvertising::setMaxInterval(uint16_t maxinterval) { * @param [in] scanRequestWhitelistOnly If true, only allow scan requests from those on the white list. * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. */ -void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { +void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { ESP_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly); if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; @@ -173,21 +173,20 @@ void BLEAdvertising::setScanResponseData(BLEAdvertisementData& advertisementData void BLEAdvertising::start() { ESP_LOGD(LOG_TAG, ">> start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); - // We have a vector of service UUIDs that we wish to advertise. In order to use the // ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte) // representations. If we have 1 or more services to advertise then we allocate enough // storage to host them and then copy them in one at a time into the contiguous storage. int numServices = m_serviceUUIDs.size(); if (numServices > 0) { - m_advData.service_uuid_len = 16*numServices; - m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; + m_advData.service_uuid_len = 16 * numServices; + m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; uint8_t* p = m_advData.p_service_uuid; - for (int i=0; iuuid.uuid128, 16); - p+=16; + p += 16; } } else { m_advData.service_uuid_len = 0; @@ -196,8 +195,8 @@ void BLEAdvertising::start() { esp_err_t errRc; - if (m_customAdvData == false) { - // Set the configuration for advertising. + if (!m_customAdvData) { + // Set the configuration for advertising. m_advData.set_scan_rsp = false; errRc = ::esp_ble_gap_config_adv_data(&m_advData); if (errRc != ESP_OK) { @@ -206,7 +205,7 @@ void BLEAdvertising::start() { } } - if (m_customScanResponseData == false) { + if (!m_customScanResponseData) { m_advData.set_scan_rsp = true; errRc = ::esp_ble_gap_config_adv_data(&m_advData); if (errRc != ESP_OK) { @@ -270,7 +269,7 @@ void BLEAdvertisementData::setAppearance(uint16_t appearance) { char cdata[2]; cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_APPEARANCE; // 0x19 - addData(std::string(cdata, 2) + std::string((char *)&appearance,2)); + addData(std::string(cdata, 2) + std::string((char*) &appearance, 2)); } // setAppearance @@ -280,12 +279,12 @@ void BLEAdvertisementData::setAppearance(uint16_t appearance) { */ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { char cdata[2]; - switch(uuid.bitSize()) { + switch (uuid.bitSize()) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_CMPL; // 0x03 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2)); break; } @@ -293,7 +292,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_CMPL; // 0x05 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4)); break; } @@ -301,7 +300,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; // 0x07 - addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid128, 16)); break; } @@ -341,7 +340,7 @@ void BLEAdvertisementData::setManufacturerData(std::string data) { char cdata[2]; cdata[0] = data.length() + 1; cdata[1] = ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE; // 0xff - addData(std::string(cdata, 2) + data); + addData(std::string(cdata, 2) + data); ESP_LOGD("BLEAdvertisementData", "<< setManufacturerData"); } // setManufacturerData @@ -355,7 +354,7 @@ void BLEAdvertisementData::setName(std::string name) { char cdata[2]; cdata[0] = name.length() + 1; cdata[1] = ESP_BLE_AD_TYPE_NAME_CMPL; // 0x09 - addData(std::string(cdata, 2) + name); + addData(std::string(cdata, 2) + name); ESP_LOGD("BLEAdvertisementData", "<< setName"); } // setName @@ -366,12 +365,12 @@ void BLEAdvertisementData::setName(std::string name) { */ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { char cdata[2]; - switch(uuid.bitSize()) { + switch (uuid.bitSize()) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_PART; // 0x02 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); + addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid16, 2)); break; } @@ -379,7 +378,7 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_PART; // 0x04 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); + addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid32, 4)); break; } @@ -387,7 +386,7 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_PART; // 0x06 - addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); + addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid128, 16)); break; } @@ -404,12 +403,12 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { */ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { char cdata[2]; - switch(uuid.bitSize()) { + switch (uuid.bitSize()) { case 16: { // [Len] [0x16] [UUID16] data cdata[0] = data.length() + 3; cdata[1] = ESP_BLE_AD_TYPE_SERVICE_DATA; // 0x16 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2) + data); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2) + data); break; } @@ -417,7 +416,7 @@ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { // [Len] [0x20] [UUID32] data cdata[0] = data.length() + 5; cdata[1] = ESP_BLE_AD_TYPE_32SERVICE_DATA; // 0x20 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4) + data); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4) + data); break; } @@ -425,7 +424,7 @@ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { // [Len] [0x21] [UUID128] data cdata[0] = data.length() + 17; cdata[1] = ESP_BLE_AD_TYPE_128SERVICE_DATA; // 0x21 - addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16) + data); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid128, 16) + data); break; } @@ -444,12 +443,11 @@ void BLEAdvertisementData::setShortName(std::string name) { char cdata[2]; cdata[0] = name.length() + 1; cdata[1] = ESP_BLE_AD_TYPE_NAME_SHORT; // 0x08 - addData(std::string(cdata, 2) + name); + addData(std::string(cdata, 2) + name); ESP_LOGD("BLEAdvertisementData", "<< setShortName"); } // setShortName - /** * @brief Retrieve the payload that is to be advertised. * @return The payload that is to be advertised. diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index e3165298..f7cfc240 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -19,7 +19,6 @@ class BLEAdvertisementData { // Only a subset of the possible BLE architected advertisement fields are currently exposed. Others will // be exposed on demand/request or as time permits. - // public: void setAppearance(uint16_t appearance); void setCompleteServices(BLEUUID uuid); diff --git a/cpp_utils/BLEBeacon.cpp b/cpp_utils/BLEBeacon.cpp index a63197ca..854b661d 100644 --- a/cpp_utils/BLEBeacon.cpp +++ b/cpp_utils/BLEBeacon.cpp @@ -25,7 +25,7 @@ BLEBeacon::BLEBeacon() { } // BLEBeacon std::string BLEBeacon::getData() { - return std::string((char*)&m_beaconData, sizeof(m_beaconData)); + return std::string((char*) &m_beaconData, sizeof(m_beaconData)); } // getData uint16_t BLEBeacon::getMajor() { diff --git a/cpp_utils/BLEBeacon.h b/cpp_utils/BLEBeacon.h index 0b02e2bd..277bd670 100644 --- a/cpp_utils/BLEBeacon.h +++ b/cpp_utils/BLEBeacon.h @@ -23,7 +23,7 @@ class BLEBeacon { uint16_t major; uint16_t minor; int8_t signalPower; - } __attribute__((packed))m_beaconData; + } __attribute__((packed)) m_beaconData; public: BLEBeacon(); std::string getData(); diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 56a27ddb..6f696c7c 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -44,15 +44,15 @@ BLECharacteristic::BLECharacteristic(const char* uuid, uint32_t properties) : BL BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { m_bleUUID = uuid; m_handle = NULL_HANDLE; - m_properties = (esp_gatt_char_prop_t)0; + m_properties = (esp_gatt_char_prop_t) 0; m_pCallbacks = nullptr; - setBroadcastProperty((properties & PROPERTY_BROADCAST) !=0); - setReadProperty((properties & PROPERTY_READ) !=0); - setWriteProperty((properties & PROPERTY_WRITE) !=0); - setNotifyProperty((properties & PROPERTY_NOTIFY) !=0); - setIndicateProperty((properties & PROPERTY_INDICATE) !=0); - setWriteNoResponseProperty((properties & PROPERTY_WRITE_NR) !=0); + setBroadcastProperty((properties & PROPERTY_BROADCAST) != 0); + setReadProperty((properties & PROPERTY_READ) != 0); + setWriteProperty((properties & PROPERTY_WRITE) != 0); + setNotifyProperty((properties & PROPERTY_NOTIFY) != 0); + setIndicateProperty((properties & PROPERTY_INDICATE) != 0); + setWriteNoResponseProperty((properties & PROPERTY_WRITE_NR) != 0); } // BLECharacteristic /** @@ -216,7 +216,7 @@ void BLECharacteristic::handleGATTServerEvent( esp_ble_gatts_cb_param_t* param) { ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); - switch(event) { + switch (event) { // Events handled: // // ESP_GATTS_ADD_CHAR_EVT @@ -267,7 +267,7 @@ void BLECharacteristic::handleGATTServerEvent( case ESP_GATTS_ADD_CHAR_EVT: { if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) && getHandle() == param->add_char.attr_handle && - getService()->getHandle()==param->add_char.service_handle) { + getService()->getHandle() == param->add_char.service_handle) { m_semaphoreCreateEvt.give(); } break; @@ -357,7 +357,7 @@ void BLECharacteristic::handleGATTServerEvent( // // If the is_long flag is set then this is a follow on from an original read and we will already have sent at least 22 bytes. // If the is_long flag is not set then we need to check how much data we are going to send. If we are sending LESS than -// 22 bytes, then we "just" send it and thats the end of the story. +// 22 bytes, then we "just" send it and that's the end of the story. // If we are sending 22 bytes exactly, we just send it BUT we will get a follow on request. // If we are sending more than 22 bytes, we send the first 22 bytes and we will get a follow on request. // Because of follow on request processing, we need to maintain an offset of how much data we have already sent @@ -367,12 +367,10 @@ void BLECharacteristic::handleGATTServerEvent( // // The following code has deliberately not been factored to make it fewer statements because this would cloud the // the logic flow comprehension. -// + // TODO requires some more research to confirm that 512 is max PDU like in bluetooth specs uint16_t maxOffset = BLEDevice::getMTU() - 1; - if (BLEDevice::getMTU() > 512) { - maxOffset = 512; - } + if (BLEDevice::getMTU() > 512) maxOffset = 512; if (param->read.need_rsp) { ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); esp_gatt_rsp_t rsp; @@ -401,7 +399,7 @@ void BLECharacteristic::handleGATTServerEvent( std::string value = m_value.getValue(); - if (value.length()+1 > maxOffset) { + if (value.length() + 1 > maxOffset) { // Too big for a single shot entry. m_value.setReadOffset(maxOffset); rsp.attr_value.len = maxOffset; @@ -477,7 +475,6 @@ void BLECharacteristic::handleGATTServerEvent( * @return N/A */ void BLECharacteristic::indicate() { - ESP_LOGD(LOG_TAG, ">> indicate: length: %d", m_value.getValue().length()); assert(getService() != nullptr); @@ -493,7 +490,7 @@ void BLECharacteristic::indicate() { // Test to see if we have a 0x2902 descriptor. If we do, then check to see if indications are enabled // and, if not, prevent the indication. - BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); + BLE2902 *p2902 = (BLE2902*) getDescriptorByUUID((uint16_t) 0x2902); if (p2902 != nullptr && !p2902->getIndications()) { ESP_LOGD(LOG_TAG, "<< indications disabled; ignoring"); return; @@ -531,11 +528,9 @@ void BLECharacteristic::indicate() { void BLECharacteristic::notify() { ESP_LOGD(LOG_TAG, ">> notify: length: %d", m_value.getValue().length()); - assert(getService() != nullptr); assert(getService()->getServer() != nullptr); - GeneralUtils::hexDump((uint8_t*)m_value.getValue().data(), m_value.getValue().length()); if (getService()->getServer()->getConnectedCount() == 0) { @@ -546,7 +541,7 @@ void BLECharacteristic::notify() { // Test to see if we have a 0x2902 descriptor. If we do, then check to see if notification is enabled // and, if not, prevent the notification. - BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); + BLE2902 *p2902 = (BLE2902*) getDescriptorByUUID((uint16_t) 0x2902); if (p2902 != nullptr && !p2902->getNotifications()) { ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring"); return; @@ -585,9 +580,9 @@ void BLECharacteristic::notify() { void BLECharacteristic::setBroadcastProperty(bool value) { //ESP_LOGD(LOG_TAG, "setBroadcastProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_BROADCAST); + m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_BROADCAST); } else { - m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); } } // setBroadcastProperty @@ -597,7 +592,7 @@ void BLECharacteristic::setBroadcastProperty(bool value) { * @param [in] pCallbacks An instance of a callbacks structure used to define any callbacks for the characteristic. */ void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) { - ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallbacks); + ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t) pCallbacks); m_pCallbacks = pCallbacks; ESP_LOGD(LOG_TAG, "<< setCallbacks"); } // setCallbacks @@ -627,9 +622,9 @@ void BLECharacteristic::setHandle(uint16_t handle) { void BLECharacteristic::setIndicateProperty(bool value) { //ESP_LOGD(LOG_TAG, "setIndicateProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_INDICATE); + m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_INDICATE); } else { - m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); } } // setIndicateProperty @@ -641,9 +636,9 @@ void BLECharacteristic::setIndicateProperty(bool value) { void BLECharacteristic::setNotifyProperty(bool value) { //ESP_LOGD(LOG_TAG, "setNotifyProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_NOTIFY); } else { - m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); } } // setNotifyProperty @@ -655,9 +650,9 @@ void BLECharacteristic::setNotifyProperty(bool value) { void BLECharacteristic::setReadProperty(bool value) { //ESP_LOGD(LOG_TAG, "setReadProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_READ); + m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_READ); } else { - m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_READ); + m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_READ); } } // setReadProperty @@ -668,7 +663,7 @@ void BLECharacteristic::setReadProperty(bool value) { * @param [in] length The length of the data in bytes. */ void BLECharacteristic::setValue(uint8_t* data, size_t length) { - char *pHex = BLEUtils::buildHexData(nullptr, data, length); + char* pHex = BLEUtils::buildHexData(nullptr, data, length); ESP_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s", length, pHex, getUUID().toString().c_str()); free(pHex); if (length > ESP_GATT_MAX_ATTR_LEN) { @@ -688,43 +683,43 @@ void BLECharacteristic::setValue(uint8_t* data, size_t length) { * @return N/A. */ void BLECharacteristic::setValue(std::string value) { - setValue((uint8_t*)(value.data()), value.length()); + setValue((uint8_t*) (value.data()), value.length()); } // setValue void BLECharacteristic::setValue(uint16_t& data16) { uint8_t temp[2]; - temp[0]=data16; - temp[1]=data16>>8; + temp[0] = data16; + temp[1] = data16 >> 8; setValue(temp, 2); } // setValue void BLECharacteristic::setValue(uint32_t& data32) { uint8_t temp[4]; - temp[0]=data32; - temp[1]=data32>>8; - temp[2]=data32>>16; - temp[3]=data32>>24; + temp[0] = data32; + temp[1] = data32 >> 8; + temp[2] = data32 >> 16; + temp[3] = data32 >> 24; setValue(temp, 4); } // setValue void BLECharacteristic::setValue(int& data32) { uint8_t temp[4]; - temp[0]=data32; - temp[1]=data32>>8; - temp[2]=data32>>16; - temp[3]=data32>>24; + temp[0] = data32; + temp[1] = data32 >> 8; + temp[2] = data32 >> 16; + temp[3] = data32 >> 24; setValue(temp, 4); } // setValue void BLECharacteristic::setValue(float& data32) { uint8_t temp[4]; - *((float *)temp) = data32; + *((float*) temp) = data32; setValue(temp, 4); } // setValue void BLECharacteristic::setValue(double& data64) { uint8_t temp[8]; - *((double *)temp) = data64; + *((double*) temp) = data64; setValue(temp, 8); } // setValue @@ -736,9 +731,9 @@ void BLECharacteristic::setValue(double& data64) { void BLECharacteristic::setWriteNoResponseProperty(bool value) { //ESP_LOGD(LOG_TAG, "setWriteNoResponseProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } else { - m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } } // setWriteNoResponseProperty @@ -750,9 +745,9 @@ void BLECharacteristic::setWriteNoResponseProperty(bool value) { void BLECharacteristic::setWriteProperty(bool value) { //ESP_LOGD(LOG_TAG, "setWriteProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE); + m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE); } else { - m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE); } } // setWriteProperty @@ -766,12 +761,12 @@ std::string BLECharacteristic::toString() { stringstream << std::hex << std::setfill('0'); stringstream << "UUID: " << m_bleUUID.toString() + ", handle: 0x" << std::setw(2) << m_handle; stringstream << " " << - ((m_properties & ESP_GATT_CHAR_PROP_BIT_READ)?"Read ":"") << - ((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE)?"Write ":"") << - ((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE_NR)?"WriteNoResponse ":"") << - ((m_properties & ESP_GATT_CHAR_PROP_BIT_BROADCAST)?"Broadcast ":"") << - ((m_properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)?"Notify ":"") << - ((m_properties & ESP_GATT_CHAR_PROP_BIT_INDICATE)?"Indicate ":""); + ((m_properties & ESP_GATT_CHAR_PROP_BIT_READ) ? "Read " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE) ? "Write " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) ? "WriteNoResponse " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_BROADCAST) ? "Broadcast " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY) ? "Notify " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_INDICATE) ? "Indicate " : ""); return stringstream.str(); } // toString @@ -783,7 +778,7 @@ BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {} * @brief Callback function to support a read request. * @param [in] pCharacteristic The characteristic that is the source of the event. */ -void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic) { +void BLECharacteristicCallbacks::onRead(BLECharacteristic* pCharacteristic) { ESP_LOGD("BLECharacteristicCallbacks", ">> onRead: default"); ESP_LOGD("BLECharacteristicCallbacks", "<< onRead"); } // onRead @@ -793,7 +788,7 @@ void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic) { * @brief Callback function to support a write request. * @param [in] pCharacteristic The characteristic that is the source of the event. */ -void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic) { +void BLECharacteristicCallbacks::onWrite(BLECharacteristic* pCharacteristic) { ESP_LOGD("BLECharacteristicCallbacks", ">> onWrite: default"); ESP_LOGD("BLECharacteristicCallbacks", "<< onWrite"); } // onWrite diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index e627628e..bad5ba7a 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -27,30 +27,30 @@ class BLECharacteristicCallbacks; */ class BLEDescriptorMap { public: - void setByUUID(const char* uuid, BLEDescriptor *pDescriptor); - void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor); - void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor); + void setByUUID(const char* uuid, BLEDescriptor* pDescriptor); + void setByUUID(BLEUUID uuid, BLEDescriptor* pDescriptor); + void setByHandle(uint16_t handle, BLEDescriptor* pDescriptor); BLEDescriptor* getByUUID(const char* uuid); BLEDescriptor* getByUUID(BLEUUID uuid); BLEDescriptor* getByHandle(uint16_t handle); - std::string toString(); + std::string toString(); void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); BLEDescriptor* getFirst(); BLEDescriptor* getNext(); private: - std::map m_uuidMap; - std::map m_handleMap; - std::map::iterator m_iterator; + std::map m_uuidMap; + std::map m_handleMap; + std::map::iterator m_iterator; }; /** * @brief The model of a %BLE Characteristic. * - * A %BLE Characteristic is an identified value container that manages a value. It is exposed by a %BLE server and + * A %BLE Characteristic is an identified value container that manages a value. It is exposed by a %BLE server and * can be read and written to by a %BLE client. */ class BLECharacteristic { @@ -59,13 +59,13 @@ class BLECharacteristic { BLECharacteristic(BLEUUID uuid, uint32_t properties = 0); virtual ~BLECharacteristic(); - void addDescriptor(BLEDescriptor* pDescriptor); + void addDescriptor(BLEDescriptor* pDescriptor); BLEDescriptor* getDescriptorByUUID(const char* descriptorUUID); BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); - //size_t getLength(); - BLEUUID getUUID(); - std::string getValue(); - uint8_t* getData(); + //size_t getLength(); + BLEUUID getUUID(); + std::string getValue(); + uint8_t* getData(); void indicate(); void notify(); @@ -80,19 +80,19 @@ class BLECharacteristic { void setValue(uint32_t& data32); void setValue(int& data32); void setValue(float& data32); - void setValue(double& data64); + void setValue(double& data64); void setWriteProperty(bool value); void setWriteNoResponseProperty(bool value); std::string toString(); uint16_t getHandle(); void setAccessPermissions(esp_gatt_perm_t perm); - static const uint32_t PROPERTY_READ = 1<<0; - static const uint32_t PROPERTY_WRITE = 1<<1; - static const uint32_t PROPERTY_NOTIFY = 1<<2; - static const uint32_t PROPERTY_BROADCAST = 1<<3; - static const uint32_t PROPERTY_INDICATE = 1<<4; - static const uint32_t PROPERTY_WRITE_NR = 1<<5; + static const uint32_t PROPERTY_READ = 1 << 0; + static const uint32_t PROPERTY_WRITE = 1 << 1; + static const uint32_t PROPERTY_NOTIFY = 1 << 2; + static const uint32_t PROPERTY_BROADCAST = 1 << 3; + static const uint32_t PROPERTY_INDICATE = 1 << 4; + static const uint32_t PROPERTY_WRITE_NR = 1 << 5; private: @@ -101,24 +101,24 @@ class BLECharacteristic { friend class BLEDescriptor; friend class BLECharacteristicMap; - BLEUUID m_bleUUID; - BLEDescriptorMap m_descriptorMap; - uint16_t m_handle; - esp_gatt_char_prop_t m_properties; + BLEUUID m_bleUUID; + BLEDescriptorMap m_descriptorMap; + uint16_t m_handle; + esp_gatt_char_prop_t m_properties; BLECharacteristicCallbacks* m_pCallbacks; - BLEService* m_pService; - BLEValue m_value; - esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + BLEService* m_pService; + BLEValue m_value; + esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); - void executeCreate(BLEService* pService); + void executeCreate(BLEService* pService); esp_gatt_char_prop_t getProperties(); - BLEService* getService(); - void setHandle(uint16_t handle); + BLEService* getService(); + void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt"); }; // BLECharacteristic @@ -128,7 +128,7 @@ class BLECharacteristic { * @brief Callbacks that can be associated with a %BLE characteristic to inform of events. * * When a server application creates a %BLE characteristic, we may wish to be informed when there is either - * a read or write request to the characteristic's value. An application can register a + * a read or write request to the characteristic's value. An application can register a * sub-classed instance of this class and will be notified when such an event happens. */ class BLECharacteristicCallbacks { diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index 86a97441..8e7d097d 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -56,9 +56,7 @@ BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) { */ BLECharacteristic* BLECharacteristicMap::getFirst() { m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } + if (m_iterator == m_uuidMap.end()) return nullptr; BLECharacteristic* pRet = m_iterator->first; m_iterator++; return pRet; @@ -70,9 +68,7 @@ BLECharacteristic* BLECharacteristicMap::getFirst() { * @return The next characteristic in the map. */ BLECharacteristic* BLECharacteristicMap::getNext() { - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } + if (m_iterator == m_uuidMap.end()) return nullptr; BLECharacteristic* pRet = m_iterator->first; m_iterator++; return pRet; @@ -103,8 +99,8 @@ void BLECharacteristicMap::handleGATTServerEvent( * @return N/A. */ void BLECharacteristicMap::setByHandle(uint16_t handle, - BLECharacteristic *characteristic) { - m_handleMap.insert(std::pair(handle, characteristic)); + BLECharacteristic* characteristic) { + m_handleMap.insert(std::pair(handle, characteristic)); } // setByHandle @@ -115,9 +111,9 @@ void BLECharacteristicMap::setByHandle(uint16_t handle, * @return N/A. */ void BLECharacteristicMap::setByUUID( - BLECharacteristic *pCharacteristic, BLEUUID uuid) { - m_uuidMap.insert(std::pair(pCharacteristic, uuid.toString())); + BLECharacteristic* pCharacteristic, + m_uuidMap.insert(std::pair(pCharacteristic, uuid.toString())); } // setByUUID @@ -128,7 +124,7 @@ void BLECharacteristicMap::setByUUID( std::string BLECharacteristicMap::toString() { std::stringstream stringStream; stringStream << std::hex << std::setfill('0'); - int count=0; + int count = 0; for (auto &myPair: m_uuidMap) { if (count > 0) { stringStream << "\n"; diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 141cf0f5..2a8d6f0c 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -40,7 +40,6 @@ * We will assume that a BLERemoteService contains a map that maps BLEUUIDs to the set of owned characteristics * and that a BLECharacteristic contains a map that maps BLEUUIDs to the set of owned descriptors. * - * */ static const char* LOG_TAG = "BLEClient"; @@ -151,7 +150,7 @@ void BLEClient::gattClientEventHandler( esp_ble_gattc_cb_param_t* evtParam) { // Execute handler code based on the type of event received. - switch(event) { + switch (event) { // // ESP_GATTC_DISCONNECT_EVT @@ -238,7 +237,7 @@ void BLEClient::gattClientEventHandler( evtParam->search_res.start_handle, evtParam->search_res.end_handle ); - m_servicesMap.insert(std::pair(uuid.toString(), pRemoteService)); + m_servicesMap.insert(std::pair(uuid.toString(), pRemoteService)); break; } // ESP_GATTC_SEARCH_RES_EVT @@ -369,7 +368,7 @@ std::map* BLEClient::getServices() { ESP_LOGE(LOG_TAG, "esp_ble_gattc_search_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return &m_servicesMap; } - // If sucessfull, remember that we now have services. + // If successful, remember that we now have services. m_haveServices = (m_semaphoreSearchCmplEvt.wait("getServices") == 0); ESP_LOGD(LOG_TAG, "<< getServices"); return &m_servicesMap; @@ -400,7 +399,7 @@ void BLEClient::handleGAPEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) { ESP_LOGD(LOG_TAG, "BLEClient ... handling GAP event!"); - switch(event) { + switch (event) { // // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT // @@ -410,7 +409,7 @@ void BLEClient::handleGAPEvent( // - esp_bd_addr_t remote_addr // case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: { - m_semaphoreRssiCmplEvt.give((uint32_t)param->read_rssi_cmpl.rssi); + m_semaphoreRssiCmplEvt.give((uint32_t) param->read_rssi_cmpl.rssi); break; } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index a60ed102..d66a8b4f 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -47,8 +47,8 @@ class BLEClient { bool isConnected(); // Return true if we are connected. - void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a given characteristic at a given service. + void setClientCallbacks(BLEClientCallbacks* pClientCallbacks); std::string toString(); // Return a string representation of this client. @@ -66,9 +66,9 @@ class BLEClient { uint16_t getConnId(); esp_gatt_if_t getGattcIf(); - BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); // The BD address of the remote server. uint16_t m_conn_id; // int m_deviceType; + BLEAddress m_peerAddress = BLEAddress((uint8_t*) "\0\0\0\0\0\0"); // The BD address of the remote server. esp_gatt_if_t m_gattc_if; bool m_haveServices; // Have we previously obtain the set of services from the remote server. bool m_isConnected; // Are we currently connected. @@ -80,7 +80,6 @@ class BLEClient { FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt"); std::map m_servicesMap; void clearServices(); // Clear any existing services. - }; // class BLEDevice diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 58ff78b4..5f23026f 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -37,13 +37,13 @@ BLEDescriptor::BLEDescriptor(const char* uuid) : BLEDescriptor(BLEUUID(uuid)) { */ BLEDescriptor::BLEDescriptor(BLEUUID uuid) { m_bleUUID = uuid; - m_value.attr_value = (uint8_t *)malloc(ESP_GATT_MAX_ATTR_LEN); // Allocate storage for the value. m_value.attr_len = 0; // Initial length is 0. m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; // Maximum length of the data. m_handle = NULL_HANDLE; // Handle is initially unknown. m_pCharacteristic = nullptr; // No initial characteristic. m_pCallback = nullptr; // No initial callback. + m_value.attr_value = (uint8_t*) malloc(ESP_GATT_MAX_ATTR_LEN); // Allocate storage for the value. } // BLEDescriptor @@ -133,8 +133,8 @@ uint8_t* BLEDescriptor::getValue() { void BLEDescriptor::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { - switch(event) { + esp_ble_gatts_cb_param_t* param) { + switch (event) { // ESP_GATTS_ADD_CHAR_DESCR_EVT // // add_char_descr: @@ -247,10 +247,9 @@ void BLEDescriptor::handleGATTServerEvent( break; } // ESP_GATTS_READ_EVT - default: { + default: break; - } - }// switch event + } // switch event } // handleGATTServerEvent @@ -259,7 +258,7 @@ void BLEDescriptor::handleGATTServerEvent( * @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor. */ void BLEDescriptor::setCallbacks(BLEDescriptorCallbacks* pCallback) { - ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallback); + ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t) pCallback); m_pCallback = pCallback; ESP_LOGD(LOG_TAG, "<< setCallbacks"); } // setCallbacks @@ -298,7 +297,7 @@ void BLEDescriptor::setValue(uint8_t* data, size_t length) { * @param [in] value The value of the descriptor in string form. */ void BLEDescriptor::setValue(std::string value) { - setValue((uint8_t *)value.data(), value.length()); + setValue((uint8_t*) value.data(), value.length()); } // setValue void BLEDescriptor::setAccessPermissions(esp_gatt_perm_t perm) { diff --git a/cpp_utils/BLEDescriptorMap.cpp b/cpp_utils/BLEDescriptorMap.cpp index 4e372e1d..075b3025 100644 --- a/cpp_utils/BLEDescriptorMap.cpp +++ b/cpp_utils/BLEDescriptorMap.cpp @@ -21,7 +21,7 @@ * @return The descriptor. If not present, then nullptr is returned. */ BLEDescriptor* BLEDescriptorMap::getByUUID(const char* uuid) { - return getByUUID(BLEUUID(uuid)); + return getByUUID(BLEUUID(uuid)); } @@ -57,8 +57,8 @@ BLEDescriptor* BLEDescriptorMap::getByHandle(uint16_t handle) { * @param [in] characteristic The descriptor to cache. * @return N/A. */ -void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor *pDescriptor){ - m_uuidMap.insert(std::pair(uuid, pDescriptor)); +void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor* pDescriptor){ + m_uuidMap.insert(std::pair(uuid, pDescriptor)); } // setByUUID @@ -69,8 +69,8 @@ void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor *pDescriptor){ * @param [in] characteristic The descriptor to cache. * @return N/A. */ -void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor) { - m_uuidMap.insert(std::pair(uuid.toString(), pDescriptor)); +void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor* pDescriptor) { + m_uuidMap.insert(std::pair(uuid.toString(), pDescriptor)); } // setByUUID @@ -80,9 +80,8 @@ void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor) { * @param [in] descriptor The descriptor to cache. * @return N/A. */ -void BLEDescriptorMap::setByHandle(uint16_t handle, - BLEDescriptor *pDescriptor) { - m_handleMap.insert(std::pair(handle, pDescriptor)); +void BLEDescriptorMap::setByHandle(uint16_t handle, BLEDescriptor* pDescriptor) { + m_handleMap.insert(std::pair(handle, pDescriptor)); } // setByHandle @@ -93,8 +92,8 @@ void BLEDescriptorMap::setByHandle(uint16_t handle, std::string BLEDescriptorMap::toString() { std::stringstream stringStream; stringStream << std::hex << std::setfill('0'); - int count=0; - for (auto &myPair: m_uuidMap) { + int count = 0; + for (auto &myPair : m_uuidMap) { if (count > 0) { stringStream << "\n"; } @@ -114,7 +113,7 @@ std::string BLEDescriptorMap::toString() { void BLEDescriptorMap::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { + esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every descriptor we have. for (auto &myPair : m_uuidMap) { myPair.second->handleGATTServerEvent(event, gatts_if, param); @@ -128,10 +127,8 @@ void BLEDescriptorMap::handleGATTServerEvent( */ BLEDescriptor* BLEDescriptorMap::getFirst() { m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLEDescriptor *pRet = m_iterator->second; + if (m_iterator == m_uuidMap.end()) return nullptr; + BLEDescriptor* pRet = m_iterator->second; m_iterator++; return pRet; } // getFirst @@ -142,10 +139,8 @@ BLEDescriptor* BLEDescriptorMap::getFirst() { * @return The next descriptor in the map. */ BLEDescriptor* BLEDescriptorMap::getNext() { - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLEDescriptor *pRet = m_iterator->second; + if (m_iterator == m_uuidMap.end()) return nullptr; + BLEDescriptor* pRet = m_iterator->second; m_iterator++; return pRet; } // getNext diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 157074d4..fd86abe0 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -42,7 +42,7 @@ BLEServer* BLEDevice::m_pServer = nullptr; BLEScan* BLEDevice::m_pScan = nullptr; BLEClient* BLEDevice::m_pClient = nullptr; bool initialized = false; // Have we been initialized? -esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; +esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t) 0; BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; uint16_t BLEDevice::m_localMTU = 23; BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; @@ -98,11 +98,11 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; BLEUtils::dumpGattServerEvent(event, gatts_if, param); - switch(event) { + switch (event) { case ESP_GATTS_CONNECT_EVT: { BLEDevice::m_localMTU = 23; #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityLevel){ + if (BLEDevice::m_securityLevel) { esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); } #endif // CONFIG_BLE_SMP_ENABLE @@ -146,23 +146,22 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; switch(event) { case ESP_GATTC_CONNECT_EVT: { - if(BLEDevice::getMTU() != 23){ + if (BLEDevice::getMTU() != 23) { esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } } #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityLevel){ + if (BLEDevice::m_securityLevel) { esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); } #endif // CONFIG_BLE_SMP_ENABLE break; } // ESP_GATTC_CONNECT_EVT - default: { + default: break; - } } // switch @@ -179,34 +178,33 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; */ /* STATIC */ void BLEDevice::gapEventHandler( esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t *param) { + esp_ble_gap_cb_param_t* param) { BLEUtils::dumpGapEvent(event, param); - switch(event) { - - case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); - break; - case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); - break; - case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); - break; - case ESP_GAP_BLE_NC_REQ_EVT: - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); + switch (event) { + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + break; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); + break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); + break; + case ESP_GAP_BLE_NC_REQ_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityCallbacks != nullptr) { + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); } #endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityCallbacks != nullptr) { esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); } #endif // CONFIG_BLE_SMP_ENABLE @@ -214,15 +212,14 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; /* * TODO should we add white/black list comparison? */ - case ESP_GAP_BLE_SEC_REQ_EVT: - /* send the positive(true) security response to the peer device to accept the security request. - If not accept the security request, should sent the security response with negative(false) accept value*/ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ + case ESP_GAP_BLE_SEC_REQ_EVT: + /* send the positive(true) security response to the peer device to accept the security request. + If not accept the security request, should sent the security response with negative(false) accept value*/ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityCallbacks != nullptr) { esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); - } - else{ + } else { esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); } #endif // CONFIG_BLE_SMP_ENABLE @@ -230,34 +227,33 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; /* * */ - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. - ///show the passkey number to the user to input it in the peer deivce. - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + // show the passkey number to the user to input it in the peer device. + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityCallbacks != nullptr) { ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); } #endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_KEY_EVT: - //shows the ble key type info share with peer device to the user. - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); + break; + case ESP_GAP_BLE_KEY_EVT: + //shows the ble key type info share with peer device to the user. + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); #endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_AUTH_CMPL_EVT: - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if(BLEDevice::m_securityCallbacks!=nullptr){ - BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); - } + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityCallbacks != nullptr) { + BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); + } #endif // CONFIG_BLE_SMP_ENABLE - break; - default: { + break; + default: break; - } } // switch if (BLEDevice::m_pServer != nullptr) { @@ -316,7 +312,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; */ /* STATIC */ std::string BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID) { ESP_LOGD(LOG_TAG, ">> getValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - BLEClient *pClient = createClient(); + BLEClient* pClient = createClient(); pClient->connect(bdAddress); std::string ret = pClient->getValue(serviceUUID, characteristicUUID); pClient->disconnect(); @@ -330,8 +326,8 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; * @param deviceName The device name of the device. */ /* STATIC */ void BLEDevice::init(std::string deviceName) { - if(!initialized){ - initialized = true; // Set the initialization flag to ensure we are only initialized once. + if (!initialized) { + initialized = true; // Set the initialization flag to ensure we are only initialized once. esp_err_t errRc = ESP_OK; #ifdef ARDUINO_ARCH_ESP32 @@ -354,13 +350,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; } #ifndef CLASSIC_BT_ENABLED - // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue - errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); - //errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); #else errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); if (errRc != ESP_OK) { @@ -371,7 +361,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; #endif esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); - if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED){ + if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED) { errRc = esp_bluedroid_init(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -379,7 +369,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; } } - if (bt_state != ESP_BLUEDROID_STATUS_ENABLED){ + if (bt_state != ESP_BLUEDROID_STATUS_ENABLED) { errRc = esp_bluedroid_enable(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -424,7 +414,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; }; #endif // CONFIG_BLE_SMP_ENABLE } - vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. + vTaskDelay(200 / portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. } // init @@ -459,7 +449,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; */ /* STATIC */ void BLEDevice::setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value) { ESP_LOGD(LOG_TAG, ">> setValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - BLEClient *pClient = createClient(); + BLEClient* pClient = createClient(); pClient->connect(bdAddress); pClient->setValue(serviceUUID, characteristicUUID, value); pClient->disconnect(); @@ -527,7 +517,7 @@ void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { esp_err_t BLEDevice::setMTU(uint16_t mtu) { ESP_LOGD(LOG_TAG, ">> setLocalMTU: %d", mtu); esp_err_t err = esp_ble_gatt_set_local_mtu(mtu); - if(err == ESP_OK){ + if (err == ESP_OK) { m_localMTU = mtu; } else { ESP_LOGE(LOG_TAG, "can't set local mtu value: %d", mtu); @@ -548,8 +538,7 @@ bool BLEDevice::getInitialized() { } BLEAdvertising* BLEDevice::getAdvertising() { - if(m_bleAdvertising == nullptr) - { + if (m_bleAdvertising == nullptr) { m_bleAdvertising = new BLEAdvertising(); ESP_LOGI(LOG_TAG, "create advertising"); } diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 63183d0b..9fd53ebb 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -47,13 +47,13 @@ class BLEDevice { static void startAdvertising(); private: - static BLEServer *m_pServer; - static BLEScan *m_pScan; - static BLEClient *m_pClient; - static esp_ble_sec_act_t m_securityLevel; + static BLEServer* m_pServer; + static BLEScan* m_pScan; + static BLEClient* m_pClient; + static esp_ble_sec_act_t m_securityLevel; static BLESecurityCallbacks* m_securityCallbacks; - static uint16_t m_localMTU; - static BLEAdvertising *m_bleAdvertising; + static uint16_t m_localMTU; + static BLEAdvertising* m_bleAdvertising; static esp_gatt_if_t getGattcIF(); diff --git a/cpp_utils/BLEEddystoneTLM.cpp b/cpp_utils/BLEEddystoneTLM.cpp index b5466b13..a92bcdb2 100755 --- a/cpp_utils/BLEEddystoneTLM.cpp +++ b/cpp_utils/BLEEddystoneTLM.cpp @@ -16,21 +16,21 @@ static const char LOG_TAG[] = "BLEEddystoneTLM"; #define ENDIAN_CHANGE_U32(x) ((((x)&0xFF000000)>>24) + (((x)&0x00FF0000)>>8)) + ((((x)&0xFF00)<<8) + (((x)&0xFF)<<24)) BLEEddystoneTLM::BLEEddystoneTLM() { - beconUUID = 0xFEAA; - m_eddystoneData.frameType = EDDYSTONE_TLM_FRAME_TYPE; - m_eddystoneData.version = 0; - m_eddystoneData.volt = 3300; // 3300mV = 3.3V - m_eddystoneData.temp = (uint16_t)((float)23.00); - m_eddystoneData.advCount = 0; - m_eddystoneData.tmil = 0; + beaconUUID = 0xFEAA; + m_eddystoneData.frameType = EDDYSTONE_TLM_FRAME_TYPE; + m_eddystoneData.version = 0; + m_eddystoneData.volt = 3300; // 3300mV = 3.3V + m_eddystoneData.temp = (uint16_t) ((float) 23.00); + m_eddystoneData.advCount = 0; + m_eddystoneData.tmil = 0; } // BLEEddystoneTLM std::string BLEEddystoneTLM::getData() { - return std::string((char*)&m_eddystoneData, sizeof(m_eddystoneData)); + return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData)); } // getData BLEUUID BLEEddystoneTLM::getUUID() { - return BLEUUID(beconUUID); + return BLEUUID(beaconUUID); } // getUUID uint8_t BLEEddystoneTLM::getVersion() { @@ -60,35 +60,35 @@ std::string BLEEddystoneTLM::toString() { ss << "Version "; ss << std::dec << m_eddystoneData.version; ss << "\n"; - + ss << "Battery Voltage "; ss << std::dec << ENDIAN_CHANGE_U16(m_eddystoneData.volt); ss << " mV\n"; - - ss << "Temperature "; - ss << (float)m_eddystoneData.temp; - ss << " °C\n"; - - ss << "Adv. Count "; + + ss << "Temperature "; + ss << (float) m_eddystoneData.temp; + ss << " °C\n"; + + ss << "Adv. Count "; ss << std::dec << ENDIAN_CHANGE_U32(m_eddystoneData.advCount); ss << "\n"; - + ss << "Time "; rawsec = ENDIAN_CHANGE_U32(m_eddystoneData.tmil); std::stringstream buffstream; buffstream << "0000"; - buffstream << std::dec << rawsec/864000; + buffstream << std::dec << rawsec / 864000; std::string buff = buffstream.str(); - ss << buff.substr(buff.length()-4, buff.length()); + ss << buff.substr(buff.length() - 4, buff.length()); ss << "."; buffstream.str(""); buffstream.clear(); buffstream << "00"; - buffstream << std::dec << (rawsec/36000)%24; + buffstream << std::dec << (rawsec / 36000) % 24; buff = buffstream.str(); ss << buff.substr(buff.length()-2, buff.length()); ss << ":"; @@ -96,17 +96,17 @@ std::string BLEEddystoneTLM::toString() { buffstream.str(""); buffstream.clear(); buffstream << "00"; - buffstream << std::dec << (rawsec/600)%60; + buffstream << std::dec << (rawsec / 600) % 60; buff = buffstream.str(); - ss << buff.substr(buff.length()-2, buff.length()); + ss << buff.substr(buff.length() - 2, buff.length()); ss << ":"; buffstream.str(""); buffstream.clear(); buffstream << "00"; - buffstream << std::dec << (rawsec/10)%60; + buffstream << std::dec << (rawsec / 10) % 60; buff = buffstream.str(); - ss << buff.substr(buff.length()-2, buff.length()); + ss << buff.substr(buff.length() - 2, buff.length()); ss << "\n"; return ss.str(); @@ -124,7 +124,7 @@ void BLEEddystoneTLM::setData(std::string data) { } // setData void BLEEddystoneTLM::setUUID(BLEUUID l_uuid) { - beconUUID = l_uuid.getNative()->uuid.uuid16; + beaconUUID = l_uuid.getNative()->uuid.uuid16; } // setUUID void BLEEddystoneTLM::setVersion(uint8_t version) { diff --git a/cpp_utils/BLEEddystoneTLM.h b/cpp_utils/BLEEddystoneTLM.h index 76bd6a43..a93e224f 100755 --- a/cpp_utils/BLEEddystoneTLM.h +++ b/cpp_utils/BLEEddystoneTLM.h @@ -17,33 +17,34 @@ * * https://github.com/google/eddystone */ class BLEEddystoneTLM { +public: + BLEEddystoneTLM(); + std::string getData(); + BLEUUID getUUID(); + uint8_t getVersion(); + uint16_t getVolt(); + float getTemp(); + uint32_t getCount(); + uint32_t getTime(); + std::string toString(); + void setData(std::string data); + void setUUID(BLEUUID l_uuid); + void setVersion(uint8_t version); + void setVolt(uint16_t volt); + void setTemp(float temp); + void setCount(uint32_t advCount); + void setTime(uint32_t tmil); + private: - uint16_t beconUUID; + uint16_t beaconUUID; struct { uint8_t frameType; - int8_t version; + uint8_t version; uint16_t volt; uint16_t temp; uint32_t advCount; uint32_t tmil; - } __attribute__((packed))m_eddystoneData; -public: - BLEEddystoneTLM(); - std::string getData(); - BLEUUID getUUID(); - uint8_t getVersion(); - uint16_t getVolt(); - float getTemp(); - uint32_t getCount(); - uint32_t getTime(); - std::string toString(); - void setData(std::string data); - void setUUID(BLEUUID l_uuid); - void setVersion(uint8_t version); - void setVolt(uint16_t volt); - void setTemp(float temp); - void setCount(uint32_t advCount); - void setTime(uint32_t tmil); + } __attribute__((packed)) m_eddystoneData; }; // BLEEddystoneTLM diff --git a/cpp_utils/BLEEddystoneURL.cpp b/cpp_utils/BLEEddystoneURL.cpp index 6c12b246..af3b674c 100755 --- a/cpp_utils/BLEEddystoneURL.cpp +++ b/cpp_utils/BLEEddystoneURL.cpp @@ -13,19 +13,19 @@ static const char LOG_TAG[] = "BLEEddystoneURL"; BLEEddystoneURL::BLEEddystoneURL() { - beconUUID = 0xFEAA; - lengthURL = 0; - m_eddystoneData.frameType = EDDYSTONE_URL_FRAME_TYPE; - m_eddystoneData.advertisedTxPower = 0; - memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); + beaconUUID = 0xFEAA; + lengthURL = 0; + m_eddystoneData.frameType = EDDYSTONE_URL_FRAME_TYPE; + m_eddystoneData.advertisedTxPower = 0; + memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); } // BLEEddystoneURL std::string BLEEddystoneURL::getData() { - return std::string((char*)&m_eddystoneData, sizeof(m_eddystoneData)); + return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData)); } // getData BLEUUID BLEEddystoneURL::getUUID() { - return BLEUUID(beconUUID); + return BLEUUID(beaconUUID); } // getUUID int8_t BLEEddystoneURL::getPower() { @@ -33,7 +33,7 @@ int8_t BLEEddystoneURL::getPower() { } // getPower std::string BLEEddystoneURL::getURL() { - return std::string((char*)&m_eddystoneData.url, sizeof(m_eddystoneData.url)); + return std::string((char*) &m_eddystoneData.url, sizeof(m_eddystoneData.url)); } // getURL std::string BLEEddystoneURL::getDecodedURL() { @@ -56,58 +56,58 @@ std::string BLEEddystoneURL::getDecodedURL() { decodedURL += m_eddystoneData.url[0]; } - for (int i=1;i33&&m_eddystoneData.url[i]<127) { - decodedURL += m_eddystoneData.url[i]; - } else { - switch (m_eddystoneData.url[i]) { - case 0x00: - decodedURL += ".com/"; - break; - case 0x01: - decodedURL += ".org/"; - break; - case 0x02: - decodedURL += ".edu/"; - break; - case 0x03: - decodedURL += ".net/"; - break; - case 0x04: - decodedURL += ".info/"; - break; - case 0x05: - decodedURL += ".biz/"; - break; - case 0x06: - decodedURL += ".gov/"; - break; - case 0x07: - decodedURL += ".com"; - break; - case 0x08: - decodedURL += ".org"; - break; - case 0x09: - decodedURL += ".edu"; - break; - case 0x0A: - decodedURL += ".net"; - break; - case 0x0B: - decodedURL += ".info"; - break; - case 0x0C: - decodedURL += ".biz"; - break; - case 0x0D: - decodedURL += ".gov"; - break; - } - } - } - - + for (int i = 1; i < lengthURL; i++) { + if (m_eddystoneData.url[i] > 33 && m_eddystoneData.url[i] < 127) { + decodedURL += m_eddystoneData.url[i]; + } else { + switch (m_eddystoneData.url[i]) { + case 0x00: + decodedURL += ".com/"; + break; + case 0x01: + decodedURL += ".org/"; + break; + case 0x02: + decodedURL += ".edu/"; + break; + case 0x03: + decodedURL += ".net/"; + break; + case 0x04: + decodedURL += ".info/"; + break; + case 0x05: + decodedURL += ".biz/"; + break; + case 0x06: + decodedURL += ".gov/"; + break; + case 0x07: + decodedURL += ".com"; + break; + case 0x08: + decodedURL += ".org"; + break; + case 0x09: + decodedURL += ".edu"; + break; + case 0x0A: + decodedURL += ".net"; + break; + case 0x0B: + decodedURL += ".info"; + break; + case 0x0C: + decodedURL += ".biz"; + break; + case 0x0D: + decodedURL += ".gov"; + break; + default: + break; + } + } + } return decodedURL; } // getDecodedURL @@ -121,14 +121,13 @@ void BLEEddystoneURL::setData(std::string data) { ESP_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and max expected %d", data.length(), sizeof(m_eddystoneData)); return; } - memset(&m_eddystoneData, 0, sizeof(m_eddystoneData)); + memset(&m_eddystoneData, 0, sizeof(m_eddystoneData)); memcpy(&m_eddystoneData, data.data(), data.length()); - lengthURL=data.length()-(sizeof(m_eddystoneData)-sizeof(m_eddystoneData.url)); - + lengthURL = data.length() - (sizeof(m_eddystoneData) - sizeof(m_eddystoneData.url)); } // setData void BLEEddystoneURL::setUUID(BLEUUID l_uuid) { - beconUUID = l_uuid.getNative()->uuid.uuid16; + beaconUUID = l_uuid.getNative()->uuid.uuid16; } // setUUID void BLEEddystoneURL::setPower(int8_t advertisedTxPower) { @@ -137,12 +136,12 @@ void BLEEddystoneURL::setPower(int8_t advertisedTxPower) { void BLEEddystoneURL::setURL(std::string url) { if (url.length() > sizeof(m_eddystoneData.url)) { - ESP_LOGE(LOG_TAG, "Unable to set the url ... length passed in was %d and max expected %d", url.length(), sizeof(m_eddystoneData.url)); - return; + ESP_LOGE(LOG_TAG, "Unable to set the url ... length passed in was %d and max expected %d", url.length(), sizeof(m_eddystoneData.url)); + return; } memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); memcpy(m_eddystoneData.url, url.data(), url.length()); - lengthURL=url.length(); + lengthURL = url.length(); } // setURL diff --git a/cpp_utils/BLEEddystoneURL.h b/cpp_utils/BLEEddystoneURL.h index 2025cb19..0b538c07 100755 --- a/cpp_utils/BLEEddystoneURL.h +++ b/cpp_utils/BLEEddystoneURL.h @@ -17,25 +17,26 @@ * * https://github.com/google/eddystone */ class BLEEddystoneURL { -private: - uint16_t beconUUID; - uint8_t lengthURL; - struct { - uint8_t frameType; - int8_t advertisedTxPower; - uint8_t url[16]; - } __attribute__((packed))m_eddystoneData; public: BLEEddystoneURL(); std::string getData(); - BLEUUID getUUID(); - int8_t getPower(); + BLEUUID getUUID(); + int8_t getPower(); std::string getURL(); std::string getDecodedURL(); - void setData(std::string data); - void setUUID(BLEUUID l_uuid); - void setPower(int8_t advertisedTxPower); - void setURL(std::string url); + void setData(std::string data); + void setUUID(BLEUUID l_uuid); + void setPower(int8_t advertisedTxPower); + void setURL(std::string url); + +private: + uint16_t beaconUUID; + uint8_t lengthURL; + struct { + uint8_t frameType; + int8_t advertisedTxPower; + uint8_t url[16]; + } __attribute__((packed)) m_eddystoneData; }; // BLEEddystoneURL diff --git a/cpp_utils/BLEExceptions.h b/cpp_utils/BLEExceptions.h index 369fcaf6..ea9db855 100644 --- a/cpp_utils/BLEExceptions.h +++ b/cpp_utils/BLEExceptions.h @@ -17,13 +17,13 @@ class BLEDisconnectedException : public std::exception { - const char *what() const throw () { + const char* what() const throw () { return "BLE Disconnected"; } }; class BLEUuidNotFoundException : public std::exception { - const char *what() const throw () { + const char* what() const throw () { return "No such UUID"; } }; diff --git a/cpp_utils/BLEHIDDevice.cpp b/cpp_utils/BLEHIDDevice.cpp index cc6c531d..69e18be7 100644 --- a/cpp_utils/BLEHIDDevice.cpp +++ b/cpp_utils/BLEHIDDevice.cpp @@ -22,15 +22,15 @@ BLEHIDDevice::BLEHIDDevice(BLEServer* server) { /* * Mandatory characteristic for device info service */ - m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a50, BLECharacteristic::PROPERTY_READ); + m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a50, BLECharacteristic::PROPERTY_READ); /* * Mandatory characteristics for HID service */ - m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4a, BLECharacteristic::PROPERTY_READ); - m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4b, BLECharacteristic::PROPERTY_READ); - m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4c, BLECharacteristic::PROPERTY_WRITE_NR); - m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4e, BLECharacteristic::PROPERTY_WRITE_NR | BLECharacteristic::PROPERTY_READ); + m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4a, BLECharacteristic::PROPERTY_READ); + m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4b, BLECharacteristic::PROPERTY_READ); + m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4c, BLECharacteristic::PROPERTY_WRITE_NR); + m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4e, BLECharacteristic::PROPERTY_WRITE_NR | BLECharacteristic::PROPERTY_READ); /* * Mandatory battery level characteristic with notification and presence descriptor @@ -40,7 +40,7 @@ BLEHIDDevice::BLEHIDDevice(BLEServer* server) { batteryLevelDescriptor->setNamespace(1); batteryLevelDescriptor->setUnit(0x27ad); - m_batteryLevelCharacteristic = m_batteryService->createCharacteristic((uint16_t)0x2a19, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + m_batteryLevelCharacteristic = m_batteryService->createCharacteristic((uint16_t) 0x2a19, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); m_batteryLevelCharacteristic->addDescriptor(batteryLevelDescriptor); m_batteryLevelCharacteristic->addDescriptor(new BLE2902()); @@ -48,8 +48,8 @@ BLEHIDDevice::BLEHIDDevice(BLEServer* server) { * This value is setup here because its default value in most usage cases, its very rare to use boot mode * and we want to simplify library using as much as possible */ - const uint8_t pMode[] = {0x01}; - protocolMode()->setValue((uint8_t*)pMode, 1); + const uint8_t pMode[] = { 0x01 }; + protocolMode()->setValue((uint8_t*) pMode, 1); } BLEHIDDevice::~BLEHIDDevice() { @@ -74,8 +74,8 @@ void BLEHIDDevice::startServices() { /* * @brief Create manufacturer characteristic (this characteristic is optional) */ -BLECharacteristic* BLEHIDDevice::manufacturer() { - m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t)0x2a29, BLECharacteristic::PROPERTY_READ); +BLECharacteristic* BLEHIDDevice::manufacturer() { + m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ); return m_manufacturerCharacteristic; } @@ -91,7 +91,7 @@ void BLEHIDDevice::manufacturer(std::string name) { * @brief */ void BLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) { - uint8_t pnp[] = {sig, (uint8_t)(vid>>8), (uint8_t)vid, (uint8_t)(pid>>8), (uint8_t)pid, (uint8_t)(version>>8), (uint8_t)version}; + uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version }; m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); } @@ -99,8 +99,8 @@ void BLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version * @brief */ void BLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) { - uint8_t info[] = {0x11,0x1, country, flags}; - m_hidInfoCharacteristic->setValue(info, sizeof(info));; + uint8_t info[] = { 0x11, 0x1, country, flags }; + m_hidInfoCharacteristic->setValue(info, sizeof(info)); } /* @@ -109,15 +109,15 @@ void BLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) { * @return pointer to new input report characteristic */ BLECharacteristic* BLEHIDDevice::inputReport(uint8_t reportID) { - BLECharacteristic* inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); - BLEDescriptor* inputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + BLECharacteristic* inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor* inputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t) 0x2908)); BLE2902* p2902 = new BLE2902(); inputReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); inputReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); p2902->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - uint8_t desc1_val[] = {reportID, 0x01}; - inputReportDescriptor->setValue((uint8_t*)desc1_val, 2); + uint8_t desc1_val[] = { reportID, 0x01 }; + inputReportDescriptor->setValue((uint8_t*) desc1_val, 2); inputReportCharacteristic->addDescriptor(p2902); inputReportCharacteristic->addDescriptor(inputReportDescriptor); @@ -130,13 +130,13 @@ BLECharacteristic* BLEHIDDevice::inputReport(uint8_t reportID) { * @return Pointer to new output report characteristic */ BLECharacteristic* BLEHIDDevice::outputReport(uint8_t reportID) { - BLECharacteristic* outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); - BLEDescriptor* outputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + BLECharacteristic* outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); + BLEDescriptor* outputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t) 0x2908)); outputReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); outputReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - uint8_t desc1_val[] = {reportID, 0x02}; - outputReportDescriptor->setValue((uint8_t*)desc1_val, 2); + uint8_t desc1_val[] = { reportID, 0x02 }; + outputReportDescriptor->setValue((uint8_t*) desc1_val, 2); outputReportCharacteristic->addDescriptor(outputReportDescriptor); return outputReportCharacteristic; @@ -148,13 +148,14 @@ BLECharacteristic* BLEHIDDevice::outputReport(uint8_t reportID) { * @return Pointer to new feature report characteristic */ BLECharacteristic* BLEHIDDevice::featureReport(uint8_t reportID) { - BLECharacteristic* featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); - BLEDescriptor* featureReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); + BLECharacteristic* featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + BLEDescriptor* featureReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t) 0x2908)); + featureReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); featureReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - uint8_t desc1_val[] = {reportID, 0x03}; - featureReportDescriptor->setValue((uint8_t*)desc1_val, 2); + uint8_t desc1_val[] = { reportID, 0x03 }; + featureReportDescriptor->setValue((uint8_t*) desc1_val, 2); featureReportCharacteristic->addDescriptor(featureReportDescriptor); return featureReportCharacteristic; @@ -164,7 +165,7 @@ BLECharacteristic* BLEHIDDevice::featureReport(uint8_t reportID) { * @brief */ BLECharacteristic* BLEHIDDevice::bootInput() { - BLECharacteristic* bootInputCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a22, BLECharacteristic::PROPERTY_NOTIFY); + BLECharacteristic* bootInputCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a22, BLECharacteristic::PROPERTY_NOTIFY); bootInputCharacteristic->addDescriptor(new BLE2902()); return bootInputCharacteristic; @@ -174,20 +175,20 @@ BLECharacteristic* BLEHIDDevice::bootInput() { * @brief */ BLECharacteristic* BLEHIDDevice::bootOutput() { - return m_hidService->createCharacteristic((uint16_t)0x2a32, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); + return m_hidService->createCharacteristic((uint16_t) 0x2a32, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); } /* * @brief */ -BLECharacteristic* BLEHIDDevice::hidControl() { +BLECharacteristic* BLEHIDDevice::hidControl() { return m_hidControlCharacteristic; } /* * @brief */ -BLECharacteristic* BLEHIDDevice::protocolMode() { +BLECharacteristic* BLEHIDDevice::protocolMode() { return m_protocolModeCharacteristic; } @@ -204,11 +205,11 @@ BLECharacteristic* BLEHIDDevice::batteryLevel() { -BLECharacteristic* BLEHIDDevice::reportMap() { +BLECharacteristic* BLEHIDDevice::reportMap() { return m_reportMapCharacteristic; } -BLECharacteristic* BLEHIDDevice::pnp() { +BLECharacteristic* BLEHIDDevice::pnp() { return m_pnpCharacteristic; } diff --git a/cpp_utils/BLEHIDDevice.h b/cpp_utils/BLEHIDDevice.h index 319fd42a..33e6b46c 100644 --- a/cpp_utils/BLEHIDDevice.h +++ b/cpp_utils/BLEHIDDevice.h @@ -17,15 +17,15 @@ #include "BLE2902.h" #include "HIDTypes.h" -#define GENERIC_HID 960 -#define HID_KEYBOARD 961 -#define HID_MOUSE 962 -#define HID_JOYSTICK 963 -#define HID_GAMEPAD 964 -#define HID_TABLET 965 -#define HID_CARD_READER 966 -#define HID_DIGITAL_PEN 967 -#define HID_BARCODE 968 +#define GENERIC_HID 0x03C0 +#define HID_KEYBOARD 0x03C1 +#define HID_MOUSE 0x03C2 +#define HID_JOYSTICK 0x03C3 +#define HID_GAMEPAD 0x03C4 +#define HID_TABLET 0x03C5 +#define HID_CARD_READER 0x03C6 +#define HID_DIGITAL_PEN 0x03C7 +#define HID_BARCODE 0x03C8 class BLEHIDDevice { public: diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 33d43eee..14177eac 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -146,12 +146,8 @@ static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { * @param [in] evtParam Payload data for the event. * @returns N/A */ -void BLERemoteCharacteristic::gattClientEventHandler( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t* evtParam) { - switch(event) { - // +void BLERemoteCharacteristic::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { + switch (event) { // ESP_GATTC_NOTIFY_EVT // // notify @@ -165,25 +161,15 @@ void BLERemoteCharacteristic::gattClientEventHandler( // We have received a notification event which means that the server wishes us to know about a notification // piece of data. What we must now do is find the characteristic with the associated handle and then // invoke its notification callback (if it has one). - // case ESP_GATTC_NOTIFY_EVT: { - if (evtParam->notify.handle != getHandle()) { - break; - } + if (evtParam->notify.handle != getHandle()) break; if (m_notifyCallback != nullptr) { ESP_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s", toString().c_str()); - m_notifyCallback( - this, - evtParam->notify.value, - evtParam->notify.value_len, - evtParam->notify.is_notify - ); + m_notifyCallback(this, evtParam->notify.value, evtParam->notify.value_len, evtParam->notify.is_notify); } // End we have a callback function ... break; } // ESP_GATTC_NOTIFY_EVT - - // // ESP_GATTC_READ_CHAR_EVT // This event indicates that the server has responded to the read request. // @@ -193,20 +179,16 @@ void BLERemoteCharacteristic::gattClientEventHandler( // - uint16_t handle // - uint8_t* value // - uint16_t value_len - // case ESP_GATTC_READ_CHAR_EVT: { // If this event is not for us, then nothing further to do. - if (evtParam->read.handle != getHandle()) { - break; - } + if (evtParam->read.handle != getHandle()) break; // At this point, we have determined that the event is for us, so now we save the value // and unlock the semaphore to ensure that the requestor of the data can continue. if (evtParam->read.status == ESP_GATT_OK) { - m_value = std::string((char*)evtParam->read.value, evtParam->read.value_len); - if(m_rawData != nullptr) - free(m_rawData); - + m_value = std::string((char*) evtParam->read.value, evtParam->read.value_len); + if (m_rawData != nullptr) free(m_rawData); + m_rawData = (uint8_t*) calloc(evtParam->read.value_len, sizeof(uint8_t)); memcpy(m_rawData, evtParam->read.value, evtParam->read.value_len); } else { @@ -217,56 +199,41 @@ void BLERemoteCharacteristic::gattClientEventHandler( break; } // ESP_GATTC_READ_CHAR_EVT - - // // ESP_GATTC_REG_FOR_NOTIFY_EVT // // reg_for_notify: // - esp_gatt_status_t status // - uint16_t handle - // case ESP_GATTC_REG_FOR_NOTIFY_EVT: { // If the request is not for this BLERemoteCharacteristic then move on to the next. - if (evtParam->reg_for_notify.handle != getHandle()) { - break; - } + if (evtParam->reg_for_notify.handle != getHandle()) break; // We have processed the notify registration and can unlock the semaphore. m_semaphoreRegForNotifyEvt.give(); break; } // ESP_GATTC_REG_FOR_NOTIFY_EVT - - // // ESP_GATTC_UNREG_FOR_NOTIFY_EVT // // unreg_for_notify: // - esp_gatt_status_t status // - uint16_t handle - // case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { - if (evtParam->unreg_for_notify.handle != getHandle()) { - break; - } + if (evtParam->unreg_for_notify.handle != getHandle()) break; // We have processed the notify un-registration and can unlock the semaphore. m_semaphoreRegForNotifyEvt.give(); break; } // ESP_GATTC_UNREG_FOR_NOTIFY_EVT: - - // // ESP_GATTC_WRITE_CHAR_EVT // // write: // - esp_gatt_status_t status // - uint16_t conn_id // - uint16_t handle - // case ESP_GATTC_WRITE_CHAR_EVT: { // Determine if this event is for us and, if not, pass onwards. - if (evtParam->write.handle != getHandle()) { - break; - } + if (evtParam->write.handle != getHandle()) break; // There is nothing further we need to do here. This is merely an indication // that the write has completed and we can unlock the caller. @@ -275,9 +242,8 @@ void BLERemoteCharacteristic::gattClientEventHandler( } // ESP_GATTC_WRITE_CHAR_EVT - default: { + default: break; - } } // End switch }; // gattClientEventHandler @@ -294,7 +260,7 @@ void BLERemoteCharacteristic::retrieveDescriptors() { // For each descriptor we find, create a BLERemoteDescriptor instance. uint16_t offset = 0; esp_gattc_descr_elem_t result; - while(1) { + while (true) { uint16_t count = 1; esp_gatt_status_t status = ::esp_ble_gattc_get_all_descr( getRemoteService()->getClient()->getGattcIf(), @@ -314,14 +280,12 @@ void BLERemoteCharacteristic::retrieveDescriptors() { break; } - if (count == 0) { - break; - } + if (count == 0) break; ESP_LOGE(LOG_TAG, ""); ESP_LOGD(LOG_TAG, "Found a descriptor: Handle: %d, UUID: %s", result.handle, BLEUUID(result.uuid).toString().c_str()); // We now have a new characteristic ... let us add that to our set of known characteristics - BLERemoteDescriptor *pNewRemoteDescriptor = new BLERemoteDescriptor( + BLERemoteDescriptor* pNewRemoteDescriptor = new BLERemoteDescriptor( result.handle, BLEUUID(result.uuid), this @@ -339,7 +303,7 @@ void BLERemoteCharacteristic::retrieveDescriptors() { /** * @brief Retrieve the map of descriptors keyed by UUID. */ -std::map* BLERemoteCharacteristic::getDescriptors() { +std::map* BLERemoteCharacteristic::getDescriptors() { return &m_descriptorMap; } // getDescriptors @@ -363,7 +327,7 @@ uint16_t BLERemoteCharacteristic::getHandle() { BLERemoteDescriptor* BLERemoteCharacteristic::getDescriptor(BLEUUID uuid) { ESP_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str()); std::string v = uuid.toString(); - for (auto &myPair : m_descriptorMap) { + for (auto& myPair : m_descriptorMap) { if (myPair.first == v) { ESP_LOGD(LOG_TAG, "<< getDescriptor: found"); return myPair.second; @@ -396,10 +360,10 @@ BLEUUID BLERemoteCharacteristic::getUUID() { * @brief Read an unsigned 16 bit value * @return The unsigned 16 bit value. */ -uint16_t BLERemoteCharacteristic::readUInt16(void) { +uint16_t BLERemoteCharacteristic::readUInt16() { std::string value = readValue(); if (value.length() >= 2) { - return *(uint16_t*)(value.data()); + return *(uint16_t*) value.data(); } return 0; } // readUInt16 @@ -409,10 +373,10 @@ uint16_t BLERemoteCharacteristic::readUInt16(void) { * @brief Read an unsigned 32 bit value. * @return the unsigned 32 bit value. */ -uint32_t BLERemoteCharacteristic::readUInt32(void) { +uint32_t BLERemoteCharacteristic::readUInt32() { std::string value = readValue(); if (value.length() >= 4) { - return *(uint32_t*)(value.data()); + return *(uint32_t*) value.data(); } return 0; } // readUInt32 @@ -422,10 +386,10 @@ uint32_t BLERemoteCharacteristic::readUInt32(void) { * @brief Read a byte value * @return The value as a byte */ -uint8_t BLERemoteCharacteristic::readUInt8(void) { +uint8_t BLERemoteCharacteristic::readUInt8() { std::string value = readValue(); if (value.length() >= 1) { - return (uint8_t)value[0]; + return (uint8_t) value[0]; } return 0; } // readUInt8 @@ -499,9 +463,8 @@ void BLERemoteCharacteristic::registerForNotify( } uint8_t val[] = {0x01, 0x00}; - if(!notifications) - val[0] = 0x02; - BLERemoteDescriptor *desc = getDescriptor(BLEUUID("0x2902")); + if (!notifications) val[0] = 0x02; + BLERemoteDescriptor* desc = getDescriptor(BLEUUID("0x2902")); desc->writeValue(val, 2); } // End Register else { // If we weren't passed a callback function, then this is an unregistration. @@ -516,7 +479,7 @@ void BLERemoteCharacteristic::registerForNotify( } uint8_t val[] = {0x00, 0x00}; - BLERemoteDescriptor *desc = getDescriptor(BLEUUID("0x2902")); + BLERemoteDescriptor* desc = getDescriptor(BLEUUID("0x2902")); desc->writeValue(val, 2); } // End Unregister @@ -615,11 +578,11 @@ void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { * @param [in] response Whether we require a response from the write. */ void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) { - writeValue(std::string((char *)data, length), response); + writeValue(std::string((char*) data, length), response); } // writeValue /** - * @brief Read raw data from remote characteristic as hex bytes + * @brief Read raw data from remote characteristic as hex bytes * @return return pointer data read */ uint8_t* BLERemoteCharacteristic::readRawData() { diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 044f8f62..0750c1a9 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -37,9 +37,9 @@ class BLERemoteCharacteristic { bool canWrite(); bool canWriteNoResponse(); BLERemoteDescriptor* getDescriptor(BLEUUID uuid); - std::map* getDescriptors(); uint16_t getHandle(); BLEUUID getUUID(); + std::map* getDescriptors(); std::string readValue(void); uint8_t readUInt8(void); uint16_t readUInt16(void); @@ -59,25 +59,24 @@ class BLERemoteCharacteristic { // Private member functions void gattClientEventHandler( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); - BLERemoteService* getRemoteService(); - void removeDescriptors(); - void retrieveDescriptors(); + void removeDescriptors(); + void retrieveDescriptors(); // Private properties - BLEUUID m_uuid; + BLEUUID m_uuid; esp_gatt_char_prop_t m_charProp; - uint16_t m_handle; - BLERemoteService* m_pRemoteService; - FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt"); + uint16_t m_handle; + BLERemoteService* m_pRemoteService; + FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt"); FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt"); - FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); - std::string m_value; - uint8_t *m_rawData; + FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); + std::string m_value; + uint8_t* m_rawData; void (*m_notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify); // We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID. diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index 2cdc7db1..4947976b 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -55,7 +55,7 @@ BLEUUID BLERemoteDescriptor::getUUID() { } // getUUID -std::string BLERemoteDescriptor::readValue(void) { +std::string BLERemoteDescriptor::readValue() { ESP_LOGD(LOG_TAG, ">> readValue: %s", toString().c_str()); // Check to see that we are connected. @@ -87,28 +87,28 @@ std::string BLERemoteDescriptor::readValue(void) { } // readValue -uint8_t BLERemoteDescriptor::readUInt8(void) { +uint8_t BLERemoteDescriptor::readUInt8() { std::string value = readValue(); if (value.length() >= 1) { - return (uint8_t)value[0]; + return (uint8_t) value[0]; } return 0; } // readUInt8 -uint16_t BLERemoteDescriptor::readUInt16(void) { +uint16_t BLERemoteDescriptor::readUInt16() { std::string value = readValue(); if (value.length() >= 2) { - return *(uint16_t*)(value.data()); + return *(uint16_t*) value.data(); } return 0; } // readUInt16 -uint32_t BLERemoteDescriptor::readUInt32(void) { +uint32_t BLERemoteDescriptor::readUInt32() { std::string value = readValue(); if (value.length() >= 4) { - return *(uint32_t*)(value.data()); + return *(uint32_t*) value.data(); } return 0; } // readUInt32 @@ -118,7 +118,7 @@ uint32_t BLERemoteDescriptor::readUInt32(void) { * @brief Return a string representation of this BLE Remote Descriptor. * @retun A string representation of this BLE Remote Descriptor. */ -std::string BLERemoteDescriptor::toString(void) { +std::string BLERemoteDescriptor::toString() { std::stringstream ss; ss << "handle: " << getHandle() << ", uuid: " << getUUID().toString(); return ss.str(); @@ -131,10 +131,7 @@ std::string BLERemoteDescriptor::toString(void) { * @param [in] length The length of the data to send. * @param [in] response True if we expect a response. */ -void BLERemoteDescriptor::writeValue( - uint8_t* data, - size_t length, - bool response) { +void BLERemoteDescriptor::writeValue(uint8_t* data, size_t length, bool response) { ESP_LOGD(LOG_TAG, ">> writeValue: %s", toString().c_str()); // Check to see that we are connected. if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { @@ -163,10 +160,8 @@ void BLERemoteDescriptor::writeValue( * @param [in] newValue The data to send to the remote descriptor. * @param [in] response True if we expect a response. */ -void BLERemoteDescriptor::writeValue( - std::string newValue, - bool response) { - writeValue(newValue.data(), newValue.length()); +void BLERemoteDescriptor::writeValue(std::string newValue, bool response) { + writeValue(newValue.data(), newValue.length(), response); } // writeValue @@ -175,9 +170,7 @@ void BLERemoteDescriptor::writeValue( * @param [in] The single byte to write. * @param [in] True if we expect a response. */ -void BLERemoteDescriptor::writeValue( - uint8_t newValue, - bool response) { +void BLERemoteDescriptor::writeValue(uint8_t newValue, bool response) { writeValue(&newValue, 1, response); } // writeValue diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index ef0588bc..a1bf06e7 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -60,8 +60,8 @@ static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { void BLERemoteService::gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *evtParam) { - switch(event) { + esp_ble_gattc_cb_param_t* evtParam) { + switch (event) { // // ESP_GATTC_GET_CHAR_EVT // @@ -108,9 +108,8 @@ void BLERemoteService::gattClientEventHandler( break; } // ESP_GATTC_GET_CHAR_EVT */ - default: { + default: break; - } } // switch // Send the event to each of the characteristics owned by this service. @@ -127,10 +126,10 @@ void BLERemoteService::gattClientEventHandler( * @throws BLEUuidNotFoundException */ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) { - return getCharacteristic(BLEUUID(uuid)); + return getCharacteristic(BLEUUID(uuid)); } // getCharacteristic - - + + /** * @brief Get the characteristic object for the UUID. * @param [in] uuid Characteristic uuid. @@ -163,14 +162,13 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { * @return N/A */ void BLERemoteService::retrieveCharacteristics() { - ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); removeCharacteristics(); // Forget any previous characteristics. uint16_t offset = 0; esp_gattc_char_elem_t result; - while(1) { + while (true) { uint16_t count = 1; esp_gatt_status_t status = ::esp_ble_gattc_get_all_char( getClient()->getGattcIf(), @@ -219,7 +217,7 @@ void BLERemoteService::retrieveCharacteristics() { * @brief Retrieve a map of all the characteristics of this service. * @return A map of all the characteristics of this service. */ -std::map * BLERemoteService::getCharacteristics() { +std::map* BLERemoteService::getCharacteristics() { ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); // If is possible that we have not read the characteristics associated with the service so do that // now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking @@ -234,7 +232,7 @@ std::map * BLERemoteService::getCharacte /** * @brief This function is designed to get characteristics map when we have multiple characteristics with the same UUID */ -void BLERemoteService::getCharacteristics(std::map* pCharacteristicMap){ +void BLERemoteService::getCharacteristics(std::map* pCharacteristicMap) { pCharacteristicMap = &m_characteristicMapByHandle; } // Get the characteristics map. diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 222c6e45..387afbdf 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -26,7 +26,6 @@ class BLERemoteCharacteristic; */ class BLERemoteService { public: - virtual ~BLERemoteService(); // Public methods @@ -67,10 +66,10 @@ class BLERemoteService { // Properties // We maintain a map of characteristics owned by this service keyed by a string representation of the UUID. - std::map m_characteristicMap; + std::map m_characteristicMap; // We maintain a map of characteristics owned by this service keyed by a handle. - std::map m_characteristicMapByHandle; + std::map m_characteristicMapByHandle; bool m_haveCharacteristics; // Have we previously obtained the characteristics. BLEClient* m_pClient; diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 3046b7c8..25b60a0b 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -48,7 +48,7 @@ void BLEScan::handleGAPEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) { - switch(event) { + switch (event) { // ESP_GAP_BLE_SCAN_RESULT_EVT // --------------------------- @@ -90,12 +90,12 @@ void BLEScan::handleGAPEvent( break; } -// Examine our list of previously scanned addresses and, if we found this one already, -// ignore it. + // Examine our list of previously scanned addresses and, if we found this one already, + // ignore it. BLEAddress advertisedAddress(param->scan_rst.bda); bool found = false; - for (int i=0; i> Dump scan results:"); - for (int i=0; i> handleGATTServerEvent: %s", - BLEUtils::gattServerEventTypeToString(event).c_str()); +void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { + ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); // Invoke the handler for every Service we have. m_serviceMap.handleGATTServerEvent(event, gatts_if, param); - switch(event) { + switch (event) { // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. // add_char: // - esp_gatt_status_t status @@ -220,7 +216,7 @@ void BLEServer::handleGATTServerEvent( m_connId = param->connect.conn_id; // Save the connection id. if (m_pServerCallbacks != nullptr) { m_pServerCallbacks->onConnect(this); - m_pServerCallbacks->onConnect(this, param); + m_pServerCallbacks->onConnect(this, param); } m_connectedCount++; // Increment the number of connected devices count. break; @@ -307,9 +303,8 @@ void BLEServer::handleGATTServerEvent( break; } - default: { + default: break; - } } ESP_LOGD(LOG_TAG, "<< handleGATTServerEvent"); } // handleGATTServerEvent @@ -345,9 +340,9 @@ void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { /* * Remove service */ -void BLEServer::removeService(BLEService *service) { +void BLEServer::removeService(BLEService* service) { service->stop(); - service->executeDelete(); + service->executeDelete(); m_serviceMap.removeService(service); } @@ -370,7 +365,7 @@ void BLEServerCallbacks::onConnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); } // onConnect -void BLEServerCallbacks::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) { +void BLEServerCallbacks::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) { ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); ESP_LOGD("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 7f40faef..e3362b75 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -42,12 +42,13 @@ class BLEServiceMap { std::string toString(); BLEService* getFirst(); BLEService* getNext(); - void removeService(BLEService *service); + void removeService(BLEService* service); private: std::map m_handleMap; std::map m_uuidMap; std::map::iterator m_iterator; + }; @@ -58,13 +59,13 @@ class BLEServer { public: uint32_t getConnectedCount(); BLEService* createService(const char* uuid); - BLEService* createService(BLEUUID uuid, uint32_t numHandles=15, uint8_t inst_id=0); + BLEService* createService(BLEUUID uuid, uint32_t numHandles = 15, uint8_t inst_id = 0); BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); - void removeService(BLEService *service); BLEService* getServiceByUUID(const char* uuid); BLEService* getServiceByUUID(BLEUUID uuid); + void removeService(BLEService* service); private: BLEServer(); @@ -85,9 +86,9 @@ class BLEServer { void createApp(uint16_t appId); uint16_t getConnId(); uint16_t getGattsIf(); - void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); void registerApp(); + void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); }; // BLEServer @@ -105,7 +106,7 @@ class BLEServerCallbacks { * @param [in] pServer A reference to the %BLE server that received the client connection. */ virtual void onConnect(BLEServer* pServer); - virtual void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param); + virtual void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t* param); /** * @brief Handle an existing client disconnection. * @@ -117,6 +118,5 @@ class BLEServerCallbacks { }; // BLEServerCallbacks - #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLESERVER_H_ */ diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 340ea560..93472f61 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -61,7 +61,7 @@ BLEService::BLEService(BLEUUID uuid, uint32_t numHandles) { * @return N/A. */ -void BLEService::executeCreate(BLEServer *pServer) { +void BLEService::executeCreate(BLEServer* pServer) { //ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); //getUUID(); // Needed for a weird bug fix //char x[1000]; @@ -75,11 +75,7 @@ void BLEService::executeCreate(BLEServer *pServer) { srvc_id.is_primary = true; srvc_id.id.inst_id = m_id; srvc_id.id.uuid = *m_uuid.getNative(); - esp_err_t errRc = ::esp_ble_gatts_create_service( - getServer()->getGattsIf(), - &srvc_id, - m_numHandles // The maximum number of handles associated with the service. - ); + esp_err_t errRc = ::esp_ble_gatts_create_service(getServer()->getGattsIf(), &srvc_id, m_numHandles); // The maximum number of handles associated with the service. if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -101,7 +97,7 @@ void BLEService::executeDelete() { ESP_LOGD(LOG_TAG, ">> executeDelete()"); m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_DELETE_EVT - esp_err_t errRc = ::esp_ble_gatts_delete_service( getHandle() ); + esp_err_t errRc = ::esp_ble_gatts_delete_service(getHandle()); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gatts_delete_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -141,20 +137,19 @@ BLEUUID BLEService::getUUID() { * @return Start the service. */ void BLEService::start() { -// We ask the BLE runtime to start the service and then create each of the characteristics. -// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event -// obtained as a result of calling esp_ble_gatts_create_service(). -// + // We ask the BLE runtime to start the service and then create each of the characteristics. + // We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event + // obtained as a result of calling esp_ble_gatts_create_service(). + // ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); if (m_handle == NULL_HANDLE) { ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!"); return; } - BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); - while(pCharacteristic != nullptr) { + while (pCharacteristic != nullptr) { m_lastCreatedCharacteristic = pCharacteristic; pCharacteristic->executeCreate(this); @@ -177,13 +172,11 @@ void BLEService::start() { /** * @brief Stop the service. - * @return Stop the service. */ void BLEService::stop() { -// We ask the BLE runtime to start the service and then create each of the characteristics. -// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event -// obtained as a result of calling esp_ble_gatts_create_service(). -// + // We ask the BLE runtime to start the service and then create each of the characteristics. + // We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event + // obtained as a result of calling esp_ble_gatts_create_service(). ESP_LOGD(LOG_TAG, ">> stop(): Stopping service (esp_ble_gatts_stop_service): %s", toString().c_str()); if (m_handle == NULL_HANDLE) { ESP_LOGE(LOG_TAG, "<< !!! We attempted to stop a service but don't know its handle!"); @@ -232,10 +225,9 @@ uint16_t BLEService::getHandle() { * @param [in] pCharacteristic A pointer to the characteristic to be added. */ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { -// We maintain a mapping of characteristics owned by this service. These are managed by the -// BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic -// to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF). -// + // We maintain a mapping of characteristics owned by this service. These are managed by the + // BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic + // to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF). ESP_LOGD(LOG_TAG, ">> addCharacteristic()"); ESP_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s", pCharacteristic->getUUID().toString().c_str(), @@ -264,7 +256,7 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) { return createCharacteristic(BLEUUID(uuid), properties); } - + /** * @brief Create a new BLE Characteristic associated with this service. @@ -273,7 +265,7 @@ BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t p * @return The new BLE characteristic. */ BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) { - BLECharacteristic *pCharacteristic = new BLECharacteristic(uuid, properties); + BLECharacteristic* pCharacteristic = new BLECharacteristic(uuid, properties); addCharacteristic(pCharacteristic); return pCharacteristic; } // createCharacteristic @@ -282,13 +274,8 @@ BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t prope /** * @brief Handle a GATTS server event. */ -void BLEService::handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { - - - switch(event) { +void BLEService::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { + switch (event) { // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. // add_char: // - esp_gatt_status_t status @@ -377,9 +364,8 @@ void BLEService::handleGATTServerEvent( break; } // ESP_GATTS_DELETE_EVT - default: { + default: break; - } // Default } // Switch // Invoke the GATTS handler in each of the associated characteristics. diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 93b4b2c6..a4eea23b 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -27,17 +27,13 @@ class BLECharacteristicMap { void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid); void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid); void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); - BLECharacteristic* getByUUID(const char* uuid); + BLECharacteristic* getByUUID(const char* uuid); BLECharacteristic* getByUUID(BLEUUID uuid); BLECharacteristic* getByHandle(uint16_t handle); BLECharacteristic* getFirst(); BLECharacteristic* getNext(); std::string toString(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param); - + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); private: std::map m_uuidMap; @@ -57,16 +53,16 @@ class BLEService { BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); void dump(); void executeCreate(BLEServer* pServer); - void executeDelete(); + void executeDelete(); BLECharacteristic* getCharacteristic(const char* uuid); BLECharacteristic* getCharacteristic(BLEUUID uuid); BLEUUID getUUID(); BLEServer* getServer(); void start(); - void stop(); + void stop(); std::string toString(); uint16_t getHandle(); - uint8_t m_id = 0; + uint8_t m_id = 0; private: BLEService(const char* uuid, uint32_t numHandles); @@ -91,12 +87,10 @@ class BLEService { uint32_t m_numHandles; BLECharacteristic* getLastCreatedCharacteristic(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); void setHandle(uint16_t handle); //void setService(esp_gatt_srvc_id_t srvc_id); + }; // BLEService diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index dd828fae..d8610a85 100644 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -17,9 +17,9 @@ * @return The characteristic. */ BLEService* BLEServiceMap::getByUUID(const char* uuid) { - return getByUUID(BLEUUID(uuid)); + return getByUUID(BLEUUID(uuid)); } - + /** * @brief Return the service by UUID. * @param [in] UUID The UUID to look up the service. @@ -53,8 +53,8 @@ BLEService* BLEServiceMap::getByHandle(uint16_t handle) { * @return N/A. */ void BLEServiceMap::setByUUID(BLEUUID uuid, - BLEService *service) { - m_uuidMap.insert(std::pair(service, uuid.toString())); + BLEService* service) { + m_uuidMap.insert(std::pair(service, uuid.toString())); } // setByUUID @@ -66,7 +66,7 @@ void BLEServiceMap::setByUUID(BLEUUID uuid, */ void BLEServiceMap::setByHandle(uint16_t handle, BLEService* service) { - m_handleMap.insert(std::pair(handle, service)); + m_handleMap.insert(std::pair(handle, service)); } // setByHandle @@ -86,7 +86,7 @@ std::string BLEServiceMap::toString() { void BLEServiceMap::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { + esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every Service we have. for (auto &myPair : m_uuidMap) { myPair.first->handleGATTServerEvent(event, gatts_if, param); @@ -99,9 +99,7 @@ void BLEServiceMap::handleGATTServerEvent( */ BLEService* BLEServiceMap::getFirst() { m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } + if (m_iterator == m_uuidMap.end()) return nullptr; BLEService* pRet = m_iterator->first; m_iterator++; return pRet; @@ -112,9 +110,7 @@ BLEService* BLEServiceMap::getFirst() { * @return The next service in the map. */ BLEService* BLEServiceMap::getNext() { - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } + if (m_iterator == m_uuidMap.end()) return nullptr; BLEService* pRet = m_iterator->first; m_iterator++; return pRet; @@ -124,7 +120,7 @@ BLEService* BLEServiceMap::getNext() { * @brief Removes service from maps. * @return N/A. */ -void BLEServiceMap::removeService(BLEService *service){ +void BLEServiceMap::removeService(BLEService* service) { m_handleMap.erase(service->getHandle()); m_uuidMap.erase(service); } // removeService diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 9ca7cdd7..512c24ea 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -41,7 +41,7 @@ static const char* LOG_TAG = "BLEUUID"; */ static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { assert(size > 0); - target+=(size-1); // Point target to the last byte of the target data + target += (size - 1); // Point target to the last byte of the target data while (size > 0) { *target = *source; target--; @@ -80,11 +80,11 @@ BLEUUID::BLEUUID(std::string value) { } else if (value.length() == 16) { m_uuid.len = ESP_UUID_LEN_128; - memrcpy(m_uuid.uuid.uuid128, (uint8_t*)value.data(), 16); + memrcpy(m_uuid.uuid.uuid128, (uint8_t*) value.data(), 16); } else if (value.length() == 36) { -// If the length of the string is 36 bytes then we will assume it is a long hex string in -// UUID format. + // If the length of the string is 36 bytes then we will assume it is a long hex string in + // UUID format. m_uuid.len = ESP_UUID_LEN_128; int vals[16]; sscanf(value.c_str(), "%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x", @@ -106,12 +106,10 @@ BLEUUID::BLEUUID(std::string value) { &vals[0] ); - int i; - for (i=0; i<16; i++) { + for (int i = 0; i < 16; i++) { m_uuid.uuid.uuid128[i] = vals[i]; } - } - else { + } else { ESP_LOGE(LOG_TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes"); m_valueSet = false; } @@ -136,7 +134,7 @@ BLEUUID::BLEUUID(uint8_t* pData, size_t size, bool msbFirst) { } else { memcpy(m_uuid.uuid.uuid128, pData, 16); } - m_valueSet = true; + m_valueSet = true; } // BLEUUID @@ -149,7 +147,6 @@ BLEUUID::BLEUUID(uint16_t uuid) { m_uuid.len = ESP_UUID_LEN_16; m_uuid.uuid.uuid16 = uuid; m_valueSet = true; - } // BLEUUID @@ -194,24 +191,18 @@ BLEUUID::BLEUUID() { * @brief Get the number of bits in this uuid. * @return The number of bits in the UUID. One of 16, 32 or 128. */ -int BLEUUID::bitSize() { - if (m_valueSet == false) { - return 0; - } - switch(m_uuid.len) { - case ESP_UUID_LEN_16: { +uint8_t BLEUUID::bitSize() { + if (!m_valueSet) return 0; + switch (m_uuid.len) { + case ESP_UUID_LEN_16: return 16; - } - case ESP_UUID_LEN_32: { + case ESP_UUID_LEN_32: return 32; - } - case ESP_UUID_LEN_128: { + case ESP_UUID_LEN_128: return 128; - } - default: { + default: ESP_LOGE(LOG_TAG, "Unknown UUID length: %d", m_uuid.len); return 0; - } } // End of switch } // bitSize @@ -224,9 +215,7 @@ int BLEUUID::bitSize() { */ bool BLEUUID::equals(BLEUUID uuid) { //ESP_LOGD(TAG, "Comparing: %s to %s", toString().c_str(), uuid.toString().c_str()); - if (m_valueSet == false || uuid.m_valueSet == false) { - return false; - } + if (!m_valueSet || !uuid.m_valueSet) return false; if (uuid.m_uuid.len != m_uuid.len) { return uuid.toString() == toString(); @@ -253,14 +242,14 @@ bool BLEUUID::equals(BLEUUID uuid) { * NNNNNNNN * */ -BLEUUID BLEUUID::fromString(std::string _uuid){ +BLEUUID BLEUUID::fromString(std::string _uuid) { uint8_t start = 0; if (strstr(_uuid.c_str(), "0x") != nullptr) { // If the string starts with 0x, skip those characters. start = 2; } uint8_t len = _uuid.length() - start; // Calculate the length of the string we are going to use. - if( len == 4) { + if (len == 4) { uint16_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); return BLEUUID(x); } else if (len == 8) { @@ -280,7 +269,7 @@ BLEUUID BLEUUID::fromString(std::string _uuid){ */ esp_bt_uuid_t* BLEUUID::getNative() { //ESP_LOGD(TAG, ">> getNative()") - if (m_valueSet == false) { + if (!m_valueSet) { ESP_LOGD(LOG_TAG, "<< Return of un-initialized UUID!"); return nullptr; } @@ -299,7 +288,7 @@ BLEUUID BLEUUID::to128() { //ESP_LOGD(LOG_TAG, ">> toFull() - %s", toString().c_str()); // If we either don't have a value or are already a 128 bit UUID, nothing further to do. - if (m_valueSet == false || m_uuid.len == ESP_UUID_LEN_128) { + if (!m_valueSet || m_uuid.len == ESP_UUID_LEN_128) { return *this; } @@ -356,9 +345,7 @@ BLEUUID BLEUUID::to128() { * @return A string representation of the UUID. */ std::string BLEUUID::toString() { - if (m_valueSet == false) { // If we have no value, nothing to format. - return ""; - } + if (!m_valueSet) return ""; // If we have no value, nothing to format. // If the UUIDs are 16 or 32 bit, pad correctly. std::stringstream ss; @@ -386,24 +373,23 @@ std::string BLEUUID::toString() { // // UUID string format: // AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP - // ss << std::hex << std::setfill('0') << - std::setw(2) << (int)m_uuid.uuid.uuid128[15] << - std::setw(2) << (int)m_uuid.uuid.uuid128[14] << - std::setw(2) << (int)m_uuid.uuid.uuid128[13] << - std::setw(2) << (int)m_uuid.uuid.uuid128[12] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[11] << - std::setw(2) << (int)m_uuid.uuid.uuid128[10] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[9] << - std::setw(2) << (int)m_uuid.uuid.uuid128[8] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[7] << - std::setw(2) << (int)m_uuid.uuid.uuid128[6] << "-" << - std::setw(2) << (int)m_uuid.uuid.uuid128[5] << - std::setw(2) << (int)m_uuid.uuid.uuid128[4] << - std::setw(2) << (int)m_uuid.uuid.uuid128[3] << - std::setw(2) << (int)m_uuid.uuid.uuid128[2] << - std::setw(2) << (int)m_uuid.uuid.uuid128[1] << - std::setw(2) << (int)m_uuid.uuid.uuid128[0]; + std::setw(2) << (int) m_uuid.uuid.uuid128[15] << + std::setw(2) << (int) m_uuid.uuid.uuid128[14] << + std::setw(2) << (int) m_uuid.uuid.uuid128[13] << + std::setw(2) << (int) m_uuid.uuid.uuid128[12] << "-" << + std::setw(2) << (int) m_uuid.uuid.uuid128[11] << + std::setw(2) << (int) m_uuid.uuid.uuid128[10] << "-" << + std::setw(2) << (int) m_uuid.uuid.uuid128[9] << + std::setw(2) << (int) m_uuid.uuid.uuid128[8] << "-" << + std::setw(2) << (int) m_uuid.uuid.uuid128[7] << + std::setw(2) << (int) m_uuid.uuid.uuid128[6] << "-" << + std::setw(2) << (int) m_uuid.uuid.uuid128[5] << + std::setw(2) << (int) m_uuid.uuid.uuid128[4] << + std::setw(2) << (int) m_uuid.uuid.uuid128[3] << + std::setw(2) << (int) m_uuid.uuid.uuid128[2] << + std::setw(2) << (int) m_uuid.uuid.uuid128[1] << + std::setw(2) << (int) m_uuid.uuid.uuid128[0]; return ss.str(); } // toString diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 53f1a8f2..0dd5f461 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -31,8 +31,8 @@ static const char* LOG_TAG = "BLEUtils"; // Tag for logging. /* -static std::map g_addressMap; -static std::map g_connIdMap; +static std::map g_addressMap; +static std::map g_connIdMap; */ typedef struct { @@ -628,7 +628,7 @@ static std::string gattIdToString(esp_gatt_id_t gattId) { * @brief Convert an esp_ble_addr_type_t to a string representation. */ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { - switch(type) { + switch (type) { case BLE_ADDR_TYPE_PUBLIC: return "BLE_ADDR_TYPE_PUBLIC"; case BLE_ADDR_TYPE_RANDOM: @@ -650,19 +650,19 @@ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { */ std::string BLEUtils::adFlagsToString(uint8_t adFlags) { std::stringstream ss; - if (adFlags & (1<<0)) { + if (adFlags & (1 << 0)) { ss << "[LE Limited Discoverable Mode] "; } - if (adFlags & (1<<1)) { + if (adFlags & (1 << 1)) { ss << "[LE General Discoverable Mode] "; } - if (adFlags & (1<<2)) { + if (adFlags & (1 << 2)) { ss << "[BR/EDR Not Supported] "; } - if (adFlags & (1<<3)) { + if (adFlags & (1 << 3)) { ss << "[Simultaneous LE and BR/EDR to Same Device Capable (Controller)] "; } - if (adFlags & (1<<4)) { + if (adFlags & (1 << 4)) { ss << "[Simultaneous LE and BR/EDR to Same Device Capable (Host)] "; } return ss.str(); @@ -678,82 +678,57 @@ std::string BLEUtils::adFlagsToString(uint8_t adFlags) { * @return A string representation of the type. */ const char* BLEUtils::advTypeToString(uint8_t advType) { - switch(advType) { - case ESP_BLE_AD_TYPE_FLAG: // 0x01 + switch (advType) { + case ESP_BLE_AD_TYPE_FLAG: // 0x01 return "ESP_BLE_AD_TYPE_FLAG"; - - case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 + case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 return "ESP_BLE_AD_TYPE_16SRV_PART"; - - case ESP_BLE_AD_TYPE_16SRV_CMPL: // 0x03 + case ESP_BLE_AD_TYPE_16SRV_CMPL: // 0x03 return "ESP_BLE_AD_TYPE_16SRV_CMPL"; - - case ESP_BLE_AD_TYPE_32SRV_PART: // 0x04 + case ESP_BLE_AD_TYPE_32SRV_PART: // 0x04 return "ESP_BLE_AD_TYPE_32SRV_PART"; - - case ESP_BLE_AD_TYPE_32SRV_CMPL: // 0x05 + case ESP_BLE_AD_TYPE_32SRV_CMPL: // 0x05 return "ESP_BLE_AD_TYPE_32SRV_CMPL"; - - case ESP_BLE_AD_TYPE_128SRV_PART: // 0x06 + case ESP_BLE_AD_TYPE_128SRV_PART: // 0x06 return "ESP_BLE_AD_TYPE_128SRV_PART"; - - case ESP_BLE_AD_TYPE_128SRV_CMPL: // 0x07 + case ESP_BLE_AD_TYPE_128SRV_CMPL: // 0x07 return "ESP_BLE_AD_TYPE_128SRV_CMPL"; - - case ESP_BLE_AD_TYPE_NAME_SHORT: // 0x08 + case ESP_BLE_AD_TYPE_NAME_SHORT: // 0x08 return "ESP_BLE_AD_TYPE_NAME_SHORT"; - - case ESP_BLE_AD_TYPE_NAME_CMPL: // 0x09 + case ESP_BLE_AD_TYPE_NAME_CMPL: // 0x09 return "ESP_BLE_AD_TYPE_NAME_CMPL"; - - case ESP_BLE_AD_TYPE_TX_PWR: // 0x0a + case ESP_BLE_AD_TYPE_TX_PWR: // 0x0a return "ESP_BLE_AD_TYPE_TX_PWR"; - - case ESP_BLE_AD_TYPE_DEV_CLASS: // 0x0b + case ESP_BLE_AD_TYPE_DEV_CLASS: // 0x0b return "ESP_BLE_AD_TYPE_DEV_CLASS"; - - case ESP_BLE_AD_TYPE_SM_TK: // 0x10 + case ESP_BLE_AD_TYPE_SM_TK: // 0x10 return "ESP_BLE_AD_TYPE_SM_TK"; - - case ESP_BLE_AD_TYPE_SM_OOB_FLAG: // 0x11 + case ESP_BLE_AD_TYPE_SM_OOB_FLAG: // 0x11 return "ESP_BLE_AD_TYPE_SM_OOB_FLAG"; - - case ESP_BLE_AD_TYPE_INT_RANGE: // 0x12 + case ESP_BLE_AD_TYPE_INT_RANGE: // 0x12 return "ESP_BLE_AD_TYPE_INT_RANGE"; - - case ESP_BLE_AD_TYPE_SOL_SRV_UUID: // 0x14 + case ESP_BLE_AD_TYPE_SOL_SRV_UUID: // 0x14 return "ESP_BLE_AD_TYPE_SOL_SRV_UUID"; - - case ESP_BLE_AD_TYPE_128SOL_SRV_UUID: // 0x15 + case ESP_BLE_AD_TYPE_128SOL_SRV_UUID: // 0x15 return "ESP_BLE_AD_TYPE_128SOL_SRV_UUID"; - - case ESP_BLE_AD_TYPE_SERVICE_DATA: // 0x16 + case ESP_BLE_AD_TYPE_SERVICE_DATA: // 0x16 return "ESP_BLE_AD_TYPE_SERVICE_DATA"; - - case ESP_BLE_AD_TYPE_PUBLIC_TARGET: // 0x17 + case ESP_BLE_AD_TYPE_PUBLIC_TARGET: // 0x17 return "ESP_BLE_AD_TYPE_PUBLIC_TARGET"; - - case ESP_BLE_AD_TYPE_RANDOM_TARGET: // 0x18 + case ESP_BLE_AD_TYPE_RANDOM_TARGET: // 0x18 return "ESP_BLE_AD_TYPE_RANDOM_TARGET"; - - case ESP_BLE_AD_TYPE_APPEARANCE: // 0x19 + case ESP_BLE_AD_TYPE_APPEARANCE: // 0x19 return "ESP_BLE_AD_TYPE_APPEARANCE"; - - case ESP_BLE_AD_TYPE_ADV_INT: // 0x1a + case ESP_BLE_AD_TYPE_ADV_INT: // 0x1a return "ESP_BLE_AD_TYPE_ADV_INT"; - case ESP_BLE_AD_TYPE_32SOL_SRV_UUID: return "ESP_BLE_AD_TYPE_32SOL_SRV_UUID"; - - case ESP_BLE_AD_TYPE_32SERVICE_DATA: // 0x20 + case ESP_BLE_AD_TYPE_32SERVICE_DATA: // 0x20 return "ESP_BLE_AD_TYPE_32SERVICE_DATA"; - - case ESP_BLE_AD_TYPE_128SERVICE_DATA: // 0x21 + case ESP_BLE_AD_TYPE_128SERVICE_DATA: // 0x21 return "ESP_BLE_AD_TYPE_128SERVICE_DATA"; - case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xff return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; - default: ESP_LOGD(LOG_TAG, " adv data type: 0x%x", advType); return ""; @@ -768,8 +743,7 @@ esp_gatt_id_t BLEUtils::buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id) { return retGattId; } -esp_gatt_srvc_id_t BLEUtils::buildGattSrvcId(esp_gatt_id_t gattId, - bool is_primary) { +esp_gatt_srvc_id_t BLEUtils::buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary) { esp_gatt_srvc_id_t retSrvcId; retSrvcId.id = gattId; retSrvcId.is_primary = is_primary; @@ -784,29 +758,26 @@ esp_gatt_srvc_id_t BLEUtils::buildGattSrvcId(esp_gatt_id_t gattId, * @param [in] length The length of the data to convert. * @return A pointer to the formatted buffer. */ -char* BLEUtils::buildHexData(uint8_t *target, uint8_t *source, uint8_t length) { -// Guard against too much data. - if (length > 100) { - length = 100; - } +char* BLEUtils::buildHexData(uint8_t* target, uint8_t* source, uint8_t length) { + // Guard against too much data. + if (length > 100) length = 100; if (target == nullptr) { - target = (uint8_t *)malloc(length * 2 + 1); + target = (uint8_t*) malloc(length * 2 + 1); if (target == nullptr) { ESP_LOGE(LOG_TAG, "buildHexData: malloc failed"); return nullptr; } } - char *startOfData = (char *)target; + char* startOfData = (char*) target; - int i; - for (i=0; iadv_data_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_data_cmpl.status); break; } // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT - - // // ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT // // adv_data_raw_cmpl // - esp_bt_status_t status - // case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_data_raw_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_data_raw_cmpl.status); break; } // ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT - - // // ESP_GAP_BLE_ADV_START_COMPLETE_EVT // // adv_start_cmpl // - esp_bt_status_t status - // case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_start_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_start_cmpl.status); break; } // ESP_GAP_BLE_ADV_START_COMPLETE_EVT - - // // ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT // // adv_stop_cmpl // - esp_bt_status_t status - // case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_stop_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_stop_cmpl.status); break; } // ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT - - // // ESP_GAP_BLE_AUTH_CMPL_EVT // // auth_cmpl @@ -1150,7 +1081,6 @@ void BLEUtils::dumpGapEvent( // - uint8_t fail_reason // - esp_bd_addr_type_t addr_type // - esp_bt_dev_type_t dev_type - // case ESP_GAP_BLE_AUTH_CMPL_EVT: { ESP_LOGD(LOG_TAG, "[bd_addr: %s, key_present: %d, key: ***, key_type: %d, success: %d, fail_reason: %d, addr_type: ***, dev_type: %s]", BLEAddress(param->ble_security.auth_cmpl.bd_addr).toString().c_str(), @@ -1163,38 +1093,26 @@ void BLEUtils::dumpGapEvent( break; } // ESP_GAP_BLE_AUTH_CMPL_EVT - - // // ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT // // clear_bond_dev_cmpl // - esp_bt_status_t status - // case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->clear_bond_dev_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->clear_bond_dev_cmpl.status); break; } // ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT - - // // ESP_GAP_BLE_LOCAL_IR_EVT - // case ESP_GAP_BLE_LOCAL_IR_EVT: { break; } // ESP_GAP_BLE_LOCAL_IR_EVT - - // // ESP_GAP_BLE_LOCAL_ER_EVT - // case ESP_GAP_BLE_LOCAL_ER_EVT: { break; } // ESP_GAP_BLE_LOCAL_ER_EVT - - // // ESP_GAP_BLE_NC_REQ_EVT - // case ESP_GAP_BLE_NC_REQ_EVT: { ESP_LOGD(LOG_TAG, "[bd_addr: %s, passkey: %d]", BLEAddress(param->ble_security.key_notif.bd_addr).toString().c_str(), @@ -1202,15 +1120,12 @@ void BLEUtils::dumpGapEvent( break; } // ESP_GAP_BLE_NC_REQ_EVT - - // // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT // // read_rssi_cmpl // - esp_bt_status_t status // - int8_t rssi // - esp_bd_addr_t remote_addr - // case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: { ESP_LOGD(LOG_TAG, "[status: %d, rssi: %d, remote_addr: %s]", param->read_rssi_cmpl.status, @@ -1220,20 +1135,15 @@ void BLEUtils::dumpGapEvent( break; } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT - - // // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT // // scan_param_cmpl. // - esp_bt_status_t status - // case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_param_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_param_cmpl.status); break; } // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT - - // // ESP_GAP_BLE_SCAN_RESULT_EVT // // scan_rst: @@ -1248,9 +1158,8 @@ void BLEUtils::dumpGapEvent( // - num_resps // - adv_data_len // - scan_rsp_len - // case ESP_GAP_BLE_SCAN_RESULT_EVT: { - switch(param->scan_rst.search_evt) { + switch (param->scan_rst.search_evt) { case ESP_GAP_SEARCH_INQ_RES_EVT: { ESP_LOGD(LOG_TAG, "search_evt: %s, bda: %s, dev_type: %s, ble_addr_type: %s, ble_evt_type: %s, rssi: %d, ble_adv: ??, flag: %d (%s), num_resps: %d, adv_data_len: %d, scan_rsp_len: %d", searchEventTypeToString(param->scan_rst.search_evt), @@ -1276,29 +1185,21 @@ void BLEUtils::dumpGapEvent( break; } // ESP_GAP_BLE_SCAN_RESULT_EVT - - // // ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT // // scan_rsp_data_cmpl // - esp_bt_status_t status - // case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_cmpl.status); break; } // ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT - - // // ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT - // case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_raw_cmpl.status); + ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_raw_cmpl.status); break; } // ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT - - // // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT // // scan_start_cmpl @@ -1308,20 +1209,15 @@ void BLEUtils::dumpGapEvent( break; } // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT - - // // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT // // scan_stop_cmpl // - esp_bt_status_t status - // case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: { ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status); break; } // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT - - // // ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT // // update_conn_params @@ -1332,7 +1228,6 @@ void BLEUtils::dumpGapEvent( // - uint16_t latency // - uint16_t conn_int // - uint16_t timeout - // case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: { ESP_LOGD(LOG_TAG, "[status: %d, bd_addr: %s, min_int: %d, max_int: %d, latency: %d, conn_int: %d, timeout: %d]", param->update_conn_params.status, @@ -1346,16 +1241,12 @@ void BLEUtils::dumpGapEvent( break; } // ESP_GAP_BLE_SCAN_UPDATE_CONN_PARAMS_EVT - - // // ESP_GAP_BLE_SEC_REQ_EVT - // case ESP_GAP_BLE_SEC_REQ_EVT: { ESP_LOGD(LOG_TAG, "[bd_addr: %s]", BLEAddress(param->ble_security.ble_req.bd_addr).toString().c_str()); break; } // ESP_GAP_BLE_SEC_REQ_EVT - default: { ESP_LOGD(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); break; @@ -1375,10 +1266,9 @@ void BLEUtils::dumpGattClientEvent( esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { - //esp_ble_gattc_cb_param_t *evtParam = (esp_ble_gattc_cb_param_t *)param; + //esp_ble_gattc_cb_param_t* evtParam = (esp_ble_gattc_cb_param_t*) param; ESP_LOGD(LOG_TAG, "GATT Event: %s", BLEUtils::gattClientEventTypeToString(event).c_str()); - switch(event) { - // + switch (event) { // ESP_GATTC_CLOSE_EVT // // close: @@ -1386,7 +1276,6 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t conn_id // - esp_bd_addr_t remote_bda // - esp_gatt_conn_reason_t reason - // case ESP_GATTC_CLOSE_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, reason:%s, conn_id: %d]", BLEUtils::gattStatusToString(evtParam->close.status).c_str(), @@ -1395,7 +1284,6 @@ void BLEUtils::dumpGattClientEvent( break; } - // // ESP_GATTC_CONNECT_EVT // // connect: @@ -1410,7 +1298,6 @@ void BLEUtils::dumpGattClientEvent( break; } - // // ESP_GATTC_DISCONNECT_EVT // // disconnect: @@ -1426,7 +1313,6 @@ void BLEUtils::dumpGattClientEvent( break; } // ESP_GATTC_DISCONNECT_EVT - // // ESP_GATTC_GET_CHAR_EVT // // get_char: @@ -1435,7 +1321,6 @@ void BLEUtils::dumpGattClientEvent( // - esp_gatt_srvc_id_t srvc_id // - esp_gatt_id_t char_id // - esp_gatt_char_prop_t char_prop - // /* case ESP_GATTC_GET_CHAR_EVT: { @@ -1465,7 +1350,6 @@ void BLEUtils::dumpGattClientEvent( } // ESP_GATTC_GET_CHAR_EVT */ - // // ESP_GATTC_NOTIFY_EVT // // notify @@ -1488,7 +1372,6 @@ void BLEUtils::dumpGattClientEvent( break; } - // // ESP_GATTC_OPEN_EVT // // open: @@ -1506,8 +1389,6 @@ void BLEUtils::dumpGattClientEvent( break; } // ESP_GATTC_OPEN_EVT - - // // ESP_GATTC_READ_CHAR_EVT // // Callback to indicate that requested data that we wanted to read is now available. @@ -1530,7 +1411,7 @@ void BLEUtils::dumpGattClientEvent( if (evtParam->read.status == ESP_GATT_OK) { GeneralUtils::hexDump(evtParam->read.value, evtParam->read.value_len); /* - char *pHexData = BLEUtils::buildHexData(nullptr, evtParam->read.value, evtParam->read.value_len); + char* pHexData = BLEUtils::buildHexData(nullptr, evtParam->read.value, evtParam->read.value_len); ESP_LOGD(LOG_TAG, "value: %s \"%s\"", pHexData, BLEUtils::buildPrintData(evtParam->read.value, evtParam->read.value_len).c_str()); free(pHexData); */ @@ -1538,14 +1419,11 @@ void BLEUtils::dumpGattClientEvent( break; } // ESP_GATTC_READ_CHAR_EVT - - // // ESP_GATTC_REG_EVT // // reg: // - esp_gatt_status_t status // - uint16_t app_id - // case ESP_GATTC_REG_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, app_id: 0x%x]", BLEUtils::gattStatusToString(evtParam->reg.status).c_str(), @@ -1553,8 +1431,6 @@ void BLEUtils::dumpGattClientEvent( break; } // ESP_GATTC_REG_EVT - - // // ESP_GATTC_REG_FOR_NOTIFY_EVT // // reg_for_notify: @@ -1569,14 +1445,11 @@ void BLEUtils::dumpGattClientEvent( break; } // ESP_GATTC_REG_FOR_NOTIFY_EVT - - // // ESP_GATTC_SEARCH_CMPL_EVT // // search_cmpl: // - esp_gatt_status_t status // - uint16_t conn_id - // case ESP_GATTC_SEARCH_CMPL_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d]", BLEUtils::gattStatusToString(evtParam->search_cmpl.status).c_str(), @@ -1584,8 +1457,6 @@ void BLEUtils::dumpGattClientEvent( break; } // ESP_GATTC_SEARCH_CMPL_EVT - - // // ESP_GATTC_SEARCH_RES_EVT // // search_res: @@ -1593,7 +1464,6 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t start_handle // - uint16_t end_handle // - esp_gatt_id_t srvc_id - // case ESP_GATTC_SEARCH_RES_EVT: { ESP_LOGD(LOG_TAG, "[conn_id: %d, start_handle: %d 0x%.2x, end_handle: %d 0x%.2x, srvc_id: %s", evtParam->search_res.conn_id, @@ -1605,8 +1475,6 @@ void BLEUtils::dumpGattClientEvent( break; } // ESP_GATTC_SEARCH_RES_EVT - - // // ESP_GATTC_WRITE_CHAR_EVT // // write: @@ -1614,7 +1482,6 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t conn_id // - uint16_t handle // - uint16_t offset - // case ESP_GATTC_WRITE_CHAR_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x, offset: %d]", BLEUtils::gattStatusToString(evtParam->write.status).c_str(), @@ -1647,7 +1514,7 @@ void BLEUtils::dumpGattServerEvent( esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* evtParam) { ESP_LOGD(LOG_TAG, "GATT ServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); - switch(event) { + switch (event) { case ESP_GATTS_ADD_CHAR_DESCR_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", @@ -1687,7 +1554,6 @@ void BLEUtils::dumpGattServerEvent( // conf: // - esp_gatt_status_t status – The status code. // - uint16_t conn_id – The connection used. - // case ESP_GATTS_CONF_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, conn_id: 0x%.2x]", gattStatusToString(evtParam->conf.status).c_str(), @@ -1733,22 +1599,21 @@ void BLEUtils::dumpGattServerEvent( // - uint32_t trans_id // - esp_bd_addr_t bda // - uint8_t exec_write_flag - // case ESP_GATTS_EXEC_WRITE_EVT: { char* pWriteFlagText; - switch(evtParam->exec_write.exec_write_flag) { + switch (evtParam->exec_write.exec_write_flag) { case ESP_GATT_PREP_WRITE_EXEC: { - pWriteFlagText = (char*)"WRITE"; + pWriteFlagText = (char*) "WRITE"; break; } case ESP_GATT_PREP_WRITE_CANCEL: { - pWriteFlagText = (char*)"CANCEL"; + pWriteFlagText = (char*) "CANCEL"; break; } default: - pWriteFlagText = (char*)""; + pWriteFlagText = (char*) ""; break; } @@ -1800,7 +1665,6 @@ void BLEUtils::dumpGattServerEvent( // start: // - esp_gatt_status_t status // - uint16_t service_handle - // case ESP_GATTS_START_EVT: { ESP_LOGD(LOG_TAG, "[status: %s, service_handle: 0x%.2x]", gattStatusToString(evtParam->start.status).c_str(), @@ -1821,7 +1685,6 @@ void BLEUtils::dumpGattServerEvent( // - bool is_prep – Is this a write prepare? If set, then this is to be considered part of the received value and not the whole value. A subsequent ESP_GATTS_EXEC_WRITE will mark the total. // - uint16_t len – The length of the incoming value part. // - uint8_t* value – The data for this value part. - // case ESP_GATTS_WRITE_EVT: { ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, offset: %d, need_rsp: %d, is_prep: %d, len: %d]", evtParam->write.conn_id, @@ -1851,7 +1714,7 @@ void BLEUtils::dumpGattServerEvent( * @return The event type as a string. */ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { - switch(eventType) { + switch (eventType) { case ESP_BLE_EVT_CONN_ADV: return "ESP_BLE_EVT_CONN_ADV"; case ESP_BLE_EVT_CONN_DIR_ADV: @@ -1876,89 +1739,61 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { * @return A string representation of the event type. */ const char* BLEUtils::gapEventToString(uint32_t eventType) { - switch(eventType) { + switch (eventType) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT"; - case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT"; - case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_START_COMPLETE_EVT"; - - case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: /*!< When stop adv complete, the event comes */ + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: /* !< When stop adv complete, the event comes */ return "ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT"; - - case ESP_GAP_BLE_AUTH_CMPL_EVT: /* Authentication complete indication. */ + case ESP_GAP_BLE_AUTH_CMPL_EVT: /* Authentication complete indication. */ return "ESP_GAP_BLE_AUTH_CMPL_EVT"; - case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: return "ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT"; - case ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT: return "ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT"; - - case ESP_GAP_BLE_KEY_EVT: /* BLE key event for peer device keys */ + case ESP_GAP_BLE_KEY_EVT: /* BLE key event for peer device keys */ return "ESP_GAP_BLE_KEY_EVT"; - - case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ return "ESP_GAP_BLE_LOCAL_IR_EVT"; - - case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ return "ESP_GAP_BLE_LOCAL_ER_EVT"; - - case ESP_GAP_BLE_NC_REQ_EVT: /* Numeric Comparison request event */ + case ESP_GAP_BLE_NC_REQ_EVT: /* Numeric Comparison request event */ return "ESP_GAP_BLE_NC_REQ_EVT"; - - case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ return "ESP_GAP_BLE_OOB_REQ_EVT"; - - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: /* passkey notification event */ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: /* passkey notification event */ return "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"; - - case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ return "ESP_GAP_BLE_PASSKEY_REQ_EVT"; - case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: return "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT"; - case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: return "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT"; - case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT"; - case ESP_GAP_BLE_SCAN_RESULT_EVT: return "ESP_GAP_BLE_SCAN_RESULT_EVT"; - case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT"; - case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT"; - case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_START_COMPLETE_EVT"; - case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: return "ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT"; - - case ESP_GAP_BLE_SEC_REQ_EVT: /* BLE security request */ + case ESP_GAP_BLE_SEC_REQ_EVT: /* BLE security request */ return "ESP_GAP_BLE_SEC_REQ_EVT"; - case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: return "ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT"; - case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: return "ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT"; - case ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT: return "ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT"; - case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: return "ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT"; - - default: ESP_LOGD(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); return "Unknown event type"; @@ -1967,7 +1802,7 @@ const char* BLEUtils::gapEventToString(uint32_t eventType) { std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID) { - const characteristicMap_t *p = g_characteristicsMappings; + const characteristicMap_t* p = g_characteristicsMappings; while (strlen(p->name) > 0) { if (p->assignedNumber == characteristicUUID) { return std::string(p->name); @@ -1984,7 +1819,7 @@ std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID * @return The string representation of a descriptor UUID. */ std::string BLEUtils::gattDescriptorUUIDToString(uint32_t descriptorUUID) { - gattdescriptor_t* p = (gattdescriptor_t *)g_descriptor_ids; + gattdescriptor_t* p = (gattdescriptor_t*) g_descriptor_ids; while (strlen(p->name) > 0) { if (p->assignedNumber == descriptorUUID) { return std::string(p->name); @@ -2020,7 +1855,7 @@ std::string BLEUtils::gattServiceIdToString(esp_gatt_srvc_id_t srvcId) { std::string BLEUtils::gattServiceToString(uint32_t serviceId) { - gattService_t* p = (gattService_t *)g_gattServices; + gattService_t* p = (gattService_t*) g_gattServices; while (strlen(p->name) > 0) { if (p->assignedNumber == serviceId) { return std::string(p->name); @@ -2038,7 +1873,7 @@ std::string BLEUtils::gattServiceToString(uint32_t serviceId) { * @return A string representation of the status. */ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { - switch(status) { + switch (status) { case ESP_GATT_OK: return "ESP_GATT_OK"; case ESP_GATT_INVALID_HANDLE: @@ -2133,11 +1968,11 @@ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { std::string BLEUtils::getMember(uint32_t memberId) { - member_t* p = (member_t *)members_ids; + member_t* p = (member_t*) members_ids; while (strlen(p->name) > 0) { if (p->assignedNumber == memberId) { - return std::string(p->name); + return std::string(p->name); } p++; } @@ -2150,7 +1985,7 @@ std::string BLEUtils::getMember(uint32_t memberId) { * @return The search event type as a string. */ const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { - switch(searchEvt) { + switch (searchEvt) { case ESP_GAP_SEARCH_INQ_RES_EVT: return "ESP_GAP_SEARCH_INQ_RES_EVT"; case ESP_GAP_SEARCH_INQ_CMPL_EVT: diff --git a/cpp_utils/BLEUtils.h b/cpp_utils/BLEUtils.h index b2baee53..7981691c 100644 --- a/cpp_utils/BLEUtils.h +++ b/cpp_utils/BLEUtils.h @@ -23,12 +23,12 @@ class BLEUtils { static const char* addressTypeToString(esp_ble_addr_type_t type); static std::string adFlagsToString(uint8_t adFlags); static const char* advTypeToString(uint8_t advType); - static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id=0); - static esp_gatt_srvc_id_t buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary=true); static char* buildHexData(uint8_t* target, uint8_t* source, uint8_t length); static std::string buildPrintData(uint8_t* source, size_t length); static std::string characteristicPropertiesToString(esp_gatt_char_prop_t prop); static const char* devTypeToString(esp_bt_dev_type_t type); + static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id = 0); + static esp_gatt_srvc_id_t buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary = true); static void dumpGapEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); @@ -47,7 +47,7 @@ class BLEUtils { static std::string gattCharacteristicUUIDToString(uint32_t characteristicUUID); static std::string gattClientEventTypeToString(esp_gattc_cb_event_t eventType); static std::string gattCloseReasonToString(esp_gatt_conn_reason_t reason); - static std::string gattcServiceElementToString(esp_gattc_service_elem_t *pGATTCServiceElement); + static std::string gattcServiceElementToString(esp_gattc_service_elem_t* pGATTCServiceElement); static std::string gattDescriptorUUIDToString(uint32_t descriptorUUID); static std::string gattServerEventTypeToString(esp_gatts_cb_event_t eventType); static std::string gattServiceIdToString(esp_gatt_srvc_id_t srvcId); diff --git a/cpp_utils/BLEValue.cpp b/cpp_utils/BLEValue.cpp index 49818e27..aac0f500 100644 --- a/cpp_utils/BLEValue.cpp +++ b/cpp_utils/BLEValue.cpp @@ -42,7 +42,7 @@ void BLEValue::addPart(std::string part) { */ void BLEValue::addPart(uint8_t* pData, size_t length) { ESP_LOGD(LOG_TAG, ">> addPart: length=%d", length); - m_accumulation += std::string((char *)pData, length); + m_accumulation += std::string((char*) pData, length); } // addPart @@ -65,9 +65,7 @@ void BLEValue::cancel() { void BLEValue::commit() { ESP_LOGD(LOG_TAG, ">> commit"); // If there is nothing to commit, do nothing. - if (m_accumulation.length() == 0) { - return; - } + if (m_accumulation.length() == 0) return; setValue(m_accumulation); m_accumulation = ""; m_readOffset = 0; @@ -79,7 +77,7 @@ void BLEValue::commit() { * @return A pointer to the data. */ uint8_t* BLEValue::getData() { - return (uint8_t*)m_value.data(); + return (uint8_t*) m_value.data(); } @@ -132,11 +130,8 @@ void BLEValue::setValue(std::string value) { * @param [in] The length of the new current value. */ void BLEValue::setValue(uint8_t* pData, size_t length) { - m_value = std::string((char*)pData, length); + m_value = std::string((char*) pData, length); } // setValue - - - #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEValue.h b/cpp_utils/BLEValue.h index 92a7f9a4..5df904c1 100644 --- a/cpp_utils/BLEValue.h +++ b/cpp_utils/BLEValue.h @@ -17,13 +17,13 @@ class BLEValue { public: BLEValue(); - void addPart(std::string part); - void addPart(uint8_t* pData, size_t length); - void cancel(); - void commit(); - uint8_t* getData(); - size_t getLength(); - uint16_t getReadOffset(); + void addPart(std::string part); + void addPart(uint8_t* pData, size_t length); + void cancel(); + void commit(); + uint8_t* getData(); + size_t getLength(); + uint16_t getReadOffset(); std::string getValue(); void setReadOffset(uint16_t readOffset); void setValue(std::string value); @@ -33,6 +33,7 @@ class BLEValue { std::string m_accumulation; uint16_t m_readOffset; std::string m_value; + }; #endif // CONFIG_BT_ENABLED #endif /* COMPONENTS_CPP_UTILS_BLEVALUE_H_ */ diff --git a/cpp_utils/BLEXML/Characteristics/code/run.sh b/cpp_utils/BLEXML/Characteristics/code/run.sh index 77f193fe..f0c7930a 100755 --- a/cpp_utils/BLEXML/Characteristics/code/run.sh +++ b/cpp_utils/BLEXML/Characteristics/code/run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash BASE_URL="https://www.bluetooth.com/api/gatt/XmlFile?xmlFileName=" RESULT=characteristics.json COUNT=0 @@ -17,4 +17,4 @@ do COUNT=$(expr ${COUNT} + 1) done echo -e "\n]\n" >> ${RESULT} -echo "done" \ No newline at end of file +echo "done" diff --git a/cpp_utils/BLEXML/Services/code/run.sh b/cpp_utils/BLEXML/Services/code/run.sh index bfa084d0..6716a53a 100755 --- a/cpp_utils/BLEXML/Services/code/run.sh +++ b/cpp_utils/BLEXML/Services/code/run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash BASE_URL="https://www.bluetooth.com/api/gatt/XmlFile?xmlFileName=" RESULT=services.json COUNT=0 @@ -17,4 +17,4 @@ do COUNT=$(expr ${COUNT} + 1) done echo -e "\n]\n" >> ${RESULT} -echo "done" \ No newline at end of file +echo "done" diff --git a/cpp_utils/CPPNVS.cpp b/cpp_utils/CPPNVS.cpp index b204045b..18914ab5 100644 --- a/cpp_utils/CPPNVS.cpp +++ b/cpp_utils/CPPNVS.cpp @@ -85,7 +85,7 @@ int NVS::get(std::string key, std::string* result, bool isBlob) { return rc; } } - char *data = (char *)malloc(length); + char* data = (char*) malloc(length); if (isBlob) { ::nvs_get_blob(m_handle, key.c_str(), data, &length); } else { diff --git a/cpp_utils/CPPNVS.h b/cpp_utils/CPPNVS.h index 6afbc166..9155891b 100644 --- a/cpp_utils/CPPNVS.h +++ b/cpp_utils/CPPNVS.h @@ -21,15 +21,17 @@ class NVS { void erase(); void erase(std::string key); - int get(std::string key, std::string* result, bool isBlob=false); - int get(std::string key, uint8_t* result, size_t &length); + int get(std::string key, std::string* result, bool isBlob = false); + int get(std::string key, uint8_t* result, size_t& length); int get(std::string key, uint32_t& value); - void set(std::string key, std::string data, bool isBlob=false); + void set(std::string key, std::string data, bool isBlob = false); void set(std::string key, uint32_t value); void set(std::string key, uint8_t* data, size_t length); + private: std::string m_name; nvs_handle m_handle; + }; #endif /* COMPONENTS_CPP_UTILS_CPPNVS_H_ */ diff --git a/cpp_utils/Console.cpp b/cpp_utils/Console.cpp index c88d8372..c0f5655f 100644 --- a/cpp_utils/Console.cpp +++ b/cpp_utils/Console.cpp @@ -42,7 +42,7 @@ ArgTable::~ArgTable() { */ int size = m_argTableEntries.size(); m_argtable = new void*[size + 1]; - int i=0; + int i = 0; for (auto it = m_argTableEntries.begin(); it != m_argTableEntries.end(); ++it) { m_argtable[i] = it->second->getEntry(); i++; @@ -54,42 +54,42 @@ ArgTable::~ArgTable() { ArgTableEntry_Date ArgTable::addDate(std::string name, std::string shortopts, std::string longopts, std::string glossary) { ArgTableEntry_Date* pDate = new ArgTableEntry_Date(shortopts, longopts, glossary); - m_argTableEntries.push_back(std::make_pair(name, pDate)); + m_argTableEntries.push_back(std::make_pair(name, pDate)); return *pDate; } // ArgTable#addDate ArgTableEntry_Double ArgTable::addDouble(std::string name, std::string shortopts, std::string longopts, std::string glossary) { ArgTableEntry_Double* pDouble = new ArgTableEntry_Double(shortopts, longopts, glossary); - m_argTableEntries.push_back(std::make_pair(name, pDouble)); + m_argTableEntries.push_back(std::make_pair(name, pDouble)); return *pDouble; } // ArgTable#addDouble ArgTableEntry_File ArgTable::addFile(std::string name, std::string shortopts, std::string longopts, std::string glossary) { ArgTableEntry_File* pFile = new ArgTableEntry_File(shortopts, longopts, glossary); - m_argTableEntries.push_back(std::make_pair(name, pFile)); + m_argTableEntries.push_back(std::make_pair(name, pFile)); return *pFile; } // ArgTable#addFile ArgTableEntry_Int ArgTable::addInt(std::string name, std::string shortopts, std::string longopts, std::string glossary) { ArgTableEntry_Int* pInt = new ArgTableEntry_Int(shortopts, longopts, glossary); - m_argTableEntries.push_back(std::make_pair(name, pInt)); + m_argTableEntries.push_back(std::make_pair(name, pInt)); return *pInt; } // ArgTable#addInt ArgTableEntry_Lit ArgTable::addLit(std::string name, std::string shortopts, std::string longopts, std::string glossary) { ArgTableEntry_Lit* pLit = new ArgTableEntry_Lit(shortopts, longopts, glossary); - m_argTableEntries.push_back(std::make_pair(name, pLit)); + m_argTableEntries.push_back(std::make_pair(name, pLit)); return *pLit; } // ArgTable#addLit ArgTableEntry_String ArgTable::addString(std::string name, std::string shortopts, std::string longopts, std::string glossary, int min, int max) { ArgTableEntry_String* pStr = new ArgTableEntry_String(shortopts, longopts, glossary, min, max); - m_argTableEntries.push_back(std::make_pair(name, pStr)); + m_argTableEntries.push_back(std::make_pair(name, pStr)); return *pStr; } // ArgTable#addString @@ -147,46 +147,56 @@ ArgTableEntry_Double::ArgTableEntry_Double(std::string shortopts, std::string lo m_type = ArgType_t::DBL; } + int ArgTableEntry_Double::getCount() { return m_argDbl->count; } + ArgTableEntry_File::ArgTableEntry_File(std::string shortopts, std::string longopts, std::string glossary) { m_argFile = arg_filen(shortopts.c_str(), longopts.c_str(), "", 0, 1, glossary.c_str()); - m_type = ArgType_t::FILE; + m_type = ArgType_t::FILE; } + int ArgTableEntry_File::getCount() { return m_argFile->count; } + ArgTableEntry_Int::ArgTableEntry_Int(std::string shortopts, std::string longopts, std::string glossary) { m_argInt = arg_intn(shortopts.c_str(), longopts.c_str(), "", 0, 1, glossary.c_str()); m_type = ArgType_t::INT; } + int ArgTableEntry_Int::getCount() { return m_argInt->count; } + ArgTableEntry_Lit::ArgTableEntry_Lit(std::string shortopts, std::string longopts, std::string glossary) { m_argLit = arg_litn(shortopts.c_str(), longopts.c_str(), 0, 1, glossary.c_str()); m_type = ArgType_t::LIT; } + int ArgTableEntry_Lit::getCount() { return m_argLit->count; } + int ArgTableEntry_Regex::getCount() { return m_argRex->count; } + ArgTableEntry_String::ArgTableEntry_String(std::string shortopts, std::string longopts, std::string glossary, int min, int max) { m_argStr = arg_strn(shortopts.c_str(), longopts.c_str(), "", min, max, glossary.c_str()); m_type = ArgType_t::STR; } + int ArgTableEntry_String::getCount() { return m_argStr->count; } @@ -195,6 +205,7 @@ int ArgTableEntry_String::getCount() { Console::Console() { } + Console::~Console() { } diff --git a/cpp_utils/Console.h b/cpp_utils/Console.h index 2ce40fa2..a94f63a1 100644 --- a/cpp_utils/Console.h +++ b/cpp_utils/Console.h @@ -52,7 +52,7 @@ class ArgTableEntry_Int : public ArgTableEntry_Generic { public: ArgTableEntry_Int(std::string shortopts, std::string longopts, std::string glossary); int getCount(); - int getValue(int index=0); + int getValue(int index = 0); void* getEntry() { return m_argInt; } @@ -65,7 +65,7 @@ class ArgTableEntry_Double : public ArgTableEntry_Generic { public: ArgTableEntry_Double(std::string shortopts, std::string longopts, std::string glossary); int getCount(); - double getValue(int index=0); + double getValue(int index = 0); void* getEntry() { return m_argDbl; } @@ -78,7 +78,7 @@ class ArgTableEntry_String : public ArgTableEntry_Generic { public: ArgTableEntry_String(std::string shortopts, std::string longopts, std::string glossary, int min, int max); int getCount(); - std::string getValue(int index=0); + std::string getValue(int index = 0); void* getEntry() { return m_argStr; } @@ -90,7 +90,7 @@ class ArgTableEntry_Regex : public ArgTableEntry_Generic { struct arg_rex* m_argRex; public: int getCount(); - std::string getValue(int index=0); + std::string getValue(int index = 0); void* getEntry() { return m_argRex; } @@ -103,9 +103,9 @@ class ArgTableEntry_File : public ArgTableEntry_Generic { public: ArgTableEntry_File(std::string shortopts, std::string longopts, std::string glossary); int getCount(); - std::string getFilename(int index=0); - std::string getBasename(int index=0); - std::string getExtension(int index=0); + std::string getFilename(int index = 0); + std::string getBasename(int index = 0); + std::string getExtension(int index = 0); void* getEntry() { return m_argFile; } @@ -118,7 +118,7 @@ class ArgTableEntry_Date : public ArgTableEntry_Generic { public: ArgTableEntry_Date(std::string shortopts, std::string longopts, std::string glossary); int getCount(); - struct tm* getValue(int index=0); + struct tm* getValue(int index = 0); void* getEntry() { return m_argDate; } diff --git a/cpp_utils/FTPCallbacks.cpp b/cpp_utils/FTPCallbacks.cpp index d88c2300..69e462c9 100644 --- a/cpp_utils/FTPCallbacks.cpp +++ b/cpp_utils/FTPCallbacks.cpp @@ -24,7 +24,7 @@ void FTPFileCallbacks::onStoreStart(std::string fileName) { */ size_t FTPFileCallbacks::onStoreData(uint8_t* data, size_t size) { ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onStoreData: size=%d", size); - m_storeFile.write((char *)data, size); // Store data received. + m_storeFile.write((char*) data, size); // Store data received. ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onStoreData: size=%d", size); return size; } // FTPFileCallbacks#onStoreData @@ -65,7 +65,7 @@ void FTPFileCallbacks::onRetrieveStart(std::string fileName) { */ size_t FTPFileCallbacks::onRetrieveData(uint8_t* data, size_t size) { ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onRetrieveData"); - m_retrieveFile.read((char *)data, size); + m_retrieveFile.read((char*) data, size); size_t readSize = m_retrieveFile.gcount(); m_byteCount += readSize; ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onRetrieveData: sizeRead=%d", readSize); @@ -88,14 +88,11 @@ void FTPFileCallbacks::onRetrieveEnd() { * @return a list of files in the file system. */ std::string FTPFileCallbacks::onDir() { - DIR* dir = opendir(FTPServer::getCurrentDirectory().c_str()); std::stringstream ss; - while(1) { + while (true) { struct dirent* pDirentry = readdir(dir); - if (pDirentry == nullptr) { - break; - } + if (pDirentry == nullptr) break; ss << pDirentry->d_name << "\r\n"; } closedir(dir); @@ -131,7 +128,7 @@ void FTPCallbacks::onRetrieveStart(std::string fileName) { } // FTPCallbacks#onRetrieveStart -size_t FTPCallbacks::onRetrieveData(uint8_t *data, size_t size) { +size_t FTPCallbacks::onRetrieveData(uint8_t* data, size_t size) { ESP_LOGD(LOG_TAG,">> FTPCallbacks::onRetrieveData"); ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onRetrieveData: 0"); return 0; diff --git a/cpp_utils/FTPServer.cpp b/cpp_utils/FTPServer.cpp index f8d006fd..63cf6a9c 100644 --- a/cpp_utils/FTPServer.cpp +++ b/cpp_utils/FTPServer.cpp @@ -22,24 +22,24 @@ static const char* LOG_TAG = "FTPServer"; // trim from start (in place) static void ltrim(std::string &s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { - return !std::isspace(ch); - })); + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); } // ltrim // trim from end (in place) static void rtrim(std::string &s) { - s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { - return !std::isspace(ch); - }).base(), s.end()); + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); } // rtrim // trim from both ends (in place) static void trim(std::string &s) { - ltrim(s); - rtrim(s); + ltrim(s); + rtrim(s); } // trim @@ -128,13 +128,13 @@ std::string FTPServer::listenPassive() { struct sockaddr_in clientAddrInfo; unsigned int addrInfoSize = sizeof(clientAddrInfo); - getsockname(m_clientSocket, (struct sockaddr*)&clientAddrInfo, &addrInfoSize); + getsockname(m_clientSocket, (struct sockaddr*) &clientAddrInfo, &addrInfoSize); struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); serverAddress.sin_port = htons(0); - int rc = bind(m_passiveSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + int rc = bind(m_passiveSocket, (struct sockaddr*) &serverAddress, sizeof(serverAddress)); if (rc == -1) { ESP_LOGD(LOG_TAG, "bind: %s", strerror(errno)); } @@ -150,10 +150,9 @@ std::string FTPServer::listenPassive() { ESP_LOGD(LOG_TAG, "getsockname: %s", strerror(errno)); } - std::stringstream ss; - ss << ((clientAddrInfo.sin_addr.s_addr >> 0) & 0xff) << - "," << ((clientAddrInfo.sin_addr.s_addr >> 8) & 0xff) << + ss << ((clientAddrInfo.sin_addr.s_addr >> 0) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 8) & 0xff) << "," << ((clientAddrInfo.sin_addr.s_addr >> 16) & 0xff) << "," << ((clientAddrInfo.sin_addr.s_addr >> 24) & 0xff) << "," << ((serverAddress.sin_port >> 0) & 0xff) << @@ -203,7 +202,7 @@ void FTPServer::onList(std::istringstream& ss) { sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. if (m_callbacks != nullptr) { std::string dirString = m_callbacks->onDir(); - sendData((uint8_t *)dirString.data(), dirString.length()); + sendData((uint8_t*) dirString.data(), dirString.length()); } closeData(); sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. @@ -211,7 +210,7 @@ void FTPServer::onList(std::istringstream& ss) { } // FTPServer#onList -void FTPServer::onMkd(std::istringstream &ss) { +void FTPServer::onMkd(std::istringstream& ss) { std::string path; ss >> path; ESP_LOGD(LOG_TAG, ">> onMkd: path=%s", path.c_str()); @@ -247,9 +246,9 @@ void FTPServer::onPort(std::istringstream& ss) { char c; uint16_t h1, h2, h3, h4, p1, p2; ss >> h1 >> c >> h2 >> c >> h3 >> c >> h4 >> c >> p1 >> c >> p2; - m_dataPort = p1*256 + p2; + m_dataPort = p1 * 256 + p2; ESP_LOGD(LOG_TAG, "%d.%d.%d.%d %d", h1, h2, h3, h4, m_dataPort); - m_dataIp = h1<<24 | h2<<16 | h3<<8 | h4; + m_dataIp = h1 << 24 | h2 << 16 | h3 << 8 | h4; sendResponse(RESPONSE_200_COMMAND_OK); // Command okay. m_isPassive = false; @@ -351,7 +350,6 @@ void FTPServer::onQuit(std::istringstream& ss) { * @param ss The parameter stream. */ void FTPServer::onRetr(std::istringstream& ss) { - // We open a data connection back to the client. We then invoke the callback to indicate that we have // started a retrieve operation. We call the retrieve callback to request the next chunk of data and // transmit this down the data connection. We repeat this until there is no more data to send at which @@ -362,12 +360,11 @@ void FTPServer::onRetr(std::istringstream& ss) { ss >> fileName; uint8_t data[m_chunkSize]; - if (m_callbacks != nullptr) { try { m_callbacks->onRetrieveStart(fileName); - } catch(FTPServer::FileException& e) { - sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. + } catch (FTPServer::FileException& e) { + sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. ESP_LOGD(LOG_TAG, "<< onRetr: Returned 550 to client."); return; } @@ -377,7 +374,7 @@ void FTPServer::onRetr(std::istringstream& ss) { openData(); if (m_callbacks != nullptr) { int readSize = m_callbacks->onRetrieveData(data, m_chunkSize); - while(readSize > 0) { + while (readSize > 0) { sendData(data, readSize); readSize = m_callbacks->onRetrieveData(data, m_chunkSize); } @@ -432,11 +429,7 @@ void FTPServer::onType(std::istringstream& ss) { ESP_LOGD(LOG_TAG, ">> onType"); std::string type; ss >> type; - if (type.compare("I") == 0) { - m_isImage = true; - } else { - m_isImage = false; - } + m_isImage = (type.compare("I") == 0); sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); // Command okay. ESP_LOGD(LOG_TAG, "<< onType: isImage=%d", m_isImage); } // FTPServer#onType @@ -455,15 +448,10 @@ void FTPServer::onType(std::istringstream& ss) { void FTPServer::onUser(std::istringstream& ss) { // When we receive a user command, we next want to know if we should ask for a password. If the m_loginRequired // flag is set then we do indeed want a password and will send the response that we wish one. - std::string userName; ss >> userName; ESP_LOGD(LOG_TAG, ">> onUser: userName=%s", userName.c_str()); - if (m_loginRequired) { - sendResponse(FTPServer::RESPONSE_331_PASSWORD_REQUIRED); - } else { - sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); // Command okay. - } + sendResponse(m_loginRequired ? FTPServer::RESPONSE_331_PASSWORD_REQUIRED : FTPServer::RESPONSE_200_COMMAND_OK); m_suppliedUserid = userName; // Save the username that was supplied. ESP_LOGD(LOG_TAG, "<< onUser"); } // FTPServer#onUser @@ -500,7 +488,7 @@ bool FTPServer::openData() { return false; } closePassive(); - } else { + } else { // Handle an active connection ... here we connect to the client. m_dataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); @@ -526,24 +514,20 @@ void FTPServer::processCommand() { sendResponse(FTPServer::RESPONSE_220_SERVICE_READY); // Service ready. ESP_LOGD(LOG_TAG, ">> FTPServer::processCommand"); m_lastCommand = ""; - while(1) { + while (true) { std::string line = ""; char currentChar; char lastChar = '\0'; int rc = recv(m_clientSocket, ¤tChar, 1, 0); - while(rc != -1 && rc!=0) { + while (rc != -1 && rc != 0) { line += currentChar; - if (lastChar == '\r' && currentChar == '\n') { - break; - } - //printf("%c\n", currentChar); + if (lastChar == '\r' && currentChar == '\n') break; +// printf("%c\n", currentChar); lastChar = currentChar; rc = recv(m_clientSocket, ¤tChar, 1, 0); } // End while we are waiting for a line. - if (rc == 0 || rc == -1) { // If we didn't get a line or an error, then we have finished processing commands. - break; - } + if (rc == 0 || rc == -1) break; // If we didn't get a line or an error, then we have finished processing commands. std::string command; std::istringstream ss(line); @@ -553,58 +537,58 @@ void FTPServer::processCommand() { // We now have a command to process. ESP_LOGD(LOG_TAG, "Command: \"%s\"", command.c_str()); - if (command.compare("USER")==0) { + if (command.compare("USER") == 0) { onUser(ss); } - else if (command.compare("PASS")==0) { + else if (command.compare("PASS") == 0) { onPass(ss); } else if (m_loginRequired && !m_isAuthenticated) { sendResponse(RESPONSE_530_NOT_LOGGED_IN); } - else if (command.compare("PASV")==0) { + else if (command.compare("PASV") == 0) { onPasv(ss); } - else if (command.compare("SYST")==0) { + else if (command.compare("SYST") == 0) { onSyst(ss); } - else if (command.compare("PORT")==0) { + else if (command.compare("PORT") == 0) { onPort(ss); } - else if (command.compare("LIST")==0) { + else if (command.compare("LIST") == 0) { onList(ss); } - else if (command.compare("TYPE")==0) { + else if (command.compare("TYPE") == 0) { onType(ss); } - else if (command.compare("RETR")==0) { + else if (command.compare("RETR") == 0) { onRetr(ss); } - else if (command.compare("QUIT")==0) { + else if (command.compare("QUIT") == 0) { onQuit(ss); } - else if (command.compare("AUTH")==0) { + else if (command.compare("AUTH") == 0) { onAuth(ss); } - else if (command.compare("STOR")==0) { + else if (command.compare("STOR") == 0) { onStor(ss); } - else if (command.compare("PWD")==0) { + else if (command.compare("PWD") == 0) { onPWD(ss); } - else if (command.compare("MKD")==0) { + else if (command.compare("MKD") == 0) { onMkd(ss); } - else if (command.compare("XMKD")==0) { + else if (command.compare("XMKD") == 0) { onXmkd(ss); } - else if (command.compare("RMD")==0) { + else if (command.compare("RMD") == 0) { onRmd(ss); } - else if (command.compare("XRMD")==0) { + else if (command.compare("XRMD") == 0) { onXrmd(ss); } - else if (command.compare("CWD")==0) { + else if (command.compare("CWD") == 0) { onCwd(ss); } else { @@ -637,11 +621,9 @@ void FTPServer::receiveFile(std::string fileName) { sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. uint8_t buf[m_chunkSize]; uint32_t totalSizeRead = 0; - while(1) { + while (true) { int rc = recv(m_dataSocket, &buf, m_chunkSize, 0); - if (rc <= 0) { - break; - } + if (rc <= 0) break; if (m_callbacks != nullptr) { m_callbacks->onRetrieveData(buf, rc); } @@ -789,7 +771,7 @@ void FTPServer::start() { if (rc == -1) { ESP_LOGD(LOG_TAG, "listen: %s", strerror(errno)); } - while(1) { + while (true) { waitForFTPClient(); processCommand(); } @@ -804,7 +786,7 @@ int FTPServer::waitForFTPClient() { struct sockaddr_in clientAddress; socklen_t clientAddressLength = sizeof(clientAddress); - m_clientSocket = accept(m_serverSocket, (struct sockaddr *)&clientAddress, &clientAddressLength); + m_clientSocket = accept(m_serverSocket, (struct sockaddr*) &clientAddress, &clientAddressLength); char ipAddr[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &clientAddress.sin_addr, ipAddr, sizeof(ipAddr)); @@ -812,7 +794,7 @@ int FTPServer::waitForFTPClient() { struct sockaddr_in socketAddressInfo; unsigned int socketAddressInfoSize = sizeof(socketAddressInfo); - getsockname(m_clientSocket, (struct sockaddr*)&socketAddressInfo, &socketAddressInfoSize); + getsockname(m_clientSocket, (struct sockaddr*) &socketAddressInfo, &socketAddressInfoSize); inet_ntop(AF_INET, &socketAddressInfo.sin_addr, ipAddr, sizeof(ipAddr)); ESP_LOGD(LOG_TAG, "Connected at %s [%d]", ipAddr, socketAddressInfo.sin_port); diff --git a/cpp_utils/FTPServer.h b/cpp_utils/FTPServer.h index 7dc56f13..5736201d 100644 --- a/cpp_utils/FTPServer.h +++ b/cpp_utils/FTPServer.h @@ -24,28 +24,63 @@ class FTPCallbacks { virtual void onRetrieveEnd(); virtual std::string onDir(); virtual ~FTPCallbacks(); + }; /** * An implementation of FTPCallbacks that uses Posix File I/O to perform file access. */ class FTPFileCallbacks : public FTPCallbacks { +public: + void onStoreStart(std::string fileName) override; // Called for a STOR request. + size_t onStoreData(uint8_t* data, size_t size) override; // Called when a chunk of STOR data becomes available. + void onStoreEnd() override; // Called at the end of a STOR request. + void onRetrieveStart(std::string fileName) override; // Called at the start of a RETR request. + size_t onRetrieveData(uint8_t* data, size_t size) override; // Called to retrieve a chunk of RETR data. + void onRetrieveEnd() override; // Called when we have retrieved all the data. + std::string onDir() override; // Called to retrieve all the directory entries. + private: - std::ofstream m_storeFile; // File used to store data from the client. + std::ofstream m_storeFile; // File used to store data from the client. std::ifstream m_retrieveFile; // File used to retrieve data for the client. - uint32_t m_byteCount; // Count of bytes sent over wire. -public: - void onStoreStart(std::string fileName) override; // Called for a STOR request. - size_t onStoreData(uint8_t* data, size_t size) override; // Called when a chunk of STOR data becomes available. - void onStoreEnd() override; // Called at the end of a STOR request. - void onRetrieveStart(std::string fileName) override; // Called at the start of a RETR request. - size_t onRetrieveData(uint8_t* data, size_t size) override; // Called to retrieve a chunk of RETR data. - void onRetrieveEnd() override; // Called when we have retrieved all the data. - std::string onDir() override; // Called to retrieve all the directory entries. + uint32_t m_byteCount; // Count of bytes sent over wire. + }; class FTPServer { +public: + FTPServer(); + virtual ~FTPServer(); + void setCredentials(std::string userid, std::string password); + void start(); + void setPort(uint16_t port); + void setCallbacks(FTPCallbacks* pFTPCallbacks); + static std::string getCurrentDirectory(); + class FileException: public std::exception { + }; + + // Response codes. + static const int RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION = 150; + static const int RESPONSE_200_COMMAND_OK = 200; + static const int RESPONSE_202_COMMAND_NOT_IMPLEMENTED = 202; + static const int RESPONSE_212_DIRECTORY_STATUS = 212; + static const int RESPONSE_213_FILE_STATUS = 213; + static const int RESPONSE_214_HELP_MESSAGE = 214; + static const int RESPONSE_220_SERVICE_READY = 220; + static const int RESPONSE_221_CLOSING_CONTROL_CONNECTION = 221; + static const int RESPONSE_230_USER_LOGGED_IN = 230; + static const int RESPONSE_226_CLOSING_DATA_CONNECTION = 226; + static const int RESPONSE_227_ENTERING_PASSIVE_MODE = 227; + static const int RESPONSE_331_PASSWORD_REQUIRED = 331; + static const int RESPONSE_332_NEED_ACCOUNT = 332; + static const int RESPONSE_500_COMMAND_UNRECOGNIZED = 500; + static const int RESPONSE_502_COMMAND_NOT_IMPLEMENTED = 502; + static const int RESPONSE_503_BAD_SEQUENCE = 503; + static const int RESPONSE_530_NOT_LOGGED_IN = 530; + static const int RESPONSE_550_ACTION_NOT_TAKEN = 550; + static const int RESPONSE_553_FILE_NAME_NOT_ALLOWED = 553; + private: int m_serverSocket; // The socket the FTP server is listening on. int m_clientSocket; // The current client socket. @@ -98,40 +133,6 @@ class FTPServer { int waitForFTPClient(); void processCommand(); -public: - FTPServer(); - virtual ~FTPServer(); - void setCredentials(std::string userid, std::string password); - void start(); - void setPort(uint16_t port); - void setCallbacks(FTPCallbacks* pFTPCallbacks); - static std::string getCurrentDirectory(); - class FileException: public std::exception { - - }; - - // Response codes. - static const int RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION = 150; - static const int RESPONSE_200_COMMAND_OK = 200; - static const int RESPONSE_202_COMMAND_NOT_IMPLEMENTED = 202; - static const int RESPONSE_212_DIRECTORY_STATUS = 212; - static const int RESPONSE_213_FILE_STATUS = 213; - static const int RESPONSE_214_HELP_MESSAGE = 214; - static const int RESPONSE_220_SERVICE_READY = 220; - static const int RESPONSE_221_CLOSING_CONTROL_CONNECTION = 221; - static const int RESPONSE_230_USER_LOGGED_IN = 230; - static const int RESPONSE_226_CLOSING_DATA_CONNECTION = 226; - static const int RESPONSE_227_ENTERING_PASSIVE_MODE = 227; - static const int RESPONSE_331_PASSWORD_REQUIRED = 331; - static const int RESPONSE_332_NEED_ACCOUNT = 332; - static const int RESPONSE_500_COMMAND_UNRECOGNIZED = 500; - static const int RESPONSE_502_COMMAND_NOT_IMPLEMENTED = 502; - static const int RESPONSE_503_BAD_SEQUENCE = 503; - static const int RESPONSE_530_NOT_LOGGED_IN = 530; - static const int RESPONSE_550_ACTION_NOT_TAKEN = 550; - static const int RESPONSE_553_FILE_NAME_NOT_ALLOWED = 553; }; - - #endif /* NETWORKING_FTPSERVER_FTPSERVER_H_ */ diff --git a/cpp_utils/File.cpp b/cpp_utils/File.cpp index 670936aa..3b245a85 100644 --- a/cpp_utils/File.cpp +++ b/cpp_utils/File.cpp @@ -33,15 +33,13 @@ File::File(std::string path, uint8_t type) { std::string File::getContent(bool base64Encode) { uint32_t size = length(); ESP_LOGD(LOG_TAG, "File:: getContent(), path=%s, length=%d", m_path.c_str(), size); - if (size == 0) { - return ""; - } - uint8_t *pData = (uint8_t *)malloc(size); + if (size == 0) return ""; + uint8_t* pData = (uint8_t*) malloc(size); if (pData == nullptr) { ESP_LOGE(LOG_TAG, "getContent: Failed to allocate memory"); return ""; } - FILE *file = fopen(m_path.c_str(), "r"); + FILE* file = fopen(m_path.c_str(), "r"); fread(pData, size, 1, file); fclose(file); std::string ret((char *)pData, size); @@ -65,19 +63,17 @@ std::string File::getContent(uint32_t offset, uint32_t readSize) { uint32_t fileSize = length(); ESP_LOGD(LOG_TAG, "File:: getContent(), name=%s, fileSize=%d, offset=%d, readSize=%d", m_path.c_str(), fileSize, offset, readSize); - if (fileSize == 0 || offset > fileSize) { - return ""; - } - uint8_t *pData = (uint8_t *)malloc(readSize); + if (fileSize == 0 || offset > fileSize) return ""; + uint8_t* pData = (uint8_t*) malloc(readSize); if (pData == nullptr) { ESP_LOGE(LOG_TAG, "getContent: Failed to allocate memory"); return ""; } - FILE *file = fopen(m_path.c_str(), "r"); + FILE* file = fopen(m_path.c_str(), "r"); fseek(file, offset, SEEK_SET); size_t bytesRead = fread(pData, 1, readSize, file); fclose(file); - std::string ret((char *)pData, bytesRead); + std::string ret((char*) pData, bytesRead); free(pData); return ret; } // getContent @@ -92,10 +88,8 @@ std::string File::getPath() { */ std::string File::getName() { size_t pos = m_path.find_last_of('/'); - if (pos == std::string::npos) { - return m_path; - } - return m_path.substr(pos+1); + if (pos == std::string::npos) return m_path; + return m_path.substr(pos + 1); } // getName @@ -116,10 +110,8 @@ uint8_t File::getType() { uint32_t File::length() { struct stat buf; int rc = stat(m_path.c_str(), &buf); - if (rc != 0 || S_ISDIR(buf.st_mode)) { - return 0; - } - return buf.st_size; + if (rc != 0 || S_ISDIR(buf.st_mode)) return 0; + return (uint32_t) buf.st_size; } // length @@ -130,8 +122,6 @@ uint32_t File::length() { bool File::isDirectory() { struct stat buf; int rc = stat(m_path.c_str(), &buf); - if (rc != 0) { - return false; - } + if (rc != 0) return false; return S_ISDIR(buf.st_mode); } // isDirectory diff --git a/cpp_utils/File.h b/cpp_utils/File.h index 19ef7c34..c1c18014 100644 --- a/cpp_utils/File.h +++ b/cpp_utils/File.h @@ -17,7 +17,7 @@ class File { public: File(std::string name, uint8_t type = DT_UNKNOWN); - std::string getContent(bool base64Encode=false); + std::string getContent(bool base64Encode = false); std::string getContent(uint32_t offset, uint32_t size); std::string getName(); std::string getPath(); diff --git a/cpp_utils/FileSystem.cpp b/cpp_utils/FileSystem.cpp index 564c68b4..1920235c 100644 --- a/cpp_utils/FileSystem.cpp +++ b/cpp_utils/FileSystem.cpp @@ -26,25 +26,28 @@ static const char* LOG_TAG = "FileSystem"; * @return N/A. */ void FileSystem::dumpDirectory(std::string path) { - DIR *pDir = ::opendir(path.c_str()); + DIR* pDir = ::opendir(path.c_str()); if (pDir == nullptr) { ESP_LOGD(LOG_TAG, "Unable to open directory: %s [errno=%d]", path.c_str(), errno); return; } - struct dirent *pDirent; + struct dirent* pDirent; ESP_LOGD(LOG_TAG, "Directory dump of %s", path.c_str()); - while((pDirent = readdir(pDir)) != nullptr) { + while ((pDirent = readdir(pDir)) != nullptr) { std::string type; - switch(pDirent->d_type) { - case DT_UNKNOWN: - type = "Unknown"; - break; - case DT_REG: - type = "Regular"; - break; - case DT_DIR: - type = "Directory"; - break; + switch (pDirent->d_type) { + case DT_UNKNOWN: + type = "Unknown"; + break; + case DT_REG: + type = "Regular"; + break; + case DT_DIR: + type = "Directory"; + break; + default: + type = "Unknown"; + break; } ESP_LOGD(LOG_TAG, "Entry: d_ino: %d, d_name: %s, d_type: %s", pDirent->d_ino, pDirent->d_name, type.c_str()); } @@ -59,14 +62,14 @@ void FileSystem::dumpDirectory(std::string path) { */ std::vector FileSystem::getDirectoryContents(std::string path) { std::vector ret; - DIR *pDir = ::opendir(path.c_str()); + DIR* pDir = ::opendir(path.c_str()); if (pDir == nullptr) { ESP_LOGE(LOG_TAG, "getDirectoryContents:: Unable to open directory: %s [errno=%d]", path.c_str(), errno); return ret; } - struct dirent *pDirent; + struct dirent* pDirent; ESP_LOGD(LOG_TAG, "Directory dump of %s", path.c_str()); - while((pDirent = readdir(pDir)) != nullptr) { + while ((pDirent = readdir(pDir)) != nullptr) { File file(path +"/" + std::string(pDirent->d_name), pDirent->d_type); ret.push_back(file); } @@ -82,9 +85,7 @@ std::vector FileSystem::getDirectoryContents(std::string path) { bool FileSystem::isDirectory(std::string path) { struct stat statBuf; int rc = stat(path.c_str(), &statBuf); - if (rc != 0) { - return false; - } + if (rc != 0) return false; return S_ISDIR(statBuf.st_mode); } // isDirectory @@ -129,11 +130,11 @@ std::vector FileSystem::pathSplit(std::string path) { std::istringstream stream(path); std::vector ret; std::string pathPart; - while(std::getline(stream, pathPart, '/')) { + while (std::getline(stream, pathPart, '/')) { ret.push_back(pathPart); } // Debug - for (int i=0; i> wait: Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); - if (m_usePthreads) { pthread_mutex_lock(&m_pthread_mutex); } else { @@ -134,7 +119,7 @@ void FreeRTOS::Semaphore::give() { xSemaphoreGive(m_semaphore); } // #ifdef ARDUINO_ARCH_ESP32 -// FreeRTOS::sleep(10); +// FreeRTOS::sleep(10); // #endif m_owner = std::string(""); @@ -171,8 +156,7 @@ void FreeRTOS::Semaphore::giveFromISR() { * @param [in] owner The new owner (for debugging) * @return True if we took the semaphore. */ -bool FreeRTOS::Semaphore::take(std::string owner) -{ +bool FreeRTOS::Semaphore::take(std::string owner) { ESP_LOGD(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); bool rc = false; if (m_usePthreads) { @@ -198,13 +182,12 @@ bool FreeRTOS::Semaphore::take(std::string owner) * @return True if we took the semaphore. */ bool FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { - ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); bool rc = false; if (m_usePthreads) { assert(false); // We apparently don't have a timed wait for pthreads. } else { - rc = ::xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); + rc = ::xSemaphoreTake(m_semaphore, timeoutMs / portTICK_PERIOD_MS); } m_owner = owner; if (rc) { diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index ab0e83d8..224ed667 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -23,7 +23,7 @@ class FreeRTOS { public: static void sleep(uint32_t ms); - static void startTask(void task(void *), std::string taskName, void *param=nullptr, int stackSize = 2048); + static void startTask(void task(void*), std::string taskName, void* param = nullptr, int stackSize = 2048); static void deleteTask(TaskHandle_t pTask = nullptr); static uint32_t getTimeSinceStart(); @@ -36,10 +36,10 @@ class FreeRTOS { void give(uint32_t value); void giveFromISR(); void setName(std::string name); - bool take(std::string owner=""); - bool take(uint32_t timeoutMs, std::string owner=""); + bool take(std::string owner = ""); + bool take(uint32_t timeoutMs, std::string owner = ""); std::string toString(); - uint32_t wait(std::string owner=""); + uint32_t wait(std::string owner = ""); private: SemaphoreHandle_t m_semaphore; @@ -48,6 +48,7 @@ class FreeRTOS { std::string m_owner; uint32_t m_value; bool m_usePthreads; + }; }; diff --git a/cpp_utils/FreeRTOSTimer.cpp b/cpp_utils/FreeRTOSTimer.cpp index 4baec3e2..4b096000 100644 --- a/cpp_utils/FreeRTOSTimer.cpp +++ b/cpp_utils/FreeRTOSTimer.cpp @@ -11,10 +11,10 @@ #include "FreeRTOSTimer.h" -static std::map timersMap; +static std::map timersMap; void FreeRTOSTimer::internalCallback(TimerHandle_t xTimer) { - FreeRTOSTimer *timer = timersMap.at(xTimer); + FreeRTOSTimer* timer = timersMap.at(xTimer); timer->callback(timer); } @@ -33,7 +33,7 @@ void FreeRTOSTimer::internalCallback(TimerHandle_t xTimer) { * * @code{.cpp} * void callback(FreeRTOSTimer *pTimer) { - * // Callback code here ... + * // Callback code here ... * } * @endcode * @@ -43,12 +43,7 @@ void FreeRTOSTimer::internalCallback(TimerHandle_t xTimer) { * @param [in] data Data to be passed to the callback. * @param [in] callback Callback function to be fired when the timer expires. */ -FreeRTOSTimer::FreeRTOSTimer( - char *name, - TickType_t period, - UBaseType_t reload, - void *data, - void (*callback)(FreeRTOSTimer *pTimer)) { +FreeRTOSTimer::FreeRTOSTimer(char* name, TickType_t period, UBaseType_t reload, void* data, void (*callback)(FreeRTOSTimer* pTimer)) { /* * The callback function to actually be called is saved as member data in the object and * a static callback function is called. This will be passed the FreeRTOS timer handle @@ -124,7 +119,7 @@ void FreeRTOSTimer::changePeriod(TickType_t newPeriod, TickType_t blockTime) { * * @return The name of the timer. */ -const char *FreeRTOSTimer::getName() { +const char* FreeRTOSTimer::getName() { return ::pcTimerGetTimerName(timerHandle); } // getName @@ -134,6 +129,6 @@ const char *FreeRTOSTimer::getName() { * * @return The user supplied data associated with the timer. */ -void *FreeRTOSTimer::getData() { +void* FreeRTOSTimer::getData() { return ::pvTimerGetTimerID(timerHandle); } // getData diff --git a/cpp_utils/FreeRTOSTimer.h b/cpp_utils/FreeRTOSTimer.h index 8556ea3f..5c71a0e7 100644 --- a/cpp_utils/FreeRTOSTimer.h +++ b/cpp_utils/FreeRTOSTimer.h @@ -15,21 +15,22 @@ */ class FreeRTOSTimer { public: - FreeRTOSTimer(char *name, TickType_t period, UBaseType_t reload, void *data, void (*callback)(FreeRTOSTimer *pTimer)); + FreeRTOSTimer(char* name, TickType_t period, UBaseType_t reload, void* data, void (*callback)(FreeRTOSTimer* pTimer)); virtual ~FreeRTOSTimer(); - void changePeriod(TickType_t newPeriod, TickType_t blockTime=portMAX_DELAY); - void *getData(); - const char *getName(); + void changePeriod(TickType_t newPeriod, TickType_t blockTime = portMAX_DELAY); + void* getData(); + const char* getName(); TickType_t getPeriod(); - void reset(TickType_t blockTime=portMAX_DELAY); - void start(TickType_t blockTime=portMAX_DELAY); - void stop(TickType_t blockTime=portMAX_DELAY); + void reset(TickType_t blockTime = portMAX_DELAY); + void start(TickType_t blockTime = portMAX_DELAY); + void stop(TickType_t blockTime = portMAX_DELAY); private: TimerHandle_t timerHandle; TickType_t period; - void (*callback)(FreeRTOSTimer *pTimer); + void (*callback)(FreeRTOSTimer* pTimer); static void internalCallback(TimerHandle_t xTimer); + }; #endif /* COMPONENTS_CPP_UTILS_FREERTOSTIMER_H_ */ diff --git a/cpp_utils/GPIO.cpp b/cpp_utils/GPIO.cpp index 23cf6fb7..bc3409cd 100644 --- a/cpp_utils/GPIO.cpp +++ b/cpp_utils/GPIO.cpp @@ -22,15 +22,11 @@ static bool g_isrServiceInstalled = false; * @param [in] handler The function to be invoked when the interrupt is detected. * @param [in] pArgs Optional arguments to pass to the handler. */ -void ESP32CPP::GPIO::addISRHandler( - gpio_num_t pin, - gpio_isr_t handler, - void* pArgs) { - +void ESP32CPP::GPIO::addISRHandler(gpio_num_t pin, gpio_isr_t handler, void* pArgs) { ESP_LOGD(LOG_TAG, ">> addISRHandler: pin=%d", pin); // If we have not yet installed the ISR service handler, install it now. - if (g_isrServiceInstalled == false) { + if (!g_isrServiceInstalled) { ESP_LOGD(LOG_TAG, "Installing the global ISR service"); esp_err_t errRc = ::gpio_install_isr_service(0); if (errRc != ESP_OK) { @@ -70,10 +66,7 @@ void ESP32CPP::GPIO::high(gpio_num_t pin) { * @return The value of true if the pin is valid and false otherwise. */ bool ESP32CPP::GPIO::inRange(gpio_num_t pin) { - if (pin>=0 && pin<=39) { - return true; - } - return false; + return (pin >= 0 && pin <= 39); } // inRange @@ -155,14 +148,11 @@ void ESP32CPP::GPIO::setInput(gpio_num_t pin) { * @param [in] intrType The type of interrupt. * @return N/A. */ -void ESP32CPP::GPIO::setInterruptType( - gpio_num_t pin, - gpio_int_type_t intrType) { +void ESP32CPP::GPIO::setInterruptType(gpio_num_t pin, gpio_int_type_t intrType) { esp_err_t rc = ::gpio_set_intr_type(pin, intrType); if (rc != ESP_OK) { ESP_LOGE(LOG_TAG, "setInterruptType: %d", rc); } - } // setInterruptType @@ -204,9 +194,9 @@ void ESP32CPP::GPIO::write(gpio_num_t pin, bool value) { */ void ESP32CPP::GPIO::writeByte(gpio_num_t pins[], uint8_t value, int bits) { ESP_LOGD(LOG_TAG, ">> writeByte: value: %.2x, bits: %d", value, bits); - for (int i=0; i -namespace ESP32CPP -{ +namespace ESP32CPP { /** * @brief Interface to %GPIO functions. * diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 2c0f0846..80c5cc6e 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -23,20 +23,20 @@ static const char* LOG_TAG = "GeneralUtils"; static const char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; static int base64EncodedLength(size_t length) { return (length + 2 - ((length + 2) % 3)) / 3 * 4; } // base64EncodedLength -static int base64EncodedLength(const std::string &in) { +static int base64EncodedLength(const std::string& in) { return base64EncodedLength(in.length()); } // base64EncodedLength -static void a3_to_a4(unsigned char * a4, unsigned char * a3) { +static void a3_to_a4(unsigned char* a4, unsigned char* a3) { a4[0] = (a3[0] & 0xfc) >> 2; a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); @@ -44,7 +44,7 @@ static void a3_to_a4(unsigned char * a4, unsigned char * a3) { } // a3_to_a4 -static void a4_to_a3(unsigned char * a3, unsigned char * a4) { +static void a4_to_a3(unsigned char* a3, unsigned char* a4) { a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; @@ -56,7 +56,7 @@ static void a4_to_a3(unsigned char * a3, unsigned char * a4) { * @param [in] in * @param [out] out */ -bool GeneralUtils::base64Encode(const std::string &in, std::string *out) { +bool GeneralUtils::base64Encode(const std::string& in, std::string* out) { int i = 0, j = 0; size_t enc_len = 0; unsigned char a3[3]; @@ -127,16 +127,16 @@ bool GeneralUtils::endsWith(std::string str, char c) { if (str.empty()) { return false; } - if (str.at(str.length()-1) == c) { + if (str.at(str.length() - 1) == c) { return true; } return false; } // endsWidth -static int DecodedLength(const std::string &in) { +static int DecodedLength(const std::string& in) { int numEq = 0; - int n = in.size(); + int n = (int) in.size(); for (std::string::const_reverse_iterator it = in.rbegin(); *it == '='; ++it) { ++numEq; @@ -160,7 +160,7 @@ static unsigned char b64_lookup(unsigned char c) { * @param [in] in The string to be decoded. * @param [out] out The resulting data. */ -bool GeneralUtils::base64Decode(const std::string &in, std::string *out) { +bool GeneralUtils::base64Decode(const std::string& in, std::string* out) { int i = 0, j = 0; size_t dec_len = 0; unsigned char a3[3]; @@ -300,8 +300,8 @@ void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { ESP_LOGV(LOG_TAG, " -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); strcpy(ascii, ""); strcpy(hex, ""); - uint32_t index=0; - while(index < length) { + uint32_t index = 0; + while (index < length) { sprintf(tempBuf, "%.2x ", pData[index]); strcat(hex, tempBuf); if (isprint(pData[index])) { @@ -312,18 +312,18 @@ void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { strcat(ascii, tempBuf); index++; if (index % 16 == 0) { - ESP_LOGV(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); + ESP_LOGV(LOG_TAG, "%.4x %s %s", lineNumber * 16, hex, ascii); strcpy(ascii, ""); strcpy(hex, ""); lineNumber++; } } if (index %16 != 0) { - while(index % 16 != 0) { + while (index % 16 != 0) { strcat(hex, " "); index++; } - ESP_LOGV(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); + ESP_LOGV(LOG_TAG, "%.4x %s %s", lineNumber * 16, hex, ascii); } } // hexDump @@ -335,7 +335,7 @@ void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { */ std::string GeneralUtils::ipToString(uint8_t *ip) { std::stringstream s; - s << (int)ip[0] << '.' << (int)ip[1] << '.' << (int)ip[2] << '.' << (int)ip[3]; + s << (int) ip[0] << '.' << (int) ip[1] << '.' << (int) ip[2] << '.' << (int) ip[3]; return s.str(); } // ipToString @@ -351,7 +351,7 @@ std::vector GeneralUtils::split(std::string source, char delimiter) std::vector strings; std::istringstream iss(source); std::string s; - while(std::getline(iss, s, delimiter)) { + while (std::getline(iss, s, delimiter)) { strings.push_back(trim(s)); } return strings; @@ -364,7 +364,7 @@ std::vector GeneralUtils::split(std::string source, char delimiter) * @return A string representation of the error code. */ const char* GeneralUtils::errorToString(esp_err_t errCode) { - switch(errCode) { + switch (errCode) { case ESP_OK: return "ESP_OK"; case ESP_FAIL: @@ -431,8 +431,9 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { return "ESP_ERR_WIFI_TIMEOUT"; case ESP_ERR_WIFI_WAKE_FAIL: return "ESP_ERR_WIFI_WAKE_FAIL"; + default: + return "Unknown ESP_ERR error"; } - return "Unknown ESP_ERR error"; } // errorToString /** @@ -443,75 +444,72 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { * @note: wifi_err_reason_t values as of April 2018 are: (1-24, 200-204) and are defined in ~/esp-idf/components/esp32/include/esp_wifi_types.h. */ const char* GeneralUtils::wifiErrorToString(uint8_t errCode) { - if (errCode == ESP_OK) - return "ESP_OK (received SYSTEM_EVENT_STA_GOT_IP event)"; - if (errCode == UINT8_MAX) - return "Not Connected (default value)"; - - switch((wifi_err_reason_t) errCode) { - case WIFI_REASON_UNSPECIFIED: - return "WIFI_REASON_UNSPECIFIED"; - case WIFI_REASON_AUTH_EXPIRE: - return "WIFI_REASON_AUTH_EXPIRE"; - case WIFI_REASON_AUTH_LEAVE: - return "WIFI_REASON_AUTH_LEAVE"; - case WIFI_REASON_ASSOC_EXPIRE: - return "WIFI_REASON_ASSOC_EXPIRE"; - case WIFI_REASON_ASSOC_TOOMANY: - return "WIFI_REASON_ASSOC_TOOMANY"; - case WIFI_REASON_NOT_AUTHED: - return "WIFI_REASON_NOT_AUTHED"; - case WIFI_REASON_NOT_ASSOCED: - return "WIFI_REASON_NOT_ASSOCED"; - case WIFI_REASON_ASSOC_LEAVE: - return "WIFI_REASON_ASSOC_LEAVE"; - case WIFI_REASON_ASSOC_NOT_AUTHED: - return "WIFI_REASON_ASSOC_NOT_AUTHED"; - case WIFI_REASON_DISASSOC_PWRCAP_BAD: - return "WIFI_REASON_DISASSOC_PWRCAP_BAD"; - case WIFI_REASON_DISASSOC_SUPCHAN_BAD: - return "WIFI_REASON_DISASSOC_SUPCHAN_BAD"; - case WIFI_REASON_IE_INVALID: - return "WIFI_REASON_IE_INVALID"; - case WIFI_REASON_MIC_FAILURE: - return "WIFI_REASON_MIC_FAILURE"; - case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: - return "WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT"; - case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: - return "WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT"; - case WIFI_REASON_IE_IN_4WAY_DIFFERS: - return "WIFI_REASON_IE_IN_4WAY_DIFFERS"; - case WIFI_REASON_GROUP_CIPHER_INVALID: - return "WIFI_REASON_GROUP_CIPHER_INVALID"; - case WIFI_REASON_PAIRWISE_CIPHER_INVALID: - return "WIFI_REASON_PAIRWISE_CIPHER_INVALID"; - case WIFI_REASON_AKMP_INVALID: - return "WIFI_REASON_AKMP_INVALID"; - case WIFI_REASON_UNSUPP_RSN_IE_VERSION: - return "WIFI_REASON_UNSUPP_RSN_IE_VERSION"; - case WIFI_REASON_INVALID_RSN_IE_CAP: - return "WIFI_REASON_INVALID_RSN_IE_CAP"; - case WIFI_REASON_802_1X_AUTH_FAILED: - return "WIFI_REASON_802_1X_AUTH_FAILED"; - case WIFI_REASON_CIPHER_SUITE_REJECTED: - return "WIFI_REASON_CIPHER_SUITE_REJECTED"; - - case WIFI_REASON_BEACON_TIMEOUT: - return "WIFI_REASON_BEACON_TIMEOUT"; - case WIFI_REASON_NO_AP_FOUND: - return "WIFI_REASON_NO_AP_FOUND"; - case WIFI_REASON_AUTH_FAIL: - return "WIFI_REASON_AUTH_FAIL"; - case WIFI_REASON_ASSOC_FAIL: - return "WIFI_REASON_ASSOC_FAIL"; - case WIFI_REASON_HANDSHAKE_TIMEOUT: - return "WIFI_REASON_HANDSHAKE_TIMEOUT"; + if (errCode == ESP_OK) return "ESP_OK (received SYSTEM_EVENT_STA_GOT_IP event)"; + if (errCode == UINT8_MAX) return "Not Connected (default value)"; + + switch ((wifi_err_reason_t) errCode) { + case WIFI_REASON_UNSPECIFIED: + return "WIFI_REASON_UNSPECIFIED"; + case WIFI_REASON_AUTH_EXPIRE: + return "WIFI_REASON_AUTH_EXPIRE"; + case WIFI_REASON_AUTH_LEAVE: + return "WIFI_REASON_AUTH_LEAVE"; + case WIFI_REASON_ASSOC_EXPIRE: + return "WIFI_REASON_ASSOC_EXPIRE"; + case WIFI_REASON_ASSOC_TOOMANY: + return "WIFI_REASON_ASSOC_TOOMANY"; + case WIFI_REASON_NOT_AUTHED: + return "WIFI_REASON_NOT_AUTHED"; + case WIFI_REASON_NOT_ASSOCED: + return "WIFI_REASON_NOT_ASSOCED"; + case WIFI_REASON_ASSOC_LEAVE: + return "WIFI_REASON_ASSOC_LEAVE"; + case WIFI_REASON_ASSOC_NOT_AUTHED: + return "WIFI_REASON_ASSOC_NOT_AUTHED"; + case WIFI_REASON_DISASSOC_PWRCAP_BAD: + return "WIFI_REASON_DISASSOC_PWRCAP_BAD"; + case WIFI_REASON_DISASSOC_SUPCHAN_BAD: + return "WIFI_REASON_DISASSOC_SUPCHAN_BAD"; + case WIFI_REASON_IE_INVALID: + return "WIFI_REASON_IE_INVALID"; + case WIFI_REASON_MIC_FAILURE: + return "WIFI_REASON_MIC_FAILURE"; + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + return "WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT"; + case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: + return "WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT"; + case WIFI_REASON_IE_IN_4WAY_DIFFERS: + return "WIFI_REASON_IE_IN_4WAY_DIFFERS"; + case WIFI_REASON_GROUP_CIPHER_INVALID: + return "WIFI_REASON_GROUP_CIPHER_INVALID"; + case WIFI_REASON_PAIRWISE_CIPHER_INVALID: + return "WIFI_REASON_PAIRWISE_CIPHER_INVALID"; + case WIFI_REASON_AKMP_INVALID: + return "WIFI_REASON_AKMP_INVALID"; + case WIFI_REASON_UNSUPP_RSN_IE_VERSION: + return "WIFI_REASON_UNSUPP_RSN_IE_VERSION"; + case WIFI_REASON_INVALID_RSN_IE_CAP: + return "WIFI_REASON_INVALID_RSN_IE_CAP"; + case WIFI_REASON_802_1X_AUTH_FAILED: + return "WIFI_REASON_802_1X_AUTH_FAILED"; + case WIFI_REASON_CIPHER_SUITE_REJECTED: + return "WIFI_REASON_CIPHER_SUITE_REJECTED"; + case WIFI_REASON_BEACON_TIMEOUT: + return "WIFI_REASON_BEACON_TIMEOUT"; + case WIFI_REASON_NO_AP_FOUND: + return "WIFI_REASON_NO_AP_FOUND"; + case WIFI_REASON_AUTH_FAIL: + return "WIFI_REASON_AUTH_FAIL"; + case WIFI_REASON_ASSOC_FAIL: + return "WIFI_REASON_ASSOC_FAIL"; + case WIFI_REASON_HANDSHAKE_TIMEOUT: + return "WIFI_REASON_HANDSHAKE_TIMEOUT"; + default: + return "Unknown ESP_ERR error"; } - return "Unknown ESP_ERR error"; } // wifiErrorToString - /** * @brief Convert a string to lower case. * @param [in] value The string to convert to lower case. @@ -528,15 +526,9 @@ std::string GeneralUtils::toLower(std::string& value) { /** * @brief Remove white space from a string. */ -std::string GeneralUtils::trim(const std::string& str) -{ - size_t first = str.find_first_not_of(' '); - if (std::string::npos == first) - { - return str; - } - size_t last = str.find_last_not_of(' '); - return str.substr(first, (last - first + 1)); +std::string GeneralUtils::trim(const std::string& str) { + size_t first = str.find_first_not_of(' '); + if (std::string::npos == first) return str; + size_t last = str.find_last_not_of(' '); + return str.substr(first, (last - first + 1)); } // trim - - diff --git a/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index 3706040e..8eecbd4d 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -23,7 +23,7 @@ class GeneralUtils { static void dumpInfo(); static bool endsWith(std::string str, char c); static const char* errorToString(esp_err_t errCode); - static const char* wifiErrorToString(uint8_t value); + static const char* wifiErrorToString(uint8_t value); static void hexDump(const uint8_t* pData, uint32_t length); static std::string ipToString(uint8_t* ip); static std::vector split(std::string source, char delimiter); diff --git a/cpp_utils/HIDKeyboardTypes.h b/cpp_utils/HIDKeyboardTypes.h index ef48a526..4e221d57 100644 --- a/cpp_utils/HIDKeyboardTypes.h +++ b/cpp_utils/HIDKeyboardTypes.h @@ -26,9 +26,9 @@ /* Modifiers */ enum MODIFIER_KEY { - KEY_CTRL = 1, - KEY_SHIFT = 2, - KEY_ALT = 4, + KEY_CTRL = 1, + KEY_SHIFT = 2, + KEY_ALT = 4, }; @@ -43,37 +43,37 @@ enum MEDIA_KEY { }; enum FUNCTION_KEY { - KEY_F1 = 128, /* F1 key */ - KEY_F2, /* F2 key */ - KEY_F3, /* F3 key */ - KEY_F4, /* F4 key */ - KEY_F5, /* F5 key */ - KEY_F6, /* F6 key */ - KEY_F7, /* F7 key */ - KEY_F8, /* F8 key */ - KEY_F9, /* F9 key */ - KEY_F10, /* F10 key */ - KEY_F11, /* F11 key */ - KEY_F12, /* F12 key */ + KEY_F1 = 128, /* F1 key */ + KEY_F2, /* F2 key */ + KEY_F3, /* F3 key */ + KEY_F4, /* F4 key */ + KEY_F5, /* F5 key */ + KEY_F6, /* F6 key */ + KEY_F7, /* F7 key */ + KEY_F8, /* F8 key */ + KEY_F9, /* F9 key */ + KEY_F10, /* F10 key */ + KEY_F11, /* F11 key */ + KEY_F12, /* F12 key */ - KEY_PRINT_SCREEN, /* Print Screen key */ - KEY_SCROLL_LOCK, /* Scroll lock */ - KEY_CAPS_LOCK, /* caps lock */ - KEY_NUM_LOCK, /* num lock */ - KEY_INSERT, /* Insert key */ - KEY_HOME, /* Home key */ - KEY_PAGE_UP, /* Page Up key */ - KEY_PAGE_DOWN, /* Page Down key */ + KEY_PRINT_SCREEN, /* Print Screen key */ + KEY_SCROLL_LOCK, /* Scroll lock */ + KEY_CAPS_LOCK, /* caps lock */ + KEY_NUM_LOCK, /* num lock */ + KEY_INSERT, /* Insert key */ + KEY_HOME, /* Home key */ + KEY_PAGE_UP, /* Page Up key */ + KEY_PAGE_DOWN, /* Page Down key */ - RIGHT_ARROW, /* Right arrow */ - LEFT_ARROW, /* Left arrow */ - DOWN_ARROW, /* Down arrow */ - UP_ARROW, /* Up arrow */ + RIGHT_ARROW, /* Right arrow */ + LEFT_ARROW, /* Left arrow */ + DOWN_ARROW, /* Down arrow */ + UP_ARROW, /* Up arrow */ }; typedef struct { - unsigned char usage; - unsigned char modifier; + unsigned char usage; + unsigned char modifier; } KEYMAP; #ifdef US_KEYBOARD diff --git a/cpp_utils/HIDTypes.h b/cpp_utils/HIDTypes.h index 726b84be..64850ef8 100644 --- a/cpp_utils/HIDTypes.h +++ b/cpp_utils/HIDTypes.h @@ -89,8 +89,8 @@ #define MAX_HID_REPORT_SIZE (64) typedef struct { - uint32_t length; - uint8_t data[MAX_HID_REPORT_SIZE]; + uint32_t length; + uint8_t data[MAX_HID_REPORT_SIZE]; } HID_REPORT; #endif diff --git a/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp index d9040659..a6bbbc34 100644 --- a/cpp_utils/HttpParser.cpp +++ b/cpp_utils/HttpParser.cpp @@ -42,11 +42,11 @@ static std::string lineTerminator = "\r\n"; * @param [in] str The string we are parsing. * @param [in] token The token delimiter. */ -static std::string toStringToken(std::string::iterator &it, std::string &str, std::string &token) { +static std::string toStringToken(std::string::iterator& it, std::string& str, std::string& token) { std::string ret; std::string part; auto itToken = token.begin(); - for(; it != str.end(); ++it) { + for (; it != str.end(); ++it) { if ((*it) == (*itToken)) { part += (*itToken); ++itToken; @@ -74,7 +74,7 @@ static std::string toStringToken(std::string::iterator &it, std::string &str, st * @param [in] token The token terminating the parse. * @return The parsed string token. */ -static std::string toCharToken(std::string::iterator &it, std::string &str, char token) { +static std::string toCharToken(std::string::iterator& it, std::string& str, char token) { std::string ret; for(; it != str.end(); ++it) { if ((*it) == token) { @@ -97,9 +97,9 @@ static std::string toCharToken(std::string::iterator &it, std::string &str, char * @param [in] line The line of text to parse. * @return A pair of the form name/value. */ -std::pair parseHeader(std::string &line) { - auto it = line.begin(); - std::string name = toCharToken(it, line, ':'); // Parse the line until we find a ':' +std::pair parseHeader(std::string& line) { + auto it = line.begin(); + std::string name = toCharToken(it, line, ':'); // Parse the line until we find a ':' // We normalize the header name to be lower case. GeneralUtils::toLower(name); auto value = GeneralUtils::trim(toStringToken(it, line, lineTerminator)); @@ -142,9 +142,7 @@ std::string HttpParser::getHeader(const std::string& name) { // We normalize the header name to be lower case. std::string localName = name; GeneralUtils::toLower(localName); - if (!hasHeader(localName)) { - return ""; - } + if (!hasHeader(localName)) return ""; return m_headers.at(localName); } // getHeader @@ -169,11 +167,11 @@ std::string HttpParser::getVersion() { } // getVersion std::string HttpParser::getStatus() { - return m_status; + return m_status; } // getStatus std::string HttpParser::getReason() { - return m_reason; + return m_reason; } // getReason /** @@ -198,7 +196,7 @@ void HttpParser::parse(Socket s) { line = s.readToDelim(lineTerminator); parseRequestLine(line); line = s.readToDelim(lineTerminator); - while(!line.empty()) { + while (!line.empty()) { m_headers.insert(parseHeader(line)); line = s.readToDelim(lineTerminator); } @@ -216,13 +214,13 @@ void HttpParser::parse(Socket s) { int length = std::atoi(val.c_str()); uint8_t data[length]; s.receive(data, length, true); - m_body = std::string((char *)data, length); + m_body = std::string((char*) data, length); } else { uint8_t data[512]; int rc = s.receive(data, sizeof(data)); - if (rc > 0) { - m_body = std::string((char *)data, rc); - } + if (rc > 0) { + m_body = std::string((char*) data, rc); + } } ESP_LOGD(LOG_TAG, "<< parse: Size of body: %d", m_body.length()); } // parse @@ -253,10 +251,9 @@ void HttpParser::parse(std::string message) { * @brief Parse A request line. * @param [in] line The request line to parse. */ -// A request Line is built from: -// -// -void HttpParser::parseRequestLine(std::string &line) { +void HttpParser::parseRequestLine(std::string& line) { + // A request Line is built from: + // ESP_LOGD(LOG_TAG, ">> parseRequestLine: \"%s\" [%d]", line.c_str(), line.length()); std::string::iterator it = line.begin(); @@ -275,17 +272,15 @@ void HttpParser::parseRequestLine(std::string &line) { * @brief Parse a response message. * @param [in] line The response to parse. */ -// A response is built from: -// A status line, any number of header lines, a body -// -void HttpParser::parseResponse(std::string message) -{ +void HttpParser::parseResponse(std::string message) { + // A response is built from: + // A status line, any number of header lines, a body auto it = message.begin(); auto line = toStringToken(it, message, lineTerminator); parseStatusLine(line); line = toStringToken(it, message, lineTerminator); - while(!line.empty()) { + while (!line.empty()) { ESP_LOGD(LOG_TAG, "Header: \"%s\"", line.c_str()); m_headers.insert(parseHeader(line)); line = toStringToken(it, message, lineTerminator); @@ -298,11 +293,9 @@ void HttpParser::parseResponse(std::string message) * @brief Parse A status line. * @param [in] line The status line to parse. */ -// A status Line is built from: -// -// -void HttpParser::parseStatusLine(std::string &line) -{ +void HttpParser::parseStatusLine(std::string& line) { + // A status Line is built from: + // ESP_LOGD(LOG_TAG, ">> ParseStatusLine: \"%s\" [%d]", line.c_str(), line.length()); std::string::iterator it = line.begin(); // Get the version @@ -314,4 +307,3 @@ void HttpParser::parseStatusLine(std::string &line) ESP_LOGD(LOG_TAG, "<< ParseStatusLine: method: %s, version: %s, status: %s", m_method.c_str(), m_version.c_str(), m_status.c_str()); } // parseRequestLine - diff --git a/cpp_utils/HttpParser.h b/cpp_utils/HttpParser.h index 47aafcea..06485293 100644 --- a/cpp_utils/HttpParser.h +++ b/cpp_utils/HttpParser.h @@ -12,17 +12,6 @@ #include "Socket.h" class HttpParser { -private: - std::string m_method; - std::string m_url; - std::string m_version; - std::string m_body; - std::string m_status; - std::string m_reason; - std::map m_headers; - void dump(); - void parseRequestLine(std::string &line); - void parseStatusLine(std::string &line); public: HttpParser(); virtual ~HttpParser(); @@ -32,12 +21,25 @@ class HttpParser { std::string getMethod(); std::string getURL(); std::string getVersion(); - std::string getStatus(); - std::string getReason(); + std::string getStatus(); + std::string getReason(); bool hasHeader(const std::string& name); void parse(std::string message); void parse(Socket s); - void parseResponse(std::string message); + void parseResponse(std::string message); + +private: + std::string m_method; + std::string m_url; + std::string m_version; + std::string m_body; + std::string m_status; + std::string m_reason; + std::map m_headers; + void dump(); + void parseRequestLine(std::string& line); + void parseStatusLine(std::string& line); + }; #endif /* CPP_UTILS_HTTPPARSER_H_ */ diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp index 9261af60..ff9336fb 100644 --- a/cpp_utils/HttpRequest.cpp +++ b/cpp_utils/HttpRequest.cpp @@ -41,6 +41,8 @@ #include #include +#define STATE_NAME 0 +#define STATE_VALUE 1 static const char* LOG_TAG="HttpRequest"; @@ -79,10 +81,10 @@ const char HttpRequest::HTTP_METHOD_PUT[] = "PUT"; std::string buildWebsocketKeyResponseHash(std::string requestKey) { std::string newKey = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; uint8_t shaData[20]; - esp_sha(SHA1, (uint8_t*)newKey.data(), newKey.length(), shaData); + esp_sha(SHA1, (uint8_t*) newKey.data(), newKey.length(), shaData); //GeneralUtils::hexDump(shaData, 20); std::string retStr; - GeneralUtils::base64Encode(std::string((char*)shaData, sizeof(shaData)), &retStr); + GeneralUtils::base64Encode(std::string((char*) shaData, sizeof(shaData)), &retStr); return retStr; } // buildWebsocketKeyResponseHash @@ -104,9 +106,8 @@ HttpRequest::HttpRequest(Socket clientSocket) { // a delimiter and then examine each of the parts to see if any of those are "Upgrade". std::vector parts = GeneralUtils::split(getHeader(HTTP_HEADER_CONNECTION), ','); bool upgradeFound = false; - if (std::find(parts.begin(), parts.end(), "Upgrade") != parts.end()) - { - upgradeFound = true; + if (std::find(parts.begin(), parts.end(), "Upgrade") != parts.end()) { + upgradeFound = true; } // Is this a Web Socket? @@ -114,7 +115,7 @@ HttpRequest::HttpRequest(Socket clientSocket) { !getHeader(HTTP_HEADER_HOST).empty() && getHeader(HTTP_HEADER_UPGRADE) == "websocket" && //getHeader(HTTP_HEADER_CONNECTION) == "Upgrade" && - upgradeFound == true && + upgradeFound && !getHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY).empty() && !getHeader(HTTP_HEADER_SEC_WEBSOCKET_VERSION).empty()) { ESP_LOGD(LOG_TAG, "Websocket detected!"); @@ -153,9 +154,6 @@ void HttpRequest::close() { } // close_cpp - - - /** * @brief Dump the HttpRequest for debugging purposes. */ @@ -203,9 +201,6 @@ std::string HttpRequest::getPath() { } // getPath -#define STATE_NAME 0 -#define STATE_VALUE 1 - /** * @brief Get the query part of the request. * The query is a set of name = value pairs. The return is a map keyed by the name items. @@ -218,9 +213,9 @@ std::map HttpRequest::getQuery() { std::map queryMap; std::string possibleQueryString = getPath(); - int qindex = possibleQueryString.find_first_of("?") ; + int qindex = possibleQueryString.find_first_of("?"); if (qindex < 0) { - ESP_LOGD(LOG_TAG, "No query string present") ; + ESP_LOGD(LOG_TAG, "No query string present"); return queryMap ; } std::string queryString = possibleQueryString.substr(qindex + 1, -1) ; @@ -235,7 +230,7 @@ std::map HttpRequest::getQuery() { std::string name = ""; std::string value; // Loop through each character in the query string. - for (int i=0; i HttpRequest::getQuery() { } // getQuery - /** * @brief Get the underlying socket. * @return The underlying socket. @@ -353,11 +347,11 @@ std::vector HttpRequest::pathSplit() { std::istringstream stream(getPath()); std::vector ret; std::string pathPart; - while(std::getline(stream, pathPart, '/')) { + while (std::getline(stream, pathPart, '/')) { ret.push_back(pathPart); } // Debug - for (int i=0; i parseForm(); // Parse the body as a form. std::vector pathSplit(); std::string urlDecode(std::string str); // Decode a URL. +private: + Socket m_clientSocket; // The socket connected to the client. + bool m_isClosed; // Is the client connection closed? + HttpParser m_parser; // The parse to parse HTTP data. + WebSocket* m_pWebSocket; // A possible reference to a WebSocket object instance. + }; #endif /* COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ */ diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp index 4c5c1a4e..c6e9683c 100644 --- a/cpp_utils/HttpResponse.cpp +++ b/cpp_utils/HttpResponse.cpp @@ -26,12 +26,13 @@ const int HttpResponse::HTTP_STATUS_NOT_IMPLEMENTED = 501; const int HttpResponse::HTTP_STATUS_SERVICE_UNAVAILABLE = 503; static std::string lineTerminator = "\r\n"; -HttpResponse::HttpResponse(HttpRequest *request) { m_request = request; m_status = 200; +HttpResponse::HttpResponse(HttpRequest* request) { m_headerCommitted = false; // We have not yet sent a header. } + HttpResponse::~HttpResponse() { } @@ -43,9 +44,7 @@ HttpResponse::~HttpResponse() { * @param [in] value The value of the header. */ void HttpResponse::addHeader(const std::string name, const std::string value) { - if (m_headerCommitted) { - return; - } + if (m_headerCommitted) return; m_responseHeaders.insert(std::pair(name, value)); } // addHeader @@ -57,7 +56,7 @@ void HttpResponse::addHeader(const std::string name, const std::string value) { */ void HttpResponse::close() { // If we haven't yet sent the header of the data, send that now. - if (m_headerCommitted == false) { + if (!m_headerCommitted) { sendHeader(); } m_request->close(); @@ -70,9 +69,7 @@ void HttpResponse::close() { * @return The value of the named header. */ std::string HttpResponse::getHeader(std::string name) { - if (m_responseHeaders.find(name) == m_responseHeaders.end()) { - return ""; - } + if (m_responseHeaders.find(name) == m_responseHeaders.end()) return ""; return m_responseHeaders.at(name); } // getHeader @@ -97,7 +94,7 @@ void HttpResponse::sendData(std::string data) { } // If we haven't yet sent the header of the data, send that now. - if (m_headerCommitted == false) { + if (!m_headerCommitted) { sendHeader(); } @@ -115,7 +112,7 @@ void HttpResponse::sendData(uint8_t* pData, size_t size) { } // If we haven't yet sent the header of the data, send that now. - if (m_headerCommitted == false) { + if (!m_headerCommitted) { sendHeader(); } @@ -124,8 +121,7 @@ void HttpResponse::sendData(uint8_t* pData, size_t size) { ESP_LOGD(LOG_TAG, "<< sendData"); } // sendData -void HttpResponse::sendFile(std::string fileName, size_t bufSize) -{ +void HttpResponse::sendFile(std::string fileName, size_t bufSize) { ESP_LOGI(LOG_TAG, "Opening file: %s", fileName.c_str()); std::ifstream ifStream; ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); // Attempt to open the file for reading. @@ -139,15 +135,15 @@ void HttpResponse::sendFile(std::string fileName, size_t bufSize) close(); return; // Since we failed to open the file, no further work to be done. } - + // We now have an open file and want to push the content of that file through to the browser. // because of defect #252 we have to do some pretty important re-work here. Specifically, we can't host the whole file in // RAM at one time. Instead what we have to do is ensure that we only have enough data in RAM to be sent. - + setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); uint8_t *pData = new uint8_t[bufSize]; - while(!ifStream.eof()) { - ifStream.read((char *)pData, bufSize); + while (!ifStream.eof()) { + ifStream.read((char*) pData, bufSize); sendData(pData, ifStream.gcount()); } delete[] pData; @@ -160,7 +156,7 @@ void HttpResponse::sendFile(std::string fileName, size_t bufSize) */ void HttpResponse::sendHeader() { // If we haven't yet sent the header of the data, send that now. - if (m_headerCommitted == false) { + if (!m_headerCommitted) { std::ostringstream oss; oss << m_request->getVersion() << " " << m_status << " " << m_statusMessage << lineTerminator; for (auto it = m_responseHeaders.begin(); it != m_responseHeaders.end(); ++it) { @@ -188,5 +184,3 @@ void HttpResponse::setStatus(const int status, const std::string message) { m_status = status; m_statusMessage = message; } // setStatus - - diff --git a/cpp_utils/HttpResponse.h b/cpp_utils/HttpResponse.h index 7080ea90..cc7be388 100644 --- a/cpp_utils/HttpResponse.h +++ b/cpp_utils/HttpResponse.h @@ -14,15 +14,6 @@ #include "HttpRequest.h" class HttpResponse { -private: - bool m_headerCommitted; // Has the header been sent? - HttpRequest* m_request; // The request associated with this response. - std::map m_responseHeaders; // The headers to be sent with the response. - int m_status; // The status to be sent with the response. - std::string m_statusMessage; // The status message to be sent with the response. - - void sendHeader(); // Send the header to the client. - public: static const int HTTP_STATUS_CONTINUE; static const int HTTP_STATUS_SWITCHING_PROTOCOL; @@ -46,8 +37,17 @@ class HttpResponse { std::map getHeaders(); // Get all headers. void sendData(std::string data); // Send data to the client. void sendData(uint8_t* pData, size_t size); // Send data to the client. - void sendFile(std::string fileName, size_t bufSize=4*1024); // Send file contents if exists. void setStatus(int status, std::string message); // Set the response status. + void sendFile(std::string fileName, size_t bufSize = 4 * 1024); // Send file contents if exists. +private: + bool m_headerCommitted; // Has the header been sent? + HttpRequest* m_request; // The request associated with this response. + std::map m_responseHeaders; // The headers to be sent with the response. + int m_status; // The status to be sent with the response. + std::string m_statusMessage; // The status message to be sent with the response. + + void sendHeader(); // Send the header to the client. + }; #endif /* COMPONENTS_CPP_UTILS_HTTPRESPONSE_H_ */ diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp index 1b1845f1..fb90fcc9 100644 --- a/cpp_utils/HttpServer.cpp +++ b/cpp_utils/HttpServer.cpp @@ -26,17 +26,16 @@ static const char* LOG_TAG = "HttpServer"; #undef close - /** * Constructor for HTTP Server */ HttpServer::HttpServer() { - m_fileBufferSize = 4*1024; // Default size of the file buffer. m_portNumber = 80; // The default port number. m_clientTimeout = 5; // The default timeout 5 seconds. m_rootPath = ""; // The default path. m_useSSL = false; // Default SSL is no. setDirectoryListing(false); // Default directory listing is disabled. + m_fileBufferSize = 4 * 1024; // Default size of the file buffer. } // HttpServer @@ -44,6 +43,7 @@ HttpServer::~HttpServer() { ESP_LOGD(LOG_TAG, "~HttpServer"); } + /** * @brief Be an HTTP server task. * Here we define a Task that will be run when the HTTP server starts. It is this task @@ -52,7 +52,7 @@ HttpServer::~HttpServer() { */ class HttpServerTask: public Task { public: - HttpServerTask(std::string name): Task(name, 16*1024) { + HttpServerTask(std::string name): Task(name, 16 * 1024) { m_pHttpServer = nullptr; }; @@ -70,7 +70,7 @@ class HttpServerTask: public Task { * * @param [in] request The HTTP request to process. */ - void processRequest(HttpRequest &request) { + void processRequest(HttpRequest& request) { ESP_LOGD("HttpServerTask", ">> processRequest: Method: %s, Path: %s", request.getMethod().c_str(), request.getPath().c_str()); @@ -107,9 +107,9 @@ class HttpServerTask: public Task { // If the file name ends with a '/' then remove it ... we are normalizing to NO trailing slashes. if (GeneralUtils::endsWith(fileName, '/')) { - fileName = fileName.substr(0, fileName.length()-1); + fileName = fileName.substr(0, fileName.length() - 1); } - + HttpResponse response(&request); // Test if the path is a directory. if (FileSystem::isDirectory(fileName)) { @@ -129,20 +129,18 @@ class HttpServerTask: public Task { * @param [in] data A reference to the HttpServer. */ void run(void* data) { - m_pHttpServer = (HttpServer*)data; // The passed in data is an instance of an HttpServer. + m_pHttpServer = (HttpServer*) data; // The passed in data is an instance of an HttpServer. m_pHttpServer->m_socket.setSSL(m_pHttpServer->m_useSSL); m_pHttpServer->m_socket.listen(m_pHttpServer->m_portNumber, false /* is datagram */, true /* Allow address reuse */); ESP_LOGD("HttpServerTask", "Listening on port %d", m_pHttpServer->getPort()); Socket clientSocket; - while(1) { // Loop forever. - + while (true) { // Loop forever. ESP_LOGD("HttpServerTask", "Waiting for new peer client"); try { clientSocket = m_pHttpServer->m_socket.accept(); // Block waiting for a new external client connection. clientSocket.setTimeout(m_pHttpServer->getClientTimeout()); - } - catch(std::exception &e) { + } catch (std::exception& e) { ESP_LOGE("HttpServerTask", "Caught an exception waiting for new client!"); m_pHttpServer->m_semaphoreServerStarted.give(); // Release the semaphore .. we are now no longer running. return; @@ -174,7 +172,7 @@ class HttpServerTask: public Task { * Example: * @code{.cpp} * static void handle_REST_WiFi(WebServer::HttpRequest *pRequest, WebServer::HttpResponse *pResponse) { - * ... + * ... * } * * webServer.addPathHandler("GET", "\\/ESP32\\/WiFi", handle_REST_WiFi); @@ -187,7 +185,7 @@ class HttpServerTask: public Task { void HttpServer::addPathHandler( std::string method, std::regex* pathExpr, - void (*handler)(HttpRequest *pHttpRequest, HttpResponse *pHttpResponse)) { + void (*handler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse)) { // We are maintaining a C++ vector of PathHandler objects. We add a new entry into that vector. m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); @@ -204,7 +202,7 @@ void HttpServer::addPathHandler( * Example: * @code{.cpp} * static void handle_REST_WiFi(WebServer::HttpRequest *pRequest, WebServer::HttpResponse *pResponse) { - * ... + * ... * } * * webServer.addPathHandler("GET", "/ESP32/WiFi", handle_REST_WiFi); @@ -217,7 +215,7 @@ void HttpServer::addPathHandler( void HttpServer::addPathHandler( std::string method, std::string path, - void (*handler)(HttpRequest *pHttpRequest, HttpResponse *pHttpResponse)) { + void (*handler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse)) { // We are maintaining a C++ vector of PathHandler objects. We add a new entry into that vector. m_pathHandlers.push_back(PathHandler(method, path, handler)); @@ -299,8 +297,7 @@ void HttpServer::listDirectory(std::string path, HttpResponse& response) { ss << "" << it->getName() << ""; if (it->isDirectory()) { ss << "<dir>"; - } - else { + } else { ss << "" << it->length() << ""; } @@ -314,6 +311,7 @@ void HttpServer::listDirectory(std::string path, HttpResponse& response) { response.close(); } // listDirectory + /** * @brief Set different socket timeout for new connections. * @param [in] use Set to true to enable directory listing. @@ -322,6 +320,7 @@ void HttpServer::setClientTimeout(uint32_t timeout) { m_clientTimeout = timeout; } + /** * @brief Get current socket's timeout for new connections. * @param [in] use Set to true to enable directory listing. @@ -330,6 +329,7 @@ uint32_t HttpServer::getClientTimeout() { return m_clientTimeout; } + /** * @brief Set whether or not we will list directories. * @param [in] use Set to true to enable directory listing. @@ -369,7 +369,6 @@ void HttpServer::setFileBufferSize(size_t fileBufferSize) { * @return N/A. */ void HttpServer::setRootPath(std::string path) { - // Should the user have supplied a path that ends in a "/" remove the trailing slash. This also // means that "/" becomes "". if (GeneralUtils::endsWith(path, '/')) { @@ -393,7 +392,7 @@ void HttpServer::start(uint16_t portNumber, bool useSSL) { // Take the semaphore that says that we are now running. If we are already running, then end here as // there is nothing further to do. - if (m_semaphoreServerStarted.take(100, "start") == false) { + if (!m_semaphoreServerStarted.take(100, "start")) { ESP_LOGD(LOG_TAG, "<< start: Already running"); return; } @@ -428,7 +427,7 @@ void HttpServer::stop() { * @param [in] pathPattern The path pattern to be matched. * @param [in] webServerRequestHandler The request handler to be called. */ -PathHandler::PathHandler(std::string method, std::regex *pRegex, +PathHandler::PathHandler(std::string method, std::regex* pRegex, void (*pWebServerRequestHandler) ( HttpRequest* pHttpRequest, @@ -471,12 +470,9 @@ PathHandler::PathHandler(std::string method, std::string matchPath, * @return True if the path matches. */ bool PathHandler::match(std::string method, std::string path) { - if (method != m_method) { - return false; - } + if (method != m_method) return false; if (m_isRegex) { ESP_LOGD("PathHandler", "regex matching: %s with %s", m_textPattern.c_str(), path.c_str()); - return std::regex_search(path, *m_pRegex); } ESP_LOGD("PathHandler", "plain matching: %s with %s", m_textPattern.c_str(), path.c_str()); @@ -490,10 +486,6 @@ bool PathHandler::match(std::string method, std::string path) { * @param [in] response An object representing the response. * @return N/A. */ -void PathHandler::invokePathHandler(HttpRequest* request, HttpResponse *response) { +void PathHandler::invokePathHandler(HttpRequest* request, HttpResponse* response) { m_pRequestHandler(request, response); } // invokePathHandler - - - - diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h index 95558c05..8baa7bbb 100644 --- a/cpp_utils/HttpServer.h +++ b/cpp_utils/HttpServer.h @@ -83,7 +83,7 @@ class HttpServer { void setDirectoryListing(bool use); // Should we list the content of directories? void setFileBufferSize(size_t fileBufferSize); // Set the size of the file buffer void setRootPath(std::string path); // Set the root of the file system path. - void start(uint16_t portNumber, bool useSSL=false); + void start(uint16_t portNumber, bool useSSL = false); void stop(); // Stop a previously started server. private: diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index dfcfeff0..5013956f 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -67,7 +67,7 @@ void I2C::endTransaction() { ESP_LOGE(LOG_TAG, "i2c_master_stop: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - errRc = ::i2c_master_cmd_begin(m_portNum, m_cmd, 1000/portTICK_PERIOD_MS); + errRc = ::i2c_master_cmd_begin(m_portNum, m_cmd, 1000 / portTICK_PERIOD_MS); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "i2c_master_cmd_begin: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } @@ -81,8 +81,7 @@ void I2C::endTransaction() { * * @return The address of the %I2C slave. */ -uint8_t I2C::getAddress() const -{ +uint8_t I2C::getAddress() const { return m_address; } @@ -136,7 +135,7 @@ void I2C::read(uint8_t* bytes, size_t length, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "read(size=%d, ack=%d)", length, ack); } - if (m_directionKnown == false) { + if (!m_directionKnown) { m_directionKnown = true; esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack); if (errRc != ESP_OK) { @@ -157,11 +156,11 @@ void I2C::read(uint8_t* bytes, size_t length, bool ack) { * @param [in] ack Whether or not we should send an ACK to the slave after reading a byte. * @return N/A. */ -void I2C::read(uint8_t *byte, bool ack) { +void I2C::read(uint8_t* byte, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "read(size=1, ack=%d)", ack); } - if (m_directionKnown == false) { + if (!m_directionKnown) { m_directionKnown = true; esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack); if (errRc != ESP_OK) { @@ -180,12 +179,11 @@ void I2C::read(uint8_t *byte, bool ack) { * @return N/A. */ void I2C::scan() { - uint8_t i; printf("Data Pin: %d, Clock Pin: %d\n", this->m_sdaPin, this->m_sclPin); printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n"); printf("00: "); - for (i=3; i<0x78; i++) { - if (i%16 == 0) { + for (uint8_t i = 3; i < 0x78; i++) { + if (i % 16 == 0) { printf("\n%.2x:", i); } if (slavePresent(i)) { @@ -203,8 +201,7 @@ void I2C::scan() { * * @param [in] address The address of the %I2C slave. */ -void I2C::setAddress(uint8_t address) -{ +void I2C::setAddress(uint8_t address) { this->m_address = address; } // setAddress @@ -230,7 +227,7 @@ bool I2C::slavePresent(uint8_t address) { ::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, 1 /* expect ack */); ::i2c_master_stop(cmd); - esp_err_t espRc = ::i2c_master_cmd_begin(m_portNum, cmd, 100/portTICK_PERIOD_MS); + esp_err_t espRc = ::i2c_master_cmd_begin(m_portNum, cmd, 100 / portTICK_PERIOD_MS); i2c_cmd_link_delete(cmd); return espRc == 0; // Return true if the slave is present and false otherwise. } // slavePresent @@ -305,7 +302,7 @@ void I2C::write(uint8_t *bytes, size_t length, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "write(length=%d, ack=%d)", length, ack); } - if (m_directionKnown == false) { + if (!m_directionKnown) { m_directionKnown = true; esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack); if (errRc != ESP_OK) { @@ -317,5 +314,3 @@ void I2C::write(uint8_t *bytes, size_t length, bool ack) { ESP_LOGE(LOG_TAG, "i2c_master_write: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } } // write - - diff --git a/cpp_utils/I2C.h b/cpp_utils/I2C.h index 9886d63a..da065d91 100644 --- a/cpp_utils/I2C.h +++ b/cpp_utils/I2C.h @@ -17,14 +17,6 @@ * @brief Interface to %I2C functions. */ class I2C { -private: - uint8_t m_address; - i2c_cmd_handle_t m_cmd; - bool m_directionKnown; - gpio_num_t m_sdaPin; - gpio_num_t m_sclPin; - i2c_port_t m_portNum; - public: /** * @brief The default SDA pin. @@ -46,16 +38,25 @@ class I2C { void endTransaction(); uint8_t getAddress() const; void init(uint8_t address, gpio_num_t sdaPin = DEFAULT_SDA_PIN, gpio_num_t sclPin = DEFAULT_CLK_PIN, uint32_t clkSpeed = DEFAULT_CLK_SPEED, i2c_port_t portNum = I2C_NUM_0); - void read(uint8_t* bytes, size_t length, bool ack=true); - void read(uint8_t* byte, bool ack=true); + void read(uint8_t* bytes, size_t length, bool ack = true); + void read(uint8_t* byte, bool ack = true); void scan(); void setAddress(uint8_t address); void setDebug(bool enabled); bool slavePresent(uint8_t address); void start(); void stop(); - void write(uint8_t byte, bool ack=true); - void write(uint8_t* bytes, size_t length, bool ack=true); + void write(uint8_t byte, bool ack = true); + void write(uint8_t* bytes, size_t length, bool ack = true); + +private: + uint8_t m_address; + i2c_cmd_handle_t m_cmd; + bool m_directionKnown; + gpio_num_t m_sdaPin; + gpio_num_t m_sclPin; + i2c_port_t m_portNum; + }; #endif /* MAIN_I2C_H_ */ diff --git a/cpp_utils/I2S.cpp b/cpp_utils/I2S.cpp index c5bcb997..15ba2fa7 100644 --- a/cpp_utils/I2S.cpp +++ b/cpp_utils/I2S.cpp @@ -27,7 +27,6 @@ static const char* LOG_TAG = "I2S"; static intr_handle_t i2s_intr_handle; - /** * A representation of a DMA buffer. */ @@ -67,6 +66,7 @@ DMABuffer::~DMABuffer() { delete[] m_desc.buf; } // ~DMABuffer + /** * @brief Dump the state of the buffer. * @return N/A @@ -74,7 +74,7 @@ DMABuffer::~DMABuffer() { void DMABuffer::dump() { std::ostringstream ss; ss << "size: " << m_desc.size; - ss << ", buf: 0x" << std::hex << (uint32_t)m_desc.buf << std::dec; + ss << ", buf: 0x" << std::hex << (uint32_t) m_desc.buf << std::dec; ss << ", length: " << m_desc.length; ss << ", offset: " << m_desc.offset; ss << ", sosf: " << m_desc.sosf; @@ -82,10 +82,8 @@ void DMABuffer::dump() { ss << ", owner: " << m_desc.owner; ESP_LOGD(LOG_TAG, "Desc: %s", ss.str().c_str()); int length = 100; - if (length > m_desc.length) { - length = m_desc.length; - } - GeneralUtils::hexDump((uint8_t*)m_desc.buf, length); + if (length > m_desc.length) length = m_desc.length; + GeneralUtils::hexDump((uint8_t*) m_desc.buf, length); } /** @@ -95,23 +93,15 @@ void DMABuffer::dump() { * @return The number of bytes actually copied. */ uint32_t DMABuffer::getData(uint8_t* pData, uint32_t length) { - uint8_t* pBuf = (uint8_t*)m_desc.buf; - if (length > getLength()) { - length = getLength(); - } + uint8_t* pBuf = (uint8_t*) m_desc.buf; + if (length > getLength()) length = getLength(); uint32_t i; - // // The descriptor buffer is filled with data that contains: - // // b1 00 b0 00 b3 00 b2 00 b5 00 b4 00 b7 00 b6 00 - // // Our goal is to populate the passed in buffer with data of the form: - // // b0 b1 b2 b3 b4 b5 b6 b7 ... - // // The following alogrithm does that. - // - for (i=0; igetDesc()); - pCurrentDMABuffer = pCurrentDMABuffer->getNext(); - I2S0.int_clr.val = I2S0.int_raw.val; - pI2S->m_dmaSemaphore.giveFromISR(); +static void IRAM_ATTR i2s_isr(void* arg) { + I2S* pI2S = (I2S*) arg; + //ESP_EARLY_LOGV(LOG_TAG, "I2S isr"); + pLastDMABuffer = pCurrentDMABuffer; + //logI2SIntr(); + //logDesc(pCurrentDMABuffer->getDesc()); + pCurrentDMABuffer = pCurrentDMABuffer->getNext(); + I2S0.int_clr.val = I2S0.int_raw.val; + pI2S->m_dmaSemaphore.giveFromISR(); } /** @@ -261,20 +246,20 @@ void I2S::cameraMode(dma_config_t config, int desc_count, int sample_count) { const uint32_t const_high = 0x38; - gpio_matrix_in(config.pin_d0, I2S0I_DATA_IN0_IDX, false); - gpio_matrix_in(config.pin_d1, I2S0I_DATA_IN1_IDX, false); - gpio_matrix_in(config.pin_d2, I2S0I_DATA_IN2_IDX, false); - gpio_matrix_in(config.pin_d3, I2S0I_DATA_IN3_IDX, false); - gpio_matrix_in(config.pin_d4, I2S0I_DATA_IN4_IDX, false); - gpio_matrix_in(config.pin_d5, I2S0I_DATA_IN5_IDX, false); - gpio_matrix_in(config.pin_d6, I2S0I_DATA_IN6_IDX, false); - gpio_matrix_in(config.pin_d7, I2S0I_DATA_IN7_IDX, false); - gpio_matrix_in(config.pin_vsync, I2S0I_V_SYNC_IDX, true); - gpio_matrix_in(config.pin_href, I2S0I_H_SYNC_IDX, false); - //gpio_matrix_in(const_high, I2S0I_V_SYNC_IDX, false); - //gpio_matrix_in(const_high, I2S0I_H_SYNC_IDX, false); - gpio_matrix_in(const_high, I2S0I_H_ENABLE_IDX, false); - gpio_matrix_in(config.pin_pclk, I2S0I_WS_IN_IDX, false); + gpio_matrix_in(config.pin_d0, I2S0I_DATA_IN0_IDX, false); + gpio_matrix_in(config.pin_d1, I2S0I_DATA_IN1_IDX, false); + gpio_matrix_in(config.pin_d2, I2S0I_DATA_IN2_IDX, false); + gpio_matrix_in(config.pin_d3, I2S0I_DATA_IN3_IDX, false); + gpio_matrix_in(config.pin_d4, I2S0I_DATA_IN4_IDX, false); + gpio_matrix_in(config.pin_d5, I2S0I_DATA_IN5_IDX, false); + gpio_matrix_in(config.pin_d6, I2S0I_DATA_IN6_IDX, false); + gpio_matrix_in(config.pin_d7, I2S0I_DATA_IN7_IDX, false); + gpio_matrix_in(config.pin_vsync, I2S0I_V_SYNC_IDX, true); + gpio_matrix_in(config.pin_href, I2S0I_H_SYNC_IDX, false); +// gpio_matrix_in(const_high, I2S0I_V_SYNC_IDX, false); +// gpio_matrix_in(const_high, I2S0I_H_SYNC_IDX, false); + gpio_matrix_in(const_high, I2S0I_H_ENABLE_IDX, false); + gpio_matrix_in(config.pin_pclk, I2S0I_WS_IN_IDX, false); // Enable and configure I2S peripheral periph_module_enable(PERIPH_I2S0_MODULE); @@ -345,10 +330,10 @@ void I2S::cameraMode(dma_config_t config, int desc_count, int sample_count) { ESP_LOGD(LOG_TAG, "Initializing %d descriptors", desc_count); - DMABuffer *pFirst = new DMABuffer(); - DMABuffer *pLast = pFirst; - for (int i=1; isetNext(pNewDMABuffer); pLast = pNewDMABuffer; } @@ -356,45 +341,45 @@ void I2S::cameraMode(dma_config_t config, int desc_count, int sample_count) { pCurrentDMABuffer = pFirst; // I2S_RX_EOF_NUM_REG - I2S0.rx_eof_num = sample_count; - - // I2S_IN_LINK_REG -> I2S_INLINK_ADDR - I2S0.in_link.addr = (uint32_t) pFirst; - - // I2S_IN_LINK_REG -> I2S_INLINK_START - I2S0.in_link.start = 1; - - I2S0.int_clr.val = I2S0.int_raw.val; - I2S0.int_ena.val = 0; - I2S0.int_ena.in_done = 1; - - // Register the interrupt handler. - esp_intr_alloc( - ETS_I2S0_INTR_SOURCE, - ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, - &i2s_isr, - this, - &i2s_intr_handle); - - m_dmaSemaphore.take(); - // Start the interrupt handler - esp_intr_enable(i2s_intr_handle); - - I2S0.conf.rx_start = 1; - - /* - while(1) { - m_dmaSemaphore.wait(); - uint32_t dataLength = pLastDMABuffer->getLength(); - ESP_LOGD(LOG_TAG, "Got a DMA buffer; length=%d", dataLength); - //pLastDMABuffer->dump(); - uint8_t *pData = new uint8_t[dataLength]; - pLastDMABuffer->getData(pData, dataLength); + I2S0.rx_eof_num = sample_count; + + // I2S_IN_LINK_REG -> I2S_INLINK_ADDR + I2S0.in_link.addr = (uint32_t) pFirst; + + // I2S_IN_LINK_REG -> I2S_INLINK_START + I2S0.in_link.start = 1; + + I2S0.int_clr.val = I2S0.int_raw.val; + I2S0.int_ena.val = 0; + I2S0.int_ena.in_done = 1; + + // Register the interrupt handler. + esp_intr_alloc( + ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, + &i2s_isr, + this, + &i2s_intr_handle); + + m_dmaSemaphore.take(); + // Start the interrupt handler + esp_intr_enable(i2s_intr_handle); + + I2S0.conf.rx_start = 1; + + /* + while(1) { + m_dmaSemaphore.wait(); + uint32_t dataLength = pLastDMABuffer->getLength(); + ESP_LOGD(LOG_TAG, "Got a DMA buffer; length=%d", dataLength); + //pLastDMABuffer->dump(); + uint8_t *pData = new uint8_t[dataLength]; + pLastDMABuffer->getData(pData, dataLength); GeneralUtils::hexDump(pData, dataLength); delete[] pData; m_dmaSemaphore.take(); - } - */ + } + */ ESP_LOGD(LOG_TAG, "<< cameraMode"); } diff --git a/cpp_utils/IFTTT.cpp b/cpp_utils/IFTTT.cpp index 0cf7a6d9..6af2279d 100644 --- a/cpp_utils/IFTTT.cpp +++ b/cpp_utils/IFTTT.cpp @@ -37,7 +37,7 @@ void IFTTT::trigger( std::string value2, std::string value3) { m_restClient.setURL("https://maker.ifttt.com/trigger/" + event + "/with/key/" + m_key); - cJSON *root; + cJSON* root; root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "value1", value1.c_str()); diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index c55f3a78..31f8cdb1 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -125,11 +125,8 @@ void JsonArray::addString(std::string value) { * @return The boolean value at the given index. */ bool JsonArray::getBoolean(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); - if (node->valueint == 0) { - return false; - } - return true; + cJSON* node = cJSON_GetArrayItem(m_node, item); + return (node->valueint != 0); } // getBoolean @@ -139,7 +136,7 @@ bool JsonArray::getBoolean(int item) { * @return The double value at the given index. */ double JsonArray::getDouble(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); + cJSON* node = cJSON_GetArrayItem(m_node, item); return node->valuedouble; } // getDouble @@ -150,7 +147,7 @@ double JsonArray::getDouble(int item) { * @return The int value at the given index. */ int JsonArray::getInt(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); + cJSON* node = cJSON_GetArrayItem(m_node, item); return node->valueint; } // getInt @@ -161,7 +158,7 @@ int JsonArray::getInt(int item) { * @return The object value at the given index. */ JsonObject JsonArray::getObject(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); + cJSON* node = cJSON_GetArrayItem(m_node, item); return JsonObject(node); } // getObject @@ -172,7 +169,7 @@ JsonObject JsonArray::getObject(int item) { * @return The object value at the given index. */ std::string JsonArray::getString(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); + cJSON* node = cJSON_GetArrayItem(m_node, item); return std::string(node->valuestring); } // getString @@ -182,7 +179,7 @@ std::string JsonArray::getString(int item) { * @return A JSON string representation of the array. */ std::string JsonArray::toString() { - char *data = cJSON_Print(m_node); + char* data = cJSON_Print(m_node); std::string ret(data); free(data); return ret; @@ -194,7 +191,7 @@ std::string JsonArray::toString() { * @return A string representation. */ std::string JsonArray::toStringUnformatted() { - char *data = cJSON_PrintUnformatted(m_node); + char* data = cJSON_PrintUnformatted(m_node); std::string ret(data); free(data); return ret; @@ -217,7 +214,7 @@ JsonObject::JsonObject(cJSON* node) { } // JsonObject JsonArray JsonObject::getArray(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); return JsonArray(node); } @@ -228,10 +225,8 @@ JsonArray JsonObject::getArray(std::string name) { * @return The boolean value from the object. */ bool JsonObject::getBoolean(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); - if (node == nullptr) { - return false; - } + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) return false; return cJSON_IsTrue(node); } // getBoolean @@ -242,10 +237,8 @@ bool JsonObject::getBoolean(std::string name) { * @return The double value from the object. */ double JsonObject::getDouble(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); - if (node == nullptr) { - return 0.0; - } + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) return 0.0; return node->valuedouble; } // getDouble @@ -256,10 +249,8 @@ double JsonObject::getDouble(std::string name) { * @return The int value from the object. */ int JsonObject::getInt(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); - if (node == nullptr) { - return 0; - } + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) return 0; return node->valueint; } // getInt @@ -270,7 +261,7 @@ int JsonObject::getInt(std::string name) { * @return The object value from the object. */ JsonObject JsonObject::getObject(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); return JsonObject(node); } // getObject @@ -281,10 +272,8 @@ JsonObject JsonObject::getObject(std::string name) { * @return The string value from the object. A zero length string is returned when the object is not present. */ std::string JsonObject::getString(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); - if (node == nullptr) { - return ""; - } + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) return ""; return std::string(node->valuestring); } // getString @@ -325,7 +314,7 @@ void JsonObject::setArray(std::string name, JsonArray array) { * @return N/A. */ void JsonObject::setBoolean(std::string name, bool value) { - cJSON_AddItemToObject(m_node, name.c_str(), value?cJSON_CreateTrue():cJSON_CreateFalse()); + cJSON_AddItemToObject(m_node, name.c_str(), value ? cJSON_CreateTrue() : cJSON_CreateFalse()); } // setBoolean @@ -347,7 +336,7 @@ void JsonObject::setDouble(std::string name, double value) { * @return N/A. */ void JsonObject::setInt(std::string name, int value) { - cJSON_AddItemToObject(m_node, name.c_str(), cJSON_CreateNumber((double)value)); + cJSON_AddItemToObject(m_node, name.c_str(), cJSON_CreateNumber((double) value)); } // setInt @@ -378,7 +367,7 @@ void JsonObject::setString(std::string name, std::string value) { * @return A JSON string representation of the object. */ std::string JsonObject::toString() { - char *data = cJSON_Print(m_node); + char* data = cJSON_Print(m_node); std::string ret(data); free(data); return ret; @@ -390,7 +379,7 @@ std::string JsonObject::toString() { * @return A string representation. */ std::string JsonObject::toStringUnformatted() { - char *data = cJSON_PrintUnformatted(m_node); + char* data = cJSON_PrintUnformatted(m_node); std::string ret(data); free(data); return ret; diff --git a/cpp_utils/JSON.h b/cpp_utils/JSON.h index 13f8f0a2..f132a0a7 100644 --- a/cpp_utils/JSON.h +++ b/cpp_utils/JSON.h @@ -25,6 +25,7 @@ class JSON { static void deleteArray(JsonArray jsonArray); static JsonObject parseObject(std::string text); static JsonArray parseArray(std::string text); + }; // JSON @@ -33,7 +34,6 @@ class JSON { */ class JsonArray { public: - int getInt(int item); JsonObject getObject(int item); std::string getString(int item); @@ -47,6 +47,7 @@ class JsonArray { std::string toString(); std::string toStringUnformatted(); std::size_t size(); + private: JsonArray(cJSON* node); friend class JSON; @@ -54,7 +55,8 @@ class JsonArray { /** * @brief The underlying cJSON node. */ - cJSON *m_node; + cJSON* m_node; + }; // JsonArray @@ -88,6 +90,7 @@ class JsonObject { * @brief The underlying cJSON node. */ cJSON* m_node; + }; // JsonObject diff --git a/cpp_utils/MAX7219.cpp b/cpp_utils/MAX7219.cpp index d83615d9..4cac347d 100644 --- a/cpp_utils/MAX7219.cpp +++ b/cpp_utils/MAX7219.cpp @@ -76,7 +76,7 @@ const static uint8_t charTable[] = { 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000 }; -MAX7219::MAX7219(SPI *spi, int numDevices) { +MAX7219::MAX7219(SPI* spi, int numDevices) { assert(spi != nullptr); this->spi = spi; if (numDevices <= 0 || numDevices > 8) { @@ -84,7 +84,7 @@ MAX7219::MAX7219(SPI *spi, int numDevices) { } maxDevices = numDevices; - for (int i = 0; i < 64; i++) { + for (uint8_t i = 0; i < 64; i++) { status[i] = 0x00; } for (int i = 0; i < maxDevices; i++) { @@ -106,21 +106,18 @@ int MAX7219::getDeviceCount() { void MAX7219::shutdown(bool b, int addr) { - if (addr < 0 || addr >= maxDevices) - return; + if (addr < 0 || addr >= maxDevices) return; + if (b) { spiTransfer(addr, OP_SHUTDOWN, 0); - } - else { + } else { spiTransfer(addr, OP_SHUTDOWN, 1); } } void MAX7219::setScanLimit(int limit, int addr) { - if (addr < 0 || addr >= maxDevices) { - return; - } + if (addr < 0 || addr >= maxDevices) return; if (limit >= 0 && limit < 8) { spiTransfer(addr, OP_SCANLIMIT, limit); } @@ -128,9 +125,7 @@ void MAX7219::setScanLimit(int limit, int addr) { void MAX7219::setIntensity(int intensity, int addr) { - if (addr < 0 || addr >= maxDevices) { - return; - } + if (addr < 0 || addr >= maxDevices) return; if (intensity >= 0 && intensity < 16) { spiTransfer(addr, OP_INTENSITY, intensity); } @@ -138,13 +133,11 @@ void MAX7219::setIntensity(int intensity, int addr) { void MAX7219::clearDisplay(int addr) { - int offset; + if (addr < 0 || addr >= maxDevices) return; - if (addr < 0 || addr >= maxDevices) { - return; - } + int offset; offset = addr * 8; - for (int i = 0; i < 8; i++) { + for (uint8_t i = 0; i < 8; i++) { status[offset + i] = 0; spiTransfer(addr, i + 1, status[offset + i]); } @@ -152,12 +145,11 @@ void MAX7219::clearDisplay(int addr) { void MAX7219::setLed(int row, int column, bool state, int addr) { + if (addr < 0 || addr >= maxDevices) return; + int offset; - uint8_t val = 0x00; + uint8_t val = 0; - if (addr < 0 || addr >= maxDevices) { - return; - } if (row < 0 || row > 7 || column < 0 || column > 7) { return; } @@ -165,8 +157,7 @@ void MAX7219::setLed(int row, int column, bool state, int addr) { val = 0b10000000 >> column; if (state) { status[offset + row] = status[offset + row] | val; - } - else { + } else { val = ~val; status[offset + row] = status[offset + row] & val; } @@ -175,28 +166,20 @@ void MAX7219::setLed(int row, int column, bool state, int addr) { void MAX7219::setRow(int row, uint8_t value, int addr) { - int offset; - if (addr < 0 || addr >= maxDevices) { - return; - } - if (row < 0 || row > 7) { - return; - } - offset = addr * 8; + if (addr < 0 || addr >= maxDevices) return; + if (row < 0 || row > 7) return; + + int offset = addr * 8; status[offset + row] = value; spiTransfer(addr, row + 1, status[offset + row]); } void MAX7219::setColumn(int col, uint8_t value, int addr) { - uint8_t val; + if (addr < 0 || addr >= maxDevices) return; + if (col < 0 || col > 7) return; - if (addr < 0 || addr >= maxDevices) { - return; - } - if (col < 0 || col > 7) { - return; - } + uint8_t val; for (int row = 0; row < 8; row++) { val = value >> (7 - row); val = val & 0x01; @@ -206,17 +189,11 @@ void MAX7219::setColumn(int col, uint8_t value, int addr) { void MAX7219::setDigit(int digit, uint8_t value, bool dp, int addr) { - int offset; - uint8_t v; + if (addr < 0 || addr >= maxDevices) return; + if (digit < 0 || digit > 7 || value > 15) return; - if (addr < 0 || addr >= maxDevices) { - return; - } - if (digit < 0 || digit > 7 || value > 15) { - return; - } - offset = addr * 8; - v = charTable[value]; + int offset = addr * 8; + uint8_t v = charTable[value]; if (dp) { v |= 0b10000000; } @@ -226,22 +203,13 @@ void MAX7219::setDigit(int digit, uint8_t value, bool dp, int addr) { void MAX7219::setChar(int digit, char value, bool dp, int addr) { - int offset; - uint8_t index, v; + if (addr < 0 || addr >= maxDevices) return; + if (digit < 0 || digit > 7) return; - if (addr < 0 || addr >= maxDevices) { - return; - } - if (digit < 0 || digit > 7) { - return; - } - offset = addr * 8; - index = (uint8_t) value; - if (index > 127) { - //no defined beyond index 127, so we use the space char - index = 32; - } - v = charTable[index]; + int offset = addr * 8; + uint8_t index = (uint8_t) value; + if (index > 127) index = 32; // not defined beyond index 127, so we use the space char + uint8_t v = charTable[index]; if (dp) { v |= 0b10000000; } @@ -250,7 +218,7 @@ void MAX7219::setChar(int digit, char value, bool dp, int addr) { } -void MAX7219::spiTransfer(int addr, volatile uint8_t opcode, volatile uint8_t data) { +void MAX7219::spiTransfer(int addr, volatile uint8_t opcode, volatile uint8_t data) { //Create an array with the data to shift out int offset = addr * 2; int maxbytes = maxDevices * 2; @@ -268,13 +236,13 @@ void MAX7219::spiTransfer(int addr, volatile uint8_t opcode, volatile uint8_t da } void MAX7219::setNumber(uint32_t number, int addr) { - //number = number % (uint32_t)pow(10, maxDevices); - for (auto i=0; i<8; i++) { - if (number == 0 && i > 0){ + // number = number % (uint32_t) pow(10, maxDevices); + for (uint8_t i = 0; i < 8; i++) { + if (number == 0 && i > 0) { setChar(i, ' ', addr); } else { - setDigit(i, number%10, addr); - number = number/10; + setDigit(i, number % 10, addr); + number = number / 10; } } } diff --git a/cpp_utils/MAX7219.h b/cpp_utils/MAX7219.h index b5ae41f4..d3faf353 100644 --- a/cpp_utils/MAX7219.h +++ b/cpp_utils/MAX7219.h @@ -50,18 +50,6 @@ * and setColumn(). */ class MAX7219 { -private: - - /* Send out a single command to the device */ - void spiTransfer(int addr, uint8_t opcode, uint8_t data); - - /* We keep track of the led-status for all 8 devices in this array */ - uint8_t status[64]; - - /* The maximum number of devices we use */ - int maxDevices; - SPI *spi; - public: /** * @brief Create a new %MAX7219 controller @@ -70,7 +58,7 @@ class MAX7219 { * @param [in] numDevices maximum number of devices that can be controlled that are * daisy chained together. */ - MAX7219(SPI *spi, int numDevices = 1); + MAX7219(SPI* spi, int numDevices = 1); /** @@ -101,7 +89,7 @@ class MAX7219 { * @param [in] dp Sets the decimal point. * @param [in] addr Address of the display. */ - void setChar(int digit, char value, bool dp=false, int addr=0); + void setChar(int digit, char value, bool dp=false, int addr = 0); /** @@ -111,7 +99,7 @@ class MAX7219 { * @param [in] value each bit set to 1 will light up the corresponding Led. * @param [in] addr address of the display. */ - void setColumn(int col, uint8_t value, int addr=0); + void setColumn(int col, uint8_t value, int addr = 0); /** @@ -122,7 +110,7 @@ class MAX7219 { * @param [in] dp Sets the decimal point. * @param [in] addr Address of the display. */ - void setDigit(int digit, uint8_t value, bool dp=false, int addr=0); + void setDigit(int digit, uint8_t value, bool dp=false, int addr = 0); /** @@ -131,7 +119,7 @@ class MAX7219 { * @param [in] intensity the brightness of the display. (0..15). * @param [in] addr The address of the display to control. */ - void setIntensity(int intensity, int addr=0); + void setIntensity(int intensity, int addr = 0); /** @@ -142,7 +130,7 @@ class MAX7219 { * @param [in] state If true the led is switched on, if false it is switched off. * @param [in] addr Address of the display. */ - void setLed(int row, int col, bool state, int addr=0); + void setLed(int row, int col, bool state, int addr = 0); /** @@ -153,7 +141,7 @@ class MAX7219 { * @param [in] number The number to display. * @param [in] addr Address of the display. */ - void setNumber(uint32_t number, int addr=0); + void setNumber(uint32_t number, int addr = 0); /** @@ -163,7 +151,7 @@ class MAX7219 { * @param [in] value Each bit set to 1 will light up the corresponding Led. * @param [in] addr Address of the display. */ - void setRow(int row, uint8_t value, int addr=0); + void setRow(int row, uint8_t value, int addr = 0); /** @@ -175,7 +163,7 @@ class MAX7219 { * @param [in] limit Number of digits to be displayed (1..8). * @param [in] addr Address of the display to control. */ - void setScanLimit(int limit, int addr=0); + void setScanLimit(int limit, int addr = 0); /** @@ -185,7 +173,18 @@ class MAX7219 { * for normal operation. * @param [in] addr The address of the display to control. */ - void shutdown(bool status, int addr=0); + void shutdown(bool status, int addr = 0); + +private: + /* Send out a single command to the device */ + void spiTransfer(int addr, uint8_t opcode, uint8_t data); + + /* We keep track of the led-status for all 8 devices in this array */ + uint8_t status[64]; + + /* The maximum number of devices we use */ + int maxDevices; + SPI* spi; }; diff --git a/cpp_utils/MFRC522.cpp b/cpp_utils/MFRC522.cpp index a860e9ba..6feb57c1 100644 --- a/cpp_utils/MFRC522.cpp +++ b/cpp_utils/MFRC522.cpp @@ -41,29 +41,29 @@ static const char LOG_TAG[] = "MFRC522"; /** * Writes a byte to the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. + * @param reg The register to write to. One of the PCD_Register enums. + * @param value The value to write. */ -void MFRC522::PCD_WriteRegister( - PCD_Register reg, ///< The register to write to. One of the PCD_Register enums. - byte value ///< The value to write. - ) { +void MFRC522::PCD_WriteRegister(PCD_Register reg, byte value) { uint8_t data[2]; data[0] = reg; data[1] = value; m_spi.transfer(data, 2); } // End PCD_WriteRegister() + /** * Writes a number of bytes to the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. + * @param reg The register to write to. One of the PCD_Register enums. + * @param count The number of bytes to write to the register + * @param values The values to write. Byte array. */ -void MFRC522::PCD_WriteRegister( PCD_Register reg, ///< The register to write to. One of the PCD_Register enums. - byte count, ///< The number of bytes to write to the register - byte *values ///< The values to write. Byte array. - ) { - uint8_t* pData = new uint8_t[count+1]; +void MFRC522::PCD_WriteRegister(PCD_Register reg, byte count, byte* values) { + uint8_t* pData = new uint8_t[count + 1]; pData[0] = reg; - memcpy(pData+1, values, count); - m_spi.transfer(pData, count+1); + memcpy(pData + 1, values, count); + m_spi.transfer(pData, count + 1); delete[] pData; } // End PCD_WriteRegister() @@ -71,9 +71,9 @@ void MFRC522::PCD_WriteRegister( PCD_Register reg, ///< The register to write to /** * Reads a byte from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. + * @param reg The register to read from. One of the PCD_Register enums. */ -byte MFRC522::PCD_ReadRegister( PCD_Register reg ///< The register to read from. One of the PCD_Register enums. - ) { +byte MFRC522::PCD_ReadRegister(PCD_Register reg) { uint8_t data[2]; data[0] = reg | 0x80; data[1] = 0; @@ -81,18 +81,18 @@ byte MFRC522::PCD_ReadRegister( PCD_Register reg ///< The register to read from. return data[1]; } // End PCD_ReadRegister() + /** * Reads a number of bytes from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. + * @param reg The register to read from. One of the PCD_Register enums. + * @param count The number of bytes to read + * @param values Byte array to store the values in. + * @param rxAlign Only bit positions rxAlign..7 in values[0] are updated. */ -void MFRC522::PCD_ReadRegister( PCD_Register reg, ///< The register to read from. One of the PCD_Register enums. - byte count, ///< The number of bytes to read - byte *values, ///< Byte array to store the values in. - byte rxAlign ///< Only bit positions rxAlign..7 in values[0] are updated. - ) { - if (count == 0) { - return; - } +void MFRC522::PCD_ReadRegister(PCD_Register reg, byte count, byte* values, byte rxAlign) { + if (count == 0) return; + //Serial.print(F("Reading ")); Serial.print(count); Serial.println(F(" bytes from register.")); byte address = 0x80 | reg; // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. byte index = 0; // Index in values array. @@ -114,23 +114,25 @@ void MFRC522::PCD_ReadRegister( PCD_Register reg, ///< The register to read from values[index] = m_spi.transferByte(0); // Read the final byte. Send 0 to stop reading. } // End PCD_ReadRegister() + /** * Sets the bits given in mask in register reg. + * @param reg The register to update. One of the PCD_Register enums. + * @param mask The bits to set. */ -void MFRC522::PCD_SetRegisterBitMask( PCD_Register reg, ///< The register to update. One of the PCD_Register enums. - byte mask ///< The bits to set. - ) { +void MFRC522::PCD_SetRegisterBitMask(PCD_Register reg, byte mask) { byte tmp; tmp = PCD_ReadRegister(reg); - PCD_WriteRegister(reg, tmp | mask); // set bit mask + PCD_WriteRegister(reg, tmp | mask); // set bit mask } // End PCD_SetRegisterBitMask() + /** * Clears the bits given in mask from register reg. + * @param reg The register to update. One of the PCD_Register enums. + * @param mask The bits to clear. */ -void MFRC522::PCD_ClearRegisterBitMask( PCD_Register reg, ///< The register to update. One of the PCD_Register enums. - byte mask ///< The bits to clear. - ) { +void MFRC522::PCD_ClearRegisterBitMask(PCD_Register reg, byte mask) { byte tmp; tmp = PCD_ReadRegister(reg); PCD_WriteRegister(reg, tmp & (~mask)); // clear bit mask @@ -139,13 +141,12 @@ void MFRC522::PCD_ClearRegisterBitMask( PCD_Register reg, ///< The register to u /** * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. - * + * @param data In: Pointer to the data to transfer to the FIFO for CRC calculation. + * @param length In: The number of bytes to transfer. + * @param result Out: Pointer to result buffer. Result is written to result[0..1], low byte first. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_CalculateCRC( byte *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. - byte length, ///< In: The number of bytes to transfer. - byte *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low byte first. - ) { +MFRC522::StatusCode MFRC522::PCD_CalculateCRC(byte *data, byte length, byte *result) { PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. PCD_WriteRegister(DivIrqReg, 0x04); // Clear the CRCIRq interrupt request bit PCD_WriteRegister(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization @@ -160,6 +161,7 @@ MFRC522::StatusCode MFRC522::PCD_CalculateCRC( byte *data, ///< In: Pointer to // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved byte n = PCD_ReadRegister(DivIrqReg); if (n & 0x04) { // CRCIRq bit set - calculation done + if (n & 0x04) { // CRCIRq bit set - calculation done PCD_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. // Transfer the result from the registers to the result buffer result[0] = PCD_ReadRegister(CRCResultRegL); @@ -172,9 +174,8 @@ MFRC522::StatusCode MFRC522::PCD_CalculateCRC( byte *data, ///< In: Pointer to } // End PCD_CalculateCRC() -///////////////////////////////////////////////////////////////////////////////////// // Functions for manipulating the MFRC522 -///////////////////////////////////////////////////////////////////////////////////// + /** * Initializes the MFRC522 chip. @@ -185,19 +186,17 @@ void MFRC522::PCD_Init() { bool hardReset = false; - // Set the chipSelectPin as digital output, do not select the slave yet -/* - pinMode(_chipSelectPin, OUTPUT); - digitalWrite(_chipSelectPin, HIGH); -*/ + // pinMode(_chipSelectPin, OUTPUT); + // digitalWrite(_chipSelectPin, HIGH); + // If a valid pin number has been set, pull device out of power down / reset state. if (_resetPowerDownPin != UNUSED_PIN) { // Set the resetPowerDownPin as digital output, do not reset or power down. //pinMode(_resetPowerDownPin, OUTPUT); ESP32CPP::GPIO::setInput((gpio_num_t)_resetPowerDownPin); - if (ESP32CPP::GPIO::read((gpio_num_t)_resetPowerDownPin) == false) { // The MFRC522 chip is in power down mode. + if (!ESP32CPP::GPIO::read((gpio_num_t)_resetPowerDownPin)) { // The MFRC522 chip is in power down mode. ESP32CPP::GPIO::setOutput((gpio_num_t)_resetPowerDownPin); ESP32CPP::GPIO::high((gpio_num_t)_resetPowerDownPin); // Exit power down mode. This triggers a hard reset. // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let us be generous: 50ms. @@ -221,27 +220,30 @@ void MFRC522::PCD_Init() { // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. PCD_WriteRegister(TModeReg, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds - PCD_WriteRegister(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. PCD_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. + PCD_WriteRegister(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. + PCD_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. PCD_WriteRegister(TReloadRegL, 0xE8); - PCD_WriteRegister(TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting + PCD_WriteRegister(TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting PCD_WriteRegister(ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) - PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) + PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) } // End PCD_Init() + /** * Initializes the MFRC522 chip. + * @param chipSelectPin Pin connected to MFRC522's SPI slave select input (Pin 24, NSS, active low) + * @param resetPowerDownPin Pin connected to MFRC522's reset and power down input (Pin 6, NRSTPD, active low) */ -void MFRC522::PCD_Init( byte chipSelectPin, ///< Arduino pin connected to MFRC522's SPI slave select input (Pin 24, NSS, active low) - byte resetPowerDownPin ///< Arduino pin connected to MFRC522's reset and power down input (Pin 6, NRSTPD, active low) - ) { +void MFRC522::PCD_Init(byte chipSelectPin, byte resetPowerDownPin) { _chipSelectPin = chipSelectPin; _resetPowerDownPin = resetPowerDownPin; // Set the chipSelectPin as digital output, do not select the slave yet PCD_Init(); } // End PCD_Init() + /** * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. */ @@ -252,11 +254,12 @@ void MFRC522::PCD_Reset() { // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let us be generous: 50ms. FreeRTOS::sleep(50); // Wait for the PowerDown bit in CommandReg to be cleared - while (PCD_ReadRegister(CommandReg) & (1<<4)) { + while (PCD_ReadRegister(CommandReg) & (1 << 4)) { // PCD still restarting - unlikely after waiting 50ms, but better safe than sorry. } } // End PCD_Reset() + /** * Turns the antenna on by enabling pins TX1 and TX2. * After a reset these pins are disabled. @@ -268,6 +271,7 @@ void MFRC522::PCD_AntennaOn() { } } // End PCD_AntennaOn() + /** * Turns the antenna off by disabling pins TX1 and TX2. */ @@ -275,6 +279,7 @@ void MFRC522::PCD_AntennaOff() { PCD_ClearRegisterBitMask(TxControlReg, 0x03); } // End PCD_AntennaOff() + /** * Get the current MFRC522 Receiver Gain (RxGain[2:0]) value. * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf @@ -283,21 +288,23 @@ void MFRC522::PCD_AntennaOff() { * @return Value of the RxGain, scrubbed to the 3 bits used. */ byte MFRC522::PCD_GetAntennaGain() { - return PCD_ReadRegister(RFCfgReg) & (0x07<<4); + return PCD_ReadRegister(RFCfgReg) & (0x07 << 4); } // End PCD_GetAntennaGain() + /** * Set the MFRC522 Receiver Gain (RxGain) to value specified by given mask. * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf * NOTE: Given mask is scrubbed with (0x07<<4)=01110000b as RCFfgReg may use reserved bits. */ void MFRC522::PCD_SetAntennaGain(byte mask) { - if (PCD_GetAntennaGain() != mask) { // only bother if there is a change - PCD_ClearRegisterBitMask(RFCfgReg, (0x07<<4)); // clear needed to allow 000 pattern + if (PCD_GetAntennaGain() != mask) { // only bother if there is a change + PCD_ClearRegisterBitMask(RFCfgReg, (0x07 << 4)); // clear needed to allow 000 pattern PCD_SetRegisterBitMask(RFCfgReg, mask & (0x07<<4)); // only set RxGain[2:0] bits } } // End PCD_SetAntennaGain() + /** * Performs a self-test of the MFRC522 * See 16.1.1 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf @@ -335,9 +342,7 @@ bool MFRC522::PCD_PerformSelfTest() { // It is reported that some devices does not trigger CRCIRq flag // during selftest. n = PCD_ReadRegister(FIFOLevelReg); - if (n >= 64) { - break; - } + if (n >= 64) break; } PCD_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. @@ -373,9 +378,7 @@ bool MFRC522::PCD_PerformSelfTest() { // Verify that the results match up to our expectations for (uint8_t i = 0; i < 64; i++) { - if (result[i] != (reference[i])) { - return false; - } + if (result[i] != (reference[i])) return false; } // Test passed; all is good. @@ -383,46 +386,48 @@ bool MFRC522::PCD_PerformSelfTest() { } // End PCD_PerformSelfTest() ///////////////////////////////////////////////////////////////////////////////////// + // Functions for communicating with PICCs ///////////////////////////////////////////////////////////////////////////////////// + /** * Executes the Transceive command. * CRC validation can only be done if backData and backLen are specified. - * + * @param sendData Pointer to the data to transfer to the FIFO. + * @param sendLen Number of bytes to transfer to the FIFO. + * @param backData nullptr or pointer to buffer if data should be read back after executing the command. + * @param backLen In: Max number of bytes to write to *backData. Out: The number of bytes returned. + * @param validBits In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. Default nullptr. + * @param rxAlign In: Defines the bit position in backData[0] for the first bit received. Default 0. + * @param checkCRC In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_TransceiveData( byte *sendData, ///< Pointer to the data to transfer to the FIFO. - byte sendLen, ///< Number of bytes to transfer to the FIFO. - byte *backData, ///< nullptr or pointer to buffer if data should be read back after executing the command. - byte *backLen, ///< In: Max number of bytes to write to *backData. Out: The number of bytes returned. - byte *validBits, ///< In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. Default nullptr. - byte rxAlign, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool checkCRC ///< In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. - ) { byte waitIRq = 0x30; // RxIRq and IdleIRq +MFRC522::StatusCode MFRC522::PCD_TransceiveData(byte* sendData, byte sendLen, byte* backData, byte* backLen, byte* validBits, byte rxAlign, bool checkCRC) { return PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, sendData, sendLen, backData, backLen, validBits, rxAlign, checkCRC); } // End PCD_TransceiveData() + /** * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. * CRC validation can only be done if backData and backLen are specified. * + * @param command The command to execute. One of the PCD_Command enums. + * @param waitIRq The bits in the ComIrqReg register that signals successful completion of the command. + * @param sendData Pointer to the data to transfer to the FIFO. + * @param sendLen Number of bytes to transfer to the FIFO. + * @param backData nullptr or pointer to buffer if data should be read back after executing the command. + * @param backLen In: Max number of bytes to write to *backData. Out: The number of bytes returned. + * @param validBits In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. + * @param rxAlign In: Defines the bit position in backData[0] for the first bit received. Default 0. + * @param checkCRC In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The command to execute. One of the PCD_Command enums. - byte waitIRq, ///< The bits in the ComIrqReg register that signals successful completion of the command. - byte *sendData, ///< Pointer to the data to transfer to the FIFO. - byte sendLen, ///< Number of bytes to transfer to the FIFO. - byte *backData, ///< nullptr or pointer to buffer if data should be read back after executing the command. - byte *backLen, ///< In: Max number of bytes to write to *backData. Out: The number of bytes returned. - byte *validBits, ///< In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. - byte rxAlign, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool checkCRC ///< In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. - ) { +MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC(byte command, byte waitIRq, byte* sendData, byte sendLen, byte* backData, byte* backLen, byte* validBits, byte rxAlign, bool checkCRC) { // Prepare values for BitFramingReg - byte txLastBits = validBits ? *validBits : 0; byte bitFraming = (rxAlign << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + byte txLastBits = validBits ? *validBits : (byte) 0; PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. PCD_WriteRegister(ComIrqReg, 0x7F); // Clear all seven interrupt request bits @@ -449,9 +454,7 @@ MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The co } } // 35.7ms and nothing happend. Communication with the MFRC522 might be down. - if (i == 0) { - return STATUS_TIMEOUT; - } + if (i == 0) return STATUS_TIMEOUT; // Stop now if any errors except collisions were detected. byte errorRegValue = PCD_ReadRegister(ErrorReg); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr @@ -464,10 +467,8 @@ MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The co // If the caller wants data back, get it from the MFRC522. if (backData && backLen) { byte n = PCD_ReadRegister(FIFOLevelReg); // Number of bytes in the FIFO - if (n > *backLen) { - return STATUS_NO_ROOM; - } - *backLen = n; // Number of bytes returned + if (n > *backLen) return STATUS_NO_ROOM; + *backLen = n; // Number of bytes returned PCD_ReadRegister(FIFODataReg, n, backData, rxAlign); // Get received data from FIFO _validBits = PCD_ReadRegister(ControlReg) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last received byte. If this value is 000b, the whole byte is valid. if (validBits) { @@ -483,19 +484,13 @@ MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The co // Perform CRC_A validation if requested. if (backData && backLen && checkCRC) { // In this case a MIFARE Classic NAK is not OK. - if (*backLen == 1 && _validBits == 4) { - return STATUS_MIFARE_NACK; - } + if (*backLen == 1 && _validBits == 4) return STATUS_MIFARE_NACK; // We need at least the CRC_A value and all 8 bits of the last byte must be received. - if (*backLen < 2 || _validBits != 0) { - return STATUS_CRC_WRONG; - } + if (*backLen < 2 || _validBits != 0) return STATUS_CRC_WRONG; // Verify CRC_A - do our own calculation and store the control in controlBuffer. byte controlBuffer[2]; MFRC522::StatusCode status = PCD_CalculateCRC(&backData[0], *backLen - 2, &controlBuffer[0]); - if (status != STATUS_OK) { - return status; - } + if (status != STATUS_OK) return status; if ((backData[*backLen - 2] != controlBuffer[0]) || (backData[*backLen - 1] != controlBuffer[1])) { return STATUS_CRC_WRONG; } @@ -504,58 +499,57 @@ MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The co return STATUS_OK; } // End PCD_CommunicateWithPICC() + /** * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. * + * @param bufferATQA The buffer to store the ATQA (Answer to request) in + * @param bufferSize Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PICC_RequestA( byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in - byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. - ) { +MFRC522::StatusCode MFRC522::PICC_RequestA(byte* bufferATQA, byte* bufferSize) { return PICC_REQA_or_WUPA(PICC_CMD_REQA, bufferATQA, bufferSize); } // End PICC_RequestA() + /** * Transmits a Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. * + * @param bufferATQA The buffer to store the ATQA (Answer to request) in + * @param bufferSize Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PICC_WakeupA( byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in - byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. - ) { +MFRC522::StatusCode MFRC522::PICC_WakeupA(byte* bufferATQA, byte* bufferSize) { return PICC_REQA_or_WUPA(PICC_CMD_WUPA, bufferATQA, bufferSize); } // End PICC_WakeupA() + /** * Transmits REQA or WUPA commands. * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. * + * @param command The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + * @param bufferATQA The buffer to store the ATQA (Answer to request) in + * @param bufferSize Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PICC_REQA_or_WUPA( byte command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA - byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in - byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. - ) { +MFRC522::StatusCode MFRC522::PICC_REQA_or_WUPA(byte command, byte* bufferATQA, byte* bufferSize) { byte validBits; MFRC522::StatusCode status; - if (bufferATQA == nullptr || *bufferSize < 2) { // The ATQA response is 2 bytes long. - return STATUS_NO_ROOM; - } PCD_ClearRegisterBitMask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. validBits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) byte. TxLastBits = BitFramingReg[2..0] + if (bufferATQA == nullptr || *bufferSize < 2) return STATUS_NO_ROOM; // The ATQA response is 2 bytes long. status = PCD_TransceiveData(&command, 1, bufferATQA, bufferSize, &validBits); - if (status != STATUS_OK) { - return status; - } - if (*bufferSize != 2 || validBits != 0) { // ATQA must be exactly 16 bits. - return STATUS_ERROR; - } + if (status != STATUS_OK) return status; + + if (*bufferSize != 2 || validBits != 0) return STATUS_ERROR; // ATQA must be exactly 16 bits. return STATUS_OK; } // End PICC_REQA_or_WUPA() + /** * Transmits SELECT/ANTICOLLISION commands to select a single PICC. * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or PICC_WakeupA(). @@ -573,9 +567,7 @@ MFRC522::StatusCode MFRC522::PICC_REQA_or_WUPA( byte command, ///< The command * * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. - byte validBits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply uid->size. - ) { +MFRC522::StatusCode MFRC522::PICC_Select(Uid* uid, byte validBits) { bool uidComplete; bool selectDone; bool useCascadeTag; @@ -589,7 +581,7 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct byte bufferUsed; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO. byte rxAlign; // Used in BitFramingReg. Defines the bit position for the first bit received. byte txLastBits; // Used in BitFramingReg. The number of valid bits in the last transmitted byte. - byte *responseBuffer; + byte* responseBuffer; byte responseLength; // Description of buffer structure: @@ -615,9 +607,7 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct // 3 uid6 uid7 uid8 uid9 // Sanity checks - if (validBits > 80) { - return STATUS_INVALID; - } + if (validBits > 80) return STATUS_INVALID; // Prepare MFRC522 PCD_ClearRegisterBitMask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. @@ -632,22 +622,18 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct uidIndex = 0; useCascadeTag = validBits && uid->size > 4; // When we know that the UID has more than 4 bytes break; - case 2: buffer[0] = PICC_CMD_SEL_CL2; uidIndex = 3; useCascadeTag = validBits && uid->size > 7; // When we know that the UID has more than 7 bytes break; - case 3: buffer[0] = PICC_CMD_SEL_CL3; uidIndex = 6; useCascadeTag = false; // Never used in CL3. break; - default: return STATUS_INTERNAL_ERROR; - break; } // How many UID bits are known in this Cascade Level? @@ -686,16 +672,13 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; // Calculate CRC_A result = PCD_CalculateCRC(buffer, 7, &buffer[7]); - if (result != STATUS_OK) { - return result; - } txLastBits = 0; // 0 => All 8 bits are valid. bufferUsed = 9; + if (result != STATUS_OK) return result; // Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx) responseBuffer = &buffer[6]; responseLength = 3; - } - else { // This is an ANTICOLLISION. + } else { // This is an ANTICOLLISION. //Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); txLastBits = currentLevelKnownBits % 8; count = currentLevelKnownBits / 8; // Number of whole bytes in the UID part. @@ -715,31 +698,25 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct result = PCD_TransceiveData(buffer, bufferUsed, responseBuffer, &responseLength, &txLastBits, rxAlign); if (result == STATUS_COLLISION) { // More than one PICC in the field => collision. byte valueOfCollReg = PCD_ReadRegister(CollReg); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] - if (valueOfCollReg & 0x20) { // CollPosNotValid - return STATUS_COLLISION; // Without a valid collision position we cannot continue - } + if (valueOfCollReg & 0x20) return STATUS_COLLISION; // CollPosNotValid, Without a valid collision position we cannot continue byte collisionPos = valueOfCollReg & 0x1F; // Values 0-31, 0 means bit 32. if (collisionPos == 0) { collisionPos = 32; } - if (collisionPos <= currentLevelKnownBits) { // No progress - should not happen - return STATUS_INTERNAL_ERROR; - } + if (collisionPos <= currentLevelKnownBits) return STATUS_INTERNAL_ERROR; // No progress - should not happen + // Choose the PICC with the bit set. currentLevelKnownBits = collisionPos; count = (currentLevelKnownBits - 1) % 8; // The bit to modify index = 1 + (currentLevelKnownBits / 8) + (count ? 1 : 0); // First byte is index 0. buffer[index] |= (1 << count); } - else if (result != STATUS_OK) { - return result; - } + else if (result != STATUS_OK) return result; else { // STATUS_OK if (currentLevelKnownBits >= 32) { // This was a SELECT. selectDone = true; // No more anticollision // We continue below outside the while. - } - else { // This was an ANTICOLLISION. + } else { // This was an ANTICOLLISION. // We now have all 32 bits of the UID in this Cascade Level currentLevelKnownBits = 32; // Run loop again to do the SELECT. @@ -757,21 +734,18 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct } // Check response SAK (Select Acknowledge) - if (responseLength != 3 || txLastBits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A). - return STATUS_ERROR; - } + if (responseLength != 3 || txLastBits != 0) return STATUS_ERROR; // SAK must be exactly 24 bits (1 byte + CRC_A). + // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed anymore. result = PCD_CalculateCRC(responseBuffer, 1, &buffer[2]); - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; + if ((buffer[2] != responseBuffer[1]) || (buffer[3] != responseBuffer[2])) { return STATUS_CRC_WRONG; } if (responseBuffer[0] & 0x04) { // Cascade bit set - UID not complete yes cascadeLevel++; - } - else { + } else { uidComplete = true; uid->sak = responseBuffer[0]; } @@ -783,6 +757,7 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct return STATUS_OK; } // End PICC_Select() + /** * Instructs a PICC in state ACTIVE(*) to go to state HALT. * @@ -797,9 +772,7 @@ MFRC522::StatusCode MFRC522::PICC_HaltA() { buffer[1] = 0; // Calculate CRC_A result = PCD_CalculateCRC(buffer, 2, &buffer[2]); - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; // Send the command. // The standard says: @@ -807,12 +780,10 @@ MFRC522::StatusCode MFRC522::PICC_HaltA() { // HLTA command, this response shall be interpreted as 'not acknowledge'. // We interpret that this way: Only STATUS_TIMEOUT is a success. result = PCD_TransceiveData(buffer, sizeof(buffer), nullptr, 0); - if (result == STATUS_TIMEOUT) { - return STATUS_OK; - } - if (result == STATUS_OK) { // That is ironically NOT ok in this case ;-) - return STATUS_ERROR; - } + + if (result == STATUS_TIMEOUT) return STATUS_OK; + if (result == STATUS_OK) return STATUS_ERROR; // That is ironically NOT ok in this case ;-) + return result; } // End PICC_HaltA() @@ -820,6 +791,7 @@ MFRC522::StatusCode MFRC522::PICC_HaltA() { // Functions for communicating with MIFARE PICCs ///////////////////////////////////////////////////////////////////////////////////// + /** * Executes the MFRC522 MFAuthent command. * This command manages MIFARE authentication to enable a secure communication to any MIFARE Mini, MIFARE 1K and MIFARE 4K card. @@ -830,34 +802,35 @@ MFRC522::StatusCode MFRC522::PICC_HaltA() { * * All keys are set to FFFFFFFFFFFFh at chip delivery. * + * @param command PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B + * @param blockAddr The block number. See numbering in the comments in the .h file. + * @param key Pointer to the Crypteo1 key to use (6 bytes) + * @param uid Pointer to Uid struct. The first 4 bytes of the UID is used. * @return STATUS_OK on success, STATUS_??? otherwise. Probably STATUS_TIMEOUT if you supply the wrong key. */ -MFRC522::StatusCode MFRC522::PCD_Authenticate(byte command, ///< PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B - byte blockAddr, ///< The block number. See numbering in the comments in the .h file. - MIFARE_Key *key, ///< Pointer to the Crypteo1 key to use (6 bytes) - Uid *uid ///< Pointer to Uid struct. The first 4 bytes of the UID is used. - ) { byte waitIRq = 0x10; // IdleIRq +MFRC522::StatusCode MFRC522::PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key* key, Uid* uid) { // Build command buffer byte sendData[12]; sendData[0] = command; sendData[1] = blockAddr; for (byte i = 0; i < MF_KEY_SIZE; i++) { // 6 key bytes - sendData[2+i] = key->keyByte[i]; + sendData[2 + i] = key->keyByte[i]; } // Use the last uid bytes as specified in http://cache.nxp.com/documents/application_note/AN10927.pdf // section 3.2.5 "MIFARE Classic Authentication". // The only missed case is the MF1Sxxxx shortcut activation, // but it requires cascade tag (CT) byte, that is not part of uid. - for (byte i = 0; i < 4; i++) { // The last 4 bytes of the UID - sendData[8+i] = uid->uidByte[i+uid->size-4]; + for (byte i = 0; i < 4; i++) { // The last 4 bytes of the UID + sendData[8 + i] = uid->uidByte[i + uid->size - 4]; } // Start the authentication. return PCD_CommunicateWithPICC(PCD_MFAuthent, waitIRq, &sendData[0], sizeof(sendData)); } // End PCD_Authenticate() + /** * Used to exit the PCD from its authenticated state. * Remember to call this function after communicating with an authenticated PICC - otherwise no new communications can start. @@ -867,6 +840,7 @@ void MFRC522::PCD_StopCrypto1() { PCD_ClearRegisterBitMask(Status2Reg, 0x08); // Status2Reg[7..0] bits are: TempSensClear I2CForceHS reserved reserved MFCrypto1On ModemState[2:0] } // End PCD_StopCrypto1() + /** * Reads 16 bytes (+ 2 bytes CRC_A) from the active PICC. * @@ -881,32 +855,29 @@ void MFRC522::PCD_StopCrypto1() { * The buffer must be at least 18 bytes because a CRC_A is also returned. * Checks the CRC_A before returning STATUS_OK. * + * @param blockAddr MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The first page to return data from. + * @param buffer The buffer to store the data in + * @param bufferSize Buffer size, at least 18 bytes. Also number of bytes returned if STATUS_OK. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Read( byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The first page to return data from. - byte *buffer, ///< The buffer to store the data in - byte *bufferSize ///< Buffer size, at least 18 bytes. Also number of bytes returned if STATUS_OK. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Read(byte blockAddr, byte* buffer, byte* bufferSize) { MFRC522::StatusCode result; // Sanity check - if (buffer == nullptr || *bufferSize < 18) { - return STATUS_NO_ROOM; - } + if (buffer == nullptr || *bufferSize < 18) return STATUS_NO_ROOM; // Build command buffer buffer[0] = PICC_CMD_MF_READ; buffer[1] = blockAddr; // Calculate CRC_A result = PCD_CalculateCRC(buffer, 2, &buffer[2]); - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; // Transmit the buffer and receive the response, validate CRC_A. return PCD_TransceiveData(buffer, 4, buffer, bufferSize, nullptr, 0, true); } // End MIFARE_Read() + /** * Writes 16 bytes to the active PICC. * @@ -915,19 +886,16 @@ MFRC522::StatusCode MFRC522::MIFARE_Read( byte blockAddr, ///< MIFARE Classic: * For MIFARE Ultralight the operation is called "COMPATIBILITY WRITE". * Even though 16 bytes are transferred to the Ultralight PICC, only the least significant 4 bytes (bytes 0 to 3) * are written to the specified address. It is recommended to set the remaining bytes 04h to 0Fh to all logic 0. - * * + * @param blockAddr MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The page (2-15) to write to. + * @param buffer The 16 bytes to write to the PICC + * @param bufferSize Buffer size, must be at least 16 bytes. Exactly 16 bytes are written. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Write( byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The page (2-15) to write to. - byte *buffer, ///< The 16 bytes to write to the PICC - byte bufferSize ///< Buffer size, must be at least 16 bytes. Exactly 16 bytes are written. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Write(byte blockAddr, byte* buffer, byte bufferSize) { MFRC522::StatusCode result; // Sanity check - if (buffer == nullptr || bufferSize < 16) { - return STATUS_INVALID; - } + if (buffer == nullptr || bufferSize < 16) return STATUS_INVALID; // Mifare Classic protocol requires two communications to perform a write. // Step 1: Tell the PICC we want to write to block blockAddr. @@ -935,34 +903,28 @@ MFRC522::StatusCode MFRC522::MIFARE_Write( byte blockAddr, ///< MIFARE Classic: cmdBuffer[0] = PICC_CMD_MF_WRITE; cmdBuffer[1] = blockAddr; result = PCD_MIFARE_Transceive(cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; // Step 2: Transfer the data result = PCD_MIFARE_Transceive(buffer, bufferSize); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; return STATUS_OK; } // End MIFARE_Write() + /** * Writes a 4 byte page to the active MIFARE Ultralight PICC. - * + * @param page The page (2-15) to write to. + * @param buffer The 4 bytes to write to the PICC + * @param bufferSize Buffer size, must be at least 4 bytes. Exactly 4 bytes are written. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Ultralight_Write( byte page, ///< The page (2-15) to write to. - byte *buffer, ///< The 4 bytes to write to the PICC - byte bufferSize ///< Buffer size, must be at least 4 bytes. Exactly 4 bytes are written. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Ultralight_Write(byte page, byte* buffer, byte bufferSize) { MFRC522::StatusCode result; // Sanity check - if (buffer == nullptr || bufferSize < 4) { - return STATUS_INVALID; - } + if (buffer == nullptr || bufferSize < 4) return STATUS_INVALID; // Build commmand buffer byte cmdBuffer[6]; @@ -972,106 +934,105 @@ MFRC522::StatusCode MFRC522::MIFARE_Ultralight_Write( byte page, ///< The page // Perform the write result = PCD_MIFARE_Transceive(cmdBuffer, 6); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; + return STATUS_OK; } // End MIFARE_Ultralight_Write() + /** * MIFARE Decrement subtracts the delta from the value of the addressed block, and stores the result in a volatile memory. * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. * Use MIFARE_Transfer() to store the result in a block. + * @param blockAddr The block (0-0xff) number. + * @param delta This number is subtracted from the value of block blockAddr. * * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Decrement( byte blockAddr, ///< The block (0-0xff) number. - int32_t delta ///< This number is subtracted from the value of block blockAddr. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Decrement(byte blockAddr, int32_t delta) { return MIFARE_TwoStepHelper(PICC_CMD_MF_DECREMENT, blockAddr, delta); } // End MIFARE_Decrement() + /** * MIFARE Increment adds the delta to the value of the addressed block, and stores the result in a volatile memory. * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. * Use MIFARE_Transfer() to store the result in a block. * + * @param blockAddr The block (0-0xff) number. + * @param delta This number is added to the value of block blockAddr. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Increment( byte blockAddr, ///< The block (0-0xff) number. - int32_t delta ///< This number is added to the value of block blockAddr. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Increment(byte blockAddr, int32_t delta) { return MIFARE_TwoStepHelper(PICC_CMD_MF_INCREMENT, blockAddr, delta); } // End MIFARE_Increment() + /** * MIFARE Restore copies the value of the addressed block into a volatile memory. * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. * Use MIFARE_Transfer() to store the result in a block. * + * @param blockAddr The block (0-0xff) number. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Restore( byte blockAddr ///< The block (0-0xff) number. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Restore(byte blockAddr) { // The datasheet describes Restore as a two step operation, but does not explain what data to transfer in step 2. // Doing only a single step does not work, so I chose to transfer 0L in step two. return MIFARE_TwoStepHelper(PICC_CMD_MF_RESTORE, blockAddr, 0L); } // End MIFARE_Restore() + /** * Helper function for the two-step MIFARE Classic protocol operations Decrement, Increment and Restore. * + * @param command The command to use + * @param blockAddr The block (0-0xff) number. + * @param data The data to transfer in step 2 * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_TwoStepHelper( byte command, ///< The command to use - byte blockAddr, ///< The block (0-0xff) number. - int32_t data ///< The data to transfer in step 2 - ) { +MFRC522::StatusCode MFRC522::MIFARE_TwoStepHelper(byte command, byte blockAddr, int32_t data) { MFRC522::StatusCode result; byte cmdBuffer[2]; // We only need room for 2 bytes. // Step 1: Tell the PICC the command and block address cmdBuffer[0] = command; cmdBuffer[1] = blockAddr; - result = PCD_MIFARE_Transceive( cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + result = PCD_MIFARE_Transceive(cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) return result; // Step 2: Transfer the data - result = PCD_MIFARE_Transceive( (byte *)&data, 4, true); // Adds CRC_A and accept timeout as success. - if (result != STATUS_OK) { - return result; - } + result = PCD_MIFARE_Transceive((byte*) &data, 4, true); // Adds CRC_A and accept timeout as success. + if (result != STATUS_OK) return result; return STATUS_OK; } // End MIFARE_TwoStepHelper() + /** * MIFARE Transfer writes the value stored in the volatile memory into one MIFARE Classic block. * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. * + * @param blockAddr The block (0-0xff) number. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Transfer( byte blockAddr ///< The block (0-0xff) number. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Transfer(byte blockAddr) { MFRC522::StatusCode result; byte cmdBuffer[2]; // We only need room for 2 bytes. // Tell the PICC we want to transfer the result into block blockAddr. cmdBuffer[0] = PICC_CMD_MF_TRANSFER; cmdBuffer[1] = blockAddr; - result = PCD_MIFARE_Transceive( cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + result = PCD_MIFARE_Transceive(cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) return result; return STATUS_OK; } // End MIFARE_Transfer() + /** * Helper routine to read the current value from a Value Block. * @@ -1083,7 +1044,7 @@ MFRC522::StatusCode MFRC522::MIFARE_Transfer( byte blockAddr ///< The block (0-0 * @param[out] value Current value of the Value Block. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_GetValue(byte blockAddr, int32_t *value) { +MFRC522::StatusCode MFRC522::MIFARE_GetValue(byte blockAddr, int32_t* value) { MFRC522::StatusCode status; byte buffer[18]; byte size = sizeof(buffer); @@ -1092,11 +1053,12 @@ MFRC522::StatusCode MFRC522::MIFARE_GetValue(byte blockAddr, int32_t *value) { status = MIFARE_Read(blockAddr, buffer, &size); if (status == STATUS_OK) { // Extract the value - *value = (int32_t(buffer[3])<<24) | (int32_t(buffer[2])<<16) | (int32_t(buffer[1])<<8) | int32_t(buffer[0]); + *value = (int32_t(buffer[3]) << 24) | (int32_t(buffer[2]) << 16) | (int32_t(buffer[1]) << 8) | int32_t(buffer[0]); } return status; } // End MIFARE_GetValue() + /** * Helper routine to write a specific value into a Value Block. * @@ -1129,6 +1091,7 @@ MFRC522::StatusCode MFRC522::MIFARE_SetValue(byte blockAddr, int32_t value) { return MIFARE_Write(blockAddr, buffer, 16); } // End MIFARE_SetValue() + /** * Authenticate with a NTAG216. * @@ -1138,8 +1101,7 @@ MFRC522::StatusCode MFRC522::MIFARE_SetValue(byte blockAddr, int32_t value) { * @param[in] pACK result success???. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) //Authenticate with 32bit password -{ +MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) { //Authenticate with 32bit password // TODO: Fix cmdBuffer length and rxlength. They really should match. // (Better still, rxlength should not even be necessary.) @@ -1148,14 +1110,13 @@ MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) //Aut cmdBuffer[0] = 0x1B; //Comando de autentificacion - for (byte i = 0; i<4; i++) - cmdBuffer[i+1] = passWord[i]; + for (byte i = 0; i < 4; i++) { + cmdBuffer[i + 1] = passWord[i]; + } result = PCD_CalculateCRC(cmdBuffer, 5, &cmdBuffer[5]); - if (result!=STATUS_OK) { - return result; - } + if (result!=STATUS_OK) return result; // Transceive the data, store the reply in cmdBuffer[] byte waitIRq = 0x30; // RxIRq and IdleIRq @@ -1167,9 +1128,7 @@ MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) //Aut pACK[0] = cmdBuffer[0]; pACK[1] = cmdBuffer[1]; - if (result!=STATUS_OK) { - return result; - } + if (result!=STATUS_OK) return result; return STATUS_OK; } // End PCD_NTAG216_AUTH() @@ -1179,30 +1138,27 @@ MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) //Aut // Support functions ///////////////////////////////////////////////////////////////////////////////////// + /** * Wrapper for MIFARE protocol communication. * Adds CRC_A, executes the Transceive command and checks that the response is MF_ACK or a timeout. * + * @param sendData Pointer to the data to transfer to the FIFO. Do NOT include the CRC_A. + * @param sendLen Number of bytes in sendData. + * @param acceptTimeout True => A timeout is also success * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_MIFARE_Transceive( byte *sendData, ///< Pointer to the data to transfer to the FIFO. Do NOT include the CRC_A. - byte sendLen, ///< Number of bytes in sendData. - bool acceptTimeout ///< True => A timeout is also success - ) { +MFRC522::StatusCode MFRC522::PCD_MIFARE_Transceive(byte* sendData, byte sendLen, bool acceptTimeout) { MFRC522::StatusCode result; byte cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. // Sanity check - if (sendData == nullptr || sendLen > 16) { - return STATUS_INVALID; - } + if (sendData == nullptr || sendLen > 16) return STATUS_INVALID; // Copy sendData[] to cmdBuffer[] and add CRC_A memcpy(cmdBuffer, sendData, sendLen); result = PCD_CalculateCRC(cmdBuffer, sendLen, &cmdBuffer[sendLen]); - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; sendLen += 2; // Transceive the data, store the reply in cmdBuffer[] @@ -1210,19 +1166,11 @@ MFRC522::StatusCode MFRC522::PCD_MIFARE_Transceive( byte *sendData, ///< Pointe byte cmdBufferSize = sizeof(cmdBuffer); byte validBits = 0; result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, sendLen, cmdBuffer, &cmdBufferSize, &validBits); - if (acceptTimeout && result == STATUS_TIMEOUT) { - return STATUS_OK; - } - if (result != STATUS_OK) { - return result; - } + if (acceptTimeout && result == STATUS_TIMEOUT) return STATUS_OK; + if (result != STATUS_OK) return result; // The PICC must reply with a 4 bit ACK - if (cmdBufferSize != 1 || validBits != 4) { - return STATUS_ERROR; - } - if (cmdBuffer[0] != MF_ACK) { - return STATUS_MIFARE_NACK; - } + if (cmdBufferSize != 1 || validBits != 4) return STATUS_ERROR; + if (cmdBuffer[0] != MF_ACK) return STATUS_MIFARE_NACK; return STATUS_OK; } // End PCD_MIFARE_Transceive() @@ -1230,10 +1178,10 @@ MFRC522::StatusCode MFRC522::PCD_MIFARE_Transceive( byte *sendData, ///< Pointe /** * Translates the SAK (Select Acknowledge) to a PICC type. * + * @param sak The SAK byte returned from PICC_Select(). * @return PICC_Type */ -MFRC522::PICC_Type MFRC522::PICC_GetType(byte sak ///< The SAK byte returned from PICC_Select(). - ) { +MFRC522::PICC_Type MFRC522::PICC_GetType(byte sak) { // http://www.nxp.com/documents/application_note/AN10833.pdf // 3.2 Coding of Select Acknowledge (SAK) // ignore 8-bit (iso14443 starts with LSBit = bit 1) @@ -1276,19 +1224,21 @@ void MFRC522::PCD_DumpVersionToSerial() { ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); // When 0x00 or 0xFF is returned, communication probably failed - if ((v == 0x00) || (v == 0xFF)) + if ((v == 0x00) || (v == 0xFF)) { ESP_LOGD(LOG_TAG, "WARNING: Communication failure, is the MFRC522 properly connected?"); + } } // End PCD_DumpVersionToSerial() + /** * Dumps debug info about the selected PICC to Serial. * On success the PICC is halted after dumping the data. * For MIFARE Classic the factory default key of 0xFFFFFFFFFFFF is tried. * + * @param uid Pointer to Uid struct returned from a successful PICC_Select(). * @DEPRECATED Kept for bakward compatibility */ -void MFRC522::PICC_DumpToSerial(Uid *uid ///< Pointer to Uid struct returned from a successful PICC_Select(). - ) { +void MFRC522::PICC_DumpToSerial(Uid* uid) { MIFARE_Key key; // Dump UID, SAK and Type @@ -1325,17 +1275,17 @@ void MFRC522::PICC_DumpToSerial(Uid *uid ///< Pointer to Uid struct returned fro break; // No memory dump here } - ESP_LOGD(LOG_TAG,""); PICC_HaltA(); // Already done if it was a MIFARE Classic PICC. } // End PICC_DumpToSerial() + /** * Dumps card info (UID,SAK,Type) about the selected PICC to Serial. * + * @param uid Pointer to Uid struct returned from a successful PICC_Select(). * @DEPRECATED kept for backward compatibility */ -void MFRC522::PICC_DumpDetailsToSerial(Uid *uid ///< Pointer to Uid struct returned from a successful PICC_Select(). - ) { +void MFRC522::PICC_DumpDetailsToSerial(Uid* uid) { // UID std::ostringstream oss; oss << std::hex << std::setfill('0'); @@ -1351,7 +1301,6 @@ void MFRC522::PICC_DumpDetailsToSerial(Uid *uid ///< Pointer to Uid struct retur oss << "" << "Card SAK: " << std::setw(2) << (int)uid->sak; ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); - // (suggested) PICC type PICC_Type piccType = PICC_GetType(uid->sak); oss.str(""); @@ -1359,31 +1308,30 @@ void MFRC522::PICC_DumpDetailsToSerial(Uid *uid ///< Pointer to Uid struct retur ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); } // End PICC_DumpDetailsToSerial() + /** * Dumps memory contents of a MIFARE Classic PICC. * On success the PICC is halted after dumping the data. + * + * @param uid Pointer to Uid struct returned from a successful PICC_Select(). + * @param piccType One of the PICC_Type enums. + * @param key Key A used for all sectors. */ -void MFRC522::PICC_DumpMifareClassicToSerial( Uid *uid, ///< Pointer to Uid struct returned from a successful PICC_Select(). - PICC_Type piccType, ///< One of the PICC_Type enums. - MIFARE_Key *key ///< Key A used for all sectors. - ) { +void MFRC522::PICC_DumpMifareClassicToSerial(Uid* uid, PICC_Type piccType, MIFARE_Key* key) { byte no_of_sectors = 0; switch (piccType) { case PICC_TYPE_MIFARE_MINI: // Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes. no_of_sectors = 5; break; - case PICC_TYPE_MIFARE_1K: // Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes. no_of_sectors = 16; break; - case PICC_TYPE_MIFARE_4K: // Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes. no_of_sectors = 40; break; - default: // Should not happen. Ignore. break; } @@ -1399,15 +1347,17 @@ void MFRC522::PICC_DumpMifareClassicToSerial( Uid *uid, ///< Pointer to Uid st PCD_StopCrypto1(); } // End PICC_DumpMifareClassicToSerial() + /** * Dumps memory contents of a sector of a MIFARE Classic PICC. * Uses PCD_Authenticate(), MIFARE_Read() and PCD_StopCrypto1. * Always uses PICC_CMD_MF_AUTH_KEY_A because only Key A can always read the sector trailer access bits. + * + * @param uid Pointer to Uid struct returned from a successful PICC_Select(). + * @param key Key A for the sector. + * @param sector The sector to dump, 0..39. */ -void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to Uid struct returned from a successful PICC_Select(). - MIFARE_Key *key, ///< Key A for the sector. - byte sector ///< The sector to dump, 0..39. - ) { +void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid* uid, MIFARE_Key* key, byte sector) { MFRC522::StatusCode status; byte firstBlock; // Address of lowest address to dump actually last block dumped) byte no_of_blocks; // Number of blocks in sector @@ -1436,8 +1386,7 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U else if (sector < 40) { // Sectors 32-39 has 16 blocks each no_of_blocks = 16; firstBlock = 128 + (sector - 32) * no_of_blocks; - } - else { // Illegal input, no MIFARE Classic PICC has more than 40 sectors. + } else { // Illegal input, no MIFARE Classic PICC has more than 40 sectors. return; } @@ -1452,28 +1401,23 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U // Sector number - only on first line std::ostringstream oss; if (isSectorTrailer) { - if(sector < 10) { + if (sector < 10) { oss << " "; // Pad with spaces - } - else { + } else { oss << " "; // Pad with spaces } - oss << (int)sector; - + oss << (int) sector; oss << " "; - } - else { oss << " "; + } else { } // Block number - if(blockAddr < 10) { + if (blockAddr < 10) { oss << " "; // Pad with spaces - } - else { - if(blockAddr < 100) { + } else { + if (blockAddr < 100) { oss << " "; // Pad with spaces - } - else { + } else { oss << " "; // Pad with spaces } } @@ -1502,7 +1446,6 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U oss << std::hex << std::setfill('0'); for (byte index = 0; index < 16; index++) { oss << " " << std::setw(2) << (int)buffer[index]; - if ((index % 4) == 3) { oss << " "; } @@ -1527,8 +1470,7 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U if (no_of_blocks == 4) { group = blockOffset; firstInGroup = true; - } - else { + } else { group = blockOffset / 5; firstInGroup = (group == 3) || (group != (blockOffset + 1) / 5); } @@ -1536,15 +1478,15 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U if (firstInGroup) { // Print access bits oss << std::dec; - oss << " [ " << (int)((g[group] >> 2) & 1) << " " << (int)((g[group] >> 1) & 1) << " " << (int) ((g[group] >> 0) & 1) << " ] "; + oss << " [ " << ((g[group] >> 2) & 1) << " " << ((g[group] >> 1) & 1) << " " << ((g[group] >> 0) & 1) << " ] "; if (invertedError) { oss << " Inverted access bits did not match! "; } } if (group != 3 && (g[group] == 1 || g[group] == 6)) { // Not a sector trailer, a value block - int32_t value = (int32_t(buffer[3])<<24) | (int32_t(buffer[2])<<16) | (int32_t(buffer[1])<<8) | int32_t(buffer[0]); - oss << " Value=0x" << std::hex << value << " Adr=0x" << (int)buffer[12]; + int32_t value = (int32_t(buffer[3]) << 24) | (int32_t(buffer[2]) << 16) | (int32_t(buffer[1]) << 8) | int32_t(buffer[0]); + oss << " Value=0x" << std::hex << value << " Adr=0x" << (int) buffer[12]; } ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); } @@ -1552,6 +1494,7 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U return; } // End PICC_DumpMifareClassicSectorToSerial() + /** * Dumps memory contents of a MIFARE Ultralight PICC. */ @@ -1564,7 +1507,7 @@ void MFRC522::PICC_DumpMifareUltralightToSerial() { std::ostringstream oss; oss << "Page 0 1 2 3"; // Try the mpages of the original Ultralight. Ultralight C has more pages. - for (byte page = 0; page < 16; page +=4) { // Read returns data for 4 pages at a time. + for (byte page = 0; page < 16; page += 4) { // Read returns data for 4 pages at a time. // Read pages byteCount = sizeof(buffer); status = MIFARE_Read(page, buffer, &byteCount); @@ -1582,22 +1525,24 @@ void MFRC522::PICC_DumpMifareUltralightToSerial() { for (byte index = 0; index < 4; index++) { i = 4 * offset + index; - oss << std::hex << std::setw(2) << (int)buffer[i]; + oss << std::hex << std::setw(2) << (int) buffer[i]; } ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); } } } // End PICC_DumpMifareUltralightToSerial() + /** * Calculates the bit pattern needed for the specified access bits. In the [C1 C2 C3] tuples C1 is MSB (=4) and C3 is LSB (=1). + * + * @param accessBitBuffer Pointer to byte 6, 7 and 8 in the sector trailer. Bytes [0..2] will be set. + * @param g0 Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) + * @param g1 Access bits C1 C2 C3] for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) + * @param g2 Access bits C1 C2 C3] for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) + * @param g3 Access bits C1 C2 C3] for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) */ -void MFRC522::MIFARE_SetAccessBits( byte *accessBitBuffer, ///< Pointer to byte 6, 7 and 8 in the sector trailer. Bytes [0..2] will be set. - byte g0, ///< Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) - byte g1, ///< Access bits C1 C2 C3] for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) - byte g2, ///< Access bits C1 C2 C3] for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) - byte g3 ///< Access bits C1 C2 C3] for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) - ) { +void MFRC522::MIFARE_SetAccessBits(byte* accessBitBuffer, byte g0, byte g1, byte g2, byte g3) { byte c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2); byte c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1); byte c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0); @@ -1611,6 +1556,7 @@ void MFRC522::MIFARE_SetAccessBits( byte *accessBitBuffer, ///< Pointer to byte // Convenience functions - does not add extra functionality ///////////////////////////////////////////////////////////////////////////////////// + /** * Returns true if a PICC responds to PICC_CMD_REQA. * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. @@ -1631,6 +1577,7 @@ bool MFRC522::PICC_IsNewCardPresent() { return (result == STATUS_OK || result == STATUS_COLLISION); } // End PICC_IsNewCardPresent() + /** * Simple wrapper around PICC_Select. * Returns true if a UID could be read. diff --git a/cpp_utils/MFRC522.h b/cpp_utils/MFRC522.h index 2e060712..9e03ba4f 100644 --- a/cpp_utils/MFRC522.h +++ b/cpp_utils/MFRC522.h @@ -76,7 +76,6 @@ #define MFRC522_h - #include #include @@ -258,7 +257,6 @@ class MFRC522 { PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. - PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset. // The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on the sector. // The read/write commands can also be used for MIFARE Ultralight. @@ -329,18 +327,20 @@ class MFRC522 { ///////////////////////////////////////////////////////////////////////////////////// // Functions for setting up the Arduino ///////////////////////////////////////////////////////////////////////////////////// + //MFRC522(); ///////////////////////////////////////////////////////////////////////////////////// // Basic interface functions for communicating with the MFRC522 ///////////////////////////////////////////////////////////////////////////////////// + void PCD_WriteRegister(PCD_Register reg, byte value); - void PCD_WriteRegister(PCD_Register reg, byte count, byte *values); + void PCD_WriteRegister(PCD_Register reg, byte count, byte* values); byte PCD_ReadRegister(PCD_Register reg); - void PCD_ReadRegister(PCD_Register reg, byte count, byte *values, byte rxAlign = 0); + void PCD_ReadRegister(PCD_Register reg, byte count, byte* values, byte rxAlign = 0); void PCD_SetRegisterBitMask(PCD_Register reg, byte mask); void PCD_ClearRegisterBitMask(PCD_Register reg, byte mask); - StatusCode PCD_CalculateCRC(byte *data, byte length, byte *result); + StatusCode PCD_CalculateCRC(byte* data, byte length, byte* result); ///////////////////////////////////////////////////////////////////////////////////// // Functions for manipulating the MFRC522 @@ -357,46 +357,46 @@ class MFRC522 { ///////////////////////////////////////////////////////////////////////////////////// // Functions for communicating with PICCs ///////////////////////////////////////////////////////////////////////////////////// - StatusCode PCD_TransceiveData(byte *sendData, byte sendLen, byte *backData, byte *backLen, byte *validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); - StatusCode PCD_CommunicateWithPICC(byte command, byte waitIRq, byte *sendData, byte sendLen, byte *backData = nullptr, byte *backLen = nullptr, byte *validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); - StatusCode PICC_RequestA(byte *bufferATQA, byte *bufferSize); - StatusCode PICC_WakeupA(byte *bufferATQA, byte *bufferSize); - StatusCode PICC_REQA_or_WUPA(byte command, byte *bufferATQA, byte *bufferSize); - virtual StatusCode PICC_Select(Uid *uid, byte validBits = 0); + StatusCode PCD_TransceiveData(byte* sendData, byte sendLen, byte* backData, byte* backLen, byte* validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); + StatusCode PCD_CommunicateWithPICC(byte command, byte waitIRq, byte* sendData, byte sendLen, byte* backData = nullptr, byte* backLen = nullptr, byte* validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); + StatusCode PICC_RequestA(byte* bufferATQA, byte* bufferSize); + StatusCode PICC_WakeupA(byte* bufferATQA, byte* bufferSize); + StatusCode PICC_REQA_or_WUPA(byte command, byte* bufferATQA, byte* bufferSize); + virtual StatusCode PICC_Select(Uid* uid, byte validBits = 0); StatusCode PICC_HaltA(); ///////////////////////////////////////////////////////////////////////////////////// // Functions for communicating with MIFARE PICCs ///////////////////////////////////////////////////////////////////////////////////// - StatusCode PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key *key, Uid *uid); + StatusCode PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key* key, Uid* uid); void PCD_StopCrypto1(); - StatusCode MIFARE_Read(byte blockAddr, byte *buffer, byte *bufferSize); - StatusCode MIFARE_Write(byte blockAddr, byte *buffer, byte bufferSize); - StatusCode MIFARE_Ultralight_Write(byte page, byte *buffer, byte bufferSize); + StatusCode MIFARE_Read(byte blockAddr, byte* buffer, byte* bufferSize); + StatusCode MIFARE_Write(byte blockAddr, byte* buffer, byte bufferSize); + StatusCode MIFARE_Ultralight_Write(byte page, byte* buffer, byte bufferSize); StatusCode MIFARE_Decrement(byte blockAddr, int32_t delta); StatusCode MIFARE_Increment(byte blockAddr, int32_t delta); StatusCode MIFARE_Restore(byte blockAddr); StatusCode MIFARE_Transfer(byte blockAddr); - StatusCode MIFARE_GetValue(byte blockAddr, int32_t *value); + StatusCode MIFARE_GetValue(byte blockAddr, int32_t* value); StatusCode MIFARE_SetValue(byte blockAddr, int32_t value); - StatusCode PCD_NTAG216_AUTH(byte *passWord, byte pACK[]); + StatusCode PCD_NTAG216_AUTH(byte* passWord, byte pACK[]); ///////////////////////////////////////////////////////////////////////////////////// // Support functions ///////////////////////////////////////////////////////////////////////////////////// - StatusCode PCD_MIFARE_Transceive(byte *sendData, byte sendLen, bool acceptTimeout = false); + StatusCode PCD_MIFARE_Transceive(byte* sendData, byte sendLen, bool acceptTimeout = false); static PICC_Type PICC_GetType(byte sak); // Support functions for debuging void PCD_DumpVersionToSerial(); - void PICC_DumpToSerial(Uid *uid); - void PICC_DumpDetailsToSerial(Uid *uid); - void PICC_DumpMifareClassicToSerial(Uid *uid, PICC_Type piccType, MIFARE_Key *key); - void PICC_DumpMifareClassicSectorToSerial(Uid *uid, MIFARE_Key *key, byte sector); + void PICC_DumpToSerial(Uid* uid); + void PICC_DumpDetailsToSerial(Uid* uid); + void PICC_DumpMifareClassicToSerial(Uid* uid, PICC_Type piccType, MIFARE_Key* key); + void PICC_DumpMifareClassicSectorToSerial(Uid* uid, MIFARE_Key* key, byte sector); void PICC_DumpMifareUltralightToSerial(); // Advanced functions for MIFARE - void MIFARE_SetAccessBits(byte *accessBitBuffer, byte g0, byte g1, byte g2, byte g3); + void MIFARE_SetAccessBits(byte* accessBitBuffer, byte g0, byte g1, byte g2, byte g3); ///////////////////////////////////////////////////////////////////////////////////// // Convenience functions - does not add extra functionality diff --git a/cpp_utils/MFRC522Debug.h b/cpp_utils/MFRC522Debug.h index 6c1c9ac6..e8033495 100644 --- a/cpp_utils/MFRC522Debug.h +++ b/cpp_utils/MFRC522Debug.h @@ -4,11 +4,10 @@ #define MFRC522Debug_h class MFRC522Debug { -private: - public: // Get human readable code and type static const char* PICC_GetTypeName(MFRC522::PICC_Type type); static const char* GetStatusCodeName(MFRC522::StatusCode code); + }; #endif // MFRC522Debug_h diff --git a/cpp_utils/MMU.cpp b/cpp_utils/MMU.cpp index e43328fb..57c0ccce 100644 --- a/cpp_utils/MMU.cpp +++ b/cpp_utils/MMU.cpp @@ -27,18 +27,18 @@ typedef struct { static addressRange_t entryNumberToAddressRange(uint32_t entryNumber) { addressRange_t ret; if (entryNumber < 64) { - ret.low = 0x3F400000 + 64*1024*entryNumber; - ret.high = 0x3F400000 + 64*1024*(entryNumber+1)-1; + ret.low = 0x3F400000 + 64 * 1024 * entryNumber; + ret.high = 0x3F400000 + 64 * 1024 * (entryNumber + 1) - 1; return ret; } - ret.low = 0x40000000 + 64*1024*(entryNumber-64); - ret.high = 0x40000000 + 64*1024*(entryNumber+1-64)-1; + ret.low = 0x40000000 + 64 * 1024 * (entryNumber - 64); + ret.high = 0x40000000 + 64 * 1024 * (entryNumber + 1 - 64) - 1; return ret; } static uint32_t flashPageToOffset(uint32_t page) { - return page*64*1024; + return page * 64 * 1024; } @@ -47,7 +47,7 @@ static uint32_t flashPageToOffset(uint32_t page) { const uint32_t mappingInvalid = 1 << 8; printf("PRO CPU MMU\n"); - for (int i=0; i<256; i++) { + for (uint8_t i = 0; i < 256; i++) { if (!(DPORT_PRO_FLASH_MMU_TABLE[i] & mappingInvalid)) { addressRange_t addressRange = entryNumberToAddressRange(i); printf("Entry: %2d (0x%8.8x - 0x%8.8x), Page: %d - offset: 0x%x\n", @@ -59,7 +59,7 @@ static uint32_t flashPageToOffset(uint32_t page) { } printf("\n"); printf("APP CPU MMU\n"); - for (int i=0; i<256; i++) { + for (uint8_t i = 0; i < 256; i++) { if (!(DPORT_APP_FLASH_MMU_TABLE[i] & mappingInvalid)) { addressRange_t addressRange = entryNumberToAddressRange(i); printf("Entry: %2d (0x%8.8x - 0x%8.8x), Page: %d - offset: 0x%x\n", @@ -78,17 +78,16 @@ extern "C" { static void IRAM_ATTR mapFlashToVMA_Internal(uint32_t flashOffset, void* vma, size_t size) { printf(">> MMU::mapFlashToVMA: flash offset: 0x%x, VMA: 0x%x, size: %d\n", flashOffset, (uint32_t)vma, size); uint32_t mmuEntryStart; // The MMU table entry to start mapping. - uint32_t mmuEntryEnd; // The MMU table entry to end mapping. + uint32_t mmuEntryEnd; // The MMU table entry to end mapping. - if ((uint32_t)vma >= 0x40000000 && (uint32_t)vma < 0x40C00000) { - mmuEntryStart = (((uint32_t)vma - 0x40000000)/(64*1024)) + 64; - mmuEntryEnd = (((uint32_t)vma - 0x40000000 + size)/(64*1024)) + 64; + if ((uint32_t) vma >= 0x40000000 && (uint32_t) vma < 0x40C00000) { + mmuEntryStart = (((uint32_t) vma - 0x40000000) / (64 * 1024)) + 64; + mmuEntryEnd = (((uint32_t) vma - 0x40000000 + size) / (64 * 1024)) + 64; } - else if ((uint32_t)vma >= 0x3F400000 && (uint32_t)vma < 0x3F800000) { - mmuEntryStart = (((uint32_t)vma - 0x3F400000)/(64*1024)); - mmuEntryEnd = (((uint32_t)vma - 0x3F400000 + size)/(64*1024)); - } - else { + else if ((uint32_t) vma >= 0x3F400000 && (uint32_t) vma < 0x3F800000) { + mmuEntryStart = (((uint32_t) vma - 0x3F400000) / (64 * 1024)); + mmuEntryEnd = (((uint32_t) vma - 0x3F400000 + size) / (64 * 1024)); + } else { printf(" - Unable to map from flash to VMA."); return; } @@ -98,14 +97,14 @@ static void IRAM_ATTR mapFlashToVMA_Internal(uint32_t flashOffset, void* vma, si uint32_t pFlashEnd = flashOffset + size; printf(" - Mapping flash to VMA via MMU. MMU entries start: %d, end: %d, mapping flash 0x%x (flash page: %d) to 0x%x (flash page: %d)\n", - mmuEntryStart, mmuEntryEnd, pFlashStart, pFlashStart/(64*1024), pFlashEnd, pFlashEnd/(64*1024)); + mmuEntryStart, mmuEntryEnd, pFlashStart, pFlashStart/(64 * 1024), pFlashEnd, pFlashEnd / (64 * 1024)); - uint32_t flashRegion = pFlashStart / (64*1024); // Determine the 64K chunk of flash to be mapped (we map in units of 64K). + uint32_t flashRegion = pFlashStart / (64 * 1024); // Determine the 64K chunk of flash to be mapped (we map in units of 64K). spi_flash_disable_interrupts_caches_and_other_cpu(); // For each of the mapping entries, map it to the corresponding flash region. - for (uint32_t i=mmuEntryStart; i<=mmuEntryEnd; i++) { + for (uint32_t i = mmuEntryStart; i <= mmuEntryEnd; i++) { DPORT_PRO_FLASH_MMU_TABLE[i] = flashRegion; // There are two tables. One for the PRO CPU and one for the APP CPU. DPORT_APP_FLASH_MMU_TABLE[i] = flashRegion; // Map both of them to the flash region. flashRegion++; diff --git a/cpp_utils/MPU6050.cpp b/cpp_utils/MPU6050.cpp index 5bcf5bb9..0f6931ae 100644 --- a/cpp_utils/MPU6050.cpp +++ b/cpp_utils/MPU6050.cpp @@ -9,14 +9,15 @@ #define MPU6050_GYRO_XOUT_H 0x43 #define MPU6050_PWR_MGMT_1 0x6B + /** * @brief Construct an %MPU6050 handler. */ MPU6050::MPU6050() { accel_x = accel_y = accel_z = 0; gyro_x = gyro_y = gyro_z = 0; - i2c=nullptr; - inited=false; + i2c = nullptr; + inited = false; } @@ -43,7 +44,7 @@ void MPU6050::readAccel() { uint8_t data[6]; i2c->beginTransaction(); i2c->read(data, 5, true); - i2c->read(data+5, false); + i2c->read(data + 5, false); i2c->endTransaction(); accel_x = (data[0] << 8) | data[1]; @@ -51,6 +52,7 @@ void MPU6050::readAccel() { accel_z = (data[4] << 8) | data[5]; } // readAccel + /** * @brief Read the gyroscopic values from the device. * @@ -66,7 +68,7 @@ void MPU6050::readGyro() { uint8_t data[6]; i2c->beginTransaction(); i2c->read(data, 5, true); - i2c->read(data+5, false); + i2c->read(data + 5, false); i2c->endTransaction(); gyro_x = (data[0] << 8) | data[1]; @@ -74,6 +76,7 @@ void MPU6050::readGyro() { gyro_z = (data[4] << 8) | data[5]; } // readGyro + /** * @brief Initialize the %MPU6050. * @param [in] sdaPin The %GPIO pin to use for %I2C SDA. @@ -94,5 +97,5 @@ void MPU6050::init(gpio_num_t sdaPin, gpio_num_t clkPin) { i2c->write(MPU6050_PWR_MGMT_1); i2c->write(0); i2c->endTransaction(); - inited=true; + inited = true; } diff --git a/cpp_utils/MPU6050.h b/cpp_utils/MPU6050.h index fe69193d..8207f6d6 100644 --- a/cpp_utils/MPU6050.h +++ b/cpp_utils/MPU6050.h @@ -16,61 +16,49 @@ * A call to init() must precede all other API calls. */ class MPU6050 { -private: - I2C *i2c; - short accel_x, accel_y, accel_z; - short gyro_x, gyro_y, gyro_z; - bool inited; public: MPU6050(); virtual ~MPU6050(); - /** * @brief Get the X acceleration value. */ - short getAccelX() const - { + short getAccelX() const { return accel_x; } /** * @brief Get the Y acceleration value. */ - short getAccelY() const - { + short getAccelY() const { return accel_y; } /** * @brief Get the Z acceleration value. */ - short getAccelZ() const - { + short getAccelZ() const { return accel_z; } /** * @brief Get the X gyroscopic value. */ - short getGyroX() const - { + short getGyroX() const { return gyro_x; } /** * @brief Get the Y gyroscopic value. */ - short getGyroY() const - { + short getGyroY() const { return gyro_y; } /** * @brief Get the Z gyroscopic value. */ - short getGyroZ() const - { + short getGyroZ() const { return gyro_z; } @@ -86,10 +74,16 @@ class MPU6050 { return sqrt(accel_x * accel_x + accel_y * accel_y + accel_z * accel_z); } - void init(gpio_num_t sdaPin=I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin=I2C::DEFAULT_CLK_PIN); + void init(gpio_num_t sdaPin = I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin = I2C::DEFAULT_CLK_PIN); void readAccel(); - void readGyro(); + +private: + I2C* i2c; + short accel_x, accel_y, accel_z; + short gyro_x, gyro_y, gyro_z; + bool inited; + }; #endif /* MAIN_MPU6050_H_ */ diff --git a/cpp_utils/MRFC522Debug.cpp b/cpp_utils/MRFC522Debug.cpp index 82cd031d..56ba91d0 100644 --- a/cpp_utils/MRFC522Debug.cpp +++ b/cpp_utils/MRFC522Debug.cpp @@ -1,46 +1,48 @@ #include "MFRC522Debug.h" + /** * Returns a __FlashStringHelper pointer to the PICC type name. * + * @param piccType One of the PICC_Type enums. * @return const __FlashStringHelper * */ -const char* MFRC522Debug::PICC_GetTypeName(MFRC522::PICC_Type piccType ///< One of the PICC_Type enums. -) { +const char* MFRC522Debug::PICC_GetTypeName(MFRC522::PICC_Type piccType) { switch (piccType) { - case MFRC522::PICC_TYPE_ISO_14443_4: return "PICC compliant with ISO/IEC 14443-4"; + case MFRC522::PICC_TYPE_ISO_14443_4: return "PICC compliant with ISO/IEC 14443-4"; case MFRC522::PICC_TYPE_ISO_18092: return "PICC compliant with ISO/IEC 18092 (NFC)"; - case MFRC522::PICC_TYPE_MIFARE_MINI: return "MIFARE Mini, 320 bytes"; + case MFRC522::PICC_TYPE_MIFARE_MINI: return "MIFARE Mini, 320 bytes"; case MFRC522::PICC_TYPE_MIFARE_1K: return "MIFARE 1KB"; case MFRC522::PICC_TYPE_MIFARE_4K: return "MIFARE 4KB"; case MFRC522::PICC_TYPE_MIFARE_UL: return "MIFARE Ultralight or Ultralight C"; - case MFRC522::PICC_TYPE_MIFARE_PLUS: return "MIFARE Plus"; - case MFRC522::PICC_TYPE_MIFARE_DESFIRE: return "MIFARE DESFire"; - case MFRC522::PICC_TYPE_TNP3XXX: return "MIFARE TNP3XXX"; - case MFRC522::PICC_TYPE_NOT_COMPLETE: return "SAK indicates UID is not complete."; + case MFRC522::PICC_TYPE_MIFARE_PLUS: return "MIFARE Plus"; + case MFRC522::PICC_TYPE_MIFARE_DESFIRE: return "MIFARE DESFire"; + case MFRC522::PICC_TYPE_TNP3XXX: return "MIFARE TNP3XXX"; + case MFRC522::PICC_TYPE_NOT_COMPLETE: return "SAK indicates UID is not complete."; case MFRC522::PICC_TYPE_UNKNOWN: - default: return "Unknown type"; + default: return "Unknown type"; } } // End PICC_GetTypeName() + /** * Returns a __FlashStringHelper pointer to a status code name. * + * @param code One of the StatusCode enums. * @return const __FlashStringHelper * */ -const char *MFRC522Debug::GetStatusCodeName(MFRC522::StatusCode code ///< One of the StatusCode enums. -) { +const char *MFRC522Debug::GetStatusCodeName(MFRC522::StatusCode code) { switch (code) { - case MFRC522::STATUS_OK: return "Success."; + case MFRC522::STATUS_OK: return "Success."; case MFRC522::STATUS_ERROR: return "Error in communication."; - case MFRC522::STATUS_COLLISION: return "Collission detected."; - case MFRC522::STATUS_TIMEOUT: return "Timeout in communication."; - case MFRC522::STATUS_NO_ROOM: return "A buffer is not big enough."; - case MFRC522::STATUS_INTERNAL_ERROR: return "Internal error in the code. Should not happen."; - case MFRC522::STATUS_INVALID: return "Invalid argument."; + case MFRC522::STATUS_COLLISION: return "Collision detected."; + case MFRC522::STATUS_TIMEOUT: return "Timeout in communication."; + case MFRC522::STATUS_NO_ROOM: return "A buffer is not big enough."; + case MFRC522::STATUS_INTERNAL_ERROR: return "Internal error in the code. Should not happen."; + case MFRC522::STATUS_INVALID: return "Invalid argument."; case MFRC522::STATUS_CRC_WRONG: return "The CRC_A does not match."; - case MFRC522::STATUS_MIFARE_NACK: return "A MIFARE PICC responded with NAK."; - default: return "Unknown error"; + case MFRC522::STATUS_MIFARE_NACK: return "A MIFARE PICC responded with NAK."; + default: return "Unknown error"; } } // End GetStatusCodeName() diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index b9524dcf..5638b266 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -80,4 +80,3 @@ build_ble: install: build_ble rm -rf ${ARDUINO_LIBS}/ESP32_BLE unzip Arduino/ESP32_BLE.zip -d $(ARDUINO_LIBS) - diff --git a/cpp_utils/Memory.cpp b/cpp_utils/Memory.cpp index e3be59db..002e6578 100644 --- a/cpp_utils/Memory.cpp +++ b/cpp_utils/Memory.cpp @@ -29,7 +29,7 @@ size_t Memory::m_lastHeapSize = 0; */ /* STATIC */ bool Memory::checkIntegrity() { bool rc = ::heap_caps_check_integrity_all(true); - if (rc == false && m_pRecords != nullptr) { + if (!rc && m_pRecords != nullptr) { dumpRanges(); abort(); } @@ -58,32 +58,29 @@ size_t Memory::m_lastHeapSize = 0; */ /* STATIC */ void Memory::dumpRanges() { // Each record contained in the Heap trace has the following format: - // // * uint32_t ccount – Timestamp of record. // * void* address – Address that was allocated or released. // * size_t size – Size of the block that was requested and allocated. // * void* alloced_by[CONFIG_HEAP_TRACING_STACK_DEPTH] – Call stack of allocator // * void* freed_by[CONFIG_HEAP_TRACING_STACK_DEPTH] – Call stack of releasor - // - if (m_pRecords == nullptr) { - return; - } + if (m_pRecords == nullptr) return; + esp_log_level_set("*", ESP_LOG_NONE); - size_t count = heap_trace_get_count(); + size_t count = (size_t) heap_trace_get_count(); heap_trace_record_t record; printf(">>> dumpRanges\n"); - for (size_t i=0; i + +#define OV7670_I2C_ADDR (0x21) + extern "C" { #include #include } -static const char *LOG_TAG="OV7670"; +static const char* LOG_TAG = "OV7670"; static bool getBit(uint8_t value, uint8_t bitNum) { - return (value & (1<> 3) << 3; uint8_t green = (((byte1 & 0b111) << 3) | ((byte2 & 0b11100000) >> 5)) << 2; uint8_t blue = (byte2 & 0b11111) << 3; - *pLine = (red + green + blue)/3; + *pLine = (red + green + blue) / 3; pLine++; - i+=2; + i += 2; } pLine = pLineSave; - GeneralUtils::hexDump(pLine, length/2); + GeneralUtils::hexDump(pLine, length / 2); } void OV7670::setFormat(uint8_t value) { @@ -120,7 +123,6 @@ void OV7670::setTestPattern(uint8_t value) { * */ -#define OV7670_I2C_ADDR (0x21) /* static void IRAM_ATTR isr_vsync(void* arg) { ESP_EARLY_LOGD(LOG_TAG, "VSYNC"); @@ -183,20 +185,19 @@ OV7670::~OV7670() { } -static esp_err_t camera_enable_out_clock(camera_config_t* config) -{ +static esp_err_t camera_enable_out_clock(camera_config_t* config) { ESP_LOGD(LOG_TAG, ">> camera_enable_out_clock: freq_hz=%d, pin=%d", config->xclk_freq_hz, config->pin_xclk); periph_module_enable(PERIPH_LEDC_MODULE); ledc_timer_config_t timer_conf; - timer_conf.duty_resolution = (ledc_timer_bit_t)1; timer_conf.freq_hz = config->xclk_freq_hz; + timer_conf.duty_resolution = (ledc_timer_bit_t) 1; timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE; timer_conf.timer_num = config->ledc_timer; esp_err_t err = ledc_timer_config(&timer_conf); if (err != ESP_OK) { - ESP_LOGE(LOG_TAG, "ledc_timer_config failed, rc=%x", err); - return err; + ESP_LOGE(LOG_TAG, "ledc_timer_config failed, rc=%x", err); + return err; } ledc_channel_config_t ch_conf; @@ -209,8 +210,8 @@ static esp_err_t camera_enable_out_clock(camera_config_t* config) err = ledc_channel_config(&ch_conf); if (err != ESP_OK) { - ESP_LOGE(LOG_TAG, "ledc_channel_config failed, rc=%x", err); - return err; + ESP_LOGE(LOG_TAG, "ledc_channel_config failed, rc=%x", err); + return err; } ESP_LOGD(LOG_TAG, "<< camera_enable_out_clock"); return ESP_OK; @@ -274,88 +275,90 @@ void OV7670::dump() { uint32_t outputFormat = getBit(com7, 2) << 1 | getBit(com7, 0); //uint32_t outputFormat = (com7 & (1<<2)) >> 1 | (com7 & (1<<0)); std::string outputFormatString; - switch(outputFormat) { - case 0b00: - outputFormatString = "YUV"; - break; - case 0b10: - outputFormatString = "RGB"; - break; - case 0b01: - outputFormatString = "Raw Bayer RGB"; - break; - case 0b11: - outputFormatString = "Process Bayer RGB"; - break; - default: - outputFormatString = "Unknown"; - break; - } - ESP_LOGD(LOG_TAG, "Output format: %s", outputFormatString.c_str()); - if (outputFormat == 0b10) { - uint8_t com15 = readRegister(OV7670_REG_COM15); - uint8_t rgbType = getBit(com15, 5) << 1 | getBit(com15,4); - char *rgbTypeString; - switch(rgbType) { + switch (outputFormat) { case 0b00: + outputFormatString = "YUV"; + break; case 0b10: - rgbTypeString = (char*)"Normal RGB Output"; + outputFormatString = "RGB"; break; case 0b01: - rgbTypeString = (char*)"RGB 565"; + outputFormatString = "Raw Bayer RGB"; break; case 0b11: - rgbTypeString = (char*)"RGB 555"; + outputFormatString = "Process Bayer RGB"; break; default: - rgbTypeString = (char*)"Unknown"; + outputFormatString = "Unknown"; break; + } + ESP_LOGD(LOG_TAG, "Output format: %s", outputFormatString.c_str()); + if (outputFormat == 0b10) { + uint8_t com15 = readRegister(OV7670_REG_COM15); + uint8_t rgbType = getBit(com15, 5) << 1 | getBit(com15, 4); + char* rgbTypeString; + switch (rgbType) { + case 0b00: + case 0b10: + rgbTypeString = (char*) "Normal RGB Output"; + break; + case 0b01: + rgbTypeString = (char*) "RGB 565"; + break; + case 0b11: + rgbTypeString = (char*) "RGB 555"; + break; + default: + rgbTypeString = (char*) "Unknown"; + break; } ESP_LOGD(LOG_TAG, "Rgb Type: %s", rgbTypeString); } - ESP_LOGD(LOG_TAG, "Color bar: %s", getBit(com7, 1)?"Enabled":"Disabled"); + ESP_LOGD(LOG_TAG, "Color bar: %s", getBit(com7, 1) ? "Enabled" : "Disabled"); uint8_t scaling_xsc = readRegister(OV7670_REG_SCALING_XSC); uint8_t scaling_ysc = readRegister(OV7670_REG_SCALING_YSC); uint32_t testPattern = getBit(scaling_xsc, 7) << 1 | getBit(scaling_ysc, 7); - char *testPatternString; - switch(testPattern) { - case 0b00: - testPatternString = (char*)"No test output"; - break; - case 0b01: - testPatternString = (char*)"Shifting 1"; - break; - case 0b10: - testPatternString = (char*)"8-bar color bar"; - break; - case 0b11: - testPatternString = (char*)"Fade to gray color bar"; - break; - default: - testPatternString = (char*)"Unknown"; - break; + char* testPatternString; + switch (testPattern) { + case 0b00: + testPatternString = (char*) "No test output"; + break; + case 0b01: + testPatternString = (char*) "Shifting 1"; + break; + case 0b10: + testPatternString = (char*) "8-bar color bar"; + break; + case 0b11: + testPatternString = (char*) "Fade to gray color bar"; + break; + default: + testPatternString = (char*) "Unknown"; + break; } ESP_LOGD(LOG_TAG, "Test pattern: %s", testPatternString); ESP_LOGD(LOG_TAG, "Horizontal scale factor: %d", scaling_xsc & 0x3f); ESP_LOGD(LOG_TAG, "Vertical scale factor: %d", scaling_ysc & 0x3f); uint8_t com15 = readRegister(OV7670_REG_COM15); - switch((com15 & 0b11000000) >> 6) { - case 0b00: - case 0b01: - ESP_LOGD(LOG_TAG, "Output range: 0x10 to 0xf0"); - break; - case 0b10: - ESP_LOGD(LOG_TAG, "Output range: 0x01 to 0xfe"); - break; - case 0b11: - ESP_LOGD(LOG_TAG, "Output range: 0x00 to 0xff"); - break; + switch ((com15 & 0b11000000) >> 6) { + case 0b00: + case 0b01: + ESP_LOGD(LOG_TAG, "Output range: 0x10 to 0xf0"); + break; + case 0b10: + ESP_LOGD(LOG_TAG, "Output range: 0x01 to 0xfe"); + break; + case 0b11: + ESP_LOGD(LOG_TAG, "Output range: 0x00 to 0xff"); + break; + default: + break; } } // dump /* -static void log(char *marker) { +static void log(char* marker) { ESP_LOGD(LOG_TAG, "%s", marker); FreeRTOS::sleep(100); } @@ -394,7 +397,7 @@ void OV7670::init(camera_config_t cameraConfig) { // Create the I2C interface. m_i2c = new I2C(); - m_i2c->init(OV7670_I2C_ADDR, (gpio_num_t)m_cameraConfig.pin_sscb_sda, (gpio_num_t)m_cameraConfig.pin_sscb_scl); + m_i2c->init(OV7670_I2C_ADDR, (gpio_num_t) m_cameraConfig.pin_sscb_sda, (gpio_num_t) m_cameraConfig.pin_sscb_scl); m_i2c->scan(); ESP_LOGD(LOG_TAG, "Do you see 0x21 listed?"); resetCamera(); @@ -430,46 +433,42 @@ void OV7670::init(camera_config_t cameraConfig) { } */ - I2S i2s; - dma_config_t dmaConfig; - dmaConfig.pin_d0 = cameraConfig.pin_d0; - dmaConfig.pin_d1 = cameraConfig.pin_d1; - dmaConfig.pin_d2 = cameraConfig.pin_d2; - dmaConfig.pin_d3 = cameraConfig.pin_d3; - dmaConfig.pin_d4 = cameraConfig.pin_d4; - dmaConfig.pin_d5 = cameraConfig.pin_d5; - dmaConfig.pin_d6 = cameraConfig.pin_d6; - dmaConfig.pin_d7 = cameraConfig.pin_d7; - dmaConfig.pin_href = cameraConfig.pin_href; - dmaConfig.pin_pclk = cameraConfig.pin_pclk; - dmaConfig.pin_vsync = cameraConfig.pin_vsync; - i2s.cameraMode(dmaConfig, 50, 360*2); - ESP_LOGD(LOG_TAG, "Waiting for data!"); - while(1) { - DMAData dmaData = i2s.waitForData(); - //GeneralUtils::hexDump(dmaData.getData(), dmaData.getLength()); - toGrayscale(dmaData.getData(), dmaData.getLength()); - dmaData.free(); - } - - ESP_LOGD(LOG_TAG, "Waiting for positive edge on VSYNC"); - while (gpio_get_level(m_cameraConfig.pin_vsync) == 0) { - ; - } - while (gpio_get_level(m_cameraConfig.pin_vsync) != 0) { - ; - } - - - ESP_LOGD(LOG_TAG, "Got VSYNC"); - - - - - while(1) { - FreeRTOS::sleep(1000); - ESP_LOGD(LOG_TAG, "VSYNC Counter: %d, lastHref=%d, pclk=%d", vsyncCounter, lastHref, pclkCounter); - } + I2S i2s; + dma_config_t dmaConfig; + dmaConfig.pin_d0 = cameraConfig.pin_d0; + dmaConfig.pin_d1 = cameraConfig.pin_d1; + dmaConfig.pin_d2 = cameraConfig.pin_d2; + dmaConfig.pin_d3 = cameraConfig.pin_d3; + dmaConfig.pin_d4 = cameraConfig.pin_d4; + dmaConfig.pin_d5 = cameraConfig.pin_d5; + dmaConfig.pin_d6 = cameraConfig.pin_d6; + dmaConfig.pin_d7 = cameraConfig.pin_d7; + dmaConfig.pin_href = cameraConfig.pin_href; + dmaConfig.pin_pclk = cameraConfig.pin_pclk; + dmaConfig.pin_vsync = cameraConfig.pin_vsync; + i2s.cameraMode(dmaConfig, 50, 360 * 2); + ESP_LOGD(LOG_TAG, "Waiting for data!"); + while (true) { + DMAData dmaData = i2s.waitForData(); +// GeneralUtils::hexDump(dmaData.getData(), dmaData.getLength()); + toGrayscale(dmaData.getData(), dmaData.getLength()); + dmaData.free(); + } + + ESP_LOGD(LOG_TAG, "Waiting for positive edge on VSYNC"); + while (gpio_get_level(m_cameraConfig.pin_vsync) == 0) { + ; + } + while (gpio_get_level(m_cameraConfig.pin_vsync) != 0) { + ; + } + + ESP_LOGD(LOG_TAG, "Got VSYNC"); + + while (true) { + FreeRTOS::sleep(1000); + ESP_LOGD(LOG_TAG, "VSYNC Counter: %d, lastHref=%d, pclk=%d", vsyncCounter, lastHref, pclkCounter); + } ESP_LOGD(LOG_TAG, "<< init"); } // init diff --git a/cpp_utils/OV7670.h b/cpp_utils/OV7670.h index b7baf1f9..0fb4fa19 100644 --- a/cpp_utils/OV7670.h +++ b/cpp_utils/OV7670.h @@ -198,11 +198,13 @@ class OV7670 { void setRGBFormat(uint8_t value); void setTestPattern(uint8_t value); void resetCamera(); + private: uint8_t readRegister(uint8_t reg); void writeRegister(uint8_t reg, uint8_t value); camera_config_t m_cameraConfig; - I2C *m_i2c; + I2C* m_i2c; + }; #endif /* CPP_UTILS_OV7670_H_ */ diff --git a/cpp_utils/PCF8574.cpp b/cpp_utils/PCF8574.cpp index 4d850a8b..d19222fa 100644 --- a/cpp_utils/PCF8574.cpp +++ b/cpp_utils/PCF8574.cpp @@ -22,6 +22,7 @@ PCF8574::PCF8574(uint8_t address) { lastWrite = 0; } + /** * @brief Class instance destructor. */ @@ -49,11 +50,9 @@ uint8_t PCF8574::read() { * @return True if the pin is high, false otherwise. Undefined if there is no signal on the pin. */ bool PCF8574::readBit(uint8_t bit) { - if (bit > 7) { - return false; - } + if (bit > 7) return false; uint8_t value = read(); - return (value & (1< 7) { - return; - } + if (bit > 7) return; if (invert) { value = !value; } diff --git a/cpp_utils/PCF8574.h b/cpp_utils/PCF8574.h index 15b11da9..616fdbfb 100644 --- a/cpp_utils/PCF8574.h +++ b/cpp_utils/PCF8574.h @@ -21,7 +21,7 @@ class PCF8574 { public: PCF8574(uint8_t address); virtual ~PCF8574(); - void init(gpio_num_t sdaPin=I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin=I2C::DEFAULT_CLK_PIN); + void init(gpio_num_t sdaPin = I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin = I2C::DEFAULT_CLK_PIN); uint8_t read(); bool readBit(uint8_t bit); void setInvert(bool value); @@ -32,6 +32,7 @@ class PCF8574 { I2C i2c = I2C(); uint8_t lastWrite; bool invert = false; + }; #endif /* COMPONENTS_CPP_UTILS_PCF8574_H_ */ diff --git a/cpp_utils/PCF8575.cpp b/cpp_utils/PCF8575.cpp index 3714abb1..24b15c1d 100644 --- a/cpp_utils/PCF8575.cpp +++ b/cpp_utils/PCF8575.cpp @@ -8,6 +8,7 @@ #include "PCF8575.h" #include "I2C.h" + /** * @brief Class constructor. * @@ -22,6 +23,7 @@ PCF8575::PCF8575(uint8_t address) { m_lastWrite = 0; } + /** * @brief Class instance destructor. */ @@ -36,8 +38,8 @@ PCF8575::~PCF8575() { uint16_t PCF8575::read() { uint16_t value; i2c.beginTransaction(); - i2c.read((uint8_t*)&value,true); - i2c.read(((uint8_t*)&value) + 1,true); + i2c.read((uint8_t*) &value, true); + i2c.read(((uint8_t*) &value) + 1, true); i2c.endTransaction(); return value; } // read @@ -50,11 +52,9 @@ uint16_t PCF8575::read() { * @return True if the pin is high, false otherwise. Undefined if there is no signal on the pin. */ bool PCF8575::readBit(uint16_t bit) { - if (bit > 7) { - return false; - } + if (bit > 7) return false; uint16_t value = read(); - return (value & (1< 15) { - return; - } + if (bit > 15) return; if (invert) { value = !value; } if (value) { - m_lastWrite |= (1< 100) { - percent = 100; - } + if (percent > 100) percent = 100; uint32_t value = max * percent / 100; - if (value >= max) { - value = max-1; - } + if (value >= max) value = max - 1; setDuty(value); - } // setDutyPercentage diff --git a/cpp_utils/PWM.h b/cpp_utils/PWM.h index 01c30a48..8cc1ce39 100644 --- a/cpp_utils/PWM.h +++ b/cpp_utils/PWM.h @@ -32,11 +32,13 @@ class PWM { void setDuty(uint32_t duty); void setDutyPercentage(uint8_t percent); void setFrequency(uint32_t freq); - void stop(bool idleLevel=false); + void stop(bool idleLevel = false); + private: ledc_channel_t m_channel; ledc_timer_t m_timer; ledc_timer_bit_t m_dutyResolution; // Bit size of timer. + }; #endif /* COMPONENTS_CPP_UTILS_PWM_H_ */ diff --git a/cpp_utils/PubSubClient.cpp b/cpp_utils/PubSubClient.cpp index 8aaca5eb..30cb6282 100644 --- a/cpp_utils/PubSubClient.cpp +++ b/cpp_utils/PubSubClient.cpp @@ -30,6 +30,7 @@ class PubSubClientTask: public Task { Task(name, 16 * 1024) { taskName = name; }; + private: std::string taskName; /** @@ -40,30 +41,24 @@ class PubSubClientTask: public Task { PubSubClient* pPubSubClient = (PubSubClient*) data; ESP_LOGD("PubSubClientTask", "PubSubClientTask Task started!"); - while(1) { + while (true) { if (pPubSubClient->connected()) { - uint16_t len = pPubSubClient->readPacket(); if (len > 0) { // if there was data pPubSubClient->keepAliveTimer->reset(0); //lastInActivity = t; - mqtt_message *msg = new mqtt_message; + mqtt_message* msg = new mqtt_message; pPubSubClient->parseData(msg, len); //pPubSubClient->dumpData(msg); ESP_LOGD(TAG, "Message type (%s)!", pPubSubClient->messageType_toString(msg->type).c_str()); - if (msg->type == PUBLISH) { - if (pPubSubClient->callback) { - if (msg->qos == QOS0) { - pPubSubClient->callback(msg->topic, msg->payload); - } else if (msg->qos == QOS1) { pPubSubClient->callback(msg->topic, msg->payload); @@ -73,31 +68,27 @@ class PubSubClientTask: public Task { pPubSubClient->buffer[3] = (msg->msgId & 0xFF); int rc = pPubSubClient->_client->send(pPubSubClient->buffer, 4); - if(rc < 0) pPubSubClient->_state = CONNECTION_LOST; + if (rc < 0) pPubSubClient->_state = CONNECTION_LOST; pPubSubClient->keepAliveTimer->reset(0); //lastOutActivity = t; - - }else if(msg->qos == QOS2) { + } else if(msg->qos == QOS2) { ESP_LOGD(TAG, "QOS2 is not supported!"); - }else{ + } else { ESP_LOGD(TAG, "QOS-Level unkonwon yet!"); } - } } else if (msg->type == PINGREQ) { pPubSubClient->buffer[0] = PINGRESP; pPubSubClient->buffer[1] = 0; int rc = pPubSubClient->_client->send(pPubSubClient->buffer, 2); - if(rc < 0) pPubSubClient->_state = CONNECTION_LOST; + if (rc < 0) pPubSubClient->_state = CONNECTION_LOST; } else if (msg->type == PINGRESP) { pPubSubClient->PING_outstanding = false; - - }else if (msg->type == SUBACK) { + } else if (msg->type == SUBACK) { pPubSubClient->SUBACK_outstanding = false; pPubSubClient->timeoutTimer->stop(0); - - }else if (msg->type == UNSUBACK) { + } else if (msg->type == UNSUBACK) { pPubSubClient->UNSUBACK_Outstanding = false; pPubSubClient->timeoutTimer->stop(0); } @@ -105,10 +96,11 @@ class PubSubClientTask: public Task { delete(msg); } } - } // While (1) + } // while (true) } // run }; // PubSubClientTask + PubSubClient::PubSubClient() { setup(); this->_state = DISCONNECTED; @@ -116,12 +108,14 @@ PubSubClient::PubSubClient() { setCallback(NULL); } + PubSubClient::PubSubClient(Socket& client) { setup(); this->_state = DISCONNECTED; setClient(client); } + PubSubClient::PubSubClient(std::string addr, uint16_t port) { setup(); this->_state = DISCONNECTED; @@ -129,6 +123,7 @@ PubSubClient::PubSubClient(std::string addr, uint16_t port) { setServer(addr, port); } + PubSubClient::PubSubClient(std::string addr, uint16_t port, Socket& client) { setup(); this->_state = DISCONNECTED; @@ -136,8 +131,8 @@ PubSubClient::PubSubClient(std::string addr, uint16_t port, Socket& client) { setClient(client); } -PubSubClient::PubSubClient(std::string addr, uint16_t port, - MQTT_CALLBACK_SIGNATURE, Socket& client) { + +PubSubClient::PubSubClient(std::string addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Socket& client) { setup(); this->_state = DISCONNECTED; setServer(addr, port); @@ -145,6 +140,7 @@ PubSubClient::PubSubClient(std::string addr, uint16_t port, setClient(client); } + PubSubClient::~PubSubClient() { _client->close(); keepAliveTimer->stop(0); @@ -156,39 +152,41 @@ PubSubClient::~PubSubClient() { delete (m_task); } + /** * @brief This is a Timer called routine mapping routine, which calls * the PubSubClient member function keepAliveChecker. * @param The FreeRTOSTimer root instance for this callback function. * @return N/A. */ -void keepAliveTimerMapper(FreeRTOSTimer *pTimer) { +void keepAliveTimerMapper(FreeRTOSTimer* pTimer) { PubSubClient* m_pubSubClient = (PubSubClient*) pTimer->getData(); m_pubSubClient->keepAliveChecker(); } //keepAliveChecker + /** * @brief This is a Timer called routine mapping routine, which calls * the PubSubClient member function timeoutChecker. * @param The FreeRTOSTimer root instance for this callback function. * @return N/A. */ -void timeoutTimerMapper(FreeRTOSTimer *pTimer) { +void timeoutTimerMapper(FreeRTOSTimer* pTimer) { PubSubClient* m_pubSubClient = (PubSubClient*) pTimer->getData(); m_pubSubClient->timeoutChecker(); } //keepAliveChecker + /** * @brief This is a internal setup routine for the PubSubClient. * @param N/A. * @return N/A. */ -void PubSubClient::setup(void) { +void PubSubClient::setup() { PING_outstanding = false; SUBACK_outstanding = false; UNSUBACK_Outstanding = false; - keepAliveTimer = new FreeRTOSTimer((char*) "keepAliveTimer", (MQTT_KEEPALIVE * 1000) / portTICK_PERIOD_MS, true, this, keepAliveTimerMapper); @@ -208,8 +206,8 @@ void PubSubClient::setup(void) { * @param N/A. * @return N/A. */ -void PubSubClient::keepAliveChecker(void){ +void PubSubClient::keepAliveChecker() { if (PING_outstanding && connected()) { _state = CONNECTION_TIMEOUT; //_client->close(); @@ -218,7 +216,7 @@ void PubSubClient::keepAliveChecker(void){ buffer[0] = PINGREQ; buffer[1] = 0; int rc = _client->send(buffer, 2); - if(rc < 0) _state = CONNECTION_LOST; + if (rc < 0) _state = CONNECTION_LOST; ESP_LOGD(TAG, "send KeepAlive REQUEST!"); PING_outstanding = true; } @@ -232,8 +230,7 @@ void PubSubClient::keepAliveChecker(void){ * @param N/A. * @return N/A. */ -void PubSubClient::timeoutChecker(void){ - +void PubSubClient::timeoutChecker() { if (connected() && (SUBACK_outstanding || UNSUBACK_Outstanding)) { _state = CONNECTION_TIMEOUT; //_client->close(); @@ -241,15 +238,17 @@ void PubSubClient::timeoutChecker(void){ } } //keepAliveChecker + /** * @brief Connect to a MQTT server. * @param [in] Device id to identify this device. * @return success (true), or no success (false). */ -bool PubSubClient::connect(const char *id) { +bool PubSubClient::connect(const char* id) { return connect(id, NULL, NULL, 0, 0, 0, 0); } + /** * @brief Connect to a MQTT server. * @param [in] Device id to identify this device. @@ -257,10 +256,11 @@ bool PubSubClient::connect(const char *id) { * [in] my password. * @return success (true), or no success (false). */ -bool PubSubClient::connect(const char *id, const char *user, const char *pass) { +bool PubSubClient::connect(const char* id, const char* user, const char* pass) { return connect(id, user, pass, 0, 0, 0, 0); } + /** * @brief Connect to a MQTT server. * @param [in] Device id to identify this device. @@ -270,11 +270,11 @@ bool PubSubClient::connect(const char *id, const char *user, const char *pass) { * [in] last will: payload. * @return success (true), or no success (false). */ -bool PubSubClient::connect(const char *id, const char* willTopic, - uint8_t willQos, bool willRetain, const char* willMessage) { +bool PubSubClient::connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage) { return connect(id, NULL, NULL, willTopic, willQos, willRetain, willMessage); } + /** * @brief Connect to a MQTT server. * @param [in] Device id to identify this device. @@ -286,9 +286,7 @@ bool PubSubClient::connect(const char *id, const char* willTopic, * [in] last will: payload. * @return success (true), or no success (false). */ -bool PubSubClient::connect(const char *id, const char *user, const char *pass, - const char* willTopic, uint8_t willQos, bool willRetain, - const char* willMessage) { +bool PubSubClient::connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage) { _config.id = id; _config.user = user; @@ -302,6 +300,7 @@ bool PubSubClient::connect(const char *id, const char *user, const char *pass, return connect(); } + /** * @brief Connect to a MQTT server with the with the previous settings. * Note: do not call this function without settings, this will not work! @@ -309,30 +308,25 @@ bool PubSubClient::connect(const char *id, const char *user, const char *pass, * @param N/A * @return success (true), or no success (false). */ -bool PubSubClient::connect(){ - +bool PubSubClient::connect() { if (!connected()) { - ESP_LOGD(TAG, "Connect to mqtt server..."); - ESP_LOGD(TAG, "ip: %s port: %d", _config.ip.c_str(), _config.port); int result = _client->connect((char *)_config.ip.c_str(), _config.port); if (result == 0) { - nextMsgId = 1; // Leave room in the buffer for header and variable length field uint16_t length = 5; - unsigned int j; #if MQTT_VERSION == MQTT_VERSION_3_1 - uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; + uint8_t d[9] = { 0x00, 0x06, 'M', 'Q', 'I', 's', 'd', 'p', MQTT_VERSION }; #define MQTT_HEADER_VERSION_LENGTH 9 #elif MQTT_VERSION == MQTT_VERSION_3_1_1 uint8_t d[7] = { 0x00, 0x04, 'M', 'Q', 'T', 'T', MQTT_VERSION }; #define MQTT_HEADER_VERSION_LENGTH 7 #endif - for (j = 0; j < MQTT_HEADER_VERSION_LENGTH; j++) { + for (unsigned int j = 0; j < MQTT_HEADER_VERSION_LENGTH; j++) { buffer[length++] = d[j]; } @@ -399,6 +393,7 @@ bool PubSubClient::connect(){ return true; } + /** * @brief Receiving a MQTT packet and store it in the buffer to parse it. * @param N/A. @@ -412,9 +407,10 @@ uint16_t PubSubClient::readPacket() { res = 0; // This will cause the packet to be ignored. } - return res; + return (uint16_t) res; } + /** * @brief Publish a MQTT message. * @param [in] my topic. @@ -425,6 +421,7 @@ bool PubSubClient::publish(const char* topic, const char* payload) { return publish(topic, (const uint8_t*) payload, strlen(payload), false); } + /** * @brief Publish a MQTT message. * @param [in] my topic. @@ -432,11 +429,11 @@ bool PubSubClient::publish(const char* topic, const char* payload) { * [in] is this a retained message (true/false) * @return success (true), or no success (false). */ -bool PubSubClient::publish(const char* topic, const char* payload, - bool retained) { +bool PubSubClient::publish(const char* topic, const char* payload, bool retained) { return publish(topic, (const uint8_t*) payload, strlen(payload), retained); } + /** * @brief Publish a MQTT message. * @param [in] my topic. @@ -444,11 +441,11 @@ bool PubSubClient::publish(const char* topic, const char* payload, * [in] length of the message * @return success (true), or no success (false). */ -bool PubSubClient::publish(const char* topic, const uint8_t* payload, - unsigned int plength) { +bool PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { return publish(topic, payload, plength, false); } + /** * @brief Publish a MQTT message. * @param [in] my topic. @@ -457,8 +454,7 @@ bool PubSubClient::publish(const char* topic, const uint8_t* payload, * [in] is this a retained message (true/false) * @return success (true), or no success (false). */ -bool PubSubClient::publish(const char* topic, const uint8_t* payload, - unsigned int plength, bool retained) { +bool PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, bool retained) { if (connected()) { if (MQTT_MAX_PACKET_SIZE < 5 + 2 + strlen(topic) + plength) { // Too long @@ -480,6 +476,7 @@ bool PubSubClient::publish(const char* topic, const uint8_t* payload, return false; } + //bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, // unsigned int plength, bool retained) { // uint8_t llen = 0; @@ -527,6 +524,7 @@ bool PubSubClient::publish(const char* topic, const uint8_t* payload, // return rc == tlen + 4 + plength; //} + /** * @brief Send a MQTT message over socket. * @param [in] MQTT header. @@ -557,13 +555,13 @@ bool PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { } //#ifdef MQTT_MAX_TRANSFER_SIZE -// uint8_t* writeBuf = buf+(4-llen); -// uint16_t bytesRemaining = length+1+llen; //Match the length type +// uint8_t* writeBuf = buf + (4 - llen); +// uint16_t bytesRemaining = length + 1 + llen; //Match the length type // uint8_t bytesToWrite; // bool result = true; -// while((bytesRemaining > 0) && result) { -// bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; -// rc = _client->write(writeBuf,bytesToWrite); +// while ((bytesRemaining > 0) && result) { +// bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE) ? MQTT_MAX_TRANSFER_SIZE : bytesRemaining; +// rc = _client->write(writeBuf, bytesToWrite); // result = (rc == bytesToWrite); // bytesRemaining -= rc; // writeBuf += rc; @@ -577,6 +575,7 @@ bool PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { //#endif } + /** * @brief Subscribe a MQTT topic. * @param [in] my topic @@ -584,11 +583,8 @@ bool PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { * @return request transmitted with success (true), or no success (false). */ bool PubSubClient::subscribe(const char* topic, bool ack) { + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) return false; // Too long - if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { - // Too long - return false; - } if (connected()) { // Leave room in the buffer for header and variable length field uint16_t length = 5; @@ -601,36 +597,35 @@ bool PubSubClient::subscribe(const char* topic, bool ack) { length = writeString(topic, buffer, length); buffer[length++] = QOS1; - if(write(SUBSCRIBE | QOS1, buffer, length - 5)){ + if (write(SUBSCRIBE | QOS1, buffer, length - 5)) { SUBACK_outstanding = true; - if(ack) timeoutTimer->start(0); + if (ack) timeoutTimer->start(0); return true; } } return false; } + /** * @brief Check the state of subscription. If there was received a subscription * ACK, we return a true here. * @return Is subscription validated with ACK (true/false) */ -bool PubSubClient::isSubscribeDone(void){ +bool PubSubClient::isSubscribeDone() { return !SUBACK_outstanding; } + /** * @brief Unsubscribe a MQTT topic. * @param [in] my topic * [in] qos of unsubscription * @return request transmitted with success (true), or no success (false). */ -bool PubSubClient::unsubscribe(const char* topic, bool ack) { +bool PubSubClient::unsubscribe(const char* topic, bool ack) { + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) return false; // Too long - if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { - // Too long - return false; - } if (connected()) { uint16_t length = 5; nextMsgId++; @@ -641,24 +636,26 @@ bool PubSubClient::unsubscribe(const char* topic, bool ack) { buffer[length++] = (nextMsgId & 0xFF); length = writeString(topic, buffer, length); - if(write(UNSUBSCRIBE | QOS1, buffer, length - 5)){ + if (write(UNSUBSCRIBE | QOS1, buffer, length - 5)) { UNSUBACK_Outstanding = true; - if(ack) timeoutTimer->start(0); + if (ack) timeoutTimer->start(0); return true; } } return false; } + /** * @brief Check the state of unsubscription. If there was received a unsubscription * ACK, we return a true here. * @return Is unsubscription validated with ACK (true/false) */ -bool PubSubClient::isUnsubscribeDone(void){ +bool PubSubClient::isUnsubscribeDone() { return !UNSUBACK_Outstanding; } + /** * @brief Disconnect form MQTT server and close the socket. * @return N/A. @@ -673,11 +670,11 @@ void PubSubClient::disconnect() { timeoutTimer->stop(0); } + /** * @brief calculation help to send a string. */ -uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, - uint16_t pos) { +uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { const char* idp = string; uint16_t i = 0; pos += 2; @@ -690,34 +687,30 @@ uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, return pos; } + /** * @brief Check the connection to the MQTT server. * @return connected (true/false) */ bool PubSubClient::connected() { + if (this->_state != CONNECTED) return false; + bool rc = true; - if (this->_state == CONNECTED) { - - bool rc = true; - - if (_client == nullptr) { - rc = false; - } else if (!_client->isValid()) { - rc = false; + if (_client == nullptr) { + rc = false; + } else if (!_client->isValid()) { + rc = false; - this->_state = CONNECTION_LOST; + this->_state = CONNECTION_LOST; - if (_client->isValid()) _client->close(); - keepAliveTimer->stop(0); - timeoutTimer->stop(0); - } - return rc; + if (_client->isValid()) _client->close(); + keepAliveTimer->stop(0); + timeoutTimer->stop(0); } - return false; - ESP_LOGD(TAG, "_state = %d", _state); - + return rc; } + /** * @brief Set server ip and Port of my MQTT server. * @param [in] ip of the distant MQTT server. @@ -730,6 +723,7 @@ PubSubClient& PubSubClient::setServer(std::string ip, uint16_t port) { return *this; } + /** * @brief Set the callback function for incoming data. * @param [in] callback function @@ -740,6 +734,7 @@ PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { return *this; } + /** * @brief Set the socket, which we want to use for our MQTT communication. * @param [in] the new socket instance @@ -750,6 +745,7 @@ PubSubClient& PubSubClient::setClient(Socket& client) { return *this; } + /** * @brief Get the current MYTT state form the instance. * @param N/A. @@ -759,25 +755,28 @@ int PubSubClient::state() { return this->_state; } + /** * @brief Parsing the received data in to the internal message struct. */ -void PubSubClient::parseData(mqtt_message* msg, uint16_t len){ - +void PubSubClient::parseData(mqtt_message* msg, uint16_t len) { /********* Parse Fixed header *********/ msg->type = buffer[0] & 0xF0; /* read DUP-Flag */ - if(msg->type == PUBLISH) - msg->dup = (bool)(buffer[0] & 0x18)>>3; + if (msg->type == PUBLISH) { + msg->dup = (bool) (buffer[0] & 0x18) >> 3; + } /* read QoS-Level */ - if(msg->type == PUBLISH) + if(msg->type == PUBLISH) { msg->qos = (buffer[0] & 0x06); + } /* read RETAIN-Frag */ - if(msg->type == PUBLISH) - msg->retained = (bool)(buffer[0] & 0x01); + if(msg->type == PUBLISH) { + msg->retained = (bool) (buffer[0] & 0x01); + } uint8_t remainingLength = buffer[1]; @@ -785,36 +784,36 @@ void PubSubClient::parseData(mqtt_message* msg, uint16_t len){ int pos = 2; /* read topic name */ - if(msg->type == PUBLISH){ + if (msg->type == PUBLISH) { uint16_t topicLen = (buffer[2] << 8) + buffer[3]; msg->topic = ""; - for(int i = 4; i<(topicLen+4); i++){ + for (int i = 4; i < (topicLen + 4); i++) { msg->topic += (char) buffer[i]; pos++; } } /* read Message ID */ - if(msg->type == PUBLISH || msg->type == PUBACK || msg->type == PUBREC || msg->type == PUBCOMP || msg->type == SUBACK || msg->type == UNSUBACK){ - msg->msgId = (buffer[pos]<<8) + (buffer[pos+1]); + if (msg->type == PUBLISH || msg->type == PUBACK || msg->type == PUBREC || msg->type == PUBCOMP || msg->type == SUBACK || msg->type == UNSUBACK) { + msg->msgId = (buffer[pos] << 8) + (buffer[pos + 1]); pos += 2; } /********* read Payload *********/ - if(msg->type == PUBLISH){ + if (msg->type == PUBLISH) { msg->payload = ""; - for(int i = pos; i payload += (char)buffer[i]; + for (int i = pos; i < remainingLength + 2; i++) { + msg->payload += (char) buffer[i]; } } } + /** * @brief Dump the message struct. */ -void PubSubClient::dumpData(mqtt_message* msg){ - +void PubSubClient::dumpData(mqtt_message* msg) { ESP_LOGD(TAG, "mqtt_message_type: %s", messageType_toString(msg->type).c_str()); ESP_LOGD(TAG, "mqtt_qos: %d", msg->qos); ESP_LOGD(TAG, "retained: %d", msg->retained); @@ -824,30 +823,31 @@ void PubSubClient::dumpData(mqtt_message* msg){ ESP_LOGD(TAG, "msgId: %d", msg->msgId); } + /** * @brief Convert the MQTT message type to string. * @param [in] message type byte. * @return message type as std::string. */ -std::string PubSubClient::messageType_toString(uint8_t type){ +std::string PubSubClient::messageType_toString(uint8_t type) { std::string str = "Not in list!"; - switch(type){ - case CONNECT : str = "CONNECT"; break; - case CONNACK : str = "CONNACK"; break; - case PUBLISH : str = "PUBLISH"; break; - case PUBACK : str = "PUBACK"; break; - case PUBREC : str = "PUBREC"; break; - case PUBREL : str = "PUBREL"; break; - case PUBCOMP : str = "PUBCOMP"; break; - case SUBSCRIBE : str = "SUBSCRIBE"; break; - case SUBACK : str = "SUBACK"; break; - case UNSUBSCRIBE: str = "UNSUBSCRIBE"; break; - case UNSUBACK : str = "UNSUBACK"; break; - case PINGREQ : str = "PINGREQ"; break; - case PINGRESP : str = "PINGRESP"; break; - case DISCONNECT : str = "DISCONNECT"; break; - case Reserved : str = "Reserved"; break; + switch (type) { + case CONNECT : str = "CONNECT"; break; + case CONNACK : str = "CONNACK"; break; + case PUBLISH : str = "PUBLISH"; break; + case PUBACK : str = "PUBACK"; break; + case PUBREC : str = "PUBREC"; break; + case PUBREL : str = "PUBREL"; break; + case PUBCOMP : str = "PUBCOMP"; break; + case SUBSCRIBE : str = "SUBSCRIBE"; break; + case SUBACK : str = "SUBACK"; break; + case UNSUBSCRIBE: str = "UNSUBSCRIBE"; break; + case UNSUBACK : str = "UNSUBACK"; break; + case PINGREQ : str = "PINGREQ"; break; + case PINGRESP : str = "PINGRESP"; break; + case DISCONNECT : str = "DISCONNECT"; break; + case Reserved : str = "Reserved"; break; + default : break; } - return str; } diff --git a/cpp_utils/PubSubClient.h b/cpp_utils/PubSubClient.h index 8e56ca59..c5da6ca7 100644 --- a/cpp_utils/PubSubClient.h +++ b/cpp_utils/PubSubClient.h @@ -51,14 +51,14 @@ struct mqtt_InitTypeDef{ const char* user; const char* pass; - const char * id; + const char* id; const char* willTopic; uint8_t willQos; bool willRetain; const char* willMessage; }; -typedef enum{ +typedef enum { CONNECTION_TIMEOUT = -4, CONNECTION_LOST = -3, CONNECT_FAILED = -2, @@ -69,9 +69,9 @@ typedef enum{ CONNECT_UNAVAILABLE = 3, CONNECT_BAD_CREDENTIALS = 4, CONNECT_UNAUTHORIZED = 5, -}mqtt_state; +} mqtt_state; -typedef enum{ +typedef enum { CONNECT = 1 << 4, // Client request to connect to Server CONNACK = 2 << 4, // Connect Acknowledgment PUBLISH = 3 << 4, // Publish message @@ -87,15 +87,15 @@ typedef enum{ PINGRESP = 13 << 4, // PING Response DISCONNECT = 14 << 4, // Client is Disconnecting Reserved = 15 << 4, // Reserved -}mqtt_message_type; +} mqtt_message_type; -typedef enum{ +typedef enum { QOS0 = (0 << 1), QOS1 = (1 << 1), QOS2 = (2 << 1), -}mqtt_qos; +} mqtt_qos; -struct mqtt_message{ +struct mqtt_message { uint8_t type; uint8_t qos; bool retained; @@ -105,7 +105,7 @@ struct mqtt_message{ uint16_t msgId; }; -#define MQTT_CALLBACK_SIGNATURE void (*callback)(std::string, std::string) +#define MQTT_CALLBACK_SIGNATURE void (*callback) (std::string, std::string) class PubSubClientTask; @@ -130,19 +130,19 @@ class PubSubClient { void disconnect(); bool publish(const char* topic, const char* payload); bool publish(const char* topic, const char* payload, bool retained); - bool publish(const char* topic, const uint8_t * payload, unsigned int plength); - bool publish(const char* topic, const uint8_t * payload, unsigned int plength, bool retained); + bool publish(const char* topic, const uint8_t* payload, unsigned int plength); + bool publish(const char* topic, const uint8_t* payload, unsigned int plength, bool retained); //bool publish_P(const char* topic, const uint8_t * payload, unsigned int plength, bool retained); - bool subscribe (const char* topic, bool ack=false); - bool unsubscribe (const char* topic, bool ack=false); - bool isSubscribeDone (void); - bool isUnsubscribeDone (void); + bool subscribe(const char* topic, bool ack = false); + bool unsubscribe(const char* topic, bool ack = false); + bool isSubscribeDone(); + bool isUnsubscribeDone(); - bool connected (void); - int state (void); - void keepAliveChecker (void); - void timeoutChecker (void); + bool connected(); + int state(); + void keepAliveChecker(); + void timeoutChecker(); private: friend class PubSubClientTask; @@ -159,12 +159,12 @@ class PubSubClient { FreeRTOSTimer* timeoutTimer; MQTT_CALLBACK_SIGNATURE; - void setup (void); - uint16_t readPacket(); - bool write (uint8_t header, uint8_t* buf, uint16_t length); + void setup(); + uint16_t readPacket(); + bool write(uint8_t header, uint8_t* buf, uint16_t length); uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); - void parseData (mqtt_message* msg, uint16_t len); - void dumpData (mqtt_message* msg); + void parseData(mqtt_message* msg, uint16_t len); + void dumpData(mqtt_message* msg); std::string messageType_toString(uint8_t type); }; diff --git a/cpp_utils/RESTClient.cpp b/cpp_utils/RESTClient.cpp index 7160dca6..a72f80b5 100644 --- a/cpp_utils/RESTClient.cpp +++ b/cpp_utils/RESTClient.cpp @@ -17,7 +17,7 @@ #include "RESTClient.h" -static char tag[] = "RESTClient"; +static const char* LOG_TAG = "RESTClient"; RESTClient::RESTClient() { @@ -37,15 +37,15 @@ RESTClient::~RESTClient() { * @brief Perform an HTTP GET request. */ long RESTClient::get() { - long response_code; // Added return response_code 2018_4_12 + long response_code; // Added return response_code 2018_4_12 prepForCall(); ::curl_easy_setopt(m_curlHandle, CURLOPT_HTTPGET, 1); int rc = ::curl_easy_perform(m_curlHandle); if (rc != CURLE_OK) { - ESP_LOGE(tag, "get(): %s", getErrorMessage().c_str()); + ESP_LOGE(LOG_TAG, "get(): %s", getErrorMessage().c_str()); } - curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &response_code); // Added return response_code 2018_4_12 - return response_code; // Added return response_code 2018_4_12 + curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &response_code); // Added return response_code 2018_4_12 + return response_code; // Added return response_code 2018_4_12 } // get @@ -56,15 +56,15 @@ long RESTClient::get() { * */ long RESTClient::post(std::string body) { - long response_code; // Added return response_code 2018_4_12 + long response_code; // Added return response_code 2018_4_12 prepForCall(); ::curl_easy_setopt(m_curlHandle, CURLOPT_POSTFIELDS, body.c_str()); int rc = ::curl_easy_perform(m_curlHandle); if (rc != CURLE_OK) { - ESP_LOGE(tag, "post(): %s", getErrorMessage().c_str()); + ESP_LOGE(LOG_TAG, "post(): %s", getErrorMessage().c_str()); } - curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &response_code);// Added return response_code 2018_4_12 - return response_code;// Added return response_code 2018_4_12 + curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &response_code);// Added return response_code 2018_4_12 + return response_code;// Added return response_code 2018_4_12 } // post @@ -90,10 +90,10 @@ std::string RESTClient::getErrorMessage() { * * @return The number of bytes of data processed. */ -size_t RESTClient::handleData(void *buffer, size_t size, size_t nmemb, void *userp) { +size_t RESTClient::handleData(void* buffer, size_t size, size_t nmemb, void* userp) { //printf("handleData: size: %d, num: %d\n", size, nmemb); - RESTClient *pClient = (RESTClient *)userp; - pClient->m_response.append((const char *)buffer, size*nmemb); + RESTClient* pClient = (RESTClient*) userp; + pClient->m_response.append((const char*) buffer, size * nmemb); return size * nmemb; } // handleData @@ -139,7 +139,7 @@ void RESTClient::prepForCall() { } // prepForCall -RESTTimings::RESTTimings(RESTClient *client) { +RESTTimings::RESTTimings(RESTClient* client) { this->client = client; } diff --git a/cpp_utils/RESTClient.h b/cpp_utils/RESTClient.h index 4ad91d5a..f44612ac 100644 --- a/cpp_utils/RESTClient.h +++ b/cpp_utils/RESTClient.h @@ -19,9 +19,10 @@ class RESTClient; */ class RESTTimings { public: - RESTTimings(RESTClient *client); + RESTTimings(RESTClient* client); void refresh(); std::string toString(); + private: double m_namelookup = 0; double m_connect = 0; @@ -29,7 +30,8 @@ class RESTTimings { double m_pretransfer = 0; double m_starttransfer = 0; double m_total = 0; - RESTClient *client = nullptr; + RESTClient* client = nullptr; + }; /** @@ -107,19 +109,18 @@ class RESTClient { m_verbose = value; }; - - private: - CURL *m_curlHandle; + CURL* m_curlHandle; std::string m_url; char m_errbuf[CURL_ERROR_SIZE]; - struct curl_slist *m_headers = nullptr; + struct curl_slist* m_headers = nullptr; bool m_verbose = false; friend class RESTTimings; - RESTTimings *m_timings; + RESTTimings* m_timings; std::string m_response; - static size_t handleData(void *buffer, size_t size, size_t nmemb, void *userp); + static size_t handleData(void* buffer, size_t size, size_t nmemb, void* userp); void prepForCall(); + }; #endif /* CONFIG_LIBCURL_PRESENT */ #endif /* MAIN_RESTCLIENT_H_ */ diff --git a/cpp_utils/RMT.cpp b/cpp_utils/RMT.cpp index 750da1a6..9f9a5950 100644 --- a/cpp_utils/RMT.cpp +++ b/cpp_utils/RMT.cpp @@ -8,6 +8,7 @@ #include #include "RMT.h" + //static char tag[] = "RMT"; /** * @brief Create a class instance. @@ -22,7 +23,7 @@ RMT::RMT(gpio_num_t pin, rmt_channel_t channel) { config.rmt_mode = RMT_MODE_TX; config.channel = channel; config.gpio_num = pin; - config.mem_block_num = 8-this->channel; + config.mem_block_num = 8 - this->channel; config.clk_div = 8; config.tx_config.loop_en = 0; config.tx_config.carrier_en = 0; @@ -32,7 +33,6 @@ RMT::RMT(gpio_num_t pin, rmt_channel_t channel) { config.tx_config.carrier_level = (rmt_carrier_level_t)1; config.tx_config.carrier_duty_percent = 50; - ESP_ERROR_CHECK(rmt_config(&config)); ESP_ERROR_CHECK(rmt_driver_install(this->channel, 0, 0)); } @@ -45,6 +45,7 @@ RMT::~RMT() { ESP_ERROR_CHECK(::rmt_driver_uninstall(this->channel)); } + /** * @brief Start receiving. */ @@ -52,6 +53,7 @@ void RMT::rxStart() { ESP_ERROR_CHECK(::rmt_rx_start(this->channel, true)); } + /** * @brief Stop receiving. */ @@ -59,6 +61,7 @@ void RMT::rxStop() { ESP_ERROR_CHECK(::rmt_rx_stop(this->channel)); } + /** * @brief Start transmitting. */ @@ -66,6 +69,7 @@ void RMT::txStart() { ESP_ERROR_CHECK(::rmt_tx_start(this->channel, true)); } + /** * @brief Stop transmitting. */ @@ -73,6 +77,7 @@ void RMT::txStop() { ESP_ERROR_CHECK(::rmt_tx_stop(this->channel)); } + /** * @brief Write the items out through the RMT. * @@ -81,7 +86,7 @@ void RMT::txStop() { * is cleared. */ void RMT::write() { - add(false,0); + add(false, 0); ESP_ERROR_CHECK(::rmt_write_items(this->channel, &items[0], items.size(), true)); clear(); } @@ -100,12 +105,13 @@ void RMT::add(bool level, uint32_t duration) { item.duration0 = duration; items.push_back(item); } else { - items.at(bitCount/2).level1 = level; - items.at(bitCount/2).duration1 = duration; + items.at(bitCount / 2).level1 = level; + items.at(bitCount / 2).duration1 = duration; } bitCount++; } + /** * @brief Clear any previously written level/duration pairs that have not been sent. */ diff --git a/cpp_utils/RMT.h b/cpp_utils/RMT.h index 90e59007..8c079f80 100644 --- a/cpp_utils/RMT.h +++ b/cpp_utils/RMT.h @@ -15,7 +15,7 @@ */ class RMT { public: - RMT(gpio_num_t pin, rmt_channel_t channel=RMT_CHANNEL_0); + RMT(gpio_num_t pin, rmt_channel_t channel = RMT_CHANNEL_0); virtual ~RMT(); void add(bool level, uint32_t duration); void clear(); @@ -25,11 +25,11 @@ class RMT { void txStop(); void write(); - private: rmt_channel_t channel; std::vector items; int bitCount = 0; + }; #endif /* COMPONENTS_CPP_UTILS_RMT_H_ */ diff --git a/cpp_utils/SOC.cpp b/cpp_utils/SOC.cpp index 2f4d3a9a..fa12d4e6 100644 --- a/cpp_utils/SOC.cpp +++ b/cpp_utils/SOC.cpp @@ -34,9 +34,9 @@ void SOC::I2S::dump() { I2S0.clkm_conf.clka_en); uint32_t clockSpeed; if (I2S0.clkm_conf.clkm_div_a == 0) { - clockSpeed = 160000000/I2S0.clkm_conf.clkm_div_num; + clockSpeed = 160000000 / I2S0.clkm_conf.clkm_div_num; } else { - clockSpeed = 160000000/(I2S0.clkm_conf.clkm_div_num + I2S0.clkm_conf.clkm_div_b/I2S0.clkm_conf.clkm_div_a); + clockSpeed = 160000000 / (I2S0.clkm_conf.clkm_div_num + I2S0.clkm_conf.clkm_div_b / I2S0.clkm_conf.clkm_div_a); } printf("Clock speed: %d\n", clockSpeed); printf("\n"); @@ -44,8 +44,8 @@ void SOC::I2S::dump() { printf("I2S_CONF_REG\n"); printf("------------\n"); printf("tx_slave_mod: %s, rx_slave_mod: %s, rx_msb_right: %d, rx_right_first: %d\n", - I2S0.conf.tx_slave_mod==0?"Master":"Slave", - I2S0.conf.rx_slave_mod==0?"Master":"Slave", + (I2S0.conf.tx_slave_mod == 0) ? "Master" : "Slave", + (I2S0.conf.rx_slave_mod == 0) ? "Master" : "Slave", I2S0.conf.rx_msb_right, I2S0.conf.rx_right_first); printf("\n"); diff --git a/cpp_utils/SPI.cpp b/cpp_utils/SPI.cpp index 5ce45d15..1312e706 100644 --- a/cpp_utils/SPI.cpp +++ b/cpp_utils/SPI.cpp @@ -101,6 +101,7 @@ void SPI::setHost(spi_host_device_t host) { m_host = host; } // setHost + /** * @brief Send and receive data through %SPI. This is a blocking call. * @@ -111,7 +112,7 @@ void SPI::transfer(uint8_t* data, size_t dataLen) { assert(data != nullptr); assert(dataLen > 0); #ifdef DEBUG - for (auto i=0; i %2d %.2x", i, data[i]); } #endif @@ -141,5 +142,3 @@ uint8_t SPI::transferByte(uint8_t value) { transfer(&value, 1); return value; } // transferByte - - diff --git a/cpp_utils/SPI.h b/cpp_utils/SPI.h index d2de5d99..6ecf64e1 100644 --- a/cpp_utils/SPI.h +++ b/cpp_utils/SPI.h @@ -22,32 +22,33 @@ class SPI { int clkPin = DEFAULT_CLK_PIN, int csPin = DEFAULT_CS_PIN); void setHost(spi_host_device_t host); - void transfer(uint8_t *data, size_t dataLen); + void transfer(uint8_t* data, size_t dataLen); uint8_t transferByte(uint8_t value); + /** * @brief The default MOSI pin. */ - static const int DEFAULT_MOSI_PIN = GPIO_NUM_13; - - /** - * @brief The default MISO pin. - */ - static const int DEFAULT_MISO_PIN = GPIO_NUM_12; - - /** - * @brief The default CLK pin. - */ - static const int DEFAULT_CLK_PIN = GPIO_NUM_14; - - /** - * @brief The default CS pin. - */ - static const int DEFAULT_CS_PIN = GPIO_NUM_15; - - /** - * @brief Value of unset pin. - */ - static const int PIN_NOT_SET = -1; + static const int DEFAULT_MOSI_PIN = GPIO_NUM_13; + + /** + * @brief The default MISO pin. + */ + static const int DEFAULT_MISO_PIN = GPIO_NUM_12; + + /** + * @brief The default CLK pin. + */ + static const int DEFAULT_CLK_PIN = GPIO_NUM_14; + + /** + * @brief The default CS pin. + */ + static const int DEFAULT_CS_PIN = GPIO_NUM_15; + + /** + * @brief Value of unset pin. + */ + static const int PIN_NOT_SET = -1; private: spi_device_handle_t m_handle; diff --git a/cpp_utils/SSLUtils.cpp b/cpp_utils/SSLUtils.cpp index 92ebcfc4..36621b3e 100644 --- a/cpp_utils/SSLUtils.cpp +++ b/cpp_utils/SSLUtils.cpp @@ -20,7 +20,7 @@ SSLUtils::~SSLUtils() { void SSLUtils::setCertificate(std::string certificate) { size_t len = certificate.length(); - m_certificate = (char*)malloc(len + 1); + m_certificate = (char*) malloc(len + 1); memcpy(m_certificate, certificate.data(), len); m_certificate[len] = '\0'; } @@ -31,7 +31,7 @@ char* SSLUtils::getCertificate() { void SSLUtils::setKey(std::string key) { size_t len = key.length(); - m_key = (char*)malloc(len + 1); + m_key = (char*) malloc(len + 1); memcpy(m_key, key.data(), len); m_key[len] = '\0'; } diff --git a/cpp_utils/SmartLED.cpp b/cpp_utils/SmartLED.cpp index e0267585..430c6317 100644 --- a/cpp_utils/SmartLED.cpp +++ b/cpp_utils/SmartLED.cpp @@ -9,13 +9,13 @@ #include "string.h" #include -const char* LOG_TAG = "SmartLED"; +static const char* LOG_TAG = "SmartLED"; SmartLED::SmartLED() { m_brightness = 100; m_pixelCount = 0; m_pixels = nullptr; - m_colorOrder = (char *)"GRB"; + m_colorOrder = (char*) "GRB"; } // SmartLED @@ -32,7 +32,7 @@ SmartLED::~SmartLED() { * The LEDs are not actually updated until a call to show() is subsequently made. */ void SmartLED::clear() { - for (auto i=0; im_pixelCount; i++) { + for (auto i = 0; i < this->m_pixelCount; i++) { m_pixels[i].red = 0; m_pixels[i].green = 0; m_pixels[i].blue = 0; @@ -48,6 +48,7 @@ uint32_t SmartLED::getBrightness() { return m_brightness; } // getBrightness + /** * @brief Return the number of pixels in the chain. * @return The number of pixels in the chain as previously set by setPixelCount(). @@ -65,6 +66,7 @@ void SmartLED::setBrightness(uint32_t percent) { m_brightness = percent; } // setBrightness + /** * @brief Set the color order of data sent to the LEDs. * @@ -77,7 +79,7 @@ void SmartLED::setBrightness(uint32_t percent) { * an alternate order by supply an alternate three character string made up of 'R', 'G' and 'B' * for example "RGB". */ -void SmartLED::setColorOrder(char *colorOrder) { +void SmartLED::setColorOrder(char* colorOrder) { if (colorOrder != nullptr && strlen(colorOrder) == 3) { m_colorOrder = colorOrder; } @@ -94,9 +96,8 @@ void SmartLED::setColorOrder(char *colorOrder) { * @param [in] green The amount of green in the pixel. * @param [in] blue The amount of blue in the pixel. */ -void SmartLED::setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue) { +void SmartLED::setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue) { //assert(index < m_pixelCount); - m_pixels[index].red = red; m_pixels[index].green = green; m_pixels[index].blue = blue; @@ -127,12 +128,12 @@ void SmartLED::setPixel(uint16_t index, pixel_t pixel) { */ void SmartLED::setPixel(uint16_t index, uint32_t pixel) { //assert(index < m_pixelCount); - m_pixels[index].red = pixel & 0xff; m_pixels[index].green = (pixel & 0xff00) >> 8; m_pixels[index].blue = (pixel & 0xff0000) >> 16; } // setPixel + void SmartLED::setPixelCount(uint16_t pixelCount) { ESP_LOGD(LOG_TAG, ">> setPixelCount: %d", pixelCount); if (m_pixels != nullptr) { @@ -143,6 +144,7 @@ void SmartLED::setPixelCount(uint16_t pixelCount) { ESP_LOGD(LOG_TAG, "<< setPixelCount"); } + /** * @brief Set the given pixel to the specified HSB color. * @@ -153,66 +155,60 @@ void SmartLED::setPixelCount(uint16_t pixelCount) { * @param [in] saturation The amount of saturation in the pixel (0-255). * @param [in] brightness The amount of brightness in the pixel (0-255). */ -void SmartLED::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness) { - double sat_red; - double sat_green; - double sat_blue; - double ctmp_red; - double ctmp_green; - double ctmp_blue; - double new_red; - double new_green; - double new_blue; - double dSaturation=(double)saturation/255; - double dBrightness=(double)brightness/255; - - //assert(index < pixelCount); - - if (hue < 120) { - sat_red = (120 - hue) / 60.0; - sat_green = hue / 60.0; - sat_blue = 0; - } else if (hue < 240) { - sat_red = 0; - sat_green = (240 - hue) / 60.0; - sat_blue = (hue - 120) / 60.0; - } else { - sat_red = (hue - 240) / 60.0; - sat_green = 0; - sat_blue = (360 - hue) / 60.0; - } - - if (sat_red>1.0) { - sat_red = 1.0; - } - if (sat_green>1.0) { - sat_green = 1.0; - } - if (sat_blue>1.0) { - sat_blue = 1.0; - } - - ctmp_red = 2 * dSaturation * sat_red + (1 - dSaturation); - ctmp_green = 2 * dSaturation * sat_green + (1 - dSaturation); - ctmp_blue = 2 * dSaturation * sat_blue + (1 - dSaturation); - - if (dBrightness < 0.5) { - new_red = dBrightness * ctmp_red; - new_green = dBrightness * ctmp_green; - new_blue = dBrightness * ctmp_blue; - } else { - new_red = (1 - dBrightness) * ctmp_red + 2 * dBrightness - 1; - new_green = (1 - dBrightness) * ctmp_green + 2 * dBrightness - 1; - new_blue = (1 - dBrightness) * ctmp_blue + 2 * dBrightness - 1; - } - - m_pixels[index].red = (uint8_t)(new_red*255); - m_pixels[index].green = (uint8_t)(new_green*255); - m_pixels[index].blue = (uint8_t)(new_blue*255); -} // setHSBPixel - - - +void SmartLED::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness) { + double sat_red; + double sat_green; + double sat_blue; + double ctmp_red; + double ctmp_green; + double ctmp_blue; + double new_red; + double new_green; + double new_blue; + double dSaturation=(double) saturation / 255; + double dBrightness=(double) brightness / 255; + + //assert(index < pixelCount); + + if (hue < 120) { + sat_red = (120 - hue) / 60.0; + sat_green = hue / 60.0; + sat_blue = 0; + } else if (hue < 240) { + sat_red = 0; + sat_green = (240 - hue) / 60.0; + sat_blue = (hue - 120) / 60.0; + } else { + sat_red = (hue - 240) / 60.0; + sat_green = 0; + sat_blue = (360 - hue) / 60.0; + } + if (sat_red>1.0) { + sat_red = 1.0; + } + if (sat_green>1.0) { + sat_green = 1.0; + } + if (sat_blue>1.0) { + sat_blue = 1.0; + } + ctmp_red = 2 * dSaturation * sat_red + (1 - dSaturation); + ctmp_green = 2 * dSaturation * sat_green + (1 - dSaturation); + ctmp_blue = 2 * dSaturation * sat_blue + (1 - dSaturation); + + if (dBrightness < 0.5) { + new_red = dBrightness * ctmp_red; + new_green = dBrightness * ctmp_green; + new_blue = dBrightness * ctmp_blue; + } else { + new_red = (1 - dBrightness) * ctmp_red + 2 * dBrightness - 1; + new_green = (1 - dBrightness) * ctmp_green + 2 * dBrightness - 1; + new_blue = (1 - dBrightness) * ctmp_blue + 2 * dBrightness - 1; + } + m_pixels[index].red = (uint8_t)(new_red * 255); + m_pixels[index].green = (uint8_t)(new_green * 255); + m_pixels[index].blue = (uint8_t)(new_blue * 255); +} // setHSBPixel diff --git a/cpp_utils/SmartLED.h b/cpp_utils/SmartLED.h index e70c4843..7903f697 100644 --- a/cpp_utils/SmartLED.h +++ b/cpp_utils/SmartLED.h @@ -36,13 +36,14 @@ class SmartLED { virtual void init() = 0; virtual void show() = 0; void setBrightness(uint32_t percent); - void setColorOrder(char *order); + void setColorOrder(char* order); void setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue); void setPixel(uint16_t index, pixel_t pixel); void setPixel(uint16_t index, uint32_t pixel); void setPixelCount(uint16_t pixelCount); void setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness); void clear(); + protected: uint32_t m_brightness; char* m_colorOrder; diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 296b3a4b..32b494ab 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -61,14 +61,12 @@ SockServ::~SockServ() { * socket is placed on a queue and a semaphore signaled that a new client is available. */ /* static */ void SockServ::acceptTask(void* data) { - SockServ* pSockServ = (SockServ*)data; + SockServ* pSockServ = (SockServ*) data; try { - while(1) { + while (true) { ESP_LOGD(LOG_TAG, "Waiting on accept"); Socket tempSock = pSockServ->m_serverSocket.accept(); - if (!tempSock.isValid()) { - continue; - } + if (!tempSock.isValid()) continue; pSockServ->m_clientSet.insert(tempSock); xQueueSendToBack(pSockServ->m_acceptQueue, &tempSock, portMAX_DELAY); @@ -82,7 +80,6 @@ SockServ::~SockServ() { } // acceptTask - /** * @brief Determine the number of connected partners. * @@ -117,12 +114,12 @@ bool SockServ::getSSL() { * @return The amount of data returned or 0 if there was an error. */ size_t SockServ::receiveData(Socket s, void* pData, size_t maxData) { - int rc = s.receive((uint8_t*)pData, maxData); + int rc = s.receive((uint8_t*) pData, maxData); if (rc == -1) { ESP_LOGE(LOG_TAG, "recv(): %s", strerror(errno)); return 0; } - return rc; + return (size_t) rc; } // receiveData @@ -132,7 +129,7 @@ size_t SockServ::receiveData(Socket s, void* pData, size_t maxData) { * @param[in] str A string from which sequence of bytes will be used to send to the partner. */ void SockServ::sendData(std::string str) { - sendData((uint8_t *)str.data(), str.size()); + sendData((uint8_t*) str.data(), str.size()); } // sendData @@ -144,7 +141,7 @@ void SockServ::sendData(std::string str) { */ void SockServ::sendData(uint8_t* data, size_t length) { for (auto it = m_clientSet.begin(); it != m_clientSet.end(); ++it) { - (*it).send(data, length); + (*it).send(data, length); } } // sendData @@ -173,7 +170,7 @@ void SockServ::start() { //m_serverSocket.setSSL(m_useSSL); m_serverSocket.listen(m_port); // Create a socket and start listening on it. ESP_LOGD(LOG_TAG, "Now listening on port %d", m_port); - FreeRTOS::startTask(acceptTask, "acceptTask", this, 8*1024); + FreeRTOS::startTask(acceptTask, "acceptTask", this, 8 * 1024); } // start @@ -201,11 +198,11 @@ Socket SockServ::waitForData(std::set& socketSet) { } // End for int rc = ::select( - maxFd+1, // Number of sockets to scan &readSet, // Set of read sockets nullptr, // Set of write sockets nullptr, // Set of exception sockets nullptr // Timeout + maxFd + 1, // Number of sockets to scan ); if (rc == -1) { ESP_LOGE(LOG_TAG, "Error with select"); @@ -213,7 +210,7 @@ Socket SockServ::waitForData(std::set& socketSet) { return s; } - for ( auto it = socketSet.begin(); it != socketSet.end(); ++it) { + for (auto it = socketSet.begin(); it != socketSet.end(); ++it) { if (FD_ISSET(it->getFD(), &readSet)) { return *it; } @@ -241,5 +238,3 @@ Socket SockServ::waitForNewClient() { ESP_LOGD(LOG_TAG, "<< waitForNewClient"); return tempSocket; } // waitForNewClient - - diff --git a/cpp_utils/SockServ.h b/cpp_utils/SockServ.h index e0ffee76..770ec954 100644 --- a/cpp_utils/SockServ.h +++ b/cpp_utils/SockServ.h @@ -51,11 +51,12 @@ class SockServ { void sendData(uint8_t* data, size_t length); void sendData(std::string str); void setPort(uint16_t port); - void setSSL(bool use=true); + void setSSL(bool use = true); void start(); void stop(); Socket waitForData(std::set& socketSet); Socket waitForNewClient(); + }; #endif /* MAIN_SOCKSERV_H_ */ diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index bc5a064c..db5991ab 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -13,8 +13,6 @@ #include - - #include #include #include @@ -32,11 +30,11 @@ static const char* LOG_TAG = "Socket"; #undef bind static void my_debug( - void *ctx, + void* ctx, int level, - const char *file, + const char* file, int line, - const char *str) { + const char* str) { ((void) level); ((void) ctx); @@ -62,7 +60,7 @@ Socket Socket::accept() { ESP_LOGD(LOG_TAG, ">> accept: Accepting on %s; sockFd: %d, using SSL: %d", addressToString(&addr).c_str(), m_sock, getSSL()); struct sockaddr_in client_addr; socklen_t sin_size; - int clientSockFD = ::lwip_accept_r(m_sock, (struct sockaddr *)&client_addr, &sin_size); + int clientSockFD = ::lwip_accept_r(m_sock, (struct sockaddr*) &client_addr, &sin_size); //printf("------> new connection client %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); if (clientSockFD == -1) { SocketException se(errno); @@ -92,7 +90,7 @@ Socket Socket::accept() { * @return A string representation of the address. */ std::string Socket::addressToString(struct sockaddr* addr) { - struct sockaddr_in *pInAddr = (struct sockaddr_in *)addr; + struct sockaddr_in* pInAddr = (struct sockaddr_in*) addr; char temp[30]; char ip[20]; inet_ntop(AF_INET, &pInAddr->sin_addr, ip, sizeof(ip)); @@ -119,7 +117,7 @@ int Socket::bind(uint16_t port, uint32_t address) { serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = htonl(address); serverAddress.sin_port = htons(port); - int rc = ::lwip_bind_r(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + int rc = ::lwip_bind_r(m_sock, (struct sockaddr*) &serverAddress, sizeof(serverAddress)); if (rc != 0) { ESP_LOGE(LOG_TAG, "<< bind: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); return rc; @@ -172,7 +170,7 @@ int Socket::connect(struct in_addr address, uint16_t port) { inet_ntop(AF_INET, &address, msg, sizeof(msg)); ESP_LOGD(LOG_TAG, "Connecting to %s:[%d]", msg, port); createSocket(); - int rc = ::lwip_connect_r(m_sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); + int rc = ::lwip_connect_r(m_sock, (struct sockaddr*) &serverAddress, sizeof(struct sockaddr_in)); if (rc == -1) { ESP_LOGE(LOG_TAG, "connect_cpp: Error: %s", strerror(errno)); close(); @@ -193,7 +191,7 @@ int Socket::connect(struct in_addr address, uint16_t port) { */ int Socket::connect(char* strAddress, uint16_t port) { struct in_addr address; - inet_pton(AF_INET, (char *)strAddress, &address); + inet_pton(AF_INET, strAddress, &address); return connect(address, port); } @@ -207,8 +205,7 @@ int Socket::createSocket(bool isDatagram) { ESP_LOGD(LOG_TAG, ">> createSocket: isDatagram: %d", isDatagram); if (isDatagram) { m_sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - } - else { + } else { m_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } if (m_sock == -1) { @@ -290,13 +287,12 @@ bool Socket::operator <(const Socket& other) const { /** * @brief Set the socket option. */ -int Socket::setSocketOption(int option, void* value, size_t len) -{ - int res = ::setsockopt(m_sock, SOL_SOCKET, option, value, len); - if(res < 0) { - ESP_LOGE(LOG_TAG, "%X : %d", option, errno); - } - return res; +int Socket::setSocketOption(int option, void* value, size_t len) { + int res = ::setsockopt(m_sock, SOL_SOCKET, option, value, len); + if (res < 0) { + ESP_LOGE(LOG_TAG, "%X : %d", option, errno); + } + return res; } // setSocketOption @@ -304,15 +300,14 @@ int Socket::setSocketOption(int option, void* value, size_t len) * @brief Socket timeout. * @param [in] seconds to wait. */ -int Socket::setTimeout(uint32_t seconds) -{ - struct timeval tv; - tv.tv_sec = seconds; - tv.tv_usec = 0; - if(setSocketOption(SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)) < 0) { - return -1; - } - return setSocketOption(SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); +int Socket::setTimeout(uint32_t seconds) { + struct timeval tv; + tv.tv_sec = seconds; + tv.tv_usec = 0; + if (setSocketOption(SO_RCVTIMEO, (char*) &tv, sizeof(struct timeval)) < 0) { + return -1; + } + return setSocketOption(SO_SNDTIMEO, (char*) &tv, sizeof(struct timeval)); } @@ -320,21 +315,15 @@ std::string Socket::readToDelim(std::string delim) { std::string ret; std::string part; auto it = delim.begin(); - while(1) { + while (true) { uint8_t val; int rc = receive(&val, 1); - if (rc == -1) { - return ""; - } - if (rc == 0) { - return ret+part; - } + if (rc == -1) return ""; + if (rc == 0) return ret + part; if (*it == val) { part+= val; ++it; - if (it == delim.end()) { - return ret; - } + if (it == delim.end()) return ret; } else { if (part.empty()) { ret += part; @@ -347,7 +336,6 @@ std::string Socket::readToDelim(std::string delim) { } // readToDelim - /** * @brief Receive data from the partner. * Receive data from the socket partner. If exact = false, we read as much data as @@ -360,13 +348,13 @@ std::string Socket::readToDelim(std::string delim) { */ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { //ESP_LOGD(LOG_TAG, ">> receive: sockFd: %d, length: %d, exact: %d", m_sock, length, exact); - if (exact == false) { + if (!exact) { int rc; if (getSSL()) { do { rc = mbedtls_ssl_read(&m_sslContext, data, length); ESP_LOGD(LOG_TAG, "rc=%d, MBEDTLS_ERR_SSL_WANT_READ=%d", rc, MBEDTLS_ERR_SSL_WANT_READ); - } while(rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); + } while (rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); } else { rc = ::lwip_recv_r(m_sock, data, length, 0); if (rc == -1) { @@ -375,16 +363,16 @@ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { } //GeneralUtils::hexDump(data, rc); //ESP_LOGD(LOG_TAG, "<< receive: rc: %d", rc); - return rc; + return (size_t) rc; } // Read what we can, doesn't need to be an exact amount. size_t amountToRead = length; int rc; - while(amountToRead > 0) { + while (amountToRead > 0) { if (getSSL()) { do { rc = mbedtls_ssl_read(&m_sslContext, data, amountToRead); - } while(rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); + } while (rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); } else { rc = ::lwip_recv_r(m_sock, data, amountToRead, 0); } @@ -392,9 +380,7 @@ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { ESP_LOGE(LOG_TAG, "receive: %s", strerror(errno)); return 0; } - if (rc == 0) { - break; - } + if (rc == 0) break; amountToRead -= rc; data += rc; } @@ -411,7 +397,7 @@ size_t Socket::receive(uint8_t* data, size_t length, bool exact) { * @param [in] pAddr An area into which we can store the address of the partner. * @return The length of the data received. */ -int Socket::receiveFrom(uint8_t* data, size_t length, struct sockaddr *pAddr) { +int Socket::receiveFrom(uint8_t* data, size_t length, struct sockaddr *pAddr) { socklen_t addrLen = sizeof(struct sockaddr); int rc = ::recvfrom(m_sock, data, length, 0, pAddr, &addrLen); return rc; @@ -430,35 +416,34 @@ int Socket::send(const uint8_t* data, size_t length) const { ESP_LOGD(LOG_TAG, "send: Raw binary of length: %d", length); //GeneralUtils::hexDump(data, length); int rc = ERR_OK; - while (length > 0) - { - if (getSSL()) { - rc = mbedtls_ssl_write((mbedtls_ssl_context*)&m_sslContext, data, length); - // retry with same parameters if MBEDTLS_ERR_SSL_WANT_WRITE or MBEDTLS_ERR_SSL_WANT_READ - if ((rc != MBEDTLS_ERR_SSL_WANT_WRITE) && (rc != MBEDTLS_ERR_SSL_WANT_READ)) { - if (rc < 0) { - // no cure for other errors - log and exit - ESP_LOGE(LOG_TAG, "send: SSL write error %d", rc); - return rc; - } else { - // not all data was written, try again for the remainder - length -= rc; - data += rc; - } - } - } else { - rc = ::lwip_send_r(m_sock, data, length, 0); - if ((rc < 0) && (errno != EAGAIN)) { - // no cure for errors other than EAGAIN - log and exit - ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); - return rc; - } else if (rc > 0) { - // not all data was written, try again for the remainder - length -= rc; - data += rc; - } - } - } + while (length > 0) { + if (getSSL()) { + rc = mbedtls_ssl_write((mbedtls_ssl_context*)&m_sslContext, data, length); + // retry with same parameters if MBEDTLS_ERR_SSL_WANT_WRITE or MBEDTLS_ERR_SSL_WANT_READ + if ((rc != MBEDTLS_ERR_SSL_WANT_WRITE) && (rc != MBEDTLS_ERR_SSL_WANT_READ)) { + if (rc < 0) { + // no cure for other errors - log and exit + ESP_LOGE(LOG_TAG, "send: SSL write error %d", rc); + return rc; + } else { + // not all data was written, try again for the remainder + length -= rc; + data += rc; + } + } + } else { + rc = ::lwip_send_r(m_sock, data, length, 0); + if ((rc < 0) && (errno != EAGAIN)) { + // no cure for errors other than EAGAIN - log and exit + ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); + return rc; + } else if (rc > 0) { + // not all data was written, try again for the remainder + length -= rc; + data += rc; + } + } + } return rc; } // send @@ -471,19 +456,19 @@ int Socket::send(const uint8_t* data, size_t length) const { */ int Socket::send(std::string value) const { ESP_LOGD(LOG_TAG, "send: Binary of length: %d", value.length()); - return send((uint8_t *)value.data(), value.size()); + return send((uint8_t*) value.data(), value.size()); } // send int Socket::send(uint16_t value) { ESP_LOGD(LOG_TAG, "send: 16bit value: %.2x", value); - return send((uint8_t *)&value, sizeof(value)); + return send((uint8_t*) &value, sizeof(value)); } // send_cpp int Socket::send(uint32_t value) { ESP_LOGD(LOG_TAG, "send: 32bit value: %.2x", value); - return send((uint8_t *)&value, sizeof(value)); + return send((uint8_t*) &value, sizeof(value)); } // send @@ -512,7 +497,7 @@ void Socket::sendTo(const uint8_t* data, size_t length, struct sockaddr* pAddr) */ void Socket::setReuseAddress(bool value) { ESP_LOGD(LOG_TAG, ">> setReuseAddress: %d", value); - int val = value?1:0; + int val = value ? 1 : 0; setSocketOption(SO_REUSEADDR, &val, sizeof(val)); ESP_LOGD(LOG_TAG, "<< setReuseAddress"); } // setReuseAddress @@ -527,9 +512,9 @@ void Socket::setSSL(bool sslValue) { ESP_LOGD(LOG_TAG, ">> setSSL: %s", sslValue?"Yes":"No"); m_useSSL = sslValue; - if (sslValue == true) { - char* pvtKey = SSLUtils::getKey(); - char* certificate = SSLUtils::getCertificate(); + if (sslValue) { + char *pvtKey = SSLUtils::getKey(); + char *certificate = SSLUtils::getCertificate(); if (pvtKey == nullptr) { ESP_LOGE(LOG_TAG, "No private key file"); return; @@ -547,66 +532,56 @@ void Socket::setSSL(bool sslValue) { mbedtls_entropy_init(&m_entropy); mbedtls_ctr_drbg_init(&m_ctr_drbg); - int ret = mbedtls_x509_crt_parse(&m_srvcert, (unsigned char*)certificate, strlen(certificate)+1); - if( ret != 0 ) { - ESP_LOGD(LOG_TAG, "mbedtls_x509_crt_parse returned 0x%x", -ret ); + int ret = mbedtls_x509_crt_parse(&m_srvcert, (unsigned char *) certificate, strlen(certificate) + 1); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_x509_crt_parse returned 0x%x", -ret); goto exit; } - ret = mbedtls_pk_parse_key(&m_pkey, (unsigned char*)pvtKey, strlen(pvtKey)+1, NULL, 0 ); - if( ret != 0 ) { + ret = mbedtls_pk_parse_key(&m_pkey, (unsigned char *) pvtKey, strlen(pvtKey) + 1, NULL, 0); + if (ret != 0) { ESP_LOGD(LOG_TAG, "mbedtls_pk_parse_key returned 0x%x", -ret); goto exit; } - ret = mbedtls_ctr_drbg_seed( &m_ctr_drbg, mbedtls_entropy_func, &m_entropy, (const unsigned char*) pers, strlen(pers)); - if( ret != 0 ) { - ESP_LOGD(LOG_TAG, "! mbedtls_ctr_drbg_seed returned %d\n",ret); - goto exit; - } + ret = mbedtls_ctr_drbg_seed(&m_ctr_drbg, mbedtls_entropy_func, &m_entropy, (const unsigned char*) pers, strlen(pers)); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "! mbedtls_ctr_drbg_seed returned %d\n", ret); + goto exit; + } ret = mbedtls_ssl_config_defaults(&m_conf, - MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT); - if(ret != 0 ) { + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { ESP_LOGD(LOG_TAG, "mbedtls_ssl_config_defaults returned %d\n\n", ret); goto exit; } mbedtls_ssl_conf_authmode(&m_conf, MBEDTLS_SSL_VERIFY_NONE); - mbedtls_ssl_conf_rng(&m_conf, mbedtls_ctr_drbg_random, &m_ctr_drbg); +// mbedtls_ssl_conf_ca_chain( &m_conf, m_srvcert.next, NULL); + ret = mbedtls_ssl_conf_own_cert(&m_conf, &m_srvcert, &m_pkey); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_conf_own_cert returned %d\n\n", ret); + goto exit; + } -// mbedtls_ssl_conf_ca_chain( &m_conf, m_srvcert.next, NULL); - ret = mbedtls_ssl_conf_own_cert( &m_conf, &m_srvcert, &m_pkey); - if(ret != 0) { - ESP_LOGD(LOG_TAG, "mbedtls_ssl_conf_own_cert returned %d\n\n", ret); - goto exit; - } - - mbedtls_ssl_conf_dbg(&m_conf, my_debug, nullptr); + mbedtls_ssl_conf_dbg(&m_conf, my_debug, nullptr); #ifdef CONFIG_MBEDTLS_DEBUG - mbedtls_debug_set_threshold(4); + mbedtls_debug_set_threshold(4); #endif - ret = mbedtls_ssl_setup(&m_sslContext, &m_conf); - if(ret != 0) { - ESP_LOGD(LOG_TAG, "mbedtls_ssl_setup returned %d\n\n", ret ); - goto exit; - } -/* - ret = mbedtls_ssl_set_hostname(&m_sslContext, "192.168.1.99"); - if(ret != 0) { - ESP_LOGD(LOG_TAG, "mbedtls_ssl_set_hostname returned %d\n\n", ret ); - goto exit; - } - */ - - exit: - return; + ret = mbedtls_ssl_setup(&m_sslContext, &m_conf); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_setup returned %d\n\n", ret); + goto exit; + } } +exit: + return; } // setSSL @@ -619,14 +594,12 @@ void Socket::sslHandshake() { ESP_LOGD(LOG_TAG, " - Reset complete"); mbedtls_ssl_set_bio(&m_sslContext, &m_sslSock, mbedtls_net_send, mbedtls_net_recv, NULL); - while(1) { + while (true) { int ret = mbedtls_ssl_handshake(&m_sslContext); - if (ret == 0) { - break; - } + if (ret == 0) break; - if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - ESP_LOGD(LOG_TAG, "mbedtls_ssl_handshake returned %d\n\n", ret ); + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_handshake returned %d\n\n", ret); return; } } // End while @@ -675,13 +648,10 @@ SocketInputRecordStreambuf::~SocketInputRecordStreambuf() { * */ SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() { - if (m_sizeRead >= m_dataLength) { - return EOF; - } - int bytesRead = m_socket.receive((uint8_t*)m_buffer, m_bufferSize, true); - if (bytesRead == 0) { - return EOF; - } + if (m_sizeRead >= m_dataLength) return EOF; + int bytesRead = m_socket.receive((uint8_t*) m_buffer, m_bufferSize, true); + if (bytesRead == 0) return EOF; + m_sizeRead += bytesRead; setg(m_buffer, m_buffer, m_buffer + bytesRead); return traits_type::to_int_type(*gptr()); @@ -690,5 +660,3 @@ SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() { SocketException::SocketException(int myErrno) { m_errno = myErrno; } - - diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 6804ab93..f9a2bac6 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -45,10 +45,13 @@ class SocketException: public std::exception { public: SocketException(int myErrno); + private: int m_errno; + }; + /** * @brief Encapsulate a socket. * @@ -75,43 +78,46 @@ class Socket { int getFD() const; bool getSSL() const; bool isValid(); - int listen(uint16_t port, bool isDatagram=false, bool reuseAddress=false); + int listen(uint16_t port, bool isDatagram = false, bool reuseAddress = false); bool operator<(const Socket& other) const; std::string readToDelim(std::string delim); - size_t receive(uint8_t* data, size_t length, bool exact=false); + size_t receive(uint8_t* data, size_t length, bool exact = false); int receiveFrom(uint8_t* data, size_t length, struct sockaddr* pAddr); int send(std::string value) const; int send(const uint8_t* data, size_t length) const; int send(uint16_t value); int send(uint32_t value); void sendTo(const uint8_t* data, size_t length, struct sockaddr* pAddr); - void setSSL(bool sslValue=true); + void setSSL(bool sslValue = true); std::string toString(); private: int m_sock; // The underlying TCP/IP socket bool m_useSSL; // Should we use SSL - mbedtls_net_context m_sslSock; - mbedtls_entropy_context m_entropy; - mbedtls_ctr_drbg_context m_ctr_drbg; - mbedtls_ssl_context m_sslContext; - mbedtls_ssl_config m_conf; - mbedtls_x509_crt m_srvcert; - mbedtls_pk_context m_pkey; - void sslHandshake(); + mbedtls_net_context m_sslSock; + mbedtls_entropy_context m_entropy; + mbedtls_ctr_drbg_context m_ctr_drbg; + mbedtls_ssl_context m_sslContext; + mbedtls_ssl_config m_conf; + mbedtls_x509_crt m_srvcert; + mbedtls_pk_context m_pkey; + void sslHandshake(); + }; class SocketInputRecordStreambuf : public std::streambuf { public: - SocketInputRecordStreambuf(Socket socket, size_t dataLength, size_t bufferSize=512); + SocketInputRecordStreambuf(Socket socket, size_t dataLength, size_t bufferSize = 512); ~SocketInputRecordStreambuf(); int_type underflow(); + private: char* m_buffer; Socket m_socket; size_t m_dataLength; size_t m_bufferSize; size_t m_sizeRead; + }; diff --git a/cpp_utils/System.cpp b/cpp_utils/System.cpp index e9e7c525..3b774db4 100644 --- a/cpp_utils/System.cpp +++ b/cpp_utils/System.cpp @@ -47,9 +47,6 @@ typedef volatile struct { } pin_ctrl; // The 36 exposed pads. - io_mux_reg_t pad_gpio36; // GPIO36 - io_mux_reg_t pad_gpio37; // GPIO37 - io_mux_reg_t pad_gpio38; // GPIO38 io_mux_reg_t pad_gpio39; // GPIO39 io_mux_reg_t pad_gpio34; // GPIO34 io_mux_reg_t pad_gpio35; // GPIO35 @@ -67,6 +64,7 @@ typedef volatile struct { io_mux_reg_t pad_gpio4; // GPIO4 io_mux_reg_t pad_gpio16; // GPIO16 io_mux_reg_t pad_gpio17; // GPIO17 + io_mux_reg_t pad_gpio37; // GPIO37 io_mux_reg_t pad_sd_data2; // GPIO9 io_mux_reg_t pad_sd_data3; // GPIO10 io_mux_reg_t pad_sd_cmd; // GPIO11 @@ -85,7 +83,7 @@ typedef volatile struct { io_mux_reg_t pad_gpio24; // GPIO24 } io_mux_dev_t; -static io_mux_dev_t* IO_MUX = (io_mux_dev_t*)0x3ff49000; +static io_mux_dev_t* IO_MUX = (io_mux_dev_t*) 0x3ff49000; static const io_mux_reg_t* io_mux_translate[] = { &IO_MUX->pad_gpio0, // 0 @@ -110,12 +108,6 @@ static const io_mux_reg_t* io_mux_translate[] = { &IO_MUX->pad_gpio19, // 19 &IO_MUX->pad_gpio20, // 20 &IO_MUX->pad_gpio21, // 21 - &IO_MUX->pad_gpio22, // 22 - &IO_MUX->pad_gpio23, // 23 - &IO_MUX->pad_gpio24, // 24 - &IO_MUX->pad_gpio25, // 25 - &IO_MUX->pad_gpio26, // 26 - &IO_MUX->pad_gpio27, // 27 nullptr, // 28 nullptr, // 29 nullptr, // 30 @@ -411,7 +403,7 @@ const static char* outSignalStrings[] = { printf("GPIO_FUNCn_OUT_SEL_CFG_REG\n"); printf("--------------------------\n"); printf("%3s %4s\n", "Pin", "Func"); - for (int i=0; iblockNumber); - dp.data = std::string((char *)&pRecv_data->data, receivedSize-4); + dp.data = std::string((char*) &pRecv_data->data, receivedSize - 4); fwrite(dp.data.data(), dp.data.length(), 1, file); sendAck(dp.blockNumber); - ESP_LOGD(tag, "Block size: %d", dp.data.length()); + ESP_LOGD(LOG_TAG, "Block size: %d", dp.data.length()); if (dp.data.length() < TFTP_DATA_SIZE) { finished = true; } @@ -213,8 +212,8 @@ void TFTP::TFTP_Transaction::sendAck(uint16_t blockNumber) { ackData.opCode = htons(TFTP_OPCODE_ACK); ackData.blockNumber = htons(blockNumber); - ESP_LOGD(tag, "Sending ack to %s, blockNumber=%d", Socket::addressToString(&m_partnerAddress).c_str(), blockNumber); - m_partnerSocket.sendTo((uint8_t *)&ackData, sizeof(ackData), &m_partnerAddress); + ESP_LOGD(LOG_TAG, "Sending ack to %s, blockNumber=%d", Socket::addressToString(&m_partnerAddress).c_str(), blockNumber); + m_partnerSocket.sendTo((uint8_t*) &ackData, sizeof(ackData), &m_partnerAddress); } // sendAck @@ -233,26 +232,29 @@ void TFTP::start(uint16_t port) { * or write a file to the server. Once we have received a request we then call the appropriate * handler to handle that type of request. When the request has been completed, we start again. */ - ESP_LOGD(tag, "Starting TFTP::start() on port %d", port); + ESP_LOGD(LOG_TAG, "Starting TFTP::start() on port %d", port); Socket serverSocket; serverSocket.listen(port, true); // Create a listening socket that is a datagram. - while(true) { + while (true) { // This would be a good place to start a transaction in the background. - TFTP_Transaction *pTFTPTransaction = new TFTP_Transaction(); + TFTP_Transaction* pTFTPTransaction = new TFTP_Transaction(); pTFTPTransaction->setBaseDir(m_baseDir); uint16_t receivedOpCode = pTFTPTransaction->waitForRequest(&serverSocket); - switch(receivedOpCode) { - // Handle the write request (client file upload) + switch (receivedOpCode) { + // Handle the write request (client file upload) case opcode::TFTP_OPCODE_WRQ: { pTFTPTransaction->processWRQ(); break; } - // Handle the read request (server file download) + // Handle the read request (server file download) case opcode::TFTP_OPCODE_RRQ: { pTFTPTransaction->processRRQ(); break; } + default: + ESP_LOGE(LOG_TAG, "Unknown opcode: %d", receivedOpCode); + break; } delete pTFTPTransaction; } // End while loop @@ -292,12 +294,12 @@ void TFTP::TFTP_Transaction::waitForAck(uint16_t blockNumber) { uint16_t blockNumber; } ackData; - ESP_LOGD(tag, "TFTP: Waiting for an acknowledgment request"); - int sizeRead = m_partnerSocket.receiveFrom((uint8_t *)&ackData, sizeof(ackData), &m_partnerAddress); - ESP_LOGD(tag, "TFTP: Received some data."); + ESP_LOGD(LOG_TAG, "TFTP: Waiting for an acknowledgment request"); + int sizeRead = m_partnerSocket.receiveFrom((uint8_t*) &ackData, sizeof(ackData), &m_partnerAddress); + ESP_LOGD(LOG_TAG, "TFTP: Received some data."); if (sizeRead != sizeof(ackData)) { - ESP_LOGE(tag, "waitForAck: Received %d but expected %d", sizeRead, sizeof(ackData)); + ESP_LOGE(LOG_TAG, "waitForAck: Received %d but expected %d", sizeRead, sizeof(ackData)); sendError(ERROR_CODE_NOTDEFINED, "Ack not correct size"); return; } @@ -306,12 +308,12 @@ void TFTP::TFTP_Transaction::waitForAck(uint16_t blockNumber) { ackData.blockNumber = ntohs(ackData.blockNumber); if (ackData.opCode != opcode::TFTP_OPCODE_ACK) { - ESP_LOGE(tag, "waitForAck: Received opcode %d but expected %d", ackData.opCode, opcode::TFTP_OPCODE_ACK); + ESP_LOGE(LOG_TAG, "waitForAck: Received opcode %d but expected %d", ackData.opCode, opcode::TFTP_OPCODE_ACK); return; } if (ackData.blockNumber != blockNumber) { - ESP_LOGE(tag, "waitForAck: Blocknumber received %d but expected %d", ackData.blockNumber, blockNumber); + ESP_LOGE(LOG_TAG, "waitForAck: Blocknumber received %d but expected %d", ackData.blockNumber, blockNumber); return; } } // waitForAck @@ -326,29 +328,28 @@ void TFTP::TFTP_Transaction::waitForAck(uint16_t blockNumber) { * @param pServerSocket The server socket on which to listen for client requests. * @return The op code received. */ -uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { /* * 2 bytes string 1 byte string 1 byte * ----------------------------------------------- * RRQ/ | 01/02 | Filename | 0 | Mode | 0 | * WRQ ----------------------------------------------- */ +uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket* pServerSocket) { union { uint8_t buf[TFTP_DATA_SIZE]; uint16_t opCode; } record; size_t length = 100; - ESP_LOGD(tag, "TFTP: Waiting for a request"); + ESP_LOGD(LOG_TAG, "TFTP: Waiting for a request"); pServerSocket->receiveFrom(record.buf, length, &m_partnerAddress); // Save the filename, mode and op code. - m_filename = std::string((char *)(record.buf + 2)); - m_mode = std::string((char *)(record.buf + 3 + m_filename.length())); + m_filename = std::string((char*) (record.buf + 2)); + m_mode = std::string((char*) (record.buf + 3 + m_filename.length())); m_opCode = ntohs(record.opCode); - switch(m_opCode) { - + switch (m_opCode) { // Handle the Write Request command. case TFTP_OPCODE_WRQ: { m_partnerSocket.createSocket(true); @@ -357,7 +358,6 @@ uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { break; } - // Handle the Read request command. case TFTP_OPCODE_RRQ: { m_partnerSocket.createSocket(true); @@ -366,7 +366,7 @@ uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { } default: { - ESP_LOGD(tag, "Un-handled opcode: %d", m_opCode); + ESP_LOGD(LOG_TAG, "Un-handled opcode: %d", m_opCode); break; } } @@ -387,10 +387,10 @@ void TFTP::TFTP_Transaction::sendError(uint16_t code, std::string message) { * ----------------------------------------- */ int size = 2 + 2 + message.length() + 1; - uint8_t *buf = (uint8_t *)malloc(size); - *(uint16_t *)(&buf[0]) = htons(opcode::TFTP_OPCODE_ERROR); - *(uint16_t *)(&buf[2]) = htons(code); - strcpy((char *)(&buf[4]), message.c_str()); + uint8_t* buf = (uint8_t*) malloc(size); + *(uint16_t*) (&buf[0]) = htons(opcode::TFTP_OPCODE_ERROR); + *(uint16_t*) (&buf[2]) = htons(code); + strcpy((char*) (&buf[4]), message.c_str()); m_partnerSocket.sendTo(buf, size, &m_partnerAddress); free(buf); } // sendError diff --git a/cpp_utils/TFTP.h b/cpp_utils/TFTP.h index 3f40bd2b..56733598 100644 --- a/cpp_utils/TFTP.h +++ b/cpp_utils/TFTP.h @@ -36,7 +36,7 @@ class TFTP { public: TFTP(); virtual ~TFTP(); - void start(uint16_t port=TFTP_DEFAULT_PORT); + void start(uint16_t port = TFTP_DEFAULT_PORT); void setBaseDir(std::string baseDir); /** * @brief Internal class for %TFTP processing. @@ -50,7 +50,8 @@ class TFTP { void sendError(uint16_t code, std::string message); void setBaseDir(std::string baseDir); void waitForAck(uint16_t blockNumber); - uint16_t waitForRequest(Socket *pServerSocket); + uint16_t waitForRequest(Socket* pServerSocket); + private: /** * Socket on which the server will communicate with the client.. @@ -61,9 +62,12 @@ class TFTP { std::string m_filename; // The name of the file. std::string m_mode; std::string m_baseDir; // The base directory. + }; + private: std::string m_baseDir; + }; #endif /* COMPONENTS_CPP_UTILS_TFTP_H_ */ diff --git a/cpp_utils/Task.cpp b/cpp_utils/Task.cpp index 2177b888..43f587e2 100644 --- a/cpp_utils/Task.cpp +++ b/cpp_utils/Task.cpp @@ -14,7 +14,7 @@ #include "Task.h" #include "sdkconfig.h" -static char tag[] = "Task"; +static const char* LOG_TAG = "Task"; /** @@ -44,7 +44,7 @@ Task::~Task() { */ /* static */ void Task::delay(int ms) { - ::vTaskDelay(ms/portTICK_PERIOD_MS); + ::vTaskDelay(ms / portTICK_PERIOD_MS); } // delay /** @@ -54,10 +54,10 @@ Task::~Task() { * @param [in] pTaskInstance The task to run. */ void Task::runTask(void* pTaskInstance) { - Task* pTask = (Task*)pTaskInstance; - ESP_LOGD(tag, ">> runTask: taskName=%s", pTask->m_taskName.c_str()); + Task* pTask = (Task*) pTaskInstance; + ESP_LOGD(LOG_TAG, ">> runTask: taskName=%s", pTask->m_taskName.c_str()); pTask->run(pTask->m_taskData); - ESP_LOGD(tag, "<< runTask: taskName=%s", pTask->m_taskName.c_str()); + ESP_LOGD(LOG_TAG, "<< runTask: taskName=%s", pTask->m_taskName.c_str()); pTask->stop(); } // runTask @@ -69,7 +69,7 @@ void Task::runTask(void* pTaskInstance) { */ void Task::start(void* taskData) { if (m_handle != nullptr) { - ESP_LOGW(tag, "Task::start - There might be a task already running!"); + ESP_LOGW(LOG_TAG, "Task::start - There might be a task already running!"); } m_taskData = taskData; ::xTaskCreatePinnedToCore(&runTask, m_taskName.c_str(), m_stackSize, this, m_priority, &m_handle, m_coreId); @@ -82,9 +82,7 @@ void Task::start(void* taskData) { * @return N/A. */ void Task::stop() { - if (m_handle == nullptr) { - return; - } + if (m_handle == nullptr) return; xTaskHandle temp = m_handle; m_handle = nullptr; ::vTaskDelete(temp); diff --git a/cpp_utils/Task.h b/cpp_utils/Task.h index f7d88754..5eb4966b 100644 --- a/cpp_utils/Task.h +++ b/cpp_utils/Task.h @@ -33,13 +33,13 @@ */ class Task { public: - Task(std::string taskName="Task", uint16_t stackSize=10000, uint8_t priority=5); + Task(std::string taskName = "Task", uint16_t stackSize = 10000, uint8_t priority = 5); virtual ~Task(); void setStackSize(uint16_t stackSize); void setPriority(uint8_t priority); void setName(std::string name); void setCore(BaseType_t coreId); - void start(void* taskData=nullptr); + void start(void* taskData = nullptr); void stop(); /** * @brief Body of the task to execute. @@ -50,17 +50,18 @@ class Task { * * @param [in] data The data passed in to the newly started task. */ - virtual void run(void *data) = 0; // Make run pure virtual + virtual void run(void* data) = 0; // Make run pure virtual static void delay(int ms); private: xTaskHandle m_handle; void* m_taskData; - static void runTask(void *data); + static void runTask(void* data); std::string m_taskName; uint16_t m_stackSize; uint8_t m_priority; BaseType_t m_coreId; + }; #endif /* COMPONENTS_CPP_UTILS_TASK_H_ */ diff --git a/cpp_utils/U8G2.h b/cpp_utils/U8G2.h index db57ba64..386272fc 100644 --- a/cpp_utils/U8G2.h +++ b/cpp_utils/U8G2.h @@ -24,7 +24,7 @@ class U8G2 { u8g2_ClearBuffer(&m_u8g2); } - void drawBitmap(uint32_t x, uint32_t y, uint32_t cnt, uint32_t h, const uint8_t *bitmap) { + void drawBitmap(uint32_t x, uint32_t y, uint32_t cnt, uint32_t h, const uint8_t* bitmap) { u8g2_DrawBitmap(&m_u8g2, x, y, cnt, h, bitmap); } @@ -75,7 +75,6 @@ class U8G2 { u8g2_DrawRFrame(&m_u8g2, x, y, w, h, r); } - uint32_t drawStr(uint32_t x, uint32_t y, std::string s) { return u8g2_DrawStr(&m_u8g2, x, y, s.c_str()); } @@ -92,11 +91,11 @@ class U8G2 { u8g2_DrawVLine(&m_u8g2, x, y, h); } - int8_t getAscent(void) { + int8_t getAscent() { return u8g2_GetAscent(&m_u8g2); } - int8_t getDescent(void) { + int8_t getDescent() { return u8g2_GetDescent(&m_u8g2); } @@ -107,19 +106,23 @@ class U8G2 { void initDisplay() { u8g2_InitDisplay(&m_u8g2); } + void sendBuffer() { u8g2_SendBuffer(&m_u8g2); } - void setFont(const uint8_t *font) { + void setFont(const uint8_t* font) { u8g2_SetFont(&m_u8g2, font); } + void setPowerSave(uint8_t is_enable) { u8g2_SetPowerSave(&m_u8g2, is_enable); // wake up display } private: u8g2_t m_u8g2; + }; + #endif // CONFIG_U8G2_PRESENT #endif /* COMPONENTS_CPP_UTILS_U8G2_H_ */ diff --git a/cpp_utils/WS2812.cpp b/cpp_utils/WS2812.cpp index 624480f0..c0f622d7 100644 --- a/cpp_utils/WS2812.cpp +++ b/cpp_utils/WS2812.cpp @@ -33,7 +33,7 @@ static const char* LOG_TAG = "WS2812"; * a logic 1 for 0.7us * a logic 0 for 0.6us */ -static void setItem1(rmt_item32_t *pItem) { +static void setItem1(rmt_item32_t* pItem) { assert(pItem != nullptr); pItem->level0 = 1; pItem->duration0 = 10; @@ -49,7 +49,7 @@ static void setItem1(rmt_item32_t *pItem) { * a logic 1 for 0.35us * a logic 0 for 0.8us */ -static void setItem0(rmt_item32_t *pItem) { +static void setItem0(rmt_item32_t* pItem) { assert(pItem != nullptr); pItem->level0 = 1; pItem->duration0 = 4; @@ -61,7 +61,7 @@ static void setItem0(rmt_item32_t *pItem) { /** * Add an RMT terminator into the RMT data. */ -static void setTerminator(rmt_item32_t *pItem) { +static void setTerminator(rmt_item32_t* pItem) { assert(pItem != nullptr); pItem->level0 = 0; pItem->duration0 = 0; @@ -74,7 +74,7 @@ static void setTerminator(rmt_item32_t *pItem) { * type which should be one of 'R', 'G' or 'B'. */ static uint8_t getChannelValueByType(char type, pixel_t pixel) { - switch(type) { + switch (type) { case 'r': case 'R': return pixel.red; @@ -84,13 +84,13 @@ static uint8_t getChannelValueByType(char type, pixel_t pixel) { case 'g': case 'G': return pixel.green; + default: + ESP_LOGW(LOG_TAG, "Unknown color channel 0x%2x", type); + return 0; } - ESP_LOGW(LOG_TAG, "Unknown color channel 0x%2x", type); - return 0; } // getChannelValueByType - /** * @brief Construct a wrapper for the pixels. * @@ -113,7 +113,7 @@ WS2812::WS2812(gpio_num_t dinPin, uint16_t pixelCount, int channel) { assert(ESP32CPP::GPIO::inRange(dinPin)); this->pixelCount = pixelCount; - this->channel = (rmt_channel_t)channel; + this->channel = (rmt_channel_t) channel; // The number of items is number of pixels * 24 bits per pixel + the terminator. // Remember that an item is TWO RMT output bits ... for NeoPixels this is correct because @@ -121,24 +121,23 @@ WS2812::WS2812(gpio_num_t dinPin, uint16_t pixelCount, int channel) { this->items = new rmt_item32_t[pixelCount * 24 + 1]; this->pixels = new pixel_t[pixelCount]; - this->colorOrder = (char *)"GRB"; + this->colorOrder = (char*) "GRB"; clear(); rmt_config_t config; config.rmt_mode = RMT_MODE_TX; config.channel = this->channel; config.gpio_num = dinPin; - config.mem_block_num = 8-this->channel; + config.mem_block_num = 8 - this->channel; config.clk_div = 8; config.tx_config.loop_en = 0; config.tx_config.carrier_en = 0; config.tx_config.idle_output_en = 1; - config.tx_config.idle_level = (rmt_idle_level_t)0; + config.tx_config.idle_level = (rmt_idle_level_t) 0; config.tx_config.carrier_freq_hz = 10000; config.tx_config.carrier_level = (rmt_carrier_level_t)1; config.tx_config.carrier_duty_percent = 50; - ESP_ERROR_CHECK(rmt_config(&config)); ESP_ERROR_CHECK(rmt_driver_install(this->channel, 0, 0)); } // WS2812 @@ -152,19 +151,19 @@ WS2812::WS2812(gpio_num_t dinPin, uint16_t pixelCount, int channel) { void WS2812::show() { auto pCurrentItem = this->items; - for (auto i=0; ipixelCount; i++) { + for (uint16_t i = 0; i < this->pixelCount; i++) { uint32_t currentPixel = (getChannelValueByType(this->colorOrder[0], this->pixels[i]) << 16) | (getChannelValueByType(this->colorOrder[1], this->pixels[i]) << 8) | (getChannelValueByType(this->colorOrder[2], this->pixels[i])); ESP_LOGD(LOG_TAG, "Pixel value: %x", currentPixel); - for (int j=23; j>=0; j--) { + for (uint8_t j = 23; j > =0; j--) { // We have 24 bits of data representing the red, green amd blue channels. The value of the // 24 bits to output is in the variable current_pixel. We now need to stream this value // through RMT in most significant bit first. To do this, we iterate through each of the 24 // bits from MSB to LSB. - if (currentPixel & (1<channel, this->items, this->pixelCount*24, 1 /* wait till done */)); + ESP_ERROR_CHECK(rmt_write_items(this->channel, this->items, this->pixelCount * 24, 1 /* wait till done */)); } // show @@ -191,7 +190,7 @@ void WS2812::show() { * an alternate order by supply an alternate three character string made up of 'R', 'G' and 'B' * for example "RGB". */ -void WS2812::setColorOrder(char *colorOrder) { +void WS2812::setColorOrder(char* colorOrder) { if (colorOrder != nullptr && strlen(colorOrder) == 3) { this->colorOrder = colorOrder; } @@ -208,14 +207,14 @@ void WS2812::setColorOrder(char *colorOrder) { * @param [in] green The amount of green in the pixel. * @param [in] blue The amount of blue in the pixel. */ -void WS2812::setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue) { +void WS2812::setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue) { assert(index < pixelCount); - this->pixels[index].red = red; this->pixels[index].green = green; this->pixels[index].blue = blue; } // setPixel + /** * @brief Set the given pixel to the specified color. * @@ -240,7 +239,6 @@ void WS2812::setPixel(uint16_t index, pixel_t pixel) { */ void WS2812::setPixel(uint16_t index, uint32_t pixel) { assert(index < pixelCount); - this->pixels[index].red = pixel & 0xff; this->pixels[index].green = (pixel & 0xff00) >> 8; this->pixels[index].blue = (pixel & 0xff0000) >> 16; @@ -256,64 +254,65 @@ void WS2812::setPixel(uint16_t index, uint32_t pixel) { * @param [in] saturation The amount of saturation in the pixel (0-255). * @param [in] brightness The amount of brightness in the pixel (0-255). */ -void WS2812::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness) { - double sat_red; - double sat_green; - double sat_blue; - double ctmp_red; - double ctmp_green; - double ctmp_blue; - double new_red; - double new_green; - double new_blue; - double dSaturation=(double)saturation/255; - double dBrightness=(double)brightness/255; - - assert(index < pixelCount); - - if (hue < 120) { - sat_red = (120 - hue) / 60.0; - sat_green = hue / 60.0; - sat_blue = 0; - } else if (hue < 240) { - sat_red = 0; - sat_green = (240 - hue) / 60.0; - sat_blue = (hue - 120) / 60.0; - } else { - sat_red = (hue - 240) / 60.0; - sat_green = 0; - sat_blue = (360 - hue) / 60.0; - } - - if (sat_red>1.0) { - sat_red=1.0; - } - if (sat_green>1.0) { - sat_green=1.0; - } - if (sat_blue>1.0) { - sat_blue=1.0; - } - - ctmp_red = 2 * dSaturation * sat_red + (1 - dSaturation); - ctmp_green = 2 * dSaturation * sat_green + (1 - dSaturation); - ctmp_blue = 2 * dSaturation * sat_blue + (1 - dSaturation); - - if (dBrightness < 0.5) { - new_red = dBrightness * ctmp_red; - new_green = dBrightness * ctmp_green; - new_blue = dBrightness * ctmp_blue; - } else { - new_red = (1 - dBrightness) * ctmp_red + 2 * dBrightness - 1; - new_green = (1 - dBrightness) * ctmp_green + 2 * dBrightness - 1; - new_blue = (1 - dBrightness) * ctmp_blue + 2 * dBrightness - 1; - } - - this->pixels[index].red = (uint8_t)(new_red*255); - this->pixels[index].green = (uint8_t)(new_green*255); - this->pixels[index].blue = (uint8_t)(new_blue*255); +void WS2812::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness) { + double sat_red; + double sat_green; + double sat_blue; + double ctmp_red; + double ctmp_green; + double ctmp_blue; + double new_red; + double new_green; + double new_blue; + double dSaturation = (double) saturation / 255; + double dBrightness = (double) brightness / 255; + + assert(index < pixelCount); + + if (hue < 120) { + sat_red = (120 - hue) / 60.0; + sat_green = hue / 60.0; + sat_blue = 0; + } else if (hue < 240) { + sat_red = 0; + sat_green = (240 - hue) / 60.0; + sat_blue = (hue - 120) / 60.0; + } else { + sat_red = (hue - 240) / 60.0; + sat_green = 0; + sat_blue = (360 - hue) / 60.0; + } + + if (sat_red > 1.0) { + sat_red = 1.0; + } + if (sat_green > 1.0) { + sat_green = 1.0; + } + if (sat_blue > 1.0) { + sat_blue = 1.0; + } + + ctmp_red = 2 * dSaturation * sat_red + (1 - dSaturation); + ctmp_green = 2 * dSaturation * sat_green + (1 - dSaturation); + ctmp_blue = 2 * dSaturation * sat_blue + (1 - dSaturation); + + if (dBrightness < 0.5) { + new_red = dBrightness * ctmp_red; + new_green = dBrightness * ctmp_green; + new_blue = dBrightness * ctmp_blue; + } else { + new_red = (1 - dBrightness) * ctmp_red + 2 * dBrightness - 1; + new_green = (1 - dBrightness) * ctmp_green + 2 * dBrightness - 1; + new_blue = (1 - dBrightness) * ctmp_blue + 2 * dBrightness - 1; + } + + this->pixels[index].red = (uint8_t)(new_red * 255); + this->pixels[index].green = (uint8_t)(new_green * 255); + this->pixels[index].blue = (uint8_t)(new_blue * 255); } // setHSBPixel + /** * @brief Clear all the pixel colors. * @@ -321,13 +320,14 @@ void WS2812::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8 * The LEDs are not actually updated until a call to show(). */ void WS2812::clear() { - for (auto i=0; ipixelCount; i++) { + for (uint16_t i = 0; i < this->pixelCount; i++) { this->pixels[i].red = 0; this->pixels[i].green = 0; this->pixels[i].blue = 0; } } // clear + /** * @brief Class instance destructor. */ diff --git a/cpp_utils/WS2812.h b/cpp_utils/WS2812.h index f599c1b7..f11cbc89 100644 --- a/cpp_utils/WS2812.h +++ b/cpp_utils/WS2812.h @@ -48,21 +48,23 @@ typedef struct { */ class WS2812 { public: - WS2812(gpio_num_t gpioNum, uint16_t pixelCount, int channel=RMT_CHANNEL_0); + WS2812(gpio_num_t gpioNum, uint16_t pixelCount, int channel = RMT_CHANNEL_0); void show(); - void setColorOrder(char *order); + void setColorOrder(char* order); void setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue); void setPixel(uint16_t index, pixel_t pixel); void setPixel(uint16_t index, uint32_t pixel); void setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness); void clear(); virtual ~WS2812(); + private: - char *colorOrder; + char* colorOrder; uint16_t pixelCount; rmt_channel_t channel; - rmt_item32_t *items; - pixel_t *pixels; + rmt_item32_t* items; + pixel_t* pixels; + }; #endif /* MAIN_WS2812_H_ */ diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index c250a4d5..0fc666af 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -18,107 +18,113 @@ #include #include -static char tag[] = "WebServer"; +#define STATE_NAME 0 +#define STATE_VALUE 1 + +static const char* LOG_TAG = "WebServer"; struct WebServerUserData { - WebServer *pWebServer; - WebServer::HTTPMultiPart *pMultiPart; - WebServer::WebSocketHandler *pWebSocketHandler; - void *originalUserData; + WebServer* pWebServer; + WebServer::HTTPMultiPart* pMultiPart; + WebServer::WebSocketHandler* pWebSocketHandler; + void* originalUserData; }; + /** * @brief Convert a Mongoose event type to a string. * @param [in] event The received event type. * @return The string representation of the event. */ static std::string mongoose_eventToString(int event) { - switch (event) { - case MG_EV_CONNECT: - return "MG_EV_CONNECT"; - case MG_EV_ACCEPT: - return "MG_EV_ACCEPT"; - case MG_EV_CLOSE: - return "MG_EV_CLOSE"; - case MG_EV_SEND: - return "MG_EV_SEND"; - case MG_EV_RECV: - return "MG_EV_RECV"; - case MG_EV_POLL: - return "MG_EV_POLL"; - case MG_EV_TIMER: - return "MG_EV_TIMER"; - case MG_EV_HTTP_PART_DATA: - return "MG_EV_HTTP_PART_DATA"; - case MG_EV_HTTP_MULTIPART_REQUEST: - return "MG_EV_HTTP_MULTIPART_REQUEST"; - case MG_EV_HTTP_PART_BEGIN: - return "MG_EV_HTTP_PART_BEGIN"; - case MG_EV_HTTP_PART_END: - return "MG_EV_HTTP_PART_END"; - case MG_EV_HTTP_MULTIPART_REQUEST_END: - return "MG_EV_HTTP_MULTIPART_REQUEST_END"; - case MG_EV_HTTP_REQUEST: - return "MG_EV_HTTP_REQUEST"; - case MG_EV_HTTP_REPLY: - return "MG_EV_HTTP_REPLY"; - case MG_EV_HTTP_CHUNK: - return "MG_EV_HTTP_CHUNK"; - case MG_EV_MQTT_CONNACK: - return "MG_EV_MQTT_CONNACK"; - case MG_EV_MQTT_CONNECT: - return "MG_EV_MQTT_CONNECT"; - case MG_EV_MQTT_DISCONNECT: - return "MG_EV_MQTT_DISCONNECT"; - case MG_EV_MQTT_PINGREQ: - return "MG_EV_MQTT_PINGREQ"; - case MG_EV_MQTT_PINGRESP: - return "MG_EV_MQTT_PINGRESP"; - case MG_EV_MQTT_PUBACK: - return "MG_EV_MQTT_PUBACK"; - case MG_EV_MQTT_PUBCOMP: - return "MG_EV_MQTT_PUBCOMP"; - case MG_EV_MQTT_PUBLISH: - return "MG_EV_MQTT_PUBLISH"; - case MG_EV_MQTT_PUBREC: - return "MG_EV_MQTT_PUBREC"; - case MG_EV_MQTT_PUBREL: - return "MG_EV_MQTT_PUBREL"; - case MG_EV_MQTT_SUBACK: - return "MG_EV_MQTT_SUBACK"; - case MG_EV_MQTT_SUBSCRIBE: - return "MG_EV_MQTT_SUBSCRIBE"; - case MG_EV_MQTT_UNSUBACK: - return "MG_EV_MQTT_UNSUBACK"; - case MG_EV_MQTT_UNSUBSCRIBE: - return "MG_EV_MQTT_UNSUBSCRIBE"; - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: - return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: - return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; - case MG_EV_WEBSOCKET_FRAME: - return "MG_EV_WEBSOCKET_FRAME"; - case MG_EV_WEBSOCKET_CONTROL_FRAME: - return "MG_EV_WEBSOCKET_CONTROL_FRAME"; - } - std::string s; - s += "Unknown event: "; - s += event; - return s; + switch (event) { + case MG_EV_CONNECT: + return "MG_EV_CONNECT"; + case MG_EV_ACCEPT: + return "MG_EV_ACCEPT"; + case MG_EV_CLOSE: + return "MG_EV_CLOSE"; + case MG_EV_SEND: + return "MG_EV_SEND"; + case MG_EV_RECV: + return "MG_EV_RECV"; + case MG_EV_POLL: + return "MG_EV_POLL"; + case MG_EV_TIMER: + return "MG_EV_TIMER"; + case MG_EV_HTTP_PART_DATA: + return "MG_EV_HTTP_PART_DATA"; + case MG_EV_HTTP_MULTIPART_REQUEST: + return "MG_EV_HTTP_MULTIPART_REQUEST"; + case MG_EV_HTTP_PART_BEGIN: + return "MG_EV_HTTP_PART_BEGIN"; + case MG_EV_HTTP_PART_END: + return "MG_EV_HTTP_PART_END"; + case MG_EV_HTTP_MULTIPART_REQUEST_END: + return "MG_EV_HTTP_MULTIPART_REQUEST_END"; + case MG_EV_HTTP_REQUEST: + return "MG_EV_HTTP_REQUEST"; + case MG_EV_HTTP_REPLY: + return "MG_EV_HTTP_REPLY"; + case MG_EV_HTTP_CHUNK: + return "MG_EV_HTTP_CHUNK"; + case MG_EV_MQTT_CONNACK: + return "MG_EV_MQTT_CONNACK"; + case MG_EV_MQTT_CONNECT: + return "MG_EV_MQTT_CONNECT"; + case MG_EV_MQTT_DISCONNECT: + return "MG_EV_MQTT_DISCONNECT"; + case MG_EV_MQTT_PINGREQ: + return "MG_EV_MQTT_PINGREQ"; + case MG_EV_MQTT_PINGRESP: + return "MG_EV_MQTT_PINGRESP"; + case MG_EV_MQTT_PUBACK: + return "MG_EV_MQTT_PUBACK"; + case MG_EV_MQTT_PUBCOMP: + return "MG_EV_MQTT_PUBCOMP"; + case MG_EV_MQTT_PUBLISH: + return "MG_EV_MQTT_PUBLISH"; + case MG_EV_MQTT_PUBREC: + return "MG_EV_MQTT_PUBREC"; + case MG_EV_MQTT_PUBREL: + return "MG_EV_MQTT_PUBREL"; + case MG_EV_MQTT_SUBACK: + return "MG_EV_MQTT_SUBACK"; + case MG_EV_MQTT_SUBSCRIBE: + return "MG_EV_MQTT_SUBSCRIBE"; + case MG_EV_MQTT_UNSUBACK: + return "MG_EV_MQTT_UNSUBACK"; + case MG_EV_MQTT_UNSUBSCRIBE: + return "MG_EV_MQTT_UNSUBSCRIBE"; + case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: + return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: + return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; + case MG_EV_WEBSOCKET_FRAME: + return "MG_EV_WEBSOCKET_FRAME"; + case MG_EV_WEBSOCKET_CONTROL_FRAME: + return "MG_EV_WEBSOCKET_CONTROL_FRAME"; + } + std::string s; + s += "Unknown event: "; + s += event; + return s; } //eventToString -static void dumpHttpMessage(struct http_message *pHttpMessage) { - ESP_LOGD(tag, "HTTP Message"); - ESP_LOGD(tag, "Message: %.*s", (int)pHttpMessage->message.len, pHttpMessage->message.p); + +static void dumpHttpMessage(struct http_message* pHttpMessage) { + ESP_LOGD(LOG_TAG, "HTTP Message"); + ESP_LOGD(LOG_TAG, "Message: %.*s", (int) pHttpMessage->message.len, pHttpMessage->message.p); } /* -static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, struct mg_str fname) { - ESP_LOGD(tag, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); - return fname; +static struct mg_str uploadFileNameHandler(struct mg_connection* mgConnection, struct mg_str fname) { + ESP_LOGD(LOG_TAG, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); + return fname; } */ + /** * @brief Mongoose event handler. * The event handler is called when an event occurs associated with the WebServer @@ -129,147 +135,139 @@ static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, s * @param [in] eventData Data associated with the event. * @return N/A. */ -static void mongoose_event_handler_web_server( - struct mg_connection *mgConnection, // The network connection associated with the event. - int event, // The type of event. - void *eventData // Data associated with the event. -) { - if (event == MG_EV_POLL) { - return; - } - ESP_LOGD(tag, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); - switch (event) { - case MG_EV_SEND: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; - pWebServer->continueConnection(mgConnection); - break; - } - - case MG_EV_HTTP_REQUEST: { - struct http_message *message = (struct http_message *) eventData; - dumpHttpMessage(message); - - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; - pWebServer->processRequest(mgConnection, message); - break; - } // MG_EV_HTTP_REQUEST - - case MG_EV_HTTP_MULTIPART_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - ESP_LOGD(tag, "User_data address 0x%d", (uint32_t)pWebServerUserData); - WebServer *pWebServer = pWebServerUserData->pWebServer; - if (pWebServer->m_pMultiPartFactory == nullptr) { - return; - } - WebServer::HTTPMultiPart *pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pMultiPart = pMultiPart; - p2->pWebSocketHandler = nullptr; - mgConnection->user_data = p2; - //struct http_message *message = (struct http_message *) eventData; - //dumpHttpMessage(message); - break; - } // MG_EV_HTTP_MULTIPART_REQUEST - - case MG_EV_HTTP_MULTIPART_REQUEST_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pMultiPart != nullptr) { - delete pWebServerUserData->pMultiPart; - pWebServerUserData->pMultiPart = nullptr; - } - mgConnection->user_data = pWebServerUserData->originalUserData; - delete pWebServerUserData; - WebServer::HTTPResponse httpResponse = WebServer::HTTPResponse(mgConnection); - httpResponse.setStatus(200); - httpResponse.sendData(""); - break; - } // MG_EV_HTTP_MULTIPART_REQUEST_END - - case MG_EV_HTTP_PART_BEGIN: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->begin(std::string(part->var_name), std::string(part->file_name)); - } - break; - } // MG_EV_HTTP_PART_BEGIN - - case MG_EV_HTTP_PART_DATA: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->data(std::string(part->data.p, part->data.len)); - } - break; - } // MG_EV_HTTP_PART_DATA - - case MG_EV_HTTP_PART_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); - if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->end(); - } - break; - } // MG_EV_HTTP_PART_END - - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; - if (pWebServer->m_pWebSocketHandlerFactory != nullptr) { - if (pWebServerUserData->pWebSocketHandler != nullptr) { - ESP_LOGD(tag, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); - } - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); - mgConnection->user_data = p2; - } else { - ESP_LOGD(tag, "We received a WebSocket request but we have no handler factory!"); - } - break; - } // MG_EV_WEBSOCKET_HANDSHAKE_REQUEST - - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); - return; - } - pWebServerUserData->pWebSocketHandler->onCreated(); - break; - } // MG_EV_WEBSOCKET_HANDSHAKE_DONE - - - /* - * When we receive a MG_EV_WEBSOCKET_FRAME then we have received a chunk of data over the network. - * Our goal will be to send this to the web socket handler (if one exists). - */ - case MG_EV_WEBSOCKET_FRAME: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); - return; - } - struct websocket_message *ws_message = (websocket_message *)eventData; - ESP_LOGD(tag, "Received data length: %d", ws_message->size); - pWebServerUserData->pWebSocketHandler->onMessage(std::string((char *)ws_message->data, ws_message->size)); - break; - } // MG_EV_WEBSOCKET_FRAME - - } // End of switch +static void mongoose_event_handler_web_server(struct mg_connection* mgConnection, int event, void* eventData) { + if (event == MG_EV_POLL) return; + ESP_LOGD(LOG_TAG, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); + switch (event) { + case MG_EV_SEND: { + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + WebServer* pWebServer = pWebServerUserData->pWebServer; + pWebServer->continueConnection(mgConnection); + break; + } + + case MG_EV_HTTP_REQUEST: { + struct http_message* message = (struct http_message*) eventData; + dumpHttpMessage(message); + + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + WebServer* pWebServer = pWebServerUserData->pWebServer; + pWebServer->processRequest(mgConnection, message); + break; + } // MG_EV_HTTP_REQUEST + + case MG_EV_HTTP_MULTIPART_REQUEST: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + ESP_LOGD(LOG_TAG, "User_data address 0x%d", (uint32_t) pWebServerUserData); + WebServer* pWebServer = pWebServerUserData->pWebServer; + if (pWebServer->m_pMultiPartFactory == nullptr) return; + WebServer::HTTPMultiPart* pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); + struct WebServerUserData* p2 = new WebServerUserData(); + ESP_LOGD(LOG_TAG, "New User_data address 0x%d", (uint32_t) p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pMultiPart = pMultiPart; + p2->pWebSocketHandler = nullptr; + mgConnection->user_data = p2; + //struct http_message* message = (struct http_message*) eventData; + //dumpHttpMessage(message); + break; + } // MG_EV_HTTP_MULTIPART_REQUEST + + case MG_EV_HTTP_MULTIPART_REQUEST_END: { + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + if (pWebServerUserData->pMultiPart != nullptr) { + delete pWebServerUserData->pMultiPart; + pWebServerUserData->pMultiPart = nullptr; + } + mgConnection->user_data = pWebServerUserData->originalUserData; + delete pWebServerUserData; + WebServer::HTTPResponse httpResponse = WebServer::HTTPResponse(mgConnection); + httpResponse.setStatus(200); + httpResponse.sendData(""); + break; + } // MG_EV_HTTP_MULTIPART_REQUEST_END + + case MG_EV_HTTP_PART_BEGIN: { + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + struct mg_http_multipart_part* part = (struct mg_http_multipart_part*) eventData; + ESP_LOGD(LOG_TAG, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t) part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->begin(std::string(part->var_name), std::string(part->file_name)); + } + break; + } // MG_EV_HTTP_PART_BEGIN + + case MG_EV_HTTP_PART_DATA: { + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + struct mg_http_multipart_part* part = (struct mg_http_multipart_part*) eventData; + ESP_LOGD(LOG_TAG, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t) part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->data(std::string(part->data.p, part->data.len)); + } + break; + } // MG_EV_HTTP_PART_DATA + + case MG_EV_HTTP_PART_END: { + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + struct mg_http_multipart_part* part = (struct mg_http_multipart_part*) eventData; + ESP_LOGD(LOG_TAG, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + if (pWebServerUserData->pMultiPart != nullptr) { + pWebServerUserData->pMultiPart->end(); + } + break; + } // MG_EV_HTTP_PART_END + + case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: { + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + WebServer* pWebServer = pWebServerUserData->pWebServer; + if (pWebServer->m_pWebSocketHandlerFactory != nullptr) { + if (pWebServerUserData->pWebSocketHandler != nullptr) { + ESP_LOGD(LOG_TAG, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); + } + struct WebServerUserData* p2 = new WebServerUserData(); + ESP_LOGD(LOG_TAG, "New User_data address 0x%d", (uint32_t) p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); + mgConnection->user_data = p2; + } else { + ESP_LOGD(LOG_TAG, "We received a WebSocket request but we have no handler factory!"); + } + break; + } // MG_EV_WEBSOCKET_HANDSHAKE_REQUEST + + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + if (pWebServerUserData->pWebSocketHandler == nullptr) { + ESP_LOGE(LOG_TAG, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + return; + } + pWebServerUserData->pWebSocketHandler->onCreated(); + break; + } // MG_EV_WEBSOCKET_HANDSHAKE_DONE + + + /* + * When we receive a MG_EV_WEBSOCKET_FRAME then we have received a chunk of data over the network. + * Our goal will be to send this to the web socket handler (if one exists). + */ + case MG_EV_WEBSOCKET_FRAME: { + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + if (pWebServerUserData->pWebSocketHandler == nullptr) { + ESP_LOGE(LOG_TAG, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + return; + } + struct websocket_message* ws_message = (websocket_message*) eventData; + ESP_LOGD(LOG_TAG, "Received data length: %d", ws_message->size); + pWebServerUserData->pWebSocketHandler->onMessage(std::string((char*) ws_message->data, ws_message->size)); + break; + } // MG_EV_WEBSOCKET_FRAME + + } // End of switch } // End of mongoose_event_handler @@ -277,9 +275,9 @@ static void mongoose_event_handler_web_server( * @brief Constructor. */ WebServer::WebServer() { - m_rootPath = ""; - m_pMultiPartFactory = nullptr; - m_pWebSocketHandlerFactory = nullptr; + m_rootPath = ""; + m_pMultiPartFactory = nullptr; + m_pWebSocketHandlerFactory = nullptr; } // WebServer @@ -292,7 +290,7 @@ WebServer::~WebServer() { * @return The current root path. */ const std::string& WebServer::getRootPath() { - return m_rootPath; + return m_rootPath; } // getRootPath @@ -317,15 +315,17 @@ const std::string& WebServer::getRootPath() { * @param [in] handler The callback function to be invoked when a request arrives. */ void WebServer::addPathHandler(const std::string& method, const std::string& pathExpr, - void (* handler)(WebServer::HTTPRequest* pHttpRequest, + void (*handler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { - m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); + m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); } // addPathHandler -void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr, void (* handler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { - m_pathHandlers.push_back(PathHandler(std::move(method), pathExpr, handler)); + +void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr, void (*handler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { + m_pathHandlers.push_back(PathHandler(std::move(method), pathExpr, handler)); } // addPathHandler + /** * @brief Run the web server listening at the given port. * @@ -335,31 +335,31 @@ void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr * @return N/A. */ void WebServer::start(uint16_t port) { - ESP_LOGD(tag, "WebServer task starting"); - struct mg_mgr mgr; - mg_mgr_init(&mgr, NULL); - - std::stringstream stringStream; - stringStream << ':' << port; - struct mg_connection *mgConnection = mg_bind(&mgr, stringStream.str().c_str(), mongoose_event_handler_web_server); - - if (mgConnection == NULL) { - ESP_LOGE(tag, "No connection from the mg_bind()"); - vTaskDelete(NULL); - return; - } - - struct WebServerUserData *pWebServerUserData = new WebServerUserData(); - pWebServerUserData->pWebServer = this; - pWebServerUserData->pMultiPart = nullptr; - mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. - ESP_LOGD(tag, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); - mg_set_protocol_http_websocket(mgConnection); - - ESP_LOGD(tag, "WebServer listening on port %d", port); - while (1) { - mg_mgr_poll(&mgr, 2000); - } + ESP_LOGD(LOG_TAG, "WebServer task starting"); + struct mg_mgr mgr; + mg_mgr_init(&mgr, NULL); + + std::stringstream stringStream; + stringStream << ':' << port; + struct mg_connection *mgConnection = mg_bind(&mgr, stringStream.str().c_str(), mongoose_event_handler_web_server); + + if (mgConnection == NULL) { + ESP_LOGE(LOG_TAG, "No connection from the mg_bind()"); + vTaskDelete(NULL); + return; + } + + struct WebServerUserData* pWebServerUserData = new WebServerUserData(); + pWebServerUserData->pWebServer = this; + pWebServerUserData->pMultiPart = nullptr; + mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. + ESP_LOGD(LOG_TAG, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); + mg_set_protocol_http_websocket(mgConnection); + + ESP_LOGD(LOG_TAG, "WebServer listening on port %d", port); + while (true) { + mg_mgr_poll(&mgr, 2000); + } } // run @@ -368,7 +368,7 @@ void WebServer::start(uint16_t port) { * @param [in] pMultiPart A pointer to the multi part factory. */ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { - m_pMultiPartFactory = pMultiPartFactory; + m_pMultiPartFactory = pMultiPartFactory; } @@ -390,11 +390,12 @@ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { * @return N/A. */ void WebServer::setRootPath(const std::string& path) { - m_rootPath = path; + m_rootPath = path; } // setRootPath + void WebServer::setRootPath(std::string&& path) { - m_rootPath = std::move(path); + m_rootPath = std::move(path); } // setRootPath @@ -405,7 +406,7 @@ void WebServer::setRootPath(std::string&& path) { * @return N/A. */ void WebServer::setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHandlerFactory) { - m_pWebSocketHandlerFactory = pWebSocketHandlerFactory; + m_pWebSocketHandlerFactory = pWebSocketHandlerFactory; } // setWebSocketHandlerFactory @@ -414,9 +415,9 @@ void WebServer::setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHa * @param [in] nc The network connection for the response. */ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { - m_nc = nc; - m_status = 200; - m_dataSent = false; + m_nc = nc; + m_status = 200; + m_dataSent = false; } // HTTPResponse @@ -426,11 +427,11 @@ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { * @param [in] value The value of the header. */ void WebServer::HTTPResponse::addHeader(const std::string& name, const std::string& value) { - m_headers[name] = value; + m_headers[name] = value; } // addHeader void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) { - m_headers[std::move(name)] = std::move(value); + m_headers[std::move(name)] = std::move(value); } // addHeader @@ -442,9 +443,10 @@ std::string WebServer::HTTPResponse::buildHeaders() { std::string headers; unsigned long headers_len = 0; - for(auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { - if(iter != m_headers.begin()) - headers_len += 2; + for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + if (iter != m_headers.begin()) { + headers_len += 2; + } headers_len += iter->first.length(); headers_len += 2; headers_len += iter->second.length(); @@ -453,8 +455,9 @@ std::string WebServer::HTTPResponse::buildHeaders() { headers.resize(headers_len); // Will not have to resize and recopy during the next loop, we have 2 loops but it still ends up being faster for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { - if(iter != m_headers.begin()) - headers += "\r\n"; + if (iter != m_headers.begin()) { + headers += "\r\n"; + } headers += iter->first; headers += ": "; headers += iter->second; @@ -462,6 +465,7 @@ std::string WebServer::HTTPResponse::buildHeaders() { return headers; } // buildHeaders + /** * @brief Send data to the HTTP caller. * Send the data to the HTTP caller. No further data should be sent after this call. @@ -469,13 +473,10 @@ std::string WebServer::HTTPResponse::buildHeaders() { * @return N/A. */ void WebServer::HTTPResponse::sendData(const std::string& data) { - sendData((uint8_t *)data.data(), data.length()); + sendData((uint8_t*) data.data(), data.length()); } // sendData - - - /** * @brief Send data to the HTTP caller. * Send the data to the HTTP caller. No further data should be sent after this call. @@ -484,15 +485,15 @@ void WebServer::HTTPResponse::sendData(const std::string& data) { * @return N/A. */ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { - if (m_dataSent) { - ESP_LOGE(tag, "HTTPResponse: Data already sent! Attempt to send again/more."); - return; - } - m_dataSent = true; - - mg_send_head(m_nc, m_status, length, buildHeaders().c_str()); - mg_send(m_nc, pData, length); - m_nc->flags |= MG_F_SEND_AND_CLOSE; + if (m_dataSent) { + ESP_LOGE(LOG_TAG, "HTTPResponse: Data already sent! Attempt to send again/more."); + return; + } + m_dataSent = true; + + mg_send_head(m_nc, m_status, length, buildHeaders().c_str()); + mg_send(m_nc, pData, length); + m_nc->flags |= MG_F_SEND_AND_CLOSE; } // sendData @@ -500,7 +501,7 @@ void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { * */ void WebServer::HTTPResponse::sendData(const char* pData, size_t length) { - sendData((uint8_t*) pData, length); + sendData((uint8_t*) pData, length); } // sendData @@ -508,11 +509,11 @@ void WebServer::HTTPResponse::sendData(const char* pData, size_t length) { * */ void WebServer::HTTPResponse::sendChunkHead() { - if(m_dataSent) { - ESP_LOGE(tag, "HTTPResponse: Chunk headers already sent! Attempt to send again/more."); - } - m_dataSent = true; - mg_send_head(m_nc, m_status, -1, buildHeaders().c_str()); + if (m_dataSent) { + ESP_LOGE(LOG_TAG, "HTTPResponse: Chunk headers already sent! Attempt to send again/more."); + } + m_dataSent = true; + mg_send_head(m_nc, m_status, -1, buildHeaders().c_str()); } // sendChunkHead @@ -520,7 +521,7 @@ void WebServer::HTTPResponse::sendChunkHead() { * */ void WebServer::HTTPResponse::sendChunk(const char* pData, size_t length) { - mg_send_http_chunk(m_nc, pData, length); + mg_send_http_chunk(m_nc, pData, length); } // sendChunkHead @@ -528,7 +529,7 @@ void WebServer::HTTPResponse::sendChunk(const char* pData, size_t length) { * */ void WebServer::HTTPResponse::closeConnection() { - m_nc->flags |= MG_F_SEND_AND_CLOSE; + m_nc->flags |= MG_F_SEND_AND_CLOSE; } // closeConnection @@ -538,11 +539,12 @@ void WebServer::HTTPResponse::closeConnection() { * @return N/A. */ void WebServer::HTTPResponse::setHeaders(const std::map& headers) { - m_headers = headers; + m_headers = headers; } // setHeaders + void WebServer::HTTPResponse::setHeaders(std::map&& headers) { - m_headers = std::move(headers); + m_headers = std::move(headers); } // setHeaders @@ -551,7 +553,7 @@ void WebServer::HTTPResponse::setHeaders(std::map&& he * @return The current root path. */ const std::string& WebServer::HTTPResponse::getRootPath() const { - return m_rootPath; + return m_rootPath; } // getRootPath @@ -561,13 +563,15 @@ const std::string& WebServer::HTTPResponse::getRootPath() const { * @return N/A. */ void WebServer::HTTPResponse::setRootPath(const std::string& path) { - m_rootPath = path; + m_rootPath = path; } // setRootPath + void WebServer::HTTPResponse::setRootPath(std::string&& path) { - m_rootPath = std::move(path); + m_rootPath = std::move(path); } // setRootPath + /** * @brief Set the status value in the HTTP response. * @@ -576,7 +580,7 @@ void WebServer::HTTPResponse::setRootPath(std::string&& path) { * @return N/A. */ void WebServer::HTTPResponse::setStatus(int status) { - m_status = status; + m_status = status; } // setStatus @@ -590,74 +594,73 @@ void WebServer::HTTPResponse::setStatus(int status) { * @param [in] mgConnection The network connection on which the request was received. * @param [in] message The message representing the request. */ -void WebServer::processRequest(struct mg_connection *mgConnection, struct http_message* message) { - ESP_LOGD(tag, "WebServer::processRequest: Matching: %.*s", (int)message->uri.len, message->uri.p); - HTTPResponse httpResponse = HTTPResponse(mgConnection); - - /* - * Iterate through each of the path handlers looking for a match with the method and specified path. - */ - std::vector::iterator it; - for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { - if ((*it).match(message->method.p, message->method.len, message->uri.p)) { - HTTPRequest httpRequest(message); - (*it).invoke(&httpRequest, &httpResponse); - ESP_LOGD(tag, "Found a match!!"); - return; - } - } // End of examine path handlers. - - // Because we reached here, it means that we did NOT match a handler. Now we want to attempt - // to retrieve the corresponding file content. - std::string filePath; - filePath.reserve(httpResponse.getRootPath().length() + message->uri.len + 1); - filePath += httpResponse.getRootPath(); - filePath.append(message->uri.p, message->uri.len); - ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); - FILE* file = nullptr; - - if(strcmp(filePath.c_str(), "/") != 0) - file = fopen(filePath.c_str(), "rb"); - if (file != nullptr) { - auto pData = (uint8_t*)malloc(MAX_CHUNK_LENGTH); - size_t read = fread(pData, 1, MAX_CHUNK_LENGTH, file); - - if(read >= MAX_CHUNK_LENGTH) { - httpResponse.sendChunkHead(); - httpResponse.sendChunk((char*)pData, read); - fclose(unfinishedConnection[mgConnection->sock]); - unfinishedConnection[mgConnection->sock] = file; - } else { - fclose(file); - httpResponse.sendData(pData, read); - } - free(pData); - } else { - // Handle unable to open file - httpResponse.setStatus(404); // Not found - httpResponse.sendData(""); - } +void WebServer::processRequest(struct mg_connection* mgConnection, struct http_message* message) { + ESP_LOGD(LOG_TAG, "WebServer::processRequest: Matching: %.*s", (int) message->uri.len, message->uri.p); + HTTPResponse httpResponse = HTTPResponse(mgConnection); + + /* + * Iterate through each of the path handlers looking for a match with the method and specified path. + */ + std::vector::iterator it; + for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { + if ((*it).match(message->method.p, message->method.len, message->uri.p)) { + HTTPRequest httpRequest(message); + (*it).invoke(&httpRequest, &httpResponse); + ESP_LOGD(LOG_TAG, "Found a match!!"); + return; + } + } // End of examine path handlers. + + // Because we reached here, it means that we did NOT match a handler. Now we want to attempt + // to retrieve the corresponding file content. + std::string filePath; + filePath.reserve(httpResponse.getRootPath().length() + message->uri.len + 1); + filePath += httpResponse.getRootPath(); + filePath.append(message->uri.p, message->uri.len); + ESP_LOGD(LOG_TAG, "Opening file: %s", filePath.c_str()); + FILE* file = nullptr; + + if (strcmp(filePath.c_str(), "/") != 0) { + file = fopen(filePath.c_str(), "rb"); + } + if (file != nullptr) { + auto pData = (uint8_t*)malloc(MAX_CHUNK_LENGTH); + size_t read = fread(pData, 1, MAX_CHUNK_LENGTH, file); + + if (read >= MAX_CHUNK_LENGTH) { + httpResponse.sendChunkHead(); + httpResponse.sendChunk((char*) pData, read); + fclose(unfinishedConnection[mgConnection->sock]); + unfinishedConnection[mgConnection->sock] = file; + } else { + fclose(file); + httpResponse.sendData(pData, read); + } + free(pData); + } else { + // Handle unable to open file + httpResponse.setStatus(404); // Not found + httpResponse.sendData(""); + } } // processRequest void WebServer::continueConnection(struct mg_connection* mgConnection) { - if(unfinishedConnection.count(mgConnection->sock) == 0) { - return; - } - - HTTPResponse httpResponse = HTTPResponse(mgConnection); - - FILE* file = unfinishedConnection[mgConnection->sock]; - auto pData = (char*) malloc(MAX_CHUNK_LENGTH); - size_t length = fread(pData, 1, MAX_CHUNK_LENGTH, file); - - httpResponse.sendChunk(pData, length); - if(length < MAX_CHUNK_LENGTH) { - fclose(file); - httpResponse.closeConnection(); - unfinishedConnection.erase(mgConnection->sock); - httpResponse.sendChunk("", 0); - } - free(pData); + if (unfinishedConnection.count(mgConnection->sock) == 0) return; + + HTTPResponse httpResponse = HTTPResponse(mgConnection); + + FILE* file = unfinishedConnection[mgConnection->sock]; + auto pData = (char*) malloc(MAX_CHUNK_LENGTH); + size_t length = fread(pData, 1, MAX_CHUNK_LENGTH, file); + + httpResponse.sendChunk(pData, length); + if (length < MAX_CHUNK_LENGTH) { + fclose(file); + httpResponse.closeConnection(); + unfinishedConnection.erase(mgConnection->sock); + httpResponse.sendChunk("", 0); + } + free(pData); } @@ -668,16 +671,16 @@ void WebServer::continueConnection(struct mg_connection* mgConnection) { * @param [in] pathPattern The path pattern to be matched. * @param [in] webServerRequestHandler The request handler to be called. */ -WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { - m_method = method; - m_pattern = std::regex(pathPattern); - m_requestHandler = webServerRequestHandler; +WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { + m_method = method; + m_pattern = std::regex(pathPattern); + m_requestHandler = webServerRequestHandler; } // PathHandler -WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { - m_method = std::move(method); - m_pattern = std::regex(pathPattern); - m_requestHandler = webServerRequestHandler; +WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { + m_method = std::move(method); + m_pattern = std::regex(pathPattern); + m_requestHandler = webServerRequestHandler; } // PathHandler @@ -689,13 +692,12 @@ WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pat * @param [in] path The path to be matched. * @return True if the path matches. */ - bool WebServer::PathHandler::match(const char* method, size_t method_len, const char* path) { - //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); - if (method_len != m_method.length() || strncmp(method, m_method.c_str(), method_len) != 0) { - return false; - } - return std::regex_search(path, m_pattern); + //ESP_LOGD(LOG_TAG, "match: %s with %s", m_pattern.c_str(), path.c_str()); + if (method_len != m_method.length() || strncmp(method, m_method.c_str(), method_len) != 0) { + return false; + } + return std::regex_search(path, m_pattern); } // match @@ -705,8 +707,8 @@ bool WebServer::PathHandler::match(const char* method, size_t method_len, const * @param [in] response An object representing the response. * @return N/A. */ -void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer::HTTPResponse *response) { - m_requestHandler(request, response); +void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer::HTTPResponse* response) { + m_requestHandler(request, response); } // invoke @@ -717,7 +719,7 @@ void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer:: * @param [in] message The description of the underlying Mongoose message. */ WebServer::HTTPRequest::HTTPRequest(struct http_message* message) { - m_message = message; + m_message = message; } // HTTPRequest @@ -729,9 +731,10 @@ WebServer::HTTPRequest::HTTPRequest(struct http_message* message) { * @return The body of the request. */ const char* WebServer::HTTPRequest::getBody() const { - return m_message->body.p; + return m_message->body.p; } // getBody + /** * @brief Get the length of the body of the request. * When an HTTP request is either PUT or POST then it may contain a payload that is also @@ -739,7 +742,7 @@ const char* WebServer::HTTPRequest::getBody() const { * @return The length of the body of the request. */ size_t WebServer::HTTPRequest::getBodyLen() const { - return m_message->body.len; + return m_message->body.len; } // getBodyLen @@ -751,16 +754,17 @@ size_t WebServer::HTTPRequest::getBodyLen() const { * @return The body of the request. */ const char* WebServer::HTTPRequest::getMethod() const { - return m_message->method.p; + return m_message->method.p; } // getMethod + /** * @brief Get the length of the method of the request. * An HTTP request contains a request method which is one of GET, PUT, POST, etc. * @return The length of the method of the request. */ size_t WebServer::HTTPRequest::getMethodLen() const { - return m_message->method.len; + return m_message->method.len; } // getMethodLen @@ -773,9 +777,10 @@ size_t WebServer::HTTPRequest::getMethodLen() const { * @return The path of the request. */ const char* WebServer::HTTPRequest::getPath() const { - return m_message->uri.p; + return m_message->uri.p; } // getPath + /** * @brief Get the path of the request. * The path of an HTTP request is the portion of the URL that follows the hostname/port pair @@ -783,11 +788,10 @@ const char* WebServer::HTTPRequest::getPath() const { * @return The path of the request. */ size_t WebServer::HTTPRequest::getPathLen() const { - return m_message->uri.len; + return m_message->uri.len; } // getPath -#define STATE_NAME 0 -#define STATE_VALUE 1 + /** * @brief Get the query part of the request. * The query is a set of name = value pairs. The return is a map keyed by the name items. @@ -795,48 +799,47 @@ size_t WebServer::HTTPRequest::getPathLen() const { * @return The query part of the request. */ std::map WebServer::HTTPRequest::getQuery() const { - // Walk through all the characters in the query string maintaining a simple state machine - // that lets us know what we are parsing. - std::map queryMap; - const char* queryString = m_message->query_string.p; - size_t queryStringLen = m_message->query_string.len; - int i=0; - - /* - * We maintain a simple state machine with states of: - * * STATE_NAME - We are parsing a name. - * * STATE_VALUE - We are parsing a value. - */ - int state = STATE_NAME; - std::string name = ""; - std::string value; - // Loop through each character in the query string. - for (i=0; i queryMap; + const char* queryString = m_message->query_string.p; + size_t queryStringLen = m_message->query_string.len; + + /* + * We maintain a simple state machine with states of: + * * STATE_NAME - We are parsing a name. + * * STATE_VALUE - We are parsing a value. + */ + int state = STATE_NAME; + std::string name = ""; + std::string value; + // Loop through each character in the query string. + for (size_t i = 0; i < queryStringLen; i++) { + char currentChar = queryString[i]; + if (state == STATE_NAME) { + if (currentChar != '=') { + name += currentChar; + } else { + state = STATE_VALUE; + value = ""; + } + } // End state = STATE_NAME + else { // if (state == STATE_VALUE) + if (currentChar != '&') { + value += currentChar; + } else { + //ESP_LOGD(LOG_TAG, "name=%s, value=%s", name.c_str(), value.c_str()); + queryMap[name] = value; + state = STATE_NAME; + name = ""; + } + } // End state = STATE_VALUE + } // End for loop + if (state == STATE_VALUE) { + //ESP_LOGD(LOG_TAG, "name=%s, value=%s", name.c_str(), value.c_str()); + queryMap[name] = value; + } + return queryMap; } // getQuery @@ -860,17 +863,17 @@ std::map WebServer::HTTPRequest::getQuery() const { * @return A vector of the constituent parts of the path. */ std::vector WebServer::HTTPRequest::pathSplit() const { - std::istringstream stream(std::string(getPath(), getPathLen())); // I don't know if there's a better istringstream constructor for this - std::vector ret; - std::string pathPart; - while(std::getline(stream, pathPart, '/')) { - ret.push_back(pathPart); - } - // Debug - for (int i=0; i ret; + std::string pathPart; + while (std::getline(stream, pathPart, '/')) { + ret.push_back(pathPart); + } + // Debug + for (int i = 0; i < ret.size(); i++) { + ESP_LOGD(LOG_TAG, "part[%d]: %s", i, ret[i].c_str()); + } + return ret; } // pathSplit /** @@ -882,8 +885,8 @@ std::vector WebServer::HTTPRequest::pathSplit() const { * @return N/A. */ void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::string& fileName) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", - varName.c_str(), fileName.c_str()); + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", + varName.c_str(), fileName.c_str()); } // WebServer::HTTPMultiPart::begin @@ -893,7 +896,7 @@ void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::stri * @return N/A. */ void WebServer::HTTPMultiPart::end() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::end()"); + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::end()"); } // WebServer::HTTPMultiPart::end @@ -905,7 +908,7 @@ void WebServer::HTTPMultiPart::end() { * @return N/A. */ void WebServer::HTTPMultiPart::data(const std::string& data) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); } // WebServer::HTTPMultiPart::data @@ -914,7 +917,7 @@ void WebServer::HTTPMultiPart::data(const std::string& data) { * @return N/A. */ void WebServer::HTTPMultiPart::multipartEnd() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartEnd()"); + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::multipartEnd()"); } // WebServer::HTTPMultiPart::multipartEnd @@ -923,7 +926,7 @@ void WebServer::HTTPMultiPart::multipartEnd() { * @return N/A. */ void WebServer::HTTPMultiPart::multipartStart() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartStart()"); + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::multipartStart()"); } // WebServer::HTTPMultiPart::multipartStart @@ -932,7 +935,6 @@ void WebServer::HTTPMultiPart::multipartStart() { * @return N/A. */ void WebServer::WebSocketHandler::onCreated() { - } // onCreated @@ -942,7 +944,6 @@ void WebServer::WebSocketHandler::onCreated() { * @return N/A. */ void WebServer::WebSocketHandler::onMessage(const std::string& message){ - } // onMessage /** @@ -950,7 +951,6 @@ void WebServer::WebSocketHandler::onMessage(const std::string& message){ * @return N/A */ void WebServer::WebSocketHandler::onClosed() { - } // onClosed @@ -960,12 +960,13 @@ void WebServer::WebSocketHandler::onClosed() { * @return N/A. */ void WebServer::WebSocketHandler::sendData(const std::string& message) { - ESP_LOGD(tag, "WebSocketHandler::sendData(length=%d)", message.length()); - mg_send_websocket_frame(m_mgConnection, - WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, - message.data(), message.length()); + ESP_LOGD(LOG_TAG, "WebSocketHandler::sendData(length=%d)", message.length()); + mg_send_websocket_frame(m_mgConnection, + WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, + message.data(), message.length()); } // sendData + /** * @brief Send data down the WebSocket * @param [in] data The message to send down the socket. @@ -973,9 +974,9 @@ void WebServer::WebSocketHandler::sendData(const std::string& message) { * @return N/A. */ void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { - mg_send_websocket_frame(m_mgConnection, - WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, - data, size); + mg_send_websocket_frame(m_mgConnection, + WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, + data, size); } // sendData @@ -986,9 +987,7 @@ void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { * @return N/A. */ void WebServer::WebSocketHandler::close() { - mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_CLOSE, nullptr, 0); + mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_CLOSE, nullptr, 0); } // close - - #endif // CONFIG_MONGOOSE_PRESENT diff --git a/cpp_utils/WebServer.h b/cpp_utils/WebServer.h index ae06647b..961b6662 100644 --- a/cpp_utils/WebServer.h +++ b/cpp_utils/WebServer.h @@ -27,183 +27,196 @@ class WebServer; */ class WebServer { public: - /** - * @brief Request wrapper for an HTTP request. - */ - class HTTPRequest { - public: - HTTPRequest(struct http_message* message); - const char* getMethod() const; - const char* getPath() const; - const char* getBody() const; - size_t getMethodLen() const; - size_t getPathLen() const; - size_t getBodyLen() const; - std::map getQuery() const; - std::vector pathSplit() const; - private: - struct http_message* m_message; - }; // HTTPRequest - - /** - * @brief Response wrapper for an HTTP response. - */ - class HTTPResponse { - public: - HTTPResponse(struct mg_connection *nc); - void addHeader(const std::string& name, const std::string& value); - void addHeader(std::string&& name, std::string&& value); - void setStatus(int status); - void setHeaders(const std::map& headers); - void setHeaders(std::map&& headers); - void sendData(const std::string& data); - void sendData(const uint8_t* pData, size_t length); - void sendData(const char* pData, size_t length); - const std::string& getRootPath() const; - void setRootPath(const std::string& path); - void setRootPath(std::string&& path); - void sendChunkHead(); - void sendChunk(const char* pData, size_t length); - void closeConnection(); - private: - struct mg_connection *m_nc; - std::string m_rootPath; - int m_status; - std::map m_headers; - bool m_dataSent; - std::string buildHeaders(); - }; // HTTPResponse - - /** - * @brief Handler for a Multipart. - * - * This class is usually subclassed to provide your own implementation. Typically - * a factory is implemented based on HTTPMultiPartFactory that creates instances. This - * is then registered with the WebServer. When done, when ever the WebServer receives multi - * part requests, this handler for Multipart is called. The call sequence is usually: - * ~~~ - * multipartStart - * begin - * data* - * end - * begin - * data* - * end - * ... - * multipartEnd - * ~~~ - * - * There can be multiple begin, data, data, ..., end groups. - */ - class HTTPMultiPart { - public: - virtual ~HTTPMultiPart() = default; - virtual void begin(const std::string& varName, const std::string& fileName); - virtual void end(); - virtual void data(const std::string& data); - virtual void multipartEnd(); - virtual void multipartStart(); - }; // HTTPMultiPart - - /** - * @brief Factory for creating Multipart instances. - * This class is meant to be implemented to provide a constructor for a custom - * HTTPMultiPart instance. - * @code{.cpp} - * class MyMultiPart : public WebServer::HTTPMultiPart { - * public: - * void begin(std::string varName, std::string fileName) { - * ESP_LOGD(tag, "MyMultiPart begin(): varName=%s, fileName=%s", - * varName.c_str(), fileName.c_str()); - * } - * - * void end() { - * ESP_LOGD(tag, "MyMultiPart end()"); - * } - * - * void data(std::string data) { - * ESP_LOGD(tag, "MyMultiPart data(): length=%d", data.length()); - * } - * - * void multipartEnd() { - * ESP_LOGD(tag, "MyMultiPart multipartEnd()"); - * } - * - * void multipartStart() { - * ESP_LOGD(tag, "MyMultiPart multipartStart()"); - * } - * }; - * - * class MyMultiPartFactory : public WebServer::HTTPMultiPartFactory { - * WebServer::HTTPMultiPart *createNew() { - * return new MyMultiPart(); - * } - * }; - * @endcode - */ - class HTTPMultiPartFactory { - public: - /** - * @brief Create a new HTTPMultiPart instance. - * @return A new HTTPMultiPart instance. - */ - virtual HTTPMultiPart *newInstance() = 0; - }; - - /** - * @brief The handler for path matching. - * - */ - class PathHandler { - public: - PathHandler(const std::string& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); - PathHandler(std::string&& method, const std::string& pathPattern, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); - bool match(const char* method, size_t method_len, const char* path); - void invoke(HTTPRequest *request, HTTPResponse *response); - private: - std::string m_method; - std::regex m_pattern; - void (*m_requestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse); - }; // PathHandler - - /** - * @brief A WebSocket handler for handling WebSockets. - */ - class WebSocketHandler { - public: - void onCreated(); - virtual void onMessage(const std::string& message); - void onClosed(); - void sendData(const std::string& message); - void sendData(const uint8_t* data, uint32_t size); - void close(); - private: - struct mg_connection *m_mgConnection; - }; - - class WebSocketHandlerFactory { - public: - virtual WebSocketHandler *newInstance() = 0; - }; - - WebServer(); - virtual ~WebServer(); - void addPathHandler(const std::string& method, const std::string& pathExpr, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); - void addPathHandler(std::string&& method, const std::string& pathExpr, void (* webServerRequestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); - const std::string& getRootPath(); - void setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory); - void setRootPath(const std::string& path); - void setRootPath(std::string&& path); - void setWebSocketHandlerFactory(WebSocketHandlerFactory *pWebSocketHandlerFactory); - void start(unsigned short port = 80); - void processRequest(struct mg_connection *mgConnection, struct http_message *message); - void continueConnection(struct mg_connection* mgConnection); - HTTPMultiPartFactory *m_pMultiPartFactory; - WebSocketHandlerFactory *m_pWebSocketHandlerFactory; + /** + * @brief Request wrapper for an HTTP request. + */ + class HTTPRequest { + public: + HTTPRequest(struct http_message* message); + const char* getMethod() const; + const char* getPath() const; + const char* getBody() const; + size_t getMethodLen() const; + size_t getPathLen() const; + size_t getBodyLen() const; + std::map getQuery() const; + std::vector pathSplit() const; + + private: + struct http_message* m_message; + + }; // HTTPRequest + + /** + * @brief Response wrapper for an HTTP response. + */ + class HTTPResponse { + public: + HTTPResponse(struct mg_connection* nc); + void addHeader(const std::string& name, const std::string& value); + void addHeader(std::string&& name, std::string&& value); + void setStatus(int status); + void setHeaders(const std::map& headers); + void setHeaders(std::map&& headers); + void sendData(const std::string& data); + void sendData(const uint8_t* pData, size_t length); + void sendData(const char* pData, size_t length); + const std::string& getRootPath() const; + void setRootPath(const std::string& path); + void setRootPath(std::string&& path); + void sendChunkHead(); + void sendChunk(const char* pData, size_t length); + void closeConnection(); + + private: + struct mg_connection* m_nc; + std::string m_rootPath; + int m_status; + std::map m_headers; + bool m_dataSent; + std::string buildHeaders(); + + }; // HTTPResponse + + /** + * @brief Handler for a Multipart. + * + * This class is usually subclassed to provide your own implementation. Typically + * a factory is implemented based on HTTPMultiPartFactory that creates instances. This + * is then registered with the WebServer. When done, when ever the WebServer receives multi + * part requests, this handler for Multipart is called. The call sequence is usually: + * ~~~ + * multipartStart + * begin + * data* + * end + * begin + * data* + * end + * ... + * multipartEnd + * ~~~ + * + * There can be multiple begin, data, data, ..., end groups. + */ + class HTTPMultiPart { + public: + virtual ~HTTPMultiPart() = default; + virtual void begin(const std::string& varName, const std::string& fileName); + virtual void end(); + virtual void data(const std::string& data); + virtual void multipartEnd(); + virtual void multipartStart(); + + }; // HTTPMultiPart + + /** + * @brief Factory for creating Multipart instances. + * This class is meant to be implemented to provide a constructor for a custom + * HTTPMultiPart instance. + * @code{.cpp} + * class MyMultiPart : public WebServer::HTTPMultiPart { + * public: + * void begin(std::string varName, std::string fileName) { + * ESP_LOGD(tag, "MyMultiPart begin(): varName=%s, fileName=%s", + * varName.c_str(), fileName.c_str()); + * } + * + * void end() { + * ESP_LOGD(tag, "MyMultiPart end()"); + * } + * + * void data(std::string data) { + * ESP_LOGD(tag, "MyMultiPart data(): length=%d", data.length()); + * } + * + * void multipartEnd() { + * ESP_LOGD(tag, "MyMultiPart multipartEnd()"); + * } + * + * void multipartStart() { + * ESP_LOGD(tag, "MyMultiPart multipartStart()"); + * } + * }; + * + * class MyMultiPartFactory : public WebServer::HTTPMultiPartFactory { + * WebServer::HTTPMultiPart *createNew() { + * return new MyMultiPart(); + * } + * }; + * @endcode + */ + class HTTPMultiPartFactory { + public: + /** + * @brief Create a new HTTPMultiPart instance. + * @return A new HTTPMultiPart instance. + */ + virtual HTTPMultiPart* newInstance() = 0; + + }; + + /** + * @brief The handler for path matching. + * + */ + class PathHandler { + public: + PathHandler(const std::string& method, const std::string& pathPattern, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + PathHandler(std::string&& method, const std::string& pathPattern, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + bool match(const char* method, size_t method_len, const char* path); + void invoke(HTTPRequest* request, HTTPResponse* response); + + private: + std::string m_method; + std::regex m_pattern; + void (*m_requestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse); + + }; // PathHandler + + /** + * @brief A WebSocket handler for handling WebSockets. + */ + class WebSocketHandler { + public: + void onCreated(); + virtual void onMessage(const std::string& message); + void onClosed(); + void sendData(const std::string& message); + void sendData(const uint8_t* data, uint32_t size); + void close(); + + private: + struct mg_connection* m_mgConnection; + + }; + + class WebSocketHandlerFactory { + public: + virtual WebSocketHandler* newInstance() = 0; + + }; + + WebServer(); + virtual ~WebServer(); + void addPathHandler(const std::string& method, const std::string& pathExpr, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + void addPathHandler(std::string&& method, const std::string& pathExpr, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + const std::string& getRootPath(); + void setMultiPartFactory(HTTPMultiPartFactory* pMultiPartFactory); + void setRootPath(const std::string& path); + void setRootPath(std::string&& path); + void setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHandlerFactory); + void start(unsigned short port = 80); + void processRequest(struct mg_connection* mgConnection, struct http_message* message); + void continueConnection(struct mg_connection* mgConnection); + HTTPMultiPartFactory* m_pMultiPartFactory; + WebSocketHandlerFactory* m_pWebSocketHandlerFactory; + private: - std::string m_rootPath; - std::vector m_pathHandlers; - std::map unfinishedConnection; + std::string m_rootPath; + std::vector m_pathHandlers; + std::map unfinishedConnection; + }; #endif // CONFIG_MONGOOSE_PRESENT diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp index e7fc05fb..aa9a7c57 100644 --- a/cpp_utils/WebSocket.cpp +++ b/cpp_utils/WebSocket.cpp @@ -50,8 +50,8 @@ struct Frame { */ static void dumpFrame(Frame frame) { std::ostringstream oss; - oss << "Fin: " << (int)frame.fin << ", OpCode: " << (int)frame.opCode; - switch(frame.opCode) { + oss << "Fin: " << (int) frame.fin << ", OpCode: " << (int) frame.opCode; + switch (frame.opCode) { case OPCODE_BINARY: { oss << " BINARY"; break; @@ -81,7 +81,7 @@ static void dumpFrame(Frame frame) { break; } } - oss << ", Mask: " << (int)frame.mask << ", len: " << (int)frame.len; + oss << ", Mask: " << (int) frame.mask << ", len: " << (int) frame.len; ESP_LOGD(LOG_TAG, "WebSocket frame: %s", oss.str().c_str()); } // dumpFrame @@ -101,6 +101,7 @@ class WebSocketReader: public Task { void end() { m_end = true; } + private: bool m_end; /** @@ -114,18 +115,16 @@ class WebSocketReader: public Task { Socket peerSocket = pWebSocket->getSocket(); Frame frame; - while(1) { - if (m_end) { - break; - } - ESP_LOGD("WebSocketReader", "Waiting on socket data for socket %s", peerSocket.toString().c_str()); - int length = peerSocket.receive((uint8_t*)&frame, sizeof(frame), true); // Read exact + while (true) { + if (m_end) break; + ESP_LOGD(LOG_TAG, "Waiting on socket data for socket %s", peerSocket.toString().c_str()); + int length = peerSocket.receive((uint8_t*) &frame, sizeof(frame), true); // Read exact if (length != sizeof(frame)) { ESP_LOGD(LOG_TAG, "Socket read error"); pWebSocket->close(); return; } - ESP_LOGD("WebSocketReader", "Received data from web socket. Length: %d", length); + ESP_LOGD(LOG_TAG, "Received data from web socket. Length: %d", length); dumpFrame(frame); // The following section parses the WebSocket frame. @@ -135,12 +134,12 @@ class WebSocketReader: public Task { payloadLen = frame.len; } else if (frame.len == 126) { uint16_t tempLen; - peerSocket.receive((uint8_t*)&tempLen, sizeof(tempLen), true); + peerSocket.receive((uint8_t*) &tempLen, sizeof(tempLen), true); payloadLen = ntohs(tempLen); } else if (frame.len == 127) { uint64_t tempLen; - peerSocket.receive((uint8_t*)&tempLen, sizeof(tempLen), true); - payloadLen = ntohl((uint32_t)tempLen); + peerSocket.receive((uint8_t*) &tempLen, sizeof(tempLen), true); + payloadLen = ntohl((uint32_t) tempLen); } if (frame.mask == 1) { peerSocket.receive(mask, sizeof(mask), true); @@ -152,12 +151,12 @@ class WebSocketReader: public Task { ESP_LOGD("WebSocketReader", "Web socket payload, length=%d:", payloadLen); } - WebSocketHandler *pWebSocketHandler = pWebSocket->getHandler(); - switch(frame.opCode) { + WebSocketHandler* pWebSocketHandler = pWebSocket->getHandler(); + switch (frame.opCode) { case OPCODE_TEXT: case OPCODE_BINARY: { if (pWebSocketHandler != nullptr) { - WebSocketInputStreambuf streambuf(pWebSocket->getSocket(), payloadLen, frame.mask==1?mask:nullptr); + WebSocketInputStreambuf streambuf(pWebSocket->getSocket(), payloadLen, (frame.mask == 1) ? mask : nullptr); pWebSocketHandler->onMessage(&streambuf, pWebSocket); //streambuf.discard(); } @@ -187,12 +186,12 @@ class WebSocketReader: public Task { } default: { - ESP_LOGD("WebSocketReader", "Unknown opcode: %d", frame.opCode); + ESP_LOGD("WebSocketReader", "Unknown opcode: %d", frame.opCode); break; } } // Switch opCode - } // While (1) + } // while (true) ESP_LOGD("WebSocketReader", "<< run"); } // run }; // WebSocketReader @@ -218,7 +217,7 @@ void WebSocketHandler::onClose() { * ``` * This will read the whole message into the string stream. */ -void WebSocketHandler::onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf, WebSocket *pWebSocket) { +void WebSocketHandler::onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf, WebSocket* pWebSocket) { ESP_LOGD("WebSocketHandler", ">> onMessage"); ESP_LOGD("WebSocketHandler", "<< onMessage"); } // onData @@ -279,7 +278,7 @@ void WebSocket::close(uint16_t status, std::string message) { frame.opCode = OPCODE_CLOSE; frame.mask = 0; frame.len = message.length() + 2; - int rc = m_socket.send((uint8_t *)&frame, sizeof(frame)); + int rc = m_socket.send((uint8_t*) &frame, sizeof(frame)); if (rc > 0) { rc = m_socket.send(status); @@ -329,15 +328,15 @@ void WebSocket::send(std::string data, uint8_t sendType) { frame.rsv1 = 0; frame.rsv2 = 0; frame.rsv3 = 0; - frame.opCode = sendType==SEND_TYPE_TEXT?OPCODE_TEXT:OPCODE_BINARY; + frame.opCode = (sendType == SEND_TYPE_TEXT) ? OPCODE_TEXT : OPCODE_BINARY; frame.mask = 0; if (data.length() < 126) { frame.len = data.length(); - m_socket.send((uint8_t *)&frame, sizeof(frame)); + m_socket.send((uint8_t*) &frame, sizeof(frame)); } else { frame.len = 126; - m_socket.send((uint8_t *)&frame, sizeof(frame)); - m_socket.send(htons((uint16_t)data.length())); // Convert to network byte order from host byte order + m_socket.send((uint8_t*) &frame, sizeof(frame)); + m_socket.send(htons((uint16_t) data.length())); // Convert to network byte order from host byte order } m_socket.send((uint8_t*)data.data(), data.length()); ESP_LOGD(LOG_TAG, "<< send"); @@ -358,14 +357,14 @@ void WebSocket::send(uint8_t* data, uint16_t length, uint8_t sendType) { frame.rsv1 = 0; frame.rsv2 = 0; frame.rsv3 = 0; - frame.opCode = sendType==SEND_TYPE_TEXT?OPCODE_TEXT:OPCODE_BINARY; + frame.opCode = (sendType==SEND_TYPE_TEXT) ? OPCODE_TEXT : OPCODE_BINARY; frame.mask = 0; if (length < 126) { frame.len = length; - m_socket.send((uint8_t *)&frame, sizeof(frame)); + m_socket.send((uint8_t*) &frame, sizeof(frame)); } else { frame.len = 126; - m_socket.send((uint8_t *)&frame, sizeof(frame)); + m_socket.send((uint8_t*) &frame, sizeof(frame)); m_socket.send(htons(length)); // Convert to network byte order from host byte order } m_socket.send(data, length); @@ -405,7 +404,7 @@ void WebSocket::startReader() { WebSocketInputStreambuf::WebSocketInputStreambuf( Socket socket, size_t dataLength, - uint8_t *pMask, + uint8_t* pMask, size_t bufferSize) { m_socket = socket; // The socket we will be reading from m_dataLength = dataLength; // The size of the record we wish to read. @@ -476,7 +475,7 @@ WebSocketInputStreambuf::int_type WebSocketInputStreambuf::underflow() { // We wish to refill the buffer. We want to read data from the socket. We want to read either // the size of the buffer to fill it or the maximum number of bytes remaining to be read. // We will choose which ever is smaller as the number of bytes to read into the buffer. - int remainingBytes = getRecordSize()-m_sizeRead; + int remainingBytes = getRecordSize() - m_sizeRead; size_t sizeToRead; if (remainingBytes < m_bufferSize) { sizeToRead = remainingBytes; @@ -485,7 +484,7 @@ WebSocketInputStreambuf::int_type WebSocketInputStreambuf::underflow() { } ESP_LOGD("WebSocketInputRecordStreambuf", "- getting next buffer of data; size request: %d", sizeToRead); - int bytesRead = m_socket.receive((uint8_t*)m_buffer, sizeToRead, true); + int bytesRead = m_socket.receive((uint8_t*) m_buffer, sizeToRead, true); if (bytesRead == 0) { ESP_LOGD("WebSocketInputRecordStreambuf", "<< underflow: Read 0 bytes"); return EOF; @@ -493,8 +492,8 @@ WebSocketInputStreambuf::int_type WebSocketInputStreambuf::underflow() { // If the WebSocket frame shows that we have a mask bit set then we have to unmask the data. if (m_pMask != nullptr) { - for (int i=0; i> onMessage"); // Test to see if we are currently active. If not, this is the start of a transfer. if (!m_active) { - ESP_LOGD("FileTransferWebSocketHandler", "Not yet active!"); // Read a chunk of data into memory. std::stringstream buffer; @@ -82,7 +71,7 @@ class FileTransferWebSocketHandler : public WebSocketHandler { // "length": // Length of file. Optional. // } JsonObject jo = JSON::parseObject(buffer.str()); - m_fileName = jo.getString("name"); + m_fileName = jo.getString("name"); assert(m_fileName.length() > 0); // Doesn't make any sense to receive a zero length file name. if (jo.hasItem("length")) { m_fileLength = jo.getInt("length"); @@ -91,9 +80,9 @@ class FileTransferWebSocketHandler : public WebSocketHandler { ESP_LOGD("FileTransferWebSocketHandler", "Target file is %s", fileName.c_str()); // If the file to create ends in a "/" then we are being asked to create a directory. - if (m_fileName.substr(m_fileName.size()-1)=="/") { + if (m_fileName.substr(m_fileName.size() - 1) == "/") { ESP_LOGD("FileTransferWebSocketHandler", "Is a directory!!"); - fileName = fileName.substr(0, fileName.size()-1); // Remove the trailing slash + fileName = fileName.substr(0, fileName.size() - 1); // Remove the trailing slash struct stat statbuf; if (stat(fileName.c_str(), &statbuf) == 0) { if (S_ISREG(statbuf.st_mode)) { @@ -114,7 +103,7 @@ class FileTransferWebSocketHandler : public WebSocketHandler { return; } } - m_active = true; + m_active = true; ESP_LOGD("FileTransferWebSocketHandler", "Filename: %s, length: %d", fileName.c_str(), m_fileLength); } // !active --- Not active else { @@ -133,7 +122,6 @@ class FileTransferWebSocketHandler : public WebSocketHandler { } } // onMessage - /** * @brief Handle a close event on the web socket. */ @@ -146,8 +134,17 @@ class FileTransferWebSocketHandler : public WebSocketHandler { if (m_ofStream.is_open()) { m_ofStream.close(); // Close the file now that we have finished writing to it. } - delete this; // Delete ourself. + delete this; // Delete ourselves. } // onClose + +private: + std::string m_fileName; // The name of the file we are receiving. + uint32_t m_fileLength; // We may optionally receive a file length. + uint32_t m_sizeReceived; // The size of the data actually received so far. + bool m_active; // Are we actively processing a file. + std::ofstream m_ofStream; // The file stream we are writing to when active. + std::string m_rootPath; // The root path for file names. + }; // FileTransferWebSocketHandler } // End un-named namespace diff --git a/cpp_utils/WebSocketFileTransfer.h b/cpp_utils/WebSocketFileTransfer.h index 98cbda9b..6eb8de43 100644 --- a/cpp_utils/WebSocketFileTransfer.h +++ b/cpp_utils/WebSocketFileTransfer.h @@ -17,7 +17,7 @@ class WebSocketFileTransfer { public: WebSocketFileTransfer(std::string rootPath); - void start(WebSocket *pWebSocket); + void start(WebSocket* pWebSocket); }; #endif /* COMPONENTS_CPP_UTILS_WEBSOCKETFILETRANSFER_H_ */ diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 09d59d55..3c6d4112 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -47,10 +47,10 @@ static void setDNSServer(char *ip) { * @brief Creates and uses a default event handler */ WiFi::WiFi() - : ip(0) - , gw(0) - , netmask(0) - , m_pWifiEventHandler(nullptr) + : ip(0) + , gw(0) + , netmask(0) + , m_pWifiEventHandler(nullptr) { m_eventLoopStarted = false; m_initCalled = false; @@ -94,14 +94,14 @@ void WiFi::addDNSServer(const std::string& ip) { void WiFi::addDNSServer(const char* ip) { ip_addr_t dns_server; - if(inet_pton(AF_INET, ip, &dns_server)) { + if (inet_pton(AF_INET, ip, &dns_server)) { addDNSServer(ip); } } // addDNSServer void WiFi::addDNSServer(ip_addr_t ip) { - ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*) (&ip))[0], ((uint8_t*) (&ip))[1], ((uint8_t*) (&ip))[2], ((uint8_t*) (&ip))[3]); init(); ::dns_setserver(m_dnsCount, &ip); m_dnsCount++; @@ -132,14 +132,14 @@ void WiFi::setDNSServer(int numdns, const std::string& ip) { void WiFi::setDNSServer(int numdns, const char* ip) { ip_addr_t dns_server; - if(inet_pton(AF_INET, ip, &dns_server)) { + if (inet_pton(AF_INET, ip, &dns_server)) { setDNSServer(numdns, dns_server); } } // setDNSServer void WiFi::setDNSServer(int numdns, ip_addr_t ip) { - ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*)(&ip))[0], ((uint8_t*)(&ip))[1], ((uint8_t*)(&ip))[2], ((uint8_t*)(&ip))[3]); + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*) (&ip))[0], ((uint8_t*) (&ip))[1], ((uint8_t*) (&ip))[2], ((uint8_t*) (&ip))[3]); init(); ::dns_setserver(numdns, &ip); } // setDNSServer @@ -163,14 +163,14 @@ uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bo init(); if (ip != 0 && gw != 0 && netmask != 0) { - ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client - tcpip_adapter_ip_info_t ipInfo; - ipInfo.ip.addr = ip; - ipInfo.gw.addr = gw; - ipInfo.netmask.addr = netmask; + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; - ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); } esp_err_t errRc = ::esp_wifi_set_mode(mode); @@ -195,23 +195,22 @@ uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bo } m_connectFinished.take("connectAP"); // Take the semaphore to wait for a connection. - do { - ESP_LOGD(LOG_TAG, "esp_wifi_connect"); - errRc = ::esp_wifi_connect(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } - } - while (!m_connectFinished.take(5000, "connectAP")); // retry if not connected within 5s - m_connectFinished.give(); + do { + ESP_LOGD(LOG_TAG, "esp_wifi_connect"); + errRc = ::esp_wifi_connect(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + } + while (!m_connectFinished.take(5000, "connectAP")); // retry if not connected within 5s + m_connectFinished.give(); ESP_LOGD(LOG_TAG, "<< connectAP"); return m_apConnectionStatus; // Return ESP_OK if we are now connected and wifi_err_reason_t if not. } // connectAP - /** * @brief Dump diagnostics to the log. */ @@ -233,7 +232,6 @@ bool WiFi::isConnectedToAP() { } // isConnected - /** * @brief Primary event handler interface. */ @@ -243,7 +241,7 @@ bool WiFi::isConnectedToAP() { // processing. We can then retrieve the specific/custom event handler from within it and invoke that. This then makes this // an indirection vector to the real caller. - WiFi *pWiFi = (WiFi *)ctx; // retrieve the WiFi object from the passed in context. + WiFi* pWiFi = (WiFi*) ctx; // retrieve the WiFi object from the passed in context. // Invoke the event handler. esp_err_t rc; @@ -256,7 +254,6 @@ bool WiFi::isConnectedToAP() { // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that // indicates we are waiting for a connection complete. if (event->event_id == SYSTEM_EVENT_STA_GOT_IP || event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { - if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { // If we connected and have an IP, change the status to ESP_OK. Otherwise, change it to the reason code. pWiFi->m_apConnectionStatus = ESP_OK; } else { @@ -303,14 +300,15 @@ std::string WiFi::getApSSID() { wifi_config_t conf; //init(); esp_wifi_get_config(WIFI_IF_AP, &conf); - return std::string((char *)conf.sta.ssid); + return std::string((char*) conf.sta.ssid); } // getApSSID + /** * @brief Get the current ESP32 IP form AP. * @return The ESP32 IP. */ -std::string WiFi::getApIp(){ +std::string WiFi::getApIp() { tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); char ipAddrStr[30]; inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); @@ -321,7 +319,7 @@ std::string WiFi::getApIp(){ * @brief Get the current AP netmask. * @return The Netmask IP. */ -std::string WiFi::getApNetmask(){ +std::string WiFi::getApNetmask() { tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); char ipAddrStr[30]; inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); @@ -332,7 +330,7 @@ std::string WiFi::getApNetmask(){ * @brief Get the current AP Gateway IP. * @return The Gateway IP. */ -std::string WiFi::getApGateway(){ +std::string WiFi::getApGateway() { tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); char ipAddrStr[30]; inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); @@ -353,13 +351,13 @@ struct in_addr WiFi::getHostByName(const std::string& hostName) { struct in_addr WiFi::getHostByName(const char* hostName) { struct in_addr retAddr; - struct hostent *he = gethostbyname(hostName); + struct hostent* he = gethostbyname(hostName); if (he == nullptr) { retAddr.s_addr = 0; ESP_LOGD(LOG_TAG, "Unable to resolve %s - %d", hostName, h_errno); } else { - retAddr = *(struct in_addr *)(he->h_addr_list[0]); - ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); + retAddr = *(struct in_addr*) (he->h_addr_list[0]); + ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t*) &retAddr); } return retAddr; } // getHostByName @@ -372,7 +370,7 @@ struct in_addr WiFi::getHostByName(const char* hostName) { std::string WiFi::getMode() { wifi_mode_t mode; esp_wifi_get_mode(&mode); - switch(mode) { + switch (mode) { case WIFI_MODE_NULL: return "WIFI_MODE_NULL"; case WIFI_MODE_STA: @@ -397,11 +395,12 @@ tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { return ipInfo; } // getStaIpInfo + /** * @brief Get the current ESP32 IP form STA. * @return The ESP32 IP. */ -std::string WiFi::getStaIp(){ +std::string WiFi::getStaIp() { tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); char ipAddrStr[30]; inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); @@ -413,7 +412,7 @@ std::string WiFi::getStaIp(){ * @brief Get the current STA netmask. * @return The Netmask IP. */ -std::string WiFi::getStaNetmask(){ +std::string WiFi::getStaNetmask() { tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); char ipAddrStr[30]; inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); @@ -425,13 +424,14 @@ std::string WiFi::getStaNetmask(){ * @brief Get the current STA Gateway IP. * @return The Gateway IP. */ -std::string WiFi::getStaGateway(){ +std::string WiFi::getStaGateway() { tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); char ipAddrStr[30]; inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); return std::string(ipAddrStr); } // getStaGateway + /** * @brief Get the MAC address of the STA interface. * @return The MAC address of the STA interface. @@ -452,7 +452,7 @@ std::string WiFi::getStaMac() { std::string WiFi::getStaSSID() { wifi_config_t conf; esp_wifi_get_config(WIFI_IF_STA, &conf); - return std::string((char *)conf.ap.ssid); + return std::string((char*) conf.ap.ssid); } // getStaSSID @@ -460,7 +460,6 @@ std::string WiFi::getStaSSID() { * @brief Initialize WiFi. */ /* PRIVATE */ void WiFi::init() { - // If we have already started the event loop, then change the handler otherwise // start the event loop. if (m_eventLoopStarted) { @@ -530,15 +529,20 @@ std::vector WiFi::scan() { esp_err_t rc = ::esp_wifi_scan_start(&conf, true); if (rc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); - return apRecords; + ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); + return apRecords; } uint16_t apCount; // Number of access points available. rc = ::esp_wifi_scan_get_ap_num(&apCount); - ESP_LOGD(LOG_TAG, "Count of found access points: %d", apCount); + if (rc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_scan_get_ap_num: %d", rc); + return apRecords; + } else { + ESP_LOGD(LOG_TAG, "Count of found access points: %d", apCount); + } - wifi_ap_record_t* list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); + wifi_ap_record_t* list = (wifi_ap_record_t*) malloc(sizeof(wifi_ap_record_t) * apCount); if (list == nullptr) { ESP_LOGE(LOG_TAG, "Failed to allocate memory"); return apRecords; @@ -550,18 +554,18 @@ std::vector WiFi::scan() { abort(); } - for (auto i=0; i rhs.m_rssi;}); + [](const WiFiAPRecord& lhs, const WiFiAPRecord& rhs){ return lhs.m_rssi > rhs.m_rssi; }); return apRecords; } // scan @@ -584,6 +588,7 @@ void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_au startAP(ssid, password, auth, 0, false, 4); } // startAP + /** * @brief Start being an access point. * @@ -649,9 +654,9 @@ void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_au * @param[in] wifiEventHandler The class that will be used to process events. */ void WiFi::setWifiEventHandler(WiFiEventHandler* wifiEventHandler) { - ESP_LOGD(LOG_TAG, ">> setWifiEventHandler: 0x%d", (uint32_t)wifiEventHandler); - this->m_pWifiEventHandler = wifiEventHandler; - ESP_LOGD(LOG_TAG, "<< setWifiEventHandler"); + ESP_LOGD(LOG_TAG, ">> setWifiEventHandler: 0x%d", (uint32_t) wifiEventHandler); + this->m_pWifiEventHandler = wifiEventHandler; + ESP_LOGD(LOG_TAG, "<< setWifiEventHandler"); } // setWifiEventHandler @@ -677,19 +682,16 @@ void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::st } // setIPInfo - void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { uint32_t new_ip; uint32_t new_gw; uint32_t new_netmask; - auto success = (bool)inet_pton(AF_INET, ip, &new_ip); + auto success = (bool) inet_pton(AF_INET, ip, &new_ip); success = success && inet_pton(AF_INET, gw, &new_gw); success = success && inet_pton(AF_INET, netmask, &new_netmask); - if(!success) { - return; - } + if (!success) return; setIPInfo(new_ip, new_gw, new_netmask); } // setIPInfo @@ -708,7 +710,7 @@ void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { this->gw = gw; this->netmask = netmask; - if(ip != 0 && gw != 0 && netmask != 0) { + if (ip != 0 && gw != 0 && netmask != 0) { tcpip_adapter_ip_info_t ipInfo; ipInfo.ip.addr = ip; ipInfo.gw.addr = gw; @@ -729,7 +731,7 @@ void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { */ std::string WiFiAPRecord::toString() { std::string auth; - switch(getAuthMode()) { + switch (getAuthMode()) { case WIFI_AUTH_OPEN: auth = "WIFI_AUTH_OPEN"; break; @@ -745,17 +747,18 @@ std::string WiFiAPRecord::toString() { case WIFI_AUTH_WPA_WPA2_PSK: auth = "WIFI_AUTH_WPA_WPA2_PSK"; break; - default: - auth = ""; - break; - } -// std::stringstream s; -// s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; - auto info_str = (char*) malloc(6 + 32 + 8 + 22 + 8 + 3 + 1); - sprintf(info_str, "ssid: %s, auth: %s, rssi: %d", m_ssid.c_str(), auth.c_str(), (int) m_rssi); - return std::string(std::move(info_str)); + default: + auth = ""; + break; + } +// std::stringstream s; +// s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; + auto info_str = (char*) malloc(6 + 32 + 8 + 22 + 8 + 3 + 1); + sprintf(info_str, "ssid: %s, auth: %s, rssi: %d", m_ssid.c_str(), auth.c_str(), (int) m_rssi); + return std::string(std::move(info_str)); } // toString + /* MDNS::MDNS() { esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); @@ -765,6 +768,7 @@ MDNS::MDNS() { } } + MDNS::~MDNS() { if (m_mdns_server != nullptr) { mdns_free(m_mdns_server); @@ -773,6 +777,7 @@ MDNS::~MDNS() { } */ + /** * @brief Define the service for mDNS. * @@ -783,25 +788,26 @@ MDNS::~MDNS() { */ /* void MDNS::serviceAdd(const std::string& service, const std::string& proto, uint16_t port) { - serviceAdd(service.c_str(), proto.c_str(), port); + serviceAdd(service.c_str(), proto.c_str(), port); } // serviceAdd void MDNS::serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance) { - serviceInstanceSet(service.c_str(), proto.c_str(), instance.c_str()); + serviceInstanceSet(service.c_str(), proto.c_str(), instance.c_str()); } // serviceInstanceSet void MDNS::servicePortSet(const std::string& service, const std::string& proto, uint16_t port) { - servicePortSet(service.c_str(), proto.c_str(), port); + servicePortSet(service.c_str(), proto.c_str(), port); } // servicePortSet void MDNS::serviceRemove(const std::string& service, const std::string& proto) { - serviceRemove(service.c_str(), proto.c_str()); + serviceRemove(service.c_str(), proto.c_str()); } // serviceRemove */ + /** * @brief Set the mDNS hostname. * @@ -810,10 +816,11 @@ void MDNS::serviceRemove(const std::string& service, const std::string& proto) { */ /* void MDNS::setHostname(const std::string& hostname) { - setHostname(hostname.c_str()); + setHostname(hostname.c_str()); } // setHostname */ + /** * @brief Set the mDNS instance. * @@ -822,10 +829,11 @@ void MDNS::setHostname(const std::string& hostname) { */ /* void MDNS::setInstance(const std::string& instance) { - setInstance(instance.c_str()); + setInstance(instance.c_str()); } // setInstance */ + /** * @brief Define the service for mDNS. * @@ -863,14 +871,15 @@ void MDNS::servicePortSet(const char* service, const char* proto, uint16_t port) void MDNS::serviceRemove(const char* service, const char* proto) { - esp_err_t errRc = ::mdns_service_remove(m_mdns_server, service, proto); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_service_remove: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } + esp_err_t errRc = ::mdns_service_remove(m_mdns_server, service, proto); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_remove: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } // serviceRemove - */ + + /** * @brief Set the mDNS hostname. * @@ -880,13 +889,14 @@ void MDNS::serviceRemove(const char* service, const char* proto) { /* void MDNS::setHostname(const char* hostname) { esp_err_t errRc = ::mdns_set_hostname(m_mdns_server,hostname); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_set_hostname: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_hostname: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } // setHostname */ + /** * @brief Set the mDNS instance. * @@ -895,10 +905,10 @@ void MDNS::setHostname(const char* hostname) { */ /* void MDNS::setInstance(const char* instance) { - esp_err_t errRc = ::mdns_set_instance(m_mdns_server, instance); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "mdns_set_instance: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - abort(); - } + esp_err_t errRc = ::mdns_set_instance(m_mdns_server, instance); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_instance: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } // setInstance */ diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index c1b6bedc..c0bfa07a 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -22,62 +22,65 @@ /* class MDNS { public: - MDNS(); - ~MDNS(); - void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); - void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); - void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); - void serviceRemove(const std::string& service, const std::string& proto); - void setHostname(const std::string& hostname); - void setInstance(const std::string& instance); - // If we the above functions with a basic char*, a copy would be created into an std::string, - // making the whole thing require twice as much processing power and speed - void serviceAdd(const char* service, const char* proto, uint16_t port); - void serviceInstanceSet(const char* service, const char* proto, const char* instance); - void servicePortSet(const char* service, const char* proto, uint16_t port); - void serviceRemove(const char* service, const char* proto); - void setHostname(const char* hostname); - void setInstance(const char* instance); + MDNS(); + ~MDNS(); + void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); + void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); + void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); + void serviceRemove(const std::string& service, const std::string& proto); + void setHostname(const std::string& hostname); + void setInstance(const std::string& instance); + // If we the above functions with a basic char*, a copy would be created into an std::string, + // making the whole thing require twice as much processing power and speed + void serviceAdd(const char* service, const char* proto, uint16_t port); + void serviceInstanceSet(const char* service, const char* proto, const char* instance); + void servicePortSet(const char* service, const char* proto, uint16_t port); + void serviceRemove(const char* service, const char* proto); + void setHostname(const char* hostname); + void setInstance(const char* instance); + private: - mdns_server_t *m_mdns_server = nullptr; + mdns_server_t* m_mdns_server = nullptr; + }; */ class WiFiAPRecord { public: - friend class WiFi; - - /** - * @brief Get the auth mode. - * @return The auth mode. - */ - wifi_auth_mode_t getAuthMode() { - return m_authMode; - } - - /** - * @brief Get the RSSI. - * @return the RSSI. - */ - int8_t getRSSI() { - return m_rssi; - } - - /** - * @brief Get the SSID. - * @return the SSID. - */ - std::string getSSID() { - return m_ssid; - } - - std::string toString(); + friend class WiFi; + + /** + * @brief Get the auth mode. + * @return The auth mode. + */ + wifi_auth_mode_t getAuthMode() { + return m_authMode; + } + + /** + * @brief Get the RSSI. + * @return the RSSI. + */ + int8_t getRSSI() { + return m_rssi; + } + + /** + * @brief Get the SSID. + * @return the SSID. + */ + std::string getSSID() { + return m_ssid; + } + + std::string toString(); private: - uint8_t m_bssid[6]; - int8_t m_rssi; - std::string m_ssid; - wifi_auth_mode_t m_authMode; + uint8_t m_bssid[6]; + int8_t m_rssi; + std::string m_ssid; + wifi_auth_mode_t m_authMode; + }; /** @@ -107,52 +110,53 @@ class WiFiAPRecord { */ class WiFi { private: - static esp_err_t eventHandler(void* ctx, system_event_t* event); - void init(); - uint32_t ip; - uint32_t gw; - uint32_t netmask; - WiFiEventHandler* m_pWifiEventHandler; - uint8_t m_dnsCount=0; - bool m_eventLoopStarted; - bool m_initCalled; - uint8_t m_apConnectionStatus; // ESP_OK = we are connected to an access point. Otherwise receives wifi_err_reason_t. + static esp_err_t eventHandler(void* ctx, system_event_t* event); + void init(); + uint32_t ip; + uint32_t gw; + uint32_t netmask; + WiFiEventHandler* m_pWifiEventHandler; + uint8_t m_dnsCount = 0; + bool m_eventLoopStarted; + bool m_initCalled; + uint8_t m_apConnectionStatus; // ESP_OK = we are connected to an access point. Otherwise receives wifi_err_reason_t. FreeRTOS::Semaphore m_connectFinished = FreeRTOS::Semaphore("ConnectFinished"); public: - WiFi(); - ~WiFi(); - void addDNSServer(const std::string& ip); - void addDNSServer(const char* ip); - void addDNSServer(ip_addr_t ip); - void setDNSServer(int numdns, const std::string& ip); - void setDNSServer(int numdns, const char* ip); - void setDNSServer(int numdns, ip_addr_t ip); - struct in_addr getHostByName(const std::string& hostName); - struct in_addr getHostByName(const char* hostName); - uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection=true, wifi_mode_t mode=WIFI_MODE_STA); - void dump(); - bool isConnectedToAP(); - static std::string getApMac(); - static tcpip_adapter_ip_info_t getApIpInfo(); - static std::string getApSSID(); + WiFi(); + ~WiFi(); + void addDNSServer(const std::string& ip); + void addDNSServer(const char* ip); + void addDNSServer(ip_addr_t ip); + void setDNSServer(int numdns, const std::string& ip); + void setDNSServer(int numdns, const char* ip); + void setDNSServer(int numdns, ip_addr_t ip); + struct in_addr getHostByName(const std::string& hostName); + struct in_addr getHostByName(const char* hostName); + uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection = true, wifi_mode_t mode = WIFI_MODE_STA); + void dump(); + bool isConnectedToAP(); + static std::string getApMac(); + static tcpip_adapter_ip_info_t getApIpInfo(); + static std::string getApSSID(); static std::string getApIp(); static std::string getApNetmask(); static std::string getApGateway(); - static std::string getMode(); - static tcpip_adapter_ip_info_t getStaIpInfo(); - static std::string getStaMac(); - static std::string getStaSSID(); + static std::string getMode(); + static tcpip_adapter_ip_info_t getStaIpInfo(); + static std::string getStaMac(); + static std::string getStaSSID(); static std::string getStaIp(); static std::string getStaNetmask(); static std::string getStaGateway(); - std::vector scan(); - void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth = WIFI_AUTH_OPEN); - void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection); - void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); - void setIPInfo(const char* ip, const char* gw, const char* netmask); - void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); - void setWifiEventHandler(WiFiEventHandler *wifiEventHandler); + std::vector scan(); + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth = WIFI_AUTH_OPEN); + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection); + void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); + void setIPInfo(const char* ip, const char* gw, const char* netmask); + void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); + void setWifiEventHandler(WiFiEventHandler* wifiEventHandler); + }; #endif /* MAIN_WIFI_H_ */ diff --git a/cpp_utils/WiFiEventHandler.cpp b/cpp_utils/WiFiEventHandler.cpp index c5a4fa2f..204a0661 100644 --- a/cpp_utils/WiFiEventHandler.cpp +++ b/cpp_utils/WiFiEventHandler.cpp @@ -24,15 +24,15 @@ static const char* LOG_TAG = "WiFiEventHandler"; * @return ESP_OK if the event was handled otherwise an error. */ esp_err_t WiFiEventHandler::eventHandler(void* ctx, system_event_t* event) { - ESP_LOGD(LOG_TAG, ">> eventHandler called: ctx=0x%x, event=0x%x", (uint32_t)ctx, (uint32_t)event); - WiFiEventHandler *pWiFiEventHandler = (WiFiEventHandler *)ctx; + ESP_LOGD(LOG_TAG, ">> eventHandler called: ctx=0x%x, event=0x%x", (uint32_t) ctx, (uint32_t) event); + WiFiEventHandler* pWiFiEventHandler = (WiFiEventHandler*) ctx; if (ctx == nullptr) { ESP_LOGD(LOG_TAG, "No context"); return ESP_OK; } esp_err_t rc = ESP_OK; - switch(event->event_id) { + switch (event->event_id) { case SYSTEM_EVENT_AP_START: { rc = pWiFiEventHandler->apStart(); break; @@ -95,15 +95,15 @@ esp_err_t WiFiEventHandler::eventHandler(void* ctx, system_event_t* event) { default: break; - } - - if (pWiFiEventHandler->m_nextHandler != nullptr) { - printf("Found a next handler\n"); - rc = eventHandler(pWiFiEventHandler->m_nextHandler, event); - } else { - //printf("NOT Found a next handler\n"); - } - return rc; + } + + if (pWiFiEventHandler->m_nextHandler != nullptr) { + ESP_LOGD(LOG_TAG, "Found a next handler"); + rc = eventHandler(pWiFiEventHandler->m_nextHandler, event); + } else { + //ESP_LOGD(LOG_TAG, "NOT Found a next handler"); + } + return rc; } // eventHandler @@ -134,8 +134,8 @@ system_event_cb_t WiFiEventHandler::getEventHandler() { * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::staGotIp(system_event_sta_got_ip_t info) { - ESP_LOGD(LOG_TAG, "default staGotIp"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default staGotIp"); + return ESP_OK; } // staGotIp @@ -145,8 +145,8 @@ esp_err_t WiFiEventHandler::staGotIp(system_event_sta_got_ip_t info) { * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStart() { - ESP_LOGD(LOG_TAG, "default apStart"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default apStart"); + return ESP_OK; } // apStart @@ -156,26 +156,26 @@ esp_err_t WiFiEventHandler::apStart() { * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStop() { - ESP_LOGD(LOG_TAG, "default apStop"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default apStop"); + return ESP_OK; } // apStop esp_err_t WiFiEventHandler::wifiReady() { - ESP_LOGD(LOG_TAG, "default wifiReady"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default wifiReady"); + return ESP_OK; } // wifiReady esp_err_t WiFiEventHandler::staStart() { - ESP_LOGD(LOG_TAG, "default staStart"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default staStart"); + return ESP_OK; } // staStart esp_err_t WiFiEventHandler::staStop() { - ESP_LOGD(LOG_TAG, "default staStop"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default staStop"); + return ESP_OK; } // staStop @@ -186,8 +186,8 @@ esp_err_t WiFiEventHandler::staStop() { * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::staConnected(system_event_sta_connected_t info) { - ESP_LOGD(LOG_TAG, "default staConnected"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default staConnected"); + return ESP_OK; } // staConnected @@ -198,8 +198,8 @@ esp_err_t WiFiEventHandler::staConnected(system_event_sta_connected_t info) { * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::staDisconnected(system_event_sta_disconnected_t info) { - ESP_LOGD(LOG_TAG, "default staDisconnected"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default staDisconnected"); + return ESP_OK; } // staDisconnected @@ -210,8 +210,8 @@ esp_err_t WiFiEventHandler::staDisconnected(system_event_sta_disconnected_t info * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStaConnected(system_event_ap_staconnected_t info) { - ESP_LOGD(LOG_TAG, "default apStaConnected"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default apStaConnected"); + return ESP_OK; } // apStaConnected @@ -222,8 +222,8 @@ esp_err_t WiFiEventHandler::apStaConnected(system_event_ap_staconnected_t info) * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStaDisconnected(system_event_ap_stadisconnected_t info) { - ESP_LOGD(LOG_TAG, "default apStaDisconnected"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default apStaDisconnected"); + return ESP_OK; } // apStaDisconnected @@ -234,8 +234,8 @@ esp_err_t WiFiEventHandler::apStaDisconnected(system_event_ap_stadisconnected_t * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::staScanDone(system_event_sta_scan_done_t info) { - ESP_LOGD(LOG_TAG, "default staScanDone"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default staScanDone"); + return ESP_OK; } // staScanDone @@ -246,8 +246,8 @@ esp_err_t WiFiEventHandler::staScanDone(system_event_sta_scan_done_t info) { * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::staAuthChange(system_event_sta_authmode_change_t info) { - ESP_LOGD(LOG_TAG, "default staAuthChange"); - return ESP_OK; + ESP_LOGD(LOG_TAG, "default staAuthChange"); + return ESP_OK; } // staAuthChange diff --git a/cpp_utils/WiFiEventHandler.h b/cpp_utils/WiFiEventHandler.h index 4dd46dba..9efb837f 100644 --- a/cpp_utils/WiFiEventHandler.h +++ b/cpp_utils/WiFiEventHandler.h @@ -117,9 +117,10 @@ class WiFiEventHandler { * Get the next WiFi event handler in the chain, if there is one. * @return The next WiFi event handler in the chain or nullptr if there is none. */ - WiFiEventHandler *getNextHandler() { + WiFiEventHandler* getNextHandler() { return m_nextHandler; } + /** * Set the next WiFi event handler in the chain. * @param [in] nextHandler The next WiFi event handler in the chain. @@ -132,6 +133,7 @@ class WiFiEventHandler { friend class WiFi; WiFiEventHandler *m_nextHandler; static esp_err_t eventHandler(void* ctx, system_event_t* event); + }; #endif /* MAIN_WIFIEVENTHANDLER_H_ */ diff --git a/cpp_utils/mainpage.dox b/cpp_utils/mainpage.dox index 3597471d..5701909e 100644 --- a/cpp_utils/mainpage.dox +++ b/cpp_utils/mainpage.dox @@ -5,4 +5,4 @@ * functions. * * The Github repository is [https://github.com/nkolban/esp32-snippets](https://github.com/nkolban/esp32-snippets). - */ \ No newline at end of file + */ From 5501cf62f196fc90f3e4742600fe7073d4cae8e7 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 16:28:03 -0600 Subject: [PATCH 328/381] Sanitize Neopixel logging --- cpp_utils/NeoPixelWiFiEventHandler.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cpp_utils/NeoPixelWiFiEventHandler.cpp b/cpp_utils/NeoPixelWiFiEventHandler.cpp index 1632a0f7..229fd7ff 100644 --- a/cpp_utils/NeoPixelWiFiEventHandler.cpp +++ b/cpp_utils/NeoPixelWiFiEventHandler.cpp @@ -5,8 +5,11 @@ * Author: kolban */ #include +#include #include "NeoPixelWiFiEventHandler.h" +static const char* LOG_TAG = "NeoPixelWiFiEventHandler"; + NeoPixelWiFiEventHandler::NeoPixelWiFiEventHandler(gpio_num_t gpioPin) { this->gpioPin = gpioPin; ws2812 = new WS2812(gpioPin, 8); @@ -17,42 +20,42 @@ NeoPixelWiFiEventHandler::~NeoPixelWiFiEventHandler() { } esp_err_t NeoPixelWiFiEventHandler::apStart() { - printf("XXX apStart\n"); + ESP_LOGD(LOG_TAG, "XXX apStart"); ws2812->setPixel(0, 0, 00, 64); ws2812->show(); return ESP_OK; } esp_err_t NeoPixelWiFiEventHandler::staConnected(system_event_sta_connected_t info) { - printf("XXX staConnected\n"); + ESP_LOGD(LOG_TAG, "XXX staConnected"); ws2812->setPixel(0, 57, 89, 66); ws2812->show(); return ESP_OK; } esp_err_t NeoPixelWiFiEventHandler::staDisconnected(system_event_sta_disconnected_t info) { - printf("XXX staDisconnected\n"); + ESP_LOGD(LOG_TAG, "XXX staDisconnected"); ws2812->setPixel(0, 64, 0, 0); ws2812->show(); return ESP_OK; } esp_err_t NeoPixelWiFiEventHandler::staStart() { - printf("XXX staStart\n"); + ESP_LOGD(LOG_TAG, "XXX staStart"); ws2812->setPixel(0, 64, 64, 0); ws2812->show(); return ESP_OK; } esp_err_t NeoPixelWiFiEventHandler::staGotIp(system_event_sta_got_ip_t info) { - printf("XXX staGotIp\n"); + ESP_LOGD(LOG_TAG, "XXX staGotIp"); ws2812->setPixel(0, 0, 64, 0); ws2812->show(); return ESP_OK; } esp_err_t NeoPixelWiFiEventHandler::wifiReady() { - printf("XXX wifiReady\n"); + ESP_LOGD(LOG_TAG, "XXX wifiReady"); ws2812->setPixel(0, 64, 64, 0); ws2812->show(); return ESP_OK; From 466332c6a6e7346e8ac9127a7e6b459a193b6a8f Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 16:32:08 -0600 Subject: [PATCH 329/381] BT: uint16_t numHandles --- cpp_utils/BLEService.cpp | 6 +++--- cpp_utils/BLEService.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 340ea560..1f293aad 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -35,7 +35,7 @@ static const char* LOG_TAG = "BLEService"; // Tag for logging. * @param [in] uuid The UUID of the service. * @param [in] numHandles The maximum number of handles associated with the service. */ -BLEService::BLEService(const char* uuid, uint32_t numHandles) : BLEService(BLEUUID(uuid), numHandles) { +BLEService::BLEService(const char* uuid, uint16_t numHandles) : BLEService(BLEUUID(uuid), numHandles) { } @@ -44,7 +44,7 @@ BLEService::BLEService(const char* uuid, uint32_t numHandles) : BLEService(BLEUU * @param [in] uuid The UUID of the service. * @param [in] numHandles The maximum number of handles associated with the service. */ -BLEService::BLEService(BLEUUID uuid, uint32_t numHandles) { +BLEService::BLEService(BLEUUID uuid, uint16_t numHandles) { m_uuid = uuid; m_handle = NULL_HANDLE; m_pServer = nullptr; @@ -264,7 +264,7 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) { return createCharacteristic(BLEUUID(uuid), properties); } - + /** * @brief Create a new BLE Characteristic associated with this service. diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 93b4b2c6..de4cc992 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -27,7 +27,7 @@ class BLECharacteristicMap { void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid); void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid); void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); - BLECharacteristic* getByUUID(const char* uuid); + BLECharacteristic* getByUUID(const char* uuid); BLECharacteristic* getByUUID(BLEUUID uuid); BLECharacteristic* getByHandle(uint16_t handle); BLECharacteristic* getFirst(); @@ -69,8 +69,8 @@ class BLEService { uint8_t m_id = 0; private: - BLEService(const char* uuid, uint32_t numHandles); - BLEService(BLEUUID uuid, uint32_t numHandles); + BLEService(const char* uuid, uint16_t numHandles); + BLEService(BLEUUID uuid, uint16_t numHandles); friend class BLEServer; friend class BLEServiceMap; friend class BLEDescriptor; @@ -88,7 +88,7 @@ class BLEService { FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); FreeRTOS::Semaphore m_semaphoreStopEvt = FreeRTOS::Semaphore("StopEvt"); - uint32_t m_numHandles; + uint16_t m_numHandles; BLECharacteristic* getLastCreatedCharacteristic(); void handleGATTServerEvent( From 814c3244a83181a50b9ebb1dc1907130f321fd6c Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 16:42:45 -0600 Subject: [PATCH 330/381] FreeRTOS: Check if value is pdTRUE --- cpp_utils/FreeRTOS.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 1ae01d73..77c6921e 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -134,7 +134,7 @@ void FreeRTOS::Semaphore::give() { xSemaphoreGive(m_semaphore); } // #ifdef ARDUINO_ARCH_ESP32 -// FreeRTOS::sleep(10); +// FreeRTOS::sleep(10); // #endif m_owner = std::string(""); @@ -178,7 +178,7 @@ bool FreeRTOS::Semaphore::take(std::string owner) if (m_usePthreads) { pthread_mutex_lock(&m_pthread_mutex); } else { - rc = ::xSemaphoreTake(m_semaphore, portMAX_DELAY); + rc = ::xSemaphoreTake(m_semaphore, portMAX_DELAY) == pdTRUE; } m_owner = owner; if (rc) { @@ -204,7 +204,7 @@ bool FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { if (m_usePthreads) { assert(false); // We apparently don't have a timed wait for pthreads. } else { - rc = ::xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); + rc = ::xSemaphoreTake(m_semaphore, timeoutMs / portTICK_PERIOD_MS) == pdTRUE; } m_owner = owner; if (rc) { From a3b7fe373c1dea042473bed295e4a88fc8443230 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 16:49:06 -0600 Subject: [PATCH 331/381] Return bool from Ringbuffer::send --- cpp_utils/FreeRTOS.cpp | 4 ++-- cpp_utils/FreeRTOS.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 1ae01d73..2d65bbcf 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -279,8 +279,8 @@ void Ringbuffer::returnItem(void* item) { * @param [in] wait How long to wait before giving up. The default is to wait indefinitely. * @return */ -uint32_t Ringbuffer::send(void* data, size_t length, TickType_t wait) { - return ::xRingbufferSend(m_handle, data, length, wait); +bool Ringbuffer::send(void* data, size_t length, TickType_t wait) { + return ::xRingbufferSend(m_handle, data, length, wait) == pdTRUE; } // send diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index ab0e83d8..45013419 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -62,7 +62,7 @@ class Ringbuffer { void* receive(size_t* size, TickType_t wait = portMAX_DELAY); void returnItem(void* item); - uint32_t send(void* data, size_t length, TickType_t wait = portMAX_DELAY); + bool send(void* data, size_t length, TickType_t wait = portMAX_DELAY); private: RingbufHandle_t m_handle; }; From 409a178be94b254e2c35d6cc5746b96e024f44b5 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 16:50:50 -0600 Subject: [PATCH 332/381] FreeRTOS: Stack size is uint32_t --- cpp_utils/FreeRTOS.cpp | 2 +- cpp_utils/FreeRTOS.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 1ae01d73..c9919ea0 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -32,7 +32,7 @@ void FreeRTOS::sleep(uint32_t ms) { * @param[in] param An optional parameter to be passed to the started task. * @param[in] stackSize An optional paremeter supplying the size of the stack in which to run the task. */ -void FreeRTOS::startTask(void task(void*), std::string taskName, void *param, int stackSize) { +void FreeRTOS::startTask(void task(void*), std::string taskName, void *param, uint32_t stackSize) { ::xTaskCreate(task, taskName.data(), stackSize, param, 5, NULL); } // startTask diff --git a/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index ab0e83d8..e13062be 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -23,7 +23,7 @@ class FreeRTOS { public: static void sleep(uint32_t ms); - static void startTask(void task(void *), std::string taskName, void *param=nullptr, int stackSize = 2048); + static void startTask(void task(void *), std::string taskName, void *param=nullptr, uint32_t stackSize = 2048); static void deleteTask(TaskHandle_t pTask = nullptr); static uint32_t getTimeSinceStart(); From 009d00336527459ed31f408cba983072fa7c02db Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 17:11:52 -0600 Subject: [PATCH 333/381] GPIO: Add checks for bool returns --- cpp_utils/GPIO.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/GPIO.cpp b/cpp_utils/GPIO.cpp index 23cf6fb7..38bd19a4 100644 --- a/cpp_utils/GPIO.cpp +++ b/cpp_utils/GPIO.cpp @@ -124,7 +124,7 @@ void ESP32CPP::GPIO::low(gpio_num_t pin) { * @return True if the pin is high, false if the pin is low. */ bool ESP32CPP::GPIO::read(gpio_num_t pin) { - return ::gpio_get_level(pin); + return ::gpio_get_level(pin) == 1; } // read @@ -189,7 +189,7 @@ void ESP32CPP::GPIO::setOutput(gpio_num_t pin) { */ void ESP32CPP::GPIO::write(gpio_num_t pin, bool value) { //ESP_LOGD(LOG_TAG, ">> write: pin: %d, value: %d", pin, value); - esp_err_t errRc = ::gpio_set_level(pin, value); + esp_err_t errRc = ::gpio_set_level(pin, value ? 1 : 0); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< gpio_set_level: pin=%d, rc=%d %s", pin, errRc, GeneralUtils::errorToString(errRc)); } From 18e77c7abeef0536d6d728763e6c27fa49ae8369 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 17:13:27 -0600 Subject: [PATCH 334/381] I2C: Configurable pullups --- cpp_utils/I2C.cpp | 6 +++--- cpp_utils/I2C.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index dfcfeff0..97d44141 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -95,7 +95,7 @@ uint8_t I2C::getAddress() const * @param [in] sclPin The pin to use for SCL clock. * @return N/A. */ -void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin, uint32_t clockSpeed, i2c_port_t portNum) { +void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin, uint32_t clockSpeed, i2c_port_t portNum, bool pullup) { ESP_LOGD(LOG_TAG, ">> I2c::init. address=%d, sda=%d, scl=%d, clockSpeed=%d, portNum=%d", address, sdaPin, sclPin, clockSpeed, portNum); assert(portNum < I2C_NUM_MAX); m_portNum = portNum; @@ -107,8 +107,8 @@ void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin, uint32_t c conf.mode = I2C_MODE_MASTER; conf.sda_io_num = sdaPin; conf.scl_io_num = sclPin; - conf.sda_pullup_en = GPIO_PULLUP_ENABLE; - conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.sda_pullup_en = pullup ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; + conf.scl_pullup_en = pullup ? GPIO_PULLUP_ENABLE: GPIO_PULLUP_DISABLE; conf.master.clk_speed = clockSpeed; esp_err_t errRc = ::i2c_param_config(m_portNum, &conf); if (errRc != ESP_OK) { diff --git a/cpp_utils/I2C.h b/cpp_utils/I2C.h index 9886d63a..62872e42 100644 --- a/cpp_utils/I2C.h +++ b/cpp_utils/I2C.h @@ -45,7 +45,7 @@ class I2C { void beginTransaction(); void endTransaction(); uint8_t getAddress() const; - void init(uint8_t address, gpio_num_t sdaPin = DEFAULT_SDA_PIN, gpio_num_t sclPin = DEFAULT_CLK_PIN, uint32_t clkSpeed = DEFAULT_CLK_SPEED, i2c_port_t portNum = I2C_NUM_0); + void init(uint8_t address, gpio_num_t sdaPin = DEFAULT_SDA_PIN, gpio_num_t sclPin = DEFAULT_CLK_PIN, uint32_t clkSpeed = DEFAULT_CLK_SPEED, i2c_port_t portNum = I2C_NUM_0, bool pullup = true); void read(uint8_t* bytes, size_t length, bool ack=true); void read(uint8_t* byte, bool ack=true); void scan(); From 7843cebaf4980b58c4dcefc5922020f0aec67f70 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 17:14:50 -0600 Subject: [PATCH 335/381] Memory.cpp: variable size fixes --- cpp_utils/Memory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/Memory.cpp b/cpp_utils/Memory.cpp index e3be59db..a1ce04d0 100644 --- a/cpp_utils/Memory.cpp +++ b/cpp_utils/Memory.cpp @@ -47,7 +47,7 @@ size_t Memory::m_lastHeapSize = 0; /* STATIC */ void Memory::dumpHeapChange(std::string tag) { size_t currentUsage = heap_caps_get_free_size(MALLOC_CAP_8BIT); - int diff = currentUsage - m_lastHeapSize; + size_t diff = currentUsage - m_lastHeapSize; ESP_LOGD(LOG_TAG, "%s: Heap changed by %d bytes (%d to %d)", tag.c_str(), diff, m_lastHeapSize, currentUsage); m_lastHeapSize = currentUsage; } // dumpHeapChange @@ -69,7 +69,7 @@ size_t Memory::m_lastHeapSize = 0; return; } esp_log_level_set("*", ESP_LOG_NONE); - size_t count = heap_trace_get_count(); + size_t count = (size_t) heap_trace_get_count(); heap_trace_record_t record; printf(">>> dumpRanges\n"); for (size_t i=0; i Date: Sun, 14 Oct 2018 17:24:19 -0600 Subject: [PATCH 336/381] PubSub: pass pdTRUE instead of true --- cpp_utils/PubSubClient.cpp | 7 +++---- cpp_utils/PubSubClient.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cpp_utils/PubSubClient.cpp b/cpp_utils/PubSubClient.cpp index 8aaca5eb..3d1c365d 100644 --- a/cpp_utils/PubSubClient.cpp +++ b/cpp_utils/PubSubClient.cpp @@ -190,10 +190,10 @@ void PubSubClient::setup(void) { keepAliveTimer = new FreeRTOSTimer((char*) "keepAliveTimer", - (MQTT_KEEPALIVE * 1000) / portTICK_PERIOD_MS, true, this, + (MQTT_KEEPALIVE * 1000) / portTICK_PERIOD_MS, pdTRUE, this, keepAliveTimerMapper); timeoutTimer = new FreeRTOSTimer((char*) "timeoutTimer", - (MQTT_KEEPALIVE * 1000) / portTICK_PERIOD_MS, true, this, + (MQTT_KEEPALIVE * 1000) / portTICK_PERIOD_MS, pdTRUE, this, timeoutTimerMapper); m_task = new PubSubClientTask("PubSubClientTask"); } // setup @@ -404,8 +404,7 @@ bool PubSubClient::connect(){ * @param N/A. * @return Number of received bytes. */ -uint16_t PubSubClient::readPacket() { - +size_t PubSubClient::readPacket() { size_t res = _client->receive(buffer, MQTT_MAX_PACKET_SIZE); if (res > MQTT_MAX_PACKET_SIZE) { diff --git a/cpp_utils/PubSubClient.h b/cpp_utils/PubSubClient.h index 8e56ca59..fe82af03 100644 --- a/cpp_utils/PubSubClient.h +++ b/cpp_utils/PubSubClient.h @@ -160,7 +160,7 @@ class PubSubClient { MQTT_CALLBACK_SIGNATURE; void setup (void); - uint16_t readPacket(); + size_t readPacket(); bool write (uint8_t header, uint8_t* buf, uint16_t length); uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); void parseData (mqtt_message* msg, uint16_t len); From db61e20ae6871fab172fd31cfc6348e21b212cbb Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 17:25:31 -0600 Subject: [PATCH 337/381] Fixed ternary operator spacing --- cpp_utils/I2C.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index 97d44141..dbf2a3da 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -108,7 +108,7 @@ void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin, uint32_t c conf.sda_io_num = sdaPin; conf.scl_io_num = sclPin; conf.sda_pullup_en = pullup ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; - conf.scl_pullup_en = pullup ? GPIO_PULLUP_ENABLE: GPIO_PULLUP_DISABLE; + conf.scl_pullup_en = pullup ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; conf.master.clk_speed = clockSpeed; esp_err_t errRc = ::i2c_param_config(m_portNum, &conf); if (errRc != ESP_OK) { From 61bb6ed15cb8414a7d13747478aefc346446aba6 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 17:28:49 -0600 Subject: [PATCH 338/381] PWM: idle level is bool --- cpp_utils/PWM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/PWM.cpp b/cpp_utils/PWM.cpp index f7119c4a..0dbf73fe 100644 --- a/cpp_utils/PWM.cpp +++ b/cpp_utils/PWM.cpp @@ -150,5 +150,5 @@ void PWM::setFrequency(uint32_t freq) { * @return N/A. */ void PWM::stop(bool idleLevel) { - ESP_ERROR_CHECK(::ledc_stop(LEDC_HIGH_SPEED_MODE, m_channel, idleLevel)); + ESP_ERROR_CHECK(::ledc_stop(LEDC_HIGH_SPEED_MODE, m_channel, idleLevel ? 1 : 0)); } // stop From 871a946968168c24326d795af5568af0f93dcbf9 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 17:31:41 -0600 Subject: [PATCH 339/381] WebSocket: Opcodes are uint8_t --- cpp_utils/WebSocket.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp index e7fc05fb..99172ac6 100644 --- a/cpp_utils/WebSocket.cpp +++ b/cpp_utils/WebSocket.cpp @@ -21,12 +21,12 @@ extern "C" { static const char* LOG_TAG = "WebSocket"; // WebSocket op codes as found in a WebSocket frame. -static const int OPCODE_CONTINUE = 0x00; -static const int OPCODE_TEXT = 0x01; -static const int OPCODE_BINARY = 0x02; -static const int OPCODE_CLOSE = 0x08; -static const int OPCODE_PING = 0x09; -static const int OPCODE_PONG = 0x0a; +static const uint8_t OPCODE_CONTINUE = 0x00; +static const uint8_t OPCODE_TEXT = 0x01; +static const uint8_t OPCODE_BINARY = 0x02; +static const uint8_t OPCODE_CLOSE = 0x08; +static const uint8_t OPCODE_PING = 0x09; +static const uint8_t OPCODE_PONG = 0x0a; // Structure definition for the WebSocket frame. @@ -119,7 +119,7 @@ class WebSocketReader: public Task { break; } ESP_LOGD("WebSocketReader", "Waiting on socket data for socket %s", peerSocket.toString().c_str()); - int length = peerSocket.receive((uint8_t*)&frame, sizeof(frame), true); // Read exact + size_t length = peerSocket.receive((uint8_t*)&frame, sizeof(frame), true); // Read exact if (length != sizeof(frame)) { ESP_LOGD(LOG_TAG, "Socket read error"); pWebSocket->close(); @@ -476,7 +476,7 @@ WebSocketInputStreambuf::int_type WebSocketInputStreambuf::underflow() { // We wish to refill the buffer. We want to read data from the socket. We want to read either // the size of the buffer to fill it or the maximum number of bytes remaining to be read. // We will choose which ever is smaller as the number of bytes to read into the buffer. - int remainingBytes = getRecordSize()-m_sizeRead; + size_t remainingBytes = getRecordSize()-m_sizeRead; size_t sizeToRead; if (remainingBytes < m_bufferSize) { sizeToRead = remainingBytes; @@ -485,7 +485,7 @@ WebSocketInputStreambuf::int_type WebSocketInputStreambuf::underflow() { } ESP_LOGD("WebSocketInputRecordStreambuf", "- getting next buffer of data; size request: %d", sizeToRead); - int bytesRead = m_socket.receive((uint8_t*)m_buffer, sizeToRead, true); + size_t bytesRead = m_socket.receive((uint8_t*)m_buffer, sizeToRead, true); if (bytesRead == 0) { ESP_LOGD("WebSocketInputRecordStreambuf", "<< underflow: Read 0 bytes"); return EOF; From e215f9d70730bef3336f103b73fb8eb12ff90611 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 17:39:54 -0600 Subject: [PATCH 340/381] PCF8574 & PCF8575: i2c on heap --- cpp_utils/PCF8574.cpp | 18 ++++++++++-------- cpp_utils/PCF8574.h | 2 +- cpp_utils/PCF8575.cpp | 22 ++++++++++++---------- cpp_utils/PCF8575.h | 4 ++-- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/cpp_utils/PCF8574.cpp b/cpp_utils/PCF8574.cpp index 4d850a8b..3f56807c 100644 --- a/cpp_utils/PCF8574.cpp +++ b/cpp_utils/PCF8574.cpp @@ -18,7 +18,8 @@ * @param [in] address The %I2C address of the device on the %I2C bus. */ PCF8574::PCF8574(uint8_t address) { - i2c.setAddress(address); + i2c = new I2C(); + i2c->setAddress(address); lastWrite = 0; } @@ -26,6 +27,7 @@ PCF8574::PCF8574(uint8_t address) { * @brief Class instance destructor. */ PCF8574::~PCF8574() { + delete i2c; } @@ -35,9 +37,9 @@ PCF8574::~PCF8574() { */ uint8_t PCF8574::read() { uint8_t value; - i2c.beginTransaction(); - i2c.read(&value,true); - i2c.endTransaction(); + i2c->beginTransaction(); + i2c->read(&value,true); + i2c->endTransaction(); return value; } // read @@ -66,9 +68,9 @@ void PCF8574::write(uint8_t value) { if (invert) { value = ~value; } - i2c.beginTransaction(); - i2c.write(value, true); - i2c.endTransaction(); + i2c->beginTransaction(); + i2c->write(value, true); + i2c->endTransaction(); lastWrite = value; } // write @@ -117,5 +119,5 @@ void PCF8574::setInvert(bool value) { * @param [in] clkPin The pin to use for the %I2C CLK functions. */ void PCF8574::init(gpio_num_t sdaPin, gpio_num_t clkPin) { - i2c.init(0, sdaPin, clkPin); + i2c->init(0, sdaPin, clkPin); } // init diff --git a/cpp_utils/PCF8574.h b/cpp_utils/PCF8574.h index 15b11da9..777d17f8 100644 --- a/cpp_utils/PCF8574.h +++ b/cpp_utils/PCF8574.h @@ -29,7 +29,7 @@ class PCF8574 { void writeBit(uint8_t bit, bool value); private: - I2C i2c = I2C(); + I2C* i2c; uint8_t lastWrite; bool invert = false; }; diff --git a/cpp_utils/PCF8575.cpp b/cpp_utils/PCF8575.cpp index 3714abb1..87435654 100644 --- a/cpp_utils/PCF8575.cpp +++ b/cpp_utils/PCF8575.cpp @@ -18,7 +18,8 @@ * @param [in] address The %I2C address of the device on the %I2C bus. */ PCF8575::PCF8575(uint8_t address) { - i2c.setAddress(address); + i2c = new I2C(); + i2c->setAddress(address); m_lastWrite = 0; } @@ -26,6 +27,7 @@ PCF8575::PCF8575(uint8_t address) { * @brief Class instance destructor. */ PCF8575::~PCF8575() { + delete i2c; } @@ -35,10 +37,10 @@ PCF8575::~PCF8575() { */ uint16_t PCF8575::read() { uint16_t value; - i2c.beginTransaction(); - i2c.read((uint8_t*)&value,true); - i2c.read(((uint8_t*)&value) + 1,true); - i2c.endTransaction(); + i2c->beginTransaction(); + i2c->read((uint8_t*)&value,true); + i2c->read(((uint8_t*)&value) + 1,true); + i2c->endTransaction(); return value; } // read @@ -67,10 +69,10 @@ void PCF8575::write(uint16_t value) { if (invert) { value = ~value; } - i2c.beginTransaction(); - i2c.write(value & 0xff, true); - i2c.write((value >> 8) & 0xff, true); - i2c.endTransaction(); + i2c->beginTransaction(); + i2c->write(value & 0xff, true); + i2c->write((value >> 8) & 0xff, true); + i2c->endTransaction(); m_lastWrite = value; } // write @@ -119,5 +121,5 @@ void PCF8575::setInvert(bool value) { * @param [in] clkPin The pin to use for the %I2C CLK functions. */ void PCF8575::init(gpio_num_t sdaPin, gpio_num_t clkPin) { - i2c.init(0, sdaPin, clkPin); + i2c->init(0, sdaPin, clkPin); } // init diff --git a/cpp_utils/PCF8575.h b/cpp_utils/PCF8575.h index 467fae6c..c37eed8b 100644 --- a/cpp_utils/PCF8575.h +++ b/cpp_utils/PCF8575.h @@ -29,8 +29,8 @@ class PCF8575 { void writeBit(uint16_t bit, bool value); private: - I2C i2c = I2C(); - uint8_t m_lastWrite; + I2C* i2c; + uint16_t m_lastWrite; bool invert = false; }; From bd24fabae4e76e58d660225a3fdfa9f27cfb1639 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 17:41:31 -0600 Subject: [PATCH 341/381] Release memory from CLASSIC_BT when not enabled --- cpp_utils/BLEDevice.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 157074d4..dcba79eb 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -354,13 +354,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; } #ifndef CLASSIC_BT_ENABLED - // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue - errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); - //errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); #else errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); if (errRc != ESP_OK) { From b50dec3621f6e4acb02140b3da078542f35fb6c9 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 18:18:50 -0600 Subject: [PATCH 342/381] Fix array dereferencing --- cpp_utils/BLEAdvertising.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index ea3825ac..d4fd6332 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -300,7 +300,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; // 0x07 - addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid128, 16)); + addData(std::string(cdata, 2) + std::string((char*) uuid.getNative()->uuid.uuid128, 16)); break; } From 2dcd6aa02cd353f1c5e0efbef4c00cb7b4989981 Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 18:34:12 -0600 Subject: [PATCH 343/381] Fix HttpResponse line mangling --- cpp_utils/HttpResponse.cpp | 4 ++-- cpp_utils/HttpResponse.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp index c6e9683c..46f3e9bd 100644 --- a/cpp_utils/HttpResponse.cpp +++ b/cpp_utils/HttpResponse.cpp @@ -26,9 +26,9 @@ const int HttpResponse::HTTP_STATUS_NOT_IMPLEMENTED = 501; const int HttpResponse::HTTP_STATUS_SERVICE_UNAVAILABLE = 503; static std::string lineTerminator = "\r\n"; +HttpResponse::HttpResponse(HttpRequest* request) { m_request = request; m_status = 200; -HttpResponse::HttpResponse(HttpRequest* request) { m_headerCommitted = false; // We have not yet sent a header. } @@ -125,7 +125,7 @@ void HttpResponse::sendFile(std::string fileName, size_t bufSize) { ESP_LOGI(LOG_TAG, "Opening file: %s", fileName.c_str()); std::ifstream ifStream; ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); // Attempt to open the file for reading. - + // If we failed to open the requested file, then it probably didn't exist so return a not found. if (!ifStream.is_open()) { ESP_LOGE(LOG_TAG, "Unable to open file %s for reading", fileName.c_str()); diff --git a/cpp_utils/HttpResponse.h b/cpp_utils/HttpResponse.h index cc7be388..5dfaa976 100644 --- a/cpp_utils/HttpResponse.h +++ b/cpp_utils/HttpResponse.h @@ -39,6 +39,7 @@ class HttpResponse { void sendData(uint8_t* pData, size_t size); // Send data to the client. void setStatus(int status, std::string message); // Set the response status. void sendFile(std::string fileName, size_t bufSize = 4 * 1024); // Send file contents if exists. + private: bool m_headerCommitted; // Has the header been sent? HttpRequest* m_request; // The request associated with this response. From 4cfef4d077945ee3429de6d7416f4635432e35ea Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 18:34:39 -0600 Subject: [PATCH 344/381] Change return type of bitSize() to uint8_t --- cpp_utils/BLEUUID.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index 5fb7795b..e81dd130 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -24,7 +24,7 @@ class BLEUUID { BLEUUID(uint8_t* pData, size_t size, bool msbFirst); BLEUUID(esp_gatt_id_t gattId); BLEUUID(); - int bitSize(); // Get the number of bits in this uuid. + uint8_t bitSize(); // Get the number of bits in this uuid. bool equals(BLEUUID uuid); esp_bt_uuid_t* getNative(); BLEUUID to128(); From d14071a74898ea98d224cd862de04507f8c728de Mon Sep 17 00:00:00 2001 From: toxuin Date: Sun, 14 Oct 2018 18:35:03 -0600 Subject: [PATCH 345/381] Fix line mangling on merge Force gcc to ignore unused-but-set-parameter Fix line misplacement by merge Cast string data to uint8_t pointer Fix line mangling on merge Fix line mangling on merge Fix line mangling on merge --- cpp_utils/BLECharacteristicMap.cpp | 14 ++++---------- cpp_utils/BLERemoteDescriptor.cpp | 2 +- cpp_utils/BLERemoteService.cpp | 1 + cpp_utils/MFRC522.cpp | 9 ++++----- cpp_utils/SockServ.cpp | 30 +++++++++++++++--------------- cpp_utils/System.cpp | 10 ++++++---- cpp_utils/WS2812.cpp | 2 +- 7 files changed, 32 insertions(+), 36 deletions(-) diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index 8e7d097d..d73aae99 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -81,12 +81,9 @@ BLECharacteristic* BLECharacteristicMap::getNext() { * @param [in] gatts_if * @param [in] param */ -void BLECharacteristicMap::handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param) { +void BLECharacteristicMap::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every Service we have. - for (auto &myPair : m_uuidMap) { + for (auto& myPair : m_uuidMap) { myPair.first->handleGATTServerEvent(event, gatts_if, param); } } // handleGATTServerEvent @@ -98,8 +95,7 @@ void BLECharacteristicMap::handleGATTServerEvent( * @param [in] characteristic The characteristic to cache. * @return N/A. */ -void BLECharacteristicMap::setByHandle(uint16_t handle, - BLECharacteristic* characteristic) { +void BLECharacteristicMap::setByHandle(uint16_t handle, BLECharacteristic* characteristic) { m_handleMap.insert(std::pair(handle, characteristic)); } // setByHandle @@ -110,9 +106,7 @@ void BLECharacteristicMap::setByHandle(uint16_t handle, * @param [in] characteristic The characteristic to cache. * @return N/A. */ -void BLECharacteristicMap::setByUUID( - BLEUUID uuid) { - BLECharacteristic* pCharacteristic, +void BLECharacteristicMap::setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid) { m_uuidMap.insert(std::pair(pCharacteristic, uuid.toString())); } // setByUUID diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index 4947976b..ea7eeebd 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -161,7 +161,7 @@ void BLERemoteDescriptor::writeValue(uint8_t* data, size_t length, bool response * @param [in] response True if we expect a response. */ void BLERemoteDescriptor::writeValue(std::string newValue, bool response) { - writeValue(newValue.data(), newValue.length(), response); + writeValue((uint8_t*) newValue.data(), newValue.length(), response); } // writeValue diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index a1bf06e7..d632cff5 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -233,6 +233,7 @@ std::map* BLERemoteService::getCharacteri * @brief This function is designed to get characteristics map when we have multiple characteristics with the same UUID */ void BLERemoteService::getCharacteristics(std::map* pCharacteristicMap) { +#pragma GCC diagnostic ignored "-Wunused-but-set-parameter" pCharacteristicMap = &m_characteristicMapByHandle; } // Get the characteristics map. diff --git a/cpp_utils/MFRC522.cpp b/cpp_utils/MFRC522.cpp index 6feb57c1..4082d4ac 100644 --- a/cpp_utils/MFRC522.cpp +++ b/cpp_utils/MFRC522.cpp @@ -146,7 +146,7 @@ void MFRC522::PCD_ClearRegisterBitMask(PCD_Register reg, byte mask) { * @param result Out: Pointer to result buffer. Result is written to result[0..1], low byte first. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_CalculateCRC(byte *data, byte length, byte *result) { +MFRC522::StatusCode MFRC522::PCD_CalculateCRC(byte* data, byte length, byte* result) { PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. PCD_WriteRegister(DivIrqReg, 0x04); // Clear the CRCIRq interrupt request bit PCD_WriteRegister(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization @@ -160,7 +160,6 @@ MFRC522::StatusCode MFRC522::PCD_CalculateCRC(byte *data, byte length, byte *res for (uint16_t i = 5000; i > 0; i--) { // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved byte n = PCD_ReadRegister(DivIrqReg); - if (n & 0x04) { // CRCIRq bit set - calculation done if (n & 0x04) { // CRCIRq bit set - calculation done PCD_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. // Transfer the result from the registers to the result buffer @@ -403,8 +402,8 @@ bool MFRC522::PCD_PerformSelfTest() { * @param checkCRC In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. * @return STATUS_OK on success, STATUS_??? otherwise. */ - byte waitIRq = 0x30; // RxIRq and IdleIRq MFRC522::StatusCode MFRC522::PCD_TransceiveData(byte* sendData, byte sendLen, byte* backData, byte* backLen, byte* validBits, byte rxAlign, bool checkCRC) { + byte waitIRq = 0x30; // RxIRq and IdleIRq return PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, sendData, sendLen, backData, backLen, validBits, rxAlign, checkCRC); } // End PCD_TransceiveData() @@ -426,8 +425,8 @@ MFRC522::StatusCode MFRC522::PCD_TransceiveData(byte* sendData, byte sendLen, by */ MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC(byte command, byte waitIRq, byte* sendData, byte sendLen, byte* backData, byte* backLen, byte* validBits, byte rxAlign, bool checkCRC) { // Prepare values for BitFramingReg - byte bitFraming = (rxAlign << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] byte txLastBits = validBits ? *validBits : (byte) 0; + byte bitFraming = (rxAlign << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. PCD_WriteRegister(ComIrqReg, 0x7F); // Clear all seven interrupt request bits @@ -808,8 +807,8 @@ MFRC522::StatusCode MFRC522::PICC_HaltA() { * @param uid Pointer to Uid struct. The first 4 bytes of the UID is used. * @return STATUS_OK on success, STATUS_??? otherwise. Probably STATUS_TIMEOUT if you supply the wrong key. */ - byte waitIRq = 0x10; // IdleIRq MFRC522::StatusCode MFRC522::PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key* key, Uid* uid) { + byte waitIRq = 0x10; // IdleIRq // Build command buffer byte sendData[12]; diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 32b494ab..38ecc823 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -30,7 +30,6 @@ static const char* LOG_TAG = "SockServ"; */ SockServ::SockServ(uint16_t port) : SockServ() { this->m_port = port; - } // SockServ @@ -62,8 +61,8 @@ SockServ::~SockServ() { */ /* static */ void SockServ::acceptTask(void* data) { SockServ* pSockServ = (SockServ*) data; - try { - while (true) { + while (true) { + try { ESP_LOGD(LOG_TAG, "Waiting on accept"); Socket tempSock = pSockServ->m_serverSocket.accept(); if (!tempSock.isValid()) continue; @@ -71,11 +70,12 @@ SockServ::~SockServ() { pSockServ->m_clientSet.insert(tempSock); xQueueSendToBack(pSockServ->m_acceptQueue, &tempSock, portMAX_DELAY); pSockServ->m_clientSemaphore.give(); + } catch (std::exception e) { + ESP_LOGD(LOG_TAG, "acceptTask ending"); + pSockServ->m_clientSemaphore.give(); // Wake up any waiting clients. + FreeRTOS::deleteTask(); + break; } - } catch(std::exception e) { - ESP_LOGD(LOG_TAG, "acceptTask ending"); - pSockServ->m_clientSemaphore.give(); // Wake up any waiting clients. - FreeRTOS::deleteTask(); } } // acceptTask @@ -114,12 +114,12 @@ bool SockServ::getSSL() { * @return The amount of data returned or 0 if there was an error. */ size_t SockServ::receiveData(Socket s, void* pData, size_t maxData) { - int rc = s.receive((uint8_t*) pData, maxData); + size_t rc = s.receive((uint8_t*) pData, maxData); if (rc == -1) { ESP_LOGE(LOG_TAG, "recv(): %s", strerror(errno)); return 0; } - return (size_t) rc; + return rc; } // receiveData @@ -190,7 +190,7 @@ Socket SockServ::waitForData(std::set& socketSet) { fd_set readSet; int maxFd = -1; - for ( auto it = socketSet.begin(); it != socketSet.end(); ++it) { + for (auto it = socketSet.begin(); it != socketSet.end(); ++it) { FD_SET(it->getFD(), &readSet); if (it->getFD() > maxFd) { maxFd = it->getFD(); @@ -198,11 +198,11 @@ Socket SockServ::waitForData(std::set& socketSet) { } // End for int rc = ::select( - &readSet, // Set of read sockets - nullptr, // Set of write sockets - nullptr, // Set of exception sockets - nullptr // Timeout - maxFd + 1, // Number of sockets to scan + maxFd+1, // Number of sockets to scan + &readSet, // Set of read sockets + nullptr, // Set of write sockets + nullptr, // Set of exception sockets + nullptr // Timeout ); if (rc == -1) { ESP_LOGE(LOG_TAG, "Error with select"); diff --git a/cpp_utils/System.cpp b/cpp_utils/System.cpp index 3b774db4..2e24f88b 100644 --- a/cpp_utils/System.cpp +++ b/cpp_utils/System.cpp @@ -47,6 +47,9 @@ typedef volatile struct { } pin_ctrl; // The 36 exposed pads. + io_mux_reg_t pad_gpio36; // GPIO36 + io_mux_reg_t pad_gpio37; // GPIO37 + io_mux_reg_t pad_gpio38; // GPIO38 io_mux_reg_t pad_gpio39; // GPIO39 io_mux_reg_t pad_gpio34; // GPIO34 io_mux_reg_t pad_gpio35; // GPIO35 @@ -64,7 +67,6 @@ typedef volatile struct { io_mux_reg_t pad_gpio4; // GPIO4 io_mux_reg_t pad_gpio16; // GPIO16 io_mux_reg_t pad_gpio17; // GPIO17 - io_mux_reg_t pad_gpio37; // GPIO37 io_mux_reg_t pad_sd_data2; // GPIO9 io_mux_reg_t pad_sd_data3; // GPIO10 io_mux_reg_t pad_sd_cmd; // GPIO11 @@ -406,16 +408,16 @@ const static char* outSignalStrings[] = { for (uint8_t i = 0; i < numPins; i++) { const char *signal; if (GPIO.func_out_sel_cfg[i].func_sel == 256) { - signal = (char *)"[GPIO]"; + signal = (char*) "[GPIO]"; } else if (GPIO.func_out_sel_cfg[i].func_sel == 257) { - signal = (char *)"N/A"; + signal = (char*) "N/A"; } else { signal = outSignalStrings[GPIO.func_out_sel_cfg[i].func_sel]; } printf("%2d %4d %s\n", i, GPIO.func_out_sel_cfg[i].func_sel, signal); const io_mux_reg_t* io_mux = gpioToIoMux(i); if (GPIO.func_out_sel_cfg[i].func_sel == 256 && io_mux != nullptr) { - printf("0x%x - function: %d, ie: %d\n", (uint32_t)io_mux, io_mux->mcu_sel + 1, io_mux->func_ie); + printf("0x%x - function: %d, ie: %d\n", (uint32_t) io_mux, io_mux->mcu_sel + 1, io_mux->func_ie); } } diff --git a/cpp_utils/WS2812.cpp b/cpp_utils/WS2812.cpp index c0f622d7..0b8a3cbe 100644 --- a/cpp_utils/WS2812.cpp +++ b/cpp_utils/WS2812.cpp @@ -158,7 +158,7 @@ void WS2812::show() { (getChannelValueByType(this->colorOrder[2], this->pixels[i])); ESP_LOGD(LOG_TAG, "Pixel value: %x", currentPixel); - for (uint8_t j = 23; j > =0; j--) { + for (uint8_t j = 23; j >= 0; j--) { // We have 24 bits of data representing the red, green amd blue channels. The value of the // 24 bits to output is in the variable current_pixel. We now need to stream this value // through RMT in most significant bit first. To do this, we iterate through each of the 24 From 5a97e6802ec820036a016450508fba7f54a61224 Mon Sep 17 00:00:00 2001 From: Friedemann Stoffregen Date: Mon, 15 Oct 2018 13:05:16 +0200 Subject: [PATCH 346/381] added getServiceCount() to BLEServer.cpp --- cpp_utils/BLEServer.cpp | 15 +++++++++++++++ cpp_utils/BLEServer.h | 1 + 2 files changed, 16 insertions(+) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 7e7007b2..72cb5570 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -115,6 +115,21 @@ BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { } +/** + * @brief Returns the amount of services registered to this server + * @return The amount of registered services + */ +int BLEServer::getServiceCount() { + int count = 0; + if(m_serviceMap.getFirst() == nullptr) return 0; + while(m_serviceMap.getNext() != nullptr){ + count++; + } + + return count; +} + + /** * @brief Retrieve the advertising object that can be used to advertise the existence of the server. * diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index e3362b75..83439d2a 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -43,6 +43,7 @@ class BLEServiceMap { BLEService* getFirst(); BLEService* getNext(); void removeService(BLEService* service); + int getServiceCount(); private: std::map m_handleMap; From 6a8c821695ea9d2a178d808378bad5f206a105c4 Mon Sep 17 00:00:00 2001 From: Friedemann Stoffregen Date: Mon, 15 Oct 2018 13:19:53 +0200 Subject: [PATCH 347/381] improved getServiceCount() --- cpp_utils/BLEServer.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 72cb5570..31c64d3d 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -120,13 +120,7 @@ BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { * @return The amount of registered services */ int BLEServer::getServiceCount() { - int count = 0; - if(m_serviceMap.getFirst() == nullptr) return 0; - while(m_serviceMap.getNext() != nullptr){ - count++; - } - - return count; + return m_serviceMap.size(); } From 693e2c93c58204f91acc1ab096b2315bdc26fcd5 Mon Sep 17 00:00:00 2001 From: Friedemann Stoffregen Date: Wed, 17 Oct 2018 08:45:30 +0200 Subject: [PATCH 348/381] improved BLEServer::getServiceCount() and added parameter for optional count of default services --- cpp_utils/BLEServer.cpp | 6 +++++- cpp_utils/BLEServer.h | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 31c64d3d..05d29772 100644 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -117,9 +117,13 @@ BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { /** * @brief Returns the amount of services registered to this server + * @param [in] includeDefaultServices Add the amount of default BluetoothLE services defined by the BLE standard * @return The amount of registered services */ -int BLEServer::getServiceCount() { +int BLEServer::getServiceCount(bool includeDefaultServices = false) { + if(includeDefaultServices){ + return m_serviceMap.size() + 2; + } return m_serviceMap.size(); } diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 83439d2a..df612848 100644 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -43,7 +43,6 @@ class BLEServiceMap { BLEService* getFirst(); BLEService* getNext(); void removeService(BLEService* service); - int getServiceCount(); private: std::map m_handleMap; @@ -67,6 +66,7 @@ class BLEServer { BLEService* getServiceByUUID(const char* uuid); BLEService* getServiceByUUID(BLEUUID uuid); void removeService(BLEService* service); + int getServiceCount(bool includeDefaultServices = false); private: BLEServer(); From 96f76c02146811ed884d992e1fec621979dec583 Mon Sep 17 00:00:00 2001 From: Friedemann Stoffregen Date: Wed, 17 Oct 2018 09:58:39 +0200 Subject: [PATCH 349/381] fixed some issues --- cpp_utils/BLEServer.cpp | 6 +++--- cpp_utils/BLEServer.h | 4 +++- cpp_utils/BLEServiceMap.cpp | 30 +++++++++++++++++++++--------- 3 files changed, 27 insertions(+), 13 deletions(-) mode change 100644 => 100755 cpp_utils/BLEServer.cpp mode change 100644 => 100755 cpp_utils/BLEServer.h mode change 100644 => 100755 cpp_utils/BLEServiceMap.cpp diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp old mode 100644 new mode 100755 index 05d29772..b40cd345 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -120,11 +120,11 @@ BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { * @param [in] includeDefaultServices Add the amount of default BluetoothLE services defined by the BLE standard * @return The amount of registered services */ -int BLEServer::getServiceCount(bool includeDefaultServices = false) { +int BLEServer::getServiceCount(bool includeDefaultServices) { if(includeDefaultServices){ - return m_serviceMap.size() + 2; + return m_serviceMap.getRegisteredServiceCount() + 2; } - return m_serviceMap.size(); + return m_serviceMap.getRegisteredServiceCount(); } diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h old mode 100644 new mode 100755 index df612848..799eb25d --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -43,6 +43,8 @@ class BLEServiceMap { BLEService* getFirst(); BLEService* getNext(); void removeService(BLEService* service); + int getRegisteredServiceCount(); + private: std::map m_handleMap; @@ -66,7 +68,7 @@ class BLEServer { BLEService* getServiceByUUID(const char* uuid); BLEService* getServiceByUUID(BLEUUID uuid); void removeService(BLEService* service); - int getServiceCount(bool includeDefaultServices = false); + int getServiceCount(bool includeDefaultServices); private: BLEServer(); diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp old mode 100644 new mode 100755 index d8610a85..aa371f47 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -17,9 +17,9 @@ * @return The characteristic. */ BLEService* BLEServiceMap::getByUUID(const char* uuid) { - return getByUUID(BLEUUID(uuid)); + return getByUUID(BLEUUID(uuid)); } - + /** * @brief Return the service by UUID. * @param [in] UUID The UUID to look up the service. @@ -53,8 +53,8 @@ BLEService* BLEServiceMap::getByHandle(uint16_t handle) { * @return N/A. */ void BLEServiceMap::setByUUID(BLEUUID uuid, - BLEService* service) { - m_uuidMap.insert(std::pair(service, uuid.toString())); + BLEService *service) { + m_uuidMap.insert(std::pair(service, uuid.toString())); } // setByUUID @@ -66,7 +66,7 @@ void BLEServiceMap::setByUUID(BLEUUID uuid, */ void BLEServiceMap::setByHandle(uint16_t handle, BLEService* service) { - m_handleMap.insert(std::pair(handle, service)); + m_handleMap.insert(std::pair(handle, service)); } // setByHandle @@ -86,7 +86,7 @@ std::string BLEServiceMap::toString() { void BLEServiceMap::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param) { + esp_ble_gatts_cb_param_t *param) { // Invoke the handler for every Service we have. for (auto &myPair : m_uuidMap) { myPair.first->handleGATTServerEvent(event, gatts_if, param); @@ -99,7 +99,9 @@ void BLEServiceMap::handleGATTServerEvent( */ BLEService* BLEServiceMap::getFirst() { m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) return nullptr; + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } BLEService* pRet = m_iterator->first; m_iterator++; return pRet; @@ -110,7 +112,9 @@ BLEService* BLEServiceMap::getFirst() { * @return The next service in the map. */ BLEService* BLEServiceMap::getNext() { - if (m_iterator == m_uuidMap.end()) return nullptr; + if (m_iterator == m_uuidMap.end()) { + return nullptr; + } BLEService* pRet = m_iterator->first; m_iterator++; return pRet; @@ -120,9 +124,17 @@ BLEService* BLEServiceMap::getNext() { * @brief Removes service from maps. * @return N/A. */ -void BLEServiceMap::removeService(BLEService* service) { +void BLEServiceMap::removeService(BLEService *service){ m_handleMap.erase(service->getHandle()); m_uuidMap.erase(service); } // removeService +/** + * @brief Returns the amount of registered services + * @return amount of registered services + */ +int BLEServiceMap::getRegisteredServiceCount(){ + return m_handleMap.size(); +} + #endif /* CONFIG_BT_ENABLED */ From 47563cb00715c7ccced11f58cbeb813b394f2f5f Mon Sep 17 00:00:00 2001 From: toxuin Date: Wed, 17 Oct 2018 20:08:01 -0600 Subject: [PATCH 350/381] Fix codestyle --- .gitignore | 8 +++++++- cpp_utils/BLEServer.cpp | 2 +- cpp_utils/BLEServiceMap.cpp | 26 ++++++++++---------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 099e1f7c..15d2ae94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,10 @@ .project .cproject .settings/ -/esp32-snippets/.vs + +# IDEs +.vs/ +.idea/ + +# CMake +cmake-build-debug/ diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index b40cd345..e5c675b8 100755 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -121,7 +121,7 @@ BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { * @return The amount of registered services */ int BLEServer::getServiceCount(bool includeDefaultServices) { - if(includeDefaultServices){ + if (includeDefaultServices) { return m_serviceMap.getRegisteredServiceCount() + 2; } return m_serviceMap.getRegisteredServiceCount(); diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index aa371f47..c749be16 100755 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -17,9 +17,9 @@ * @return The characteristic. */ BLEService* BLEServiceMap::getByUUID(const char* uuid) { - return getByUUID(BLEUUID(uuid)); + return getByUUID(BLEUUID(uuid)); } - + /** * @brief Return the service by UUID. * @param [in] UUID The UUID to look up the service. @@ -52,9 +52,8 @@ BLEService* BLEServiceMap::getByHandle(uint16_t handle) { * @param [in] characteristic The service to cache. * @return N/A. */ -void BLEServiceMap::setByUUID(BLEUUID uuid, - BLEService *service) { - m_uuidMap.insert(std::pair(service, uuid.toString())); +void BLEServiceMap::setByUUID(BLEUUID uuid, BLEService* service) { + m_uuidMap.insert(std::pair(service, uuid.toString())); } // setByUUID @@ -64,9 +63,8 @@ void BLEServiceMap::setByUUID(BLEUUID uuid, * @param [in] service The service to cache. * @return N/A. */ -void BLEServiceMap::setByHandle(uint16_t handle, - BLEService* service) { - m_handleMap.insert(std::pair(handle, service)); +void BLEServiceMap::setByHandle(uint16_t handle, BLEService* service) { + m_handleMap.insert(std::pair(handle, service)); } // setByHandle @@ -86,7 +84,7 @@ std::string BLEServiceMap::toString() { void BLEServiceMap::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { + esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every Service we have. for (auto &myPair : m_uuidMap) { myPair.first->handleGATTServerEvent(event, gatts_if, param); @@ -99,9 +97,7 @@ void BLEServiceMap::handleGATTServerEvent( */ BLEService* BLEServiceMap::getFirst() { m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } + if (m_iterator == m_uuidMap.end()) return nullptr; BLEService* pRet = m_iterator->first; m_iterator++; return pRet; @@ -112,9 +108,7 @@ BLEService* BLEServiceMap::getFirst() { * @return The next service in the map. */ BLEService* BLEServiceMap::getNext() { - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } + if (m_iterator == m_uuidMap.end()) return nullptr; BLEService* pRet = m_iterator->first; m_iterator++; return pRet; @@ -124,7 +118,7 @@ BLEService* BLEServiceMap::getNext() { * @brief Removes service from maps. * @return N/A. */ -void BLEServiceMap::removeService(BLEService *service){ +void BLEServiceMap::removeService(BLEService* service) { m_handleMap.erase(service->getHandle()); m_uuidMap.erase(service); } // removeService From 125ceb3df67e92cc3d5c639240adaa9601d2d186 Mon Sep 17 00:00:00 2001 From: toxuin Date: Wed, 17 Oct 2018 20:49:25 -0600 Subject: [PATCH 351/381] Change local includes from brackets to quotes --- cpp_utils/BLESecurity.cpp | 2 +- cpp_utils/File.cpp | 2 +- cpp_utils/GeneralUtils.cpp | 4 ++-- cpp_utils/MFRC522.cpp | 4 ++-- cpp_utils/SockServ.cpp | 4 ++-- cpp_utils/TFTP.cpp | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cpp_utils/BLESecurity.cpp b/cpp_utils/BLESecurity.cpp index 114f630d..921f5424 100644 --- a/cpp_utils/BLESecurity.cpp +++ b/cpp_utils/BLESecurity.cpp @@ -5,7 +5,7 @@ * Author: chegewara */ -#include +#include "BLESecurity.h" #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) diff --git a/cpp_utils/File.cpp b/cpp_utils/File.cpp index 3b245a85..3ac4672f 100644 --- a/cpp_utils/File.cpp +++ b/cpp_utils/File.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include "GeneralUtils.h" static const char* LOG_TAG = "File"; /** diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 80c5cc6e..8d58d4eb 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include "FreeRTOS.h" #include #include #include @@ -440,7 +440,7 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { * @brief Convert a wifi_err_reason_t code to a string. * @param [in] errCode The errCode to be converted. * @return A string representation of the error code. - * + * * @note: wifi_err_reason_t values as of April 2018 are: (1-24, 200-204) and are defined in ~/esp-idf/components/esp32/include/esp_wifi_types.h. */ const char* GeneralUtils::wifiErrorToString(uint8_t errCode) { diff --git a/cpp_utils/MFRC522.cpp b/cpp_utils/MFRC522.cpp index 4082d4ac..17ca1754 100644 --- a/cpp_utils/MFRC522.cpp +++ b/cpp_utils/MFRC522.cpp @@ -18,8 +18,8 @@ #include "MFRC522.h" #include "MFRC522Debug.h" -#include -#include +#include "FreeRTOS.h" +#include "GPIO.h" #include #include #include diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 38ecc823..020ed096 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -7,15 +7,15 @@ #include #include -#include #include #include - #include + #include #include #include "sdkconfig.h" +#include "FreeRTOS.h" #include "SockServ.h" #include "Socket.h" diff --git a/cpp_utils/TFTP.cpp b/cpp_utils/TFTP.cpp index d554bd45..b1591a9f 100644 --- a/cpp_utils/TFTP.cpp +++ b/cpp_utils/TFTP.cpp @@ -9,13 +9,13 @@ #include "TFTP.h" #include -#include -#include +#include "FreeRTOS.h" +#include "GeneralUtils.h" #include #include #include #include -#include +#include "Socket.h" #include "sdkconfig.h" From 0ebec95c1f8f49467182d9de1c27caea0c828ddb Mon Sep 17 00:00:00 2001 From: Ryotaro Onuki Date: Thu, 18 Oct 2018 14:03:36 +0900 Subject: [PATCH 352/381] fix an infinite loop in WS2812.cpp just a simple mistake --- cpp_utils/WS2812.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp_utils/WS2812.cpp b/cpp_utils/WS2812.cpp index 0b8a3cbe..af4bc512 100644 --- a/cpp_utils/WS2812.cpp +++ b/cpp_utils/WS2812.cpp @@ -158,7 +158,7 @@ void WS2812::show() { (getChannelValueByType(this->colorOrder[2], this->pixels[i])); ESP_LOGD(LOG_TAG, "Pixel value: %x", currentPixel); - for (uint8_t j = 23; j >= 0; j--) { + for (int8_t j = 23; j >= 0; j--) { // We have 24 bits of data representing the red, green amd blue channels. The value of the // 24 bits to output is in the variable current_pixel. We now need to stream this value // through RMT in most significant bit first. To do this, we iterate through each of the 24 From 913abdffc3186d915f341be24e12d9bbacfdb354 Mon Sep 17 00:00:00 2001 From: Paul Breugnot Date: Sun, 11 Nov 2018 11:17:55 +0100 Subject: [PATCH 353/381] Add slave select and release when reading / writting registers --- cpp_utils/MFRC522.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cpp_utils/MFRC522.cpp b/cpp_utils/MFRC522.cpp index 17ca1754..940c170c 100644 --- a/cpp_utils/MFRC522.cpp +++ b/cpp_utils/MFRC522.cpp @@ -48,7 +48,11 @@ void MFRC522::PCD_WriteRegister(PCD_Register reg, byte value) { uint8_t data[2]; data[0] = reg; data[1] = value; + + ESP32CPP::GPIO::low((gpio_num_t)_chipSelectPin); // Select slave m_spi.transfer(data, 2); + ESP32CPP::GPIO::high((gpio_num_t)_chipSelectPin); // Release slave + } // End PCD_WriteRegister() @@ -63,7 +67,11 @@ void MFRC522::PCD_WriteRegister(PCD_Register reg, byte count, byte* values) { uint8_t* pData = new uint8_t[count + 1]; pData[0] = reg; memcpy(pData + 1, values, count); + + ESP32CPP::GPIO::low((gpio_num_t)_chipSelectPin); // Select slave m_spi.transfer(pData, count + 1); + ESP32CPP::GPIO::high((gpio_num_t)_chipSelectPin); // Release slave + delete[] pData; } // End PCD_WriteRegister() @@ -77,7 +85,11 @@ byte MFRC522::PCD_ReadRegister(PCD_Register reg) { uint8_t data[2]; data[0] = reg | 0x80; data[1] = 0; + + ESP32CPP::GPIO::low((gpio_num_t)_chipSelectPin); // Select slave m_spi.transfer(data, 2); + ESP32CPP::GPIO::high((gpio_num_t)_chipSelectPin); // Release slave + return data[1]; } // End PCD_ReadRegister() @@ -97,6 +109,8 @@ void MFRC522::PCD_ReadRegister(PCD_Register reg, byte count, byte* values, byte byte address = 0x80 | reg; // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. byte index = 0; // Index in values array. count--; // One read is performed outside of the loop + + ESP32CPP::GPIO::low((gpio_num_t)_chipSelectPin); // Select slave m_spi.transferByte(address); // Tell MFRC522 which address we want to read if (rxAlign) { // Only update bit positions rxAlign..7 in values[0] // Create bit mask for bit positions rxAlign..7 @@ -112,6 +126,7 @@ void MFRC522::PCD_ReadRegister(PCD_Register reg, byte count, byte* values, byte index++; } values[index] = m_spi.transferByte(0); // Read the final byte. Send 0 to stop reading. + ESP32CPP::GPIO::high((gpio_num_t)_chipSelectPin); // Release slave } // End PCD_ReadRegister() @@ -181,6 +196,8 @@ MFRC522::StatusCode MFRC522::PCD_CalculateCRC(byte* data, byte length, byte* res */ void MFRC522::PCD_Init() { //m_spi.setHost(VSPI_HOST); + + ESP32CPP::GPIO::setOutput((gpio_num_t)_chipSelectPin); m_spi.init(); bool hardReset = false; From 5cc6294fa74125188d062d208076e0f1d3d2373d Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 13 Nov 2018 17:43:04 +0100 Subject: [PATCH 354/381] Revert "Release memory from CLASSIC_BT when not enabled" --- cpp_utils/BLEDevice.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index fd86abe0..77380d2b 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -350,7 +350,13 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; } #ifndef CLASSIC_BT_ENABLED - esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue + errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); + //errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } #else errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); if (errRc != ESP_OK) { From e8b5df49c857ddc07212db3f37dd0b9bae7f7e13 Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 14 Nov 2018 18:52:36 +0100 Subject: [PATCH 355/381] fix memory leak, add multi connection features --- cpp_utils/BLEScan.cpp | 116 ++++++++++++++++++++++++++---------------- cpp_utils/BLEScan.h | 17 ++++--- 2 files changed, 82 insertions(+), 51 deletions(-) diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 25b60a0b..7164626b 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -48,22 +48,21 @@ void BLEScan::handleGAPEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) { - switch (event) { - - // ESP_GAP_BLE_SCAN_RESULT_EVT - // --------------------------- - // scan_rst: - // esp_gap_search_evt_t search_evt - // esp_bd_addr_t bda - // esp_bt_dev_type_t dev_type - // esp_ble_addr_type_t ble_addr_type - // esp_ble_evt_type_t ble_evt_type - // int rssi - // uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX] - // int flag - // int num_resps - // uint8_t adv_data_len - // uint8_t scan_rsp_len + switch(event) { + + // --------------------------- + // scan_rst: + // esp_gap_search_evt_t search_evt + // esp_bd_addr_t bda + // esp_bt_dev_type_t dev_type + // esp_ble_addr_type_t ble_addr_type + // esp_ble_evt_type_t ble_evt_type + // int rssi + // uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX] + // int flag + // int num_resps + // uint8_t adv_data_len + // uint8_t scan_rsp_len case ESP_GAP_BLE_SCAN_RESULT_EVT: { switch(param->scan_rst.search_evt) { @@ -73,11 +72,12 @@ void BLEScan::handleGAPEvent( // Event that indicates that the duration allowed for the search has completed or that we have been // asked to stop. case ESP_GAP_SEARCH_INQ_CMPL_EVT: { + ESP_LOGW(LOG_TAG, "ESP_GAP_SEARCH_INQ_CMPL_EVT"); m_stopped = true; + m_semaphoreScanEnd.give(); if (m_scanCompleteCB != nullptr) { m_scanCompleteCB(m_scanResults); } - m_semaphoreScanEnd.give(); break; } // ESP_GAP_SEARCH_INQ_CMPL_EVT @@ -90,52 +90,58 @@ void BLEScan::handleGAPEvent( break; } - // Examine our list of previously scanned addresses and, if we found this one already, - // ignore it. +// Examine our list of previously scanned addresses and, if we found this one already, +// ignore it. BLEAddress advertisedAddress(param->scan_rst.bda); bool found = false; - for (uint32_t i = 0; i < m_scanResults.getCount(); i++) { - if (m_scanResults.getDevice(i).getAddress().equals(advertisedAddress)) { - found = true; - break; - } + if (m_scanResults.m_vectorAdvertisedDevices.count(advertisedAddress.toString()) != 0) { + found = true; } + if (found && !m_wantDuplicates) { // If we found a previous entry AND we don't want duplicates, then we are done. ESP_LOGD(LOG_TAG, "Ignoring %s, already seen it.", advertisedAddress.toString().c_str()); + vTaskDelay(1); // <--- allow to switch task in case we scan infinity and dont have new devices to report, or we will blocked here break; } // We now construct a model of the advertised device that we have just found for the first // time. - BLEAdvertisedDevice advertisedDevice; - advertisedDevice.setAddress(advertisedAddress); - advertisedDevice.setRSSI(param->scan_rst.rssi); - advertisedDevice.setAdFlag(param->scan_rst.flag); - advertisedDevice.parseAdvertisement((uint8_t*)param->scan_rst.ble_adv); - advertisedDevice.setScan(this); + // ESP_LOG_BUFFER_HEXDUMP(LOG_TAG, (uint8_t*)param->scan_rst.ble_adv, param->scan_rst.adv_data_len + param->scan_rst.scan_rsp_len, ESP_LOG_DEBUG); + // ESP_LOGW(LOG_TAG, "bytes length: %d + %d, addr type: %d", param->scan_rst.adv_data_len, param->scan_rst.scan_rsp_len, param->scan_rst.ble_addr_type); + BLEAdvertisedDevice *advertisedDevice = new BLEAdvertisedDevice(); + advertisedDevice->setAddress(advertisedAddress); + advertisedDevice->setRSSI(param->scan_rst.rssi); + advertisedDevice->setAdFlag(param->scan_rst.flag); + advertisedDevice->parseAdvertisement((uint8_t*)param->scan_rst.ble_adv, param->scan_rst.adv_data_len + param->scan_rst.scan_rsp_len); + advertisedDevice->setScan(this); + advertisedDevice->setAddressType(param->scan_rst.ble_addr_type); - if (m_pAdvertisedDeviceCallbacks) { - m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); + if (!found) { // If we have previously seen this device, don't record it again. + m_scanResults.m_vectorAdvertisedDevices.insert(std::pair(advertisedAddress.toString(), advertisedDevice)); } - if (!found) { // If we have previously seen this device, don't record it again. - m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice); + if (m_pAdvertisedDeviceCallbacks) { + m_pAdvertisedDeviceCallbacks->onResult(*advertisedDevice); } + if(found) + delete advertisedDevice; break; } // ESP_GAP_SEARCH_INQ_RES_EVT - default: + default: { break; + } } // switch - search_evt break; } // ESP_GAP_BLE_SCAN_RESULT_EVT - default: + default: { break; + } // default } // End switch } // gapEventHandler @@ -188,15 +194,23 @@ void BLEScan::setWindow(uint16_t windowMSecs) { * @brief Start scanning. * @param [in] duration The duration in seconds for which to scan. * @param [in] scanCompleteCB A function to be called when scanning has completed. + * @param [in] are we continue scan (true) or we want to clear stored devices (false) * @return True if scan started or false if there was an error. */ -bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)) { +bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults), bool is_continue) { ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); m_semaphoreScanEnd.take(std::string("start")); m_scanCompleteCB = scanCompleteCB; // Save the callback to be invoked when the scan completes. - m_scanResults.m_vectorAdvertisedDevices.clear(); + // if we are connecting to devices that are advertising even after being connected, multiconnecting peripherals + // then we should not clear map or we will connect the same device few times + if(!is_continue) { + for(auto _dev : m_scanResults.m_vectorAdvertisedDevices){ + delete _dev.second; + } + m_scanResults.m_vectorAdvertisedDevices.clear(); + } esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); @@ -226,8 +240,8 @@ bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)) { * @param [in] duration The duration in seconds for which to scan. * @return The BLEScanResults. */ -BLEScanResults BLEScan::start(uint32_t duration) { - if (start(duration, nullptr)) { +BLEScanResults BLEScan::start(uint32_t duration, bool is_continue) { + if(start(duration, nullptr, is_continue)) { m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. } return m_scanResults; @@ -244,24 +258,31 @@ void BLEScan::stop() { esp_err_t errRc = ::esp_ble_gap_stop_scanning(); m_stopped = true; + m_semaphoreScanEnd.give(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); return; } - m_semaphoreScanEnd.give(); - ESP_LOGD(LOG_TAG, "<< stop()"); } // stop +// delete peer device from cache after disconnecting, it is required in case we are connecting to devices with not public address +void BLEScan::erase(BLEAddress address) { + ESP_LOGI(LOG_TAG, "erase device: %s", address.toString().c_str()); + BLEAdvertisedDevice *advertisedDevice = m_scanResults.m_vectorAdvertisedDevices.find(address.toString())->second; + m_scanResults.m_vectorAdvertisedDevices.erase(address.toString()); + delete advertisedDevice; +} + /** * @brief Dump the scan results to the log. */ void BLEScanResults::dump() { ESP_LOGD(LOG_TAG, ">> Dump scan results:"); - for (uint32_t i = 0; i < getCount(); i++) { + for (int i=0; isecond; + for (auto it = m_vectorAdvertisedDevices.begin(); it != m_vectorAdvertisedDevices.end(); it++) { + dev = *it->second; + if (x==i) break; + x++; + } + return dev; } diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index aec4e3db..ae4663e1 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -11,7 +11,8 @@ #if defined(CONFIG_BT_ENABLED) #include -#include +// #include +#include #include "BLEAdvertisedDevice.h" #include "BLEClient.h" #include "FreeRTOS.h" @@ -37,7 +38,7 @@ class BLEScanResults { private: friend BLEScan; - std::vector m_vectorAdvertisedDevices; + std::map m_vectorAdvertisedDevices; }; /** @@ -53,9 +54,11 @@ class BLEScan { bool wantDuplicates = false); void setInterval(uint16_t intervalMSecs); void setWindow(uint16_t windowMSecs); - bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults)); - BLEScanResults start(uint32_t duration); + bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults), bool is_continue = false); + BLEScanResults start(uint32_t duration, bool is_continue = false); void stop(); + void erase(BLEAddress address); + // void setPower(esp_power_level_t powerLevel); private: BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. @@ -63,12 +66,12 @@ class BLEScan { void handleGAPEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); - void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t* payload); + void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); esp_ble_scan_params_t m_scan_params; - BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks; - bool m_stopped; + BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks = nullptr; + bool m_stopped = true; FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); BLEScanResults m_scanResults; bool m_wantDuplicates; From a4e3d4355ad38c1c3ff57e35fb164128a78c0626 Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 14 Nov 2018 19:24:52 +0100 Subject: [PATCH 356/381] Fix issue with parsing scan response #612 --- cpp_utils/BLEAdvertisedDevice.cpp | 25 +++++++++++++++++-------- cpp_utils/BLEAdvertisedDevice.h | 7 +++++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 104b2e12..dbda9172 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -228,16 +228,16 @@ bool BLEAdvertisedDevice::haveTXPower() { * * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile */ -void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { +void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload, size_t total_len) { uint8_t length; uint8_t ad_type; uint8_t sizeConsumed = 0; bool finished = false; setPayload(payload); + while(!finished) { length = *payload; // Retrieve the length of the record. payload++; // Skip to type - while (!finished) { sizeConsumed += 1 + length; // increase the size consumed. if (length != 0) { // A length of 0 indicates that we have reached the end. @@ -250,7 +250,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { ad_type, BLEUtils::advTypeToString(ad_type), length, pHex); free(pHex); - switch (ad_type) { + switch(ad_type) { case ESP_BLE_AD_TYPE_NAME_CMPL: { // Adv Data Type: 0x09 setName(std::string(reinterpret_cast(payload), length)); break; @@ -273,7 +273,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { case ESP_BLE_AD_TYPE_16SRV_CMPL: case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02 - for (int var = 0; var < length / 2; ++var) { + for (int var = 0; var < length/2; ++var) { setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 2))); } break; @@ -281,7 +281,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { case ESP_BLE_AD_TYPE_32SRV_CMPL: case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04 - for (int var = 0; var < length / 4; ++var) { + for (int var = 0; var < length/4; ++var) { setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 4))); } break; @@ -308,7 +308,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_SERVICE_DATA"); break; } - uint16_t uuid = *(uint16_t*) payload; + uint16_t uuid = *(uint16_t*)payload; setServiceDataUUID(BLEUUID(uuid)); if (length > 2) { setServiceData(std::string(reinterpret_cast(payload + 2), length - 2)); @@ -335,7 +335,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { break; } - setServiceDataUUID(BLEUUID(payload, (size_t) 16, false)); + setServiceDataUUID(BLEUUID(payload, (size_t)16, false)); if (length > 16) { setServiceData(std::string(reinterpret_cast(payload + 16), length - 16)); } @@ -351,7 +351,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { } // Length <> 0 - if (sizeConsumed >= 31 || length == 0) { + if (sizeConsumed >= total_len || length == 0) { finished = true; } } // !finished @@ -514,5 +514,14 @@ void BLEAdvertisedDevice::setPayload(uint8_t* payload) { m_payload = payload; } +esp_ble_addr_type_t BLEAdvertisedDevice::getAddressType() { + return m_addressType; +} + +void BLEAdvertisedDevice::setAddressType(esp_ble_addr_type_t type) { + m_addressType = type; +} + #endif /* CONFIG_BT_ENABLED */ + diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index eebdd6ef..aaa1417c 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -40,6 +40,8 @@ class BLEAdvertisedDevice { BLEUUID getServiceUUID(); int8_t getTXPower(); uint8_t* getPayload(); + esp_ble_addr_type_t getAddressType(); + void setAddressType(esp_ble_addr_type_t type); bool isAdvertisingService(BLEUUID uuid); @@ -56,7 +58,7 @@ class BLEAdvertisedDevice { private: friend class BLEScan; - void parseAdvertisement(uint8_t* payload); + void parseAdvertisement(uint8_t* payload, size_t total_len=62); void setAddress(BLEAddress address); void setAdFlag(uint8_t adFlag); void setAdvertizementResult(uint8_t* payload); @@ -81,6 +83,7 @@ class BLEAdvertisedDevice { bool m_haveTXPower; + BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); uint8_t m_adFlag; uint16_t m_appearance; int m_deviceType; @@ -93,7 +96,7 @@ class BLEAdvertisedDevice { std::string m_serviceData; BLEUUID m_serviceDataUUID; uint8_t* m_payload; - BLEAddress m_address = BLEAddress((uint8_t*) "\0\0\0\0\0\0"); + esp_ble_addr_type_t m_addressType; }; /** From 68411ff57b9db1b80e4c9913c9ee619f0404f219 Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 14 Nov 2018 19:49:30 +0100 Subject: [PATCH 357/381] Fix issue #618 --- cpp_utils/BLEUUID.cpp | 72 ++++++++++++++++++++++++------------------- cpp_utils/BLEUUID.h | 4 +-- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 512c24ea..ee8b6c9d 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -70,46 +70,56 @@ static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { */ BLEUUID::BLEUUID(std::string value) { m_valueSet = true; - if (value.length() == 2) { + if (value.length() == 4) { m_uuid.len = ESP_UUID_LEN_16; - m_uuid.uuid.uuid16 = value[0] | (value[1] << 8); + m_uuid.uuid.uuid16 = 0; + for(int i=0;i '9') MSB -= 7; + if(LSB > '9') LSB -= 7; + m_uuid.uuid.uuid16 += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(2-i)*4; + i+=2; + } } - else if (value.length() == 4) { + else if (value.length() == 8) { m_uuid.len = ESP_UUID_LEN_32; - m_uuid.uuid.uuid32 = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); + m_uuid.uuid.uuid32 = 0; + for(int i=0;i '9') MSB -= 7; + if(LSB > '9') LSB -= 7; + m_uuid.uuid.uuid32 += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(6-i)*4; + i+=2; + } + } + } } - else if (value.length() == 16) { + else if (value.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be investigated (lack of time) m_uuid.len = ESP_UUID_LEN_128; - memrcpy(m_uuid.uuid.uuid128, (uint8_t*) value.data(), 16); + memrcpy(m_uuid.uuid.uuid128, (uint8_t*)value.data(), 16); } else if (value.length() == 36) { // If the length of the string is 36 bytes then we will assume it is a long hex string in // UUID format. m_uuid.len = ESP_UUID_LEN_128; - int vals[16]; - sscanf(value.c_str(), "%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x", - &vals[15], - &vals[14], - &vals[13], - &vals[12], - &vals[11], - &vals[10], - &vals[9], - &vals[8], - &vals[7], - &vals[6], - &vals[5], - &vals[4], - &vals[3], - &vals[2], - &vals[1], - &vals[0] - ); - - for (int i = 0; i < 16; i++) { - m_uuid.uuid.uuid128[i] = vals[i]; + int n = 0; + for(int i=0;i '9') MSB -= 7; + if(LSB > '9') LSB -= 7; + m_uuid.uuid.uuid128[15-n++] = ((MSB&0x0F) <<4) | (LSB & 0x0F); + i+=2; } - } else { + } + else { ESP_LOGE(LOG_TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes"); m_valueSet = false; } @@ -249,7 +259,7 @@ BLEUUID BLEUUID::fromString(std::string _uuid) { } uint8_t len = _uuid.length() - start; // Calculate the length of the string we are going to use. - if (len == 4) { + if(len == 4) { uint16_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); return BLEUUID(x); } else if (len == 8) { @@ -269,7 +279,7 @@ BLEUUID BLEUUID::fromString(std::string _uuid) { */ esp_bt_uuid_t* BLEUUID::getNative() { //ESP_LOGD(TAG, ">> getNative()") - if (!m_valueSet) { + if (m_valueSet == false) { ESP_LOGD(LOG_TAG, "<< Return of un-initialized UUID!"); return nullptr; } diff --git a/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index e81dd130..700739be 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -32,8 +32,8 @@ class BLEUUID { static BLEUUID fromString(std::string uuid); // Create a BLEUUID from a string private: - esp_bt_uuid_t m_uuid; // The underlying UUID structure that this class wraps. - bool m_valueSet; // Is there a value set for this instance. + esp_bt_uuid_t m_uuid; // The underlying UUID structure that this class wraps. + bool m_valueSet = false; // Is there a value set for this instance. }; // BLEUUID #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLEUUID_H_ */ From 573644495bc718298d5d818a72ca3588a0834d7d Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 14 Nov 2018 23:09:24 +0100 Subject: [PATCH 358/381] Changes, bugfixes, new features. Add server and client multi connect feature. --- cpp_utils/BLECharacteristic.cpp | 188 ++++++++----------- cpp_utils/BLECharacteristic.h | 62 +++---- cpp_utils/BLEClient.cpp | 93 ++++++++-- cpp_utils/BLEClient.h | 22 ++- cpp_utils/BLEDevice.cpp | 248 +++++++++++++++++--------- cpp_utils/BLEDevice.h | 42 +++-- cpp_utils/BLERemoteCharacteristic.cpp | 33 ++-- cpp_utils/BLERemoteCharacteristic.h | 40 ++--- cpp_utils/BLERemoteService.cpp | 10 +- cpp_utils/BLERemoteService.h | 2 +- cpp_utils/BLEServer.cpp | 166 +++++++++-------- cpp_utils/BLEServer.h | 57 +++--- cpp_utils/BLEService.cpp | 28 ++- cpp_utils/BLEService.h | 15 +- cpp_utils/BLEUtils.cpp | 110 ++++++------ 15 files changed, 628 insertions(+), 488 deletions(-) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 6f696c7c..f3cf02b8 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -44,7 +44,7 @@ BLECharacteristic::BLECharacteristic(const char* uuid, uint32_t properties) : BL BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { m_bleUUID = uuid; m_handle = NULL_HANDLE; - m_properties = (esp_gatt_char_prop_t) 0; + m_properties = (esp_gatt_char_prop_t)0; m_pCallbacks = nullptr; setBroadcastProperty((properties & PROPERTY_BROADCAST) != 0); @@ -87,7 +87,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { return; } - m_pService = pService; // Save the service for to which this characteristic belongs. + m_pService = pService; // Save the service to which this characteristic belongs. ESP_LOGD(LOG_TAG, "Registering characteristic (esp_ble_gatts_add_char): uuid: %s, service: %s", getUUID().toString().c_str(), @@ -96,14 +96,6 @@ void BLECharacteristic::executeCreate(BLEService* pService) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; - m_semaphoreCreateEvt.take("executeCreate"); - - /* - esp_attr_value_t value; - value.attr_len = m_value.getLength(); - value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; - value.attr_value = m_value.getData(); - */ esp_err_t errRc = ::esp_ble_gatts_add_char( m_pService->getHandle(), @@ -119,19 +111,6 @@ void BLECharacteristic::executeCreate(BLEService* pService) { return; } - m_semaphoreCreateEvt.wait("executeCreate"); - - // Now that we have registered the characteristic, we must also register all the descriptors associated with this - // characteristic. We iterate through each of those and invoke the registration call to register them with the - // ESP environment. - - BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); - - while (pDescriptor != nullptr) { - pDescriptor->executeCreate(this); - pDescriptor = m_descriptorMap.getNext(); - } // End while - ESP_LOGD(LOG_TAG, "<< executeCreate"); } // executeCreate @@ -216,7 +195,7 @@ void BLECharacteristic::handleGATTServerEvent( esp_ble_gatts_cb_param_t* param) { ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); - switch (event) { + switch(event) { // Events handled: // // ESP_GATTS_ADD_CHAR_EVT @@ -246,7 +225,7 @@ void BLECharacteristic::handleGATTServerEvent( } else { m_value.cancel(); } - +// ??? esp_err_t errRc = ::esp_ble_gatts_send_response( gatts_if, param->write.conn_id, @@ -267,8 +246,15 @@ void BLECharacteristic::handleGATTServerEvent( case ESP_GATTS_ADD_CHAR_EVT: { if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) && getHandle() == param->add_char.attr_handle && - getService()->getHandle() == param->add_char.service_handle) { - m_semaphoreCreateEvt.give(); + getService()->getHandle()==param->add_char.service_handle) { + + // we have created characteristic, now we can create descriptors + BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); + while (pDescriptor != nullptr) { + pDescriptor->executeCreate(this); + pDescriptor = m_descriptorMap.getNext(); + } // End while + } break; } // ESP_GATTS_ADD_CHAR_EVT @@ -357,7 +343,7 @@ void BLECharacteristic::handleGATTServerEvent( // // If the is_long flag is set then this is a follow on from an original read and we will already have sent at least 22 bytes. // If the is_long flag is not set then we need to check how much data we are going to send. If we are sending LESS than -// 22 bytes, then we "just" send it and that's the end of the story. +// 22 bytes, then we "just" send it and thats the end of the story. // If we are sending 22 bytes exactly, we just send it BUT we will get a follow on request. // If we are sending more than 22 bytes, we send the first 22 bytes and we will get a follow on request. // Because of follow on request processing, we need to maintain an offset of how much data we have already sent @@ -367,10 +353,11 @@ void BLECharacteristic::handleGATTServerEvent( // // The following code has deliberately not been factored to make it fewer statements because this would cloud the // the logic flow comprehension. +// - // TODO requires some more research to confirm that 512 is max PDU like in bluetooth specs - uint16_t maxOffset = BLEDevice::getMTU() - 1; - if (BLEDevice::getMTU() > 512) maxOffset = 512; + // get mtu for peer device that we are sending read request to + uint16_t maxOffset = getService()->getServer()->getPeerMTU(param->read.conn_id) - 1; + ESP_LOGD(LOG_TAG, "mtu value: %d", maxOffset); if (param->read.need_rsp) { ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); esp_gatt_rsp_t rsp; @@ -393,10 +380,6 @@ void BLECharacteristic::handleGATTServerEvent( } } else { // read.is_long == false - if (m_pCallbacks != nullptr) { // If is.long is false then this is the first (or only) request to read data, so invoke the callback - m_pCallbacks->onRead(this); // Invoke the read callback. - } - std::string value = m_value.getValue(); if (value.length() + 1 > maxOffset) { @@ -411,6 +394,10 @@ void BLECharacteristic::handleGATTServerEvent( rsp.attr_value.offset = 0; memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len); } + + if (m_pCallbacks != nullptr) { // If is.long is false then this is the first (or only) request to read data, so invoke the callback + m_pCallbacks->onRead(this); // Invoke the read callback. + } } rsp.attr_value.handle = param->read.handle; rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; @@ -440,12 +427,13 @@ void BLECharacteristic::handleGATTServerEvent( // - uint16_t conn_id – The connection used. // case ESP_GATTS_CONF_EVT: { - m_semaphoreConfEvt.give(); + ESP_LOGD(LOG_TAG, "m_handle = %d, conf->handle = %d", m_handle, param->conf.handle); + if(param->conf.conn_id == getService()->getServer()->getConnId()) // && param->conf.handle == m_handle) // bug in esp-idf and not implemented in arduino yet + m_semaphoreConfEvt.give(param->conf.status); break; } case ESP_GATTS_CONNECT_EVT: { - m_semaphoreConfEvt.give(); break; } @@ -475,46 +463,9 @@ void BLECharacteristic::handleGATTServerEvent( * @return N/A */ void BLECharacteristic::indicate() { - ESP_LOGD(LOG_TAG, ">> indicate: length: %d", m_value.getValue().length()); - - assert(getService() != nullptr); - assert(getService()->getServer() != nullptr); - - GeneralUtils::hexDump((uint8_t*)m_value.getValue().data(), m_value.getValue().length()); - - if (getService()->getServer()->getConnectedCount() == 0) { - ESP_LOGD(LOG_TAG, "<< indicate: No connected clients."); - return; - } - - // Test to see if we have a 0x2902 descriptor. If we do, then check to see if indications are enabled - // and, if not, prevent the indication. - - BLE2902 *p2902 = (BLE2902*) getDescriptorByUUID((uint16_t) 0x2902); - if (p2902 != nullptr && !p2902->getIndications()) { - ESP_LOGD(LOG_TAG, "<< indications disabled; ignoring"); - return; - } - - if (m_value.getValue().length() > (BLEDevice::getMTU() - 3)) { - ESP_LOGI(LOG_TAG, "- Truncating to %d bytes (maximum indicate size)", BLEDevice::getMTU() - 3); - } - - size_t length = m_value.getValue().length(); - - m_semaphoreConfEvt.take("indicate"); - - esp_err_t errRc = ::esp_ble_gatts_send_indicate( - getService()->getServer()->getGattsIf(), - getService()->getServer()->getConnId(), - getHandle(), length, (uint8_t*)m_value.getValue().data(), true); // The need_confirm = true makes this an indication. - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - m_semaphoreConfEvt.wait("indicate"); + ESP_LOGD(LOG_TAG, ">> indicate: length: %d", m_value.getValue().length()); + notify(false); ESP_LOGD(LOG_TAG, "<< indicate"); } // indicate @@ -525,7 +476,7 @@ void BLECharacteristic::indicate() { * will not block; it is a fire and forget. * @return N/A. */ -void BLECharacteristic::notify() { +void BLECharacteristic::notify(bool is_notification) { ESP_LOGD(LOG_TAG, ">> notify: length: %d", m_value.getValue().length()); assert(getService() != nullptr); @@ -541,31 +492,40 @@ void BLECharacteristic::notify() { // Test to see if we have a 0x2902 descriptor. If we do, then check to see if notification is enabled // and, if not, prevent the notification. - BLE2902 *p2902 = (BLE2902*) getDescriptorByUUID((uint16_t) 0x2902); - if (p2902 != nullptr && !p2902->getNotifications()) { - ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring"); - return; + BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); + if(is_notification) { + if (p2902 != nullptr && !p2902->getNotifications()) { + ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring"); + return; + } } - - if (m_value.getValue().length() > (BLEDevice::getMTU() - 3)) { - ESP_LOGI(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", BLEDevice::getMTU() - 3); + else{ + if (p2902 != nullptr && !p2902->getIndications()) { + ESP_LOGD(LOG_TAG, "<< indications disabled; ignoring"); + return; + } } + for (auto &myPair : getService()->getServer()->getPeerDevices(false)) { + uint16_t _mtu = (myPair.second.mtu); + if (m_value.getValue().length() > _mtu - 3) { + ESP_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu - 3); + } - size_t length = m_value.getValue().length(); - - m_semaphoreConfEvt.take("notify"); - - esp_err_t errRc = ::esp_ble_gatts_send_indicate( - getService()->getServer()->getGattsIf(), - getService()->getServer()->getConnId(), - getHandle(), length, (uint8_t*)m_value.getValue().data(), false); // The need_confirm = false makes this a notify. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + size_t length = m_value.getValue().length(); + if(!is_notification) + m_semaphoreConfEvt.take("indicate"); + esp_err_t errRc = ::esp_ble_gatts_send_indicate( + getService()->getServer()->getGattsIf(), + myPair.first, + getHandle(), length, (uint8_t*)m_value.getValue().data(), !is_notification); // The need_confirm = false makes this a notify. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_ %s: rc=%d %s",is_notification?"notify":"indicate", errRc, GeneralUtils::errorToString(errRc)); + m_semaphoreConfEvt.give(); + return; + } + if(!is_notification) + m_semaphoreConfEvt.wait("indicate"); } - - m_semaphoreConfEvt.wait("notify"); - ESP_LOGD(LOG_TAG, "<< notify"); } // Notify @@ -580,9 +540,9 @@ void BLECharacteristic::notify() { void BLECharacteristic::setBroadcastProperty(bool value) { //ESP_LOGD(LOG_TAG, "setBroadcastProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_BROADCAST); + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_BROADCAST); } else { - m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); } } // setBroadcastProperty @@ -592,7 +552,7 @@ void BLECharacteristic::setBroadcastProperty(bool value) { * @param [in] pCallbacks An instance of a callbacks structure used to define any callbacks for the characteristic. */ void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) { - ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t) pCallbacks); + ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallbacks); m_pCallbacks = pCallbacks; ESP_LOGD(LOG_TAG, "<< setCallbacks"); } // setCallbacks @@ -622,9 +582,9 @@ void BLECharacteristic::setHandle(uint16_t handle) { void BLECharacteristic::setIndicateProperty(bool value) { //ESP_LOGD(LOG_TAG, "setIndicateProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_INDICATE); + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_INDICATE); } else { - m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); } } // setIndicateProperty @@ -636,9 +596,9 @@ void BLECharacteristic::setIndicateProperty(bool value) { void BLECharacteristic::setNotifyProperty(bool value) { //ESP_LOGD(LOG_TAG, "setNotifyProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_NOTIFY); } else { - m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); } } // setNotifyProperty @@ -650,9 +610,9 @@ void BLECharacteristic::setNotifyProperty(bool value) { void BLECharacteristic::setReadProperty(bool value) { //ESP_LOGD(LOG_TAG, "setReadProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_READ); + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_READ); } else { - m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_READ); + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_READ); } } // setReadProperty @@ -683,7 +643,7 @@ void BLECharacteristic::setValue(uint8_t* data, size_t length) { * @return N/A. */ void BLECharacteristic::setValue(std::string value) { - setValue((uint8_t*) (value.data()), value.length()); + setValue((uint8_t*)(value.data()), value.length()); } // setValue void BLECharacteristic::setValue(uint16_t& data16) { @@ -713,13 +673,13 @@ void BLECharacteristic::setValue(int& data32) { void BLECharacteristic::setValue(float& data32) { uint8_t temp[4]; - *((float*) temp) = data32; + *((float*)temp) = data32; setValue(temp, 4); } // setValue void BLECharacteristic::setValue(double& data64) { uint8_t temp[8]; - *((double*) temp) = data64; + *((double*)temp) = data64; setValue(temp, 8); } // setValue @@ -731,9 +691,9 @@ void BLECharacteristic::setValue(double& data64) { void BLECharacteristic::setWriteNoResponseProperty(bool value) { //ESP_LOGD(LOG_TAG, "setWriteNoResponseProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } else { - m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } } // setWriteNoResponseProperty @@ -745,9 +705,9 @@ void BLECharacteristic::setWriteNoResponseProperty(bool value) { void BLECharacteristic::setWriteProperty(bool value) { //ESP_LOGD(LOG_TAG, "setWriteProperty(%d)", value); if (value) { - m_properties = (esp_gatt_char_prop_t) (m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE); + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE); } else { - m_properties = (esp_gatt_char_prop_t) (m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE); } } // setWriteProperty diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index bad5ba7a..49af0ba3 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -27,22 +27,19 @@ class BLECharacteristicCallbacks; */ class BLEDescriptorMap { public: - void setByUUID(const char* uuid, BLEDescriptor* pDescriptor); - void setByUUID(BLEUUID uuid, BLEDescriptor* pDescriptor); + void setByUUID(const char* uuid, BLEDescriptor* pDescriptor); + void setByUUID(BLEUUID uuid, BLEDescriptor* pDescriptor); void setByHandle(uint16_t handle, BLEDescriptor* pDescriptor); BLEDescriptor* getByUUID(const char* uuid); BLEDescriptor* getByUUID(BLEUUID uuid); BLEDescriptor* getByHandle(uint16_t handle); std::string toString(); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); BLEDescriptor* getFirst(); BLEDescriptor* getNext(); private: std::map m_uuidMap; - std::map m_handleMap; + std::map m_handleMap; std::map::iterator m_iterator; }; @@ -50,7 +47,7 @@ class BLEDescriptorMap { /** * @brief The model of a %BLE Characteristic. * - * A %BLE Characteristic is an identified value container that manages a value. It is exposed by a %BLE server and + * A BLE Characteristic is an identified value container that manages a value. It is exposed by a BLE server and * can be read and written to by a %BLE client. */ class BLECharacteristic { @@ -59,16 +56,15 @@ class BLECharacteristic { BLECharacteristic(BLEUUID uuid, uint32_t properties = 0); virtual ~BLECharacteristic(); - void addDescriptor(BLEDescriptor* pDescriptor); + void addDescriptor(BLEDescriptor* pDescriptor); BLEDescriptor* getDescriptorByUUID(const char* descriptorUUID); BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); - //size_t getLength(); - BLEUUID getUUID(); - std::string getValue(); - uint8_t* getData(); + BLEUUID getUUID(); + std::string getValue(); + uint8_t* getData(); void indicate(); - void notify(); + void notify(bool is_notification = true); void setBroadcastProperty(bool value); void setCallbacks(BLECharacteristicCallbacks* pCallbacks); void setIndicateProperty(bool value); @@ -80,19 +76,19 @@ class BLECharacteristic { void setValue(uint32_t& data32); void setValue(int& data32); void setValue(float& data32); - void setValue(double& data64); + void setValue(double& data64); void setWriteProperty(bool value); void setWriteNoResponseProperty(bool value); std::string toString(); uint16_t getHandle(); void setAccessPermissions(esp_gatt_perm_t perm); - static const uint32_t PROPERTY_READ = 1 << 0; - static const uint32_t PROPERTY_WRITE = 1 << 1; - static const uint32_t PROPERTY_NOTIFY = 1 << 2; - static const uint32_t PROPERTY_BROADCAST = 1 << 3; - static const uint32_t PROPERTY_INDICATE = 1 << 4; - static const uint32_t PROPERTY_WRITE_NR = 1 << 5; + static const uint32_t PROPERTY_READ = 1<<0; + static const uint32_t PROPERTY_WRITE = 1<<1; + static const uint32_t PROPERTY_NOTIFY = 1<<2; + static const uint32_t PROPERTY_BROADCAST = 1<<3; + static const uint32_t PROPERTY_INDICATE = 1<<4; + static const uint32_t PROPERTY_WRITE_NR = 1<<5; private: @@ -101,24 +97,24 @@ class BLECharacteristic { friend class BLEDescriptor; friend class BLECharacteristicMap; - BLEUUID m_bleUUID; - BLEDescriptorMap m_descriptorMap; - uint16_t m_handle; - esp_gatt_char_prop_t m_properties; + BLEUUID m_bleUUID; + BLEDescriptorMap m_descriptorMap; + uint16_t m_handle; + esp_gatt_char_prop_t m_properties; BLECharacteristicCallbacks* m_pCallbacks; - BLEService* m_pService; - BLEValue m_value; - esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + BLEService* m_pService; + BLEValue m_value; + esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); - void executeCreate(BLEService* pService); + void executeCreate(BLEService* pService); esp_gatt_char_prop_t getProperties(); - BLEService* getService(); - void setHandle(uint16_t handle); + BLEService* getService(); + void setHandle(uint16_t handle); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt"); }; // BLECharacteristic diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 2a8d6f0c..4408b140 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -18,6 +18,7 @@ #include #include #include +#include "BLEDevice.h" #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" #endif @@ -40,13 +41,14 @@ * We will assume that a BLERemoteService contains a map that maps BLEUUIDs to the set of owned characteristics * and that a BLECharacteristic contains a map that maps BLEUUIDs to the set of owned descriptors. * + * */ static const char* LOG_TAG = "BLEClient"; BLEClient::BLEClient() { m_pClientCallbacks = nullptr; - m_conn_id = 0; - m_gattc_if = 0; + m_conn_id = ESP_GATT_IF_NONE; + m_gattc_if = ESP_GATT_IF_NONE; m_haveServices = false; m_isConnected = false; // Initially, we are flagged as not connected. } // BLEClient @@ -80,22 +82,31 @@ void BLEClient::clearServices() { ESP_LOGD(LOG_TAG, "<< clearServices"); } // clearServices +/** + * Add overloaded function to ease connect to peer device with not public address + */ +bool BLEClient::connect(BLEAdvertisedDevice* device) { + BLEAddress address = device->getAddress(); + esp_ble_addr_type_t type = device->getAddressType(); + return connect(address, type); +} /** * @brief Connect to the partner (BLE Server). * @param [in] address The address of the partner. * @return True on success. */ -bool BLEClient::connect(BLEAddress address) { +bool BLEClient::connect(BLEAddress address, esp_ble_addr_type_t type) { ESP_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str()); // We need the connection handle that we get from registering the application. We register the app // and then block on its completion. When the event has arrived, we will have the handle. + m_appId = BLEDevice::m_appId++; + BLEDevice::addPeerDevice(this, true, ESP_GATT_IF_NONE); m_semaphoreRegEvt.take("connect"); - clearServices(); // Delete any services that may exist. - - esp_err_t errRc = ::esp_ble_gattc_app_register(0); + // clearServices(); // we dont need to delete services since every client is unique? + esp_err_t errRc = ::esp_ble_gattc_app_register(m_appId); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return false; @@ -110,7 +121,7 @@ bool BLEClient::connect(BLEAddress address) { errRc = ::esp_ble_gattc_open( getGattcIf(), *getPeerAddress().getNative(), // address - BLE_ADDR_TYPE_PUBLIC, // Note: This was added on 2018-04-03 when the latest ESP-IDF was detected to have changed the signature. + type, // Note: This was added on 2018-04-03 when the latest ESP-IDF was detected to have changed the signature. 1 // direct connection ); if (errRc != ESP_OK) { @@ -135,8 +146,6 @@ void BLEClient::disconnect() { ESP_LOGE(LOG_TAG, "esp_ble_gattc_close: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } - esp_ble_gattc_app_unregister(getGattcIf()); - m_peerAddress = BLEAddress("00:00:00:00:00:00"); ESP_LOGD(LOG_TAG, "<< disconnect()"); } // disconnect @@ -149,8 +158,21 @@ void BLEClient::gattClientEventHandler( esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { + ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", + gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); + // Execute handler code based on the type of event received. - switch (event) { + switch(event) { + + case ESP_GATTC_SRVC_CHG_EVT: + ESP_LOGI(LOG_TAG, "SERVICE CHANGED"); + break; + + case ESP_GATTC_CLOSE_EVT: { + // esp_ble_gattc_app_unregister(m_appId); + // BLEDevice::removePeerDevice(m_gattc_if, true); + break; + } // // ESP_GATTC_DISCONNECT_EVT @@ -178,7 +200,6 @@ void BLEClient::gattClientEventHandler( // - esp_gatt_status_t status // - uint16_t conn_id // - esp_bd_addr_t remote_bda - // - uint16_t mtu // case ESP_GATTC_OPEN_EVT: { m_conn_id = evtParam->open.conn_id; @@ -206,6 +227,26 @@ void BLEClient::gattClientEventHandler( break; } // ESP_GATTC_REG_EVT + case ESP_GATTC_CFG_MTU_EVT: + if(evtParam->cfg_mtu.status != ESP_GATT_OK) { + ESP_LOGE(LOG_TAG,"Config mtu failed"); + } + m_mtu = evtParam->cfg_mtu.mtu; + break; + + case ESP_GATTC_CONNECT_EVT: { + BLEDevice::updatePeerDevice(this, true, m_gattc_if); + esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, evtParam->connect.conn_id); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(evtParam->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTC_CONNECT_EVT // // ESP_GATTC_SEARCH_CMPL_EVT @@ -215,6 +256,20 @@ void BLEClient::gattClientEventHandler( // - uint16_t conn_id // case ESP_GATTC_SEARCH_CMPL_EVT: { + esp_ble_gattc_cb_param_t* p_data = (esp_ble_gattc_cb_param_t*)evtParam; + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(LOG_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } +#ifndef ARDUINO_ARCH_ESP32 + if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) { + ESP_LOGI(LOG_TAG, "Get service information from remote device"); + } else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) { + ESP_LOGI(LOG_TAG, "Get service information from flash"); + } else { + ESP_LOGI(LOG_TAG, "unknown service source"); + } +#endif m_semaphoreSearchCmplEvt.give(0); break; } // ESP_GATTC_SEARCH_CMPL_EVT @@ -238,6 +293,7 @@ void BLEClient::gattClientEventHandler( evtParam->search_res.end_handle ); m_servicesMap.insert(std::pair(uuid.toString(), pRemoteService)); + m_servicesMapByInstID.insert(std::pair(pRemoteService, evtParam->search_res.srvc_id.inst_id)); break; } // ESP_GATTC_SEARCH_RES_EVT @@ -336,7 +392,7 @@ BLERemoteService* BLEClient::getService(BLEUUID uuid) { } } // End of each of the services. ESP_LOGD(LOG_TAG, "<< getService: not found"); - throw new BLEUuidNotFoundException; + return nullptr; } // getService @@ -355,20 +411,22 @@ std::map* BLEClient::getServices() { * and will culminate with an ESP_GATTC_SEARCH_CMPL_EVT when all have been received. */ ESP_LOGD(LOG_TAG, ">> getServices"); - +// TODO implement retrieving services from cache clearServices(); // Clear any services that may exist. - esp_err_t errRc = esp_ble_gattc_search_service( + ESP_LOGD(LOG_TAG, "esp_ble_gattc_get_service: %d services from peer device", count); + errRc = esp_ble_gattc_search_service( getGattcIf(), getConnId(), - nullptr // Filter UUID + NULL // Filter UUID ); + m_semaphoreSearchCmplEvt.take("getServices"); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_search_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return &m_servicesMap; } - // If successful, remember that we now have services. + // If sucessfull, remember that we now have services. m_haveServices = (m_semaphoreSearchCmplEvt.wait("getServices") == 0); ESP_LOGD(LOG_TAG, "<< getServices"); return &m_servicesMap; @@ -450,6 +508,9 @@ void BLEClient::setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::s ESP_LOGD(LOG_TAG, "<< setValue"); } // setValue +uint16_t BLEClient::getMTU() { + return m_mtu; +} /** * @brief Return a string representation of this client. diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index d66a8b4f..1b8144d5 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -19,9 +19,11 @@ #include "BLERemoteService.h" #include "BLEService.h" #include "BLEAddress.h" +#include "BLEAdvertisedDevice.h" class BLERemoteService; class BLEClientCallbacks; +class BLEAdvertisedDevice; /** * @brief A model of a %BLE client. @@ -31,7 +33,8 @@ class BLEClient { BLEClient(); ~BLEClient(); - bool connect(BLEAddress address); // Connect to the remote BLE Server + bool connect(BLEAdvertisedDevice* device); + bool connect(BLEAddress address, esp_ble_addr_type_t type = BLE_ADDR_TYPE_PUBLIC); // Connect to the remote BLE Server void disconnect(); // Disconnect from the remote BLE Server BLEAddress getPeerAddress(); // Get the address of the remote BLE Server int getRssi(); // Get the RSSI of the remote BLE Server @@ -47,12 +50,15 @@ class BLEClient { bool isConnected(); // Return true if we are connected. + void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a given characteristic at a given service. - void setClientCallbacks(BLEClientCallbacks* pClientCallbacks); std::string toString(); // Return a string representation of this client. + uint16_t getConnId(); + esp_gatt_if_t getGattcIf(); + uint16_t getMTU(); - +uint16_t m_appId; private: friend class BLEDevice; friend class BLERemoteService; @@ -64,14 +70,12 @@ class BLEClient { esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param); - uint16_t getConnId(); - esp_gatt_if_t getGattcIf(); + BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); // The BD address of the remote server. uint16_t m_conn_id; // int m_deviceType; - BLEAddress m_peerAddress = BLEAddress((uint8_t*) "\0\0\0\0\0\0"); // The BD address of the remote server. esp_gatt_if_t m_gattc_if; - bool m_haveServices; // Have we previously obtain the set of services from the remote server. - bool m_isConnected; // Are we currently connected. + bool m_haveServices = false; // Have we previously obtain the set of services from the remote server. + bool m_isConnected = false; // Are we currently connected. BLEClientCallbacks* m_pClientCallbacks; FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); @@ -79,7 +83,9 @@ class BLEClient { FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt"); FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt"); std::map m_servicesMap; + std::map m_servicesMapByInstID; void clearServices(); // Clear any existing services. + uint16_t m_mtu = 23; }; // class BLEDevice diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 77380d2b..add7e41e 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -41,11 +41,16 @@ static const char* LOG_TAG = "BLEDevice"; BLEServer* BLEDevice::m_pServer = nullptr; BLEScan* BLEDevice::m_pScan = nullptr; BLEClient* BLEDevice::m_pClient = nullptr; -bool initialized = false; // Have we been initialized? -esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t) 0; +bool initialized = false; +esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; -uint16_t BLEDevice::m_localMTU = 23; +uint16_t BLEDevice::m_localMTU = 23; // not sure if this variable is useful BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; +uint16_t BLEDevice::m_appId = 0; +std::map BLEDevice::m_connectedClientsMap; +gap_event_handler BLEDevice::m_customGapHandler = nullptr; +gattc_event_handler BLEDevice::m_customGattcHandler = nullptr; +gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; /** * @brief Create a new instance of a client. @@ -74,7 +79,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; abort(); #endif // CONFIG_GATTS_ENABLE m_pServer = new BLEServer(); - m_pServer->createApp(0); + m_pServer->createApp(m_appId++); ESP_LOGD(LOG_TAG, "<< createServer"); return m_pServer; } // createServer @@ -100,20 +105,14 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; switch (event) { case ESP_GATTS_CONNECT_EVT: { - BLEDevice::m_localMTU = 23; #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityLevel) { + if(BLEDevice::m_securityLevel){ esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); } #endif // CONFIG_BLE_SMP_ENABLE break; } // ESP_GATTS_CONNECT_EVT - case ESP_GATTS_MTU_EVT: { - BLEDevice::m_localMTU = param->mtu.mtu; - ESP_LOGI(LOG_TAG, "ESP_GATTS_MTU_EVT, MTU %d", BLEDevice::m_localMTU); - break; - } default: { break; } @@ -123,6 +122,11 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; if (BLEDevice::m_pServer != nullptr) { BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); } + + if(m_customGattsHandler != nullptr) { + m_customGattsHandler(event, gatts_if, param); + } + } // gattServerEventHandler @@ -146,30 +150,29 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; switch(event) { case ESP_GATTC_CONNECT_EVT: { - if (BLEDevice::getMTU() != 23) { - esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, param->connect.conn_id); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - } #ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityLevel) { + if(BLEDevice::m_securityLevel){ esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); } #endif // CONFIG_BLE_SMP_ENABLE break; - } // ESP_GATTC_CONNECT_EVT + } // ESP_GATTS_CONNECT_EVT default: break; } // switch + for(auto &myPair : BLEDevice::getPeerDevices(true)) { + conn_status_t conn_status = (conn_status_t)myPair.second; + if(((BLEClient*)conn_status.peer_device)->getGattcIf() == gattc_if || ((BLEClient*)conn_status.peer_device)->getGattcIf() == ESP_GATT_IF_NONE || gattc_if == ESP_GATT_IF_NONE){ + ((BLEClient*)conn_status.peer_device)->gattClientEventHandler(event, gattc_if, param); + } + } - - // If we have a client registered, call it. - if (BLEDevice::m_pClient != nullptr) { - BLEDevice::m_pClient->gattClientEventHandler(event, gattc_if, param); + if(m_customGattcHandler != nullptr) { + m_customGattcHandler(event, gattc_if, param); } + } // gattClientEventHandler @@ -178,33 +181,34 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; */ /* STATIC */ void BLEDevice::gapEventHandler( esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param) { + esp_ble_gap_cb_param_t *param) { BLEUtils::dumpGapEvent(event, param); - switch (event) { - case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); - break; - case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); - break; - case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); - break; - case ESP_GAP_BLE_NC_REQ_EVT: - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { - esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); + switch(event) { + + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + break; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); + break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); + break; + case ESP_GAP_BLE_NC_REQ_EVT: /* NUMERIC CONFIRMATION */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks != nullptr){ + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); } #endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks != nullptr){ esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); } #endif // CONFIG_BLE_SMP_ENABLE @@ -212,14 +216,15 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; /* * TODO should we add white/black list comparison? */ - case ESP_GAP_BLE_SEC_REQ_EVT: - /* send the positive(true) security response to the peer device to accept the security request. - If not accept the security request, should sent the security response with negative(false) accept value*/ - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { + case ESP_GAP_BLE_SEC_REQ_EVT: + /* send the positive(true) security response to the peer device to accept the security request. + If not accept the security request, should sent the security response with negative(false) accept value*/ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); - } else { + } + else{ esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); } #endif // CONFIG_BLE_SMP_ENABLE @@ -227,39 +232,36 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; /* * */ - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. - // show the passkey number to the user to input it in the peer device. - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { - ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: //the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + //display the passkey number to the user to input it in the peer deivce within 30 seconds + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); + if(BLEDevice::m_securityCallbacks!=nullptr){ BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); } #endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_KEY_EVT: - //shows the ble key type info share with peer device to the user. - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); + break; + case ESP_GAP_BLE_KEY_EVT: + //shows the ble key type info share with peer device to the user. + ESP_LOGD(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); #endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_AUTH_CMPL_EVT: - ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { - BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); - } + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks != nullptr){ + BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); + } #endif // CONFIG_BLE_SMP_ENABLE - break; - default: + break; + default: { break; + } } // switch - if (BLEDevice::m_pServer != nullptr) { - BLEDevice::m_pServer->handleGAPEvent(event, param); - } - if (BLEDevice::m_pClient != nullptr) { BLEDevice::m_pClient->handleGAPEvent(event, param); } @@ -268,10 +270,13 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; BLEDevice::getScan()->handleGAPEvent(event, param); } - /* - * Security events: - */ + if(m_bleAdvertising != nullptr) { + BLEDevice::getAdvertising()->gapEventHandler(event, param); + } + if(m_customGapHandler != nullptr) { + BLEDevice::m_customGapHandler(event, param); + } } // gapEventHandler @@ -326,7 +331,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; * @param deviceName The device name of the device. */ /* STATIC */ void BLEDevice::init(std::string deviceName) { - if (!initialized) { + if(!initialized){ initialized = true; // Set the initialization flag to ensure we are only initialized once. esp_err_t errRc = ESP_OK; @@ -342,6 +347,9 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; return; } +#ifndef CLASSIC_BT_ENABLED + esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); +#endif esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); errRc = esp_bt_controller_init(&bt_cfg); if (errRc != ESP_OK) { @@ -350,9 +358,7 @@ BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; } #ifndef CLASSIC_BT_ENABLED - // esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); //FIXME waiting for response from esp-idf issue errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); - //errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; @@ -544,12 +550,12 @@ bool BLEDevice::getInitialized() { } BLEAdvertising* BLEDevice::getAdvertising() { - if (m_bleAdvertising == nullptr) { + if(m_bleAdvertising == nullptr) { m_bleAdvertising = new BLEAdvertising(); ESP_LOGI(LOG_TAG, "create advertising"); } ESP_LOGD(LOG_TAG, "get advertising"); - return m_bleAdvertising; + return m_bleAdvertising; } void BLEDevice::startAdvertising() { @@ -558,4 +564,80 @@ void BLEDevice::startAdvertising() { ESP_LOGD(LOG_TAG, "<< startAdvertising"); } // startAdvertising +/* multi connect support */ +/* requires a little more work */ +std::map BLEDevice::getPeerDevices(bool _client) { + return m_connectedClientsMap; +} + +BLEClient* BLEDevice::getClientByGattIf(uint16_t conn_id) { + return (BLEClient*)m_connectedClientsMap.find(conn_id)->second.peer_device; +} + +void BLEDevice::updatePeerDevice(void* peer, bool _client, uint16_t conn_id) { + ESP_LOGD(LOG_TAG, "update conn_id: %d, GATT role: %s", conn_id, _client? "client":"server"); + std::map::iterator it = m_connectedClientsMap.find(ESP_GATT_IF_NONE); + if (it != m_connectedClientsMap.end()) { + std::swap(m_connectedClientsMap[conn_id], it->second); + m_connectedClientsMap.erase(it); + }else{ + it = m_connectedClientsMap.find(conn_id); + if (it != m_connectedClientsMap.end()) { + conn_status_t _st = it->second; + _st.peer_device = peer; + std::swap(m_connectedClientsMap[conn_id], _st); + } + } +} + +void BLEDevice::addPeerDevice(void* peer, bool _client, uint16_t conn_id) { + ESP_LOGI(LOG_TAG, "add conn_id: %d, GATT role: %s", conn_id, _client? "client":"server"); + conn_status_t status = { + .peer_device = peer, + .connected = true, + .mtu = 23 + }; + + m_connectedClientsMap.insert(std::pair(conn_id, status)); +} + +void BLEDevice::removePeerDevice(uint16_t conn_id, bool _client) { + ESP_LOGI(LOG_TAG, "remove: %d, GATT role %s", conn_id, _client?"client":"server"); + m_connectedClientsMap.erase(conn_id); +} + +/* multi connect support */ + +/** + * @brief de-Initialize the %BLE environment. + * @param release_memory release the internal BT stack memory + */ +/* STATIC */ void BLEDevice::deinit(bool release_memory) { + if (!initialized) return; + + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); +#ifndef ARDUINO_ARCH_ESP32 + if (release_memory) { + esp_bt_mem_release(ESP_BT_MODE_BTDM); // <-- require tests because we released classic BT memory and this can cause crash (most likely not, esp-idf takes care of it) + } else { + initialized = false; + } +#endif +} + +void BLEDevice::setCustomGapHandler(gap_event_handler handler) { + m_customGapHandler = handler; +} + +void BLEDevice::setCustomGattcHandler(gattc_event_handler handler) { + m_customGattcHandler = handler; +} + +void BLEDevice::setCustomGattsHandler(gatts_event_handler handler) { + m_customGattsHandler = handler; +} + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 9fd53ebb..e9cd40a3 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -22,8 +22,12 @@ #include "BLEAddress.h" /** - * @brief %BLE functions. + * @brief BLE functions. */ +typedef void (*gap_event_handler)(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); +typedef void (*gattc_event_handler)(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param); +typedef void (*gatts_event_handler)(esp_gatts_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gatts_cb_param_t* param); + class BLEDevice { public: @@ -43,19 +47,31 @@ class BLEDevice { static esp_err_t setMTU(uint16_t mtu); static uint16_t getMTU(); static bool getInitialized(); // Returns the state of the device, is it initialized or not? - static BLEAdvertising* getAdvertising(); - static void startAdvertising(); + /* move advertising to BLEDevice for saving ram and flash in beacons */ + static BLEAdvertising* getAdvertising(); + static void startAdvertising(); + static uint16_t m_appId; + /* multi connect */ + static std::map getPeerDevices(bool client); + static void addPeerDevice(void* peer, bool is_client, uint16_t conn_id); + static void updatePeerDevice(void* peer, bool _client, uint16_t conn_id); + static void removePeerDevice(uint16_t conn_id, bool client); + static BLEClient* getClientByGattIf(uint16_t conn_id); + static void setCustomGapHandler(gap_event_handler handler); + static void setCustomGattcHandler(gattc_event_handler handler); + static void setCustomGattsHandler(gatts_event_handler handler); + static void deinit(bool release_memory = false); + static uint16_t m_localMTU; + static esp_ble_sec_act_t m_securityLevel; private: - static BLEServer* m_pServer; - static BLEScan* m_pScan; - static BLEClient* m_pClient; - static esp_ble_sec_act_t m_securityLevel; + static BLEServer* m_pServer; + static BLEScan* m_pScan; + static BLEClient* m_pClient; static BLESecurityCallbacks* m_securityCallbacks; - static uint16_t m_localMTU; - static BLEAdvertising* m_bleAdvertising; - + static BLEAdvertising* m_bleAdvertising; static esp_gatt_if_t getGattcIF(); + static std::map m_connectedClientsMap; static void gattClientEventHandler( esp_gattc_cb_event_t event, @@ -71,6 +87,12 @@ class BLEDevice { esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); +public: +/* custom gap and gatt handlers for flexibility */ + static gap_event_handler m_customGapHandler; + static gattc_event_handler m_customGattcHandler; + static gatts_event_handler m_customGattsHandler; + }; // class BLE #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 14177eac..1768dbb6 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -147,7 +147,7 @@ static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { * @returns N/A */ void BLERemoteCharacteristic::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { - switch (event) { + switch(event) { // ESP_GATTC_NOTIFY_EVT // // notify @@ -187,8 +187,7 @@ void BLERemoteCharacteristic::gattClientEventHandler(esp_gattc_cb_event_t event, // and unlock the semaphore to ensure that the requestor of the data can continue. if (evtParam->read.status == ESP_GATT_OK) { m_value = std::string((char*) evtParam->read.value, evtParam->read.value_len); - if (m_rawData != nullptr) free(m_rawData); - + if(m_rawData != nullptr) free(m_rawData); m_rawData = (uint8_t*) calloc(evtParam->read.value_len, sizeof(uint8_t)); memcpy(m_rawData, evtParam->read.value, evtParam->read.value_len); } else { @@ -260,8 +259,8 @@ void BLERemoteCharacteristic::retrieveDescriptors() { // For each descriptor we find, create a BLERemoteDescriptor instance. uint16_t offset = 0; esp_gattc_descr_elem_t result; - while (true) { - uint16_t count = 1; + while(true) { + uint16_t count = 10; esp_gatt_status_t status = ::esp_ble_gattc_get_all_descr( getRemoteService()->getClient()->getGattcIf(), getRemoteService()->getClient()->getConnId(), @@ -281,7 +280,7 @@ void BLERemoteCharacteristic::retrieveDescriptors() { } if (count == 0) break; - ESP_LOGE(LOG_TAG, ""); + ESP_LOGD(LOG_TAG, "Found a descriptor: Handle: %d, UUID: %s", result.handle, BLEUUID(result.uuid).toString().c_str()); // We now have a new characteristic ... let us add that to our set of known characteristics @@ -327,7 +326,7 @@ uint16_t BLERemoteCharacteristic::getHandle() { BLERemoteDescriptor* BLERemoteCharacteristic::getDescriptor(BLEUUID uuid) { ESP_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str()); std::string v = uuid.toString(); - for (auto& myPair : m_descriptorMap) { + for (auto &myPair : m_descriptorMap) { if (myPair.first == v) { ESP_LOGD(LOG_TAG, "<< getDescriptor: found"); return myPair.second; @@ -363,7 +362,7 @@ BLEUUID BLERemoteCharacteristic::getUUID() { uint16_t BLERemoteCharacteristic::readUInt16() { std::string value = readValue(); if (value.length() >= 2) { - return *(uint16_t*) value.data(); + return *(uint16_t*)(value.data()); } return 0; } // readUInt16 @@ -376,7 +375,7 @@ uint16_t BLERemoteCharacteristic::readUInt16() { uint32_t BLERemoteCharacteristic::readUInt32() { std::string value = readValue(); if (value.length() >= 4) { - return *(uint32_t*) value.data(); + return *(uint32_t*)(value.data()); } return 0; } // readUInt32 @@ -389,7 +388,7 @@ uint32_t BLERemoteCharacteristic::readUInt32() { uint8_t BLERemoteCharacteristic::readUInt8() { std::string value = readValue(); if (value.length() >= 1) { - return (uint8_t) value[0]; + return (uint8_t)value[0]; } return 0; } // readUInt8 @@ -439,12 +438,7 @@ std::string BLERemoteCharacteristic::readValue() { * unregistering a notification. * @return N/A. */ -void BLERemoteCharacteristic::registerForNotify( - void (*notifyCallback)( - BLERemoteCharacteristic* pBLERemoteCharacteristic, - uint8_t* pData, - size_t length, - bool isNotify), bool notifications) { +void BLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, bool notifications) { ESP_LOGD(LOG_TAG, ">> registerForNotify(): %s", toString().c_str()); m_notifyCallback = notifyCallback; // Save the notification callback. @@ -463,8 +457,8 @@ void BLERemoteCharacteristic::registerForNotify( } uint8_t val[] = {0x01, 0x00}; - if (!notifications) val[0] = 0x02; - BLERemoteDescriptor* desc = getDescriptor(BLEUUID("0x2902")); + if(!notifications) val[0] = 0x02; + BLERemoteDescriptor* desc = getDescriptor(BLEUUID((uint16_t)0x2902)); desc->writeValue(val, 2); } // End Register else { // If we weren't passed a callback function, then this is an unregistration. @@ -535,7 +529,6 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { } m_semaphoreWriteCharEvt.take("writeValue"); - // Invoke the ESP-IDF API to perform the write. esp_err_t errRc = ::esp_ble_gattc_write_char( m_pRemoteService->getClient()->getGattcIf(), @@ -578,7 +571,7 @@ void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { * @param [in] response Whether we require a response from the write. */ void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) { - writeValue(std::string((char*) data, length), response); + writeValue(std::string((char*)data, length), response); } // writeValue /** diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 0750c1a9..fbcafe8d 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -21,6 +21,7 @@ class BLERemoteService; class BLERemoteDescriptor; +typedef void (*notify_callback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify); /** * @brief A model of a remote %BLE characteristic. @@ -37,18 +38,18 @@ class BLERemoteCharacteristic { bool canWrite(); bool canWriteNoResponse(); BLERemoteDescriptor* getDescriptor(BLEUUID uuid); + std::map* getDescriptors(); uint16_t getHandle(); BLEUUID getUUID(); - std::map* getDescriptors(); - std::string readValue(void); - uint8_t readUInt8(void); - uint16_t readUInt16(void); - uint32_t readUInt32(void); - void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify), bool notifications = true); + std::string readValue(); + uint8_t readUInt8(); + uint16_t readUInt16(); + uint32_t readUInt32(); + void registerForNotify(notify_callback _callback, bool notifications = true); void writeValue(uint8_t* data, size_t length, bool response = false); void writeValue(std::string newValue, bool response = false); void writeValue(uint8_t newValue, bool response = false); - std::string toString(void); + std::string toString(); uint8_t* readRawData(); private: @@ -58,26 +59,23 @@ class BLERemoteCharacteristic { friend class BLERemoteDescriptor; // Private member functions - void gattClientEventHandler( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t* evtParam); + void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); BLERemoteService* getRemoteService(); - void removeDescriptors(); - void retrieveDescriptors(); + void removeDescriptors(); + void retrieveDescriptors(); // Private properties - BLEUUID m_uuid; + BLEUUID m_uuid; esp_gatt_char_prop_t m_charProp; - uint16_t m_handle; - BLERemoteService* m_pRemoteService; - FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt"); + uint16_t m_handle; + BLERemoteService* m_pRemoteService; + FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt"); FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt"); - FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); - std::string m_value; - uint8_t* m_rawData; - void (*m_notifyCallback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify); + FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); + std::string m_value; + uint8_t *m_rawData; + notify_callback m_notifyCallback; // We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID. std::map m_descriptorMap; diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index d632cff5..ef349986 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -126,10 +126,9 @@ void BLERemoteService::gattClientEventHandler( * @throws BLEUuidNotFoundException */ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) { - return getCharacteristic(BLEUUID(uuid)); + return getCharacteristic(BLEUUID(uuid)); } // getCharacteristic - - + /** * @brief Get the characteristic object for the UUID. * @param [in] uuid Characteristic uuid. @@ -152,7 +151,8 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { return myPair.second; } } - throw new BLEUuidNotFoundException(); + // throw new BLEUuidNotFoundException(); // <-- we dont want exception here, which will cause app crash, we want to search if any characteristic can be found one after another + return nullptr; } // getCharacteristic @@ -169,7 +169,7 @@ void BLERemoteService::retrieveCharacteristics() { uint16_t offset = 0; esp_gattc_char_elem_t result; while (true) { - uint16_t count = 1; + uint16_t count = 10; // this value is used as in parameter that allows to search max 10 chars with the same uuid esp_gatt_status_t status = ::esp_ble_gattc_get_all_char( getClient()->getGattcIf(), getClient()->getConnId(), diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 387afbdf..0f45bd0a 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -33,7 +33,7 @@ class BLERemoteService { BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); // Get the specified characteristic reference. BLERemoteCharacteristic* getCharacteristic(uint16_t uuid); // Get the specified characteristic reference. std::map* getCharacteristics(); - void getCharacteristics(std::map* pCharacteristicMap); // Get the characteristics map. + std::map* getCharacteristicsByHandle(); // Get the characteristics map. BLEClient* getClient(void); // Get a reference to the client associated with this service. uint16_t getHandle(); // Get the handle of this service. diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index e5c675b8..63962027 100755 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -10,8 +10,7 @@ #include #include #include -#include -//#include +#include "GeneralUtils.h" #include "BLEDevice.h" #include "BLEServer.h" #include "BLEService.h" @@ -33,18 +32,17 @@ static const char* LOG_TAG = "BLEServer"; * the BLEDevice class. */ BLEServer::BLEServer() { - m_appId = -1; - m_gatts_if = -1; + m_appId = ESP_GATT_IF_NONE; + m_gatts_if = ESP_GATT_IF_NONE; m_connectedCount = 0; - m_connId = -1; + m_connId = ESP_GATT_IF_NONE; m_pServerCallbacks = nullptr; - //createApp(0); } // BLEServer void BLEServer::createApp(uint16_t appId) { m_appId = appId; - registerApp(); + registerApp(appId); } // createApp @@ -79,12 +77,10 @@ BLEService* BLEServer::createService(BLEUUID uuid, uint32_t numHandles, uint8_t if (m_serviceMap.getByUUID(uuid) != nullptr) { ESP_LOGW(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.", uuid.toString().c_str()); - //m_semaphoreCreateEvt.give(); - //return nullptr; } BLEService* pService = new BLEService(uuid, numHandles); - pService->m_id = inst_id; + pService->m_instId = inst_id; m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server. pService->executeCreate(this); // Perform the API calls to actually create the service. @@ -104,7 +100,6 @@ BLEService* BLEServer::getServiceByUUID(const char* uuid) { return m_serviceMap.getByUUID(uuid); } - /** * @brief Get a %BLE Service by its UUID * @param [in] uuid The UUID of the new service. @@ -114,20 +109,6 @@ BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { return m_serviceMap.getByUUID(uuid); } - -/** - * @brief Returns the amount of services registered to this server - * @param [in] includeDefaultServices Add the amount of default BluetoothLE services defined by the BLE standard - * @return The amount of registered services - */ -int BLEServer::getServiceCount(bool includeDefaultServices) { - if (includeDefaultServices) { - return m_serviceMap.getRegisteredServiceCount() + 2; - } - return m_serviceMap.getRegisteredServiceCount(); -} - - /** * @brief Retrieve the advertising object that can be used to advertise the existence of the server. * @@ -137,7 +118,6 @@ BLEAdvertising* BLEServer::getAdvertising() { return BLEDevice::getAdvertising(); } - uint16_t BLEServer::getConnId() { return m_connId; } @@ -156,41 +136,6 @@ uint16_t BLEServer::getGattsIf() { return m_gatts_if; } -/** - * @brief Handle a received GAP event. - * - * @param [in] event - * @param [in] param - */ -void BLEServer::handleGAPEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param) { - ESP_LOGD(LOG_TAG, "BLEServer ... handling GAP event!"); - switch (event) { - case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { - /* - esp_ble_adv_params_t adv_params; - adv_params.adv_int_min = 0x20; - adv_params.adv_int_max = 0x40; - adv_params.adv_type = ADV_TYPE_IND; - adv_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; - adv_params.channel_map = ADV_CHNL_ALL; - adv_params.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; - ESP_LOGD(tag, "Starting advertising"); - esp_err_t errRc = ::esp_ble_gap_start_advertising(&adv_params); - if (errRc != ESP_OK) { - ESP_LOGE(tag, "esp_ble_gap_start_advertising: rc=%d %s", errRc, espToString(errRc)); - return; - } - */ - break; - } - - default: - break; - } -} // handleGAPEvent - /** * @brief Handle a GATT Server Event. @@ -201,12 +146,10 @@ void BLEServer::handleGAPEvent( * */ void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { - ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); - - // Invoke the handler for every Service we have. - m_serviceMap.handleGATTServerEvent(event, gatts_if, param); + ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", + BLEUtils::gattServerEventTypeToString(event).c_str()); - switch (event) { + switch(event) { // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. // add_char: // - esp_gatt_status_t status @@ -218,20 +161,23 @@ void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t break; } // ESP_GATTS_ADD_CHAR_EVT + case ESP_GATTS_MTU_EVT: + updatePeerMTU(param->mtu.conn_id, param->mtu.mtu); + break; // ESP_GATTS_CONNECT_EVT // connect: // - uint16_t conn_id // - esp_bd_addr_t remote_bda - // - bool is_connected // case ESP_GATTS_CONNECT_EVT: { - m_connId = param->connect.conn_id; // Save the connection id. + m_connId = param->connect.conn_id; + addPeerDevice((void*)this, false, m_connId); if (m_pServerCallbacks != nullptr) { m_pServerCallbacks->onConnect(this); - m_pServerCallbacks->onConnect(this, param); + m_pServerCallbacks->onConnect(this, param); } - m_connectedCount++; // Increment the number of connected devices count. + m_connectedCount++; // Increment the number of connected devices count. break; } // ESP_GATTS_CONNECT_EVT @@ -245,7 +191,7 @@ void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t // * esp_gatt_srvc_id_t service_id // case ESP_GATTS_CREATE_EVT: { - BLEService* pService = m_serviceMap.getByUUID(param->create.service_id.id.uuid); + BLEService* pService = m_serviceMap.getByUUID(param->create.service_id.id.uuid, param->create.service_id.id.inst_id); // <--- very big bug for multi services with the same uuid m_serviceMap.setByHandle(param->create.service_handle, pService); m_semaphoreCreateEvt.give(); break; @@ -255,9 +201,9 @@ void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t // ESP_GATTS_DISCONNECT_EVT // // disconnect - // - uint16_t conn_id - // - esp_bd_addr_t remote_bda - // - bool is_connected + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + // - esp_gatt_conn_reason_t reason // // If we receive a disconnect event then invoke the callback for disconnects (if one is present). // we also want to start advertising again. @@ -267,6 +213,7 @@ void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t m_pServerCallbacks->onDisconnect(this); } startAdvertising(); //- do this with some delay from the loop() + removePeerDevice(param->disconnect.conn_id, false); break; } // ESP_GATTS_DISCONNECT_EVT @@ -316,9 +263,17 @@ void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t break; } + case ESP_GATTS_OPEN_EVT: + m_semaphoreOpenEvt.give(param->open.status); + break; + default: break; } + + // Invoke the handler for every Service we have. + m_serviceMap.handleGATTServerEvent(event, gatts_if, param); + ESP_LOGD(LOG_TAG, "<< handleGATTServerEvent"); } // handleGATTServerEvent @@ -328,7 +283,7 @@ void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t * * @return N/A */ -void BLEServer::registerApp() { +void BLEServer::registerApp(uint16_t m_appId) { ESP_LOGD(LOG_TAG, ">> registerApp - %d", m_appId); m_semaphoreRegisterAppEvt.take("registerApp"); // Take the mutex, will be released by ESP_GATTS_REG_EVT event. ::esp_ble_gatts_app_register(m_appId); @@ -355,7 +310,7 @@ void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { */ void BLEServer::removeService(BLEService* service) { service->stop(); - service->executeDelete(); + service->executeDelete(); m_serviceMap.removeService(service); } @@ -371,6 +326,31 @@ void BLEServer::startAdvertising() { ESP_LOGD(LOG_TAG, "<< startAdvertising"); } // startAdvertising +/** + * Allow to connect GATT server to peer device + * Probably can be used in ANCS for iPhone + */ +bool BLEServer::connect(BLEAddress address) { + esp_bd_addr_t addr; + memcpy(&addr, address.getNative(), 6); + // Perform the open connection request against the target BLE Server. + m_semaphoreOpenEvt.take("connect"); + esp_err_t errRc = ::esp_ble_gatts_open( + getGattsIf(), + addr, // address + 1 // direct connection + ); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return false; + } + + uint32_t rc = m_semaphoreOpenEvt.wait("connect"); // Wait for the connection to complete. + ESP_LOGD(LOG_TAG, "<< connect(), rc=%d", rc==ESP_GATT_OK); + return rc == ESP_GATT_OK; +} // connect + + void BLEServerCallbacks::onConnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); @@ -391,4 +371,38 @@ void BLEServerCallbacks::onDisconnect(BLEServer* pServer) { ESP_LOGD("BLEServerCallbacks", "<< onDisconnect()"); } // onDisconnect +/* multi connect support */ +/* TODO do some more tweaks */ +void BLEServer::updatePeerMTU(uint16_t conn_id, uint16_t mtu) { + // set mtu in conn_status_t + const std::map::iterator it = m_connectedServersMap.find(conn_id); + if (it != m_connectedServersMap.end()) { + it->second.mtu = mtu; + std::swap(m_connectedServersMap[conn_id], it->second); + } +} + +std::map BLEServer::getPeerDevices(bool _client) { + return m_connectedServersMap; +} + + +uint16_t BLEServer::getPeerMTU(uint16_t conn_id) { + return m_connectedServersMap.find(conn_id)->second.mtu; +} + +void BLEServer::addPeerDevice(void* peer, bool _client, uint16_t conn_id) { + conn_status_t status = { + .peer_device = peer, + .connected = true, + .mtu = 23 + }; + + m_connectedServersMap.insert(std::pair(conn_id, status)); +} + +void BLEServer::removePeerDevice(uint16_t conn_id, bool _client) { + m_connectedServersMap.erase(conn_id); +} +/* multi connect support */ #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 799eb25d..1dbcab65 100755 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -13,6 +13,7 @@ #include #include +// #include "BLEDevice.h" #include "BLEUUID.h" #include "BLEAdvertising.h" @@ -20,8 +21,15 @@ #include "BLEService.h" #include "BLESecurity.h" #include "FreeRTOS.h" +#include "BLEAddress.h" class BLEServerCallbacks; +/* TODO possibly refactor this struct */ +typedef struct { + void *peer_device; // peer device BLEClient or BLEServer - maybe its better to have 2 structures or union here + bool connected; // do we need it? + uint16_t mtu; // every peer device negotiate own mtu +} conn_status_t; /** @@ -31,26 +39,20 @@ class BLEServiceMap { public: BLEService* getByHandle(uint16_t handle); BLEService* getByUUID(const char* uuid); - BLEService* getByUUID(BLEUUID uuid); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param); + BLEService* getByUUID(BLEUUID uuid, uint8_t inst_id = 0); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); void setByHandle(uint16_t handle, BLEService* service); void setByUUID(const char* uuid, BLEService* service); void setByUUID(BLEUUID uuid, BLEService* service); std::string toString(); BLEService* getFirst(); BLEService* getNext(); - void removeService(BLEService* service); - int getRegisteredServiceCount(); - + void removeService(BLEService *service); private: std::map m_handleMap; std::map m_uuidMap; std::map::iterator m_iterator; - }; @@ -61,14 +63,24 @@ class BLEServer { public: uint32_t getConnectedCount(); BLEService* createService(const char* uuid); - BLEService* createService(BLEUUID uuid, uint32_t numHandles = 15, uint8_t inst_id = 0); + BLEService* createService(BLEUUID uuid, uint32_t numHandles=15, uint8_t inst_id=0); BLEAdvertising* getAdvertising(); void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); + void removeService(BLEService* service); BLEService* getServiceByUUID(const char* uuid); BLEService* getServiceByUUID(BLEUUID uuid); - void removeService(BLEService* service); - int getServiceCount(bool includeDefaultServices); + bool connect(BLEAddress address); + uint16_t m_appId; + + /* multi connection support */ + std::map getPeerDevices(bool client); + void addPeerDevice(void* peer, bool is_client, uint16_t conn_id); + void removePeerDevice(uint16_t conn_id, bool client); + BLEServer* getServerByConnId(uint16_t conn_id); + void updatePeerMTU(uint16_t connId, uint16_t mtu); + uint16_t getPeerMTU(uint16_t conn_id); + private: BLEServer(); @@ -76,22 +88,23 @@ class BLEServer { friend class BLECharacteristic; friend class BLEDevice; esp_ble_adv_data_t m_adv_data; - uint16_t m_appId; // BLEAdvertising m_bleAdvertising; - uint16_t m_connId; - uint32_t m_connectedCount; - uint16_t m_gatts_if; - FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt"); - FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + uint16_t m_connId; + uint32_t m_connectedCount; + uint16_t m_gatts_if; + std::map m_connectedServersMap; + + FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt"); + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); BLEServiceMap m_serviceMap; - BLEServerCallbacks* m_pServerCallbacks; + BLEServerCallbacks* m_pServerCallbacks = nullptr; void createApp(uint16_t appId); uint16_t getConnId(); uint16_t getGattsIf(); void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); - void registerApp(); - void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); + void registerApp(uint16_t); }; // BLEServer @@ -109,7 +122,7 @@ class BLEServerCallbacks { * @param [in] pServer A reference to the %BLE server that received the client connection. */ virtual void onConnect(BLEServer* pServer); - virtual void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t* param); + virtual void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param); /** * @brief Handle an existing client disconnection. * diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 68019608..9e8b05ae 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -62,18 +62,13 @@ BLEService::BLEService(BLEUUID uuid, uint16_t numHandles) { */ void BLEService::executeCreate(BLEServer* pServer) { - //ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); - //getUUID(); // Needed for a weird bug fix - //char x[1000]; - //memcpy(x, &m_uuid, sizeof(m_uuid)); - //char x[10]; - //memcpy(x, &deleteMe, 10); + ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); m_pServer = pServer; m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT esp_gatt_srvc_id_t srvc_id; srvc_id.is_primary = true; - srvc_id.id.inst_id = m_id; + srvc_id.id.inst_id = m_instId; srvc_id.id.uuid = *m_uuid.getNative(); esp_err_t errRc = ::esp_ble_gatts_create_service(getServer()->getGattsIf(), &srvc_id, m_numHandles); // The maximum number of handles associated with the service. @@ -137,10 +132,10 @@ BLEUUID BLEService::getUUID() { * @return Start the service. */ void BLEService::start() { - // We ask the BLE runtime to start the service and then create each of the characteristics. - // We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event - // obtained as a result of calling esp_ble_gatts_create_service(). - // +// We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). +// ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); if (m_handle == NULL_HANDLE) { ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!"); @@ -174,9 +169,9 @@ void BLEService::start() { * @brief Stop the service. */ void BLEService::stop() { - // We ask the BLE runtime to start the service and then create each of the characteristics. - // We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event - // obtained as a result of calling esp_ble_gatts_create_service(). +// We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). ESP_LOGD(LOG_TAG, ">> stop(): Stopping service (esp_ble_gatts_stop_service): %s", toString().c_str()); if (m_handle == NULL_HANDLE) { ESP_LOGE(LOG_TAG, "<< !!! We attempted to stop a service but don't know its handle!"); @@ -228,6 +223,7 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { // We maintain a mapping of characteristics owned by this service. These are managed by the // BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic // to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF). + ESP_LOGD(LOG_TAG, ">> addCharacteristic()"); ESP_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s", pCharacteristic->getUUID().toString().c_str(), @@ -256,7 +252,7 @@ void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) { return createCharacteristic(BLEUUID(uuid), properties); } - + /** * @brief Create a new BLE Characteristic associated with this service. @@ -342,7 +338,7 @@ void BLEService::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t // * - bool is_primary // case ESP_GATTS_CREATE_EVT: { - if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid)) && m_id == param->create.service_id.id.inst_id) { + if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid)) && m_instId == param->create.service_id.id.inst_id) { setHandle(param->create.service_handle); m_semaphoreCreateEvt.give(); } diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 711ac707..b42d57f2 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -27,7 +27,7 @@ class BLECharacteristicMap { void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid); void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid); void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); - BLECharacteristic* getByUUID(const char* uuid); + BLECharacteristic* getByUUID(const char* uuid); BLECharacteristic* getByUUID(BLEUUID uuid); BLECharacteristic* getByHandle(uint16_t handle); BLECharacteristic* getFirst(); @@ -53,16 +53,16 @@ class BLEService { BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); void dump(); void executeCreate(BLEServer* pServer); - void executeDelete(); + void executeDelete(); BLECharacteristic* getCharacteristic(const char* uuid); BLECharacteristic* getCharacteristic(BLEUUID uuid); BLEUUID getUUID(); BLEServer* getServer(); void start(); - void stop(); + void stop(); std::string toString(); uint16_t getHandle(); - uint8_t m_id = 0; + uint8_t m_instId = 0; private: BLEService(const char* uuid, uint16_t numHandles); @@ -75,8 +75,8 @@ class BLEService { BLECharacteristicMap m_characteristicMap; uint16_t m_handle; - BLECharacteristic* m_lastCreatedCharacteristic; - BLEServer* m_pServer; + BLECharacteristic* m_lastCreatedCharacteristic = nullptr; + BLEServer* m_pServer = nullptr; BLEUUID m_uuid; FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); @@ -87,10 +87,9 @@ class BLEService { uint16_t m_numHandles; BLECharacteristic* getLastCreatedCharacteristic(); - void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); void setHandle(uint16_t handle); //void setService(esp_gatt_srvc_id_t srvc_id); - }; // BLEService diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 0dd5f461..d6384931 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -730,7 +730,7 @@ const char* BLEUtils::advTypeToString(uint8_t advType) { case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xff return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; default: - ESP_LOGD(LOG_TAG, " adv data type: 0x%x", advType); + ESP_LOGV(LOG_TAG, " adv data type: 0x%x", advType); return ""; } // End switch } // advTypeToString @@ -1034,13 +1034,13 @@ const char* BLEUtils::devTypeToString(esp_bt_dev_type_t type) { void BLEUtils::dumpGapEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) { - ESP_LOGD(LOG_TAG, "Received a GAP event: %s", gapEventToString(event)); + ESP_LOGV(LOG_TAG, "Received a GAP event: %s", gapEventToString(event)); switch (event) { // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT // adv_data_cmpl // - esp_bt_status_t case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_data_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->adv_data_cmpl.status); break; } // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT @@ -1049,7 +1049,7 @@ void BLEUtils::dumpGapEvent( // adv_data_raw_cmpl // - esp_bt_status_t status case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_data_raw_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->adv_data_raw_cmpl.status); break; } // ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT @@ -1058,7 +1058,7 @@ void BLEUtils::dumpGapEvent( // adv_start_cmpl // - esp_bt_status_t status case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_start_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->adv_start_cmpl.status); break; } // ESP_GAP_BLE_ADV_START_COMPLETE_EVT @@ -1067,7 +1067,7 @@ void BLEUtils::dumpGapEvent( // adv_stop_cmpl // - esp_bt_status_t status case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->adv_stop_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->adv_stop_cmpl.status); break; } // ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT @@ -1082,7 +1082,7 @@ void BLEUtils::dumpGapEvent( // - esp_bd_addr_type_t addr_type // - esp_bt_dev_type_t dev_type case ESP_GAP_BLE_AUTH_CMPL_EVT: { - ESP_LOGD(LOG_TAG, "[bd_addr: %s, key_present: %d, key: ***, key_type: %d, success: %d, fail_reason: %d, addr_type: ***, dev_type: %s]", + ESP_LOGV(LOG_TAG, "[bd_addr: %s, key_present: %d, key: ***, key_type: %d, success: %d, fail_reason: %d, addr_type: ***, dev_type: %s]", BLEAddress(param->ble_security.auth_cmpl.bd_addr).toString().c_str(), param->ble_security.auth_cmpl.key_present, param->ble_security.auth_cmpl.key_type, @@ -1098,7 +1098,7 @@ void BLEUtils::dumpGapEvent( // clear_bond_dev_cmpl // - esp_bt_status_t status case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->clear_bond_dev_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->clear_bond_dev_cmpl.status); break; } // ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT @@ -1114,7 +1114,7 @@ void BLEUtils::dumpGapEvent( // ESP_GAP_BLE_NC_REQ_EVT case ESP_GAP_BLE_NC_REQ_EVT: { - ESP_LOGD(LOG_TAG, "[bd_addr: %s, passkey: %d]", + ESP_LOGV(LOG_TAG, "[bd_addr: %s, passkey: %d]", BLEAddress(param->ble_security.key_notif.bd_addr).toString().c_str(), param->ble_security.key_notif.passkey); break; @@ -1127,7 +1127,7 @@ void BLEUtils::dumpGapEvent( // - int8_t rssi // - esp_bd_addr_t remote_addr case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d, rssi: %d, remote_addr: %s]", + ESP_LOGV(LOG_TAG, "[status: %d, rssi: %d, remote_addr: %s]", param->read_rssi_cmpl.status, param->read_rssi_cmpl.rssi, BLEAddress(param->read_rssi_cmpl.remote_addr).toString().c_str() @@ -1140,7 +1140,7 @@ void BLEUtils::dumpGapEvent( // scan_param_cmpl. // - esp_bt_status_t status case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_param_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_param_cmpl.status); break; } // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT @@ -1161,7 +1161,7 @@ void BLEUtils::dumpGapEvent( case ESP_GAP_BLE_SCAN_RESULT_EVT: { switch (param->scan_rst.search_evt) { case ESP_GAP_SEARCH_INQ_RES_EVT: { - ESP_LOGD(LOG_TAG, "search_evt: %s, bda: %s, dev_type: %s, ble_addr_type: %s, ble_evt_type: %s, rssi: %d, ble_adv: ??, flag: %d (%s), num_resps: %d, adv_data_len: %d, scan_rsp_len: %d", + ESP_LOGV(LOG_TAG, "search_evt: %s, bda: %s, dev_type: %s, ble_addr_type: %s, ble_evt_type: %s, rssi: %d, ble_adv: ??, flag: %d (%s), num_resps: %d, adv_data_len: %d, scan_rsp_len: %d", searchEventTypeToString(param->scan_rst.search_evt), BLEAddress(param->scan_rst.bda).toString().c_str(), devTypeToString(param->scan_rst.dev_type), @@ -1178,7 +1178,7 @@ void BLEUtils::dumpGapEvent( } // ESP_GAP_SEARCH_INQ_RES_EVT default: { - ESP_LOGD(LOG_TAG, "search_evt: %s",searchEventTypeToString(param->scan_rst.search_evt)); + ESP_LOGV(LOG_TAG, "search_evt: %s",searchEventTypeToString(param->scan_rst.search_evt)); break; } } @@ -1190,13 +1190,13 @@ void BLEUtils::dumpGapEvent( // scan_rsp_data_cmpl // - esp_bt_status_t status case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_rsp_data_cmpl.status); break; } // ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT // ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_raw_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_rsp_data_raw_cmpl.status); break; } // ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT @@ -1205,7 +1205,7 @@ void BLEUtils::dumpGapEvent( // scan_start_cmpl // - esp_bt_status_t status case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_start_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_start_cmpl.status); break; } // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT @@ -1214,7 +1214,7 @@ void BLEUtils::dumpGapEvent( // scan_stop_cmpl // - esp_bt_status_t status case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status); + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status); break; } // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT @@ -1229,7 +1229,7 @@ void BLEUtils::dumpGapEvent( // - uint16_t conn_int // - uint16_t timeout case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d, bd_addr: %s, min_int: %d, max_int: %d, latency: %d, conn_int: %d, timeout: %d]", + ESP_LOGV(LOG_TAG, "[status: %d, bd_addr: %s, min_int: %d, max_int: %d, latency: %d, conn_int: %d, timeout: %d]", param->update_conn_params.status, BLEAddress(param->update_conn_params.bda).toString().c_str(), param->update_conn_params.min_int, @@ -1243,12 +1243,12 @@ void BLEUtils::dumpGapEvent( // ESP_GAP_BLE_SEC_REQ_EVT case ESP_GAP_BLE_SEC_REQ_EVT: { - ESP_LOGD(LOG_TAG, "[bd_addr: %s]", BLEAddress(param->ble_security.ble_req.bd_addr).toString().c_str()); + ESP_LOGV(LOG_TAG, "[bd_addr: %s]", BLEAddress(param->ble_security.ble_req.bd_addr).toString().c_str()); break; } // ESP_GAP_BLE_SEC_REQ_EVT default: { - ESP_LOGD(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); + ESP_LOGV(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); break; } // default } // switch @@ -1267,7 +1267,7 @@ void BLEUtils::dumpGattClientEvent( esp_ble_gattc_cb_param_t* evtParam) { //esp_ble_gattc_cb_param_t* evtParam = (esp_ble_gattc_cb_param_t*) param; - ESP_LOGD(LOG_TAG, "GATT Event: %s", BLEUtils::gattClientEventTypeToString(event).c_str()); + ESP_LOGV(LOG_TAG, "GATT Event: %s", BLEUtils::gattClientEventTypeToString(event).c_str()); switch (event) { // ESP_GATTC_CLOSE_EVT // @@ -1277,7 +1277,7 @@ void BLEUtils::dumpGattClientEvent( // - esp_bd_addr_t remote_bda // - esp_gatt_conn_reason_t reason case ESP_GATTC_CLOSE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, reason:%s, conn_id: %d]", + ESP_LOGV(LOG_TAG, "[status: %s, reason:%s, conn_id: %d]", BLEUtils::gattStatusToString(evtParam->close.status).c_str(), BLEUtils::gattCloseReasonToString(evtParam->close.reason).c_str(), evtParam->close.conn_id); @@ -1291,7 +1291,7 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t conn_id // - esp_bd_addr_t remote_bda case ESP_GATTC_CONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s]", evtParam->connect.conn_id, BLEAddress(evtParam->connect.remote_bda).toString().c_str() ); @@ -1305,7 +1305,7 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t conn_id // - esp_bd_addr_t remote_bda case ESP_GATTC_DISCONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[reason: %s, conn_id: %d, remote_bda: %s]", + ESP_LOGV(LOG_TAG, "[reason: %s, conn_id: %d, remote_bda: %s]", BLEUtils::gattCloseReasonToString(evtParam->disconnect.reason).c_str(), evtParam->disconnect.conn_id, BLEAddress(evtParam->disconnect.remote_bda).toString().c_str() @@ -1331,7 +1331,7 @@ void BLEUtils::dumpGattClientEvent( if (evtParam->get_char.char_id.uuid.len == ESP_UUID_LEN_16) { description = BLEUtils::gattCharacteristicUUIDToString(evtParam->get_char.char_id.uuid.uuid.uuid16); } - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s, char_id: %s [description: %s]\nchar_prop: %s]", + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s, char_id: %s [description: %s]\nchar_prop: %s]", BLEUtils::gattStatusToString(evtParam->get_char.status).c_str(), evtParam->get_char.conn_id, BLEUtils::gattServiceIdToString(evtParam->get_char.srvc_id).c_str(), @@ -1340,7 +1340,7 @@ void BLEUtils::dumpGattClientEvent( BLEUtils::characteristicPropertiesToString(evtParam->get_char.char_prop).c_str() ); } else { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s]", + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s]", BLEUtils::gattStatusToString(evtParam->get_char.status).c_str(), evtParam->get_char.conn_id, BLEUtils::gattServiceIdToString(evtParam->get_char.srvc_id).c_str() @@ -1361,7 +1361,7 @@ void BLEUtils::dumpGattClientEvent( // bool is_notify // case ESP_GATTC_NOTIFY_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, handle: %d 0x%.2x, value_len: %d, is_notify: %d]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s, handle: %d 0x%.2x, value_len: %d, is_notify: %d]", evtParam->notify.conn_id, BLEAddress(evtParam->notify.remote_bda).toString().c_str(), evtParam->notify.handle, @@ -1381,7 +1381,7 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t mtu // case ESP_GATTC_OPEN_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, remote_bda: %s, mtu: %d]", + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, remote_bda: %s, mtu: %d]", BLEUtils::gattStatusToString(evtParam->open.status).c_str(), evtParam->open.conn_id, BLEAddress(evtParam->open.remote_bda).toString().c_str(), @@ -1401,7 +1401,7 @@ void BLEUtils::dumpGattClientEvent( // uint16_t value_type // uint16_t value_len case ESP_GATTC_READ_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x, value_len: %d]", + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x, value_len: %d]", BLEUtils::gattStatusToString(evtParam->read.status).c_str(), evtParam->read.conn_id, evtParam->read.handle, @@ -1412,7 +1412,7 @@ void BLEUtils::dumpGattClientEvent( GeneralUtils::hexDump(evtParam->read.value, evtParam->read.value_len); /* char* pHexData = BLEUtils::buildHexData(nullptr, evtParam->read.value, evtParam->read.value_len); - ESP_LOGD(LOG_TAG, "value: %s \"%s\"", pHexData, BLEUtils::buildPrintData(evtParam->read.value, evtParam->read.value_len).c_str()); + ESP_LOGV(LOG_TAG, "value: %s \"%s\"", pHexData, BLEUtils::buildPrintData(evtParam->read.value, evtParam->read.value_len).c_str()); free(pHexData); */ } @@ -1425,7 +1425,7 @@ void BLEUtils::dumpGattClientEvent( // - esp_gatt_status_t status // - uint16_t app_id case ESP_GATTC_REG_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, app_id: 0x%x]", + ESP_LOGV(LOG_TAG, "[status: %s, app_id: 0x%x]", BLEUtils::gattStatusToString(evtParam->reg.status).c_str(), evtParam->reg.app_id); break; @@ -1437,7 +1437,7 @@ void BLEUtils::dumpGattClientEvent( // - esp_gatt_status_t status // - uint16_t handle case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, handle: %d 0x%.2x]", + ESP_LOGV(LOG_TAG, "[status: %s, handle: %d 0x%.2x]", BLEUtils::gattStatusToString(evtParam->reg_for_notify.status).c_str(), evtParam->reg_for_notify.handle, evtParam->reg_for_notify.handle @@ -1451,7 +1451,7 @@ void BLEUtils::dumpGattClientEvent( // - esp_gatt_status_t status // - uint16_t conn_id case ESP_GATTC_SEARCH_CMPL_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d]", + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d]", BLEUtils::gattStatusToString(evtParam->search_cmpl.status).c_str(), evtParam->search_cmpl.conn_id); break; @@ -1465,7 +1465,7 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t end_handle // - esp_gatt_id_t srvc_id case ESP_GATTC_SEARCH_RES_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, start_handle: %d 0x%.2x, end_handle: %d 0x%.2x, srvc_id: %s", + ESP_LOGV(LOG_TAG, "[conn_id: %d, start_handle: %d 0x%.2x, end_handle: %d 0x%.2x, srvc_id: %s", evtParam->search_res.conn_id, evtParam->search_res.start_handle, evtParam->search_res.start_handle, @@ -1483,7 +1483,7 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t handle // - uint16_t offset case ESP_GATTC_WRITE_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x, offset: %d]", + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x, offset: %d]", BLEUtils::gattStatusToString(evtParam->write.status).c_str(), evtParam->write.conn_id, evtParam->write.handle, @@ -1513,11 +1513,11 @@ void BLEUtils::dumpGattServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* evtParam) { - ESP_LOGD(LOG_TAG, "GATT ServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); + ESP_LOGV(LOG_TAG, "GATT ServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); switch (event) { case ESP_GATTS_ADD_CHAR_DESCR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", + ESP_LOGV(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", gattStatusToString(evtParam->add_char_descr.status).c_str(), evtParam->add_char_descr.attr_handle, evtParam->add_char_descr.attr_handle, @@ -1529,7 +1529,7 @@ void BLEUtils::dumpGattServerEvent( case ESP_GATTS_ADD_CHAR_EVT: { if (evtParam->add_char.status == ESP_GATT_OK) { - ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", + ESP_LOGV(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", gattStatusToString(evtParam->add_char.status).c_str(), evtParam->add_char.attr_handle, evtParam->add_char.attr_handle, @@ -1555,7 +1555,7 @@ void BLEUtils::dumpGattServerEvent( // - esp_gatt_status_t status – The status code. // - uint16_t conn_id – The connection used. case ESP_GATTS_CONF_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: 0x%.2x]", + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: 0x%.2x]", gattStatusToString(evtParam->conf.status).c_str(), evtParam->conf.conn_id); break; @@ -1563,21 +1563,21 @@ void BLEUtils::dumpGattServerEvent( case ESP_GATTS_CONGEST_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, congested: %d]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, congested: %d]", evtParam->congest.conn_id, evtParam->congest.congested); break; } // ESP_GATTS_CONGEST_EVT case ESP_GATTS_CONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s]", evtParam->connect.conn_id, BLEAddress(evtParam->connect.remote_bda).toString().c_str()); break; } // ESP_GATTS_CONNECT_EVT case ESP_GATTS_CREATE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, service_handle: %d 0x%.2x, service_id: [%s]]", + ESP_LOGV(LOG_TAG, "[status: %s, service_handle: %d 0x%.2x, service_id: [%s]]", gattStatusToString(evtParam->create.status).c_str(), evtParam->create.service_handle, evtParam->create.service_handle, @@ -1586,7 +1586,7 @@ void BLEUtils::dumpGattServerEvent( } // ESP_GATTS_CREATE_EVT case ESP_GATTS_DISCONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s]", evtParam->connect.conn_id, BLEAddress(evtParam->connect.remote_bda).toString().c_str()); break; @@ -1617,7 +1617,7 @@ void BLEUtils::dumpGattServerEvent( break; } - ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, exec_write_flag: 0x%.2x=%s]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, exec_write_flag: 0x%.2x=%s]", evtParam->exec_write.conn_id, evtParam->exec_write.trans_id, BLEAddress(evtParam->exec_write.bda).toString().c_str(), @@ -1628,14 +1628,14 @@ void BLEUtils::dumpGattServerEvent( case ESP_GATTS_MTU_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, mtu: %d]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, mtu: %d]", evtParam->mtu.conn_id, evtParam->mtu.mtu); break; } // ESP_GATTS_MTU_EVT case ESP_GATTS_READ_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, is_long: %d, need_rsp:%d]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, is_long: %d, need_rsp:%d]", evtParam->read.conn_id, evtParam->read.trans_id, BLEAddress(evtParam->read.bda).toString().c_str(), @@ -1646,14 +1646,14 @@ void BLEUtils::dumpGattServerEvent( } // ESP_GATTS_READ_EVT case ESP_GATTS_RESPONSE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, handle: 0x%.2x]", + ESP_LOGV(LOG_TAG, "[status: %s, handle: 0x%.2x]", gattStatusToString(evtParam->rsp.status).c_str(), evtParam->rsp.handle); break; } // ESP_GATTS_RESPONSE_EVT case ESP_GATTS_REG_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, app_id: %d]", + ESP_LOGV(LOG_TAG, "[status: %s, app_id: %d]", gattStatusToString(evtParam->reg.status).c_str(), evtParam->reg.app_id); break; @@ -1666,7 +1666,7 @@ void BLEUtils::dumpGattServerEvent( // - esp_gatt_status_t status // - uint16_t service_handle case ESP_GATTS_START_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, service_handle: 0x%.2x]", + ESP_LOGV(LOG_TAG, "[status: %s, service_handle: 0x%.2x]", gattStatusToString(evtParam->start.status).c_str(), evtParam->start.service_handle); break; @@ -1686,7 +1686,7 @@ void BLEUtils::dumpGattServerEvent( // - uint16_t len – The length of the incoming value part. // - uint8_t* value – The data for this value part. case ESP_GATTS_WRITE_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, offset: %d, need_rsp: %d, is_prep: %d, len: %d]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, offset: %d, need_rsp: %d, is_prep: %d, len: %d]", evtParam->write.conn_id, evtParam->write.trans_id, BLEAddress(evtParam->write.bda).toString().c_str(), @@ -1696,13 +1696,13 @@ void BLEUtils::dumpGattServerEvent( evtParam->write.is_prep, evtParam->write.len); char* pHex = buildHexData(nullptr, evtParam->write.value, evtParam->write.len); - ESP_LOGD(LOG_TAG, "[Data: %s]", pHex); + ESP_LOGV(LOG_TAG, "[Data: %s]", pHex); free(pHex); break; } // ESP_GATTS_WRITE_EVT default: - ESP_LOGD(LOG_TAG, "dumpGattServerEvent: *** NOT CODED ***"); + ESP_LOGV(LOG_TAG, "dumpGattServerEvent: *** NOT CODED ***"); break; } } // dumpGattServerEvent @@ -1726,7 +1726,7 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { case ESP_BLE_EVT_SCAN_RSP: return "ESP_BLE_EVT_SCAN_RSP"; default: - ESP_LOGD(LOG_TAG, "Unknown esp_ble_evt_type_t: %d (0x%.2x)", eventType, eventType); + ESP_LOGV(LOG_TAG, "Unknown esp_ble_evt_type_t: %d (0x%.2x)", eventType, eventType); return "*** Unknown ***"; } } // eventTypeToString @@ -1795,7 +1795,7 @@ const char* BLEUtils::gapEventToString(uint32_t eventType) { case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: return "ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT"; default: - ESP_LOGD(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); + ESP_LOGV(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); return "Unknown event type"; } } // gapEventToString @@ -2001,7 +2001,7 @@ const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { case ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT: return "ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT"; default: - ESP_LOGD(LOG_TAG, "Unknown event type: 0x%x", searchEvt); + ESP_LOGV(LOG_TAG, "Unknown event type: 0x%x", searchEvt); return "Unknown event type"; } } // searchEventTypeToString From ae045aa4ef0881ef66ae749f2f6607b671a75970 Mon Sep 17 00:00:00 2001 From: chegewara Date: Sat, 17 Nov 2018 10:16:10 +0100 Subject: [PATCH 359/381] Fix issues in last commit --- cpp_utils/BLEAdvertising.cpp | 186 +++++++++++++++++++++++++------- cpp_utils/BLEAdvertising.h | 13 ++- cpp_utils/BLECharacteristic.cpp | 2 +- cpp_utils/BLEClient.cpp | 3 +- cpp_utils/BLERemoteService.h | 1 + cpp_utils/BLEServer.h | 1 + cpp_utils/BLEServiceMap.cpp | 2 +- cpp_utils/BLEUUID.cpp | 2 - cpp_utils/BLEUtils.cpp | 4 +- 9 files changed, 164 insertions(+), 50 deletions(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index d4fd6332..0ad0da78 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -23,14 +23,21 @@ #include #include "BLEUtils.h" #include "GeneralUtils.h" +#include "string.h" +#include "mbedtls/md.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" + + +#define BLOCK_SIZE 64 +#define HMAC MBEDTLS_MD_SHA256 #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" #endif - +static bool is_advertising = false; static const char* LOG_TAG = "BLEAdvertising"; - /** * @brief Construct a default advertising object. * @@ -38,7 +45,7 @@ static const char* LOG_TAG = "BLEAdvertising"; BLEAdvertising::BLEAdvertising() { m_advData.set_scan_rsp = false; m_advData.include_name = true; - m_advData.include_txpower = true; + m_advData.include_txpower = false; m_advData.min_interval = 0x20; m_advData.max_interval = 0x40; m_advData.appearance = 0x00; @@ -61,6 +68,65 @@ BLEAdvertising::BLEAdvertising() { m_customScanResponseData = false; // No custom scan response data } // BLEAdvertising +void BLEAdvertising::setPrivateAddress(esp_ble_addr_type_t type) { + esp_bd_addr_t addr; + m_advParams.own_addr_type = type; + + if(type == BLE_ADDR_TYPE_RPA_PUBLIC) { + esp_ble_gap_config_local_privacy(true); + return; + } + else{ + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_entropy_context entropy; + mbedtls_md_context_t ctx; + + char pers[] = "aes generate key"; + int ret; + unsigned char key[BLOCK_SIZE] = {0}; + const char inp[] = "random static address"; + unsigned char outp[BLOCK_SIZE/2]; + + mbedtls_entropy_init( &entropy ); + mbedtls_ctr_drbg_init( &ctr_drbg ); + + if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, + (unsigned char *) pers, strlen( pers ) ) ) != 0 ) + { + printf( " failed\n ! mbedtls_ctr_drbg_init returned -0x%04x\n", -ret ); + goto exit; + } + + if( ( ret = mbedtls_ctr_drbg_random( &ctr_drbg, key, BLOCK_SIZE ) ) != 0 ) + { + printf( " failed\n ! mbedtls_ctr_drbg_random returned -0x%04x\n", -ret ); + goto exit; + } + + mbedtls_md_init(&ctx); + mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(HMAC), true); + mbedtls_md_hmac_starts(&ctx, (const unsigned char *)key, BLOCK_SIZE); + mbedtls_md_hmac_update(&ctx, (const unsigned char *)inp, strlen(inp)); + mbedtls_md_hmac_finish(&ctx, outp); + mbedtls_md_free(&ctx); + ESP_LOG_BUFFER_HEX("random key", key, BLOCK_SIZE); + ESP_LOG_BUFFER_HEX("HASH step 2", outp, BLOCK_SIZE/2); + + memcpy(addr, outp, 6); + } + if(type == BLE_ADDR_TYPE_RANDOM) { + addr[0] &= 0x3F; // <--- Format of non-resolvable private address 00xx xxxx + } + else{ + addr[0] |= 0xC0; // <--- Format of static address 11xx xxxx + } + + esp_ble_gap_set_rand_addr(addr); + ESP_LOG_BUFFER_HEX("random address", addr, 6); + exit: + return; +} + /** * @brief Add a service uuid to exposed list of services. @@ -107,7 +173,7 @@ void BLEAdvertising::setMaxInterval(uint16_t maxinterval) { * @param [in] scanRequestWhitelistOnly If true, only allow scan requests from those on the white list. * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. */ -void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { +void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { ESP_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly); if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; @@ -173,20 +239,21 @@ void BLEAdvertising::setScanResponseData(BLEAdvertisementData& advertisementData void BLEAdvertising::start() { ESP_LOGD(LOG_TAG, ">> start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); + // We have a vector of service UUIDs that we wish to advertise. In order to use the // ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte) // representations. If we have 1 or more services to advertise then we allocate enough // storage to host them and then copy them in one at a time into the contiguous storage. int numServices = m_serviceUUIDs.size(); if (numServices > 0) { - m_advData.service_uuid_len = 16 * numServices; - m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; + m_advData.service_uuid_len = 16*numServices; + m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; uint8_t* p = m_advData.p_service_uuid; - for (int i = 0; i < numServices; i++) { + for (int i=0; iuuid.uuid128, 16); - p += 16; + p+=16; } } else { m_advData.service_uuid_len = 0; @@ -195,36 +262,45 @@ void BLEAdvertising::start() { esp_err_t errRc; - if (!m_customAdvData) { + if(!is_advertising){ + + // m_semaphoreSetAdv.take("config_adv"); + if (m_customAdvData == false) { // Set the configuration for advertising. - m_advData.set_scan_rsp = false; - errRc = ::esp_ble_gap_config_adv_data(&m_advData); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + m_advData.set_scan_rsp = false; + errRc = ::esp_ble_gap_config_adv_data(&m_advData); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + // m_semaphoreSetAdv.give(); + return; + } } - } - if (!m_customScanResponseData) { - m_advData.set_scan_rsp = true; - errRc = ::esp_ble_gap_config_adv_data(&m_advData); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + // m_semaphoreSetAdv.take("config_rsp"); + if (m_customScanResponseData == false) { + m_advData.set_scan_rsp = true; + errRc = ::esp_ble_gap_config_adv_data(&m_advData); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + // m_semaphoreSetAdv.give(); + return; + } } } - // If we had services to advertise then we previously allocated some storage for them. // Here we release that storage. if (m_advData.service_uuid_len > 0) { delete[] m_advData.p_service_uuid; m_advData.p_service_uuid = nullptr; } + is_advertising = true; + // m_semaphoreSetAdv.take("start"); // Start advertising. errRc = ::esp_ble_gap_start_advertising(&m_advParams); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + // m_semaphoreSetAdv.give(); return; } ESP_LOGD(LOG_TAG, "<< start"); @@ -238,6 +314,7 @@ void BLEAdvertising::start() { */ void BLEAdvertising::stop() { ESP_LOGD(LOG_TAG, ">> stop"); + // m_semaphoreSetAdv.take("stop"); esp_err_t errRc = ::esp_ble_gap_stop_advertising(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -269,7 +346,8 @@ void BLEAdvertisementData::setAppearance(uint16_t appearance) { char cdata[2]; cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_APPEARANCE; // 0x19 - addData(std::string(cdata, 2) + std::string((char*) &appearance, 2)); + addData(std::string(cdata, 2) + std::string((char *)&appearance,2)); + esp_ble_gap_config_local_icon(appearance); } // setAppearance @@ -279,12 +357,12 @@ void BLEAdvertisementData::setAppearance(uint16_t appearance) { */ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { char cdata[2]; - switch (uuid.bitSize()) { + switch(uuid.bitSize()) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_CMPL; // 0x03 - addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2)); + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); break; } @@ -292,7 +370,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_CMPL; // 0x05 - addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4)); + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); break; } @@ -300,7 +378,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; // 0x07 - addData(std::string(cdata, 2) + std::string((char*) uuid.getNative()->uuid.uuid128, 16)); + addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); break; } @@ -340,7 +418,7 @@ void BLEAdvertisementData::setManufacturerData(std::string data) { char cdata[2]; cdata[0] = data.length() + 1; cdata[1] = ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE; // 0xff - addData(std::string(cdata, 2) + data); + addData(std::string(cdata, 2) + data); ESP_LOGD("BLEAdvertisementData", "<< setManufacturerData"); } // setManufacturerData @@ -354,7 +432,7 @@ void BLEAdvertisementData::setName(std::string name) { char cdata[2]; cdata[0] = name.length() + 1; cdata[1] = ESP_BLE_AD_TYPE_NAME_CMPL; // 0x09 - addData(std::string(cdata, 2) + name); + addData(std::string(cdata, 2) + name); ESP_LOGD("BLEAdvertisementData", "<< setName"); } // setName @@ -365,12 +443,12 @@ void BLEAdvertisementData::setName(std::string name) { */ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { char cdata[2]; - switch (uuid.bitSize()) { + switch(uuid.bitSize()) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_PART; // 0x02 - addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid16, 2)); + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); break; } @@ -378,7 +456,7 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_PART; // 0x04 - addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid32, 4)); + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); break; } @@ -386,7 +464,7 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_PART; // 0x06 - addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid128, 16)); + addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); break; } @@ -403,12 +481,12 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { */ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { char cdata[2]; - switch (uuid.bitSize()) { + switch(uuid.bitSize()) { case 16: { // [Len] [0x16] [UUID16] data cdata[0] = data.length() + 3; cdata[1] = ESP_BLE_AD_TYPE_SERVICE_DATA; // 0x16 - addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2) + data); + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2) + data); break; } @@ -416,7 +494,7 @@ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { // [Len] [0x20] [UUID32] data cdata[0] = data.length() + 5; cdata[1] = ESP_BLE_AD_TYPE_32SERVICE_DATA; // 0x20 - addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4) + data); + addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4) + data); break; } @@ -424,7 +502,7 @@ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { // [Len] [0x21] [UUID128] data cdata[0] = data.length() + 17; cdata[1] = ESP_BLE_AD_TYPE_128SERVICE_DATA; // 0x21 - addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid128, 16) + data); + addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16) + data); break; } @@ -443,11 +521,12 @@ void BLEAdvertisementData::setShortName(std::string name) { char cdata[2]; cdata[0] = name.length() + 1; cdata[1] = ESP_BLE_AD_TYPE_NAME_SHORT; // 0x08 - addData(std::string(cdata, 2) + name); + addData(std::string(cdata, 2) + name); ESP_LOGD("BLEAdvertisementData", "<< setShortName"); } // setShortName + /** * @brief Retrieve the payload that is to be advertised. * @return The payload that is to be advertised. @@ -456,5 +535,34 @@ std::string BLEAdvertisementData::getPayload() { return m_payload; } // getPayload +void BLEAdvertising::gapEventHandler( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param) { + + ESP_LOGD(LOG_TAG, "gapEventHandler [event no: %d]", (int)event); + + switch(event) { + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { + // m_semaphoreSetAdv.give(); + break; + } + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: { + // m_semaphoreSetAdv.give(); + break; + } + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: { + // m_semaphoreSetAdv.give(); + break; + } + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: { + ESP_LOGI(LOG_TAG, "STOP advertising"); + start(); + break; + } + default: + break; + } +} + -#endif /* CONFIG_BT_ENABLED */ +#endif /* CONFIG_BT_ENABLED */ \ No newline at end of file diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index f7cfc240..82f35e6e 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -12,6 +12,7 @@ #include #include "BLEUUID.h" #include +#include "FreeRTOS.h" /** * @brief Advertisement data set by the programmer to be published by the %BLE server. @@ -19,6 +20,7 @@ class BLEAdvertisementData { // Only a subset of the possible BLE architected advertisement fields are currently exposed. Others will // be exposed on demand/request or as time permits. + // public: void setAppearance(uint16_t appearance); void setCompleteServices(BLEUUID uuid); @@ -55,13 +57,18 @@ class BLEAdvertising { void setAdvertisementData(BLEAdvertisementData& advertisementData); void setScanFilter(bool scanRequertWhitelistOnly, bool connectWhitelistOnly); void setScanResponseData(BLEAdvertisementData& advertisementData); + void setPrivateAddress(esp_ble_addr_type_t type = BLE_ADDR_TYPE_RANDOM); + + void gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); private: esp_ble_adv_data_t m_advData; esp_ble_adv_params_t m_advParams; std::vector m_serviceUUIDs; - bool m_customAdvData; // Are we using custom advertising data? - bool m_customScanResponseData; // Are we using custom scan response data? + bool m_customAdvData = false; // Are we using custom advertising data? + bool m_customScanResponseData = false; // Are we using custom scan response data? + FreeRTOS::Semaphore m_semaphoreSetAdv = FreeRTOS::Semaphore("startAdvert"); + }; #endif /* CONFIG_BT_ENABLED */ -#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */ +#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */ \ No newline at end of file diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index f3cf02b8..3acb70a5 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -427,7 +427,7 @@ void BLECharacteristic::handleGATTServerEvent( // - uint16_t conn_id – The connection used. // case ESP_GATTS_CONF_EVT: { - ESP_LOGD(LOG_TAG, "m_handle = %d, conf->handle = %d", m_handle, param->conf.handle); + // ESP_LOGD(LOG_TAG, "m_handle = %d", m_handle); if(param->conf.conn_id == getService()->getServer()->getConnId()) // && param->conf.handle == m_handle) // bug in esp-idf and not implemented in arduino yet m_semaphoreConfEvt.give(param->conf.status); break; diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 4408b140..66baf166 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -414,8 +414,7 @@ std::map* BLEClient::getServices() { // TODO implement retrieving services from cache clearServices(); // Clear any services that may exist. - ESP_LOGD(LOG_TAG, "esp_ble_gattc_get_service: %d services from peer device", count); - errRc = esp_ble_gattc_search_service( + esp_err_t errRc = esp_ble_gattc_search_service( getGattcIf(), getConnId(), NULL // Filter UUID diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 0f45bd0a..2ab86738 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -34,6 +34,7 @@ class BLERemoteService { BLERemoteCharacteristic* getCharacteristic(uint16_t uuid); // Get the specified characteristic reference. std::map* getCharacteristics(); std::map* getCharacteristicsByHandle(); // Get the characteristics map. + void getCharacteristics(std::map* pCharacteristicMap); BLEClient* getClient(void); // Get a reference to the client associated with this service. uint16_t getHandle(); // Get the handle of this service. diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 1dbcab65..23d602a8 100755 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -48,6 +48,7 @@ class BLEServiceMap { BLEService* getFirst(); BLEService* getNext(); void removeService(BLEService *service); + int getRegisteredServiceCount(); private: std::map m_handleMap; diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp index c749be16..cf4f75f4 100755 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -25,7 +25,7 @@ BLEService* BLEServiceMap::getByUUID(const char* uuid) { * @param [in] UUID The UUID to look up the service. * @return The characteristic. */ -BLEService* BLEServiceMap::getByUUID(BLEUUID uuid) { +BLEService* BLEServiceMap::getByUUID(BLEUUID uuid, uint8_t inst_id) { for (auto &myPair : m_uuidMap) { if (myPair.first->getUUID().equals(uuid)) { return myPair.first; diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index ee8b6c9d..fd0f5ad5 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -95,8 +95,6 @@ BLEUUID::BLEUUID(std::string value) { m_uuid.uuid.uuid32 += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(6-i)*4; i+=2; } - } - } } else if (value.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be investigated (lack of time) m_uuid.len = ESP_UUID_LEN_128; diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index d6384931..b503b7ca 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -1001,8 +1001,8 @@ std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType return "ESP_GATTS_CREAT_ATTR_TAB_EVT"; case ESP_GATTS_SET_ATTR_VAL_EVT: return "ESP_GATTS_SET_ATTR_VAL_EVT"; - case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: - return "ESP_GATTS_SEND_SERVICE_CHANGE_EVT"; + // case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: + // return "ESP_GATTS_SEND_SERVICE_CHANGE_EVT"; default: return "Unknown"; } From 011ea72506b7287a3b3a0742d68e3bb7cc6d276c Mon Sep 17 00:00:00 2001 From: chegewara Date: Mon, 19 Nov 2018 15:07:05 +0100 Subject: [PATCH 360/381] Few more fixes, arduino compatibility tested --- cpp_utils/BLEAdvertising.cpp | 179 ++++++++++---------------------- cpp_utils/BLEAdvertising.h | 6 +- cpp_utils/BLECharacteristic.cpp | 29 +++--- cpp_utils/BLECharacteristic.h | 4 +- cpp_utils/BLEDescriptor.cpp | 60 ++--------- cpp_utils/BLEDescriptor.h | 4 +- cpp_utils/BLEDescriptorMap.cpp | 16 +-- cpp_utils/BLEDevice.cpp | 2 +- cpp_utils/BLEService.cpp | 1 - cpp_utils/BLEUtils.cpp | 50 ++++++--- cpp_utils/GeneralUtils.cpp | 4 + 11 files changed, 137 insertions(+), 218 deletions(-) diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 0ad0da78..41ad87a2 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -23,21 +23,14 @@ #include #include "BLEUtils.h" #include "GeneralUtils.h" -#include "string.h" -#include "mbedtls/md.h" -#include "mbedtls/entropy.h" -#include "mbedtls/ctr_drbg.h" - - -#define BLOCK_SIZE 64 -#define HMAC MBEDTLS_MD_SHA256 #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" #endif -static bool is_advertising = false; + static const char* LOG_TAG = "BLEAdvertising"; + /** * @brief Construct a default advertising object. * @@ -45,7 +38,7 @@ static const char* LOG_TAG = "BLEAdvertising"; BLEAdvertising::BLEAdvertising() { m_advData.set_scan_rsp = false; m_advData.include_name = true; - m_advData.include_txpower = false; + m_advData.include_txpower = true; m_advData.min_interval = 0x20; m_advData.max_interval = 0x40; m_advData.appearance = 0x00; @@ -63,70 +56,12 @@ BLEAdvertising::BLEAdvertising() { m_advParams.own_addr_type = BLE_ADDR_TYPE_PUBLIC; m_advParams.channel_map = ADV_CHNL_ALL; m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; + m_advParams.peer_addr_type = BLE_ADDR_TYPE_PUBLIC; m_customAdvData = false; // No custom advertising data m_customScanResponseData = false; // No custom scan response data } // BLEAdvertising -void BLEAdvertising::setPrivateAddress(esp_ble_addr_type_t type) { - esp_bd_addr_t addr; - m_advParams.own_addr_type = type; - - if(type == BLE_ADDR_TYPE_RPA_PUBLIC) { - esp_ble_gap_config_local_privacy(true); - return; - } - else{ - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_entropy_context entropy; - mbedtls_md_context_t ctx; - - char pers[] = "aes generate key"; - int ret; - unsigned char key[BLOCK_SIZE] = {0}; - const char inp[] = "random static address"; - unsigned char outp[BLOCK_SIZE/2]; - - mbedtls_entropy_init( &entropy ); - mbedtls_ctr_drbg_init( &ctr_drbg ); - - if( ( ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, - (unsigned char *) pers, strlen( pers ) ) ) != 0 ) - { - printf( " failed\n ! mbedtls_ctr_drbg_init returned -0x%04x\n", -ret ); - goto exit; - } - - if( ( ret = mbedtls_ctr_drbg_random( &ctr_drbg, key, BLOCK_SIZE ) ) != 0 ) - { - printf( " failed\n ! mbedtls_ctr_drbg_random returned -0x%04x\n", -ret ); - goto exit; - } - - mbedtls_md_init(&ctx); - mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(HMAC), true); - mbedtls_md_hmac_starts(&ctx, (const unsigned char *)key, BLOCK_SIZE); - mbedtls_md_hmac_update(&ctx, (const unsigned char *)inp, strlen(inp)); - mbedtls_md_hmac_finish(&ctx, outp); - mbedtls_md_free(&ctx); - ESP_LOG_BUFFER_HEX("random key", key, BLOCK_SIZE); - ESP_LOG_BUFFER_HEX("HASH step 2", outp, BLOCK_SIZE/2); - - memcpy(addr, outp, 6); - } - if(type == BLE_ADDR_TYPE_RANDOM) { - addr[0] &= 0x3F; // <--- Format of non-resolvable private address 00xx xxxx - } - else{ - addr[0] |= 0xC0; // <--- Format of static address 11xx xxxx - } - - esp_ble_gap_set_rand_addr(addr); - ESP_LOG_BUFFER_HEX("random address", addr, 6); - exit: - return; -} - /** * @brief Add a service uuid to exposed list of services. @@ -158,22 +93,31 @@ void BLEAdvertising::setAppearance(uint16_t appearance) { } // setAppearance void BLEAdvertising::setMinInterval(uint16_t mininterval) { - m_advData.min_interval = mininterval; m_advParams.adv_int_min = mininterval; } // setMinInterval void BLEAdvertising::setMaxInterval(uint16_t maxinterval) { - m_advData.max_interval = maxinterval; m_advParams.adv_int_max = maxinterval; } // setMaxInterval +void BLEAdvertising::setMinPreferred(uint16_t mininterval) { + m_advData.min_interval = mininterval; +} // + +void BLEAdvertising::setMaxPreferred(uint16_t maxinterval) { + m_advData.max_interval = maxinterval; +} // + +void BLEAdvertising::setScanResponse(bool set) { + m_scanResp = set; +} /** * @brief Set the filtering for the scan filter. * @param [in] scanRequestWhitelistOnly If true, only allow scan requests from those on the white list. * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. */ -void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { +void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { ESP_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly); if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; @@ -239,21 +183,20 @@ void BLEAdvertising::setScanResponseData(BLEAdvertisementData& advertisementData void BLEAdvertising::start() { ESP_LOGD(LOG_TAG, ">> start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); - // We have a vector of service UUIDs that we wish to advertise. In order to use the // ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte) // representations. If we have 1 or more services to advertise then we allocate enough // storage to host them and then copy them in one at a time into the contiguous storage. int numServices = m_serviceUUIDs.size(); if (numServices > 0) { - m_advData.service_uuid_len = 16*numServices; - m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; + m_advData.service_uuid_len = 16 * numServices; + m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; uint8_t* p = m_advData.p_service_uuid; - for (int i=0; iuuid.uuid128, 16); - p+=16; + p += 16; } } else { m_advData.service_uuid_len = 0; @@ -262,45 +205,40 @@ void BLEAdvertising::start() { esp_err_t errRc; - if(!is_advertising){ - - // m_semaphoreSetAdv.take("config_adv"); - if (m_customAdvData == false) { + if (!m_customAdvData) { // Set the configuration for advertising. - m_advData.set_scan_rsp = false; - errRc = ::esp_ble_gap_config_adv_data(&m_advData); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - // m_semaphoreSetAdv.give(); - return; - } + m_advData.set_scan_rsp = false; + m_advData.include_name = !m_scanResp; + m_advData.include_txpower = !m_scanResp; + errRc = ::esp_ble_gap_config_adv_data(&m_advData); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; } + } - // m_semaphoreSetAdv.take("config_rsp"); - if (m_customScanResponseData == false) { - m_advData.set_scan_rsp = true; - errRc = ::esp_ble_gap_config_adv_data(&m_advData); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - // m_semaphoreSetAdv.give(); - return; - } + if (!m_customScanResponseData && m_scanResp) { + m_advData.set_scan_rsp = true; + m_advData.include_name = m_scanResp; + m_advData.include_txpower = m_scanResp; + errRc = ::esp_ble_gap_config_adv_data(&m_advData); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; } } + // If we had services to advertise then we previously allocated some storage for them. // Here we release that storage. if (m_advData.service_uuid_len > 0) { delete[] m_advData.p_service_uuid; m_advData.p_service_uuid = nullptr; } - is_advertising = true; - // m_semaphoreSetAdv.take("start"); // Start advertising. errRc = ::esp_ble_gap_start_advertising(&m_advParams); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - // m_semaphoreSetAdv.give(); return; } ESP_LOGD(LOG_TAG, "<< start"); @@ -314,7 +252,6 @@ void BLEAdvertising::start() { */ void BLEAdvertising::stop() { ESP_LOGD(LOG_TAG, ">> stop"); - // m_semaphoreSetAdv.take("stop"); esp_err_t errRc = ::esp_ble_gap_stop_advertising(); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -346,8 +283,7 @@ void BLEAdvertisementData::setAppearance(uint16_t appearance) { char cdata[2]; cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_APPEARANCE; // 0x19 - addData(std::string(cdata, 2) + std::string((char *)&appearance,2)); - esp_ble_gap_config_local_icon(appearance); + addData(std::string(cdata, 2) + std::string((char*) &appearance, 2)); } // setAppearance @@ -357,12 +293,12 @@ void BLEAdvertisementData::setAppearance(uint16_t appearance) { */ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { char cdata[2]; - switch(uuid.bitSize()) { + switch (uuid.bitSize()) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_CMPL; // 0x03 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2)); break; } @@ -370,7 +306,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_CMPL; // 0x05 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4)); break; } @@ -378,7 +314,7 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; // 0x07 - addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); + addData(std::string(cdata, 2) + std::string((char*) uuid.getNative()->uuid.uuid128, 16)); break; } @@ -418,7 +354,7 @@ void BLEAdvertisementData::setManufacturerData(std::string data) { char cdata[2]; cdata[0] = data.length() + 1; cdata[1] = ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE; // 0xff - addData(std::string(cdata, 2) + data); + addData(std::string(cdata, 2) + data); ESP_LOGD("BLEAdvertisementData", "<< setManufacturerData"); } // setManufacturerData @@ -432,7 +368,7 @@ void BLEAdvertisementData::setName(std::string name) { char cdata[2]; cdata[0] = name.length() + 1; cdata[1] = ESP_BLE_AD_TYPE_NAME_CMPL; // 0x09 - addData(std::string(cdata, 2) + name); + addData(std::string(cdata, 2) + name); ESP_LOGD("BLEAdvertisementData", "<< setName"); } // setName @@ -443,12 +379,12 @@ void BLEAdvertisementData::setName(std::string name) { */ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { char cdata[2]; - switch(uuid.bitSize()) { + switch (uuid.bitSize()) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_PART; // 0x02 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2)); + addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid16, 2)); break; } @@ -456,7 +392,7 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_PART; // 0x04 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4)); + addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid32, 4)); break; } @@ -464,7 +400,7 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_PART; // 0x06 - addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16)); + addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid128, 16)); break; } @@ -481,12 +417,12 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { */ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { char cdata[2]; - switch(uuid.bitSize()) { + switch (uuid.bitSize()) { case 16: { // [Len] [0x16] [UUID16] data cdata[0] = data.length() + 3; cdata[1] = ESP_BLE_AD_TYPE_SERVICE_DATA; // 0x16 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid16,2) + data); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2) + data); break; } @@ -494,7 +430,7 @@ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { // [Len] [0x20] [UUID32] data cdata[0] = data.length() + 5; cdata[1] = ESP_BLE_AD_TYPE_32SERVICE_DATA; // 0x20 - addData(std::string(cdata, 2) + std::string((char *)&uuid.getNative()->uuid.uuid32,4) + data); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4) + data); break; } @@ -502,7 +438,7 @@ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { // [Len] [0x21] [UUID128] data cdata[0] = data.length() + 17; cdata[1] = ESP_BLE_AD_TYPE_128SERVICE_DATA; // 0x21 - addData(std::string(cdata, 2) + std::string((char *)uuid.getNative()->uuid.uuid128,16) + data); + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid128, 16) + data); break; } @@ -521,12 +457,11 @@ void BLEAdvertisementData::setShortName(std::string name) { char cdata[2]; cdata[0] = name.length() + 1; cdata[1] = ESP_BLE_AD_TYPE_NAME_SHORT; // 0x08 - addData(std::string(cdata, 2) + name); + addData(std::string(cdata, 2) + name); ESP_LOGD("BLEAdvertisementData", "<< setShortName"); } // setShortName - /** * @brief Retrieve the payload that is to be advertised. * @return The payload that is to be advertised. @@ -535,11 +470,11 @@ std::string BLEAdvertisementData::getPayload() { return m_payload; } // getPayload -void BLEAdvertising::gapEventHandler( +void BLEAdvertising::handleGAPEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) { - ESP_LOGD(LOG_TAG, "gapEventHandler [event no: %d]", (int)event); + ESP_LOGD(LOG_TAG, "handleGAPEvent [event no: %d]", (int)event); switch(event) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { @@ -565,4 +500,4 @@ void BLEAdvertising::gapEventHandler( } -#endif /* CONFIG_BT_ENABLED */ \ No newline at end of file +#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 82f35e6e..3128b50f 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -59,7 +59,10 @@ class BLEAdvertising { void setScanResponseData(BLEAdvertisementData& advertisementData); void setPrivateAddress(esp_ble_addr_type_t type = BLE_ADDR_TYPE_RANDOM); - void gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); + void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); + void setMinPreferred(uint16_t); + void setMaxPreferred(uint16_t); + void setScanResponse(bool); private: esp_ble_adv_data_t m_advData; @@ -68,6 +71,7 @@ class BLEAdvertising { bool m_customAdvData = false; // Are we using custom advertising data? bool m_customScanResponseData = false; // Are we using custom scan response data? FreeRTOS::Semaphore m_semaphoreSetAdv = FreeRTOS::Semaphore("startAdvert"); + bool m_scanResp = true; }; #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 3acb70a5..bdeee727 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -96,13 +96,12 @@ void BLECharacteristic::executeCreate(BLEService* pService) { esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; - + m_semaphoreCreateEvt.take("executeCreate"); esp_err_t errRc = ::esp_ble_gatts_add_char( m_pService->getHandle(), getUUID().getNative(), static_cast(m_permissions), getProperties(), - //&value, nullptr, &control); // Whether to auto respond or not. @@ -110,6 +109,13 @@ void BLECharacteristic::executeCreate(BLEService* pService) { ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_add_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } + m_semaphoreCreateEvt.wait("executeCreate"); + + BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); + while (pDescriptor != nullptr) { + pDescriptor->executeCreate(this); + pDescriptor = m_descriptorMap.getNext(); + } // End while ESP_LOGD(LOG_TAG, "<< executeCreate"); } // executeCreate @@ -244,17 +250,14 @@ void BLECharacteristic::handleGATTServerEvent( // - uint16_t service_handle // - esp_bt_uuid_t char_uuid case ESP_GATTS_ADD_CHAR_EVT: { - if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) && - getHandle() == param->add_char.attr_handle && - getService()->getHandle()==param->add_char.service_handle) { - + if (getHandle() == param->add_char.attr_handle) { // we have created characteristic, now we can create descriptors - BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); - while (pDescriptor != nullptr) { - pDescriptor->executeCreate(this); - pDescriptor = m_descriptorMap.getNext(); - } // End while - + // BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); + // while (pDescriptor != nullptr) { + // pDescriptor->executeCreate(this); + // pDescriptor = m_descriptorMap.getNext(); + // } // End while + m_semaphoreCreateEvt.give(); } break; } // ESP_GATTS_ADD_CHAR_EVT @@ -427,7 +430,7 @@ void BLECharacteristic::handleGATTServerEvent( // - uint16_t conn_id – The connection used. // case ESP_GATTS_CONF_EVT: { - // ESP_LOGD(LOG_TAG, "m_handle = %d", m_handle); + ESP_LOGD(LOG_TAG, "m_handle = %d, conf->handle = %d", m_handle, param->conf.handle); if(param->conf.conn_id == getService()->getServer()->getConnId()) // && param->conf.handle == m_handle) // bug in esp-idf and not implemented in arduino yet m_semaphoreConfEvt.give(param->conf.status); break; diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 49af0ba3..5eb1e8d6 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -38,9 +38,9 @@ class BLEDescriptorMap { BLEDescriptor* getFirst(); BLEDescriptor* getNext(); private: - std::map m_uuidMap; + std::map m_uuidMap; std::map m_handleMap; - std::map::iterator m_iterator; + std::map::iterator m_iterator; }; diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 5f23026f..4d7b5efd 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -29,21 +29,21 @@ static const char* LOG_TAG = "BLEDescriptor"; /** * @brief BLEDescriptor constructor. */ -BLEDescriptor::BLEDescriptor(const char* uuid) : BLEDescriptor(BLEUUID(uuid)) { +BLEDescriptor::BLEDescriptor(const char* uuid, uint16_t len) : BLEDescriptor(BLEUUID(uuid), len) { } /** * @brief BLEDescriptor constructor. */ -BLEDescriptor::BLEDescriptor(BLEUUID uuid) { +BLEDescriptor::BLEDescriptor(BLEUUID uuid, uint16_t max_len) { m_bleUUID = uuid; m_value.attr_len = 0; // Initial length is 0. - m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; // Maximum length of the data. + m_value.attr_max_len = max_len; // Maximum length of the data. m_handle = NULL_HANDLE; // Handle is initially unknown. m_pCharacteristic = nullptr; // No initial characteristic. m_pCallback = nullptr; // No initial callback. - m_value.attr_value = (uint8_t*) malloc(ESP_GATT_MAX_ATTR_LEN); // Allocate storage for the value. + m_value.attr_value = (uint8_t*) malloc(max_len); // Allocate storage for the value. } // BLEDescriptor @@ -70,12 +70,12 @@ void BLEDescriptor::executeCreate(BLECharacteristic* pCharacteristic) { m_pCharacteristic = pCharacteristic; // Save the characteristic associated with this service. esp_attr_control_t control; - control.auto_rsp = ESP_GATT_RSP_BY_APP; + control.auto_rsp = ESP_GATT_AUTO_RSP; m_semaphoreCreateEvt.take("executeCreate"); esp_err_t errRc = ::esp_ble_gatts_add_char_descr( pCharacteristic->getService()->getHandle(), getUUID().getNative(), - (esp_gatt_perm_t)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), + (esp_gatt_perm_t)m_permissions, &m_value, &control); if (errRc != ESP_OK) { @@ -143,17 +143,6 @@ void BLEDescriptor::handleGATTServerEvent( // - uint16_t service_handle // - esp_bt_uuid_t char_uuid case ESP_GATTS_ADD_CHAR_DESCR_EVT: { - /* - ESP_LOGD(LOG_TAG, "DEBUG: m_pCharacteristic: %x", (uint32_t)m_pCharacteristic); - ESP_LOGD(LOG_TAG, "DEBUG: m_bleUUID: %s, add_char_descr.char_uuid: %s, equals: %d", - m_bleUUID.toString().c_str(), - BLEUUID(param->add_char_descr.char_uuid).toString().c_str(), - m_bleUUID.equals(BLEUUID(param->add_char_descr.char_uuid))); - ESP_LOGD(LOG_TAG, "DEBUG: service->getHandle: %x, add_char_descr.service_handle: %x", - m_pCharacteristic->getService()->getHandle(), param->add_char_descr.service_handle); - ESP_LOGD(LOG_TAG, "DEBUG: service->lastCharacteristic: %x", - (uint32_t)m_pCharacteristic->getService()->getLastCreatedCharacteristic()); - */ if (m_pCharacteristic != nullptr && m_bleUUID.equals(BLEUUID(param->add_char_descr.descr_uuid)) && m_pCharacteristic->getService()->getHandle() == param->add_char_descr.service_handle && @@ -180,23 +169,6 @@ void BLEDescriptor::handleGATTServerEvent( if (param->write.handle == m_handle) { setValue(param->write.value, param->write.len); // Set the value of the descriptor. - esp_gatt_rsp_t rsp; // Build a response. - rsp.attr_value.len = getLength(); - rsp.attr_value.handle = m_handle; - rsp.attr_value.offset = 0; - rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; - memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.len); - esp_err_t errRc = ::esp_ble_gatts_send_response( - gatts_if, - param->write.conn_id, - param->write.trans_id, - ESP_GATT_OK, - &rsp); - - if (errRc != ESP_OK) { // Check the return code from the send of the response. - ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - if (m_pCallback != nullptr) { // We have completed the write, if there is a user supplied callback handler, invoke it now. m_pCallback->onWrite(this); // Invoke the onWrite callback handler. } @@ -223,26 +195,6 @@ void BLEDescriptor::handleGATTServerEvent( m_pCallback->onRead(this); // Invoke the onRead callback method in the callback handler. } - if (param->read.need_rsp) { // Do we need a response - ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); - esp_gatt_rsp_t rsp; - rsp.attr_value.len = getLength(); - rsp.attr_value.handle = param->read.handle; - rsp.attr_value.offset = 0; - rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; - memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.len); - - esp_err_t errRc = ::esp_ble_gatts_send_response( - gatts_if, - param->read.conn_id, - param->read.trans_id, - ESP_GATT_OK, - &rsp); - - if (errRc != ESP_OK) { // Check the return code from the send of the response. - ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - } // End of need a response. } // End of this is our handle break; } // ESP_GATTS_READ_EVT diff --git a/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index d9e0aefb..03cc5791 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -24,8 +24,8 @@ class BLEDescriptorCallbacks; */ class BLEDescriptor { public: - BLEDescriptor(const char* uuid); - BLEDescriptor(BLEUUID uuid); + BLEDescriptor(const char* uuid, uint16_t max_len = 100); + BLEDescriptor(BLEUUID uuid, uint16_t max_len = 100); virtual ~BLEDescriptor(); uint16_t getHandle(); // Get the handle of the descriptor. diff --git a/cpp_utils/BLEDescriptorMap.cpp b/cpp_utils/BLEDescriptorMap.cpp index 075b3025..6b845833 100644 --- a/cpp_utils/BLEDescriptorMap.cpp +++ b/cpp_utils/BLEDescriptorMap.cpp @@ -32,8 +32,8 @@ BLEDescriptor* BLEDescriptorMap::getByUUID(const char* uuid) { */ BLEDescriptor* BLEDescriptorMap::getByUUID(BLEUUID uuid) { for (auto &myPair : m_uuidMap) { - if (myPair.second->getUUID().equals(uuid)) { - return myPair.second; + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; } } //return m_uuidMap.at(uuid.toString()); @@ -58,7 +58,7 @@ BLEDescriptor* BLEDescriptorMap::getByHandle(uint16_t handle) { * @return N/A. */ void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor* pDescriptor){ - m_uuidMap.insert(std::pair(uuid, pDescriptor)); + m_uuidMap.insert(std::pair(pDescriptor, uuid)); } // setByUUID @@ -70,7 +70,7 @@ void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor* pDescriptor){ * @return N/A. */ void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor* pDescriptor) { - m_uuidMap.insert(std::pair(uuid.toString(), pDescriptor)); + m_uuidMap.insert(std::pair(pDescriptor, uuid.toString())); } // setByUUID @@ -98,7 +98,7 @@ std::string BLEDescriptorMap::toString() { stringStream << "\n"; } count++; - stringStream << "handle: 0x" << std::setw(2) << myPair.second->getHandle() << ", uuid: " + myPair.second->getUUID().toString(); + stringStream << "handle: 0x" << std::setw(2) << myPair.first->getHandle() << ", uuid: " + myPair.first->getUUID().toString(); } return stringStream.str(); } // toString @@ -116,7 +116,7 @@ void BLEDescriptorMap::handleGATTServerEvent( esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every descriptor we have. for (auto &myPair : m_uuidMap) { - myPair.second->handleGATTServerEvent(event, gatts_if, param); + myPair.first->handleGATTServerEvent(event, gatts_if, param); } } // handleGATTServerEvent @@ -128,7 +128,7 @@ void BLEDescriptorMap::handleGATTServerEvent( BLEDescriptor* BLEDescriptorMap::getFirst() { m_iterator = m_uuidMap.begin(); if (m_iterator == m_uuidMap.end()) return nullptr; - BLEDescriptor* pRet = m_iterator->second; + BLEDescriptor* pRet = m_iterator->first; m_iterator++; return pRet; } // getFirst @@ -140,7 +140,7 @@ BLEDescriptor* BLEDescriptorMap::getFirst() { */ BLEDescriptor* BLEDescriptorMap::getNext() { if (m_iterator == m_uuidMap.end()) return nullptr; - BLEDescriptor* pRet = m_iterator->second; + BLEDescriptor* pRet = m_iterator->first; m_iterator++; return pRet; } // getNext diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index add7e41e..2745d4c8 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -271,7 +271,7 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; } if(m_bleAdvertising != nullptr) { - BLEDevice::getAdvertising()->gapEventHandler(event, param); + BLEDevice::getAdvertising()->handleGAPEvent(event, param); } if(m_customGapHandler != nullptr) { diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 9e8b05ae..7e7d67ac 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -292,7 +292,6 @@ void BLEService::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t } pCharacteristic->setHandle(param->add_char.attr_handle); m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic); - //ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str()); break; } // Reached the correct service. break; diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index b503b7ca..abec5a8a 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -41,6 +41,7 @@ typedef struct { } member_t; static const member_t members_ids[] = { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 {0xFE08, "Microsoft"}, {0xFE09, "Pillsy, Inc."}, {0xFE0A, "ruwido austria gmbh"}, @@ -290,6 +291,7 @@ static const member_t members_ids[] = { {0xFEFE, "GN ReSound A/S"}, {0xFEFF, "GN Netcom"}, {0xFFFF, "Reserved"}, /*for testing purposes only*/ +#endif {0, "" } }; @@ -299,6 +301,7 @@ typedef struct { } gattdescriptor_t; static const gattdescriptor_t g_descriptor_ids[] = { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 {0x2905,"Characteristic Aggregate Format"}, {0x2900,"Characteristic Extended Properties"}, {0x2904,"Characteristic Presentation Format"}, @@ -314,6 +317,7 @@ static const gattdescriptor_t g_descriptor_ids[] = { {0x290E,"Time Trigger Setting"}, {0x2906,"Valid Range"}, {0x290A,"Value Trigger Setting"}, +#endif { 0, "" } }; @@ -323,6 +327,7 @@ typedef struct { } characteristicMap_t; static const characteristicMap_t g_characteristicsMappings[] = { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 {0x2A7E,"Aerobic Heart Rate Lower Limit"}, {0x2A84,"Aerobic Heart Rate Upper Limit"}, {0x2A7F,"Aerobic Threshold"}, @@ -539,6 +544,7 @@ static const characteristicMap_t g_characteristicsMappings[] = { {0x2A9D,"Weight Measurement"}, {0x2A9E,"Weight Scale Feature"}, {0x2A79,"Wind Chill"}, +#endif {0, ""} }; @@ -556,6 +562,7 @@ typedef struct { * Definition of the service ids to names that we know about. */ static const gattService_t g_gattServices[] = { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 {"Alert Notification Service", "org.bluetooth.service.alert_notification", 0x1811}, {"Automation IO", "org.bluetooth.service.automation_io", 0x1815 }, {"Battery Service","org.bluetooth.service.battery_service", 0x180F}, @@ -591,6 +598,7 @@ static const gattService_t g_gattServices[] = { {"Tx Power", "org.bluetooth.service.tx_power", 0x1804}, {"User Data", "org.bluetooth.service.user_data", 0x181C}, {"Weight Scale", "org.bluetooth.service.weight_scale", 0x181D}, +#endif {"", "", 0 } }; @@ -629,6 +637,7 @@ static std::string gattIdToString(esp_gatt_id_t gattId) { */ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { switch (type) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case BLE_ADDR_TYPE_PUBLIC: return "BLE_ADDR_TYPE_PUBLIC"; case BLE_ADDR_TYPE_RANDOM: @@ -637,6 +646,7 @@ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { return "BLE_ADDR_TYPE_RPA_PUBLIC"; case BLE_ADDR_TYPE_RPA_RANDOM: return "BLE_ADDR_TYPE_RPA_RANDOM"; +#endif default: return " esp_ble_addr_type_t"; } @@ -679,6 +689,7 @@ std::string BLEUtils::adFlagsToString(uint8_t adFlags) { */ const char* BLEUtils::advTypeToString(uint8_t advType) { switch (advType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_BLE_AD_TYPE_FLAG: // 0x01 return "ESP_BLE_AD_TYPE_FLAG"; case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 @@ -729,6 +740,7 @@ const char* BLEUtils::advTypeToString(uint8_t advType) { return "ESP_BLE_AD_TYPE_128SERVICE_DATA"; case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xff return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; +#endif default: ESP_LOGV(LOG_TAG, " adv data type: 0x%x", advType); return ""; @@ -812,42 +824,35 @@ std::string BLEUtils::buildPrintData(uint8_t* source, size_t length) { */ std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) { switch (reason) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GATT_CONN_UNKNOWN: { return "ESP_GATT_CONN_UNKNOWN"; } - case ESP_GATT_CONN_L2C_FAILURE: { return "ESP_GATT_CONN_L2C_FAILURE"; } - case ESP_GATT_CONN_TIMEOUT: { return "ESP_GATT_CONN_TIMEOUT"; } - case ESP_GATT_CONN_TERMINATE_PEER_USER: { return "ESP_GATT_CONN_TERMINATE_PEER_USER"; } - case ESP_GATT_CONN_TERMINATE_LOCAL_HOST: { return "ESP_GATT_CONN_TERMINATE_LOCAL_HOST"; } - case ESP_GATT_CONN_FAIL_ESTABLISH: { return "ESP_GATT_CONN_FAIL_ESTABLISH"; } - case ESP_GATT_CONN_LMP_TIMEOUT: { return "ESP_GATT_CONN_LMP_TIMEOUT"; } - case ESP_GATT_CONN_CONN_CANCEL: { return "ESP_GATT_CONN_CONN_CANCEL"; } - case ESP_GATT_CONN_NONE: { return "ESP_GATT_CONN_NONE"; } - +#endif default: { return "Unknown"; } @@ -857,6 +862,7 @@ std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) { std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType) { switch (eventType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GATTC_ACL_EVT: return "ESP_GATTC_ACL_EVT"; case ESP_GATTC_ADV_DATA_EVT: @@ -939,6 +945,7 @@ std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType return "ESP_GATTC_WRITE_CHAR_EVT"; case ESP_GATTC_WRITE_DESCR_EVT: return "ESP_GATTC_WRITE_DESCR_EVT"; +#endif default: ESP_LOGW(LOG_TAG, "Unknown GATT Client event type: %d", eventType); return "Unknown"; @@ -953,6 +960,7 @@ std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType */ std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType) { switch (eventType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GATTS_REG_EVT: return "ESP_GATTS_REG_EVT"; case ESP_GATTS_READ_EVT: @@ -1001,8 +1009,9 @@ std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType return "ESP_GATTS_CREAT_ATTR_TAB_EVT"; case ESP_GATTS_SET_ATTR_VAL_EVT: return "ESP_GATTS_SET_ATTR_VAL_EVT"; - // case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: - // return "ESP_GATTS_SEND_SERVICE_CHANGE_EVT"; + case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: + return "ESP_GATTS_SEND_SERVICE_CHANGE_EVT"; +#endif default: return "Unknown"; } @@ -1016,12 +1025,14 @@ std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType */ const char* BLEUtils::devTypeToString(esp_bt_dev_type_t type) { switch (type) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_BT_DEVICE_TYPE_BREDR: return "ESP_BT_DEVICE_TYPE_BREDR"; case ESP_BT_DEVICE_TYPE_BLE: return "ESP_BT_DEVICE_TYPE_BLE"; case ESP_BT_DEVICE_TYPE_DUMO: return "ESP_BT_DEVICE_TYPE_DUMO"; +#endif default: return "Unknown"; } @@ -1036,6 +1047,7 @@ void BLEUtils::dumpGapEvent( esp_ble_gap_cb_param_t* param) { ESP_LOGV(LOG_TAG, "Received a GAP event: %s", gapEventToString(event)); switch (event) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT // adv_data_cmpl // - esp_bt_status_t @@ -1246,7 +1258,7 @@ void BLEUtils::dumpGapEvent( ESP_LOGV(LOG_TAG, "[bd_addr: %s]", BLEAddress(param->ble_security.ble_req.bd_addr).toString().c_str()); break; } // ESP_GAP_BLE_SEC_REQ_EVT - +#endif default: { ESP_LOGV(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); break; @@ -1269,6 +1281,7 @@ void BLEUtils::dumpGattClientEvent( //esp_ble_gattc_cb_param_t* evtParam = (esp_ble_gattc_cb_param_t*) param; ESP_LOGV(LOG_TAG, "GATT Event: %s", BLEUtils::gattClientEventTypeToString(event).c_str()); switch (event) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 // ESP_GATTC_CLOSE_EVT // // close: @@ -1492,7 +1505,7 @@ void BLEUtils::dumpGattClientEvent( ); break; } // ESP_GATTC_WRITE_CHAR_EVT - +#endif default: break; } @@ -1515,6 +1528,7 @@ void BLEUtils::dumpGattServerEvent( esp_ble_gatts_cb_param_t* evtParam) { ESP_LOGV(LOG_TAG, "GATT ServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); switch (event) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GATTS_ADD_CHAR_DESCR_EVT: { ESP_LOGV(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", @@ -1700,7 +1714,7 @@ void BLEUtils::dumpGattServerEvent( free(pHex); break; } // ESP_GATTS_WRITE_EVT - +#endif default: ESP_LOGV(LOG_TAG, "dumpGattServerEvent: *** NOT CODED ***"); break; @@ -1715,6 +1729,7 @@ void BLEUtils::dumpGattServerEvent( */ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { switch (eventType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_BLE_EVT_CONN_ADV: return "ESP_BLE_EVT_CONN_ADV"; case ESP_BLE_EVT_CONN_DIR_ADV: @@ -1725,6 +1740,7 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { return "ESP_BLE_EVT_NON_CONN_ADV"; case ESP_BLE_EVT_SCAN_RSP: return "ESP_BLE_EVT_SCAN_RSP"; +#endif default: ESP_LOGV(LOG_TAG, "Unknown esp_ble_evt_type_t: %d (0x%.2x)", eventType, eventType); return "*** Unknown ***"; @@ -1740,6 +1756,7 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { */ const char* BLEUtils::gapEventToString(uint32_t eventType) { switch (eventType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT"; case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: @@ -1794,6 +1811,7 @@ const char* BLEUtils::gapEventToString(uint32_t eventType) { return "ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT"; case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: return "ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT"; +#endif default: ESP_LOGV(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); return "Unknown event type"; @@ -1874,6 +1892,7 @@ std::string BLEUtils::gattServiceToString(uint32_t serviceId) { */ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { switch (status) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GATT_OK: return "ESP_GATT_OK"; case ESP_GATT_INVALID_HANDLE: @@ -1960,6 +1979,7 @@ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { return "ESP_GATT_PRC_IN_PROGRESS"; case ESP_GATT_OUT_OF_RANGE: return "ESP_GATT_OUT_OF_RANGE"; +#endif default: return "Unknown"; } @@ -1986,6 +2006,7 @@ std::string BLEUtils::getMember(uint32_t memberId) { */ const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { switch (searchEvt) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GAP_SEARCH_INQ_RES_EVT: return "ESP_GAP_SEARCH_INQ_RES_EVT"; case ESP_GAP_SEARCH_INQ_CMPL_EVT: @@ -2000,6 +2021,7 @@ const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { return "ESP_GAP_SEARCH_DI_DISC_CMPL_EVT"; case ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT: return "ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT"; +#endif default: ESP_LOGV(LOG_TAG, "Unknown event type: 0x%x", searchEvt); return "Unknown event type"; diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index 8d58d4eb..dbbc65be 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -365,6 +365,7 @@ std::vector GeneralUtils::split(std::string source, char delimiter) */ const char* GeneralUtils::errorToString(esp_err_t errCode) { switch (errCode) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_OK: return "ESP_OK"; case ESP_FAIL: @@ -431,6 +432,7 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { return "ESP_ERR_WIFI_TIMEOUT"; case ESP_ERR_WIFI_WAKE_FAIL: return "ESP_ERR_WIFI_WAKE_FAIL"; +#endif default: return "Unknown ESP_ERR error"; } @@ -448,6 +450,7 @@ const char* GeneralUtils::wifiErrorToString(uint8_t errCode) { if (errCode == UINT8_MAX) return "Not Connected (default value)"; switch ((wifi_err_reason_t) errCode) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case WIFI_REASON_UNSPECIFIED: return "WIFI_REASON_UNSPECIFIED"; case WIFI_REASON_AUTH_EXPIRE: @@ -504,6 +507,7 @@ const char* GeneralUtils::wifiErrorToString(uint8_t errCode) { return "WIFI_REASON_ASSOC_FAIL"; case WIFI_REASON_HANDSHAKE_TIMEOUT: return "WIFI_REASON_HANDSHAKE_TIMEOUT"; +#endif default: return "Unknown ESP_ERR error"; } From 91f46d723cd9b54cfeba9e7cda6c2406cd3b5174 Mon Sep 17 00:00:00 2001 From: rkone Date: Tue, 27 Nov 2018 11:11:25 -0500 Subject: [PATCH 361/381] fix duplicate close parens --- cpp_utils/BLEUUID.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index ee8b6c9d..fd0f5ad5 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -95,8 +95,6 @@ BLEUUID::BLEUUID(std::string value) { m_uuid.uuid.uuid32 += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(6-i)*4; i+=2; } - } - } } else if (value.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be investigated (lack of time) m_uuid.len = ESP_UUID_LEN_128; From 8687fc40e9953dc876dccab401278b0f3cb26dfe Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 27 Nov 2018 17:25:01 +0100 Subject: [PATCH 362/381] Few bugixes, compatibility changes and requested functionalities --- cpp_utils/BLEAdvertisedDevice.cpp | 14 +++---- cpp_utils/BLEAdvertisedDevice.h | 3 +- cpp_utils/BLECharacteristic.cpp | 2 +- cpp_utils/BLEClient.cpp | 27 +++++++------ cpp_utils/BLEDevice.cpp | 5 ++- cpp_utils/BLERemoteCharacteristic.cpp | 57 ++++++++++++++------------- cpp_utils/BLEScan.cpp | 12 +++++- cpp_utils/BLEServer.cpp | 13 ++++++ cpp_utils/BLEServer.h | 3 +- cpp_utils/BLEUtils.cpp | 2 +- 10 files changed, 84 insertions(+), 54 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index dbda9172..41d5faba 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -233,7 +233,8 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload, size_t total_len) uint8_t ad_type; uint8_t sizeConsumed = 0; bool finished = false; - setPayload(payload); + m_payload = payload; + m_payloadLength = total_len; while(!finished) { length = *payload; // Retrieve the length of the record. @@ -351,9 +352,9 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload, size_t total_len) } // Length <> 0 - if (sizeConsumed >= total_len || length == 0) { + if (sizeConsumed >= total_len) finished = true; - } + } // !finished } // parseAdvertisement @@ -510,10 +511,6 @@ uint8_t* BLEAdvertisedDevice::getPayload() { return m_payload; } -void BLEAdvertisedDevice::setPayload(uint8_t* payload) { - m_payload = payload; -} - esp_ble_addr_type_t BLEAdvertisedDevice::getAddressType() { return m_addressType; } @@ -522,6 +519,9 @@ void BLEAdvertisedDevice::setAddressType(esp_ble_addr_type_t type) { m_addressType = type; } +size_t BLEAdvertisedDevice::getPayloadLength() { + return m_payloadLength; +} #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index aaa1417c..aec83746 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -40,6 +40,7 @@ class BLEAdvertisedDevice { BLEUUID getServiceUUID(); int8_t getTXPower(); uint8_t* getPayload(); + size_t getPayloadLength(); esp_ble_addr_type_t getAddressType(); void setAddressType(esp_ble_addr_type_t type); @@ -72,7 +73,6 @@ class BLEAdvertisedDevice { void setServiceUUID(const char* serviceUUID); void setServiceUUID(BLEUUID serviceUUID); void setTXPower(int8_t txPower); - void setPayload(uint8_t* payload); bool m_haveAppearance; bool m_haveManufacturerData; @@ -96,6 +96,7 @@ class BLEAdvertisedDevice { std::string m_serviceData; BLEUUID m_serviceDataUUID; uint8_t* m_payload; + size_t m_payloadLength = 0; esp_ble_addr_type_t m_addressType; }; diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index bdeee727..dee4454a 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -430,7 +430,7 @@ void BLECharacteristic::handleGATTServerEvent( // - uint16_t conn_id – The connection used. // case ESP_GATTS_CONF_EVT: { - ESP_LOGD(LOG_TAG, "m_handle = %d, conf->handle = %d", m_handle, param->conf.handle); + // ESP_LOGD(LOG_TAG, "m_handle = %d, conf->handle = %d", m_handle, param->conf.handle); if(param->conf.conn_id == getService()->getServer()->getConnId()) // && param->conf.handle == m_handle) // bug in esp-idf and not implemented in arduino yet m_semaphoreConfEvt.give(param->conf.status); break; diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 66baf166..8009f408 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -102,7 +102,7 @@ bool BLEClient::connect(BLEAddress address, esp_ble_addr_type_t type) { // We need the connection handle that we get from registering the application. We register the app // and then block on its completion. When the event has arrived, we will have the handle. m_appId = BLEDevice::m_appId++; - BLEDevice::addPeerDevice(this, true, ESP_GATT_IF_NONE); + BLEDevice::addPeerDevice(this, true, m_appId); m_semaphoreRegEvt.take("connect"); // clearServices(); // we dont need to delete services since every client is unique? @@ -119,10 +119,10 @@ bool BLEClient::connect(BLEAddress address, esp_ble_addr_type_t type) { // Perform the open connection request against the target BLE Server. m_semaphoreOpenEvt.take("connect"); errRc = ::esp_ble_gattc_open( - getGattcIf(), + m_gattc_if, *getPeerAddress().getNative(), // address type, // Note: This was added on 2018-04-03 when the latest ESP-IDF was detected to have changed the signature. - 1 // direct connection + 1 // direct connection <-- maybe needs to be changed in case of direct indirect connection??? ); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -184,10 +184,12 @@ void BLEClient::gattClientEventHandler( case ESP_GATTC_DISCONNECT_EVT: { // If we receive a disconnect event, set the class flag that indicates that we are // no longer connected. + m_isConnected = false; if (m_pClientCallbacks != nullptr) { m_pClientCallbacks->onDisconnect(this); } - m_isConnected = false; + BLEDevice::removePeerDevice(m_appId, true); + esp_ble_gattc_app_unregister(m_gattc_if); m_semaphoreRssiCmplEvt.give(); m_semaphoreSearchCmplEvt.give(1); break; @@ -261,14 +263,15 @@ void BLEClient::gattClientEventHandler( ESP_LOGE(LOG_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); break; } -#ifndef ARDUINO_ARCH_ESP32 - if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) { - ESP_LOGI(LOG_TAG, "Get service information from remote device"); - } else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) { - ESP_LOGI(LOG_TAG, "Get service information from flash"); - } else { - ESP_LOGI(LOG_TAG, "unknown service source"); - } +#ifndef ARDUINO_ARCH_ESP32 +// commented out just for now to keep backward compatibility + // if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) { + // ESP_LOGI(LOG_TAG, "Get service information from remote device"); + // } else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) { + // ESP_LOGI(LOG_TAG, "Get service information from flash"); + // } else { + // ESP_LOGI(LOG_TAG, "unknown service source"); + // } #endif m_semaphoreSearchCmplEvt.give(0); break; diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 2745d4c8..33efabdb 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -603,7 +603,8 @@ void BLEDevice::addPeerDevice(void* peer, bool _client, uint16_t conn_id) { void BLEDevice::removePeerDevice(uint16_t conn_id, bool _client) { ESP_LOGI(LOG_TAG, "remove: %d, GATT role %s", conn_id, _client?"client":"server"); - m_connectedClientsMap.erase(conn_id); + if(m_connectedClientsMap.find(conn_id) != m_connectedClientsMap.end()) + m_connectedClientsMap.erase(conn_id); } /* multi connect support */ @@ -621,7 +622,7 @@ void BLEDevice::removePeerDevice(uint16_t conn_id, bool _client) { esp_bt_controller_deinit(); #ifndef ARDUINO_ARCH_ESP32 if (release_memory) { - esp_bt_mem_release(ESP_BT_MODE_BTDM); // <-- require tests because we released classic BT memory and this can cause crash (most likely not, esp-idf takes care of it) + esp_bt_controller_mem_release(ESP_BT_MODE_BTDM); // <-- require tests because we released classic BT memory and this can cause crash (most likely not, esp-idf takes care of it) } else { initialized = false; } diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 1768dbb6..21d91b47 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -473,7 +473,7 @@ void BLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, } uint8_t val[] = {0x00, 0x00}; - BLERemoteDescriptor* desc = getDescriptor(BLEUUID("0x2902")); + BLERemoteDescriptor* desc = getDescriptor((uint16_t)0x2902); desc->writeValue(val, 2); } // End Unregister @@ -520,7 +520,32 @@ std::string BLERemoteCharacteristic::toString() { * @return N/A. */ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { - ESP_LOGD(LOG_TAG, ">> writeValue(), length: %d", newValue.length()); + writeValue((uint8_t*)newValue.c_str(), strlen(newValue.c_str()), response); +} // writeValue + + +/** + * @brief Write the new value for the characteristic. + * + * This is a convenience function. Many BLE characteristics are a single byte of data. + * @param [in] newValue The new byte value to write. + * @param [in] response Whether we require a response from the write. + * @return N/A. + */ +void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { + writeValue(&newValue, 1, response); +} // writeValue + + +/** + * @brief Write the new value for the characteristic from a data buffer. + * @param [in] data A pointer to a data buffer. + * @param [in] length The length of the data in the data buffer. + * @param [in] response Whether we require a response from the write. + */ +void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) { + // writeValue(std::string((char*)data, length), response); + ESP_LOGD(LOG_TAG, ">> writeValue(), length: %d", length); // Check to see that we are connected. if (!getRemoteService()->getClient()->isConnected()) { @@ -534,8 +559,8 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getConnId(), getHandle(), - newValue.length(), - (uint8_t*)newValue.data(), + length, + data, response?ESP_GATT_WRITE_TYPE_RSP:ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE ); @@ -550,30 +575,6 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { ESP_LOGD(LOG_TAG, "<< writeValue"); } // writeValue - -/** - * @brief Write the new value for the characteristic. - * - * This is a convenience function. Many BLE characteristics are a single byte of data. - * @param [in] newValue The new byte value to write. - * @param [in] response Whether we require a response from the write. - * @return N/A. - */ -void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { - writeValue(std::string(reinterpret_cast(&newValue), 1), response); -} // writeValue - - -/** - * @brief Write the new value for the characteristic from a data buffer. - * @param [in] data A pointer to a data buffer. - * @param [in] length The length of the data in the data buffer. - * @param [in] response Whether we require a response from the write. - */ -void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) { - writeValue(std::string((char*)data, length), response); -} // writeValue - /** * @brief Read raw data from remote characteristic as hex bytes * @return return pointer data read diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 7164626b..e1778b84 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -101,7 +101,7 @@ void BLEScan::handleGAPEvent( if (found && !m_wantDuplicates) { // If we found a previous entry AND we don't want duplicates, then we are done. ESP_LOGD(LOG_TAG, "Ignoring %s, already seen it.", advertisedAddress.toString().c_str()); - vTaskDelay(1); // <--- allow to switch task in case we scan infinity and dont have new devices to report, or we will blocked here + vTaskDelay(1); // <--- allow to switch task in case we scan infinity and dont have new devices to report, or we are blocked here break; } @@ -314,5 +314,15 @@ BLEAdvertisedDevice BLEScanResults::getDevice(uint32_t i) { return dev; } +BLEScanResults BLEScan::getResults() { + return m_scanResults; +} + +void BLEScan::clearResults() { + for(auto _dev : m_scanResults.m_vectorAdvertisedDevices){ + delete _dev.second; + } + m_scanResults.m_vectorAdvertisedDevices.clear(); +} #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 63962027..9b52762d 100755 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -405,4 +405,17 @@ void BLEServer::removePeerDevice(uint16_t conn_id, bool _client) { m_connectedServersMap.erase(conn_id); } /* multi connect support */ + +/** + * Update connection parameters can be called only after connection has been established + */ +void BLEServer::updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) { + esp_ble_conn_update_params_t conn_params; + memcpy(conn_params.bda, remote_bda, sizeof(esp_bd_addr_t)); + conn_params.latency = latency; + conn_params.max_int = maxInterval; // max_int = 0x20*1.25ms = 40ms + conn_params.min_int = minInterval; // min_int = 0x10*1.25ms = 20ms + conn_params.timeout = timeout; // timeout = 400*10ms = 4000ms + esp_ble_gap_update_conn_params(&conn_params); +} #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index 23d602a8..d39d8bfe 100755 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -73,6 +73,7 @@ class BLEServer { BLEService* getServiceByUUID(BLEUUID uuid); bool connect(BLEAddress address); uint16_t m_appId; + void updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout); /* multi connection support */ std::map getPeerDevices(bool client); @@ -81,6 +82,7 @@ class BLEServer { BLEServer* getServerByConnId(uint16_t conn_id); void updatePeerMTU(uint16_t connId, uint16_t mtu); uint16_t getPeerMTU(uint16_t conn_id); + uint16_t getConnId(); private: @@ -102,7 +104,6 @@ class BLEServer { BLEServerCallbacks* m_pServerCallbacks = nullptr; void createApp(uint16_t appId); - uint16_t getConnId(); uint16_t getGattsIf(); void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); void registerApp(uint16_t); diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index abec5a8a..02cdf9ae 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -947,7 +947,7 @@ std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType return "ESP_GATTC_WRITE_DESCR_EVT"; #endif default: - ESP_LOGW(LOG_TAG, "Unknown GATT Client event type: %d", eventType); + ESP_LOGV(LOG_TAG, "Unknown GATT Client event type: %d", eventType); return "Unknown"; } } // gattClientEventTypeToString From ba964d5dd7a61ef0ba89d340399b0dc29011ec82 Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 27 Nov 2018 18:52:18 +0100 Subject: [PATCH 363/381] Add missing BLEScan.h --- cpp_utils/BLEScan.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index ae4663e1..2f71a727 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -58,7 +58,8 @@ class BLEScan { BLEScanResults start(uint32_t duration, bool is_continue = false); void stop(); void erase(BLEAddress address); - // void setPower(esp_power_level_t powerLevel); + BLEScanResults getResults(); + void clearResults(); private: BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. From 0d01a6402ca404cce499b77c5c9c39055d204226 Mon Sep 17 00:00:00 2001 From: rkone Date: Fri, 7 Dec 2018 14:58:17 -0500 Subject: [PATCH 364/381] uint8_t < 256 is always true --- cpp_utils/MMU.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_utils/MMU.cpp b/cpp_utils/MMU.cpp index 57c0ccce..ee7adb59 100644 --- a/cpp_utils/MMU.cpp +++ b/cpp_utils/MMU.cpp @@ -47,7 +47,7 @@ static uint32_t flashPageToOffset(uint32_t page) { const uint32_t mappingInvalid = 1 << 8; printf("PRO CPU MMU\n"); - for (uint8_t i = 0; i < 256; i++) { + for (uint16_t i = 0; i < 256; i++) { if (!(DPORT_PRO_FLASH_MMU_TABLE[i] & mappingInvalid)) { addressRange_t addressRange = entryNumberToAddressRange(i); printf("Entry: %2d (0x%8.8x - 0x%8.8x), Page: %d - offset: 0x%x\n", @@ -59,7 +59,7 @@ static uint32_t flashPageToOffset(uint32_t page) { } printf("\n"); printf("APP CPU MMU\n"); - for (uint8_t i = 0; i < 256; i++) { + for (uint16_t i = 0; i < 256; i++) { if (!(DPORT_APP_FLASH_MMU_TABLE[i] & mappingInvalid)) { addressRange_t addressRange = entryNumberToAddressRange(i); printf("Entry: %2d (0x%8.8x - 0x%8.8x), Page: %d - offset: 0x%x\n", From ab386db94ca1a836da1f01f3ebe495173597ebea Mon Sep 17 00:00:00 2001 From: Markus Keppeler Date: Fri, 14 Dec 2018 16:54:48 +0100 Subject: [PATCH 365/381] Add access to remote address in BLERemoteCharacteristic --- cpp_utils/BLERemoteCharacteristic.cpp | 8 ++++++++ cpp_utils/BLERemoteCharacteristic.h | 1 + 2 files changed, 9 insertions(+) diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 21d91b47..db52cdc5 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -582,5 +582,13 @@ void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool resp uint8_t* BLERemoteCharacteristic::readRawData() { return m_rawData; } +/** + * @brief Get the address of the remote server + * @return RemoteAddress + */ +BLEAddress BLERemoteCharacteristic::getRemoteAddress(){ + return m_pRemoteService->getClient()->getPeerAddress(); +} + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index fbcafe8d..47517fff 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -51,6 +51,7 @@ class BLERemoteCharacteristic { void writeValue(uint8_t newValue, bool response = false); std::string toString(); uint8_t* readRawData(); + BLEAddress getRemoteAddress(); private: BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); From 5d616801938d066f52e2fcb925cedcfef87d3b4e Mon Sep 17 00:00:00 2001 From: luisan00 Date: Mon, 17 Dec 2018 11:48:40 +0100 Subject: [PATCH 366/381] Fix error: 'BLEAddress' does not name a type When: compiling a project. Where: Line 54 in: BLEAddress getRemoteAddress(); Drops error: 'BLEAddress' does not name a type My Solution: Add the include: #include "BLEAddress.h" --- cpp_utils/BLERemoteCharacteristic.h | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 47517fff..b8764f65 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -17,6 +17,7 @@ #include "BLERemoteService.h" #include "BLERemoteDescriptor.h" #include "BLEUUID.h" +#include "BLEAddress.h" #include "FreeRTOS.h" class BLERemoteService; From 89aac0a853e29eb29fd80d2d1a7bac606d08d896 Mon Sep 17 00:00:00 2001 From: Ryotaro Onuki Date: Wed, 19 Dec 2018 18:51:35 +0900 Subject: [PATCH 367/381] fix #654 --- cpp_utils/BLERemoteCharacteristic.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index db52cdc5..927431aa 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -44,6 +44,7 @@ BLERemoteCharacteristic::BLERemoteCharacteristic( m_charProp = charProp; m_pRemoteService = pRemoteService; m_notifyCallback = nullptr; + m_rawData = nullptr; retrieveDescriptors(); // Get the descriptors for this characteristic ESP_LOGD(LOG_TAG, "<< BLERemoteCharacteristic"); @@ -54,6 +55,7 @@ BLERemoteCharacteristic::BLERemoteCharacteristic( *@brief Destructor. */ BLERemoteCharacteristic::~BLERemoteCharacteristic() { + if(m_rawData != nullptr) free(m_rawData); removeDescriptors(); // Release resources for any descriptor information we may have allocated. } // ~BLERemoteCharacteristic From 2f13d7483447f53d13fbf2f7aa573f33221b9b3d Mon Sep 17 00:00:00 2001 From: Bill Den Beste Date: Thu, 20 Dec 2018 18:24:05 -0800 Subject: [PATCH 368/381] init should not set I2C addr to 0 init takes sdaPin and clkPin but does not take an I2C address. Therefore the call to init should not set the I2C address to 0. It should leave it alone. --- cpp_utils/PCF8574.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp_utils/PCF8574.cpp b/cpp_utils/PCF8574.cpp index 79be44c0..d246b7a6 100644 --- a/cpp_utils/PCF8574.cpp +++ b/cpp_utils/PCF8574.cpp @@ -116,5 +116,6 @@ void PCF8574::setInvert(bool value) { * @param [in] clkPin The pin to use for the %I2C CLK functions. */ void PCF8574::init(gpio_num_t sdaPin, gpio_num_t clkPin) { - i2c->init(0, sdaPin, clkPin); + uint8_t addr = i2c->getAddress(); + i2c->init(addr, sdaPin, clkPin); } // init From 20c090064e830f661ac3355a0099a511184aff95 Mon Sep 17 00:00:00 2001 From: Samuel Rounce Date: Mon, 24 Dec 2018 12:31:21 +0000 Subject: [PATCH 369/381] Add CMakeLists.txt to allow use with ESP-IDF CMake preview --- cpp_utils/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 cpp_utils/CMakeLists.txt diff --git a/cpp_utils/CMakeLists.txt b/cpp_utils/CMakeLists.txt new file mode 100644 index 00000000..fab2ea80 --- /dev/null +++ b/cpp_utils/CMakeLists.txt @@ -0,0 +1,7 @@ +# Edit following two lines to set component requirements (see docs) +set(COMPONENT_REQUIRES ) +set(COMPONENT_PRIV_REQUIRES ) + +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() From b3670f10af98163b468dc1068edffdff8c719792 Mon Sep 17 00:00:00 2001 From: Samuel Rounce Date: Tue, 25 Dec 2018 10:48:21 +0000 Subject: [PATCH 370/381] Fix for greedy file(GLOB) --- cpp_utils/CMakeLists.txt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cpp_utils/CMakeLists.txt b/cpp_utils/CMakeLists.txt index fab2ea80..cb9b7267 100644 --- a/cpp_utils/CMakeLists.txt +++ b/cpp_utils/CMakeLists.txt @@ -1,7 +1,20 @@ # Edit following two lines to set component requirements (see docs) -set(COMPONENT_REQUIRES ) +set(COMPONENT_REQUIRES + "console" + "fatfs" + "json" + "mdns" + "nvs_flash" +) set(COMPONENT_PRIV_REQUIRES ) +file(GLOB COMPONENT_SRCS + LIST_DIRECTORIES false + "*.h" + "*.cpp" + "*.c" + "*.S" +) set(COMPONENT_ADD_INCLUDEDIRS ".") register_component() From ea3154a3a5abddd935b2b0a83532e66968f2d56d Mon Sep 17 00:00:00 2001 From: wakwak_koba <5591750+wakwak-koba@users.noreply.github.com> Date: Thu, 17 Jan 2019 21:35:18 +0900 Subject: [PATCH 371/381] fixed bugs --- cpp_utils/BLEDevice.cpp | 18 +++++++++++--- cpp_utils/BLERemoteCharacteristic.cpp | 14 +++++++---- cpp_utils/BLERemoteCharacteristic.h | 2 +- cpp_utils/BLERemoteService.cpp | 34 ++++++++++++++++++++------- cpp_utils/BLEUtils.cpp | 20 ---------------- 5 files changed, 51 insertions(+), 37 deletions(-) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 33efabdb..360f97e3 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -162,10 +162,22 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; break; } // switch for(auto &myPair : BLEDevice::getPeerDevices(true)) { - conn_status_t conn_status = (conn_status_t)myPair.second; - if(((BLEClient*)conn_status.peer_device)->getGattcIf() == gattc_if || ((BLEClient*)conn_status.peer_device)->getGattcIf() == ESP_GATT_IF_NONE || gattc_if == ESP_GATT_IF_NONE){ - ((BLEClient*)conn_status.peer_device)->gattClientEventHandler(event, gattc_if, param); + BLEClient* client = (BLEClient*)((conn_status_t)myPair.second).peer_device; + bool raiseEvent = false; + switch(event) { + case ESP_GATTC_REG_EVT: + raiseEvent = (myPair.first == param->reg.app_id); + break; + case ESP_GATTC_DISCONNECT_EVT: + raiseEvent = (client->getConnId() == param->disconnect.conn_id && client->getGattcIf() == gattc_if); + break; + default: + raiseEvent = (client->getGattcIf() == gattc_if || client->getGattcIf() == ESP_GATT_IF_NONE || gattc_if == ESP_GATT_IF_NONE); + break; } + + if(raiseEvent) + client->gattClientEventHandler(event, gattc_if, param); } if(m_customGattcHandler != nullptr) { diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index 927431aa..aa49b385 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -149,6 +149,10 @@ static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { * @returns N/A */ void BLERemoteCharacteristic::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { + + ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", + gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); + switch(event) { // ESP_GATTC_NOTIFY_EVT // @@ -262,7 +266,7 @@ void BLERemoteCharacteristic::retrieveDescriptors() { uint16_t offset = 0; esp_gattc_descr_elem_t result; while(true) { - uint16_t count = 10; + uint16_t count = 1; esp_gatt_status_t status = ::esp_ble_gattc_get_all_descr( getRemoteService()->getClient()->getGattcIf(), getRemoteService()->getClient()->getConnId(), @@ -272,7 +276,7 @@ void BLERemoteCharacteristic::retrieveDescriptors() { offset ); - if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. + if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { // We have reached the end of the entries. break; } @@ -461,7 +465,8 @@ void BLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, uint8_t val[] = {0x01, 0x00}; if(!notifications) val[0] = 0x02; BLERemoteDescriptor* desc = getDescriptor(BLEUUID((uint16_t)0x2902)); - desc->writeValue(val, 2); + if(desc != nullptr) + desc->writeValue(val, 2); } // End Register else { // If we weren't passed a callback function, then this is an unregistration. esp_err_t errRc = ::esp_ble_gattc_unregister_for_notify( @@ -476,7 +481,8 @@ void BLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, uint8_t val[] = {0x00, 0x00}; BLERemoteDescriptor* desc = getDescriptor((uint16_t)0x2902); - desc->writeValue(val, 2); + if(desc != nullptr) + desc->writeValue(val, 2); } // End Unregister m_semaphoreRegForNotifyEvt.wait("registerForNotify"); diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index b8764f65..32305abf 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -53,6 +53,7 @@ class BLERemoteCharacteristic { std::string toString(); uint8_t* readRawData(); BLEAddress getRemoteAddress(); + BLERemoteService* getRemoteService(); private: BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); @@ -63,7 +64,6 @@ class BLERemoteCharacteristic { // Private member functions void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); - BLERemoteService* getRemoteService(); void removeDescriptors(); void retrieveDescriptors(); diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index ef349986..3b4f2a67 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -61,6 +61,10 @@ void BLERemoteService::gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { + + ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", + gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); + switch (event) { // // ESP_GATTC_GET_CHAR_EVT @@ -162,14 +166,14 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { * @return N/A */ void BLERemoteService::retrieveCharacteristics() { - ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); + ESP_LOGD(LOG_TAG, ">> retrieveCharacteristics() for service: %s", getUUID().toString().c_str()); removeCharacteristics(); // Forget any previous characteristics. uint16_t offset = 0; esp_gattc_char_elem_t result; while (true) { - uint16_t count = 10; // this value is used as in parameter that allows to search max 10 chars with the same uuid + uint16_t count = 1; esp_gatt_status_t status = ::esp_ble_gattc_get_all_char( getClient()->getGattcIf(), getClient()->getConnId(), @@ -180,7 +184,7 @@ void BLERemoteService::retrieveCharacteristics() { offset ); - if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. + if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) { // We have reached the end of the entries. break; } @@ -209,8 +213,8 @@ void BLERemoteService::retrieveCharacteristics() { } // Loop forever (until we break inside the loop). m_haveCharacteristics = true; // Remember that we have received the characteristics. - ESP_LOGD(LOG_TAG, "<< getCharacteristics()"); -} // getCharacteristics + ESP_LOGD(LOG_TAG, "<< retrieveCharacteristics()"); +} // retrieveCharacteristics /** @@ -229,6 +233,22 @@ std::map* BLERemoteService::getCharacteri return &m_characteristicMap; } // getCharacteristics +/** + * @brief Retrieve a map of all the characteristics of this service. + * @return A map of all the characteristics of this service. + */ +std::map* BLERemoteService::getCharacteristicsByHandle() { + ESP_LOGD(LOG_TAG, ">> getCharacteristicsByHandle() for service: %s", getUUID().toString().c_str()); + // If is possible that we have not read the characteristics associated with the service so do that + // now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking + // call and does not return until all the characteristics are available. + if (!m_haveCharacteristics) { + retrieveCharacteristics(); + } + ESP_LOGD(LOG_TAG, "<< getCharacteristicsByHandle() for service: %s", getUUID().toString().c_str()); + return &m_characteristicMapByHandle; +} // getCharacteristicsByHandle + /** * @brief This function is designed to get characteristics map when we have multiple characteristics with the same UUID */ @@ -292,10 +312,6 @@ std::string BLERemoteService::getValue(BLEUUID characteristicUuid) { * @return N/A. */ void BLERemoteService::removeCharacteristics() { - for (auto &myPair : m_characteristicMap) { - delete myPair.second; - //m_characteristicMap.erase(myPair.first); // Should be no need to delete as it will be deleted by the clear - } m_characteristicMap.clear(); // Clear the map for (auto &myPair : m_characteristicMapByHandle) { delete myPair.second; diff --git a/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index 02cdf9ae..a9b735df 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -637,7 +637,6 @@ static std::string gattIdToString(esp_gatt_id_t gattId) { */ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { switch (type) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case BLE_ADDR_TYPE_PUBLIC: return "BLE_ADDR_TYPE_PUBLIC"; case BLE_ADDR_TYPE_RANDOM: @@ -646,7 +645,6 @@ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { return "BLE_ADDR_TYPE_RPA_PUBLIC"; case BLE_ADDR_TYPE_RPA_RANDOM: return "BLE_ADDR_TYPE_RPA_RANDOM"; -#endif default: return " esp_ble_addr_type_t"; } @@ -689,7 +687,6 @@ std::string BLEUtils::adFlagsToString(uint8_t adFlags) { */ const char* BLEUtils::advTypeToString(uint8_t advType) { switch (advType) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_BLE_AD_TYPE_FLAG: // 0x01 return "ESP_BLE_AD_TYPE_FLAG"; case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 @@ -740,7 +737,6 @@ const char* BLEUtils::advTypeToString(uint8_t advType) { return "ESP_BLE_AD_TYPE_128SERVICE_DATA"; case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xff return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; -#endif default: ESP_LOGV(LOG_TAG, " adv data type: 0x%x", advType); return ""; @@ -824,7 +820,6 @@ std::string BLEUtils::buildPrintData(uint8_t* source, size_t length) { */ std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) { switch (reason) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GATT_CONN_UNKNOWN: { return "ESP_GATT_CONN_UNKNOWN"; } @@ -852,7 +847,6 @@ std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) { case ESP_GATT_CONN_NONE: { return "ESP_GATT_CONN_NONE"; } -#endif default: { return "Unknown"; } @@ -862,7 +856,6 @@ std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) { std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType) { switch (eventType) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GATTC_ACL_EVT: return "ESP_GATTC_ACL_EVT"; case ESP_GATTC_ADV_DATA_EVT: @@ -945,7 +938,6 @@ std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType return "ESP_GATTC_WRITE_CHAR_EVT"; case ESP_GATTC_WRITE_DESCR_EVT: return "ESP_GATTC_WRITE_DESCR_EVT"; -#endif default: ESP_LOGV(LOG_TAG, "Unknown GATT Client event type: %d", eventType); return "Unknown"; @@ -960,7 +952,6 @@ std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType */ std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType) { switch (eventType) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GATTS_REG_EVT: return "ESP_GATTS_REG_EVT"; case ESP_GATTS_READ_EVT: @@ -1011,7 +1002,6 @@ std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType return "ESP_GATTS_SET_ATTR_VAL_EVT"; case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: return "ESP_GATTS_SEND_SERVICE_CHANGE_EVT"; -#endif default: return "Unknown"; } @@ -1025,14 +1015,12 @@ std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType */ const char* BLEUtils::devTypeToString(esp_bt_dev_type_t type) { switch (type) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_BT_DEVICE_TYPE_BREDR: return "ESP_BT_DEVICE_TYPE_BREDR"; case ESP_BT_DEVICE_TYPE_BLE: return "ESP_BT_DEVICE_TYPE_BLE"; case ESP_BT_DEVICE_TYPE_DUMO: return "ESP_BT_DEVICE_TYPE_DUMO"; -#endif default: return "Unknown"; } @@ -1729,7 +1717,6 @@ void BLEUtils::dumpGattServerEvent( */ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { switch (eventType) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_BLE_EVT_CONN_ADV: return "ESP_BLE_EVT_CONN_ADV"; case ESP_BLE_EVT_CONN_DIR_ADV: @@ -1740,7 +1727,6 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { return "ESP_BLE_EVT_NON_CONN_ADV"; case ESP_BLE_EVT_SCAN_RSP: return "ESP_BLE_EVT_SCAN_RSP"; -#endif default: ESP_LOGV(LOG_TAG, "Unknown esp_ble_evt_type_t: %d (0x%.2x)", eventType, eventType); return "*** Unknown ***"; @@ -1756,7 +1742,6 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { */ const char* BLEUtils::gapEventToString(uint32_t eventType) { switch (eventType) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: return "ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT"; case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: @@ -1811,7 +1796,6 @@ const char* BLEUtils::gapEventToString(uint32_t eventType) { return "ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT"; case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: return "ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT"; -#endif default: ESP_LOGV(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); return "Unknown event type"; @@ -1892,7 +1876,6 @@ std::string BLEUtils::gattServiceToString(uint32_t serviceId) { */ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { switch (status) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GATT_OK: return "ESP_GATT_OK"; case ESP_GATT_INVALID_HANDLE: @@ -1979,7 +1962,6 @@ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { return "ESP_GATT_PRC_IN_PROGRESS"; case ESP_GATT_OUT_OF_RANGE: return "ESP_GATT_OUT_OF_RANGE"; -#endif default: return "Unknown"; } @@ -2006,7 +1988,6 @@ std::string BLEUtils::getMember(uint32_t memberId) { */ const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { switch (searchEvt) { -#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_GAP_SEARCH_INQ_RES_EVT: return "ESP_GAP_SEARCH_INQ_RES_EVT"; case ESP_GAP_SEARCH_INQ_CMPL_EVT: @@ -2021,7 +2002,6 @@ const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { return "ESP_GAP_SEARCH_DI_DISC_CMPL_EVT"; case ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT: return "ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT"; -#endif default: ESP_LOGV(LOG_TAG, "Unknown event type: 0x%x", searchEvt); return "Unknown event type"; From e63c941d11ac827e4378a3c2a08366940d645377 Mon Sep 17 00:00:00 2001 From: wakwak_koba <5591750+wakwak-koba@users.noreply.github.com> Date: Fri, 18 Jan 2019 08:25:09 +0900 Subject: [PATCH 372/381] fixed conversation https://github.com/nkolban/esp32-snippets/pull/783#discussion_r248701585 https://github.com/nkolban/esp32-snippets/pull/783#discussion_r248702002 --- cpp_utils/BLEClient.cpp | 33 ++++++++++++++++++++------------- cpp_utils/BLEDevice.cpp | 18 +++--------------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 8009f408..9dba8469 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -182,19 +182,22 @@ void BLEClient::gattClientEventHandler( // - uint16_t conn_id // - esp_bd_addr_t remote_bda case ESP_GATTC_DISCONNECT_EVT: { - // If we receive a disconnect event, set the class flag that indicates that we are - // no longer connected. - m_isConnected = false; - if (m_pClientCallbacks != nullptr) { - m_pClientCallbacks->onDisconnect(this); - } - BLEDevice::removePeerDevice(m_appId, true); - esp_ble_gattc_app_unregister(m_gattc_if); - m_semaphoreRssiCmplEvt.give(); - m_semaphoreSearchCmplEvt.give(1); + ESP_LOGE(__func__, "disconnect event, conn_id: %d", evtParam->disconnect.conn_id); + if(getConnId() != evtParam->disconnect.conn_id) break; + m_semaphoreOpenEvt.give(evtParam->disconnect.reason); + if(!m_isConnected) + break; + // If we receive a disconnect event, set the class flag that indicates that we are + // no longer connected. + esp_ble_gattc_close(m_gattc_if, m_conn_id); + m_isConnected = false; + if (m_pClientCallbacks != nullptr) { + m_pClientCallbacks->onDisconnect(this); + } + break; } // ESP_GATTC_DISCONNECT_EVT - + // // ESP_GATTC_OPEN_EVT // @@ -224,8 +227,12 @@ void BLEClient::gattClientEventHandler( // uint16_t app_id // case ESP_GATTC_REG_EVT: { - m_gattc_if = gattc_if; - m_semaphoreRegEvt.give(); + if(m_appId == evtParam->reg.app_id){ + ESP_LOGI(__func__, "register app id: %d, %d, gattc_if: %d", m_appId, evtParam->reg.app_id, gattc_if); + m_gattc_if = gattc_if; + m_appId = evtParam->reg.app_id; + m_semaphoreRegEvt.give(); + } break; } // ESP_GATTC_REG_EVT diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 360f97e3..33efabdb 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -162,22 +162,10 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; break; } // switch for(auto &myPair : BLEDevice::getPeerDevices(true)) { - BLEClient* client = (BLEClient*)((conn_status_t)myPair.second).peer_device; - bool raiseEvent = false; - switch(event) { - case ESP_GATTC_REG_EVT: - raiseEvent = (myPair.first == param->reg.app_id); - break; - case ESP_GATTC_DISCONNECT_EVT: - raiseEvent = (client->getConnId() == param->disconnect.conn_id && client->getGattcIf() == gattc_if); - break; - default: - raiseEvent = (client->getGattcIf() == gattc_if || client->getGattcIf() == ESP_GATT_IF_NONE || gattc_if == ESP_GATT_IF_NONE); - break; + conn_status_t conn_status = (conn_status_t)myPair.second; + if(((BLEClient*)conn_status.peer_device)->getGattcIf() == gattc_if || ((BLEClient*)conn_status.peer_device)->getGattcIf() == ESP_GATT_IF_NONE || gattc_if == ESP_GATT_IF_NONE){ + ((BLEClient*)conn_status.peer_device)->gattClientEventHandler(event, gattc_if, param); } - - if(raiseEvent) - client->gattClientEventHandler(event, gattc_if, param); } if(m_customGattcHandler != nullptr) { From e6d9ff2b9ec8adbccd7d629e85fb419c9d56a9e4 Mon Sep 17 00:00:00 2001 From: chegewara Date: Tue, 22 Jan 2019 10:36:43 +0100 Subject: [PATCH 373/381] Bugfixes --- cpp_utils/BLEAdvertisedDevice.cpp | 11 ++-- cpp_utils/BLEAdvertisedDevice.h | 3 +- cpp_utils/BLEAdvertising.cpp | 8 +-- cpp_utils/BLEBeacon.cpp | 9 +++- cpp_utils/BLECharacteristic.cpp | 9 ++-- cpp_utils/BLEClient.cpp | 77 ++++++++++++++++----------- cpp_utils/BLEClient.h | 6 +-- cpp_utils/BLEDescriptor.cpp | 9 ++-- cpp_utils/BLEDevice.cpp | 20 ++++--- cpp_utils/BLEDevice.h | 4 +- cpp_utils/BLERemoteCharacteristic.cpp | 42 ++++++--------- cpp_utils/BLERemoteCharacteristic.h | 10 ++-- cpp_utils/BLERemoteDescriptor.cpp | 9 ++-- cpp_utils/BLERemoteService.cpp | 63 +++------------------- cpp_utils/BLEScan.cpp | 10 ++-- cpp_utils/BLEServer.cpp | 9 ++-- cpp_utils/BLEService.cpp | 8 +-- cpp_utils/BLEUUID.cpp | 9 ++-- cpp_utils/BLEValue.cpp | 12 +++-- cpp_utils/FreeRTOS.cpp | 8 ++- cpp_utils/GeneralUtils.cpp | 8 ++- 21 files changed, 172 insertions(+), 172 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 41d5faba..d3e60bdd 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -13,14 +13,16 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include #include #include "BLEAdvertisedDevice.h" #include "BLEUtils.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" -#endif +#define LOG_TAG "" +#else +#include "esp_log.h" static const char* LOG_TAG="BLEAdvertisedDevice"; +#endif BLEAdvertisedDevice::BLEAdvertisedDevice() { m_adFlag = 0; @@ -523,5 +525,8 @@ size_t BLEAdvertisedDevice::getPayloadLength() { return m_payloadLength; } +void BLEAdvertisedDeviceCallbacks::onResult(BLEAdvertisedDevice dev) {} +void BLEAdvertisedDeviceCallbacks::onResult(BLEAdvertisedDevice* dev) {} + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index aec83746..829f67fe 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -116,7 +116,8 @@ class BLEAdvertisedDeviceCallbacks { * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the * device that was found. During any individual scan, a device will only be detected one time. */ - virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0; + virtual void onResult(BLEAdvertisedDevice advertisedDevice); + virtual void onResult(BLEAdvertisedDevice* advertisedDevice); }; #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index 41ad87a2..230d77cb 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -19,16 +19,18 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) #include "BLEAdvertising.h" -#include #include #include "BLEUtils.h" #include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEAdvertising"; #endif -static const char* LOG_TAG = "BLEAdvertising"; /** diff --git a/cpp_utils/BLEBeacon.cpp b/cpp_utils/BLEBeacon.cpp index 854b661d..68f8d8ed 100644 --- a/cpp_utils/BLEBeacon.cpp +++ b/cpp_utils/BLEBeacon.cpp @@ -7,12 +7,17 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) #include -#include #include "BLEBeacon.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEBeacon"; +#endif #define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) -static const char LOG_TAG[] = "BLEBeacon"; BLEBeacon::BLEBeacon() { m_beaconData.manufacturerId = 0x4c00; diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index dee4454a..e3402874 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -11,7 +11,6 @@ #include #include #include "sdkconfig.h" -#include #include #include "BLECharacteristic.h" #include "BLEService.h" @@ -19,11 +18,13 @@ #include "BLEUtils.h" #include "BLE2902.h" #include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" -#endif - +#define LOG_TAG "" +#else +#include "esp_log.h" static const char* LOG_TAG = "BLECharacteristic"; +#endif #define NULL_HANDLE (0xffff) diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 9dba8469..32069a3f 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -6,7 +6,6 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include #include #include #include @@ -19,10 +18,15 @@ #include #include #include "BLEDevice.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEClient"; #endif + /* * Design * ------ @@ -43,7 +47,6 @@ * * */ -static const char* LOG_TAG = "BLEClient"; BLEClient::BLEClient() { m_pClientCallbacks = nullptr; @@ -51,6 +54,21 @@ BLEClient::BLEClient() { m_gattc_if = ESP_GATT_IF_NONE; m_haveServices = false; m_isConnected = false; // Initially, we are flagged as not connected. + + + m_appId = BLEDevice::m_appId++; + m_appId = m_appId%100; + BLEDevice::addPeerDevice(this, true, m_appId); + m_semaphoreRegEvt.take("connect"); + + esp_err_t errRc = ::esp_ble_gattc_app_register(m_appId); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreRegEvt.wait("connect"); + } // BLEClient @@ -60,10 +78,10 @@ BLEClient::BLEClient() { BLEClient::~BLEClient() { // We may have allocated service references associated with this client. Before we are finished // with the client, we must release resources. - for (auto &myPair : m_servicesMap) { - delete myPair.second; - } - m_servicesMap.clear(); + clearServices(); + esp_ble_gattc_app_unregister(m_gattc_if); + BLEDevice::removePeerDevice(m_appId, true); + } // ~BLEClient @@ -78,6 +96,7 @@ void BLEClient::clearServices() { delete myPair.second; } m_servicesMap.clear(); + m_servicesMapByInstID.clear(); m_haveServices = false; ESP_LOGD(LOG_TAG, "<< clearServices"); } // clearServices @@ -99,26 +118,12 @@ bool BLEClient::connect(BLEAdvertisedDevice* device) { bool BLEClient::connect(BLEAddress address, esp_ble_addr_type_t type) { ESP_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str()); -// We need the connection handle that we get from registering the application. We register the app -// and then block on its completion. When the event has arrived, we will have the handle. - m_appId = BLEDevice::m_appId++; - BLEDevice::addPeerDevice(this, true, m_appId); - m_semaphoreRegEvt.take("connect"); - - // clearServices(); // we dont need to delete services since every client is unique? - esp_err_t errRc = ::esp_ble_gattc_app_register(m_appId); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return false; - } - - m_semaphoreRegEvt.wait("connect"); - + clearServices(); m_peerAddress = address; // Perform the open connection request against the target BLE Server. m_semaphoreOpenEvt.take("connect"); - errRc = ::esp_ble_gattc_open( + esp_err_t errRc = ::esp_ble_gattc_open( m_gattc_if, *getPeerAddress().getNative(), // address type, // Note: This was added on 2018-04-03 when the latest ESP-IDF was detected to have changed the signature. @@ -141,6 +146,7 @@ bool BLEClient::connect(BLEAddress address, esp_ble_addr_type_t type) { */ void BLEClient::disconnect() { ESP_LOGD(LOG_TAG, ">> disconnect()"); + // ESP_LOGW(__func__, "gattIf: %d, connId: %d", getGattcIf(), getConnId()); esp_err_t errRc = ::esp_ble_gattc_close(getGattcIf(), getConnId()); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_close: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); @@ -165,12 +171,13 @@ void BLEClient::gattClientEventHandler( switch(event) { case ESP_GATTC_SRVC_CHG_EVT: + if(getConnId() != evtParam->search_res.conn_id) + break; + ESP_LOGI(LOG_TAG, "SERVICE CHANGED"); break; case ESP_GATTC_CLOSE_EVT: { - // esp_ble_gattc_app_unregister(m_appId); - // BLEDevice::removePeerDevice(m_gattc_if, true); break; } @@ -197,7 +204,7 @@ void BLEClient::gattClientEventHandler( } break; } // ESP_GATTC_DISCONNECT_EVT - + // // ESP_GATTC_OPEN_EVT // @@ -207,12 +214,15 @@ void BLEClient::gattClientEventHandler( // - esp_bd_addr_t remote_bda // case ESP_GATTC_OPEN_EVT: { + if(getConnId() != ESP_GATT_IF_NONE) + break; m_conn_id = evtParam->open.conn_id; if (m_pClientCallbacks != nullptr) { m_pClientCallbacks->onConnect(this); } if (evtParam->open.status == ESP_GATT_OK) { m_isConnected = true; // Flag us as connected. + m_mtu = evtParam->open.mtu; } m_semaphoreOpenEvt.give(evtParam->open.status); break; @@ -240,10 +250,13 @@ void BLEClient::gattClientEventHandler( if(evtParam->cfg_mtu.status != ESP_GATT_OK) { ESP_LOGE(LOG_TAG,"Config mtu failed"); } - m_mtu = evtParam->cfg_mtu.mtu; + else + m_mtu = evtParam->cfg_mtu.mtu; break; case ESP_GATTC_CONNECT_EVT: { + if(evtParam->connect.conn_id != getConnId()) + break; BLEDevice::updatePeerDevice(this, true, m_gattc_if); esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, evtParam->connect.conn_id); if (errRc != ESP_OK) { @@ -265,10 +278,10 @@ void BLEClient::gattClientEventHandler( // - uint16_t conn_id // case ESP_GATTC_SEARCH_CMPL_EVT: { - esp_ble_gattc_cb_param_t* p_data = (esp_ble_gattc_cb_param_t*)evtParam; - if (p_data->search_cmpl.status != ESP_GATT_OK){ - ESP_LOGE(LOG_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + if(evtParam->search_cmpl.conn_id != getConnId()) break; + if (evtParam->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(LOG_TAG, "search service failed, error status = %x", evtParam->search_cmpl.status); } #ifndef ARDUINO_ARCH_ESP32 // commented out just for now to keep backward compatibility @@ -280,7 +293,7 @@ void BLEClient::gattClientEventHandler( // ESP_LOGI(LOG_TAG, "unknown service source"); // } #endif - m_semaphoreSearchCmplEvt.give(0); + m_semaphoreSearchCmplEvt.give(evtParam->search_cmpl.status); break; } // ESP_GATTC_SEARCH_CMPL_EVT @@ -295,6 +308,8 @@ void BLEClient::gattClientEventHandler( // - esp_gatt_id_t srvc_id // case ESP_GATTC_SEARCH_RES_EVT: { + if(getConnId() != evtParam->search_res.conn_id) + break; BLEUUID uuid = BLEUUID(evtParam->search_res.srvc_id); BLERemoteService* pRemoteService = new BLERemoteService( evtParam->search_res.srvc_id, diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 1b8144d5..2bf352d4 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -65,14 +65,10 @@ uint16_t m_appId; friend class BLERemoteCharacteristic; friend class BLERemoteDescriptor; - void gattClientEventHandler( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t* param); + void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param); BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); // The BD address of the remote server. uint16_t m_conn_id; -// int m_deviceType; esp_gatt_if_t m_gattc_if; bool m_haveServices = false; // Have we previously obtain the set of services from the remote server. bool m_isConnected = false; // Are we currently connected. diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 4d7b5efd..ba5753de 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -11,16 +11,19 @@ #include #include #include "sdkconfig.h" -#include #include #include "BLEService.h" #include "BLEDescriptor.h" #include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEDescriptor"; #endif -static const char* LOG_TAG = "BLEDescriptor"; + #define NULL_HANDLE (0xffff) diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 33efabdb..af401fde 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -19,7 +19,6 @@ #include // ESP32 BLE #include // ESP32 BLE #include // ESP32 ESP-IDF -#include // ESP32 ESP-IDF #include // Part of C++ Standard library #include // Part of C++ Standard library #include // Part of C++ Standard library @@ -28,19 +27,24 @@ #include "BLEClient.h" #include "BLEUtils.h" #include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" -#include "esp32-hal-bt.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEDevice"; #endif -static const char* LOG_TAG = "BLEDevice"; +#if defined(ARDUINO_ARCH_ESP32) +#include "esp32-hal-bt.h" +#endif /** * Singletons for the BLEDevice. */ -BLEServer* BLEDevice::m_pServer = nullptr; +// BLEServer* BLEDevice::m_pServer = nullptr; BLEScan* BLEDevice::m_pScan = nullptr; -BLEClient* BLEDevice::m_pClient = nullptr; +// BLEClient* BLEDevice::m_pClient = nullptr; bool initialized = false; esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; @@ -62,7 +66,7 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; ESP_LOGE(LOG_TAG, "BLE GATTC is not enabled - CONFIG_GATTC_ENABLE not defined"); abort(); #endif // CONFIG_GATTC_ENABLE - m_pClient = new BLEClient(); + BLEClient* m_pClient = new BLEClient(); ESP_LOGD(LOG_TAG, "<< createClient"); return m_pClient; } // createClient @@ -78,7 +82,7 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; ESP_LOGE(LOG_TAG, "BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined"); abort(); #endif // CONFIG_GATTS_ENABLE - m_pServer = new BLEServer(); + BLEServer* m_pServer = new BLEServer(); m_pServer->createApp(m_appId++); ESP_LOGD(LOG_TAG, "<< createServer"); return m_pServer; diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index e9cd40a3..8f74dc35 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -65,9 +65,9 @@ class BLEDevice { static esp_ble_sec_act_t m_securityLevel; private: - static BLEServer* m_pServer; + // static BLEServer* m_pServer; static BLEScan* m_pScan; - static BLEClient* m_pClient; + // static BLEClient* m_pClient; static BLESecurityCallbacks* m_securityCallbacks; static BLEAdvertising* m_bleAdvertising; static esp_gatt_if_t getGattcIF(); diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index aa49b385..c032ed29 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -11,7 +11,6 @@ #if defined(CONFIG_BT_ENABLED) #include -#include #include #include @@ -19,12 +18,15 @@ #include "BLEUtils.h" #include "GeneralUtils.h" #include "BLERemoteDescriptor.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLERemoteCharacteristic"; // The logging tag for this class. #endif -static const char* LOG_TAG = "BLERemoteCharacteristic"; // The logging tag for this class. /** * @brief Constructor. @@ -44,7 +46,6 @@ BLERemoteCharacteristic::BLERemoteCharacteristic( m_charProp = charProp; m_pRemoteService = pRemoteService; m_notifyCallback = nullptr; - m_rawData = nullptr; retrieveDescriptors(); // Get the descriptors for this characteristic ESP_LOGD(LOG_TAG, "<< BLERemoteCharacteristic"); @@ -55,7 +56,6 @@ BLERemoteCharacteristic::BLERemoteCharacteristic( *@brief Destructor. */ BLERemoteCharacteristic::~BLERemoteCharacteristic() { - if(m_rawData != nullptr) free(m_rawData); removeDescriptors(); // Release resources for any descriptor information we may have allocated. } // ~BLERemoteCharacteristic @@ -149,10 +149,6 @@ static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { * @returns N/A */ void BLERemoteCharacteristic::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { - - ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", - gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); - switch(event) { // ESP_GATTC_NOTIFY_EVT // @@ -525,10 +521,10 @@ std::string BLERemoteCharacteristic::toString() { * @brief Write the new value for the characteristic. * @param [in] newValue The new value to write. * @param [in] response Do we expect a response? - * @return N/A. + * @return false if not connected or cant perform write for some reason. */ -void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { - writeValue((uint8_t*)newValue.c_str(), strlen(newValue.c_str()), response); +bool BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { + return writeValue((uint8_t*)newValue.c_str(), strlen(newValue.c_str()), response); } // writeValue @@ -538,10 +534,10 @@ void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { * This is a convenience function. Many BLE characteristics are a single byte of data. * @param [in] newValue The new byte value to write. * @param [in] response Whether we require a response from the write. - * @return N/A. + * @return false if not connected or cant perform write for some reason. */ -void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { - writeValue(&newValue, 1, response); +bool BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { + return writeValue(&newValue, 1, response); } // writeValue @@ -550,15 +546,16 @@ void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { * @param [in] data A pointer to a data buffer. * @param [in] length The length of the data in the data buffer. * @param [in] response Whether we require a response from the write. + * @return false if not connected or cant perform write for some reason. */ -void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) { +bool BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) { // writeValue(std::string((char*)data, length), response); ESP_LOGD(LOG_TAG, ">> writeValue(), length: %d", length); // Check to see that we are connected. if (!getRemoteService()->getClient()->isConnected()) { ESP_LOGE(LOG_TAG, "Disconnected"); - throw BLEDisconnectedException(); + return false; } m_semaphoreWriteCharEvt.take("writeValue"); @@ -575,12 +572,13 @@ void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool resp if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + return false; } m_semaphoreWriteCharEvt.wait("writeValue"); ESP_LOGD(LOG_TAG, "<< writeValue"); + return true; } // writeValue /** @@ -590,13 +588,5 @@ void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool resp uint8_t* BLERemoteCharacteristic::readRawData() { return m_rawData; } -/** - * @brief Get the address of the remote server - * @return RemoteAddress - */ -BLEAddress BLERemoteCharacteristic::getRemoteAddress(){ - return m_pRemoteService->getClient()->getPeerAddress(); -} - #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 32305abf..ab90c75a 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -17,7 +17,6 @@ #include "BLERemoteService.h" #include "BLERemoteDescriptor.h" #include "BLEUUID.h" -#include "BLEAddress.h" #include "FreeRTOS.h" class BLERemoteService; @@ -47,13 +46,11 @@ class BLERemoteCharacteristic { uint16_t readUInt16(); uint32_t readUInt32(); void registerForNotify(notify_callback _callback, bool notifications = true); - void writeValue(uint8_t* data, size_t length, bool response = false); - void writeValue(std::string newValue, bool response = false); - void writeValue(uint8_t newValue, bool response = false); + bool writeValue(uint8_t* data, size_t length, bool response = false); + bool writeValue(std::string newValue, bool response = false); + bool writeValue(uint8_t newValue, bool response = false); std::string toString(); uint8_t* readRawData(); - BLEAddress getRemoteAddress(); - BLERemoteService* getRemoteService(); private: BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); @@ -64,6 +61,7 @@ class BLERemoteCharacteristic { // Private member functions void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); + BLERemoteService* getRemoteService(); void removeDescriptors(); void retrieveDescriptors(); diff --git a/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index ea7eeebd..96a8a577 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -9,12 +9,15 @@ #include #include "BLERemoteDescriptor.h" #include "GeneralUtils.h" -#include -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLERemoteDescriptor"; #endif -static const char* LOG_TAG = "BLERemoteDescriptor"; + BLERemoteDescriptor::BLERemoteDescriptor( diff --git a/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index 3b4f2a67..91089eab 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -11,13 +11,16 @@ #include "BLERemoteService.h" #include "BLEUtils.h" #include "GeneralUtils.h" -#include #include -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLERemoteService"; #endif -static const char* LOG_TAG = "BLERemoteService"; + BLERemoteService::BLERemoteService( esp_gatt_id_t srvcId, @@ -61,57 +64,7 @@ void BLERemoteService::gattClientEventHandler( esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { - - ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", - gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); - switch (event) { - // - // ESP_GATTC_GET_CHAR_EVT - // - // get_char: - // - esp_gatt_status_t status - // - uin1t6_t conn_id - // - esp_gatt_srvc_id_t srvc_id - // - esp_gatt_id_t char_id - // - esp_gatt_char_prop_t char_prop - // - /* - case ESP_GATTC_GET_CHAR_EVT: { - // Is this event for this service? If yes, then the local srvc_id and the event srvc_id will be - // the same. - if (compareSrvcId(m_srvcId, evtParam->get_char.srvc_id) == false) { - break; - } - - // If the status is NOT OK then we have a problem and continue. - if (evtParam->get_char.status != ESP_GATT_OK) { - m_semaphoreGetCharEvt.give(); - break; - } - - // This is an indication that we now have the characteristic details for a characteristic owned - // by this service so remember it. - m_characteristicMap.insert(std::pair( - BLEUUID(evtParam->get_char.char_id.uuid).toString(), - new BLERemoteCharacteristic(evtParam->get_char.char_id, evtParam->get_char.char_prop, this) )); - - - // Now that we have received a characteristic, lets ask for the next one. - esp_err_t errRc = ::esp_ble_gattc_get_characteristic( - m_pClient->getGattcIf(), - m_pClient->getConnId(), - &m_srvcId, - &evtParam->get_char.char_id); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - break; - } - - //m_semaphoreGetCharEvt.give(); - break; - } // ESP_GATTC_GET_CHAR_EVT -*/ default: break; } // switch @@ -173,7 +126,7 @@ void BLERemoteService::retrieveCharacteristics() { uint16_t offset = 0; esp_gattc_char_elem_t result; while (true) { - uint16_t count = 1; + uint16_t count = 1; // this value is used as in parameter that allows to search max 10 chars with the same uuid esp_gatt_status_t status = ::esp_ble_gattc_get_all_char( getClient()->getGattcIf(), getClient()->getConnId(), @@ -254,7 +207,7 @@ std::map* BLERemoteService::getCharacteristi */ void BLERemoteService::getCharacteristics(std::map* pCharacteristicMap) { #pragma GCC diagnostic ignored "-Wunused-but-set-parameter" - pCharacteristicMap = &m_characteristicMapByHandle; + *pCharacteristicMap = m_characteristicMapByHandle; } // Get the characteristics map. /** diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index e1778b84..a96e6df6 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -8,7 +8,6 @@ #if defined(CONFIG_BT_ENABLED) -#include #include #include @@ -17,11 +16,15 @@ #include "BLEScan.h" #include "BLEUtils.h" #include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEScan"; #endif -static const char* LOG_TAG = "BLEScan"; + /** @@ -122,6 +125,7 @@ void BLEScan::handleGAPEvent( } if (m_pAdvertisedDeviceCallbacks) { + m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice); m_pAdvertisedDeviceCallbacks->onResult(*advertisedDevice); } if(found) diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 9b52762d..6a780aa9 100755 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -7,7 +7,6 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include #include #include #include "GeneralUtils.h" @@ -18,11 +17,15 @@ #include #include #include -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEServer"; #endif -static const char* LOG_TAG = "BLEServer"; + /** diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index 7e7d67ac..3034cf18 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -11,7 +11,6 @@ #if defined(CONFIG_BT_ENABLED) #include #include -#include #include #include @@ -22,13 +21,16 @@ #include "BLEUtils.h" #include "GeneralUtils.h" -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEService"; // Tag for logging. #endif #define NULL_HANDLE (0xffff) -static const char* LOG_TAG = "BLEService"; // Tag for logging. /** * @brief Construct an instance of the BLEService diff --git a/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index fd0f5ad5..4ddf8fc2 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -6,7 +6,6 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include #include #include #include @@ -14,12 +13,16 @@ #include #include #include "BLEUUID.h" -static const char* LOG_TAG = "BLEUUID"; -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEUUID"; #endif + /** * @brief Copy memory from source to target but in reverse order. * diff --git a/cpp_utils/BLEValue.cpp b/cpp_utils/BLEValue.cpp index aac0f500..ec1e61f5 100644 --- a/cpp_utils/BLEValue.cpp +++ b/cpp_utils/BLEValue.cpp @@ -6,15 +6,17 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) - -#include - #include "BLEValue.h" -#ifdef ARDUINO_ARCH_ESP32 + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG="BLEValue"; #endif -static const char* LOG_TAG="BLEValue"; + BLEValue::BLEValue() { m_accumulation = ""; diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 9514eed4..40e5bbf3 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -11,10 +11,14 @@ #include #include #include "FreeRTOS.h" -#include #include "sdkconfig.h" - +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" static const char* LOG_TAG = "FreeRTOS"; +#endif /** * Sleep for the specified number of milliseconds. diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index dbbc65be..019c81bd 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -6,7 +6,6 @@ */ #include "GeneralUtils.h" -#include #include #include #include @@ -20,7 +19,14 @@ #include #include +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" static const char* LOG_TAG = "GeneralUtils"; +#endif + static const char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" From d141a913eb4f26d081a645e9a83b66f534e2661f Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 30 Jan 2019 14:50:59 +0100 Subject: [PATCH 374/381] Bugfix: write long characteristic value Bugfix: memory leak in BLERemoteCharacteristic Bugfix: BLEClient use gattc_if for connections Add: add delete BLEClient callbacks to avoid memory leak Add: add BLEServer disconnect function --- cpp_utils/BLEAdvertisedDevice.h | 2 +- cpp_utils/BLECharacteristic.cpp | 48 ++++++++++++++++----------- cpp_utils/BLECharacteristic.h | 1 + cpp_utils/BLEClient.cpp | 25 ++++++++------ cpp_utils/BLEClient.h | 5 +-- cpp_utils/BLEDevice.cpp | 8 ++--- cpp_utils/BLEDevice.h | 2 +- cpp_utils/BLERemoteCharacteristic.cpp | 1 + cpp_utils/BLERemoteCharacteristic.h | 6 ++-- cpp_utils/BLEServer.cpp | 6 ++++ cpp_utils/BLEServer.h | 1 + 11 files changed, 61 insertions(+), 44 deletions(-) diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index 829f67fe..cd206d4a 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -95,7 +95,7 @@ class BLEAdvertisedDevice { int8_t m_txPower; std::string m_serviceData; BLEUUID m_serviceDataUUID; - uint8_t* m_payload; + uint8_t* m_payload = nullptr; size_t m_payloadLength = 0; esp_ble_addr_type_t m_addressType; }; diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index e3402874..80f9c7e7 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -224,21 +224,24 @@ void BLECharacteristic::handleGATTServerEvent( // - uint8_t exec_write_flag - Either ESP_GATT_PREP_WRITE_EXEC or ESP_GATT_PREP_WRITE_CANCEL // case ESP_GATTS_EXEC_WRITE_EVT: { - if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { - m_value.commit(); - if (m_pCallbacks != nullptr) { - m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. + if(m_writeEvt){ + m_writeEvt = false; + if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { + m_value.commit(); + if (m_pCallbacks != nullptr) { + m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. + } + } else { + m_value.cancel(); + } + // ??? + esp_err_t errRc = ::esp_ble_gatts_send_response( + gatts_if, + param->write.conn_id, + param->write.trans_id, ESP_GATT_OK, nullptr); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - } else { - m_value.cancel(); - } -// ??? - esp_err_t errRc = ::esp_ble_gatts_send_response( - gatts_if, - param->write.conn_id, - param->write.trans_id, ESP_GATT_OK, nullptr); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } break; } // ESP_GATTS_EXEC_WRITE_EVT @@ -284,7 +287,11 @@ void BLECharacteristic::handleGATTServerEvent( if (param->write.handle == m_handle) { if (param->write.is_prep) { m_value.addPart(param->write.value, param->write.len); + m_writeEvt = true; } else { + if (m_pCallbacks != nullptr && param->write.is_prep != true) { + m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. + } setValue(param->write.value, param->write.len); } @@ -313,9 +320,6 @@ void BLECharacteristic::handleGATTServerEvent( } } // Response needed - if (m_pCallbacks != nullptr && param->write.is_prep != true) { - m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. - } } // Match on handles. break; } // ESP_GATTS_WRITE_EVT @@ -384,6 +388,9 @@ void BLECharacteristic::handleGATTServerEvent( } } else { // read.is_long == false + if (m_pCallbacks != nullptr) { // If is.long is false then this is the first (or only) request to read data, so invoke the callback + m_pCallbacks->onRead(this); // Invoke the read callback. + } std::string value = m_value.getValue(); if (value.length() + 1 > maxOffset) { @@ -399,9 +406,9 @@ void BLECharacteristic::handleGATTServerEvent( memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len); } - if (m_pCallbacks != nullptr) { // If is.long is false then this is the first (or only) request to read data, so invoke the callback - m_pCallbacks->onRead(this); // Invoke the read callback. - } + // if (m_pCallbacks != nullptr) { // If is.long is false then this is the first (or only) request to read data, so invoke the callback + // m_pCallbacks->onRead(this); // Invoke the read callback. + // } } rsp.attr_value.handle = param->read.handle; rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; @@ -530,6 +537,7 @@ void BLECharacteristic::notify(bool is_notification) { if(!is_notification) m_semaphoreConfEvt.wait("indicate"); } + delete(p2902); ESP_LOGD(LOG_TAG, "<< notify"); } // Notify diff --git a/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index 5eb1e8d6..82a8d7d0 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -105,6 +105,7 @@ class BLECharacteristic { BLEService* m_pService; BLEValue m_value; esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + bool m_writeEvt = false; void handleGATTServerEvent( esp_gatts_cb_event_t event, diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 32069a3f..d6e75988 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -81,6 +81,8 @@ BLEClient::~BLEClient() { clearServices(); esp_ble_gattc_app_unregister(m_gattc_if); BLEDevice::removePeerDevice(m_appId, true); + if(m_deleteCallbacks) + delete m_pClientCallbacks; } // ~BLEClient @@ -171,15 +173,14 @@ void BLEClient::gattClientEventHandler( switch(event) { case ESP_GATTC_SRVC_CHG_EVT: - if(getConnId() != evtParam->search_res.conn_id) + if(m_gattc_if != gattc_if) break; ESP_LOGI(LOG_TAG, "SERVICE CHANGED"); break; - case ESP_GATTC_CLOSE_EVT: { + case ESP_GATTC_CLOSE_EVT: break; - } // // ESP_GATTC_DISCONNECT_EVT @@ -189,8 +190,8 @@ void BLEClient::gattClientEventHandler( // - uint16_t conn_id // - esp_bd_addr_t remote_bda case ESP_GATTC_DISCONNECT_EVT: { - ESP_LOGE(__func__, "disconnect event, conn_id: %d", evtParam->disconnect.conn_id); - if(getConnId() != evtParam->disconnect.conn_id) + ESP_LOGE(__func__, "disconnect event, reason: %d, connId: %d, my connId: %d, my IF: %d, gattc_if: %d", (int)evtParam->disconnect.reason, evtParam->disconnect.conn_id, getConnId(), getGattcIf(), gattc_if); + if(m_gattc_if != gattc_if) break; m_semaphoreOpenEvt.give(evtParam->disconnect.reason); if(!m_isConnected) @@ -214,7 +215,7 @@ void BLEClient::gattClientEventHandler( // - esp_bd_addr_t remote_bda // case ESP_GATTC_OPEN_EVT: { - if(getConnId() != ESP_GATT_IF_NONE) + if(m_gattc_if != gattc_if) break; m_conn_id = evtParam->open.conn_id; if (m_pClientCallbacks != nullptr) { @@ -240,7 +241,6 @@ void BLEClient::gattClientEventHandler( if(m_appId == evtParam->reg.app_id){ ESP_LOGI(__func__, "register app id: %d, %d, gattc_if: %d", m_appId, evtParam->reg.app_id, gattc_if); m_gattc_if = gattc_if; - m_appId = evtParam->reg.app_id; m_semaphoreRegEvt.give(); } break; @@ -255,8 +255,9 @@ void BLEClient::gattClientEventHandler( break; case ESP_GATTC_CONNECT_EVT: { - if(evtParam->connect.conn_id != getConnId()) + if(m_gattc_if != gattc_if) break; + m_conn_id = evtParam->connect.conn_id; BLEDevice::updatePeerDevice(this, true, m_gattc_if); esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, evtParam->connect.conn_id); if (errRc != ESP_OK) { @@ -278,7 +279,7 @@ void BLEClient::gattClientEventHandler( // - uint16_t conn_id // case ESP_GATTC_SEARCH_CMPL_EVT: { - if(evtParam->search_cmpl.conn_id != getConnId()) + if(m_gattc_if != gattc_if) break; if (evtParam->search_cmpl.status != ESP_GATT_OK){ ESP_LOGE(LOG_TAG, "search service failed, error status = %x", evtParam->search_cmpl.status); @@ -308,8 +309,9 @@ void BLEClient::gattClientEventHandler( // - esp_gatt_id_t srvc_id // case ESP_GATTC_SEARCH_RES_EVT: { - if(getConnId() != evtParam->search_res.conn_id) + if(m_gattc_if != gattc_if) break; + BLEUUID uuid = BLEUUID(evtParam->search_res.srvc_id); BLERemoteService* pRemoteService = new BLERemoteService( evtParam->search_res.srvc_id, @@ -515,8 +517,9 @@ bool BLEClient::isConnected() { /** * @brief Set the callbacks that will be invoked. */ -void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks) { +void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks, bool deleteCallbacks) { m_pClientCallbacks = pClientCallbacks; + m_deleteCallbacks = deleteCallbacks; } // setClientCallbacks diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 2bf352d4..4b3c7eea 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -50,7 +50,7 @@ class BLEClient { bool isConnected(); // Return true if we are connected. - void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); + void setClientCallbacks(BLEClientCallbacks *pClientCallbacks, bool deleteCallbacks = true); void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a given characteristic at a given service. std::string toString(); // Return a string representation of this client. @@ -72,8 +72,9 @@ uint16_t m_appId; esp_gatt_if_t m_gattc_if; bool m_haveServices = false; // Have we previously obtain the set of services from the remote server. bool m_isConnected = false; // Are we currently connected. + bool m_deleteCallbacks = true; - BLEClientCallbacks* m_pClientCallbacks; + BLEClientCallbacks* m_pClientCallbacks = nullptr; FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt"); diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index af401fde..78bd13d3 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -42,7 +42,7 @@ static const char* LOG_TAG = "BLEDevice"; /** * Singletons for the BLEDevice. */ -// BLEServer* BLEDevice::m_pServer = nullptr; +BLEServer* BLEDevice::m_pServer = nullptr; BLEScan* BLEDevice::m_pScan = nullptr; // BLEClient* BLEDevice::m_pClient = nullptr; bool initialized = false; @@ -82,7 +82,7 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; ESP_LOGE(LOG_TAG, "BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined"); abort(); #endif // CONFIG_GATTS_ENABLE - BLEServer* m_pServer = new BLEServer(); + BLEDevice::m_pServer = new BLEServer(); m_pServer->createApp(m_appId++); ESP_LOGD(LOG_TAG, "<< createServer"); return m_pServer; @@ -266,10 +266,6 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; } } // switch - if (BLEDevice::m_pClient != nullptr) { - BLEDevice::m_pClient->handleGAPEvent(event, param); - } - if (BLEDevice::m_pScan != nullptr) { BLEDevice::getScan()->handleGAPEvent(event, param); } diff --git a/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 8f74dc35..b01f09be 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -65,7 +65,7 @@ class BLEDevice { static esp_ble_sec_act_t m_securityLevel; private: - // static BLEServer* m_pServer; + static BLEServer* m_pServer; static BLEScan* m_pScan; // static BLEClient* m_pClient; static BLESecurityCallbacks* m_securityCallbacks; diff --git a/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index c032ed29..ef16da7b 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -57,6 +57,7 @@ BLERemoteCharacteristic::BLERemoteCharacteristic( */ BLERemoteCharacteristic::~BLERemoteCharacteristic() { removeDescriptors(); // Release resources for any descriptor information we may have allocated. + if(m_rawData != nullptr) free(m_rawData); } // ~BLERemoteCharacteristic diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index ab90c75a..910b11a4 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -51,6 +51,7 @@ class BLERemoteCharacteristic { bool writeValue(uint8_t newValue, bool response = false); std::string toString(); uint8_t* readRawData(); + BLERemoteService* getRemoteService(); private: BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); @@ -61,7 +62,6 @@ class BLERemoteCharacteristic { // Private member functions void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); - BLERemoteService* getRemoteService(); void removeDescriptors(); void retrieveDescriptors(); @@ -74,8 +74,8 @@ class BLERemoteCharacteristic { FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt"); FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); std::string m_value; - uint8_t *m_rawData; - notify_callback m_notifyCallback; + uint8_t *m_rawData = nullptr; + notify_callback m_notifyCallback = nullptr; // We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID. std::map m_descriptorMap; diff --git a/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp index 6a780aa9..83497331 100755 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -9,6 +9,7 @@ #if defined(CONFIG_BT_ENABLED) #include #include +#include #include "GeneralUtils.h" #include "BLEDevice.h" #include "BLEServer.h" @@ -421,4 +422,9 @@ void BLEServer::updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, conn_params.timeout = timeout; // timeout = 400*10ms = 4000ms esp_ble_gap_update_conn_params(&conn_params); } + +void BLEServer::disconnect(uint16_t connId){ + esp_ble_gatts_close(m_gatts_if, connId); +} + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h index d39d8bfe..d2f8038d 100755 --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -72,6 +72,7 @@ class BLEServer { BLEService* getServiceByUUID(const char* uuid); BLEService* getServiceByUUID(BLEUUID uuid); bool connect(BLEAddress address); + void disconnect(uint16_t connId); uint16_t m_appId; void updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout); From 3de1717ba5e1fc6d1c7726b13fa6721c90acc86e Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 30 Jan 2019 15:59:21 +0100 Subject: [PATCH 375/381] Add static pin feature --- cpp_utils/BLESecurity.cpp | 11 +++++++++++ cpp_utils/BLESecurity.h | 1 + 2 files changed, 12 insertions(+) diff --git a/cpp_utils/BLESecurity.cpp b/cpp_utils/BLESecurity.cpp index 921f5424..f3b2cd3c 100644 --- a/cpp_utils/BLESecurity.cpp +++ b/cpp_utils/BLESecurity.cpp @@ -61,6 +61,17 @@ void BLESecurity::setKeySize(uint8_t key_size) { esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &m_keySize, sizeof(uint8_t)); } //setKeySize +/** + * Setup for static PIN connection, call it first and then call setAuthenticationMode eventually to change it + */ +void BLESecurity::setStaticPIN(uint32_t pin){ + uint32_t passkey = pin; + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t)); + setCapability(ESP_IO_CAP_OUT); + setKeySize(); + setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); +} /** * @brief Debug function to display what keys are exchanged by peers diff --git a/cpp_utils/BLESecurity.h b/cpp_utils/BLESecurity.h index 48d09d2f..dc6d6d71 100644 --- a/cpp_utils/BLESecurity.h +++ b/cpp_utils/BLESecurity.h @@ -21,6 +21,7 @@ class BLESecurity { void setInitEncryptionKey(uint8_t init_key); void setRespEncryptionKey(uint8_t resp_key); void setKeySize(uint8_t key_size = 16); + void setStaticPIN(uint32_t pin); static char* esp_key_type_to_str(esp_ble_key_type_t key_type); private: From 6005e319e8072f57fd2bc9f786a3042fa23db184 Mon Sep 17 00:00:00 2001 From: chegewara Date: Wed, 30 Jan 2019 16:56:25 +0100 Subject: [PATCH 376/381] Update arduino examples --- .../Arduino/BLE_client/BLE_client.ino | 4 +- .../Arduino/BLE_server/BLE_server.ino | 3 +- .../BLE_client/BLE_client_encrypted.ino | 138 -------------- .../BLE_client_numeric_confirmation.ino | 169 ------------------ .../BLE_client/BLE_client_passkey.ino | 168 ----------------- .../BLE_server_passkey/BLE_server_passkey.ino | 6 +- .../Arduino/security/StaticPIN/StaticPIN.ino | 47 +++++ 7 files changed, 56 insertions(+), 479 deletions(-) delete mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted.ino delete mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation.ino delete mode 100644 cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey.ino create mode 100644 cpp_utils/tests/BLETests/Arduino/security/StaticPIN/StaticPIN.ino diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino index c0b6163c..fb032a69 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino @@ -6,9 +6,9 @@ //#include "BLEScan.h" // The remote service we wish to connect to. -static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"); // The characteristic of the remote service we are interested in. -static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); +static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8"); static BLEAddress *pServerAddress; static boolean doConnect = false; diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino b/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino index 38224a67..79793975 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino @@ -29,6 +29,7 @@ void setup() { pCharacteristic->setValue("Hello World says Neil"); pService->start(); BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->start(); Serial.println("Characteristic defined! Now you can read it in your phone!"); } @@ -36,4 +37,4 @@ void setup() { void loop() { // put your main code here, to run repeatedly: delay(2000); -} \ No newline at end of file +} diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted.ino deleted file mode 100644 index e77d774f..00000000 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted.ino +++ /dev/null @@ -1,138 +0,0 @@ -/** - * A BLE client example that is rich in capabilities. - */ - -#include "BLEDevice.h" -//#include "BLEScan.h" - -// The remote service we wish to connect to. -static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); -// The characteristic of the remote service we are interested in. -static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); - -static BLEAddress *pServerAddress; -static boolean doConnect = false; -static boolean connected = false; -static BLERemoteCharacteristic* pRemoteCharacteristic; - -static void notifyCallback( - BLERemoteCharacteristic* pBLERemoteCharacteristic, - uint8_t* pData, - size_t length, - bool isNotify) { - Serial.print("Notify callback for characteristic "); - Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); - Serial.print(" of data length "); - Serial.println(length); -} - -bool connectToServer(BLEAddress pAddress) { - Serial.print("Forming a connection to "); - Serial.println(pAddress.toString().c_str()); - - /* - * Here we have implemented simplest security. This kind security does not provide authentication - */ - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - BLEClient* pClient = BLEDevice::createClient(); - Serial.println(" - Created client"); - - // Connect to the remove BLE Server. - pClient->connect(pAddress); - Serial.println(" - Connected to server"); - - // Obtain a reference to the service we are after in the remote BLE server. - BLERemoteService* pRemoteService = pClient->getService(serviceUUID); - if (pRemoteService == nullptr) { - Serial.print("Failed to find our service UUID: "); - Serial.println(serviceUUID.toString().c_str()); - return false; - } - Serial.println(" - Found our service"); - - - // Obtain a reference to the characteristic in the service of the remote BLE server. - pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); - if (pRemoteCharacteristic == nullptr) { - Serial.print("Failed to find our characteristic UUID: "); - Serial.println(charUUID.toString().c_str()); - return false; - } - Serial.println(" - Found our characteristic"); - - // Read the value of the characteristic. - std::string value = pRemoteCharacteristic->readValue(); - Serial.print("The characteristic value was: "); - Serial.println(value.c_str()); - - pRemoteCharacteristic->registerForNotify(notifyCallback); -} -/** - * Scan for BLE servers and find the first one that advertises the service we are looking for. - */ -class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { - /** - * Called for each advertising BLE server. - */ - void onResult(BLEAdvertisedDevice advertisedDevice) { - Serial.print("BLE Advertised Device found: "); - Serial.println(advertisedDevice.toString().c_str()); - - // We have found a device, let us now see if it contains the service we are looking for. - if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { - - // - Serial.print("Found our device! address: "); - advertisedDevice.getScan()->stop(); - - pServerAddress = new BLEAddress(advertisedDevice.getAddress()); - doConnect = true; - - } // Found our server - } // onResult -}; // MyAdvertisedDeviceCallbacks - - -void setup() { - Serial.begin(115200); - Serial.println("Starting Arduino BLE Client application..."); - BLEDevice::init(""); - - // Retrieve a Scanner and set the callback we want to use to be informed when we - // have detected a new device. Specify that we want active scanning and start the - // scan to run for 30 seconds. - BLEScan* pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); - pBLEScan->setActiveScan(true); - pBLEScan->start(30); -} // End of setup. - - -// This is the Arduino main loop function. -void loop() { - - // If the flag "doConnect" is true then we have scanned for and found the desired - // BLE Server with which we wish to connect. Now we connect to it. Once we are - // connected we set the connected flag to be true. - if (doConnect == true) { - if (connectToServer(*pServerAddress)) { - Serial.println("We are now connected to the BLE Server."); - connected = true; - } else { - Serial.println("We have failed to connect to the server; there is nothin more we will do."); - } - doConnect = false; - } - - // If we are connected to a peer BLE Server, update the characteristic each time we are reached - // with the current time since boot. - if (connected) { - String newValue = "Time since boot: " + String(millis()/1000); - Serial.println("Setting new characteristic value to \"" + newValue + "\""); - - // Set the characteristic's value to be the array of bytes that is actually a string. - pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); - } - - delay(1000); // Delay a second between loops. -} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation.ino deleted file mode 100644 index 846a664b..00000000 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation.ino +++ /dev/null @@ -1,169 +0,0 @@ -/** - * A BLE client example that is rich in capabilities. - */ - -#include "BLEDevice.h" -//#include "BLEScan.h" - -// The remote service we wish to connect to. -static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); -// The characteristic of the remote service we are interested in. -static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); - -static BLEAddress *pServerAddress; -static boolean doConnect = false; -static boolean connected = false; -static BLERemoteCharacteristic* pRemoteCharacteristic; - -class MySecurity : public BLESecurityCallbacks { - - uint32_t onPassKeyRequest(){ - return 123456; - } - void onPassKeyNotify(uint32_t pass_key){ - ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); - } - bool onConfirmPIN(uint32_t pass_key){ - ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); - vTaskDelay(5000); - return true; - } - bool onSecurityRequest(){ - ESP_LOGI(LOG_TAG, "Security Request"); - return true; - } - void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ - if(auth_cmpl.success){ - ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); - esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); - ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); - } - ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); - } -}; - -static void notifyCallback( - BLERemoteCharacteristic* pBLERemoteCharacteristic, - uint8_t* pData, - size_t length, - bool isNotify) { - Serial.print("Notify callback for characteristic "); - Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); - Serial.print(" of data length "); - Serial.println(length); -} - -bool connectToServer(BLEAddress pAddress) { - Serial.print("Forming a connection to "); - Serial.println(pAddress.toString().c_str()); - - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - BLEDevice::setSecurityCallbacks(new MySecurity()); - - BLESecurity *pSecurity = new BLESecurity(); - pSecurity->setKeySize(); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); - pSecurity->setCapability(ESP_IO_CAP_IO); - pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - BLEClient* pClient = BLEDevice::createClient(); - Serial.println(" - Created client"); - - // Connect to the remove BLE Server. - pClient->connect(pAddress); - Serial.println(" - Connected to server"); - - // Obtain a reference to the service we are after in the remote BLE server. - BLERemoteService* pRemoteService = pClient->getService(serviceUUID); - if (pRemoteService == nullptr) { - Serial.print("Failed to find our service UUID: "); - Serial.println(serviceUUID.toString().c_str()); - return false; - } - Serial.println(" - Found our service"); - - - // Obtain a reference to the characteristic in the service of the remote BLE server. - pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); - if (pRemoteCharacteristic == nullptr) { - Serial.print("Failed to find our characteristic UUID: "); - Serial.println(charUUID.toString().c_str()); - return false; - } - Serial.println(" - Found our characteristic"); - - // Read the value of the characteristic. - std::string value = pRemoteCharacteristic->readValue(); - Serial.print("The characteristic value was: "); - Serial.println(value.c_str()); - - pRemoteCharacteristic->registerForNotify(notifyCallback); -} -/** - * Scan for BLE servers and find the first one that advertises the service we are looking for. - */ -class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { - /** - * Called for each advertising BLE server. - */ - void onResult(BLEAdvertisedDevice advertisedDevice) { - Serial.print("BLE Advertised Device found: "); - Serial.println(advertisedDevice.toString().c_str()); - - // We have found a device, let us now see if it contains the service we are looking for. - if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { - - // - Serial.print("Found our device! address: "); - advertisedDevice.getScan()->stop(); - - pServerAddress = new BLEAddress(advertisedDevice.getAddress()); - doConnect = true; - - } // Found our server - } // onResult -}; // MyAdvertisedDeviceCallbacks - - -void setup() { - Serial.begin(115200); - Serial.println("Starting Arduino BLE Client application..."); - BLEDevice::init(""); - - // Retrieve a Scanner and set the callback we want to use to be informed when we - // have detected a new device. Specify that we want active scanning and start the - // scan to run for 30 seconds. - BLEScan* pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); - pBLEScan->setActiveScan(true); - pBLEScan->start(30); -} // End of setup. - - -// This is the Arduino main loop function. -void loop() { - - // If the flag "doConnect" is true then we have scanned for and found the desired - // BLE Server with which we wish to connect. Now we connect to it. Once we are - // connected we set the connected flag to be true. - if (doConnect == true) { - if (connectToServer(*pServerAddress)) { - Serial.println("We are now connected to the BLE Server."); - connected = true; - } else { - Serial.println("We have failed to connect to the server; there is nothin more we will do."); - } - doConnect = false; - } - - // If we are connected to a peer BLE Server, update the characteristic each time we are reached - // with the current time since boot. - if (connected) { - String newValue = "Time since boot: " + String(millis()/1000); - Serial.println("Setting new characteristic value to \"" + newValue + "\""); - - // Set the characteristic's value to be the array of bytes that is actually a string. - pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); - } - - delay(1000); // Delay a second between loops. -} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey.ino deleted file mode 100644 index 763a87d2..00000000 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey.ino +++ /dev/null @@ -1,168 +0,0 @@ -/** - * A BLE client example that is rich in capabilities. - */ - -#include "BLEDevice.h" -//#include "BLEScan.h" - -// The remote service we wish to connect to. -static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); -// The characteristic of the remote service we are interested in. -static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); - -static BLEAddress *pServerAddress; -static boolean doConnect = false; -static boolean connected = false; -static BLERemoteCharacteristic* pRemoteCharacteristic; - -class MySecurity : public BLESecurityCallbacks { - - uint32_t onPassKeyRequest(){ - return 123456; - } - void onPassKeyNotify(uint32_t pass_key){ - ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); - } - bool onConfirmPIN(uint32_t pass_key){ - ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); - vTaskDelay(5000); - return true; - } - bool onSecurityRequest(){ - ESP_LOGI(LOG_TAG, "Security Request"); - return true; - } - void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ - if(auth_cmpl.success){ - ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); - esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); - ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); - } - ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); - } -}; - -static void notifyCallback( - BLERemoteCharacteristic* pBLERemoteCharacteristic, - uint8_t* pData, - size_t length, - bool isNotify) { - Serial.print("Notify callback for characteristic "); - Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); - Serial.print(" of data length "); - Serial.println(length); -} - -bool connectToServer(BLEAddress pAddress) { - Serial.print("Forming a connection to "); - Serial.println(pAddress.toString().c_str()); - - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - BLEDevice::setSecurityCallbacks(new MySecurity()); - - BLESecurity *pSecurity = new BLESecurity(); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); - pSecurity->setCapability(ESP_IO_CAP_OUT); - pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - BLEClient* pClient = BLEDevice::createClient(); - Serial.println(" - Created client"); - - // Connect to the remove BLE Server. - pClient->connect(pAddress); - Serial.println(" - Connected to server"); - - // Obtain a reference to the service we are after in the remote BLE server. - BLERemoteService* pRemoteService = pClient->getService(serviceUUID); - if (pRemoteService == nullptr) { - Serial.print("Failed to find our service UUID: "); - Serial.println(serviceUUID.toString().c_str()); - return false; - } - Serial.println(" - Found our service"); - - - // Obtain a reference to the characteristic in the service of the remote BLE server. - pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); - if (pRemoteCharacteristic == nullptr) { - Serial.print("Failed to find our characteristic UUID: "); - Serial.println(charUUID.toString().c_str()); - return false; - } - Serial.println(" - Found our characteristic"); - - // Read the value of the characteristic. - std::string value = pRemoteCharacteristic->readValue(); - Serial.print("The characteristic value was: "); - Serial.println(value.c_str()); - - pRemoteCharacteristic->registerForNotify(notifyCallback); -} -/** - * Scan for BLE servers and find the first one that advertises the service we are looking for. - */ -class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { - /** - * Called for each advertising BLE server. - */ - void onResult(BLEAdvertisedDevice advertisedDevice) { - Serial.print("BLE Advertised Device found: "); - Serial.println(advertisedDevice.toString().c_str()); - - // We have found a device, let us now see if it contains the service we are looking for. - if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { - - // - Serial.print("Found our device! address: "); - advertisedDevice.getScan()->stop(); - - pServerAddress = new BLEAddress(advertisedDevice.getAddress()); - doConnect = true; - - } // Found our server - } // onResult -}; // MyAdvertisedDeviceCallbacks - - -void setup() { - Serial.begin(115200); - Serial.println("Starting Arduino BLE Client application..."); - BLEDevice::init(""); - - // Retrieve a Scanner and set the callback we want to use to be informed when we - // have detected a new device. Specify that we want active scanning and start the - // scan to run for 30 seconds. - BLEScan* pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); - pBLEScan->setActiveScan(true); - pBLEScan->start(30); -} // End of setup. - - -// This is the Arduino main loop function. -void loop() { - - // If the flag "doConnect" is true then we have scanned for and found the desired - // BLE Server with which we wish to connect. Now we connect to it. Once we are - // connected we set the connected flag to be true. - if (doConnect == true) { - if (connectToServer(*pServerAddress)) { - Serial.println("We are now connected to the BLE Server."); - connected = true; - } else { - Serial.println("We have failed to connect to the server; there is nothin more we will do."); - } - doConnect = false; - } - - // If we are connected to a peer BLE Server, update the characteristic each time we are reached - // with the current time since boot. - if (connected) { - String newValue = "Time since boot: " + String(millis()/1000); - Serial.println("Setting new characteristic value to \"" + newValue + "\""); - - // Set the characteristic's value to be the array of bytes that is actually a string. - pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); - } - - delay(1000); // Delay a second between loops. -} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino index a48ad482..93f8c89f 100644 --- a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino @@ -14,7 +14,11 @@ #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" class MySecurity : public BLESecurityCallbacks { - + + bool onConfirmPIN(uint32_t pin){ + return false; + } + uint32_t onPassKeyRequest(){ ESP_LOGI(LOG_TAG, "PassKeyRequest"); return 123456; diff --git a/cpp_utils/tests/BLETests/Arduino/security/StaticPIN/StaticPIN.ino b/cpp_utils/tests/BLETests/Arduino/security/StaticPIN/StaticPIN.ino new file mode 100644 index 00000000..d89c14f9 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/StaticPIN/StaticPIN.ino @@ -0,0 +1,47 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("ESP32"); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setStaticPIN(123456); + + //set static passkey + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} From 8fac333f3bbe1e3567e1f4c0dbb9c778a5910d36 Mon Sep 17 00:00:00 2001 From: chegewara Date: Fri, 8 Feb 2019 09:53:49 +0100 Subject: [PATCH 377/381] Fix missing return --- cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino index fb032a69..7bce73f1 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino @@ -62,6 +62,8 @@ bool connectToServer(BLEAddress pAddress) { Serial.println(value.c_str()); pRemoteCharacteristic->registerForNotify(notifyCallback); + + return true; } /** * Scan for BLE servers and find the first one that advertises the service we are looking for. From 43491c7ae825c8fd1d6b08af4869500693ea6b1c Mon Sep 17 00:00:00 2001 From: chegewara Date: Fri, 8 Feb 2019 09:57:01 +0100 Subject: [PATCH 378/381] Fix #803 --- cpp_utils/BLECharacteristic.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index 80f9c7e7..2868ee26 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -289,10 +289,10 @@ void BLECharacteristic::handleGATTServerEvent( m_value.addPart(param->write.value, param->write.len); m_writeEvt = true; } else { + setValue(param->write.value, param->write.len); if (m_pCallbacks != nullptr && param->write.is_prep != true) { m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. } - setValue(param->write.value, param->write.len); } ESP_LOGD(LOG_TAG, " - Response to write event: New value: handle: %.2x, uuid: %s", @@ -503,7 +503,11 @@ void BLECharacteristic::notify(bool is_notification) { // Test to see if we have a 0x2902 descriptor. If we do, then check to see if notification is enabled // and, if not, prevent the notification. - BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); + BLE2902* p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); + if(p2902 == nullptr){ + ESP_LOGE(LOG_TAG, "Characteristic without 0x2902 descriptor"); + return; + } if(is_notification) { if (p2902 != nullptr && !p2902->getNotifications()) { ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring"); @@ -537,7 +541,6 @@ void BLECharacteristic::notify(bool is_notification) { if(!is_notification) m_semaphoreConfEvt.wait("indicate"); } - delete(p2902); ESP_LOGD(LOG_TAG, "<< notify"); } // Notify From 2c256fcf35fc133ca5dab92f23249a9feb63b3bc Mon Sep 17 00:00:00 2001 From: daschiller Date: Tue, 12 Feb 2019 11:01:32 +0100 Subject: [PATCH 379/381] Notify after setBatteryLevel This was discussed in #739, but never implemented. --- cpp_utils/BLEHIDDevice.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp_utils/BLEHIDDevice.cpp b/cpp_utils/BLEHIDDevice.cpp index 69e18be7..46770b74 100644 --- a/cpp_utils/BLEHIDDevice.cpp +++ b/cpp_utils/BLEHIDDevice.cpp @@ -194,6 +194,7 @@ BLECharacteristic* BLEHIDDevice::protocolMode() { void BLEHIDDevice::setBatteryLevel(uint8_t level) { m_batteryLevelCharacteristic->setValue(&level, 1); + m_batteryLevelCharacteristic->notify(); } /* * @brief Returns battery level characteristic From e7e26536a2f7b6cd36fc396ecd957116f4131d40 Mon Sep 17 00:00:00 2001 From: h2zero <32826625+h2zero@users.noreply.github.com> Date: Tue, 30 Apr 2019 18:29:05 -0600 Subject: [PATCH 380/381] Update FreeRTOS.cpp --- cpp_utils/FreeRTOS.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 40e5bbf3..1920fa43 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -67,6 +67,8 @@ uint32_t FreeRTOS::getTimeSinceStart() { */ uint32_t FreeRTOS::Semaphore::wait(std::string owner) { ESP_LOGV(LOG_TAG, ">> wait: Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); + + m_owner = owner; if (m_usePthreads) { pthread_mutex_lock(&m_pthread_mutex); @@ -74,8 +76,6 @@ uint32_t FreeRTOS::Semaphore::wait(std::string owner) { xSemaphoreTake(m_semaphore, portMAX_DELAY); } - m_owner = owner; - if (m_usePthreads) { pthread_mutex_unlock(&m_pthread_mutex); } else { @@ -83,7 +83,6 @@ uint32_t FreeRTOS::Semaphore::wait(std::string owner) { } ESP_LOGV(LOG_TAG, "<< wait: Semaphore released: %s", toString().c_str()); - m_owner = std::string(""); return m_value; } // wait @@ -93,7 +92,8 @@ FreeRTOS::Semaphore::Semaphore(std::string name) { if (m_usePthreads) { pthread_mutex_init(&m_pthread_mutex, nullptr); } else { - m_semaphore = xSemaphoreCreateMutex(); + m_semaphore = xSemaphoreCreateBinary(); + xSemaphoreGive(m_semaphore); } m_name = name; From fe3d318acddf87c6918944f24e8b899d63c816dd Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Thu, 1 Aug 2019 11:36:56 -0500 Subject: [PATCH 381/381] Update README.md --- README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index c785b3c6..fbea27ae 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,2 @@ # ESP32 Snippets -This repository hosts a set of ESP32 snippets that are in different -stages of completeness. They are made available in the hope that there -may be something of value to you and under the notion that something -is better than nothing. The samples are being categorized. - -* vfs - Virtual File System -* wifi - WiFi access -* curl - Examples of curl usage +This repository is no longer being actively maintained. It was previously archived to make it read-only but, by request, has been un-archived so that others may continue to post comments and issues. However, please understand that issues raised are unlikely to be resolved against this repository.