-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path02_button_class.py
More file actions
306 lines (259 loc) · 16.2 KB
/
Copy path02_button_class.py
File metadata and controls
306 lines (259 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# ==============================================================================
# 02_button_class.py - คลาส Button สำหรับระบบ TitraLab (Button Class for TitraLab)
# ==============================================================================
# สัปดาห์ที่ 1: การอ่านปุ่มกดด้วย OOP (Week 1: Button Reading with OOP)
# เวลา: ~30 นาที (Time: ~30 minutes)
#
# วัตถุประสงค์การเรียนรู้ (Learning Objectives):
# 1. เข้าใจการอ่าน GPIO Input (Understand GPIO Input reading)
# 2. เข้าใจหลักการ Debounce (Understand debounce principle)
# 3. เชื่อมโยงกับการควบคุมเมนูในระบบไทเทรชัน (Connect to menu control)
#
# บริบทการใช้งานจริง - เหมือนการควบคุมเมนูใน Week 3 (Real Application Context):
# - ปุ่ม 1 (GPIO34): SELECT - เลือก/ยืนยัน (Select/Confirm)
# - ปุ่ม 2 (GPIO35): UP - เลื่อนขึ้น/เพิ่มค่า (Scroll up/Increase)
# - ปุ่ม 3 (GPIO39): DOWN - เลื่อนลง/ลดค่า (Scroll down/Decrease)
#
# หมายเหตุสำคัญเกี่ยวกับฮาร์ดแวร์ (Important Hardware Notes):
# - GPIO34, 35, 39 เป็นขา Input-only (ไม่มี internal pull-up)
# - บอร์ด TitraLab มี hardware debounce ผ่าน 74HC14D Schmitt trigger
# - จึงไม่จำเป็นต้องทำ software debounce แต่เพิ่มไว้เพื่อความปลอดภัย
#
# ==============================================================================
from machine import Pin
import time
# ปล่อยให้แอป MicroPad สั่งหยุดได้ (allow the MicroPad app to stop this script)
try:
from scilabpro import stop_requested
except ImportError:
stop_requested = None
class Button:
"""
คลาสอ่านปุ่มกดสำหรับระบบควบคุมไทเทรชัน
(Button Class for Titration Control System)
ในระบบ TitraLab เต็มรูปแบบ (Week 3) ปุ่มจะใช้สำหรับ:
(In the full TitraLab system, buttons are used for:)
- ควบคุมเมนูการตั้งค่า (Control settings menu)
- เริ่ม/หยุดการไทเทรชัน (Start/Stop titration)
- ยืนยันจุด calibration (Confirm calibration points)
- เลือกโหมดการทำงาน (Select operation mode)
คุณสมบัติของ TitraLab (TitraLab Features):
- Hardware debounce ผ่าน IC 74HC14D (Schmitt trigger)
- ปุ่มกดแล้วอ่านค่า HIGH (Active-high logic)
- Pull-down resistor ภายนอก (External pull-down)
ตัวอย่างการใช้งาน (Usage Example):
btn = Button(34, "SELECT")
if btn.is_pressed():
print("Button pressed!")
"""
def __init__(self, pin_number, name="Button", debounce_ms=50):
"""
สร้าง Button object (Create Button object)
พารามิเตอร์ (Parameters):
pin_number (int): หมายเลข GPIO (34, 35, หรือ 39)
name (str): ชื่อปุ่มสำหรับแสดงผล (Button name for display)
debounce_ms (int): เวลา debounce (มิลลิวินาที)
- TitraLab มี hardware debounce แล้ว
- ค่านี้เป็นการป้องกันเพิ่มเติม
หมายเหตุ (Note):
GPIO34, 35, 39 เป็นขา Input-only บน ESP32
ไม่สามารถใช้ Pin.PULL_UP ได้ (ต้องใช้ resistor ภายนอก)
"""
self.pin_number = pin_number
self.name = name
self.debounce_ms = debounce_ms
# สร้าง Pin เป็น INPUT (ไม่มี pull-up ภายใน)
# Create Pin as INPUT (no internal pull-up available)
self._pin = Pin(pin_number, Pin.IN)
# ตัวแปรสำหรับ debounce (Variables for debounce)
self._last_press_time = 0 # เวลาที่กดครั้งล่าสุด (Last press time)
self._was_pressed = False # สถานะกดค้าง (Was button held)
print(f"Button '{name}' พร้อมใช้งานที่ GPIO{pin_number} (ready)")
print(f" - ขา Input-only (ไม่มี internal pull-up)")
print(f" - Hardware debounce: 74HC14D Schmitt trigger")
def is_active(self):
"""
ตรวจสอบว่าปุ่มถูกกดอยู่หรือไม่ (Check if button is currently pressed)
คืนค่า (Returns):
True ถ้ากำลังกดอยู่ (if currently pressed)
False ถ้าไม่ได้กด (if not pressed)
ใช้สำหรับ (Use for):
- ตรวจสอบการกดค้าง (Detect held button)
- รอให้ปล่อยปุ่ม (Wait for button release)
"""
# บอร์ด TitraLab: กด = HIGH (1), ไม่กด = LOW (0)
return self._pin.value() == 1
def is_pressed(self):
"""
ตรวจจับการกดปุ่มครั้งใหม่พร้อม debounce
(Detect new button press with debounce)
คืนค่า True เพียงครั้งเดียวต่อการกดหนึ่งครั้ง
(Returns True only ONCE per press)
การทำงาน (How it works):
1. ตรวจสอบว่ากดอยู่หรือไม่
2. ตรวจสอบว่าเป็นการกดใหม่ (ไม่ใช่กดค้าง)
3. ตรวจสอบเวลา debounce
ใช้สำหรับ (Use for):
- นับจำนวนครั้งที่กด (Count button presses)
- การเลือกเมนู (Menu selection)
- การยืนยันค่า (Value confirmation)
"""
current_time = time.ticks_ms()
is_active_now = self.is_active()
# ตรวจสอบว่าเป็นการกดใหม่ (ไม่ใช่กดค้างจากครั้งก่อน)
# Check if this is a new press (not held from before)
if is_active_now and not self._was_pressed:
# ตรวจสอบว่าผ่านเวลา debounce แล้ว
# Check if debounce time has passed
if time.ticks_diff(current_time, self._last_press_time) > self.debounce_ms:
self._last_press_time = current_time
self._was_pressed = True
return True
# รีเซ็ตสถานะเมื่อปล่อยปุ่ม (Reset state when button released)
if not is_active_now:
self._was_pressed = False
return False
def wait_for_press(self, timeout_ms=None):
"""
รอจนกว่าปุ่มจะถูกกด (Wait until button is pressed)
พารามิเตอร์ (Parameters):
timeout_ms (int): เวลารอสูงสุด (None = รอไม่จำกัด)
คืนค่า (Returns):
True ถ้ากดปุ่ม (if button pressed)
False ถ้าหมดเวลา (if timeout)
ใช้สำหรับ (Use for):
- รอยืนยันการ calibration (Wait for calibration confirm)
- รอเริ่มการไทเทรชัน (Wait to start titration)
"""
start_time = time.ticks_ms()
print(f"รอการกดปุ่ม {self.name}... (Waiting for {self.name}...)")
while True:
if self.is_pressed():
return True
# ตรวจสอบ timeout (Check for timeout)
if timeout_ms is not None:
elapsed = time.ticks_diff(time.ticks_ms(), start_time)
if elapsed > timeout_ms:
print("หมดเวลา! (Timeout!)")
return False
time.sleep_ms(10) # หน่วงเวลาเล็กน้อยประหยัดพลังงาน (Small delay to save power)
def wait_for_release(self):
"""
รอจนกว่าปุ่มจะถูกปล่อย (Wait until button is released)
ใช้สำหรับ (Use for):
- ป้องกันการกดซ้ำ (Prevent repeated presses)
- รอให้ action เสร็จก่อนรับคำสั่งใหม่
"""
while self.is_active():
time.sleep_ms(10)
# ==============================================================================
# โค้ดทดสอบ - จำลองการควบคุมเมนูไทเทรชัน (Test Code - Simulate Menu Control)
# ==============================================================================
if __name__ == "__main__":
print("=" * 60)
print("ทดสอบคลาส Button - จำลองการควบคุมเมนูไทเทรชัน")
print("Button Class Test - Simulate Titration Menu Control")
print("=" * 60)
print("\nคำอธิบายปุ่ม (Button Description):")
print(" - ปุ่ม 1 (GPIO34): SELECT - เลือก/ยืนยัน")
print(" - ปุ่ม 2 (GPIO35): UP - เลื่อนขึ้น")
print(" - ปุ่ม 3 (GPIO39): DOWN - เลื่อนลง")
print("\nกด Ctrl+C เพื่อหยุด (Press Ctrl+C to stop)")
print("-" * 60)
# สร้าง Button objects (Create Button objects)
# ชื่อปุ่มตรงกับการใช้งานใน Week 3
btn_select = Button(34, "SELECT") # ยืนยัน/เลือก
btn_up = Button(35, "UP") # เลื่อนขึ้น
btn_down = Button(39, "DOWN") # เลื่อนลง
# จำลองตัวเลือกเมนู (Simulate menu options)
menu_items = [
"1. เริ่มไทเทรชัน (Start Titration)",
"2. สอบเทียบ pH (Calibrate pH)",
"3. ตั้งค่าปั๊ม (Pump Settings)",
"4. ดูประวัติ (View History)"
]
current_index = 0 # ตำแหน่งเมนูปัจจุบัน (Current menu position)
def show_menu():
"""แสดงเมนูพร้อมตัวชี้ (Display menu with pointer)"""
print("\n" + "=" * 40)
print("เมนูหลัก (Main Menu)")
print("=" * 40)
for i, item in enumerate(menu_items):
pointer = ">>>" if i == current_index else " "
print(f"{pointer} {item}")
print("-" * 40)
# แสดงเมนูครั้งแรก (Show menu initially)
show_menu()
try:
while True:
# ตรวจคำสั่งหยุดจากแอป (check for an app stop request)
if stop_requested is not None and stop_requested():
print("\nได้รับคำสั่งหยุดจากแอป (Stop requested by app)")
break
# ตรวจสอบปุ่ม SELECT (Check SELECT button)
if btn_select.is_pressed():
selected = menu_items[current_index]
print(f"\n*** เลือก: {selected} ***")
print("(ในระบบจริงจะเข้าสู่ฟังก์ชันที่เลือก)")
print("*** Selected - Would execute function ***")
time.sleep(0.5)
show_menu()
# ตรวจสอบปุ่ม UP (Check UP button)
if btn_up.is_pressed():
current_index = (current_index - 1) % len(menu_items)
print(f"เลื่อนขึ้น -> ตำแหน่ง {current_index + 1}")
show_menu()
# ตรวจสอบปุ่ม DOWN (Check DOWN button)
if btn_down.is_pressed():
current_index = (current_index + 1) % len(menu_items)
print(f"เลื่อนลง -> ตำแหน่ง {current_index + 1}")
show_menu()
# หน่วงเวลาเล็กน้อยเพื่อลดการใช้ CPU (Small delay to reduce CPU usage)
time.sleep_ms(20)
except KeyboardInterrupt:
print("\n" + "=" * 60)
print("หยุดโปรแกรม (Program stopped by user)")
print("=" * 60)
# ==============================================================================
# แบบฝึกหัด (Exercises)
# ==============================================================================
#
# แบบฝึกหัดที่ 1: เพิ่มฟังก์ชัน long_press()
# (Exercise 1: Add long_press() function)
# - ตรวจจับการกดค้างนานกว่า 1 วินาที
# - ใช้สำหรับ: ยกเลิก, รีเซ็ตระบบ
# - คำแนะนำ: ใช้ time.ticks_diff() เทียบเวลา
#
# แบบฝึกหัดที่ 2: สร้างคลาส MenuController
# (Exercise 2: Create MenuController class)
# - รวมปุ่ม 3 ปุ่มเข้าด้วยกัน
# - จัดการเมนูอัตโนมัติ
# - มี callback function เมื่อเลือกเมนู
#
# แบบฝึกหัดที่ 3: เพิ่มระบบนับค่าด้วยปุ่ม UP/DOWN
# (Exercise 3: Add value counter with UP/DOWN buttons)
# - กด UP = เพิ่มค่าทีละ 1
# - กด DOWN = ลดค่าทีละ 1
# - กด SELECT = ยืนยันค่า
# - ใช้สำหรับ: ตั้งค่าปริมาตร, ตั้งค่าความเร็วปั๊ม
#
# ==============================================================================
#
# ข้อมูลทางเทคนิคเพิ่มเติม (Additional Technical Information)
# ==============================================================================
#
# ทำไม GPIO34, 35, 39 ถึงเป็น Input-only?
# (Why are GPIO34, 35, 39 input-only?)
# - ESP32 แบ่ง GPIO เป็นหลายกลุ่ม
# - GPIO34-39 เชื่อมต่อกับ ADC1 เท่านั้น
# - ไม่มีวงจร output driver
# - ไม่มี internal pull-up/pull-down resistor
#
# Hardware Debounce บนบอร์ด TitraLab:
# (Hardware Debounce on TitraLab board:)
# - ใช้ IC 74HC14D (Hex Schmitt Trigger Inverter)
# - Schmitt trigger มี hysteresis ช่วยกรองสัญญาณรบกวน
# - ไม่ต้องเขียนโค้ด debounce ซับซ้อน
# - แต่ยังใส่ software debounce เผื่อความปลอดภัย
#
# ==============================================================================