navstar: Simple navigation application

It can display GPS information, show arrow to selected point and save
track.

It can use several location sources, including /dev/gnss0; by default,
it is configured with fake nmea source.
This commit is contained in:
Pavel Machek
2026-02-14 18:53:23 +01:00
parent d3e90c5bb4
commit 603132577e
4 changed files with 1951 additions and 0 deletions
@@ -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