# Arc Gauge

## Lab 2: Arc Widget (Circular Gauge)

***

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

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

* **Circular Display**: แสดงค่าแบบวงกลมที่สวยงามและอ่านง่าย เหมาะกับ gauge แบบมืออาชีพ
* **Angular Data**: เหมาะอย่างยิ่งกับข้อมูลที่มีขอบเขต เช่น Gyroscope, Compass, Temperature, Speed
* **Space Efficient**: Arc ใช้พื้นที่แบบ compact กว่า bar แต่ให้ข้อมูลเท่ากัน
* **Dashboard UI**: Circular gauge เป็นองค์ประกอบหลักของ dashboard สมัยใหม่ทั้งในรถยนต์และ Smart Home

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

1. **Arc Widget**: สร้าง circular gauge ด้วย `lv_arc_create()`
2. **Rotation & Angles**: กำหนดมุมเริ่มต้น, sweep angle (270 degree gauge)
3. **Arc Styling**: ปรับความหนา, สี background, สี indicator
4. **Center Label**: แสดงค่าตัวเลขตรงกลาง arc
5. **Timer Animation**: อัพเดทค่าแบบ smooth ด้วย timer

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

1. สร้าง Arc widget กำหนด rotation ที่ 135 degree (เริ่มจากล่างซ้าย)
2. ตั้งค่า background angles เป็น 0-270 degree (sweep 270 degree)
3. เพิ่ม label ตรงกลางแสดงค่าปัจจุบัน
4. ใช้ timer อัพเดทค่าจาก simulated sensor
5. ปรับ style ให้ดูเป็น professional gauge

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2Fl4zyKaOhLOlFVX3eozp5%2Fweek4_ex2_arc_gauge.gif?alt=media&#x26;token=ef169857-eafa-4ec5-87f6-f65abb0e3604" alt=""><figcaption></figcaption></figure>

***

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

#### 2.1 Arc Geometry (ระบบมุมของ LVGL)

```
┌─────────────────────────────────────────────────────────────┐
│                   ARC ANGLE SYSTEM (LVGL)                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│    LVGL ใช้ระบบมุม clockwise จาก 3 o'clock:                   │
│                                                             │
│                    0 (3 o'clock)                            │
│                       │                                     │
│                       │                                     │
│    270 (12 o'clock)───●───90 (6 o'clock)                    │
│                       │                                     │
│                       │                                     │
│                   180 (9 o'clock)                           │
│                                                             │
│    *** สำคัญ: มุมเพิ่มตามเข็มนาฬิกา (CW) ***                      │
│                                                             │
│    Default rotation = 0  → เริ่มที่ 3 o'clock                   │
│    rotation = 135       → เริ่มที่ bottom-left                  │
│    rotation = 90        → เริ่มที่ 6 o'clock                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.2 Gauge Configuration (270-degree sweep)

```
┌─────────────────────────────────────────────────────────────┐
│            270-DEGREE GAUGE CONFIGURATION                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│    การตั้งค่า gauge แบบ 270 degree (เปิดด้านล่าง):                │
│                                                             │
│    lv_arc_set_rotation(arc, 135);      ← เริ่มที่ล่างซ้าย         │
│    lv_arc_set_bg_angles(arc, 0, 270);  ← sweep 270 degree   │
│                                                             │
│              MIN                  MAX                       │
│               \    ╭────────╮    /                          │
│                \  ╱          ╲  /                           │
│                 ╱   135 degC  ╲                             │
│                │    ┌─────┐    │                            │
│                │    │ 45  │    │                            │
│                │    │ degC│    │  ← Center label            │
│                │    └─────┘    │                            │
│                 ╲  ████████  ╱   ← Indicator (filled)       │
│                  ╲  ██████  ╱                               │
│                   ╰════╝╔══╯                                │
│                    GAP (90 degree at bottom)                │
│                                                             │
│    ████ = Indicator (ค่าปัจจุบัน)                               │
│    ──── = Background (ส่วนที่เหลือ)                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.3 Arc Parts (สำหรับ styling)

```
┌─────────────────────────────────────────────────────────────┐
│                    ARC PARTS                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   LV_PART_MAIN       → พื้นหลัง (background track)             │
│   LV_PART_INDICATOR  → ส่วนที่แสดงค่า (filled arc)              │
│   LV_PART_KNOB       → จุดหมุน (ถ้ามี interactive)              │
│                                                             │
│                  ╭────────────╮                             │
│                ╱  ░░░░░░░░░░░░ ╲  ← LV_PART_MAIN (gray)     │
│               │ ░░░░░░░░░░░░░░ │                            │
│               │                │                            │
│               │ ████████       │  ← LV_PART_INDICATOR (blue)│
│                ╲ ██████████  ╱                              │
│                  ╰════●════╯      ← LV_PART_KNOB            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

***

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

#### 3.1 Arc Creation & Configuration

<table><thead><tr><th width="299.241455078125">Function</th><th>Parameters</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_arc_create(parent)</code></td><td>parent: lv_obj_t*</td><td>สร้าง arc widget</td></tr><tr><td><code>lv_arc_set_range(obj, min, max)</code></td><td>min, max: int32_t</td><td>กำหนดช่วงค่า</td></tr><tr><td><code>lv_arc_set_value(obj, val)</code></td><td>val: int32_t</td><td>ตั้งค่า indicator</td></tr><tr><td><code>lv_arc_get_value(obj)</code></td><td>-</td><td>อ่านค่าปัจจุบัน</td></tr></tbody></table>

#### 3.2 Angle & Rotation

<table><thead><tr><th width="340.03692626953125">Function</th><th>Parameters</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_arc_set_rotation(obj, deg)</code></td><td>deg: int32_t</td><td>หมุนจุดเริ่มต้น (องศา)</td></tr><tr><td><code>lv_arc_set_bg_angles(obj, start, end)</code></td><td>start, end: uint32_t</td><td>กำหนดมุม background track</td></tr><tr><td><code>lv_arc_set_angles(obj, start, end)</code></td><td>start, end: uint32_t</td><td>กำหนดมุม indicator</td></tr><tr><td><code>lv_arc_set_mode(obj, mode)</code></td><td>mode: lv_arc_mode_t</td><td>NORMAL / REVERSE / SYMMETRICAL</td></tr></tbody></table>

#### 3.3 Arc Styling

<table><thead><tr><th width="421.75213623046875">Function / Property</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_obj_set_size(arc, w, h)</code></td><td>ขนาด (ต้องเป็นสี่เหลี่ยมจัตุรัส w=h)</td></tr><tr><td><code>lv_obj_set_style_arc_width(obj, px, part)</code></td><td>ความหนาของ arc (pixels)</td></tr><tr><td><code>lv_obj_set_style_arc_color(obj, color, part)</code></td><td>สีของ arc</td></tr><tr><td><code>lv_obj_set_style_arc_rounded(obj, bool, part)</code></td><td>ปลาย arc โค้งมน</td></tr></tbody></table>

#### 3.4 Make Arc Non-Interactive (Gauge Mode)

```c
/* ลบ knob และ click ออก → เป็น gauge แสดงค่าอย่างเดียว */
lv_obj_remove_style(arc, NULL, LV_PART_KNOB);
lv_obj_remove_flag(arc, LV_OBJ_FLAG_CLICKABLE);
```

***

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

#### 4.1 Step 1: Global Variables

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

/* ---- Global Widget Handles ---- */
static lv_obj_t * gauge_arc;        /* Arc หลัก */
static lv_obj_t * value_label;      /* Label ค่าตรงกลาง */
static lv_obj_t * unit_label;       /* Label หน่วย */
static lv_obj_t * min_label;        /* Label ค่า min */
static lv_obj_t * max_label;        /* Label ค่า max */
static lv_timer_t * sensor_timer;   /* Timer อัพเดท sensor */
```

#### 4.2 Step 2: Simulated Sensor Data

```c
/*
 * simulate_temperature() - จำลองค่าอุณหภูมิ
 * Range: 15-45 degC, เปลี่ยนแบบ smooth
 */
static int32_t simulate_temperature(void)
{
    static uint32_t tick = 0;
    tick++;

    /* Slow sine wave: center 30, amplitude 15 */
    float angle = (float)tick * 0.02f;
    int32_t temp = 30 + (int32_t)(15.0f * sinf(angle));

    /* Add small noise +/- 1 degree */
    temp += (lv_rand(0, 2) - 1);

    /* Clamp to range */
    if (temp < 15) temp = 15;
    if (temp > 45) temp = 45;

    return temp;
}
```

#### 4.3 Step 3: Timer Callback

```c
/*
 * sensor_timer_cb() - อัพเดทค่าทุก 200ms
 */
static void sensor_timer_cb(lv_timer_t * timer)
{
    (void)timer;

    int32_t temp = simulate_temperature();

    /* อัพเดท Arc value */
    lv_arc_set_value(gauge_arc, temp);

    /* อัพเดท Center label */
    lv_label_set_text_fmt(value_label, "%d", (int)temp);

    /* เปลี่ยนสี indicator ตามค่า */
    lv_color_t color;
    if (temp <= 20) {
        color = lv_palette_main(LV_PALETTE_BLUE);       /* เย็น */
    } else if (temp <= 35) {
        color = lv_palette_main(LV_PALETTE_GREEN);      /* ปกติ */
    } else {
        color = lv_palette_main(LV_PALETTE_RED);        /* ร้อน */
    }

    lv_obj_set_style_arc_color(gauge_arc, color, LV_PART_INDICATOR);
}
```

#### 4.4 Step 4: Main Function - สร้าง Gauge UI

```c
void part2_ex2_arc_gauge(void)
{
    /* ---- Background สีเข้ม ---- */
    lv_obj_set_style_bg_color(lv_screen_active(),
                               lv_color_hex(0x0f0f23), 0);

    /* ---- Title ---- */
    lv_obj_t * title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Temperature Gauge");
    lv_obj_set_style_text_color(title, lv_color_hex(0x00d4ff), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);

    /* ---- Arc Gauge ---- */
    gauge_arc = lv_arc_create(lv_screen_active());
    lv_obj_set_size(gauge_arc, 180, 180);
    lv_obj_align(gauge_arc, LV_ALIGN_CENTER, 0, 5);

    /* ตั้งค่า gauge 270 degree */
    lv_arc_set_rotation(gauge_arc, 135);
    lv_arc_set_bg_angles(gauge_arc, 0, 270);
    lv_arc_set_range(gauge_arc, 15, 45);
    lv_arc_set_value(gauge_arc, 30);

    /* ลบ knob ทำเป็น read-only gauge */
    lv_obj_remove_style(gauge_arc, NULL, LV_PART_KNOB);
    lv_obj_remove_flag(gauge_arc, LV_OBJ_FLAG_CLICKABLE);

    /* Styling: background track */
    lv_obj_set_style_arc_width(gauge_arc, 15, LV_PART_MAIN);
    lv_obj_set_style_arc_color(gauge_arc,
                                lv_color_hex(0x2d2d44), LV_PART_MAIN);

    /* Styling: indicator */
    lv_obj_set_style_arc_width(gauge_arc, 15, LV_PART_INDICATOR);
    lv_obj_set_style_arc_color(gauge_arc,
                                lv_palette_main(LV_PALETTE_GREEN),
                                LV_PART_INDICATOR);
    lv_obj_set_style_arc_rounded(gauge_arc, true, LV_PART_INDICATOR);

    /* ---- Center Value Label ---- */
    value_label = lv_label_create(gauge_arc);
    lv_label_set_text(value_label, "30");
    lv_obj_set_style_text_color(value_label, lv_color_white(), 0);
    lv_obj_set_style_text_font(value_label, &lv_font_montserrat_24, 0);
    lv_obj_center(value_label);

    /* ---- Unit Label ---- */
    unit_label = lv_label_create(gauge_arc);
    lv_label_set_text(unit_label, "degC");
    lv_obj_set_style_text_color(unit_label, lv_color_hex(0x888888), 0);
    lv_obj_align(unit_label, LV_ALIGN_CENTER, 0, 22);

    /* ---- Min/Max Labels ---- */
    min_label = lv_label_create(lv_screen_active());
    lv_label_set_text(min_label, "15");
    lv_obj_set_style_text_color(min_label, lv_color_hex(0x6666ff), 0);
    lv_obj_align_to(min_label, gauge_arc, LV_ALIGN_OUT_BOTTOM_LEFT, 20, -15);

    max_label = lv_label_create(lv_screen_active());
    lv_label_set_text(max_label, "45");
    lv_obj_set_style_text_color(max_label, lv_color_hex(0xff4444), 0);
    lv_obj_align_to(max_label, gauge_arc, LV_ALIGN_OUT_BOTTOM_RIGHT, -20, -15);

    /* ---- Status label ---- */
    lv_obj_t * status = lv_label_create(lv_screen_active());
    lv_label_set_text(status, "Live Monitoring");
    lv_obj_set_style_text_color(status, lv_color_hex(0x555555), 0);
    lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -8);

    /* ---- Timer: อัพเดททุก 200ms ---- */
    sensor_timer = lv_timer_create(sensor_timer_cb, 200, NULL);
}
```

#### 4.5 UI Layout Summary

```
┌──────────────────────────────────────┐
│         Temperature Gauge            │
│                                      │
│              ╭═══════╮               │
│            ╱ ░░░░░░░░░ ╲             │
│           │ ░░░░░░░░░░░ │            │
│           │     30      │            │
│           │    degC     │            │
│            ╲ ████████ ╱              │
│        15   ╰═══╝╔══╯  45            │
│                                      │
│          Live Monitoring             │
└──────────────────────────────────────┘
```

***

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

#### 5.1 Arc Size ต้องเป็นสี่เหลี่ยมจัตุรัส

```c
/* ถูกต้อง: Width = Height */
lv_obj_set_size(arc, 200, 200);   /* สี่เหลี่ยมจัตุรัส */

/* ผิด: Width != Height → arc จะบิดเบี้ยว */
lv_obj_set_size(arc, 200, 150);   /* ไม่แนะนำ */
```

#### 5.2 Common Rotation Patterns

```c
/* 270-degree gauge (เปิดด้านล่าง) - ยอดนิยม */
lv_arc_set_rotation(arc, 135);
lv_arc_set_bg_angles(arc, 0, 270);

/* 180-degree gauge (ครึ่งวงกลมบน) */
lv_arc_set_rotation(arc, 180);
lv_arc_set_bg_angles(arc, 0, 180);

/* 360-degree gauge (วงกลมเต็ม เริ่มจาก 12 นาฬิกา) */
lv_arc_set_rotation(arc, 270);
lv_arc_set_bg_angles(arc, 0, 360);
```

#### 5.3 Center Label Pattern

```c
/* วิธีที่ดีที่สุด: สร้าง label เป็น child ของ arc */
lv_obj_t * label = lv_label_create(arc);  /* parent = arc */
lv_obj_center(label);                      /* อยู่ตรงกลาง arc เสมอ */

/* ไม่แนะนำ: สร้างแยกแล้ว align_to */
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_obj_align_to(label, arc, LV_ALIGN_CENTER, 0, 0);
/* ถ้าย้าย arc, label จะไม่ตามไป */
```

#### 5.4 Multiple Arcs (Nested Gauges)

```c
/* สร้าง arc ซ้อนกัน สำหรับแสดงหลายค่า */
lv_obj_t * outer = lv_arc_create(lv_screen_active());
lv_obj_set_size(outer, 200, 200);
lv_obj_set_style_arc_width(outer, 12, LV_PART_INDICATOR);

lv_obj_t * inner = lv_arc_create(lv_screen_active());
lv_obj_set_size(inner, 170, 170);   /* เล็กกว่า outer */
lv_obj_set_style_arc_width(inner, 12, LV_PART_INDICATOR);

lv_obj_align(outer, LV_ALIGN_CENTER, 0, 0);
lv_obj_align(inner, LV_ALIGN_CENTER, 0, 0);
```

#### 5.5 Color Zones ด้วย Multiple Indicator Colors

```c
/* Pattern: เปลี่ยนสี indicator ตามค่า
 * ใช้ในงาน speedometer, temperature gauge */
void update_arc_color_zone(lv_obj_t * arc, int32_t value,
                           int32_t yellow_threshold,
                           int32_t red_threshold)
{
    lv_color_t color;

    if (value < yellow_threshold) {
        color = lv_palette_main(LV_PALETTE_GREEN);
    } else if (value < red_threshold) {
        color = lv_palette_main(LV_PALETTE_YELLOW);
    } else {
        color = lv_palette_main(LV_PALETTE_RED);
    }

    lv_obj_set_style_arc_color(arc, color, LV_PART_INDICATOR);
}
```

#### 5.6 Performance Tips

```c
/* 1. Arc width ไม่ควรเกิน 30px (render ช้า) */
lv_obj_set_style_arc_width(arc, 15, LV_PART_INDICATOR);  /* OK */
lv_obj_set_style_arc_width(arc, 50, LV_PART_INDICATOR);  /* ช้า */

/* 2. ลด update rate ถ้าไม่ต้องการ real-time */
lv_timer_create(cb, 200, NULL);  /* 5 FPS เพียงพอสำหรับ temp */
lv_timer_create(cb, 50, NULL);   /* 20 FPS สำหรับ speedometer */

/* 3. ใช้ LV_ANIM_OFF ถ้า update ถี่มาก */
```

***

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

#### Exercise 1: Speedometer (0-200 km/h)

สร้าง Speedometer ที่แสดงความเร็ว 0-200 km/h:

**Requirements:**

* Arc 270-degree gauge แสดงความเร็ว
* Center label แสดง "120 km/h" (ค่าปัจจุบัน)
* **3 Color zones** บน indicator:
  * 0-80 km/h: สีเขียว (Safe zone)
  * 81-140 km/h: สีเหลือง (Caution zone)
  * 141-200 km/h: สีแดง (Danger zone)
* Timer จำลองความเร็วที่เปลี่ยนแปลง (เร่ง/ลดสลับกัน)
* Min/Max labels ที่ขอบ gauge ("0" และ "200")

```
┌──────────────────────────────────────┐
│           Speedometer                │
│                                      │
│              ╭═══════╮               │
│            ╱ ████░░░░░ ╲             │
│           │ ████░░░░░░░ │            │
│           │    120      │            │
│           │    km/h     │            │
│            ╲ ████████ ╱              │
│        0    ╰═══╝╔══╯  200           │
│                                      │
└──────────────────────────────────────┘
```

**Hints:**

* `lv_arc_set_range(arc, 0, 200)`
* ใช้ `update_arc_color_zone()` pattern จาก Section 5.5
* จำลองความเร็ว: เริ่ม 0, เพิ่มทีละ 3-5, ถึง 200 แล้วลดกลับ

***

#### Exercise 2: Temperature Gauge with Animated Needle (-10 to 50 degC)

สร้าง Temperature Gauge ที่มี animation แบบ smooth:

**Requirements:**

* Arc 270-degree แสดงอุณหภูมิ -10 ถึง 50 degC
* Center label แสดงค่าอุณหภูมิ + unit
* **Line needle** ชี้ค่าปัจจุบัน (ใช้ `lv_line_create()`)
* Color zones ตามอุณหภูมิ:
  * -10 ถึง 5 degC: สีฟ้า (Cold)
  * 6 ถึง 35 degC: สีเขียว (Normal)
  * 36 ถึง 50 degC: สีแดง (Hot)
* Status label แสดง "Cold" / "Normal" / "Hot"
* Timer จำลองอุณหภูมิเปลี่ยนทุก 300ms

```
┌──────────────────────────────────────┐
│         Temperature Gauge            │
│                                      │
│              ╭═══════╮               │
│            ╱ ████████░ ╲             │
│           │ █████  ╱░░░ │            │
│           │    22 ╱     │ ← needle   │
│           │   degC      │            │
│            ╲ ████████ ╱              │
│       -10   ╰═══╝╔══╯  50            │
│                                      │
│          Status: Normal              │
└──────────────────────────────────────┘
```

**Hints:**

* Needle: ใช้ `lv_line_create()` กับ **static** point array
* คำนวณมุมเข็ม: `angle = 135 + (value - min) * 270 / (max - min)`
* ใช้ `sin()` / `cos()` คำนวณ endpoint ของ line
* อัพเดท line points ใน timer callback

***

### 7. References

* [LVGL Arc](https://docs.lvgl.io/9.2/widgets/arc.html)
* [lv\_example\_arc\_1.c](https://github.com/lvgl/lvgl/blob/release/v9.2/examples/widgets/arc/lv_example_arc_1.c)

***
