Skip to content
Merged
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
85 changes: 85 additions & 0 deletions custom/integration/rooms_provider.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#include "rooms_provider.h"

#include <stddef.h>

static room_entity_t ENT_BAKERY_MAIN = {
.entity_id = "light.bakery_main",
.kind = ROOM_ENTITY_LIGHT,
.available = true,
.on = false,
.value = -1,
};

static room_entity_t ENT_BEDROOM_MAIN = {
.entity_id = "light.bedroom_main",
.kind = ROOM_ENTITY_LIGHT,
.available = true,
.on = true,
.value = 75,
};

static room_entity_t ENT_LIVING_MAIN = {
.entity_id = "light.living_main",
.kind = ROOM_ENTITY_LIGHT,
.available = true,
.on = false,
.value = -1,
};

static room_entity_t* ROOM0_ENTS[] = {&ENT_BAKERY_MAIN};
static room_entity_t* ROOM1_ENTS[] = {&ENT_BEDROOM_MAIN};
static room_entity_t* ROOM2_ENTS[] = {&ENT_LIVING_MAIN};

static room_t ROOMS[] = {
{
.room_id = "bakery",
.name = "Bakery",
.entities = (room_entity_t**)ROOM0_ENTS,
.entity_count = sizeof(ROOM0_ENTS) / sizeof(ROOM0_ENTS[0]),
.temp_c = 24,
.humidity = 48,
},
{
.room_id = "bedroom",
.name = "Bedroom",
.entities = (room_entity_t**)ROOM1_ENTS,
.entity_count = sizeof(ROOM1_ENTS) / sizeof(ROOM1_ENTS[0]),
.temp_c = 23,
.humidity = 50,
},
{
.room_id = "living",
.name = "Living Room",
.entities = (room_entity_t**)ROOM2_ENTS,
.entity_count = sizeof(ROOM2_ENTS) / sizeof(ROOM2_ENTS[0]),
.temp_c = 25,
.humidity = 45,
},
};

static rooms_state_t DEFAULT_STATE = {
.rooms = ROOMS,
.room_count = sizeof(ROOMS) / sizeof(ROOMS[0]),
};

static const rooms_state_t* s_current_state = &DEFAULT_STATE;

const rooms_state_t* rooms_provider_get_state(void)
{
return s_current_state;
}

void rooms_provider_set_state(const rooms_state_t* state)
{
s_current_state = state;
Comment on lines +70 to +79
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Avoid storing pointers to caller-owned room snapshots

The provider advertises that callers can treat the returned snapshot as provider-owned, but rooms_provider_set_state simply assigns s_current_state to the caller’s pointer without copying or retaining ownership. If an integration builds a rooms_state_t on the stack (as the tests do) or frees/overwrites the structure after the setter returns, subsequent calls to rooms_provider_get_state will read dangling memory and the UI will dereference invalid entities. The provider should allocate or copy the snapshot it owns, or document and enforce that the passed state must outlive the provider.

Useful? React with 👍 / 👎.

}

void rooms_provider_reset_state(void)
{
s_current_state = &DEFAULT_STATE;
}
38 changes: 38 additions & 0 deletions custom/integration/rooms_provider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#pragma once

#include "../ui/pages/ui_rooms_model.h"

#ifdef __cplusplus
extern "C"
{
#endif

/**
* @brief Retrieve the current rooms state snapshot.
*
* The pointer remains owned by the provider. Callers should treat the
* returned data as read-only and copy it if a longer lifetime is needed.
*/
const rooms_state_t* rooms_provider_get_state(void);

/**
* @brief Replace the active rooms state snapshot.
*
* Passing NULL clears the current snapshot so consumers can reset their
* views while awaiting new data.
*/
void rooms_provider_set_state(const rooms_state_t* state);

/**
* @brief Restore the provider's built-in mock snapshot.
*/
void rooms_provider_reset_state(void);

#ifdef __cplusplus
}
#endif
57 changes: 2 additions & 55 deletions custom/ui/pages/ui_page_rooms.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <stdbool.h>
#include <string.h>

#include "../../integration/rooms_provider.h"
#include "../ui_theme.h"
#include "../ui_wallpaper.h"
#include "../widgets/ui_room_card.h"
Expand Down Expand Up @@ -45,60 +46,6 @@ typedef struct

static ui_page_rooms_ctx_t* s_ctx = NULL;

static room_entity_t ENT_BAKERY_MAIN = {
.entity_id = "light.bakery_main",
.kind = ROOM_ENTITY_LIGHT,
.available = true,
.on = false,
.value = -1,
};

static room_entity_t ENT_BEDROOM_MAIN = {
.entity_id = "light.bedroom_main",
.kind = ROOM_ENTITY_LIGHT,
.available = true,
.on = true,
.value = 75,
};

static room_entity_t ENT_LIVING_MAIN = {
.entity_id = "light.living_main",
.kind = ROOM_ENTITY_LIGHT,
.available = true,
.on = false,
.value = -1,
};

static room_entity_t* ROOM0_ENTS[] = {&ENT_BAKERY_MAIN};
static room_entity_t* ROOM1_ENTS[] = {&ENT_BEDROOM_MAIN};
static room_entity_t* ROOM2_ENTS[] = {&ENT_LIVING_MAIN};

static room_t ROOMS[] = {
{.room_id = "bakery",
.name = "Bakery",
.entities = (room_entity_t**)ROOM0_ENTS,
.entity_count = 1,
.temp_c = 24,
.humidity = 48},
{.room_id = "bedroom",
.name = "Bedroom",
.entities = (room_entity_t**)ROOM1_ENTS,
.entity_count = 1,
.temp_c = 23,
.humidity = 50},
{.room_id = "living",
.name = "Living Room",
.entities = (room_entity_t**)ROOM2_ENTS,
.entity_count = 1,
.temp_c = 25,
.humidity = 45},
};

static rooms_state_t INITIAL_STATE = {
.rooms = ROOMS,
.room_count = sizeof(ROOMS) / sizeof(ROOMS[0]),
};

static void ui_page_rooms_delete_cb(lv_event_t* event)
{
if (event == NULL)
Expand Down Expand Up @@ -395,7 +342,7 @@ lv_obj_t* ui_page_rooms_create(lv_obj_t* parent)

s_ctx = ctx;

ui_page_rooms_set_state(&INITIAL_STATE);
ui_page_rooms_set_state(rooms_provider_get_state());
play_intro(ctx);

return page;
Expand Down
1 change: 1 addition & 0 deletions platforms/desktop/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ set(ROOMS_PAGE_SHARED_SRCS
custom/ui/widgets/ui_room_card.c
custom/ui/ui_theme.c
custom/ui/ui_wallpaper.c
custom/integration/rooms_provider.c
)

add_executable(rooms_page_test ${ROOMS_PAGE_SHARED_SRCS} tests/ui/rooms_page_test.c)
Expand Down
5 changes: 5 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,16 @@ endif()
# -----------------------------
add_library(ui_rooms_under_test
${REPO_ROOT}/custom/ui/pages/ui_page_rooms.c
${REPO_ROOT}/custom/ui/pages/ui_rooms_model.c
${REPO_ROOT}/custom/ui/widgets/ui_room_card.c
${REPO_ROOT}/custom/ui/ui_theme.c
${REPO_ROOT}/custom/ui/ui_wallpaper.c
${REPO_ROOT}/custom/integration/rooms_provider.c
)
target_include_directories(ui_rooms_under_test PUBLIC
${REPO_ROOT}/custom/ui
${REPO_ROOT}/custom/ui/pages
${REPO_ROOT}/custom/integration
)
target_link_libraries(ui_rooms_under_test PUBLIC lvgl::lvgl lvgl_config)

Expand Down
3 changes: 3 additions & 0 deletions tests/ui/rooms_page_snapshot.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <string.h>

#include "custom/ui/pages/ui_page_rooms.h"
#include "integration/rooms_provider.h"
#include "lvgl.h"

#define SNAP_SCREEN_WIDTH 1280
Expand Down Expand Up @@ -50,6 +51,8 @@ int main(void)
lv_obj_t* screen = lv_screen_active();
lv_obj_clean(screen);

rooms_provider_reset_state();

lv_obj_t* page = ui_page_rooms_create(screen);
if (page == NULL)
{
Expand Down
73 changes: 73 additions & 0 deletions tests/ui/rooms_page_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <string.h>

#include "custom/ui/pages/ui_page_rooms.h"
#include "integration/rooms_provider.h"
#include "lvgl.h"

#define TEST_SCREEN_WIDTH 1280
Expand Down Expand Up @@ -101,6 +102,8 @@ int main(void)
lv_obj_t* screen = lv_screen_active();
lv_obj_clean(screen);

rooms_provider_reset_state();

lv_obj_t* page = ui_page_rooms_create(screen);
if (page == NULL)
{
Expand Down Expand Up @@ -142,6 +145,76 @@ int main(void)
}
}

room_entity_t bakery_main = {
.entity_id = "light.bakery_main",
.kind = ROOM_ENTITY_LIGHT,
.available = true,
.on = true,
.value = 10,
};
room_entity_t bedroom_main = {
.entity_id = "light.bedroom_main",
.kind = ROOM_ENTITY_LIGHT,
.available = true,
.on = false,
.value = -1,
};
room_entity_t living_scene = {
.entity_id = "switch.living_scene",
.kind = ROOM_ENTITY_SWITCH,
.available = true,
.on = true,
.value = -1,
};

room_entity_t* bakery_entities[] = {&bakery_main};
room_entity_t* bedroom_entities[] = {&bedroom_main};
room_entity_t* living_entities[] = {&living_scene};

room_t updated_rooms[] = {
{.room_id = "bakery",
.name = "Bakery",
.entities = bakery_entities,
.entity_count = 1,
.temp_c = 22,
.humidity = 41},
{.room_id = "bedroom",
.name = "Bedroom",
.entities = bedroom_entities,
.entity_count = 1,
.temp_c = 21,
.humidity = 46},
{.room_id = "living",
.name = "Living Room",
.entities = living_entities,
.entity_count = 1,
.temp_c = 24,
.humidity = 44},
};

rooms_state_t updated_state = {
.rooms = updated_rooms,
.room_count = sizeof(updated_rooms) / sizeof(updated_rooms[0]),
};

rooms_provider_set_state(&updated_state);
ui_page_rooms_set_state(rooms_provider_get_state());

lv_obj_t* living_toggle = ui_page_rooms_get_toggle("living");
if (!ensure(living_toggle != NULL, "Living toggle missing after update"))
{
return 1;
}
lv_obj_send_event(living_toggle, LV_EVENT_CLICKED, NULL);
if (!ensure(capture.last_entity != NULL
&& strcmp(capture.last_entity, "switch.living_scene") == 0,
"Updated entity id not reflected"))
{
return 1;
}

rooms_provider_reset_state();

for (size_t i = 0; i < k_room_count; i++)
{
lv_obj_t* card = ui_page_rooms_get_card(room_ids[i]);
Expand Down
Loading