From 50a7ae811b4c926743deb2988fe6302477b29534 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Sat, 28 Aug 2021 19:31:44 +0200 Subject: [PATCH] basic led control implemented --- .gitmodules | 3 + components/esp-nimble-cpp | 1 + main/CMakeLists.txt | 10 ++- main/ble.cpp | 69 +++++++++++++++++ main/ble.h | 4 + main/led.cpp | 159 ++++++++++++++++++++++++++++++++++++++ main/led.h | 4 + main/main.cpp | 88 ++++++++++++++++++++- main/webserver.cpp | 59 ++++++++++++++ main/webserver.h | 4 + main/wifi.cpp | 70 +++++++++++++++++ main/wifi.h | 6 ++ sdkconfig | 14 +++- 13 files changed, 486 insertions(+), 5 deletions(-) create mode 160000 components/esp-nimble-cpp create mode 100644 main/ble.cpp create mode 100644 main/ble.h create mode 100644 main/led.cpp create mode 100644 main/led.h create mode 100644 main/webserver.cpp create mode 100644 main/webserver.h create mode 100644 main/wifi.cpp create mode 100644 main/wifi.h diff --git a/.gitmodules b/.gitmodules index 65764d8..bb648b8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "components/arduino-esp32"] path = components/arduino-esp32 url = git@github.com:0xFEEDC0DE64/arduino-esp32.git +[submodule "components/esp-nimble-cpp"] + path = components/esp-nimble-cpp + url = git@github.com:0xFEEDC0DE64/esp-nimble-cpp.git diff --git a/components/esp-nimble-cpp b/components/esp-nimble-cpp new file mode 160000 index 0000000..7f853fa --- /dev/null +++ b/components/esp-nimble-cpp @@ -0,0 +1 @@ +Subproject commit 7f853fa04b8caca7d86e815485fe24f8118b3263 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 4da302b..a1f91fd 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,13 +1,21 @@ set(headers + ble.h + led.h + webserver.h + wifi.h ) set(sources + ble.cpp + led.cpp main.cpp + webserver.cpp + wifi.cpp ) set(dependencies freertos nvs_flash esp_http_server esp_https_ota mdns app_update esp_system esp_websocket_client driver - arduino-esp32 ArduinoJson FastLED-idf cpputils date espchrono espcpputils esphttpdutils espwifistack expected fmt + arduino-esp32 ArduinoJson FastLED-idf cpputils date espchrono espcpputils esp-nimble-cpp esphttpdutils espwifistack expected fmt ) idf_component_register( diff --git a/main/ble.cpp b/main/ble.cpp new file mode 100644 index 0000000..86c1f61 --- /dev/null +++ b/main/ble.cpp @@ -0,0 +1,69 @@ +#include "ble.h" + +// esp-idf includes +#include + +// 3rdparty lib includes +#include +#include + +// local includes +#include "wifi.h" + +namespace { +constexpr const char * const TAG = "BLE"; + +BLEServer *server{}; +BLEService *service{}; +BLECharacteristic *characteristic{}; + +class BleCallbacks : public NimBLECharacteristicCallbacks +{ +public: + void onWrite(NimBLECharacteristic* pCharacteristic) override; +}; + +BleCallbacks bleCallbacks; +} + +void ble_setup() +{ + BLEDevice::init(deviceName); + + const auto serviceUuid{"26e743b6-8cf2-43b0-8bd0-f762a03e120e"}; + + server = BLEDevice::createServer(); + + service = server->createService(serviceUuid); + + characteristic = service->createCharacteristic("87d4bcc1-233f-4317-87e9-02b93f60f27b", NIMBLE_PROPERTY::WRITE); + characteristic->setCallbacks(&bleCallbacks); + + service->start(); + + BLEAdvertising *advertising = BLEDevice::getAdvertising(); + advertising->addServiceUUID(serviceUuid); + advertising->setScanResponse(true); + BLEDevice::startAdvertising(); +} + +void ble_update() +{ + +} + +namespace { +void BleCallbacks::onWrite(NimBLECharacteristic* pCharacteristic) +{ + const auto &val = pCharacteristic->getValue(); + + StaticJsonDocument<256> doc; + if (const auto error = deserializeJson(doc, val)) + { + ESP_LOGW(TAG, "ignoring cmd with invalid json: %.*s %s", val.size(), val.data(), error.c_str()); + return; + } + + // TODO +} +} diff --git a/main/ble.h b/main/ble.h new file mode 100644 index 0000000..59d9d54 --- /dev/null +++ b/main/ble.h @@ -0,0 +1,4 @@ +#pragma once + +void ble_setup(); +void ble_update(); diff --git a/main/led.cpp b/main/led.cpp new file mode 100644 index 0000000..f81ba8d --- /dev/null +++ b/main/led.cpp @@ -0,0 +1,159 @@ +#include "led.h" + +// system includes +#include + +// esp-idf includes +#include + +// Arduino includes +#include + +// 3rdparty lib includes +#include +#include + +#define LEDC_CHANNEL_0 0 +#define LEDC_CHANNEL_1 0 +#define LEDC_CHANNEL_2 0 +#define LEDC_TIMER_13_BIT 13 +#define LEDC_BASE_FREQ 5000 + +namespace { +std::array leds; + +CRGBPalette16 currentPalette; +TBlendType currentBlending; + +extern CRGBPalette16 myRedWhiteBluePalette; +extern const TProgmemPalette16 myRedWhiteBluePalette_p; + +void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255); +} + +namespace { +void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax) +{ + // calculate duty, 8191 from 2 ^ 13 - 1 + uint32_t duty = (8191 / valueMax) * min(value, valueMax); + + // write duty to LEDC + ledcWrite(channel, duty); +} + +void FillLEDsFromPaletteColors( uint8_t colorIndex) +{ + uint8_t brightness = 255; + + for( int i = 0; i < leds.size(); ++i) { + leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending); + colorIndex += 3; + } +} + +void SetupTotallyRandomPalette() +{ + for( int i = 0; i < 16; ++i) { + currentPalette[i] = CHSV( random8(), 255, random8()); + } +} + +void SetupBlackAndWhiteStripedPalette() +{ + // 'black out' all 16 palette entries... + fill_solid( currentPalette, 16, CRGB::Black); + // and set every fourth one to white. + currentPalette[0] = CRGB::White; + currentPalette[4] = CRGB::White; + currentPalette[8] = CRGB::White; + currentPalette[12] = CRGB::White; + +} + +void SetupPurpleAndGreenPalette() +{ + CRGB purple = CHSV( HUE_PURPLE, 255, 255); + CRGB green = CHSV( HUE_GREEN, 255, 255); + CRGB black = CRGB::Black; + + currentPalette = CRGBPalette16( + green, green, black, black, + purple, purple, black, black, + green, green, black, black, + purple, purple, black, black ); +} + +const TProgmemPalette16 myRedWhiteBluePalette_p = +{ + CRGB::Red, + CRGB::Gray, // 'white' is too bright compared to red and blue + CRGB::Blue, + CRGB::Black, + + CRGB::Red, + CRGB::Gray, + CRGB::Blue, + CRGB::Black, + + CRGB::Red, + CRGB::Red, + CRGB::Gray, + CRGB::Gray, + CRGB::Blue, + CRGB::Blue, + CRGB::Black, + CRGB::Black +}; + + +void ChangePalettePeriodically() +{ + constexpr auto millis = []() -> unsigned int { return espchrono::millis_clock::now().time_since_epoch().count(); }; + + uint8_t secondHand = (millis() / 1000) % 60; + static uint8_t lastSecond = 99; + + if( lastSecond != secondHand) { + lastSecond = secondHand; + if( secondHand == 0) { currentPalette = RainbowColors_p; currentBlending = LINEARBLEND; } + if( secondHand == 10) { currentPalette = RainbowStripeColors_p; currentBlending = NOBLEND; } + if( secondHand == 15) { currentPalette = RainbowStripeColors_p; currentBlending = LINEARBLEND; } + if( secondHand == 20) { SetupPurpleAndGreenPalette(); currentBlending = LINEARBLEND; } + if( secondHand == 25) { SetupTotallyRandomPalette(); currentBlending = LINEARBLEND; } + if( secondHand == 30) { SetupBlackAndWhiteStripedPalette(); currentBlending = NOBLEND; } + if( secondHand == 35) { SetupBlackAndWhiteStripedPalette(); currentBlending = LINEARBLEND; } + if( secondHand == 40) { currentPalette = CloudColors_p; currentBlending = LINEARBLEND; } + if( secondHand == 45) { currentPalette = PartyColors_p; currentBlending = LINEARBLEND; } + if( secondHand == 50) { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND; } + if( secondHand == 55) { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; } + } +} +} + + +void led_setup() +{ + currentPalette = RainbowColors_p; + currentBlending = LINEARBLEND; + + ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT); + ledcSetup(LEDC_CHANNEL_1, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT); + ledcSetup(LEDC_CHANNEL_2, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT); + ledcAttachPin(27, LEDC_CHANNEL_0); + ledcAttachPin(33, LEDC_CHANNEL_1); + ledcAttachPin(32, LEDC_CHANNEL_2); +} + +void led_update() +{ + ChangePalettePeriodically(); + + static uint8_t startIndex = 0; + startIndex = startIndex + 1; /* motion speed */ + + FillLEDsFromPaletteColors( startIndex); + + ledcAnalogWrite(LEDC_CHANNEL_0, leds[0].red); + ledcAnalogWrite(LEDC_CHANNEL_1, leds[0].green); + ledcAnalogWrite(LEDC_CHANNEL_2, leds[0].blue); +} diff --git a/main/led.h b/main/led.h new file mode 100644 index 0000000..e6a3c24 --- /dev/null +++ b/main/led.h @@ -0,0 +1,4 @@ +#pragma once + +void led_setup(); +void led_update(); diff --git a/main/main.cpp b/main/main.cpp index 31dfb9c..3116a1c 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1 +1,87 @@ -extern "C" void app_main() {} +#include "sdkconfig.h" + +// esp-idf includes +#include +#include +#if defined(CONFIG_ESP_TASK_WDT_PANIC) || defined(CONFIG_ESP_TASK_WDT) +#include +#include +#endif +#ifdef CONFIG_APP_ROLLBACK_ENABLE +#include +#endif +#include + +// 3rdparty lib includes +#include + +// local includes +#include "wifi.h" +#include "ble.h" +#include "webserver.h" +#include "led.h" + +using namespace std::chrono_literals; + +namespace { +constexpr const char * const TAG = "MAIN"; +} // namespace + +extern "C" void app_main() +{ +#if defined(CONFIG_ESP_TASK_WDT_PANIC) || defined(CONFIG_ESP_TASK_WDT) + { + const auto taskHandle = xTaskGetCurrentTaskHandle(); + if (!taskHandle) + { + ESP_LOGE(TAG, "could not get handle to current main task!"); + } + else if (const auto result = esp_task_wdt_add(taskHandle); result != ESP_OK) + { + ESP_LOGE(TAG, "could not add main task to watchdog: %s", esp_err_to_name(result)); + } + } +#endif + +#ifdef CONFIG_APP_ROLLBACK_ENABLE + esp_ota_img_states_t ota_state; + if (const esp_partition_t * const running = esp_ota_get_running_partition()) + { + if (const auto result = esp_ota_get_state_partition(running, &ota_state); result == ESP_ERR_NOT_FOUND) + ota_state = ESP_OTA_IMG_VALID; + else if (result != ESP_OK) + { + ESP_LOGE(TAG, "esp_ota_get_state_partition() failed with %s", esp_err_to_name(result)); + ota_state = ESP_OTA_IMG_UNDEFINED; + } + } + else + { + ESP_LOGE(TAG, "esp_ota_get_running_partition() returned nullptr"); + ota_state = ESP_OTA_IMG_UNDEFINED; + } +#endif + + wifi_setup(); + ble_setup(); + webserver_setup(); + led_setup(); + + while (true) + { +#if defined(CONFIG_ESP_TASK_WDT_PANIC) || defined(CONFIG_ESP_TASK_WDT) + if (const auto result = esp_task_wdt_reset(); result != ESP_OK) + ESP_LOGE(TAG, "esp_task_wdt_reset() failed with %s", esp_err_to_name(result)); +#endif + + wifi_update(); + ble_update(); + webserver_update(); + led_update(); + } +} + +auto espchrono::local_clock::timezone() noexcept -> time_zone +{ + return time_zone{1h, DayLightSavingMode::EuropeanSummerTime}; +} diff --git a/main/webserver.cpp b/main/webserver.cpp new file mode 100644 index 0000000..d606583 --- /dev/null +++ b/main/webserver.cpp @@ -0,0 +1,59 @@ +#include "webserver.h" + +// esp-idf includes +#include +#include + +// 3rdparty lib includes +#include +#include + +// local includes + +using namespace esphttpdutils; + +namespace { +constexpr const char * const TAG = "WEBSERVER"; + +httpd_handle_t httpdHandle; + +esp_err_t webserver_root_handler(httpd_req_t *req); +} // namespace + +void webserver_setup() +{ + { + httpd_config_t httpConfig HTTPD_DEFAULT_CONFIG(); + httpConfig.core_id = 1; + httpConfig.max_uri_handlers = 14; + + const auto result = httpd_start(&httpdHandle, &httpConfig); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_start(): %s", esp_err_to_name(result)); + if (result != ESP_OK) + return; + } + + for (const httpd_uri_t &uri : { + httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL }, + }) + { + const auto result = httpd_register_uri_handler(httpdHandle, &uri); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_register_uri_handler() for %s: %s", uri.uri, esp_err_to_name(result)); + //if (result != ESP_OK) + // return result; + } +} + +void webserver_update() +{ + +} + +namespace { +esp_err_t webserver_root_handler(httpd_req_t *req) +{ + std::string body = "hello world"; + + CALL_AND_EXIT(webserver_resp_send_succ, req, "text/html", body) +} +} diff --git a/main/webserver.h b/main/webserver.h new file mode 100644 index 0000000..0b4c966 --- /dev/null +++ b/main/webserver.h @@ -0,0 +1,4 @@ +#pragma once + +void webserver_setup(); +void webserver_update(); diff --git a/main/wifi.cpp b/main/wifi.cpp new file mode 100644 index 0000000..013f0ee --- /dev/null +++ b/main/wifi.cpp @@ -0,0 +1,70 @@ +#include "wifi.h" + +// esp-idf includes +#include + +// 3rdparty lib includes +#include + +namespace { +constexpr const char * const TAG = "WIFI"; +wifi_stack::config makeWifiConfig(); +} // namespace + +char deviceName[32] = "ledcontrol"; + +void wifi_setup() +{ + if (const auto result = wifi_stack::get_default_mac_addr()) + std::sprintf(deviceName, "ledcontrol_%02hhx%02hhx%02hhx", result->at(3), result->at(4), result->at(5)); + else + ESP_LOGE(TAG, "get_default_mac_addr() failed: %.*s", result.error().size(), result.error().data()); + + ESP_LOGI(TAG, "deviceName = %s", deviceName); + + wifi_stack::init(makeWifiConfig()); +} + +void wifi_update() +{ + wifi_stack::update(makeWifiConfig()); +} + +namespace { +wifi_stack::config makeWifiConfig() +{ + return wifi_stack::config { + .wifiEnabled = true, + .hostname = deviceName, + .sta = { + .wifis = std::array { + wifi_stack::wifi_entry { .ssid = "camp++", .key = {} }, + wifi_stack::wifi_entry { .ssid = {}, .key = {} }, + wifi_stack::wifi_entry { .ssid = {}, .key = {} }, + wifi_stack::wifi_entry { .ssid = {}, .key = {} }, + wifi_stack::wifi_entry { .ssid = {}, .key = {} }, + wifi_stack::wifi_entry { .ssid = {}, .key = {} }, + wifi_stack::wifi_entry { .ssid = {}, .key = {} }, + wifi_stack::wifi_entry { .ssid = {}, .key = {} }, + wifi_stack::wifi_entry { .ssid = {}, .key = {} }, + wifi_stack::wifi_entry { .ssid = {}, .key = {} } + }, + .min_rssi = -90 + }, + .ap = { + .ssid = deviceName, + .key = "CampPlusPlus", + .static_ip = { + .ip = {192, 168, 0, 1}, + .subnet = {255, 255, 255, 0}, + .gateway = {192, 168, 0, 1} + }, + .channel = 1, + .authmode = WIFI_AUTH_WPA2_PSK, + .ssid_hidden = false, + .max_connection = 4, + .beacon_interval = 100 + } + }; +} +} diff --git a/main/wifi.h b/main/wifi.h new file mode 100644 index 0000000..8de27ed --- /dev/null +++ b/main/wifi.h @@ -0,0 +1,6 @@ +#pragma once + +extern char deviceName[32]; + +void wifi_setup(); +void wifi_update(); diff --git a/sdkconfig b/sdkconfig index 7caef8e..f0b6ecc 100644 --- a/sdkconfig +++ b/sdkconfig @@ -130,8 +130,8 @@ CONFIG_PARTITION_TABLE_MD5=y # # FastLED # -CONFIG_FASTLED_METHOD_I2S=y -# CONFIG_FASTLED_METHOD_RMT is not set +# CONFIG_FASTLED_METHOD_I2S is not set +CONFIG_FASTLED_METHOD_RMT=y # end of FastLED # @@ -813,7 +813,7 @@ CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y # # LWIP # -CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +CONFIG_LWIP_LOCAL_HOSTNAME="ledcontroller" CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y # CONFIG_LWIP_L2_TO_L3_COPY is not set # CONFIG_LWIP_IRAM_OPTIMIZATION is not set @@ -1257,6 +1257,14 @@ CONFIG_WPA_MBEDTLS_CRYPTO=y # CONFIG_WPA_WPS_WARS is not set # CONFIG_WPA_11KV_SUPPORT is not set # end of Supplicant + +# +# ESP-NimBLE-CPP configuration +# +# CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT is not set +# CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT is not set +# CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT is not set +# end of ESP-NimBLE-CPP configuration # end of Component config #