Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion firmware/main/apps/app_launcher/app_launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
#include "app_launcher.h"
#include <hal/hal.h>
#include <hal/hal_toggles.h>
#include <mooncake.h>
#include <mooncake_log.h>
#include <stackchan/stackchan.h>
Expand Down Expand Up @@ -70,7 +71,16 @@ void AppLauncher::onLauncherDestroy()
void AppLauncher::create_launcher_view()
{
_view = std::make_unique<view::LauncherView>();
_view->init(getAppProps());

auto app_props = getAppProps();
std::vector<mooncake::AppProps_t> filtered_props;
for (const auto& props : app_props) {
if (hal_toggles::is_app_enabled(props.info.name)) {
filtered_props.push_back(props);
}
}

_view->init(filtered_props);
_view->onAppClicked = [&](int appID) {
mclog::tagInfo(getAppInfo().name, "handle open app, app id: {}", appID);
openApp(appID);
Expand Down
9 changes: 9 additions & 0 deletions firmware/main/apps/app_setup/app_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* SPDX-License-Identifier: MIT
*/
#include "app_setup.h"
#include "workers/feature_toggle.h"
#include <hal/hal.h>
#include <mooncake.h>
#include <mooncake_log.h>
Expand Down Expand Up @@ -117,6 +118,14 @@ void AppSetup::onOpen()
// }}
},
},
{
"Others",
{{"Feature Toggles",
[&]() {
_destroy_menu = true;
_worker = std::make_unique<FeatureToggleWorker>();
}}},
},
};

LvglLockGuard lock;
Expand Down
88 changes: 88 additions & 0 deletions firmware/main/apps/app_setup/workers/feature_toggle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "feature_toggle.h"
#include "toggle_row.h"
#include <mooncake_log.h>
#include <hal/hal.h>
#include "common.h"

using namespace setup_workers;
using namespace hal_toggles;

static const char* TAG = "FeatureToggle";

FeatureToggleWorker::FeatureToggleWorker()
{
if (!hal_toggles::init_nvs()) {
mclog::tagError(TAG, "NVS init failed");
return;
}

for (int i = 0; i < TOGGLE_COUNT; i++) {
_states[i] = hal_toggles::get_toggle_state(TOGGLE_LIST[i].key);
}

create_toggle_ui();
}

void FeatureToggleWorker::create_toggle_ui()
{
auto screen = lv_screen_active();

_panel = std::make_unique<uitk::lvgl_cpp::Container>(screen);
_panel->setAlign(LV_ALIGN_CENTER);
_panel->setPos(0, 0);
_panel->setSize(320, 240);
_panel->setBgColor(lv_color_hex(0xEDF4FF));
_panel->setRadius(0);
_panel->setBorderWidth(0);
_panel->setFlexFlow(LV_FLEX_FLOW_COLUMN);
_panel->setFlexAlign(LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
_panel->setPadding(15, 20, 10, 20);
_panel->setPadRow(10);

_title = std::make_unique<uitk::lvgl_cpp::Label>(_panel->get());
_title->setText("Feature Toggles");
_title->setTextFont(&lv_font_montserrat_20);
_title->setTextColor(lv_color_hex(0x26206A));
_title->setTextAlign(LV_TEXT_ALIGN_CENTER);

_panel->setScrollbarMode(LV_SCROLLBAR_MODE_OFF);

for (int i = 0; i < TOGGLE_COUNT; i++) {
auto row = std::make_unique<ToggleRow>(_panel->get(), TOGGLE_LIST[i].display_name);
row->set_state(_states[i]);
_toggle_rows_widgets.push_back(std::move(row));
}

_reset_button = std::make_unique<uitk::lvgl_cpp::Button>(_panel->get());
_reset_button->setSize(260, 40);
apply_button_common_style(*_reset_button);
_reset_button->label().setText("Reset");
_reset_button->onClick().connect([this]() {
hal_toggles::reset_toggles_to_defaults();
for (int i = 0; i < TOGGLE_COUNT; i++) {
_states[i] = true;
_toggle_rows_widgets[i]->set_state(true);
}
mclog::tagInfo(TAG, "Toggles reset to defaults");
});

_back_button = std::make_unique<uitk::lvgl_cpp::Button>(_panel->get());
_back_button->setSize(260, 40);
apply_button_common_style(*_back_button);
_back_button->label().setText("Back");
_back_button->onClick().connect([this]() {
_is_done = true;
});
}

void FeatureToggleWorker::update()
{
for (int i = 0; i < TOGGLE_COUNT; i++) {
bool current_state = _toggle_rows_widgets[i]->get_state();
if (current_state != _states[i]) {
_states[i] = current_state;
hal_toggles::set_toggle_state(TOGGLE_LIST[i].key, current_state);
mclog::tagInfo(TAG, "Toggle %s = %s", TOGGLE_LIST[i].display_name, current_state ? "ON" : "OFF");
}
}
}
26 changes: 26 additions & 0 deletions firmware/main/apps/app_setup/workers/feature_toggle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once
#include "workers.h"
#include "toggle_row.h"
#include <hal/hal_toggles.h>

namespace setup_workers {

class FeatureToggleWorker : public WorkerBase {
public:
FeatureToggleWorker();
void update() override;

private:
void create_toggle_ui();
void update_toggle_display();

std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
std::unique_ptr<uitk::lvgl_cpp::Label> _title;
std::vector<std::unique_ptr<ToggleRow>> _toggle_rows_widgets;
std::unique_ptr<uitk::lvgl_cpp::Button> _reset_button;
std::unique_ptr<uitk::lvgl_cpp::Button> _back_button;

bool _states[hal_toggles::TOGGLE_COUNT];
};

} // namespace setup_workers
47 changes: 47 additions & 0 deletions firmware/main/apps/app_setup/workers/toggle_row.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "toggle_row.h"
#include <cstring>

namespace setup_workers {

ToggleRow::ToggleRow(lv_obj_t* parent, const char* label_text)
: _state(false)
{
auto row = lv_obj_create(parent);
lv_obj_set_size(row, 280, 28);
lv_obj_add_flag(row, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_bg_opa(row, 0, LV_PART_MAIN);
lv_obj_set_style_border_width(row, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(row, 0, LV_PART_MAIN);

auto label = lv_label_create(row);
lv_label_set_text(label, label_text);
lv_obj_add_flag(label, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_remove_flag(label, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, LV_PART_MAIN);
lv_obj_set_style_text_color(label, lv_color_hex(0x26206A), LV_PART_MAIN);
lv_label_set_long_mode(label, LV_LABEL_LONG_DOT);

_sw = lv_switch_create(row);
lv_obj_set_style_bg_color(_sw, lv_color_hex(0xB8D3FD), LV_PART_MAIN);
lv_obj_set_style_bg_color(_sw, lv_color_hex(0xB8D3FD), LV_PART_INDICATOR);
}

void ToggleRow::set_state(bool enabled)
{
_state = enabled;
if (enabled) {
lv_obj_add_state(_sw, LV_STATE_CHECKED);
} else {
lv_obj_remove_state(_sw, LV_STATE_CHECKED);
}
}

bool ToggleRow::get_state() const
{
return lv_obj_has_state(_sw, LV_STATE_CHECKED);
}

} // namespace setup_workers
19 changes: 19 additions & 0 deletions firmware/main/apps/app_setup/workers/toggle_row.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once
#include <smooth_lvgl.hpp>
#include <lvgl.h>

namespace setup_workers {

class ToggleRow {
public:
ToggleRow(lv_obj_t* parent, const char* label_text);
void set_state(bool enabled);
bool get_state() const;
lv_obj_t* get_switch() const { return _sw; }

private:
lv_obj_t* _sw;
bool _state;
};

} // namespace setup_workers
115 changes: 115 additions & 0 deletions firmware/main/hal/hal_toggles.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "hal_toggles.h"
#include <nvs_flash.h>
#include <nvs.h>
#include <esp_log.h>
#include <cstring>

static const char* TAG = "hal_toggles";

namespace hal_toggles {

static bool _initialized = false;

bool init_nvs()
{
if (_initialized) {
return true;
}
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
nvs_flash_erase();
err = nvs_flash_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS init failed after erase: %s", esp_err_to_name(err));
return false;
}
} else if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS init failed: %s", esp_err_to_name(err));
return false;
}
_initialized = true;
ESP_LOGI(TAG, "NVS toggles initialized");
return true;
}

bool is_first_boot()
{
init_nvs();
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
if (err != ESP_OK) {
return true;
}
nvs_close(handle);
return false;
}

bool get_toggle_state(std::string_view key)
{
init_nvs();
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to open NVS, returning default true for %.*s", (int)key.size(), key.data());
return true;
}

uint8_t value = 1;
err = nvs_get_u8(handle, key.data(), &value);
if (err != ESP_OK) {
value = 1;
}
nvs_close(handle);
return value != 0;
}

void set_toggle_state(std::string_view key, bool value)
{
init_nvs();
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open NVS for write");
return;
}
err = nvs_set_u8(handle, key.data(), value ? 1 : 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set toggle %.*s", (int)key.size(), key.data());
}
nvs_commit(handle);
nvs_close(handle);
ESP_LOGI(TAG, "Set toggle %.*s = %s", (int)key.size(), key.data(), value ? "ON" : "OFF");
}

void reset_toggles_to_defaults()
{
init_nvs();
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open NVS for reset");
return;
}

for (int i = 0; i < TOGGLE_COUNT; i++) {
err = nvs_set_u8(handle, TOGGLE_LIST[i].key, TOGGLE_LIST[i].default_value ? 1 : 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to reset toggle %s", TOGGLE_LIST[i].key);
}
}
nvs_commit(handle);
nvs_close(handle);
ESP_LOGI(TAG, "All toggles reset to defaults");
}

bool is_app_enabled(std::string_view app_name)
{
for (int i = 0; i < TOGGLE_COUNT; i++) {
if (app_name == TOGGLE_LIST[i].display_name) {
return get_toggle_state(TOGGLE_LIST[i].key);
}
}
return true;
}

} // namespace hal_toggles
40 changes: 40 additions & 0 deletions firmware/main/hal/hal_toggles.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <string_view>

namespace hal_toggles {

constexpr const char* NVS_NAMESPACE = "feature_toggles";

constexpr const char* KEY_AI_AGENT = "ai_agent";
constexpr const char* KEY_AVATAR = "avatar";
constexpr const char* KEY_ESPNOW_REMOTE = "espnow_remote";
constexpr const char* KEY_APP_CENTER = "app_center";
constexpr const char* KEY_EZDATA = "ezdata";
constexpr const char* KEY_DANCE = "dance";

constexpr uint8_t TOGGLE_COUNT = 6;

struct ToggleInfo {
const char* key;
const char* display_name;
bool default_value;
};

const ToggleInfo TOGGLE_LIST[TOGGLE_COUNT] = {
{KEY_AI_AGENT, "AI.AGENT", true},
{KEY_AVATAR, "AVATAR", true},
{KEY_ESPNOW_REMOTE, "ESPNOW.REMOTE", true},
{KEY_APP_CENTER, "APP.CENTER", true},
{KEY_EZDATA, "EZDATA", true},
{KEY_DANCE, "DANCE", true},
};

bool get_toggle_state(std::string_view key);
void set_toggle_state(std::string_view key, bool value);
void reset_toggles_to_defaults();
bool is_first_boot();
bool init_nvs();
bool is_app_enabled(std::string_view app_name);

} // namespace hal_toggles