คู่มือการใช้งาน

วิธีออกแบบหน้าจอเบื้องต้น และการเขียนฟังก์ชันของคุณเองให้ทำงานเมื่อมีการกดปุ่มหรือเปลี่ยนค่า

1. เริ่มต้นใช้งาน

  1. สมัครสมาชิกและเข้าสู่ระบบ
  2. ที่แดชบอร์ดกด สร้างโปรเจกต์ใหม่ แล้วเลือกเทมเพลตที่ใกล้เคียง หรือเริ่มจากหน้าว่าง
  3. ในหน้าออกแบบ เปิดแท็บ บอร์ด ทางขวา เลือกรุ่นบอร์ดให้ตรงกับที่มี ขนาดจอและขาต่อจอจะถูกตั้งให้เอง
  4. ลากวิดเจ็ตจากแถบซ้ายมาวางบนจอ ตั้งค่าในแผงขวา แล้วกด บันทึก
  5. กด ▶ ทดสอบรัน เพื่อลองแตะปุ่มและเลื่อนสไลเดอร์บนจอจำลอง
  6. เสียบบอร์ดเข้ากับคอมพิวเตอร์ แล้วกด ⚡ เบิร์นลงบอร์ด หรือกด สร้างโค้ด ESP32 เพื่อดูและดาวน์โหลดโค้ด

2. ส่วนต่าง ๆ ของหน้าออกแบบ

แถบซ้าย
รายการวิดเจ็ต แบ่งเป็นกลุ่มพื้นฐาน รับค่า แสดงผล และขั้นสูง คลิกหรือลากมาวางบนจอ
ตรงกลาง
จอจำลองขนาดเท่าจอจริง แท็บด้านบนคือหน้าจอแต่ละหน้า กด + หน้าจอ เพื่อเพิ่ม และดับเบิลคลิกที่แท็บเพื่อเปลี่ยนชื่อ
แผงขวา
แท็บคุณสมบัติ (ค่าของวิดเจ็ตที่เลือก) แท็บเลเยอร์ (ลำดับการซ้อน) และแท็บบอร์ด (รุ่นบอร์ด การหมุนจอ และ Wi-Fi)
แถบบน
ชื่องาน ซูม กริดสำหรับจัดแนว ย้อนกลับ บันทึก ผู้ช่วย AI ทดสอบรัน และสร้างโค้ด

3. วาง ย้าย และปรับขนาด

  • ลากกล่องวิดเจ็ตเพื่อย้าย ลากจุดสี่เหลี่ยมที่มุมขวาล่างเพื่อปรับขนาด หรือพิมพ์ตำแหน่งและขนาดในแผงคุณสมบัติ
  • วิดเจ็ตที่ซ้อนกัน ตัวที่อยู่บนสุดในแท็บเลเยอร์จะอยู่ด้านหน้า ใช้ปุ่ม ขึ้นหน้าสุด ในแผงคุณสมบัติเพื่อนำวิดเจ็ตที่เลือกขึ้นมาด้านหน้า
ปุ่มลัดทำอะไร
← → ↑ ↓ขยับวิดเจ็ตที่เลือกทีละ 1 px (กด Shift ค้างไว้ = ทีละ 10 px)
Ctrl + Dทำซ้ำวิดเจ็ตที่เลือก
Deleteลบวิดเจ็ตที่เลือก
Ctrl + Z / Ctrl + Yย้อนกลับ / ทำซ้ำสิ่งที่ย้อน
Ctrl + Sบันทึกงาน
Escหยุดทดสอบรัน หรือยกเลิกการเลือก

4. ตั้งค่าวิดเจ็ต

  • ชื่อตัวแปร ใช้อ้างถึงวิดเจ็ตในโค้ด ชื่อจะถูกเติม ui_ ข้างหน้า เช่น ตั้งชื่อ LabelTemp จะได้ตัวแปร ui_LabelTemp ควรตั้งเป็นภาษาอังกฤษที่บอกหน้าที่ชัดเจน
  • หน้าตา ปรับสี ขนาดตัวอักษร ความมน เส้นขอบ และความทึบได้ ภาพบนจอจำลองตรงกับที่จะขึ้นบนจอจริง
  • ใช้ข้อความภาษาไทยได้ ระบบจะแนบฟอนต์ไทยให้เอง แต่ใช้อีโมจิและอักษรจีนหรือญี่ปุ่นไม่ได้ (ขึ้นเป็นกล่องว่าง)
  • ติ๊ก ซ่อนตอนเริ่มต้น เพื่อให้วิดเจ็ตยังไม่แสดงจนกว่าโค้ดของคุณจะสั่งให้แสดง

5. ทำให้ปุ่มทำงาน

เลือกวิดเจ็ต แล้วดูหัวข้อ การทำงาน ในแผงคุณสมบัติ ช่อง เมื่อกด มีสามแบบ

ไม่ทำอะไร
ค่าเริ่มต้น
ไปยังหน้าจออื่น
เลือกหน้าปลายทางและแอนิเมชันได้ ไม่ต้องเขียนโค้ด
เรียกฟังก์ชันของฉัน
เขียนโค้ดเองว่าจะให้เกิดอะไรขึ้น เช่น สั่งรีเลย์ อ่านเซนเซอร์ หรือเปลี่ยนข้อความบนจอ (ดูหัวข้อถัดไป)

วิดเจ็ตที่รับค่าได้ (สวิตช์ ช่องติ๊ก สไลเดอร์ วงแหวน ดรอปดาวน์ ล้อเลือกค่า ช่องพิมพ์ข้อความ) มีช่องติ๊ก เรียกฟังก์ชันเมื่อค่าเปลี่ยน เพิ่มมาด้วย ใช้เมื่อต้องการรู้ค่าที่ผู้ใช้เลือก

6. เขียนฟังก์ชันของฉัน

  1. เลือกวิดเจ็ต แล้วตั้ง ชื่อตัวแปร ก่อน เช่น BtnLamp
  2. ในหัวข้อ การทำงาน เลือก เมื่อกด = เรียกฟังก์ชันของฉัน
  3. แผงจะบอกชื่อฟังก์ชันที่จะได้ เช่น void ui_onBtnLampClicked(void)
  4. พิมพ์โค้ดในช่อง โค้ดเมื่อกด เฉพาะคำสั่งข้างในฟังก์ชัน ไม่ต้องพิมพ์บรรทัดชื่อฟังก์ชันและวงเล็บปีกกา
  5. กดบันทึก โค้ดจะเก็บอยู่ในโปรเจกต์ และถูกใส่ลงไฟล์ .ino ทุกครั้งที่สร้างโค้ดหรือเบิร์น

สิ่งที่พิมพ์ในช่องจะกลายเป็นเนื้อในของฟังก์ชันในไฟล์ .ino แบบนี้

void ui_onBtnLampClicked(void)
{
    // โค้ดที่คุณพิมพ์ในช่อง "โค้ดเมื่อกด"
}

ถ้าเว้นช่องว่างไว้ ระบบจะใส่โค้ดตัวอย่างที่พิมพ์ชื่อปุ่มที่ถูกกดออกทาง Serial Monitor ให้ ใช้ตรวจว่าปุ่มทำงานก่อนเขียนโค้ดจริง

ในฟังก์ชันใช้อะไรได้บ้าง

  • คำสั่ง Arduino ทั้งหมด เช่น pinMode, digitalWrite, digitalRead, analogRead, millis และ Serial.println
  • คำสั่ง LVGL กับวิดเจ็ตบนจอ ผ่านชื่อ ui_ชื่อตัวแปร เช่น เปลี่ยนข้อความ ซ่อนหรือแสดง หรือเปลี่ยนสี
  • ฟังก์ชันของวิดเจ็ตพิเศษ เช่น ui_symbol_set_on() ของสัญลักษณ์สำเร็จรูป หรือ ui_shape_set_angle() ของรูปทรง (ดูไฟล์ HOWTO.md ที่ได้พร้อมโค้ด)
  • setBacklight(0-100) ปรับความสว่างหน้าจอ
  • getLocalTime() อ่านเวลาไทยจริงจาก NTP เมื่อใส่ชื่อ Wi-Fi ในแท็บบอร์ดไว้ (ดูตัวอย่างรีเลย์ตั้งเวลาตามนาฬิกา)

ฟังก์ชันเมื่อค่าเปลี่ยน

เมื่อติ๊ก "เรียกฟังก์ชันเมื่อค่าเปลี่ยน" จะได้ฟังก์ชันชื่อ ui_on ตามด้วยชื่อตัวแปร และ Changed ค่าที่ผู้ใช้เลือกส่งมาในตัวแปรตามชนิดของวิดเจ็ต

วิดเจ็ตตัวแปรที่ได้รับความหมาย
สวิตช์, ช่องติ๊กbool checkedtrue เมื่อเปิดหรือติ๊ก, false เมื่อปิด
สไลเดอร์, วงแหวนint32_t valueค่าปัจจุบัน อยู่ระหว่างค่าต่ำสุดถึงสูงสุดที่ตั้งไว้
ดรอปดาวน์, ล้อเลือกค่าuint16_t indexลำดับตัวเลือกที่เลือก เริ่มนับจาก 0
ช่องพิมพ์ข้อความconst char * textข้อความทั้งหมดในช่อง

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

ทุกตัวอย่างคอมไพล์ผ่านกับบอร์ด CYD 2.8" แล้ว ตั้งชื่อตัวแปรของวิดเจ็ตให้ตรงตามที่บอก แล้วคัดลอกโค้ดไปวางในช่องโค้ดได้เลย

1. ปุ่มเปิด/ปิดไฟ

วิดเจ็ตที่ต้องมี: ปุ่มกดชื่อ BtnLamp (เมื่อกด = เรียกฟังก์ชันของฉัน) และข้อความชื่อ LabelLamp

ฟังก์ชันที่ได้: void ui_onBtnLampClicked(void)

static bool on = false;   // static = จำค่าไว้ระหว่างการกดแต่ละครั้ง
on = !on;
pinMode(22, OUTPUT);
digitalWrite(22, on ? HIGH : LOW);
lv_label_set_text(ui_LabelLamp, on ? "LAMP: ON" : "LAMP: OFF");

กดแต่ละครั้งสลับเปิด/ปิด ขา 22 เป็นแค่ตัวอย่าง ให้ใช้ขาที่ว่างและต่อรีเลย์หรือหลอดไฟไว้บนบอร์ดของคุณ

2. นับจำนวนครั้งที่กด

วิดเจ็ตที่ต้องมี: ปุ่มกดชื่อ BtnCount และข้อความชื่อ LabelCount

ฟังก์ชันที่ได้: void ui_onBtnCountClicked(void)

static int count = 0;
count++;
lv_label_set_text_fmt(ui_LabelCount, "%d", count);

lv_label_set_text_fmt ใส่ตัวเลขลงข้อความแบบเดียวกับ printf

3. สไลเดอร์ปรับความสว่างหน้าจอ

วิดเจ็ตที่ต้องมี: สไลเดอร์ชื่อ SliderLight (ต่ำสุด 0 สูงสุด 100 ติ๊ก "เรียกฟังก์ชันเมื่อค่าเปลี่ยน") และข้อความชื่อ LabelLight

ฟังก์ชันที่ได้: void ui_onSliderLightChanged(int32_t value)

setBacklight((uint8_t)value);   // ปรับไฟหน้าจอ 0-100 %
lv_label_set_text_fmt(ui_LabelLight, "%d%%", (int)value);

value คือค่าปัจจุบันของสไลเดอร์ ส่วน setBacklight() มีอยู่ในไฟล์ .ino ที่สร้างให้อยู่แล้ว

4. สวิตช์เปิดพัดลม และแสดง/ซ่อนวิดเจ็ต

วิดเจ็ตที่ต้องมี: สวิตช์ชื่อ SwitchFan (ติ๊ก "เรียกฟังก์ชันเมื่อค่าเปลี่ยน") และกรอบชื่อ PanelFan

ฟังก์ชันที่ได้: void ui_onSwitchFanChanged(bool checked)

pinMode(27, OUTPUT);
digitalWrite(27, checked ? HIGH : LOW);
if (checked) lv_obj_clear_flag(ui_PanelFan, LV_OBJ_FLAG_HIDDEN);   // แสดง
else         lv_obj_add_flag(ui_PanelFan, LV_OBJ_FLAG_HIDDEN);     // ซ่อน

checked เป็น true เมื่อเปิดสวิตช์ และ false เมื่อปิด

5. อ่านค่าแสงด้วย analogRead

วิดเจ็ตที่ต้องมี: ปุ่มกดชื่อ BtnLdr (เมื่อกด = เรียกฟังก์ชันของฉัน) และข้อความชื่อ LabelLdr

ฟังก์ชันที่ได้: void ui_onBtnLdrClicked(void)

int raw = analogRead(34);             // LDR บนบอร์ด CYD ต่ออยู่ที่ขา 34 ได้ค่า 0-4095
int mv  = analogReadMilliVolts(34);   // หรืออ่านเป็นแรงดัน หน่วยมิลลิโวลต์
lv_label_set_text_fmt(ui_LabelLdr, "LDR: %d (%d mV)", raw, mv);
Serial.printf("LDR raw=%d mV=%d\n", raw, mv);

กดแต่ละครั้งอ่านค่าเซนเซอร์แสง (LDR) ที่มีอยู่บนบอร์ด CYD หนึ่งครั้ง analogRead ได้ค่า 0-4095 ถ้าต่อเซนเซอร์อื่นเอง บอร์ด CYD มีขา 35 ว่างให้อ่านค่า analog (ขา 22 และ 27 อ่านค่า analog ไม่ได้ขณะเปิด Wi-Fi)

6. อ่านปุ่มหรือสวิตช์ด้วย digitalRead

วิดเจ็ตที่ต้องมี: ปุ่มกดชื่อ BtnBoot (เมื่อกด = เรียกฟังก์ชันของฉัน) และข้อความชื่อ LabelBoot

ฟังก์ชันที่ได้: void ui_onBtnBootClicked(void)

pinMode(0, INPUT_PULLUP);                 // ปุ่ม BOOT บนบอร์ดต่ออยู่ที่ขา 0
bool pressed = (digitalRead(0) == LOW);   // กดอยู่ = LOW เพราะปุ่มต่อลงกราวด์
lv_label_set_text(ui_LabelBoot, pressed ? "BOOT: PRESSED" : "BOOT: RELEASED");

กดปุ่ม BOOT บนบอร์ดค้างไว้แล้วแตะปุ่มบนจอ ข้อความจะเป็น PRESSED ถ้าต่อสวิตช์เอง ให้ต่อระหว่างขาที่ใช้กับ GND แล้วใช้ INPUT_PULLUP แบบเดียวกัน (ขา 35 ไม่มี pull-up ในตัว ต้องต่อตัวต้านทานเพิ่ม)

7. อ่านค่าต่อเนื่องอัตโนมัติ (analogRead + digitalRead)

วิดเจ็ตที่ต้องมี: สวิตช์ชื่อ SwitchLive (ติ๊ก "เรียกฟังก์ชันเมื่อค่าเปลี่ยน") และข้อความชื่อ LabelLive

ฟังก์ชันที่ได้: void ui_onSwitchLiveChanged(bool checked)

static lv_timer_t * timer = NULL;   // static = จำตัวจับเวลาไว้ ไม่สร้างซ้ำ
if (checked && timer == NULL) {
    pinMode(0, INPUT_PULLUP);
    timer = lv_timer_create([](lv_timer_t * t) {   // ทำงานซ้ำทุก 500 ms
        int  light = analogRead(34);
        bool boot  = (digitalRead(0) == LOW);
        lv_label_set_text_fmt(ui_LabelLive, "LDR %d | BOOT %s", light, boot ? "ON" : "OFF");
    }, 500, NULL);
} else if (!checked && timer != NULL) {
    lv_timer_del(timer);   // ปิดสวิตช์ = หยุดอ่าน
    timer = NULL;
}

เปิดสวิตช์ = เริ่มอ่านทุก 500 ms ด้วยตัวจับเวลาของ LVGL ปิดสวิตช์ = หยุด ไม่ใช้ delay() จอจึงไม่ค้าง เปลี่ยน 500 เป็นช่วงเวลาที่ต้องการได้

8. อ่านอุณหภูมิและความชื้นจาก DHT22

วิดเจ็ตที่ต้องมี: ปุ่มกดชื่อ BtnDht (เมื่อกด = เรียกฟังก์ชันของฉัน) และข้อความชื่อ LabelTemp กับ LabelHumi

ฟังก์ชันที่ได้: void ui_onBtnDhtClicked(void)

static lv_timer_t * timer = NULL;   // กดครั้งแรก = เริ่มอ่านทุก 3 วินาที (DHT22 อ่านถี่กว่า 2 วินาทีไม่ได้)
if (timer == NULL) {
    timer = lv_timer_create([](lv_timer_t * t) {
        const int PIN = 27;   // ขา DATA ของ DHT22
        uint8_t d[5] = {0, 0, 0, 0, 0};
        bool ok = true;

        // รอจนขาเปลี่ยนจากระดับ level - คืนเวลาที่รอ (ไมโครวินาที) หรือ -1 ถ้าเกิน limit
        auto wait = [](int pin, int level, uint32_t limit) -> int32_t {
            uint32_t start = micros();
            while (digitalRead(pin) == level) {
                if (micros() - start > limit) return -1;
            }
            return (int32_t)(micros() - start);
        };

        // สัญญาณเริ่ม: ดึงขาลง 1.2 ms แล้วปล่อย
        pinMode(PIN, OUTPUT);
        digitalWrite(PIN, LOW);
        delayMicroseconds(1200);
        pinMode(PIN, INPUT_PULLUP);

        // อ่าน 40 บิต ห้ามมีงานอื่นแทรกระหว่างนี้ (ใช้เวลาราว 5 ms)
        static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
        portENTER_CRITICAL(&mux);
        if (wait(PIN, HIGH, 100) < 0 || wait(PIN, LOW, 100) < 0 || wait(PIN, HIGH, 100) < 0) ok = false;
        for (int i = 0; ok && i < 40; i++) {
            if (wait(PIN, LOW, 80) < 0) { ok = false; break; }
            int32_t high = wait(PIN, HIGH, 100);   // HIGH สั้น ~27 us = 0, ยาว ~70 us = 1
            if (high < 0) { ok = false; break; }
            d[i / 8] = (d[i / 8] << 1) | (high > 40 ? 1 : 0);
        }
        portEXIT_CRITICAL(&mux);

        if (!ok || (uint8_t)(d[0] + d[1] + d[2] + d[3]) != d[4]) {
            lv_label_set_text(ui_LabelTemp, "DHT22: ERROR");
            return;
        }
        float humi = ((d[0] << 8) | d[1]) * 0.1f;
        float temp = (((d[2] & 0x7F) << 8) | d[3]) * 0.1f;
        if (d[2] & 0x80) temp = -temp;

        char buf[32];
        snprintf(buf, sizeof(buf), "TEMP: %.1f C", temp);
        lv_label_set_text(ui_LabelTemp, buf);
        snprintf(buf, sizeof(buf), "HUMI: %.1f %%", humi);
        lv_label_set_text(ui_LabelHumi, buf);
        Serial.printf("DHT22 %.1f C  %.1f %%\n", temp, humi);
    }, 3000, NULL);
    lv_timer_ready(timer);   // อ่านครั้งแรกทันที ไม่ต้องรอ 3 วินาที
}

ต่อ DHT22: VCC เข้า 3.3V, GND เข้า GND, DATA เข้าขา 27 (ช่อง CN1 ของบอร์ด CYD) ถ้าเป็นเซนเซอร์เปล่าไม่ใช่โมดูล ให้ต่อตัวต้านทาน 10k จาก DATA ไป 3.3V เพิ่ม โค้ดนี้อ่าน DHT22 เองโดยไม่ต้องลงไลบรารี กดปุ่มครั้งแรกแล้วจะอ่านค่าใหม่ทุก 3 วินาทีเอง ถ้าขึ้น ERROR ให้ตรวจสายและขา

9. รีเลย์ตั้งเวลาเปิดแล้วปิดเอง

วิดเจ็ตที่ต้องมี: ปุ่มกดชื่อ BtnRelay (เมื่อกด = เรียกฟังก์ชันของฉัน) และข้อความชื่อ LabelRelay

ฟังก์ชันที่ได้: void ui_onBtnRelayClicked(void)

static const int PIN = 22;                        // ขาที่ต่อรีเลย์
static const int RELAY_ON = HIGH, RELAY_OFF = LOW;  // รีเลย์แบบ active LOW ให้สลับเป็น LOW / HIGH
static const uint32_t RUN_MS = 10UL * 60 * 1000;  // เปิดนาน 10 นาที (แก้ตัวเลขได้)
static lv_timer_t * timer = NULL;
static uint32_t endAt = 0;

pinMode(PIN, OUTPUT);
if (timer != NULL) {                // กดซ้ำระหว่างนับถอยหลัง = ยกเลิก ปิดรีเลย์ทันที
    lv_timer_del(timer);
    timer = NULL;
    digitalWrite(PIN, RELAY_OFF);
    lv_label_set_text(ui_LabelRelay, "RELAY: OFF");
    return;
}

digitalWrite(PIN, RELAY_ON);        // เปิดรีเลย์ แล้วนับถอยหลัง
endAt = millis() + RUN_MS;
timer = lv_timer_create([](lv_timer_t * t) {   // ทำงานทุก 1 วินาที
    int32_t left = (int32_t)(endAt - millis());
    if (left <= 0) {                // ครบเวลา = ปิดรีเลย์
        digitalWrite(PIN, RELAY_OFF);
        lv_label_set_text(ui_LabelRelay, "RELAY: OFF");
        lv_timer_del(t);
        timer = NULL;
        return;
    }
    int sec = (left + 999) / 1000;
    lv_label_set_text_fmt(ui_LabelRelay, "RELAY: ON %02d:%02d", sec / 60, sec % 60);
}, 1000, NULL);
lv_timer_ready(timer);              // แสดงเวลาที่เหลือทันที

กดหนึ่งครั้ง = รีเลย์เปิดและนับถอยหลัง 10 นาที ครบเวลาแล้วปิดเอง กดอีกครั้งระหว่างนับ = ยกเลิกและปิดทันที แก้ระยะเวลาที่ RUN_MS ถ้ารีเลย์ทำงานกลับด้าน (โมดูลแบบ active LOW) ให้สลับค่า RELAY_ON กับ RELAY_OFF ขา 22 เป็นแค่ตัวอย่าง ให้ใช้ขาที่ต่อรีเลย์ไว้จริง

10. รีเลย์ตั้งเวลาตามนาฬิกา (NTP)

วิดเจ็ตที่ต้องมี: สวิตช์ชื่อ SwitchSchedule (ติ๊ก "เรียกฟังก์ชันเมื่อค่าเปลี่ยน") ข้อความชื่อ LabelClock และใส่ชื่อ Wi-Fi กับรหัสผ่านในแท็บบอร์ด

ฟังก์ชันที่ได้: void ui_onSwitchScheduleChanged(bool checked)

static const int PIN = 22;                          // ขาที่ต่อรีเลย์
static const int RELAY_ON = HIGH, RELAY_OFF = LOW;  // รีเลย์แบบ active LOW ให้สลับเป็น LOW / HIGH
static const int ON_MIN  = 18 * 60 + 0;             // เปิดเวลา 18:00 (ชั่วโมง * 60 + นาที)
static const int OFF_MIN =  6 * 60 + 0;             // ปิดเวลา 06:00
static lv_timer_t * timer = NULL;

pinMode(PIN, OUTPUT);
if (!checked) {                                     // ปิดสวิตช์ = เลิกตั้งเวลา และปิดรีเลย์
    if (timer != NULL) { lv_timer_del(timer); timer = NULL; }
    digitalWrite(PIN, RELAY_OFF);
    lv_label_set_text(ui_LabelClock, "SCHEDULE: OFF");
    return;
}
if (timer != NULL) return;
timer = lv_timer_create([](lv_timer_t * t) {        // ตรวจเวลาทุก 1 วินาที
    struct tm now;
    if (!getLocalTime(&now, 0)) {                   // ยังไม่ได้เวลาจากอินเทอร์เน็ต
        lv_label_set_text(ui_LabelClock, "WAITING FOR NTP...");
        return;
    }
    int m = now.tm_hour * 60 + now.tm_min;
    bool on = (ON_MIN < OFF_MIN) ? (m >= ON_MIN && m < OFF_MIN)    // ช่วงในวันเดียว เช่น 08:00-17:00
                                 : (m >= ON_MIN || m < OFF_MIN);   // ช่วงข้ามเที่ยงคืน เช่น 18:00-06:00
    digitalWrite(PIN, on ? RELAY_ON : RELAY_OFF);
    lv_label_set_text_fmt(ui_LabelClock, "%02d:%02d:%02d  RELAY %s", now.tm_hour, now.tm_min, now.tm_sec, on ? "ON" : "OFF");
}, 1000, NULL);
lv_timer_ready(timer);                              // ตรวจครั้งแรกทันที

ใส่ชื่อ Wi-Fi ในแท็บบอร์ดแล้ว บอร์ดจะซิงค์เวลาไทยจากอินเทอร์เน็ตเองตอนเปิดเครื่อง เปิดสวิตช์ = รีเลย์เปิดปิดตามเวลา ON_MIN ถึง OFF_MIN ทุกวัน (ตัวอย่างนี้ 18:00 ถึง 06:00 ข้ามเที่ยงคืนได้) ปิดสวิตช์ = เลิกตั้งเวลาและปิดรีเลย์ ถ้าไฟดับหรือรีสตาร์ต ต้องเปิดสวิตช์ใหม่ ระหว่างยังไม่ได้เวลาจะขึ้น WAITING FOR NTP

11. ดรอปดาวน์เลือกโหมด

วิดเจ็ตที่ต้องมี: ดรอปดาวน์ชื่อ DropMode (ติ๊ก "เรียกฟังก์ชันเมื่อค่าเปลี่ยน")

ฟังก์ชันที่ได้: void ui_onDropModeChanged(uint16_t index)

switch (index) {
    case 0: Serial.println("เลือกตัวเลือกที่ 1"); break;
    case 1: Serial.println("เลือกตัวเลือกที่ 2"); break;
    default: Serial.println("เลือกตัวเลือกอื่น"); break;
}

index คือลำดับของตัวเลือกที่เลือก เริ่มนับจาก 0 ตามบรรทัดในช่องตัวเลือก

12. ไปหน้าจออื่นจากโค้ด

วิดเจ็ตที่ต้องมี: ปุ่มกดชื่อ BtnSettings และหน้าจอชื่อ Settings

ฟังก์ชันที่ได้: void ui_onBtnSettingsClicked(void)

lv_scr_load_anim(ui_Settings, LV_SCR_LOAD_ANIM_MOVE_LEFT, 300, 0, false);

ถ้าแค่ต้องการเปลี่ยนหน้า ไม่ต้องเขียนโค้ด ให้เลือก "ไปยังหน้าจออื่น" แทน ใช้โค้ดเมื่อต้องการทำอย่างอื่นด้วย เช่น ตรวจเงื่อนไขก่อนไป

13. สลับสถานะปั๊ม (สัญลักษณ์สำเร็จรูป)

วิดเจ็ตที่ต้องมี: สัญลักษณ์ปั๊มชื่อ Pump1 และปุ่มกดชื่อ BtnPump

ฟังก์ชันที่ได้: void ui_onBtnPumpClicked(void)

ui_symbol_set_on(ui_Pump1, !ui_symbol_get_on(ui_Pump1));

สัญลักษณ์อื่นใช้แบบเดียวกัน ถังน้ำและแบตเตอรี่ใช้ ui_symbol_set_value() เพื่อเปลี่ยนระดับ

14. อ่านข้อความจากช่องพิมพ์

วิดเจ็ตที่ต้องมี: ช่องพิมพ์ข้อความชื่อ InputName (ติ๊ก "เรียกฟังก์ชันเมื่อค่าเปลี่ยน")

ฟังก์ชันที่ได้: void ui_onInputNameChanged(const char * text)

Serial.print("พิมพ์ว่า: ");
Serial.println(text);

ฟังก์ชันถูกเรียกทุกครั้งที่ข้อความเปลี่ยน text คือข้อความทั้งหมดในช่อง

8. ข้อควรระวัง

  • อย่าใช้ delay() นาน ๆ ในฟังก์ชัน ฟังก์ชันถูกเรียกระหว่างที่จอกำลังทำงาน ถ้าหน่วงนาน จอจะค้างจนกว่าฟังก์ชันจะจบ งานที่ต้องรอเวลา ให้จดเวลาด้วย millis() แล้วตรวจทีหลังแทน
  • ใช้ static เพื่อจำค่า ตัวแปรธรรมดาในฟังก์ชันจะหายเมื่อฟังก์ชันจบ ถ้าต้องจำค่าไว้ใช้ครั้งถัดไป เช่น สถานะเปิด/ปิด หรือจำนวนครั้ง ให้ใส่ static ข้างหน้า
  • ข้อความไทยที่ตั้งจากโค้ด ต้องครอบด้วย ui_thai("...") และในหน้าออกแบบต้องมีข้อความไทยขนาดตัวอักษรนั้นอยู่แล้วอย่างน้อยหนึ่งที่ ไม่งั้นบอร์ดจะไม่มีฟอนต์ไทยขนาดนั้น ตัวอย่างในคู่มือนี้จึงใช้ข้อความอังกฤษบนจอ
  • เปลี่ยนชื่อตัวแปรได้ ชื่อฟังก์ชันจะเปลี่ยนตาม และโค้ดที่เขียนไว้ยังอยู่ แต่ถ้าโค้ดอื่นอ้างถึงชื่อเดิม (เช่น ui_LabelLamp) ต้องแก้ตามเอง
  • ทดสอบรันไม่ได้รันโค้ด C ของคุณ จอจำลองบอกว่าฟังก์ชันไหนถูกเรียก (ดูในหน้าต่างด้านล่าง) แต่ผลของโค้ดจริง เช่น รีเลย์ทำงาน จะเห็นเมื่อเบิร์นลงบอร์ดแล้วเท่านั้น
  • คอมไพล์ไม่ผ่าน ข้อความในหน้าต่างคอมไพล์จะบอกชื่อไฟล์และบรรทัด ส่วนใหญ่เกิดจากลืมเครื่องหมาย ; พิมพ์ชื่อวิดเจ็ตผิด หรือใช้ชื่อวิดเจ็ตที่ถูกลบไปแล้ว

9. เบิร์นลงบอร์ด

  • ⚡ เบิร์นลงบอร์ด ใช้กับ Chrome หรือ Edge บนคอมพิวเตอร์ เสียบบอร์ดด้วยสาย USB ที่ส่งข้อมูลได้ (สายชาร์จอย่างเดียวใช้ไม่ได้) แล้วเลือกพอร์ตของบอร์ด เซิร์ฟเวอร์จะคอมไพล์ให้ แล้วเบราว์เซอร์เบิร์นลงบอร์ดเอง
  • สร้างโค้ด ESP32 ดูโค้ดทุกไฟล์และดาวน์โหลดเป็น .zip ไปเปิดด้วย PlatformIO ได้ ในไฟล์ HOWTO.md มีวิธีใช้และฟังก์ชันของวิดเจ็ตพิเศษที่โปรเจกต์นั้นใช้
  • หลังเบิร์น หน้าต่าง Serial Monitor ในหน้าออกแบบแสดงข้อความจาก Serial.println() ของบอร์ด ใช้ดูว่าฟังก์ชันของคุณถูกเรียกหรือไม่