You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Merge branch 'main' of https://github.com/MicroPythonOS/MicroPythonOS
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "Cellular",
|
||||
"publisher": "Pavel Machek",
|
||||
"short_description": "Application for placing phone calls",
|
||||
"long_description": "Simple application for monitoring network state and placing phone calls.",
|
||||
"icon_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.cellular/icons/cz.ucw.pavel.cellular_0.0.1_64x64.png",
|
||||
"download_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.cellular/mpks/cz.ucw.pavel.cellular_0.0.1.mpk",
|
||||
"fullname": "cz.ucw.pavel.cellular",
|
||||
"version": "0.0.1",
|
||||
"category": "utilities",
|
||||
"activities": [
|
||||
{
|
||||
"entrypoint": "assets/main.py",
|
||||
"classname": "Main",
|
||||
"intent_filters": [
|
||||
{
|
||||
"action": "main",
|
||||
"category": "launcher"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
from mpos import Activity
|
||||
|
||||
"""
|
||||
Simple cellular-network example
|
||||
"""
|
||||
|
||||
import time
|
||||
import os
|
||||
import json
|
||||
|
||||
try:
|
||||
import lvgl as lv
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from mpos import Activity, MposKeyboard
|
||||
|
||||
TMP = "/tmp/cmd.json"
|
||||
|
||||
|
||||
def run_cmd_json(cmd):
|
||||
rc = os.system(cmd + " > " + TMP)
|
||||
if rc != 0:
|
||||
raise RuntimeError("command failed")
|
||||
|
||||
with open(TMP, "r") as f:
|
||||
data = f.read().strip()
|
||||
|
||||
return json.loads(data)
|
||||
|
||||
def dbus_json(cmd):
|
||||
return run_cmd_json("sudo /home/mobian/g/MicroPythonOS/internal_filesystem/apps/cz.ucw.pavel.cellular/assets/phone.py " + cmd)
|
||||
|
||||
class CellularManager:
|
||||
def init(self):
|
||||
v = dbus_json("loc_on")
|
||||
|
||||
def poll(self):
|
||||
v = dbus_json("signal")
|
||||
print(v)
|
||||
self.signal = v
|
||||
|
||||
def call(self, num):
|
||||
v = dbus_json("call '%s'" % num)
|
||||
|
||||
def sms(self, num, text):
|
||||
v = dbus_json("call '%s' '%s'" % (num, text))
|
||||
|
||||
cm = CellularManager()
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# User interface
|
||||
# ------------------------------------------------------------
|
||||
|
||||
class Main(Activity):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# --------------------
|
||||
|
||||
def onCreate(self):
|
||||
self.screen = lv.obj()
|
||||
#self.screen.remove_flag(lv.obj.FLAG.SCROLLABLE)
|
||||
|
||||
# Top labels
|
||||
self.lbl_time = lv.label(self.screen)
|
||||
self.lbl_time.set_style_text_font(lv.font_montserrat_34, 0)
|
||||
self.lbl_time.set_text("Startup...")
|
||||
self.lbl_time.align(lv.ALIGN.TOP_LEFT, 6, 22)
|
||||
|
||||
self.lbl_date = lv.label(self.screen)
|
||||
self.lbl_date.set_style_text_font(lv.font_montserrat_20, 0)
|
||||
self.lbl_date.align_to(self.lbl_time, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 5)
|
||||
self.lbl_date.set_text("(details here?")
|
||||
|
||||
self.lbl_month = lv.label(self.screen)
|
||||
self.lbl_month.set_style_text_font(lv.font_montserrat_20, 0)
|
||||
self.lbl_month.align(lv.ALIGN.TOP_RIGHT, -6, 22)
|
||||
|
||||
self.number = lv.textarea(self.screen)
|
||||
#self.number.set_accepted_chars("0123456789")
|
||||
self.number.set_one_line(True)
|
||||
self.number.set_style_text_font(lv.font_montserrat_34, 0)
|
||||
self.number.align_to(self.lbl_date, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 12)
|
||||
|
||||
self.call = lv.button(self.screen)
|
||||
self.call.align_to(self.number, lv.ALIGN.OUT_RIGHT_MID, 2, 0)
|
||||
self.call.add_event_cb(lambda e: self.on_call(), lv.EVENT.CLICKED, None)
|
||||
|
||||
# Two text areas on single screen don't work well.
|
||||
# Perhaps make it dialog?
|
||||
#self.sms = lv.textarea(self.screen)
|
||||
#self.sms.set_style_text_font(lv.font_montserrat_24, 0)
|
||||
#self.sms.align_to(self.number, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 10)
|
||||
|
||||
l = lv.label(self.call)
|
||||
l.set_text("Call")
|
||||
l.center()
|
||||
|
||||
kb = lv.keyboard(self.screen)
|
||||
kb.set_textarea(self.number)
|
||||
kb.set_size(lv.pct(100), lv.pct(33))
|
||||
|
||||
self.setContentView(self.screen)
|
||||
cm.init()
|
||||
|
||||
def onResume(self, screen):
|
||||
self.timer = lv.timer_create(self.tick, 60000, None)
|
||||
self.tick(0)
|
||||
|
||||
def onPause(self, screen):
|
||||
if self.timer:
|
||||
self.timer.delete()
|
||||
self.timer = None
|
||||
|
||||
# --------------------
|
||||
|
||||
def on_call(self):
|
||||
num = self.number.get_text()
|
||||
cm.call(num)
|
||||
|
||||
def on_sms(self):
|
||||
num = self.number.get_text()
|
||||
text = self.sms.get_text()
|
||||
cm.sms(num, text)
|
||||
|
||||
def tick(self, t):
|
||||
now = time.localtime()
|
||||
y, m, d = now[0], now[1], now[2]
|
||||
hh, mm, ss = now[3], now[4], now[5]
|
||||
|
||||
self.lbl_month.set_text("busy")
|
||||
|
||||
cm.poll()
|
||||
s = ""
|
||||
s += cm.signal["OperatorName"] + "\n"
|
||||
s += "RegistrationState %d\n" % cm.signal["RegistrationState"]
|
||||
s += "State %d " % cm.signal["State"]
|
||||
sq, re = cm.signal["SignalQuality"]
|
||||
s += "Signal %d\n" % sq
|
||||
|
||||
self.lbl_month.set_text(s)
|
||||
self.lbl_time.set_text("%02d:%02d" % (hh, mm))
|
||||
s = ""
|
||||
self.lbl_date.set_text("%04d-%02d-%02d %s" % (y, m, d, s))
|
||||
|
||||
|
||||
# --------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
from pydbus import SystemBus, Variant
|
||||
import pydbus
|
||||
import time
|
||||
import sys
|
||||
import json
|
||||
|
||||
"""
|
||||
Lets make it class Phone, one method would be reading battery information, one would be reading operator name / signal strength, one would be getting wifi enabled/disabled / AP name.
|
||||
|
||||
sudo apt install python3-pydbus
|
||||
|
||||
sudo mmcli --list-modems
|
||||
sudo mmcli -m 6 --location-enable-gps-nmea --location-enable-gps-raw
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class Phone:
|
||||
verbose = False
|
||||
|
||||
def __init__(self):
|
||||
self.bus = pydbus.SystemBus()
|
||||
|
||||
def init_sess(self):
|
||||
self.sess = pydbus.SessionBus()
|
||||
|
||||
def get_mobile_loc(self):
|
||||
loc = None
|
||||
mm = self.bus.get("org.freedesktop.ModemManager1")
|
||||
for modem_path in mm.GetManagedObjects():
|
||||
modem = self.bus.get(".ModemManager1", modem_path)
|
||||
loc = modem.GetLocation()
|
||||
return loc
|
||||
|
||||
def get_cell_signal(self):
|
||||
loc = None
|
||||
mm = self.bus.get("org.freedesktop.ModemManager1")
|
||||
for modem_path in mm.GetManagedObjects():
|
||||
modem = self.bus.get(".ModemManager1", modem_path)
|
||||
|
||||
loc = {}
|
||||
|
||||
def attr(v):
|
||||
loc[v] = getattr(modem, v, None)
|
||||
|
||||
attr("OperatorName")
|
||||
attr("OperatorCode") # 0..11 according to MMState
|
||||
attr("State") # 0..11 according to MMState
|
||||
attr("AccessTechnologies")
|
||||
attr("Model")
|
||||
attr("Manufacturer")
|
||||
attr("Revision")
|
||||
attr("EquipmentIdentifier")
|
||||
|
||||
attr("Gsm")
|
||||
attr("Umts")
|
||||
attr("Lte")
|
||||
|
||||
attr("SignalQuality")
|
||||
attr("RegistrationState")
|
||||
|
||||
return loc
|
||||
|
||||
def start_call(self, num):
|
||||
mm = self.bus.get("org.freedesktop.ModemManager1")
|
||||
|
||||
for modem_path in mm.GetManagedObjects():
|
||||
modem = self.bus.get("org.freedesktop.ModemManager1", modem_path)
|
||||
voice = modem["org.freedesktop.ModemManager1.Modem.Voice"]
|
||||
|
||||
call_properties = {
|
||||
"number": Variant('s', num)
|
||||
}
|
||||
|
||||
call_path = voice.CreateCall(call_properties)
|
||||
#call = self.bus.get("org.freedesktop.ModemManager1", call_path)
|
||||
#call_iface = call["org.freedesktop.ModemManager1.Call"]
|
||||
#call_iface.Start()
|
||||
|
||||
return { "call": call_path }
|
||||
|
||||
def send_sms(self, num, text):
|
||||
mm = self.bus.get("org.freedesktop.ModemManager1")
|
||||
|
||||
for modem_path in mm.GetManagedObjects():
|
||||
modem = self.bus.get("org.freedesktop.ModemManager1", modem_path)
|
||||
messaging = modem["org.freedesktop.ModemManager1.Modem.Messaging"]
|
||||
|
||||
sms_properties = {
|
||||
"number": Variant('s', num),
|
||||
"text": Variant('s', text)
|
||||
}
|
||||
|
||||
sms_path = messaging.Create(sms_properties)
|
||||
sms = self.bus.get("org.freedesktop.ModemManager1", sms_path)
|
||||
sms_iface = sms["org.freedesktop.ModemManager1.Sms"]
|
||||
sms_iface.Send()
|
||||
|
||||
return { "sms": sms_path }
|
||||
|
||||
# 0x01 = 3GPP LAC/CI
|
||||
# 0x02 = GPS NMEA
|
||||
# 0x04 = GPS RAW
|
||||
# 0x08 = CDMA BS
|
||||
# 0x10 = GPS Unmanaged
|
||||
CELL_ID = 0x01
|
||||
GPS_NMEA = 0x02
|
||||
GPS_RAW = 0x04
|
||||
|
||||
def enable_mobile_loc(self, gps_on, cell_on):
|
||||
"""
|
||||
Enable GPS RAW + NMEA.
|
||||
"""
|
||||
mm = self.bus.get("org.freedesktop.ModemManager1")
|
||||
for modem_path in mm.GetManagedObjects():
|
||||
modem = self.bus.get(".ModemManager1", modem_path)
|
||||
|
||||
# Setup(uint32 sources, boolean signal_location)
|
||||
# signal_location=True makes ModemManager emit LocationUpdated signals
|
||||
if gps_on:
|
||||
sources = self.GPS_NMEA | self.GPS_RAW
|
||||
else:
|
||||
sources = 0
|
||||
if cell_on:
|
||||
sources |= self.CELL_ID;
|
||||
modem.Setup(sources, True)
|
||||
|
||||
continue
|
||||
# Optional: explicitly enable (some modems require it)
|
||||
try:
|
||||
modem.SetEnable(True)
|
||||
except Exception:
|
||||
print("Cant setenable")
|
||||
return { 'result' : 'setenable failed' }
|
||||
return { 'result': 'ok' }
|
||||
|
||||
phone = Phone()
|
||||
|
||||
def handle_cmd(v, a):
|
||||
if v == "bat":
|
||||
print(json.dumps(phone.get_battery_info()))
|
||||
sys.exit(0)
|
||||
if v == "loc":
|
||||
print(json.dumps(phone.get_mobile_loc()))
|
||||
sys.exit(0)
|
||||
if v == "loc_on":
|
||||
print(json.dumps(phone.enable_mobile_loc(True, True)))
|
||||
sys.exit(0)
|
||||
if v == "loc_off":
|
||||
print(json.dumps(phone.enable_mobile_loc(False, False)))
|
||||
sys.exit(0)
|
||||
if v == "signal":
|
||||
print(json.dumps(phone.get_cell_signal()))
|
||||
sys.exit(0)
|
||||
if v == "call":
|
||||
print(json.dumps(phone.start_call(a[2])))
|
||||
sys.exit(0)
|
||||
if v == "sms":
|
||||
print(json.dumps(phone.send_sms(a[2], a[3])))
|
||||
sys.exit(0)
|
||||
print("Unknown command "+v)
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
handle_cmd(sys.argv[1], sys.argv)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "Floodit",
|
||||
"publisher": "Pavel Machek",
|
||||
"short_description": "Simple game with colors.",
|
||||
"long_description": "Game with colors, where objective is to turn whole board into single color in minimum number of steps.",
|
||||
"icon_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.floodit/icons/cz.ucw.pavel.floodit_0.0.1_64x64.png",
|
||||
"download_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.floodit/mpks/cz.ucw.pavel.floodit_0.0.1.mpk",
|
||||
"fullname": "cz.ucw.pavel.floodit",
|
||||
"version": "0.0.1",
|
||||
"category": "utilities",
|
||||
"activities": [
|
||||
{
|
||||
"entrypoint": "assets/main.py",
|
||||
"classname": "Main",
|
||||
"intent_filters": [
|
||||
{
|
||||
"action": "main",
|
||||
"category": "launcher"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
import time
|
||||
import random
|
||||
|
||||
"""
|
||||
Flood-It game
|
||||
|
||||
Fill the entire board with a single color
|
||||
using the smallest number of moves.
|
||||
|
||||
Touch a color button to flood the region
|
||||
starting from the top-left corner.
|
||||
"""
|
||||
|
||||
from mpos import Activity
|
||||
|
||||
try:
|
||||
import lvgl as lv
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class Main(Activity):
|
||||
|
||||
COLS = 10
|
||||
ROWS = 10
|
||||
|
||||
COLORS = [
|
||||
0xE74C3C, # red
|
||||
0xF1C40F, # yellow
|
||||
0x2ECC71, # green
|
||||
0x3498DB, # blue
|
||||
0x9B59B6, # purple
|
||||
0xE67E22, # orange
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.board = []
|
||||
self.cells = []
|
||||
|
||||
self.moves = 0
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def onCreate(self):
|
||||
|
||||
self.screen = lv.obj()
|
||||
self.screen.remove_flag(lv.obj.FLAG.SCROLLABLE)
|
||||
|
||||
font = lv.font_montserrat_20
|
||||
|
||||
score = lv.label(self.screen)
|
||||
score.align(lv.ALIGN.TOP_LEFT, 5, 25)
|
||||
score.set_text("Moves")
|
||||
score.set_style_text_font(font, 0)
|
||||
self.lb_score = score
|
||||
|
||||
d = lv.display_get_default()
|
||||
self.SCREEN_WIDTH = d.get_horizontal_resolution()
|
||||
self.SCREEN_HEIGHT = d.get_vertical_resolution()
|
||||
|
||||
# color buttons
|
||||
btn_size = 45
|
||||
spacing = 5
|
||||
|
||||
self.CELL = min(
|
||||
self.SCREEN_WIDTH // (self.COLS + 2),
|
||||
(self.SCREEN_HEIGHT - btn_size) // (self.ROWS + 3)
|
||||
)
|
||||
|
||||
board_x = (self.SCREEN_WIDTH - self.CELL * self.COLS) // 2
|
||||
board_y = (self.SCREEN_HEIGHT - self.CELL * self.ROWS) // 2
|
||||
|
||||
for r in range(self.ROWS):
|
||||
row = []
|
||||
for c in range(self.COLS):
|
||||
|
||||
o = lv.obj(self.screen)
|
||||
o.set_size(self.CELL - 2, self.CELL - 2)
|
||||
|
||||
o.set_pos(
|
||||
board_x + c * self.CELL + 1,
|
||||
board_y + r * self.CELL + 1 - btn_size // 2
|
||||
)
|
||||
|
||||
o.set_style_radius(4, 0)
|
||||
o.set_style_border_width(1, 0)
|
||||
|
||||
row.append(o)
|
||||
|
||||
self.cells.append(row)
|
||||
|
||||
|
||||
for i, col in enumerate(self.COLORS):
|
||||
|
||||
btn = lv.button(self.screen)
|
||||
btn.set_size(btn_size, btn_size)
|
||||
|
||||
btn.align(
|
||||
lv.ALIGN.BOTTOM_LEFT,
|
||||
5 + i * (btn_size + spacing),
|
||||
-5
|
||||
)
|
||||
|
||||
btn.set_style_bg_color(lv.color_hex(col), 0)
|
||||
|
||||
btn.add_event_cb(
|
||||
lambda e, c=i: self.pick_color(c),
|
||||
lv.EVENT.CLICKED,
|
||||
None
|
||||
)
|
||||
|
||||
focusgroup = lv.group_get_default()
|
||||
if focusgroup:
|
||||
focusgroup.add_obj(self.screen)
|
||||
|
||||
self.setContentView(self.screen)
|
||||
|
||||
self.new_game()
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def new_game(self):
|
||||
|
||||
self.moves = 0
|
||||
self.lb_score.set_text("Moves\n0")
|
||||
|
||||
self.board = [
|
||||
[random.randrange(len(self.COLORS)) for _ in range(self.COLS)]
|
||||
for _ in range(self.ROWS)
|
||||
]
|
||||
|
||||
self.redraw()
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def pick_color(self, color):
|
||||
|
||||
start_color = self.board[0][0]
|
||||
|
||||
if start_color == color:
|
||||
return
|
||||
|
||||
self.flood_fill(start_color, color)
|
||||
|
||||
self.moves += 1
|
||||
self.lb_score.set_text("Moves\n%d" % self.moves)
|
||||
|
||||
self.redraw()
|
||||
|
||||
if self.check_win():
|
||||
self.win()
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def flood_fill(self, old, new):
|
||||
|
||||
stack = [(0, 0)]
|
||||
|
||||
while stack:
|
||||
|
||||
r, c = stack.pop()
|
||||
|
||||
if not (0 <= r < self.ROWS and 0 <= c < self.COLS):
|
||||
continue
|
||||
|
||||
if self.board[r][c] != old:
|
||||
continue
|
||||
|
||||
self.board[r][c] = new
|
||||
|
||||
stack.append((r + 1, c))
|
||||
stack.append((r - 1, c))
|
||||
stack.append((r, c + 1))
|
||||
stack.append((r, c - 1))
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def check_win(self):
|
||||
|
||||
color = self.board[0][0]
|
||||
|
||||
for r in range(self.ROWS):
|
||||
for c in range(self.COLS):
|
||||
if self.board[r][c] != color:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def win(self):
|
||||
|
||||
label = lv.label(self.screen)
|
||||
label.set_text("Finished in %d moves!" % self.moves)
|
||||
label.center()
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def redraw(self):
|
||||
|
||||
for r in range(self.ROWS):
|
||||
for c in range(self.COLS):
|
||||
|
||||
v = self.board[r][c]
|
||||
|
||||
self.cells[r][c].set_style_bg_color(
|
||||
lv.color_hex(self.COLORS[v]), 0
|
||||
)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "Gyro",
|
||||
"publisher": "Pavel Machek",
|
||||
"short_description": "Gyro",
|
||||
"long_description": "Simple gyro app.",
|
||||
"icon_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.gyro/icons/cz.ucw.pavel.gyro_0.0.1_64x64.png",
|
||||
"download_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.gyro/mpks/cz.ucw.pavel.gyro_0.0.1.mpk",
|
||||
"fullname": "cz.ucw.pavel.gyro",
|
||||
"version": "0.0.1",
|
||||
"category": "utilities",
|
||||
"activities": [
|
||||
{
|
||||
"entrypoint": "assets/main.py",
|
||||
"classname": "Main",
|
||||
"intent_filters": [
|
||||
{
|
||||
"action": "main",
|
||||
"category": "launcher"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "Navstar",
|
||||
"publisher": "Pavel Machek",
|
||||
"short_description": "Simple navigation app.",
|
||||
"long_description": "Simple navigation app using data from NAVSTAR GPS and other GNSS systems.",
|
||||
"icon_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.xxx/icons/cz.ucw.pavel.xxx_0.0.1_64x64.png",
|
||||
"download_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.xxx/mpks/cz.ucw.pavel.xxx_0.0.1.mpk",
|
||||
"fullname": "cz.ucw.pavel.xxx",
|
||||
"version": "0.0.1",
|
||||
"category": "utilities",
|
||||
"activities": [
|
||||
{
|
||||
"entrypoint": "assets/main.py",
|
||||
"classname": "Main",
|
||||
"intent_filters": [
|
||||
{
|
||||
"action": "main",
|
||||
"category": "launcher"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,306 @@
|
||||
import lvgl as lv
|
||||
import mpos
|
||||
from mpos import Activity, MposKeyboard
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Canvas (LVGL)
|
||||
# -----------------------------
|
||||
|
||||
class Canvas:
|
||||
"""
|
||||
LVGL canvas + layer drawing Canvas.
|
||||
|
||||
This matches ports where:
|
||||
- lv.canvas has init_layer() / finish_layer()
|
||||
- primitives are drawn via lv.draw_* into lv.layer_t
|
||||
"""
|
||||
|
||||
def __init__(self, scr, canvas):
|
||||
self.scr = scr
|
||||
|
||||
# Screen size
|
||||
self.W = scr.get_width()
|
||||
self.H = scr.get_height()
|
||||
|
||||
# Bottom button bar
|
||||
self.margin = 2
|
||||
self.bar_h = 39
|
||||
|
||||
# Canvas drawing area (everything above button bar)
|
||||
self.draw_w = self.W
|
||||
self.draw_h = self.H - (self.bar_h + self.margin * 2)
|
||||
|
||||
self.canvas = canvas
|
||||
|
||||
# Background: white (change if you want dark theme)
|
||||
self.canvas.set_style_bg_color(lv.color_white(), lv.PART.MAIN)
|
||||
|
||||
# Buffer: your working example uses 4 bytes/pixel
|
||||
# Reality filter: this depends on LV_COLOR_DEPTH; but your example proves it works.
|
||||
self.buf = bytearray(self.draw_w * self.draw_h * 4)
|
||||
self.canvas.set_buffer(self.buf, self.draw_w, self.draw_h, lv.COLOR_FORMAT.NATIVE)
|
||||
|
||||
# Layer used for draw engine
|
||||
self.layer = lv.layer_t()
|
||||
self.canvas.init_layer(self.layer)
|
||||
|
||||
# Persistent draw descriptors (avoid allocations)
|
||||
self._line_dsc = lv.draw_line_dsc_t()
|
||||
lv.draw_line_dsc_t.init(self._line_dsc)
|
||||
self._line_dsc.width = 1
|
||||
self._line_dsc.color = lv.color_black()
|
||||
self._line_dsc.round_end = 1
|
||||
self._line_dsc.round_start = 1
|
||||
|
||||
self._label_dsc = lv.draw_label_dsc_t()
|
||||
lv.draw_label_dsc_t.init(self._label_dsc)
|
||||
self._label_dsc.color = lv.color_black()
|
||||
self._label_dsc.font = lv.font_montserrat_24
|
||||
|
||||
self._rect_dsc = lv.draw_rect_dsc_t()
|
||||
lv.draw_rect_dsc_t.init(self._rect_dsc)
|
||||
self._rect_dsc.bg_opa = lv.OPA.TRANSP
|
||||
self._rect_dsc.border_opa = lv.OPA.COVER
|
||||
self._rect_dsc.border_width = 1
|
||||
self._rect_dsc.border_color = lv.color_black()
|
||||
|
||||
self._fill_dsc = lv.draw_rect_dsc_t()
|
||||
lv.draw_rect_dsc_t.init(self._fill_dsc)
|
||||
self._fill_dsc.bg_opa = lv.OPA.COVER
|
||||
self._fill_dsc.bg_color = lv.color_black()
|
||||
self._fill_dsc.border_width = 1
|
||||
|
||||
# Clear once
|
||||
self.clear()
|
||||
|
||||
# ----------------------------
|
||||
# Layer lifecycle
|
||||
# ----------------------------
|
||||
|
||||
def _begin(self):
|
||||
# Start drawing into the layer
|
||||
self.canvas.init_layer(self.layer)
|
||||
|
||||
def _end(self):
|
||||
# Commit drawing
|
||||
self.canvas.finish_layer(self.layer)
|
||||
|
||||
# ----------------------------
|
||||
# Public API: drawing
|
||||
# ----------------------------
|
||||
|
||||
def clear(self):
|
||||
# Clear the canvas background
|
||||
self.canvas.fill_bg(lv.color_white(), lv.OPA.COVER)
|
||||
|
||||
def text(self, x, y, s, fg = lv.color_black()):
|
||||
self._begin()
|
||||
|
||||
dsc = lv.draw_label_dsc_t()
|
||||
lv.draw_label_dsc_t.init(dsc)
|
||||
dsc.text = str(s)
|
||||
dsc.font = lv.font_montserrat_24
|
||||
dsc.color = lv.color_black()
|
||||
|
||||
area = lv.area_t()
|
||||
area.x1 = x
|
||||
area.y1 = y
|
||||
area.x2 = x + self.W
|
||||
area.y2 = y + self.H
|
||||
|
||||
lv.draw_label(self.layer, dsc, area)
|
||||
|
||||
self._end()
|
||||
|
||||
def line(self, x1, y1, x2, y2, fg = lv.color_black()):
|
||||
self._begin()
|
||||
|
||||
dsc = self._line_dsc
|
||||
dsc.p1 = lv.point_precise_t()
|
||||
dsc.p2 = lv.point_precise_t()
|
||||
dsc.p1.x = int(x1)
|
||||
dsc.p1.y = int(y1)
|
||||
dsc.p2.x = int(x2)
|
||||
dsc.p2.y = int(y2)
|
||||
|
||||
lv.draw_line(self.layer, dsc)
|
||||
|
||||
self._end()
|
||||
|
||||
def circle(self, x, y, r, fg = lv.color_black()):
|
||||
# Rounded rectangle trick (works everywhere)
|
||||
self._begin()
|
||||
|
||||
a = lv.area_t()
|
||||
a.x1 = int(x - r)
|
||||
a.y1 = int(y - r)
|
||||
a.x2 = int(x + r)
|
||||
a.y2 = int(y + r)
|
||||
|
||||
dsc = self._rect_dsc
|
||||
dsc.radius = lv.RADIUS_CIRCLE
|
||||
dsc.border_color = fg
|
||||
|
||||
lv.draw_rect(self.layer, dsc, a)
|
||||
|
||||
self._end()
|
||||
|
||||
def fill_circle(self, x, y, r, fg = lv.color_black(), bg = lv.color_white()):
|
||||
self._begin()
|
||||
|
||||
a = lv.area_t()
|
||||
a.x1 = int(x - r)
|
||||
a.y1 = int(y - r)
|
||||
a.x2 = int(x + r)
|
||||
a.y2 = int(y + r)
|
||||
|
||||
dsc = self._rect_dsc
|
||||
dsc.radius = lv.RADIUS_CIRCLE
|
||||
dsc.border_color = fg
|
||||
dsc.bg_color = bg
|
||||
|
||||
lv.draw_rect(self.layer, dsc, a)
|
||||
|
||||
self._end()
|
||||
|
||||
def fill_rect(self, x, y, sx, sy, fg = lv.color_black(), bg = lv.color_white()):
|
||||
self._begin()
|
||||
|
||||
a = lv.area_t()
|
||||
a.x1 = x
|
||||
a.y1 = y
|
||||
a.x2 = x+sx
|
||||
a.y2 = y+sy
|
||||
|
||||
dsc = self._fill_dsc
|
||||
dsc.border_color = fg
|
||||
dsc.bg_color = bg
|
||||
|
||||
lv.draw_rect(self.layer, dsc, a)
|
||||
|
||||
self._end()
|
||||
|
||||
def update(self):
|
||||
# Nothing needed; drawing is committed per primitive.
|
||||
# If you want, you can change the implementation so that:
|
||||
# - draw ops happen between clear() and update()
|
||||
# But then you must ensure the app calls update() once per frame.
|
||||
pass
|
||||
|
||||
# ----------------------------
|
||||
# App logic
|
||||
# ----------------------------
|
||||
|
||||
class PagedCanvas(Activity):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.page = 0
|
||||
self.pages = 3
|
||||
|
||||
def onCreate(self):
|
||||
self.scr = lv.obj()
|
||||
scr = self.scr
|
||||
|
||||
# Screen size
|
||||
self.W = scr.get_width()
|
||||
self.H = scr.get_height()
|
||||
|
||||
# Bottom button bar
|
||||
self.margin = 2
|
||||
self.bar_h = 39
|
||||
|
||||
# Canvas drawing area (everything above button bar)
|
||||
self.draw_w = self.W
|
||||
self.draw_h = self.H - (self.bar_h + self.margin * 2)
|
||||
|
||||
# Canvas
|
||||
self.canvas = lv.canvas(self.scr)
|
||||
self.canvas.set_size(self.draw_w, self.draw_h)
|
||||
self.canvas.align(lv.ALIGN.TOP_LEFT, 0, 0)
|
||||
self.canvas.set_style_border_width(0, 0)
|
||||
|
||||
self.c = Canvas(self.scr, self.canvas)
|
||||
|
||||
# Build buttons
|
||||
self.build_buttons()
|
||||
self.setContentView(self.c.scr)
|
||||
|
||||
# ----------------------------
|
||||
# Button bar
|
||||
# ----------------------------
|
||||
|
||||
def _make_btn(self, parent, x, y, w, h, label):
|
||||
b = lv.button(parent)
|
||||
b.set_pos(x, y)
|
||||
b.set_size(w, h)
|
||||
|
||||
l = lv.label(b)
|
||||
l.set_text(label)
|
||||
l.center()
|
||||
|
||||
return b
|
||||
|
||||
def _btn_cb(self, evt, tag):
|
||||
self.page = tag
|
||||
|
||||
def template_buttons(self, names):
|
||||
margin = self.margin
|
||||
y = self.H - self.bar_h - margin
|
||||
|
||||
num = len(names)
|
||||
if num == 0:
|
||||
self.buttons = []
|
||||
return
|
||||
|
||||
w = (self.W - margin * (num + 1)) // num
|
||||
h = self.bar_h
|
||||
x0 = margin
|
||||
|
||||
self.buttons = []
|
||||
|
||||
for i, label in enumerate(names):
|
||||
x = x0 + (w + margin) * i
|
||||
btn = self._make_btn(self.scr, x, y, w, h, label)
|
||||
|
||||
# capture index correctly
|
||||
btn.add_event_cb(
|
||||
lambda evt, idx=i: self._btn_cb(evt, idx),
|
||||
lv.EVENT.CLICKED,
|
||||
None
|
||||
)
|
||||
|
||||
self.buttons.append(btn)
|
||||
|
||||
def build_buttons(self):
|
||||
self.template_buttons(["Pg0", "Pg1", "Pg2", "Pg3", "..."])
|
||||
|
||||
def onResume(self, screen):
|
||||
self.timer = lv.timer_create(self.tick, 1000, None)
|
||||
|
||||
def onPause(self, screen):
|
||||
if self.timer:
|
||||
self.timer.delete()
|
||||
self.timer = None
|
||||
|
||||
def tick(self, t):
|
||||
self.update()
|
||||
self.draw()
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
def draw_page_example(self):
|
||||
ui = self.c
|
||||
ui.clear()
|
||||
|
||||
st = 28
|
||||
y = 2*st
|
||||
ui.text(0, y, "Hello world, page is %d" % self.page)
|
||||
y += st
|
||||
|
||||
def draw(self):
|
||||
self.draw_page_example()
|
||||
|
||||
def handle_buttons(self):
|
||||
ui = self.c
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
@@ -12,7 +12,7 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from mpos import Activity, MposKeyboard
|
||||
from mpos import Activity, MposKeyboard, DownloadManager
|
||||
|
||||
import ujson
|
||||
import utime
|
||||
@@ -55,22 +55,87 @@ class WData:
|
||||
99: "Thunderstorm + hail",
|
||||
}
|
||||
|
||||
def init(self):
|
||||
pass
|
||||
|
||||
def code_to_text(self, code):
|
||||
return self.WMO_CODES.get(int(code), "Unknown")
|
||||
|
||||
class Hourly(WData):
|
||||
def __init__(self, cw):
|
||||
self.temp = cw["temperature_2m"]
|
||||
self.wind = cw["windspeed"]
|
||||
self.code = self.code_to_text(cw["weather_code"])
|
||||
def get(self, v, cw, ind):
|
||||
if ind == None:
|
||||
return cw[v]
|
||||
else:
|
||||
return cw[v][ind]
|
||||
|
||||
def full(self):
|
||||
return f"{self.code}\nTemp {self.temp:.1f} dew {self.dew:.1f} pres {self.pres:1f}\n" \
|
||||
f"Precip {self.precip}\nWind {self.wind} gust {self.gust}"
|
||||
|
||||
def short(self):
|
||||
r = f"{self.code} {self.temp:.1f}°C"
|
||||
if self.dew + 3 > self.temp:
|
||||
r += f" dew {self.dew:.1f}°C"
|
||||
if self.gust > self.wind + 5:
|
||||
r += f" {self.gust:.0f} g"
|
||||
elif self.wind > 10:
|
||||
r += f" {self.wind:.0f} w"
|
||||
# FIXME: add precip
|
||||
return r
|
||||
|
||||
def similar(self, prev):
|
||||
if self.code != prev.code:
|
||||
return False
|
||||
if abs(self.temp - prev.temp) > 3:
|
||||
return False
|
||||
if abs(self.wind - prev.wind) > 10:
|
||||
return False
|
||||
if abs(self.gust - prev.gust) > 10:
|
||||
return False
|
||||
return True
|
||||
|
||||
def summarize(self):
|
||||
return f"{self.code}\nTemp {self.temp}\nWind {self.wind}"
|
||||
return self.ftime() + self.short()
|
||||
|
||||
class Hourly(WData):
|
||||
def init(self, cw, ind):
|
||||
super().init()
|
||||
self.time = None
|
||||
self.temp = self.get("temperature_2m", cw, ind)
|
||||
self.dew = self.get("dewpoint_2m", cw, ind)
|
||||
self.pres = self.get("pressure_msl", cw, ind)
|
||||
self.precip = self.get("precipitation", cw, ind)
|
||||
self.wind = self.get("wind_speed_10m", cw, ind)
|
||||
self.gust = self.get("wind_gusts_10m", cw, ind)
|
||||
self.raw_code = self.get("weather_code", cw, ind)
|
||||
self.code = self.code_to_text(self.raw_code)
|
||||
|
||||
def ftime(self):
|
||||
if self.time:
|
||||
return self.time[11:13] + "h "
|
||||
return ""
|
||||
|
||||
class Daily(WData):
|
||||
def init(self, cw, ind):
|
||||
super().init()
|
||||
self.temp = self.get("temperature_2m_max", cw, ind)
|
||||
self.temp_min = self.get("temperature_2m_min", cw, ind)
|
||||
self.dew = self.get("dewpoint_2m_max", cw, ind)
|
||||
self.dew_min = self.get("dewpoint_2m_min", cw, ind)
|
||||
self.pres = None
|
||||
self.precip = self.get("precipitation_sum", cw, ind)
|
||||
self.wind = self.get("wind_speed_10m_max", cw, ind)
|
||||
self.gust = self.get("wind_gusts_10m_max", cw, ind)
|
||||
self.raw_code = self.get("weather_code", cw, ind)
|
||||
self.code = self.code_to_text(self.raw_code)
|
||||
|
||||
def ftime(self):
|
||||
return self.time[8:10] + ". "
|
||||
|
||||
class Weather:
|
||||
name = "Prague"
|
||||
lat = 50.08
|
||||
lon = 14.44
|
||||
# LKPR airport
|
||||
lat = 50 + 6/60.
|
||||
lon = 14 + 15/60.
|
||||
|
||||
def __init__(self):
|
||||
self.now = None
|
||||
@@ -84,68 +149,102 @@ class Weather:
|
||||
# See https://open-meteo.com/en/docs?forecast_days=1¤t=relative_humidity_2m
|
||||
|
||||
host = "api.open-meteo.com"
|
||||
port = 80 # HTTP only
|
||||
path = (
|
||||
"/v1/forecast?"
|
||||
"latitude={}&longitude={}"
|
||||
"¤t=temperature_2m,dewpoint_2m,pressure_msl,precipitation,weather_code,windspeed"
|
||||
"¤t=temperature_2m,dewpoint_2m,pressure_msl,precipitation,weather_code,wind_speed_10m,wind_gusts_10m"
|
||||
"&forecast_hours=8"
|
||||
"&hourly=temperature_2m,dewpoint_2m,pressure_msl,precipitation,weather_code,wind_speed_10m,wind_gusts_10m"
|
||||
"&forecast_days=10"
|
||||
"&daily=temperature_2m_max,temperature_2m_min,dewpoint_2m_min,dewpoint_2m_max,pressure_msl_min,pressure_msl_max,precipitation_sum,weather_code,wind_speed_10m_max,wind_gusts_10m_max"
|
||||
"&timezone=auto"
|
||||
).format(self.lat, self.lon)
|
||||
|
||||
print("Weather fetch: ", path)
|
||||
|
||||
# Resolve DNS
|
||||
addr = socket.getaddrinfo(host, port, socket.AF_INET)[0][-1]
|
||||
print("DNS", addr)
|
||||
|
||||
s = socket.socket()
|
||||
s.connect(addr)
|
||||
|
||||
# Send HTTP request
|
||||
request = (
|
||||
"GET {} HTTP/1.1\r\n"
|
||||
"Host: {}\r\n"
|
||||
"Connection: close\r\n\r\n"
|
||||
).format(path, host)
|
||||
|
||||
s.send(request.encode())
|
||||
|
||||
# ---- Read response ----
|
||||
# Skip HTTP headers
|
||||
buffer = b""
|
||||
while True:
|
||||
chunk = s.recv(256)
|
||||
if not chunk:
|
||||
raise Exception("No response")
|
||||
buffer += chunk
|
||||
header_end = buffer.find(b"\r\n\r\n")
|
||||
if header_end != -1:
|
||||
body = buffer[header_end + 4:]
|
||||
break
|
||||
|
||||
|
||||
# Read remaining body
|
||||
while True:
|
||||
chunk = s.recv(512)
|
||||
if not chunk:
|
||||
break
|
||||
body += chunk
|
||||
|
||||
s.close()
|
||||
|
||||
# Strip non-json parts
|
||||
body = body[5:]
|
||||
body = body[:-7]
|
||||
|
||||
print("Have result:", body.decode())
|
||||
data = DownloadManager.download_url("https://"+host+path)
|
||||
if not data:
|
||||
self.summary = "Download error"
|
||||
return
|
||||
|
||||
#print("Have result:", body.decode())
|
||||
|
||||
# Parse JSON
|
||||
data = ujson.loads(body)
|
||||
data = ujson.loads(data)
|
||||
|
||||
# ---- Extract data ----
|
||||
print("\n\n")
|
||||
|
||||
s = ""
|
||||
|
||||
print("---- ")
|
||||
cw = data["current"]
|
||||
self.now = Hourly(cw)
|
||||
self.summary = self.now.summarize()
|
||||
self.now = Hourly()
|
||||
self.now.init(cw, None)
|
||||
prev = self.now
|
||||
t = self.now.summarize()
|
||||
s += t + "\n"
|
||||
print(t)
|
||||
|
||||
self.hourly = []
|
||||
d = data["hourly"]
|
||||
times = d["time"]
|
||||
#print(d)
|
||||
|
||||
print("---- ")
|
||||
for i in range(len(times)):
|
||||
h = Hourly()
|
||||
h.init(d, i)
|
||||
h.time = times[i]
|
||||
self.hourly.append(h)
|
||||
if not h.similar(prev):
|
||||
t = h.summarize()
|
||||
s += t + "\n"
|
||||
print(t)
|
||||
prev = h
|
||||
|
||||
self.daily = []
|
||||
d = data["daily"]
|
||||
times = d["time"]
|
||||
#print(d)
|
||||
|
||||
print("---- ")
|
||||
for i in range(len(times)):
|
||||
h = Daily()
|
||||
h.init(d, i)
|
||||
h.time = times[i]
|
||||
self.daily.append(h)
|
||||
if i == 0:
|
||||
prev = h
|
||||
elif not h.similar(prev):
|
||||
t = h.summarize()
|
||||
s += t + "\n"
|
||||
print(t)
|
||||
prev = h
|
||||
|
||||
|
||||
self.summary = s
|
||||
|
||||
def summarize_future():
|
||||
now = utime.time()
|
||||
|
||||
# Rain detection in next 24h
|
||||
for h in weather.hourly[:24]:
|
||||
if h["precip"] >= 1.0:
|
||||
return "Rain soon"
|
||||
|
||||
# Temperature trend
|
||||
if len(weather.hourly) > 24:
|
||||
t0 = weather.hourly[0]["temp"]
|
||||
t24 = weather.hourly[24]["temp"]
|
||||
if abs(t24 - t0) < 2:
|
||||
return "No change expected"
|
||||
if t24 > t0:
|
||||
return "Getting warmer"
|
||||
else:
|
||||
return "Getting cooler"
|
||||
|
||||
return "Stable weather"
|
||||
|
||||
|
||||
weather = Weather()
|
||||
|
||||
@@ -167,32 +266,38 @@ class Main(Activity):
|
||||
|
||||
# ---- MAIN SCREEN ----
|
||||
|
||||
label_time = lv.label(scr_main)
|
||||
label_time.set_text("(time)")
|
||||
label_time.align(lv.ALIGN.TOP_LEFT, 10, 40)
|
||||
label_time.set_style_text_font(lv.font_montserrat_24, 0)
|
||||
self.label_time = label_time
|
||||
|
||||
label_weather = lv.label(scr_main)
|
||||
label_weather.set_text(f"Weather for {weather.name} ({weather.lat}, {weather.lon})")
|
||||
label_weather.align_to(label_time, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 10)
|
||||
label_weather.set_text(f"{weather.name} ({weather.lat}, {weather.lon})")
|
||||
label_weather.align(lv.ALIGN.TOP_LEFT, 10, 24)
|
||||
label_weather.set_style_text_font(lv.font_montserrat_14, 0)
|
||||
self.label_weather = label_weather
|
||||
|
||||
btn_hourly = lv.button(scr_main)
|
||||
btn_hourly.align(lv.ALIGN.TOP_RIGHT, -5, 24)
|
||||
lv.label(btn_hourly).set_text("Reload")
|
||||
btn_hourly.add_event_cb(lambda x: self.do_load(), lv.EVENT.CLICKED, None)
|
||||
|
||||
label_time = lv.label(scr_main)
|
||||
label_time.set_text("(time)")
|
||||
label_time.align_to(btn_hourly, lv.ALIGN.TOP_LEFT, -85, -10)
|
||||
label_time.set_style_text_font(lv.font_montserrat_24, 0)
|
||||
self.label_time = label_time
|
||||
|
||||
label_summary = lv.label(scr_main)
|
||||
label_summary.set_text("(weather)")
|
||||
#label_summary.set_long_mode(lv.label.LONG.WRAP)
|
||||
label_summary.set_width(300)
|
||||
#label_summary.set_width(300)
|
||||
label_summary.align_to(label_weather, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 5)
|
||||
label_summary.set_style_text_font(lv.font_montserrat_24, 0)
|
||||
self.label_summary = label_summary
|
||||
|
||||
btn_hourly = lv.button(scr_main)
|
||||
btn_hourly.set_size(100, 40)
|
||||
btn_hourly.align(lv.ALIGN.BOTTOM_LEFT, 10, -10)
|
||||
lv.label(btn_hourly).set_text("Reload")
|
||||
|
||||
btn_hourly.add_event_cb(lambda x: self.do_load(), lv.EVENT.CLICKED, None)
|
||||
if False:
|
||||
btn_daily = lv.button(scr_main)
|
||||
btn_daily.set_size(100, 40)
|
||||
btn_daily.align(lv.ALIGN.BOTTOM_RIGHT, -10, -10)
|
||||
lv.label(btn_daily).set_text("Daily")
|
||||
|
||||
|
||||
self.setContentView(self.screen)
|
||||
|
||||
@@ -223,3 +328,87 @@ class Main(Activity):
|
||||
self.label_summary.set_text("Requesting...")
|
||||
weather.fetch()
|
||||
|
||||
# --------------------
|
||||
|
||||
def code():
|
||||
# -----------------------------
|
||||
# LVGL UI
|
||||
# -----------------------------
|
||||
|
||||
scr_main = lv.obj()
|
||||
scr_hourly = lv.obj()
|
||||
scr_daily = lv.obj()
|
||||
|
||||
|
||||
# ---- HOURLY SCREEN ----
|
||||
|
||||
hourly_list = lv.list(scr_hourly)
|
||||
hourly_list.set_size(320, 200)
|
||||
hourly_list.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
|
||||
btn_back1 = lv.button(scr_hourly)
|
||||
btn_back1.set_size(80, 30)
|
||||
btn_back1.align(lv.ALIGN.BOTTOM_MID, 0, -5)
|
||||
lv.label(btn_back1).set_text("Back")
|
||||
|
||||
# ---- DAILY SCREEN ----
|
||||
|
||||
daily_list = lv.list(scr_daily)
|
||||
daily_list.set_size(320, 200)
|
||||
daily_list.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
|
||||
btn_back2 = lv.button(scr_daily)
|
||||
btn_back2.set_size(80, 30)
|
||||
btn_back2.align(lv.ALIGN.BOTTOM_MID, 0, -5)
|
||||
lv.label(btn_back2).set_text("Back")
|
||||
|
||||
def foo():
|
||||
btn_hourly.add_event_cb(go_hourly, lv.EVENT.CLICKED, None)
|
||||
btn_daily.add_event_cb(go_daily, lv.EVENT.CLICKED, None)
|
||||
btn_back1.add_event_cb(go_back, lv.EVENT.CLICKED, None)
|
||||
btn_back2.add_event_cb(go_back, lv.EVENT.CLICKED, None)
|
||||
|
||||
# -----------------------------
|
||||
# STARTUP
|
||||
# -----------------------------
|
||||
|
||||
def go_hourly(e):
|
||||
populate_hourly()
|
||||
lv.scr_load(scr_hourly)
|
||||
|
||||
def go_daily(e):
|
||||
populate_daily()
|
||||
lv.scr_load(scr_daily)
|
||||
|
||||
def go_back(e):
|
||||
lv.scr_load(scr_main)
|
||||
|
||||
def update_ui():
|
||||
if weather.current_temp is not None:
|
||||
text = "%s %.1f C" % (
|
||||
weather_code_to_text(weather.current_code),
|
||||
weather.current_temp
|
||||
)
|
||||
label_weather.set_text(text)
|
||||
|
||||
label_summary.set_text(weather.summary)
|
||||
|
||||
def populate_hourly():
|
||||
hourly_list.clean()
|
||||
for h in weather.hourly[:24]:
|
||||
line = "%s %.1fC %.1fmm" % (
|
||||
h["time"][11:16],
|
||||
h["temp"],
|
||||
h["precip"]
|
||||
)
|
||||
hourly_list.add_text(line)
|
||||
|
||||
def populate_daily():
|
||||
daily_list.clean()
|
||||
for d in weather.daily:
|
||||
line = "%s %.1f/%.1f" % (
|
||||
d["date"],
|
||||
d["high"],
|
||||
d["low"]
|
||||
)
|
||||
daily_list.add_text(line)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import sys
|
||||
from . import hx8357d
|
||||
from . import _hx8357d_init
|
||||
|
||||
# Register _hx8357d_init in sys.modules so __import__('_hx8357d_init') can find it
|
||||
# This is needed because display_driver_framework.py uses __import__('_hx8357d_init')
|
||||
# expecting a top-level module, but _hx8357d_init is in the hx8357d package subdirectory
|
||||
sys.modules['_hx8357d_init'] = _hx8357d_init
|
||||
|
||||
# Explicitly define __all__ and re-export public symbols from hx8357d module
|
||||
__all__ = [
|
||||
'HX8357D',
|
||||
'STATE_HIGH',
|
||||
'STATE_LOW',
|
||||
'STATE_PWM',
|
||||
'BYTE_ORDER_RGB',
|
||||
'BYTE_ORDER_BGR',
|
||||
]
|
||||
|
||||
# Re-export the public symbols
|
||||
HX8357D = hx8357d.HX8357D
|
||||
STATE_HIGH = hx8357d.STATE_HIGH
|
||||
STATE_LOW = hx8357d.STATE_LOW
|
||||
STATE_PWM = hx8357d.STATE_PWM
|
||||
BYTE_ORDER_RGB = hx8357d.BYTE_ORDER_RGB
|
||||
BYTE_ORDER_BGR = hx8357d.BYTE_ORDER_BGR
|
||||
@@ -0,0 +1,93 @@
|
||||
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
|
||||
|
||||
import time
|
||||
from micropython import const # NOQA
|
||||
|
||||
import lvgl as lv # NOQA
|
||||
import lcd_bus # NOQA
|
||||
|
||||
|
||||
_SWRESET = const(0x01)
|
||||
_SLPOUT = const(0x11)
|
||||
_DISPON = const(0x29)
|
||||
_COLMOD = const(0x3A)
|
||||
_MADCTL = const(0x36)
|
||||
_TEON = const(0x35)
|
||||
_TEARLINE = const(0x44)
|
||||
_SETOSC = const(0xB0)
|
||||
_SETPWR1 = const(0xB1)
|
||||
_SETRGB = const(0xB3)
|
||||
_SETCOM = const(0xB6)
|
||||
_SETCYC = const(0xB4)
|
||||
_SETC = const(0xB9)
|
||||
_SETSTBA = const(0xC0)
|
||||
_SETPANEL = const(0xCC)
|
||||
_SETGAMMA = const(0xE0)
|
||||
|
||||
|
||||
def init(self):
|
||||
param_buf = bytearray(34)
|
||||
param_mv = memoryview(param_buf)
|
||||
|
||||
time.sleep_ms(300) # NOQA
|
||||
param_buf[:3] = bytearray([0xFF, 0x83, 0x57])
|
||||
self.set_params(_SETC, param_mv[:3])
|
||||
|
||||
param_buf[0] = 0x80
|
||||
self.set_params(_SETRGB, param_mv[:1])
|
||||
|
||||
param_buf[:4] = bytearray([0x00, 0x06, 0x06, 0x25])
|
||||
self.set_params(_SETCOM, param_mv[:4])
|
||||
|
||||
param_buf[0] = 0x68
|
||||
self.set_params(_SETOSC, param_mv[:1])
|
||||
|
||||
param_buf[0] = 0x05
|
||||
self.set_params(_SETPANEL, param_mv[:1])
|
||||
|
||||
param_buf[:6] = bytearray([0x00, 0x15, 0x1C, 0x1C, 0x83, 0xAA])
|
||||
self.set_params(_SETPWR1, param_mv[:6])
|
||||
|
||||
param_buf[:6] = bytearray([0x50, 0x50, 0x01, 0x3C, 0x1E, 0x08])
|
||||
self.set_params(_SETSTBA, param_mv[:6])
|
||||
|
||||
param_buf[:7] = bytearray([0x02, 0x40, 0x00, 0x2A, 0x2A, 0x0D, 0x78])
|
||||
self.set_params(_SETCYC, param_mv[:7])
|
||||
|
||||
param_buf[:34] = bytearray([
|
||||
0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, 0x42, 0x3A,
|
||||
0x27, 0x1B, 0x08, 0x09, 0x03, 0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35,
|
||||
0x41, 0x4b, 0x4b, 0x42, 0x3A, 0x27, 0x1B, 0x08, 0x09, 0x03, 0x00, 0x01])
|
||||
self.set_params(_SETGAMMA, param_mv[:34])
|
||||
|
||||
param_buf[0] = (
|
||||
self._madctl(
|
||||
self._color_byte_order,
|
||||
self._ORIENTATION_TABLE # NOQA
|
||||
)
|
||||
)
|
||||
self.set_params(_MADCTL, param_mv[:1])
|
||||
|
||||
color_size = lv.color_format_get_size(self._color_space)
|
||||
if color_size == 2: # NOQA
|
||||
pixel_format = 0x55
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f'{self.__class__.__name__} IC only supports '
|
||||
'lv.COLOR_FORMAT.RGB565'
|
||||
)
|
||||
|
||||
param_buf[0] = pixel_format
|
||||
self.set_params(_COLMOD, param_mv[:1])
|
||||
|
||||
param_buf[0] = 0x00
|
||||
self.set_params(_TEON, param_mv[:1])
|
||||
|
||||
param_buf[:2] = bytearray([0x00, 0x02])
|
||||
self.set_params(_TEARLINE, param_mv[:2])
|
||||
|
||||
time.sleep_ms(150) # NOQA
|
||||
self.set_params(_SLPOUT)
|
||||
|
||||
time.sleep_ms(50) # NOQA
|
||||
self.set_params(_DISPON)
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
|
||||
|
||||
import display_driver_framework
|
||||
|
||||
|
||||
STATE_HIGH = display_driver_framework.STATE_HIGH
|
||||
STATE_LOW = display_driver_framework.STATE_LOW
|
||||
STATE_PWM = display_driver_framework.STATE_PWM
|
||||
|
||||
BYTE_ORDER_RGB = display_driver_framework.BYTE_ORDER_RGB
|
||||
BYTE_ORDER_BGR = display_driver_framework.BYTE_ORDER_BGR
|
||||
|
||||
|
||||
class HX8357D(display_driver_framework.DisplayDriver):
|
||||
pass
|
||||
@@ -0,0 +1,127 @@
|
||||
# Copyright (c) 2024 - 2025 Kevin G. Schlosser
|
||||
|
||||
import lvgl as lv # NOQA
|
||||
from micropython import const # NOQA
|
||||
import micropython # NOQA
|
||||
import machine # NOQA
|
||||
import pointer_framework
|
||||
import time
|
||||
|
||||
|
||||
_CMD_X_READ = const(0xD0) # 12 bit resolution
|
||||
_CMD_Y_READ = const(0x90) # 12 bit resolution
|
||||
_CMD_Z1_READ = const(0xB0)
|
||||
_CMD_Z2_READ = const(0xC0)
|
||||
_MIN_RAW_COORD = const(10)
|
||||
_MAX_RAW_COORD = const(4090)
|
||||
|
||||
|
||||
class XPT2046(pointer_framework.PointerDriver):
|
||||
touch_threshold = 400
|
||||
confidence = 5
|
||||
margin = 50
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device: machine.SPI.Bus,
|
||||
display_width: int,
|
||||
display_height: int,
|
||||
lcd_cs: int,
|
||||
touch_cs: int,
|
||||
touch_cal=None,
|
||||
startup_rotation=lv.DISPLAY_ROTATION._0,
|
||||
debug=False,
|
||||
):
|
||||
self._device = device # machine.SPI.Bus() instance, shared with display
|
||||
self._debug = debug
|
||||
|
||||
self.lcd_cs = machine.Pin(lcd_cs, machine.Pin.OUT, value=0)
|
||||
self.touch_cs = machine.Pin(touch_cs, machine.Pin.OUT, value=1)
|
||||
|
||||
self._width = display_width
|
||||
self._height = display_height
|
||||
|
||||
self._tx_buf = bytearray(3)
|
||||
self._tx_mv = memoryview(self._tx_buf)
|
||||
|
||||
self._rx_buf = bytearray(3)
|
||||
self._rx_mv = memoryview(self._rx_buf)
|
||||
|
||||
self.__confidence = max(min(self.confidence, 25), 3)
|
||||
self.__points = [[0, 0] for _ in range(self.__confidence)]
|
||||
|
||||
margin = max(min(self.margin, 100), 1)
|
||||
self.__margin = margin * margin
|
||||
|
||||
super().__init__(
|
||||
touch_cal=touch_cal, startup_rotation=startup_rotation, debug=debug
|
||||
)
|
||||
|
||||
def _read_reg(self, reg, num_bytes):
|
||||
self._tx_buf[0] = reg
|
||||
self._device.write_readinto(self._tx_mv[:num_bytes], self._rx_mv[:num_bytes])
|
||||
return ((self._rx_buf[1] << 8) | self._rx_buf[2]) >> 3
|
||||
|
||||
def _get_coords(self):
|
||||
try:
|
||||
self.lcd_cs.value(1) # deselect LCD to avoid conflicts
|
||||
self.touch_cs.value(0) # select touch chip
|
||||
|
||||
z1 = self._read_reg(_CMD_Z1_READ, 3)
|
||||
z2 = self._read_reg(_CMD_Z2_READ, 3)
|
||||
z = z1 + ((_MAX_RAW_COORD + 6) - z2)
|
||||
if z < self.touch_threshold:
|
||||
return None # Not touched
|
||||
|
||||
points = self.__points
|
||||
count = 0
|
||||
end_time = time.ticks_us() + 5000
|
||||
while time.ticks_us() < end_time:
|
||||
if count == self.__confidence:
|
||||
break
|
||||
|
||||
raw_x = self._read_reg(_CMD_X_READ, 3)
|
||||
if raw_x < _MIN_RAW_COORD:
|
||||
continue
|
||||
|
||||
raw_y = self._read_reg(_CMD_Y_READ, 3)
|
||||
if raw_y > _MAX_RAW_COORD:
|
||||
continue
|
||||
|
||||
# put in buff
|
||||
points[count][0] = raw_x
|
||||
points[count][1] = raw_y
|
||||
count += 1
|
||||
|
||||
finally:
|
||||
self.touch_cs.value(1) # deselect touch chip
|
||||
self.lcd_cs.value(0) # select LCD
|
||||
|
||||
if not count:
|
||||
return None # Not touched
|
||||
|
||||
meanx = sum([points[i][0] for i in range(count)]) // count
|
||||
meany = sum([points[i][1] for i in range(count)]) // count
|
||||
dev = (
|
||||
sum(
|
||||
[
|
||||
(points[i][0] - meanx) ** 2 + (points[i][1] - meany) ** 2
|
||||
for i in range(count)
|
||||
]
|
||||
)
|
||||
/ count
|
||||
)
|
||||
if dev >= self.__margin:
|
||||
return None # Not touched
|
||||
|
||||
x = pointer_framework.remap(
|
||||
meanx, _MIN_RAW_COORD, _MAX_RAW_COORD, 0, self._orig_width
|
||||
)
|
||||
y = pointer_framework.remap(
|
||||
meany, _MIN_RAW_COORD, _MAX_RAW_COORD, 0, self._orig_height
|
||||
)
|
||||
if self._debug:
|
||||
print(
|
||||
f"{self.__class__.__name__}_TP_DATA({count=} {meanx=} {meany=} {z1=} {z2=} {z=})"
|
||||
) # NOQA
|
||||
return self.PRESSED, x, y
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user