# 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="/files/4tJ3J48pK6OlEDumA6m6" 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)

***


---

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