You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Merge branch 'matouch_esp32_s3_2_8_spi'
This commit is contained in:
+12
-2
@@ -1,8 +1,18 @@
|
||||
0.7.2
|
||||
0.8.0
|
||||
=====
|
||||
|
||||
Builtin Apps:
|
||||
- OSUpdate app: replace "force update" checkbox with improved button labels
|
||||
- Launcher: fit at least 3 apps per row on a 240px display
|
||||
- OSUpdate: replace 'force update' checkbox with improved button labels
|
||||
|
||||
Frameworks:
|
||||
- SDCard: add support for SDIO/SD/MMC mode
|
||||
- CameraManager and CameraActivity: work fully camera-agnostic
|
||||
|
||||
OS:
|
||||
- Scale MicroPythonOS boot logo down if necessary
|
||||
- Add board support: MaTouch ESP32-S3 SPI IPS 2.8' with Camera OV3660
|
||||
- UI: Don't show battery icon if not present
|
||||
|
||||
0.7.1
|
||||
=====
|
||||
|
||||
@@ -87,7 +87,7 @@ class Launcher(Activity):
|
||||
# Grid parameters
|
||||
icon_size = 64
|
||||
label_height = 24
|
||||
iconcont_width = icon_size + label_height
|
||||
iconcont_width = int(icon_size * 1.1)
|
||||
iconcont_height = icon_size + label_height
|
||||
|
||||
for app in AppManager.get_app_list():
|
||||
|
||||
@@ -67,6 +67,16 @@ class BatteryManager:
|
||||
initial_adc_value = BatteryManager.read_raw_adc()
|
||||
print(f"Reading ADC at init to fill cache: {initial_adc_value} => {BatteryManager.read_battery_voltage(raw_adc_value=initial_adc_value)}V => {BatteryManager.get_battery_percentage(raw_adc_value=initial_adc_value)}%")
|
||||
|
||||
@staticmethod
|
||||
def has_battery():
|
||||
"""
|
||||
Check if battery monitoring is initialized.
|
||||
|
||||
Returns:
|
||||
bool: True if init_adc() was called, False otherwise
|
||||
"""
|
||||
return _adc_pin is not None
|
||||
|
||||
@staticmethod
|
||||
def read_raw_adc(force_refresh=False):
|
||||
"""
|
||||
|
||||
@@ -10,8 +10,8 @@ import mpos.ui.focus_direction
|
||||
from mpos import InputManager
|
||||
|
||||
# Same as Waveshare ESP32-S3-Touch-LCD-2 and Fri3d Camp 2026 Badge
|
||||
TFT_HOR_RES=320
|
||||
TFT_VER_RES=240
|
||||
TFT_HOR_RES=240
|
||||
TFT_VER_RES=320
|
||||
|
||||
# Fri3d Camp 2024 Badge:
|
||||
#TFT_HOR_RES=296
|
||||
@@ -126,20 +126,36 @@ SensorManager.init(None)
|
||||
|
||||
# === CAMERA HARDWARE ===
|
||||
|
||||
try:
|
||||
# Try to initialize webcam to verify it's available
|
||||
def init_cam(width, height, colormode):
|
||||
try:
|
||||
# Try to initialize webcam to verify it's available
|
||||
import webcam
|
||||
return webcam.init("/dev/video0", width=width, height=height)
|
||||
except Exception as e:
|
||||
print(f"Info: webcam initialization failed, camera will not be available: {e}")
|
||||
|
||||
def deinit_cam(cam_obj):
|
||||
import webcam
|
||||
test_cam = webcam.init("/dev/video0", width=320, height=240)
|
||||
if test_cam:
|
||||
webcam.deinit(test_cam)
|
||||
from mpos import CameraManager
|
||||
CameraManager.add_camera(CameraManager.Camera(
|
||||
lens_facing=CameraManager.CameraCharacteristics.LENS_FACING_FRONT,
|
||||
name="Video4Linux2 Camera",
|
||||
vendor="ACME"
|
||||
))
|
||||
except Exception as e:
|
||||
print(f"Info: webcam initialization failed, camera will not be available: {e}")
|
||||
webcam.deinit(cam_obj)
|
||||
|
||||
def capture_cam(cam_obj, colormode):
|
||||
import webcam
|
||||
return webcam.capture_frame(cam_obj, "rgb565" if colormode else "grayscale")
|
||||
|
||||
def apply_cam_settings(cam_obj, prefs):
|
||||
print("V4L Camera doesn't support settings for now, skipping...")
|
||||
|
||||
from mpos import CameraManager
|
||||
CameraManager.add_camera(CameraManager.Camera(
|
||||
lens_facing=CameraManager.CameraCharacteristics.LENS_FACING_FRONT,
|
||||
name="Video4Linux2 Camera",
|
||||
vendor="ACME",
|
||||
init=init_cam,
|
||||
deinit=deinit_cam,
|
||||
capture=capture_cam,
|
||||
apply_settings=apply_cam_settings
|
||||
))
|
||||
|
||||
|
||||
print("linux.py finished")
|
||||
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
|
||||
print("matouch_esp32_s3_2_8.py initialization")
|
||||
# Hardware initialization for Makerfabs MaTouch ESP32-S3 SPI 2.8" with Camera
|
||||
# Manufacturer's website: https://www.makerfabs.com/matouch-esp32-s3-spi-ips-2-8-with-camera-ov3660.html
|
||||
# Hardware Specifications:
|
||||
# - MCU: ESP32-S3 with 16MB Flash, 8MB Octal PSRAM
|
||||
# - Display: 2.8" IPS LCD, 320x240 resolution, ST7789 driver, SPI interface
|
||||
# - Touch: GT911 capacitive touch controller (5-point), I2C interface
|
||||
# - Camera: OV3660 (3MP, up to 2048x1536)
|
||||
# - No IMU sensor (unlike Fri3d and Waveshare boards)
|
||||
# - No NeoPixel LEDs
|
||||
# - No buzzer or I2S audio
|
||||
|
||||
from micropython import const
|
||||
import st7789
|
||||
import lcd_bus
|
||||
import machine
|
||||
|
||||
import lvgl as lv
|
||||
import task_handler
|
||||
|
||||
import mpos.ui
|
||||
|
||||
# Pin configuration for Display (SPI)
|
||||
# Correct pins from hardware schematic
|
||||
SPI_BUS = 1
|
||||
SPI_FREQ = 40000000
|
||||
LCD_SCLK = 14
|
||||
LCD_MOSI = 13
|
||||
LCD_MISO = 12
|
||||
LCD_DC = 21
|
||||
LCD_CS = 15
|
||||
LCD_BL = 48
|
||||
|
||||
# Display resolution
|
||||
TFT_HOR_RES = 320
|
||||
TFT_VER_RES = 240
|
||||
|
||||
# Initialize SPI bus for display
|
||||
spi_bus = machine.SPI.Bus(
|
||||
host=SPI_BUS,
|
||||
mosi=LCD_MOSI,
|
||||
miso=LCD_MISO,
|
||||
sck=LCD_SCLK
|
||||
)
|
||||
|
||||
display_bus = lcd_bus.SPIBus(
|
||||
spi_bus=spi_bus,
|
||||
freq=SPI_FREQ,
|
||||
dc=LCD_DC,
|
||||
cs=LCD_CS,
|
||||
)
|
||||
|
||||
# Allocate frame buffers
|
||||
# Buffer size calculation: 2 bytes per pixel (RGB565) * width * height / divisor
|
||||
# Using 28800 bytes (same as Waveshare and Fri3d) for good performance
|
||||
_BUFFER_SIZE = const(28800)
|
||||
fb1 = display_bus.allocate_framebuffer(_BUFFER_SIZE, lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_DMA)
|
||||
fb2 = display_bus.allocate_framebuffer(_BUFFER_SIZE, lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_DMA)
|
||||
|
||||
# Initialize ST7789 display
|
||||
mpos.ui.main_display = st7789.ST7789(
|
||||
data_bus=display_bus,
|
||||
frame_buffer1=fb1,
|
||||
frame_buffer2=fb2,
|
||||
display_width=TFT_VER_RES,
|
||||
display_height=TFT_HOR_RES,
|
||||
color_space=lv.COLOR_FORMAT.RGB565,
|
||||
color_byte_order=st7789.BYTE_ORDER_BGR,
|
||||
rgb565_byte_swap=True,
|
||||
backlight_pin=LCD_BL,
|
||||
backlight_on_state=st7789.STATE_PWM,
|
||||
)
|
||||
|
||||
mpos.ui.main_display.init()
|
||||
mpos.ui.main_display.set_power(True)
|
||||
mpos.ui.main_display.set_backlight(100)
|
||||
|
||||
# Touch handling
|
||||
try:
|
||||
import i2c
|
||||
i2c_bus = i2c.I2C.Bus(host=0, scl=38, sda=39)
|
||||
import mpos.indev.gt911 as gt911
|
||||
touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=gt911.I2C_ADDR, reg_bits=gt911.BITS)
|
||||
indev = gt911.GT911(touch_dev, reset_pin=1, interrupt_pin=40, debug=False) # debug makes it slower
|
||||
from mpos import InputManager
|
||||
InputManager.register_indev(indev)
|
||||
except Exception as e:
|
||||
print(f"Touch init got exception: {e}")
|
||||
|
||||
# IO0 Button interrupt handler
|
||||
def io0_interrupt_handler(pin):
|
||||
print("IO0 button pressed!")
|
||||
from mpos import back_screen
|
||||
back_screen()
|
||||
|
||||
io0_pin = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
io0_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler=io0_interrupt_handler)
|
||||
|
||||
# Initialize LVGL
|
||||
lv.init()
|
||||
|
||||
# Initialize SD card in SDIO mode
|
||||
from mpos import sdcard
|
||||
sdcard.init(cmd_pin=2,clk_pin=42,d0_pin=41)
|
||||
|
||||
# === LED HARDWARE ===
|
||||
# Note: MaTouch ESP32-S3 has no NeoPixel LEDs
|
||||
# LightsManager will not be initialized (functions will return False)
|
||||
|
||||
# === CAMERA HARDWARE ===
|
||||
from mpos import CameraManager
|
||||
|
||||
def init_cam(width, height, colormode):
|
||||
toreturn = None
|
||||
try:
|
||||
from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling
|
||||
|
||||
# Map resolution to FrameSize enum using CameraManager
|
||||
frame_size = CameraManager.resolution_to_framesize(width, height)
|
||||
print(f"init_internal_cam: Using FrameSize {frame_size} for {width}x{height}")
|
||||
|
||||
# Try to initialize, with one retry for I2C poweroff issue
|
||||
max_attempts = 3
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
cam = Camera(
|
||||
data_pins=[7,5,4,6,16,8,3,46],
|
||||
vsync_pin=11,
|
||||
href_pin=10,
|
||||
sda_pin=39,
|
||||
scl_pin=38,
|
||||
pclk_pin=17,
|
||||
xclk_pin=9,
|
||||
xclk_freq=20000000,
|
||||
powerdown_pin=-1,
|
||||
reset_pin=-1,
|
||||
pixel_format=PixelFormat.RGB565 if colormode else PixelFormat.GRAYSCALE,
|
||||
frame_size=frame_size,
|
||||
#grab_mode=GrabMode.WHEN_EMPTY,
|
||||
grab_mode=GrabMode.LATEST,
|
||||
fb_count=1
|
||||
)
|
||||
cam.set_vflip(True)
|
||||
|
||||
toreturn=cam
|
||||
break
|
||||
except Exception as e:
|
||||
if attempt < max_attempts-1:
|
||||
print(f"init_cam attempt {attempt} failed: {e}, retrying...")
|
||||
else:
|
||||
print(f"init_cam final exception: {e}")
|
||||
break
|
||||
|
||||
if toreturn:
|
||||
try:
|
||||
from mpos import InputManager
|
||||
indev = InputManager.list_indevs()[0]
|
||||
indev.enable(False)
|
||||
InputManager.unregister_indev(indev)
|
||||
print("input disabled")
|
||||
except Exception as e:
|
||||
print(f"init_cam: disabling indev got exception: {e}")
|
||||
try:
|
||||
import i2c
|
||||
i2c_bus = i2c.I2C.Bus(host=0, scl=38, sda=39)
|
||||
import mpos.indev.gt911 as gt911
|
||||
touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=gt911.I2C_ADDR, reg_bits=gt911.BITS)
|
||||
indev = gt911.GT911(touch_dev, reset_pin=1, interrupt_pin=40, debug=True) # remove debug because it's slower
|
||||
print("new indev created")
|
||||
from mpos import InputManager
|
||||
InputManager.register_indev(indev)
|
||||
print("new indev registered")
|
||||
except Exception as e:
|
||||
print(f"Indev enable got exception: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"init_cam exception: {e}")
|
||||
|
||||
return toreturn
|
||||
|
||||
def deinit_cam(cam):
|
||||
cam.deinit()
|
||||
# Power off, otherwise it keeps using a lot of current
|
||||
try:
|
||||
from machine import Pin, I2C
|
||||
i2c = I2C(1, scl=Pin(38), sda=Pin(39)) # Adjust pins and frequency
|
||||
camera_addr = 0x3C # for OV3660
|
||||
reg_addr = 0x3008
|
||||
reg_high = (reg_addr >> 8) & 0xFF # 0x30
|
||||
reg_low = reg_addr & 0xFF # 0x08
|
||||
power_off_command = 0x42 # Power off command
|
||||
i2c.writeto(camera_addr, bytes([reg_high, reg_low, power_off_command]))
|
||||
except Exception as e:
|
||||
print(f"Warning: powering off camera got exception: {e}")
|
||||
import time
|
||||
time.sleep_ms(100)
|
||||
try:
|
||||
import i2c
|
||||
i2c_bus = i2c.I2C.Bus(host=0, scl=38, sda=39)
|
||||
import mpos.indev.gt911 as gt911
|
||||
touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=gt911.I2C_ADDR, reg_bits=gt911.BITS)
|
||||
indev = gt911.GT911(touch_dev, reset_pin=1, interrupt_pin=40, debug=True) # remove debug because it's slower
|
||||
print("new indev created")
|
||||
from mpos import InputManager
|
||||
InputManager.register_indev(indev)
|
||||
print("new indev registered")
|
||||
except Exception as e:
|
||||
print(f"Indev enable got exception: {e}")
|
||||
|
||||
def capture_cam(cam_obj, colormode):
|
||||
return cam_obj.capture()
|
||||
|
||||
def apply_cam_settings(cam_obj, prefs):
|
||||
return CameraManager.ov_apply_camera_settings(cam_obj, prefs)
|
||||
|
||||
# MaTouch ESP32-S3 has OV3660 camera (3MP, up to 2048x1536)
|
||||
# Camera pins are available but initialization is handled by the camera driver
|
||||
CameraManager.add_camera(CameraManager.Camera(
|
||||
lens_facing=CameraManager.CameraCharacteristics.LENS_FACING_FRONT,
|
||||
name="OV3660",
|
||||
vendor="OmniVision",
|
||||
init=init_cam,
|
||||
deinit=deinit_cam,
|
||||
capture=capture_cam,
|
||||
apply_settings=apply_cam_settings
|
||||
))
|
||||
|
||||
print("matouch_esp32_s3_2_8.py finished")
|
||||
print("Board capabilities:")
|
||||
print(" - Display: 320x240 ST7789 with GT911 touch")
|
||||
print(" - Camera: OV3660 (3MP)")
|
||||
print(" - No LEDs")
|
||||
@@ -112,12 +112,6 @@ try:
|
||||
except Exception as e:
|
||||
print(f"Warning: powering off camera got exception: {e}")
|
||||
|
||||
# === AUDIO HARDWARE: Waveshare board has no buzzer or I2S audio so no need to initialize.
|
||||
|
||||
# === LED HARDWARE ===
|
||||
# Note: Waveshare board has no NeoPixel LEDs
|
||||
# LightsManager will not be initialized (functions will return False)
|
||||
|
||||
# === SENSOR HARDWARE ===
|
||||
from mpos import SensorManager
|
||||
|
||||
@@ -128,11 +122,82 @@ SensorManager.init(i2c_bus, address=0x6B, mounted_position=SensorManager.FACING_
|
||||
# === CAMERA HARDWARE ===
|
||||
from mpos import CameraManager
|
||||
|
||||
def init_cam(width, height, colormode):
|
||||
toreturn = None
|
||||
try:
|
||||
from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling
|
||||
|
||||
# Map resolution to FrameSize enum using CameraManager
|
||||
frame_size = CameraManager.resolution_to_framesize(width, height)
|
||||
print(f"init_internal_cam: Using FrameSize {frame_size} for {width}x{height}")
|
||||
|
||||
# Try to initialize, with one retry for I2C poweroff issue
|
||||
max_attempts = 3
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
cam = Camera(
|
||||
data_pins=[12,13,15,11,14,10,7,2],
|
||||
vsync_pin=6,
|
||||
href_pin=4,
|
||||
sda_pin=21,
|
||||
scl_pin=16,
|
||||
pclk_pin=9,
|
||||
xclk_pin=8,
|
||||
xclk_freq=20000000,
|
||||
powerdown_pin=-1,
|
||||
reset_pin=-1,
|
||||
pixel_format=PixelFormat.RGB565 if self.colormode else PixelFormat.GRAYSCALE,
|
||||
frame_size=frame_size,
|
||||
#grab_mode=GrabMode.WHEN_EMPTY,
|
||||
grab_mode=GrabMode.LATEST,
|
||||
fb_count=1
|
||||
)
|
||||
cam.set_vflip(True)
|
||||
toreturn=cam
|
||||
break
|
||||
except Exception as e:
|
||||
if attempt < max_attempts-1:
|
||||
print(f"init_cam attempt {attempt} failed: {e}, retrying...")
|
||||
else:
|
||||
print(f"init_cam final exception: {e}")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"init_cam exception: {e}")
|
||||
|
||||
return toreturn
|
||||
|
||||
def deinit_cam(cam):
|
||||
cam.deinit()
|
||||
# Power off, otherwise it keeps using a lot of current
|
||||
try:
|
||||
from machine import Pin, I2C
|
||||
i2c = I2C(1, scl=Pin(16), sda=Pin(21)) # Adjust pins and frequency
|
||||
camera_addr = 0x3C # for OV5640
|
||||
reg_addr = 0x3008
|
||||
reg_high = (reg_addr >> 8) & 0xFF # 0x30
|
||||
reg_low = reg_addr & 0xFF # 0x08
|
||||
power_off_command = 0x42 # Power off command
|
||||
i2c.writeto(camera_addr, bytes([reg_high, reg_low, power_off_command]))
|
||||
except Exception as e:
|
||||
print(f"Warning: powering off camera got exception: {e}")
|
||||
import time
|
||||
time.sleep_ms(100)
|
||||
|
||||
def capture_cam(cam_obj, colormode):
|
||||
return cam_obj.capture()
|
||||
|
||||
def apply_cam_settings(cam_obj, prefs):
|
||||
return CameraManager.ov_apply_camera_settings(cam_obj, prefs)
|
||||
|
||||
# Waveshare ESP32-S3-Touch-LCD-2 has OV5640 camera
|
||||
CameraManager.add_camera(CameraManager.Camera(
|
||||
lens_facing=CameraManager.CameraCharacteristics.LENS_FACING_BACK,
|
||||
name="OV5640",
|
||||
vendor="OmniVision"
|
||||
vendor="OmniVision",
|
||||
init=init_cam,
|
||||
deinit=deinit_cam,
|
||||
capture=capture_cam,
|
||||
apply_settings=apply_cam_settings
|
||||
))
|
||||
|
||||
print("waveshare_esp32_s3_touch_lcd_2.py finished")
|
||||
|
||||
@@ -37,7 +37,7 @@ class Camera:
|
||||
Represents a camera device with its characteristics.
|
||||
"""
|
||||
|
||||
def __init__(self, lens_facing, name=None, vendor=None, version=None):
|
||||
def __init__(self, lens_facing, name=None, vendor=None, version=None, init=None, deinit=None, capture=None, apply_settings=None):
|
||||
"""Initialize camera metadata.
|
||||
|
||||
Args:
|
||||
@@ -50,6 +50,10 @@ class Camera:
|
||||
self.name = name or "Camera"
|
||||
self.vendor = vendor or "Unknown"
|
||||
self.version = version or 1
|
||||
self.init_function = init
|
||||
self.deinit_function = deinit
|
||||
self.capture_function = capture
|
||||
self.apply_settings_function = apply_settings
|
||||
|
||||
def __repr__(self):
|
||||
facing_names = {
|
||||
@@ -60,6 +64,21 @@ class Camera:
|
||||
facing_str = facing_names.get(self.lens_facing, f"UNKNOWN({self.lens_facing})")
|
||||
return f"Camera({self.name}, facing={facing_str})"
|
||||
|
||||
def init(self, width, height, colormode):
|
||||
if self.init_function:
|
||||
return self.init_function(width, height, colormode)
|
||||
|
||||
def deinit(self, cam_obj=None):
|
||||
if self.deinit_function:
|
||||
return self.deinit_function(cam_obj)
|
||||
|
||||
def capture(self, cam_obj, colormode=None):
|
||||
if self.capture_function:
|
||||
return self.capture_function(cam_obj, colormode)
|
||||
|
||||
def apply_settings(self, cam_obj, prefs):
|
||||
if self.apply_settings_function:
|
||||
return self.apply_settings_function(cam_obj, prefs)
|
||||
|
||||
class CameraManager:
|
||||
"""
|
||||
@@ -180,6 +199,165 @@ class CameraManager:
|
||||
"""
|
||||
return len(CameraManager._cameras)
|
||||
|
||||
@staticmethod
|
||||
def resolution_to_framesize(width, height):
|
||||
"""Map resolution (width, height) to FrameSize enum.
|
||||
|
||||
Args:
|
||||
width: Image width in pixels
|
||||
height: Image height in pixels
|
||||
|
||||
Returns:
|
||||
FrameSize enum value corresponding to the resolution, or R240X240 as default
|
||||
"""
|
||||
try:
|
||||
from camera import FrameSize
|
||||
except ImportError:
|
||||
print("Warning: camera module not available")
|
||||
return None
|
||||
|
||||
# Format: (width, height): FrameSize
|
||||
resolution_map = {
|
||||
(96, 96): FrameSize.R96X96,
|
||||
(160, 120): FrameSize.QQVGA,
|
||||
(128, 128): FrameSize.R128X128,
|
||||
(176, 144): FrameSize.QCIF,
|
||||
(240, 176): FrameSize.HQVGA,
|
||||
(240, 240): FrameSize.R240X240,
|
||||
(320, 240): FrameSize.QVGA,
|
||||
(320, 320): FrameSize.R320X320,
|
||||
(400, 296): FrameSize.CIF,
|
||||
(480, 320): FrameSize.HVGA,
|
||||
(480, 480): FrameSize.R480X480,
|
||||
(640, 480): FrameSize.VGA,
|
||||
(640, 640): FrameSize.R640X640,
|
||||
(720, 720): FrameSize.R720X720,
|
||||
(800, 600): FrameSize.SVGA,
|
||||
(800, 800): FrameSize.R800X800,
|
||||
(1024, 768): FrameSize.XGA,
|
||||
(960, 960): FrameSize.R960X960,
|
||||
(1280, 720): FrameSize.HD,
|
||||
(1024, 1024): FrameSize.R1024X1024,
|
||||
# These are disabled in camera_settings.py because they use a lot of RAM:
|
||||
(1280, 1024): FrameSize.SXGA,
|
||||
(1280, 1280): FrameSize.R1280X1280,
|
||||
(1600, 1200): FrameSize.UXGA,
|
||||
(1920, 1080): FrameSize.FHD,
|
||||
}
|
||||
|
||||
return resolution_map.get((width, height), FrameSize.R240X240)
|
||||
|
||||
@staticmethod
|
||||
def ov_apply_camera_settings(cam, prefs):
|
||||
if not cam or not prefs:
|
||||
print("ov_apply_camera_settings: Skipping because invalid prefs or cam object")
|
||||
return
|
||||
|
||||
try:
|
||||
# Basic image adjustments
|
||||
brightness = prefs.get_int("brightness")
|
||||
cam.set_brightness(brightness)
|
||||
|
||||
contrast = prefs.get_int("contrast")
|
||||
cam.set_contrast(contrast)
|
||||
|
||||
saturation = prefs.get_int("saturation")
|
||||
cam.set_saturation(saturation)
|
||||
|
||||
# Orientation
|
||||
hmirror = prefs.get_bool("hmirror")
|
||||
cam.set_hmirror(hmirror)
|
||||
|
||||
vflip = prefs.get_bool("vflip")
|
||||
cam.set_vflip(vflip)
|
||||
|
||||
# Special effect
|
||||
special_effect = prefs.get_int("special_effect")
|
||||
cam.set_special_effect(special_effect)
|
||||
|
||||
# Exposure control (apply master switch first, then manual value)
|
||||
exposure_ctrl = prefs.get_bool("exposure_ctrl")
|
||||
cam.set_exposure_ctrl(exposure_ctrl)
|
||||
|
||||
if not exposure_ctrl:
|
||||
aec_value = prefs.get_int("aec_value")
|
||||
cam.set_aec_value(aec_value)
|
||||
|
||||
# Mode-specific default comes from constructor
|
||||
ae_level = prefs.get_int("ae_level")
|
||||
cam.set_ae_level(ae_level)
|
||||
|
||||
aec2 = prefs.get_bool("aec2")
|
||||
cam.set_aec2(aec2)
|
||||
|
||||
# Gain control (apply master switch first, then manual value)
|
||||
gain_ctrl = prefs.get_bool("gain_ctrl")
|
||||
cam.set_gain_ctrl(gain_ctrl)
|
||||
|
||||
if not gain_ctrl:
|
||||
agc_gain = prefs.get_int("agc_gain")
|
||||
cam.set_agc_gain(agc_gain)
|
||||
|
||||
gainceiling = prefs.get_int("gainceiling")
|
||||
cam.set_gainceiling(gainceiling)
|
||||
|
||||
# White balance (apply master switch first, then mode)
|
||||
whitebal = prefs.get_bool("whitebal")
|
||||
cam.set_whitebal(whitebal)
|
||||
|
||||
if not whitebal:
|
||||
wb_mode = prefs.get_int("wb_mode")
|
||||
cam.set_wb_mode(wb_mode)
|
||||
|
||||
awb_gain = prefs.get_bool("awb_gain")
|
||||
cam.set_awb_gain(awb_gain)
|
||||
|
||||
# Sensor-specific settings (try/except for unsupported sensors)
|
||||
try:
|
||||
sharpness = prefs.get_int("sharpness")
|
||||
cam.set_sharpness(sharpness)
|
||||
except:
|
||||
pass # Not supported on OV2640?
|
||||
|
||||
try:
|
||||
denoise = prefs.get_int("denoise")
|
||||
cam.set_denoise(denoise)
|
||||
except:
|
||||
pass # Not supported on OV2640?
|
||||
|
||||
# Advanced corrections
|
||||
colorbar = prefs.get_bool("colorbar")
|
||||
cam.set_colorbar(colorbar)
|
||||
|
||||
dcw = prefs.get_bool("dcw")
|
||||
cam.set_dcw(dcw)
|
||||
|
||||
bpc = prefs.get_bool("bpc")
|
||||
cam.set_bpc(bpc)
|
||||
|
||||
wpc = prefs.get_bool("wpc")
|
||||
cam.set_wpc(wpc)
|
||||
|
||||
# Mode-specific default comes from constructor
|
||||
raw_gma = prefs.get_bool("raw_gma")
|
||||
print(f"applying raw_gma: {raw_gma}")
|
||||
cam.set_raw_gma(raw_gma)
|
||||
|
||||
lenc = prefs.get_bool("lenc")
|
||||
cam.set_lenc(lenc)
|
||||
|
||||
# JPEG quality (only relevant for JPEG format)
|
||||
#try:
|
||||
# quality = prefs.get_int("quality", 85)
|
||||
# cam.set_quality(quality)
|
||||
#except:
|
||||
# pass # Not in JPEG mode
|
||||
|
||||
print("Camera settings applied successfully")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error applying camera settings: {e}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Class method delegation (at module level)
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
|
||||
|
||||
# this driver uses a special i2c bus implimentation I have written.
|
||||
# This implimentation takes into consideration the ESP32 and it having
|
||||
# threading available. It also has some convience methods built into it
|
||||
# that figure out what is wanting to be done automatically.
|
||||
# read more about it's use in the stub files.
|
||||
|
||||
from micropython import const # NOQA
|
||||
import pointer_framework
|
||||
import machine # NOQA
|
||||
import time
|
||||
|
||||
|
||||
_CMD_REG = const(0x8040)
|
||||
_CMD_CHECK_REG = const(0x8046)
|
||||
_CMD_READ_DATA = const(0x01)
|
||||
|
||||
_ESD_CHECK_REG = const(0x8041)
|
||||
|
||||
_MODULE_SWITCH_1 = const(0x804D)
|
||||
_CMD_INT_RISING_EDGE = const(0x00)
|
||||
_CMD_INT_FALLING_EDGE = const(0x01)
|
||||
_CMD_INT_LOW_LEVEL = const(0x02)
|
||||
_CMD_INT_HIGH_LEVEL = const(0x03)
|
||||
|
||||
_STATUS_REG = const(0x814E)
|
||||
_POINT_1_REG = const(0x8150)
|
||||
|
||||
_PRODUCT_ID_REG = const(0x8140)
|
||||
_FIRMWARE_VERSION_REG = const(0x8144)
|
||||
_VENDOR_ID_REG = const(0x814A)
|
||||
|
||||
_X_CORD_RES_REG = const(0x8146)
|
||||
_Y_CORD_RES_REG = const(0x8148)
|
||||
|
||||
#I2C_ADDR = 0x5D
|
||||
I2C_ADDR = 0x14
|
||||
BITS = 16
|
||||
|
||||
_ADDR2 = const(0x14)
|
||||
|
||||
_USE_INTERRUPTS = False # Interrupt handler based? Or just polling?
|
||||
|
||||
class GT911(pointer_framework.PointerDriver):
|
||||
|
||||
def _read_reg(self, reg, num_bytes=None, buf=None):
|
||||
self._tx_buf[0] = reg >> 8
|
||||
self._tx_buf[1] = reg & 0xFF
|
||||
try:
|
||||
if num_bytes is not None:
|
||||
self._device.write_readinto(self._tx_mv[:2], self._rx_mv[:num_bytes])
|
||||
else:
|
||||
self._device.write_readinto(self._tx_mv[:2], buf)
|
||||
except Exception as e:
|
||||
print(f"GT911 _read_reg got exception: {e}")
|
||||
|
||||
def _write_reg(self, reg, value=None, buf=None):
|
||||
try:
|
||||
if value is not None:
|
||||
self._tx_buf[0] = value
|
||||
self._device.write_mem(reg, self._tx_mv[:1])
|
||||
elif buf is not None:
|
||||
self._device.write_mem(reg, buf)
|
||||
except Exception as e:
|
||||
print(f"GT911 _write_reg got exception: {e}")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
reset_pin=None,
|
||||
interrupt_pin=None,
|
||||
touch_cal=None,
|
||||
startup_rotation=pointer_framework.lv.DISPLAY_ROTATION._0, # NOQA
|
||||
debug=False
|
||||
):
|
||||
self._tx_buf = bytearray(3)
|
||||
self._tx_mv = memoryview(self._tx_buf)
|
||||
self._rx_buf = bytearray(6)
|
||||
self._rx_mv = memoryview(self._rx_buf)
|
||||
|
||||
self._device = device
|
||||
|
||||
self.__x = 0
|
||||
self.__y = 0
|
||||
self.__last_state = self.RELEASED
|
||||
self._interrupt_flag = False
|
||||
|
||||
if isinstance(reset_pin, int):
|
||||
reset_pin = machine.Pin(reset_pin, machine.Pin.OUT)
|
||||
|
||||
if isinstance(interrupt_pin, int) and _USE_INTERRUPTS:
|
||||
interrupt_pin = machine.Pin(interrupt_pin, machine.Pin.IN)
|
||||
else:
|
||||
interrupt_pin = machine.Pin(interrupt_pin, machine.Pin.OUT)
|
||||
|
||||
self._reset_pin = reset_pin
|
||||
self._interrupt_pin = interrupt_pin
|
||||
|
||||
self.hw_reset()
|
||||
super().__init__(
|
||||
touch_cal=touch_cal, startup_rotation=startup_rotation, debug=debug
|
||||
)
|
||||
|
||||
def _interrupt_handler(self, pin):
|
||||
"""Interrupt handler called when touch event occurs"""
|
||||
self._interrupt_flag = True
|
||||
|
||||
def hw_reset(self):
|
||||
if self._reset_pin:
|
||||
if self._interrupt_pin:
|
||||
self._interrupt_pin.init(self._interrupt_pin.OUT)
|
||||
self._interrupt_pin(0)
|
||||
self._reset_pin(0)
|
||||
time.sleep_ms(10) # NOQA
|
||||
if self._interrupt_pin:
|
||||
self._interrupt_pin(1) # causes it to stay on 0x14 address
|
||||
#self._interrupt_pin(0) # causes it to go to 0x5D address
|
||||
time.sleep_ms(1) # NOQA
|
||||
self._reset_pin(1)
|
||||
time.sleep_ms(5) # NOQA
|
||||
if self._interrupt_pin:
|
||||
self._interrupt_pin(0)
|
||||
time.sleep_ms(50) # NOQA
|
||||
if self._interrupt_pin and _USE_INTERRUPTS:
|
||||
self._interrupt_pin.init(mode=self._interrupt_pin.IN)
|
||||
time.sleep_ms(50) # NOQA
|
||||
|
||||
self._write_reg(_ESD_CHECK_REG, 0x00)
|
||||
self._write_reg(_CMD_CHECK_REG, _CMD_READ_DATA)
|
||||
self._write_reg(_CMD_REG, _CMD_READ_DATA)
|
||||
|
||||
self._read_reg(_PRODUCT_ID_REG, 4)
|
||||
|
||||
product_id = ''
|
||||
for item in self._rx_buf[:4]:
|
||||
try:
|
||||
product_id += chr(item)
|
||||
except: # NOQA
|
||||
break
|
||||
|
||||
print('Touch Product id:', product_id)
|
||||
|
||||
self._read_reg(_FIRMWARE_VERSION_REG, 2)
|
||||
print(
|
||||
'Touch Firmware version:',
|
||||
hex(self._rx_buf[0] + (self._rx_buf[1] << 8))
|
||||
)
|
||||
|
||||
self._read_reg(_VENDOR_ID_REG, 1)
|
||||
print(f'Touch Vendor id: 0x{hex(self._rx_buf[0])[2:].upper()}')
|
||||
x, y = self.hw_size
|
||||
print(f'Touch resolution: width={x}, height={y}')
|
||||
|
||||
# Set up interrupt handler if interrupt pin is available
|
||||
if self._interrupt_pin and _USE_INTERRUPTS:
|
||||
self._interrupt_pin.irq(trigger=machine.Pin.IRQ_FALLING, handler=self._interrupt_handler)
|
||||
# Setting _MODULE_SWITCH_1 will "hang" the touch input after a second or 2 of initial swipe
|
||||
#self._write_reg(_MODULE_SWITCH_1, _CMD_INT_FALLING_EDGE) # stops working
|
||||
#self._write_reg(_MODULE_SWITCH_1, _CMD_INT_RISING_EDGE) # stops working
|
||||
# Unknown IRQ_LOW_LEVEL:
|
||||
#self._interrupt_pin.irq(trigger=machine.Pin.IRQ_LOW_LEVEL, handler=self._interrupt_handler)
|
||||
#self._write_reg(_MODULE_SWITCH_1, _CMD_INT_LOW_LEVEL)
|
||||
|
||||
@property
|
||||
def hw_size(self):
|
||||
self._read_reg(_X_CORD_RES_REG, 2)
|
||||
x = self._rx_buf[0] + (self._rx_buf[1] << 8)
|
||||
|
||||
self._read_reg(_Y_CORD_RES_REG, 2)
|
||||
y = self._rx_buf[0] + (self._rx_buf[1] << 8)
|
||||
|
||||
return x, y
|
||||
|
||||
@property
|
||||
def firmware_config(self):
|
||||
try:
|
||||
import gt911_extension
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'you need to upload the gt911_extension.py file to the MCU'
|
||||
)
|
||||
return gt911_extension.GT911Extension(self, self._device)
|
||||
|
||||
def _get_coords(self):
|
||||
# If interrupt pin is available, only fetch data when interrupt flag is set
|
||||
if self._interrupt_pin and not self._interrupt_flag and _USE_INTERRUPTS:
|
||||
return self.__last_state, self.__x, self.__y
|
||||
|
||||
# Clear interrupt flag before reading
|
||||
if self._interrupt_pin and _USE_INTERRUPTS:
|
||||
self._interrupt_flag = False
|
||||
#self._write_reg(_MODULE_SWITCH_1, _CMD_INT_FALLING_EDGE)
|
||||
#print("[GT911] Interrupt-triggered read")
|
||||
|
||||
self._read_reg(_STATUS_REG, 1)
|
||||
touch_cnt = self._rx_buf[0] & 0x0F
|
||||
status = self._rx_buf[0] & 0x80
|
||||
|
||||
if status:
|
||||
if touch_cnt == 1:
|
||||
self._read_reg(_POINT_1_REG, 6)
|
||||
|
||||
x = self._rx_buf[0] + (self._rx_buf[1] << 8)
|
||||
y = self._rx_buf[2] + (self._rx_buf[3] << 8)
|
||||
|
||||
self._write_reg(_STATUS_REG, 0x00)
|
||||
|
||||
self.__x = x
|
||||
self.__y = y
|
||||
self.__last_state = self.PRESSED
|
||||
|
||||
elif touch_cnt == 0:
|
||||
self.__last_state = self.RELEASED
|
||||
|
||||
self._write_reg(_STATUS_REG, 0x00)
|
||||
|
||||
return self.__last_state, self.__x, self.__y
|
||||
@@ -23,25 +23,56 @@ def init_rootscreen():
|
||||
# Show logo
|
||||
img = lv.image(screen)
|
||||
img.set_src("M:builtin/res/mipmap-mdpi/MicroPythonOS-logo-white-long-w296.png") # from the MPOS-logo repo
|
||||
if width < 296:
|
||||
img.set_scale(int(256 * width/296))
|
||||
img.set_blend_mode(lv.BLEND_MODE.DIFFERENCE)
|
||||
img.center()
|
||||
|
||||
def single_address_i2c_scan(i2c_bus, address):
|
||||
"""
|
||||
Scan a specific I2C address to check if a device is present.
|
||||
|
||||
Args:
|
||||
i2c_bus: An I2C bus object (machine.I2C instance)
|
||||
address: Integer address to scan (0-127)
|
||||
|
||||
Returns:
|
||||
True if a device responds at the specified address, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Attempt to write a single byte to the address
|
||||
# This will raise an exception if no device responds
|
||||
i2c_bus.writeto(address, b'')
|
||||
return True
|
||||
except OSError:
|
||||
# No device at this address
|
||||
return False
|
||||
except Exception as e:
|
||||
# Handle any other exceptions gracefully
|
||||
print(f"single_address_i2c_scan: error scanning address 0x{address:02x}: {e}")
|
||||
return False
|
||||
|
||||
def detect_board():
|
||||
import sys
|
||||
if sys.platform == "linux" or sys.platform == "darwin": # linux and macOS
|
||||
return "linux"
|
||||
elif sys.platform == "esp32":
|
||||
from machine import Pin, I2C
|
||||
i2c0 = I2C(0, sda=Pin(48), scl=Pin(47))
|
||||
if {0x15, 0x6B} <= set(i2c0.scan()): # touch screen and IMU (at least, possibly more)
|
||||
return "waveshare_esp32_s3_touch_lcd_2"
|
||||
else:
|
||||
i2c0 = I2C(0, sda=Pin(9), scl=Pin(18))
|
||||
if {0x6B} <= set(i2c0.scan()): # IMU (plus possibly the Communicator's LANA TNY at 0x38)
|
||||
return "fri3d_2024"
|
||||
else: # if {0x6A} <= set(i2c0.scan()): # IMU (plus a few others, to be added later, but this should work)
|
||||
return "fri3d_2026"
|
||||
|
||||
i2c0 = I2C(0, sda=Pin(39), scl=Pin(38))
|
||||
if single_address_i2c_scan(i2c0, 0x14) or single_address_i2c_scan(i2c0, 0x5D): # "ghost" or real GT911 touch screen
|
||||
return "matouch_esp32_s3_2_8"
|
||||
|
||||
i2c0 = I2C(0, sda=Pin(48), scl=Pin(47)) # IO48 is floating on matouch and therefore, using that for I2C will find many devices, so do this after matouch_esp32_s3_2_8
|
||||
if single_address_i2c_scan(i2c0, 0x15) and single_address_i2c_scan(i2c0, 0x6B): # CST816S touch screen and IMU
|
||||
return "waveshare_esp32_s3_touch_lcd_2"
|
||||
|
||||
i2c0 = I2C(0, sda=Pin(9), scl=Pin(18))
|
||||
if single_address_i2c_scan(i2c0, 0x6B): # IMU (plus possibly the Communicator's LANA TNY at 0x38)
|
||||
return "fri3d_2024"
|
||||
|
||||
# default: if single_address_i2c_scan(i2c0, 0x6A): # IMU but currently not installed
|
||||
return "fri3d_2026"
|
||||
|
||||
board = detect_board()
|
||||
print(f"Initializing {board} hardware")
|
||||
|
||||
@@ -3,17 +3,133 @@ import machine
|
||||
import vfs
|
||||
|
||||
class SDCardManager:
|
||||
def __init__(self, spi_bus, cs_pin):
|
||||
def __init__(self, mode=None, spi_bus=None, cs_pin=None, cmd_pin=None, clk_pin=None,
|
||||
d0_pin=None, d1_pin=None, d2_pin=None, d3_pin=None, slot=1, width=None, freq=20000000):
|
||||
self._sdcard = None
|
||||
self._mode = None
|
||||
|
||||
# Auto-detect mode: if SDIO pins provided, use SDIO; otherwise use SPI
|
||||
if cmd_pin is not None or clk_pin is not None or d0_pin is not None:
|
||||
self._mode = 'sdio'
|
||||
else:
|
||||
self._mode = 'spi'
|
||||
|
||||
# Allow explicit mode override only if explicitly provided (not default)
|
||||
if mode is not None and mode in ('spi', 'sdio'):
|
||||
self._mode = mode
|
||||
|
||||
print(f"SD card mode: {self._mode.upper()}")
|
||||
|
||||
if self._mode == 'spi':
|
||||
self._init_spi(spi_bus, cs_pin)
|
||||
elif self._mode == 'sdio':
|
||||
self._init_sdio(cmd_pin, clk_pin, d0_pin, d1_pin, d2_pin, d3_pin, slot, width, freq)
|
||||
|
||||
def _init_spi(self, spi_bus, cs_pin):
|
||||
"""Initialize SD card in SPI mode."""
|
||||
if spi_bus is None or cs_pin is None:
|
||||
print("ERROR: SPI mode requires spi_bus and cs_pin parameters")
|
||||
print(" - Provide: init(spi_bus=machine.SPI(...), cs_pin=pin_number)")
|
||||
return
|
||||
|
||||
try:
|
||||
self._sdcard = machine.SDCard(spi_bus=spi_bus, cs=cs_pin)
|
||||
self._sdcard.info()
|
||||
print("SD card initialized successfully")
|
||||
print("SD card initialized successfully in SPI mode")
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to initialize SD card: {e}")
|
||||
print(f"ERROR: Failed to initialize SD card in SPI mode: {e}")
|
||||
print(" - Possible causes: Invalid SPI configuration, SD card not inserted, faulty wiring, or firmware issue")
|
||||
print(f" - Check: SPI pins for the SPI bus, card insertion, VCC (3.3V/5V), GND")
|
||||
print(" - Try: Hard reset ESP32, test with known-good SD card")
|
||||
|
||||
def _init_sdio(self, cmd_pin, clk_pin, d0_pin, d1_pin=None, d2_pin=None, d3_pin=None,
|
||||
slot=1, width=None, freq=20000000):
|
||||
"""Initialize SD card in SDIO mode."""
|
||||
# Validate required SDIO parameters
|
||||
if cmd_pin is None or clk_pin is None or d0_pin is None:
|
||||
print("ERROR: SDIO mode requires cmd_pin, clk_pin, and d0_pin parameters")
|
||||
print(" - Provide: init(mode='sdio', cmd_pin=X, clk_pin=Y, d0_pin=Z, ...)")
|
||||
return
|
||||
|
||||
# Auto-detect SDIO width based on provided data pins
|
||||
# This happens BEFORE explicit width validation to allow user override
|
||||
if width is None:
|
||||
# Count how many data pins are provided
|
||||
data_pins_provided = sum([
|
||||
d0_pin is not None,
|
||||
d1_pin is not None,
|
||||
d2_pin is not None,
|
||||
d3_pin is not None
|
||||
])
|
||||
|
||||
if data_pins_provided == 1:
|
||||
# Only d0_pin provided: use 1-bit mode
|
||||
width = 1
|
||||
print("INFO: Auto-detected SDIO width=1 (only d0_pin provided)")
|
||||
elif data_pins_provided == 4:
|
||||
# All four data pins provided: use 4-bit mode
|
||||
width = 4
|
||||
print("INFO: Auto-detected SDIO width=4 (all four data pins provided)")
|
||||
else:
|
||||
# Partial pins provided: this is an error
|
||||
print(f"ERROR: Invalid SDIO pin configuration - {data_pins_provided} data pins provided")
|
||||
print(" - For 1-bit mode: provide only d0_pin")
|
||||
print(" - For 4-bit mode: provide all four pins (d0_pin, d1_pin, d2_pin, d3_pin)")
|
||||
print(" - Or explicitly specify width parameter to override auto-detection")
|
||||
return
|
||||
|
||||
# Validate width parameter
|
||||
if width not in (1, 4):
|
||||
print(f"ERROR: SDIO width must be 1 or 4, got {width}")
|
||||
return
|
||||
|
||||
# Validate slot parameter
|
||||
if slot not in (0, 1):
|
||||
print(f"ERROR: SDIO slot must be 0 or 1, got {slot}")
|
||||
return
|
||||
|
||||
# Validate that provided pins match the requested width
|
||||
if width == 4:
|
||||
if d1_pin is None or d2_pin is None or d3_pin is None:
|
||||
print("ERROR: SDIO 4-bit mode requires all four data pins (d0_pin, d1_pin, d2_pin, d3_pin)")
|
||||
print(" - Provide all four data pins for 4-bit mode")
|
||||
print(" - Or use 1-bit mode with only d0_pin")
|
||||
return
|
||||
elif width == 1:
|
||||
if d1_pin is not None or d2_pin is not None or d3_pin is not None:
|
||||
print("ERROR: SDIO 1-bit mode should only have d0_pin, but extra pins were provided")
|
||||
print(" - For 1-bit mode: provide only d0_pin")
|
||||
print(" - For 4-bit mode: provide all four pins (d0_pin, d1_pin, d2_pin, d3_pin)")
|
||||
return
|
||||
|
||||
try:
|
||||
# For 4-bit mode, all data pins are required
|
||||
if width == 4:
|
||||
self._sdcard = machine.SDCard(
|
||||
slot=slot,
|
||||
cmd=cmd_pin,
|
||||
clk=clk_pin,
|
||||
data_pins=(d0_pin,d1_pin,d2_pin,d3_pin,),
|
||||
width=width,
|
||||
freq=freq
|
||||
)
|
||||
else: # 1-bit mode
|
||||
self._sdcard = machine.SDCard(
|
||||
slot=slot,
|
||||
cmd=cmd_pin,
|
||||
clk=clk_pin,
|
||||
data_pins=(d0_pin,),
|
||||
width=width,
|
||||
freq=freq
|
||||
)
|
||||
|
||||
self._sdcard.info()
|
||||
print(f"SD card initialized successfully in SDIO mode (slot={slot}, width={width}-bit, freq={freq}Hz)")
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to initialize SD card in SDIO mode: {e}")
|
||||
print(" - Possible causes: Invalid SDIO pin configuration, SD card not inserted, faulty wiring, or firmware issue")
|
||||
print(f" - Check: SDIO pins (CMD, CLK, D0-D3), card insertion, VCC (3.3V), GND")
|
||||
print(" - Try: Hard reset ESP32, verify pin assignments, test with known-good SD card")
|
||||
|
||||
def _try_mount(self, mount_point):
|
||||
try:
|
||||
@@ -119,11 +235,42 @@ class SDCardManager:
|
||||
# --- Singleton pattern ---
|
||||
_manager = None
|
||||
|
||||
def init(spi_bus, cs_pin):
|
||||
"""Initialize the global SD card manager."""
|
||||
def init(mode=None, spi_bus=None, cs_pin=None, cmd_pin=None, clk_pin=None,
|
||||
d0_pin=None, d1_pin=None, d2_pin=None, d3_pin=None, slot=1, width=None, freq=20000000):
|
||||
"""
|
||||
Initialize the global SD card manager.
|
||||
|
||||
SPI mode (default):
|
||||
init(spi_bus=machine.SPI(...), cs_pin=pin_number)
|
||||
|
||||
SDIO mode with auto-detection:
|
||||
init(mode='sdio', cmd_pin=X, clk_pin=Y, d0_pin=Z, d1_pin=A, d2_pin=B, d3_pin=C, slot=1, freq=20000000)
|
||||
|
||||
SDIO width auto-detection:
|
||||
- If only d0_pin is provided: width is auto-set to 1 (1-bit mode)
|
||||
- If all four data pins (d0, d1, d2, d3) are provided: width is auto-set to 4 (4-bit mode)
|
||||
- If width parameter is explicitly provided: that value is used (overrides auto-detection)
|
||||
- If partial data pins are provided (e.g., only d0 and d1): raises an error
|
||||
|
||||
Auto-detection of mode:
|
||||
If SDIO pins are provided, SDIO mode is used automatically.
|
||||
"""
|
||||
global _manager
|
||||
if _manager is None:
|
||||
_manager = SDCardManager(spi_bus, cs_pin)
|
||||
_manager = SDCardManager(
|
||||
mode=mode,
|
||||
spi_bus=spi_bus,
|
||||
cs_pin=cs_pin,
|
||||
cmd_pin=cmd_pin,
|
||||
clk_pin=clk_pin,
|
||||
d0_pin=d0_pin,
|
||||
d1_pin=d1_pin,
|
||||
d2_pin=d2_pin,
|
||||
d3_pin=d3_pin,
|
||||
slot=slot,
|
||||
width=width,
|
||||
freq=freq
|
||||
)
|
||||
else:
|
||||
print("WARNING: SDCardManager already initialized")
|
||||
print(" - Use existing instance via get()")
|
||||
@@ -133,22 +280,32 @@ def get():
|
||||
"""Get the global SD card manager instance."""
|
||||
if _manager is None:
|
||||
print("ERROR: SDCardManager not initialized")
|
||||
print(" - Call init(spi_bus, cs_pin) first in lib/mpos/board/*.py")
|
||||
print(" - Call init() with appropriate parameters first in lib/mpos/board/*.py")
|
||||
print(" - SPI mode: init(spi_bus=machine.SPI(...), cs_pin=pin_number)")
|
||||
print(" - SDIO mode: init(mode='sdio', cmd_pin=X, clk_pin=Y, d0_pin=Z, ...)")
|
||||
return _manager
|
||||
|
||||
def get_mode():
|
||||
"""Get the current SD card mode ('spi' or 'sdio')."""
|
||||
mgr = get()
|
||||
if mgr is None:
|
||||
print("ERROR: Cannot get mode - SDCardManager not initialized")
|
||||
return None
|
||||
return mgr._mode
|
||||
|
||||
def mount(mount_point):
|
||||
mgr = get()
|
||||
if mgr is None:
|
||||
print("ERROR: Cannot mount - SDCardManager not initialized")
|
||||
print(" - Call init(spi_bus, cs_pin) first")
|
||||
print(" - Call init() with appropriate parameters first")
|
||||
return False
|
||||
return mgr.mount(mount_point)
|
||||
return mgr.mount_with_optional_format(mount_point)
|
||||
|
||||
def mount_with_optional_format(mount_point):
|
||||
mgr = get()
|
||||
if mgr is None:
|
||||
print("ERROR: Cannot mount with format - SDCardManager not initialized")
|
||||
print(" - Call init(spi_bus, cs_pin) first")
|
||||
print(" - Call init() with appropriate parameters first")
|
||||
return False
|
||||
success = mgr.mount_with_optional_format(mount_point)
|
||||
if not success:
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import lvgl as lv
|
||||
import time
|
||||
|
||||
try:
|
||||
import webcam
|
||||
except Exception as e:
|
||||
print(f"Info: could not import webcam module: {e}")
|
||||
|
||||
from ..time import epoch_seconds
|
||||
from .camera_settings import CameraSettingsActivity
|
||||
from ..camera_manager import CameraManager
|
||||
from .. import ui as mpos_ui
|
||||
from ..app.activity import Activity
|
||||
|
||||
@@ -33,7 +29,6 @@ class CameraActivity(Activity):
|
||||
image_dsc = None
|
||||
scanqr_mode = False
|
||||
scanqr_intent = False
|
||||
use_webcam = False
|
||||
capture_timer = None
|
||||
|
||||
prefs = None # regular prefs
|
||||
@@ -97,7 +92,6 @@ class CameraActivity(Activity):
|
||||
snap_label.set_text(lv.SYMBOL.OK)
|
||||
snap_label.center()
|
||||
|
||||
|
||||
self.status_label_cont = lv.obj(self.main_screen)
|
||||
width = mpos_ui.DisplayMetrics.pct_of_width(70)
|
||||
height = mpos_ui.DisplayMetrics.pct_of_width(60)
|
||||
@@ -136,22 +130,11 @@ class CameraActivity(Activity):
|
||||
|
||||
def start_cam(self):
|
||||
# Init camera:
|
||||
self.cam = self.init_internal_cam(self.width, self.height)
|
||||
self.cam = CameraManager.get_cameras()[0].init(self.width, self.height, self.colormode)
|
||||
if self.cam:
|
||||
self.image.set_rotation(900) # internal camera is rotated 90 degrees
|
||||
# Apply saved camera settings, only for internal camera for now:
|
||||
self.apply_camera_settings(self.scanqr_prefs if self.scanqr_mode else self.prefs, self.cam, self.use_webcam) # needs to be done AFTER the camera is initialized
|
||||
else:
|
||||
print("camera app: no internal camera found, trying webcam on /dev/video0")
|
||||
try:
|
||||
# Initialize webcam with desired resolution directly
|
||||
print(f"Initializing webcam at {self.width}x{self.height}")
|
||||
self.cam = webcam.init("/dev/video0", width=self.width, height=self.height)
|
||||
self.use_webcam = True
|
||||
except Exception as e:
|
||||
print(f"camera app: webcam exception: {e}")
|
||||
# Start refreshing:
|
||||
if self.cam:
|
||||
CameraManager.get_cameras()[0].apply_settings(self.cam, self.scanqr_prefs if self.scanqr_mode else self.prefs) # needs to be done AFTER the camera is initialized
|
||||
# Start refreshing:
|
||||
print("Camera app initialized, continuing...")
|
||||
self.update_preview_image()
|
||||
self.capture_timer = lv.timer_create(self.try_capture, 100, None)
|
||||
@@ -159,24 +142,8 @@ class CameraActivity(Activity):
|
||||
def stop_cam(self):
|
||||
if self.capture_timer:
|
||||
self.capture_timer.delete()
|
||||
if self.use_webcam:
|
||||
webcam.deinit(self.cam)
|
||||
elif self.cam:
|
||||
self.cam.deinit()
|
||||
# Power off, otherwise it keeps using a lot of current
|
||||
try:
|
||||
from machine import Pin, I2C
|
||||
i2c = I2C(1, scl=Pin(16), sda=Pin(21)) # Adjust pins and frequency
|
||||
#devices = i2c.scan()
|
||||
#print([hex(addr) for addr in devices]) # finds it on 60 = 0x3C after init
|
||||
camera_addr = 0x3C # for OV5640
|
||||
reg_addr = 0x3008
|
||||
reg_high = (reg_addr >> 8) & 0xFF # 0x30
|
||||
reg_low = reg_addr & 0xFF # 0x08
|
||||
power_off_command = 0x42 # Power off command
|
||||
i2c.writeto(camera_addr, bytes([reg_high, reg_low, power_off_command]))
|
||||
except Exception as e:
|
||||
print(f"Warning: powering off camera got exception: {e}")
|
||||
if self.cam:
|
||||
CameraManager.get_cameras()[0].deinit(self.cam)
|
||||
self.cam = None
|
||||
if self.image_dsc: # it's important to delete the image when stopping the camera, otherwise LVGL might try to display it and crash
|
||||
print("emptying self.current_cam_buffer...")
|
||||
@@ -347,15 +314,14 @@ class CameraActivity(Activity):
|
||||
|
||||
def open_settings(self):
|
||||
from ..content.intent import Intent
|
||||
intent = Intent(activity_class=CameraSettingsActivity, extras={"prefs": self.prefs if not self.scanqr_mode else self.scanqr_prefs, "use_webcam": self.use_webcam, "scanqr_mode": self.scanqr_mode})
|
||||
intent = Intent(activity_class=CameraSettingsActivity, extras={"prefs": self.prefs if not self.scanqr_mode else self.scanqr_prefs, "scanqr_mode": self.scanqr_mode})
|
||||
self.startActivity(intent)
|
||||
|
||||
def try_capture(self, event):
|
||||
if not self.cam:
|
||||
return
|
||||
try:
|
||||
if self.use_webcam and self.cam:
|
||||
self.current_cam_buffer = webcam.capture_frame(self.cam, "rgb565" if self.colormode else "grayscale")
|
||||
elif self.cam and self.cam.frame_available():
|
||||
self.current_cam_buffer = self.cam.capture()
|
||||
self.current_cam_buffer = CameraManager.get_cameras()[0].capture(self.cam, self.colormode)
|
||||
except Exception as e:
|
||||
print(f"Camera capture exception: {e}")
|
||||
return
|
||||
@@ -365,82 +331,10 @@ class CameraActivity(Activity):
|
||||
self.image.set_src(self.image_dsc)
|
||||
if self.scanqr_mode:
|
||||
self.qrdecode_one()
|
||||
if not self.use_webcam and self.cam:
|
||||
self.cam.free_buffer() # After QR decoding, free the old buffer, otherwise the camera doesn't provide a new one
|
||||
|
||||
def init_internal_cam(self, width, height):
|
||||
"""Initialize internal camera with specified resolution.
|
||||
|
||||
Automatically retries once if initialization fails (to handle I2C poweroff issue).
|
||||
"""
|
||||
try:
|
||||
from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling
|
||||
|
||||
# Map resolution to FrameSize enum
|
||||
# Format: (width, height): FrameSize
|
||||
resolution_map = {
|
||||
(96, 96): FrameSize.R96X96,
|
||||
(160, 120): FrameSize.QQVGA,
|
||||
(128, 128): FrameSize.R128X128,
|
||||
(176, 144): FrameSize.QCIF,
|
||||
(240, 176): FrameSize.HQVGA,
|
||||
(240, 240): FrameSize.R240X240,
|
||||
(320, 240): FrameSize.QVGA,
|
||||
(320, 320): FrameSize.R320X320,
|
||||
(400, 296): FrameSize.CIF,
|
||||
(480, 320): FrameSize.HVGA,
|
||||
(480, 480): FrameSize.R480X480,
|
||||
(640, 480): FrameSize.VGA,
|
||||
(640, 640): FrameSize.R640X640,
|
||||
(720, 720): FrameSize.R720X720,
|
||||
(800, 600): FrameSize.SVGA,
|
||||
(800, 800): FrameSize.R800X800,
|
||||
(1024, 768): FrameSize.XGA,
|
||||
(960, 960): FrameSize.R960X960,
|
||||
(1280, 720): FrameSize.HD,
|
||||
(1024, 1024): FrameSize.R1024X1024,
|
||||
# These are disabled in camera_settings.py because they use a lot of RAM:
|
||||
(1280, 1024): FrameSize.SXGA,
|
||||
(1280, 1280): FrameSize.R1280X1280,
|
||||
(1600, 1200): FrameSize.UXGA,
|
||||
(1920, 1080): FrameSize.FHD,
|
||||
}
|
||||
|
||||
frame_size = resolution_map.get((width, height), FrameSize.R240X240)
|
||||
print(f"init_internal_cam: Using FrameSize {frame_size} for {width}x{height}")
|
||||
|
||||
# Try to initialize, with one retry for I2C poweroff issue
|
||||
max_attempts = 3
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
cam = Camera(
|
||||
data_pins=[12,13,15,11,14,10,7,2],
|
||||
vsync_pin=6,
|
||||
href_pin=4,
|
||||
sda_pin=21,
|
||||
scl_pin=16,
|
||||
pclk_pin=9,
|
||||
xclk_pin=8,
|
||||
xclk_freq=20000000,
|
||||
powerdown_pin=-1,
|
||||
reset_pin=-1,
|
||||
pixel_format=PixelFormat.RGB565 if self.colormode else PixelFormat.GRAYSCALE,
|
||||
frame_size=frame_size,
|
||||
#grab_mode=GrabMode.WHEN_EMPTY,
|
||||
grab_mode=GrabMode.LATEST,
|
||||
fb_count=1
|
||||
)
|
||||
cam.set_vflip(True)
|
||||
return cam
|
||||
except Exception as e:
|
||||
if attempt < max_attempts-1:
|
||||
print(f"init_cam attempt {attempt} failed: {e}, retrying...")
|
||||
else:
|
||||
print(f"init_cam final exception: {e}")
|
||||
return None
|
||||
self.cam.free_buffer() # After QR decoding, free the old buffer, otherwise the camera doesn't provide a new one
|
||||
except Exception as e:
|
||||
print(f"init_cam exception: {e}")
|
||||
return None
|
||||
pass # some camera API's don't have this
|
||||
|
||||
def print_qr_buffer(self, buffer):
|
||||
try:
|
||||
@@ -461,149 +355,3 @@ class CameraActivity(Activity):
|
||||
if buffer.startswith(bom):
|
||||
return buffer[3:]
|
||||
return buffer
|
||||
|
||||
|
||||
def apply_camera_settings(self, prefs, cam, use_webcam):
|
||||
"""Apply all saved camera settings to the camera.
|
||||
|
||||
Only applies settings when use_webcam is False (ESP32 camera).
|
||||
Settings are applied in dependency order (master switches before dependent values).
|
||||
|
||||
Args:
|
||||
cam: Camera object
|
||||
use_webcam: Boolean indicating if using webcam
|
||||
"""
|
||||
if not cam or use_webcam:
|
||||
print("apply_camera_settings: Skipping (no camera or webcam mode)")
|
||||
return
|
||||
|
||||
try:
|
||||
# Basic image adjustments
|
||||
brightness = prefs.get_int("brightness")
|
||||
cam.set_brightness(brightness)
|
||||
|
||||
contrast = prefs.get_int("contrast")
|
||||
cam.set_contrast(contrast)
|
||||
|
||||
saturation = prefs.get_int("saturation")
|
||||
cam.set_saturation(saturation)
|
||||
|
||||
# Orientation
|
||||
hmirror = prefs.get_bool("hmirror")
|
||||
cam.set_hmirror(hmirror)
|
||||
|
||||
vflip = prefs.get_bool("vflip")
|
||||
cam.set_vflip(vflip)
|
||||
|
||||
# Special effect
|
||||
special_effect = prefs.get_int("special_effect")
|
||||
cam.set_special_effect(special_effect)
|
||||
|
||||
# Exposure control (apply master switch first, then manual value)
|
||||
exposure_ctrl = prefs.get_bool("exposure_ctrl")
|
||||
cam.set_exposure_ctrl(exposure_ctrl)
|
||||
|
||||
if not exposure_ctrl:
|
||||
aec_value = prefs.get_int("aec_value")
|
||||
cam.set_aec_value(aec_value)
|
||||
|
||||
# Mode-specific default comes from constructor
|
||||
ae_level = prefs.get_int("ae_level")
|
||||
cam.set_ae_level(ae_level)
|
||||
|
||||
aec2 = prefs.get_bool("aec2")
|
||||
cam.set_aec2(aec2)
|
||||
|
||||
# Gain control (apply master switch first, then manual value)
|
||||
gain_ctrl = prefs.get_bool("gain_ctrl")
|
||||
cam.set_gain_ctrl(gain_ctrl)
|
||||
|
||||
if not gain_ctrl:
|
||||
agc_gain = prefs.get_int("agc_gain")
|
||||
cam.set_agc_gain(agc_gain)
|
||||
|
||||
gainceiling = prefs.get_int("gainceiling")
|
||||
cam.set_gainceiling(gainceiling)
|
||||
|
||||
# White balance (apply master switch first, then mode)
|
||||
whitebal = prefs.get_bool("whitebal")
|
||||
cam.set_whitebal(whitebal)
|
||||
|
||||
if not whitebal:
|
||||
wb_mode = prefs.get_int("wb_mode")
|
||||
cam.set_wb_mode(wb_mode)
|
||||
|
||||
awb_gain = prefs.get_bool("awb_gain")
|
||||
cam.set_awb_gain(awb_gain)
|
||||
|
||||
# Sensor-specific settings (try/except for unsupported sensors)
|
||||
try:
|
||||
sharpness = prefs.get_int("sharpness")
|
||||
cam.set_sharpness(sharpness)
|
||||
except:
|
||||
pass # Not supported on OV2640?
|
||||
|
||||
try:
|
||||
denoise = prefs.get_int("denoise")
|
||||
cam.set_denoise(denoise)
|
||||
except:
|
||||
pass # Not supported on OV2640?
|
||||
|
||||
# Advanced corrections
|
||||
colorbar = prefs.get_bool("colorbar")
|
||||
cam.set_colorbar(colorbar)
|
||||
|
||||
dcw = prefs.get_bool("dcw")
|
||||
cam.set_dcw(dcw)
|
||||
|
||||
bpc = prefs.get_bool("bpc")
|
||||
cam.set_bpc(bpc)
|
||||
|
||||
wpc = prefs.get_bool("wpc")
|
||||
cam.set_wpc(wpc)
|
||||
|
||||
# Mode-specific default comes from constructor
|
||||
raw_gma = prefs.get_bool("raw_gma")
|
||||
print(f"applying raw_gma: {raw_gma}")
|
||||
cam.set_raw_gma(raw_gma)
|
||||
|
||||
lenc = prefs.get_bool("lenc")
|
||||
cam.set_lenc(lenc)
|
||||
|
||||
# JPEG quality (only relevant for JPEG format)
|
||||
#try:
|
||||
# quality = prefs.get_int("quality", 85)
|
||||
# cam.set_quality(quality)
|
||||
#except:
|
||||
# pass # Not in JPEG mode
|
||||
|
||||
print("Camera settings applied successfully")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error applying camera settings: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
def zoom_button_click_unused(self, e):
|
||||
print("zooming...")
|
||||
if self.use_webcam:
|
||||
print("zoom_button_click is not supported for webcam")
|
||||
return
|
||||
if self.cam:
|
||||
startX = self.prefs.get_int("startX", CameraSettingsActivity.startX_default)
|
||||
startY = self.prefs.get_int("startX", CameraSettingsActivity.startY_default)
|
||||
endX = self.prefs.get_int("startX", CameraSettingsActivity.endX_default)
|
||||
endY = self.prefs.get_int("startX", CameraSettingsActivity.endY_default)
|
||||
offsetX = self.prefs.get_int("startX", CameraSettingsActivity.offsetX_default)
|
||||
offsetY = self.prefs.get_int("startX", CameraSettingsActivity.offsetY_default)
|
||||
totalX = self.prefs.get_int("startX", CameraSettingsActivity.totalX_default)
|
||||
totalY = self.prefs.get_int("startX", CameraSettingsActivity.totalY_default)
|
||||
outputX = self.prefs.get_int("startX", CameraSettingsActivity.outputX_default)
|
||||
outputY = self.prefs.get_int("startX", CameraSettingsActivity.outputY_default)
|
||||
scale = self.prefs.get_bool("scale", CameraSettingsActivity.scale_default)
|
||||
binning = self.prefs.get_bool("binning", CameraSettingsActivity.binning_default)
|
||||
result = self.cam.set_res_raw(startX,startY,endX,endY,offsetX,offsetY,totalX,totalY,outputX,outputY,scale,binning)
|
||||
print(f"self.cam.set_res_raw returned {result}")
|
||||
"""
|
||||
|
||||
@@ -74,8 +74,7 @@ class CameraSettingsActivity(Activity):
|
||||
"raw_gma": False, # Disable raw gamma for better contrast
|
||||
}
|
||||
|
||||
# Resolution options for both ESP32 and webcam
|
||||
# Webcam supports all ESP32 resolutions via automatic cropping/padding
|
||||
# Resolution options are the same for all cameras for now (can be split later)
|
||||
RESOLUTIONS = [
|
||||
("96x96", "96x96"),
|
||||
("160x120", "160x120"),
|
||||
@@ -114,7 +113,6 @@ class CameraSettingsActivity(Activity):
|
||||
self.dependent_controls = {}
|
||||
|
||||
def onCreate(self):
|
||||
self.use_webcam = self.getIntent().extras.get("use_webcam")
|
||||
self.prefs = self.getIntent().extras.get("prefs")
|
||||
self.scanqr_mode = self.getIntent().extras.get("scanqr_mode")
|
||||
|
||||
@@ -132,16 +130,14 @@ class CameraSettingsActivity(Activity):
|
||||
basic_tab = tabview.add_tab("Basic")
|
||||
self.create_basic_tab(basic_tab, self.prefs)
|
||||
|
||||
# Create Advanced and Expert tabs only for ESP32 camera
|
||||
if not self.use_webcam or True: # for now, show all tabs
|
||||
advanced_tab = tabview.add_tab("Advanced")
|
||||
self.create_advanced_tab(advanced_tab, self.prefs)
|
||||
advanced_tab = tabview.add_tab("Advanced")
|
||||
self.create_advanced_tab(advanced_tab, self.prefs)
|
||||
|
||||
expert_tab = tabview.add_tab("Expert")
|
||||
self.create_expert_tab(expert_tab, self.prefs)
|
||||
expert_tab = tabview.add_tab("Expert")
|
||||
self.create_expert_tab(expert_tab, self.prefs)
|
||||
|
||||
#raw_tab = tabview.add_tab("Raw")
|
||||
#self.create_raw_tab(raw_tab, self.prefs)
|
||||
#raw_tab = tabview.add_tab("Raw")
|
||||
#self.create_raw_tab(raw_tab, self.prefs)
|
||||
|
||||
self.setContentView(screen)
|
||||
|
||||
@@ -244,16 +240,13 @@ class CameraSettingsActivity(Activity):
|
||||
button_cont.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
button_cont.set_style_border_width(0, lv.PART.MAIN)
|
||||
|
||||
save_button = lv.button(button_cont)
|
||||
save_button.set_size(lv.SIZE_CONTENT, lv.SIZE_CONTENT)
|
||||
save_button.align(lv.ALIGN.BOTTOM_LEFT, 0, 0)
|
||||
save_button.add_event_cb(lambda e: self.save_and_close(), lv.EVENT.CLICKED, None)
|
||||
save_label = lv.label(save_button)
|
||||
savetext = "Save"
|
||||
if self.scanqr_mode:
|
||||
savetext += " QR tweaks"
|
||||
save_label.set_text(savetext)
|
||||
save_label.center()
|
||||
erase_button = lv.button(button_cont)
|
||||
erase_button.set_size(DisplayMetrics.pct_of_width(20), lv.SIZE_CONTENT)
|
||||
erase_button.align(lv.ALIGN.BOTTOM_LEFT, 0, 0)
|
||||
erase_button.add_event_cb(lambda e: self.erase_and_close(), lv.EVENT.CLICKED, None)
|
||||
erase_label = lv.label(erase_button)
|
||||
erase_label.set_text("Erase")
|
||||
erase_label.center()
|
||||
|
||||
cancel_button = lv.button(button_cont)
|
||||
cancel_button.set_size(DisplayMetrics.pct_of_width(25), lv.SIZE_CONTENT)
|
||||
@@ -267,13 +260,17 @@ class CameraSettingsActivity(Activity):
|
||||
cancel_label.set_text("Cancel")
|
||||
cancel_label.center()
|
||||
|
||||
erase_button = lv.button(button_cont)
|
||||
erase_button.set_size(DisplayMetrics.pct_of_width(20), lv.SIZE_CONTENT)
|
||||
erase_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
|
||||
erase_button.add_event_cb(lambda e: self.erase_and_close(), lv.EVENT.CLICKED, None)
|
||||
erase_label = lv.label(erase_button)
|
||||
erase_label.set_text("Erase")
|
||||
erase_label.center()
|
||||
save_button = lv.button(button_cont)
|
||||
save_button.set_size(lv.SIZE_CONTENT, lv.SIZE_CONTENT)
|
||||
save_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
|
||||
save_button.add_event_cb(lambda e: self.save_and_close(), lv.EVENT.CLICKED, None)
|
||||
save_label = lv.label(save_button)
|
||||
savetext = "Save"
|
||||
if self.scanqr_mode:
|
||||
savetext += " QR tweaks"
|
||||
save_label.set_text(savetext)
|
||||
save_label.center()
|
||||
|
||||
|
||||
|
||||
def create_basic_tab(self, tab, prefs):
|
||||
|
||||
@@ -29,6 +29,18 @@ class InputManager:
|
||||
if indev and indev not in cls._registered_indevs:
|
||||
cls._registered_indevs.append(indev)
|
||||
|
||||
@classmethod
|
||||
def unregister_indev(cls, indev):
|
||||
"""
|
||||
Unregister an input device.
|
||||
|
||||
Parameters:
|
||||
- indev: LVGL input device object to remove
|
||||
"""
|
||||
if indev in cls._registered_indevs:
|
||||
indev.enable(False)
|
||||
cls._registered_indevs.remove(indev)
|
||||
|
||||
@classmethod
|
||||
def list_indevs(cls):
|
||||
"""
|
||||
|
||||
@@ -106,22 +106,49 @@ def create_notification_bar():
|
||||
#notif_icon = lv.label(notification_bar)
|
||||
#notif_icon.set_text(lv.SYMBOL.BELL)
|
||||
#notif_icon.align_to(time_label, lv.ALIGN.OUT_RIGHT_MID, PADDING_TINY, 0)
|
||||
# Battery percentage
|
||||
#battery_label = lv.label(notification_bar)
|
||||
#battery_label.set_text("100%")
|
||||
#battery_label.align(lv.ALIGN.RIGHT_MID, 0, 0)
|
||||
#battery_label.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
# Battery icon
|
||||
battery_icon = lv.label(notification_bar)
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_FULL)
|
||||
#battery_icon.align_to(battery_label, lv.ALIGN.OUT_LEFT_MID, 0, 0)
|
||||
battery_icon.align(lv.ALIGN.RIGHT_MID, -DisplayMetrics.pct_of_width(10), 0)
|
||||
battery_icon.add_flag(lv.obj.FLAG.HIDDEN) # keep it hidden until it has a correct value
|
||||
|
||||
# WiFi icon
|
||||
wifi_icon = lv.label(notification_bar)
|
||||
wifi_icon.set_text(lv.SYMBOL.WIFI)
|
||||
wifi_icon.align_to(battery_icon, lv.ALIGN.OUT_LEFT_MID, -DisplayMetrics.pct_of_width(1), 0)
|
||||
wifi_icon.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
wifi_icon.align(lv.ALIGN.RIGHT_MID, -DisplayMetrics.pct_of_width(10), 0)
|
||||
|
||||
# Battery percentage
|
||||
if BatteryManager.has_battery():
|
||||
#battery_label = lv.label(notification_bar)
|
||||
#battery_label.set_text("100%")
|
||||
#battery_label.align(lv.ALIGN.RIGHT_MID, 0, 0)
|
||||
#battery_label.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
# Battery icon
|
||||
battery_icon = lv.label(notification_bar)
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_FULL)
|
||||
#battery_icon.align_to(battery_label, lv.ALIGN.OUT_LEFT_MID, 0, 0)
|
||||
battery_icon.align(lv.ALIGN.RIGHT_MID, -DisplayMetrics.pct_of_width(10), 0)
|
||||
wifi_icon.align_to(battery_icon, lv.ALIGN.OUT_LEFT_MID, -DisplayMetrics.pct_of_width(1), 0)
|
||||
battery_icon.add_flag(lv.obj.FLAG.HIDDEN) # keep it hidden until it has a correct value
|
||||
def update_battery_icon(timer=None):
|
||||
try:
|
||||
percent = BatteryManager.get_battery_percentage()
|
||||
except Exception as e:
|
||||
print(f"BatteryManager.get_battery_percentage got exception, not updating battery_icon: {e}")
|
||||
return
|
||||
if percent > 80:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_FULL)
|
||||
elif percent > 60:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_3)
|
||||
elif percent > 40:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_2)
|
||||
elif percent > 20:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_1)
|
||||
else:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_EMPTY)
|
||||
battery_icon.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
# Percentage is not shown for now:
|
||||
#battery_label.set_text(f"{round(percent)}%")
|
||||
#battery_label.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
update_battery_icon() # run it immediately instead of waiting for the timer
|
||||
lv.timer_create(update_battery_icon, BATTERY_ICON_UPDATE_INTERVAL, None)
|
||||
|
||||
# Update time
|
||||
def update_time(timer):
|
||||
hours = mpos.time.localtime()[3]
|
||||
@@ -136,28 +163,6 @@ def create_notification_bar():
|
||||
except Exception as e:
|
||||
print("Warning: could not check WLAN status:", str(e))
|
||||
|
||||
def update_battery_icon(timer=None):
|
||||
try:
|
||||
percent = BatteryManager.get_battery_percentage()
|
||||
except Exception as e:
|
||||
print(f"BatteryManager.get_battery_percentage got exception, not updating battery_icon: {e}")
|
||||
return
|
||||
if percent > 80:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_FULL)
|
||||
elif percent > 60:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_3)
|
||||
elif percent > 40:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_2)
|
||||
elif percent > 20:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_1)
|
||||
else:
|
||||
battery_icon.set_text(lv.SYMBOL.BATTERY_EMPTY)
|
||||
battery_icon.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
# Percentage is not shown for now:
|
||||
#battery_label.set_text(f"{round(percent)}%")
|
||||
#battery_label.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
update_battery_icon() # run it immediately instead of waiting for the timer
|
||||
|
||||
def update_wifi_icon(timer):
|
||||
from mpos import WifiService
|
||||
if WifiService.is_connected():
|
||||
@@ -197,7 +202,6 @@ def create_notification_bar():
|
||||
lv.timer_create(update_temperature, TEMPERATURE_UPDATE_INTERVAL, None)
|
||||
#lv.timer_create(update_memfree, MEMFREE_UPDATE_INTERVAL, None)
|
||||
lv.timer_create(update_wifi_icon, WIFI_ICON_UPDATE_INTERVAL, None)
|
||||
lv.timer_create(update_battery_icon, BATTERY_ICON_UPDATE_INTERVAL, None)
|
||||
|
||||
# hide bar animation
|
||||
global hide_bar_animation
|
||||
|
||||
+1
-1
Submodule lvgl_micropython updated: 3edda407ad...ec3ecd4150
@@ -47,6 +47,23 @@ if [ $result -ne 0 ]; then
|
||||
fi
|
||||
cp "$buildfile" "$outdir"/MicroPythonOS_waveshare-esp32-s3-touch-lcd-2_dev_"$version".bin
|
||||
|
||||
./scripts/build_lvgl_micropython.sh esp32 prod matouch-esp32-s3-2-8
|
||||
result=$?
|
||||
if [ $result -ne 0 ]; then
|
||||
echo "build_lvgl_micropython.sh esp32 prod matouch-esp32-s3-2-8 got error: $result"
|
||||
exit 1
|
||||
fi
|
||||
cp "$buildfile" "$outdir"/MicroPythonOS_matouch-esp32-s3-2-8_prod_"$version".bin
|
||||
cp "$updatefile" "$updatesdir"/MicroPythonOS_matouch-esp32-s3-2-8_prod_"$version".ota
|
||||
|
||||
./scripts/build_lvgl_micropython.sh esp32 dev matouch-esp32-s3-2-8
|
||||
result=$?
|
||||
if [ $result -ne 0 ]; then
|
||||
echo "build_lvgl_micropython.sh esp32 dev matouch-esp32-s3-2-8 got error: $result"
|
||||
exit 1
|
||||
fi
|
||||
cp "$buildfile" "$outdir"/MicroPythonOS_matouch-esp32-s3-2-8_dev_"$version".bin
|
||||
|
||||
./scripts/build_lvgl_micropython.sh unix dev
|
||||
cp "$builddir"/lvgl_micropy_unix "$outdir"/MicroPythonOS_amd64_linux_"$version".elf
|
||||
result=$?
|
||||
|
||||
@@ -14,7 +14,6 @@ if [ -z "$target" ]; then
|
||||
echo "Example: $0 unix"
|
||||
echo "Example: $0 macOS"
|
||||
echo "Example: $0 esp32"
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -42,19 +41,6 @@ fi
|
||||
echo "Resulting file:"
|
||||
cat "$idfile"
|
||||
|
||||
# Adding it doesn't hurt - it won't be used anyway as RLOTTIE is disabled in lv_conf.h
|
||||
echo "Check need to add esp_rlottie"
|
||||
#if ! grep rlottie "$idfile"; then
|
||||
if false; then
|
||||
echo "Adding esp_rlottie to $idfile"
|
||||
echo " esp_rlottie:
|
||||
git: https://github.com/MicroPythonOS/esp_rlottie" >> "$idfile"
|
||||
echo "Resulting file:"
|
||||
cat "$idfile"
|
||||
else
|
||||
echo "No need to add esp_rlottie to $idfile"
|
||||
fi
|
||||
|
||||
echo "Check need to add lvgl_micropython manifest to micropython-camera-API's manifest..."
|
||||
camani="$codebasedir"/micropython-camera-API/src/manifest.py
|
||||
rellvglmani=lvgl_micropython/build/manifest.py
|
||||
@@ -114,9 +100,6 @@ if [ "$target" == "esp32" ]; then
|
||||
rm -rf lib/micropython/ports/esp32/build-ESP32_GENERIC_S3-SPIRAM_OCT/
|
||||
python3 make.py --ota --partition-size=4194304 --flash-size=16 esp32 BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT DISPLAY=st7789 INDEV=cst816s USER_C_MODULE="$codebasedir"/micropython-camera-API/src/micropython.cmake USER_C_MODULE="$codebasedir"/secp256k1-embedded-ecdh/micropython.cmake USER_C_MODULE="$codebasedir"/c_mpos/micropython.cmake CONFIG_FREERTOS_USE_TRACE_FACILITY=y CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y "$frozenmanifest"
|
||||
popd
|
||||
echo "Grepping..."
|
||||
pwd
|
||||
grep FrameSize.R480X480 -nril .
|
||||
elif [ "$target" == "unix" -o "$target" == "macOS" ]; then
|
||||
manifest=$(readlink -f "$codebasedir"/manifests/manifest.py)
|
||||
frozenmanifest="FROZEN_MANIFEST=$manifest"
|
||||
|
||||
@@ -70,6 +70,10 @@ $mpremote fs cp ../internal_filesystem_excluded/data/com.micropythonos.system.wi
|
||||
$mpremote fs mkdir :/apps
|
||||
$mpremote fs cp -r apps/com.micropythonos.* :/apps/
|
||||
find apps/ -maxdepth 1 -type l | while read symlink; do
|
||||
if echo $symlink | grep quasiboats; then
|
||||
echo "Skipping $symlink because it's needlessly big..."
|
||||
continue
|
||||
fi
|
||||
echo "Handling symlink $symlink"
|
||||
$mpremote fs mkdir :/"$symlink"
|
||||
$mpremote fs cp -r "$symlink"/* :/"$symlink"/
|
||||
|
||||
Reference in New Issue
Block a user