// // Created by erki on 25/01/24. // #include "clock_core.hpp" #include "esp_expected.hpp" #include "clock_core_event.hpp" #include "string_helpers.hpp" #include "vfd_driver.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace { const char* TAG = "clock_core"; struct Context { clock_core::EventGroup* events = nullptr; VfdDriver* vfd = nullptr; Context(const Context&) = delete; Context(Context&&) = delete; Context& operator=(const Context&) = delete; Context& operator=(Context&&) = delete; static Context* getContext() { static Context ctx; return &ctx; } private: Context() = default; }; void wifiEventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { static int retry_num = 0; auto* ctx = Context::getContext(); if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { if (retry_num < 10) { esp_wifi_connect(); retry_num++; ESP_LOGI(TAG, "Retrying to connect to AP."); } else { ctx->events->setEvent(clock_core::EventGroup::WIFI_FAILED); } ESP_LOGW(TAG, "Connection to AP failed."); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { auto* event = static_cast(event_data); ESP_LOGI(TAG, "Connected to AP. Got IP: " IPSTR, IP2STR(&event->ip_info.ip)); retry_num = 0; ESP_LOGI(TAG, "FOLLOW: Events: %p, context: %p", ctx->events, ctx); ctx->events->setEvent(clock_core::EventGroup::WIFI_CONNECTED); } } void sntpEventHandler(struct timeval*) { Context::getContext()->events->setEvent(clock_core::EventGroup::SNTP_SYNCED); } void timerEventHandler(TimerHandle_t timer) { auto context = static_cast(pvTimerGetTimerID(timer)); context->events->setEvent(clock_core::EventGroup::CLOCK_UPDATE); } esp_err_t sntpInitialize() { esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org"); config.smooth_sync = true; config.server_from_dhcp = true; config.renew_servers_after_new_IP = true; config.sync_cb = sntpEventHandler; config.start = false; config.ip_event_to_renew = IP_EVENT_STA_GOT_IP; return esp_netif_sntp_init(&config); } std::expected wifiInitialize(const char* wifi_ssid, const char* wifi_password) { TRY_ESP(esp_netif_init()); TRY_ESP(esp_event_loop_create_default()); esp_netif_create_default_wifi_sta(); wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); TRY_ESP(esp_wifi_init(&config)); TRY_ESP(sntpInitialize()); esp_event_handler_instance_t instance_any_id; esp_event_handler_instance_t instance_got_ip; TRY_ESP(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifiEventHandler, Context::getContext(), &instance_any_id)); TRY_ESP(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifiEventHandler, Context::getContext(), &instance_got_ip)); wifi_sta_config_t sta_config; std::memset(&sta_config, 0, sizeof(sta_config)); std::strncpy((char*) sta_config.ssid, wifi_ssid, 32); std::strncpy((char*) sta_config.password, wifi_password, 64); sta_config.threshold.authmode = WIFI_AUTH_WPA2_PSK; wifi_config_t wifi_config = { .sta = sta_config }; TRY_ESP(esp_wifi_set_mode(WIFI_MODE_STA)); TRY_ESP(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); TRY_ESP(esp_wifi_start()); return {}; } etl::string<9> getCurrentTimeString() { std::time_t time_now; std::tm time_info{}; std::time(&time_now); localtime_r(&time_now, &time_info); etl::string<9> buffer; wrapUnsafeStringOps(buffer, [&time_info](char* string, const int available) { std::strftime(string, available, " %H.%M ", &time_info); }); return buffer; } bool IRAM_ATTR vfdUpdateEventHandler(gptimer_handle_t, const gptimer_alarm_event_data_t*, void* user_ctx) { auto context = static_cast(user_ctx); BaseType_t task_awoken = pdFALSE; xEventGroupSetBitsFromISR(context->events->rtos_event_group, clock_core::EventGroup::DISPLAY_UPDATE, &task_awoken); return task_awoken == pdTRUE; } void initAndStartVfdTimer(Context* ctx) { std::uint32_t clock_source_hz; esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_APB, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &clock_source_hz); auto config = Utility::zeroInitialized(); config.clk_src = GPTIMER_CLK_SRC_DEFAULT; config.direction = GPTIMER_COUNT_UP; config.resolution_hz = 1'000'000; gptimer_handle_t timer = nullptr; gptimer_new_timer(&config, &timer); gptimer_event_callbacks_t callbacks = { .on_alarm = vfdUpdateEventHandler }; gptimer_register_event_callbacks(timer, &callbacks, ctx); gptimer_enable(timer); auto alarm_config = Utility::zeroInitialized(); alarm_config.alarm_count = 10'000; alarm_config.reload_count = 0; alarm_config.flags.auto_reload_on_alarm = true; gptimer_set_alarm_action(timer, &alarm_config); gptimer_start(timer); } } namespace clock_core { void run(const char* wifi_ssid, const char* wifi_password) { ESP_ERROR_CHECK(VfdDriver::defaultInitializeBus(SPI2_HOST, GPIO_NUM_6, GPIO_NUM_7, GPIO_NUM_5)); EventGroup events; Context::getContext()->events = &events; Context::getContext()->vfd = new VfdDriver(SPI2_HOST, CREATE_GPIO(GPIO_NUM_9), CREATE_GPIO(GPIO_NUM_10)); ESP_LOGI(TAG, "INITIAL: Events: %p, context: %p", &events, Context::getContext()); auto clock_timer = xTimerCreate("clock_core", pdMS_TO_TICKS(60'000), pdTRUE, Context::getContext(), timerEventHandler); if (const auto res = wifiInitialize(wifi_ssid, wifi_password); !res.has_value()) { ESP_LOGI(TAG, "WiFi setup errored in clock_core::run. Error: %s", esp_err_to_name(res.error())); abort(); } while (true) { const EventBits_t bits = events.waitForEvent(); if (bits & EventGroup::WIFI_CONNECTED) { ESP_LOGI(TAG, "WiFi setup in clock_core::run successful."); if (const auto res = esp_netif_sntp_start(); res != ESP_OK) { ESP_LOGE(TAG, "SNTP setup failed. Error: %s", esp_err_to_name(res)); abort(); } xTimerStart(clock_timer, pdMS_TO_TICKS(10)); initAndStartVfdTimer(Context::getContext()); } else if (bits & EventGroup::WIFI_FAILED) { ESP_LOGE(TAG, "WiFi setup in clock_core::run failed."); } else if (bits & EventGroup::SNTP_SYNCED) { ESP_LOGI(TAG, "SNTP sync successful."); } else if (bits & EventGroup::CLOCK_UPDATE) { const auto time = getCurrentTimeString(); Context::getContext()->vfd->setDisplayString(time); ESP_LOGI(TAG, "Current time: \"%s\"", time.c_str()); } else if (bits & EventGroup::DISPLAY_UPDATE) { Context::getContext()->vfd->update(); } else { ESP_LOGE(TAG, "WiFi setup encountered an unexpected set of bits. %lu", bits); } } } }