> For the complete documentation index, see [llms.txt](https://docs.aic-eec.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/hmi-development/gpio-to-hmi-display/ux-ui-workshops/gpio-dashboard.md).

# GPIO Dashboard

## Lab 5: GPIO Dashboard

### 1. โครงสร้างภาพรวมของ Lab

#### Why? - ทำไมต้องเรียนรู้เรื่องนี้

GPIO Dashboard เป็น Lab สุดท้ายของ Part 1 Section I ที่รวมทุก widget เข้าด้วยกัน:

* **Industrial HMI**: ในโรงงานจริง operator ต้องเห็นและควบคุม I/O หลายตัวจากหน้าจอเดียว
* **Factory Floor Monitoring**: แสดงสถานะ Motor, Valve, Sensor, Alarm พร้อมกัน
* **Multi-Widget Integration**: รวม Label + Button + Switch + LED ที่เรียนมาทั้งหมด
* **Layout Management**: จัดวาง widget หลายตัวให้เป็นระเบียบด้วย Container, Flex, Grid
* **Data Structure Design**: ใช้ struct array จัดการข้อมูลหลาย channel อย่างมีระบบ
* **Scalable Architecture**: ออกแบบ code ที่เพิ่ม/ลด channel ได้ง่าย

ใน Embedded System จริง dashboard คือหน้าจอที่ใช้งานบ่อยที่สุด เพราะ operator ต้องเห็นภาพรวมทั้งหมดจากที่เดียว

#### What? - จะได้เรียนรู้อะไร

1. **Container Widget**: สร้าง container สำหรับจัดกลุ่ม widgets
2. **Flex Layout**: จัดวาง widget ด้วย `lv_obj_set_flex_flow()`, `lv_obj_set_flex_align()`
3. **Grid Positioning**: คำนวณตำแหน่ง widgets แบบ 2D grid
4. **Struct Array Pattern**: ใช้ `typedef struct` + array จัดการหลาย items
5. **User Data Pattern**: ส่ง context ไปยัง callback ด้วย user\_data
6. **Batch Operations**: ควบคุมทุก GPIO พร้อมกันด้วยปุ่ม All ON / All OFF
7. **Color Theming**: ใช้ color constants จัดธีม dark/light

#### How? - ทำอย่างไร

สร้าง Dashboard ที่มี 4 GPIO channels (LED + Switch) ใน Container แบบ grid พร้อมปุ่ม All ON / All OFF สำหรับ batch control

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FWbDCcR4wGljwlBZyrLA4%2Fweek3_ex5_gpio_dashboard.gif?alt=media&amp;token=92b7f930-0a90-49c8-9bdc-8efd943e728b" alt=""><figcaption></figcaption></figure>

***

### 2. หลักการทำงานและ Flowchart

#### 2.1 Dashboard Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                      GPIO DASHBOARD                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌──────────────────────────────────────────────────────────┐  │
│   │                    Title: GPIO Dashboard                    │
│   └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│   ┌──────────────────────────────────────────────────────────┐  │
│   │                    CONTAINER                             │  │
│   │  ┌────────────────┐    ┌────────────────┐                │  │
│   │  │ LED1    ●      │    │ LED2    ●      │                │  │
│   │  │ [Name] [Switch]│    │ [Name] [Switch]│                │  │
│   │  └────────────────┘    └────────────────┘                │  │
│   │  ┌────────────────┐    ┌────────────────┐                │  │
│   │  │ LED3    ●      │    │ LED4    ●      │                │  │
│   │  │ [Name] [Switch]│    │ [Name] [Switch]│                │  │
│   │  └────────────────┘    └────────────────┘                │  │
│   └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│   ┌────────────────┐              ┌────────────────┐            │
│   │   [ All ON ]   │              │   [ All OFF ]  │            │
│   │     (Green)    │              │      (Red)     │            │
│   └────────────────┘              └────────────────┘            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

#### 2.2 Data Structure Design

```
┌────────────────────────────────────────────────────────────────┐
│                    DATA STRUCTURE                              │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│   typedef struct {                                             │
│       lv_obj_t *led;       // LED widget reference             │
│       lv_obj_t *sw;        // Switch widget reference          │
│       lv_obj_t *label;     // Name label reference             │
│       const char *name;    // GPIO name ("LED1", etc.)         │
│       bool state;          // Current ON/OFF state             │
│   } gpio_item_t;                                               │
│                                                                │
│   Array Layout:                                                │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │  gpio_items[0]  │  gpio_items[1]  │  gpio_items[2]  │...│  │
│   │  LED1, Red      │  LED2, Green    │  LED3, Blue     │...│  │
│   └─────────────────────────────────────────────────────────┘  │
│                                                                │
│   Benefits:                                                    │
│   ├── Easy iteration with for loop                             │
│   ├── Pass item pointer as user_data to callback               │
│   └── Scalable - just change NUM_GPIOS                         │
│                                                                │
└────────────────────────────────────────────────────────────────┘
```

#### 2.3 Grid Positioning Calculation

```
┌─────────────────────────────────────────────────────────────────┐
│                 GRID POSITIONING                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Container Size: 420 x 200 pixels                              │
│   Grid: 2 columns × 2 rows                                      │
│                                                                 │
│   Calculation:                                                  │
│   x_pos = (i % 2) * x_spacing + x_offset                        │
│   y_pos = (i / 2) * y_spacing + y_offset                        │
│                                                                 │
│   Example with x_spacing=200, y_spacing=80, offset=20:          │
│                                                                 │
│     i=0: x=(0%2)*200+20= 20    i=1: x=(1%2)*200+20=220          │
│          y=(0/2)*80+20 = 20         y=(1/2)*80+20 = 20          │
│                                                                 │
│     i=2: x=(2%2)*200+20= 20    i=3: x=(3%2)*200+20=220          │
│          y=(2/2)*80+20 =100         y=(3/2)*80+20 =100          │
│                                                                 │
│   Visual:                                                       │
│     ┌─────────────────────────────────────────┐                 │
│     │  (20,20)               (220,20)         │                 │
│     │    ● LED1                ● LED2         │                 │
│     │                                         │                 │
│     │  (20,100)              (220,100)        │                 │
│     │    ● LED3                ● LED4         │                 │
│     └─────────────────────────────────────────┘                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

#### 2.4 Program Flowchart

```
┌─────────────────────────────────────────────────────────────────┐
│                     PROGRAM FLOW                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                      ┌─────────┐                                │
│                      │  Start  │                                │
│                      └────┬────┘                                │
│                           │                                     │
│                           ▼                                     │
│                ┌─────────────────────┐                          │
│                │ Define gpio_names[] │                          │
│                │ Define gpio_colors[]│                          │
│                └──────────┬──────────┘                          │
│                           │                                     │
│                           ▼                                     │
│                ┌─────────────────────┐                          │
│                │ Create Container    │                          │
│                │ (420x200, no scroll)│                          │
│                └──────────┬──────────┘                          │
│                           │                                     │
│                           ▼                                     │
│                ┌─────────────────────┐                          │
│                │ for i = 0 to 3:     │                          │
│                │   Create LED        │                          │
│                │   Create Label      │                          │
│                │   Create Switch     │◄──────┐                  │
│                │   Set user_data     │       │                  │
│                └──────────┬──────────┘       │                  │
│                           │          Loop    │                  │
│                           └──────────────────┘                  │
│                           │                                     │
│                           ▼                                     │
│           ┌───────────────────────────────┐                     │
│           │                               │                     │
│           ▼                               ▼                     │
│   ┌──────────────┐               ┌──────────────┐               │
│   │ Create       │               │ Create       │               │
│   │ "All ON" Btn │               │ "All OFF" Btn│               │
│   └──────┬───────┘               └───────┬──────┘               │
│          │                               │                      │
│          └───────────────┬───────────────┘                      │
│                          │                                      │
│                          ▼                                      │
│               ┌─────────────────────┐                           │
│               │ LVGL Main Loop      │◄─────────────────┐        │
│               └─────────┬───────────┘                  │        │
│                         │                              │        │
│       ┌─────────────────┼─────────────────┐            │        │
│       │                 │                 │            │        │
│  Switch[i]         All ON Btn       All OFF Btn        │        │
│  Changed           Clicked          Clicked            │        │
│       │                 │                 │            │        │
│       ▼                 ▼                 ▼            │        │
│  ┌──────────┐    ┌────────────┐   ┌─────────────┐      │        │
│  │ Get item │    │ Loop all:  │   │ Loop all:   │      │        │
│  │ from     │    │ LED ON     │   │ LED OFF     │      │        │
│  │ user_data│    │ SW checked │   │ SW unchecked│      │        │
│  │ Update   │    │ Invalidate │   │ Invalidate  │      │        │
│  └─────┬────┘    └─────┬──────┘   └─────┬───────┘      │        │
│        │               │                │              │        │
│        └───────────────┴────────────────┘              │        │
│                        │                               │        │
│                        └───────────────────────────────┘        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

***

### 3. ฟังก์ชันสำคัญ (API Reference)

#### 3.1 Container Functions

| Function                       | Description                        | Parameters         |
| ------------------------------ | ---------------------------------- | ------------------ |
| `lv_obj_create(parent)`        | สร้าง Container (Base Object)      | parent             |
| `lv_obj_set_size(obj, w, h)`   | กำหนดขนาด                          | obj, width, height |
| `lv_obj_set_pos(obj, x, y)`    | กำหนดตำแหน่ง absolute ภายใน parent | obj, x, y          |
| `lv_obj_clear_flag(obj, flag)` | ลบ flag (เช่น disable scroll)      | obj, flag          |
| `lv_obj_invalidate(obj)`       | Force redraw                       | obj                |

#### 3.2 Flex Layout Functions

| Function                                         | Description         | Parameters                |
| ------------------------------------------------ | ------------------- | ------------------------- |
| `lv_obj_set_flex_flow(obj, flow)`                | ตั้ง flex direction | obj, `LV_FLEX_FLOW_*`     |
| `lv_obj_set_flex_align(obj, main, cross, track)` | ตั้ง alignment      | obj, 3x `LV_FLEX_ALIGN_*` |
| `lv_obj_set_flex_grow(obj, grow)`                | ยืดขยายอัตโนมัติ    | obj, grow factor (0-1)    |

#### 3.3 Alignment Functions

| Function                                 | Description                 | Parameters                           |
| ---------------------------------------- | --------------------------- | ------------------------------------ |
| `lv_obj_align(obj, align, x, y)`         | จัดตำแหน่งเทียบ parent      | obj, `LV_ALIGN_*`, x\_ofs, y\_ofs    |
| `lv_obj_align_to(obj, ref, align, x, y)` | จัดตำแหน่งเทียบ object อื่น | obj, ref\_obj, align, x\_ofs, y\_ofs |

#### 3.4 Important Flags & Style

```c
/* ปิด scroll บน container */
lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);

/* ปิด scroll บนหน้าจอหลัก */
lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE);

/* Color theme constants */
#define AIC_COLOR_BG_DARK    lv_color_hex(0x1a1a2e)    /* พื้นหลังหลัก */
#define AIC_COLOR_BG_CARD    lv_color_hex(0x0f0f23)    /* พื้นหลัง card */
/* หรือใช้ lv_color_hex() โดยตรงก็ได้ */
```

#### 3.5 State Management Functions

| Function                         | Description                |
| -------------------------------- | -------------------------- |
| `lv_obj_add_state(obj, state)`   | เพิ่ม state (เช่น ON)      |
| `lv_obj_clear_state(obj, state)` | ลบ state (เช่น OFF)        |
| `lv_obj_has_state(obj, state)`   | ตรวจสอบ state (true/false) |

***

### 4. โค้ดตัวอย่าง (Code Examples)

#### 4.1 Complete Code - part1\_ex5\_gpio\_dashboard()

จากไฟล์ `part1_examples.c`:

```c
/*******************************************************************************
 * Example 5: GPIO Dashboard
 *
 * Combined widgets: LED, Switch, Button, Container
 * Platform: PSoC Edge E84 + LVGL v9.2.0
 ******************************************************************************/
#define EX5_NUM_GPIOS 4

/* Data structure สำหรับแต่ละ GPIO channel */
typedef struct {
    lv_obj_t *led;
    lv_obj_t *sw;
    lv_obj_t *label;
    const char *name;
    bool state;
} gpio_item_t;

/* Global array เก็บทุก channel */
static gpio_item_t ex5_gpios[EX5_NUM_GPIOS];

/* [Callback] เมื่อ switch ถูก toggle */
static void ex5_switch_cb(lv_event_t * e)
{
    gpio_item_t * gpio = (gpio_item_t *)lv_event_get_user_data(e);
    lv_obj_t * sw = lv_event_get_target(e);

    gpio->state = lv_obj_has_state(sw, LV_STATE_CHECKED);

    if(gpio->state) {
        lv_led_on(gpio->led);
        printf("%s: ON\r\n", gpio->name);
    } else {
        lv_led_off(gpio->led);
        printf("%s: OFF\r\n", gpio->name);
    }
}

/* [Callback] All ON button */
static void ex5_all_on_cb(lv_event_t * e)
{
    (void)e;
    for(int i = 0; i < EX5_NUM_GPIOS; i++) {
        ex5_gpios[i].state = true;
        lv_led_on(ex5_gpios[i].led);
        lv_obj_add_state(ex5_gpios[i].sw, LV_STATE_CHECKED);
        lv_obj_invalidate(ex5_gpios[i].sw);
        lv_obj_invalidate(ex5_gpios[i].led);
    }
    printf("All LEDs: ON\r\n");
}

/* [Callback] All OFF button */
static void ex5_all_off_cb(lv_event_t * e)
{
    (void)e;
    for(int i = 0; i < EX5_NUM_GPIOS; i++) {
        ex5_gpios[i].state = false;
        lv_led_off(ex5_gpios[i].led);
        lv_obj_clear_state(ex5_gpios[i].sw, LV_STATE_CHECKED);
        lv_obj_invalidate(ex5_gpios[i].sw);
        lv_obj_invalidate(ex5_gpios[i].led);
    }
    printf("All LEDs: OFF\r\n");
}

void part1_ex5_gpio_dashboard(void)
{
    const char *gpio_names[] = {"LED1", "LED2", "LED3", "LED4"};
    lv_color_t gpio_colors[] = {
        lv_palette_main(LV_PALETTE_RED),
        lv_palette_main(LV_PALETTE_GREEN),
        lv_palette_main(LV_PALETTE_BLUE),
        lv_palette_main(LV_PALETTE_YELLOW)
    };

    /* [1] Background + ปิด scroll หน้าจอหลัก */
    lv_obj_set_style_bg_color(lv_screen_active(),
                              lv_color_hex(0x1a1a2e), LV_PART_MAIN);
    lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE);

    /* [2] Title */
    lv_obj_t * title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Part 1 - Example 5: GPIO Dashboard");
    lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);

    /* [3] Container - กรอบจัดกลุ่ม widget */
    lv_obj_t * cont = lv_obj_create(lv_screen_active());
    lv_obj_set_size(cont, 420, 200);
    lv_obj_align(cont, LV_ALIGN_CENTER, 0, 10);
    lv_obj_set_style_bg_color(cont, lv_color_hex(0x0f0f23), 0);
    lv_obj_set_style_border_width(cont, 2, 0);
    lv_obj_set_style_border_color(cont, lv_color_hex(0x444444), 0);
    lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);

    /* [4] สร้าง GPIO items ใน 2x2 grid */
    for(int i = 0; i < EX5_NUM_GPIOS; i++) {
        ex5_gpios[i].name = gpio_names[i];
        ex5_gpios[i].state = false;

        /* คำนวณ grid position */
        int x_pos = (i % 2) * 200 + 20;    /* Column: 20 หรือ 220 */
        int y_pos = (i / 2) * 80 + 20;     /* Row: 20 หรือ 100 */

        /* LED */
        ex5_gpios[i].led = lv_led_create(cont);
        lv_obj_set_size(ex5_gpios[i].led, 40, 40);
        lv_obj_set_pos(ex5_gpios[i].led, x_pos, y_pos);
        lv_led_set_color(ex5_gpios[i].led, gpio_colors[i]);
        lv_led_off(ex5_gpios[i].led);

        /* Label - จัดอยู่ขวาของ LED */
        ex5_gpios[i].label = lv_label_create(cont);
        lv_label_set_text(ex5_gpios[i].label, gpio_names[i]);
        lv_obj_set_style_text_color(ex5_gpios[i].label,
            lv_color_hex(0xFFFFFF), 0);
        lv_obj_align_to(ex5_gpios[i].label, ex5_gpios[i].led,
                        LV_ALIGN_OUT_RIGHT_MID, 10, 0);

        /* Switch - จัดอยู่ขวาของ Label */
        ex5_gpios[i].sw = lv_switch_create(cont);
        lv_obj_set_size(ex5_gpios[i].sw, 60, 30);
        lv_obj_align_to(ex5_gpios[i].sw, ex5_gpios[i].label,
                        LV_ALIGN_OUT_RIGHT_MID, 15, 0);
        lv_obj_add_event_cb(ex5_gpios[i].sw, ex5_switch_cb,
                           LV_EVENT_VALUE_CHANGED, &ex5_gpios[i]);
    }

    /* [5] All ON button */
    lv_obj_t * btn_all_on = lv_button_create(lv_screen_active());
    lv_obj_add_event_cb(btn_all_on, ex5_all_on_cb, LV_EVENT_CLICKED, NULL);
    lv_obj_align(btn_all_on, LV_ALIGN_BOTTOM_LEFT, 40, -50);
    lv_obj_set_style_bg_color(btn_all_on,
        lv_palette_main(LV_PALETTE_GREEN), 0);
    lv_obj_set_style_pad_hor(btn_all_on, 30, 0);
    lv_obj_set_style_pad_ver(btn_all_on, 15, 0);

    lv_obj_t * label_on = lv_label_create(btn_all_on);
    lv_label_set_text(label_on, "All ON");
    lv_obj_center(label_on);

    /* [6] All OFF button */
    lv_obj_t * btn_all_off = lv_button_create(lv_screen_active());
    lv_obj_add_event_cb(btn_all_off, ex5_all_off_cb, LV_EVENT_CLICKED, NULL);
    lv_obj_align(btn_all_off, LV_ALIGN_BOTTOM_RIGHT, -40, -50);
    lv_obj_set_style_bg_color(btn_all_off,
        lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_set_style_pad_hor(btn_all_off, 30, 0);
    lv_obj_set_style_pad_ver(btn_all_off, 15, 0);

    lv_obj_t * label_off = lv_label_create(btn_all_off);
    lv_label_set_text(label_off, "All OFF");
    lv_obj_center(label_off);
}
```

#### 4.2 Code Breakdown

**Part A: User Data Pattern**

```c
/* เมื่อสร้าง - ส่ง pointer ไปกับ callback */
lv_obj_add_event_cb(ex5_gpios[i].sw, ex5_switch_cb,
                   LV_EVENT_VALUE_CHANGED, &ex5_gpios[i]);
                                          ^^^^^^^^^^^^^^
                                          user_data: pointer ไปยัง struct

/* ใน callback - ดึง pointer กลับ */
static void ex5_switch_cb(lv_event_t * e)
{
    gpio_item_t * gpio = (gpio_item_t *)lv_event_get_user_data(e);
    /* ตอนนี้เรารู้แล้วว่า switch ตัวนี้คือ GPIO channel ไหน */
    printf("Toggled: %s\n", gpio->name);
}
```

**Part B: Grid Position Calculation**

```c
for(int i = 0; i < EX5_NUM_GPIOS; i++) {
    /* 2 columns, 2 rows */
    int x_pos = (i % 2) * 200 + 20;  /* i=0->20, i=1->220, i=2->20, i=3->220 */
    int y_pos = (i / 2) * 80 + 20;   /* i=0->20, i=1->20,  i=2->100, i=3->100 */

    lv_obj_set_pos(ex5_gpios[i].led, x_pos, y_pos);
}
```

**Part C: Relative Alignment (align\_to)**

```c
/* จัด Label ชิดขวาของ LED */
lv_obj_align_to(label, led, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
/*              ^      ^     ^                      ^   ^
 *              |      |     |                      |   y offset
 *              |      |     |                      x offset (ห่าง 10px)
 *              |      |     alignment type
 *              |      reference object
 *              object ที่จะจัด
 */
```

**Part D: Batch Update with Invalidate**

```c
for(int i = 0; i < EX5_NUM_GPIOS; i++) {
    lv_led_on(ex5_gpios[i].led);
    lv_obj_add_state(ex5_gpios[i].sw, LV_STATE_CHECKED);

    /* Force redraw ทันที (ไม่ต้องรอ LVGL timer) */
    lv_obj_invalidate(ex5_gpios[i].sw);
    lv_obj_invalidate(ex5_gpios[i].led);
}
```

***

### 5. องค์ความรู้และเทคนิค

#### 5.1 Container Styling Pattern

```c
/* สร้าง container พร้อมธีม dark */
lv_obj_t * cont = lv_obj_create(parent);
lv_obj_set_size(cont, 420, 200);
lv_obj_align(cont, LV_ALIGN_CENTER, 0, 0);

/* Style: พื้นหลังเข้ม + border บาง */
lv_obj_set_style_bg_color(cont, lv_color_hex(0x0f0f23), 0);
lv_obj_set_style_border_width(cont, 2, 0);
lv_obj_set_style_border_color(cont, lv_color_hex(0x444444), 0);
lv_obj_set_style_radius(cont, 10, 0);    /* มุมโค้ง */
lv_obj_set_style_pad_all(cont, 10, 0);   /* ขอบใน */

/* ปิด scroll - สำคัญมากสำหรับ dashboard */
lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE);
```

#### 5.2 Flex Layout สำหรับ Dashboard

```c
/* Flex Row: จัดแนวนอน */
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(cont,
    LV_FLEX_ALIGN_SPACE_EVENLY,   /* main axis: กระจายเท่ากัน */
    LV_FLEX_ALIGN_CENTER,         /* cross axis: กลาง */
    LV_FLEX_ALIGN_CENTER);        /* track: กลาง */

/* Flex Column Wrap: จัดแนวตั้ง ขึ้นแถวใหม่อัตโนมัติ */
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_style_flex_main_place(cont, LV_FLEX_ALIGN_SPACE_EVENLY, 0);
```

#### 5.3 Programmatic State Change + Event

```c
/* เปลี่ยน state จาก code */
lv_obj_add_state(sw, LV_STATE_CHECKED);

/* Force visual update */
lv_obj_invalidate(sw);

/* สำคัญ: add_state ไม่ trigger callback
 * ถ้าต้องการ callback ทำงานด้วย ใช้:
 */
lv_event_send(sw, LV_EVENT_VALUE_CHANGED, NULL);
```

#### 5.4 Color Theming Best Practices

```c
/* กำหนด color constants ไว้ที่เดียว */
/* สีพื้นหลังหลัก */
static const lv_color_t bg_dark  = {.red = 0x1a, .green = 0x1a, .blue = 0x2e};
/* สีพื้นหลัง card */
static const lv_color_t bg_card  = {.red = 0x0f, .green = 0x0f, .blue = 0x23};

/* หรือใช้ lv_color_hex() */
lv_obj_set_style_bg_color(obj, lv_color_hex(0x1a1a2e), 0);

/* LVGL Palette สำหรับสีมาตรฐาน */
lv_palette_main(LV_PALETTE_RED);      /* สีหลัก */
lv_palette_lighten(LV_PALETTE_RED, 2); /* สีอ่อนลง 2 ระดับ */
lv_palette_darken(LV_PALETTE_RED, 2);  /* สีเข้มขึ้น 2 ระดับ */
```

#### 5.5 Scalable Design Pattern

```c
/* เปลี่ยน NUM_GPIOS เพิ่ม/ลด channel ได้ทันที */
#define NUM_GPIOS 4    /* เปลี่ยนเป็น 6 หรือ 8 ได้เลย */

/* คำนวณ grid อัตโนมัติ */
#define COLS 2
int x = (i % COLS) * x_spacing + x_offset;
int y = (i / COLS) * y_spacing + y_offset;

/* ถ้า NUM_GPIOS=6 จะได้ 3 rows x 2 cols อัตโนมัติ */
```

***

### 6. แบบฝึกหัด (Exercises)

#### Exercise 1: 2-Column Sensor Dashboard (Intermediate)

สร้าง Dashboard แบบ 2 คอลัมน์:

* **ซ้าย**: Sensor Readings (4 ค่า) พร้อม LED สีตามระดับ
* **ขวา**: Controls (4 switches) สำหรับเปิด/ปิดอุปกรณ์

**Requirements:**

* คอลัมน์ซ้าย: Temperature, Humidity, Pressure, Light (ค่าจำลอง)
* คอลัมน์ขวา: Motor, Fan, Pump, Alarm (switch + LED)
* มีปุ่ม "Emergency Stop" (สีแดง) ปิดทุก output ทันที
* แสดง Active outputs: "2/4 Running"

**Expected Output:**

```
+---------------------------------------------------+
|            Industrial I/O Dashboard               |
|              Outputs: 2/4 Running                 |
+--------------------------+------------------------+
|     SENSORS (Input)      |    CONTROLS (Output)   |
+--------------------------+------------------------+
| [*] Temp:    28.5 C     | [*] Motor   [ON ]       |
| [*] Humid:   65.2 %     | [O] Fan     [OFF]       |
| [*] Press:   1013 hPa   | [*] Pump    [ON ]       |
| [*] Light:   850 lux    | [O] Alarm   [OFF]       |
+--------------------------+------------------------+
|           [ EMERGENCY STOP ]                      |
+---------------------------------------------------+
```

**Hints:**

* ใช้ 2 container วางเคียงกัน (ซ้าย sensor, ขวา control)
* Sensor LED ใช้ `lv_led_set_brightness()` แสดงระดับค่า
* Emergency Stop ใช้ loop เหมือน All OFF

***

#### Exercise 2: Tabbed Dashboard with TabView (Advanced)

สร้าง Dashboard แบบ TabView มี 3 tabs:

* **Tab 1 "Inputs"**: แสดงสถานะ Digital Input 4 ช่อง (LED + label)
* **Tab 2 "Outputs"**: ควบคุม Digital Output 4 ช่อง (Switch + LED)
* **Tab 3 "Settings"**: ปรับ Refresh Rate (Slider) + Auto-mode switch

**Requirements:**

* Tab 1: Input LEDs อัพเดทค่าสุ่มทุก 1 วินาที (ใช้ `lv_timer_create()`)
* Tab 2: Output switches ควบคุม LED + แสดงสถานะ
* Tab 3: Slider ปรับ refresh rate (100ms - 2000ms) + Auto switch

**Expected Output:**

```
+---------------------------------------------------+
| [ Inputs ] [ Outputs ] [ Settings ]     <-- Tabs  |
+---------------------------------------------------+
|                                                   |
|  Tab "Outputs" content:                           |
|                                                   |
|    [*] OUT0  [O ==== ]  OFF                       |
|    [O] OUT1  [ ==== O]  ON                        |
|    [*] OUT2  [O ==== ]  OFF                       |
|    [O] OUT3  [ ==== O]  ON                        |
|                                                   |
|    [ All ON ]  [ All OFF ]                        |
|                                                   |
+---------------------------------------------------+
```

**Hints:**

* `lv_tabview_create(parent)` สร้าง TabView
* `lv_tabview_add_tab(tv, "name")` เพิ่ม tab (ได้ container กลับมา)
* `lv_timer_create(timer_cb, period_ms, user_data)` สร้าง periodic timer
* Tab 3 slider callback เปลี่ยน timer period

> **ต่อไป Part II** **(Advance HMI Display)** จะเป็นการนำสิ่งที่เรียนมาควบคุม Hardware จริง!

***

### 8. References

* [LVGL Container (Base Object)](https://docs.lvgl.io/9.2/widgets/obj.html)
* [LVGL LED Widget](https://docs.lvgl.io/9.2/widgets/led.html)
* [LVGL Switch Widget](https://docs.lvgl.io/9.2/widgets/switch.html)
* [LVGL Button Widget](https://docs.lvgl.io/9.2/widgets/button.html)

***

**Previous Lab:** Lab 4: Switch Toggle **Next:** Week 3 Part II - Hardware Integration
