diff --git a/internal_filesystem/apps/com.micropythonos.showbattery/assets/show_battery.py b/internal_filesystem/apps/com.micropythonos.showbattery/assets/show_battery.py index e2bff8d5..08b525f9 100644 --- a/internal_filesystem/apps/com.micropythonos.showbattery/assets/show_battery.py +++ b/internal_filesystem/apps/com.micropythonos.showbattery/assets/show_battery.py @@ -42,6 +42,7 @@ I want application that will show big time (hour, minutes), with smaller seconds import lvgl as lv import mpos.time from mpos import Activity, BatteryManager +from mpos.battery_manager import MAX_VOLTAGE, MIN_VOLTAGE HISTORY_LEN = 60 @@ -52,71 +53,60 @@ class ShowBattery(Activity): refresh_timer = None - # Widgets - lbl_time = None - lbl_sec = None - lbl_text = None - - bat_outline = None - bat_fill = None - - clear_cache_checkbox = None # Add reference to checkbox - history_v = [] history_p = [] def onCreate(self): - scr = lv.obj() + main_content = lv.obj() + main_content.set_flex_flow(lv.FLEX_FLOW.COLUMN) + main_content.set_style_pad_all(0, 0) + main_content.set_size(lv.pct(100), lv.pct(100)) - # --- TIME --- - self.lbl_time = lv.label(scr) - self.lbl_time.set_style_text_font(lv.font_montserrat_40, 0) - self.lbl_time.align(lv.ALIGN.TOP_LEFT, 5, 5) + # --- TOP FLEX BOX: INFORMATION --- - self.lbl_sec = lv.label(scr) - self.lbl_sec.set_style_text_font(lv.font_montserrat_24, 0) - self.lbl_sec.align_to(self.lbl_time, lv.ALIGN.OUT_RIGHT_BOTTOM, 24, -4) + info_column = lv.obj(main_content) + info_column.set_flex_flow(lv.FLEX_FLOW.COLUMN) + info_column.set_style_pad_all(1, 1) + info_column.set_size(lv.pct(100), lv.SIZE_CONTENT) - # --- CHECKBOX --- - self.clear_cache_checkbox = lv.checkbox(scr) + self.lbl_datetime = lv.label(info_column) + self.lbl_datetime.set_style_text_font(lv.font_montserrat_16, 0) + + self.lbl_battery = lv.label(info_column) + self.lbl_battery.set_style_text_font(lv.font_montserrat_24, 0) + + self.lbl_battery_raw = lv.label(info_column) + self.lbl_battery_raw.set_style_text_font(lv.font_montserrat_14, 0) + + self.clear_cache_checkbox = lv.checkbox(info_column) self.clear_cache_checkbox.set_text("Real-time values") - self.clear_cache_checkbox.align(lv.ALIGN.TOP_LEFT, 5, 50) - self.lbl_text = lv.label(scr) - self.lbl_text.set_style_text_font(lv.font_montserrat_16, 0) - self.lbl_text.align(lv.ALIGN.TOP_LEFT, 5, 80) + # --- BOTTOM FLEX BOX: GRAPH --- - # --- BATTERY ICON --- - self.bat_outline = lv.obj(scr) - self.bat_size = 225 - self.bat_outline.set_size(80, self.bat_size) - self.bat_outline.align(lv.ALIGN.TOP_RIGHT, -10, 10) - self.bat_outline.set_style_border_width(2, 0) - self.bat_outline.set_style_radius(4, 0) + self.canvas_width = main_content.get_width() + self.canvas_height = 100 - self.bat_fill = lv.obj(self.bat_outline) - self.bat_fill.align(lv.ALIGN.BOTTOM_MID, 0, -2) - self.bat_fill.set_width(52) - self.bat_fill.set_style_radius(2, 0) + canvas_column = lv.obj(main_content) + canvas_column.set_flex_flow(lv.FLEX_FLOW.COLUMN) + canvas_column.set_style_pad_all(0, 0) + canvas_column.set_size(self.canvas_width, self.canvas_height) + + self.canvas = lv.canvas(canvas_column) + self.canvas.set_size(self.canvas_width, self.canvas_height) + buffer = bytearray(self.canvas_width * self.canvas_height * 4) + self.canvas.set_buffer( + buffer, self.canvas_width, self.canvas_height, lv.COLOR_FORMAT.NATIVE + ) - # --- CANVAS --- - self.canvas = lv.canvas(scr) - self.canvas.set_size(220, 100) - self.canvas.align(lv.ALIGN.BOTTOM_LEFT, 5, -5) - self.canvas.set_style_border_width(1, 0) - self.canvas.set_style_bg_color(lv.color_white(), lv.PART.MAIN) - buffer = bytearray(220 * 100 * 4) - self.canvas.set_buffer(buffer, 220, 100, lv.COLOR_FORMAT.NATIVE) self.layer = lv.layer_t() self.canvas.init_layer(self.layer) - - self.setContentView(scr) + self.setContentView(main_content) def draw_line(self, color, x1, y1, x2, y2): dsc = lv.draw_line_dsc_t() lv.draw_line_dsc_t.init(dsc) dsc.color = color - dsc.width = 4 + dsc.width = 2 dsc.round_end = 1 dsc.round_start = 1 dsc.p1 = lv.point_precise_t() @@ -125,17 +115,47 @@ class ShowBattery(Activity): dsc.p2 = lv.point_precise_t() dsc.p2.x = x2 dsc.p2.y = y2 - lv.draw_line(self.layer,dsc) + lv.draw_line(self.layer, dsc) self.canvas.finish_layer(self.layer) + def draw_graph(self): + self.canvas.fill_bg(lv.color_white(), lv.OPA.COVER) + self.canvas.clean() + + w = self.canvas_width + h = self.canvas_height + + if len(self.history_v) < 2: + return + + v_range = max(MAX_VOLTAGE - MIN_VOLTAGE, 0.01) + + for i in range(1, len(self.history_v)): + x1 = int((i - 1) * w / HISTORY_LEN) + x2 = int(i * w / HISTORY_LEN) + + yv1 = h - int((self.history_v[i - 1] - MIN_VOLTAGE) / v_range * h) + yv2 = h - int((self.history_v[i] - MIN_VOLTAGE) / v_range * h) + + yp1 = h - int(self.history_p[i - 1] / 100 * h) + yp2 = h - int(self.history_p[i] / 100 * h) + + self.draw_line(DARKPINK, x1, yv1, x2, yv2) + self.draw_line(BLACK, x1, yp1, x2, yp2) + def onResume(self, screen): super().onResume(screen) def update(timer): + # --- DATE+TIME --- now = mpos.time.localtime() - + year, month, day = now[0], now[1], now[2] hour, minute, second = now[3], now[4], now[5] - date = f"{now[0]}-{now[1]:02}-{now[2]:02}" + self.lbl_datetime.set_text( + f"{year}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}" + ) + + # --- BATTERY VALUES --- if self.clear_cache_checkbox.get_state() & lv.STATE.CHECKED: # Get "real-time" values by clearing the cache before reading @@ -144,25 +164,27 @@ class ShowBattery(Activity): voltage = BatteryManager.read_battery_voltage() percent = BatteryManager.get_battery_percentage() - # --- TIME --- - self.lbl_time.set_text(f"{hour:02}:{minute:02}") - self.lbl_sec.set_text(f":{second:02}") - - # --- BATTERY VALUES --- - date += f"\n{voltage:.2f}V {percent:.0f}%" - date += f"\nRaw ADC: {BatteryManager.read_raw_adc()}" - self.lbl_text.set_text(date) - - # --- BATTERY ICON --- - fill_h = int((percent / 100) * (self.bat_size * 0.9)) - self.bat_fill.set_height(fill_h) - - if percent >= 30: - self.bat_fill.set_style_bg_color(lv.palette_main(lv.PALETTE.GREEN), 0) + if percent > 80: + symbol = lv.SYMBOL.BATTERY_FULL + elif percent > 60: + symbol = lv.SYMBOL.BATTERY_3 + elif percent > 40: + symbol = lv.SYMBOL.BATTERY_2 + elif percent > 20: + symbol = lv.SYMBOL.BATTERY_1 else: - self.bat_fill.set_style_bg_color(lv.palette_main(lv.PALETTE.RED), 0) + symbol = lv.SYMBOL.BATTERY_EMPTY - # --- HISTORY --- + self.lbl_battery.set_text(f"{symbol} {voltage:.2f}V {percent:.0f}%") + if percent >= 30: + bg_color = lv.PALETTE.GREEN + else: + bg_color = lv.PALETTE.RED + self.lbl_battery.set_style_text_color(lv.palette_main(bg_color), 0) + + self.lbl_battery_raw.set_text(f"Raw ADC: {BatteryManager.read_raw_adc()}") + + # --- HISTORY GRAPH --- self.history_v.append(voltage) self.history_p.append(percent) @@ -174,33 +196,6 @@ class ShowBattery(Activity): self.refresh_timer = lv.timer_create(update, 1000, None) - def draw_graph(self): - self.canvas.fill_bg(lv.color_white(), lv.OPA.COVER) - self.canvas.clean() - - w = self.canvas.get_width() - h = self.canvas.get_height() - - if len(self.history_v) < 2: - return - - v_min = 3.3 - v_max = 4.2 - v_range = max(v_max - v_min, 0.01) - - for i in range(1, len(self.history_v)): - x1 = int((i - 1) * w / HISTORY_LEN) - x2 = int(i * w / HISTORY_LEN) - - yv1 = h - int((self.history_v[i - 1] - v_min) / v_range * h) - yv2 = h - int((self.history_v[i] - v_min) / v_range * h) - - yp1 = h - int(self.history_p[i - 1] / 100 * h) - yp2 = h - int(self.history_p[i] / 100 * h) - - self.draw_line(DARKPINK, x1, yv1, x2, yv2) - self.draw_line(BLACK, x1, yp1, x2, yp2) - def onPause(self, screen): super().onPause(screen) if self.refresh_timer: diff --git a/internal_filesystem/lib/mpos/board/m5stack_fire.py b/internal_filesystem/lib/mpos/board/m5stack_fire.py index 7da98c44..3f41a0f8 100644 --- a/internal_filesystem/lib/mpos/board/m5stack_fire.py +++ b/internal_filesystem/lib/mpos/board/m5stack_fire.py @@ -2,42 +2,66 @@ # Manufacturer's website at https://https://docs.m5stack.com/en/core/fire_v2.7 # Original author: https://github.com/ancebfer +import time + import drivers.display.ili9341 as ili9341 import lcd_bus -import machine - import lvgl as lv -import task_handler - +import machine import mpos.ui import mpos.ui.focus_direction -from mpos import InputManager +from machine import PWM, Pin +from micropython import const +from mpos import AudioManager, InputManager -# Pin configuration -SPI_BUS = 1 # SPI2 -SPI_FREQ = 40000000 -LCD_SCLK = 18 -LCD_MOSI = 23 -LCD_DC = 27 -LCD_CS = 14 -LCD_BL = 32 -LCD_RST = 33 -LCD_TYPE = 2 # ILI9341 type 2 +# Display settings: +SPI_BUS = const(1) # SPI2 +SPI_FREQ = const(40000000) -TFT_HOR_RES=320 -TFT_VER_RES=240 +LCD_SCLK = const(18) +LCD_MOSI = const(23) +LCD_DC = const(27) +LCD_CS = const(14) +LCD_BL = const(32) +LCD_RST = const(33) +LCD_TYPE = const(2) # ILI9341 type 2 + +TFT_HOR_RES = const(320) +TFT_VER_RES = const(240) + +# Button settings: +BUTTON_A = const(39) # A +BUTTON_B = const(38) # B +BUTTON_C = const(37) # C + +# Misc settings: +BATTERY_PIN = const(35) + +# Buzzer +BUZZER_PIN = const(25) + + +print("m5stack_fire.py init buzzer") +buzzer = PWM(Pin(BUZZER_PIN, Pin.OUT, value=1), duty=5) +AudioManager(i2s_pins=None, buzzer_instance=buzzer) +AudioManager.set_volume(40) +AudioManager.play_rtttl("Star Trek:o=4,d=20,b=200:8f.,a#,4d#6.,8d6,a#.,g.,c6.,4f6") +while AudioManager.is_playing(): + time.sleep(0.1) + + +print("m5stack_fire.py machine.SPI.Bus() initialization") +try: + spi_bus = machine.SPI.Bus(host=SPI_BUS, mosi=LCD_MOSI, sck=LCD_SCLK) +except Exception as e: + print(f"Error initializing SPI bus: {e}") + print("Attempting hard reset in 3sec...") + time.sleep(3) + machine.reset() + + +display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, freq=SPI_FREQ, dc=LCD_DC, cs=LCD_CS) -spi_bus = machine.SPI.Bus( - host=SPI_BUS, - mosi=LCD_MOSI, - sck=LCD_SCLK -) -display_bus = lcd_bus.SPIBus( - spi_bus=spi_bus, - freq=SPI_FREQ, - dc=LCD_DC, - cs=LCD_CS -) # M5Stack-Fire ILI9342 uses ILI9341 type 2 with a modified orientation table. class ILI9341(ili9341.ILI9341): @@ -45,9 +69,10 @@ class ILI9341(ili9341.ILI9341): 0x00, 0x40 | 0x20, # _MADCTL_MX | _MADCTL_MV 0x80 | 0x40, # _MADCTL_MY | _MADCTL_MX - 0x80 | 0x20 # _MADCTL_MY | _MADCTL_MV + 0x80 | 0x20, # _MADCTL_MY | _MADCTL_MV ) + mpos.ui.main_display = ILI9341( data_bus=display_bus, display_width=TFT_HOR_RES, @@ -58,7 +83,7 @@ mpos.ui.main_display = ILI9341( reset_pin=LCD_RST, reset_state=ili9341.STATE_LOW, backlight_pin=LCD_BL, - backlight_on_state=ili9341.STATE_PWM + backlight_on_state=ili9341.STATE_PWM, ) mpos.ui.main_display.init(LCD_TYPE) mpos.ui.main_display.set_power(True) @@ -68,12 +93,9 @@ mpos.ui.main_display.set_backlight(25) lv.init() # Button handling code: -from machine import Pin -import time - -btn_a = Pin(39, Pin.IN, Pin.PULL_UP) # A -btn_b = Pin(38, Pin.IN, Pin.PULL_UP) # B -btn_c = Pin(37, Pin.IN, Pin.PULL_UP) # C +btn_a = Pin(BUTTON_A, Pin.IN, Pin.PULL_UP) # A +btn_b = Pin(BUTTON_B, Pin.IN, Pin.PULL_UP) # B +btn_c = Pin(BUTTON_C, Pin.IN, Pin.PULL_UP) # C # Key repeat configuration # This whole debounce logic is only necessary because LVGL 9.2.2 seems to have an issue where diff --git a/internal_filesystem/lib/mpos/board/odroid_go.py b/internal_filesystem/lib/mpos/board/odroid_go.py index b32d0767..4aac438d 100644 --- a/internal_filesystem/lib/mpos/board/odroid_go.py +++ b/internal_filesystem/lib/mpos/board/odroid_go.py @@ -12,9 +12,9 @@ import lcd_bus import lvgl as lv import machine import mpos.ui -from machine import ADC, Pin +from machine import ADC, PWM, Pin from micropython import const -from mpos import InputManager +from mpos import AudioManager, BatteryManager, InputManager # Display settings: SPI_HOST = const(1) @@ -49,14 +49,26 @@ CROSSBAR_Y = const(35) # Misc settings: LED_BLUE = const(2) BATTERY_PIN = const(36) -SPEAKER_ENABLE_PIN = const(25) -SPEAKER_PIN = const(26) + +# Buzzer +BUZZER_PIN = const(26) +BUZZER_DAC_PIN = const(25) +BUZZER_TONE_CHANNEL = const(0) print("odroid_go.py turn on blue LED") blue_led = machine.Pin(LED_BLUE, machine.Pin.OUT) blue_led.on() +print("odroid_go.py init buzzer") +buzzer = PWM(Pin(BUZZER_PIN, Pin.OUT, value=1), duty=5) +dac_pin = Pin(BUZZER_DAC_PIN, Pin.OUT, value=1) +dac_pin.value(1) # Unmute +AudioManager(i2s_pins=None, buzzer_instance=buzzer) +AudioManager.set_volume(40) +AudioManager.play_rtttl("Star Trek:o=4,d=20,b=200:8f.,a#,4d#6.,8d6,a#.,g.,c6.,4f6") +while AudioManager.is_playing(): + time.sleep(0.1) print("odroid_go.py machine.SPI.Bus() initialization") try: @@ -102,24 +114,23 @@ lv.init() print("odroid_go.py Battery initialization...") -from mpos import BatteryManager def adc_to_voltage(raw_adc_value): """ The percentage calculation uses MIN_VOLTAGE = 3.15 and MAX_VOLTAGE = 4.15 - 0% at 3.15V -> raw_adc_value = 270 + 0% at 3.15V -> raw_adc_value = 210 100% at 4.15V -> raw_adc_value = 310 4.15 - 3.15 = 1V - 310 - 270 = 40 raw ADC steps + 310 - 210 = 100 raw ADC steps - So each raw ADC step is 1V / 40 = 0.025V + So each raw ADC step is 1V / 100 = 0.01V Offset calculation: - 270 * 0.025 = 6.75V. but we want it to be 3.15V - So the offset is 3.15V - 6.75V = -3.6V + 210 * 0.01 = 2.1V. but we want it to be 3.15V + So the offset is 3.15V - 2.1V = 1.05V """ - voltage = raw_adc_value * 0.025 - 3.6 + voltage = raw_adc_value * 0.01 + 1.05 return voltage @@ -198,6 +209,13 @@ def input_callback(indev, data): elif button_volume.value() == 0: print("Volume button pressed -> reset") blue_led.on() + AudioManager.play_rtttl( + "Outro:o=5,d=32,b=160,b=160:c6,b,a,g,f,e,d,c", + stream_type=AudioManager.STREAM_ALARM, + volume=40, + ) + while AudioManager.is_playing(): + time.sleep(0.1) machine.reset() elif button_select.value() == 0: current_key = lv.KEY.BACKSPACE diff --git a/internal_filesystem/lib/mpos/main.py b/internal_filesystem/lib/mpos/main.py index dc4270d0..16a73420 100644 --- a/internal_filesystem/lib/mpos/main.py +++ b/internal_filesystem/lib/mpos/main.py @@ -104,20 +104,28 @@ def detect_board(): if i2c0 := fail_save_i2c(sda=21, scl=22): if single_address_i2c_scan(i2c0, 0x68): # IMU (MPU6886) return "m5stack_fire" - + + import machine + unique_id_prefix = machine.unique_id()[0] + + print("odroid_go ?") + if unique_id_prefix == 0x30: + return "odroid_go" + print("fri3d_2024 ?") if i2c0 := fail_save_i2c(sda=9, scl=18): if single_address_i2c_scan(i2c0, 0x6B): # IMU (plus possibly the Communicator's LANA TNY at 0x38) return "fri3d_2024" - import machine - if machine.unique_id()[0] == 0xdc: # prototype board had: dc:b4:d9:0b:7d:80 + print("fri3d_2026 ?") + if unique_id_prefix == 0xDC: # prototype board had: dc:b4:d9:0b:7d:80 # or: if single_address_i2c_scan(i2c0, 0x6A): # IMU currently not installed on prototype board return "fri3d_2026" - print("odroid_go ?") - #if check_pins(0, 13, 27, 39): # not good because it matches other boards (like fri3d_2024 and fri3d_2026) - return "odroid_go" + raise Exception( + "Unknown ESP32-S3 board: couldn't detect known I2C devices or unique_id prefix" + ) + # EXECUTION STARTS HERE