# 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="/files/18164t2JTdPAMlePC9hb" 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/hmi-development/gpio-to-hmi-display/ux-ui-workshops/gpio-dashboard.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
