Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
| ||||||
WithPlants: A Handheld Plants Explorer
To connect with plants. Capture, sense, and listen to plants with a pocket-sized device that makes every walk an adventure.
Description
WithPlants is a portable handheld device designed to bring natural education into daily life, reconnecting people with their environment. Instead of only learning about plants through commercialized products, WithPlants encourage people to naturally rediscover plants during everyday walks, hikes, or park visits, create moments of discovery, observing and collecting. It enables new ways of learning and caring about plants, bringing plants closer to children and urban residents in an accessible way.
The device allows users to:
-Meet new plants: Capture plant images and instantly display their names.
-Habitat of plants: Explore environmental conditions such as temperature, humidity, and air pressure where plants grow.
-Listen to plants: Record and replay natural sounds in the environment, from birdsong to the subtle sound of touching leaves.
- Collect Plant Samples: On the back of the prototype, a semi-open specimen pouch is added for collecting leaves, flowers, or seeds during the walk, which can later be used to create pressed specimens. This tactile element deepens the bond between people and nature by turning observation into a creative, personal activity.
What Makes It Special
WithPlants is educational, wellness-oriented, and scalable.
-Uses M5Stack hardware in a way that encourages less screen time and more outdoor exploration.-Combines visual (camera & UI), auditory (environmental sounds), and tactile (collecting specimens) experiences.
-Future development includes not only scientific plant data, but also cultural meanings and traditional uses, making each encounter richer.
-The minimal UI, natural colour palette, and embodied activities are intended to promote well-being and mindfulness.
UI & UX
The interface follows a minimal, magazine-like design style, provide simple, intuitive navigation for the core functions accessible from the home screen. With green and warm grey as base tones, highlighted by pink, pale yellow, sky blue, the colours are often seen in nature.
Build Instructions
1. Connect CoreS3 Lite with the ENV IV sensor.
2. Flash the program using UIFlow2 / Python.
The UI is built with UIFlow2’s UI editor for layout.
Further Development
- Physical button module (Unit Dual Button) for direct photo capture.
- Enhanced storage of photos & audio clips via SD card.
-3D-printed enclosure for a polished, portable form factor with lanyard
- AI plant recognition model (local/online) includes information:
Basic botanical info (species, seasonal traits, unique features).
Guidance for specimen collection.
Traditional and cultural meanings of plants in different contexts.
- WithPlants could evolve into a platform combining hardware, app, and community-driven nature learning.
- Microscope add-on for detailed plant observation (In the early development stage, I considered using the AtomS3R M12 Cam as the camera module. The plan was to transmit photos via an AP TCP connection and attach a microscope-compatible M12 lens to enable close-up observation of plant surfaces. However, due to time constraints, this approach did not yield satisfactory results in the short term and was not included in the final product.)
import os, time, struct
import M5
from M5 import *
from M5 import Mic, Speaker
from machine import Pin, I2C, SoftI2C
from unit import ENVUnit
import m5ui
import lvgl as lv
import camera
MOCK_PLANT_NAME = "Camphor Tree"
MOCK_CONFIDENCE = 87.2
current_page = "nav"
pages = {}
btn_photo = None
btn_env = None
btn_audio = None
lab_Loading = None
btn_take = None
back_btn_capture = None
lab_capture = None
lbl_connection = None
btn_retake = None
lbl_meet = None
lbl_plant = None
back_btn_result = None
lbl_temp = None
lbl_humi = None
lbl_pres = None
button9 = None
button10 = None
label13 = None
button11 = None
btn_play = None
lbl_audio = None
lbl_volume = None
switch_record = None
button12 = None
button14 = None
camera_initialized = False
recording = False
volume = 50
rec_data = None
record_start_time = 0
last_capture_img = None
ENV_AVAILABLE = False
AUDIO_AVAILABLE = False
_env_unit = None
_i2c = None
env_update_timer = None
recording_check_timer = None
playback_check_timer = None
def switch_page(page_name):
"""页面切换核心函数"""
global current_page, env_update_timer, lbl_connection, last_capture_img
print("Attempting to switch to page: {}".format(page_name))
if current_page == "env" and env_update_timer:
env_update_timer.delete()
env_update_timer = None
if current_page == "audio":
reset_audio_state()
try:
print("Pages available: {}".format(list(pages.keys())))
if page_name in pages:
print("Page {} exists, value: {}".format(page_name, pages[page_name]))
else:
print("Page {} not in pages dictionary".format(page_name))
if page_name in pages and pages[page_name]:
current_page = page_name
pages[page_name].screen_load()
print("Successfully switched to page: {}".format(page_name))
if page_name == "env":
show_env_data_once()
elif page_name == "photo_capture":
if lbl_connection:
if not camera_initialized:
lbl_connection.set_text("Connecting...")
lv.timer_create(init_camera_async, 100, None)
else:
lbl_connection.set_text("Camera ready")
elif page_name == "photo_result":
if last_capture_img:
lv.timer_create(lambda t: (t.delete(), display_photo(last_capture_img)), 50, None)
elif page_name == "audio":
if lbl_audio:
lbl_audio.set_text("Swipe to record")
else:
print("ERROR: Page not found or is None: {}".format(page_name))
print("Available pages: {}".format([(k, v is not None) for k, v in pages.items()]))
if page_name == "photo_result":
print("Attempting to recreate photo_result page...")
pages["photo_result"] = build_photo_result_page()
if pages["photo_result"]:
print("Successfully recreated photo_result page")
switch_page(page_name)
else:
print("Failed to recreate photo_result page")
except Exception as e:
print("Error switching page: {}".format(e))
import sys
sys.print_exception(e)
def init_camera_async(timer):
"""异步初始化相机"""
global lbl_connection
timer.delete()
if camera_init():
if lbl_connection:
lbl_connection.set_text("Camera ready")
else:
if lbl_connection:
lbl_connection.set_text("Camera init failed")
def camera_init():
"""初始化相机"""
global camera_initialized
try:
camera.init(pixformat=camera.RGB565, framesize=camera.QVGA)
camera_initialized = True
print("Camera initialized successfully")
return True
except Exception as e:
camera_initialized = False
print("Camera init failed: {}".format(e))
return False
def camera_deinit():
"""释放相机资源"""
global camera_initialized
try:
camera.deinit()
camera_initialized = False
print("Camera deinitialized")
except:
pass
def capture_photo():
"""拍照函数"""
global last_capture_img
try:
if not camera_initialized:
if not camera_init():
return None
img = camera.snapshot()
if img:
last_capture_img = img
print("Photo captured successfully")
return img
else:
print("Capture failed - no image")
return None
except Exception as e:
print("Capture error: {}".format(e))
return None
def display_photo(img):
"""显示照片在结果页面"""
try:
M5.Lcd.show(img, 6, 35, 310, 139)
return True
except Exception as e:
print("Display photo error: {}".format(e))
return False
def on_photo_click(evt):
"""导航页面 - 相机按钮点击"""
if evt.code == lv.EVENT.RELEASED:
print("Photo button clicked")
switch_page("photo_capture")
def on_env_click(evt):
"""导航页面 - ENV按钮点击"""
if evt.code == lv.EVENT.RELEASED:
print("ENV button clicked")
switch_page("env")
def on_audio_click(evt):
"""导航页面 - 音频按钮点击"""
if evt.code == lv.EVENT.RELEASED:
print("Audio button clicked")
switch_page("audio")
def on_back_click(evt):
"""返回按钮点击 - 返回导航页"""
if evt.code == lv.EVENT.RELEASED:
print("Back button clicked")
switch_page("nav")
def on_take_photo(evt):
"""拍照按钮点击事件"""
global lbl_connection
if evt.code == lv.EVENT.RELEASED:
print("Take photo clicked")
if lbl_connection:
lbl_connection.set_text("Taking photo...")
lv.timer_create(do_capture_async, 10, None)
def do_capture_async(timer):
"""拍照异步处理"""
global last_capture_img, lbl_connection
timer.delete()
img = capture_photo()
if img:
print("Photo captured, switching to result page...")
switch_page("photo_result")
if lbl_connection:
lbl_connection.set_text("Photo captured!")
else:
if lbl_connection:
lbl_connection.set_text("Capture failed")
def on_retake_photo(evt):
"""重拍按钮点击事件"""
if evt.code == lv.EVENT.RELEASED:
print("Retake photo clicked")
switch_page("photo_capture")
def on_reset_click(evt):
"""ENV重置按钮点击"""
if evt.code == lv.EVENT.RELEASED:
print("Reset button clicked")
if lbl_temp:
lbl_temp.set_text("Temperature: Reading...")
if lbl_humi:
lbl_humi.set_text("Humidity: Reading...")
if lbl_pres:
lbl_pres.set_text("Pressure: Reading...")
lv.timer_create(reset_env_async, 100, None)
def reset_env_async(timer):
timer.delete()
if reset_env_sensor():
show_env_data_once()
else:
if lbl_temp:
lbl_temp.set_text("Temperature: Reset Failed")
if lbl_humi:
lbl_humi.set_text("Humidity: Reset Failed")
if lbl_pres:
lbl_pres.set_text("Pressure: Reset Failed")
def on_record_switch(evt):
"""录音开关切换"""
global switch_record
if evt.code == lv.EVENT.VALUE_CHANGED:
if switch_record and switch_record.get_state():
print("Record switch ON")
audio_record()
else:
print("Record switch OFF")
if recording:
stop_recording()
def on_play_click(evt):
"""播放按钮点击"""
if evt.code == lv.EVENT.RELEASED:
print("Play button clicked")
audio_play()
def build_nav_page():
"""构建导航主页"""
global btn_photo, btn_env, btn_audio, lab_Loading
try:
print("Building nav page...")
page = m5ui.M5Page(bg_c=0xecece7)
btn_photo = m5ui.M5Button(text=" MEET NEW PLANTS ", x=17, y=19,
bg_c=0xecece7, text_c=0x7dcc79,
font=lv.font_montserrat_24, parent=page)
btn_env = m5ui.M5Button(text="HABITAT OF PLANTS", x=17, y=81,
bg_c=0xecece7, text_c=0x7dcc79,
font=lv.font_montserrat_24, parent=page)
btn_audio = m5ui.M5Button(text=" LISTEN TO PLANTS ", x=20, y=145,
bg_c=0xecece7, text_c=0x7dcc79,
font=lv.font_montserrat_24, parent=page)
lab_Loading = m5ui.M5Label("YOUR PLANTS' FRIEND", x=82, y=214,
text_c=0xececec, bg_c=0xc0d36a, bg_opa=255,
font=lv.font_montserrat_14, parent=page)
btn_photo.add_event_cb(on_photo_click, lv.EVENT.RELEASED, None)
btn_env.add_event_cb(on_env_click, lv.EVENT.RELEASED, None)
btn_audio.add_event_cb(on_audio_click, lv.EVENT.RELEASED, None)
print("Nav page built successfully")
return page
except Exception as e:
print("Error building nav page: {}".format(e))
import sys
sys.print_exception(e)
return None
def build_photo_capture_page():
global btn_take, back_btn_capture, lab_capture, lbl_connection
try:
print("Building photo capture page...")
page = m5ui.M5Page(bg_c=0xecece7)
back_btn_capture = m5ui.M5Button(text="BACK", x=10, y=10,
bg_c=0xecece7, text_c=0x383838,
font=lv.font_montserrat_14, parent=page)
back_btn_capture.add_event_cb(on_back_click, lv.EVENT.RELEASED, None)
btn_take = m5ui.M5Button(text=" CAPTURE! ", x=0, y=100,
bg_c=0x7dcc79, text_c=0xecece7,
font=lv.font_montserrat_24, parent=page)
btn_take.add_event_cb(on_take_photo, lv.EVENT.RELEASED, None)
lbl_connection = m5ui.M5Label("Connecting...", x=100, y=215,
text_c=0x7dcc79, bg_c=0xecece7, bg_opa=0,
font=lv.font_montserrat_16, parent=page)
print("Photo capture page built successfully")
return page
except Exception as e:
print("Error building photo capture page: {}".format(e))
import sys
sys.print_exception(e)
return None
def build_photo_result_page():
"""构建相机结果页面 - 简化版本"""
global btn_retake, lbl_meet, lbl_plant, back_btn_result
try:
print("Building photo result page...")
print("MOCK_PLANT_NAME: {}, MOCK_CONFIDENCE: {}".format(MOCK_PLANT_NAME, MOCK_CONFIDENCE))
page = m5ui.M5Page(bg_c=0xecece7)
print("Created page object")
back_btn_result = m5ui.M5Button(text="BACK", x=10, y=5,
w=55, h=25,
bg_c=0xecece7, text_c=0x383838,
font=lv.font_montserrat_14, parent=page)
back_btn_result.add_event_cb(on_back_click, lv.EVENT.RELEASED, None)
print("Created back button")
btn_retake = m5ui.M5Button(text="RETAKE", x=250, y=5,
w=65, h=25,
bg_c=0xecece7, text_c=0x383838,
font=lv.font_montserrat_14, parent=page)
btn_retake.add_event_cb(on_retake_photo, lv.EVENT.RELEASED, None)
print("Created retake button")
lbl_meet = m5ui.M5Label("You meet the", x=6, y=180,
text_c=0x000000, bg_c=0xecece7, bg_opa=0,
font=lv.font_montserrat_24, parent=page)
print("Created meet label")
plant_text = "{}! {}%".format(MOCK_PLANT_NAME, MOCK_CONFIDENCE)
print("Plant text: {}".format(plant_text))
lbl_plant = m5ui.M5Label(plant_text,
x=6, y=209,
text_c=0x000000, bg_c=0xecece7, bg_opa=0,
font=lv.font_montserrat_24, parent=page)
print("Created plant label")
print("Photo result page built successfully")
return page
except Exception as e:
print("DETAILED ERROR building photo result page: {}".format(e))
import sys
sys.print_exception(e)
return None
def build_env_page():
"""构建环境传感器页面"""
global button9, button10, lbl_temp, lbl_humi, lbl_pres, label13
try:
print("Building env page...")
page = m5ui.M5Page(bg_c=0xecece7)
button9 = m5ui.M5Button(text="BACK", x=-1, y=0, bg_c=0xecece7,
text_c=0x383838, font=lv.font_montserrat_14, parent=page)
button9.add_event_cb(on_back_click, lv.EVENT.RELEASED, None)
button10 = m5ui.M5Button(text="RESET", x=242, y=0, bg_c=0xecece7,
text_c=0x383838, font=lv.font_montserrat_14, parent=page)
button10.add_event_cb(on_reset_click, lv.EVENT.RELEASED, None)
label13 = m5ui.M5Label("Habitat of Plants", x=18, y=48, text_c=0x9e9e9e,
bg_c=0xfff27f, bg_opa=0,
font=lv.font_montserrat_24, parent=page)
lbl_temp = m5ui.M5Label("Temperature:26.9 C", x=19, y=92, text_c=0x000000,
bg_c=0xfff27f, bg_opa=255,
font=lv.font_montserrat_24, parent=page)
lbl_humi = m5ui.M5Label("Humidity: 50.6%", x=18, y=140, text_c=0x000000,
bg_c=0x7dcc79, bg_opa=255,
font=lv.font_montserrat_24, parent=page)
lbl_pres = m5ui.M5Label("Pressure: 1007.4hPa", x=20, y=190, text_c=0x000000,
bg_c=0x69afee, bg_opa=255,
font=lv.font_montserrat_24, parent=page)
print("Env page built successfully")
return page
except Exception as e:
print("Error building env page: {}".format(e))
import sys
sys.print_exception(e)
return None
def build_audio_page():
"""构建音频页面"""
global button11, btn_play, lbl_audio, lbl_volume, switch_record, button12, button14, volume
try:
print("Building audio page...")
page = m5ui.M5Page(bg_c=0xecece7)
button11 = m5ui.M5Button(text="Back", x=0, y=0, bg_c=0xecece7,
text_c=0x404040, font=lv.font_montserrat_14, parent=page)
button11.add_event_cb(on_back_click, lv.EVENT.RELEASED, None)
lbl_audio = m5ui.M5Label("Swipe to record", x=105, y=33, text_c=0x9e9e9e,
bg_c=0xffffff, bg_opa=0,
font=lv.font_montserrat_14, parent=page)
switch_record = m5ui.M5Switch(x=21, y=61, w=274, h=96, bg_c=0xe2e2e2,
bg_c_checked=0x7dcc79, circle_c=0x93d990, parent=page)
switch_record.add_event_cb(on_record_switch, lv.EVENT.VALUE_CHANGED, None)
btn_play = m5ui.M5Button(text=" PLAY ", x=-9, y=195,
bg_c=0xea89a9, text_c=0x000000,
font=lv.font_montserrat_24, parent=page)
btn_play.add_event_cb(on_play_click, lv.EVENT.RELEASED, None)
lbl_volume = m5ui.M5Label("VOL: 50%", x=242, y=172, text_c=0x000000,
bg_c=0xffffff, bg_opa=0,
font=lv.font_montserrat_14, parent=page)
button14 = m5ui.M5Button(text="-", x=232, y=195, bg_c=0xe2e2e2,
text_c=0x488be0, font=lv.font_montserrat_24, parent=page)
button12 = m5ui.M5Button(text="+", x=274, y=194, bg_c=0xe2e2e2,
text_c=0xf65050, font=lv.font_montserrat_24, parent=page)
def on_vol_down(evt):
global volume, lbl_volume
if evt.code == lv.EVENT.RELEASED:
volume = max(0, volume - 10)
if lbl_volume:
lbl_volume.set_text("VOL: {}%".format(volume))
print("Volume: {}%".format(volume))
def on_vol_up(evt):
global volume, lbl_volume
if evt.code == lv.EVENT.RELEASED:
volume = min(100, volume + 10)
if lbl_volume:
lbl_volume.set_text("VOL: {}%".format(volume))
print("Volume: {}%".format(volume))
button14.add_event_cb(on_vol_down, lv.EVENT.RELEASED, None)
button12.add_event_cb(on_vol_up, lv.EVENT.RELEASED, None)
print("Audio page built successfully")
return page
except Exception as e:
print("Error building audio page: {}".format(e))
import sys
sys.print_exception(e)
return None
def env_init():
"""初始化ENV-IV模块"""
global ENV_AVAILABLE, _env_unit, _i2c
try:
print("Initializing ENV-IV on PORT.A (SCL=1, SDA=2)...")
try:
_i2c = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000)
print("Using hardware I2C")
except:
_i2c = SoftI2C(scl=Pin(1), sda=Pin(2), freq=100000)
print("Using software I2C")
devices = _i2c.scan()
print("I2C devices found: {}".format([hex(d) for d in devices]))
if not devices:
print("No I2C devices found - ENV-IV not connected")
ENV_AVAILABLE = False
return False
try:
from unit import ENVUnit
_env_unit = ENVUnit(i2c=_i2c, type=4)
temp = _env_unit.read_temperature()
humi = _env_unit.read_humidity()
pres = _env_unit.read_pressure()
ENV_AVAILABLE = True
print("ENV-IV initialized successfully")
print("Initial reading: T={:.1f}°C, H={:.1f}%, P={:.1f}hPa".format(temp, humi, pres))
return True
except Exception as e:
print("ENVUnit(type=4) failed: {}".format(str(e)[:50]))
ENV_AVAILABLE = False
_env_unit = None
return False
except Exception as e:
ENV_AVAILABLE = False
_env_unit = None
_i2c = None
print("ENV initialization error: {}".format(e))
return False
def show_env_data_once():
"""读取并显示ENV传感器数据"""
global lbl_temp, lbl_humi, lbl_pres, _env_unit, ENV_AVAILABLE
if not lbl_temp or not lbl_humi or not lbl_pres:
return
if not ENV_AVAILABLE or not _env_unit:
lbl_temp.set_text("Temperature: No Sensor")
lbl_humi.set_text("Humidity: No Sensor")
lbl_pres.set_text("Pressure: No Sensor")
return
print("Reading ENV sensors...")
try:
time.sleep_ms(100)
temperature = _env_unit.read_temperature()
lbl_temp.set_text("Temperature: {:.1f}°C".format(temperature))
print("Temperature: {:.1f}°C".format(temperature))
except Exception as e:
lbl_temp.set_text("Temperature: Failed")
print("Temperature read failed: {}".format(str(e)[:20]))
try:
time.sleep_ms(100)
humidity = _env_unit.read_humidity()
lbl_humi.set_text("Humidity: {:.1f}%".format(humidity))
print("Humidity: {:.1f}%".format(humidity))
except Exception as e:
lbl_humi.set_text("Humidity: Failed")
print("Humidity read failed: {}".format(str(e)[:20]))
try:
time.sleep_ms(100)
pressure = _env_unit.read_pressure()
lbl_pres.set_text("Pressure: {:.1f}hPa".format(pressure))
print("Pressure: {:.1f}hPa".format(pressure))
except Exception as e:
lbl_pres.set_text("Pressure: Failed")
print("Pressure read failed: {}".format(str(e)[:20]))
def reset_env_sensor():
"""重置ENV传感器"""
global ENV_AVAILABLE, _env_unit, _i2c
print("Resetting ENV sensor...")
ENV_AVAILABLE = False
_env_unit = None
try:
_i2c = SoftI2C(scl=Pin(1), sda=Pin(2), freq=50000)
time.sleep_ms(300)
from unit import ENVUnit
_env_unit = ENVUnit(i2c=_i2c, type=4)
time.sleep_ms(500)
temp = _env_unit.read_temperature()
ENV_AVAILABLE = True
print("ENV sensor reset successful: T={:.1f}°C".format(temp))
return True
except Exception as e:
print("ENV sensor reset failed: {}".format(e))
ENV_AVAILABLE = False
_env_unit = None
return False
def audio_init():
"""初始化音频系统"""
global AUDIO_AVAILABLE, rec_data, volume
try:
rec_data = bytearray(8000 * 25)
volume = 50
try:
Speaker.begin()
Speaker.setVolumePercentage(volume)
Speaker.end()
print("Speaker initialized successfully")
except Exception as e:
print("Speaker init warning: {}".format(e))
AUDIO_AVAILABLE = True
print("Audio initialized - 25s buffer, volume: {}%".format(volume))
except Exception as e:
AUDIO_AVAILABLE = False
rec_data = None
volume = 50
print("Audio init failed: {}".format(e))
def reset_audio_state():
"""重置音频系统状态"""
global recording, playback_check_timer, recording_check_timer, switch_record
try:
if recording:
try:
Mic.end()
except:
pass
recording = False
if switch_record:
try:
switch_record.set_state(False)
except:
pass
try:
Speaker.end()
except:
pass
if playback_check_timer:
playback_check_timer.delete()
playback_check_timer = None
if recording_check_timer:
recording_check_timer.delete()
recording_check_timer = None
print("Audio state reset")
except Exception as e:
print("Reset audio error: {}".format(e))
def stop_recording():
"""停止录音"""
global recording, recording_check_timer, switch_record
if recording:
try:
Mic.end()
except:
pass
recording = False
if switch_record:
try:
switch_record.set_state(False)
except:
pass
if lbl_audio:
lbl_audio.set_text("Recording stopped")
if recording_check_timer:
recording_check_timer.delete()
recording_check_timer = None
print("Recording stopped manually")
def audio_record():
"""开始录音"""
global recording, rec_data, record_start_time, recording_check_timer
if not AUDIO_AVAILABLE or rec_data is None:
if lbl_audio:
lbl_audio.set_text("Audio not available")
return
if recording:
if lbl_audio:
lbl_audio.set_text("Already recording...")
return
try:
if lbl_audio:
lbl_audio.set_text("Starting recording...")
try:
Mic.end()
except:
pass
Mic.begin()
time.sleep_ms(100)
Mic.record(rec_data, 8000, True)
recording = True
record_start_time = time.ticks_ms()
if lbl_audio:
lbl_audio.set_text("Recording... 0/20s")
if recording_check_timer:
recording_check_timer.delete()
recording_check_timer = None
recording_check_timer = lv.timer_create(check_recording_status_async, 200, None)
print("Recording started with 25s buffer")
except Exception as e:
if lbl_audio:
lbl_audio.set_text("Record error: {}".format(str(e)[:20]))
recording = False
print("Recording error: {}".format(e))
try:
Mic.end()
except:
pass
def check_recording_status_async(timer):
"""检查录音状态"""
global recording, record_start_time, recording_check_timer, switch_record
if not recording:
if recording_check_timer:
recording_check_timer.delete()
recording_check_timer = None
return
try:
if record_start_time:
elapsed = time.ticks_diff(time.ticks_ms(), record_start_time) // 1000
if elapsed < 20:
if lbl_audio:
lbl_audio.set_text("Recording... {}/20s".format(elapsed))
elif elapsed >= 20:
print("20 seconds reached, stopping recording...")
try:
Mic.end()
except:
pass
recording = False
if switch_record:
try:
switch_record.set_state(False)
except:
pass
if lbl_audio:
lbl_audio.set_text("Recording complete (20s)!")
if recording_check_timer:
recording_check_timer.delete()
recording_check_timer = None
return
try:
if not Mic.isRecording():
elapsed = time.ticks_diff(time.ticks_ms(), record_start_time) // 1000 if record_start_time else 0
try:
Mic.end()
except:
pass
recording = False
if switch_record:
try:
switch_record.set_state(False)
except:
pass
if lbl_audio:
lbl_audio.set_text("Recording done ({}s)!".format(elapsed))
print("Recording completed - {} seconds".format(elapsed))
if recording_check_timer:
recording_check_timer.delete()
recording_check_timer = None
except Exception as e:
print("Recording status check error: {}".format(e))
except Exception as e:
print("Recording check error: {}".format(e))
recording = False
if recording_check_timer:
recording_check_timer.delete()
recording_check_timer = None
def audio_play():
"""播放录音"""
global rec_data, volume, playback_check_timer
if not AUDIO_AVAILABLE or rec_data is None:
if lbl_audio:
lbl_audio.set_text("No audio data")
return
if recording:
if lbl_audio:
lbl_audio.set_text("Stop recording first")
return
try:
if lbl_audio:
lbl_audio.set_text("Starting playback...")
Speaker.begin()
Speaker.setVolumePercentage(volume)
time.sleep_ms(50)
Speaker.playRaw(rec_data, 8000)
if lbl_audio:
lbl_audio.set_text("Playing...")
print("Playback started - volume: {}%".format(volume))
if playback_check_timer:
playback_check_timer.delete()
playback_check_timer = None
playback_check_timer = lv.timer_create(check_playback_status_async, 300, None)
except Exception as e:
if lbl_audio:
lbl_audio.set_text("Play error: {}".format(str(e)[:20]))
print("Playback error: {}".format(e))
try:
Speaker.end()
except:
pass
def check_playback_status_async(timer):
"""检查播放状态"""
global playback_check_timer
try:
is_playing = False
try:
is_playing = Speaker.isPlaying()
except:
is_playing = False
if not is_playing:
if lbl_audio:
current_text = lbl_audio.get_text()
if "Playing..." in current_text:
lbl_audio.set_text("Playback done!")
print("Playback completed")
try:
Speaker.end()
except:
pass
if playback_check_timer:
playback_check_timer.delete()
playback_check_timer = None
except Exception as e:
print("Playback check error: {}".format(e))
if playback_check_timer:
playback_check_timer.delete()
playback_check_timer = None
def setup_ui():
"""UI系统初始化"""
global pages
print("Setting up UI...")
try:
print("Creating nav page...")
pages["nav"] = build_nav_page()
print("Nav page result: {}".format(pages["nav"] is not None))
print("Creating photo_capture page...")
pages["photo_capture"] = build_photo_capture_page()
print("Photo capture page result: {}".format(pages["photo_capture"] is not None))
print("Creating photo_result page...")
pages["photo_result"] = build_photo_result_page()
print("Photo result page result: {}".format(pages["photo_result"] is not None))
print("Creating env page...")
pages["env"] = build_env_page()
print("Env page result: {}".format(pages["env"] is not None))
print("Creating audio page...")
pages["audio"] = build_audio_page()
print("Audio page result: {}".format(pages["audio"] is not None))
print("Final page status:")
for page_name, page_obj in pages.items():
print(" {}: {}".format(page_name, "OK" if page_obj else "FAILED"))
if pages["nav"]:
switch_page("nav")
print("UI setup complete")
else:
print("CRITICAL: Nav page failed!")
except Exception as e:
print("Exception in setup_ui: {}".format(e))
import sys
sys.print_exception(e)
def main():
"""主程序入口"""
print("Starting CoreS3 Plant ID System...")
try:
M5.begin()
print("M5 initialized")
Widgets.setRotation(1)
m5ui.init()
print("m5ui initialized")
setup_ui()
env_init()
audio_init()
print("System ready!")
print("Entering main loop...")
while True:
M5.update()
time.sleep_ms(5)
except Exception as e:
print("Main error: {}".format(e))
import sys
sys.print_exception(e)
camera_deinit()
raise
if __name__ == "__main__":
main()









Comments