> 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/sensor-to-hmi-display/ux-ui-workshops/sensor-dashboard.md).

# Sensor Dashboard

## Lab 5: Sensor Dashboard (TabView)

***

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

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

* **Multi-Page UI**: ในระบบ IoT จริง เรามีข้อมูลจากหลาย Sensor ที่ต้องแสดงผลพร้อมกัน TabView ช่วยจัดกลุ่มข้อมูลอย่างเป็นระบบ
* **Organized Layout**: Dashboard ที่ดีจัดกลุ่ม content ตามประเภท ไม่แสดงทุกอย่างบนหน้าเดียว
* **Real-World Pattern**: รูปแบบ Tab-based Dashboard ใช้แพร่หลายในระบบ SCADA, HMI, และ IoT Monitoring

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

1. **TabView Widget**: สร้าง multi-tab interface ด้วย `lv_tabview_create()`
2. **Tab Content Management**: แต่ละ tab มี widgets ของตัวเอง แยกจัดการอิสระ
3. **Tab Bar Styling**: ปรับแต่ง tab bar, ปุ่ม tab, สีพื้นหลัง
4. **Tab Switching**: ควบคุมการเปลี่ยน tab ทั้งด้วยการกด และ programmatic
5. **Scroll Control**: ปิด scrolling ที่ไม่ต้องการบน content area

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

1. สร้าง TabView กับ tab bar ด้านบน (80px)
2. เพิ่ม Tab 1: IMU data - แสดง accelerometer X/Y/Z ด้วย Line Chart
3. เพิ่ม Tab 2: ADC data - แสดงค่า ADC ด้วย Bar + Label แสดงแรงดัน
4. ใช้ timer callback อัพเดทข้อมูลทุก tab ทุก 100ms
5. ปรับแต่ง tab button styling ให้ดูเป็นมืออาชีพ

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FsEHcr6F4W6uQD80PRwy4%2Fweek4_ex5_sensor_dashboard.gif?alt=media&amp;token=93e69090-7814-4bf0-ba13-775997dcea50" alt=""><figcaption></figcaption></figure>

***

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

#### 2.1 TabView Architecture

TabView เป็น widget ที่รวม Tab Bar (ปุ่มเลือก) กับ Content Area (พื้นที่แสดงผล) เข้าด้วยกัน เมื่อกดปุ่ม tab จะสลับแสดง content ของ tab นั้น

```
TabView Object Hierarchy:

  lv_tabview (parent)
  ├── Tab Bar (lv_obj - button container)
  │   ├── Button "IMU"     ← lv_tabview_get_tab_bar()
  │   └── Button "ADC"
  └── Content (lv_obj - page container)
      ├── Tab Page 0 (IMU)  ← lv_tabview_add_tab()
      │   ├── Title Label
      │   ├── Chart Widget
      │   └── Value Labels
      └── Tab Page 1 (ADC)
          ├── Title Label
          ├── Bar Widget
          └── Value Label
```

#### 2.2 Screen Layout

```
┌─────────────────────────────────────────────────────────────┐
│                   SENSOR DASHBOARD                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌───────────────────────────────────────────────────────┐ │
│   │     [  IMU  ]      [  ADC  ]       <-- Tab Bar (80px) │ │
│   └───────────────────────────────────────────────────────┘ │
│   ┌───────────────────────────────────────────────────────┐ │
│   │                                                       │ │
│   │              Content Area (480 x 240)                 │ │
│   │                                                       │ │
│   │   IMU Tab:                                            │ │
│   │     Chart (3 series: X=Red, Y=Green, Z=Blue)          │ │
│   │     Value Labels: X: +0.50  Y: -0.30  Z: +9.81        │ │
│   │                                                       │ │
│   │   ADC Tab:                                            │ │
│   │     [==============BAR================]               │ │
│   │     ADC: 2048 (1.65V)                                 │ │
│   │                                                       │ │
│   └───────────────────────────────────────────────────────┘ │
│                                                             │
│   Screen Size: 480 x 320                                    │
│   Tab Bar: 80px height                                      │
│   Content: 480 x 240                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.3 Data Flow

```
┌─────────────────────────────────────────────────────────────┐
│                     DATA FLOW                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   lv_timer_create(timer_cb, 100ms)                          │
│       │                                                     │
│       ▼ ทุก 100ms                                            │
│   dashboard_timer_cb()                                      │
│       │                                                     │
│       ├── simulate_adc_read()                               │
│       │     │                                               │
│       │     ├── pct = (adc * 100) / 4095                    │
│       │     ├── lv_bar_set_value(bar, pct)                  │
│       │     └── lv_label_set_text_fmt("ADC: %d (%.2fV)")    │
│       │                                                     │
│       └── simulate_imu_accel(&ax, &ay, &az)                 │
│            │                                                │
│            ├── lv_chart_set_next_value(chart, ser_x, ax*100)│
│            ├── lv_chart_set_next_value(chart, ser_y, ay*100)│
│            ├── lv_chart_set_next_value(chart, ser_z, az*100)│
│            └── lv_label_set_text_fmt("X:%+.1f Y:%+.1f")     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.4 Tab Bar Position Options

```
  LV_DIR_TOP (default)        LV_DIR_BOTTOM
  ┌──────────────────┐        ┌──────────────────┐
  │ [Tab1] [Tab2]    │        │                  │
  ├──────────────────┤        │    Content       │
  │                  │        │                  │
  │    Content       │        ├──────────────────┤
  │                  │        │ [Tab1] [Tab2]    │
  └──────────────────┘        └──────────────────┘

  LV_DIR_LEFT                 LV_DIR_RIGHT
  ┌────┬─────────────┐        ┌─────────────┬────┐
  │Tab1│             │        │             │Tab1│
  │    │   Content   │        │   Content   │    │
  │Tab2│             │        │             │Tab2│
  └────┴─────────────┘        └─────────────┴────┘
```

***

### 3. ฟังก์ชันสำคัญ

#### 3.1 TabView Functions

<table><thead><tr><th width="366.922607421875">Function</th><th width="212.2479248046875">Description</th><th>ตัวอย่าง</th></tr></thead><tbody><tr><td><code>lv_tabview_create(parent)</code></td><td>สร้าง TabView</td><td><code>lv_tabview_create(lv_screen_active())</code></td></tr><tr><td><code>lv_tabview_set_tab_bar_position(tv, dir)</code></td><td>ตำแหน่ง tab bar</td><td><code>...(tv, LV_DIR_TOP)</code></td></tr><tr><td><code>lv_tabview_set_tab_bar_size(tv, size)</code></td><td>ความสูง/กว้าง tab bar</td><td><code>...(tv, 80)</code></td></tr><tr><td><code>lv_tabview_add_tab(tv, "name")</code></td><td>เพิ่ม tab ใหม่ (return tab page)</td><td><code>lv_tabview_add_tab(tv, "IMU")</code></td></tr><tr><td><code>lv_tabview_get_tab_bar(tv)</code></td><td>รับ tab bar object สำหรับ styling</td><td><code>lv_tabview_get_tab_bar(tv)</code></td></tr><tr><td><code>lv_tabview_get_content(tv)</code></td><td>รับ content container</td><td><code>lv_tabview_get_content(tv)</code></td></tr><tr><td><code>lv_tabview_get_tab_active(tv)</code></td><td>ดูว่า tab ไหน active (0-based)</td><td><code>uint32_t idx = ...</code></td></tr><tr><td><code>lv_tabview_set_active(tv, idx, anim)</code></td><td>เปลี่ยน tab ด้วยโค้ด</td><td><code>...(tv, 1, LV_ANIM_ON)</code></td></tr></tbody></table>

#### 3.2 Tab Bar Position Constants

| Direction       | Description              | Content Size (480x320 screen) |
| --------------- | ------------------------ | ----------------------------- |
| `LV_DIR_TOP`    | Tab bar ด้านบน (default) | 480 x (320 - tab\_size)       |
| `LV_DIR_BOTTOM` | Tab bar ด้านล่าง         | 480 x (320 - tab\_size)       |
| `LV_DIR_LEFT`   | Tab bar ด้านซ้าย         | (480 - tab\_size) x 320       |
| `LV_DIR_RIGHT`  | Tab bar ด้านขวา          | (480 - tab\_size) x 320       |

#### 3.3 Critical Pattern - ปิด Scrolling

```c
/* CRITICAL: ต้องปิด scrolling บน content container
 * ไม่งั้นเวลา swipe จะเลื่อนหน้าแทนที่จะเปลี่ยน tab */
lv_obj_remove_flag(lv_tabview_get_content(tabview), LV_OBJ_FLAG_SCROLLABLE);
```

#### 3.4 Simulation Functions (PC Simulator)

```c
/* จำลองค่า ADC (0-4095) - ขยับช้า ๆ ขึ้นลง */
static uint16_t simulate_adc_read(void)
{
    static float phase = 0.0f;
    phase += 0.02f;
    return (uint16_t)(2048 + 1800 * sinf(phase));
}

/* จำลองค่า IMU accelerometer (m/s^2) */
static void simulate_imu_accel(float *ax, float *ay, float *az)
{
    static float t = 0.0f;
    t += 0.05f;
    *ax = 0.5f * sinf(t * 1.0f);
    *ay = 0.3f * cosf(t * 0.7f);
    *az = 9.81f + 0.2f * sinf(t * 2.0f);
}
```

***

### 4. โค้ดตัวอย่าง

#### 4.1 Global Variables

```c
#include "lvgl.h"
#include <math.h>

#define ADC_MAX         4095
#define CHART_POINTS    50

/* ADC Tab widgets */
static lv_obj_t * adc_bar;
static lv_obj_t * adc_label;

/* IMU Tab widgets */
static lv_obj_t * imu_chart;
static lv_chart_series_t * imu_ser_x;
static lv_chart_series_t * imu_ser_y;
static lv_chart_series_t * imu_ser_z;
static lv_obj_t * imu_labels[3];

/* Timer */
static lv_timer_t * dash_timer;
```

#### 4.2 Simulation Helpers

```c
static uint16_t simulate_adc_read(void)
{
    static float phase = 0.0f;
    phase += 0.02f;
    return (uint16_t)(2048 + 1800 * sinf(phase));
}

static void simulate_imu_accel(float *ax, float *ay, float *az)
{
    static float t = 0.0f;
    t += 0.05f;
    *ax = 0.5f * sinf(t * 1.0f);
    *ay = 0.3f * cosf(t * 0.7f);
    *az = 9.81f + 0.2f * sinf(t * 2.0f);
}
```

#### 4.3 Timer Callback - อัพเดทข้อมูลทุก Tab

```c
static void dashboard_timer_cb(lv_timer_t * timer)
{
    (void)timer;

    /* ===== Update ADC Tab ===== */
    uint16_t adc = simulate_adc_read();
    int32_t pct = (adc * 100) / ADC_MAX;
    lv_bar_set_value(adc_bar, pct, LV_ANIM_ON);

    float volt = ((float)adc / ADC_MAX) * 3.3f;
    lv_label_set_text_fmt(adc_label, "ADC: %d (%.2fV)", adc, (double)volt);

    /* ===== Update IMU Tab ===== */
    float ax, ay, az;
    simulate_imu_accel(&ax, &ay, &az);

    /* Scale float to int for chart (multiply by 100) */
    lv_chart_set_next_value(imu_chart, imu_ser_x, (int32_t)(ax * 100));
    lv_chart_set_next_value(imu_chart, imu_ser_y, (int32_t)(ay * 100));
    lv_chart_set_next_value(imu_chart, imu_ser_z, (int32_t)(az * 100));

    /* Update value labels */
    lv_label_set_text_fmt(imu_labels[0], "X:%+.1f", (double)ax);
    lv_label_set_text_fmt(imu_labels[1], "Y:%+.1f", (double)ay);
    lv_label_set_text_fmt(imu_labels[2], "Z:%+.1f", (double)az);
}
```

#### 4.4 สร้าง IMU Tab Content

```c
static void create_imu_tab(lv_obj_t * parent)
{
    /* Title */
    lv_obj_t * title = lv_label_create(parent);
    lv_label_set_text(title, "Accelerometer (m/s2)");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* Chart - Line type, 50 data points */
    imu_chart = lv_chart_create(parent);
    lv_obj_set_size(imu_chart, 380, 180);
    lv_obj_align(imu_chart, LV_ALIGN_CENTER, -30, 10);
    lv_chart_set_type(imu_chart, LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(imu_chart, CHART_POINTS);
    lv_chart_set_range(imu_chart, LV_CHART_AXIS_PRIMARY_Y, -200, 1200);

    /* ซ่อน point indicator เพื่อให้เห็นแค่เส้น */
    lv_obj_set_style_size(imu_chart, 0, 0, LV_PART_INDICATOR);
    lv_obj_set_style_line_width(imu_chart, 2, LV_PART_ITEMS);

    /* สร้าง 3 series: X(Red), Y(Green), Z(Blue) */
    lv_color_t colors[] = {
        lv_palette_main(LV_PALETTE_RED),
        lv_palette_main(LV_PALETTE_GREEN),
        lv_palette_main(LV_PALETTE_BLUE)
    };

    imu_ser_x = lv_chart_add_series(imu_chart, colors[0],
                                     LV_CHART_AXIS_PRIMARY_Y);
    imu_ser_y = lv_chart_add_series(imu_chart, colors[1],
                                     LV_CHART_AXIS_PRIMARY_Y);
    imu_ser_z = lv_chart_add_series(imu_chart, colors[2],
                                     LV_CHART_AXIS_PRIMARY_Y);

    /* Value labels - แสดงค่าปัจจุบัน */
    const char * axis_names[] = {"X:", "Y:", "Z:"};
    for(int i = 0; i < 3; i++) {
        imu_labels[i] = lv_label_create(parent);
        lv_label_set_text_fmt(imu_labels[i], "%s 0.0", axis_names[i]);
        lv_obj_set_style_text_color(imu_labels[i], colors[i], 0);
        lv_obj_set_style_text_font(imu_labels[i], &lv_font_montserrat_24, 0);
        lv_obj_align(imu_labels[i], LV_ALIGN_RIGHT_MID, -10, -50 + i * 50);
    }
}
```

#### 4.5 สร้าง ADC Tab Content

```c
static void create_adc_tab(lv_obj_t * parent)
{
    /* Title */
    lv_obj_t * title = lv_label_create(parent);
    lv_label_set_text(title, "ADC Monitor");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20);

    /* Bar widget - แสดง % ของค่า ADC */
    adc_bar = lv_bar_create(parent);
    lv_obj_set_size(adc_bar, 400, 50);
    lv_obj_align(adc_bar, LV_ALIGN_CENTER, 0, 0);
    lv_bar_set_range(adc_bar, 0, 100);
    lv_bar_set_value(adc_bar, 0, LV_ANIM_OFF);

    /* สี indicator ของ bar */
    lv_obj_set_style_bg_color(adc_bar, lv_palette_main(LV_PALETTE_BLUE),
                              LV_PART_INDICATOR);

    /* Label - แสดงค่า ADC ดิบ + แรงดัน */
    adc_label = lv_label_create(parent);
    lv_label_set_text(adc_label, "ADC: 0 (0.00V)");
    lv_obj_set_style_text_font(adc_label, &lv_font_montserrat_24, 0);
    lv_obj_align(adc_label, LV_ALIGN_CENTER, 0, 60);
}
```

#### 4.6 Main Function

```c
void part2_ex5_sensor_dashboard(void)
{
    /* ===== สร้าง TabView ===== */
    lv_obj_t * tabview = lv_tabview_create(lv_screen_active());
    lv_tabview_set_tab_bar_position(tabview, LV_DIR_TOP);
    lv_tabview_set_tab_bar_size(tabview, 80);

    /* ===== Style Tab Bar ===== */
    lv_obj_t * tab_btns = lv_tabview_get_tab_bar(tabview);
    lv_obj_set_style_bg_color(tab_btns,
        lv_palette_darken(LV_PALETTE_GREY, 3), 0);
    lv_obj_set_style_text_color(tab_btns,
        lv_palette_lighten(LV_PALETTE_GREY, 5), 0);

    /* ===== สร้าง Tabs ===== */
    lv_obj_t * tab_imu = lv_tabview_add_tab(tabview, "IMU");
    lv_obj_t * tab_adc = lv_tabview_add_tab(tabview, "ADC");

    /* ===== Style Tab Content (พื้นหลังสีขาว) ===== */
    lv_obj_set_style_bg_color(tab_imu, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_bg_opa(tab_imu, LV_OPA_COVER, 0);
    lv_obj_set_style_bg_color(tab_adc, lv_color_hex(0xFFFFFF), 0);
    lv_obj_set_style_bg_opa(tab_adc, LV_OPA_COVER, 0);

    /* ===== Populate Tab Content ===== */
    create_imu_tab(tab_imu);
    create_adc_tab(tab_adc);

    /* ===== CRITICAL: ปิด scrolling ===== */
    lv_obj_remove_flag(lv_tabview_get_content(tabview),
                       LV_OBJ_FLAG_SCROLLABLE);

    /* ===== Timer - อัพเดทข้อมูลทุก 100ms ===== */
    dash_timer = lv_timer_create(dashboard_timer_cb, 100, NULL);
}
```

***

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

#### 5.1 Tab Bar Sizing - ขนาดของ Tab Bar

```c
/* Tab bar size ขึ้นอยู่กับ position */

/* TOP/BOTTOM: size = ความสูง */
lv_tabview_set_tab_bar_position(tabview, LV_DIR_TOP);
lv_tabview_set_tab_bar_size(tabview, 80);  /* 80px height */
/* Content: 480 x (320 - 80) = 480 x 240 */

/* LEFT/RIGHT: size = ความกว้าง */
lv_tabview_set_tab_bar_position(tabview, LV_DIR_LEFT);
lv_tabview_set_tab_bar_size(tabview, 80);  /* 80px width */
/* Content: (480 - 80) x 320 = 400 x 320 */
```

#### 5.2 Tab Content Parent - ข้อควรระวัง

```c
/* Widgets ใน tab ต้องใช้ tab page เป็น parent ไม่ใช่ lv_screen_active() */
void create_tab_content(lv_obj_t * tab_parent)
{
    /* tab_parent มาจาก lv_tabview_add_tab() */
    lv_obj_t * widget = lv_label_create(tab_parent);  /* ถูกต้อง */

    /* อย่าทำแบบนี้: */
    /* lv_obj_t * widget = lv_label_create(lv_screen_active()); */
}
```

#### 5.3 Tab Button Styling - ปรับแต่งปุ่ม Tab

```c
lv_obj_t * tab_btns = lv_tabview_get_tab_bar(tabview);

/* สีพื้นหลัง tab bar */
lv_obj_set_style_bg_color(tab_btns,
    lv_palette_darken(LV_PALETTE_GREY, 3), 0);

/* สีตัวอักษร tab buttons */
lv_obj_set_style_text_color(tab_btns,
    lv_palette_lighten(LV_PALETTE_GREY, 5), 0);

/* เส้นใต้ tab ที่ active */
lv_obj_set_style_border_side(tab_btns,
    LV_BORDER_SIDE_BOTTOM,
    LV_PART_ITEMS | LV_STATE_CHECKED);
lv_obj_set_style_border_color(tab_btns,
    lv_palette_main(LV_PALETTE_ORANGE),
    LV_PART_ITEMS | LV_STATE_CHECKED);
lv_obj_set_style_border_width(tab_btns, 3,
    LV_PART_ITEMS | LV_STATE_CHECKED);
```

#### 5.4 Programmatic Tab Switching

```c
/* เปลี่ยน tab ด้วยโค้ด (ใช้สำหรับ auto-rotate) */
lv_tabview_set_active(tabview, 1, LV_ANIM_ON);   /* ไปยัง tab index 1 */

/* อ่าน tab ที่ active อยู่ */
uint32_t active_idx = lv_tabview_get_tab_active(tabview);
```

#### 5.5 Performance Tip - อัพเดทเฉพาะ Tab ที่ Active

```c
/* ลด CPU load โดยอัพเดทเฉพาะ tab ที่มองเห็น */
static void optimized_timer_cb(lv_timer_t * timer)
{
    uint32_t active = lv_tabview_get_tab_active(tabview_ref);
    switch(active) {
        case 0: update_imu_tab(); break;
        case 1: update_adc_tab(); break;
    }
}
```

***

### 6. แบบฝึกหัด

#### Exercise 1: เพิ่ม Tab ที่ 3 - System Status

**โจทย์:** เพิ่ม tab "System" ที่แสดงข้อมูล System Status:

* **Uptime**: เวลาตั้งแต่เริ่มต้น (วินาที) อัพเดททุก 1 วินาที
* **Memory Usage**: แสดงเป็น Bar (จำลอง 40-70%)
* **CPU Load**: แสดงเป็น Arc gauge (จำลอง 20-80%)

**การประยุกต์ใช้งานจริง:**

* ระบบ Monitoring ในโรงงาน ต้องมีหน้า System Health เสมอ
* ใช้ตรวจสอบว่า Embedded Device ทำงานปกติหรือไม่

**Hint:**

* ใช้ `static uint32_t uptime_sec` นับเวลาใน timer callback
* ถ้า timer ทำงานทุก 100ms → นับ 10 ครั้ง = 1 วินาที
* ใช้ `lv_label_set_text_fmt()` แสดง uptime

***

#### Exercise 2: Auto-Rotating Tabs

**โจทย์:** สร้างระบบ Auto-Rotate ที่เปลี่ยน tab ทุก 5 วินาทีอัตโนมัติ:

* เปลี่ยน tab ไปข้างหน้าทีละ tab
* เมื่อถึง tab สุดท้าย ย้อนกลับ tab แรก
* แสดงตัวนับถอยหลัง "Next tab in: 3s" ที่มุมล่างขวา
* เมื่อผู้ใช้กดเลือก tab เอง ให้ reset ตัวนับถอยหลัง

**การประยุกต์ใช้งานจริง:**

* Digital Signage ในห้องควบคุมโรงงาน
* Kiosk Mode ใน Dashboard ที่ไม่มีคนดูแลตลอดเวลา
* หน้าจอ Overview สำหรับผู้บริหารดูภาพรวม

**Hint:**

* ใช้ `lv_tabview_set_active(tv, next_idx, LV_ANIM_ON)`
* นับ counter ใน timer callback: `static uint32_t auto_counter`
* Reset counter เมื่อ `auto_counter >= 50` (50 x 100ms = 5s)

***

#### Exercise 3: Tab Content with Color Theme

**โจทย์:** ปรับแต่งแต่ละ tab ให้มี color theme แตกต่างกัน:

* IMU Tab: พื้นหลังสีน้ำเงินเข้ม (#16213E) ตัวอักษรสีขาว
* ADC Tab: พื้นหลังสีเขียวเข้ม (#1A3C34) ตัวอักษรสีขาว
* System Tab: พื้นหลังสีม่วงเข้ม (#2D1B3D) ตัวอักษรสีขาว

**Hint:**

* ใช้ `lv_obj_set_style_bg_color(tab, lv_color_hex(0x16213E), 0)`
* Label สีขาว: `lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0)`

***

### 7. References

* [LVGL TabView](https://docs.lvgl.io/9.2/widgets/tabview.html)
* [lv\_example\_tabview\_1.c](https://github.com/lvgl/lvgl/blob/release/v9.2/examples/widgets/tabview/lv_example_tabview_1.c)

***
